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() {