]> Cypherpunks.ru repositories - gostls13.git/commitdiff
crypto/tls: implement (*ClientHelloInfo).SupportsCertificate
authorFilippo Valsorda <filippo@golang.org>
Fri, 1 Nov 2019 23:00:33 +0000 (19:00 -0400)
committerFilippo Valsorda <filippo@golang.org>
Tue, 12 Nov 2019 01:08:34 +0000 (01:08 +0000)
We'll also use this function for a better selection logic from
Config.Certificates in a later CL.

Updates #32426

Change-Id: Ie239574d02eb7fd2cf025ec36721c8c7e082d0bc
Reviewed-on: https://go-review.googlesource.com/c/go/+/205057
Reviewed-by: Katie Hockman <katie@golang.org>
src/crypto/tls/common.go
src/crypto/tls/handshake_server.go
src/crypto/tls/tls_test.go

index 4b4e742b1b25bbc704c52d4844cfe39c7a59bb70..4b8e023089bd44da3320326dd727a094b1c68904 100644 (file)
@@ -7,7 +7,11 @@ package tls
 import (
        "container/list"
        "crypto"
+       "crypto/ecdsa"
+       "crypto/ed25519"
+       "crypto/elliptic"
        "crypto/rand"
+       "crypto/rsa"
        "crypto/sha512"
        "crypto/x509"
        "errors"
@@ -384,6 +388,10 @@ type ClientHelloInfo struct {
        // from, or write to, this connection; that will cause the TLS
        // connection to fail.
        Conn net.Conn
+
+       // config is embedded by the GetCertificate or GetConfigForClient caller,
+       // for use with SupportsCertificate.
+       config *Config
 }
 
 // CertificateRequestInfo contains information from a server's
@@ -901,6 +909,167 @@ func (c *Config) getCertificate(clientHello *ClientHelloInfo) (*Certificate, err
        return &c.Certificates[0], nil
 }
 
+// SupportsCertificate returns nil if the provided certificate is supported by
+// the client that sent the ClientHello. Otherwise, it returns an error
+// describing the reason for the incompatibility.
+//
+// If this ClientHelloInfo was passed to a GetConfigForClient or GetCertificate
+// callback, this method will take into account the associated Config. Note that
+// if GetConfigForClient returns a different Config, the change can't be
+// accounted for by this method.
+//
+// This function will call x509.ParseCertificate unless c.Leaf is set, which can
+// incur a significant performance cost.
+func (chi *ClientHelloInfo) SupportsCertificate(c *Certificate) error {
+       // Note we don't currently support certificate_authorities nor
+       // signature_algorithms_cert, and don't check the algorithms of the
+       // signatures on the chain (which anyway are a SHOULD, see RFC 8446,
+       // Section 4.4.2.2).
+
+       config := chi.config
+       if config == nil {
+               config = &Config{}
+       }
+       vers, ok := config.mutualVersion(chi.SupportedVersions)
+       if !ok {
+               return errors.New("no mutually supported protocol versions")
+       }
+
+       // If the client specified the name they are trying to connect to, the
+       // certificate needs to be valid for it.
+       if chi.ServerName != "" {
+               x509Cert, err := c.leaf()
+               if err != nil {
+                       return fmt.Errorf("failed to parse certificate: %w", err)
+               }
+               if err := x509Cert.VerifyHostname(chi.ServerName); err != nil {
+                       return fmt.Errorf("certificate is not valid for requested server name: %w", err)
+               }
+       }
+
+       // supportsRSAFallback returns nil if the certificate and connection support
+       // the static RSA key exchange, and unsupported otherwise. The logic for
+       // supporting static RSA is completely disjoint from the logic for
+       // supporting signed key exchanges, so we just check it as a fallback.
+       supportsRSAFallback := func(unsupported error) error {
+               // TLS 1.3 dropped support for the static RSA key exchange.
+               if vers == VersionTLS13 {
+                       return unsupported
+               }
+               // The static RSA key exchange works by decrypting a challenge with the
+               // RSA private key, not by signing, so check the PrivateKey implements
+               // crypto.Decrypter, like *rsa.PrivateKey does.
+               if priv, ok := c.PrivateKey.(crypto.Decrypter); ok {
+                       if _, ok := priv.Public().(*rsa.PublicKey); !ok {
+                               return unsupported
+                       }
+               } else {
+                       return unsupported
+               }
+               // Finally, there needs to be a mutual cipher suite that uses the static
+               // RSA key exchange instead of ECDHE.
+               rsaCipherSuite := selectCipherSuite(chi.CipherSuites, config.cipherSuites(), func(c *cipherSuite) bool {
+                       if c.flags&suiteECDHE != 0 {
+                               return false
+                       }
+                       if vers < VersionTLS12 && c.flags&suiteTLS12 != 0 {
+                               return false
+                       }
+                       return true
+               })
+               if rsaCipherSuite == nil {
+                       return unsupported
+               }
+               return nil
+       }
+
+       // If the client sent the signature_algorithms extension, ensure it supports
+       // schemes we can use with this certificate and TLS version.
+       if len(chi.SignatureSchemes) > 0 {
+               if _, err := selectSignatureScheme(vers, c, chi.SignatureSchemes); err != nil {
+                       return supportsRSAFallback(err)
+               }
+       }
+
+       // In TLS 1.3 we are done because supported_groups is only relevant to the
+       // ECDHE computation, point format negotiation is removed, cipher suites are
+       // only relevant to the AEAD choice, and static RSA does not exist.
+       if vers == VersionTLS13 {
+               return nil
+       }
+
+       // The only signed key exchange we support is ECDHE.
+       if !supportsECDHE(config, chi.SupportedCurves, chi.SupportedPoints) {
+               return supportsRSAFallback(errors.New("client doesn't support ECDHE, can only use legacy RSA key exchange"))
+       }
+
+       var ecdsaCipherSuite bool
+       if priv, ok := c.PrivateKey.(crypto.Signer); ok {
+               switch pub := priv.Public().(type) {
+               case *ecdsa.PublicKey:
+                       var curve CurveID
+                       switch pub.Curve {
+                       case elliptic.P256():
+                               curve = CurveP256
+                       case elliptic.P384():
+                               curve = CurveP384
+                       case elliptic.P521():
+                               curve = CurveP521
+                       default:
+                               return supportsRSAFallback(unsupportedCertificateError(c))
+                       }
+                       var curveOk bool
+                       for _, c := range chi.SupportedCurves {
+                               if c == curve && config.supportsCurve(c) {
+                                       curveOk = true
+                                       break
+                               }
+                       }
+                       if !curveOk {
+                               return errors.New("client doesn't support certificate curve")
+                       }
+                       ecdsaCipherSuite = true
+               case ed25519.PublicKey:
+                       if vers < VersionTLS12 || len(chi.SignatureSchemes) == 0 {
+                               return errors.New("connection doesn't support Ed25519")
+                       }
+                       ecdsaCipherSuite = true
+               case *rsa.PublicKey:
+               default:
+                       return supportsRSAFallback(unsupportedCertificateError(c))
+               }
+       } else {
+               return supportsRSAFallback(unsupportedCertificateError(c))
+       }
+
+       // Make sure that there is a mutually supported cipher suite that works with
+       // this certificate. Cipher suite selection will then apply the logic in
+       // reverse to pick it. See also serverHandshakeState.cipherSuiteOk.
+       cipherSuite := selectCipherSuite(chi.CipherSuites, config.cipherSuites(), func(c *cipherSuite) bool {
+               if c.flags&suiteECDHE == 0 {
+                       return false
+               }
+               if c.flags&suiteECSign != 0 {
+                       if !ecdsaCipherSuite {
+                               return false
+                       }
+               } else {
+                       if ecdsaCipherSuite {
+                               return false
+                       }
+               }
+               if vers < VersionTLS12 && c.flags&suiteTLS12 != 0 {
+                       return false
+               }
+               return true
+       })
+       if cipherSuite == nil {
+               return supportsRSAFallback(errors.New("client doesn't support any cipher suites compatible with the certificate"))
+       }
+
+       return nil
+}
+
 // BuildNameToCertificate parses c.Certificates and builds c.NameToCertificate
 // from the CommonName and SubjectAlternateName fields of each of the leaf
 // certificates.
index 33325e55797bee9d95e9b766525a5e047af00a18..65988abf0ed96dd75ce59b432f4bb6393b8dd0aa 100644 (file)
@@ -801,5 +801,6 @@ func clientHelloInfo(c *Conn, clientHello *clientHelloMsg) *ClientHelloInfo {
                SupportedProtos:   clientHello.alpnProtocols,
                SupportedVersions: supportedVersions,
                Conn:              c.conn,
+               config:            c.config,
        }
 }
index c06e580b446acaeaedafd57dc38fc86bf2f1c356..fc9cf1df0d32f0541dc5a849e218f5becee0a4a7 100644 (file)
@@ -1045,3 +1045,162 @@ func TestBuildNameToCertificate_doesntModifyCertificates(t *testing.T) {
 }
 
 func testingKey(s string) string { return strings.ReplaceAll(s, "TESTING KEY", "PRIVATE KEY") }
+
+func TestClientHelloInfo_SupportsCertificate(t *testing.T) {
+       rsaCert := &Certificate{
+               Certificate: [][]byte{testRSACertificate},
+               PrivateKey:  testRSAPrivateKey,
+       }
+       ecdsaCert := &Certificate{
+               // ECDSA P-256 certificate
+               Certificate: [][]byte{testP256Certificate},
+               PrivateKey:  testP256PrivateKey,
+       }
+       ed25519Cert := &Certificate{
+               Certificate: [][]byte{testEd25519Certificate},
+               PrivateKey:  testEd25519PrivateKey,
+       }
+
+       tests := []struct {
+               c       *Certificate
+               chi     *ClientHelloInfo
+               wantErr string
+       }{
+               {rsaCert, &ClientHelloInfo{
+                       ServerName:        "example.golang",
+                       SignatureSchemes:  []SignatureScheme{PSSWithSHA256},
+                       SupportedVersions: []uint16{VersionTLS13},
+               }, ""},
+               {ecdsaCert, &ClientHelloInfo{
+                       SignatureSchemes:  []SignatureScheme{PSSWithSHA256, ECDSAWithP256AndSHA256},
+                       SupportedVersions: []uint16{VersionTLS13, VersionTLS12},
+               }, ""},
+               {rsaCert, &ClientHelloInfo{
+                       ServerName:        "example.com",
+                       SignatureSchemes:  []SignatureScheme{PSSWithSHA256},
+                       SupportedVersions: []uint16{VersionTLS13},
+               }, "not valid for requested server name"},
+               {ecdsaCert, &ClientHelloInfo{
+                       SignatureSchemes:  []SignatureScheme{ECDSAWithP384AndSHA384},
+                       SupportedVersions: []uint16{VersionTLS13},
+               }, "signature algorithms"},
+
+               {rsaCert, &ClientHelloInfo{
+                       CipherSuites:      []uint16{TLS_RSA_WITH_AES_128_GCM_SHA256},
+                       SignatureSchemes:  []SignatureScheme{PKCS1WithSHA1},
+                       SupportedVersions: []uint16{VersionTLS13, VersionTLS12},
+               }, "signature algorithms"},
+               {rsaCert, &ClientHelloInfo{
+                       CipherSuites:      []uint16{TLS_RSA_WITH_AES_128_GCM_SHA256},
+                       SignatureSchemes:  []SignatureScheme{PKCS1WithSHA1},
+                       SupportedVersions: []uint16{VersionTLS13, VersionTLS12},
+                       config: &Config{
+                               MaxVersion: VersionTLS12,
+                       },
+               }, ""}, // Check that mutual version selection works.
+
+               {ecdsaCert, &ClientHelloInfo{
+                       CipherSuites:      []uint16{TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256},
+                       SupportedCurves:   []CurveID{CurveP256},
+                       SupportedPoints:   []uint8{pointFormatUncompressed},
+                       SignatureSchemes:  []SignatureScheme{ECDSAWithP256AndSHA256},
+                       SupportedVersions: []uint16{VersionTLS12},
+               }, ""},
+               {ecdsaCert, &ClientHelloInfo{
+                       CipherSuites:      []uint16{TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256},
+                       SupportedCurves:   []CurveID{CurveP256},
+                       SupportedPoints:   []uint8{pointFormatUncompressed},
+                       SignatureSchemes:  []SignatureScheme{ECDSAWithP384AndSHA384},
+                       SupportedVersions: []uint16{VersionTLS12},
+               }, ""}, // TLS 1.2 does not restrict curves based on the SignatureScheme.
+               {ecdsaCert, &ClientHelloInfo{
+                       CipherSuites:      []uint16{TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256},
+                       SupportedCurves:   []CurveID{CurveP256},
+                       SupportedPoints:   []uint8{pointFormatUncompressed},
+                       SignatureSchemes:  nil,
+                       SupportedVersions: []uint16{VersionTLS12},
+               }, ""}, // TLS 1.2 comes with default signature schemes.
+               {ecdsaCert, &ClientHelloInfo{
+                       CipherSuites:      []uint16{TLS_RSA_WITH_AES_128_GCM_SHA256},
+                       SupportedCurves:   []CurveID{CurveP256},
+                       SupportedPoints:   []uint8{pointFormatUncompressed},
+                       SignatureSchemes:  []SignatureScheme{ECDSAWithP256AndSHA256},
+                       SupportedVersions: []uint16{VersionTLS12},
+               }, "cipher suite"},
+               {ecdsaCert, &ClientHelloInfo{
+                       CipherSuites:      []uint16{TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256},
+                       SupportedCurves:   []CurveID{CurveP256},
+                       SupportedPoints:   []uint8{pointFormatUncompressed},
+                       SignatureSchemes:  []SignatureScheme{ECDSAWithP256AndSHA256},
+                       SupportedVersions: []uint16{VersionTLS12},
+                       config: &Config{
+                               CipherSuites: []uint16{TLS_RSA_WITH_AES_128_GCM_SHA256},
+                       },
+               }, "cipher suite"},
+               {ecdsaCert, &ClientHelloInfo{
+                       CipherSuites:      []uint16{TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256},
+                       SupportedCurves:   []CurveID{CurveP384},
+                       SupportedPoints:   []uint8{pointFormatUncompressed},
+                       SignatureSchemes:  []SignatureScheme{ECDSAWithP256AndSHA256},
+                       SupportedVersions: []uint16{VersionTLS12},
+               }, "certificate curve"},
+               {ecdsaCert, &ClientHelloInfo{
+                       CipherSuites:      []uint16{TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256},
+                       SupportedCurves:   []CurveID{CurveP256},
+                       SupportedPoints:   []uint8{1},
+                       SignatureSchemes:  []SignatureScheme{ECDSAWithP256AndSHA256},
+                       SupportedVersions: []uint16{VersionTLS12},
+               }, "doesn't support ECDHE"},
+               {ecdsaCert, &ClientHelloInfo{
+                       CipherSuites:      []uint16{TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256},
+                       SupportedCurves:   []CurveID{CurveP256},
+                       SupportedPoints:   []uint8{pointFormatUncompressed},
+                       SignatureSchemes:  []SignatureScheme{PSSWithSHA256},
+                       SupportedVersions: []uint16{VersionTLS12},
+               }, "signature algorithms"},
+
+               {ed25519Cert, &ClientHelloInfo{
+                       CipherSuites:      []uint16{TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256},
+                       SupportedCurves:   []CurveID{CurveP256}, // only relevant for ECDHE support
+                       SupportedPoints:   []uint8{pointFormatUncompressed},
+                       SignatureSchemes:  []SignatureScheme{Ed25519},
+                       SupportedVersions: []uint16{VersionTLS12},
+               }, ""},
+               {ed25519Cert, &ClientHelloInfo{
+                       CipherSuites:      []uint16{TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256},
+                       SupportedCurves:   []CurveID{CurveP256}, // only relevant for ECDHE support
+                       SupportedPoints:   []uint8{pointFormatUncompressed},
+                       SignatureSchemes:  []SignatureScheme{Ed25519},
+                       SupportedVersions: []uint16{VersionTLS10},
+               }, "doesn't support Ed25519"},
+               {ed25519Cert, &ClientHelloInfo{
+                       CipherSuites:      []uint16{TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256},
+                       SupportedCurves:   []CurveID{},
+                       SupportedPoints:   []uint8{pointFormatUncompressed},
+                       SignatureSchemes:  []SignatureScheme{Ed25519},
+                       SupportedVersions: []uint16{VersionTLS12},
+               }, "doesn't support ECDHE"},
+
+               {rsaCert, &ClientHelloInfo{
+                       CipherSuites:      []uint16{TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA},
+                       SupportedCurves:   []CurveID{CurveP256}, // only relevant for ECDHE support
+                       SupportedPoints:   []uint8{pointFormatUncompressed},
+                       SupportedVersions: []uint16{VersionTLS10},
+               }, ""},
+               {rsaCert, &ClientHelloInfo{
+                       CipherSuites:      []uint16{TLS_RSA_WITH_AES_128_GCM_SHA256},
+                       SupportedVersions: []uint16{VersionTLS12},
+               }, ""}, // static RSA fallback
+       }
+       for i, tt := range tests {
+               err := tt.chi.SupportsCertificate(tt.c)
+               switch {
+               case tt.wantErr == "" && err != nil:
+                       t.Errorf("%d: unexpected error: %v", i, err)
+               case tt.wantErr != "" && err == nil:
+                       t.Errorf("%d: unexpected success", i)
+               case tt.wantErr != "" && !strings.Contains(err.Error(), tt.wantErr):
+                       t.Errorf("%d: got error %q, expected %q", i, err, tt.wantErr)
+               }
+       }
+}