Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Forkcertpool #154

Draft
wants to merge 28 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
5be0e82
copy verify.go from x509
phbnf Feb 25, 2025
4957875
migrate packages
phbnf Feb 25, 2025
5f4d8b6
c.checkNameConstraints --> checkNameConstraints(c)
phbnf Feb 14, 2025
8b99bfd
c.isValid --> isValid(c)
phbnf Feb 14, 2025
9d503e1
c.Verify --> Verify(c)
phbnf Feb 14, 2025
16cd394
c.buildChains --> buildChains(c)
phbnf Feb 14, 2025
676da7e
c.VerifyHostName --> VerifyHostName(c)
phbnf Feb 14, 2025
7f0c6e4
delete UnhandledCriticalExtensions
phbnf Feb 14, 2025
270cad8
add TODO
phbnf Feb 14, 2025
88f2995
delete TooManyIntermediates
phbnf Feb 14, 2025
8f35b0d
delete CANotAuthorizedForThisName
phbnf Feb 14, 2025
619ca94
delete EKU and Policy chain checks
phbnf Feb 14, 2025
3656294
delete unused code
phbnf Feb 14, 2025
7393758
copy hasSanExtension
phbnf Feb 14, 2025
bc71c54
disable time checks
phbnf Feb 14, 2025
116c6dc
remove errors and hostname functions
phbnf Feb 14, 2025
9b9e4dc
remove more hostname functions
phbnf Feb 14, 2025
4b0ba83
remove VerifyHostname: it's never used
phbnf Feb 14, 2025
d7eeba0
remove error messages and options we don't use
phbnf Feb 14, 2025
54feb52
disable systemRoots
phbnf Feb 14, 2025
e7a5174
delete more unused things
phbnf Feb 14, 2025
94dee8a
delete hasSanExtension
phbnf Feb 25, 2025
19d0c02
use old error type
phbnf Feb 25, 2025
b802bf2
add struct field names
phbnf Feb 25, 2025
5c43dfa
copy cert_pool.go
phbnf Feb 25, 2025
6b5def7
migrate package
phbnf Feb 25, 2025
7f51119
delete systemRootPool
phbnf Feb 14, 2025
5f593b1
migrate options to local certPool
phbnf Feb 14, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
276 changes: 276 additions & 0 deletions internal/x509util/cert_pool.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,276 @@
// 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 x509util

import (
"bytes"
"crypto/sha256"
"crypto/x509"
"encoding/pem"
"sync"
)

type sum224 [sha256.Size224]byte

// CertPool is a set of certificates.
type CertPool struct {
byName map[string][]int // cert.RawSubject => index into lazyCerts

// lazyCerts contains funcs that return a certificate,
// lazily parsing/decompressing it as needed.
lazyCerts []lazyCert

// haveSum maps from sum224(cert.Raw) to true. It's used only
// for AddCert duplicate detection, to avoid CertPool.contains
// calls in the AddCert path (because the contains method can
// call getCert and otherwise negate savings from lazy getCert
// funcs).
haveSum map[sum224]bool

// systemPool indicates whether this is a special pool derived from the
// system roots. If it includes additional roots, it requires doing two
// verifications, one using the roots provided by the caller, and one using
// the system platform verifier.
systemPool bool
}

// lazyCert is minimal metadata about a Cert and a func to retrieve it
// in its normal expanded *Certificate form.
type lazyCert struct {
// rawSubject is the Certificate.RawSubject value.
// It's the same as the CertPool.byName key, but in []byte
// form to make CertPool.Subjects (as used by crypto/tls) do
// fewer allocations.
rawSubject []byte

// constraint is a function to run against a chain when it is a candidate to
// be added to the chain. This allows adding arbitrary constraints that are
// not specified in the certificate itself.
constraint func([]*x509.Certificate) error

// getCert returns the certificate.
//
// It is not meant to do network operations or anything else
// where a failure is likely; the func is meant to lazily
// parse/decompress data that is already known to be good. The
// error in the signature primarily is meant for use in the
// case where a cert file existed on local disk when the program
// started up is deleted later before it's read.
getCert func() (*x509.Certificate, error)
}

// NewCertPool returns a new, empty CertPool.
func NewCertPool() *CertPool {
return &CertPool{
byName: make(map[string][]int),
haveSum: make(map[sum224]bool),
}
}

// len returns the number of certs in the set.
// A nil set is a valid empty set.
func (s *CertPool) len() int {
if s == nil {
return 0
}
return len(s.lazyCerts)
}

// cert returns cert index n in s.
func (s *CertPool) cert(n int) (*x509.Certificate, func([]*x509.Certificate) error, error) {
cert, err := s.lazyCerts[n].getCert()
return cert, s.lazyCerts[n].constraint, err
}

// Clone returns a copy of s.
func (s *CertPool) Clone() *CertPool {
p := &CertPool{
byName: make(map[string][]int, len(s.byName)),
lazyCerts: make([]lazyCert, len(s.lazyCerts)),
haveSum: make(map[sum224]bool, len(s.haveSum)),
systemPool: s.systemPool,
}
for k, v := range s.byName {
indexes := make([]int, len(v))
copy(indexes, v)
p.byName[k] = indexes
}
for k := range s.haveSum {
p.haveSum[k] = true
}
copy(p.lazyCerts, s.lazyCerts)
return p
}

type potentialParent struct {
cert *x509.Certificate
constraint func([]*x509.Certificate) error
}

// findPotentialParents returns the certificates in s which might have signed
// cert.
func (s *CertPool) findPotentialParents(cert *x509.Certificate) []potentialParent {
if s == nil {
return nil
}

// consider all candidates where cert.Issuer matches cert.Subject.
// when picking possible candidates the list is built in the order
// of match plausibility as to save cycles in buildChains:
// AKID and SKID match
// AKID present, SKID missing / AKID missing, SKID present
// AKID and SKID don't match
var matchingKeyID, oneKeyID, mismatchKeyID []potentialParent
for _, c := range s.byName[string(cert.RawIssuer)] {
candidate, constraint, err := s.cert(c)
if err != nil {
continue
}
kidMatch := bytes.Equal(candidate.SubjectKeyId, cert.AuthorityKeyId)
switch {
case kidMatch:
matchingKeyID = append(matchingKeyID, potentialParent{candidate, constraint})
case (len(candidate.SubjectKeyId) == 0 && len(cert.AuthorityKeyId) > 0) ||
(len(candidate.SubjectKeyId) > 0 && len(cert.AuthorityKeyId) == 0):
oneKeyID = append(oneKeyID, potentialParent{candidate, constraint})
default:
mismatchKeyID = append(mismatchKeyID, potentialParent{candidate, constraint})
}
}

found := len(matchingKeyID) + len(oneKeyID) + len(mismatchKeyID)
if found == 0 {
return nil
}
candidates := make([]potentialParent, 0, found)
candidates = append(candidates, matchingKeyID...)
candidates = append(candidates, oneKeyID...)
candidates = append(candidates, mismatchKeyID...)
return candidates
}

func (s *CertPool) contains(cert *x509.Certificate) bool {
if s == nil {
return false
}
return s.haveSum[sha256.Sum224(cert.Raw)]
}

// AddCert adds a certificate to a pool.
func (s *CertPool) AddCert(cert *x509.Certificate) {
if cert == nil {
panic("adding nil Certificate to CertPool")
}
s.addCertFunc(sha256.Sum224(cert.Raw), string(cert.RawSubject), func() (*x509.Certificate, error) {
return cert, nil
}, nil)
}

// addCertFunc adds metadata about a certificate to a pool, along with
// a func to fetch that certificate later when needed.
//
// The rawSubject is Certificate.RawSubject and must be non-empty.
// The getCert func may be called 0 or more times.
func (s *CertPool) addCertFunc(rawSum224 sum224, rawSubject string, getCert func() (*x509.Certificate, error), constraint func([]*x509.Certificate) error) {
if getCert == nil {
panic("getCert can't be nil")
}

// Check that the certificate isn't being added twice.
if s.haveSum[rawSum224] {
return
}

s.haveSum[rawSum224] = true
s.lazyCerts = append(s.lazyCerts, lazyCert{
rawSubject: []byte(rawSubject),
getCert: getCert,
constraint: constraint,
})
s.byName[rawSubject] = append(s.byName[rawSubject], len(s.lazyCerts)-1)
}

// AppendCertsFromPEM attempts to parse a series of PEM encoded certificates.
// It appends any certificates found to s and reports whether any certificates
// were successfully parsed.
//
// On many Linux systems, /etc/ssl/cert.pem will contain 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)
if block == nil {
break
}
if block.Type != "CERTIFICATE" || len(block.Headers) != 0 {
continue
}

certBytes := block.Bytes
cert, err := x509.ParseCertificate(certBytes)
if err != nil {
continue
}
var lazyCert struct {
sync.Once
v *x509.Certificate
}
s.addCertFunc(sha256.Sum224(cert.Raw), string(cert.RawSubject), func() (*x509.Certificate, error) {
lazyCert.Do(func() {
// This can't fail, as the same bytes already parsed above.
lazyCert.v, _ = x509.ParseCertificate(certBytes)
certBytes = nil
})
return lazyCert.v, nil
}, nil)
ok = true
}

return ok
}

// Subjects returns a list of the DER-encoded subjects of
// all of the certificates in the pool.
//
// Deprecated: if s was returned by [SystemCertPool], Subjects
// will not include the system roots.
func (s *CertPool) Subjects() [][]byte {
res := make([][]byte, s.len())
for i, lc := range s.lazyCerts {
res[i] = lc.rawSubject
}
return res
}

// Equal reports whether s and other are equal.
func (s *CertPool) Equal(other *CertPool) bool {
if s == nil || other == nil {
return s == other
}
if s.systemPool != other.systemPool || len(s.haveSum) != len(other.haveSum) {
return false
}
for h := range s.haveSum {
if !other.haveSum[h] {
return false
}
}
return true
}

// AddCertWithConstraint adds a certificate to the pool with the additional
// constraint. When Certificate.Verify builds a chain which is rooted by cert,
// it will additionally pass the whole chain to constraint to determine its
// validity. If constraint returns a non-nil error, the chain will be discarded.
// constraint may be called concurrently from multiple goroutines.
func (s *CertPool) AddCertWithConstraint(cert *x509.Certificate, constraint func([]*x509.Certificate) error) {
if cert == nil {
panic("adding nil Certificate to CertPool")
}
s.addCertFunc(sha256.Sum224(cert.Raw), string(cert.RawSubject), func() (*x509.Certificate, error) {
return cert, nil
}, constraint)
}
Loading
Loading