Commit c24c6d83 authored by Adam Langley's avatar Adam Langley

crypto: move certificate verification into x509.

People have a need to verify certificates in situations other than TLS
client handshaking. Thus this CL moves certificate verification into
x509 and expands its abilities.

R=bradfitzgo
CC=golang-dev
https://golang.org/cl/4407046
parent 5500f027
......@@ -7,7 +7,6 @@ include ../../../Make.inc
TARG=crypto/tls
GOFILES=\
alert.go\
ca_set.go\
cipher_suites.go\
common.go\
conn.go\
......
......@@ -122,7 +122,7 @@ type Config struct {
// RootCAs defines the set of root certificate authorities
// that clients use when verifying server certificates.
// If RootCAs is nil, TLS uses the host's root CA set.
RootCAs *CASet
RootCAs *x509.CertPool
// NextProtos is a list of supported, application level protocols.
NextProtos []string
......@@ -158,7 +158,7 @@ func (c *Config) time() int64 {
return t()
}
func (c *Config) rootCAs() *CASet {
func (c *Config) rootCAs() *x509.CertPool {
s := c.RootCAs
if s == nil {
s = defaultRoots()
......@@ -224,7 +224,7 @@ var certFiles = []string{
var once sync.Once
func defaultRoots() *CASet {
func defaultRoots() *x509.CertPool {
once.Do(initDefaults)
return varDefaultRoots
}
......@@ -239,14 +239,14 @@ func initDefaults() {
initDefaultCipherSuites()
}
var varDefaultRoots *CASet
var varDefaultRoots *x509.CertPool
func initDefaultRoots() {
roots := NewCASet()
roots := x509.NewCertPool()
for _, file := range certFiles {
data, err := ioutil.ReadFile(file)
if err == nil {
roots.SetFromPEM(data)
roots.AppendCertsFromPEM(data)
break
}
}
......
......@@ -34,6 +34,9 @@ type Conn struct {
cipherSuite uint16
ocspResponse []byte // stapled OCSP response
peerCertificates []*x509.Certificate
// verifedChains contains the certificate chains that we built, as
// opposed to the ones presented by the server.
verifiedChains [][]*x509.Certificate
clientProtocol string
clientProtocolFallback bool
......
......@@ -88,7 +88,6 @@ func (c *Conn) clientHandshake() os.Error {
finishedHash.Write(certMsg.marshal())
certs := make([]*x509.Certificate, len(certMsg.certificates))
chain := NewCASet()
for i, asn1Data := range certMsg.certificates {
cert, err := x509.ParseCertificate(asn1Data)
if err != nil {
......@@ -96,47 +95,29 @@ func (c *Conn) clientHandshake() os.Error {
return os.ErrorString("failed to parse certificate from server: " + err.String())
}
certs[i] = cert
chain.AddCert(cert)
}
// If we don't have a root CA set configured then anything is accepted.
// TODO(rsc): Find certificates for OS X 10.6.
for cur := certs[0]; c.config.RootCAs != nil; {
parent := c.config.RootCAs.FindVerifiedParent(cur)
if parent != nil {
break
if c.config.RootCAs != nil {
opts := x509.VerifyOptions{
Roots: c.config.RootCAs,
CurrentTime: c.config.Time(),
DNSName: c.config.ServerName,
Intermediates: x509.NewCertPool(),
}
parent = chain.FindVerifiedParent(cur)
if parent == nil {
c.sendAlert(alertBadCertificate)
return os.ErrorString("could not find root certificate for chain")
for i, cert := range certs {
if i == 0 {
continue
}
if !parent.BasicConstraintsValid || !parent.IsCA {
opts.Intermediates.AddCert(cert)
}
c.verifiedChains, err = certs[0].Verify(opts)
if err != nil {
c.sendAlert(alertBadCertificate)
return os.ErrorString("intermediate certificate does not have CA bit set")
}
// KeyUsage status flags are ignored. From Engineering
// Security, Peter Gutmann: A European government CA marked its
// signing certificates as being valid for encryption only, but
// no-one noticed. Another European CA marked its signature
// keys as not being valid for signatures. A different CA
// marked its own trusted root certificate as being invalid for
// certificate signing. Another national CA distributed a
// certificate to be used to encrypt data for the country’s tax
// authority that was marked as only being usable for digital
// signatures but not for encryption. Yet another CA reversed
// the order of the bit flags in the keyUsage due to confusion
// over encoding endianness, essentially setting a random
// keyUsage in certificates that it issued. Another CA created
// a self-invalidating certificate by adding a certificate
// policy statement stipulating that the certificate had to be
// used strictly as specified in the keyUsage, and a keyUsage
// containing a flag indicating that the RSA encryption key
// could only be used for Diffie-Hellman key agreement.
cur = parent
return err
}
}
if _, ok := certs[0].PublicKey.(*rsa.PublicKey); !ok {
......
......@@ -6,6 +6,8 @@ include ../../../Make.inc
TARG=crypto/x509
GOFILES=\
cert_pool.go\
verify.go\
x509.go\
include ../../../Make.pkg
// Copyright 2009 The Go Authors. All rights reserved.
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package tls
package x509
import (
"crypto/x509"
"encoding/pem"
"strings"
)
// A CASet is a set of certificates.
type CASet struct {
bySubjectKeyId map[string][]*x509.Certificate
byName map[string][]*x509.Certificate
// Roots is a set of certificates.
type CertPool struct {
bySubjectKeyId map[string][]*Certificate
byName map[string][]*Certificate
}
// NewCASet returns a new, empty CASet.
func NewCASet() *CASet {
return &CASet{
make(map[string][]*x509.Certificate),
make(map[string][]*x509.Certificate),
// NewCertPool returns a new, empty CertPool.
func NewCertPool() *CertPool {
return &CertPool{
make(map[string][]*Certificate),
make(map[string][]*Certificate),
}
}
func nameToKey(name *x509.Name) string {
func nameToKey(name *Name) string {
return strings.Join(name.Country, ",") + "/" + strings.Join(name.Organization, ",") + "/" + strings.Join(name.OrganizationalUnit, ",") + "/" + name.CommonName
}
// FindVerifiedParent attempts to find the certificate in s which has signed
// the given certificate. If no such certificate can be found or the signature
// FindVerifiedParents attempts to find certificates in s which have signed the
// given certificate. If no such certificate can be found or the signature
// doesn't match, it returns nil.
func (s *CASet) FindVerifiedParent(cert *x509.Certificate) (parent *x509.Certificate) {
var candidates []*x509.Certificate
func (s *CertPool) FindVerifiedParents(cert *Certificate) (parents []*Certificate) {
var candidates []*Certificate
if len(cert.AuthorityKeyId) > 0 {
candidates = s.bySubjectKeyId[string(cert.AuthorityKeyId)]
......@@ -43,15 +42,15 @@ func (s *CASet) FindVerifiedParent(cert *x509.Certificate) (parent *x509.Certifi
for _, c := range candidates {
if cert.CheckSignatureFrom(c) == nil {
return c
parents = append(parents, c)
}
}
return nil
return
}
// AddCert adds a certificate to the set
func (s *CASet) AddCert(cert *x509.Certificate) {
// AddCert adds a certificate to a pool.
func (s *CertPool) AddCert(cert *Certificate) {
if len(cert.SubjectKeyId) > 0 {
keyId := string(cert.SubjectKeyId)
s.bySubjectKeyId[keyId] = append(s.bySubjectKeyId[keyId], cert)
......@@ -60,12 +59,13 @@ func (s *CASet) AddCert(cert *x509.Certificate) {
s.byName[name] = append(s.byName[name], cert)
}
// SetFromPEM attempts to parse a series of PEM encoded root certificates. It
// appends any certificates found to s and returns true if any certificates
// were successfully parsed. On many Linux systems, /etc/ssl/cert.pem will
// contains the system wide set of root CAs in a format suitable for this
// function.
func (s *CASet) SetFromPEM(pemCerts []byte) (ok bool) {
// AppendCertsFromPEM attempts to parse a series of PEM encoded root
// certificates. It appends any certificates found to s and returns true if any
// certificates were successfully parsed.
//
// On many Linux systems, /etc/ssl/cert.pem will contains the system wide set
// of root CAs in a format suitable for this function.
func (s *CertPool) AppendCertsFromPEM(pemCerts []byte) (ok bool) {
for len(pemCerts) > 0 {
var block *pem.Block
block, pemCerts = pem.Decode(pemCerts)
......@@ -76,7 +76,7 @@ func (s *CASet) SetFromPEM(pemCerts []byte) (ok bool) {
continue
}
cert, err := x509.ParseCertificate(block.Bytes)
cert, err := ParseCertificate(block.Bytes)
if err != nil {
continue
}
......
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package x509
import (
"os"
"strings"
"time"
)
type InvalidReason int
const (
// NotAuthorizedToSign results when a certificate is signed by another
// which isn't marked as a CA certificate.
NotAuthorizedToSign InvalidReason = iota
// Expired results when a certificate has expired, based on the time
// given in the VerifyOptions.
Expired
// CANotAuthorizedForThisName results when an intermediate or root
// certificate has a name constraint which doesn't include the name
// being checked.
CANotAuthorizedForThisName
)
// CertificateInvalidError results when an odd error occurs. Users of this
// library probably want to handle all these errors uniformly.
type CertificateInvalidError struct {
Cert *Certificate
Reason InvalidReason
}
func (e CertificateInvalidError) String() string {
switch e.Reason {
case NotAuthorizedToSign:
return "x509: certificate is not authorized to sign other other certificates"
case Expired:
return "x509: certificate has expired or is not yet valid"
case CANotAuthorizedForThisName:
return "x509: a root or intermediate certificate is not authorized to sign in this domain"
}
return "x509: unknown error"
}
// HostnameError results when the set of authorized names doesn't match the
// requested name.
type HostnameError struct {
Certificate *Certificate
Host string
}
func (h HostnameError) String() string {
var valid string
c := h.Certificate
if len(c.DNSNames) > 0 {
valid = strings.Join(c.DNSNames, ", ")
} else {
valid = c.Subject.CommonName
}
return "certificate is valid for " + valid + ", not " + h.Host
}
// UnknownAuthorityError results when the certificate issuer is unknown
type UnknownAuthorityError struct {
cert *Certificate
}
func (e UnknownAuthorityError) String() string {
return "x509: certificate signed by unknown authority"
}
// VerifyOptions contains parameters for Certificate.Verify. It's a structure
// because other PKIX verification APIs have ended up needing many options.
type VerifyOptions struct {
DNSName string
Intermediates *CertPool
Roots *CertPool
CurrentTime int64 // if 0, the current system time is used.
}
const (
leafCertificate = iota
intermediateCertificate
rootCertificate
)
// isValid performs validity checks on the c.
func (c *Certificate) isValid(certType int, opts *VerifyOptions) os.Error {
if opts.CurrentTime < c.NotBefore.Seconds() ||
opts.CurrentTime > c.NotAfter.Seconds() {
return CertificateInvalidError{c, Expired}
}
if len(c.PermittedDNSDomains) > 0 {
for _, domain := range c.PermittedDNSDomains {
if opts.DNSName == domain ||
(strings.HasSuffix(opts.DNSName, domain) &&
len(opts.DNSName) >= 1+len(domain) &&
opts.DNSName[len(opts.DNSName)-len(domain)-1] == '.') {
continue
}
return CertificateInvalidError{c, CANotAuthorizedForThisName}
}
}
// KeyUsage status flags are ignored. From Engineering Security, Peter
// Gutmann: A European government CA marked its signing certificates as
// being valid for encryption only, but no-one noticed. Another
// European CA marked its signature keys as not being valid for
// signatures. A different CA marked its own trusted root certificate
// as being invalid for certificate signing. Another national CA
// distributed a certificate to be used to encrypt data for the
// country’s tax authority that was marked as only being usable for
// digital signatures but not for encryption. Yet another CA reversed
// the order of the bit flags in the keyUsage due to confusion over
// encoding endianness, essentially setting a random keyUsage in
// certificates that it issued. Another CA created a self-invalidating
// certificate by adding a certificate policy statement stipulating
// that the certificate had to be used strictly as specified in the
// keyUsage, and a keyUsage containing a flag indicating that the RSA
// encryption key could only be used for Diffie-Hellman key agreement.
if certType == intermediateCertificate && (!c.BasicConstraintsValid || !c.IsCA) {
return CertificateInvalidError{c, NotAuthorizedToSign}
}
return nil
}
// Verify attempts to verify c by building one or more chains from c to a
// certificate in opts.roots, using certificates in opts.Intermediates if
// needed. If successful, it returns one or chains where the first element of
// the chain is c and the last element is from opts.Roots.
//
// WARNING: this doesn't do any revocation checking.
func (c *Certificate) Verify(opts VerifyOptions) (chains [][]*Certificate, err os.Error) {
if opts.CurrentTime == 0 {
opts.CurrentTime = time.Seconds()
}
err = c.isValid(leafCertificate, &opts)
if err != nil {
return
}
if len(opts.DNSName) > 0 {
err = c.VerifyHostname(opts.DNSName)
if err != nil {
return
}
}
return c.buildChains([]*Certificate{c}, &opts)
}
func appendToFreshChain(chain []*Certificate, cert *Certificate) []*Certificate {
n := make([]*Certificate, len(chain)+1)
copy(n, chain)
n[len(chain)] = cert
return n
}
func (c *Certificate) buildChains(currentChain []*Certificate, opts *VerifyOptions) (chains [][]*Certificate, err os.Error) {
for _, root := range opts.Roots.FindVerifiedParents(c) {
err = root.isValid(rootCertificate, opts)
if err != nil {
continue
}
chains = append(chains, appendToFreshChain(currentChain, root))
}
for _, intermediate := range opts.Intermediates.FindVerifiedParents(c) {
err = intermediate.isValid(intermediateCertificate, opts)
if err != nil {
continue
}
var childChains [][]*Certificate
childChains, err = intermediate.buildChains(appendToFreshChain(currentChain, intermediate), opts)
chains = append(chains, childChains...)
}
if len(chains) > 0 {
err = nil
}
if len(chains) == 0 && err == nil {
err = UnknownAuthorityError{c}
}
return
}
func matchHostnames(pattern, host string) bool {
if len(pattern) == 0 || len(host) == 0 {
return false
}
patternParts := strings.Split(pattern, ".", -1)
hostParts := strings.Split(host, ".", -1)
if len(patternParts) != len(hostParts) {
return false
}
for i, patternPart := range patternParts {
if patternPart == "*" {
continue
}
if patternPart != hostParts[i] {
return false
}
}
return true
}
// VerifyHostname returns nil if c is a valid certificate for the named host.
// Otherwise it returns an os.Error describing the mismatch.
func (c *Certificate) VerifyHostname(h string) os.Error {
if len(c.DNSNames) > 0 {
for _, match := range c.DNSNames {
if matchHostnames(match, h) {
return nil
}
}
// If Subject Alt Name is given, we ignore the common name.
} else if matchHostnames(c.Subject.CommonName, h) {
return nil
}
return HostnameError{c, h}
}
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment