summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Benjamin <davidben@chromium.org>2014-08-24 01:44:23 -0400
committerAdam Langley <agl@google.com>2014-08-25 22:48:18 +0000
commitd30a990850457657e3209cb0c27fbe89b3df7ad2 (patch)
tree8b87f1bcfc4424054d7f89178b28d5651fab208c
parentf9b96fa4935e16ae4e44507606f612cf6d0054c9 (diff)
downloadsrc-d30a990850457657e3209cb0c27fbe89b3df7ad2.tar.gz
Implement TLS Channel ID in runner.go
Change-Id: Ia349c7a7cdcfd49965cd0c4d6cf81a76fbffb696 Reviewed-on: https://boringssl-review.googlesource.com/1604 Reviewed-by: Adam Langley <agl@google.com>
-rw-r--r--ssl/test/runner/common.go40
-rw-r--r--ssl/test/runner/conn.go6
-rw-r--r--ssl/test/runner/handshake_client.go55
-rw-r--r--ssl/test/runner/handshake_messages.go91
-rw-r--r--ssl/test/runner/handshake_server.go51
-rw-r--r--ssl/test/runner/prf.go16
-rw-r--r--ssl/test/runner/ticket.go33
7 files changed, 259 insertions, 33 deletions
diff --git a/ssl/test/runner/common.go b/ssl/test/runner/common.go
index f14f4e9..daeeb5e 100644
--- a/ssl/test/runner/common.go
+++ b/ssl/test/runner/common.go
@@ -7,6 +7,7 @@ package main
import (
"container/list"
"crypto"
+ "crypto/ecdsa"
"crypto/rand"
"crypto/x509"
"fmt"
@@ -47,19 +48,20 @@ const (
// TLS handshake message types.
const (
- typeClientHello uint8 = 1
- typeServerHello uint8 = 2
- typeHelloVerifyRequest uint8 = 3
- typeNewSessionTicket uint8 = 4
- typeCertificate uint8 = 11
- typeServerKeyExchange uint8 = 12
- typeCertificateRequest uint8 = 13
- typeServerHelloDone uint8 = 14
- typeCertificateVerify uint8 = 15
- typeClientKeyExchange uint8 = 16
- typeFinished uint8 = 20
- typeCertificateStatus uint8 = 22
- typeNextProtocol uint8 = 67 // Not IANA assigned
+ typeClientHello uint8 = 1
+ typeServerHello uint8 = 2
+ typeHelloVerifyRequest uint8 = 3
+ typeNewSessionTicket uint8 = 4
+ typeCertificate uint8 = 11
+ typeServerKeyExchange uint8 = 12
+ typeCertificateRequest uint8 = 13
+ typeServerHelloDone uint8 = 14
+ typeCertificateVerify uint8 = 15
+ typeClientKeyExchange uint8 = 16
+ typeFinished uint8 = 20
+ typeCertificateStatus uint8 = 22
+ typeNextProtocol uint8 = 67 // Not IANA assigned
+ typeEncryptedExtensions uint8 = 203 // Not IANA assigned
)
// TLS compression types.
@@ -77,6 +79,7 @@ const (
extensionSessionTicket uint16 = 35
extensionNextProtoNeg uint16 = 13172 // not IANA assigned
extensionRenegotiationInfo uint16 = 0xff01
+ extensionChannelID uint16 = 30032 // not IANA assigned
)
// TLS signaling cipher suite values
@@ -166,6 +169,7 @@ type ConnectionState struct {
ServerName string // server name requested by client, if any (server side only)
PeerCertificates []*x509.Certificate // certificate chain presented by remote peer
VerifiedChains [][]*x509.Certificate // verified chains built from PeerCertificates
+ ChannelID *ecdsa.PublicKey // the channel ID for this connection
}
// ClientAuthType declares the policy the server will follow for
@@ -187,6 +191,7 @@ type ClientSessionState struct {
vers uint16 // SSL/TLS version negotiated for the session
cipherSuite uint16 // Ciphersuite negotiated for the session
masterSecret []byte // MasterSecret generated by client on a full handshake
+ handshakeHash []byte // Handshake hash for Channel ID purposes.
serverCertificates []*x509.Certificate // Certificate chain presented by the server
}
@@ -307,6 +312,15 @@ type Config struct {
// be used.
CurvePreferences []CurveID
+ // ChannelID contains the ECDSA key for the client to use as
+ // its TLS Channel ID.
+ ChannelID *ecdsa.PrivateKey
+
+ // RequestChannelID controls whether the server requests a TLS
+ // Channel ID. If negotiated, the client's public key is
+ // returned in the ConnectionState.
+ RequestChannelID bool
+
// Bugs specifies optional misbehaviour to be used for testing other
// implementations.
Bugs ProtocolBugs
diff --git a/ssl/test/runner/conn.go b/ssl/test/runner/conn.go
index 5371a64..2c5e920 100644
--- a/ssl/test/runner/conn.go
+++ b/ssl/test/runner/conn.go
@@ -9,6 +9,7 @@ package main
import (
"bytes"
"crypto/cipher"
+ "crypto/ecdsa"
"crypto/subtle"
"crypto/x509"
"errors"
@@ -47,6 +48,8 @@ type Conn struct {
clientProtocol string
clientProtocolFallback bool
+ channelID *ecdsa.PublicKey
+
// input/output
in, out halfConn // in.Mutex < out.Mutex
rawInput *block // raw input, right off the wire
@@ -937,6 +940,8 @@ func (c *Conn) readHandshake() (interface{}, error) {
m = new(finishedMsg)
case typeHelloVerifyRequest:
m = new(helloVerifyRequestMsg)
+ case typeEncryptedExtensions:
+ m = new(encryptedExtensionsMsg)
default:
return nil, c.in.setErrorLocked(c.sendAlert(alertUnexpectedMessage))
}
@@ -1104,6 +1109,7 @@ func (c *Conn) ConnectionState() ConnectionState {
state.PeerCertificates = c.peerCertificates
state.VerifiedChains = c.verifiedChains
state.ServerName = c.serverName
+ state.ChannelID = c.channelID
}
return state
diff --git a/ssl/test/runner/handshake_client.go b/ssl/test/runner/handshake_client.go
index ecc2bed..c683913 100644
--- a/ssl/test/runner/handshake_client.go
+++ b/ssl/test/runner/handshake_client.go
@@ -8,6 +8,7 @@ import (
"bytes"
"crypto"
"crypto/ecdsa"
+ "crypto/elliptic"
"crypto/rsa"
"crypto/subtle"
"crypto/x509"
@@ -54,6 +55,7 @@ func (c *Conn) clientHandshake() error {
nextProtoNeg: len(c.config.NextProtos) > 0,
secureRenegotiation: true,
duplicateExtension: c.config.Bugs.DuplicateExtension,
+ channelIDSupported: c.config.ChannelID != nil,
}
if c.config.Bugs.SendClientVersion != 0 {
@@ -238,7 +240,7 @@ NextCipherSuite:
if err := hs.readFinished(); err != nil {
return err
}
- if err := hs.sendFinished(); err != nil {
+ if err := hs.sendFinished(isResume); err != nil {
return err
}
} else {
@@ -248,7 +250,7 @@ NextCipherSuite:
if err := hs.establishKeys(); err != nil {
return err
}
- if err := hs.sendFinished(); err != nil {
+ if err := hs.sendFinished(isResume); err != nil {
return err
}
if err := hs.readSessionTicket(); err != nil {
@@ -565,6 +567,11 @@ func (hs *clientHandshakeState) processServerHello() (bool, error) {
return false, errors.New("server advertised unrequested NPN extension")
}
+ if !hs.hello.channelIDSupported && hs.serverHello.channelIDRequested {
+ c.sendAlert(alertHandshakeFailure)
+ return false, errors.New("server advertised unrequested Channel ID extension")
+ }
+
if hs.serverResumedSession() {
// Restore masterSecret and peerCerts from previous state
hs.masterSecret = hs.session.masterSecret
@@ -619,20 +626,22 @@ func (hs *clientHandshakeState) readSessionTicket() error {
c.sendAlert(alertUnexpectedMessage)
return unexpectedMessageError(sessionTicketMsg, msg)
}
- hs.writeServerHash(sessionTicketMsg.marshal())
hs.session = &ClientSessionState{
sessionTicket: sessionTicketMsg.ticket,
vers: c.vers,
cipherSuite: hs.suite.id,
masterSecret: hs.masterSecret,
+ handshakeHash: hs.finishedHash.server.Sum(nil),
serverCertificates: c.peerCertificates,
}
+ hs.writeServerHash(sessionTicketMsg.marshal())
+
return nil
}
-func (hs *clientHandshakeState) sendFinished() error {
+func (hs *clientHandshakeState) sendFinished(isResume bool) error {
c := hs.c
var postCCSBytes []byte
@@ -650,6 +659,34 @@ func (hs *clientHandshakeState) sendFinished() error {
postCCSBytes = append(postCCSBytes, nextProtoBytes...)
}
+ if hs.serverHello.channelIDRequested {
+ encryptedExtensions := new(encryptedExtensionsMsg)
+ if c.config.ChannelID.Curve != elliptic.P256() {
+ return fmt.Errorf("tls: Channel ID is not on P-256.")
+ }
+ var resumeHash []byte
+ if isResume {
+ resumeHash = hs.session.handshakeHash
+ }
+ r, s, err := ecdsa.Sign(c.config.rand(), c.config.ChannelID, hs.finishedHash.hashForChannelID(resumeHash))
+ if err != nil {
+ return err
+ }
+ channelID := make([]byte, 128)
+ writeIntPadded(channelID[0:32], c.config.ChannelID.X)
+ writeIntPadded(channelID[32:64], c.config.ChannelID.Y)
+ writeIntPadded(channelID[64:96], r)
+ writeIntPadded(channelID[96:128], s)
+ encryptedExtensions.channelID = channelID
+
+ c.channelID = &c.config.ChannelID.PublicKey
+
+ encryptedExtensionsBytes := encryptedExtensions.marshal()
+ hs.writeHash(encryptedExtensionsBytes, seqno)
+ seqno++
+ postCCSBytes = append(postCCSBytes, encryptedExtensionsBytes...)
+ }
+
finished := new(finishedMsg)
if c.config.Bugs.EarlyChangeCipherSpec == 2 {
finished.verifyData = hs.finishedHash.clientSum(nil)
@@ -724,3 +761,13 @@ func mutualProtocol(clientProtos, serverProtos []string) (string, bool) {
return clientProtos[0], true
}
+
+// writeIntPadded writes x into b, padded up with leading zeros as
+// needed.
+func writeIntPadded(b []byte, x *big.Int) {
+ for i := range b {
+ b[i] = 0
+ }
+ xb := x.Bytes()
+ copy(b[len(b)-len(xb):], xb)
+}
diff --git a/ssl/test/runner/handshake_messages.go b/ssl/test/runner/handshake_messages.go
index 7fe8bf5..472aa87 100644
--- a/ssl/test/runner/handshake_messages.go
+++ b/ssl/test/runner/handshake_messages.go
@@ -25,6 +25,7 @@ type clientHelloMsg struct {
signatureAndHashes []signatureAndHash
secureRenegotiation bool
duplicateExtension bool
+ channelIDSupported bool
}
func (m *clientHelloMsg) equal(i interface{}) bool {
@@ -49,7 +50,9 @@ func (m *clientHelloMsg) equal(i interface{}) bool {
m.ticketSupported == m1.ticketSupported &&
bytes.Equal(m.sessionTicket, m1.sessionTicket) &&
eqSignatureAndHashes(m.signatureAndHashes, m1.signatureAndHashes) &&
- m.secureRenegotiation == m1.secureRenegotiation
+ m.secureRenegotiation == m1.secureRenegotiation &&
+ m.duplicateExtension == m1.duplicateExtension &&
+ m.channelIDSupported == m1.channelIDSupported
}
func (m *clientHelloMsg) marshal() []byte {
@@ -97,6 +100,9 @@ func (m *clientHelloMsg) marshal() []byte {
if m.duplicateExtension {
numExtensions += 2
}
+ if m.channelIDSupported {
+ numExtensions++
+ }
if numExtensions > 0 {
extensionsLength += 4 * numExtensions
length += 2 + extensionsLength
@@ -260,6 +266,11 @@ func (m *clientHelloMsg) marshal() []byte {
z[3] = 1
z = z[5:]
}
+ if m.channelIDSupported {
+ z[0] = byte(extensionChannelID >> 8)
+ z[1] = byte(extensionChannelID & 0xff)
+ z = z[4:]
+ }
if m.duplicateExtension {
// Add a duplicate bogus extension at the beginning and end.
z[0] = 0xff
@@ -440,6 +451,11 @@ func (m *clientHelloMsg) unmarshal(data []byte) bool {
return false
}
m.secureRenegotiation = true
+ case extensionChannelID:
+ if length > 0 {
+ return false
+ }
+ m.channelIDSupported = true
}
data = data[length:]
}
@@ -461,6 +477,7 @@ type serverHelloMsg struct {
ticketSupported bool
secureRenegotiation bool
duplicateExtension bool
+ channelIDRequested bool
}
func (m *serverHelloMsg) equal(i interface{}) bool {
@@ -480,7 +497,9 @@ func (m *serverHelloMsg) equal(i interface{}) bool {
eqStrings(m.nextProtos, m1.nextProtos) &&
m.ocspStapling == m1.ocspStapling &&
m.ticketSupported == m1.ticketSupported &&
- m.secureRenegotiation == m1.secureRenegotiation
+ m.secureRenegotiation == m1.secureRenegotiation &&
+ m.duplicateExtension == m1.duplicateExtension &&
+ m.channelIDRequested == m1.channelIDRequested
}
func (m *serverHelloMsg) marshal() []byte {
@@ -514,6 +533,9 @@ func (m *serverHelloMsg) marshal() []byte {
if m.duplicateExtension {
numExtensions += 2
}
+ if m.channelIDRequested {
+ numExtensions++
+ }
if numExtensions > 0 {
extensionsLength += 4 * numExtensions
length += 2 + extensionsLength
@@ -581,6 +603,11 @@ func (m *serverHelloMsg) marshal() []byte {
z[3] = 1
z = z[5:]
}
+ if m.channelIDRequested {
+ z[0] = byte(extensionChannelID >> 8)
+ z[1] = byte(extensionChannelID & 0xff)
+ z = z[4:]
+ }
if m.duplicateExtension {
// Add a duplicate bogus extension at the beginning and end.
z[0] = 0xff
@@ -671,6 +698,11 @@ func (m *serverHelloMsg) unmarshal(data []byte) bool {
return false
}
m.secureRenegotiation = true
+ case extensionChannelID:
+ if length > 0 {
+ return false
+ }
+ m.channelIDRequested = true
}
data = data[length:]
}
@@ -1407,7 +1439,8 @@ func (m *helloVerifyRequestMsg) equal(i interface{}) bool {
return false
}
- return m.vers == m1.vers &&
+ return bytes.Equal(m.raw, m1.raw) &&
+ m.vers == m1.vers &&
bytes.Equal(m.cookie, m1.cookie)
}
@@ -1447,6 +1480,58 @@ func (m *helloVerifyRequestMsg) unmarshal(data []byte) bool {
return true
}
+type encryptedExtensionsMsg struct {
+ raw []byte
+ channelID []byte
+}
+
+func (m *encryptedExtensionsMsg) equal(i interface{}) bool {
+ m1, ok := i.(*encryptedExtensionsMsg)
+ if !ok {
+ return false
+ }
+
+ return bytes.Equal(m.raw, m1.raw) &&
+ bytes.Equal(m.channelID, m1.channelID)
+}
+
+func (m *encryptedExtensionsMsg) marshal() []byte {
+ if m.raw != nil {
+ return m.raw
+ }
+
+ length := 2 + 2 + len(m.channelID)
+
+ x := make([]byte, 4+length)
+ x[0] = typeEncryptedExtensions
+ x[1] = uint8(length >> 16)
+ x[2] = uint8(length >> 8)
+ x[3] = uint8(length)
+ x[4] = uint8(extensionChannelID >> 8)
+ x[5] = uint8(extensionChannelID & 0xff)
+ x[6] = uint8(len(m.channelID) >> 8)
+ x[7] = uint8(len(m.channelID) & 0xff)
+ copy(x[8:], m.channelID)
+
+ return x
+}
+
+func (m *encryptedExtensionsMsg) unmarshal(data []byte) bool {
+ if len(data) != 4+2+2+128 {
+ return false
+ }
+ m.raw = data
+ if (uint16(data[4])<<8)|uint16(data[5]) != extensionChannelID {
+ return false
+ }
+ if int(data[6])<<8|int(data[7]) != 128 {
+ return false
+ }
+ m.channelID = data[4+2+2:]
+
+ return true
+}
+
func eqUint16s(x, y []uint16) bool {
if len(x) != len(y) {
return false
diff --git a/ssl/test/runner/handshake_server.go b/ssl/test/runner/handshake_server.go
index d5a660a..40e1b88 100644
--- a/ssl/test/runner/handshake_server.go
+++ b/ssl/test/runner/handshake_server.go
@@ -8,6 +8,7 @@ import (
"bytes"
"crypto"
"crypto/ecdsa"
+ "crypto/elliptic"
"crypto/rsa"
"crypto/subtle"
"crypto/x509"
@@ -15,6 +16,7 @@ import (
"errors"
"fmt"
"io"
+ "math/big"
)
// serverHandshakeState contains details of a server handshake in progress.
@@ -69,7 +71,7 @@ func (c *Conn) serverHandshake() error {
if err := hs.sendFinished(); err != nil {
return err
}
- if err := hs.readFinished(); err != nil {
+ if err := hs.readFinished(isResume); err != nil {
return err
}
c.didResume = true
@@ -82,7 +84,7 @@ func (c *Conn) serverHandshake() error {
if err := hs.establishKeys(); err != nil {
return err
}
- if err := hs.readFinished(); err != nil {
+ if err := hs.readFinished(isResume); err != nil {
return err
}
if err := hs.sendSessionTicket(); err != nil {
@@ -231,6 +233,10 @@ Curves:
hs.cert = config.getCertificateForName(hs.clientHello.serverName)
}
+ if hs.clientHello.channelIDSupported && config.RequestChannelID {
+ hs.hello.channelIDRequested = true
+ }
+
_, hs.ecdsaOk = hs.cert.PrivateKey.(*ecdsa.PrivateKey)
if hs.checkForResumption() {
@@ -579,7 +585,7 @@ func (hs *serverHandshakeState) establishKeys() error {
return nil
}
-func (hs *serverHandshakeState) readFinished() error {
+func (hs *serverHandshakeState) readFinished(isResume bool) error {
c := hs.c
c.readRecord(recordTypeChangeCipherSpec)
@@ -601,6 +607,36 @@ func (hs *serverHandshakeState) readFinished() error {
c.clientProtocol = nextProto.proto
}
+ if hs.hello.channelIDRequested {
+ msg, err := c.readHandshake()
+ if err != nil {
+ return err
+ }
+ encryptedExtensions, ok := msg.(*encryptedExtensionsMsg)
+ if !ok {
+ c.sendAlert(alertUnexpectedMessage)
+ return unexpectedMessageError(encryptedExtensions, msg)
+ }
+ x := new(big.Int).SetBytes(encryptedExtensions.channelID[0:32])
+ y := new(big.Int).SetBytes(encryptedExtensions.channelID[32:64])
+ r := new(big.Int).SetBytes(encryptedExtensions.channelID[64:96])
+ s := new(big.Int).SetBytes(encryptedExtensions.channelID[96:128])
+ if !elliptic.P256().IsOnCurve(x, y) {
+ return errors.New("tls: invalid channel ID public key")
+ }
+ channelID := &ecdsa.PublicKey{elliptic.P256(), x, y}
+ var resumeHash []byte
+ if isResume {
+ resumeHash = hs.sessionState.handshakeHash
+ }
+ if !ecdsa.Verify(channelID, hs.finishedHash.hashForChannelID(resumeHash), r, s) {
+ return errors.New("tls: invalid channel ID signature")
+ }
+ c.channelID = channelID
+
+ hs.writeClientHash(encryptedExtensions.marshal())
+ }
+
msg, err := c.readHandshake()
if err != nil {
return err
@@ -632,10 +668,11 @@ func (hs *serverHandshakeState) sendSessionTicket() error {
var err error
state := sessionState{
- vers: c.vers,
- cipherSuite: hs.suite.id,
- masterSecret: hs.masterSecret,
- certificates: hs.certsFromClient,
+ vers: c.vers,
+ cipherSuite: hs.suite.id,
+ masterSecret: hs.masterSecret,
+ certificates: hs.certsFromClient,
+ handshakeHash: hs.finishedHash.server.Sum(nil),
}
m.ticket, err = c.encryptTicket(&state)
if err != nil {
diff --git a/ssl/test/runner/prf.go b/ssl/test/runner/prf.go
index 991196f..55a3614 100644
--- a/ssl/test/runner/prf.go
+++ b/ssl/test/runner/prf.go
@@ -120,6 +120,8 @@ var masterSecretLabel = []byte("master secret")
var keyExpansionLabel = []byte("key expansion")
var clientFinishedLabel = []byte("client finished")
var serverFinishedLabel = []byte("server finished")
+var channelIDLabel = []byte("TLS Channel ID signature\x00")
+var channelIDResumeLabel = []byte("Resumption\x00")
func prfForVersion(version uint16, suite *cipherSuite) func(result, secret, label, seed []byte) {
switch version {
@@ -321,3 +323,17 @@ func (h finishedHash) hashForClientCertificate(signatureAndHash signatureAndHash
digest = h.server.Sum(digest)
return digest, crypto.MD5SHA1, nil
}
+
+// hashForChannelID returns the hash to be signed for TLS Channel
+// ID. If a resumption, resumeHash has the previous handshake
+// hash. Otherwise, it is nil.
+func (h finishedHash) hashForChannelID(resumeHash []byte) []byte {
+ hash := sha256.New()
+ hash.Write(channelIDLabel)
+ if resumeHash != nil {
+ hash.Write(channelIDResumeLabel)
+ hash.Write(resumeHash)
+ }
+ hash.Write(h.server.Sum(nil))
+ return hash.Sum(nil)
+}
diff --git a/ssl/test/runner/ticket.go b/ssl/test/runner/ticket.go
index 519543b..74791d6 100644
--- a/ssl/test/runner/ticket.go
+++ b/ssl/test/runner/ticket.go
@@ -18,10 +18,11 @@ import (
// sessionState contains the information that is serialized into a session
// ticket in order to later resume a connection.
type sessionState struct {
- vers uint16
- cipherSuite uint16
- masterSecret []byte
- certificates [][]byte
+ vers uint16
+ cipherSuite uint16
+ masterSecret []byte
+ handshakeHash []byte
+ certificates [][]byte
}
func (s *sessionState) equal(i interface{}) bool {
@@ -32,7 +33,8 @@ func (s *sessionState) equal(i interface{}) bool {
if s.vers != s1.vers ||
s.cipherSuite != s1.cipherSuite ||
- !bytes.Equal(s.masterSecret, s1.masterSecret) {
+ !bytes.Equal(s.masterSecret, s1.masterSecret) ||
+ !bytes.Equal(s.handshakeHash, s1.handshakeHash) {
return false
}
@@ -50,7 +52,7 @@ func (s *sessionState) equal(i interface{}) bool {
}
func (s *sessionState) marshal() []byte {
- length := 2 + 2 + 2 + len(s.masterSecret) + 2
+ length := 2 + 2 + 2 + len(s.masterSecret) + 2 + len(s.handshakeHash) + 2
for _, cert := range s.certificates {
length += 4 + len(cert)
}
@@ -67,6 +69,12 @@ func (s *sessionState) marshal() []byte {
copy(x, s.masterSecret)
x = x[len(s.masterSecret):]
+ x[0] = byte(len(s.handshakeHash) >> 8)
+ x[1] = byte(len(s.handshakeHash))
+ x = x[2:]
+ copy(x, s.handshakeHash)
+ x = x[len(s.handshakeHash):]
+
x[0] = byte(len(s.certificates) >> 8)
x[1] = byte(len(s.certificates))
x = x[2:]
@@ -103,6 +111,19 @@ func (s *sessionState) unmarshal(data []byte) bool {
return false
}
+ handshakeHashLen := int(data[0])<<8 | int(data[1])
+ data = data[2:]
+ if len(data) < handshakeHashLen {
+ return false
+ }
+
+ s.handshakeHash = data[:handshakeHashLen]
+ data = data[handshakeHashLen:]
+
+ if len(data) < 2 {
+ return false
+ }
+
numCerts := int(data[0])<<8 | int(data[1])
data = data[2:]