ea72bd0b60
The EVP_CIPHER codepath should no longer be used with TLS. It still exists for DTLS and SSLv3. The AEAD construction in TLS does not allow for variable-overhead AEADs, so stateful AEADs do not include the length in the ad parameter. Rather the AEADs internally append the unpadded length once it is known. EVP_aead_rc4_md5_tls is modified to account for this. Tests are added (and RC4-MD5's regenerated) for each of the new AEADs. The cipher tests are all moved into crypto/cipher/test because there's now a lot of them and they clutter the directory listing. In ssl/, the stateful AEAD logic is also modified to account for stateful AEADs with a fixed IV component, and for AEADs which use a random nonce (for the explicit-IV CBC mode ciphers). The new implementation fixes a bug/quirk in stateless CBC mode ciphers where the fixed IV portion of the keyblock was generated regardless. This is at the end, so it's only relevant for EAP-TLS which generates a MSK from the end of the key block. Change-Id: I2d8b8aa11deb43bde2fd733f4f90b5d5b8cb1334 Reviewed-on: https://boringssl-review.googlesource.com/2692 Reviewed-by: Adam Langley <agl@google.com>
277 lines
6.9 KiB
Go
277 lines
6.9 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 "rc4":
|
|
return 16
|
|
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
|
|
}
|
|
|
|
func makeTestCase(length int) (*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
|
|
if *bulkCipher == "rc4" {
|
|
if *implicitIV {
|
|
return nil, fmt.Errorf("implicit IV enabled on a stream cipher")
|
|
}
|
|
|
|
stream, err := rc4.NewCipher(encKey)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
sealed = make([]byte, 0, len(input)+len(digest))
|
|
sealed = append(sealed, input...)
|
|
sealed = append(sealed, digest...)
|
|
stream.XORKeyStream(sealed, sealed)
|
|
} else {
|
|
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...)
|
|
sealed = append(sealed, digest...)
|
|
paddingLen := cbc.BlockSize() - (len(sealed) % cbc.BlockSize())
|
|
// TODO(davidben): Add tests for non-minimal padding (SSL3
|
|
// forbids, TLS allows) and arbitrary padding bytes (SSL3
|
|
// allows, TLS forbids).
|
|
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...)
|
|
}
|
|
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():],
|
|
}
|
|
return t, nil
|
|
}
|
|
|
|
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")
|
|
|
|
// 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 {
|
|
t, err := makeTestCase(l)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "%s\n", err)
|
|
os.Exit(1)
|
|
}
|
|
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("\n")
|
|
}
|
|
}
|