diff --git a/ca_pool.go b/ca_pool.go index 3d813a5..00ff57b 100644 --- a/ca_pool.go +++ b/ca_pool.go @@ -21,6 +21,7 @@ import ( "crypto/x509" "encoding/hex" "github.com/pkg/errors" + "time" ) type CaPool struct { @@ -163,6 +164,46 @@ func (self *CaPool) GetChain(cert *x509.Certificate, additionalCerts ...*x509.Ce return assembleChain(cert, chainCandidates) } +// VerifyToRoot will obtain a chain and verify it to a root CA. This is similar to the requirements that +// OpenSSL has for TLS. +func (self *CaPool) VerifyToRoot(cert *x509.Certificate) ([][]*x509.Certificate, error) { + if cert == nil { + return nil, errors.New("cannot verify a nil certificate") + } + + opts := x509.VerifyOptions{ + Intermediates: self.IntermediatesAsStdPool(), + Roots: self.RootsAsStdPool(), + CurrentTime: time.Now(), + } + + return cert.Verify(opts) +} + +// IntermediatesAsStdPool returns all intermediates in an *x509.CertPool. Useful for calling standard x509 package +// functions. +func (self *CaPool) IntermediatesAsStdPool() *x509.CertPool { + pool := x509.NewCertPool() + + for _, cert := range self.intermediates { + pool.AddCert(cert) + } + + return pool +} + +// RootsAsStdPool returns all intermediates in an *x509.CertPool. Useful for calling standard x509 package +// functions. +func (self *CaPool) RootsAsStdPool() *x509.CertPool { + pool := x509.NewCertPool() + + for _, cert := range self.roots { + pool.AddCert(cert) + } + + return pool +} + // assembleChain starts at `startCert` and build the longest chain up through ancestor signing certs as it can from `chainCandidates`. func assembleChain(startCert *x509.Certificate, chainCandidates map[string]*x509.Certificate) []*x509.Certificate { if startCert == nil { diff --git a/ca_pool_test.go b/ca_pool_test.go index 6155366..3f194c4 100644 --- a/ca_pool_test.go +++ b/ca_pool_test.go @@ -196,6 +196,39 @@ func TestCaPool(t *testing.T) { req.Equal(pki.IntermediateA1.cert, chain[2]) req.Equal(pki.RootA.cert, chain[3]) }) + + t.Run("a chain can be built and verified from a nested intermediate", func(t *testing.T) { + req = require.New(t) + + chains, err := pool.VerifyToRoot(pki.LeafA3.cert) + + req.NoError(err) + req.Len(chains, 1) + req.Len(chains[0], 5) + req.Equal(chains[0][0], pki.LeafA3.cert) + req.Equal(chains[0][1], pki.IntermediateA3.cert) + req.Equal(chains[0][2], pki.IntermediateA2.cert) + req.Equal(chains[0][3], pki.IntermediateA1.cert) + req.Equal(chains[0][4], pki.RootA.cert) + }) + + t.Run("an error is returned attempting to verify a nil cert", func(t *testing.T) { + req = require.New(t) + + chains, err := pool.VerifyToRoot(nil) + + req.Error(err) + req.Nil(chains) + }) + + t.Run("an error is returned attempting to verify a leaf not in the pool", func(t *testing.T) { + req = require.New(t) + + chains, err := pool.VerifyToRoot(pki.LeafC.cert) + + req.Error(err) + req.Nil(chains) + }) }) } diff --git a/chains_test.go b/chains_test.go index 199f2d5..c507010 100644 --- a/chains_test.go +++ b/chains_test.go @@ -445,6 +445,7 @@ func newRootCa() *testCa { ExtKeyUsage: []x509.ExtKeyUsage{}, KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, BasicConstraintsValid: true, + MaxPathLen: -1, } rootBytes, err := x509.CreateCertificate(rand.Reader, root, root, &rootKey.PublicKey, rootKey) @@ -488,8 +489,7 @@ func (ca *testCa) NewIntermediateWithAKID() *testCa { KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, BasicConstraintsValid: true, AuthorityKeyId: ca.cert.SubjectKeyId, - MaxPathLen: 0, - MaxPathLenZero: true, + MaxPathLen: 5, } intermediateKey, err := rsa.GenerateKey(rand.Reader, 4096) @@ -537,8 +537,7 @@ func (ca *testCa) NewIntermediateWithoutAKID() *testCa { ExtKeyUsage: []x509.ExtKeyUsage{}, KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, BasicConstraintsValid: true, - MaxPathLen: 0, - MaxPathLenZero: true, + MaxPathLen: 5, } intermediateKey, err := rsa.GenerateKey(rand.Reader, 4096)