Skip to content

Commit eb93c68

Browse files
committed
crypto/tls: select only compatible chains from Certificates
Now that we have a full implementation of the logic to check certificate compatibility, we can let applications just list multiple chains in Certificates (for example, an RSA and an ECDSA one) and choose the most appropriate automatically. NameToCertificate only maps each name to one chain, so simply deprecate it, and while at it simplify its implementation by not stripping trailing dots from the SNI (which is specified not to have any, see RFC 6066, Section 3) and by not supporting multi-level wildcards, which are not a thing in the WebPKI (and in crypto/x509). The performance of SupportsCertificate without Leaf is poor, but doesn't affect current users. For now document that, and address it properly in the next cycle. See #35504. While cleaning up the Certificates/GetCertificate/GetConfigForClient behavior, also support leaving Certificates/GetCertificate nil if GetConfigForClient is set, and send unrecognized_name when there are no available certificates. Fixes #29139 Fixes #18377 Change-Id: I26604db48806fe4d608388e55da52f34b7ca4566 Reviewed-on: https://go-review.googlesource.com/c/go/+/205059 Run-TryBot: Filippo Valsorda <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Katie Hockman <[email protected]>
1 parent 4b21642 commit eb93c68

File tree

7 files changed

+81
-47
lines changed

7 files changed

+81
-47
lines changed

src/crypto/tls/alert.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ const (
4040
alertNoRenegotiation alert = 100
4141
alertMissingExtension alert = 109
4242
alertUnsupportedExtension alert = 110
43+
alertUnrecognizedName alert = 112
4344
alertNoApplicationProtocol alert = 120
4445
)
4546

@@ -69,6 +70,7 @@ var alertText = map[alert]string{
6970
alertNoRenegotiation: "no renegotiation",
7071
alertMissingExtension: "missing extension",
7172
alertUnsupportedExtension: "unsupported extension",
73+
alertUnrecognizedName: "unrecognized name",
7274
alertNoApplicationProtocol: "no application protocol",
7375
}
7476

src/crypto/tls/common.go

Lines changed: 40 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -457,19 +457,26 @@ type Config struct {
457457
// If Time is nil, TLS uses time.Now.
458458
Time func() time.Time
459459

460-
// Certificates contains one or more certificate chains to present to
461-
// the other side of the connection. Server configurations must include
462-
// at least one certificate or else set GetCertificate. Clients doing
463-
// client-authentication may set either Certificates or
464-
// GetClientCertificate.
460+
// Certificates contains one or more certificate chains to present to the
461+
// other side of the connection. The first certificate compatible with the
462+
// peer's requirements is selected automatically.
463+
//
464+
// Server configurations must set one of Certificates, GetCertificate or
465+
// GetConfigForClient. Clients doing client-authentication may set either
466+
// Certificates or GetClientCertificate.
467+
//
468+
// Note: if there are multiple Certificates, and they don't have the
469+
// optional field Leaf set, certificate selection will incur a significant
470+
// per-handshake performance cost.
465471
Certificates []Certificate
466472

467473
// NameToCertificate maps from a certificate name to an element of
468474
// Certificates. Note that a certificate name can be of the form
469475
// '*.example.com' and so doesn't have to be a domain name as such.
470-
// See Config.BuildNameToCertificate
471-
// The nil value causes the first element of Certificates to be used
472-
// for all connections.
476+
//
477+
// Deprecated: NameToCertificate only allows associating a single
478+
// certificate with a given name. Leave this field nil to let the library
479+
// select the first compatible chain from Certificates.
473480
NameToCertificate map[string]*Certificate
474481

475482
// GetCertificate returns a Certificate based on the given
@@ -478,7 +485,7 @@ type Config struct {
478485
//
479486
// If GetCertificate is nil or returns nil, then the certificate is
480487
// retrieved from NameToCertificate. If NameToCertificate is nil, the
481-
// first element of Certificates will be used.
488+
// best element of Certificates will be used.
482489
GetCertificate func(*ClientHelloInfo) (*Certificate, error)
483490

484491
// GetClientCertificate, if not nil, is called when a server requests a
@@ -869,6 +876,8 @@ func (c *Config) mutualVersion(peerVersions []uint16) (uint16, bool) {
869876
return 0, false
870877
}
871878

879+
var errNoCertificates = errors.New("tls: no certificates configured")
880+
872881
// getCertificate returns the best certificate for the given ClientHelloInfo,
873882
// defaulting to the first element of c.Certificates.
874883
func (c *Config) getCertificate(clientHello *ClientHelloInfo) (*Certificate, error) {
@@ -881,31 +890,32 @@ func (c *Config) getCertificate(clientHello *ClientHelloInfo) (*Certificate, err
881890
}
882891

883892
if len(c.Certificates) == 0 {
884-
return nil, errors.New("tls: no certificates configured")
893+
return nil, errNoCertificates
885894
}
886895

887-
if len(c.Certificates) == 1 || c.NameToCertificate == nil {
896+
if len(c.Certificates) == 1 {
888897
// There's only one choice, so no point doing any work.
889898
return &c.Certificates[0], nil
890899
}
891900

892-
name := strings.ToLower(clientHello.ServerName)
893-
for len(name) > 0 && name[len(name)-1] == '.' {
894-
name = name[:len(name)-1]
895-
}
896-
897-
if cert, ok := c.NameToCertificate[name]; ok {
898-
return cert, nil
901+
if c.NameToCertificate != nil {
902+
name := strings.ToLower(clientHello.ServerName)
903+
if cert, ok := c.NameToCertificate[name]; ok {
904+
return cert, nil
905+
}
906+
if len(name) > 0 {
907+
labels := strings.Split(name, ".")
908+
labels[0] = "*"
909+
wildcardName := strings.Join(labels, ".")
910+
if cert, ok := c.NameToCertificate[wildcardName]; ok {
911+
return cert, nil
912+
}
913+
}
899914
}
900915

901-
// try replacing labels in the name with wildcards until we get a
902-
// match.
903-
labels := strings.Split(name, ".")
904-
for i := range labels {
905-
labels[i] = "*"
906-
candidate := strings.Join(labels, ".")
907-
if cert, ok := c.NameToCertificate[candidate]; ok {
908-
return cert, nil
916+
for _, cert := range c.Certificates {
917+
if err := clientHello.SupportsCertificate(&cert); err == nil {
918+
return &cert, nil
909919
}
910920
}
911921

@@ -1109,6 +1119,10 @@ func (cri *CertificateRequestInfo) SupportsCertificate(c *Certificate) error {
11091119
// BuildNameToCertificate parses c.Certificates and builds c.NameToCertificate
11101120
// from the CommonName and SubjectAlternateName fields of each of the leaf
11111121
// certificates.
1122+
//
1123+
// Deprecated: NameToCertificate only allows associating a single certificate
1124+
// with a given name. Leave that field nil to let the library select the first
1125+
// compatible chain from Certificates.
11121126
func (c *Config) BuildNameToCertificate() {
11131127
c.NameToCertificate = make(map[string]*Certificate)
11141128
for i := range c.Certificates {

src/crypto/tls/conn_test.go

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,6 @@ var certWildcardExampleCom = `308201743082011ea003020102021100a7aa6297c9416a4633
7272

7373
var certFooExampleCom = `308201753082011fa00302010202101bbdb6070b0aeffc49008cde74deef29300d06092a864886f70d01010b050030123110300e060355040a130741636d6520436f301e170d3136303831373231343234345a170d3137303831373231343234345a30123110300e060355040a130741636d6520436f305c300d06092a864886f70d0101010500034b003048024100f00ac69d8ca2829f26216c7b50f1d4bbabad58d447706476cd89a2f3e1859943748aa42c15eedc93ac7c49e40d3b05ed645cb6b81c4efba60d961f44211a54eb0203010001a351304f300e0603551d0f0101ff0404030205a030130603551d25040c300a06082b06010505070301300c0603551d130101ff04023000301a0603551d1104133011820f666f6f2e6578616d706c652e636f6d300d06092a864886f70d01010b0500034100a0957fca6d1e0f1ef4b247348c7a8ca092c29c9c0ecc1898ea6b8065d23af6d922a410dd2335a0ea15edd1394cef9f62c9e876a21e35250a0b4fe1ddceba0f36`
7474

75-
var certDoubleWildcardExampleCom = `308201753082011fa003020102021039d262d8538db8ffba30d204e02ddeb5300d06092a864886f70d01010b050030123110300e060355040a130741636d6520436f301e170d3136303831373231343331335a170d3137303831373231343331335a30123110300e060355040a130741636d6520436f305c300d06092a864886f70d0101010500034b003048024100abb6bd84b8b9be3fb9415d00f22b4ddcaec7c99855b9d818c09003e084578430e5cfd2e35faa3561f036d496aa43a9ca6e6cf23c72a763c04ae324004f6cbdbb0203010001a351304f300e0603551d0f0101ff0404030205a030130603551d25040c300a06082b06010505070301300c0603551d130101ff04023000301a0603551d1104133011820f2a2e2a2e6578616d706c652e636f6d300d06092a864886f70d01010b05000341004837521004a5b6bc7ad5d6c0dae60bb7ee0fa5e4825be35e2bb6ef07ee29396ca30ceb289431bcfd363888ba2207139933ac7c6369fa8810c819b2e2966abb4b`
76-
7775
func TestCertificateSelection(t *testing.T) {
7876
config := Config{
7977
Certificates: []Certificate{
@@ -86,9 +84,6 @@ func TestCertificateSelection(t *testing.T) {
8684
{
8785
Certificate: [][]byte{fromHex(certFooExampleCom)},
8886
},
89-
{
90-
Certificate: [][]byte{fromHex(certDoubleWildcardExampleCom)},
91-
},
9287
},
9388
}
9489

@@ -124,11 +119,8 @@ func TestCertificateSelection(t *testing.T) {
124119
if n := pointerToIndex(certificateForName("foo.example.com")); n != 2 {
125120
t.Errorf("foo.example.com returned certificate %d, not 2", n)
126121
}
127-
if n := pointerToIndex(certificateForName("foo.bar.example.com")); n != 3 {
128-
t.Errorf("foo.bar.example.com returned certificate %d, not 3", n)
129-
}
130-
if n := pointerToIndex(certificateForName("foo.bar.baz.example.com")); n != 0 {
131-
t.Errorf("foo.bar.baz.example.com returned certificate %d, not 0", n)
122+
if n := pointerToIndex(certificateForName("foo.bar.example.com")); n != 0 {
123+
t.Errorf("foo.bar.example.com returned certificate %d, not 0", n)
132124
}
133125
}
134126

src/crypto/tls/handshake_server.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,11 @@ func (hs *serverHandshakeState) processClientHello() error {
227227

228228
hs.cert, err = c.config.getCertificate(clientHelloInfo(c, hs.clientHello))
229229
if err != nil {
230-
c.sendAlert(alertInternalError)
230+
if err == errNoCertificates {
231+
c.sendAlert(alertUnrecognizedName)
232+
} else {
233+
c.sendAlert(alertInternalError)
234+
}
231235
return err
232236
}
233237
if hs.clientHello.scts {

src/crypto/tls/handshake_server_test.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"bytes"
99
"crypto"
1010
"crypto/elliptic"
11+
"crypto/x509"
1112
"encoding/pem"
1213
"errors"
1314
"fmt"
@@ -1662,3 +1663,26 @@ T+E0J8wlH24pgwQHzy7Ko2qLwn1b5PW8ecrlvP1g
16621663
serverConfig.MaxVersion = VersionTLS12
16631664
testHandshake(t, testConfig, serverConfig)
16641665
}
1666+
1667+
func TestMultipleCertificates(t *testing.T) {
1668+
clientConfig := testConfig.Clone()
1669+
clientConfig.CipherSuites = []uint16{TLS_RSA_WITH_AES_128_GCM_SHA256}
1670+
clientConfig.MaxVersion = VersionTLS12
1671+
1672+
serverConfig := testConfig.Clone()
1673+
serverConfig.Certificates = []Certificate{{
1674+
Certificate: [][]byte{testECDSACertificate},
1675+
PrivateKey: testECDSAPrivateKey,
1676+
}, {
1677+
Certificate: [][]byte{testRSACertificate},
1678+
PrivateKey: testRSAPrivateKey,
1679+
}}
1680+
1681+
_, clientState, err := testHandshake(t, clientConfig, serverConfig)
1682+
if err != nil {
1683+
t.Fatal(err)
1684+
}
1685+
if got := clientState.PeerCertificates[0].PublicKeyAlgorithm; got != x509.RSA {
1686+
t.Errorf("expected RSA certificate, got %v", got)
1687+
}
1688+
}

src/crypto/tls/handshake_server_tls13.go

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -361,16 +361,13 @@ func (hs *serverHandshakeStateTLS13) pickCertificate() error {
361361
return c.sendAlert(alertMissingExtension)
362362
}
363363

364-
// This implements a very simplistic certificate selection strategy for now:
365-
// getCertificate delegates to the application Config.GetCertificate, or
366-
// selects based on the server_name only. If the selected certificate's
367-
// public key does not match the client signature_algorithms, the handshake
368-
// is aborted. No attention is given to signature_algorithms_cert, and it is
369-
// not passed to the application Config.GetCertificate. This will need to
370-
// improve according to RFC 8446, sections 4.4.2.2 and 4.2.3.
371364
certificate, err := c.config.getCertificate(clientHelloInfo(c, hs.clientHello))
372365
if err != nil {
373-
c.sendAlert(alertInternalError)
366+
if err == errNoCertificates {
367+
c.sendAlert(alertUnrecognizedName)
368+
} else {
369+
c.sendAlert(alertInternalError)
370+
}
374371
return err
375372
}
376373
hs.sigAlg, err = selectSignatureScheme(c.vers, certificate, hs.clientHello.supportedSignatureAlgorithms)

src/crypto/tls/tls.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,9 @@ func NewListener(inner net.Listener, config *Config) net.Listener {
7575
// The configuration config must be non-nil and must include
7676
// at least one certificate or else set GetCertificate.
7777
func Listen(network, laddr string, config *Config) (net.Listener, error) {
78-
if config == nil || (len(config.Certificates) == 0 && config.GetCertificate == nil) {
79-
return nil, errors.New("tls: neither Certificates nor GetCertificate set in Config")
78+
if config == nil || len(config.Certificates) == 0 &&
79+
config.GetCertificate == nil && config.GetConfigForClient == nil {
80+
return nil, errors.New("tls: neither Certificates, GetCertificate, nor GetConfigForClient set in Config")
8081
}
8182
l, err := net.Listen(network, laddr)
8283
if err != nil {

0 commit comments

Comments
 (0)