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