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>
chromium-stable
Adam Langley 10 months ago committed by Boringssl LUCI CQ
parent f5e0c8f92a
commit 8f4e9d4154
  1. 59
      util/fipstools/acvp/acvptool/subprocess/aead.go
  2. BIN
      util/fipstools/acvp/acvptool/test/expected/ACVP-AES-GCM-randnonce.bz2
  3. 1
      util/fipstools/acvp/acvptool/test/tests.json
  4. BIN
      util/fipstools/acvp/acvptool/test/vectors/ACVP-AES-GCM-randnonce.bz2
  5. 50
      util/fipstools/acvp/modulewrapper/modulewrapper.cc

@ -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:]
}

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

@ -344,7 +344,7 @@ static bool GetConfig(const Span<const uint8_t> 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<const uint8_t> 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<const uint8_t> args[], ReplyCallback write_reply)
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 @@ static bool AESGCMSetup(EVP_AEAD_CTX *ctx, Span<const uint8_t> tag_len_span,
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 @@ static bool AESGCMSetup(EVP_AEAD_CTX *ctx, Span<const uint8_t> tag_len_span,
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 @@ static constexpr struct {
{"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},

Loading…
Cancel
Save