acvp: add AES-GCM support.
Change-Id: I7636736752ac371fc8d86fbc6bf81ca797ac5092
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/43127
Reviewed-by: Adam Langley <agl@google.com>
Reviewed-by: David Benjamin <davidben@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
new file mode 100644
index 0000000..0c38b85
--- /dev/null
+++ b/util/fipstools/acvp/acvptool/subprocess/aead.go
@@ -0,0 +1,193 @@
+// Copyright (c) 2020, Google Inc.
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+package subprocess
+
+import (
+ "encoding/hex"
+ "encoding/json"
+ "fmt"
+)
+
+// aead implements an ACVP algorithm by making requests to the subprocess
+// to encrypt and decrypt with an AEAD.
+type aead struct {
+ algo string
+}
+
+type aeadVectorSet struct {
+ Groups []aeadTestGroup `json:"testGroups"`
+}
+
+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:"tcId"`
+ PlaintextHex string `json:"pt"`
+ CiphertextHex string `json:"ct"`
+ IVHex string `json:"iv"`
+ KeyHex string `json:"key"`
+ AADHex string `json:"aad"`
+ TagHex string `json:"tag"`
+ } `json:"tests"`
+}
+
+type aeadTestGroupResponse struct {
+ ID uint64 `json:"tgId"`
+ Tests []aeadTestResponse `json:"tests"`
+}
+
+type aeadTestResponse struct {
+ ID uint64 `json:"tcId"`
+ CiphertextHex *string `json:"ct,omitempty"`
+ TagHex string `json:"tag,omitempty"`
+ PlaintextHex *string `json:"pt,omitempty"`
+ Passed *bool `json:"testPassed,omitempty"`
+}
+
+func (a *aead) Process(vectorSet []byte, m Transactable) (interface{}, error) {
+ var parsed aeadVectorSet
+ if err := json.Unmarshal(vectorSet, &parsed); err != nil {
+ return nil, err
+ }
+
+ var ret []aeadTestGroupResponse
+ // See draft-celi-acvp-symmetric.html#table-6. (NIST no longer publish HTML
+ // versions of the ACVP documents. You can find fragments in
+ // https://github.com/usnistgov/ACVP.)
+ for _, group := range parsed.Groups {
+ response := aeadTestGroupResponse{
+ ID: group.ID,
+ }
+
+ var encrypt bool
+ switch group.Direction {
+ case "encrypt":
+ encrypt = true
+ case "decrypt":
+ encrypt = false
+ default:
+ 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"
+ }
+
+ if group.KeyBits%8 != 0 || group.KeyBits < 0 {
+ return nil, fmt.Errorf("test group %d contains non-byte-multiple key length %d", group.ID, group.KeyBits)
+ }
+ keyBytes := group.KeyBits / 8
+
+ if group.TagBits%8 != 0 || group.TagBits < 0 {
+ return nil, fmt.Errorf("test group %d contains non-byte-multiple tag length %d", group.ID, group.TagBits)
+ }
+ tagBytes := group.TagBits / 8
+
+ for _, test := range group.Tests {
+ if len(test.KeyHex) != keyBytes*2 {
+ return nil, fmt.Errorf("test case %d/%d contains key %q of length %d, but expected %d-bit key", group.ID, test.ID, test.KeyHex, len(test.KeyHex), group.KeyBits)
+ }
+
+ key, err := hex.DecodeString(test.KeyHex)
+ if err != nil {
+ return nil, fmt.Errorf("failed to decode key in test case %d/%d: %s", group.ID, test.ID, err)
+ }
+
+ nonce, err := hex.DecodeString(test.IVHex)
+ if err != nil {
+ return nil, fmt.Errorf("failed to decode nonce in test case %d/%d: %s", group.ID, test.ID, err)
+ }
+
+ aad, err := hex.DecodeString(test.AADHex)
+ if err != nil {
+ return nil, fmt.Errorf("failed to decode aad in test case %d/%d: %s", group.ID, test.ID, err)
+ }
+
+ var tag []byte
+ if !encrypt {
+ if tag, err = hex.DecodeString(test.TagHex); err != nil {
+ return nil, fmt.Errorf("failed to decode tag in test case %d/%d: %s", group.ID, test.ID, err)
+ }
+ if len(tag) != tagBytes {
+ return nil, fmt.Errorf("tag in test case %d/%d is %d bytes long, but should be %d", group.ID, test.ID, len(tag), tagBytes)
+ }
+ } else if len(test.TagHex) != 0 {
+ return nil, fmt.Errorf("test case %d/%d has unexpected tag input", group.ID, test.ID)
+ }
+
+ var inputHex, otherHex string
+ if encrypt {
+ inputHex, otherHex = test.PlaintextHex, test.CiphertextHex
+ } else {
+ inputHex, otherHex = test.CiphertextHex, test.PlaintextHex
+ }
+
+ if len(otherHex) != 0 {
+ return nil, fmt.Errorf("test case %d/%d has unexpected plain/ciphertext input", group.ID, test.ID)
+ }
+
+ input, err := hex.DecodeString(inputHex)
+ if err != nil {
+ return nil, fmt.Errorf("failed to decode hex in test case %d/%d: %s", group.ID, test.ID, err)
+ }
+
+ testResp := aeadTestResponse{ID: test.ID}
+
+ if encrypt {
+ result, err := m.Transact(op, 1, uint32le(uint32(tagBytes)), key, input, nonce, aad)
+ if err != nil {
+ return nil, err
+ }
+
+ if len(result[0]) < tagBytes {
+ return nil, fmt.Errorf("ciphertext from subprocess for test case %d/%d is shorter than the tag (%d vs %d)", group.ID, test.ID, len(result[0]), tagBytes)
+ }
+
+ ciphertext := result[0][:len(result[0])-tagBytes]
+ ciphertextHex := hex.EncodeToString(ciphertext)
+ tag := result[0][len(result[0])-tagBytes:]
+
+ testResp.CiphertextHex = &ciphertextHex
+ testResp.TagHex = hex.EncodeToString(tag)
+ } else {
+ result, err := m.Transact(op, 2, uint32le(uint32(tagBytes)), key, append(input, tag...), nonce, aad)
+ if err != nil {
+ return nil, err
+ }
+
+ if len(result[0]) != 1 || (result[0][0]&0xfe) != 0 {
+ return nil, fmt.Errorf("invalid AEAD status result from subprocess")
+ }
+ passed := result[0][0] == 1
+ testResp.Passed = &passed
+ if passed {
+ plaintextHex := hex.EncodeToString(result[1])
+ testResp.PlaintextHex = &plaintextHex
+ }
+ }
+
+ response.Tests = append(response.Tests, testResp)
+ }
+
+ ret = append(ret, response)
+ }
+
+ return ret, nil
+}
diff --git a/util/fipstools/acvp/acvptool/subprocess/subprocess.go b/util/fipstools/acvp/acvptool/subprocess/subprocess.go
index af757d5..a9fbdfc 100644
--- a/util/fipstools/acvp/acvptool/subprocess/subprocess.go
+++ b/util/fipstools/acvp/acvptool/subprocess/subprocess.go
@@ -79,6 +79,7 @@
"ACVP-AES-ECB": &blockCipher{"AES", 16, true, false},
"ACVP-AES-CBC": &blockCipher{"AES-CBC", 16, true, true},
"ACVP-AES-CTR": &blockCipher{"AES-CTR", 16, false, true},
+ "ACVP-AES-GCM": &aead{"AES-GCM"},
"HMAC-SHA-1": &hmacPrimitive{"HMAC-SHA-1", 20},
"HMAC-SHA2-224": &hmacPrimitive{"HMAC-SHA2-224", 28},
"HMAC-SHA2-256": &hmacPrimitive{"HMAC-SHA2-256", 32},
diff --git a/util/fipstools/acvp/modulewrapper/modulewrapper.cc b/util/fipstools/acvp/modulewrapper/modulewrapper.cc
index 043d375..2e9598b 100644
--- a/util/fipstools/acvp/modulewrapper/modulewrapper.cc
+++ b/util/fipstools/acvp/modulewrapper/modulewrapper.cc
@@ -22,6 +22,7 @@
#include <unistd.h>
#include <cstdarg>
+#include <openssl/aead.h>
#include <openssl/aes.h>
#include <openssl/bn.h>
#include <openssl/cmac.h>
@@ -189,6 +190,21 @@
"keyLen": [128, 192, 256]
},
{
+ "algorithm": "ACVP-AES-GCM",
+ "revision": "1.0",
+ "direction": ["encrypt", "decrypt"],
+ "keyLen": [128, 192, 256],
+ "payloadLen": [{
+ "min": 0, "max": 256, "increment": 8
+ }],
+ "aadLen": [{
+ "min": 0, "max": 256, "increment": 8
+ }],
+ "tagLen": [128],
+ "ivLen": [96],
+ "ivGen": "external"
+ },
+ {
"algorithm": "HMAC-SHA-1",
"revision": "1.0",
"keyLen": [{
@@ -411,6 +427,100 @@
return WriteReply(STDOUT_FILENO, Span<const uint8_t>(out));
}
+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)) {
+ fprintf(stderr, "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 EVP_AEAD *aead;
+ switch (key.size()) {
+ case 16:
+ aead = EVP_aead_aes_128_gcm();
+ break;
+ case 24:
+ aead = EVP_aead_aes_192_gcm();
+ break;
+ case 32:
+ aead = EVP_aead_aes_256_gcm();
+ break;
+ default:
+ fprintf(stderr, "Bad AES-GCM key length %u\n",
+ static_cast<unsigned>(key.size()));
+ return false;
+ }
+
+ if (!EVP_AEAD_CTX_init(ctx, aead, key.data(), key.size(), tag_len_32,
+ nullptr)) {
+ fprintf(stderr, "Failed to setup AES-GCM with tag length %u\n",
+ static_cast<unsigned>(tag_len_32));
+ return false;
+ }
+
+ return true;
+}
+
+static bool AESGCMSeal(const Span<const uint8_t> args[]) {
+ Span<const uint8_t> tag_len_span = args[0];
+ Span<const uint8_t> key = args[1];
+ Span<const uint8_t> plaintext = args[2];
+ Span<const uint8_t> nonce = args[3];
+ Span<const uint8_t> ad = args[4];
+
+ bssl::ScopedEVP_AEAD_CTX ctx;
+ if (!AESGCMSetup(ctx.get(), tag_len_span, key)) {
+ return false;
+ }
+
+ if (EVP_AEAD_MAX_OVERHEAD + plaintext.size() < EVP_AEAD_MAX_OVERHEAD) {
+ return false;
+ }
+ std::vector<uint8_t> out(EVP_AEAD_MAX_OVERHEAD + plaintext.size());
+
+ size_t out_len;
+ if (!EVP_AEAD_CTX_seal(ctx.get(), out.data(), &out_len, out.size(),
+ nonce.data(), nonce.size(), plaintext.data(),
+ plaintext.size(), ad.data(), ad.size())) {
+ return false;
+ }
+
+ out.resize(out_len);
+ return WriteReply(STDOUT_FILENO, Span<const uint8_t>(out));
+}
+
+static bool AESGCMOpen(const Span<const uint8_t> args[]) {
+ Span<const uint8_t> tag_len_span = args[0];
+ Span<const uint8_t> key = args[1];
+ Span<const uint8_t> ciphertext = args[2];
+ Span<const uint8_t> nonce = args[3];
+ Span<const uint8_t> ad = args[4];
+
+ bssl::ScopedEVP_AEAD_CTX ctx;
+ if (!AESGCMSetup(ctx.get(), tag_len_span, key)) {
+ return false;
+ }
+
+ std::vector<uint8_t> out(ciphertext.size());
+ size_t out_len;
+ uint8_t success[1] = {0};
+
+ if (!EVP_AEAD_CTX_open(ctx.get(), out.data(), &out_len, out.size(),
+ nonce.data(), nonce.size(), ciphertext.data(),
+ ciphertext.size(), ad.data(), ad.size())) {
+ return WriteReply(STDOUT_FILENO, Span<const uint8_t>(success),
+ Span<const uint8_t>());
+ }
+
+ out.resize(out_len);
+ success[0] = 1;
+ return WriteReply(STDOUT_FILENO, Span<const uint8_t>(success),
+ Span<const uint8_t>(out));
+}
+
template <const EVP_MD *HashFunc()>
static bool HMAC(const Span<const uint8_t> args[]) {
const EVP_MD *const md = HashFunc();
@@ -658,6 +768,8 @@
{"AES-CBC/decrypt", 3, AES_CBC<AES_set_decrypt_key, AES_DECRYPT>},
{"AES-CTR/encrypt", 3, AES_CTR},
{"AES-CTR/decrypt", 3, AES_CTR},
+ {"AES-GCM/seal", 5, AESGCMSeal},
+ {"AES-GCM/open", 5, AESGCMOpen},
{"HMAC-SHA-1", 2, HMAC<EVP_sha1>},
{"HMAC-SHA2-224", 2, HMAC<EVP_sha224>},
{"HMAC-SHA2-256", 2, HMAC<EVP_sha256>},