From f3e5941515dde1d5983f395c751d3b2eedf7b33a Mon Sep 17 00:00:00 2001 From: Adam Langley Date: Thu, 16 Sep 2021 09:28:20 -0700 Subject: [PATCH] acvptool: add CS3 support. CS3 is ciphertext-stealing variant three from SP 800-38A. Change-Id: I992dc22778c91efad361f25ff65ae5966fc447c6 Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/49505 Commit-Queue: Adam Langley Reviewed-by: David Benjamin --- util/fipstools/acvp/ACVP.md | 4 + .../acvp/acvptool/subprocess/subprocess.go | 63 ++++---- .../test/expected/ACVP-AES-CBC-CS3.bz2 | Bin 0 -> 2448 bytes util/fipstools/acvp/acvptool/test/tests.json | 1 + .../test/vectors/ACVP-AES-CBC-CS3.bz2 | Bin 0 -> 3641 bytes .../acvptool/testmodulewrapper/cts_test.go | 95 ++++++++++++ .../testmodulewrapper/testmodulewrapper.go | 135 ++++++++++++++++++ 7 files changed, 267 insertions(+), 31 deletions(-) create mode 100644 util/fipstools/acvp/acvptool/test/expected/ACVP-AES-CBC-CS3.bz2 create mode 100644 util/fipstools/acvp/acvptool/test/vectors/ACVP-AES-CBC-CS3.bz2 create mode 100644 util/fipstools/acvp/acvptool/testmodulewrapper/cts_test.go diff --git a/util/fipstools/acvp/ACVP.md b/util/fipstools/acvp/ACVP.md index d1255ca4e..ba5bdbb26 100644 --- a/util/fipstools/acvp/ACVP.md +++ b/util/fipstools/acvp/ACVP.md @@ -49,6 +49,8 @@ The other commands are as follows. (Note that you only need to implement the com | 3DES/encrypt | Key, input block, num iterations¹ | Result, Previous result | | AES-CBC/decrypt | Key, ciphertext, IV, num iterations¹ | Result, Previous result | | AES-CBC/encrypt | Key, plaintext, IV, num iterations¹ | Result, Previous result | +| AES-CBC-CS3/decrypt | Key, ciphertext, IV, num iterations² | Result | +| AES-CBC-CS3/encrypt | Key, plaintext, IV, num iterations² | Result | | AES-CCM/open | Tag length, key, ciphertext, nonce, ad | One-byte success flag, plaintext or empty | | AES-CCM/seal | Tag length, key, plaintext, nonce, ad | Ciphertext | | AES-CTR/decrypt | Key, ciphertext, initial counter, constant 1 | Plaintext | @@ -106,6 +108,8 @@ The other commands are as follows. (Note that you only need to implement the com ¹ The iterated tests would result in excessive numbers of round trips if the module wrapper handled only basic operations. Thus some ACVP logic is pushed down for these tests so that the inner loop can be handled locally. Either read the NIST documentation ([block-ciphers](https://pages.nist.gov/ACVP/draft-celi-acvp-symmetric.html#name-monte-carlo-tests-for-block) [hashes](https://pages.nist.gov/ACVP/draft-celi-acvp-sha.html#name-monte-carlo-tests-for-sha-1)) to understand the iteration count and return values or, probably more fruitfully, see how these functions are handled in the `modulewrapper` directory. +² Will always be one because MCT tests are not supported for CS3. + ## Online operation If you have credentials to speak to either of the NIST ACVP servers then you can run the tool in online mode. diff --git a/util/fipstools/acvp/acvptool/subprocess/subprocess.go b/util/fipstools/acvp/acvptool/subprocess/subprocess.go index fe74993db..844c9c45d 100644 --- a/util/fipstools/acvp/acvptool/subprocess/subprocess.go +++ b/util/fipstools/acvp/acvptool/subprocess/subprocess.go @@ -71,37 +71,38 @@ func NewWithIO(cmd *exec.Cmd, in io.WriteCloser, out io.ReadCloser) *Subprocess } m.primitives = map[string]primitive{ - "SHA-1": &hashPrimitive{"SHA-1", 20}, - "SHA2-224": &hashPrimitive{"SHA2-224", 28}, - "SHA2-256": &hashPrimitive{"SHA2-256", 32}, - "SHA2-384": &hashPrimitive{"SHA2-384", 48}, - "SHA2-512": &hashPrimitive{"SHA2-512", 64}, - "SHA2-512/256": &hashPrimitive{"SHA2-512/256", 32}, - "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-AES-XTS": &xts{}, - "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-GMAC": &aead{"AES-GCM", false}, - "ACVP-AES-CCM": &aead{"AES-CCM", true}, - "ACVP-AES-KW": &aead{"AES-KW", false}, - "ACVP-AES-KWP": &aead{"AES-KWP", false}, - "HMAC-SHA-1": &hmacPrimitive{"HMAC-SHA-1", 20}, - "HMAC-SHA2-224": &hmacPrimitive{"HMAC-SHA2-224", 28}, - "HMAC-SHA2-256": &hmacPrimitive{"HMAC-SHA2-256", 32}, - "HMAC-SHA2-384": &hmacPrimitive{"HMAC-SHA2-384", 48}, - "HMAC-SHA2-512": &hmacPrimitive{"HMAC-SHA2-512", 64}, - "ctrDRBG": &drbg{"ctrDRBG", map[string]bool{"AES-128": true, "AES-192": true, "AES-256": true}}, - "hmacDRBG": &drbg{"hmacDRBG", map[string]bool{"SHA-1": true, "SHA2-224": true, "SHA2-256": true, "SHA2-384": true, "SHA2-512": true}}, - "KDF": &kdfPrimitive{}, - "KAS-KDF": &hkdf{}, - "CMAC-AES": &keyedMACPrimitive{"CMAC-AES"}, - "RSA": &rsa{}, - "kdf-components": &tlsKDF{}, - "KAS-ECC-SSC": &kas{}, - "KAS-FFC-SSC": &kasDH{}, + "SHA-1": &hashPrimitive{"SHA-1", 20}, + "SHA2-224": &hashPrimitive{"SHA2-224", 28}, + "SHA2-256": &hashPrimitive{"SHA2-256", 32}, + "SHA2-384": &hashPrimitive{"SHA2-384", 48}, + "SHA2-512": &hashPrimitive{"SHA2-512", 64}, + "SHA2-512/256": &hashPrimitive{"SHA2-512/256", 32}, + "ACVP-AES-ECB": &blockCipher{"AES", 16, 2, true, false, iterateAES}, + "ACVP-AES-CBC": &blockCipher{"AES-CBC", 16, 2, true, true, iterateAESCBC}, + "ACVP-AES-CBC-CS3": &blockCipher{"AES-CBC-CS3", 16, 1, false, true, iterateAESCBC}, + "ACVP-AES-CTR": &blockCipher{"AES-CTR", 16, 1, false, true, nil}, + "ACVP-AES-XTS": &xts{}, + "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-GMAC": &aead{"AES-GCM", false}, + "ACVP-AES-CCM": &aead{"AES-CCM", true}, + "ACVP-AES-KW": &aead{"AES-KW", false}, + "ACVP-AES-KWP": &aead{"AES-KWP", false}, + "HMAC-SHA-1": &hmacPrimitive{"HMAC-SHA-1", 20}, + "HMAC-SHA2-224": &hmacPrimitive{"HMAC-SHA2-224", 28}, + "HMAC-SHA2-256": &hmacPrimitive{"HMAC-SHA2-256", 32}, + "HMAC-SHA2-384": &hmacPrimitive{"HMAC-SHA2-384", 48}, + "HMAC-SHA2-512": &hmacPrimitive{"HMAC-SHA2-512", 64}, + "ctrDRBG": &drbg{"ctrDRBG", map[string]bool{"AES-128": true, "AES-192": true, "AES-256": true}}, + "hmacDRBG": &drbg{"hmacDRBG", map[string]bool{"SHA-1": true, "SHA2-224": true, "SHA2-256": true, "SHA2-384": true, "SHA2-512": true}}, + "KDF": &kdfPrimitive{}, + "KAS-KDF": &hkdf{}, + "CMAC-AES": &keyedMACPrimitive{"CMAC-AES"}, + "RSA": &rsa{}, + "kdf-components": &tlsKDF{}, + "KAS-ECC-SSC": &kas{}, + "KAS-FFC-SSC": &kasDH{}, } m.primitives["ECDSA"] = &ecdsa{"ECDSA", map[string]bool{"P-224": true, "P-256": true, "P-384": true, "P-521": true}, m.primitives} diff --git a/util/fipstools/acvp/acvptool/test/expected/ACVP-AES-CBC-CS3.bz2 b/util/fipstools/acvp/acvptool/test/expected/ACVP-AES-CBC-CS3.bz2 new file mode 100644 index 0000000000000000000000000000000000000000..9a6c4c041fcd28cb55b336c5e7e1ca00c5e21401 GIT binary patch literal 2448 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 LP81{{NF>F;Wj3Us literal 0 HcmV?d00001 diff --git a/util/fipstools/acvp/acvptool/testmodulewrapper/cts_test.go b/util/fipstools/acvp/acvptool/testmodulewrapper/cts_test.go new file mode 100644 index 000000000..5e7a59781 --- /dev/null +++ b/util/fipstools/acvp/acvptool/testmodulewrapper/cts_test.go @@ -0,0 +1,95 @@ +// Copyright (c) 2021, 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 main + +import ( + "bytes" + "crypto/aes" + "crypto/rand" + "encoding/hex" + "testing" +) + +func TestCTSRoundTrip(t *testing.T) { + var buf [aes.BlockSize * 8]byte + var key, iv [16]byte + rand.Reader.Read(buf[:]) + rand.Reader.Read(key[:]) + rand.Reader.Read(iv[:]) + + for i := aes.BlockSize; i < len(buf); i++ { + in := buf[:i] + ciphertext := doCTSEncrypt(key[:], in[:], iv[:]) + if len(ciphertext) != len(in) { + t.Errorf("incorrect ciphertext length for input length %d", len(in)) + continue + } + out := doCTSDecrypt(key[:], ciphertext, iv[:]) + + if !bytes.Equal(in[:], out) { + t.Errorf("did not round trip for length %d", len(in)) + } + } +} + +func TestCTSVectors(t *testing.T) { + tests := []struct { + plaintextHex string + ciphertextHex string + ivHex string + }{ + // Test vectors from OpenSSL. + { + "4920776f756c64206c696b652074686520", + "c6353568f2bf8cb4d8a580362da7ff7f97", + "00000000000000000000000000000000", + }, + { + "4920776f756c64206c696b65207468652047656e6572616c20476175277320", + "fc00783e0efdb2c1d445d4c8eff7ed2297687268d6ecccc0c07b25e25ecfe5", + "00000000000000000000000000000000", + }, + { + "4920776f756c64206c696b65207468652047656e6572616c2047617527732043", + "39312523a78662d5be7fcbcc98ebf5a897687268d6ecccc0c07b25e25ecfe584", + "00000000000000000000000000000000", + }, + { + "4920776f756c64206c696b65207468652047656e6572616c20476175277320436869636b656e2c20706c656173652c", + "97687268d6ecccc0c07b25e25ecfe584b3fffd940c16a18c1b5549d2f838029e39312523a78662d5be7fcbcc98ebf5", + "00000000000000000000000000000000", + }, + { + "4920776f756c64206c696b65207468652047656e6572616c20476175277320436869636b656e2c20706c656173652c", + "5432a630742dee7beb70f9f1400ee6a0426da5c54a9990f5ae0b7825f51f0060b557cfb581949a4bdf3bb67dedd472", + "000102030405060708090a0b0c0d0e0f", + }, + } + + key := fromHex("636869636b656e207465726979616b69") + + for i, test := range tests { + plaintext := fromHex(test.plaintextHex) + iv := fromHex(test.ivHex) + ciphertext := doCTSEncrypt(key, plaintext, iv) + if got := hex.EncodeToString(ciphertext); got != test.ciphertextHex { + t.Errorf("#%d: unexpected ciphertext %s, want %s", i, got, test.ciphertextHex) + } + plaintextAgain := doCTSDecrypt(key, ciphertext, iv) + if !bytes.Equal(plaintext, plaintextAgain) { + t.Errorf("#%d: did not round trip", i) + } + } +} diff --git a/util/fipstools/acvp/acvptool/testmodulewrapper/testmodulewrapper.go b/util/fipstools/acvp/acvptool/testmodulewrapper/testmodulewrapper.go index 08a0fd864..afb18046a 100644 --- a/util/fipstools/acvp/acvptool/testmodulewrapper/testmodulewrapper.go +++ b/util/fipstools/acvp/acvptool/testmodulewrapper/testmodulewrapper.go @@ -21,6 +21,7 @@ package main import ( "bytes" "crypto/aes" + "crypto/cipher" "crypto/hmac" "crypto/rand" "crypto/sha256" @@ -42,6 +43,8 @@ var handlers = map[string]func([][]byte) error{ "HKDF/SHA2-256": hkdfMAC, "hmacDRBG-reseed/SHA2-256": hmacDRBGReseed, "hmacDRBG-pr/SHA2-256": hmacDRBGPredictionResistance, + "AES-CBC-CS3/encrypt": ctsEncrypt, + "AES-CBC-CS3/decrypt": ctsDecrypt, } func getConfig(args [][]byte) error { @@ -142,6 +145,22 @@ func getConfig(args [][]byte) error { ], "returnedBitsLen": 256 }] + }, { + "algorithm": "ACVP-AES-CBC-CS3", + "revision": "1.0", + "payloadLen": [{ + "min": 128, + "max": 2048, + "increment": 8 + }], + "direction": [ + "encrypt", + "decrypt" + ], + "keyLen": [ + 128, + 256 + ] } ]`)) } @@ -327,6 +346,122 @@ func hmacDRBGPredictionResistance(args [][]byte) error { return reply(out) } +func swapFinalTwoAESBlocks(d []byte) { + var blockNMinus1 [aes.BlockSize]byte + copy(blockNMinus1[:], d[len(d)-2*aes.BlockSize:]) + copy(d[len(d)-2*aes.BlockSize:], d[len(d)-aes.BlockSize:]) + copy(d[len(d)-aes.BlockSize:], blockNMinus1[:]) +} + +func roundUp(n, m int) int { + return n + (m-(n%m))%m +} + +func doCTSEncrypt(key, origPlaintext, iv []byte) []byte { + // https://nvlpubs.nist.gov/nistpubs/legacy/sp/nistspecialpublication800-38a-add.pdf + if len(origPlaintext) < aes.BlockSize { + panic("input too small") + } + + plaintext := make([]byte, roundUp(len(origPlaintext), aes.BlockSize)) + copy(plaintext, origPlaintext) + + block, err := aes.NewCipher(key) + if err != nil { + panic(err) + } + cbcEncryptor := cipher.NewCBCEncrypter(block, iv) + cbcEncryptor.CryptBlocks(plaintext, plaintext) + ciphertext := plaintext + + if len(origPlaintext) > aes.BlockSize { + swapFinalTwoAESBlocks(ciphertext) + + if len(origPlaintext)%16 != 0 { + // Truncate the ciphertext + ciphertext = ciphertext[:len(ciphertext)-aes.BlockSize+(len(origPlaintext)%aes.BlockSize)] + } + } + + if len(ciphertext) != len(origPlaintext) { + panic("internal error") + } + + return ciphertext +} + +func doCTSDecrypt(key, origCiphertext, iv []byte) []byte { + if len(origCiphertext) < aes.BlockSize { + panic("input too small") + } + + ciphertext := make([]byte, roundUp(len(origCiphertext), aes.BlockSize)) + copy(ciphertext, origCiphertext) + + if len(ciphertext) > aes.BlockSize { + swapFinalTwoAESBlocks(ciphertext) + } + + block, err := aes.NewCipher(key) + if err != nil { + panic(err) + } + cbcDecrypter := cipher.NewCBCDecrypter(block, iv) + + var plaintext []byte + if overhang := len(origCiphertext) % aes.BlockSize; overhang == 0 { + cbcDecrypter.CryptBlocks(ciphertext, ciphertext) + plaintext = ciphertext + } else { + ciphertext, finalBlock := ciphertext[:len(ciphertext)-aes.BlockSize], ciphertext[len(ciphertext)-aes.BlockSize:] + var plaintextFinalBlock [aes.BlockSize]byte + block.Decrypt(plaintextFinalBlock[:], finalBlock) + copy(ciphertext[len(ciphertext)-aes.BlockSize+overhang:], plaintextFinalBlock[overhang:]) + plaintext = make([]byte, len(origCiphertext)) + cbcDecrypter.CryptBlocks(plaintext, ciphertext) + for i := 0; i < overhang; i++ { + plaintextFinalBlock[i] ^= ciphertext[len(ciphertext)-aes.BlockSize+i] + } + copy(plaintext[len(ciphertext):], plaintextFinalBlock[:overhang]) + } + + return plaintext +} + +func ctsEncrypt(args [][]byte) error { + if len(args) != 4 { + return fmt.Errorf("ctsEncrypt received %d args, wanted 4", len(args)) + } + + key, plaintext, iv, numIterations32 := args[0], args[1], args[2], args[3] + if len(numIterations32) != 4 || binary.LittleEndian.Uint32(numIterations32) != 1 { + return errors.New("only a single iteration supported for ctsEncrypt") + } + + if len(plaintext) < aes.BlockSize { + return fmt.Errorf("ctsEncrypt plaintext too short: %d bytes", len(plaintext)) + } + + return reply(doCTSEncrypt(key, plaintext, iv)) +} + +func ctsDecrypt(args [][]byte) error { + if len(args) != 4 { + return fmt.Errorf("ctsDecrypt received %d args, wanted 4", len(args)) + } + + key, ciphertext, iv, numIterations32 := args[0], args[1], args[2], args[3] + if len(numIterations32) != 4 || binary.LittleEndian.Uint32(numIterations32) != 1 { + return errors.New("only a single iteration supported for ctsDecrypt") + } + + if len(ciphertext) < aes.BlockSize { + return errors.New("ctsDecrypt ciphertext too short") + } + + return reply(doCTSDecrypt(key, ciphertext, iv)) +} + const ( maxArgs = 9 maxArgLength = 1 << 20