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