From 713df41c8b9efdf0ecdb55c60af4207112170b23 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Fri, 3 Feb 2017 15:46:38 +0700 Subject: [PATCH] verify certificates using a client TLS config, if given ref #407 --- crypto/cert_manager.go | 27 ++++++-- crypto/cert_manager_test.go | 114 ++++++++++++++++++++++++++++++- handshake/crypto_setup_client.go | 2 +- 3 files changed, 137 insertions(+), 6 deletions(-) diff --git a/crypto/cert_manager.go b/crypto/cert_manager.go index 31a2dd5e4..562278427 100644 --- a/crypto/cert_manager.go +++ b/crypto/cert_manager.go @@ -1,9 +1,11 @@ package crypto import ( + "crypto/tls" "crypto/x509" "errors" "hash/fnv" + "time" "github.com/lucas-clemente/quic-go/qerr" ) @@ -19,7 +21,8 @@ type CertManager interface { } type certManager struct { - chain []*x509.Certificate + chain []*x509.Certificate + config *tls.Config } var _ CertManager = &certManager{} @@ -27,8 +30,8 @@ var _ CertManager = &certManager{} var errNoCertificateChain = errors.New("CertManager BUG: No certicifate chain loaded") // NewCertManager creates a new CertManager -func NewCertManager() CertManager { - return &certManager{} +func NewCertManager(tlsConfig *tls.Config) CertManager { + return &certManager{config: tlsConfig} } // SetData takes the byte-slice sent in the SHLO and decompresses it into the certificate chain @@ -95,8 +98,24 @@ func (c *certManager) Verify(hostname string) error { return errNoCertificateChain } + if c.config != nil && c.config.InsecureSkipVerify { + return nil + } + leafCert := c.chain[0] - opts := x509.VerifyOptions{DNSName: hostname} + + var opts x509.VerifyOptions + if c.config != nil { + opts.Roots = c.config.RootCAs + opts.DNSName = c.config.ServerName + if c.config.Time == nil { + opts.CurrentTime = time.Now() + } else { + opts.CurrentTime = c.config.Time() + } + } else { + opts.DNSName = hostname + } // the first certificate is the leaf certificate, all others are intermediates if len(c.chain) > 1 { diff --git a/crypto/cert_manager_test.go b/crypto/cert_manager_test.go index dcb15941a..d3a51809c 100644 --- a/crypto/cert_manager_test.go +++ b/crypto/cert_manager_test.go @@ -25,7 +25,7 @@ var _ = Describe("Cert Manager", func() { BeforeEach(func() { var err error - cm = NewCertManager().(*certManager) + cm = NewCertManager(nil).(*certManager) key1, err = rsa.GenerateKey(rand.Reader, 768) Expect(err).ToNot(HaveOccurred()) key2, err = rsa.GenerateKey(rand.Reader, 768) @@ -37,6 +37,12 @@ var _ = Describe("Cert Manager", func() { Expect(err).ToNot(HaveOccurred()) }) + It("saves a client TLS config", func() { + tlsConf := &tls.Config{ServerName: "quic.clemente.io"} + cm = NewCertManager(tlsConf).(*certManager) + Expect(cm.config.ServerName).To(Equal("quic.clemente.io")) + }) + It("errors when given invalid data", func() { err := cm.SetData([]byte("foobar")) Expect(err).To(MatchError(qerr.Error(qerr.InvalidCryptoMessageParameter, "Certificate data invalid"))) @@ -239,5 +245,111 @@ var _ = Describe("Cert Manager", func() { _, ok := err.(x509.UnknownAuthorityError) Expect(ok).To(BeTrue()) }) + + It("doesn't do any certificate verification if InsecureSkipVerify is set", func() { + if runtime.GOOS == "windows" { + // certificate validation works different on windows, see https://golang.org/src/crypto/x509/verify.go line 238 + Skip("windows") + } + + template := &x509.Certificate{ + SerialNumber: big.NewInt(1), + } + + leafCert := getCertificate(template) + cm.config = &tls.Config{ + InsecureSkipVerify: true, + } + cm.chain = []*x509.Certificate{leafCert} + err := cm.Verify("quic.clemente.io") + Expect(err).ToNot(HaveOccurred()) + }) + + It("uses a different hostname from a client TLS config", func() { + if runtime.GOOS == "windows" { + // certificate validation works different on windows, see https://golang.org/src/crypto/x509/verify.go line 238 + Skip("windows") + } + + template := &x509.Certificate{ + SerialNumber: big.NewInt(1), + NotBefore: time.Now().Add(-time.Hour), + NotAfter: time.Now().Add(time.Hour), + Subject: pkix.Name{CommonName: "google.com"}, + } + + leafCert := getCertificate(template) + cm.chain = []*x509.Certificate{leafCert} + cm.config = &tls.Config{ + ServerName: "google.com", + } + err := cm.Verify("quic.clemente.io") + _, ok := err.(x509.UnknownAuthorityError) + Expect(ok).To(BeTrue()) + }) + + It("rejects certificates with a different hostname than specified in the client TLS config", func() { + if runtime.GOOS == "windows" { + // certificate validation works different on windows, see https://golang.org/src/crypto/x509/verify.go line 238 + Skip("windows") + } + + template := &x509.Certificate{ + SerialNumber: big.NewInt(1), + NotBefore: time.Now().Add(-time.Hour), + NotAfter: time.Now().Add(time.Hour), + Subject: pkix.Name{CommonName: "quic.clemente.io"}, + } + + leafCert := getCertificate(template) + cm.chain = []*x509.Certificate{leafCert} + cm.config = &tls.Config{ + ServerName: "google.com", + } + err := cm.Verify("quic.clemente.io") + _, ok := err.(x509.HostnameError) + Expect(ok).To(BeTrue()) + }) + + It("uses the time specified in a client TLS config", func() { + if runtime.GOOS == "windows" { + // certificate validation works different on windows, see https://golang.org/src/crypto/x509/verify.go line 238 + Skip("windows") + } + + template := &x509.Certificate{ + SerialNumber: big.NewInt(1), + NotBefore: time.Now().Add(-25 * time.Hour), + NotAfter: time.Now().Add(-23 * time.Hour), + } + leafCert := getCertificate(template) + cm.chain = []*x509.Certificate{leafCert} + cm.config = &tls.Config{ + Time: func() time.Time { return time.Now().Add(-24 * time.Hour) }, + } + err := cm.Verify("quic.clemente.io") + _, ok := err.(x509.UnknownAuthorityError) + Expect(ok).To(BeTrue()) + }) + + It("rejects certificates that are expired at the time specified in a client TLS config", func() { + if runtime.GOOS == "windows" { + // certificate validation works different on windows, see https://golang.org/src/crypto/x509/verify.go line 238 + Skip("windows") + } + + template := &x509.Certificate{ + SerialNumber: big.NewInt(1), + NotBefore: time.Now().Add(-time.Hour), + NotAfter: time.Now().Add(time.Hour), + } + leafCert := getCertificate(template) + cm.chain = []*x509.Certificate{leafCert} + cm.config = &tls.Config{ + Time: func() time.Time { return time.Now().Add(-24 * time.Hour) }, + } + err := cm.Verify("quic.clemente.io") + Expect(err.(x509.CertificateInvalidError).Reason).To(Equal(x509.Expired)) + }) }) }) diff --git a/handshake/crypto_setup_client.go b/handshake/crypto_setup_client.go index c647277cd..240ba0a0f 100644 --- a/handshake/crypto_setup_client.go +++ b/handshake/crypto_setup_client.go @@ -73,7 +73,7 @@ func NewCryptoSetupClient( connID: connID, version: version, cryptoStream: cryptoStream, - certManager: crypto.NewCertManager(), + certManager: crypto.NewCertManager(nil), connectionParameters: connectionParameters, keyDerivation: crypto.DeriveKeysAESGCM, aeadChanged: aeadChanged,