From 4b8508c01748267eba9ed62eb87bc0290c92f6f5 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Fri, 18 Nov 2016 14:58:43 +0700 Subject: [PATCH] verify certificate chain in certManager --- crypto/cert_manager.go | 31 +++++++ crypto/cert_manager_test.go | 129 ++++++++++++++++++++++++++ handshake/crypto_setup_client_test.go | 6 ++ 3 files changed, 166 insertions(+) diff --git a/crypto/cert_manager.go b/crypto/cert_manager.go index 8efb9b16..b067c775 100644 --- a/crypto/cert_manager.go +++ b/crypto/cert_manager.go @@ -12,6 +12,7 @@ type CertManager interface { SetData([]byte) error GetLeafCert() []byte VerifyServerProof(proof, chlo, serverConfigData []byte) (bool, error) + Verify(hostname string) error } type certManager struct { @@ -60,3 +61,33 @@ func (c *certManager) VerifyServerProof(proof, chlo, serverConfigData []byte) (b return verifyServerProof(proof, cert, chlo, serverConfigData), nil } + +func (c *certManager) Verify(hostname string) error { + if len(c.chain) == 0 { + return errNoCertificateChain + } + + leafCert, err := x509.ParseCertificate(c.GetLeafCert()) + if err != nil { + return err + } + + opts := x509.VerifyOptions{DNSName: hostname} + + // the first certificate is the leaf certificate, all others are intermediates + if len(c.chain) > 1 { + intermediates := x509.NewCertPool() + for i := 1; i < len(c.chain); i++ { + var cert *x509.Certificate + cert, err = x509.ParseCertificate(c.chain[i]) + if err != nil { + return err + } + intermediates.AddCert(cert) + } + opts.Intermediates = intermediates + } + + _, err = leafCert.Verify(opts) + return err +} diff --git a/crypto/cert_manager_test.go b/crypto/cert_manager_test.go index 3857cced..73983a63 100644 --- a/crypto/cert_manager_test.go +++ b/crypto/cert_manager_test.go @@ -1,7 +1,18 @@ package crypto import ( + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "crypto/x509/pkix" + "encoding/asn1" + "math/big" + "runtime" + "time" + "github.com/lucas-clemente/quic-go/qerr" + "github.com/lucas-clemente/quic-go/testdata" + . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) @@ -60,4 +71,122 @@ var _ = Describe("Cert Manager", func() { Expect(valid).To(BeFalse()) }) }) + + Context("verifying the certificate chain", func() { + getCertificate := func(template *x509.Certificate) *x509.Certificate { + key, err := rsa.GenerateKey(rand.Reader, 1024) + Expect(err).ToNot(HaveOccurred()) + + certDER, err := x509.CreateCertificate(rand.Reader, template, template, &key.PublicKey, key) + Expect(err).ToNot(HaveOccurred()) + leafCert, err := x509.ParseCertificate(certDER) + Expect(err).ToNot(HaveOccurred()) + return leafCert + } + + It("accepts a valid certificate", func() { + cc := NewCertChain(testdata.GetTLSConfig()).(*certChain) + cert, err := cc.getCertForSNI("quic.clemente.io") + Expect(err).ToNot(HaveOccurred()) + cm.chain = cert.Certificate + err = cm.Verify("quic.clemente.io") + Expect(err).ToNot(HaveOccurred()) + }) + + It("errors if it can't parse an intermediate certificate", func() { + cc := NewCertChain(testdata.GetTLSConfig()).(*certChain) + cert, err := cc.getCertForSNI("quic.clemente.io") + Expect(err).ToNot(HaveOccurred()) + cm.chain = cert.Certificate + Expect(cm.chain).To(HaveLen(2)) + cm.chain[1] = []byte("invalid intermediate") + err = cm.Verify("quic.clemente.io") + Expect(err).To(HaveOccurred()) + _, ok := err.(asn1.StructuralError) + Expect(ok).To(BeTrue()) + }) + + It("doesn't accept an expired certificate", 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(-time.Hour), + } + leafCert := getCertificate(template) + + cm.chain = [][]byte{leafCert.Raw} + err := cm.Verify("") + Expect(err).To(HaveOccurred()) + Expect(err.(x509.CertificateInvalidError).Reason).To(Equal(x509.Expired)) + }) + + It("doesn't accept a certificate that is not yet valid", 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(25 * time.Hour), + } + leafCert := getCertificate(template) + + cm.chain = [][]byte{leafCert.Raw} + err := cm.Verify("") + Expect(err).To(HaveOccurred()) + Expect(err.(x509.CertificateInvalidError).Reason).To(Equal(x509.Expired)) + }) + + It("doesn't accept an certificate for the wrong hostname", 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 = [][]byte{leafCert.Raw} + err := cm.Verify("quic.clemente.io") + Expect(err).To(HaveOccurred()) + _, ok := err.(x509.HostnameError) + Expect(ok).To(BeTrue()) + }) + + It("errors if the chain hasn't been set yet", func() { + err := cm.Verify("example.com") + Expect(err).To(HaveOccurred()) + }) + + It("errors if it can't parse the leaf certificate", func() { + cm.chain = [][]byte{[]byte("invalid leaf cert")} + err := cm.Verify("example.com") + Expect(err).To(HaveOccurred()) + }) + + // this tests relies on LetsEncrypt not being contained in the Root CAs + It("rejects valid certificate with missing certificate chain", func() { + if runtime.GOOS == "windows" { + Skip("LetsEncrypt Root CA is included in Windows") + } + + cert := testdata.GetCertificate() + cm.chain = [][]byte{cert.Certificate[0]} + err := cm.Verify("quic.clemente.io") + _, ok := err.(x509.UnknownAuthorityError) + Expect(ok).To(BeTrue()) + }) + }) }) diff --git a/handshake/crypto_setup_client_test.go b/handshake/crypto_setup_client_test.go index b14b81d9..1156c296 100644 --- a/handshake/crypto_setup_client_test.go +++ b/handshake/crypto_setup_client_test.go @@ -46,6 +46,8 @@ type mockCertManager struct { verifyServerProofError error verifyServerProofValue bool + + verifyError error } func (m *mockCertManager) SetData(data []byte) error { @@ -61,6 +63,10 @@ func (m *mockCertManager) VerifyServerProof(proof, chlo, serverConfigData []byte return m.verifyServerProofValue, m.verifyServerProofError } +func (m *mockCertManager) Verify(hostname string) error { + return m.verifyError +} + var _ = Describe("Crypto setup", func() { var cs *cryptoSetupClient var certManager *mockCertManager