boringssl/ssl/test/runner/shim_ticket.go
David Benjamin 4199b0d190 Add tests which modify the shim ticket.
The existing tests for this codepath require us to reconfigure the shim.
This will not work when TLS 1.3 cipher configuration is detached from
the old cipher language. It also doesn't hit codepaths like sessions
containing a TLS 1.3 version but TLS 1.2 cipher.

Instead, add some logic to the runner to rewrite tickets and build tests
out of that.

Change-Id: I57ac5d49c3069497ed9aaf430afc65c631014bf6
Reviewed-on: https://boringssl-review.googlesource.com/12024
Reviewed-by: Adam Langley <agl@google.com>
2016-11-02 18:33:33 +00:00

250 lines
6.1 KiB
Go

// Copyright (c) 2016, Google Inc.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
package runner
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/hmac"
"crypto/sha256"
"encoding/asn1"
"errors"
)
// TestShimTicketKey is the testing key assumed for the shim.
var TestShimTicketKey = make([]byte, 48)
func DecryptShimTicket(in []byte) ([]byte, error) {
name := TestShimTicketKey[:16]
macKey := TestShimTicketKey[16:32]
encKey := TestShimTicketKey[32:48]
h := hmac.New(sha256.New, macKey)
block, err := aes.NewCipher(encKey)
if err != nil {
panic(err)
}
if len(in) < len(name)+block.BlockSize()+1+h.Size() {
return nil, errors.New("tls: shim ticket too short")
}
// Check the key name.
if !bytes.Equal(name, in[:len(name)]) {
return nil, errors.New("tls: shim ticket name mismatch")
}
// Check the MAC at the end of the ticket.
mac := in[len(in)-h.Size():]
in = in[:len(in)-h.Size()]
h.Write(in)
if !hmac.Equal(mac, h.Sum(nil)) {
return nil, errors.New("tls: shim ticket MAC mismatch")
}
// The MAC covers the key name, but the encryption does not.
in = in[len(name):]
// Decrypt in-place.
iv := in[:block.BlockSize()]
in = in[block.BlockSize():]
if l := len(in); l == 0 || l % block.BlockSize() != 0 {
return nil, errors.New("tls: ticket ciphertext not a multiple of the block size")
}
out := make([]byte, len(in))
cbc := cipher.NewCBCDecrypter(block, iv)
cbc.CryptBlocks(out, in)
// Remove the padding.
pad := int(out[len(out)-1])
if pad == 0 || pad > block.BlockSize() || pad > len(in) {
return nil, errors.New("tls: bad shim ticket CBC pad")
}
for i := 0; i < pad; i++ {
if out[len(out)-1-i] != byte(pad) {
return nil, errors.New("tls: bad shim ticket CBC pad")
}
}
return out[:len(out)-pad], nil
}
func EncryptShimTicket(in []byte) []byte {
name := TestShimTicketKey[:16]
macKey := TestShimTicketKey[16:32]
encKey := TestShimTicketKey[32:48]
h := hmac.New(sha256.New, macKey)
block, err := aes.NewCipher(encKey)
if err != nil {
panic(err)
}
// Use the zero IV for rewritten tickets.
iv := make([]byte, block.BlockSize())
cbc := cipher.NewCBCEncrypter(block, iv)
pad := block.BlockSize() - (len(in) % block.BlockSize())
out := make([]byte, 0, len(name)+len(iv)+len(in)+pad+h.Size())
out = append(out, name...)
out = append(out, iv...)
out = append(out, in...)
for i := 0; i < pad; i++ {
out = append(out, byte(pad))
}
ciphertext := out[len(name)+len(iv):]
cbc.CryptBlocks(ciphertext, ciphertext)
h.Write(out)
return h.Sum(out)
}
const asn1Constructed = 0x20
func parseDERElement(in []byte) (tag byte, body, rest []byte, ok bool) {
rest = in
if len(rest) < 1 {
return
}
tag = rest[0]
rest = rest[1:]
if tag&0x1f == 0x1f {
// Long-form tags not supported.
return
}
if len(rest) < 1 {
return
}
length := int(rest[0])
rest = rest[1:]
if length > 0x7f {
lengthLength := length & 0x7f
length = 0
if lengthLength == 0 {
// No indefinite-length encoding.
return
}
// Decode long-form lengths.
for lengthLength > 0 {
if len(rest) < 1 || (length<<8)>>8 != length {
return
}
if length == 0 && rest[0] == 0 {
// Length not minimally-encoded.
return
}
length <<= 8
length |= int(rest[0])
rest = rest[1:]
lengthLength--
}
if length < 0x80 {
// Length not minimally-encoded.
return
}
}
if len(rest) < length {
return
}
body = rest[:length]
rest = rest[length:]
ok = true
return
}
func SetShimTicketVersion(in []byte, vers uint16) ([]byte, error) {
plaintext, err := DecryptShimTicket(in)
if err != nil {
return nil, err
}
tag, session, _, ok := parseDERElement(plaintext)
if !ok || tag != asn1.TagSequence|asn1Constructed {
return nil, errors.New("tls: could not decode shim session")
}
// Skip the session version.
tag, _, session, ok = parseDERElement(session)
if !ok || tag != asn1.TagInteger {
return nil, errors.New("tls: could not decode shim session")
}
// Next field is the protocol version.
tag, version, _, ok := parseDERElement(session)
if !ok || tag != asn1.TagInteger {
return nil, errors.New("tls: could not decode shim session")
}
// This code assumes both old and new versions are encoded in two
// bytes. This isn't quite right as INTEGERs are minimally-encoded, but
// we do not need to support other caess for now.
if len(version) != 2 || vers < 0x80 || vers >= 0x8000 {
return nil, errors.New("tls: unsupported version in shim session")
}
version[0] = byte(vers >> 8)
version[1] = byte(vers)
return EncryptShimTicket(plaintext), nil
}
func SetShimTicketCipherSuite(in []byte, id uint16) ([]byte, error) {
plaintext, err := DecryptShimTicket(in)
if err != nil {
return nil, err
}
tag, session, _, ok := parseDERElement(plaintext)
if !ok || tag != asn1.TagSequence|asn1Constructed {
return nil, errors.New("tls: could not decode shim session")
}
// Skip the session version.
tag, _, session, ok = parseDERElement(session)
if !ok || tag != asn1.TagInteger {
return nil, errors.New("tls: could not decode shim session")
}
// Skip the protocol version.
tag, _, session, ok = parseDERElement(session)
if !ok || tag != asn1.TagInteger {
return nil, errors.New("tls: could not decode shim session")
}
// Next field is the cipher suite.
tag, cipherSuite, _, ok := parseDERElement(session)
if !ok || tag != asn1.TagOctetString || len(cipherSuite) != 2 {
return nil, errors.New("tls: could not decode shim session")
}
cipherSuite[0] = byte(id >> 8)
cipherSuite[1] = byte(id)
return EncryptShimTicket(plaintext), nil
}