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>
chromium-5359
Adam Langley 4 years ago committed by CQ bot account: commit-bot@chromium.org
parent 6b6b66bacd
commit fda92cd640
  1. 193
      util/fipstools/acvp/acvptool/subprocess/aead.go
  2. 1
      util/fipstools/acvp/acvptool/subprocess/subprocess.go
  3. 112
      util/fipstools/acvp/modulewrapper/modulewrapper.cc

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

@ -79,6 +79,7 @@ func NewWithIO(cmd *exec.Cmd, in io.WriteCloser, out io.ReadCloser) *Subprocess
"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},

@ -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>
@ -188,6 +189,21 @@ static bool GetConfig(const Span<const uint8_t> args[]) {
"direction": ["encrypt", "decrypt"],
"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",
@ -411,6 +427,100 @@ static bool AES_CTR(const Span<const uint8_t> args[]) {
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 @@ static constexpr struct {
{"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>},

Loading…
Cancel
Save