package main import ( "crypto" "crypto/aes" "crypto/cipher" "crypto/des" "crypto/hmac" _ "crypto/md5" "crypto/rc4" _ "crypto/sha1" _ "crypto/sha256" _ "crypto/sha512" "encoding/hex" "flag" "fmt" "os" ) var bulkCipher *string = flag.String("cipher", "", "The bulk cipher to use") var mac *string = flag.String("mac", "", "The hash function to use in the MAC") var implicitIV *bool = flag.Bool("implicit-iv", false, "If true, generate tests for a cipher using a pre-TLS-1.0 implicit IV") var testStitch *bool = flag.Bool("stitch", false, "If true, generate tests for |EVP_aes_128/256_cbc_hmac_sha1/256| cipher using stitch code.") var AES_BLOCK_SIZE = 16 // rc4Stream produces a deterministic stream of pseudorandom bytes. This is to // make this script idempotent. type rc4Stream struct { cipher *rc4.Cipher } func newRc4Stream(seed string) (*rc4Stream, error) { cipher, err := rc4.NewCipher([]byte(seed)) if err != nil { return nil, err } return &rc4Stream{cipher}, nil } func (rs *rc4Stream) fillBytes(p []byte) { for i := range p { p[i] = 0 } rs.cipher.XORKeyStream(p, p) } func getHash(name string) (crypto.Hash, bool) { switch name { case "md5": return crypto.MD5, true case "sha1": return crypto.SHA1, true case "sha256": return crypto.SHA256, true case "sha384": return crypto.SHA384, true default: return 0, false } } func getKeySize(name string) int { switch name { case "aes128": return 16 case "aes256": return 32 case "3des": return 24 default: return 0 } } func newBlockCipher(name string, key []byte) (cipher.Block, error) { switch name { case "aes128": return aes.NewCipher(key) case "aes256": return aes.NewCipher(key) case "3des": return des.NewTripleDESCipher(key) default: return nil, fmt.Errorf("unknown cipher '%s'", name) } } type testCase struct { digest []byte key []byte nonce []byte input []byte ad []byte ciphertext []byte tag []byte tag_len int noSeal bool fails bool } // options adds additional options for a test. type options struct { // extraPadding causes an extra block of padding to be added. extraPadding bool // maximalPadding causes the maximum allowed amount of padding to be added. maximalPadding bool // wrongPadding causes one of the padding bytes to be wrong. wrongPadding bool // wrongPaddingOffset specifies the byte offset of the incorrect padding // byte. wrongPaddingOffset int // noPadding causes padding is to be omitted. The plaintext + MAC must // be a multiple of the block size. noPadding bool // omitMAC causes the MAC to be omitted. omitMAC bool } func makeTestCase(length int, options options) (*testCase, error) { rand, err := newRc4Stream("input stream") if err != nil { return nil, err } // For stitch test, input should include explicit iv length if applicable(TLS 1.1+). // https://github.com/openssl/openssl/blame/ad24941228eafe59fe3807d1659585c4d98eac97/crypto/evp/e_aes_cbc_hmac_sha1.c#L428-L434 eiv_len := 0 if *testStitch && !(*implicitIV) { eiv_len = AES_BLOCK_SIZE length += eiv_len } input := make([]byte, length) rand.fillBytes(input) adFull := make([]byte, 13) ad := adFull[:len(adFull)-2] rand.fillBytes(ad) adFull[len(adFull)-2] = uint8(length >> 8) adFull[len(adFull)-1] = uint8(length & 0xff) // For stitch test, // |adFull| = seq_num + content_type + protocol_version + eiv_payload_len // seq_num: 8 octets long. // content_type: 1 octets long. // protocol_version: 2 octets long. // eiv_payload_len: 2 octets long. |eiv| is explicit iv required by TLS 1.1+ if *testStitch { payload_len := length // For stitch test, |adFull| will be used in |EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_TLS1_AAD, EVP_AEAD_TLS1_AAD_LEN, adFull)|. if *implicitIV { // 0x0301 means TLS 1.0 adFull[len(adFull)-4] = 0x03 adFull[len(adFull)-3] = 0x01 } else { // 0x0302 means TLS 1.1, which requires explicit IV. adFull[len(adFull)-4] = 0x03 adFull[len(adFull)-3] = 0x02 payload_len = length - eiv_len } ad = make([]byte, 13) copy(ad, adFull) // |ad| is used in digest calculation. The |eiv_payload_len| should exclude iv length if applicable(TLS 1.1+). // https://github.com/openssl/openssl/blame/ad24941228eafe59fe3807d1659585c4d98eac97/crypto/evp/e_aes_cbc_hmac_sha1.c#L814-L821 ad[len(ad)-2] = uint8(payload_len >> 8) ad[len(ad)-1] = uint8(payload_len & 0xff) } hash, ok := getHash(*mac) if !ok { return nil, fmt.Errorf("unknown hash function '%s'", *mac) } macKey := make([]byte, hash.Size()) rand.fillBytes(macKey) h := hmac.New(hash.New, macKey) if *testStitch { // For stitch, the digest only cover the payload(without explicit iv) and |ad| instead of |adFull|. h.Write(ad) h.Write(input[eiv_len:]) } else { h.Write(adFull) h.Write(input) } digest := h.Sum(nil) size := getKeySize(*bulkCipher) if size == 0 { return nil, fmt.Errorf("unknown cipher '%s'", *bulkCipher) } encKey := make([]byte, size) rand.fillBytes(encKey) var fixedIV []byte var nonce []byte var sealed []byte var noSeal, fails bool block, err := newBlockCipher(*bulkCipher, encKey) if err != nil { return nil, err } iv := make([]byte, block.BlockSize()) rand.fillBytes(iv) if *implicitIV { fixedIV = iv } else { if *testStitch { copy(input, iv) } nonce = iv } cbc := cipher.NewCBCEncrypter(block, iv) sealed = make([]byte, 0, len(input)+len(digest)+cbc.BlockSize()) sealed = append(sealed, input...) if options.omitMAC { noSeal = true fails = true } else { sealed = append(sealed, digest...) } paddingLen := cbc.BlockSize() - len(sealed)%cbc.BlockSize() if options.noPadding { if paddingLen != cbc.BlockSize() { return nil, fmt.Errorf("invalid length for noPadding") } noSeal = true fails = true } else { if options.extraPadding || options.maximalPadding { if options.extraPadding { paddingLen += cbc.BlockSize() } else { if 256%cbc.BlockSize() != 0 { panic("256 is not a whole number of blocks") } paddingLen = 256 - len(sealed)%cbc.BlockSize() } noSeal = true } pad := make([]byte, paddingLen) for i := range pad { pad[i] = byte(paddingLen - 1) } sealed = append(sealed, pad...) if options.wrongPadding { if options.wrongPaddingOffset >= paddingLen { return nil, fmt.Errorf("invalid wrongPaddingOffset") } sealed[len(sealed)-paddingLen+options.wrongPaddingOffset]++ noSeal = true // TLS specifies the all the padding bytes. fails = true } } cbc.CryptBlocks(sealed, sealed) // For stitch, use |adFull| as output. if *testStitch { ad = adFull } key := make([]byte, 0, len(macKey)+len(encKey)+len(fixedIV)) key = append(key, macKey...) key = append(key, encKey...) key = append(key, fixedIV...) t := &testCase{ digest: digest, key: key, nonce: nonce, input: input, ad: ad, ciphertext: sealed[:len(input)], tag: sealed[len(input):], tag_len: hash.Size(), noSeal: noSeal, fails: fails, } return t, nil } func printTestCase(t *testCase) { fmt.Printf("# DIGEST: %s\n", hex.EncodeToString(t.digest)) fmt.Printf("KEY: %s\n", hex.EncodeToString(t.key)) fmt.Printf("NONCE: %s\n", hex.EncodeToString(t.nonce)) fmt.Printf("IN: %s\n", hex.EncodeToString(t.input)) fmt.Printf("AD: %s\n", hex.EncodeToString(t.ad)) fmt.Printf("CT: %s\n", hex.EncodeToString(t.ciphertext)) fmt.Printf("TAG: %s\n", hex.EncodeToString(t.tag)) fmt.Printf("TAG_LEN: %d\n", t.tag_len) if t.noSeal { fmt.Printf("NO_SEAL: 01\n") } if t.fails { fmt.Printf("FAILS: 01\n") } } func addTestCase(length int, options options) { t, err := makeTestCase(length, options) if err != nil { fmt.Fprintf(os.Stderr, "%s\n", err) os.Exit(1) } printTestCase(t) fmt.Printf("\n") } func main() { flag.Parse() commandLine := fmt.Sprintf("go run make_legacy_aead_tests.go -cipher %s -mac %s", *bulkCipher, *mac) if *implicitIV { commandLine += " -implicit-iv" } if *testStitch { if !((*bulkCipher == "aes128") || (*bulkCipher == "aes256")) { panic("'-stitch' is used to test |EVP_aes_128/256_cbc_hmac_sha1/256|. The '-cipher' should be 'aes128/256'.") } if !((*mac == "sha1") || (*mac == "sha256")) { panic("'-stitch' is used to test |EVP_aes_128/256_cbc_hmac_sha1/256|. The '-mac' should be 'sha1/256'.") } } fmt.Printf("# Generated by\n") fmt.Printf("# %s\n", commandLine) fmt.Printf("#\n") fmt.Printf("# Note: aead_test's input format splits the ciphertext and tag positions of the\n") fmt.Printf("# sealed input. But these legacy AEADs are MAC-then-encrypt and so the 'TAG' may\n") fmt.Printf("# also include padding. We write the byte length of the MAC to 'TAG_LEN' and\n") fmt.Printf("# include the unencrypted MAC in the 'DIGEST' tag above # each test case.\n") fmt.Printf("# each test case.\n") fmt.Printf("\n") // For CBC-mode ciphers, emit tests for padding flexibility. fmt.Printf("# Test with non-minimal padding.\n") addTestCase(5, options{extraPadding: true}) fmt.Printf("# Test with bad padding values.\n") addTestCase(5, options{wrongPadding: true}) hash, ok := getHash(*mac) if !ok { panic("unknown hash") } fmt.Printf("# Test with no padding.\n") addTestCase(64-hash.Size(), options{noPadding: true}) // Test with maximal padding at all rotations modulo the hash's block // size. Our smallest hash (SHA-1 at 64-byte blocks) exceeds our largest // block cipher (AES at 16-byte blocks), so this is also covers all // block cipher rotations. This is to ensure full coverage of the // kVarianceBlocks value in the constant-time logic. hashBlockSize := hash.New().BlockSize() for i := 0; i < hashBlockSize; i++ { fmt.Printf("# Test with maximal padding (%d mod %d).\n", i, hashBlockSize) addTestCase(hashBlockSize+i, options{maximalPadding: true}) } fmt.Printf("# Test if the unpadded input is too short for a MAC, but not publicly so.\n") addTestCase(0, options{omitMAC: true, maximalPadding: true}) fmt.Printf("# Test that each byte of incorrect padding is noticed.\n") for i := 0; i < 256; i++ { addTestCase(64-hash.Size(), options{ maximalPadding: true, wrongPadding: true, wrongPaddingOffset: i, }) } // Generate long enough of input to cover a non-zero num_starting_blocks // value in the constant-time CBC logic. for l := 0; l < 500; l += 5 { addTestCase(l, options{}) } }