2e2a226ac9
Change-Id: Id65e0988534056a72d9b40cc9ba5194e2d9b8a7c Reviewed-on: https://boringssl-review.googlesource.com/15904 Reviewed-by: Adam Langley <agl@google.com> Commit-Queue: Adam Langley <agl@google.com> CQ-Verified: CQ bot account: commit-bot@chromium.org <commit-bot@chromium.org>
362 lines
9.1 KiB
Go
362 lines
9.1 KiB
Go
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")
|
|
var ssl3 *bool = flag.Bool("ssl3", false, "If true, use the SSLv3 MAC and padding rather than TLS")
|
|
|
|
// 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)
|
|
}
|
|
}
|
|
|
|
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}
|
|
|
|
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}
|
|
|
|
func ssl30MAC(hash crypto.Hash, key, input, ad []byte) []byte {
|
|
padLength := 48
|
|
if hash.Size() == 20 {
|
|
padLength = 40
|
|
}
|
|
|
|
h := hash.New()
|
|
h.Write(key)
|
|
h.Write(ssl30Pad1[:padLength])
|
|
h.Write(ad)
|
|
h.Write(input)
|
|
digestBuf := h.Sum(nil)
|
|
|
|
h.Reset()
|
|
h.Write(key)
|
|
h.Write(ssl30Pad2[:padLength])
|
|
h.Write(digestBuf)
|
|
return h.Sum(digestBuf[:0])
|
|
}
|
|
|
|
type testCase struct {
|
|
digest []byte
|
|
key []byte
|
|
nonce []byte
|
|
input []byte
|
|
ad []byte
|
|
ciphertext []byte
|
|
tag []byte
|
|
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 256 bytes 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)
|
|
|
|
var adFull []byte
|
|
if *ssl3 {
|
|
adFull = make([]byte, 11)
|
|
} else {
|
|
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)
|
|
|
|
var digest []byte
|
|
if *ssl3 {
|
|
if hash != crypto.SHA1 && hash != crypto.MD5 {
|
|
return nil, fmt.Errorf("invalid hash for SSLv3: '%s'", *mac)
|
|
}
|
|
digest = ssl30MAC(hash, macKey, input, adFull)
|
|
} else {
|
|
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 || *ssl3 {
|
|
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 paddingLen != cbc.BlockSize() {
|
|
return nil, fmt.Errorf("invalid length for maximalPadding")
|
|
}
|
|
paddingLen = 256
|
|
}
|
|
noSeal = true
|
|
if *ssl3 {
|
|
// SSLv3 padding must be minimal.
|
|
fails = true
|
|
}
|
|
}
|
|
if *ssl3 {
|
|
sealed = append(sealed, make([]byte, paddingLen-1)...)
|
|
sealed = append(sealed, byte(paddingLen-1))
|
|
} else {
|
|
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
|
|
if !*ssl3 {
|
|
// 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(sealed)-hash.Size()],
|
|
tag: sealed[len(sealed)-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))
|
|
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"
|
|
}
|
|
if *ssl3 {
|
|
commandLine += " -ssl3"
|
|
}
|
|
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 sealed\n")
|
|
fmt.Printf("# input. But these legacy AEADs are MAC-then-encrypt and may include padding, so this\n")
|
|
fmt.Printf("# split isn't meaningful. The unencrypted MAC is included in the 'DIGEST' tag above\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})
|
|
|
|
fmt.Printf("# Test with maximal padding.\n")
|
|
addTestCase(64-hash.Size(), 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{})
|
|
}
|
|
}
|