acvp: add CMAC-AES support. Change by Dan Janni. Change-Id: I3f059e7b1a822c6f97128ca92a693499a3f7fa8f Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/41984 Commit-Queue: Adam Langley <agl@google.com> Reviewed-by: David Benjamin <davidben@google.com>
diff --git a/util/fipstools/acvp/acvptool/subprocess/keyedMac.go b/util/fipstools/acvp/acvptool/subprocess/keyedMac.go new file mode 100644 index 0000000..c0feac4 --- /dev/null +++ b/util/fipstools/acvp/acvptool/subprocess/keyedMac.go
@@ -0,0 +1,153 @@ +// Copyright (c) 2020, Google Inc. +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY +// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION +// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN +// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +package subprocess + +import ( + "bytes" + "encoding/hex" + "encoding/json" + "fmt" +) + +// The following structures reflect the JSON of CMAC-AES tests. See +// https://usnistgov.github.io/ACVP/artifacts/acvp_sub_mac.html#rfc.section.4.2 + +type keyedMACTestVectorSet struct { + Groups []keyedMACTestGroup `json:"testGroups"` +} + +type keyedMACTestGroup struct { + ID uint64 `json:"tgId"` + Type string `json:"testType"` + Direction string `json:"direction"` + MsgBits uint32 `json:"msgLen"` + KeyBits uint32 `json:"keyLen"` + MACBits uint32 `json:"macLen"` + Tests []struct { + ID uint64 `json:"tcId"` + KeyHex string `json:"key"` + MsgHex string `json:"message"` + MACHex string `json:"mac"` + } +} + +type keyedMACTestGroupResponse struct { + ID uint64 `json:"tgId"` + Tests []keyedMACTestResponse `json:"tests"` +} + +type keyedMACTestResponse struct { + ID uint64 `json:"tcId"` + MACHex string `json:"mac,omitempty"` + Passed *bool `json:"testPassed,omitempty"` +} + +type keyedMACPrimitive struct { + algo string +} + +func (k *keyedMACPrimitive) Process(vectorSet []byte, m Transactable) (interface{}, error) { + var vs keyedMACTestVectorSet + if err := json.Unmarshal(vectorSet, &vs); err != nil { + return nil, err + } + + var respGroups []keyedMACTestGroupResponse + for _, group := range vs.Groups { + respGroup := keyedMACTestGroupResponse{ID: group.ID} + + if group.KeyBits%8 != 0 { + return nil, fmt.Errorf("%d bit key in test group %d: fractional bytes not supported", group.KeyBits, group.ID) + } + if group.MsgBits%8 != 0 { + return nil, fmt.Errorf("%d bit message in test group %d: fractional bytes not supported", group.KeyBits, group.ID) + } + if group.MACBits%8 != 0 { + return nil, fmt.Errorf("%d bit MAC in test group %d: fractional bytes not supported", group.KeyBits, group.ID) + } + + var generate bool + switch group.Direction { + case "gen": + generate = true + case "ver": + generate = false + default: + return nil, fmt.Errorf("unknown test direction %q in test group %d", group.Direction, group.ID) + } + + outputBytes := uint32le(group.MACBits / 8) + + for _, test := range group.Tests { + respTest := keyedMACTestResponse{ID: test.ID} + + // Validate input. + if keyBits := uint32(len(test.KeyHex)) * 4; keyBits != group.KeyBits { + return nil, fmt.Errorf("test case %d/%d contains key of length %d bits, but expected %d-bit value", group.ID, test.ID, keyBits, group.KeyBits) + } + if msgBits := uint32(len(test.MsgHex)) * 4; msgBits != group.MsgBits { + return nil, fmt.Errorf("test case %d/%d contains message of length %d bits, but expected %d-bit value", group.ID, test.ID, msgBits, group.MsgBits) + } + + if generate { + if len(test.MACHex) != 0 { + return nil, fmt.Errorf("test case %d/%d contains MAC but should not", group.ID, test.ID) + } + } else { + if macBits := uint32(len(test.MACHex)) * 4; macBits != group.MACBits { + return nil, fmt.Errorf("test case %d/%d contains MAC of length %d bits, but expected %d-bit value", group.ID, test.ID, macBits, group.MACBits) + } + } + + // Set up Transact parameters. + key, err := hex.DecodeString(test.KeyHex) + if err != nil { + return nil, fmt.Errorf("failed to decode KeyHex in test case %d/%d: %v", group.ID, test.ID, err) + } + + msg, err := hex.DecodeString(test.MsgHex) + if err != nil { + return nil, fmt.Errorf("failed to decode MsgHex in test case %d/%d: %v", group.ID, test.ID, err) + } + + result, err := m.Transact(k.algo, 1, outputBytes, key, msg) + if err != nil { + return nil, fmt.Errorf("wrapper %s operation failed: %s", k.algo, err) + } + + calculatedMAC := result[0] + if len(calculatedMAC) != int(group.MACBits/8) { + return nil, fmt.Errorf("%s operation returned incorrect length value", k.algo) + } + + if generate { + respTest.MACHex = hex.EncodeToString(calculatedMAC) + } else { + expectedMAC, err := hex.DecodeString(test.MACHex) + if err != nil { + return nil, fmt.Errorf("failed to decode MACHex in test case %d/%d: %v", group.ID, test.ID, err) + } + ok := bytes.Equal(calculatedMAC, expectedMAC) + respTest.Passed = &ok + } + + respGroup.Tests = append(respGroup.Tests, respTest) + } + + respGroups = append(respGroups, respGroup) + } + + return respGroups, nil +}
diff --git a/util/fipstools/acvp/acvptool/subprocess/subprocess.go b/util/fipstools/acvp/acvptool/subprocess/subprocess.go index b9a82ee..990223e 100644 --- a/util/fipstools/acvp/acvptool/subprocess/subprocess.go +++ b/util/fipstools/acvp/acvptool/subprocess/subprocess.go
@@ -86,6 +86,7 @@ "ctrDRBG": &drbg{"ctrDRBG", map[string]bool{"AES-128": true, "AES-192": true, "AES-256": true}}, "hmacDRBG": &drbg{"hmacDRBG", map[string]bool{"SHA-1": true, "SHA2-224": true, "SHA2-256": true, "SHA2-384": true, "SHA2-512": true}}, "KDF": &kdfPrimitive{}, + "CMAC-AES": &keyedMACPrimitive{"CMAC-AES"}, } m.primitives["ECDSA"] = &ecdsa{"ECDSA", map[string]bool{"P-224": true, "P-256": true, "P-384": true, "P-521": true}, m.primitives}
diff --git a/util/fipstools/acvp/acvptool/subprocess/subprocess_test.go b/util/fipstools/acvp/acvptool/subprocess/subprocess_test.go index 09214c4..c8c0853 100644 --- a/util/fipstools/acvp/acvptool/subprocess/subprocess_test.go +++ b/util/fipstools/acvp/acvptool/subprocess/subprocess_test.go
@@ -299,6 +299,52 @@ }] }`) +var validCMACAESJSON = []byte(`{ + "vsId": 1, + "algorithm": "CMAC-AES", + "revision": "1.0", + "testGroups": [{ + "tgId": 4, + "testType": "AFT", + "direction": "gen", + "keyLen": 128, + "msgLen": 2752, + "macLen": 64, + "tests": [{ + "tcId": 25, + "key": "E2547E38B28B2C24892C133FF4770688", + "message": "89DE09D747FB4B2669B59759A15BAAF068CAF31FD938DFCFFB38ECED53BA91DD659FD91E6CCCFEC5F972B1AD66BF78FE7FE319E58F514362FC75A346C144981B63FD18195A2AD482AF83711C9ADC449F7EAD32EBD5F4DB7EB93348404EAD496B8F4C89AB5FF7ACB2CFEFD96BD0FC9645B6F1F30AB02767ECA8771106DCA47188EE42183121FB9172B8E2133DE084F6CA3924E4BF3638ADA77DAAA6F06A6494E32CBAEFC6C6D0699BB12A425DCFE5974F687B6A71879D42DE08DF018A96429CFA40E32378D35E46A4956C5D7916B6877F353D33075FD4C64F32C3250D74FF070EA358135664CD8C9B82C9454EED75A12EEC758A9E514053533A884560FAC96DDBBA4AEEB8E473F4BFDB8447B22800D7782320D6E2DAC2599111F8CA598D6720CA7C6E4FC5EDC54FC3576460AAD1644B04E1D2C81B93EA49090FDB7E33374C243B2F19177405B94BEC3C69CC24CC686D8F2B01A6B2A350E394" + }] + }] +}`) + +var callsCMACAES = []fakeTransactCall{ + fakeTransactCall{cmd: "CMAC-AES", expectedNumResults: 1, args: [][]byte{ + uint32le(64 / 8), // outputBytes + fromHex("E2547E38B28B2C24892C133FF4770688"), // key + fromHex("89DE09D747FB4B2669B59759A15BAAF068CAF31FD938DFCFFB38ECED53BA91DD659FD91E6CCCFEC5F972B1AD66BF78FE7FE319E58F514362FC75A346C144981B63FD18195A2AD482AF83711C9ADC449F7EAD32EBD5F4DB7EB93348404EAD496B8F4C89AB5FF7ACB2CFEFD96BD0FC9645B6F1F30AB02767ECA8771106DCA47188EE42183121FB9172B8E2133DE084F6CA3924E4BF3638ADA77DAAA6F06A6494E32CBAEFC6C6D0699BB12A425DCFE5974F687B6A71879D42DE08DF018A96429CFA40E32378D35E46A4956C5D7916B6877F353D33075FD4C64F32C3250D74FF070EA358135664CD8C9B82C9454EED75A12EEC758A9E514053533A884560FAC96DDBBA4AEEB8E473F4BFDB8447B22800D7782320D6E2DAC2599111F8CA598D6720CA7C6E4FC5EDC54FC3576460AAD1644B04E1D2C81B93EA49090FDB7E33374C243B2F19177405B94BEC3C69CC24CC686D8F2B01A6B2A350E394"), // msg + }}, +} + +var invalidCMACAESJSON = []byte(`{ + "vsId": 1, + "algorithm": "CMAC-AES", + "revision": "1.0", + "testGroups": [{ + "tgId": 4, + "testType": "AFT", + "direction": "gen", + "keyLen": 128, + "msgLen": 2752, + "macLen": 64, + "tests": [{ + "tcId": abc, + "key": "E2547E38B28B2C24892C133FF4770688", + "message": "89DE09D747FB4B2669B59759A15BAAF068CAF31FD938DFCFFB38ECED53BA91DD659FD91E6CCCFEC5F972B1AD66BF78FE7FE319E58F514362FC75A346C144981B63FD18195A2AD482AF83711C9ADC449F7EAD32EBD5F4DB7EB93348404EAD496B8F4C89AB5FF7ACB2CFEFD96BD0FC9645B6F1F30AB02767ECA8771106DCA47188EE42183121FB9172B8E2133DE084F6CA3924E4BF3638ADA77DAAA6F06A6494E32CBAEFC6C6D0699BB12A425DCFE5974F687B6A71879D42DE08DF018A96429CFA40E32378D35E46A4956C5D7916B6877F353D33075FD4C64F32C3250D74FF070EA358135664CD8C9B82C9454EED75A12EEC758A9E514053533A884560FAC96DDBBA4AEEB8E473F4BFDB8447B22800D7782320D6E2DAC2599111F8CA598D6720CA7C6E4FC5EDC54FC3576460AAD1644B04E1D2C81B93EA49090FDB7E33374C243B2F19177405B94BEC3C69CC24CC686D8F2B01A6B2A350E394" + }] + }] +}`) + // fakeTransactable provides a fake to return results that don't go to the ACVP // server. type fakeTransactable struct { @@ -329,7 +375,7 @@ return ret.bytes, ret.err } -func newFakeTransactable(name string, numResponses int) *fakeTransactable { +func newFakeTransactable(numResponses int) *fakeTransactable { ret := new(fakeTransactable) // Add results requested by caller. @@ -379,6 +425,18 @@ }, }, { + algo: "CMAC-AES", + p: &keyedMACPrimitive{"CMAC-AES"}, + validJSON: validCMACAESJSON, + invalidJSON: invalidCMACAESJSON, + expectedCalls: callsCMACAES, + results: []fakeTransactResult{ + {bytes: [][]byte{ + fromHex("0102030405060708"), + }}, + }, + }, + { algo: "ACVP-AES-ECB", p: &blockCipher{"AES", 16, false}, validJSON: validACVPAESECB, @@ -398,7 +456,7 @@ } for _, test := range tests { - transactable := newFakeTransactable(test.algo, len(test.expectedCalls)) + transactable := newFakeTransactable(len(test.expectedCalls)) if len(test.results) > 0 { transactable.results = test.results }
diff --git a/util/fipstools/acvp/modulewrapper/modulewrapper.cc b/util/fipstools/acvp/modulewrapper/modulewrapper.cc index d6a9b0a..5de8b5d 100644 --- a/util/fipstools/acvp/modulewrapper/modulewrapper.cc +++ b/util/fipstools/acvp/modulewrapper/modulewrapper.cc
@@ -24,6 +24,7 @@ #include <openssl/aes.h> #include <openssl/bn.h> +#include <openssl/cmac.h> #include <openssl/digest.h> #include <openssl/ec.h> #include <openssl/ec_key.h> @@ -299,6 +300,24 @@ "SHA2-512" ] }] + }, + { + "algorithm": "CMAC-AES", + "revision": "1.0", + "capabilities": [{ + "direction": ["gen", "ver"], + "msgLen": [{ + "min": 0, + "max": 65536, + "increment": 8 + }], + "keyLen": [128, 256], + "macLen": [{ + "min": 32, + "max": 128, + "increment": 8 + }] + }] } ])"; return WriteReply( @@ -566,6 +585,25 @@ return WriteReply(STDOUT_FILENO, Span<const uint8_t>(reply)); } +static bool CMAC_AES(const Span<const uint8_t> args[]) { + uint8_t mac[16]; + if (!AES_CMAC(mac, args[1].data(), args[1].size(), args[2].data(), + args[2].size())) { + return false; + } + + uint32_t mac_len; + if (args[0].size() != sizeof(mac_len)) { + return false; + } + memcpy(&mac_len, args[0].data(), sizeof(mac_len)); + if (mac_len > sizeof(mac)) { + return false; + } + + return WriteReply(STDOUT_FILENO, Span<const uint8_t>(mac, mac_len)); +} + static constexpr struct { const char name[kMaxNameLength + 1]; uint8_t expected_args; @@ -591,6 +629,7 @@ {"ECDSA/keyVer", 3, ECDSAKeyVer}, {"ECDSA/sigGen", 4, ECDSASigGen}, {"ECDSA/sigVer", 7, ECDSASigVer}, + {"CMAC-AES", 3, CMAC_AES}, }; int main() {