forked from quic-go/quic-go
verify certificate chain in certManager
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user