acvp: test with internal nonce generation. (Cherry-pick to FIPS branch from https://boringssl-review.googlesource.com/c/boringssl/+/65227) AES-GCM where the module generates the nonce itself is more directly approved by FIPS. ACVP that instead of AES-GCM with an external nonce, where compliance depends on a careful reading of the IG. Change-Id: I8a3e926a28ae633a0b0c499cb35c321ccf9c0e30 Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/65227 Reviewed-by: David Benjamin <davidben@google.com> Reviewed-by: Bob Beck <bbe@google.com> Commit-Queue: Adam Langley <agl@google.com> Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/65347
diff --git a/util/fipstools/acvp/acvptool/subprocess/aead.go b/util/fipstools/acvp/acvptool/subprocess/aead.go index e6585d1..c5170b5 100644 --- a/util/fipstools/acvp/acvptool/subprocess/aead.go +++ b/util/fipstools/acvp/acvptool/subprocess/aead.go
@@ -32,12 +32,13 @@ } 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 @@ 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"` } @@ -86,9 +88,24 @@ 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 { @@ -174,14 +191,24 @@ 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) } } else { - result, err := m.Transact(op, 2, uint32le(uint32(tagBytes)), key, append(input, tag...), nonce, aad) + ciphertext := append(input, tag...) + if randnonce { + ciphertext = append(ciphertext, nonce...) + nonce = []byte{} + } + result, err := m.Transact(op, 2, uint32le(uint32(tagBytes)), key, ciphertext, nonce, aad) if err != nil { return nil, err } @@ -205,3 +232,11 @@ 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 0000000..edab948 --- /dev/null +++ b/util/fipstools/acvp/acvptool/test/expected/ACVP-AES-GCM-randnonce.bz2 Binary files differ
diff --git a/util/fipstools/acvp/acvptool/test/tests.json b/util/fipstools/acvp/acvptool/test/tests.json index 5765de1..f28cfe7 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 0000000..0880f69 --- /dev/null +++ b/util/fipstools/acvp/acvptool/test/vectors/ACVP-AES-GCM-randnonce.bz2 Binary files differ
diff --git a/util/fipstools/acvp/modulewrapper/modulewrapper.cc b/util/fipstools/acvp/modulewrapper/modulewrapper.cc index f65492c..904cf7a 100644 --- a/util/fipstools/acvp/modulewrapper/modulewrapper.cc +++ b/util/fipstools/acvp/modulewrapper/modulewrapper.cc
@@ -296,7 +296,7 @@ "algorithm": "ACVP-AES-GCM", "revision": "1.0", "direction": ["encrypt", "decrypt"], - "keyLen": [128, 192, 256], + "keyLen": [128, 256], "payloadLen": [{ "min": 0, "max": 65536, "increment": 8 }], @@ -305,7 +305,8 @@ }], "tagLen": [32, 64, 96, 104, 112, 120, 128], "ivLen": [96], - "ivGen": "external" + "ivGen": "internal", + "ivGenMode": "8.2.2" }, { "algorithm": "ACVP-AES-GMAC", @@ -1054,13 +1055,12 @@ static bool AESGCMSetup(EVP_AEAD_CTX *ctx, Span<const uint8_t> tag_len_span, Span<const uint8_t> 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<unsigned>(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()) { @@ -1074,7 +1074,8 @@ aead = EVP_aead_aes_256_gcm(); break; default: - LOG_ERROR("Bad AES-GCM key length %u\n", static_cast<unsigned>(key.size())); + LOG_ERROR("Bad AES-GCM key length %u\n", + static_cast<unsigned>(key.size())); return false; } @@ -1088,6 +1089,41 @@ return true; } +static bool AESGCMRandNonceSetup(EVP_AEAD_CTX *ctx, + Span<const uint8_t> tag_len_span, + Span<const uint8_t> key) { + if (tag_len_span.size() != sizeof(uint32_t)) { + LOG_ERROR("Tag size value is %u bytes, not an uint32_t\n", + static_cast<unsigned>(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<unsigned>(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<unsigned>(tag_len_32)); + return false; + } + + return true; +} + static bool AESCCMSetup(EVP_AEAD_CTX *ctx, Span<const uint8_t> tag_len_span, Span<const uint8_t> key) { uint32_t tag_len_32; @@ -1969,6 +2005,8 @@ {"AES-CTR/decrypt", 4, AES_CTR}, {"AES-GCM/seal", 5, AEADSeal<AESGCMSetup>}, {"AES-GCM/open", 5, AEADOpen<AESGCMSetup>}, + {"AES-GCM-randnonce/seal", 5, AEADSeal<AESGCMRandNonceSetup>}, + {"AES-GCM-randnonce/open", 5, AEADOpen<AESGCMRandNonceSetup>}, {"AES-KW/seal", 5, AESKeyWrapSeal}, {"AES-KW/open", 5, AESKeyWrapOpen}, {"AES-KWP/seal", 5, AESPaddedKeyWrapSeal},