acvp: test with internal nonce generation. 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>
diff --git a/util/fipstools/acvp/acvptool/subprocess/aead.go b/util/fipstools/acvp/acvptool/subprocess/aead.go index c38b170..dd7e75c 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"` } @@ -87,9 +89,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 { @@ -173,17 +190,27 @@ 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 @@ 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 421e253..6804b23 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 5274598..b66f2ac 100644 --- a/util/fipstools/acvp/modulewrapper/modulewrapper.cc +++ b/util/fipstools/acvp/modulewrapper/modulewrapper.cc
@@ -344,7 +344,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 }], @@ -353,7 +353,8 @@ }], "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 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()) { @@ -1168,7 +1168,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; } @@ -1182,6 +1183,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; @@ -2123,6 +2159,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},