verify certificates using a client TLS config, if given

ref #407
This commit is contained in:
Marten Seemann
2017-02-03 15:46:38 +07:00
parent b455f3ee6a
commit 713df41c8b
3 changed files with 137 additions and 6 deletions

View File

@@ -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 {

View File

@@ -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))
})
})
})

View File

@@ -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,