diff --git a/common.go b/common.go index 8840bf8..966a487 100644 --- a/common.go +++ b/common.go @@ -90,6 +90,7 @@ const ( extensionSignatureAlgorithms uint16 = 13 extensionALPN uint16 = 16 extensionSCT uint16 = 18 + extensionExtendedMasterSecret uint16 = 23 extensionSessionTicket uint16 = 35 extensionPreSharedKey uint16 = 41 extensionEarlyData uint16 = 42 @@ -271,12 +272,8 @@ type ConnectionState struct { OCSPResponse []byte // TLSUnique contains the "tls-unique" channel binding value (see RFC 5929, - // Section 3). This value will be nil for TLS 1.3 connections and for all - // resumed connections. - // - // Deprecated: there are conditions in which this value might not be unique - // to a connection. See the Security Considerations sections of RFC 5705 and - // RFC 7627, and https://mitls.org/pages/attacks/3SHAKE#channelbindings. + // Section 3). This value will be nil for TLS 1.3 connections and for + // resumed connections that don't support Extended Master Secret (RFC 7627). TLSUnique []byte // ekm is a closure exposed via ExportKeyingMaterial. @@ -287,6 +284,10 @@ type ConnectionState struct { // slice as defined in RFC 5705. If context is nil, it is not used as part of // the seed. If the connection was set to allow renegotiation via // Config.Renegotiation, this function will return an error. +// +// There are conditions in which the returned values might not be unique to a +// connection. See the Security Considerations sections of RFC 5705 and RFC 7627, +// and https://mitls.org/pages/attacks/3SHAKE#channelbindings. func (cs *ConnectionState) ExportKeyingMaterial(label string, context []byte, length int) ([]byte, error) { return cs.ekm(label, context, length) } diff --git a/conn.go b/conn.go index 7260563..9c1f06f 100644 --- a/conn.go +++ b/conn.go @@ -50,6 +50,7 @@ type Conn struct { // connection so far. If renegotiation is disabled then this is either // zero or one. handshakes int + extMasterSecret bool didResume bool // whether this connection was a session resumption cipherSuite uint16 ocspResponse []byte // stapled OCSP response @@ -1670,7 +1671,7 @@ func (c *Conn) connectionStateLocked() ConnectionState { state.VerifiedChains = c.verifiedChains state.SignedCertificateTimestamps = c.scts state.OCSPResponse = c.ocspResponse - if !c.didResume && c.vers != VersionTLS13 { + if (!c.didResume || c.extMasterSecret) && c.vers != VersionTLS13 { if c.clientFinishedIsFirst { state.TLSUnique = c.clientFinished[:] } else { diff --git a/handshake_client.go b/handshake_client.go index cd4982e..5b42d30 100644 --- a/handshake_client.go +++ b/handshake_client.go @@ -72,6 +72,7 @@ func (c *Conn) makeClientHello() (*clientHelloMsg, *ecdh.PrivateKey, error) { vers: clientHelloVersion, compressionMethods: []uint8{compressionNone}, random: make([]byte, 32), + extendedMasterSecret: true, ocspStapling: true, scts: true, serverName: hostnameInSNI(config.ServerName), @@ -645,6 +646,19 @@ func (hs *clientHandshakeState) doFullHandshake() error { } } + if hs.serverHello.extendedMasterSecret { + c.extMasterSecret = true + hs.masterSecret = extMasterFromPreMasterSecret(c.vers, hs.suite, preMasterSecret, + hs.finishedHash.Sum()) + } else { + hs.masterSecret = masterFromPreMasterSecret(c.vers, hs.suite, preMasterSecret, + hs.hello.random, hs.serverHello.random) + } + if err := c.config.writeKeyLog(keyLogLabelTLS12, hs.hello.random, hs.masterSecret); err != nil { + c.sendAlert(alertInternalError) + return errors.New("tls: failed to write to key log: " + err.Error()) + } + if chainToSend != nil && len(chainToSend.Certificate) > 0 { certVerify := &certificateVerifyMsg{} @@ -692,12 +706,6 @@ func (hs *clientHandshakeState) doFullHandshake() error { } } - hs.masterSecret = masterFromPreMasterSecret(c.vers, hs.suite, preMasterSecret, hs.hello.random, hs.serverHello.random) - if err := c.config.writeKeyLog(keyLogLabelTLS12, hs.hello.random, hs.masterSecret); err != nil { - c.sendAlert(alertInternalError) - return errors.New("tls: failed to write to key log: " + err.Error()) - } - hs.finishedHash.discardHandshakeBuffer() return nil @@ -784,8 +792,15 @@ func (hs *clientHandshakeState) processServerHello() (bool, error) { return false, errors.New("tls: server resumed a session with a different cipher suite") } + // RFC 7627, Section 5.3 + if hs.session.extMasterSecret != hs.serverHello.extendedMasterSecret { + c.sendAlert(alertHandshakeFailure) + return false, errors.New("tls: server resumed a session with a different EMS extension") + } + // Restore master secret and certificates from previous state hs.masterSecret = hs.session.secret + c.extMasterSecret = hs.session.extMasterSecret c.peerCertificates = hs.session.peerCertificates c.activeCertHandles = hs.c.activeCertHandles c.verifiedChains = hs.session.verifiedChains diff --git a/handshake_client_tls13.go b/handshake_client_tls13.go index a96698a..ed6e807 100644 --- a/handshake_client_tls13.go +++ b/handshake_client_tls13.go @@ -136,6 +136,7 @@ func (hs *clientHandshakeStateTLS13) checkServerHelloOrHRR() error { if hs.serverHello.ocspStapling || hs.serverHello.ticketSupported || + hs.serverHello.extendedMasterSecret || hs.serverHello.secureRenegotiationSupported || len(hs.serverHello.secureRenegotiation) != 0 || len(hs.serverHello.alpnProtocol) != 0 || diff --git a/handshake_messages.go b/handshake_messages.go index ae2b892..2a18bb4 100644 --- a/handshake_messages.go +++ b/handshake_messages.go @@ -84,6 +84,7 @@ type clientHelloMsg struct { supportedSignatureAlgorithmsCert []SignatureScheme secureRenegotiationSupported bool secureRenegotiation []byte + extendedMasterSecret bool alpnProtocols []string scts bool supportedVersions []uint16 @@ -181,6 +182,11 @@ func (m *clientHelloMsg) marshal() ([]byte, error) { }) }) } + if m.extendedMasterSecret { + // RFC 7627 + exts.AddUint16(extensionExtendedMasterSecret) + exts.AddUint16(0) // empty extension_data + } if len(m.alpnProtocols) > 0 { // RFC 7301, Section 3.1 exts.AddUint16(extensionALPN) @@ -510,6 +516,9 @@ func (m *clientHelloMsg) unmarshal(data []byte) bool { return false } m.secureRenegotiationSupported = true + case extensionExtendedMasterSecret: + // RFC 7627 + m.extendedMasterSecret = true case extensionALPN: // RFC 7301, Section 3.1 var protoList cryptobyte.String @@ -627,6 +636,7 @@ type serverHelloMsg struct { ticketSupported bool secureRenegotiationSupported bool secureRenegotiation []byte + extendedMasterSecret bool alpnProtocol string scts [][]byte supportedVersion uint16 @@ -662,6 +672,10 @@ func (m *serverHelloMsg) marshal() ([]byte, error) { }) }) } + if m.extendedMasterSecret { + exts.AddUint16(extensionExtendedMasterSecret) + exts.AddUint16(0) // empty extension_data + } if len(m.alpnProtocol) > 0 { exts.AddUint16(extensionALPN) exts.AddUint16LengthPrefixed(func(exts *cryptobyte.Builder) { @@ -802,6 +816,8 @@ func (m *serverHelloMsg) unmarshal(data []byte) bool { return false } m.secureRenegotiationSupported = true + case extensionExtendedMasterSecret: + m.extendedMasterSecret = true case extensionALPN: var protoList cryptobyte.String if !extData.ReadUint16LengthPrefixed(&protoList) || protoList.Empty() { diff --git a/handshake_server.go b/handshake_server.go index a84c51b..5fa4281 100644 --- a/handshake_server.go +++ b/handshake_server.go @@ -214,6 +214,7 @@ func (hs *serverHandshakeState) processClientHello() error { return errors.New("tls: initial handshake had non-empty renegotiation extension") } + hs.hello.extendedMasterSecret = hs.clientHello.extendedMasterSecret hs.hello.secureRenegotiationSupported = hs.clientHello.secureRenegotiationSupported hs.hello.compressionMethod = compressionNone if len(hs.clientHello.serverName) > 0 { @@ -471,6 +472,17 @@ func (hs *serverHandshakeState) checkForResumption() error { return nil } + // RFC 7627, Section 5.3 + if !sessionState.extMasterSecret && hs.clientHello.extendedMasterSecret { + return nil + } + if sessionState.extMasterSecret && !hs.clientHello.extendedMasterSecret { + // Aborting is somewhat harsh, but it's a MUST and it would indicate a + // weird downgrade in client capabilities. + return errors.New("tls: session supported extended_master_secret but client does not") + } + + c.extMasterSecret = sessionState.extMasterSecret hs.sessionState = sessionState hs.suite = suite c.didResume = true @@ -647,7 +659,14 @@ func (hs *serverHandshakeState) doFullHandshake() error { c.sendAlert(alertHandshakeFailure) return err } - hs.masterSecret = masterFromPreMasterSecret(c.vers, hs.suite, preMasterSecret, hs.clientHello.random, hs.hello.random) + if hs.hello.extendedMasterSecret { + c.extMasterSecret = true + hs.masterSecret = extMasterFromPreMasterSecret(c.vers, hs.suite, preMasterSecret, + hs.finishedHash.Sum()) + } else { + hs.masterSecret = masterFromPreMasterSecret(c.vers, hs.suite, preMasterSecret, + hs.clientHello.random, hs.hello.random) + } if err := c.config.writeKeyLog(keyLogLabelTLS12, hs.clientHello.random, hs.masterSecret); err != nil { c.sendAlert(alertInternalError) return err diff --git a/prf.go b/prf.go index f0dac30..648c64b 100644 --- a/prf.go +++ b/prf.go @@ -80,6 +80,7 @@ const ( ) var masterSecretLabel = []byte("master secret") +var extendedMasterSecretLabel = []byte("extended master secret") var keyExpansionLabel = []byte("key expansion") var clientFinishedLabel = []byte("client finished") var serverFinishedLabel = []byte("server finished") @@ -115,6 +116,14 @@ func masterFromPreMasterSecret(version uint16, suite *cipherSuite, preMasterSecr return masterSecret } +// extMasterFromPreMasterSecret generates the extended master secret from the +// pre-master secret. See RFC 7627. +func extMasterFromPreMasterSecret(version uint16, suite *cipherSuite, preMasterSecret, transcript []byte) []byte { + masterSecret := make([]byte, masterSecretLength) + prfForVersion(version, suite)(masterSecret, preMasterSecret, extendedMasterSecretLabel, transcript) + 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. diff --git a/ticket.go b/ticket.go index 52ef2cb..adb6c3c 100644 --- a/ticket.go +++ b/ticket.go @@ -34,6 +34,7 @@ type SessionState struct { // uint64 created_at; // opaque secret<1..2^8-1>; // opaque extra<0..2^24-1>; + // uint8 ext_master_secret = { 0, 1 }; // uint8 early_data = { 0, 1 }; // CertificateEntry certificate_list<0..2^24-1>; // select (SessionState.early_data) { @@ -81,6 +82,7 @@ type SessionState struct { // which the ticket was received on the client. createdAt uint64 // seconds since UNIX epoch secret []byte // master secret for TLS 1.2, or the PSK for TLS 1.3 + extMasterSecret bool peerCertificates []*x509.Certificate activeCertHandles []*activeCert ocspResponse []byte @@ -117,6 +119,11 @@ func (s *SessionState) Bytes() ([]byte, error) { b.AddUint24LengthPrefixed(func(b *cryptobyte.Builder) { b.AddBytes(s.Extra) }) + if s.extMasterSecret { + b.AddUint8(1) + } else { + b.AddUint8(0) + } if s.EarlyData { b.AddUint8(1) } else { @@ -173,7 +180,7 @@ func certificatesToBytesSlice(certs []*x509.Certificate) [][]byte { func ParseSessionState(data []byte) (*SessionState, error) { ss := &SessionState{} s := cryptobyte.String(data) - var typ, earlyData uint8 + var typ, extMasterSecret, earlyData uint8 var cert Certificate if !s.ReadUint16(&ss.version) || !s.ReadUint8(&typ) || @@ -182,11 +189,20 @@ func ParseSessionState(data []byte) (*SessionState, error) { !readUint64(&s, &ss.createdAt) || !readUint8LengthPrefixed(&s, &ss.secret) || !readUint24LengthPrefixed(&s, &ss.Extra) || + !s.ReadUint8(&extMasterSecret) || !s.ReadUint8(&earlyData) || len(ss.secret) == 0 || !unmarshalCertificate(&s, &cert) { return nil, errors.New("tls: invalid session encoding") } + switch extMasterSecret { + case 0: + ss.extMasterSecret = false + case 1: + ss.extMasterSecret = true + default: + return nil, errors.New("tls: invalid session encoding") + } switch earlyData { case 0: ss.EarlyData = false @@ -279,6 +295,7 @@ func (c *Conn) sessionState() (*SessionState, error) { ocspResponse: c.ocspResponse, scts: c.scts, isClient: c.isClient, + extMasterSecret: c.extMasterSecret, verifiedChains: verifiedChains, }, nil }