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() {