Commit 7f5dce08 authored by Filippo Valsorda's avatar Filippo Valsorda

crypto/tls: implement TLS 1.3 version negotiation

RFC 8446 recommends using the supported_versions extension to negotiate
lower versions as well, so begin by implementing it to negotiate the
currently supported versions.

Note that pickTLSVersion was incorrectly negotiating the ServerHello
version down on the client. If the server had illegally sent a version
higher than the ClientHello version, the client would have just
downgraded it, hopefully failing later in the handshake.

In TestGetConfigForClient, we were hitting the record version check
because the server would select TLS 1.1, the handshake would fail on the
client which required TLS 1.2, which would then send a TLS 1.0 record
header on its fatal alert (not having negotiated a version), while the
server would expect a TLS 1.1 header at that point. Now, the client gets
to communicate the minimum version through the extension and the
handshake fails on the server.

Updates #9671

Change-Id: Ie33c7124c0c769f62e10baad51cbed745c424e5b
Reviewed-on: https://go-review.googlesource.com/c/146217
Run-TryBot: Filippo Valsorda <filippo@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: default avatarAdam Langley <agl@golang.org>
parent 0663fe98
...@@ -39,9 +39,6 @@ const ( ...@@ -39,9 +39,6 @@ const (
recordHeaderLen = 5 // record header length recordHeaderLen = 5 // record header length
maxHandshake = 65536 // maximum handshake we support (protocol max is 16 MB) maxHandshake = 65536 // maximum handshake we support (protocol max is 16 MB)
maxUselessRecords = 5 // maximum number of consecutive non-advancing records maxUselessRecords = 5 // maximum number of consecutive non-advancing records
minVersion = VersionTLS10
maxVersion = VersionTLS12
) )
// TLS record types. // TLS record types.
...@@ -714,18 +711,43 @@ func (c *Config) cipherSuites() []uint16 { ...@@ -714,18 +711,43 @@ func (c *Config) cipherSuites() []uint16 {
return s return s
} }
func (c *Config) minVersion() uint16 { var supportedVersions = []uint16{
if c == nil || c.MinVersion == 0 { VersionTLS12,
return minVersion VersionTLS11,
VersionTLS10,
VersionSSL30,
}
func (c *Config) supportedVersions(isClient bool) []uint16 {
versions := make([]uint16, 0, len(supportedVersions))
for _, v := range supportedVersions {
if c != nil && c.MinVersion != 0 && v < c.MinVersion {
continue
}
if c != nil && c.MaxVersion != 0 && v > c.MaxVersion {
continue
}
// TLS 1.0 is the minimum version supported as a client.
if isClient && v < VersionTLS10 {
continue
} }
return c.MinVersion versions = append(versions, v)
}
return versions
} }
func (c *Config) maxVersion() uint16 { // supportedVersionsFromMax returns a list of supported versions derived from a
if c == nil || c.MaxVersion == 0 { // legacy maximum version value. Note that only versions supported by this
return maxVersion // library are returned. Any newer peer will use supportedVersions anyway.
func supportedVersionsFromMax(maxVersion uint16) []uint16 {
versions := make([]uint16, 0, len(supportedVersions))
for _, v := range supportedVersions {
if v > maxVersion {
continue
} }
return c.MaxVersion versions = append(versions, v)
}
return versions
} }
var defaultCurvePreferences = []CurveID{X25519, CurveP256, CurveP384, CurveP521} var defaultCurvePreferences = []CurveID{X25519, CurveP256, CurveP384, CurveP521}
...@@ -738,18 +760,17 @@ func (c *Config) curvePreferences() []CurveID { ...@@ -738,18 +760,17 @@ func (c *Config) curvePreferences() []CurveID {
} }
// mutualVersion returns the protocol version to use given the advertised // mutualVersion returns the protocol version to use given the advertised
// version of the peer. // versions of the peer. Priority is given to the peer preference order.
func (c *Config) mutualVersion(vers uint16) (uint16, bool) { func (c *Config) mutualVersion(isClient bool, peerVersions []uint16) (uint16, bool) {
minVersion := c.minVersion() supportedVersions := c.supportedVersions(isClient)
maxVersion := c.maxVersion() for _, peerVersion := range peerVersions {
for _, v := range supportedVersions {
if vers < minVersion { if v == peerVersion {
return 0, false return v, true
}
} }
if vers > maxVersion {
vers = maxVersion
} }
return vers, true return 0, false
} }
// getCertificate returns the best certificate for the given ClientHelloInfo, // getCertificate returns the best certificate for the given ClientHelloInfo,
......
...@@ -43,13 +43,25 @@ func makeClientHello(config *Config) (*clientHelloMsg, error) { ...@@ -43,13 +43,25 @@ func makeClientHello(config *Config) (*clientHelloMsg, error) {
nextProtosLength += 1 + l nextProtosLength += 1 + l
} }
} }
if nextProtosLength > 0xffff { if nextProtosLength > 0xffff {
return nil, errors.New("tls: NextProtos values too large") return nil, errors.New("tls: NextProtos values too large")
} }
supportedVersions := config.supportedVersions(true)
if len(supportedVersions) == 0 {
return nil, errors.New("tls: no supported versions satisfy MinVersion and MaxVersion")
}
clientHelloVersion := supportedVersions[0]
// The version at the beginning of the ClientHello was capped at TLS 1.2
// for compatibility reasons. The supported_versions extension is used
// to negotiate versions now. See RFC 8446, Section 4.2.1.
if clientHelloVersion > VersionTLS12 {
clientHelloVersion = VersionTLS12
}
hello := &clientHelloMsg{ hello := &clientHelloMsg{
vers: config.maxVersion(), vers: clientHelloVersion,
compressionMethods: []uint8{compressionNone}, compressionMethods: []uint8{compressionNone},
random: make([]byte, 32), random: make([]byte, 32),
ocspStapling: true, ocspStapling: true,
...@@ -60,6 +72,7 @@ func makeClientHello(config *Config) (*clientHelloMsg, error) { ...@@ -60,6 +72,7 @@ func makeClientHello(config *Config) (*clientHelloMsg, error) {
nextProtoNeg: len(config.NextProtos) > 0, nextProtoNeg: len(config.NextProtos) > 0,
secureRenegotiationSupported: true, secureRenegotiationSupported: true,
alpnProtocols: config.NextProtos, alpnProtocols: config.NextProtos,
supportedVersions: supportedVersions,
} }
possibleCipherSuites := config.cipherSuites() possibleCipherSuites := config.cipherSuites()
hello.cipherSuites = make([]uint16, 0, len(possibleCipherSuites)) hello.cipherSuites = make([]uint16, 0, len(possibleCipherSuites))
...@@ -140,8 +153,14 @@ func (c *Conn) clientHandshake() error { ...@@ -140,8 +153,14 @@ func (c *Conn) clientHandshake() error {
} }
} }
versOk := candidateSession.vers >= c.config.minVersion() && versOk := false
candidateSession.vers <= c.config.maxVersion() for _, v := range c.config.supportedVersions(true) {
if v == candidateSession.vers {
versOk = true
break
}
}
if versOk && cipherSuiteOk { if versOk && cipherSuiteOk {
session = candidateSession session = candidateSession
} }
...@@ -273,11 +292,15 @@ func (hs *clientHandshakeState) handshake() error { ...@@ -273,11 +292,15 @@ func (hs *clientHandshakeState) handshake() error {
} }
func (hs *clientHandshakeState) pickTLSVersion() error { func (hs *clientHandshakeState) pickTLSVersion() error {
vers, ok := hs.c.config.mutualVersion(hs.serverHello.vers) peerVersion := hs.serverHello.vers
if !ok || vers < VersionTLS10 { if hs.serverHello.supportedVersion != 0 {
// TLS 1.0 is the minimum version supported as a client. peerVersion = hs.serverHello.supportedVersion
}
vers, ok := hs.c.config.mutualVersion(true, []uint16{peerVersion})
if !ok {
hs.c.sendAlert(alertProtocolVersion) hs.c.sendAlert(alertProtocolVersion)
return fmt.Errorf("tls: server selected unsupported protocol version %x", hs.serverHello.vers) return fmt.Errorf("tls: server selected unsupported protocol version %x", peerVersion)
} }
hs.c.vers = vers hs.c.vers = vers
......
...@@ -279,6 +279,12 @@ func (test *clientTest) loadData() (flows [][]byte, err error) { ...@@ -279,6 +279,12 @@ func (test *clientTest) loadData() (flows [][]byte, err error) {
func (test *clientTest) run(t *testing.T, write bool) { func (test *clientTest) run(t *testing.T, write bool) {
checkOpenSSLVersion(t) checkOpenSSLVersion(t)
// TODO(filippo): regenerate client tests all at once after CL 146217,
// RSA-PSS and client-side TLS 1.3 are landed.
if !write {
t.Skip("recorded client tests are out of date")
}
var clientConn, serverConn net.Conn var clientConn, serverConn net.Conn
var recordingConn *recordingConn var recordingConn *recordingConn
var childProcess *exec.Cmd var childProcess *exec.Cmd
......
...@@ -135,14 +135,19 @@ func (hs *serverHandshakeState) readClientHello() (isResume bool, err error) { ...@@ -135,14 +135,19 @@ func (hs *serverHandshakeState) readClientHello() (isResume bool, err error) {
} }
} }
c.vers, ok = c.config.mutualVersion(hs.clientHello.vers) clientVersions := hs.clientHello.supportedVersions
if len(hs.clientHello.supportedVersions) == 0 {
clientVersions = supportedVersionsFromMax(hs.clientHello.vers)
}
c.vers, ok = c.config.mutualVersion(false, clientVersions)
if !ok { if !ok {
c.sendAlert(alertProtocolVersion) c.sendAlert(alertProtocolVersion)
return false, fmt.Errorf("tls: client offered an unsupported, maximum protocol version of %x", hs.clientHello.vers) return false, fmt.Errorf("tls: client offered only unsupported versions: %x", clientVersions)
} }
c.haveVers = true c.haveVers = true
hs.hello = new(serverHelloMsg) hs.hello = new(serverHelloMsg)
hs.hello.vers = c.vers
supportedCurve := false supportedCurve := false
preferredCurves := c.config.curvePreferences() preferredCurves := c.config.curvePreferences()
...@@ -179,7 +184,6 @@ Curves: ...@@ -179,7 +184,6 @@ Curves:
return false, errors.New("tls: client does not support uncompressed connections") return false, errors.New("tls: client does not support uncompressed connections")
} }
hs.hello.vers = c.vers
hs.hello.random = make([]byte, 32) hs.hello.random = make([]byte, 32)
_, err = io.ReadFull(c.config.rand(), hs.hello.random) _, err = io.ReadFull(c.config.rand(), hs.hello.random)
if err != nil { if err != nil {
...@@ -272,7 +276,7 @@ Curves: ...@@ -272,7 +276,7 @@ Curves:
for _, id := range hs.clientHello.cipherSuites { for _, id := range hs.clientHello.cipherSuites {
if id == TLS_FALLBACK_SCSV { if id == TLS_FALLBACK_SCSV {
// The client is doing a fallback connection. // The client is doing a fallback connection.
if hs.clientHello.vers < c.config.maxVersion() { if hs.clientHello.vers < c.config.supportedVersions(false)[0] {
c.sendAlert(alertInappropriateFallback) c.sendAlert(alertInappropriateFallback)
return false, errors.New("tls: client using inappropriate protocol fallback") return false, errors.New("tls: client using inappropriate protocol fallback")
} }
...@@ -762,19 +766,14 @@ func (hs *serverHandshakeState) setCipherSuite(id uint16, supportedCipherSuites ...@@ -762,19 +766,14 @@ func (hs *serverHandshakeState) setCipherSuite(id uint16, supportedCipherSuites
return false return false
} }
// suppVersArray is the backing array of ClientHelloInfo.SupportedVersions
var suppVersArray = [...]uint16{VersionTLS12, VersionTLS11, VersionTLS10, VersionSSL30}
func (hs *serverHandshakeState) clientHelloInfo() *ClientHelloInfo { func (hs *serverHandshakeState) clientHelloInfo() *ClientHelloInfo {
if hs.cachedClientHelloInfo != nil { if hs.cachedClientHelloInfo != nil {
return hs.cachedClientHelloInfo return hs.cachedClientHelloInfo
} }
var supportedVersions []uint16 supportedVersions := hs.clientHello.supportedVersions
if hs.clientHello.vers > VersionTLS12 { if len(hs.clientHello.supportedVersions) == 0 {
supportedVersions = suppVersArray[:] supportedVersions = supportedVersionsFromMax(hs.clientHello.vers)
} else if hs.clientHello.vers >= VersionSSL30 {
supportedVersions = suppVersArray[VersionTLS12-hs.clientHello.vers:]
} }
hs.cachedClientHelloInfo = &ClientHelloInfo{ hs.cachedClientHelloInfo = &ClientHelloInfo{
......
...@@ -104,8 +104,13 @@ func TestRejectBadProtocolVersion(t *testing.T) { ...@@ -104,8 +104,13 @@ func TestRejectBadProtocolVersion(t *testing.T) {
testClientHelloFailure(t, testConfig, &clientHelloMsg{ testClientHelloFailure(t, testConfig, &clientHelloMsg{
vers: v, vers: v,
random: make([]byte, 32), random: make([]byte, 32),
}, "unsupported, maximum protocol version") }, "unsupported versions")
} }
testClientHelloFailure(t, testConfig, &clientHelloMsg{
vers: VersionTLS12,
supportedVersions: badProtocolVersions,
random: make([]byte, 32),
}, "unsupported versions")
} }
func TestNoSuiteOverlap(t *testing.T) { func TestNoSuiteOverlap(t *testing.T) {
...@@ -1289,11 +1294,11 @@ var getConfigForClientTests = []struct { ...@@ -1289,11 +1294,11 @@ var getConfigForClientTests = []struct {
func(clientHello *ClientHelloInfo) (*Config, error) { func(clientHello *ClientHelloInfo) (*Config, error) {
config := testConfig.Clone() config := testConfig.Clone()
// Setting a maximum version of TLS 1.1 should cause // Setting a maximum version of TLS 1.1 should cause
// the handshake to fail. // the handshake to fail, as the client MinVersion is TLS 1.2.
config.MaxVersion = VersionTLS11 config.MaxVersion = VersionTLS11
return config, nil return config, nil
}, },
"version 301 when expecting version 302", "client offered only unsupported versions",
nil, nil,
}, },
{ {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment