1package main 2 3import ( 4 "crypto" 5 "crypto/aes" 6 "crypto/cipher" 7 "crypto/des" 8 "crypto/hmac" 9 _ "crypto/md5" 10 "crypto/rc4" 11 _ "crypto/sha1" 12 _ "crypto/sha256" 13 _ "crypto/sha512" 14 "encoding/hex" 15 "flag" 16 "fmt" 17 "os" 18) 19 20var bulkCipher *string = flag.String("cipher", "", "The bulk cipher to use") 21var mac *string = flag.String("mac", "", "The hash function to use in the MAC") 22var implicitIV *bool = flag.Bool("implicit-iv", false, "If true, generate tests for a cipher using a pre-TLS-1.0 implicit IV") 23 24// rc4Stream produces a deterministic stream of pseudorandom bytes. This is to 25// make this script idempotent. 26type rc4Stream struct { 27 cipher *rc4.Cipher 28} 29 30func newRc4Stream(seed string) (*rc4Stream, error) { 31 cipher, err := rc4.NewCipher([]byte(seed)) 32 if err != nil { 33 return nil, err 34 } 35 return &rc4Stream{cipher}, nil 36} 37 38func (rs *rc4Stream) fillBytes(p []byte) { 39 for i := range p { 40 p[i] = 0 41 } 42 rs.cipher.XORKeyStream(p, p) 43} 44 45func getHash(name string) (crypto.Hash, bool) { 46 switch name { 47 case "md5": 48 return crypto.MD5, true 49 case "sha1": 50 return crypto.SHA1, true 51 case "sha256": 52 return crypto.SHA256, true 53 case "sha384": 54 return crypto.SHA384, true 55 default: 56 return 0, false 57 } 58} 59 60func getKeySize(name string) int { 61 switch name { 62 case "aes128": 63 return 16 64 case "aes256": 65 return 32 66 case "3des": 67 return 24 68 default: 69 return 0 70 } 71} 72 73func newBlockCipher(name string, key []byte) (cipher.Block, error) { 74 switch name { 75 case "aes128": 76 return aes.NewCipher(key) 77 case "aes256": 78 return aes.NewCipher(key) 79 case "3des": 80 return des.NewTripleDESCipher(key) 81 default: 82 return nil, fmt.Errorf("unknown cipher '%s'", name) 83 } 84} 85 86type testCase struct { 87 digest []byte 88 key []byte 89 nonce []byte 90 input []byte 91 ad []byte 92 ciphertext []byte 93 tag []byte 94 tag_len int 95 noSeal bool 96 fails bool 97} 98 99// options adds additional options for a test. 100type options struct { 101 // extraPadding causes an extra block of padding to be added. 102 extraPadding bool 103 // maximalPadding causes the maximum allowed amount of padding to be added. 104 maximalPadding bool 105 // wrongPadding causes one of the padding bytes to be wrong. 106 wrongPadding bool 107 // wrongPaddingOffset specifies the byte offset of the incorrect padding 108 // byte. 109 wrongPaddingOffset int 110 // noPadding causes padding is to be omitted. The plaintext + MAC must 111 // be a multiple of the block size. 112 noPadding bool 113 // omitMAC causes the MAC to be omitted. 114 omitMAC bool 115} 116 117func makeTestCase(length int, options options) (*testCase, error) { 118 rand, err := newRc4Stream("input stream") 119 if err != nil { 120 return nil, err 121 } 122 123 input := make([]byte, length) 124 rand.fillBytes(input) 125 126 adFull := make([]byte, 13) 127 ad := adFull[:len(adFull)-2] 128 rand.fillBytes(ad) 129 adFull[len(adFull)-2] = uint8(length >> 8) 130 adFull[len(adFull)-1] = uint8(length & 0xff) 131 132 hash, ok := getHash(*mac) 133 if !ok { 134 return nil, fmt.Errorf("unknown hash function '%s'", *mac) 135 } 136 137 macKey := make([]byte, hash.Size()) 138 rand.fillBytes(macKey) 139 140 h := hmac.New(hash.New, macKey) 141 h.Write(adFull) 142 h.Write(input) 143 digest := h.Sum(nil) 144 145 size := getKeySize(*bulkCipher) 146 if size == 0 { 147 return nil, fmt.Errorf("unknown cipher '%s'", *bulkCipher) 148 } 149 encKey := make([]byte, size) 150 rand.fillBytes(encKey) 151 152 var fixedIV []byte 153 var nonce []byte 154 var sealed []byte 155 var noSeal, fails bool 156 block, err := newBlockCipher(*bulkCipher, encKey) 157 if err != nil { 158 return nil, err 159 } 160 161 iv := make([]byte, block.BlockSize()) 162 rand.fillBytes(iv) 163 if *implicitIV { 164 fixedIV = iv 165 } else { 166 nonce = iv 167 } 168 169 cbc := cipher.NewCBCEncrypter(block, iv) 170 171 sealed = make([]byte, 0, len(input)+len(digest)+cbc.BlockSize()) 172 sealed = append(sealed, input...) 173 if options.omitMAC { 174 noSeal = true 175 fails = true 176 } else { 177 sealed = append(sealed, digest...) 178 } 179 paddingLen := cbc.BlockSize() - len(sealed)%cbc.BlockSize() 180 if options.noPadding { 181 if paddingLen != cbc.BlockSize() { 182 return nil, fmt.Errorf("invalid length for noPadding") 183 } 184 noSeal = true 185 fails = true 186 } else { 187 if options.extraPadding || options.maximalPadding { 188 if options.extraPadding { 189 paddingLen += cbc.BlockSize() 190 } else { 191 if 256%cbc.BlockSize() != 0 { 192 panic("256 is not a whole number of blocks") 193 } 194 paddingLen = 256 - len(sealed)%cbc.BlockSize() 195 } 196 noSeal = true 197 } 198 pad := make([]byte, paddingLen) 199 for i := range pad { 200 pad[i] = byte(paddingLen - 1) 201 } 202 sealed = append(sealed, pad...) 203 if options.wrongPadding { 204 if options.wrongPaddingOffset >= paddingLen { 205 return nil, fmt.Errorf("invalid wrongPaddingOffset") 206 } 207 sealed[len(sealed)-paddingLen+options.wrongPaddingOffset]++ 208 noSeal = true 209 // TLS specifies the all the padding bytes. 210 fails = true 211 } 212 } 213 cbc.CryptBlocks(sealed, sealed) 214 215 key := make([]byte, 0, len(macKey)+len(encKey)+len(fixedIV)) 216 key = append(key, macKey...) 217 key = append(key, encKey...) 218 key = append(key, fixedIV...) 219 t := &testCase{ 220 digest: digest, 221 key: key, 222 nonce: nonce, 223 input: input, 224 ad: ad, 225 ciphertext: sealed[:len(input)], 226 tag: sealed[len(input):], 227 tag_len: hash.Size(), 228 noSeal: noSeal, 229 fails: fails, 230 } 231 return t, nil 232} 233 234func printTestCase(t *testCase) { 235 fmt.Printf("# DIGEST: %s\n", hex.EncodeToString(t.digest)) 236 fmt.Printf("KEY: %s\n", hex.EncodeToString(t.key)) 237 fmt.Printf("NONCE: %s\n", hex.EncodeToString(t.nonce)) 238 fmt.Printf("IN: %s\n", hex.EncodeToString(t.input)) 239 fmt.Printf("AD: %s\n", hex.EncodeToString(t.ad)) 240 fmt.Printf("CT: %s\n", hex.EncodeToString(t.ciphertext)) 241 fmt.Printf("TAG: %s\n", hex.EncodeToString(t.tag)) 242 fmt.Printf("TAG_LEN: %d\n", t.tag_len) 243 if t.noSeal { 244 fmt.Printf("NO_SEAL: 01\n") 245 } 246 if t.fails { 247 fmt.Printf("FAILS: 01\n") 248 } 249} 250 251func addTestCase(length int, options options) { 252 t, err := makeTestCase(length, options) 253 if err != nil { 254 fmt.Fprintf(os.Stderr, "%s\n", err) 255 os.Exit(1) 256 } 257 printTestCase(t) 258 fmt.Printf("\n") 259} 260 261func main() { 262 flag.Parse() 263 264 commandLine := fmt.Sprintf("go run make_legacy_aead_tests.go -cipher %s -mac %s", *bulkCipher, *mac) 265 if *implicitIV { 266 commandLine += " -implicit-iv" 267 } 268 fmt.Printf("# Generated by\n") 269 fmt.Printf("# %s\n", commandLine) 270 fmt.Printf("#\n") 271 fmt.Printf("# Note: aead_test's input format splits the ciphertext and tag positions of the\n") 272 fmt.Printf("# sealed input. But these legacy AEADs are MAC-then-encrypt and so the 'TAG' may\n") 273 fmt.Printf("# also include padding. We write the byte length of the MAC to 'TAG_LEN' and\n") 274 fmt.Printf("# include the unencrypted MAC in the 'DIGEST' tag above # each test case.\n") 275 fmt.Printf("# each test case.\n") 276 fmt.Printf("\n") 277 278 // For CBC-mode ciphers, emit tests for padding flexibility. 279 fmt.Printf("# Test with non-minimal padding.\n") 280 addTestCase(5, options{extraPadding: true}) 281 282 fmt.Printf("# Test with bad padding values.\n") 283 addTestCase(5, options{wrongPadding: true}) 284 285 hash, ok := getHash(*mac) 286 if !ok { 287 panic("unknown hash") 288 } 289 290 fmt.Printf("# Test with no padding.\n") 291 addTestCase(64-hash.Size(), options{noPadding: true}) 292 293 // Test with maximal padding at all rotations modulo the hash's block 294 // size. Our smallest hash (SHA-1 at 64-byte blocks) exceeds our largest 295 // block cipher (AES at 16-byte blocks), so this is also covers all 296 // block cipher rotations. This is to ensure full coverage of the 297 // kVarianceBlocks value in the constant-time logic. 298 hashBlockSize := hash.New().BlockSize() 299 for i := 0; i < hashBlockSize; i++ { 300 fmt.Printf("# Test with maximal padding (%d mod %d).\n", i, hashBlockSize) 301 addTestCase(hashBlockSize+i, options{maximalPadding: true}) 302 } 303 304 fmt.Printf("# Test if the unpadded input is too short for a MAC, but not publicly so.\n") 305 addTestCase(0, options{omitMAC: true, maximalPadding: true}) 306 307 fmt.Printf("# Test that each byte of incorrect padding is noticed.\n") 308 for i := 0; i < 256; i++ { 309 addTestCase(64-hash.Size(), options{ 310 maximalPadding: true, 311 wrongPadding: true, 312 wrongPaddingOffset: i, 313 }) 314 } 315 316 // Generate long enough of input to cover a non-zero num_starting_blocks 317 // value in the constant-time CBC logic. 318 for l := 0; l < 500; l += 5 { 319 addTestCase(l, options{}) 320 } 321} 322