acvp: add 3DES-ECB support Change-Id: I4ffa2572acce1fdccdf4d3c33680e6d0114bd42b Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/43405 Commit-Queue: Adam Langley <agl@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 index 365399e..35c2133 100644 --- a/util/fipstools/acvp/acvptool/subprocess/block.go +++ b/util/fipstools/acvp/acvptool/subprocess/block.go
@@ -18,6 +18,7 @@ "encoding/hex" "encoding/json" "fmt" + "math/bits" ) // aesKeyShuffle is the "AES Monte Carlo Key Shuffle" from the ACVP @@ -140,6 +141,68 @@ return mctResults } +// xorKeyWithOddParityLSB XORs value into key while setting the LSB of each bit +// to establish odd parity. This embedding of a parity check in a DES key is an +// old tradition and something that NIST's tests require (despite being +// undocumented). +func xorKeyWithOddParityLSB(key, value []byte) { + for i := range key { + v := key[i] ^ value[i] + // Use LSB to establish odd parity. + v ^= byte((bits.OnesCount8(v) & 1)) ^ 1 + key[i] = v + } +} + +// desKeyShuffle implements the manipulation of the Key arrays in the "TDES +// Monte Carlo Test - ECB mode" algorithm from the ACVP specification. +func keyShuffle3DES(key, result, prevResult, prevPrevResult []byte) { + xorKeyWithOddParityLSB(key[:8], result) + xorKeyWithOddParityLSB(key[8:16], prevResult) + xorKeyWithOddParityLSB(key[16:], prevPrevResult) +} + +// iterate3DES implements "TDES Monte Carlo Test - ECB mode" from the ACVP +// specification. +func iterate3DES(transact func(n int, args ...[]byte) ([][]byte, error), encrypt bool, key, input, iv []byte) (mctResults []blockCipherMCTResult) { + for i := 0; i < 400; i++ { + var iteration blockCipherMCTResult + keyHex := hex.EncodeToString(key) + iteration.Key1Hex = keyHex[:16] + iteration.Key2Hex = keyHex[16:32] + iteration.Key3Hex = keyHex[32:] + + if encrypt { + iteration.PlaintextHex = hex.EncodeToString(input) + } else { + 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] + } + result = input + + if encrypt { + iteration.CiphertextHex = hex.EncodeToString(result) + } else { + iteration.PlaintextHex = hex.EncodeToString(result) + } + + keyShuffle3DES(key, result, prevResult, prevPrevResult) + mctResults = append(mctResults, iteration) + } + + return mctResults +} + // blockCipher implements an ACVP algorithm by making requests to the subprocess // to encrypt and decrypt with a block cipher. type blockCipher struct { @@ -165,6 +228,11 @@ CiphertextHex string `json:"ct"` IVHex string `json:"iv"` KeyHex string `json:"key"` + + // 3DES tests serialise the key differently. + Key1Hex string `json:"key1"` + Key2Hex string `json:"key2"` + Key3Hex string `json:"key3"` } `json:"tests"` } @@ -181,10 +249,15 @@ } type blockCipherMCTResult struct { - KeyHex string `json:"key"` + KeyHex string `json:"key,omitempty"` PlaintextHex string `json:"pt"` CiphertextHex string `json:"ct"` IVHex string `json:"iv,omitempty"` + + // 3DES tests serialise the key differently. + Key1Hex string `json:"key1"` + Key2Hex string `json:"key2"` + Key3Hex string `json:"key3"` } func (b *blockCipher) Process(vectorSet []byte, m Transactable) (interface{}, error) { @@ -230,6 +303,11 @@ return nil, fmt.Errorf("test group %d has unknown type %q", group.ID, group.Type) } + if group.KeyBits == 0 { + // 3DES tests fail to set this parameter. + group.KeyBits = 192 + } + if group.KeyBits%8 != 0 { return nil, fmt.Errorf("test group %d contains non-byte-multiple key length %d", group.ID, group.KeyBits) } @@ -240,6 +318,11 @@ } for _, test := range group.Tests { + if len(test.KeyHex) == 0 && len(test.Key1Hex) > 0 { + // 3DES encodes the key differently. + test.KeyHex = test.Key1Hex + test.Key2Hex + test.Key3Hex + } + 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) }
diff --git a/util/fipstools/acvp/acvptool/subprocess/subprocess.go b/util/fipstools/acvp/acvptool/subprocess/subprocess.go index 05d7fb5..3c04981 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, 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-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 1ffb432..d97ea91 100644 --- a/util/fipstools/acvp/modulewrapper/modulewrapper.cc +++ b/util/fipstools/acvp/modulewrapper/modulewrapper.cc
@@ -26,6 +26,7 @@ #include <openssl/aead.h> #include <openssl/aes.h> #include <openssl/bn.h> +#include <openssl/cipher.h> #include <openssl/cmac.h> #include <openssl/digest.h> #include <openssl/ec.h> @@ -251,6 +252,13 @@ "aadLen": [{"min": 0, "max": 1024, "increment": 8}] }, { + "algorithm": "ACVP-TDES-ECB", + "revision": "1.0", + "direction": ["encrypt", "decrypt"], + "keyLen": [192], + "keyingOption": [1] + }, + { "algorithm": "HMAC-SHA-1", "revision": "1.0", "keyLen": [{ @@ -720,6 +728,39 @@ Span<const uint8_t>(out)); } +template<bool Encrypt> +static bool TDES(const Span<const uint8_t> args[]) { + const EVP_CIPHER *cipher = EVP_des_ede3(); + + if (args[0].size() != 24) { + fprintf(stderr, "Bad key length %u for 3DES.\n", + static_cast<unsigned>(args[0].size())); + return false; + } + if (args[1].size() % 8) { + fprintf(stderr, "Bad input length %u for 3DES.\n", + static_cast<unsigned>(args[1].size())); + return false; + } + + std::vector<uint8_t> out; + out.resize(args[1].size()); + + bssl::ScopedEVP_CIPHER_CTX ctx; + int out_len, out_len2; + if (!EVP_CipherInit_ex(ctx.get(), cipher, nullptr, args[0].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<int>(out.size())) { + return false; + } + + return WriteReply(STDOUT_FILENO, 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(); @@ -975,6 +1016,8 @@ {"AES-KWP/open", 5, AESPaddedKeyWrapOpen}, {"AES-CCM/seal", 5, AEADSeal<AESCCMSetup>}, {"AES-CCM/open", 5, AEADOpen<AESCCMSetup>}, + {"3DES-ECB/encrypt", 2, TDES<true>}, + {"3DES-ECB/decrypt", 2, TDES<false>}, {"HMAC-SHA-1", 2, HMAC<EVP_sha1>}, {"HMAC-SHA2-224", 2, HMAC<EVP_sha224>}, {"HMAC-SHA2-256", 2, HMAC<EVP_sha256>},