acvp: add support for AES-ECB and AES-CBC. Change-Id: I685701304576a519e68a13d22bd557fdbf5a84fb Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/36884 Commit-Queue: David Benjamin <davidben@google.com> Reviewed-by: David Benjamin <davidben@google.com>
diff --git a/util/fipstools/acvp/acvptool/subprocess/block.go b/util/fipstools/acvp/acvptool/subprocess/block.go new file mode 100644 index 0000000..a5ed2cb --- /dev/null +++ b/util/fipstools/acvp/acvptool/subprocess/block.go
@@ -0,0 +1,252 @@ +package subprocess + +import ( + "encoding/hex" + "encoding/json" + "fmt" +) + +// blockCipher implements an ACVP algorithm by making requests to the subprocess +// to encrypt and decrypt with a block cipher. +type blockCipher struct { + algo string + blockSize int + hasIV bool + m *Subprocess +} + +type blockCipherVectorSet struct { + Groups []blockCipherTestGroup `json:"testGroups"` +} + +type blockCipherTestGroup struct { + ID uint64 `json:"tgId"` + Type string `json:"testType"` + Direction string `json:"direction"` + KeyBits int `json:"keylen"` + Tests []struct { + ID uint64 `json:"tcId"` + PlaintextHex string `json:"pt"` + CiphertextHex string `json:"ct"` + IVHex string `json:"iv"` + KeyHex string `json:"key"` + } `json:"tests"` +} + +type blockCipherTestGroupResponse struct { + ID uint64 `json:"tgId"` + Tests []blockCipherTestResponse `json:"tests"` +} + +type blockCipherTestResponse struct { + ID uint64 `json:"tcId"` + CiphertextHex string `json:"ct,omitempty"` + PlaintextHex string `json:"pt,omitempty"` + MCTResults []blockCipherMCTResult `json:"resultsArray,omitempty"` +} + +type blockCipherMCTResult struct { + KeyHex string `json:"key"` + PlaintextHex string `json:"pt"` + CiphertextHex string `json:"ct"` + IVHex string `json:"iv,omitempty"` +} + +func (b *blockCipher) Process(vectorSet []byte) (interface{}, error) { + var parsed blockCipherVectorSet + if err := json.Unmarshal(vectorSet, &parsed); err != nil { + return nil, err + } + + var ret []blockCipherTestGroupResponse + // See + // http://usnistgov.github.io/ACVP/artifacts/draft-celi-acvp-block-ciph-00.html#rfc.section.5.2 + // for details about the tests. + for _, group := range parsed.Groups { + response := blockCipherTestGroupResponse{ + 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 := b.algo + "/encrypt" + if !encrypt { + op = b.algo + "/decrypt" + } + + var mct bool + switch group.Type { + case "AFT": + mct = false + case "MCT": + mct = true + default: + return nil, fmt.Errorf("test group %d has unknown type %q", group.ID, group.Type) + } + + if group.KeyBits%8 != 0 { + return nil, fmt.Errorf("test group %d contains non-byte-multiple key length %d", group.ID, group.KeyBits) + } + keyBytes := group.KeyBits / 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 hex in test case %d/%d: %s", group.ID, test.ID, err) + } + + var inputHex string + if encrypt { + inputHex = test.PlaintextHex + } else { + inputHex = test.CiphertextHex + } + + 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) + } + + if len(input)%b.blockSize != 0 { + return nil, fmt.Errorf("test case %d/%d has input of length %d, but expected multiple of %d", group.ID, test.ID, len(input), b.blockSize) + } + + var iv []byte + if b.hasIV { + if iv, err = hex.DecodeString(test.IVHex); err != nil { + return nil, fmt.Errorf("failed to decode hex in test case %d/%d: %s", group.ID, test.ID, err) + } + if len(iv) != b.blockSize { + return nil, fmt.Errorf("test case %d/%d has IV of length %d, but expected %d", group.ID, test.ID, len(iv), b.blockSize) + } + } + + testResp := blockCipherTestResponse{ID: test.ID} + if !mct { + var result [][]byte + var err error + + if b.hasIV { + result, err = b.m.transact(op, 1, key, input, iv) + } else { + result, err = b.m.transact(op, 1, key, input) + } + if err != nil { + panic("block operation failed: " + err.Error()) + } + + if encrypt { + testResp.CiphertextHex = hex.EncodeToString(result[0]) + } else { + testResp.PlaintextHex = hex.EncodeToString(result[0]) + } + } else { + for i := 0; i < 100; i++ { + var iteration blockCipherMCTResult + iteration.KeyHex = hex.EncodeToString(key) + if encrypt { + iteration.PlaintextHex = hex.EncodeToString(input) + } else { + iteration.CiphertextHex = hex.EncodeToString(input) + } + + var result, prevResult []byte + if !b.hasIV { + for j := 0; j < 1000; j++ { + prevResult = input + result, err := b.m.transact(op, 1, key, input) + if err != nil { + panic("block operation failed") + } + input = result[0] + } + result = input + } else { + iteration.IVHex = hex.EncodeToString(iv) + + var prevInput []byte + for j := 0; j < 1000; j++ { + prevResult = result + if j > 0 { + if encrypt { + iv = result + } else { + iv = prevInput + } + } + + results, err := b.m.transact(op, 1, key, input, iv) + if err != nil { + panic("block operation failed") + } + result = results[0] + + prevInput = input + if j == 0 { + input = iv + } else { + input = prevResult + } + } + } + + if encrypt { + iteration.CiphertextHex = hex.EncodeToString(result) + } else { + iteration.PlaintextHex = hex.EncodeToString(result) + } + + switch keyBytes { + case 16: + for i := range key { + key[i] ^= result[i] + } + case 24: + for i := 0; i < 8; i++ { + key[i] ^= prevResult[i+8] + } + for i := range result { + key[i+8] ^= result[i] + } + case 32: + for i, b := range prevResult { + key[i] ^= b + } + for i, b := range result { + key[i+16] ^= b + } + default: + panic("unhandled key length") + } + + if !b.hasIV { + input = result + } else { + iv = result + input = prevResult + } + + testResp.MCTResults = append(testResp.MCTResults, iteration) + } + } + + 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 7ab1c4b..404569f 100644 --- a/util/fipstools/acvp/acvptool/subprocess/subprocess.go +++ b/util/fipstools/acvp/acvptool/subprocess/subprocess.go
@@ -43,11 +43,13 @@ } m.primitives = map[string]primitive{ - "SHA-1": &hashPrimitive{"SHA-1", 20, m}, - "SHA2-224": &hashPrimitive{"SHA2-224", 28, m}, - "SHA2-256": &hashPrimitive{"SHA2-256", 32, m}, - "SHA2-384": &hashPrimitive{"SHA2-384", 48, m}, - "SHA2-512": &hashPrimitive{"SHA2-512", 64, m}, + "SHA-1": &hashPrimitive{"SHA-1", 20, m}, + "SHA2-224": &hashPrimitive{"SHA2-224", 28, m}, + "SHA2-256": &hashPrimitive{"SHA2-256", 32, m}, + "SHA2-384": &hashPrimitive{"SHA2-384", 48, m}, + "SHA2-512": &hashPrimitive{"SHA2-512", 64, m}, + "ACVP-AES-ECB": &blockCipher{"AES", 16, false, m}, + "ACVP-AES-CBC": &blockCipher{"AES-CBC", 16, true, m}, } return m, nil
diff --git a/util/fipstools/acvp/modulewrapper/modulewrapper.cc b/util/fipstools/acvp/modulewrapper/modulewrapper.cc index 79976dc..f877c75 100644 --- a/util/fipstools/acvp/modulewrapper/modulewrapper.cc +++ b/util/fipstools/acvp/modulewrapper/modulewrapper.cc
@@ -20,6 +20,7 @@ #include <unistd.h> #include <cstdarg> +#include <openssl/aes.h> #include <openssl/sha.h> #include <openssl/span.h> @@ -145,6 +146,18 @@ " \"messageLength\": [{" " \"min\": 0, \"max\": 65528, \"increment\": 8" " }]" + "}," + "{" + " \"algorithm\": \"ACVP-AES-ECB\"," + " \"revision\": \"1.0\"," + " \"direction\": [\"encrypt\", \"decrypt\"]," + " \"keyLen\": [128, 192, 256]" + "}," + "{" + " \"algorithm\": \"ACVP-AES-CBC\"," + " \"revision\": \"1.0\"," + " \"direction\": [\"encrypt\", \"decrypt\"]," + " \"keyLen\": [128, 192, 256]" "}" "]"; return WriteReply( @@ -161,6 +174,46 @@ return WriteReply(STDOUT_FILENO, Span<const uint8_t>(digest)); } +template <int (*SetKey)(const uint8_t *key, unsigned bits, AES_KEY *out), + void (*Block)(const uint8_t *in, uint8_t *out, const AES_KEY *key)> +static bool AES(const Span<const uint8_t> args[]) { + AES_KEY key; + if (SetKey(args[0].data(), args[0].size() * 8, &key) != 0) { + return false; + } + if (args[1].size() % AES_BLOCK_SIZE != 0) { + return false; + } + + std::vector<uint8_t> out; + out.resize(args[1].size()); + for (size_t i = 0; i < args[1].size(); i += AES_BLOCK_SIZE) { + Block(args[1].data() + i, &out[i], &key); + } + return WriteReply(STDOUT_FILENO, Span<const uint8_t>(out)); +} + +template <int (*SetKey)(const uint8_t *key, unsigned bits, AES_KEY *out), + int Direction> +static bool AES_CBC(const Span<const uint8_t> args[]) { + AES_KEY key; + if (SetKey(args[0].data(), args[0].size() * 8, &key) != 0) { + return false; + } + if (args[1].size() % AES_BLOCK_SIZE != 0 || + args[2].size() != AES_BLOCK_SIZE) { + return false; + } + uint8_t iv[AES_BLOCK_SIZE]; + memcpy(iv, args[2].data(), AES_BLOCK_SIZE); + + std::vector<uint8_t> out; + out.resize(args[1].size()); + AES_cbc_encrypt(args[1].data(), out.data(), args[1].size(), &key, iv, + Direction); + return WriteReply(STDOUT_FILENO, Span<const uint8_t>(out)); +} + static constexpr struct { const char name[kMaxNameLength + 1]; uint8_t expected_args; @@ -172,6 +225,10 @@ {"SHA2-256", 1, Hash<SHA256, SHA256_DIGEST_LENGTH>}, {"SHA2-384", 1, Hash<SHA384, SHA256_DIGEST_LENGTH>}, {"SHA2-512", 1, Hash<SHA512, SHA512_DIGEST_LENGTH>}, + {"AES/encrypt", 2, AES<AES_set_encrypt_key, AES_encrypt>}, + {"AES/decrypt", 2, AES<AES_set_decrypt_key, AES_decrypt>}, + {"AES-CBC/encrypt", 3, AES_CBC<AES_set_encrypt_key, AES_ENCRYPT>}, + {"AES-CBC/decrypt", 3, AES_CBC<AES_set_decrypt_key, AES_DECRYPT>}, }; int main() {