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