Files
quic-go/crypto/cert_manager_test.go
Marten Seemann 2c920dbfc8 remove obsolete check for tls.Config.ServerName when verifying the cert
The hostname is set to tls.Config.ServerName in the client already, thus
we don't have to read that value again when verifying the certificate.
2017-06-20 09:54:08 +02:00

349 lines
11 KiB
Go

package crypto
import (
"crypto/rand"
"crypto/rsa"
"crypto/tls"
"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"
)
var _ = Describe("Cert Manager", func() {
var cm *certManager
var key1, key2 *rsa.PrivateKey
var cert1, cert2 []byte
BeforeEach(func() {
var err error
cm = NewCertManager(nil).(*certManager)
key1, err = rsa.GenerateKey(rand.Reader, 768)
Expect(err).ToNot(HaveOccurred())
key2, err = rsa.GenerateKey(rand.Reader, 768)
Expect(err).ToNot(HaveOccurred())
template := &x509.Certificate{SerialNumber: big.NewInt(1)}
cert1, err = x509.CreateCertificate(rand.Reader, template, template, &key1.PublicKey, key1)
Expect(err).ToNot(HaveOccurred())
cert2, err = x509.CreateCertificate(rand.Reader, template, template, &key2.PublicKey, key2)
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")))
})
It("gets the common certificate hashes", func() {
ccs := cm.GetCommonCertificateHashes()
Expect(ccs).ToNot(BeEmpty())
})
Context("setting the data", func() {
It("decompresses a certificate chain", func() {
chain := [][]byte{cert1, cert2}
compressed, err := compressChain(chain, nil, nil)
Expect(err).ToNot(HaveOccurred())
err = cm.SetData(compressed)
Expect(err).ToNot(HaveOccurred())
Expect(cm.chain[0].Raw).To(Equal(cert1))
Expect(cm.chain[1].Raw).To(Equal(cert2))
})
It("errors if it can't decompress the chain", func() {
err := cm.SetData([]byte("invalid data"))
Expect(err).To(MatchError(qerr.Error(qerr.InvalidCryptoMessageParameter, "Certificate data invalid")))
})
It("errors if it can't parse a certificate", func() {
chain := [][]byte{[]byte("cert1"), []byte("cert2")}
compressed, err := compressChain(chain, nil, nil)
Expect(err).ToNot(HaveOccurred())
err = cm.SetData(compressed)
_, ok := err.(asn1.StructuralError)
Expect(ok).To(BeTrue())
})
})
Context("getting the leaf cert", func() {
It("gets it", func() {
xcert1, err := x509.ParseCertificate(cert1)
Expect(err).ToNot(HaveOccurred())
xcert2, err := x509.ParseCertificate(cert2)
Expect(err).ToNot(HaveOccurred())
cm.chain = []*x509.Certificate{xcert1, xcert2}
leafCert := cm.GetLeafCert()
Expect(leafCert).To(Equal(cert1))
})
It("returns nil if the chain hasn't been set yet", func() {
leafCert := cm.GetLeafCert()
Expect(leafCert).To(BeNil())
})
})
Context("getting the leaf cert hash", func() {
It("calculates the FVN1a 64 hash", func() {
cm.chain = make([]*x509.Certificate, 1)
cm.chain[0] = &x509.Certificate{
Raw: []byte("test fnv hash"),
}
hash, err := cm.GetLeafCertHash()
Expect(err).ToNot(HaveOccurred())
// hash calculated on http://www.nitrxgen.net/hashgen/
Expect(hash).To(Equal(uint64(0x4770f6141fa0f5ad)))
})
It("errors if the certificate chain is not loaded", func() {
_, err := cm.GetLeafCertHash()
Expect(err).To(MatchError(errNoCertificateChain))
})
})
Context("verifying the server config signature", func() {
It("returns false when the chain hasn't been set yet", func() {
valid := cm.VerifyServerProof([]byte("proof"), []byte("chlo"), []byte("scfg"))
Expect(valid).To(BeFalse())
})
It("verifies the signature", func() {
chlo := []byte("client hello")
scfg := []byte("server config data")
xcert1, err := x509.ParseCertificate(cert1)
Expect(err).ToNot(HaveOccurred())
cm.chain = []*x509.Certificate{xcert1}
proof, err := signServerProof(&tls.Certificate{PrivateKey: key1}, chlo, scfg)
Expect(err).ToNot(HaveOccurred())
valid := cm.VerifyServerProof(proof, chlo, scfg)
Expect(valid).To(BeTrue())
})
It("rejects an invalid signature", func() {
xcert1, err := x509.ParseCertificate(cert1)
Expect(err).ToNot(HaveOccurred())
cm.chain = []*x509.Certificate{xcert1}
valid := cm.VerifyServerProof([]byte("invalid proof"), []byte("chlo"), []byte("scfg"))
Expect(valid).To(BeFalse())
})
})
Context("verifying the certificate chain", func() {
generateCertificate := func(template, parent *x509.Certificate, pubKey *rsa.PublicKey, privKey *rsa.PrivateKey) *x509.Certificate {
certDER, err := x509.CreateCertificate(rand.Reader, template, parent, pubKey, privKey)
Expect(err).ToNot(HaveOccurred())
cert, err := x509.ParseCertificate(certDER)
Expect(err).ToNot(HaveOccurred())
return cert
}
getCertificate := func(template *x509.Certificate) (*rsa.PrivateKey, *x509.Certificate) {
key, err := rsa.GenerateKey(rand.Reader, 1024)
Expect(err).ToNot(HaveOccurred())
return key, generateCertificate(template, template, &key.PublicKey, key)
}
It("accepts a valid certificate", func() {
cc := NewCertChain(testdata.GetTLSConfig()).(*certChain)
tlsCert, err := cc.getCertForSNI("quic.clemente.io")
Expect(err).ToNot(HaveOccurred())
for _, data := range tlsCert.Certificate {
var cert *x509.Certificate
cert, err = x509.ParseCertificate(data)
Expect(err).ToNot(HaveOccurred())
cm.chain = append(cm.chain, cert)
}
err = cm.Verify("quic.clemente.io")
Expect(err).ToNot(HaveOccurred())
})
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 = []*x509.Certificate{leafCert}
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 = []*x509.Certificate{leafCert}
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 = []*x509.Certificate{leafCert}
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())
})
// 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()
xcert, err := x509.ParseCertificate(cert.Certificate[0])
Expect(err).ToNot(HaveOccurred())
cm.chain = []*x509.Certificate{xcert}
err = cm.Verify("quic.clemente.io")
_, 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 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),
Subject: pkix.Name{CommonName: "quic.clemente.io"},
}
_, 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))
})
It("uses the Root CA given in the client 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")
}
templateRoot := &x509.Certificate{
SerialNumber: big.NewInt(1),
NotBefore: time.Now().Add(-time.Hour),
NotAfter: time.Now().Add(time.Hour),
IsCA: true,
BasicConstraintsValid: true,
}
rootKey, rootCert := getCertificate(templateRoot)
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"},
}
key, err := rsa.GenerateKey(rand.Reader, 1024)
Expect(err).ToNot(HaveOccurred())
leafCert := generateCertificate(template, rootCert, &key.PublicKey, rootKey)
rootCAPool := x509.NewCertPool()
rootCAPool.AddCert(rootCert)
cm.chain = []*x509.Certificate{leafCert}
cm.config = &tls.Config{
RootCAs: rootCAPool,
}
err = cm.Verify("google.com")
Expect(err).ToNot(HaveOccurred())
})
})
})