|  | 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") | 
|  |  | 
|  | // 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 | 
|  | } | 
|  |  | 
|  | 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) | 
|  |  | 
|  | 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) | 
|  | 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 { | 
|  | 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) | 
|  |  | 
|  | 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" | 
|  | } | 
|  | 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{}) | 
|  | } | 
|  | } |