mirror of
https://github.com/refraction-networking/utls.git
synced 2025-04-03 20:17:36 +03:00
crypto/tls: decouple handshake signatures from the handshake hash.
Prior to TLS 1.2, the handshake had a pleasing property that one could incrementally hash it and, from that, get the needed hashes for both the CertificateVerify and Finished messages. TLS 1.2 introduced negotiation for the signature and hash and it became possible for the handshake hash to be, say, SHA-384, but for the CertificateVerify to sign the handshake with SHA-1. The problem is that one doesn't know in advance which hashes will be needed and thus the handshake needs to be buffered. Go ignored this, always kept a single handshake hash, and any signatures over the handshake had to use that hash. However, there are a set of servers that inspect the client's offered signature hash functions and will abort the handshake if one of the server's certificates is signed with a hash function outside of that set. https://robertsspaceindustries.com/ is an example of such a server. Clearly not a lot of thought happened when that server code was written, but its out there and we have to deal with it. This change decouples the handshake hash from the CertificateVerify hash. This lays the groundwork for advertising support for SHA-384 but doesn't actually make that change in the interests of reviewability. Updating the advertised hash functions will cause changes in many of the testdata/ files and some errors might get lost in the noise. This change only needs to update four testdata/ files: one because a SHA-384-based handshake is now being signed with SHA-256 and the others because the TLS 1.2 CertificateRequest message now includes SHA-1. This change also has the effect of adding support for client-certificates in SSLv3 servers. However, SSLv3 is now disabled by default so this should be moot. It would be possible to avoid much of this change and just support SHA-384 for the ServerKeyExchange as the SKX only signs over the nonces and SKX params (a design mistake in TLS). However, that would leave Go in the odd situation where it advertised support for SHA-384, but would only use the handshake hash when signing client certificates. I fear that'll just cause problems in the future. Much of this code was written by davidben@ for the purposes of testing BoringSSL. Partly addresses #9757 Change-Id: I5137a472b6076812af387a5a69fc62c7373cd485 Reviewed-on: https://go-review.googlesource.com/9415 Run-TryBot: Adam Langley <agl@golang.org> Reviewed-by: Adam Langley <agl@golang.org>
This commit is contained in:
parent
ee94166b41
commit
7de6f5ad0c
12 changed files with 447 additions and 322 deletions
189
prf.go
189
prf.go
|
@ -9,8 +9,10 @@ import (
|
|||
"crypto/hmac"
|
||||
"crypto/md5"
|
||||
"crypto/sha1"
|
||||
"crypto/sha256"
|
||||
"crypto/sha512"
|
||||
"errors"
|
||||
"hash"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// Split a premaster secret in two as specified in RFC 4346, section 5.
|
||||
|
@ -64,14 +66,14 @@ func prf10(result, secret, label, seed []byte) {
|
|||
}
|
||||
}
|
||||
|
||||
// prf12New returns a function implementing the TLS 1.2 pseudo-random function,
|
||||
// as defined in RFC 5246, section 5, using the given hash.
|
||||
func prf12New(tls12Hash crypto.Hash) func(result, secret, label, seed []byte) {
|
||||
// prf12 implements the TLS 1.2 pseudo-random function, as defined in RFC 5246, section 5.
|
||||
func prf12(hashFunc func() hash.Hash) func(result, secret, label, seed []byte) {
|
||||
return func(result, secret, label, seed []byte) {
|
||||
labelAndSeed := make([]byte, len(label)+len(seed))
|
||||
copy(labelAndSeed, label)
|
||||
copy(labelAndSeed[len(label):], seed)
|
||||
pHash(result, secret, labelAndSeed, tls12Hash.New)
|
||||
|
||||
pHash(result, secret, labelAndSeed, hashFunc)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -119,41 +121,49 @@ var keyExpansionLabel = []byte("key expansion")
|
|||
var clientFinishedLabel = []byte("client finished")
|
||||
var serverFinishedLabel = []byte("server finished")
|
||||
|
||||
func prfForVersion(version uint16, tls12Hash crypto.Hash) func(result, secret, label, seed []byte) {
|
||||
func prfAndHashForVersion(version uint16, suite *cipherSuite) (func(result, secret, label, seed []byte), crypto.Hash) {
|
||||
switch version {
|
||||
case VersionSSL30:
|
||||
return prf30
|
||||
return prf30, crypto.Hash(0)
|
||||
case VersionTLS10, VersionTLS11:
|
||||
return prf10
|
||||
return prf10, crypto.Hash(0)
|
||||
case VersionTLS12:
|
||||
return prf12New(tls12Hash)
|
||||
if suite.flags&suiteSHA384 != 0 {
|
||||
return prf12(sha512.New384), crypto.SHA384
|
||||
}
|
||||
return prf12(sha256.New), crypto.SHA256
|
||||
default:
|
||||
panic("unknown version")
|
||||
}
|
||||
}
|
||||
|
||||
func prfForVersion(version uint16, suite *cipherSuite) func(result, secret, label, seed []byte) {
|
||||
prf, _ := prfAndHashForVersion(version, suite)
|
||||
return prf
|
||||
}
|
||||
|
||||
// masterFromPreMasterSecret generates the master secret from the pre-master
|
||||
// secret. See http://tools.ietf.org/html/rfc5246#section-8.1
|
||||
func masterFromPreMasterSecret(version uint16, tls12Hash crypto.Hash, preMasterSecret, clientRandom, serverRandom []byte) []byte {
|
||||
func masterFromPreMasterSecret(version uint16, suite *cipherSuite, preMasterSecret, clientRandom, serverRandom []byte) []byte {
|
||||
var seed [tlsRandomLength * 2]byte
|
||||
copy(seed[0:len(clientRandom)], clientRandom)
|
||||
copy(seed[len(clientRandom):], serverRandom)
|
||||
masterSecret := make([]byte, masterSecretLength)
|
||||
prfForVersion(version, tls12Hash)(masterSecret, preMasterSecret, masterSecretLabel, seed[0:])
|
||||
prfForVersion(version, suite)(masterSecret, preMasterSecret, masterSecretLabel, seed[0:])
|
||||
return masterSecret
|
||||
}
|
||||
|
||||
// keysFromMasterSecret generates the connection keys from the master
|
||||
// secret, given the lengths of the MAC key, cipher key and IV, as defined in
|
||||
// RFC 2246, section 6.3.
|
||||
func keysFromMasterSecret(version uint16, tls12Hash crypto.Hash, masterSecret, clientRandom, serverRandom []byte, macLen, keyLen, ivLen int) (clientMAC, serverMAC, clientKey, serverKey, clientIV, serverIV []byte) {
|
||||
func keysFromMasterSecret(version uint16, suite *cipherSuite, masterSecret, clientRandom, serverRandom []byte, macLen, keyLen, ivLen int) (clientMAC, serverMAC, clientKey, serverKey, clientIV, serverIV []byte) {
|
||||
var seed [tlsRandomLength * 2]byte
|
||||
copy(seed[0:len(clientRandom)], serverRandom)
|
||||
copy(seed[len(serverRandom):], clientRandom)
|
||||
|
||||
n := 2*macLen + 2*keyLen + 2*ivLen
|
||||
keyMaterial := make([]byte, n)
|
||||
prfForVersion(version, tls12Hash)(keyMaterial, masterSecret, keyExpansionLabel, seed[0:])
|
||||
prfForVersion(version, suite)(keyMaterial, masterSecret, keyExpansionLabel, seed[0:])
|
||||
clientMAC = keyMaterial[:macLen]
|
||||
keyMaterial = keyMaterial[macLen:]
|
||||
serverMAC = keyMaterial[:macLen]
|
||||
|
@ -168,30 +178,53 @@ func keysFromMasterSecret(version uint16, tls12Hash crypto.Hash, masterSecret, c
|
|||
return
|
||||
}
|
||||
|
||||
func newFinishedHash(version uint16, tls12Hash crypto.Hash) finishedHash {
|
||||
if version >= VersionTLS12 {
|
||||
return finishedHash{tls12Hash.New(), tls12Hash.New(), tls12Hash, nil, nil, version, prfForVersion(version, tls12Hash)}
|
||||
// lookupTLSHash looks up the corresponding crypto.Hash for a given
|
||||
// TLS hash identifier.
|
||||
func lookupTLSHash(hash uint8) (crypto.Hash, error) {
|
||||
switch hash {
|
||||
case hashSHA1:
|
||||
return crypto.SHA1, nil
|
||||
case hashSHA256:
|
||||
return crypto.SHA256, nil
|
||||
case hashSHA384:
|
||||
return crypto.SHA384, nil
|
||||
default:
|
||||
return 0, errors.New("tls: unsupported hash algorithm")
|
||||
}
|
||||
return finishedHash{sha1.New(), sha1.New(), crypto.MD5SHA1, md5.New(), md5.New(), version, prfForVersion(version, tls12Hash)}
|
||||
}
|
||||
|
||||
func newFinishedHash(version uint16, cipherSuite *cipherSuite) finishedHash {
|
||||
var buffer []byte
|
||||
if version == VersionSSL30 || version >= VersionTLS12 {
|
||||
buffer = []byte{}
|
||||
}
|
||||
|
||||
prf, hash := prfAndHashForVersion(version, cipherSuite)
|
||||
if hash != 0 {
|
||||
return finishedHash{hash.New(), hash.New(), nil, nil, buffer, version, prf}
|
||||
}
|
||||
|
||||
return finishedHash{sha1.New(), sha1.New(), md5.New(), md5.New(), buffer, version, prf}
|
||||
}
|
||||
|
||||
// A finishedHash calculates the hash of a set of handshake messages suitable
|
||||
// for including in a Finished message.
|
||||
type finishedHash struct {
|
||||
client hash.Hash
|
||||
|
||||
server hash.Hash
|
||||
serverHash crypto.Hash
|
||||
server hash.Hash
|
||||
|
||||
// Prior to TLS 1.2, an additional MD5 hash is required.
|
||||
clientMD5 hash.Hash
|
||||
serverMD5 hash.Hash
|
||||
|
||||
// In TLS 1.2, a full buffer is sadly required.
|
||||
buffer []byte
|
||||
|
||||
version uint16
|
||||
prf func(result, secret, label, seed []byte)
|
||||
}
|
||||
|
||||
func (h finishedHash) Write(msg []byte) (n int, err error) {
|
||||
func (h *finishedHash) Write(msg []byte) (n int, err error) {
|
||||
h.client.Write(msg)
|
||||
h.server.Write(msg)
|
||||
|
||||
|
@ -199,14 +232,29 @@ func (h finishedHash) Write(msg []byte) (n int, err error) {
|
|||
h.clientMD5.Write(msg)
|
||||
h.serverMD5.Write(msg)
|
||||
}
|
||||
|
||||
if h.buffer != nil {
|
||||
h.buffer = append(h.buffer, msg...)
|
||||
}
|
||||
|
||||
return len(msg), nil
|
||||
}
|
||||
|
||||
func (h finishedHash) Sum() []byte {
|
||||
if h.version >= VersionTLS12 {
|
||||
return h.client.Sum(nil)
|
||||
}
|
||||
|
||||
out := make([]byte, 0, md5.Size+sha1.Size)
|
||||
out = h.clientMD5.Sum(out)
|
||||
return h.client.Sum(out)
|
||||
}
|
||||
|
||||
// finishedSum30 calculates the contents of the verify_data member of a SSLv3
|
||||
// Finished message given the MD5 and SHA1 hashes of a set of handshake
|
||||
// messages.
|
||||
func finishedSum30(md5, sha1 hash.Hash, masterSecret []byte, magic [4]byte) []byte {
|
||||
md5.Write(magic[:])
|
||||
func finishedSum30(md5, sha1 hash.Hash, masterSecret []byte, magic []byte) []byte {
|
||||
md5.Write(magic)
|
||||
md5.Write(masterSecret)
|
||||
md5.Write(ssl30Pad1[:])
|
||||
md5Digest := md5.Sum(nil)
|
||||
|
@ -217,7 +265,7 @@ func finishedSum30(md5, sha1 hash.Hash, masterSecret []byte, magic [4]byte) []by
|
|||
md5.Write(md5Digest)
|
||||
md5Digest = md5.Sum(nil)
|
||||
|
||||
sha1.Write(magic[:])
|
||||
sha1.Write(magic)
|
||||
sha1.Write(masterSecret)
|
||||
sha1.Write(ssl30Pad1[:40])
|
||||
sha1Digest := sha1.Sum(nil)
|
||||
|
@ -241,19 +289,11 @@ var ssl3ServerFinishedMagic = [4]byte{0x53, 0x52, 0x56, 0x52}
|
|||
// Finished message.
|
||||
func (h finishedHash) clientSum(masterSecret []byte) []byte {
|
||||
if h.version == VersionSSL30 {
|
||||
return finishedSum30(h.clientMD5, h.client, masterSecret, ssl3ClientFinishedMagic)
|
||||
return finishedSum30(h.clientMD5, h.client, masterSecret, ssl3ClientFinishedMagic[:])
|
||||
}
|
||||
|
||||
out := make([]byte, finishedVerifyLength)
|
||||
if h.version >= VersionTLS12 {
|
||||
seed := h.client.Sum(nil)
|
||||
h.prf(out, masterSecret, clientFinishedLabel, seed)
|
||||
} else {
|
||||
seed := make([]byte, 0, md5.Size+sha1.Size)
|
||||
seed = h.clientMD5.Sum(seed)
|
||||
seed = h.client.Sum(seed)
|
||||
h.prf(out, masterSecret, clientFinishedLabel, seed)
|
||||
}
|
||||
h.prf(out, masterSecret, clientFinishedLabel, h.Sum())
|
||||
return out
|
||||
}
|
||||
|
||||
|
@ -261,48 +301,67 @@ func (h finishedHash) clientSum(masterSecret []byte) []byte {
|
|||
// Finished message.
|
||||
func (h finishedHash) serverSum(masterSecret []byte) []byte {
|
||||
if h.version == VersionSSL30 {
|
||||
return finishedSum30(h.serverMD5, h.server, masterSecret, ssl3ServerFinishedMagic)
|
||||
return finishedSum30(h.serverMD5, h.server, masterSecret, ssl3ServerFinishedMagic[:])
|
||||
}
|
||||
|
||||
out := make([]byte, finishedVerifyLength)
|
||||
if h.version >= VersionTLS12 {
|
||||
seed := h.server.Sum(nil)
|
||||
h.prf(out, masterSecret, serverFinishedLabel, seed)
|
||||
} else {
|
||||
seed := make([]byte, 0, md5.Size+sha1.Size)
|
||||
seed = h.serverMD5.Sum(seed)
|
||||
seed = h.server.Sum(seed)
|
||||
h.prf(out, masterSecret, serverFinishedLabel, seed)
|
||||
}
|
||||
h.prf(out, masterSecret, serverFinishedLabel, h.Sum())
|
||||
return out
|
||||
}
|
||||
|
||||
// selectClientCertSignatureAlgorithm returns a signatureAndHash to sign a
|
||||
// client's CertificateVerify with, or an error if none can be found.
|
||||
func (h finishedHash) selectClientCertSignatureAlgorithm(serverList []signatureAndHash, sigType uint8) (signatureAndHash, error) {
|
||||
if h.version < VersionTLS12 {
|
||||
// Nothing to negotiate before TLS 1.2.
|
||||
return signatureAndHash{signature: sigType}, nil
|
||||
}
|
||||
|
||||
for _, v := range serverList {
|
||||
if v.signature == sigType && isSupportedSignatureAndHash(v, supportedSignatureAlgorithms) {
|
||||
return v, nil
|
||||
}
|
||||
}
|
||||
return signatureAndHash{}, errors.New("tls: no supported signature algorithm found for signing client certificate")
|
||||
}
|
||||
|
||||
// hashForClientCertificate returns a digest, hash function, and TLS 1.2 hash
|
||||
// id suitable for signing by a TLS client certificate.
|
||||
func (h finishedHash) hashForClientCertificate(sigType uint8) ([]byte, crypto.Hash, uint8) {
|
||||
func (h finishedHash) hashForClientCertificate(signatureAndHash signatureAndHash, masterSecret []byte) ([]byte, crypto.Hash, error) {
|
||||
if (h.version == VersionSSL30 || h.version >= VersionTLS12) && h.buffer == nil {
|
||||
panic("a handshake hash for a client-certificate was requested after discarding the handshake buffer")
|
||||
}
|
||||
|
||||
if h.version == VersionSSL30 {
|
||||
if signatureAndHash.signature != signatureRSA {
|
||||
return nil, 0, errors.New("tls: unsupported signature type for client certificate")
|
||||
}
|
||||
|
||||
md5Hash := md5.New()
|
||||
md5Hash.Write(h.buffer)
|
||||
sha1Hash := sha1.New()
|
||||
sha1Hash.Write(h.buffer)
|
||||
return finishedSum30(md5Hash, sha1Hash, masterSecret, nil), crypto.MD5SHA1, nil
|
||||
}
|
||||
if h.version >= VersionTLS12 {
|
||||
digest := h.server.Sum(nil)
|
||||
return digest, h.serverHash, tls12HashID(h.serverHash)
|
||||
}
|
||||
if sigType == signatureECDSA {
|
||||
digest := h.server.Sum(nil)
|
||||
return digest, crypto.SHA1, hashSHA1
|
||||
hashAlg, err := lookupTLSHash(signatureAndHash.hash)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
hash := hashAlg.New()
|
||||
hash.Write(h.buffer)
|
||||
return hash.Sum(nil), hashAlg, nil
|
||||
}
|
||||
|
||||
digest := make([]byte, 0, 36)
|
||||
digest = h.serverMD5.Sum(digest)
|
||||
digest = h.server.Sum(digest)
|
||||
return digest, crypto.MD5SHA1, 0 /* not specified in TLS 1.2. */
|
||||
if signatureAndHash.signature == signatureECDSA {
|
||||
return h.server.Sum(nil), crypto.SHA1, nil
|
||||
}
|
||||
|
||||
return h.Sum(), crypto.MD5SHA1, nil
|
||||
}
|
||||
|
||||
// tls12HashID returns the HashAlgorithm id corresponding to the hash h, as
|
||||
// specified in RFC 5246, section A.4.1.
|
||||
func tls12HashID(h crypto.Hash) uint8 {
|
||||
switch h {
|
||||
case crypto.SHA256:
|
||||
return hashSHA256
|
||||
case crypto.SHA384:
|
||||
return hashSHA384
|
||||
}
|
||||
panic("tls12HashID called with unknown hash " + strconv.Itoa(int(h)))
|
||||
// discardHandshakeBuffer is called when there is no more need to
|
||||
// buffer the entirety of the handshake messages.
|
||||
func (h *finishedHash) discardHandshakeBuffer() {
|
||||
h.buffer = nil
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue