diff --git a/util/fipstools/acvp/acvptool/subprocess/aead.go b/util/fipstools/acvp/acvptool/subprocess/aead.go index c38b17073..dd7e75cf7 100644 --- a/util/fipstools/acvp/acvptool/subprocess/aead.go +++ b/util/fipstools/acvp/acvptool/subprocess/aead.go @@ -32,12 +32,13 @@ type aeadVectorSet struct { } type aeadTestGroup struct { - ID uint64 `json:"tgId"` - Type string `json:"testType"` - Direction string `json:"direction"` - KeyBits int `json:"keyLen"` - TagBits int `json:"tagLen"` - Tests []struct { + ID uint64 `json:"tgId"` + Type string `json:"testType"` + Direction string `json:"direction"` + KeyBits int `json:"keyLen"` + TagBits int `json:"tagLen"` + NonceSource string `json:"ivGen"` + Tests []struct { ID uint64 `json:"tcId"` PlaintextHex string `json:"pt"` CiphertextHex string `json:"ct"` @@ -57,6 +58,7 @@ type aeadTestResponse struct { ID uint64 `json:"tcId"` CiphertextHex *string `json:"ct,omitempty"` TagHex string `json:"tag,omitempty"` + NonceHex string `json:"iv,omitempty"` PlaintextHex *string `json:"pt,omitempty"` Passed *bool `json:"testPassed,omitempty"` } @@ -87,9 +89,24 @@ func (a *aead) Process(vectorSet []byte, m Transactable) (any, error) { return nil, fmt.Errorf("test group %d has unknown direction %q", group.ID, group.Direction) } - op := a.algo + "/seal" - if !encrypt { - op = a.algo + "/open" + var randnonce bool + switch group.NonceSource { + case "internal": + randnonce = true + case "external", "": + randnonce = false + default: + return nil, fmt.Errorf("test group %d has unknown nonce source %q", group.ID, group.NonceSource) + } + + op := a.algo + if randnonce { + op += "-randnonce" + } + if encrypt { + op += "/seal" + } else { + op += "/open" } if group.KeyBits%8 != 0 || group.KeyBits < 0 { @@ -173,17 +190,27 @@ func (a *aead) Process(vectorSet []byte, m Transactable) (any, error) { ciphertextHex := hex.EncodeToString(result[0]) testResp.CiphertextHex = &ciphertextHex } else { - ciphertext := result[0][:len(result[0])-tagBytes] + ciphertext := result[0] + if randnonce { + var nonce []byte + ciphertext, nonce = splitOffRight(ciphertext, 12) + testResp.NonceHex = hex.EncodeToString(nonce) + } + ciphertext, tag := splitOffRight(ciphertext, tagBytes) ciphertextHex := hex.EncodeToString(ciphertext) testResp.CiphertextHex = &ciphertextHex - tag := result[0][len(result[0])-tagBytes:] testResp.TagHex = hex.EncodeToString(tag) } response.Tests = append(response.Tests, testResp) return nil }) } else { - m.TransactAsync(op, 2, [][]byte{uint32le(uint32(tagBytes)), key, append(input, tag...), nonce, aad}, func(result [][]byte) error { + ciphertext := append(input, tag...) + if randnonce { + ciphertext = append(ciphertext, nonce...) + nonce = []byte{} + } + m.TransactAsync(op, 2, [][]byte{uint32le(uint32(tagBytes)), key, ciphertext, nonce, aad}, func(result [][]byte) error { if len(result[0]) != 1 || (result[0][0]&0xfe) != 0 { return fmt.Errorf("invalid AEAD status result from subprocess") } @@ -210,3 +237,11 @@ func (a *aead) Process(vectorSet []byte, m Transactable) (any, error) { return ret, nil } + +func splitOffRight(in []byte, suffixSize int) ([]byte, []byte) { + if len(in) < suffixSize { + panic("input too small to split") + } + split := len(in) - suffixSize + return in[:split], in[split:] +} diff --git a/util/fipstools/acvp/acvptool/test/expected/ACVP-AES-GCM-randnonce.bz2 b/util/fipstools/acvp/acvptool/test/expected/ACVP-AES-GCM-randnonce.bz2 new file mode 100644 index 000000000..edab948a3 Binary files /dev/null and b/util/fipstools/acvp/acvptool/test/expected/ACVP-AES-GCM-randnonce.bz2 differ diff --git a/util/fipstools/acvp/acvptool/test/tests.json b/util/fipstools/acvp/acvptool/test/tests.json index 421e25351..6804b23b4 100644 --- a/util/fipstools/acvp/acvptool/test/tests.json +++ b/util/fipstools/acvp/acvptool/test/tests.json @@ -5,6 +5,7 @@ {"Wrapper": "modulewrapper", "In": "vectors/ACVP-AES-CTR.bz2", "Out": "expected/ACVP-AES-CTR.bz2"}, {"Wrapper": "modulewrapper", "In": "vectors/ACVP-AES-ECB.bz2", "Out": "expected/ACVP-AES-ECB.bz2"}, {"Wrapper": "modulewrapper", "In": "vectors/ACVP-AES-GCM.bz2", "Out": "expected/ACVP-AES-GCM.bz2"}, +{"Wrapper": "modulewrapper", "In": "vectors/ACVP-AES-GCM-randnonce.bz2", "Out": "expected/ACVP-AES-GCM-randnonce.bz2"}, {"Wrapper": "modulewrapper", "In": "vectors/ACVP-AES-GMAC.bz2", "Out": "expected/ACVP-AES-GMAC.bz2"}, {"Wrapper": "modulewrapper", "In": "vectors/ACVP-AES-KW.bz2", "Out": "expected/ACVP-AES-KW.bz2"}, {"Wrapper": "modulewrapper", "In": "vectors/ACVP-AES-KWP.bz2", "Out": "expected/ACVP-AES-KWP.bz2"}, diff --git a/util/fipstools/acvp/acvptool/test/vectors/ACVP-AES-GCM-randnonce.bz2 b/util/fipstools/acvp/acvptool/test/vectors/ACVP-AES-GCM-randnonce.bz2 new file mode 100644 index 000000000..0880f6902 Binary files /dev/null and b/util/fipstools/acvp/acvptool/test/vectors/ACVP-AES-GCM-randnonce.bz2 differ diff --git a/util/fipstools/acvp/modulewrapper/modulewrapper.cc b/util/fipstools/acvp/modulewrapper/modulewrapper.cc index 5274598fb..b66f2ac11 100644 --- a/util/fipstools/acvp/modulewrapper/modulewrapper.cc +++ b/util/fipstools/acvp/modulewrapper/modulewrapper.cc @@ -344,7 +344,7 @@ static bool GetConfig(const Span args[], ReplyCallback write_repl "algorithm": "ACVP-AES-GCM", "revision": "1.0", "direction": ["encrypt", "decrypt"], - "keyLen": [128, 192, 256], + "keyLen": [128, 256], "payloadLen": [{ "min": 0, "max": 65536, "increment": 8 }], @@ -353,7 +353,8 @@ static bool GetConfig(const Span args[], ReplyCallback write_repl }], "tagLen": [32, 64, 96, 104, 112, 120, 128], "ivLen": [96], - "ivGen": "external" + "ivGen": "internal", + "ivGenMode": "8.2.2" }, { "algorithm": "ACVP-AES-GMAC", @@ -1148,13 +1149,12 @@ static bool AES_CTR(const Span args[], ReplyCallback write_reply) static bool AESGCMSetup(EVP_AEAD_CTX *ctx, Span tag_len_span, Span key) { - uint32_t tag_len_32; - if (tag_len_span.size() != sizeof(tag_len_32)) { + if (tag_len_span.size() != sizeof(uint32_t)) { LOG_ERROR("Tag size value is %u bytes, not an uint32_t\n", static_cast(tag_len_span.size())); return false; } - memcpy(&tag_len_32, tag_len_span.data(), sizeof(tag_len_32)); + const uint32_t tag_len_32 = CRYPTO_load_u32_le(tag_len_span.data()); const EVP_AEAD *aead; switch (key.size()) { @@ -1168,7 +1168,8 @@ static bool AESGCMSetup(EVP_AEAD_CTX *ctx, Span tag_len_span, aead = EVP_aead_aes_256_gcm(); break; default: - LOG_ERROR("Bad AES-GCM key length %u\n", static_cast(key.size())); + LOG_ERROR("Bad AES-GCM key length %u\n", + static_cast(key.size())); return false; } @@ -1182,6 +1183,41 @@ static bool AESGCMSetup(EVP_AEAD_CTX *ctx, Span tag_len_span, return true; } +static bool AESGCMRandNonceSetup(EVP_AEAD_CTX *ctx, + Span tag_len_span, + Span key) { + if (tag_len_span.size() != sizeof(uint32_t)) { + LOG_ERROR("Tag size value is %u bytes, not an uint32_t\n", + static_cast(tag_len_span.size())); + return false; + } + const uint32_t tag_len_32 = CRYPTO_load_u32_le(tag_len_span.data()); + + const EVP_AEAD *aead; + switch (key.size()) { + case 16: + aead = EVP_aead_aes_128_gcm_randnonce(); + break; + case 32: + aead = EVP_aead_aes_256_gcm_randnonce(); + break; + default: + LOG_ERROR("Bad AES-GCM key length %u\n", + static_cast(key.size())); + return false; + } + + constexpr size_t kNonceLength = 12; + if (!EVP_AEAD_CTX_init(ctx, aead, key.data(), key.size(), + tag_len_32 + kNonceLength, nullptr)) { + LOG_ERROR("Failed to setup AES-GCM with tag length %u\n", + static_cast(tag_len_32)); + return false; + } + + return true; +} + static bool AESCCMSetup(EVP_AEAD_CTX *ctx, Span tag_len_span, Span key) { uint32_t tag_len_32; @@ -2123,6 +2159,8 @@ static constexpr struct { {"AES-CTR/decrypt", 4, AES_CTR}, {"AES-GCM/seal", 5, AEADSeal}, {"AES-GCM/open", 5, AEADOpen}, + {"AES-GCM-randnonce/seal", 5, AEADSeal}, + {"AES-GCM-randnonce/open", 5, AEADOpen}, {"AES-KW/seal", 5, AESKeyWrapSeal}, {"AES-KW/open", 5, AESKeyWrapOpen}, {"AES-KWP/seal", 5, AESPaddedKeyWrapSeal},