mirror of
https://github.com/emmansun/gmsm.git
synced 2025-04-27 04:36:19 +08:00
align x509 implementation
This commit is contained in:
parent
64a9f8792e
commit
f47260760b
@ -1,59 +1,109 @@
|
||||
package smx509
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"runtime"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type sum224 [sha256.Size224]byte
|
||||
|
||||
// CertPool is a set of certificates.
|
||||
type CertPool struct {
|
||||
bySubjectKeyId map[string][]int
|
||||
byName map[string][]int
|
||||
certs []*Certificate
|
||||
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
|
||||
|
||||
// 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() (*Certificate, error)
|
||||
}
|
||||
|
||||
// NewCertPool returns a new, empty CertPool.
|
||||
func NewCertPool() *CertPool {
|
||||
return &CertPool{
|
||||
bySubjectKeyId: make(map[string][]int),
|
||||
byName: make(map[string][]int),
|
||||
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) (*Certificate, error) {
|
||||
return s.lazyCerts[n].getCert()
|
||||
}
|
||||
|
||||
func (s *CertPool) copy() *CertPool {
|
||||
p := &CertPool{
|
||||
bySubjectKeyId: make(map[string][]int, len(s.bySubjectKeyId)),
|
||||
byName: make(map[string][]int, len(s.byName)),
|
||||
certs: make([]*Certificate, len(s.certs)),
|
||||
}
|
||||
for k, v := range s.bySubjectKeyId {
|
||||
indexes := make([]int, len(v))
|
||||
copy(indexes, v)
|
||||
p.bySubjectKeyId[k] = indexes
|
||||
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
|
||||
}
|
||||
copy(p.certs, s.certs)
|
||||
for k := range s.haveSum {
|
||||
p.haveSum[k] = true
|
||||
}
|
||||
copy(p.lazyCerts, s.lazyCerts)
|
||||
return p
|
||||
}
|
||||
|
||||
// SystemCertPool returns a copy of the system cert pool.
|
||||
//
|
||||
// Any mutations to the returned pool are not written to disk and do
|
||||
// not affect any other pool returned by SystemCertPool.
|
||||
// On Unix systems other than macOS the environment variables SSL_CERT_FILE and
|
||||
// SSL_CERT_DIR can be used to override the system default locations for the SSL
|
||||
// certificate file and SSL certificate files directory, respectively. The
|
||||
// latter can be a colon-separated list.
|
||||
//
|
||||
// New changes in the system cert pool might not be reflected
|
||||
// in subsequent calls.
|
||||
// Any mutations to the returned pool are not written to disk and do not affect
|
||||
// any other pool returned by SystemCertPool.
|
||||
//
|
||||
// New changes in the system cert pool might not be reflected in subsequent calls.
|
||||
func SystemCertPool() (*CertPool, error) {
|
||||
if runtime.GOOS == "windows" {
|
||||
// Issue 16736, 18609:
|
||||
return nil, errors.New("crypto/x509: system root pool is not available on Windows")
|
||||
}
|
||||
|
||||
if sysRoots := systemRootsPool(); sysRoots != nil {
|
||||
return sysRoots.copy(), nil
|
||||
}
|
||||
@ -62,19 +112,44 @@ func SystemCertPool() (*CertPool, error) {
|
||||
}
|
||||
|
||||
// findPotentialParents returns the indexes of certificates in s which might
|
||||
// have signed cert. The caller must not modify the returned slice.
|
||||
func (s *CertPool) findPotentialParents(cert *Certificate) []int {
|
||||
// have signed cert.
|
||||
func (s *CertPool) findPotentialParents(cert *Certificate) []*Certificate {
|
||||
if s == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var candidates []int
|
||||
if len(cert.AuthorityKeyId) > 0 {
|
||||
candidates = s.bySubjectKeyId[string(cert.AuthorityKeyId)]
|
||||
// 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 []*Certificate
|
||||
for _, c := range s.byName[string(cert.RawIssuer)] {
|
||||
candidate, err := s.cert(c)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
kidMatch := bytes.Equal(candidate.SubjectKeyId, cert.AuthorityKeyId)
|
||||
switch {
|
||||
case kidMatch:
|
||||
matchingKeyID = append(matchingKeyID, candidate)
|
||||
case (len(candidate.SubjectKeyId) == 0 && len(cert.AuthorityKeyId) > 0) ||
|
||||
(len(candidate.SubjectKeyId) > 0 && len(cert.AuthorityKeyId) == 0):
|
||||
oneKeyID = append(oneKeyID, candidate)
|
||||
default:
|
||||
mismatchKeyID = append(mismatchKeyID, candidate)
|
||||
}
|
||||
}
|
||||
if len(candidates) == 0 {
|
||||
candidates = s.byName[string(cert.RawIssuer)]
|
||||
|
||||
found := len(matchingKeyID) + len(oneKeyID) + len(mismatchKeyID)
|
||||
if found == 0 {
|
||||
return nil
|
||||
}
|
||||
candidates := make([]*Certificate, 0, found)
|
||||
candidates = append(candidates, matchingKeyID...)
|
||||
candidates = append(candidates, oneKeyID...)
|
||||
candidates = append(candidates, mismatchKeyID...)
|
||||
return candidates
|
||||
}
|
||||
|
||||
@ -82,15 +157,7 @@ func (s *CertPool) contains(cert *Certificate) bool {
|
||||
if s == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
candidates := s.byName[string(cert.RawSubject)]
|
||||
for _, c := range candidates {
|
||||
if s.certs[c].Equal(cert) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
return s.haveSum[sha256.Sum224(cert.Raw)]
|
||||
}
|
||||
|
||||
// AddCert adds a certificate to a pool.
|
||||
@ -98,21 +165,32 @@ func (s *CertPool) AddCert(cert *Certificate) {
|
||||
if cert == nil {
|
||||
panic("adding nil Certificate to CertPool")
|
||||
}
|
||||
s.addCertFunc(sha256.Sum224(cert.Raw), string(cert.RawSubject), func() (*Certificate, error) {
|
||||
return cert, 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() (*Certificate, error)) {
|
||||
if getCert == nil {
|
||||
panic("getCert can't be nil")
|
||||
}
|
||||
|
||||
// Check that the certificate isn't being added twice.
|
||||
if s.contains(cert) {
|
||||
if s.haveSum[rawSum224] {
|
||||
return
|
||||
}
|
||||
|
||||
n := len(s.certs)
|
||||
s.certs = append(s.certs, cert)
|
||||
|
||||
if len(cert.SubjectKeyId) > 0 {
|
||||
keyId := string(cert.SubjectKeyId)
|
||||
s.bySubjectKeyId[keyId] = append(s.bySubjectKeyId[keyId], n)
|
||||
}
|
||||
name := string(cert.RawSubject)
|
||||
s.byName[name] = append(s.byName[name], n)
|
||||
s.haveSum[rawSum224] = true
|
||||
s.lazyCerts = append(s.lazyCerts, lazyCert{
|
||||
rawSubject: []byte(rawSubject),
|
||||
getCert: getCert,
|
||||
})
|
||||
s.byName[rawSubject] = append(s.byName[rawSubject], len(s.lazyCerts)-1)
|
||||
}
|
||||
|
||||
// AppendCertsFromPEM attempts to parse a series of PEM encoded certificates.
|
||||
@ -132,24 +210,38 @@ func (s *CertPool) AppendCertsFromPEM(pemCerts []byte) (ok bool) {
|
||||
continue
|
||||
}
|
||||
|
||||
cert, err := ParseCertificate(block.Bytes)
|
||||
certBytes := block.Bytes
|
||||
cert, err := ParseCertificate(certBytes)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
s.AddCert(cert)
|
||||
var lazyCert struct {
|
||||
sync.Once
|
||||
v *Certificate
|
||||
}
|
||||
s.addCertFunc(sha256.Sum224(cert.Raw), string(cert.RawSubject), func() (*Certificate, error) {
|
||||
lazyCert.Do(func() {
|
||||
// This can't fail, as the same bytes already parsed above.
|
||||
lazyCert.v, _ = ParseCertificate(certBytes)
|
||||
certBytes = nil
|
||||
})
|
||||
return lazyCert.v, nil
|
||||
})
|
||||
ok = true
|
||||
}
|
||||
|
||||
return
|
||||
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, len(s.certs))
|
||||
for i, c := range s.certs {
|
||||
res[i] = c.RawSubject
|
||||
res := make([][]byte, s.len())
|
||||
for i, lc := range s.lazyCerts {
|
||||
res[i] = lc.rawSubject
|
||||
}
|
||||
return res
|
||||
}
|
||||
}
|
1013
smx509/parser.go
Normal file
1013
smx509/parser.go
Normal file
File diff suppressed because it is too large
Load Diff
@ -65,8 +65,6 @@ func ParsePKCS8PrivateKey(der []byte) (key interface{}, err error) {
|
||||
// This kind of key is commonly encoded in PEM blocks of type "PRIVATE KEY".
|
||||
func MarshalPKCS8PrivateKey(key interface{}) ([]byte, error) {
|
||||
switch k := key.(type) {
|
||||
case *ecdsa.PrivateKey:
|
||||
return marshalPKCS8ECPrivateKey(k)
|
||||
case *sm2.PrivateKey:
|
||||
return marshalPKCS8ECPrivateKey(&k.PrivateKey)
|
||||
}
|
||||
|
@ -4,3 +4,8 @@ package smx509
|
||||
var certFiles = []string{
|
||||
"/var/ssl/certs/ca-bundle.crt",
|
||||
}
|
||||
|
||||
// Possible directories with certificate files; all will be read.
|
||||
var certDirectories = []string{
|
||||
"/var/ssl/certs",
|
||||
}
|
@ -10,3 +10,10 @@ var certFiles = []string{
|
||||
"/usr/local/share/certs/ca-root-nss.crt", // DragonFly
|
||||
"/etc/openssl/certs/ca-certificates.crt", // NetBSD
|
||||
}
|
||||
|
||||
// Possible directories with certificate files; all will be read.
|
||||
var certDirectories = []string{
|
||||
"/etc/ssl/certs", // FreeBSD 12.2+
|
||||
"/usr/local/share/certs", // FreeBSD
|
||||
"/etc/openssl/certs", // NetBSD
|
||||
}
|
@ -5,3 +5,6 @@ package smx509
|
||||
|
||||
// Possible certificate files; stop after finding one.
|
||||
var certFiles = []string{}
|
||||
|
||||
// Possible directories with certificate files; all will be read.
|
||||
var certDirectories = []string{}
|
||||
|
@ -9,3 +9,10 @@ var certFiles = []string{
|
||||
"/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem", // CentOS/RHEL 7
|
||||
"/etc/ssl/cert.pem", // Alpine Linux
|
||||
}
|
||||
|
||||
// Possible directories with certificate files; all will be read.
|
||||
var certDirectories = []string{
|
||||
"/etc/ssl/certs", // SLES10/SLES11, https://golang.org/issue/12139
|
||||
"/etc/pki/tls/certs", // Fedora/RHEL
|
||||
"/system/etc/security/cacerts", // Android
|
||||
}
|
||||
|
@ -4,7 +4,6 @@
|
||||
package smx509
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
)
|
||||
|
||||
@ -21,7 +20,7 @@ func loadSystemRoots() (*CertPool, error) {
|
||||
roots := NewCertPool()
|
||||
var bestErr error
|
||||
for _, file := range certFiles {
|
||||
data, err := ioutil.ReadFile(file)
|
||||
data, err := os.ReadFile(file)
|
||||
if err == nil {
|
||||
roots.AppendCertsFromPEM(data)
|
||||
return roots, nil
|
||||
|
@ -6,3 +6,8 @@ var certFiles = []string{
|
||||
"/etc/ssl/certs/ca-certificates.crt", // Joyent SmartOS
|
||||
"/etc/ssl/cacert.pem", // OmniOS
|
||||
}
|
||||
|
||||
// Possible directories with certificate files; all will be read.
|
||||
var certDirectories = []string{
|
||||
"/etc/certs/CA",
|
||||
}
|
||||
|
@ -4,21 +4,12 @@
|
||||
package smx509
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Possible directories with certificate files; stop after successfully
|
||||
// reading at least one file from a directory.
|
||||
var certDirectories = []string{
|
||||
"/etc/ssl/certs", // SLES10/SLES11, https://golang.org/issue/12139
|
||||
"/system/etc/security/cacerts", // Android
|
||||
"/usr/local/share/certs", // FreeBSD
|
||||
"/etc/pki/tls/certs", // Fedora/RHEL
|
||||
"/etc/openssl/certs", // NetBSD
|
||||
"/var/ssl/certs", // AIX
|
||||
}
|
||||
|
||||
const (
|
||||
// certFileEnv is the environment variable which identifies where to locate
|
||||
// the SSL certificate file. If set this overrides the system default.
|
||||
@ -43,7 +34,7 @@ func loadSystemRoots() (*CertPool, error) {
|
||||
|
||||
var firstErr error
|
||||
for _, file := range files {
|
||||
data, err := ioutil.ReadFile(file)
|
||||
data, err := os.ReadFile(file)
|
||||
if err == nil {
|
||||
roots.AppendCertsFromPEM(data)
|
||||
break
|
||||
@ -55,32 +46,58 @@ func loadSystemRoots() (*CertPool, error) {
|
||||
|
||||
dirs := certDirectories
|
||||
if d := os.Getenv(certDirEnv); d != "" {
|
||||
dirs = []string{d}
|
||||
// OpenSSL and BoringSSL both use ":" as the SSL_CERT_DIR separator.
|
||||
// See:
|
||||
// * https://golang.org/issue/35325
|
||||
// * https://www.openssl.org/docs/man1.0.2/man1/c_rehash.html
|
||||
dirs = strings.Split(d, ":")
|
||||
}
|
||||
|
||||
for _, directory := range dirs {
|
||||
fis, err := ioutil.ReadDir(directory)
|
||||
fis, err := readUniqueDirectoryEntries(directory)
|
||||
if err != nil {
|
||||
if firstErr == nil && !os.IsNotExist(err) {
|
||||
firstErr = err
|
||||
}
|
||||
continue
|
||||
}
|
||||
rootsAdded := false
|
||||
for _, fi := range fis {
|
||||
data, err := ioutil.ReadFile(directory + "/" + fi.Name())
|
||||
if err == nil && roots.AppendCertsFromPEM(data) {
|
||||
rootsAdded = true
|
||||
data, err := os.ReadFile(directory + "/" + fi.Name())
|
||||
if err == nil {
|
||||
roots.AppendCertsFromPEM(data)
|
||||
}
|
||||
}
|
||||
if rootsAdded {
|
||||
return roots, nil
|
||||
}
|
||||
}
|
||||
|
||||
if len(roots.certs) > 0 || firstErr == nil {
|
||||
if roots.len() > 0 || firstErr == nil {
|
||||
return roots, nil
|
||||
}
|
||||
|
||||
return nil, firstErr
|
||||
}
|
||||
|
||||
// readUniqueDirectoryEntries is like os.ReadDir but omits
|
||||
// symlinks that point within the directory.
|
||||
func readUniqueDirectoryEntries(dir string) ([]fs.DirEntry, error) {
|
||||
files, err := os.ReadDir(dir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
uniq := files[:0]
|
||||
for _, f := range files {
|
||||
if !isSameDirSymlink(f, dir) {
|
||||
uniq = append(uniq, f)
|
||||
}
|
||||
}
|
||||
return uniq, nil
|
||||
}
|
||||
|
||||
// isSameDirSymlink reports whether fi in dir is a symlink with a
|
||||
// target not containing a slash.
|
||||
func isSameDirSymlink(f fs.DirEntry, dir string) bool {
|
||||
if f.Type()&fs.ModeSymlink == 0 {
|
||||
return false
|
||||
}
|
||||
target, err := os.Readlink(filepath.Join(dir, f.Name()))
|
||||
return err == nil && !strings.Contains(target, "/")
|
||||
}
|
||||
|
@ -7,6 +7,10 @@ import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func loadSystemRoots() (*CertPool, error) {
|
||||
return &CertPool{systemPool: true}, nil
|
||||
}
|
||||
|
||||
// Creates a new *syscall.CertContext representing the leaf certificate in an in-memory
|
||||
// certificate store containing itself and all of the intermediate certificates specified
|
||||
// in the opts.Intermediates CertPool.
|
||||
@ -35,7 +39,11 @@ func createStoreContext(leaf *Certificate, opts *VerifyOptions) (*syscall.CertCo
|
||||
}
|
||||
|
||||
if opts.Intermediates != nil {
|
||||
for _, intermediate := range opts.Intermediates.certs {
|
||||
for i := 0; i < opts.Intermediates.len(); i++ {
|
||||
intermediate, err := opts.Intermediates.cert(i)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ctx, err := syscall.CertCreateCertificateContext(syscall.X509_ASN_ENCODING|syscall.PKCS_7_ASN_ENCODING, &intermediate.Raw[0], uint32(len(intermediate.Raw)))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -125,9 +133,9 @@ func checkChainSSLServerPolicy(c *Certificate, chainCtx *syscall.CertChainContex
|
||||
if status.Error != 0 {
|
||||
switch status.Error {
|
||||
case syscall.CERT_E_EXPIRED:
|
||||
return x509.CertificateInvalidError{&c.Certificate, x509.Expired, ""}
|
||||
return x509.CertificateInvalidError{Cert: &c.Certificate, Reason: x509.Expired, Detail: ""}
|
||||
case syscall.CERT_E_CN_NO_MATCH:
|
||||
return x509.HostnameError{&c.Certificate, opts.DNSName}
|
||||
return x509.HostnameError{Certificate: &c.Certificate, Host: opts.DNSName}
|
||||
case syscall.CERT_E_UNTRUSTEDROOT:
|
||||
return UnknownAuthorityError{c, nil, nil}
|
||||
default:
|
||||
@ -148,6 +156,44 @@ func init() {
|
||||
}
|
||||
}
|
||||
|
||||
func verifyChain(c *Certificate, chainCtx *syscall.CertChainContext, opts *VerifyOptions) (chain []*Certificate, err error) {
|
||||
err = checkChainTrustStatus(c, chainCtx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if opts != nil && len(opts.DNSName) > 0 {
|
||||
err = checkChainSSLServerPolicy(c, chainCtx, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
chain, err = extractSimpleChain(chainCtx.Chains, int(chainCtx.ChainCount))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(chain) == 0 {
|
||||
return nil, errors.New("x509: internal error: system verifier returned an empty chain")
|
||||
}
|
||||
|
||||
// Mitigate CVE-2020-0601, where the Windows system verifier might be
|
||||
// tricked into using custom curve parameters for a trusted root, by
|
||||
// double-checking all ECDSA signatures. If the system was tricked into
|
||||
// using spoofed parameters, the signature will be invalid for the correct
|
||||
// ones we parsed. (We don't support custom curves ourselves.)
|
||||
for i, parent := range chain[1:] {
|
||||
if parent.PublicKeyAlgorithm != x509.ECDSA {
|
||||
continue
|
||||
}
|
||||
if err := parent.CheckSignature(chain[i].SignatureAlgorithm,
|
||||
chain[i].RawTBSCertificate, chain[i].Signature); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return chain, nil
|
||||
}
|
||||
|
||||
// systemVerify is like Verify, except that it uses CryptoAPI calls
|
||||
// to build certificate chains and verify them.
|
||||
func (c *Certificate) systemVerify(opts *VerifyOptions) (chains [][]*Certificate, err error) {
|
||||
@ -173,11 +219,6 @@ func (c *Certificate) systemVerify(opts *VerifyOptions) (chains [][]*Certificate
|
||||
if oid, ok := windowsExtKeyUsageOIDs[eku]; ok {
|
||||
oids = append(oids, &oid[0])
|
||||
}
|
||||
// Like the standard verifier, accept SGC EKUs as equivalent to ServerAuth.
|
||||
if eku == x509.ExtKeyUsageServerAuth {
|
||||
oids = append(oids, &syscall.OID_SERVER_GATED_CRYPTO[0])
|
||||
oids = append(oids, &syscall.OID_SGC_NETSCAPE[0])
|
||||
}
|
||||
}
|
||||
if oids != nil {
|
||||
para.RequestedUsage.Type = syscall.USAGE_MATCH_TYPE_OR
|
||||
@ -195,109 +236,39 @@ func (c *Certificate) systemVerify(opts *VerifyOptions) (chains [][]*Certificate
|
||||
verifyTime = &ft
|
||||
}
|
||||
|
||||
// CertGetCertificateChain will traverse Windows's root stores
|
||||
// in an attempt to build a verified certificate chain. Once
|
||||
// it has found a verified chain, it stops. MSDN docs on
|
||||
// CERT_CHAIN_CONTEXT:
|
||||
//
|
||||
// When a CERT_CHAIN_CONTEXT is built, the first simple chain
|
||||
// begins with an end certificate and ends with a self-signed
|
||||
// certificate. If that self-signed certificate is not a root
|
||||
// or otherwise trusted certificate, an attempt is made to
|
||||
// build a new chain. CTLs are used to create the new chain
|
||||
// beginning with the self-signed certificate from the original
|
||||
// chain as the end certificate of the new chain. This process
|
||||
// continues building additional simple chains until the first
|
||||
// self-signed certificate is a trusted certificate or until
|
||||
// an additional simple chain cannot be built.
|
||||
//
|
||||
// The result is that we'll only get a single trusted chain to
|
||||
// return to our caller.
|
||||
var chainCtx *syscall.CertChainContext
|
||||
err = syscall.CertGetCertificateChain(syscall.Handle(0), storeCtx, verifyTime, storeCtx.Store, para, 0, 0, &chainCtx)
|
||||
// The default is to return only the highest quality chain,
|
||||
// setting this flag will add additional lower quality contexts.
|
||||
// These are returned in the LowerQualityChains field.
|
||||
const CERT_CHAIN_RETURN_LOWER_QUALITY_CONTEXTS = 0x00000080
|
||||
|
||||
// CertGetCertificateChain will traverse Windows's root stores in an attempt to build a verified certificate chain
|
||||
var topCtx *syscall.CertChainContext
|
||||
err = syscall.CertGetCertificateChain(syscall.Handle(0), storeCtx, verifyTime, storeCtx.Store, para, CERT_CHAIN_RETURN_LOWER_QUALITY_CONTEXTS, 0, &topCtx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer syscall.CertFreeCertificateChain(chainCtx)
|
||||
defer syscall.CertFreeCertificateChain(topCtx)
|
||||
|
||||
err = checkChainTrustStatus(c, chainCtx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
chain, topErr := verifyChain(c, topCtx, opts)
|
||||
if topErr == nil {
|
||||
chains = append(chains, chain)
|
||||
}
|
||||
|
||||
if opts != nil && len(opts.DNSName) > 0 {
|
||||
err = checkChainSSLServerPolicy(c, chainCtx, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if lqCtxCount := topCtx.LowerQualityChainCount; lqCtxCount > 0 {
|
||||
lqCtxs := (*[1 << 20]*syscall.CertChainContext)(unsafe.Pointer(topCtx.LowerQualityChains))[:lqCtxCount:lqCtxCount]
|
||||
|
||||
chain, err := extractSimpleChain(chainCtx.Chains, int(chainCtx.ChainCount))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(chain) < 1 {
|
||||
return nil, errors.New("x509: internal error: system verifier returned an empty chain")
|
||||
}
|
||||
|
||||
// Mitigate CVE-2020-0601, where the Windows system verifier might be
|
||||
// tricked into using custom curve parameters for a trusted root, by
|
||||
// double-checking all ECDSA signatures. If the system was tricked into
|
||||
// using spoofed parameters, the signature will be invalid for the correct
|
||||
// ones we parsed. (We don't support custom curves ourselves.)
|
||||
for i, parent := range chain[1:] {
|
||||
if parent.PublicKeyAlgorithm != x509.ECDSA {
|
||||
continue
|
||||
}
|
||||
if err := parent.CheckSignature(chain[i].SignatureAlgorithm,
|
||||
chain[i].RawTBSCertificate, chain[i].Signature); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return [][]*Certificate{chain}, nil
|
||||
}
|
||||
|
||||
func loadSystemRoots() (*CertPool, error) {
|
||||
// TODO: restore this functionality on Windows. We tried to do
|
||||
// it in Go 1.8 but had to revert it. See Issue 18609.
|
||||
// Returning (nil, nil) was the old behavior, prior to CL 30578.
|
||||
// The if statement here avoids vet complaining about
|
||||
// unreachable code below.
|
||||
if true {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
const CRYPT_E_NOT_FOUND = 0x80092004
|
||||
|
||||
store, err := syscall.CertOpenSystemStore(0, syscall.StringToUTF16Ptr("ROOT"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer syscall.CertCloseStore(store, 0)
|
||||
|
||||
roots := NewCertPool()
|
||||
var cert *syscall.CertContext
|
||||
for {
|
||||
cert, err = syscall.CertEnumCertificatesInStore(store, cert)
|
||||
if err != nil {
|
||||
if errno, ok := err.(syscall.Errno); ok {
|
||||
if errno == CRYPT_E_NOT_FOUND {
|
||||
break
|
||||
}
|
||||
for _, ctx := range lqCtxs {
|
||||
chain, err := verifyChain(c, ctx, opts)
|
||||
if err == nil {
|
||||
chains = append(chains, chain)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
if cert == nil {
|
||||
break
|
||||
}
|
||||
// Copy the buf, since ParseCertificate does not create its own copy.
|
||||
buf := (*[1 << 20]byte)(unsafe.Pointer(cert.EncodedCert))[:cert.Length:cert.Length]
|
||||
buf2 := make([]byte, cert.Length)
|
||||
copy(buf2, buf)
|
||||
if c, err := ParseCertificate(buf2); err == nil {
|
||||
roots.AddCert(c)
|
||||
}
|
||||
}
|
||||
return roots, nil
|
||||
|
||||
if len(chains) == 0 {
|
||||
// Return the error from the highest quality context.
|
||||
return nil, topErr
|
||||
}
|
||||
|
||||
return chains, nil
|
||||
}
|
||||
|
@ -88,13 +88,10 @@ func MarshalSM2PrivateKey(key *sm2.PrivateKey) ([]byte, error) {
|
||||
// marshalECPrivateKey marshals an EC private key into ASN.1, DER format and
|
||||
// sets the curve ID to the given OID, or omits it if OID is nil.
|
||||
func marshalECPrivateKeyWithOID(key *ecdsa.PrivateKey, oid asn1.ObjectIdentifier) ([]byte, error) {
|
||||
privateKeyBytes := key.D.Bytes()
|
||||
paddedPrivateKey := make([]byte, (key.Curve.Params().N.BitLen()+7)/8)
|
||||
copy(paddedPrivateKey[len(paddedPrivateKey)-len(privateKeyBytes):], privateKeyBytes)
|
||||
|
||||
privateKey := make([]byte, (key.Curve.Params().N.BitLen()+7)/8)
|
||||
return asn1.Marshal(ecPrivateKey{
|
||||
Version: 1,
|
||||
PrivateKey: paddedPrivateKey,
|
||||
PrivateKey: key.D.FillBytes(privateKey),
|
||||
NamedCurveOID: oid,
|
||||
PublicKey: asn1.BitString{Bytes: elliptic.Marshal(key.Curve, key.X, key.Y)},
|
||||
})
|
||||
|
185
smx509/verify.go
185
smx509/verify.go
@ -7,7 +7,6 @@ import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
"os"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strings"
|
||||
@ -15,41 +14,6 @@ import (
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// ignoreCN disables interpreting Common Name as a hostname. See issue 24151.
|
||||
var ignoreCN = strings.Contains(os.Getenv("GODEBUG"), "x509ignoreCN=1")
|
||||
|
||||
// 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 x509.InvalidReason
|
||||
Detail string
|
||||
}
|
||||
|
||||
func (e CertificateInvalidError) Error() string {
|
||||
switch e.Reason {
|
||||
case x509.NotAuthorizedToSign:
|
||||
return "x509: certificate is not authorized to sign other certificates"
|
||||
case x509.Expired:
|
||||
return "x509: certificate has expired or is not yet valid: " + e.Detail
|
||||
case x509.CANotAuthorizedForThisName:
|
||||
return "x509: a root or intermediate certificate is not authorized to sign for this name: " + e.Detail
|
||||
case x509.CANotAuthorizedForExtKeyUsage:
|
||||
return "x509: a root or intermediate certificate is not authorized for an extended key usage: " + e.Detail
|
||||
case x509.TooManyIntermediates:
|
||||
return "x509: too many intermediates for path length constraint"
|
||||
case x509.IncompatibleUsage:
|
||||
return "x509: certificate specifies an incompatible key usage"
|
||||
case x509.NameMismatch:
|
||||
return "x509: issuer name does not match subject from issuing certificate"
|
||||
case x509.NameConstraintsWithoutSANs:
|
||||
return "x509: issuer has name constraints but leaf doesn't have a SAN extension"
|
||||
case x509.UnconstrainedName:
|
||||
return "x509: issuer has name constraints but leaf contains unknown or unconstrained name: " + e.Detail
|
||||
}
|
||||
return "x509: unknown error"
|
||||
}
|
||||
|
||||
// UnknownAuthorityError results when the certificate issuer is unknown
|
||||
type UnknownAuthorityError struct {
|
||||
Cert *Certificate
|
||||
@ -77,19 +41,6 @@ func (e UnknownAuthorityError) Error() string {
|
||||
return s
|
||||
}
|
||||
|
||||
// SystemRootsError results when we fail to load the system root certificates.
|
||||
type SystemRootsError struct {
|
||||
Err error
|
||||
}
|
||||
|
||||
func (se SystemRootsError) Error() string {
|
||||
msg := "x509: failed to load system roots and no roots provided"
|
||||
if se.Err != nil {
|
||||
return msg + "; " + se.Err.Error()
|
||||
}
|
||||
return msg
|
||||
}
|
||||
|
||||
// errNotParsed is returned when a certificate without ASN.1 contents is
|
||||
// verified. Platform-specific verification needs the ASN.1 contents.
|
||||
var errNotParsed = errors.New("x509: missing ASN.1 contents; use ParseCertificate")
|
||||
@ -295,18 +246,18 @@ func (c *Certificate) checkNameConstraints(count *int,
|
||||
|
||||
*count += excludedValue.Len()
|
||||
if *count > maxConstraintComparisons {
|
||||
return CertificateInvalidError{c, x509.TooManyConstraints, ""}
|
||||
return x509.CertificateInvalidError{&c.Certificate, x509.TooManyConstraints, ""}
|
||||
}
|
||||
|
||||
for i := 0; i < excludedValue.Len(); i++ {
|
||||
constraint := excludedValue.Index(i).Interface()
|
||||
match, err := match(parsedName, constraint)
|
||||
if err != nil {
|
||||
return CertificateInvalidError{c, x509.CANotAuthorizedForThisName, err.Error()}
|
||||
return x509.CertificateInvalidError{&c.Certificate, x509.CANotAuthorizedForThisName, err.Error()}
|
||||
}
|
||||
|
||||
if match {
|
||||
return CertificateInvalidError{c, x509.CANotAuthorizedForThisName, fmt.Sprintf("%s %q is excluded by constraint %q", nameType, name, constraint)}
|
||||
return x509.CertificateInvalidError{&c.Certificate, x509.CANotAuthorizedForThisName, fmt.Sprintf("%s %q is excluded by constraint %q", nameType, name, constraint)}
|
||||
}
|
||||
}
|
||||
|
||||
@ -314,7 +265,7 @@ func (c *Certificate) checkNameConstraints(count *int,
|
||||
|
||||
*count += permittedValue.Len()
|
||||
if *count > maxConstraintComparisons {
|
||||
return CertificateInvalidError{c, x509.TooManyConstraints, ""}
|
||||
return x509.CertificateInvalidError{&c.Certificate, x509.TooManyConstraints, ""}
|
||||
}
|
||||
|
||||
ok := true
|
||||
@ -323,7 +274,7 @@ func (c *Certificate) checkNameConstraints(count *int,
|
||||
|
||||
var err error
|
||||
if ok, err = match(parsedName, constraint); err != nil {
|
||||
return CertificateInvalidError{c, x509.CANotAuthorizedForThisName, err.Error()}
|
||||
return x509.CertificateInvalidError{&c.Certificate, x509.CANotAuthorizedForThisName, err.Error()}
|
||||
}
|
||||
|
||||
if ok {
|
||||
@ -332,7 +283,7 @@ func (c *Certificate) checkNameConstraints(count *int,
|
||||
}
|
||||
|
||||
if !ok {
|
||||
return CertificateInvalidError{c, x509.CANotAuthorizedForThisName, fmt.Sprintf("%s %q is not permitted by any constraint", nameType, name)}
|
||||
return x509.CertificateInvalidError{&c.Certificate, x509.CANotAuthorizedForThisName, fmt.Sprintf("%s %q is not permitted by any constraint", nameType, name)}
|
||||
}
|
||||
|
||||
return nil
|
||||
@ -383,7 +334,7 @@ func (c *Certificate) isValid(certType int, currentChain []*Certificate, opts *V
|
||||
if len(currentChain) > 0 {
|
||||
child := currentChain[len(currentChain)-1]
|
||||
if !bytes.Equal(child.RawIssuer, c.RawSubject) {
|
||||
return CertificateInvalidError{c, x509.NameMismatch, ""}
|
||||
return x509.CertificateInvalidError{&c.Certificate, x509.NameMismatch, ""}
|
||||
}
|
||||
}
|
||||
|
||||
@ -392,14 +343,14 @@ func (c *Certificate) isValid(certType int, currentChain []*Certificate, opts *V
|
||||
now = time.Now()
|
||||
}
|
||||
if now.Before(c.NotBefore) {
|
||||
return CertificateInvalidError{
|
||||
Cert: c,
|
||||
return x509.CertificateInvalidError{
|
||||
Cert: &c.Certificate,
|
||||
Reason: x509.Expired,
|
||||
Detail: fmt.Sprintf("current time %s is before %s", now.Format(time.RFC3339), c.NotBefore.Format(time.RFC3339)),
|
||||
}
|
||||
} else if now.After(c.NotAfter) {
|
||||
return CertificateInvalidError{
|
||||
Cert: c,
|
||||
return x509.CertificateInvalidError{
|
||||
Cert: &c.Certificate,
|
||||
Reason: x509.Expired,
|
||||
Detail: fmt.Sprintf("current time %s is after %s", now.Format(time.RFC3339), c.NotAfter.Format(time.RFC3339)),
|
||||
}
|
||||
@ -419,15 +370,8 @@ func (c *Certificate) isValid(certType int, currentChain []*Certificate, opts *V
|
||||
leaf = currentChain[0]
|
||||
}
|
||||
|
||||
checkNameConstraints := (certType == intermediateCertificate || certType == rootCertificate) && c.hasNameConstraints()
|
||||
if checkNameConstraints && leaf.commonNameAsHostname() {
|
||||
// This is the deprecated, legacy case of depending on the commonName as
|
||||
// a hostname. We don't enforce name constraints against the CN, but
|
||||
// VerifyHostname will look for hostnames in there if there are no SANs.
|
||||
// In order to ensure VerifyHostname will not accept an unchecked name,
|
||||
// return an error here.
|
||||
return CertificateInvalidError{c, x509.NameConstraintsWithoutSANs, ""}
|
||||
} else if checkNameConstraints && leaf.hasSANExtension() {
|
||||
if (certType == intermediateCertificate || certType == rootCertificate) &&
|
||||
c.hasNameConstraints() && leaf.hasSANExtension() {
|
||||
err := forEachSAN(leaf.getSANExtension(), func(tag int, data []byte) error {
|
||||
switch tag {
|
||||
case nameTypeEmail:
|
||||
@ -514,13 +458,13 @@ func (c *Certificate) isValid(certType int, currentChain []*Certificate, opts *V
|
||||
// encryption key could only be used for Diffie-Hellman key agreement.
|
||||
|
||||
if certType == intermediateCertificate && (!c.BasicConstraintsValid || !c.IsCA) {
|
||||
return CertificateInvalidError{c, x509.NotAuthorizedToSign, ""}
|
||||
return x509.CertificateInvalidError{&c.Certificate, x509.NotAuthorizedToSign, ""}
|
||||
}
|
||||
|
||||
if c.BasicConstraintsValid && c.MaxPathLen >= 0 {
|
||||
numIntermediates := len(currentChain) - 1
|
||||
if numIntermediates > c.MaxPathLen {
|
||||
return CertificateInvalidError{c, x509.TooManyIntermediates, ""}
|
||||
return x509.CertificateInvalidError{&c.Certificate, x509.TooManyIntermediates, ""}
|
||||
}
|
||||
}
|
||||
|
||||
@ -554,23 +498,36 @@ func (c *Certificate) Verify(opts VerifyOptions) (chains [][]*Certificate, err e
|
||||
if len(c.Raw) == 0 {
|
||||
return nil, errNotParsed
|
||||
}
|
||||
if opts.Intermediates != nil {
|
||||
for _, intermediate := range opts.Intermediates.certs {
|
||||
if len(intermediate.Raw) == 0 {
|
||||
return nil, errNotParsed
|
||||
}
|
||||
for i := 0; i < opts.Intermediates.len(); i++ {
|
||||
c, err := opts.Intermediates.cert(i)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("crypto/x509: error fetching intermediate: %w", err)
|
||||
}
|
||||
if len(c.Raw) == 0 {
|
||||
return nil, errNotParsed
|
||||
}
|
||||
}
|
||||
|
||||
// Use Windows's own verification and chain building.
|
||||
if opts.Roots == nil && runtime.GOOS == "windows" {
|
||||
return c.systemVerify(&opts)
|
||||
if opts.Roots == nil {
|
||||
return c.systemVerify(&opts)
|
||||
}
|
||||
if opts.Roots != nil && opts.Roots.systemPool {
|
||||
platformChains, err := c.systemVerify(&opts)
|
||||
// If the platform verifier succeeded, or there are no additional
|
||||
// roots, return the platform verifier result. Otherwise, continue
|
||||
// with the Go verifier.
|
||||
if err == nil || opts.Roots.len() == 0 {
|
||||
return platformChains, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if opts.Roots == nil {
|
||||
opts.Roots = systemRootsPool()
|
||||
if opts.Roots == nil {
|
||||
return nil, SystemRootsError{systemRootsErr}
|
||||
return nil, x509.SystemRootsError{Err: systemRootsErr}
|
||||
}
|
||||
}
|
||||
|
||||
@ -614,7 +571,7 @@ func (c *Certificate) Verify(opts VerifyOptions) (chains [][]*Certificate, err e
|
||||
}
|
||||
|
||||
if len(chains) == 0 {
|
||||
return nil, CertificateInvalidError{c, x509.IncompatibleUsage, ""}
|
||||
return nil, x509.CertificateInvalidError{&c.Certificate, x509.IncompatibleUsage, ""}
|
||||
}
|
||||
|
||||
return chains, nil
|
||||
@ -684,11 +641,11 @@ func (c *Certificate) buildChains(cache map[*Certificate][][]*Certificate, curre
|
||||
}
|
||||
}
|
||||
|
||||
for _, rootNum := range opts.Roots.findPotentialParents(c) {
|
||||
considerCandidate(rootCertificate, opts.Roots.certs[rootNum])
|
||||
for _, root := range opts.Roots.findPotentialParents(c) {
|
||||
considerCandidate(rootCertificate, root)
|
||||
}
|
||||
for _, intermediateNum := range opts.Intermediates.findPotentialParents(c) {
|
||||
considerCandidate(intermediateCertificate, opts.Intermediates.certs[intermediateNum])
|
||||
for _, intermediate := range opts.Intermediates.findPotentialParents(c) {
|
||||
considerCandidate(intermediateCertificate, intermediate)
|
||||
}
|
||||
|
||||
if len(chains) > 0 {
|
||||
@ -701,11 +658,16 @@ func (c *Certificate) buildChains(cache map[*Certificate][][]*Certificate, curre
|
||||
return
|
||||
}
|
||||
|
||||
func validHostnamePattern(host string) bool { return validHostname(host, true) }
|
||||
func validHostnameInput(host string) bool { return validHostname(host, false) }
|
||||
|
||||
// validHostname reports whether host is a valid hostname that can be matched or
|
||||
// matched against according to RFC 6125 2.2, with some leniency to accommodate
|
||||
// legacy values.
|
||||
func validHostname(host string) bool {
|
||||
host = strings.TrimSuffix(host, ".")
|
||||
func validHostname(host string, isPattern bool) bool {
|
||||
if !isPattern {
|
||||
host = strings.TrimSuffix(host, ".")
|
||||
}
|
||||
|
||||
if len(host) == 0 {
|
||||
return false
|
||||
@ -716,7 +678,7 @@ func validHostname(host string) bool {
|
||||
// Empty label.
|
||||
return false
|
||||
}
|
||||
if i == 0 && part == "*" {
|
||||
if isPattern && i == 0 && part == "*" {
|
||||
// Only allow full left-most wildcards, as those are the only ones
|
||||
// we match, and matching literal '*' characters is probably never
|
||||
// the expected behavior.
|
||||
@ -735,7 +697,7 @@ func validHostname(host string) bool {
|
||||
if c == '-' && j != 0 {
|
||||
continue
|
||||
}
|
||||
if c == '_' || c == ':' {
|
||||
if c == '_' {
|
||||
// Not valid characters in hostnames, but commonly
|
||||
// found in deployments outside the WebPKI.
|
||||
continue
|
||||
@ -747,21 +709,16 @@ func validHostname(host string) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// commonNameAsHostname reports whether the Common Name field should be
|
||||
// considered the hostname that the certificate is valid for. This is a legacy
|
||||
// behavior, disabled if the Subject Alt Name extension is present.
|
||||
//
|
||||
// It applies the strict validHostname check to the Common Name field, so that
|
||||
// certificates without SANs can still be validated against CAs with name
|
||||
// constraints if there is no risk the CN would be matched as a hostname.
|
||||
// See NameConstraintsWithoutSANs and issue 24151.
|
||||
func (c *Certificate) commonNameAsHostname() bool {
|
||||
return !ignoreCN && !c.hasSANExtension() && validHostname(c.Subject.CommonName)
|
||||
func matchExactly(hostA, hostB string) bool {
|
||||
if hostA == "" || hostA == "." || hostB == "" || hostB == "." {
|
||||
return false
|
||||
}
|
||||
return toLowerCaseASCII(hostA) == toLowerCaseASCII(hostB)
|
||||
}
|
||||
|
||||
func matchHostnames(pattern, host string) bool {
|
||||
host = strings.TrimSuffix(host, ".")
|
||||
pattern = strings.TrimSuffix(pattern, ".")
|
||||
pattern = toLowerCaseASCII(pattern)
|
||||
host = toLowerCaseASCII(strings.TrimSuffix(host, "."))
|
||||
|
||||
if len(pattern) == 0 || len(host) == 0 {
|
||||
return false
|
||||
@ -820,6 +777,13 @@ func toLowerCaseASCII(in string) string {
|
||||
|
||||
// VerifyHostname returns nil if c is a valid certificate for the named host.
|
||||
// Otherwise it returns an error describing the mismatch.
|
||||
//
|
||||
// IP addresses can be optionally enclosed in square brackets and are checked
|
||||
// against the IPAddresses field. Other names are checked case insensitively
|
||||
// against the DNSNames field. If the names are valid hostnames, the certificate
|
||||
// fields can have a wildcard as the left-most label.
|
||||
//
|
||||
// Note that the legacy Common Name field is ignored.
|
||||
func (c *Certificate) VerifyHostname(h string) error {
|
||||
// IP addresses may be written in [ ].
|
||||
candidateIP := h
|
||||
@ -837,20 +801,25 @@ func (c *Certificate) VerifyHostname(h string) error {
|
||||
return x509.HostnameError{&c.Certificate, candidateIP}
|
||||
}
|
||||
|
||||
lowered := toLowerCaseASCII(h)
|
||||
candidateName := toLowerCaseASCII(h) // Save allocations inside the loop.
|
||||
validCandidateName := validHostnameInput(candidateName)
|
||||
|
||||
if c.commonNameAsHostname() {
|
||||
if matchHostnames(toLowerCaseASCII(c.Subject.CommonName), lowered) {
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
for _, match := range c.DNSNames {
|
||||
if matchHostnames(toLowerCaseASCII(match), lowered) {
|
||||
for _, match := range c.DNSNames {
|
||||
// Ideally, we'd only match valid hostnames according to RFC 6125 like
|
||||
// browsers (more or less) do, but in practice Go is used in a wider
|
||||
// array of contexts and can't even assume DNS resolution. Instead,
|
||||
// always allow perfect matches, and only apply wildcard and trailing
|
||||
// dot processing to valid hostnames.
|
||||
if validCandidateName && validHostnamePattern(match) {
|
||||
if matchHostnames(match, candidateName) {
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
if matchExactly(match, candidateName) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return x509.HostnameError{&c.Certificate, h}
|
||||
}
|
||||
|
||||
|
1860
smx509/verify_test.go
Normal file
1860
smx509/verify_test.go
Normal file
File diff suppressed because it is too large
Load Diff
686
smx509/x509.go
686
smx509/x509.go
@ -3,7 +3,6 @@ package smx509
|
||||
import (
|
||||
"bytes"
|
||||
"crypto"
|
||||
"crypto/dsa"
|
||||
"crypto/ecdsa"
|
||||
"crypto/ed25519"
|
||||
"crypto/elliptic"
|
||||
@ -19,8 +18,6 @@ import (
|
||||
"math/big"
|
||||
"net"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
@ -680,647 +677,6 @@ type distributionPointName struct {
|
||||
RelativeName pkix.RDNSequence `asn1:"optional,tag:1"`
|
||||
}
|
||||
|
||||
func parsePublicKey(algo x509.PublicKeyAlgorithm, keyData *publicKeyInfo) (interface{}, error) {
|
||||
asn1Data := keyData.PublicKey.RightAlign()
|
||||
switch algo {
|
||||
case x509.RSA:
|
||||
// RSA public keys must have a NULL in the parameters.
|
||||
// See RFC 3279, Section 2.3.1.
|
||||
if !bytes.Equal(keyData.Algorithm.Parameters.FullBytes, asn1.NullBytes) {
|
||||
return nil, errors.New("x509: RSA key missing NULL parameters")
|
||||
}
|
||||
|
||||
p := new(pkcs1PublicKey)
|
||||
rest, err := asn1.Unmarshal(asn1Data, p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(rest) != 0 {
|
||||
return nil, errors.New("x509: trailing data after RSA public key")
|
||||
}
|
||||
|
||||
if p.N.Sign() <= 0 {
|
||||
return nil, errors.New("x509: RSA modulus is not a positive number")
|
||||
}
|
||||
if p.E <= 0 {
|
||||
return nil, errors.New("x509: RSA public exponent is not a positive number")
|
||||
}
|
||||
|
||||
pub := &rsa.PublicKey{
|
||||
E: p.E,
|
||||
N: p.N,
|
||||
}
|
||||
return pub, nil
|
||||
case x509.DSA:
|
||||
var p *big.Int
|
||||
rest, err := asn1.Unmarshal(asn1Data, &p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(rest) != 0 {
|
||||
return nil, errors.New("x509: trailing data after DSA public key")
|
||||
}
|
||||
paramsData := keyData.Algorithm.Parameters.FullBytes
|
||||
params := new(dsaAlgorithmParameters)
|
||||
rest, err = asn1.Unmarshal(paramsData, params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(rest) != 0 {
|
||||
return nil, errors.New("x509: trailing data after DSA parameters")
|
||||
}
|
||||
if p.Sign() <= 0 || params.P.Sign() <= 0 || params.Q.Sign() <= 0 || params.G.Sign() <= 0 {
|
||||
return nil, errors.New("x509: zero or negative DSA parameter")
|
||||
}
|
||||
pub := &dsa.PublicKey{
|
||||
Parameters: dsa.Parameters{
|
||||
P: params.P,
|
||||
Q: params.Q,
|
||||
G: params.G,
|
||||
},
|
||||
Y: p,
|
||||
}
|
||||
return pub, nil
|
||||
case x509.ECDSA:
|
||||
paramsData := keyData.Algorithm.Parameters.FullBytes
|
||||
namedCurveOID := new(asn1.ObjectIdentifier)
|
||||
rest, err := asn1.Unmarshal(paramsData, namedCurveOID)
|
||||
if err != nil {
|
||||
return nil, errors.New("x509: failed to parse ECDSA parameters as named curve")
|
||||
}
|
||||
if len(rest) != 0 {
|
||||
return nil, errors.New("x509: trailing data after ECDSA parameters")
|
||||
}
|
||||
namedCurve := namedCurveFromOID(*namedCurveOID)
|
||||
if namedCurve == nil {
|
||||
return nil, errors.New("x509: unsupported elliptic curve")
|
||||
}
|
||||
x, y := elliptic.Unmarshal(namedCurve, asn1Data)
|
||||
if x == nil {
|
||||
return nil, errors.New("x509: failed to unmarshal elliptic curve point")
|
||||
}
|
||||
pub := &ecdsa.PublicKey{
|
||||
Curve: namedCurve,
|
||||
X: x,
|
||||
Y: y,
|
||||
}
|
||||
return pub, nil
|
||||
case x509.Ed25519:
|
||||
// RFC 8410, Section 3
|
||||
// > For all of the OIDs, the parameters MUST be absent.
|
||||
if len(keyData.Algorithm.Parameters.FullBytes) != 0 {
|
||||
return nil, errors.New("x509: Ed25519 key encoded with illegal parameters")
|
||||
}
|
||||
if len(asn1Data) != ed25519.PublicKeySize {
|
||||
return nil, errors.New("x509: wrong Ed25519 public key size")
|
||||
}
|
||||
pub := make([]byte, ed25519.PublicKeySize)
|
||||
copy(pub, asn1Data)
|
||||
return ed25519.PublicKey(pub), nil
|
||||
default:
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
func forEachSAN(extension []byte, callback func(tag int, data []byte) error) error {
|
||||
// RFC 5280, 4.2.1.6
|
||||
|
||||
// SubjectAltName ::= GeneralNames
|
||||
//
|
||||
// GeneralNames ::= SEQUENCE SIZE (1..MAX) OF GeneralName
|
||||
//
|
||||
// GeneralName ::= CHOICE {
|
||||
// otherName [0] OtherName,
|
||||
// rfc822Name [1] IA5String,
|
||||
// dNSName [2] IA5String,
|
||||
// x400Address [3] ORAddress,
|
||||
// directoryName [4] Name,
|
||||
// ediPartyName [5] EDIPartyName,
|
||||
// uniformResourceIdentifier [6] IA5String,
|
||||
// iPAddress [7] OCTET STRING,
|
||||
// registeredID [8] OBJECT IDENTIFIER }
|
||||
var seq asn1.RawValue
|
||||
rest, err := asn1.Unmarshal(extension, &seq)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if len(rest) != 0 {
|
||||
return errors.New("x509: trailing data after X.509 extension")
|
||||
}
|
||||
if !seq.IsCompound || seq.Tag != 16 || seq.Class != 0 {
|
||||
return asn1.StructuralError{Msg: "bad SAN sequence"}
|
||||
}
|
||||
|
||||
rest = seq.Bytes
|
||||
for len(rest) > 0 {
|
||||
var v asn1.RawValue
|
||||
rest, err = asn1.Unmarshal(rest, &v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := callback(v.Tag, v.Bytes); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseSANExtension(value []byte) (dnsNames, emailAddresses []string, ipAddresses []net.IP, uris []*url.URL, err error) {
|
||||
err = forEachSAN(value, func(tag int, data []byte) error {
|
||||
switch tag {
|
||||
case nameTypeEmail:
|
||||
emailAddresses = append(emailAddresses, string(data))
|
||||
case nameTypeDNS:
|
||||
dnsNames = append(dnsNames, string(data))
|
||||
case nameTypeURI:
|
||||
uri, err := url.Parse(string(data))
|
||||
if err != nil {
|
||||
return fmt.Errorf("x509: cannot parse URI %q: %s", string(data), err)
|
||||
}
|
||||
if len(uri.Host) > 0 {
|
||||
if _, ok := domainToReverseLabels(uri.Host); !ok {
|
||||
return fmt.Errorf("x509: cannot parse URI %q: invalid domain", string(data))
|
||||
}
|
||||
}
|
||||
uris = append(uris, uri)
|
||||
case nameTypeIP:
|
||||
switch len(data) {
|
||||
case net.IPv4len, net.IPv6len:
|
||||
ipAddresses = append(ipAddresses, data)
|
||||
default:
|
||||
return errors.New("x509: cannot parse IP address of length " + strconv.Itoa(len(data)))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// isValidIPMask reports whether mask consists of zero or more 1 bits, followed by zero bits.
|
||||
func isValidIPMask(mask []byte) bool {
|
||||
seenZero := false
|
||||
|
||||
for _, b := range mask {
|
||||
if seenZero {
|
||||
if b != 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
switch b {
|
||||
case 0x00, 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe:
|
||||
seenZero = true
|
||||
case 0xff:
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func parseNameConstraintsExtension(out *x509.Certificate, e pkix.Extension) (unhandled bool, err error) {
|
||||
// RFC 5280, 4.2.1.10
|
||||
|
||||
// NameConstraints ::= SEQUENCE {
|
||||
// permittedSubtrees [0] GeneralSubtrees OPTIONAL,
|
||||
// excludedSubtrees [1] GeneralSubtrees OPTIONAL }
|
||||
//
|
||||
// GeneralSubtrees ::= SEQUENCE SIZE (1..MAX) OF GeneralSubtree
|
||||
//
|
||||
// GeneralSubtree ::= SEQUENCE {
|
||||
// base GeneralName,
|
||||
// minimum [0] BaseDistance DEFAULT 0,
|
||||
// maximum [1] BaseDistance OPTIONAL }
|
||||
//
|
||||
// BaseDistance ::= INTEGER (0..MAX)
|
||||
|
||||
outer := cryptobyte.String(e.Value)
|
||||
var toplevel, permitted, excluded cryptobyte.String
|
||||
var havePermitted, haveExcluded bool
|
||||
if !outer.ReadASN1(&toplevel, cryptobyte_asn1.SEQUENCE) ||
|
||||
!outer.Empty() ||
|
||||
!toplevel.ReadOptionalASN1(&permitted, &havePermitted, cryptobyte_asn1.Tag(0).ContextSpecific().Constructed()) ||
|
||||
!toplevel.ReadOptionalASN1(&excluded, &haveExcluded, cryptobyte_asn1.Tag(1).ContextSpecific().Constructed()) ||
|
||||
!toplevel.Empty() {
|
||||
return false, errors.New("x509: invalid NameConstraints extension")
|
||||
}
|
||||
|
||||
if !havePermitted && !haveExcluded || len(permitted) == 0 && len(excluded) == 0 {
|
||||
// From RFC 5280, Section 4.2.1.10:
|
||||
// “either the permittedSubtrees field
|
||||
// or the excludedSubtrees MUST be
|
||||
// present”
|
||||
return false, errors.New("x509: empty name constraints extension")
|
||||
}
|
||||
|
||||
getValues := func(subtrees cryptobyte.String) (dnsNames []string, ips []*net.IPNet, emails, uriDomains []string, err error) {
|
||||
for !subtrees.Empty() {
|
||||
var seq, value cryptobyte.String
|
||||
var tag cryptobyte_asn1.Tag
|
||||
if !subtrees.ReadASN1(&seq, cryptobyte_asn1.SEQUENCE) ||
|
||||
!seq.ReadAnyASN1(&value, &tag) {
|
||||
return nil, nil, nil, nil, fmt.Errorf("x509: invalid NameConstraints extension")
|
||||
}
|
||||
|
||||
var (
|
||||
dnsTag = cryptobyte_asn1.Tag(2).ContextSpecific()
|
||||
emailTag = cryptobyte_asn1.Tag(1).ContextSpecific()
|
||||
ipTag = cryptobyte_asn1.Tag(7).ContextSpecific()
|
||||
uriTag = cryptobyte_asn1.Tag(6).ContextSpecific()
|
||||
)
|
||||
|
||||
switch tag {
|
||||
case dnsTag:
|
||||
domain := string(value)
|
||||
if err := isIA5String(domain); err != nil {
|
||||
return nil, nil, nil, nil, errors.New("x509: invalid constraint value: " + err.Error())
|
||||
}
|
||||
|
||||
trimmedDomain := domain
|
||||
if len(trimmedDomain) > 0 && trimmedDomain[0] == '.' {
|
||||
// constraints can have a leading
|
||||
// period to exclude the domain
|
||||
// itself, but that's not valid in a
|
||||
// normal domain name.
|
||||
trimmedDomain = trimmedDomain[1:]
|
||||
}
|
||||
if _, ok := domainToReverseLabels(trimmedDomain); !ok {
|
||||
return nil, nil, nil, nil, fmt.Errorf("x509: failed to parse dnsName constraint %q", domain)
|
||||
}
|
||||
dnsNames = append(dnsNames, domain)
|
||||
|
||||
case ipTag:
|
||||
l := len(value)
|
||||
var ip, mask []byte
|
||||
|
||||
switch l {
|
||||
case 8:
|
||||
ip = value[:4]
|
||||
mask = value[4:]
|
||||
|
||||
case 32:
|
||||
ip = value[:16]
|
||||
mask = value[16:]
|
||||
|
||||
default:
|
||||
return nil, nil, nil, nil, fmt.Errorf("x509: IP constraint contained value of length %d", l)
|
||||
}
|
||||
|
||||
if !isValidIPMask(mask) {
|
||||
return nil, nil, nil, nil, fmt.Errorf("x509: IP constraint contained invalid mask %x", mask)
|
||||
}
|
||||
|
||||
ips = append(ips, &net.IPNet{IP: net.IP(ip), Mask: net.IPMask(mask)})
|
||||
|
||||
case emailTag:
|
||||
constraint := string(value)
|
||||
if err := isIA5String(constraint); err != nil {
|
||||
return nil, nil, nil, nil, errors.New("x509: invalid constraint value: " + err.Error())
|
||||
}
|
||||
|
||||
// If the constraint contains an @ then
|
||||
// it specifies an exact mailbox name.
|
||||
if strings.Contains(constraint, "@") {
|
||||
if _, ok := parseRFC2821Mailbox(constraint); !ok {
|
||||
return nil, nil, nil, nil, fmt.Errorf("x509: failed to parse rfc822Name constraint %q", constraint)
|
||||
}
|
||||
} else {
|
||||
// Otherwise it's a domain name.
|
||||
domain := constraint
|
||||
if len(domain) > 0 && domain[0] == '.' {
|
||||
domain = domain[1:]
|
||||
}
|
||||
if _, ok := domainToReverseLabels(domain); !ok {
|
||||
return nil, nil, nil, nil, fmt.Errorf("x509: failed to parse rfc822Name constraint %q", constraint)
|
||||
}
|
||||
}
|
||||
emails = append(emails, constraint)
|
||||
|
||||
case uriTag:
|
||||
domain := string(value)
|
||||
if err := isIA5String(domain); err != nil {
|
||||
return nil, nil, nil, nil, errors.New("x509: invalid constraint value: " + err.Error())
|
||||
}
|
||||
|
||||
if net.ParseIP(domain) != nil {
|
||||
return nil, nil, nil, nil, fmt.Errorf("x509: failed to parse URI constraint %q: cannot be IP address", domain)
|
||||
}
|
||||
|
||||
trimmedDomain := domain
|
||||
if len(trimmedDomain) > 0 && trimmedDomain[0] == '.' {
|
||||
// constraints can have a leading
|
||||
// period to exclude the domain itself,
|
||||
// but that's not valid in a normal
|
||||
// domain name.
|
||||
trimmedDomain = trimmedDomain[1:]
|
||||
}
|
||||
if _, ok := domainToReverseLabels(trimmedDomain); !ok {
|
||||
return nil, nil, nil, nil, fmt.Errorf("x509: failed to parse URI constraint %q", domain)
|
||||
}
|
||||
uriDomains = append(uriDomains, domain)
|
||||
|
||||
default:
|
||||
unhandled = true
|
||||
}
|
||||
}
|
||||
|
||||
return dnsNames, ips, emails, uriDomains, nil
|
||||
}
|
||||
|
||||
if out.PermittedDNSDomains, out.PermittedIPRanges, out.PermittedEmailAddresses, out.PermittedURIDomains, err = getValues(permitted); err != nil {
|
||||
return false, err
|
||||
}
|
||||
if out.ExcludedDNSDomains, out.ExcludedIPRanges, out.ExcludedEmailAddresses, out.ExcludedURIDomains, err = getValues(excluded); err != nil {
|
||||
return false, err
|
||||
}
|
||||
out.PermittedDNSDomainsCritical = e.Critical
|
||||
|
||||
return unhandled, nil
|
||||
}
|
||||
|
||||
func parseCertificate(in *certificate) (*x509.Certificate, error) {
|
||||
out := new(x509.Certificate)
|
||||
out.Raw = in.Raw
|
||||
out.RawTBSCertificate = in.TBSCertificate.Raw
|
||||
out.RawSubjectPublicKeyInfo = in.TBSCertificate.PublicKey.Raw
|
||||
out.RawSubject = in.TBSCertificate.Subject.FullBytes
|
||||
out.RawIssuer = in.TBSCertificate.Issuer.FullBytes
|
||||
|
||||
out.Signature = in.SignatureValue.RightAlign()
|
||||
out.SignatureAlgorithm = getSignatureAlgorithmFromAI(in.TBSCertificate.SignatureAlgorithm)
|
||||
|
||||
out.PublicKeyAlgorithm =
|
||||
getPublicKeyAlgorithmFromOID(in.TBSCertificate.PublicKey.Algorithm.Algorithm)
|
||||
var err error
|
||||
out.PublicKey, err = parsePublicKey(out.PublicKeyAlgorithm, &in.TBSCertificate.PublicKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
out.Version = in.TBSCertificate.Version + 1
|
||||
out.SerialNumber = in.TBSCertificate.SerialNumber
|
||||
|
||||
var issuer, subject pkix.RDNSequence
|
||||
if rest, err := asn1.Unmarshal(in.TBSCertificate.Subject.FullBytes, &subject); err != nil {
|
||||
return nil, err
|
||||
} else if len(rest) != 0 {
|
||||
return nil, errors.New("x509: trailing data after X.509 subject")
|
||||
}
|
||||
if rest, err := asn1.Unmarshal(in.TBSCertificate.Issuer.FullBytes, &issuer); err != nil {
|
||||
return nil, err
|
||||
} else if len(rest) != 0 {
|
||||
return nil, errors.New("x509: trailing data after X.509 subject")
|
||||
}
|
||||
|
||||
out.Issuer.FillFromRDNSequence(&issuer)
|
||||
out.Subject.FillFromRDNSequence(&subject)
|
||||
|
||||
out.NotBefore = in.TBSCertificate.Validity.NotBefore
|
||||
out.NotAfter = in.TBSCertificate.Validity.NotAfter
|
||||
|
||||
for _, e := range in.TBSCertificate.Extensions {
|
||||
out.Extensions = append(out.Extensions, e)
|
||||
unhandled := false
|
||||
|
||||
if len(e.Id) == 4 && e.Id[0] == 2 && e.Id[1] == 5 && e.Id[2] == 29 {
|
||||
switch e.Id[3] {
|
||||
case 15:
|
||||
// RFC 5280, 4.2.1.3
|
||||
var usageBits asn1.BitString
|
||||
if rest, err := asn1.Unmarshal(e.Value, &usageBits); err != nil {
|
||||
return nil, err
|
||||
} else if len(rest) != 0 {
|
||||
return nil, errors.New("x509: trailing data after X.509 KeyUsage")
|
||||
}
|
||||
|
||||
var usage int
|
||||
for i := 0; i < 9; i++ {
|
||||
if usageBits.At(i) != 0 {
|
||||
usage |= 1 << uint(i)
|
||||
}
|
||||
}
|
||||
out.KeyUsage = x509.KeyUsage(usage)
|
||||
|
||||
case 19:
|
||||
// RFC 5280, 4.2.1.9
|
||||
var constraints basicConstraints
|
||||
if rest, err := asn1.Unmarshal(e.Value, &constraints); err != nil {
|
||||
return nil, err
|
||||
} else if len(rest) != 0 {
|
||||
return nil, errors.New("x509: trailing data after X.509 BasicConstraints")
|
||||
}
|
||||
|
||||
out.BasicConstraintsValid = true
|
||||
out.IsCA = constraints.IsCA
|
||||
out.MaxPathLen = constraints.MaxPathLen
|
||||
out.MaxPathLenZero = out.MaxPathLen == 0
|
||||
// TODO: map out.MaxPathLen to 0 if it has the -1 default value? (Issue 19285)
|
||||
case 17:
|
||||
out.DNSNames, out.EmailAddresses, out.IPAddresses, out.URIs, err = parseSANExtension(e.Value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(out.DNSNames) == 0 && len(out.EmailAddresses) == 0 && len(out.IPAddresses) == 0 && len(out.URIs) == 0 {
|
||||
// If we didn't parse anything then we do the critical check, below.
|
||||
unhandled = true
|
||||
}
|
||||
|
||||
case 30:
|
||||
unhandled, err = parseNameConstraintsExtension(out, e)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
case 31:
|
||||
// RFC 5280, 4.2.1.13
|
||||
|
||||
// CRLDistributionPoints ::= SEQUENCE SIZE (1..MAX) OF DistributionPoint
|
||||
//
|
||||
// DistributionPoint ::= SEQUENCE {
|
||||
// distributionPoint [0] DistributionPointName OPTIONAL,
|
||||
// reasons [1] ReasonFlags OPTIONAL,
|
||||
// cRLIssuer [2] GeneralNames OPTIONAL }
|
||||
//
|
||||
// DistributionPointName ::= CHOICE {
|
||||
// fullName [0] GeneralNames,
|
||||
// nameRelativeToCRLIssuer [1] RelativeDistinguishedName }
|
||||
|
||||
var cdp []distributionPoint
|
||||
if rest, err := asn1.Unmarshal(e.Value, &cdp); err != nil {
|
||||
return nil, err
|
||||
} else if len(rest) != 0 {
|
||||
return nil, errors.New("x509: trailing data after X.509 CRL distribution point")
|
||||
}
|
||||
|
||||
for _, dp := range cdp {
|
||||
// Per RFC 5280, 4.2.1.13, one of distributionPoint or cRLIssuer may be empty.
|
||||
if len(dp.DistributionPoint.FullName) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, fullName := range dp.DistributionPoint.FullName {
|
||||
if fullName.Tag == 6 {
|
||||
out.CRLDistributionPoints = append(out.CRLDistributionPoints, string(fullName.Bytes))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case 35:
|
||||
// RFC 5280, 4.2.1.1
|
||||
var a authKeyId
|
||||
if rest, err := asn1.Unmarshal(e.Value, &a); err != nil {
|
||||
return nil, err
|
||||
} else if len(rest) != 0 {
|
||||
return nil, errors.New("x509: trailing data after X.509 authority key-id")
|
||||
}
|
||||
out.AuthorityKeyId = a.Id
|
||||
|
||||
case 37:
|
||||
// RFC 5280, 4.2.1.12. Extended Key Usage
|
||||
|
||||
// id-ce-extKeyUsage OBJECT IDENTIFIER ::= { id-ce 37 }
|
||||
//
|
||||
// ExtKeyUsageSyntax ::= SEQUENCE SIZE (1..MAX) OF KeyPurposeId
|
||||
//
|
||||
// KeyPurposeId ::= OBJECT IDENTIFIER
|
||||
|
||||
var keyUsage []asn1.ObjectIdentifier
|
||||
if rest, err := asn1.Unmarshal(e.Value, &keyUsage); err != nil {
|
||||
return nil, err
|
||||
} else if len(rest) != 0 {
|
||||
return nil, errors.New("x509: trailing data after X.509 ExtendedKeyUsage")
|
||||
}
|
||||
|
||||
for _, u := range keyUsage {
|
||||
if extKeyUsage, ok := extKeyUsageFromOID(u); ok {
|
||||
out.ExtKeyUsage = append(out.ExtKeyUsage, extKeyUsage)
|
||||
} else {
|
||||
out.UnknownExtKeyUsage = append(out.UnknownExtKeyUsage, u)
|
||||
}
|
||||
}
|
||||
|
||||
case 14:
|
||||
// RFC 5280, 4.2.1.2
|
||||
var keyid []byte
|
||||
if rest, err := asn1.Unmarshal(e.Value, &keyid); err != nil {
|
||||
return nil, err
|
||||
} else if len(rest) != 0 {
|
||||
return nil, errors.New("x509: trailing data after X.509 key-id")
|
||||
}
|
||||
out.SubjectKeyId = keyid
|
||||
|
||||
case 32:
|
||||
// RFC 5280 4.2.1.4: Certificate Policies
|
||||
var policies []policyInformation
|
||||
if rest, err := asn1.Unmarshal(e.Value, &policies); err != nil {
|
||||
return nil, err
|
||||
} else if len(rest) != 0 {
|
||||
return nil, errors.New("x509: trailing data after X.509 certificate policies")
|
||||
}
|
||||
out.PolicyIdentifiers = make([]asn1.ObjectIdentifier, len(policies))
|
||||
for i, policy := range policies {
|
||||
out.PolicyIdentifiers[i] = policy.Policy
|
||||
}
|
||||
|
||||
default:
|
||||
// Unknown extensions are recorded if critical.
|
||||
unhandled = true
|
||||
}
|
||||
} else if e.Id.Equal(oidExtensionAuthorityInfoAccess) {
|
||||
// RFC 5280 4.2.2.1: Authority Information Access
|
||||
var aia []authorityInfoAccess
|
||||
if rest, err := asn1.Unmarshal(e.Value, &aia); err != nil {
|
||||
return nil, err
|
||||
} else if len(rest) != 0 {
|
||||
return nil, errors.New("x509: trailing data after X.509 authority information")
|
||||
}
|
||||
|
||||
for _, v := range aia {
|
||||
// GeneralName: uniformResourceIdentifier [6] IA5String
|
||||
if v.Location.Tag != 6 {
|
||||
continue
|
||||
}
|
||||
if v.Method.Equal(oidAuthorityInfoAccessOcsp) {
|
||||
out.OCSPServer = append(out.OCSPServer, string(v.Location.Bytes))
|
||||
} else if v.Method.Equal(oidAuthorityInfoAccessIssuers) {
|
||||
out.IssuingCertificateURL = append(out.IssuingCertificateURL, string(v.Location.Bytes))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Unknown extensions are recorded if critical.
|
||||
unhandled = true
|
||||
}
|
||||
|
||||
if e.Critical && unhandled {
|
||||
out.UnhandledCriticalExtensions = append(out.UnhandledCriticalExtensions, e.Id)
|
||||
}
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// ParseCertificate parses a single certificate from the given ASN.1 DER data.
|
||||
func ParseCertificate(asn1Data []byte) (*Certificate, error) {
|
||||
var cert certificate
|
||||
rest, err := asn1.Unmarshal(asn1Data, &cert)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(rest) > 0 {
|
||||
return nil, asn1.SyntaxError{Msg: "trailing data"}
|
||||
}
|
||||
|
||||
var result *x509.Certificate
|
||||
result, err = parseCertificate(&cert)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Certificate{*result}, nil
|
||||
}
|
||||
|
||||
func ParseCertificatePEM(data []byte) (*Certificate, error) {
|
||||
block, _ := pem.Decode(data)
|
||||
if block == nil {
|
||||
return nil, errors.New("failed to decode PEM block containing CSR")
|
||||
}
|
||||
return ParseCertificate(block.Bytes)
|
||||
}
|
||||
|
||||
// ParseCertificates parses one or more certificates from the given ASN.1 DER
|
||||
// data. The certificates must be concatenated with no intermediate padding.
|
||||
func ParseCertificates(asn1Data []byte) ([]*Certificate, error) {
|
||||
var v []*certificate
|
||||
|
||||
for len(asn1Data) > 0 {
|
||||
cert := new(certificate)
|
||||
var err error
|
||||
asn1Data, err = asn1.Unmarshal(asn1Data, cert)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
v = append(v, cert)
|
||||
}
|
||||
|
||||
ret := make([]*Certificate, len(v))
|
||||
for i, ci := range v {
|
||||
cert, err := parseCertificate(ci)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ret[i] = &Certificate{*cert}
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func reverseBitsInAByte(in byte) byte {
|
||||
b1 := in>>4 | in<<4
|
||||
b2 := b1>>2&0x33 | b1<<2&0xcc
|
||||
@ -1422,7 +778,7 @@ func isIA5String(s string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func buildCertExtensions(template *x509.Certificate, subjectIsEmpty bool, authorityKeyId []byte) (ret []pkix.Extension, err error) {
|
||||
func buildCertExtensions(template *x509.Certificate, subjectIsEmpty bool, authorityKeyId, subjectKeyId []byte) (ret []pkix.Extension, err error) {
|
||||
ret = make([]pkix.Extension, 10 /* maximum number of elements. */)
|
||||
n := 0
|
||||
|
||||
@ -1452,9 +808,9 @@ func buildCertExtensions(template *x509.Certificate, subjectIsEmpty bool, author
|
||||
n++
|
||||
}
|
||||
|
||||
if len(template.SubjectKeyId) > 0 && !oidInExtensions(oidExtensionSubjectKeyId, template.ExtraExtensions) {
|
||||
if len(subjectKeyId) > 0 && !oidInExtensions(oidExtensionSubjectKeyId, template.ExtraExtensions) {
|
||||
ret[n].Id = oidExtensionSubjectKeyId
|
||||
ret[n].Value, err = asn1.Marshal(template.SubjectKeyId)
|
||||
ret[n].Value, err = asn1.Marshal(subjectKeyId)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@ -1878,14 +1234,20 @@ func CreateCertificate(rand io.Reader, template, parent *x509.Certificate, pub,
|
||||
if !ok {
|
||||
return nil, errors.New("x509: certificate private key does not implement crypto.Signer")
|
||||
}
|
||||
|
||||
if template.SerialNumber == nil {
|
||||
return nil, errors.New("x509: no SerialNumber given")
|
||||
}
|
||||
|
||||
if template.BasicConstraintsValid && !template.IsCA && template.MaxPathLen != -1 && (template.MaxPathLen != 0 || template.MaxPathLenZero) {
|
||||
return nil, errors.New("x509: only CAs are allowed to specify MaxPathLen")
|
||||
}
|
||||
|
||||
hashFunc, signatureAlgorithm, err := signingParamsForPublicKey(key.Public(), template.SignatureAlgorithm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if template.SerialNumber == nil {
|
||||
return nil, errors.New("x509: no SerialNumber given")
|
||||
}
|
||||
publicKeyBytes, publicKeyAlgorithm, err := marshalPublicKey(pub)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -1921,18 +1283,13 @@ func CreateCertificate(rand io.Reader, template, parent *x509.Certificate, pub,
|
||||
Equal(crypto.PublicKey) bool
|
||||
}
|
||||
|
||||
/*
|
||||
if privPub, ok := key.Public().(privateKey); !ok {
|
||||
return nil, errors.New("x509: internal error: supported public key does not implement Equal")
|
||||
} else if parent.PublicKey != nil && !privPub.Equal(parent.PublicKey) {
|
||||
return nil, errors.New("x509: provided PrivateKey doesn't match parent's PublicKey")
|
||||
}
|
||||
*/
|
||||
if privPub, ok := key.Public().(privateKey); ok && parent.PublicKey != nil && !privPub.Equal(parent.PublicKey) {
|
||||
if privPub, ok := key.Public().(privateKey); !ok {
|
||||
return nil, errors.New("x509: internal error: supported public key does not implement Equal")
|
||||
} else if parent.PublicKey != nil && !privPub.Equal(parent.PublicKey) {
|
||||
return nil, errors.New("x509: provided PrivateKey doesn't match parent's PublicKey")
|
||||
}
|
||||
|
||||
extensions, err := buildCertExtensions(template, bytes.Equal(asn1Subject, emptyASN1Subject), authorityKeyId)
|
||||
extensions, err := buildCertExtensions(template, bytes.Equal(asn1Subject, emptyASN1Subject), authorityKeyId, subjectKeyId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -1986,14 +1343,19 @@ func CreateCertificate(rand io.Reader, template, parent *x509.Certificate, pub,
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Check the signature to ensure the crypto.Signer behaved correctly.
|
||||
// We skip this check if the signature algorithm is MD5WithRSA as we
|
||||
// only support this algorithm for signing, and not verification.
|
||||
if sigAlg := getSignatureAlgorithmFromAI(signatureAlgorithm); sigAlg != x509.MD5WithRSA {
|
||||
sigAlg := getSignatureAlgorithmFromAI(signatureAlgorithm)
|
||||
switch sigAlg {
|
||||
case x509.MD5WithRSA, x509.SHA1WithRSA, x509.ECDSAWithSHA1:
|
||||
// We skip the check if the signature algorithm is only supported for
|
||||
// signing, not verification.
|
||||
default:
|
||||
if err := checkSignature(sigAlg, c.Raw, signature, key.Public()); err != nil {
|
||||
return nil, fmt.Errorf("x509: signature over certificate returned by signer is invalid: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return signedCert, nil
|
||||
}
|
||||
|
||||
|
1310
smx509/x509_test.go
1310
smx509/x509_test.go
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user