mirror of
https://github.com/emmansun/gmsm.git
synced 2025-04-27 12:46:18 +08:00
MAGIC - bad idea to port whole x509
This commit is contained in:
parent
a1cb0a2616
commit
1183f9d3c2
2
go.mod
2
go.mod
@ -1,3 +1,5 @@
|
|||||||
module github.com/emmansun/gmsm
|
module github.com/emmansun/gmsm
|
||||||
|
|
||||||
go 1.14
|
go 1.14
|
||||||
|
|
||||||
|
require golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad
|
||||||
|
@ -446,7 +446,7 @@ func boothW6(in uint) (int, int) {
|
|||||||
return int(d), int(s & 1)
|
return int(d), int(s & 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// table[i][j] = (2^(6*i))*j*G mod P
|
// table[i][j] = (2^(6*i))*(j+1)*G mod P
|
||||||
func initTable() {
|
func initTable() {
|
||||||
p256Precomputed = new([43][32 * 8]uint64)
|
p256Precomputed = new([43][32 * 8]uint64)
|
||||||
|
|
||||||
|
11
sm2/sm2.go
11
sm2/sm2.go
@ -38,7 +38,7 @@ type combinedMult interface {
|
|||||||
CombinedMult(bigX, bigY *big.Int, baseScalar, scalar []byte) (x, y *big.Int)
|
CombinedMult(bigX, bigY *big.Int, baseScalar, scalar []byte) (x, y *big.Int)
|
||||||
}
|
}
|
||||||
|
|
||||||
// PrivateKey represents an ECDSA private key.
|
// PrivateKey represents an ECDSA SM2 private key.
|
||||||
type PrivateKey struct {
|
type PrivateKey struct {
|
||||||
ecdsa.PrivateKey
|
ecdsa.PrivateKey
|
||||||
}
|
}
|
||||||
@ -76,6 +76,15 @@ func (mode pointMarshalMode) mashal(curve elliptic.Curve, x, y *big.Int) []byte
|
|||||||
|
|
||||||
var defaultEncrypterOpts = EncrypterOpts{MarshalUncompressed}
|
var defaultEncrypterOpts = EncrypterOpts{MarshalUncompressed}
|
||||||
|
|
||||||
|
// FromECPrivateKey convert an ecdsa private key to SM2 private key
|
||||||
|
func (priv *PrivateKey) FromECPrivateKey(key *ecdsa.PrivateKey) (*PrivateKey, error) {
|
||||||
|
if key.Curve != P256() {
|
||||||
|
return nil, errors.New("It's NOT a sm2 curve private key")
|
||||||
|
}
|
||||||
|
priv.PrivateKey = *key
|
||||||
|
return priv, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Sign signs digest with priv, reading randomness from rand. The opts argument
|
// Sign signs digest with priv, reading randomness from rand. The opts argument
|
||||||
// is not currently used but, in keeping with the crypto.Signer interface,
|
// is not currently used but, in keeping with the crypto.Signer interface,
|
||||||
// should be the hash function used to digest the message.
|
// should be the hash function used to digest the message.
|
||||||
|
155
smx509/cert_pool.go
Normal file
155
smx509/cert_pool.go
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
package smx509
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/pem"
|
||||||
|
"errors"
|
||||||
|
"runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CertPool is a set of certificates.
|
||||||
|
type CertPool struct {
|
||||||
|
bySubjectKeyId map[string][]int
|
||||||
|
byName map[string][]int
|
||||||
|
certs []*Certificate
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCertPool returns a new, empty CertPool.
|
||||||
|
func NewCertPool() *CertPool {
|
||||||
|
return &CertPool{
|
||||||
|
bySubjectKeyId: make(map[string][]int),
|
||||||
|
byName: make(map[string][]int),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
for k, v := range s.byName {
|
||||||
|
indexes := make([]int, len(v))
|
||||||
|
copy(indexes, v)
|
||||||
|
p.byName[k] = indexes
|
||||||
|
}
|
||||||
|
copy(p.certs, s.certs)
|
||||||
|
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.
|
||||||
|
//
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
return loadSystemRoots()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
if s == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var candidates []int
|
||||||
|
if len(cert.AuthorityKeyId) > 0 {
|
||||||
|
candidates = s.bySubjectKeyId[string(cert.AuthorityKeyId)]
|
||||||
|
}
|
||||||
|
if len(candidates) == 0 {
|
||||||
|
candidates = s.byName[string(cert.RawIssuer)]
|
||||||
|
}
|
||||||
|
return candidates
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddCert adds a certificate to a pool.
|
||||||
|
func (s *CertPool) AddCert(cert *Certificate) {
|
||||||
|
if cert == nil {
|
||||||
|
panic("adding nil Certificate to CertPool")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that the certificate isn't being added twice.
|
||||||
|
if s.contains(cert) {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
cert, err := ParseCertificate(block.Bytes)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
s.AddCert(cert)
|
||||||
|
ok = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subjects returns a list of the DER-encoded subjects of
|
||||||
|
// all of the certificates in the pool.
|
||||||
|
func (s *CertPool) Subjects() [][]byte {
|
||||||
|
res := make([][]byte, len(s.certs))
|
||||||
|
for i, c := range s.certs {
|
||||||
|
res[i] = c.RawSubject
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
99
smx509/pkcs8.go
Normal file
99
smx509/pkcs8.go
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
package smx509
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/x509"
|
||||||
|
"crypto/x509/pkix"
|
||||||
|
"encoding/asn1"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/emmansun/gmsm/sm2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// pkcs8 reflects an ASN.1, PKCS#8 PrivateKey. See
|
||||||
|
// ftp://ftp.rsasecurity.com/pub/pkcs/pkcs-8/pkcs-8v1_2.asn
|
||||||
|
// and RFC 5208.
|
||||||
|
type pkcs8 struct {
|
||||||
|
Version int
|
||||||
|
Algo pkix.AlgorithmIdentifier
|
||||||
|
PrivateKey []byte
|
||||||
|
// optional attributes omitted.
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParsePKCS8PrivateKey parses an unencrypted private key in PKCS#8, ASN.1 DER form.
|
||||||
|
//
|
||||||
|
// It returns a *rsa.PrivateKey, a *ecdsa.PrivateKey, or a ed25519.PrivateKey.
|
||||||
|
// More types might be supported in the future.
|
||||||
|
//
|
||||||
|
// This kind of key is commonly encoded in PEM blocks of type "PRIVATE KEY".
|
||||||
|
func ParsePKCS8PrivateKey(der []byte) (key interface{}, err error) {
|
||||||
|
var privKey pkcs8
|
||||||
|
if _, err := asn1.Unmarshal(der, &privKey); err != nil {
|
||||||
|
if _, err := asn1.Unmarshal(der, &ecPrivateKey{}); err == nil {
|
||||||
|
return nil, errors.New("x509: failed to parse private key (use ParseECPrivateKey instead for this key format)")
|
||||||
|
}
|
||||||
|
if _, err := asn1.Unmarshal(der, &pkcs1PrivateKey{}); err == nil {
|
||||||
|
return nil, errors.New("x509: failed to parse private key (use ParsePKCS1PrivateKey instead for this key format)")
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !privKey.Algo.Algorithm.Equal(oidPublicKeyECDSA) {
|
||||||
|
return x509.ParsePKCS8PrivateKey(der)
|
||||||
|
}
|
||||||
|
bytes := privKey.Algo.Parameters.FullBytes
|
||||||
|
namedCurveOID := new(asn1.ObjectIdentifier)
|
||||||
|
if _, err := asn1.Unmarshal(bytes, namedCurveOID); err != nil {
|
||||||
|
namedCurveOID = nil
|
||||||
|
}
|
||||||
|
ecKey, err := parseECPrivateKey(namedCurveOID, privKey.PrivateKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("x509: failed to parse EC private key embedded in PKCS#8: " + err.Error())
|
||||||
|
}
|
||||||
|
if namedCurveOID.Equal(oidNamedCurveP256SM2) {
|
||||||
|
key, err = new(sm2.PrivateKey).FromECPrivateKey(ecKey)
|
||||||
|
} else {
|
||||||
|
key = ecKey
|
||||||
|
}
|
||||||
|
return key, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalPKCS8PrivateKey converts a private key to PKCS#8, ASN.1 DER form.
|
||||||
|
//
|
||||||
|
// The following key types are currently supported: *rsa.PrivateKey, *ecdsa.PrivateKey
|
||||||
|
// and ed25519.PrivateKey. Unsupported key types result in an 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)
|
||||||
|
}
|
||||||
|
return x509.MarshalPKCS8PrivateKey(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func marshalPKCS8ECPrivateKey(k *ecdsa.PrivateKey) ([]byte, error) {
|
||||||
|
var privKey pkcs8
|
||||||
|
oid, ok := oidFromNamedCurve(k.Curve)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("x509: unknown curve while marshaling to PKCS#8")
|
||||||
|
}
|
||||||
|
|
||||||
|
oidBytes, err := asn1.Marshal(oid)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("x509: failed to marshal curve OID: " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
privKey.Algo = pkix.AlgorithmIdentifier{
|
||||||
|
Algorithm: oidPublicKeyECDSA,
|
||||||
|
Parameters: asn1.RawValue{
|
||||||
|
FullBytes: oidBytes,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if privKey.PrivateKey, err = marshalECPrivateKeyWithOID(k, nil); err != nil {
|
||||||
|
return nil, errors.New("x509: failed to marshal EC private key while building PKCS#8: " + err.Error())
|
||||||
|
}
|
||||||
|
return asn1.Marshal(privKey)
|
||||||
|
}
|
157
smx509/pkcs8_test.go
Normal file
157
smx509/pkcs8_test.go
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
package smx509
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/ed25519"
|
||||||
|
"crypto/elliptic"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/emmansun/gmsm/sm2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Generated using:
|
||||||
|
// openssl genrsa 1024 | openssl pkcs8 -topk8 -nocrypt
|
||||||
|
var pkcs8RSAPrivateKeyHex = `30820278020100300d06092a864886f70d0101010500048202623082025e02010002818100cfb1b5bf9685ffa97b4f99df4ff122b70e59ac9b992f3bc2b3dde17d53c1a34928719b02e8fd17839499bfbd515bd6ef99c7a1c47a239718fe36bfd824c0d96060084b5f67f0273443007a24dfaf5634f7772c9346e10eb294c2306671a5a5e719ae24b4de467291bc571014b0e02dec04534d66a9bb171d644b66b091780e8d020301000102818100b595778383c4afdbab95d2bfed12b3f93bb0a73a7ad952f44d7185fd9ec6c34de8f03a48770f2009c8580bcd275e9632714e9a5e3f32f29dc55474b2329ff0ebc08b3ffcb35bc96e6516b483df80a4a59cceb71918cbabf91564e64a39d7e35dce21cb3031824fdbc845dba6458852ec16af5dddf51a8397a8797ae0337b1439024100ea0eb1b914158c70db39031dd8904d6f18f408c85fbbc592d7d20dee7986969efbda081fdf8bc40e1b1336d6b638110c836bfdc3f314560d2e49cd4fbde1e20b024100e32a4e793b574c9c4a94c8803db5152141e72d03de64e54ef2c8ed104988ca780cd11397bc359630d01b97ebd87067c5451ba777cf045ca23f5912f1031308c702406dfcdbbd5a57c9f85abc4edf9e9e29153507b07ce0a7ef6f52e60dcfebe1b8341babd8b789a837485da6c8d55b29bbb142ace3c24a1f5b54b454d01b51e2ad03024100bd6a2b60dee01e1b3bfcef6a2f09ed027c273cdbbaf6ba55a80f6dcc64e4509ee560f84b4f3e076bd03b11e42fe71a3fdd2dffe7e0902c8584f8cad877cdc945024100aa512fa4ada69881f1d8bb8ad6614f192b83200aef5edf4811313d5ef30a86cbd0a90f7b025c71ea06ec6b34db6306c86b1040670fd8654ad7291d066d06d031`
|
||||||
|
|
||||||
|
// Generated using:
|
||||||
|
// openssl ecparam -genkey -name secp224r1 | openssl pkcs8 -topk8 -nocrypt
|
||||||
|
var pkcs8P224PrivateKeyHex = `3078020100301006072a8648ce3d020106052b810400210461305f020101041cca3d72b3e88fed2684576dad9b80a9180363a5424986900e3abcab3fa13c033a0004f8f2a6372872a4e61263ed893afb919576a4cacfecd6c081a2cbc76873cf4ba8530703c6042b3a00e2205087e87d2435d2e339e25702fae1`
|
||||||
|
|
||||||
|
// Generated using:
|
||||||
|
// openssl ecparam -genkey -name secp256r1 | openssl pkcs8 -topk8 -nocrypt
|
||||||
|
var pkcs8P256PrivateKeyHex = `308187020100301306072a8648ce3d020106082a8648ce3d030107046d306b0201010420dad6b2f49ca774c36d8ae9517e935226f667c929498f0343d2424d0b9b591b43a14403420004b9c9b90095476afe7b860d8bd43568cab7bcb2eed7b8bf2fa0ce1762dd20b04193f859d2d782b1e4cbfd48492f1f533113a6804903f292258513837f07fda735`
|
||||||
|
|
||||||
|
var pkcs8SM2P256PrivateKeyHex = `308187020100301306072a8648ce3d020106082a811ccf5501822d046d306b0201010420b26da57ba53004ddcd387ad46a361b51b308481f2327d47fb10c5fb3a8c86b92a144034200040d5365bfdbdc564c5b0eda0a85ddbd753821a709de90efe0666ba2544766acf1100ac0484d166842011da5cd6139e53dedb99ce37cea9edf4941628066e861bf`
|
||||||
|
|
||||||
|
// Generated using:
|
||||||
|
// openssl ecparam -genkey -name secp384r1 | openssl pkcs8 -topk8 -nocrypt
|
||||||
|
var pkcs8P384PrivateKeyHex = `3081b6020100301006072a8648ce3d020106052b8104002204819e30819b02010104309bf832f6aaaeacb78ce47ffb15e6fd0fd48683ae79df6eca39bfb8e33829ac94aa29d08911568684c2264a08a4ceb679a164036200049070ad4ed993c7770d700e9f6dc2baa83f63dd165b5507f98e8ff29b5d2e78ccbe05c8ddc955dbf0f7497e8222cfa49314fe4e269459f8e880147f70d785e530f2939e4bf9f838325bb1a80ad4cf59272ae0e5efe9a9dc33d874492596304bd3`
|
||||||
|
|
||||||
|
// Generated using:
|
||||||
|
// openssl ecparam -genkey -name secp521r1 | openssl pkcs8 -topk8 -nocrypt
|
||||||
|
//
|
||||||
|
// Note that OpenSSL will truncate the private key if it can (i.e. it emits it
|
||||||
|
// like an integer, even though it's an OCTET STRING field). Thus if you
|
||||||
|
// regenerate this you may, randomly, find that it's a byte shorter than
|
||||||
|
// expected and the Go test will fail to recreate it exactly.
|
||||||
|
var pkcs8P521PrivateKeyHex = `3081ee020100301006072a8648ce3d020106052b810400230481d63081d3020101044200cfe0b87113a205cf291bb9a8cd1a74ac6c7b2ebb8199aaa9a5010d8b8012276fa3c22ac913369fa61beec2a3b8b4516bc049bde4fb3b745ac11b56ab23ac52e361a1818903818600040138f75acdd03fbafa4f047a8e4b272ba9d555c667962b76f6f232911a5786a0964e5edea6bd21a6f8725720958de049c6e3e6661c1c91b227cebee916c0319ed6ca003db0a3206d372229baf9dd25d868bf81140a518114803ce40c1855074d68c4e9dab9e65efba7064c703b400f1767f217dac82715ac1f6d88c74baf47a7971de4ea`
|
||||||
|
|
||||||
|
// From RFC 8410, Section 7.
|
||||||
|
var pkcs8Ed25519PrivateKeyHex = `302e020100300506032b657004220420d4ee72dbf913584ad5b6d8f1f769f8ad3afe7c28cbf1d4fbe097a88f44755842`
|
||||||
|
|
||||||
|
func TestPKCS8(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
keyHex string
|
||||||
|
keyType reflect.Type
|
||||||
|
curve elliptic.Curve
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "RSA private key",
|
||||||
|
keyHex: pkcs8RSAPrivateKeyHex,
|
||||||
|
keyType: reflect.TypeOf(&rsa.PrivateKey{}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "P-224 private key",
|
||||||
|
keyHex: pkcs8P224PrivateKeyHex,
|
||||||
|
keyType: reflect.TypeOf(&ecdsa.PrivateKey{}),
|
||||||
|
curve: elliptic.P224(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "P-256 private key",
|
||||||
|
keyHex: pkcs8P256PrivateKeyHex,
|
||||||
|
keyType: reflect.TypeOf(&ecdsa.PrivateKey{}),
|
||||||
|
curve: elliptic.P256(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "SM2 P-256 private key",
|
||||||
|
keyHex: pkcs8SM2P256PrivateKeyHex,
|
||||||
|
keyType: reflect.TypeOf(&sm2.PrivateKey{}),
|
||||||
|
curve: sm2.P256(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "P-384 private key",
|
||||||
|
keyHex: pkcs8P384PrivateKeyHex,
|
||||||
|
keyType: reflect.TypeOf(&ecdsa.PrivateKey{}),
|
||||||
|
curve: elliptic.P384(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "P-521 private key",
|
||||||
|
keyHex: pkcs8P521PrivateKeyHex,
|
||||||
|
keyType: reflect.TypeOf(&ecdsa.PrivateKey{}),
|
||||||
|
curve: elliptic.P521(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Ed25519 private key",
|
||||||
|
keyHex: pkcs8Ed25519PrivateKeyHex,
|
||||||
|
keyType: reflect.TypeOf(ed25519.PrivateKey{}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
derBytes, err := hex.DecodeString(test.keyHex)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%s: failed to decode hex: %s", test.name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
privKey, err := ParsePKCS8PrivateKey(derBytes)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%s: failed to decode PKCS#8: %s", test.name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if reflect.TypeOf(privKey) != test.keyType {
|
||||||
|
t.Errorf("%s: decoded PKCS#8 returned unexpected key type: %T", test.name, privKey)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if ecKey, isEC := privKey.(*ecdsa.PrivateKey); isEC && ecKey.Curve != test.curve {
|
||||||
|
t.Errorf("%s: decoded PKCS#8 returned unexpected curve %#v", test.name, ecKey.Curve)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
reserialised, err := MarshalPKCS8PrivateKey(privKey)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%s: failed to marshal into PKCS#8: %s", test.name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !bytes.Equal(derBytes, reserialised) {
|
||||||
|
t.Errorf("%s: marshaled PKCS#8 didn't match original: got %x, want %x", test.name, reserialised, derBytes)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const hexPKCS8TestPKCS1Key = "3082025c02010002818100b1a1e0945b9289c4d3f1329f8a982c4a2dcd59bfd372fb8085a9c517554607ebd2f7990eef216ac9f4605f71a03b04f42a5255b158cf8e0844191f5119348baa44c35056e20609bcf9510f30ead4b481c81d7865fb27b8e0090e112b717f3ee08cdfc4012da1f1f7cf2a1bc34c73a54a12b06372d09714742dd7895eadde4aa5020301000102818062b7fa1db93e993e40237de4d89b7591cc1ea1d04fed4904c643f17ae4334557b4295270d0491c161cb02a9af557978b32b20b59c267a721c4e6c956c2d147046e9ae5f2da36db0106d70021fa9343455f8f973a4b355a26fd19e6b39dee0405ea2b32deddf0f4817759ef705d02b34faab9ca93c6766e9f722290f119f34449024100d9c29a4a013a90e35fd1be14a3f747c589fac613a695282d61812a711906b8a0876c6181f0333ca1066596f57bff47e7cfcabf19c0fc69d9cd76df743038b3cb024100d0d3546fecf879b5551f2bd2c05e6385f2718a08a6face3d2aecc9d7e03645a480a46c81662c12ad6bd6901e3bd4f38029462de7290859567cdf371c79088d4f024100c254150657e460ea58573fcf01a82a4791e3d6223135c8bdfed69afe84fbe7857274f8eb5165180507455f9b4105c6b08b51fe8a481bb986a202245576b713530240045700003b7a867d0041df9547ae2e7f50248febd21c9040b12dae9c2feab0d3d4609668b208e4727a3541557f84d372ac68eaf74ce1018a4c9a0ef92682c8fd02405769731480bb3a4570abf422527c5f34bf732fa6c1e08cc322753c511ce055fac20fc770025663ad3165324314df907f1f1942f0448a7e9cdbf87ecd98b92156"
|
||||||
|
const hexPKCS8TestECKey = "3081a40201010430bdb9839c08ee793d1157886a7a758a3c8b2a17a4df48f17ace57c72c56b4723cf21dcda21d4e1ad57ff034f19fcfd98ea00706052b81040022a16403620004feea808b5ee2429cfcce13c32160e1c960990bd050bb0fdf7222f3decd0a55008e32a6aa3c9062051c4cba92a7a3b178b24567412d43cdd2f882fa5addddd726fe3e208d2c26d733a773a597abb749714df7256ead5105fa6e7b3650de236b50"
|
||||||
|
|
||||||
|
var pkcs8MismatchKeyTests = []struct {
|
||||||
|
hexKey string
|
||||||
|
errorContains string
|
||||||
|
}{
|
||||||
|
{hexKey: hexPKCS8TestECKey, errorContains: "use ParseECPrivateKey instead"},
|
||||||
|
{hexKey: hexPKCS8TestPKCS1Key, errorContains: "use ParsePKCS1PrivateKey instead"},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPKCS8MismatchKeyFormat(t *testing.T) {
|
||||||
|
for i, test := range pkcs8MismatchKeyTests {
|
||||||
|
derBytes, _ := hex.DecodeString(test.hexKey)
|
||||||
|
_, err := ParsePKCS8PrivateKey(derBytes)
|
||||||
|
if !strings.Contains(err.Error(), test.errorContains) {
|
||||||
|
t.Errorf("#%d: expected error containing %q, got %s", i, test.errorContains, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMarshalPKCS8SM2PrivateKey(t *testing.T) {
|
||||||
|
priv, _ := sm2.GenerateKey(rand.Reader)
|
||||||
|
res, err := MarshalPKCS8PrivateKey(priv)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("%v\n", err)
|
||||||
|
}
|
||||||
|
fmt.Printf("%s\n", hex.EncodeToString(res))
|
||||||
|
}
|
21
smx509/root.go
Normal file
21
smx509/root.go
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package smx509
|
||||||
|
|
||||||
|
import "sync"
|
||||||
|
|
||||||
|
var (
|
||||||
|
once sync.Once
|
||||||
|
systemRoots *CertPool
|
||||||
|
systemRootsErr error
|
||||||
|
)
|
||||||
|
|
||||||
|
func systemRootsPool() *CertPool {
|
||||||
|
once.Do(initSystemRoots)
|
||||||
|
return systemRoots
|
||||||
|
}
|
||||||
|
|
||||||
|
func initSystemRoots() {
|
||||||
|
systemRoots, systemRootsErr = loadSystemRoots()
|
||||||
|
if systemRootsErr != nil {
|
||||||
|
systemRoots = nil
|
||||||
|
}
|
||||||
|
}
|
6
smx509/root_aix.go
Normal file
6
smx509/root_aix.go
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
package smx509
|
||||||
|
|
||||||
|
// Possible certificate files; stop after finding one.
|
||||||
|
var certFiles = []string{
|
||||||
|
"/var/ssl/certs/ca-bundle.crt",
|
||||||
|
}
|
11
smx509/root_bsd.go
Normal file
11
smx509/root_bsd.go
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
// +build dragonfly freebsd netbsd openbsd
|
||||||
|
|
||||||
|
package smx509
|
||||||
|
|
||||||
|
// Possible certificate files; stop after finding one.
|
||||||
|
var certFiles = []string{
|
||||||
|
"/usr/local/etc/ssl/cert.pem", // FreeBSD
|
||||||
|
"/etc/ssl/cert.pem", // OpenBSD
|
||||||
|
"/usr/local/share/certs/ca-root-nss.crt", // DragonFly
|
||||||
|
"/etc/openssl/certs/ca-certificates.crt", // NetBSD
|
||||||
|
}
|
6
smx509/root_js.go
Normal file
6
smx509/root_js.go
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
// +build js,wasm
|
||||||
|
|
||||||
|
package smx509
|
||||||
|
|
||||||
|
// Possible certificate files; stop after finding one.
|
||||||
|
var certFiles = []string{}
|
11
smx509/root_linux.go
Normal file
11
smx509/root_linux.go
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package smx509
|
||||||
|
|
||||||
|
// Possible certificate files; stop after finding one.
|
||||||
|
var certFiles = []string{
|
||||||
|
"/etc/ssl/certs/ca-certificates.crt", // Debian/Ubuntu/Gentoo etc.
|
||||||
|
"/etc/pki/tls/certs/ca-bundle.crt", // Fedora/RHEL 6
|
||||||
|
"/etc/ssl/ca-bundle.pem", // OpenSUSE
|
||||||
|
"/etc/pki/tls/cacert.pem", // OpenELEC
|
||||||
|
"/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem", // CentOS/RHEL 7
|
||||||
|
"/etc/ssl/cert.pem", // Alpine Linux
|
||||||
|
}
|
36
smx509/root_plan9.go
Normal file
36
smx509/root_plan9.go
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
// +build plan9
|
||||||
|
|
||||||
|
package smx509
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Possible certificate files; stop after finding one.
|
||||||
|
var certFiles = []string{
|
||||||
|
"/sys/lib/tls/ca.pem",
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Certificate) systemVerify(opts *VerifyOptions) (chains [][]*Certificate, err error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadSystemRoots() (*CertPool, error) {
|
||||||
|
roots := NewCertPool()
|
||||||
|
var bestErr error
|
||||||
|
for _, file := range certFiles {
|
||||||
|
data, err := ioutil.ReadFile(file)
|
||||||
|
if err == nil {
|
||||||
|
roots.AppendCertsFromPEM(data)
|
||||||
|
return roots, nil
|
||||||
|
}
|
||||||
|
if bestErr == nil || (os.IsNotExist(bestErr) && !os.IsNotExist(err)) {
|
||||||
|
bestErr = err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if bestErr == nil {
|
||||||
|
return roots, nil
|
||||||
|
}
|
||||||
|
return nil, bestErr
|
||||||
|
}
|
8
smx509/root_solaris.go
Normal file
8
smx509/root_solaris.go
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
package smx509
|
||||||
|
|
||||||
|
// Possible certificate files; stop after finding one.
|
||||||
|
var certFiles = []string{
|
||||||
|
"/etc/certs/ca-certificates.crt", // Solaris 11.2+
|
||||||
|
"/etc/ssl/certs/ca-certificates.crt", // Joyent SmartOS
|
||||||
|
"/etc/ssl/cacert.pem", // OmniOS
|
||||||
|
}
|
85
smx509/root_unix.go
Normal file
85
smx509/root_unix.go
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
// +build aix dragonfly freebsd js,wasm linux netbsd openbsd solaris
|
||||||
|
|
||||||
|
package smx509
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
certFileEnv = "SSL_CERT_FILE"
|
||||||
|
|
||||||
|
// certDirEnv is the environment variable which identifies which directory
|
||||||
|
// to check for SSL certificate files. If set this overrides the system default.
|
||||||
|
certDirEnv = "SSL_CERT_DIR"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *Certificate) systemVerify(opts *VerifyOptions) (chains [][]*Certificate, err error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadSystemRoots() (*CertPool, error) {
|
||||||
|
roots := NewCertPool()
|
||||||
|
|
||||||
|
files := certFiles
|
||||||
|
if f := os.Getenv(certFileEnv); f != "" {
|
||||||
|
files = []string{f}
|
||||||
|
}
|
||||||
|
|
||||||
|
var firstErr error
|
||||||
|
for _, file := range files {
|
||||||
|
data, err := ioutil.ReadFile(file)
|
||||||
|
if err == nil {
|
||||||
|
roots.AppendCertsFromPEM(data)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if firstErr == nil && !os.IsNotExist(err) {
|
||||||
|
firstErr = err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dirs := certDirectories
|
||||||
|
if d := os.Getenv(certDirEnv); d != "" {
|
||||||
|
dirs = []string{d}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, directory := range dirs {
|
||||||
|
fis, err := ioutil.ReadDir(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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if rootsAdded {
|
||||||
|
return roots, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(roots.certs) > 0 || firstErr == nil {
|
||||||
|
return roots, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, firstErr
|
||||||
|
}
|
303
smx509/root_windows.go
Normal file
303
smx509/root_windows.go
Normal file
@ -0,0 +1,303 @@
|
|||||||
|
package smx509
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/x509"
|
||||||
|
"errors"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
//
|
||||||
|
// A pointer to the in-memory store is available in the returned CertContext's Store field.
|
||||||
|
// The store is automatically freed when the CertContext is freed using
|
||||||
|
// syscall.CertFreeCertificateContext.
|
||||||
|
func createStoreContext(leaf *Certificate, opts *VerifyOptions) (*syscall.CertContext, error) {
|
||||||
|
var storeCtx *syscall.CertContext
|
||||||
|
|
||||||
|
leafCtx, err := syscall.CertCreateCertificateContext(syscall.X509_ASN_ENCODING|syscall.PKCS_7_ASN_ENCODING, &leaf.Raw[0], uint32(len(leaf.Raw)))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer syscall.CertFreeCertificateContext(leafCtx)
|
||||||
|
|
||||||
|
handle, err := syscall.CertOpenStore(syscall.CERT_STORE_PROV_MEMORY, 0, 0, syscall.CERT_STORE_DEFER_CLOSE_UNTIL_LAST_FREE_FLAG, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer syscall.CertCloseStore(handle, 0)
|
||||||
|
|
||||||
|
err = syscall.CertAddCertificateContextToStore(handle, leafCtx, syscall.CERT_STORE_ADD_ALWAYS, &storeCtx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.Intermediates != nil {
|
||||||
|
for _, intermediate := range opts.Intermediates.certs {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
err = syscall.CertAddCertificateContextToStore(handle, ctx, syscall.CERT_STORE_ADD_ALWAYS, nil)
|
||||||
|
syscall.CertFreeCertificateContext(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return storeCtx, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// extractSimpleChain extracts the final certificate chain from a CertSimpleChain.
|
||||||
|
func extractSimpleChain(simpleChain **syscall.CertSimpleChain, count int) (chain []*Certificate, err error) {
|
||||||
|
if simpleChain == nil || count == 0 {
|
||||||
|
return nil, errors.New("x509: invalid simple chain")
|
||||||
|
}
|
||||||
|
|
||||||
|
simpleChains := (*[1 << 20]*syscall.CertSimpleChain)(unsafe.Pointer(simpleChain))[:count:count]
|
||||||
|
lastChain := simpleChains[count-1]
|
||||||
|
elements := (*[1 << 20]*syscall.CertChainElement)(unsafe.Pointer(lastChain.Elements))[:lastChain.NumElements:lastChain.NumElements]
|
||||||
|
for i := 0; i < int(lastChain.NumElements); i++ {
|
||||||
|
// Copy the buf, since ParseCertificate does not create its own copy.
|
||||||
|
cert := elements[i].CertContext
|
||||||
|
encodedCert := (*[1 << 20]byte)(unsafe.Pointer(cert.EncodedCert))[:cert.Length:cert.Length]
|
||||||
|
buf := make([]byte, cert.Length)
|
||||||
|
copy(buf, encodedCert)
|
||||||
|
parsedCert, err := ParseCertificate(buf)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
chain = append(chain, parsedCert)
|
||||||
|
}
|
||||||
|
|
||||||
|
return chain, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkChainTrustStatus checks the trust status of the certificate chain, translating
|
||||||
|
// any errors it finds into Go errors in the process.
|
||||||
|
func checkChainTrustStatus(c *Certificate, chainCtx *syscall.CertChainContext) error {
|
||||||
|
if chainCtx.TrustStatus.ErrorStatus != syscall.CERT_TRUST_NO_ERROR {
|
||||||
|
status := chainCtx.TrustStatus.ErrorStatus
|
||||||
|
switch status {
|
||||||
|
case syscall.CERT_TRUST_IS_NOT_TIME_VALID:
|
||||||
|
return x509.CertificateInvalidError{&c.Certificate, x509.Expired, ""}
|
||||||
|
case syscall.CERT_TRUST_IS_NOT_VALID_FOR_USAGE:
|
||||||
|
return x509.CertificateInvalidError{&c.Certificate, x509.IncompatibleUsage, ""}
|
||||||
|
// TODO(filippo): surface more error statuses.
|
||||||
|
default:
|
||||||
|
return UnknownAuthorityError{c, nil, nil}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkChainSSLServerPolicy checks that the certificate chain in chainCtx is valid for
|
||||||
|
// use as a certificate chain for a SSL/TLS server.
|
||||||
|
func checkChainSSLServerPolicy(c *Certificate, chainCtx *syscall.CertChainContext, opts *VerifyOptions) error {
|
||||||
|
servernamep, err := syscall.UTF16PtrFromString(opts.DNSName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
sslPara := &syscall.SSLExtraCertChainPolicyPara{
|
||||||
|
AuthType: syscall.AUTHTYPE_SERVER,
|
||||||
|
ServerName: servernamep,
|
||||||
|
}
|
||||||
|
sslPara.Size = uint32(unsafe.Sizeof(*sslPara))
|
||||||
|
|
||||||
|
para := &syscall.CertChainPolicyPara{
|
||||||
|
ExtraPolicyPara: (syscall.Pointer)(unsafe.Pointer(sslPara)),
|
||||||
|
}
|
||||||
|
para.Size = uint32(unsafe.Sizeof(*para))
|
||||||
|
|
||||||
|
status := syscall.CertChainPolicyStatus{}
|
||||||
|
err = syscall.CertVerifyCertificateChainPolicy(syscall.CERT_CHAIN_POLICY_SSL, chainCtx, para, &status)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(mkrautz): use the lChainIndex and lElementIndex fields
|
||||||
|
// of the CertChainPolicyStatus to provide proper context, instead
|
||||||
|
// using c.
|
||||||
|
if status.Error != 0 {
|
||||||
|
switch status.Error {
|
||||||
|
case syscall.CERT_E_EXPIRED:
|
||||||
|
return x509.CertificateInvalidError{&c.Certificate, x509.Expired, ""}
|
||||||
|
case syscall.CERT_E_CN_NO_MATCH:
|
||||||
|
return x509.HostnameError{&c.Certificate, opts.DNSName}
|
||||||
|
case syscall.CERT_E_UNTRUSTEDROOT:
|
||||||
|
return UnknownAuthorityError{c, nil, nil}
|
||||||
|
default:
|
||||||
|
return UnknownAuthorityError{c, nil, nil}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// windowsExtKeyUsageOIDs are the C NUL-terminated string representations of the
|
||||||
|
// OIDs for use with the Windows API.
|
||||||
|
var windowsExtKeyUsageOIDs = make(map[x509.ExtKeyUsage][]byte, len(extKeyUsageOIDs))
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
for _, eku := range extKeyUsageOIDs {
|
||||||
|
windowsExtKeyUsageOIDs[eku.extKeyUsage] = []byte(eku.oid.String() + "\x00")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
storeCtx, err := createStoreContext(c, opts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer syscall.CertFreeCertificateContext(storeCtx)
|
||||||
|
|
||||||
|
para := new(syscall.CertChainPara)
|
||||||
|
para.Size = uint32(unsafe.Sizeof(*para))
|
||||||
|
|
||||||
|
keyUsages := opts.KeyUsages
|
||||||
|
if len(keyUsages) == 0 {
|
||||||
|
keyUsages = []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}
|
||||||
|
}
|
||||||
|
oids := make([]*byte, 0, len(keyUsages))
|
||||||
|
for _, eku := range keyUsages {
|
||||||
|
if eku == x509.ExtKeyUsageAny {
|
||||||
|
oids = nil
|
||||||
|
break
|
||||||
|
}
|
||||||
|
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
|
||||||
|
para.RequestedUsage.Usage.Length = uint32(len(oids))
|
||||||
|
para.RequestedUsage.Usage.UsageIdentifiers = &oids[0]
|
||||||
|
} else {
|
||||||
|
para.RequestedUsage.Type = syscall.USAGE_MATCH_TYPE_AND
|
||||||
|
para.RequestedUsage.Usage.Length = 0
|
||||||
|
para.RequestedUsage.Usage.UsageIdentifiers = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var verifyTime *syscall.Filetime
|
||||||
|
if opts != nil && !opts.CurrentTime.IsZero() {
|
||||||
|
ft := syscall.NsecToFiletime(opts.CurrentTime.UnixNano())
|
||||||
|
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)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer syscall.CertFreeCertificateChain(chainCtx)
|
||||||
|
|
||||||
|
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) < 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
159
smx509/sec1.go
Normal file
159
smx509/sec1.go
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
package smx509
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/elliptic"
|
||||||
|
"encoding/asn1"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
|
||||||
|
"github.com/emmansun/gmsm/sm2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// pkcs1PrivateKey is a structure which mirrors the PKCS#1 ASN.1 for an RSA private key.
|
||||||
|
type pkcs1PrivateKey struct {
|
||||||
|
Version int
|
||||||
|
N *big.Int
|
||||||
|
E int
|
||||||
|
D *big.Int
|
||||||
|
P *big.Int
|
||||||
|
Q *big.Int
|
||||||
|
// We ignore these values, if present, because rsa will calculate them.
|
||||||
|
Dp *big.Int `asn1:"optional"`
|
||||||
|
Dq *big.Int `asn1:"optional"`
|
||||||
|
Qinv *big.Int `asn1:"optional"`
|
||||||
|
|
||||||
|
AdditionalPrimes []pkcs1AdditionalRSAPrime `asn1:"optional,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type pkcs1AdditionalRSAPrime struct {
|
||||||
|
Prime *big.Int
|
||||||
|
|
||||||
|
// We ignore these values because rsa will calculate them.
|
||||||
|
Exp *big.Int
|
||||||
|
Coeff *big.Int
|
||||||
|
}
|
||||||
|
|
||||||
|
const ecPrivKeyVersion = 1
|
||||||
|
|
||||||
|
// ecPrivateKey reflects an ASN.1 Elliptic Curve Private Key Structure.
|
||||||
|
// References:
|
||||||
|
// RFC 5915
|
||||||
|
// SEC1 - http://www.secg.org/sec1-v2.pdf
|
||||||
|
// Per RFC 5915 the NamedCurveOID is marked as ASN.1 OPTIONAL, however in
|
||||||
|
// most cases it is not.
|
||||||
|
type ecPrivateKey struct {
|
||||||
|
Version int
|
||||||
|
PrivateKey []byte
|
||||||
|
NamedCurveOID asn1.ObjectIdentifier `asn1:"optional,explicit,tag:0"`
|
||||||
|
PublicKey asn1.BitString `asn1:"optional,explicit,tag:1"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseECPrivateKey parses an EC private key in SEC 1, ASN.1 DER form.
|
||||||
|
//
|
||||||
|
// This kind of key is commonly encoded in PEM blocks of type "EC PRIVATE KEY".
|
||||||
|
func ParseECPrivateKey(der []byte) (*ecdsa.PrivateKey, error) {
|
||||||
|
return parseECPrivateKey(nil, der)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseSM2PrivateKey parses an SM2 private key
|
||||||
|
func ParseSM2PrivateKey(der []byte) (*sm2.PrivateKey, error) {
|
||||||
|
key, err := parseECPrivateKey(nil, der)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return new(sm2.PrivateKey).FromECPrivateKey(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalECPrivateKey converts an EC private key to SEC 1, ASN.1 DER form.
|
||||||
|
//
|
||||||
|
// This kind of key is commonly encoded in PEM blocks of type "EC PRIVATE KEY".
|
||||||
|
// For a more flexible key format which is not EC specific, use
|
||||||
|
// MarshalPKCS8PrivateKey.
|
||||||
|
func MarshalECPrivateKey(key *ecdsa.PrivateKey) ([]byte, error) {
|
||||||
|
oid, ok := oidFromNamedCurve(key.Curve)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("x509: unknown elliptic curve")
|
||||||
|
}
|
||||||
|
|
||||||
|
return marshalECPrivateKeyWithOID(key, oid)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalSM2PrivateKey convient method to marshal sm2 private key directly
|
||||||
|
func MarshalSM2PrivateKey(key *sm2.PrivateKey) ([]byte, error) {
|
||||||
|
return MarshalECPrivateKey(&key.PrivateKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
|
||||||
|
return asn1.Marshal(ecPrivateKey{
|
||||||
|
Version: 1,
|
||||||
|
PrivateKey: paddedPrivateKey,
|
||||||
|
NamedCurveOID: oid,
|
||||||
|
PublicKey: asn1.BitString{Bytes: elliptic.Marshal(key.Curve, key.X, key.Y)},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseECPrivateKey parses an ASN.1 Elliptic Curve Private Key Structure.
|
||||||
|
// The OID for the named curve may be provided from another source (such as
|
||||||
|
// the PKCS8 container) - if it is provided then use this instead of the OID
|
||||||
|
// that may exist in the EC private key structure.
|
||||||
|
func parseECPrivateKey(namedCurveOID *asn1.ObjectIdentifier, der []byte) (key *ecdsa.PrivateKey, err error) {
|
||||||
|
var privKey ecPrivateKey
|
||||||
|
if _, err := asn1.Unmarshal(der, &privKey); err != nil {
|
||||||
|
if _, err := asn1.Unmarshal(der, &pkcs8{}); err == nil {
|
||||||
|
return nil, errors.New("x509: failed to parse private key (use ParsePKCS8PrivateKey instead for this key format)")
|
||||||
|
}
|
||||||
|
if _, err := asn1.Unmarshal(der, &pkcs1PrivateKey{}); err == nil {
|
||||||
|
return nil, errors.New("x509: failed to parse private key (use ParsePKCS1PrivateKey instead for this key format)")
|
||||||
|
}
|
||||||
|
return nil, errors.New("x509: failed to parse EC private key: " + err.Error())
|
||||||
|
}
|
||||||
|
if privKey.Version != ecPrivKeyVersion {
|
||||||
|
return nil, fmt.Errorf("x509: unknown EC private key version %d", privKey.Version)
|
||||||
|
}
|
||||||
|
|
||||||
|
var curve elliptic.Curve
|
||||||
|
if namedCurveOID != nil {
|
||||||
|
curve = namedCurveFromOID(*namedCurveOID)
|
||||||
|
} else {
|
||||||
|
curve = namedCurveFromOID(privKey.NamedCurveOID)
|
||||||
|
}
|
||||||
|
if curve == nil {
|
||||||
|
return nil, errors.New("x509: unknown elliptic curve")
|
||||||
|
}
|
||||||
|
|
||||||
|
k := new(big.Int).SetBytes(privKey.PrivateKey)
|
||||||
|
curveOrder := curve.Params().N
|
||||||
|
if k.Cmp(curveOrder) >= 0 {
|
||||||
|
return nil, errors.New("x509: invalid elliptic curve private key value")
|
||||||
|
}
|
||||||
|
priv := new(ecdsa.PrivateKey)
|
||||||
|
priv.Curve = curve
|
||||||
|
priv.D = k
|
||||||
|
|
||||||
|
privateKey := make([]byte, (curveOrder.BitLen()+7)/8)
|
||||||
|
|
||||||
|
// Some private keys have leading zero padding. This is invalid
|
||||||
|
// according to [SEC1], but this code will ignore it.
|
||||||
|
for len(privKey.PrivateKey) > len(privateKey) {
|
||||||
|
if privKey.PrivateKey[0] != 0 {
|
||||||
|
return nil, errors.New("x509: invalid private key length")
|
||||||
|
}
|
||||||
|
privKey.PrivateKey = privKey.PrivateKey[1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Some private keys remove all leading zeros, this is also invalid
|
||||||
|
// according to [SEC1] but since OpenSSL used to do this, we ignore
|
||||||
|
// this too.
|
||||||
|
copy(privateKey[len(privateKey)-len(privKey.PrivateKey):], privKey.PrivateKey)
|
||||||
|
priv.X, priv.Y = curve.ScalarBaseMult(privateKey)
|
||||||
|
|
||||||
|
return priv, nil
|
||||||
|
}
|
77
smx509/sec1_test.go
Normal file
77
smx509/sec1_test.go
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
package smx509
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/emmansun/gmsm/sm2"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ecKeyTests = []struct {
|
||||||
|
derHex string
|
||||||
|
shouldReserialize bool
|
||||||
|
}{
|
||||||
|
// Generated using:
|
||||||
|
// openssl ecparam -genkey -name secp384r1 -outform PEM
|
||||||
|
{"3081a40201010430bdb9839c08ee793d1157886a7a758a3c8b2a17a4df48f17ace57c72c56b4723cf21dcda21d4e1ad57ff034f19fcfd98ea00706052b81040022a16403620004feea808b5ee2429cfcce13c32160e1c960990bd050bb0fdf7222f3decd0a55008e32a6aa3c9062051c4cba92a7a3b178b24567412d43cdd2f882fa5addddd726fe3e208d2c26d733a773a597abb749714df7256ead5105fa6e7b3650de236b50", true},
|
||||||
|
// Generated using MarshalSM2PrivateKey
|
||||||
|
{"30770201010420857dd87970aab4328dad891c781e3b270742aa9cf5d3d3764efe77f6c3d6e33aa00a06082a811ccf5501822da14403420004ced963a5705a0490ff13dde893cbda6de61f41fcaf917a5b4007d30cdec46426bc39b9c18d15b2a68a64dc333f262e600b675856285b42296f24741ee6f562a0", true},
|
||||||
|
// This key was generated by GnuTLS and has illegal zero-padding of the
|
||||||
|
// private key. See https://golang.org/issues/13699.
|
||||||
|
{"3078020101042100f9f43a04b9bdc3ab01f53be6df80e7a7bc3eaf7b87fc24e630a4a0aa97633645a00a06082a8648ce3d030107a1440342000441a51bc318461b4c39a45048a16d4fc2a935b1ea7fe86e8c1fa219d6f2438f7c7fd62957d3442efb94b6a23eb0ea66dda663dc42f379cda6630b21b7888a5d3d", false},
|
||||||
|
// This was generated using an old version of OpenSSL and is missing a
|
||||||
|
// leading zero byte in the private key that should be present.
|
||||||
|
{"3081db0201010441607b4f985774ac21e633999794542e09312073480baa69550914d6d43d8414441e61b36650567901da714f94dffb3ce0e2575c31928a0997d51df5c440e983ca17a00706052b81040023a181890381860004001661557afedd7ac8d6b70e038e576558c626eb62edda36d29c3a1310277c11f67a8c6f949e5430a37dcfb95d902c1b5b5379c389873b9dd17be3bdb088a4774a7401072f830fb9a08d93bfa50a03dd3292ea07928724ddb915d831917a338f6b0aecfbc3cf5352c4a1295d356890c41c34116d29eeb93779aab9d9d78e2613437740f6", false},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseECPrivateKey(t *testing.T) {
|
||||||
|
for i, test := range ecKeyTests {
|
||||||
|
derBytes, _ := hex.DecodeString(test.derHex)
|
||||||
|
key, err := ParseECPrivateKey(derBytes)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("#%d: failed to decode EC private key: %s", i, err)
|
||||||
|
}
|
||||||
|
serialized, err := MarshalECPrivateKey(key)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("#%d: failed to encode EC private key: %s", i, err)
|
||||||
|
}
|
||||||
|
matches := bytes.Equal(serialized, derBytes)
|
||||||
|
if matches != test.shouldReserialize {
|
||||||
|
t.Fatalf("#%d: when serializing key: matches=%t, should match=%t: original %x, reserialized %x", i, matches, test.shouldReserialize, serialized, derBytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const hexECTestPKCS1Key = "3082025c02010002818100b1a1e0945b9289c4d3f1329f8a982c4a2dcd59bfd372fb8085a9c517554607ebd2f7990eef216ac9f4605f71a03b04f42a5255b158cf8e0844191f5119348baa44c35056e20609bcf9510f30ead4b481c81d7865fb27b8e0090e112b717f3ee08cdfc4012da1f1f7cf2a1bc34c73a54a12b06372d09714742dd7895eadde4aa5020301000102818062b7fa1db93e993e40237de4d89b7591cc1ea1d04fed4904c643f17ae4334557b4295270d0491c161cb02a9af557978b32b20b59c267a721c4e6c956c2d147046e9ae5f2da36db0106d70021fa9343455f8f973a4b355a26fd19e6b39dee0405ea2b32deddf0f4817759ef705d02b34faab9ca93c6766e9f722290f119f34449024100d9c29a4a013a90e35fd1be14a3f747c589fac613a695282d61812a711906b8a0876c6181f0333ca1066596f57bff47e7cfcabf19c0fc69d9cd76df743038b3cb024100d0d3546fecf879b5551f2bd2c05e6385f2718a08a6face3d2aecc9d7e03645a480a46c81662c12ad6bd6901e3bd4f38029462de7290859567cdf371c79088d4f024100c254150657e460ea58573fcf01a82a4791e3d6223135c8bdfed69afe84fbe7857274f8eb5165180507455f9b4105c6b08b51fe8a481bb986a202245576b713530240045700003b7a867d0041df9547ae2e7f50248febd21c9040b12dae9c2feab0d3d4609668b208e4727a3541557f84d372ac68eaf74ce1018a4c9a0ef92682c8fd02405769731480bb3a4570abf422527c5f34bf732fa6c1e08cc322753c511ce055fac20fc770025663ad3165324314df907f1f1942f0448a7e9cdbf87ecd98b92156"
|
||||||
|
const hexECTestPKCS8Key = "30820278020100300d06092a864886f70d0101010500048202623082025e02010002818100cfb1b5bf9685ffa97b4f99df4ff122b70e59ac9b992f3bc2b3dde17d53c1a34928719b02e8fd17839499bfbd515bd6ef99c7a1c47a239718fe36bfd824c0d96060084b5f67f0273443007a24dfaf5634f7772c9346e10eb294c2306671a5a5e719ae24b4de467291bc571014b0e02dec04534d66a9bb171d644b66b091780e8d020301000102818100b595778383c4afdbab95d2bfed12b3f93bb0a73a7ad952f44d7185fd9ec6c34de8f03a48770f2009c8580bcd275e9632714e9a5e3f32f29dc55474b2329ff0ebc08b3ffcb35bc96e6516b483df80a4a59cceb71918cbabf91564e64a39d7e35dce21cb3031824fdbc845dba6458852ec16af5dddf51a8397a8797ae0337b1439024100ea0eb1b914158c70db39031dd8904d6f18f408c85fbbc592d7d20dee7986969efbda081fdf8bc40e1b1336d6b638110c836bfdc3f314560d2e49cd4fbde1e20b024100e32a4e793b574c9c4a94c8803db5152141e72d03de64e54ef2c8ed104988ca780cd11397bc359630d01b97ebd87067c5451ba777cf045ca23f5912f1031308c702406dfcdbbd5a57c9f85abc4edf9e9e29153507b07ce0a7ef6f52e60dcfebe1b8341babd8b789a837485da6c8d55b29bbb142ace3c24a1f5b54b454d01b51e2ad03024100bd6a2b60dee01e1b3bfcef6a2f09ed027c273cdbbaf6ba55a80f6dcc64e4509ee560f84b4f3e076bd03b11e42fe71a3fdd2dffe7e0902c8584f8cad877cdc945024100aa512fa4ada69881f1d8bb8ad6614f192b83200aef5edf4811313d5ef30a86cbd0a90f7b025c71ea06ec6b34db6306c86b1040670fd8654ad7291d066d06d031"
|
||||||
|
|
||||||
|
var ecMismatchKeyTests = []struct {
|
||||||
|
hexKey string
|
||||||
|
errorContains string
|
||||||
|
}{
|
||||||
|
{hexKey: hexECTestPKCS8Key, errorContains: "use ParsePKCS8PrivateKey instead"},
|
||||||
|
{hexKey: hexECTestPKCS1Key, errorContains: "use ParsePKCS1PrivateKey instead"},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestECMismatchKeyFormat(t *testing.T) {
|
||||||
|
for i, test := range ecMismatchKeyTests {
|
||||||
|
derBytes, _ := hex.DecodeString(test.hexKey)
|
||||||
|
_, err := ParseECPrivateKey(derBytes)
|
||||||
|
if !strings.Contains(err.Error(), test.errorContains) {
|
||||||
|
t.Errorf("#%d: expected error containing %q, got %s", i, test.errorContains, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMarshalSM2PrivateKey(t *testing.T) {
|
||||||
|
priv, _ := sm2.GenerateKey(rand.Reader)
|
||||||
|
res, err := MarshalSM2PrivateKey(priv)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("%v\n", err)
|
||||||
|
}
|
||||||
|
fmt.Printf("%s\n", hex.EncodeToString(res))
|
||||||
|
}
|
984
smx509/verify.go
Normal file
984
smx509/verify.go
Normal file
@ -0,0 +1,984 @@
|
|||||||
|
package smx509
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/x509"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
"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
|
||||||
|
// hintErr contains an error that may be helpful in determining why an
|
||||||
|
// authority wasn't found.
|
||||||
|
hintErr error
|
||||||
|
// hintCert contains a possible authority certificate that was rejected
|
||||||
|
// because of the error in hintErr.
|
||||||
|
hintCert *Certificate
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e UnknownAuthorityError) Error() string {
|
||||||
|
s := "x509: certificate signed by unknown authority"
|
||||||
|
if e.hintErr != nil {
|
||||||
|
certName := e.hintCert.Subject.CommonName
|
||||||
|
if len(certName) == 0 {
|
||||||
|
if len(e.hintCert.Subject.Organization) > 0 {
|
||||||
|
certName = e.hintCert.Subject.Organization[0]
|
||||||
|
} else {
|
||||||
|
certName = "serial:" + e.hintCert.SerialNumber.String()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s += fmt.Sprintf(" (possibly because of %q while trying to verify candidate authority certificate %q)", e.hintErr, certName)
|
||||||
|
}
|
||||||
|
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")
|
||||||
|
|
||||||
|
// VerifyOptions contains parameters for Certificate.Verify.
|
||||||
|
type VerifyOptions struct {
|
||||||
|
// DNSName, if set, is checked against the leaf certificate with
|
||||||
|
// Certificate.VerifyHostname or the platform verifier.
|
||||||
|
DNSName string
|
||||||
|
|
||||||
|
// Intermediates is an optional pool of certificates that are not trust
|
||||||
|
// anchors, but can be used to form a chain from the leaf certificate to a
|
||||||
|
// root certificate.
|
||||||
|
Intermediates *CertPool
|
||||||
|
// Roots is the set of trusted root certificates the leaf certificate needs
|
||||||
|
// to chain up to. If nil, the system roots or the platform verifier are used.
|
||||||
|
Roots *CertPool
|
||||||
|
|
||||||
|
// CurrentTime is used to check the validity of all certificates in the
|
||||||
|
// chain. If zero, the current time is used.
|
||||||
|
CurrentTime time.Time
|
||||||
|
|
||||||
|
// KeyUsages specifies which Extended Key Usage values are acceptable. A
|
||||||
|
// chain is accepted if it allows any of the listed values. An empty list
|
||||||
|
// means ExtKeyUsageServerAuth. To accept any key usage, include ExtKeyUsageAny.
|
||||||
|
KeyUsages []x509.ExtKeyUsage
|
||||||
|
|
||||||
|
// MaxConstraintComparisions is the maximum number of comparisons to
|
||||||
|
// perform when checking a given certificate's name constraints. If
|
||||||
|
// zero, a sensible default is used. This limit prevents pathological
|
||||||
|
// certificates from consuming excessive amounts of CPU time when
|
||||||
|
// validating. It does not apply to the platform verifier.
|
||||||
|
MaxConstraintComparisions int
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
leafCertificate = iota
|
||||||
|
intermediateCertificate
|
||||||
|
rootCertificate
|
||||||
|
)
|
||||||
|
|
||||||
|
// rfc2821Mailbox represents a “mailbox” (which is an email address to most
|
||||||
|
// people) by breaking it into the “local” (i.e. before the '@') and “domain”
|
||||||
|
// parts.
|
||||||
|
type rfc2821Mailbox struct {
|
||||||
|
local, domain string
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseRFC2821Mailbox parses an email address into local and domain parts,
|
||||||
|
// based on the ABNF for a “Mailbox” from RFC 2821. According to RFC 5280,
|
||||||
|
// Section 4.2.1.6 that's correct for an rfc822Name from a certificate: “The
|
||||||
|
// format of an rfc822Name is a "Mailbox" as defined in RFC 2821, Section 4.1.2”.
|
||||||
|
func parseRFC2821Mailbox(in string) (mailbox rfc2821Mailbox, ok bool) {
|
||||||
|
if len(in) == 0 {
|
||||||
|
return mailbox, false
|
||||||
|
}
|
||||||
|
|
||||||
|
localPartBytes := make([]byte, 0, len(in)/2)
|
||||||
|
|
||||||
|
if in[0] == '"' {
|
||||||
|
// Quoted-string = DQUOTE *qcontent DQUOTE
|
||||||
|
// non-whitespace-control = %d1-8 / %d11 / %d12 / %d14-31 / %d127
|
||||||
|
// qcontent = qtext / quoted-pair
|
||||||
|
// qtext = non-whitespace-control /
|
||||||
|
// %d33 / %d35-91 / %d93-126
|
||||||
|
// quoted-pair = ("\" text) / obs-qp
|
||||||
|
// text = %d1-9 / %d11 / %d12 / %d14-127 / obs-text
|
||||||
|
//
|
||||||
|
// (Names beginning with “obs-” are the obsolete syntax from RFC 2822,
|
||||||
|
// Section 4. Since it has been 16 years, we no longer accept that.)
|
||||||
|
in = in[1:]
|
||||||
|
QuotedString:
|
||||||
|
for {
|
||||||
|
if len(in) == 0 {
|
||||||
|
return mailbox, false
|
||||||
|
}
|
||||||
|
c := in[0]
|
||||||
|
in = in[1:]
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case c == '"':
|
||||||
|
break QuotedString
|
||||||
|
|
||||||
|
case c == '\\':
|
||||||
|
// quoted-pair
|
||||||
|
if len(in) == 0 {
|
||||||
|
return mailbox, false
|
||||||
|
}
|
||||||
|
if in[0] == 11 ||
|
||||||
|
in[0] == 12 ||
|
||||||
|
(1 <= in[0] && in[0] <= 9) ||
|
||||||
|
(14 <= in[0] && in[0] <= 127) {
|
||||||
|
localPartBytes = append(localPartBytes, in[0])
|
||||||
|
in = in[1:]
|
||||||
|
} else {
|
||||||
|
return mailbox, false
|
||||||
|
}
|
||||||
|
|
||||||
|
case c == 11 ||
|
||||||
|
c == 12 ||
|
||||||
|
// Space (char 32) is not allowed based on the
|
||||||
|
// BNF, but RFC 3696 gives an example that
|
||||||
|
// assumes that it is. Several “verified”
|
||||||
|
// errata continue to argue about this point.
|
||||||
|
// We choose to accept it.
|
||||||
|
c == 32 ||
|
||||||
|
c == 33 ||
|
||||||
|
c == 127 ||
|
||||||
|
(1 <= c && c <= 8) ||
|
||||||
|
(14 <= c && c <= 31) ||
|
||||||
|
(35 <= c && c <= 91) ||
|
||||||
|
(93 <= c && c <= 126):
|
||||||
|
// qtext
|
||||||
|
localPartBytes = append(localPartBytes, c)
|
||||||
|
|
||||||
|
default:
|
||||||
|
return mailbox, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Atom ("." Atom)*
|
||||||
|
NextChar:
|
||||||
|
for len(in) > 0 {
|
||||||
|
// atext from RFC 2822, Section 3.2.4
|
||||||
|
c := in[0]
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case c == '\\':
|
||||||
|
// Examples given in RFC 3696 suggest that
|
||||||
|
// escaped characters can appear outside of a
|
||||||
|
// quoted string. Several “verified” errata
|
||||||
|
// continue to argue the point. We choose to
|
||||||
|
// accept it.
|
||||||
|
in = in[1:]
|
||||||
|
if len(in) == 0 {
|
||||||
|
return mailbox, false
|
||||||
|
}
|
||||||
|
fallthrough
|
||||||
|
|
||||||
|
case ('0' <= c && c <= '9') ||
|
||||||
|
('a' <= c && c <= 'z') ||
|
||||||
|
('A' <= c && c <= 'Z') ||
|
||||||
|
c == '!' || c == '#' || c == '$' || c == '%' ||
|
||||||
|
c == '&' || c == '\'' || c == '*' || c == '+' ||
|
||||||
|
c == '-' || c == '/' || c == '=' || c == '?' ||
|
||||||
|
c == '^' || c == '_' || c == '`' || c == '{' ||
|
||||||
|
c == '|' || c == '}' || c == '~' || c == '.':
|
||||||
|
localPartBytes = append(localPartBytes, in[0])
|
||||||
|
in = in[1:]
|
||||||
|
|
||||||
|
default:
|
||||||
|
break NextChar
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(localPartBytes) == 0 {
|
||||||
|
return mailbox, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// From RFC 3696, Section 3:
|
||||||
|
// “period (".") may also appear, but may not be used to start
|
||||||
|
// or end the local part, nor may two or more consecutive
|
||||||
|
// periods appear.”
|
||||||
|
twoDots := []byte{'.', '.'}
|
||||||
|
if localPartBytes[0] == '.' ||
|
||||||
|
localPartBytes[len(localPartBytes)-1] == '.' ||
|
||||||
|
bytes.Contains(localPartBytes, twoDots) {
|
||||||
|
return mailbox, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(in) == 0 || in[0] != '@' {
|
||||||
|
return mailbox, false
|
||||||
|
}
|
||||||
|
in = in[1:]
|
||||||
|
|
||||||
|
// The RFC species a format for domains, but that's known to be
|
||||||
|
// violated in practice so we accept that anything after an '@' is the
|
||||||
|
// domain part.
|
||||||
|
if _, ok := domainToReverseLabels(in); !ok {
|
||||||
|
return mailbox, false
|
||||||
|
}
|
||||||
|
|
||||||
|
mailbox.local = string(localPartBytes)
|
||||||
|
mailbox.domain = in
|
||||||
|
return mailbox, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkNameConstraints checks that c permits a child certificate to claim the
|
||||||
|
// given name, of type nameType. The argument parsedName contains the parsed
|
||||||
|
// form of name, suitable for passing to the match function. The total number
|
||||||
|
// of comparisons is tracked in the given count and should not exceed the given
|
||||||
|
// limit.
|
||||||
|
func (c *Certificate) checkNameConstraints(count *int,
|
||||||
|
maxConstraintComparisons int,
|
||||||
|
nameType string,
|
||||||
|
name string,
|
||||||
|
parsedName interface{},
|
||||||
|
match func(parsedName, constraint interface{}) (match bool, err error),
|
||||||
|
permitted, excluded interface{}) error {
|
||||||
|
|
||||||
|
excludedValue := reflect.ValueOf(excluded)
|
||||||
|
|
||||||
|
*count += excludedValue.Len()
|
||||||
|
if *count > maxConstraintComparisons {
|
||||||
|
return CertificateInvalidError{c, 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()}
|
||||||
|
}
|
||||||
|
|
||||||
|
if match {
|
||||||
|
return CertificateInvalidError{c, x509.CANotAuthorizedForThisName, fmt.Sprintf("%s %q is excluded by constraint %q", nameType, name, constraint)}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
permittedValue := reflect.ValueOf(permitted)
|
||||||
|
|
||||||
|
*count += permittedValue.Len()
|
||||||
|
if *count > maxConstraintComparisons {
|
||||||
|
return CertificateInvalidError{c, x509.TooManyConstraints, ""}
|
||||||
|
}
|
||||||
|
|
||||||
|
ok := true
|
||||||
|
for i := 0; i < permittedValue.Len(); i++ {
|
||||||
|
constraint := permittedValue.Index(i).Interface()
|
||||||
|
|
||||||
|
var err error
|
||||||
|
if ok, err = match(parsedName, constraint); err != nil {
|
||||||
|
return CertificateInvalidError{c, x509.CANotAuthorizedForThisName, err.Error()}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
return CertificateInvalidError{c, x509.CANotAuthorizedForThisName, fmt.Sprintf("%s %q is not permitted by any constraint", nameType, name)}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// isValid performs validity checks on c given that it is a candidate to append
|
||||||
|
// to the chain in currentChain.
|
||||||
|
func (c *Certificate) isValid(certType int, currentChain []*Certificate, opts *VerifyOptions) error {
|
||||||
|
if len(c.UnhandledCriticalExtensions) > 0 {
|
||||||
|
return x509.UnhandledCriticalExtension{}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(currentChain) > 0 {
|
||||||
|
child := currentChain[len(currentChain)-1]
|
||||||
|
if !bytes.Equal(child.RawIssuer, c.RawSubject) {
|
||||||
|
return CertificateInvalidError{c, x509.NameMismatch, ""}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
now := opts.CurrentTime
|
||||||
|
if now.IsZero() {
|
||||||
|
now = time.Now()
|
||||||
|
}
|
||||||
|
if now.Before(c.NotBefore) {
|
||||||
|
return CertificateInvalidError{
|
||||||
|
Cert: c,
|
||||||
|
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,
|
||||||
|
Reason: x509.Expired,
|
||||||
|
Detail: fmt.Sprintf("current time %s is after %s", now.Format(time.RFC3339), c.NotAfter.Format(time.RFC3339)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
maxConstraintComparisons := opts.MaxConstraintComparisions
|
||||||
|
if maxConstraintComparisons == 0 {
|
||||||
|
maxConstraintComparisons = 250000
|
||||||
|
}
|
||||||
|
comparisonCount := 0
|
||||||
|
|
||||||
|
var leaf *Certificate
|
||||||
|
if certType == intermediateCertificate || certType == rootCertificate {
|
||||||
|
if len(currentChain) == 0 {
|
||||||
|
return errors.New("x509: internal error: empty chain when appending CA cert")
|
||||||
|
}
|
||||||
|
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() {
|
||||||
|
err := forEachSAN(leaf.getSANExtension(), func(tag int, data []byte) error {
|
||||||
|
switch tag {
|
||||||
|
case nameTypeEmail:
|
||||||
|
name := string(data)
|
||||||
|
mailbox, ok := parseRFC2821Mailbox(name)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("x509: cannot parse rfc822Name %q", mailbox)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.checkNameConstraints(&comparisonCount, maxConstraintComparisons, "email address", name, mailbox,
|
||||||
|
func(parsedName, constraint interface{}) (bool, error) {
|
||||||
|
return matchEmailConstraint(parsedName.(rfc2821Mailbox), constraint.(string))
|
||||||
|
}, c.PermittedEmailAddresses, c.ExcludedEmailAddresses); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
case nameTypeDNS:
|
||||||
|
name := string(data)
|
||||||
|
if _, ok := domainToReverseLabels(name); !ok {
|
||||||
|
return fmt.Errorf("x509: cannot parse dnsName %q", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.checkNameConstraints(&comparisonCount, maxConstraintComparisons, "DNS name", name, name,
|
||||||
|
func(parsedName, constraint interface{}) (bool, error) {
|
||||||
|
return matchDomainConstraint(parsedName.(string), constraint.(string))
|
||||||
|
}, c.PermittedDNSDomains, c.ExcludedDNSDomains); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
case nameTypeURI:
|
||||||
|
name := string(data)
|
||||||
|
uri, err := url.Parse(name)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("x509: internal error: URI SAN %q failed to parse", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.checkNameConstraints(&comparisonCount, maxConstraintComparisons, "URI", name, uri,
|
||||||
|
func(parsedName, constraint interface{}) (bool, error) {
|
||||||
|
return matchURIConstraint(parsedName.(*url.URL), constraint.(string))
|
||||||
|
}, c.PermittedURIDomains, c.ExcludedURIDomains); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
case nameTypeIP:
|
||||||
|
ip := net.IP(data)
|
||||||
|
if l := len(ip); l != net.IPv4len && l != net.IPv6len {
|
||||||
|
return fmt.Errorf("x509: internal error: IP SAN %x failed to parse", data)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.checkNameConstraints(&comparisonCount, maxConstraintComparisons, "IP address", ip.String(), ip,
|
||||||
|
func(parsedName, constraint interface{}) (bool, error) {
|
||||||
|
return matchIPConstraint(parsedName.(net.IP), constraint.(*net.IPNet))
|
||||||
|
}, c.PermittedIPRanges, c.ExcludedIPRanges); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
// Unknown SAN types are ignored.
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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, x509.NotAuthorizedToSign, ""}
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.BasicConstraintsValid && c.MaxPathLen >= 0 {
|
||||||
|
numIntermediates := len(currentChain) - 1
|
||||||
|
if numIntermediates > c.MaxPathLen {
|
||||||
|
return CertificateInvalidError{c, x509.TooManyIntermediates, ""}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 more chains where the first
|
||||||
|
// element of the chain is c and the last element is from opts.Roots.
|
||||||
|
//
|
||||||
|
// If opts.Roots is nil, the platform verifier might be used, and
|
||||||
|
// verification details might differ from what is described below. If system
|
||||||
|
// roots are unavailable the returned error will be of type SystemRootsError.
|
||||||
|
//
|
||||||
|
// Name constraints in the intermediates will be applied to all names claimed
|
||||||
|
// in the chain, not just opts.DNSName. Thus it is invalid for a leaf to claim
|
||||||
|
// example.com if an intermediate doesn't permit it, even if example.com is not
|
||||||
|
// the name being validated. Note that DirectoryName constraints are not
|
||||||
|
// supported.
|
||||||
|
//
|
||||||
|
// Extended Key Usage values are enforced nested down a chain, so an intermediate
|
||||||
|
// or root that enumerates EKUs prevents a leaf from asserting an EKU not in that
|
||||||
|
// list. (While this is not specified, it is common practice in order to limit
|
||||||
|
// the types of certificates a CA can issue.)
|
||||||
|
//
|
||||||
|
// WARNING: this function doesn't do any revocation checking.
|
||||||
|
func (c *Certificate) Verify(opts VerifyOptions) (chains [][]*Certificate, err error) {
|
||||||
|
// Platform-specific verification needs the ASN.1 contents so
|
||||||
|
// this makes the behavior consistent across platforms.
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use Windows's own verification and chain building.
|
||||||
|
if opts.Roots == nil && runtime.GOOS == "windows" {
|
||||||
|
return c.systemVerify(&opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.Roots == nil {
|
||||||
|
opts.Roots = systemRootsPool()
|
||||||
|
if opts.Roots == nil {
|
||||||
|
return nil, SystemRootsError{systemRootsErr}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = c.isValid(leafCertificate, nil, &opts)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(opts.DNSName) > 0 {
|
||||||
|
err = c.VerifyHostname(opts.DNSName)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var candidateChains [][]*Certificate
|
||||||
|
if opts.Roots.contains(c) {
|
||||||
|
candidateChains = append(candidateChains, []*Certificate{c})
|
||||||
|
} else {
|
||||||
|
if candidateChains, err = c.buildChains(nil, []*Certificate{c}, nil, &opts); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
keyUsages := opts.KeyUsages
|
||||||
|
if len(keyUsages) == 0 {
|
||||||
|
keyUsages = []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If any key usage is acceptable then we're done.
|
||||||
|
for _, usage := range keyUsages {
|
||||||
|
if usage == x509.ExtKeyUsageAny {
|
||||||
|
return candidateChains, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, candidate := range candidateChains {
|
||||||
|
if checkChainForKeyUsage(candidate, keyUsages) {
|
||||||
|
chains = append(chains, candidate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(chains) == 0 {
|
||||||
|
return nil, CertificateInvalidError{c, x509.IncompatibleUsage, ""}
|
||||||
|
}
|
||||||
|
|
||||||
|
return chains, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func appendToFreshChain(chain []*Certificate, cert *Certificate) []*Certificate {
|
||||||
|
n := make([]*Certificate, len(chain)+1)
|
||||||
|
copy(n, chain)
|
||||||
|
n[len(chain)] = cert
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
// maxChainSignatureChecks is the maximum number of CheckSignatureFrom calls
|
||||||
|
// that an invocation of buildChains will (tranistively) make. Most chains are
|
||||||
|
// less than 15 certificates long, so this leaves space for multiple chains and
|
||||||
|
// for failed checks due to different intermediates having the same Subject.
|
||||||
|
const maxChainSignatureChecks = 100
|
||||||
|
|
||||||
|
func (c *Certificate) buildChains(cache map[*Certificate][][]*Certificate, currentChain []*Certificate, sigChecks *int, opts *VerifyOptions) (chains [][]*Certificate, err error) {
|
||||||
|
var (
|
||||||
|
hintErr error
|
||||||
|
hintCert *Certificate
|
||||||
|
)
|
||||||
|
|
||||||
|
considerCandidate := func(certType int, candidate *Certificate) {
|
||||||
|
for _, cert := range currentChain {
|
||||||
|
if cert.Equal(candidate) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if sigChecks == nil {
|
||||||
|
sigChecks = new(int)
|
||||||
|
}
|
||||||
|
*sigChecks++
|
||||||
|
if *sigChecks > maxChainSignatureChecks {
|
||||||
|
err = errors.New("x509: signature check attempts limit reached while verifying certificate chain")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.CheckSignatureFrom(candidate); err != nil {
|
||||||
|
if hintErr == nil {
|
||||||
|
hintErr = err
|
||||||
|
hintCert = candidate
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = candidate.isValid(certType, currentChain, opts)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch certType {
|
||||||
|
case rootCertificate:
|
||||||
|
chains = append(chains, appendToFreshChain(currentChain, candidate))
|
||||||
|
case intermediateCertificate:
|
||||||
|
if cache == nil {
|
||||||
|
cache = make(map[*Certificate][][]*Certificate)
|
||||||
|
}
|
||||||
|
childChains, ok := cache[candidate]
|
||||||
|
if !ok {
|
||||||
|
childChains, err = candidate.buildChains(cache, appendToFreshChain(currentChain, candidate), sigChecks, opts)
|
||||||
|
cache[candidate] = childChains
|
||||||
|
}
|
||||||
|
chains = append(chains, childChains...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, rootNum := range opts.Roots.findPotentialParents(c) {
|
||||||
|
considerCandidate(rootCertificate, opts.Roots.certs[rootNum])
|
||||||
|
}
|
||||||
|
for _, intermediateNum := range opts.Intermediates.findPotentialParents(c) {
|
||||||
|
considerCandidate(intermediateCertificate, opts.Intermediates.certs[intermediateNum])
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(chains) > 0 {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
if len(chains) == 0 && err == nil {
|
||||||
|
err = UnknownAuthorityError{c, hintErr, hintCert}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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, ".")
|
||||||
|
|
||||||
|
if len(host) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, part := range strings.Split(host, ".") {
|
||||||
|
if part == "" {
|
||||||
|
// Empty label.
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if 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.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for j, c := range part {
|
||||||
|
if 'a' <= c && c <= 'z' {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if '0' <= c && c <= '9' {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if 'A' <= c && c <= 'Z' {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if c == '-' && j != 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if c == '_' || c == ':' {
|
||||||
|
// Not valid characters in hostnames, but commonly
|
||||||
|
// found in deployments outside the WebPKI.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 matchHostnames(pattern, host string) bool {
|
||||||
|
host = strings.TrimSuffix(host, ".")
|
||||||
|
pattern = strings.TrimSuffix(pattern, ".")
|
||||||
|
|
||||||
|
if len(pattern) == 0 || len(host) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
patternParts := strings.Split(pattern, ".")
|
||||||
|
hostParts := strings.Split(host, ".")
|
||||||
|
|
||||||
|
if len(patternParts) != len(hostParts) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, patternPart := range patternParts {
|
||||||
|
if i == 0 && patternPart == "*" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if patternPart != hostParts[i] {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// toLowerCaseASCII returns a lower-case version of in. See RFC 6125 6.4.1. We use
|
||||||
|
// an explicitly ASCII function to avoid any sharp corners resulting from
|
||||||
|
// performing Unicode operations on DNS labels.
|
||||||
|
func toLowerCaseASCII(in string) string {
|
||||||
|
// If the string is already lower-case then there's nothing to do.
|
||||||
|
isAlreadyLowerCase := true
|
||||||
|
for _, c := range in {
|
||||||
|
if c == utf8.RuneError {
|
||||||
|
// If we get a UTF-8 error then there might be
|
||||||
|
// upper-case ASCII bytes in the invalid sequence.
|
||||||
|
isAlreadyLowerCase = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if 'A' <= c && c <= 'Z' {
|
||||||
|
isAlreadyLowerCase = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if isAlreadyLowerCase {
|
||||||
|
return in
|
||||||
|
}
|
||||||
|
|
||||||
|
out := []byte(in)
|
||||||
|
for i, c := range out {
|
||||||
|
if 'A' <= c && c <= 'Z' {
|
||||||
|
out[i] += 'a' - 'A'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return string(out)
|
||||||
|
}
|
||||||
|
|
||||||
|
// VerifyHostname returns nil if c is a valid certificate for the named host.
|
||||||
|
// Otherwise it returns an error describing the mismatch.
|
||||||
|
func (c *Certificate) VerifyHostname(h string) error {
|
||||||
|
// IP addresses may be written in [ ].
|
||||||
|
candidateIP := h
|
||||||
|
if len(h) >= 3 && h[0] == '[' && h[len(h)-1] == ']' {
|
||||||
|
candidateIP = h[1 : len(h)-1]
|
||||||
|
}
|
||||||
|
if ip := net.ParseIP(candidateIP); ip != nil {
|
||||||
|
// We only match IP addresses against IP SANs.
|
||||||
|
// See RFC 6125, Appendix B.2.
|
||||||
|
for _, candidate := range c.IPAddresses {
|
||||||
|
if ip.Equal(candidate) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return x509.HostnameError{&c.Certificate, candidateIP}
|
||||||
|
}
|
||||||
|
|
||||||
|
lowered := toLowerCaseASCII(h)
|
||||||
|
|
||||||
|
if c.commonNameAsHostname() {
|
||||||
|
if matchHostnames(toLowerCaseASCII(c.Subject.CommonName), lowered) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for _, match := range c.DNSNames {
|
||||||
|
if matchHostnames(toLowerCaseASCII(match), lowered) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return x509.HostnameError{&c.Certificate, h}
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkChainForKeyUsage(chain []*Certificate, keyUsages []x509.ExtKeyUsage) bool {
|
||||||
|
usages := make([]x509.ExtKeyUsage, len(keyUsages))
|
||||||
|
copy(usages, keyUsages)
|
||||||
|
|
||||||
|
if len(chain) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
usagesRemaining := len(usages)
|
||||||
|
|
||||||
|
// We walk down the list and cross out any usages that aren't supported
|
||||||
|
// by each certificate. If we cross out all the usages, then the chain
|
||||||
|
// is unacceptable.
|
||||||
|
|
||||||
|
NextCert:
|
||||||
|
for i := len(chain) - 1; i >= 0; i-- {
|
||||||
|
cert := chain[i]
|
||||||
|
if len(cert.ExtKeyUsage) == 0 && len(cert.UnknownExtKeyUsage) == 0 {
|
||||||
|
// The certificate doesn't have any extended key usage specified.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, usage := range cert.ExtKeyUsage {
|
||||||
|
if usage == x509.ExtKeyUsageAny {
|
||||||
|
// The certificate is explicitly good for any usage.
|
||||||
|
continue NextCert
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const invalidUsage x509.ExtKeyUsage = -1
|
||||||
|
|
||||||
|
NextRequestedUsage:
|
||||||
|
for i, requestedUsage := range usages {
|
||||||
|
if requestedUsage == invalidUsage {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, usage := range cert.ExtKeyUsage {
|
||||||
|
if requestedUsage == usage {
|
||||||
|
continue NextRequestedUsage
|
||||||
|
} else if requestedUsage == x509.ExtKeyUsageServerAuth &&
|
||||||
|
(usage == x509.ExtKeyUsageNetscapeServerGatedCrypto ||
|
||||||
|
usage == x509.ExtKeyUsageMicrosoftServerGatedCrypto) {
|
||||||
|
// In order to support COMODO
|
||||||
|
// certificate chains, we have to
|
||||||
|
// accept Netscape or Microsoft SGC
|
||||||
|
// usages as equal to ServerAuth.
|
||||||
|
continue NextRequestedUsage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
usages[i] = invalidUsage
|
||||||
|
usagesRemaining--
|
||||||
|
if usagesRemaining == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func matchEmailConstraint(mailbox rfc2821Mailbox, constraint string) (bool, error) {
|
||||||
|
// If the constraint contains an @, then it specifies an exact mailbox
|
||||||
|
// name.
|
||||||
|
if strings.Contains(constraint, "@") {
|
||||||
|
constraintMailbox, ok := parseRFC2821Mailbox(constraint)
|
||||||
|
if !ok {
|
||||||
|
return false, fmt.Errorf("x509: internal error: cannot parse constraint %q", constraint)
|
||||||
|
}
|
||||||
|
return mailbox.local == constraintMailbox.local && strings.EqualFold(mailbox.domain, constraintMailbox.domain), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise the constraint is like a DNS constraint of the domain part
|
||||||
|
// of the mailbox.
|
||||||
|
return matchDomainConstraint(mailbox.domain, constraint)
|
||||||
|
}
|
||||||
|
|
||||||
|
func matchURIConstraint(uri *url.URL, constraint string) (bool, error) {
|
||||||
|
// From RFC 5280, Section 4.2.1.10:
|
||||||
|
// “a uniformResourceIdentifier that does not include an authority
|
||||||
|
// component with a host name specified as a fully qualified domain
|
||||||
|
// name (e.g., if the URI either does not include an authority
|
||||||
|
// component or includes an authority component in which the host name
|
||||||
|
// is specified as an IP address), then the application MUST reject the
|
||||||
|
// certificate.”
|
||||||
|
|
||||||
|
host := uri.Host
|
||||||
|
if len(host) == 0 {
|
||||||
|
return false, fmt.Errorf("URI with empty host (%q) cannot be matched against constraints", uri.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.Contains(host, ":") && !strings.HasSuffix(host, "]") {
|
||||||
|
var err error
|
||||||
|
host, _, err = net.SplitHostPort(uri.Host)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(host, "[") && strings.HasSuffix(host, "]") ||
|
||||||
|
net.ParseIP(host) != nil {
|
||||||
|
return false, fmt.Errorf("URI with IP (%q) cannot be matched against constraints", uri.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
return matchDomainConstraint(host, constraint)
|
||||||
|
}
|
||||||
|
|
||||||
|
func matchIPConstraint(ip net.IP, constraint *net.IPNet) (bool, error) {
|
||||||
|
if len(ip) != len(constraint.IP) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range ip {
|
||||||
|
if mask := constraint.Mask[i]; ip[i]&mask != constraint.IP[i]&mask {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func matchDomainConstraint(domain, constraint string) (bool, error) {
|
||||||
|
// The meaning of zero length constraints is not specified, but this
|
||||||
|
// code follows NSS and accepts them as matching everything.
|
||||||
|
if len(constraint) == 0 {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
domainLabels, ok := domainToReverseLabels(domain)
|
||||||
|
if !ok {
|
||||||
|
return false, fmt.Errorf("x509: internal error: cannot parse domain %q", domain)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RFC 5280 says that a leading period in a domain name means that at
|
||||||
|
// least one label must be prepended, but only for URI and email
|
||||||
|
// constraints, not DNS constraints. The code also supports that
|
||||||
|
// behaviour for DNS constraints.
|
||||||
|
|
||||||
|
mustHaveSubdomains := false
|
||||||
|
if constraint[0] == '.' {
|
||||||
|
mustHaveSubdomains = true
|
||||||
|
constraint = constraint[1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
constraintLabels, ok := domainToReverseLabels(constraint)
|
||||||
|
if !ok {
|
||||||
|
return false, fmt.Errorf("x509: internal error: cannot parse domain %q", constraint)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(domainLabels) < len(constraintLabels) ||
|
||||||
|
(mustHaveSubdomains && len(domainLabels) == len(constraintLabels)) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, constraintLabel := range constraintLabels {
|
||||||
|
if !strings.EqualFold(constraintLabel, domainLabels[i]) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
}
|
1626
smx509/x509.go
1626
smx509/x509.go
File diff suppressed because it is too large
Load Diff
@ -1,8 +1,12 @@
|
|||||||
package smx509
|
package smx509
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
|
"crypto/ed25519"
|
||||||
|
"crypto/elliptic"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"crypto/x509/pkix"
|
"crypto/x509/pkix"
|
||||||
"encoding/asn1"
|
"encoding/asn1"
|
||||||
@ -11,8 +15,13 @@ import (
|
|||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
"net"
|
||||||
|
"net/url"
|
||||||
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/emmansun/gmsm/sm2"
|
"github.com/emmansun/gmsm/sm2"
|
||||||
)
|
)
|
||||||
@ -43,6 +52,108 @@ bxIHjKZHc2sztHCXe7cseWGiLq0syg==
|
|||||||
-----END CERTIFICATE REQUEST-----
|
-----END CERTIFICATE REQUEST-----
|
||||||
`
|
`
|
||||||
|
|
||||||
|
const pemCertificate = `-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDATCCAemgAwIBAgIRAKQkkrFx1T/dgB/Go/xBM5swDQYJKoZIhvcNAQELBQAw
|
||||||
|
EjEQMA4GA1UEChMHQWNtZSBDbzAeFw0xNjA4MTcyMDM2MDdaFw0xNzA4MTcyMDM2
|
||||||
|
MDdaMBIxEDAOBgNVBAoTB0FjbWUgQ28wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
|
||||||
|
ggEKAoIBAQDAoJtjG7M6InsWwIo+l3qq9u+g2rKFXNu9/mZ24XQ8XhV6PUR+5HQ4
|
||||||
|
jUFWC58ExYhottqK5zQtKGkw5NuhjowFUgWB/VlNGAUBHtJcWR/062wYrHBYRxJH
|
||||||
|
qVXOpYKbIWwFKoXu3hcpg/CkdOlDWGKoZKBCwQwUBhWE7MDhpVdQ+ZljUJWL+FlK
|
||||||
|
yQK5iRsJd5TGJ6VUzLzdT4fmN2DzeK6GLeyMpVpU3sWV90JJbxWQ4YrzkKzYhMmB
|
||||||
|
EcpXTG2wm+ujiHU/k2p8zlf8Sm7VBM/scmnMFt0ynNXop4FWvJzEm1G0xD2t+e2I
|
||||||
|
5Utr04dOZPCgkm++QJgYhtZvgW7ZZiGTAgMBAAGjUjBQMA4GA1UdDwEB/wQEAwIF
|
||||||
|
oDATBgNVHSUEDDAKBggrBgEFBQcDATAMBgNVHRMBAf8EAjAAMBsGA1UdEQQUMBKC
|
||||||
|
EHRlc3QuZXhhbXBsZS5jb20wDQYJKoZIhvcNAQELBQADggEBADpqKQxrthH5InC7
|
||||||
|
X96UP0OJCu/lLEMkrjoEWYIQaFl7uLPxKH5AmQPH4lYwF7u7gksR7owVG9QU9fs6
|
||||||
|
1fK7II9CVgCd/4tZ0zm98FmU4D0lHGtPARrrzoZaqVZcAvRnFTlPX5pFkPhVjjai
|
||||||
|
/mkxX9LpD8oK1445DFHxK5UjLMmPIIWd8EOi+v5a+hgGwnJpoW7hntSl8kHMtTmy
|
||||||
|
fnnktsblSUV4lRCit0ymC7Ojhe+gzCCwkgs5kDzVVag+tnl/0e2DloIjASwOhpbH
|
||||||
|
KVcg7fBd484ht/sS+l0dsB4KDOSpd8JzVDMF8OZqlaydizoJO0yWr9GbCN1+OKq5
|
||||||
|
EhLrEqU=
|
||||||
|
-----END CERTIFICATE-----`
|
||||||
|
|
||||||
|
var pemPrivateKey = testingKey(`
|
||||||
|
-----BEGIN RSA TESTING KEY-----
|
||||||
|
MIICXAIBAAKBgQCxoeCUW5KJxNPxMp+KmCxKLc1Zv9Ny+4CFqcUXVUYH69L3mQ7v
|
||||||
|
IWrJ9GBfcaA7BPQqUlWxWM+OCEQZH1EZNIuqRMNQVuIGCbz5UQ8w6tS0gcgdeGX7
|
||||||
|
J7jgCQ4RK3F/PuCM38QBLaHx988qG8NMc6VKErBjctCXFHQt14lerd5KpQIDAQAB
|
||||||
|
AoGAYrf6Hbk+mT5AI33k2Jt1kcweodBP7UkExkPxeuQzRVe0KVJw0EkcFhywKpr1
|
||||||
|
V5eLMrILWcJnpyHE5slWwtFHBG6a5fLaNtsBBtcAIfqTQ0Vfj5c6SzVaJv0Z5rOd
|
||||||
|
7gQF6isy3t3w9IF3We9wXQKzT6q5ypPGdm6fciKQ8RnzREkCQQDZwppKATqQ41/R
|
||||||
|
vhSj90fFifrGE6aVKC1hgSpxGQa4oIdsYYHwMzyhBmWW9Xv/R+fPyr8ZwPxp2c12
|
||||||
|
33QwOLPLAkEA0NNUb+z4ebVVHyvSwF5jhfJxigim+s49KuzJ1+A2RaSApGyBZiwS
|
||||||
|
rWvWkB471POAKUYt5ykIWVZ83zcceQiNTwJBAMJUFQZX5GDqWFc/zwGoKkeR49Yi
|
||||||
|
MTXIvf7Wmv6E++eFcnT461FlGAUHRV+bQQXGsItR/opIG7mGogIkVXa3E1MCQARX
|
||||||
|
AAA7eoZ9AEHflUeuLn9QJI/r0hyQQLEtrpwv6rDT1GCWaLII5HJ6NUFVf4TTcqxo
|
||||||
|
6vdM4QGKTJoO+SaCyP0CQFdpcxSAuzpFcKv0IlJ8XzS/cy+mweCMwyJ1PFEc4FX6
|
||||||
|
wg/HcAJWY60xZTJDFN+Qfx8ZQvBEin6c2/h+zZi5IVY=
|
||||||
|
-----END RSA TESTING KEY-----
|
||||||
|
`)
|
||||||
|
|
||||||
|
const ed25519CRLKey = `-----BEGIN PRIVATE KEY-----
|
||||||
|
MC4CAQAwBQYDK2VwBCIEINdKh2096vUBYu4EIFpjShsUSh3vimKya1sQ1YTT4RZG
|
||||||
|
-----END PRIVATE KEY-----`
|
||||||
|
|
||||||
|
const ed25519CRLCertificate = `
|
||||||
|
Certificate:
|
||||||
|
Data:
|
||||||
|
Version: 3 (0x2)
|
||||||
|
Serial Number:
|
||||||
|
7a:07:a0:9d:14:04:16:fc:1f:d8:e5:fe:d1:1d:1f:8d
|
||||||
|
Signature Algorithm: ED25519
|
||||||
|
Issuer: CN = Ed25519 CRL Test CA
|
||||||
|
Validity
|
||||||
|
Not Before: Oct 30 01:20:20 2019 GMT
|
||||||
|
Not After : Dec 31 23:59:59 9999 GMT
|
||||||
|
Subject: CN = Ed25519 CRL Test CA
|
||||||
|
Subject Public Key Info:
|
||||||
|
Public Key Algorithm: ED25519
|
||||||
|
ED25519 Public-Key:
|
||||||
|
pub:
|
||||||
|
95:73:3b:b0:06:2a:31:5a:b6:a7:a6:6e:ef:71:df:
|
||||||
|
ac:6f:6b:39:03:85:5e:63:4b:f8:a6:0f:68:c6:6f:
|
||||||
|
75:21
|
||||||
|
X509v3 extensions:
|
||||||
|
X509v3 Key Usage: critical
|
||||||
|
Digital Signature, Certificate Sign, CRL Sign
|
||||||
|
X509v3 Extended Key Usage:
|
||||||
|
TLS Web Client Authentication, TLS Web Server Authentication, OCSP Signing
|
||||||
|
X509v3 Basic Constraints: critical
|
||||||
|
CA:TRUE
|
||||||
|
X509v3 Subject Key Identifier:
|
||||||
|
B7:17:DA:16:EA:C5:ED:1F:18:49:44:D3:D2:E3:A0:35:0A:81:93:60
|
||||||
|
X509v3 Authority Key Identifier:
|
||||||
|
keyid:B7:17:DA:16:EA:C5:ED:1F:18:49:44:D3:D2:E3:A0:35:0A:81:93:60
|
||||||
|
Signature Algorithm: ED25519
|
||||||
|
fc:3e:14:ea:bb:70:c2:6f:38:34:70:bc:c8:a7:f4:7c:0d:1e:
|
||||||
|
28:d7:2a:9f:22:8a:45:e8:02:76:84:1e:2d:64:2d:1e:09:b5:
|
||||||
|
29:71:1f:95:8a:4e:79:87:51:60:9a:e7:86:40:f6:60:c7:d1:
|
||||||
|
ee:68:76:17:1d:90:cc:92:93:07
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIBijCCATygAwIBAgIQegegnRQEFvwf2OX+0R0fjTAFBgMrZXAwHjEcMBoGA1UE
|
||||||
|
AxMTRWQyNTUxOSBDUkwgVGVzdCBDQTAgFw0xOTEwMzAwMTIwMjBaGA85OTk5MTIz
|
||||||
|
MTIzNTk1OVowHjEcMBoGA1UEAxMTRWQyNTUxOSBDUkwgVGVzdCBDQTAqMAUGAytl
|
||||||
|
cAMhAJVzO7AGKjFatqembu9x36xvazkDhV5jS/imD2jGb3Uho4GNMIGKMA4GA1Ud
|
||||||
|
DwEB/wQEAwIBhjAnBgNVHSUEIDAeBggrBgEFBQcDAgYIKwYBBQUHAwEGCCsGAQUF
|
||||||
|
BwMJMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFLcX2hbqxe0fGElE09LjoDUK
|
||||||
|
gZNgMB8GA1UdIwQYMBaAFLcX2hbqxe0fGElE09LjoDUKgZNgMAUGAytlcANBAPw+
|
||||||
|
FOq7cMJvODRwvMin9HwNHijXKp8iikXoAnaEHi1kLR4JtSlxH5WKTnmHUWCa54ZA
|
||||||
|
9mDH0e5odhcdkMySkwc=
|
||||||
|
-----END CERTIFICATE-----`
|
||||||
|
|
||||||
|
var testPrivateKey *rsa.PrivateKey
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
block, _ := pem.Decode([]byte(pemPrivateKey))
|
||||||
|
|
||||||
|
var err error
|
||||||
|
if testPrivateKey, err = x509.ParsePKCS1PrivateKey(block.Bytes); err != nil {
|
||||||
|
panic("Failed to parse private key: " + err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testingKey(s string) string { return strings.ReplaceAll(s, "TESTING KEY", "PRIVATE KEY") }
|
||||||
|
|
||||||
func getPublicKey(pemContent []byte) (interface{}, error) {
|
func getPublicKey(pemContent []byte) (interface{}, error) {
|
||||||
block, _ := pem.Decode(pemContent)
|
block, _ := pem.Decode(pemContent)
|
||||||
if block == nil {
|
if block == nil {
|
||||||
@ -57,7 +168,7 @@ func parseAndCheckCsr(csrblock []byte) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
fmt.Printf("%v\n", csr)
|
fmt.Printf("%v\n", csr)
|
||||||
return CheckSignature(csr)
|
return csr.CheckSignature()
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParseCertificateRequest(t *testing.T) {
|
func TestParseCertificateRequest(t *testing.T) {
|
||||||
@ -73,6 +184,7 @@ func TestParseCertificateRequest(t *testing.T) {
|
|||||||
|
|
||||||
func TestCreateCertificateRequest(t *testing.T) {
|
func TestCreateCertificateRequest(t *testing.T) {
|
||||||
priv, _ := sm2.GenerateKey(rand.Reader)
|
priv, _ := sm2.GenerateKey(rand.Reader)
|
||||||
|
|
||||||
names := pkix.Name{CommonName: "TestName"}
|
names := pkix.Name{CommonName: "TestName"}
|
||||||
var template = x509.CertificateRequest{Subject: names}
|
var template = x509.CertificateRequest{Subject: names}
|
||||||
csrblock, err := CreateCertificateRequest(rand.Reader, &template, priv)
|
csrblock, err := CreateCertificateRequest(rand.Reader, &template, priv)
|
||||||
@ -140,3 +252,420 @@ func TestMarshalPKIXPublicKey(t *testing.T) {
|
|||||||
t.Errorf("expected=%s, result=%s", publicKeyPemFromAliKms, pemContent)
|
t.Errorf("expected=%s, result=%s", publicKeyPemFromAliKms, pemContent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_CreateCertificateRequest(t *testing.T) {
|
||||||
|
random := rand.Reader
|
||||||
|
|
||||||
|
sm2Priv, err := sm2.GenerateKey(rand.Reader)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to generate SM2 key: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ecdsa256Priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to generate ECDSA key: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ecdsa384Priv, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to generate ECDSA key: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ecdsa521Priv, err := ecdsa.GenerateKey(elliptic.P521(), rand.Reader)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to generate ECDSA key: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, ed25519Priv, err := ed25519.GenerateKey(random)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to generate Ed25519 key: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
priv interface{}
|
||||||
|
sigAlgo x509.SignatureAlgorithm
|
||||||
|
}{
|
||||||
|
{"RSA", testPrivateKey, x509.SHA1WithRSA},
|
||||||
|
{"SM2-256", sm2Priv, -1},
|
||||||
|
{"ECDSA-256", ecdsa256Priv, x509.ECDSAWithSHA1},
|
||||||
|
{"ECDSA-384", ecdsa384Priv, x509.ECDSAWithSHA1},
|
||||||
|
{"ECDSA-521", ecdsa521Priv, x509.ECDSAWithSHA1},
|
||||||
|
{"Ed25519", ed25519Priv, x509.PureEd25519},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
template := x509.CertificateRequest{
|
||||||
|
Subject: pkix.Name{
|
||||||
|
CommonName: "test.example.com",
|
||||||
|
Organization: []string{"Σ Acme Co"},
|
||||||
|
},
|
||||||
|
SignatureAlgorithm: test.sigAlgo,
|
||||||
|
DNSNames: []string{"test.example.com"},
|
||||||
|
EmailAddresses: []string{"gopher@golang.org"},
|
||||||
|
IPAddresses: []net.IP{net.IPv4(127, 0, 0, 1).To4(), net.ParseIP("2001:4860:0:2001::68")},
|
||||||
|
}
|
||||||
|
|
||||||
|
derBytes, err := CreateCertificateRequest(random, &template, test.priv)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%s: failed to create certificate request: %s", test.name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
out, err := ParseCertificateRequest(derBytes)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%s: failed to create certificate request: %s", test.name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
err = out.CheckSignature()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%s: failed to check certificate request signature: %s", test.name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if out.Subject.CommonName != template.Subject.CommonName {
|
||||||
|
t.Errorf("%s: output subject common name and template subject common name don't match", test.name)
|
||||||
|
} else if len(out.Subject.Organization) != len(template.Subject.Organization) {
|
||||||
|
t.Errorf("%s: output subject organisation and template subject organisation don't match", test.name)
|
||||||
|
} else if len(out.DNSNames) != len(template.DNSNames) {
|
||||||
|
t.Errorf("%s: output DNS names and template DNS names don't match", test.name)
|
||||||
|
} else if len(out.EmailAddresses) != len(template.EmailAddresses) {
|
||||||
|
t.Errorf("%s: output email addresses and template email addresses don't match", test.name)
|
||||||
|
} else if len(out.IPAddresses) != len(template.IPAddresses) {
|
||||||
|
t.Errorf("%s: output IP addresses and template IP addresses names don't match", test.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseCIDR(s string) *net.IPNet {
|
||||||
|
_, net, err := net.ParseCIDR(s)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return net
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseURI(s string) *url.URL {
|
||||||
|
uri, err := url.Parse(s)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return uri
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCreateSelfSignedCertificate(t *testing.T) {
|
||||||
|
random := rand.Reader
|
||||||
|
|
||||||
|
sm2Priv, err := sm2.GenerateKey(rand.Reader)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to generate SM2 key: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ecdsaPriv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to generate ECDSA key: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ed25519Pub, ed25519Priv, err := ed25519.GenerateKey(random)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to generate Ed25519 key: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
pub, priv interface{}
|
||||||
|
checkSig bool
|
||||||
|
sigAlgo x509.SignatureAlgorithm
|
||||||
|
}{
|
||||||
|
{"RSA/RSA", &testPrivateKey.PublicKey, testPrivateKey, true, x509.SHA1WithRSA},
|
||||||
|
{"RSA/ECDSA", &testPrivateKey.PublicKey, ecdsaPriv, false, x509.ECDSAWithSHA384},
|
||||||
|
{"RSA/SM2", &testPrivateKey.PublicKey, sm2Priv, false, x509.UnknownSignatureAlgorithm},
|
||||||
|
{"ECDSA/RSA", &ecdsaPriv.PublicKey, testPrivateKey, false, x509.SHA256WithRSA},
|
||||||
|
{"ECDSA/ECDSA", &ecdsaPriv.PublicKey, ecdsaPriv, true, x509.ECDSAWithSHA1},
|
||||||
|
{"ECDSA/SM2", &ecdsaPriv.PublicKey, sm2Priv, false, x509.UnknownSignatureAlgorithm},
|
||||||
|
{"SM2/ECDSA", &sm2Priv.PublicKey, ecdsaPriv, false, x509.ECDSAWithSHA1},
|
||||||
|
{"RSAPSS/RSAPSS", &testPrivateKey.PublicKey, testPrivateKey, true, x509.SHA256WithRSAPSS},
|
||||||
|
{"ECDSA/RSAPSS", &ecdsaPriv.PublicKey, testPrivateKey, false, x509.SHA256WithRSAPSS},
|
||||||
|
{"SM2/RSAPSS", &sm2Priv.PublicKey, testPrivateKey, false, x509.SHA256WithRSAPSS},
|
||||||
|
{"RSAPSS/ECDSA", &testPrivateKey.PublicKey, ecdsaPriv, false, x509.ECDSAWithSHA384},
|
||||||
|
{"Ed25519", ed25519Pub, ed25519Priv, true, x509.PureEd25519},
|
||||||
|
{"SM2", &sm2Priv.PublicKey, sm2Priv, true, x509.UnknownSignatureAlgorithm},
|
||||||
|
}
|
||||||
|
|
||||||
|
testExtKeyUsage := []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}
|
||||||
|
testUnknownExtKeyUsage := []asn1.ObjectIdentifier{[]int{1, 2, 3}, []int{2, 59, 1}}
|
||||||
|
extraExtensionData := []byte("extra extension")
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
commonName := "test.example.com"
|
||||||
|
template := x509.Certificate{
|
||||||
|
// SerialNumber is negative to ensure that negative
|
||||||
|
// values are parsed. This is due to the prevalence of
|
||||||
|
// buggy code that produces certificates with negative
|
||||||
|
// serial numbers.
|
||||||
|
SerialNumber: big.NewInt(-1),
|
||||||
|
Subject: pkix.Name{
|
||||||
|
CommonName: commonName,
|
||||||
|
Organization: []string{"Σ Acme Co"},
|
||||||
|
Country: []string{"US"},
|
||||||
|
ExtraNames: []pkix.AttributeTypeAndValue{
|
||||||
|
{
|
||||||
|
Type: []int{2, 5, 4, 42},
|
||||||
|
Value: "Gopher",
|
||||||
|
},
|
||||||
|
// This should override the Country, above.
|
||||||
|
{
|
||||||
|
Type: []int{2, 5, 4, 6},
|
||||||
|
Value: "NL",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
NotBefore: time.Unix(1000, 0),
|
||||||
|
NotAfter: time.Unix(100000, 0),
|
||||||
|
|
||||||
|
SignatureAlgorithm: test.sigAlgo,
|
||||||
|
|
||||||
|
SubjectKeyId: []byte{1, 2, 3, 4},
|
||||||
|
KeyUsage: x509.KeyUsageCertSign,
|
||||||
|
|
||||||
|
ExtKeyUsage: testExtKeyUsage,
|
||||||
|
UnknownExtKeyUsage: testUnknownExtKeyUsage,
|
||||||
|
|
||||||
|
BasicConstraintsValid: true,
|
||||||
|
IsCA: true,
|
||||||
|
|
||||||
|
OCSPServer: []string{"http://ocsp.example.com"},
|
||||||
|
IssuingCertificateURL: []string{"http://crt.example.com/ca1.crt"},
|
||||||
|
|
||||||
|
DNSNames: []string{"test.example.com"},
|
||||||
|
EmailAddresses: []string{"gopher@golang.org"},
|
||||||
|
IPAddresses: []net.IP{net.IPv4(127, 0, 0, 1).To4(), net.ParseIP("2001:4860:0:2001::68")},
|
||||||
|
URIs: []*url.URL{parseURI("https://foo.com/wibble#foo")},
|
||||||
|
|
||||||
|
PolicyIdentifiers: []asn1.ObjectIdentifier{[]int{1, 2, 3}},
|
||||||
|
PermittedDNSDomains: []string{".example.com", "example.com"},
|
||||||
|
ExcludedDNSDomains: []string{"bar.example.com"},
|
||||||
|
PermittedIPRanges: []*net.IPNet{parseCIDR("192.168.1.1/16"), parseCIDR("1.2.3.4/8")},
|
||||||
|
ExcludedIPRanges: []*net.IPNet{parseCIDR("2001:db8::/48")},
|
||||||
|
PermittedEmailAddresses: []string{"foo@example.com"},
|
||||||
|
ExcludedEmailAddresses: []string{".example.com", "example.com"},
|
||||||
|
PermittedURIDomains: []string{".bar.com", "bar.com"},
|
||||||
|
ExcludedURIDomains: []string{".bar2.com", "bar2.com"},
|
||||||
|
|
||||||
|
CRLDistributionPoints: []string{"http://crl1.example.com/ca1.crl", "http://crl2.example.com/ca1.crl"},
|
||||||
|
|
||||||
|
ExtraExtensions: []pkix.Extension{
|
||||||
|
{
|
||||||
|
Id: []int{1, 2, 3, 4},
|
||||||
|
Value: extraExtensionData,
|
||||||
|
},
|
||||||
|
// This extension should override the SubjectKeyId, above.
|
||||||
|
{
|
||||||
|
Id: oidExtensionSubjectKeyId,
|
||||||
|
Critical: false,
|
||||||
|
Value: []byte{0x04, 0x04, 4, 3, 2, 1},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
derBytes, err := CreateCertificate(random, &template, &template, test.pub, test.priv)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%s: failed to create certificate: %s", test.name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
cert, err := ParseCertificate(derBytes)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%s: failed to parse certificate: %s", test.name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(cert.PolicyIdentifiers) != 1 || !cert.PolicyIdentifiers[0].Equal(template.PolicyIdentifiers[0]) {
|
||||||
|
t.Errorf("%s: failed to parse policy identifiers: got:%#v want:%#v", test.name, cert.PolicyIdentifiers, template.PolicyIdentifiers)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(cert.PermittedDNSDomains) != 2 || cert.PermittedDNSDomains[0] != ".example.com" || cert.PermittedDNSDomains[1] != "example.com" {
|
||||||
|
t.Errorf("%s: failed to parse name constraints: %#v", test.name, cert.PermittedDNSDomains)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(cert.ExcludedDNSDomains) != 1 || cert.ExcludedDNSDomains[0] != "bar.example.com" {
|
||||||
|
t.Errorf("%s: failed to parse name constraint exclusions: %#v", test.name, cert.ExcludedDNSDomains)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(cert.PermittedIPRanges) != 2 || cert.PermittedIPRanges[0].String() != "192.168.0.0/16" || cert.PermittedIPRanges[1].String() != "1.0.0.0/8" {
|
||||||
|
t.Errorf("%s: failed to parse IP constraints: %#v", test.name, cert.PermittedIPRanges)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(cert.ExcludedIPRanges) != 1 || cert.ExcludedIPRanges[0].String() != "2001:db8::/48" {
|
||||||
|
t.Errorf("%s: failed to parse IP constraint exclusions: %#v", test.name, cert.ExcludedIPRanges)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(cert.PermittedEmailAddresses) != 1 || cert.PermittedEmailAddresses[0] != "foo@example.com" {
|
||||||
|
t.Errorf("%s: failed to parse permitted email addreses: %#v", test.name, cert.PermittedEmailAddresses)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(cert.ExcludedEmailAddresses) != 2 || cert.ExcludedEmailAddresses[0] != ".example.com" || cert.ExcludedEmailAddresses[1] != "example.com" {
|
||||||
|
t.Errorf("%s: failed to parse excluded email addreses: %#v", test.name, cert.ExcludedEmailAddresses)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(cert.PermittedURIDomains) != 2 || cert.PermittedURIDomains[0] != ".bar.com" || cert.PermittedURIDomains[1] != "bar.com" {
|
||||||
|
t.Errorf("%s: failed to parse permitted URIs: %#v", test.name, cert.PermittedURIDomains)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(cert.ExcludedURIDomains) != 2 || cert.ExcludedURIDomains[0] != ".bar2.com" || cert.ExcludedURIDomains[1] != "bar2.com" {
|
||||||
|
t.Errorf("%s: failed to parse excluded URIs: %#v", test.name, cert.ExcludedURIDomains)
|
||||||
|
}
|
||||||
|
|
||||||
|
if cert.Subject.CommonName != commonName {
|
||||||
|
t.Errorf("%s: subject wasn't correctly copied from the template. Got %s, want %s", test.name, cert.Subject.CommonName, commonName)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(cert.Subject.Country) != 1 || cert.Subject.Country[0] != "NL" {
|
||||||
|
t.Errorf("%s: ExtraNames didn't override Country", test.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ext := range cert.Extensions {
|
||||||
|
if ext.Id.Equal(oidExtensionSubjectAltName) {
|
||||||
|
if ext.Critical {
|
||||||
|
t.Fatal("SAN extension is marked critical")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
found := false
|
||||||
|
for _, atv := range cert.Subject.Names {
|
||||||
|
if atv.Type.Equal([]int{2, 5, 4, 42}) {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
t.Errorf("%s: Names didn't contain oid 2.5.4.42 from ExtraNames", test.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if cert.Issuer.CommonName != commonName {
|
||||||
|
t.Errorf("%s: issuer wasn't correctly copied from the template. Got %s, want %s", test.name, cert.Issuer.CommonName, commonName)
|
||||||
|
}
|
||||||
|
|
||||||
|
if cert.SignatureAlgorithm != test.sigAlgo {
|
||||||
|
t.Errorf("%s: SignatureAlgorithm wasn't copied from template. Got %v, want %v", test.name, cert.SignatureAlgorithm, test.sigAlgo)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(cert.ExtKeyUsage, testExtKeyUsage) {
|
||||||
|
t.Errorf("%s: extkeyusage wasn't correctly copied from the template. Got %v, want %v", test.name, cert.ExtKeyUsage, testExtKeyUsage)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(cert.UnknownExtKeyUsage, testUnknownExtKeyUsage) {
|
||||||
|
t.Errorf("%s: unknown extkeyusage wasn't correctly copied from the template. Got %v, want %v", test.name, cert.UnknownExtKeyUsage, testUnknownExtKeyUsage)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(cert.OCSPServer, template.OCSPServer) {
|
||||||
|
t.Errorf("%s: OCSP servers differ from template. Got %v, want %v", test.name, cert.OCSPServer, template.OCSPServer)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(cert.IssuingCertificateURL, template.IssuingCertificateURL) {
|
||||||
|
t.Errorf("%s: Issuing certificate URLs differ from template. Got %v, want %v", test.name, cert.IssuingCertificateURL, template.IssuingCertificateURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(cert.DNSNames, template.DNSNames) {
|
||||||
|
t.Errorf("%s: SAN DNS names differ from template. Got %v, want %v", test.name, cert.DNSNames, template.DNSNames)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(cert.EmailAddresses, template.EmailAddresses) {
|
||||||
|
t.Errorf("%s: SAN emails differ from template. Got %v, want %v", test.name, cert.EmailAddresses, template.EmailAddresses)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(cert.URIs) != 1 || cert.URIs[0].String() != "https://foo.com/wibble#foo" {
|
||||||
|
t.Errorf("%s: URIs differ from template. Got %v, want %v", test.name, cert.URIs, template.URIs)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(cert.IPAddresses, template.IPAddresses) {
|
||||||
|
t.Errorf("%s: SAN IPs differ from template. Got %v, want %v", test.name, cert.IPAddresses, template.IPAddresses)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(cert.CRLDistributionPoints, template.CRLDistributionPoints) {
|
||||||
|
t.Errorf("%s: CRL distribution points differ from template. Got %v, want %v", test.name, cert.CRLDistributionPoints, template.CRLDistributionPoints)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(cert.SubjectKeyId, []byte{4, 3, 2, 1}) {
|
||||||
|
t.Errorf("%s: ExtraExtensions didn't override SubjectKeyId", test.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Contains(derBytes, extraExtensionData) {
|
||||||
|
t.Errorf("%s: didn't find extra extension in DER output", test.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if test.checkSig {
|
||||||
|
err = cert.CheckSignatureFrom(cert)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%s: signature verification failed: %s", test.name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCRLCreation(t *testing.T) {
|
||||||
|
block, _ := pem.Decode([]byte(pemPrivateKey))
|
||||||
|
privRSA, _ := x509.ParsePKCS1PrivateKey(block.Bytes)
|
||||||
|
block, _ = pem.Decode([]byte(pemCertificate))
|
||||||
|
certRSA, _ := ParseCertificate(block.Bytes)
|
||||||
|
|
||||||
|
block, _ = pem.Decode([]byte(ed25519CRLKey))
|
||||||
|
privEd25519, _ := ParsePKCS8PrivateKey(block.Bytes)
|
||||||
|
block, _ = pem.Decode([]byte(ed25519CRLCertificate))
|
||||||
|
certEd25519, _ := ParseCertificate(block.Bytes)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
priv interface{}
|
||||||
|
cert *Certificate
|
||||||
|
}{
|
||||||
|
{"RSA CA", privRSA, certRSA},
|
||||||
|
{"Ed25519 CA", privEd25519, certEd25519},
|
||||||
|
}
|
||||||
|
|
||||||
|
loc := time.FixedZone("Oz/Atlantis", int((2 * time.Hour).Seconds()))
|
||||||
|
|
||||||
|
now := time.Unix(1000, 0).In(loc)
|
||||||
|
nowUTC := now.UTC()
|
||||||
|
expiry := time.Unix(10000, 0)
|
||||||
|
|
||||||
|
revokedCerts := []pkix.RevokedCertificate{
|
||||||
|
{
|
||||||
|
SerialNumber: big.NewInt(1),
|
||||||
|
RevocationTime: nowUTC,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
SerialNumber: big.NewInt(42),
|
||||||
|
// RevocationTime should be converted to UTC before marshaling.
|
||||||
|
RevocationTime: now,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
expectedCerts := []pkix.RevokedCertificate{
|
||||||
|
{
|
||||||
|
SerialNumber: big.NewInt(1),
|
||||||
|
RevocationTime: nowUTC,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
SerialNumber: big.NewInt(42),
|
||||||
|
RevocationTime: nowUTC,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
crlBytes, err := test.cert.CreateCRL(rand.Reader, test.priv, revokedCerts, now, expiry)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%s: error creating CRL: %s", test.name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
parsedCRL, err := x509.ParseDERCRL(crlBytes)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%s: error reparsing CRL: %s", test.name, err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(parsedCRL.TBSCertList.RevokedCertificates, expectedCerts) {
|
||||||
|
t.Errorf("%s: RevokedCertificates mismatch: got %v; want %v.", test.name,
|
||||||
|
parsedCRL.TBSCertList.RevokedCertificates, expectedCerts)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user