From 1607f54fed72c6589d560254626909a64124f091 Mon Sep 17 00:00:00 2001 From: Adam Langley Date: Tue, 3 Nov 2020 15:34:57 -0800 Subject: [PATCH] acvp: move inner MCT loops into subprocess. The ACVP MCT tests involve a double loop where the inner loop iterates 1000 (AES) or 10000 (3DES) times. This change moves that inner loop into the subprocess. This significantly reduces the amount of IPC traffic at the cost of making the subprocesses more complex. The traffic volume is unimportant when talking over a local pipe, but it's significant when channels like serial links are used. Change-Id: Ia9d51335f06b743791f7885d366c8fd2f0f7eaf6 Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/43844 Commit-Queue: Adam Langley Commit-Queue: Adam Langley Reviewed-by: David Benjamin --- .../acvp/acvptool/subprocess/block.go | 110 ++++----- .../acvp/acvptool/subprocess/subprocess.go | 10 +- .../acvp/modulewrapper/modulewrapper.cc | 208 +++++++++++++++--- 3 files changed, 216 insertions(+), 112 deletions(-) diff --git a/util/fipstools/acvp/acvptool/subprocess/block.go b/util/fipstools/acvp/acvptool/subprocess/block.go index 3b468c869..376547305 100644 --- a/util/fipstools/acvp/acvptool/subprocess/block.go +++ b/util/fipstools/acvp/acvptool/subprocess/block.go @@ -60,24 +60,20 @@ func iterateAES(transact func(n int, args ...[]byte) ([][]byte, error), encrypt iteration.CiphertextHex = hex.EncodeToString(input) } - var result, prevResult []byte - for j := 0; j < 1000; j++ { - prevResult = input - result, err := transact(1, key, input) - if err != nil { - panic("block operation failed") - } - input = result[0] + results, err := transact(2, key, input, uint32le(1000)) + if err != nil { + panic(err) } - result = input + input = results[0] + prevResult := results[1] if encrypt { - iteration.CiphertextHex = hex.EncodeToString(result) + iteration.CiphertextHex = hex.EncodeToString(input) } else { - iteration.PlaintextHex = hex.EncodeToString(result) + iteration.PlaintextHex = hex.EncodeToString(input) } - aesKeyShuffle(key, result, prevResult) + aesKeyShuffle(key, input, prevResult) mctResults = append(mctResults, iteration) } @@ -96,34 +92,16 @@ func iterateAESCBC(transact func(n int, args ...[]byte) ([][]byte, error), encry iteration.CiphertextHex = hex.EncodeToString(input) } - var result, prevResult []byte 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 := transact(1, key, input, iv) - if err != nil { - panic("block operation failed") - } - result = results[0] - - prevInput = input - if j == 0 { - input = iv - } else { - input = prevResult - } + results, err := transact(2, key, input, iv, uint32le(1000)) + if err != nil { + panic("block operation failed") } + result := results[0] + prevResult := results[1] + if encrypt { iteration.CiphertextHex = hex.EncodeToString(result) } else { @@ -178,17 +156,13 @@ func iterate3DES(transact func(n int, args ...[]byte) ([][]byte, error), encrypt iteration.CiphertextHex = hex.EncodeToString(input) } - var result, prevResult, prevPrevResult []byte - for j := 0; j < 10000; j++ { - prevPrevResult = prevResult - prevResult = input - result, err := transact(1, key, input) - if err != nil { - panic("block operation failed") - } - input = result[0] + results, err := transact(3, key, input, uint32le(10000)) + if err != nil { + panic("block operation failed") } - result = input + result := results[0] + prevResult := results[1] + prevPrevResult := results[2] if encrypt { iteration.CiphertextHex = hex.EncodeToString(result) @@ -198,6 +172,7 @@ func iterate3DES(transact func(n int, args ...[]byte) ([][]byte, error), encrypt keyShuffle3DES(key, result, prevResult, prevPrevResult) mctResults = append(mctResults, iteration) + input = result } return mctResults @@ -220,29 +195,15 @@ func iterate3DESCBC(transact func(n int, args ...[]byte) ([][]byte, error), encr } iteration.IVHex = hex.EncodeToString(iv) - var result, prevResult, prevPrevResult []byte - for j := 0; j < 10000; j++ { - prevPrevResult = prevResult - prevResult = result - results, err := transact(1, key, input, iv) - if err != nil { - panic("block operation failed") - } - result = results[0] - - if encrypt { - if j == 0 { - input = iv - } else { - input = prevResult - } - iv = result - } else { - iv = input - input = result - } + results, err := transact(3, key, input, iv, uint32le(10000)) + if err != nil { + panic("block operation failed") } + result := results[0] + prevResult := results[1] + prevPrevResult := results[2] + if encrypt { iteration.CiphertextHex = hex.EncodeToString(result) } else { @@ -254,6 +215,9 @@ func iterate3DESCBC(transact func(n int, args ...[]byte) ([][]byte, error), encr if encrypt { input = prevResult iv = result + } else { + iv = prevResult + input = result } mctResults = append(mctResults, iteration) @@ -265,8 +229,12 @@ func iterate3DESCBC(transact func(n int, args ...[]byte) ([][]byte, error), encr // 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 + algo string + blockSize int + // numResults is the number of values returned by the wrapper. The one-shot + // tests always take the first value as the result, but the mctFunc may use + // them all. + numResults int inputsAreBlockMultiples bool hasIV bool mctFunc func(transact func(n int, args ...[]byte) ([][]byte, error), encrypt bool, key, input, iv []byte) (result []blockCipherMCTResult) @@ -423,9 +391,9 @@ func (b *blockCipher) Process(vectorSet []byte, m Transactable) (interface{}, er var err error if b.hasIV { - result, err = m.Transact(op, 1, key, input, iv) + result, err = m.Transact(op, b.numResults, key, input, iv, uint32le(1)) } else { - result, err = m.Transact(op, 1, key, input) + result, err = m.Transact(op, b.numResults, key, input, uint32le(1)) } if err != nil { panic("block operation failed: " + err.Error()) diff --git a/util/fipstools/acvp/acvptool/subprocess/subprocess.go b/util/fipstools/acvp/acvptool/subprocess/subprocess.go index 6f450d420..84b54b14c 100644 --- a/util/fipstools/acvp/acvptool/subprocess/subprocess.go +++ b/util/fipstools/acvp/acvptool/subprocess/subprocess.go @@ -76,11 +76,11 @@ func NewWithIO(cmd *exec.Cmd, in io.WriteCloser, out io.ReadCloser) *Subprocess "SHA2-256": &hashPrimitive{"SHA2-256", 32}, "SHA2-384": &hashPrimitive{"SHA2-384", 48}, "SHA2-512": &hashPrimitive{"SHA2-512", 64}, - "ACVP-AES-ECB": &blockCipher{"AES", 16, true, false, iterateAES}, - "ACVP-AES-CBC": &blockCipher{"AES-CBC", 16, true, true, iterateAESCBC}, - "ACVP-AES-CTR": &blockCipher{"AES-CTR", 16, false, true, nil}, - "ACVP-TDES-ECB": &blockCipher{"3DES-ECB", 8, true, false, iterate3DES}, - "ACVP-TDES-CBC": &blockCipher{"3DES-CBC", 8, true, true, iterate3DESCBC}, + "ACVP-AES-ECB": &blockCipher{"AES", 16, 2, true, false, iterateAES}, + "ACVP-AES-CBC": &blockCipher{"AES-CBC", 16, 2, true, true, iterateAESCBC}, + "ACVP-AES-CTR": &blockCipher{"AES-CTR", 16, 1, false, true, nil}, + "ACVP-TDES-ECB": &blockCipher{"3DES-ECB", 8, 3, true, false, iterate3DES}, + "ACVP-TDES-CBC": &blockCipher{"3DES-CBC", 8, 3, true, true, iterate3DESCBC}, "ACVP-AES-GCM": &aead{"AES-GCM", false}, "ACVP-AES-CCM": &aead{"AES-CCM", true}, "ACVP-AES-KW": &aead{"AES-KW", false}, diff --git a/util/fipstools/acvp/modulewrapper/modulewrapper.cc b/util/fipstools/acvp/modulewrapper/modulewrapper.cc index 804349709..b1e1dd77c 100644 --- a/util/fipstools/acvp/modulewrapper/modulewrapper.cc +++ b/util/fipstools/acvp/modulewrapper/modulewrapper.cc @@ -711,6 +711,27 @@ static bool Hash(const Span args[]) { return WriteReply(STDOUT_FILENO, Span(digest)); } +static uint32_t GetIterations(const Span iterations_bytes) { + uint32_t iterations; + if (iterations_bytes.size() != sizeof(iterations)) { + fprintf(stderr, + "Expected %u-byte input for number of iterations, but found %u " + "bytes.\n", + static_cast(sizeof(iterations)), + static_cast(iterations_bytes.size())); + abort(); + } + + memcpy(&iterations, iterations_bytes.data(), sizeof(iterations)); + if (iterations == 0 || iterations == UINT32_MAX) { + fprintf(stderr, "Invalid number of iterations: %x.\n", + static_cast(iterations)); + abort(); + } + + return iterations; +} + template static bool AES(const Span args[]) { @@ -721,13 +742,22 @@ static bool AES(const Span args[]) { if (args[1].size() % AES_BLOCK_SIZE != 0) { return false; } + std::vector result(args[1].begin(), args[1].end()); + const uint32_t iterations = GetIterations(args[2]); - std::vector 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); + std::vector prev_result; + for (uint32_t j = 0; j < iterations; j++) { + if (j == iterations - 1) { + prev_result = result; + } + + for (size_t i = 0; i < args[1].size(); i += AES_BLOCK_SIZE) { + Block(result.data() + i, result.data() + i, &key); + } } - return WriteReply(STDOUT_FILENO, Span(out)); + + return WriteReply(STDOUT_FILENO, Span(result), + Span(prev_result)); } template args[]) { if (SetKey(args[0].data(), args[0].size() * 8, &key) != 0) { return false; } - if (args[1].size() % AES_BLOCK_SIZE != 0 || + if (args[1].size() % AES_BLOCK_SIZE != 0 || args[1].empty() || args[2].size() != AES_BLOCK_SIZE) { return false; } - uint8_t iv[AES_BLOCK_SIZE]; - memcpy(iv, args[2].data(), AES_BLOCK_SIZE); + std::vector input(args[1].begin(), args[1].end()); + std::vector iv(args[2].begin(), args[2].end()); + const uint32_t iterations = GetIterations(args[3]); - std::vector 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(out)); + std::vector result(input.size()); + std::vector prev_result, prev_input; + + for (uint32_t j = 0; j < iterations; j++) { + prev_result = result; + if (j > 0) { + if (Direction == AES_ENCRYPT) { + iv = result; + } else { + iv = prev_input; + } + } + + // AES_cbc_encrypt will mutate the given IV, but we need it later. + uint8_t iv_copy[AES_BLOCK_SIZE]; + memcpy(iv_copy, iv.data(), sizeof(iv_copy)); + AES_cbc_encrypt(input.data(), result.data(), input.size(), &key, iv_copy, + Direction); + + if (Direction == AES_DECRYPT) { + prev_input = input; + } + + if (j == 0) { + input = iv; + } else { + input = prev_result; + } + } + + return WriteReply(STDOUT_FILENO, Span(result), + Span(prev_result)); } static bool AES_CTR(const Span args[]) { @@ -761,6 +819,10 @@ static bool AES_CTR(const Span args[]) { } uint8_t iv[AES_BLOCK_SIZE]; memcpy(iv, args[2].data(), AES_BLOCK_SIZE); + if (GetIterations(args[3]) != 1) { + fprintf(stderr, "Multiple iterations of AES-CTR is not supported.\n"); + return false; + } std::vector out; out.resize(args[1].size()); @@ -1018,42 +1080,116 @@ static bool AESPaddedKeyWrapOpen(const Span args[]) { Span(out)); } -template +template static bool TDES(const Span args[]) { - const EVP_CIPHER *cipher = cipher_func(); + const EVP_CIPHER *cipher = EVP_des_ede3(); if (args[0].size() != 24) { fprintf(stderr, "Bad key length %u for 3DES.\n", static_cast(args[0].size())); return false; } + bssl::ScopedEVP_CIPHER_CTX ctx; + if (!EVP_CipherInit_ex(ctx.get(), cipher, nullptr, args[0].data(), nullptr, + Encrypt ? 1 : 0) || + !EVP_CIPHER_CTX_set_padding(ctx.get(), 0)) { + return false; + } + if (args[1].size() % 8) { fprintf(stderr, "Bad input length %u for 3DES.\n", static_cast(args[1].size())); return false; } - if (HasIV && args[2].size() != EVP_CIPHER_iv_length(cipher)) { + std::vector result(args[1].begin(), args[1].end()); + + const uint32_t iterations = GetIterations(args[2]); + std::vector prev_result, prev_prev_result; + + for (uint32_t j = 0; j < iterations; j++) { + if (j == iterations - 1) { + prev_result = result; + } else if (iterations >= 2 && j == iterations - 2) { + prev_prev_result = result; + } + + int out_len; + if (!EVP_CipherUpdate(ctx.get(), result.data(), &out_len, result.data(), + result.size()) || + out_len != static_cast(result.size())) { + return false; + } + } + + return WriteReply(STDOUT_FILENO, Span(result), + Span(prev_result), + Span(prev_prev_result)); +} + +template +static bool TDES_CBC(const Span args[]) { + const EVP_CIPHER *cipher = EVP_des_ede3_cbc(); + + if (args[0].size() != 24) { + fprintf(stderr, "Bad key length %u for 3DES.\n", + static_cast(args[0].size())); + return false; + } + + if (args[1].size() % 8 || args[1].size() == 0) { + fprintf(stderr, "Bad input length %u for 3DES.\n", + static_cast(args[1].size())); + return false; + } + std::vector input(args[1].begin(), args[1].end()); + + if (args[2].size() != EVP_CIPHER_iv_length(cipher)) { fprintf(stderr, "Bad IV length %u for 3DES.\n", static_cast(args[2].size())); return false; } + std::vector iv(args[2].begin(), args[2].end()); + const uint32_t iterations = GetIterations(args[3]); - std::vector out; - out.resize(args[1].size()); - + std::vector result(input.size()); + std::vector prev_result, prev_prev_result; bssl::ScopedEVP_CIPHER_CTX ctx; - int out_len, out_len2; - if (!EVP_CipherInit_ex(ctx.get(), cipher, nullptr, args[0].data(), - HasIV ? args[2].data() : nullptr, Encrypt ? 1 : 0) || - !EVP_CIPHER_CTX_set_padding(ctx.get(), 0) || - !EVP_CipherUpdate(ctx.get(), out.data(), &out_len, args[1].data(), - args[1].size()) || - !EVP_CipherFinal_ex(ctx.get(), out.data() + out_len, &out_len2) || - (out_len + out_len2) != static_cast(out.size())) { + if (!EVP_CipherInit_ex(ctx.get(), cipher, nullptr, args[0].data(), iv.data(), + Encrypt ? 1 : 0) || + !EVP_CIPHER_CTX_set_padding(ctx.get(), 0)) { return false; } - return WriteReply(STDOUT_FILENO, Span(out)); + for (uint32_t j = 0; j < iterations; j++) { + prev_prev_result = prev_result; + prev_result = result; + + int out_len, out_len2; + if (!EVP_CipherInit_ex(ctx.get(), nullptr, nullptr, nullptr, iv.data(), + -1) || + !EVP_CipherUpdate(ctx.get(), result.data(), &out_len, input.data(), + input.size()) || + !EVP_CipherFinal_ex(ctx.get(), result.data() + out_len, &out_len2) || + (out_len + out_len2) != static_cast(result.size())) { + return false; + } + + if (Encrypt) { + if (j == 0) { + input = iv; + } else { + input = prev_result; + } + iv = result; + } else { + iv = input; + input = result; + } + } + + return WriteReply(STDOUT_FILENO, Span(result), + Span(prev_result), + Span(prev_prev_result)); } template @@ -1421,10 +1557,10 @@ static constexpr struct { {"SHA2-256", 1, Hash}, {"SHA2-384", 1, Hash}, {"SHA2-512", 1, Hash}, - {"AES/encrypt", 2, AES}, - {"AES/decrypt", 2, AES}, - {"AES-CBC/encrypt", 3, AES_CBC}, - {"AES-CBC/decrypt", 3, AES_CBC}, + {"AES/encrypt", 3, AES}, + {"AES/decrypt", 3, AES}, + {"AES-CBC/encrypt", 4, AES_CBC}, + {"AES-CBC/decrypt", 4, AES_CBC}, {"AES-CTR/encrypt", 3, AES_CTR}, {"AES-CTR/decrypt", 3, AES_CTR}, {"AES-GCM/seal", 5, AEADSeal}, @@ -1435,10 +1571,10 @@ static constexpr struct { {"AES-KWP/open", 5, AESPaddedKeyWrapOpen}, {"AES-CCM/seal", 5, AEADSeal}, {"AES-CCM/open", 5, AEADOpen}, - {"3DES-ECB/encrypt", 2, TDES}, - {"3DES-ECB/decrypt", 2, TDES}, - {"3DES-CBC/encrypt", 3, TDES}, - {"3DES-CBC/decrypt", 3, TDES}, + {"3DES-ECB/encrypt", 3, TDES}, + {"3DES-ECB/decrypt", 3, TDES}, + {"3DES-CBC/encrypt", 4, TDES_CBC}, + {"3DES-CBC/decrypt", 4, TDES_CBC}, {"HMAC-SHA-1", 2, HMAC}, {"HMAC-SHA2-224", 2, HMAC}, {"HMAC-SHA2-256", 2, HMAC},