acvptool: Add support for HMAC Change-Id: Ie3e3748cc1eb0e2f66ef052847179deaf0de239b Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/38544 Commit-Queue: Adam Langley <agl@google.com> Reviewed-by: Adam Langley <agl@google.com>
diff --git a/util/fipstools/acvp/acvptool/subprocess/hmac.go b/util/fipstools/acvp/acvptool/subprocess/hmac.go new file mode 100644 index 0000000..a99da9e --- /dev/null +++ b/util/fipstools/acvp/acvptool/subprocess/hmac.go
@@ -0,0 +1,112 @@ +package subprocess + +import ( + "encoding/hex" + "encoding/json" + "fmt" + "strconv" +) + +// The following structures reflect the JSON of ACVP HMAC tests. See +// https://usnistgov.github.io/ACVP/artifacts/acvp_sub_mac.html#hmac_test_vectors + +type hmacTestVectorSet struct { + Groups []hmacTestGroup `json:"testGroups"` +} + +type hmacTestGroup struct { + ID uint64 `json:"tgId"` + Type string `json:"testType"` + MsgBits int `json:"msgLen"` + KeyBits int `json:"keyLen"` // maximum possible value is 524288 + MACBits int `json:"macLen"` // maximum possible value is 512 + Tests []struct { + ID uint64 `json:"tcId"` + KeyHex string `json:"key"` + MsgHex string `json:"msg"` + } `json:"tests"` +} + +type hmacTestGroupResponse struct { + ID uint64 `json:"tgId"` + Tests []hmacTestResponse `json:"tests"` +} + +type hmacTestResponse struct { + ID uint64 `json:"tcId"` + MACHex string `json:"mac,omitempty"` +} + +// hmacPrimitive implements an ACVP algorithm by making requests to the +// subprocess to HMAC strings with the given key. +type hmacPrimitive struct { + // algo is the ACVP name for this algorithm and also the command name + // given to the subprocess to HMAC with this hash function. + algo string + mdLen int // mdLen is the number of bytes of output that the underlying hash produces. + m *Subprocess +} + +// hmac uses the subprocess to compute HMAC and returns the result. +func (h *hmacPrimitive) hmac(msg []byte, key []byte, outBits int) []byte { + if outBits%8 != 0 { + panic("fractional-byte output length requested: " + strconv.Itoa(outBits)) + } + outBytes := outBits / 8 + result, err := h.m.transact(h.algo, 1, msg, key) + if err != nil { + panic("HMAC operation failed: " + err.Error()) + } + if l := len(result[0]); l < outBytes { + panic(fmt.Sprintf("HMAC result too short: %d bytes but wanted %d", l, outBytes)) + } + return result[0][:outBytes] +} + +func (h *hmacPrimitive) Process(vectorSet []byte) (interface{}, error) { + var parsed hmacTestVectorSet + if err := json.Unmarshal(vectorSet, &parsed); err != nil { + return nil, err + } + + var ret []hmacTestGroupResponse + // See + // https://usnistgov.github.io/ACVP/artifacts/acvp_sub_mac.html#hmac_test_vectors + // for details about the tests. + for _, group := range parsed.Groups { + response := hmacTestGroupResponse{ + ID: group.ID, + } + if group.MACBits > h.mdLen*8 { + return nil, fmt.Errorf("test group %d specifies MAC length should be %d, but maximum possible length is %d", group.ID, group.MACBits, h.mdLen*8) + } + + for _, test := range group.Tests { + if len(test.MsgHex)*4 != group.MsgBits { + return nil, fmt.Errorf("test case %d/%d contains hex message of length %d but specifies a bit length of %d", group.ID, test.ID, len(test.MsgHex), group.MsgBits) + } + msg, err := hex.DecodeString(test.MsgHex) + if err != nil { + return nil, fmt.Errorf("failed to decode hex in test case %d/%d: %s", group.ID, test.ID, err) + } + + if len(test.KeyHex)*4 != group.KeyBits { + return nil, fmt.Errorf("test case %d/%d contains hex key of length %d but specifies a bit length of %d", group.ID, test.ID, len(test.KeyHex), group.KeyBits) + } + key, err := hex.DecodeString(test.KeyHex) + if err != nil { + return nil, fmt.Errorf("failed to decode key in test case %d/%d: %s", group.ID, test.ID, err) + } + + // https://usnistgov.github.io/ACVP/artifacts/acvp_sub_mac.html#hmac_vector_responses + response.Tests = append(response.Tests, hmacTestResponse{ + ID: test.ID, + MACHex: hex.EncodeToString(h.hmac(msg, key, group.MACBits)), + }) + } + + ret = append(ret, response) + } + + return ret, nil +}
diff --git a/util/fipstools/acvp/acvptool/subprocess/subprocess.go b/util/fipstools/acvp/acvptool/subprocess/subprocess.go index 3550b4d..1f3ff8e 100644 --- a/util/fipstools/acvp/acvptool/subprocess/subprocess.go +++ b/util/fipstools/acvp/acvptool/subprocess/subprocess.go
@@ -43,19 +43,24 @@ // WriteCloser. The returned Subprocess will call Wait on the Cmd when closed. func NewWithIO(cmd *exec.Cmd, in io.WriteCloser, out io.ReadCloser) *Subprocess { m := &Subprocess{ - cmd: cmd, + cmd: cmd, stdin: in, stdout: out, } m.primitives = map[string]primitive{ - "SHA-1": &hashPrimitive{"SHA-1", 20, m}, - "SHA2-224": &hashPrimitive{"SHA2-224", 28, m}, - "SHA2-256": &hashPrimitive{"SHA2-256", 32, m}, - "SHA2-384": &hashPrimitive{"SHA2-384", 48, m}, - "SHA2-512": &hashPrimitive{"SHA2-512", 64, m}, - "ACVP-AES-ECB": &blockCipher{"AES", 16, false, m}, - "ACVP-AES-CBC": &blockCipher{"AES-CBC", 16, true, m}, + "SHA-1": &hashPrimitive{"SHA-1", 20, m}, + "SHA2-224": &hashPrimitive{"SHA2-224", 28, m}, + "SHA2-256": &hashPrimitive{"SHA2-256", 32, m}, + "SHA2-384": &hashPrimitive{"SHA2-384", 48, m}, + "SHA2-512": &hashPrimitive{"SHA2-512", 64, m}, + "ACVP-AES-ECB": &blockCipher{"AES", 16, false, m}, + "ACVP-AES-CBC": &blockCipher{"AES-CBC", 16, true, m}, + "HMAC-SHA-1": &hmacPrimitive{"HMAC-SHA-1", 20, m}, + "HMAC-SHA2-224": &hmacPrimitive{"HMAC-SHA2-224", 28, m}, + "HMAC-SHA2-256": &hmacPrimitive{"HMAC-SHA2-256", 32, m}, + "HMAC-SHA2-384": &hmacPrimitive{"HMAC-SHA2-384", 48, m}, + "HMAC-SHA2-512": &hmacPrimitive{"HMAC-SHA2-512", 64, m}, } return m
diff --git a/util/fipstools/acvp/modulewrapper/modulewrapper.cc b/util/fipstools/acvp/modulewrapper/modulewrapper.cc index fd7639a..e6d8b21 100644 --- a/util/fipstools/acvp/modulewrapper/modulewrapper.cc +++ b/util/fipstools/acvp/modulewrapper/modulewrapper.cc
@@ -23,6 +23,8 @@ #include <cstdarg> #include <openssl/aes.h> +#include <openssl/digest.h> +#include <openssl/hmac.h> #include <openssl/sha.h> #include <openssl/span.h> @@ -160,6 +162,56 @@ " \"revision\": \"1.0\"," " \"direction\": [\"encrypt\", \"decrypt\"]," " \"keyLen\": [128, 192, 256]" + "}," + "{" + " \"algorithm\": \"HMAC-SHA-1\"," + " \"revision\": \"1.0\"," + " \"keyLen\": [{" + " \"min\": 8, \"max\": 2048, \"increment\": 8" + " }]," + " \"macLen\": [{" + " \"min\": 32, \"max\": 160, \"increment\": 8" + " }]" + "}," + "{" + " \"algorithm\": \"HMAC-SHA2-224\"," + " \"revision\": \"1.0\"," + " \"keyLen\": [{" + " \"min\": 8, \"max\": 2048, \"increment\": 8" + " }]," + " \"macLen\": [{" + " \"min\": 32, \"max\": 224, \"increment\": 8" + " }]" + "}," + "{" + " \"algorithm\": \"HMAC-SHA2-256\"," + " \"revision\": \"1.0\"," + " \"keyLen\": [{" + " \"min\": 8, \"max\": 2048, \"increment\": 8" + " }]," + " \"macLen\": [{" + " \"min\": 32, \"max\": 256, \"increment\": 8" + " }]" + "}," + "{" + " \"algorithm\": \"HMAC-SHA2-384\"," + " \"revision\": \"1.0\"," + " \"keyLen\": [{" + " \"min\": 8, \"max\": 2048, \"increment\": 8" + " }]," + " \"macLen\": [{" + " \"min\": 32, \"max\": 384, \"increment\": 8" + " }]" + "}," + "{" + " \"algorithm\": \"HMAC-SHA2-512\"," + " \"revision\": \"1.0\"," + " \"keyLen\": [{" + " \"min\": 8, \"max\": 2048, \"increment\": 8" + " }]," + " \"macLen\": [{" + " \"min\": 32, \"max\": 512, \"increment\": 8" + " }]" "}" "]"; return WriteReply( @@ -216,6 +268,18 @@ return WriteReply(STDOUT_FILENO, Span<const uint8_t>(out)); } +template <const EVP_MD *HashFunc()> +static bool HMAC(const Span<const uint8_t> args[]) { + const EVP_MD *const md = HashFunc(); + uint8_t digest[EVP_MAX_MD_SIZE]; + unsigned digest_len; + if (::HMAC(md, args[1].data(), args[1].size(), args[0].data(), args[0].size(), + digest, &digest_len) == nullptr) { + return false; + } + return WriteReply(STDOUT_FILENO, Span<const uint8_t>(digest, digest_len)); +} + static constexpr struct { const char name[kMaxNameLength + 1]; uint8_t expected_args; @@ -231,6 +295,11 @@ {"AES/decrypt", 2, AES<AES_set_decrypt_key, AES_decrypt>}, {"AES-CBC/encrypt", 3, AES_CBC<AES_set_encrypt_key, AES_ENCRYPT>}, {"AES-CBC/decrypt", 3, AES_CBC<AES_set_decrypt_key, AES_DECRYPT>}, + {"HMAC-SHA-1", 2, HMAC<EVP_sha1>}, + {"HMAC-SHA2-224", 2, HMAC<EVP_sha224>}, + {"HMAC-SHA2-256", 2, HMAC<EVP_sha256>}, + {"HMAC-SHA2-384", 2, HMAC<EVP_sha384>}, + {"HMAC-SHA2-512", 2, HMAC<EVP_sha512>}, }; int main() {