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},