blob: b5c6894801d1057c9251f61d412c76586eb93d2c [file] [log] [blame]
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{})
}
}