vendor: add cfssl dependency
This commit is contained in:
447
vendor/github.com/cloudflare/cfssl/signer/local/local.go
generated
vendored
Normal file
447
vendor/github.com/cloudflare/cfssl/signer/local/local.go
generated
vendored
Normal file
@@ -0,0 +1,447 @@
|
||||
// Package local implements certificate signature functionality for CFSSL.
|
||||
package local
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto"
|
||||
"crypto/rand"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/asn1"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"math/big"
|
||||
"net"
|
||||
"net/mail"
|
||||
"os"
|
||||
|
||||
"github.com/cloudflare/cfssl/certdb"
|
||||
"github.com/cloudflare/cfssl/config"
|
||||
cferr "github.com/cloudflare/cfssl/errors"
|
||||
"github.com/cloudflare/cfssl/helpers"
|
||||
"github.com/cloudflare/cfssl/info"
|
||||
"github.com/cloudflare/cfssl/log"
|
||||
"github.com/cloudflare/cfssl/signer"
|
||||
"github.com/google/certificate-transparency/go"
|
||||
"github.com/google/certificate-transparency/go/client"
|
||||
)
|
||||
|
||||
// Signer contains a signer that uses the standard library to
|
||||
// support both ECDSA and RSA CA keys.
|
||||
type Signer struct {
|
||||
ca *x509.Certificate
|
||||
priv crypto.Signer
|
||||
policy *config.Signing
|
||||
sigAlgo x509.SignatureAlgorithm
|
||||
dbAccessor certdb.Accessor
|
||||
}
|
||||
|
||||
// NewSigner creates a new Signer directly from a
|
||||
// private key and certificate, with optional policy.
|
||||
func NewSigner(priv crypto.Signer, cert *x509.Certificate, sigAlgo x509.SignatureAlgorithm, policy *config.Signing) (*Signer, error) {
|
||||
if policy == nil {
|
||||
policy = &config.Signing{
|
||||
Profiles: map[string]*config.SigningProfile{},
|
||||
Default: config.DefaultConfig()}
|
||||
}
|
||||
|
||||
if !policy.Valid() {
|
||||
return nil, cferr.New(cferr.PolicyError, cferr.InvalidPolicy)
|
||||
}
|
||||
|
||||
return &Signer{
|
||||
ca: cert,
|
||||
priv: priv,
|
||||
sigAlgo: sigAlgo,
|
||||
policy: policy,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// NewSignerFromFile generates a new local signer from a caFile
|
||||
// and a caKey file, both PEM encoded.
|
||||
func NewSignerFromFile(caFile, caKeyFile string, policy *config.Signing) (*Signer, error) {
|
||||
log.Debug("Loading CA: ", caFile)
|
||||
ca, err := ioutil.ReadFile(caFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Debug("Loading CA key: ", caKeyFile)
|
||||
cakey, err := ioutil.ReadFile(caKeyFile)
|
||||
if err != nil {
|
||||
return nil, cferr.Wrap(cferr.CertificateError, cferr.ReadFailed, err)
|
||||
}
|
||||
|
||||
parsedCa, err := helpers.ParseCertificatePEM(ca)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
strPassword := os.Getenv("CFSSL_CA_PK_PASSWORD")
|
||||
password := []byte(strPassword)
|
||||
if strPassword == "" {
|
||||
password = nil
|
||||
}
|
||||
|
||||
priv, err := helpers.ParsePrivateKeyPEMWithPassword(cakey, password)
|
||||
if err != nil {
|
||||
log.Debug("Malformed private key %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewSigner(priv, parsedCa, signer.DefaultSigAlgo(priv), policy)
|
||||
}
|
||||
|
||||
func (s *Signer) sign(template *x509.Certificate, profile *config.SigningProfile) (cert []byte, err error) {
|
||||
err = signer.FillTemplate(template, s.policy.Default, profile)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var initRoot bool
|
||||
if s.ca == nil {
|
||||
if !template.IsCA {
|
||||
err = cferr.New(cferr.PolicyError, cferr.InvalidRequest)
|
||||
return
|
||||
}
|
||||
template.DNSNames = nil
|
||||
template.EmailAddresses = nil
|
||||
s.ca = template
|
||||
initRoot = true
|
||||
template.MaxPathLen = signer.MaxPathLen
|
||||
} else if template.IsCA {
|
||||
template.MaxPathLen = 1
|
||||
template.DNSNames = nil
|
||||
template.EmailAddresses = nil
|
||||
}
|
||||
|
||||
derBytes, err := x509.CreateCertificate(rand.Reader, template, s.ca, template.PublicKey, s.priv)
|
||||
if err != nil {
|
||||
return nil, cferr.Wrap(cferr.CertificateError, cferr.Unknown, err)
|
||||
}
|
||||
if initRoot {
|
||||
s.ca, err = x509.ParseCertificate(derBytes)
|
||||
if err != nil {
|
||||
return nil, cferr.Wrap(cferr.CertificateError, cferr.ParseFailed, err)
|
||||
}
|
||||
}
|
||||
|
||||
cert = pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
|
||||
log.Infof("signed certificate with serial number %d", template.SerialNumber)
|
||||
return
|
||||
}
|
||||
|
||||
// replaceSliceIfEmpty replaces the contents of replaced with newContents if
|
||||
// the slice referenced by replaced is empty
|
||||
func replaceSliceIfEmpty(replaced, newContents *[]string) {
|
||||
if len(*replaced) == 0 {
|
||||
*replaced = *newContents
|
||||
}
|
||||
}
|
||||
|
||||
// PopulateSubjectFromCSR has functionality similar to Name, except
|
||||
// it fills the fields of the resulting pkix.Name with req's if the
|
||||
// subject's corresponding fields are empty
|
||||
func PopulateSubjectFromCSR(s *signer.Subject, req pkix.Name) pkix.Name {
|
||||
// if no subject, use req
|
||||
if s == nil {
|
||||
return req
|
||||
}
|
||||
|
||||
name := s.Name()
|
||||
|
||||
if name.CommonName == "" {
|
||||
name.CommonName = req.CommonName
|
||||
}
|
||||
|
||||
replaceSliceIfEmpty(&name.Country, &req.Country)
|
||||
replaceSliceIfEmpty(&name.Province, &req.Province)
|
||||
replaceSliceIfEmpty(&name.Locality, &req.Locality)
|
||||
replaceSliceIfEmpty(&name.Organization, &req.Organization)
|
||||
replaceSliceIfEmpty(&name.OrganizationalUnit, &req.OrganizationalUnit)
|
||||
if name.SerialNumber == "" {
|
||||
name.SerialNumber = req.SerialNumber
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
// OverrideHosts fills template's IPAddresses, EmailAddresses, and DNSNames with the
|
||||
// content of hosts, if it is not nil.
|
||||
func OverrideHosts(template *x509.Certificate, hosts []string) {
|
||||
if hosts != nil {
|
||||
template.IPAddresses = []net.IP{}
|
||||
template.EmailAddresses = []string{}
|
||||
template.DNSNames = []string{}
|
||||
}
|
||||
|
||||
for i := range hosts {
|
||||
if ip := net.ParseIP(hosts[i]); ip != nil {
|
||||
template.IPAddresses = append(template.IPAddresses, ip)
|
||||
} else if email, err := mail.ParseAddress(hosts[i]); err == nil && email != nil {
|
||||
template.EmailAddresses = append(template.EmailAddresses, email.Address)
|
||||
} else {
|
||||
template.DNSNames = append(template.DNSNames, hosts[i])
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Sign signs a new certificate based on the PEM-encoded client
|
||||
// certificate or certificate request with the signing profile,
|
||||
// specified by profileName.
|
||||
func (s *Signer) Sign(req signer.SignRequest) (cert []byte, err error) {
|
||||
profile, err := signer.Profile(s, req.Profile)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
block, _ := pem.Decode([]byte(req.Request))
|
||||
if block == nil {
|
||||
return nil, cferr.New(cferr.CSRError, cferr.DecodeFailed)
|
||||
}
|
||||
|
||||
if block.Type != "CERTIFICATE REQUEST" {
|
||||
return nil, cferr.Wrap(cferr.CSRError,
|
||||
cferr.BadRequest, errors.New("not a certificate or csr"))
|
||||
}
|
||||
|
||||
csrTemplate, err := signer.ParseCertificateRequest(s, block.Bytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Copy out only the fields from the CSR authorized by policy.
|
||||
safeTemplate := x509.Certificate{}
|
||||
// If the profile contains no explicit whitelist, assume that all fields
|
||||
// should be copied from the CSR.
|
||||
if profile.CSRWhitelist == nil {
|
||||
safeTemplate = *csrTemplate
|
||||
} else {
|
||||
if profile.CSRWhitelist.Subject {
|
||||
safeTemplate.Subject = csrTemplate.Subject
|
||||
}
|
||||
if profile.CSRWhitelist.PublicKeyAlgorithm {
|
||||
safeTemplate.PublicKeyAlgorithm = csrTemplate.PublicKeyAlgorithm
|
||||
}
|
||||
if profile.CSRWhitelist.PublicKey {
|
||||
safeTemplate.PublicKey = csrTemplate.PublicKey
|
||||
}
|
||||
if profile.CSRWhitelist.SignatureAlgorithm {
|
||||
safeTemplate.SignatureAlgorithm = csrTemplate.SignatureAlgorithm
|
||||
}
|
||||
if profile.CSRWhitelist.DNSNames {
|
||||
safeTemplate.DNSNames = csrTemplate.DNSNames
|
||||
}
|
||||
if profile.CSRWhitelist.IPAddresses {
|
||||
safeTemplate.IPAddresses = csrTemplate.IPAddresses
|
||||
}
|
||||
if profile.CSRWhitelist.EmailAddresses {
|
||||
safeTemplate.EmailAddresses = csrTemplate.EmailAddresses
|
||||
}
|
||||
}
|
||||
|
||||
OverrideHosts(&safeTemplate, req.Hosts)
|
||||
safeTemplate.Subject = PopulateSubjectFromCSR(req.Subject, safeTemplate.Subject)
|
||||
|
||||
// If there is a whitelist, ensure that both the Common Name and SAN DNSNames match
|
||||
if profile.NameWhitelist != nil {
|
||||
if safeTemplate.Subject.CommonName != "" {
|
||||
if profile.NameWhitelist.Find([]byte(safeTemplate.Subject.CommonName)) == nil {
|
||||
return nil, cferr.New(cferr.PolicyError, cferr.InvalidPolicy)
|
||||
}
|
||||
}
|
||||
for _, name := range safeTemplate.DNSNames {
|
||||
if profile.NameWhitelist.Find([]byte(name)) == nil {
|
||||
return nil, cferr.New(cferr.PolicyError, cferr.InvalidPolicy)
|
||||
}
|
||||
}
|
||||
for _, name := range safeTemplate.EmailAddresses {
|
||||
if profile.NameWhitelist.Find([]byte(name)) == nil {
|
||||
return nil, cferr.New(cferr.PolicyError, cferr.InvalidPolicy)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if profile.ClientProvidesSerialNumbers {
|
||||
if req.Serial == nil {
|
||||
return nil, cferr.New(cferr.CertificateError, cferr.MissingSerial)
|
||||
}
|
||||
safeTemplate.SerialNumber = req.Serial
|
||||
} else {
|
||||
// RFC 5280 4.1.2.2:
|
||||
// Certificate users MUST be able to handle serialNumber
|
||||
// values up to 20 octets. Conforming CAs MUST NOT use
|
||||
// serialNumber values longer than 20 octets.
|
||||
//
|
||||
// If CFSSL is providing the serial numbers, it makes
|
||||
// sense to use the max supported size.
|
||||
serialNumber := make([]byte, 20)
|
||||
_, err = io.ReadFull(rand.Reader, serialNumber)
|
||||
if err != nil {
|
||||
return nil, cferr.Wrap(cferr.CertificateError, cferr.Unknown, err)
|
||||
}
|
||||
|
||||
// SetBytes interprets buf as the bytes of a big-endian
|
||||
// unsigned integer. The leading byte should be masked
|
||||
// off to ensure it isn't negative.
|
||||
serialNumber[0] &= 0x7F
|
||||
|
||||
safeTemplate.SerialNumber = new(big.Int).SetBytes(serialNumber)
|
||||
}
|
||||
|
||||
if len(req.Extensions) > 0 {
|
||||
for _, ext := range req.Extensions {
|
||||
oid := asn1.ObjectIdentifier(ext.ID)
|
||||
if !profile.ExtensionWhitelist[oid.String()] {
|
||||
return nil, cferr.New(cferr.CertificateError, cferr.InvalidRequest)
|
||||
}
|
||||
|
||||
rawValue, err := hex.DecodeString(ext.Value)
|
||||
if err != nil {
|
||||
return nil, cferr.Wrap(cferr.CertificateError, cferr.InvalidRequest, err)
|
||||
}
|
||||
|
||||
safeTemplate.ExtraExtensions = append(safeTemplate.ExtraExtensions, pkix.Extension{
|
||||
Id: oid,
|
||||
Critical: ext.Critical,
|
||||
Value: rawValue,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
var certTBS = safeTemplate
|
||||
|
||||
if len(profile.CTLogServers) > 0 {
|
||||
// Add a poison extension which prevents validation
|
||||
var poisonExtension = pkix.Extension{Id: signer.CTPoisonOID, Critical: true, Value: []byte{0x05, 0x00}}
|
||||
var poisonedPreCert = certTBS
|
||||
poisonedPreCert.ExtraExtensions = append(safeTemplate.ExtraExtensions, poisonExtension)
|
||||
cert, err = s.sign(&poisonedPreCert, profile)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
derCert, _ := pem.Decode(cert)
|
||||
prechain := []ct.ASN1Cert{derCert.Bytes, s.ca.Raw}
|
||||
var sctList []ct.SignedCertificateTimestamp
|
||||
|
||||
for _, server := range profile.CTLogServers {
|
||||
log.Infof("submitting poisoned precertificate to %s", server)
|
||||
var ctclient = client.New(server)
|
||||
var resp *ct.SignedCertificateTimestamp
|
||||
resp, err = ctclient.AddPreChain(prechain)
|
||||
if err != nil {
|
||||
return nil, cferr.Wrap(cferr.CTError, cferr.PrecertSubmissionFailed, err)
|
||||
}
|
||||
sctList = append(sctList, *resp)
|
||||
}
|
||||
|
||||
var serializedSCTList []byte
|
||||
serializedSCTList, err = serializeSCTList(sctList)
|
||||
if err != nil {
|
||||
return nil, cferr.Wrap(cferr.CTError, cferr.Unknown, err)
|
||||
}
|
||||
|
||||
// Serialize again as an octet string before embedding
|
||||
serializedSCTList, err = asn1.Marshal(serializedSCTList)
|
||||
if err != nil {
|
||||
return nil, cferr.Wrap(cferr.CTError, cferr.Unknown, err)
|
||||
}
|
||||
|
||||
var SCTListExtension = pkix.Extension{Id: signer.SCTListOID, Critical: false, Value: serializedSCTList}
|
||||
certTBS.ExtraExtensions = append(certTBS.ExtraExtensions, SCTListExtension)
|
||||
}
|
||||
var signedCert []byte
|
||||
signedCert, err = s.sign(&certTBS, profile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if s.dbAccessor != nil {
|
||||
var certRecord = certdb.CertificateRecord{
|
||||
Serial: certTBS.SerialNumber.String(),
|
||||
// this relies on the specific behavior of x509.CreateCertificate
|
||||
// which updates certTBS AuthorityKeyId from the signer's SubjectKeyId
|
||||
AKI: hex.EncodeToString(certTBS.AuthorityKeyId),
|
||||
CALabel: req.Label,
|
||||
Status: "good",
|
||||
Expiry: certTBS.NotAfter,
|
||||
PEM: string(signedCert),
|
||||
}
|
||||
|
||||
err = s.dbAccessor.InsertCertificate(certRecord)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Debug("saved certificate with serial number ", certTBS.SerialNumber)
|
||||
}
|
||||
|
||||
return signedCert, nil
|
||||
}
|
||||
|
||||
func serializeSCTList(sctList []ct.SignedCertificateTimestamp) ([]byte, error) {
|
||||
var buf bytes.Buffer
|
||||
for _, sct := range sctList {
|
||||
sct, err := ct.SerializeSCT(sct)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
binary.Write(&buf, binary.BigEndian, uint16(len(sct)))
|
||||
buf.Write(sct)
|
||||
}
|
||||
|
||||
var sctListLengthField = make([]byte, 2)
|
||||
binary.BigEndian.PutUint16(sctListLengthField, uint16(buf.Len()))
|
||||
return bytes.Join([][]byte{sctListLengthField, buf.Bytes()}, nil), nil
|
||||
}
|
||||
|
||||
// Info return a populated info.Resp struct or an error.
|
||||
func (s *Signer) Info(req info.Req) (resp *info.Resp, err error) {
|
||||
cert, err := s.Certificate(req.Label, req.Profile)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
profile, err := signer.Profile(s, req.Profile)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
resp = new(info.Resp)
|
||||
if cert.Raw != nil {
|
||||
resp.Certificate = string(bytes.TrimSpace(pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw})))
|
||||
}
|
||||
resp.Usage = profile.Usage
|
||||
resp.ExpiryString = profile.ExpiryString
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// SigAlgo returns the RSA signer's signature algorithm.
|
||||
func (s *Signer) SigAlgo() x509.SignatureAlgorithm {
|
||||
return s.sigAlgo
|
||||
}
|
||||
|
||||
// Certificate returns the signer's certificate.
|
||||
func (s *Signer) Certificate(label, profile string) (*x509.Certificate, error) {
|
||||
cert := *s.ca
|
||||
return &cert, nil
|
||||
}
|
||||
|
||||
// SetPolicy sets the signer's signature policy.
|
||||
func (s *Signer) SetPolicy(policy *config.Signing) {
|
||||
s.policy = policy
|
||||
}
|
||||
|
||||
// SetDBAccessor sets the signers' cert db accessor
|
||||
func (s *Signer) SetDBAccessor(dba certdb.Accessor) {
|
||||
s.dbAccessor = dba
|
||||
}
|
||||
|
||||
// Policy returns the signer's policy.
|
||||
func (s *Signer) Policy() *config.Signing {
|
||||
return s.policy
|
||||
}
|
385
vendor/github.com/cloudflare/cfssl/signer/signer.go
generated
vendored
Normal file
385
vendor/github.com/cloudflare/cfssl/signer/signer.go
generated
vendored
Normal file
@@ -0,0 +1,385 @@
|
||||
// Package signer implements certificate signature functionality for CFSSL.
|
||||
package signer
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rsa"
|
||||
"crypto/sha1"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/asn1"
|
||||
"errors"
|
||||
"math/big"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/cloudflare/cfssl/certdb"
|
||||
"github.com/cloudflare/cfssl/config"
|
||||
"github.com/cloudflare/cfssl/csr"
|
||||
cferr "github.com/cloudflare/cfssl/errors"
|
||||
"github.com/cloudflare/cfssl/helpers"
|
||||
"github.com/cloudflare/cfssl/info"
|
||||
)
|
||||
|
||||
// MaxPathLen is the default path length for a new CA certificate.
|
||||
var MaxPathLen = 2
|
||||
|
||||
// Subject contains the information that should be used to override the
|
||||
// subject information when signing a certificate.
|
||||
type Subject struct {
|
||||
CN string
|
||||
Names []csr.Name `json:"names"`
|
||||
SerialNumber string
|
||||
}
|
||||
|
||||
// Extension represents a raw extension to be included in the certificate. The
|
||||
// "value" field must be hex encoded.
|
||||
type Extension struct {
|
||||
ID config.OID `json:"id"`
|
||||
Critical bool `json:"critical"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
// SignRequest stores a signature request, which contains the hostname,
|
||||
// the CSR, optional subject information, and the signature profile.
|
||||
//
|
||||
// Extensions provided in the signRequest are copied into the certificate, as
|
||||
// long as they are in the ExtensionWhitelist for the signer's policy.
|
||||
// Extensions requested in the CSR are ignored, except for those processed by
|
||||
// ParseCertificateRequest (mainly subjectAltName).
|
||||
type SignRequest struct {
|
||||
Hosts []string `json:"hosts"`
|
||||
Request string `json:"certificate_request"`
|
||||
Subject *Subject `json:"subject,omitempty"`
|
||||
Profile string `json:"profile"`
|
||||
Label string `json:"label"`
|
||||
Serial *big.Int `json:"serial,omitempty"`
|
||||
Extensions []Extension `json:"extensions,omitempty"`
|
||||
}
|
||||
|
||||
// appendIf appends to a if s is not an empty string.
|
||||
func appendIf(s string, a *[]string) {
|
||||
if s != "" {
|
||||
*a = append(*a, s)
|
||||
}
|
||||
}
|
||||
|
||||
// Name returns the PKIX name for the subject.
|
||||
func (s *Subject) Name() pkix.Name {
|
||||
var name pkix.Name
|
||||
name.CommonName = s.CN
|
||||
|
||||
for _, n := range s.Names {
|
||||
appendIf(n.C, &name.Country)
|
||||
appendIf(n.ST, &name.Province)
|
||||
appendIf(n.L, &name.Locality)
|
||||
appendIf(n.O, &name.Organization)
|
||||
appendIf(n.OU, &name.OrganizationalUnit)
|
||||
}
|
||||
name.SerialNumber = s.SerialNumber
|
||||
return name
|
||||
}
|
||||
|
||||
// SplitHosts takes a comma-spearated list of hosts and returns a slice
|
||||
// with the hosts split
|
||||
func SplitHosts(hostList string) []string {
|
||||
if hostList == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
return strings.Split(hostList, ",")
|
||||
}
|
||||
|
||||
// A Signer contains a CA's certificate and private key for signing
|
||||
// certificates, a Signing policy to refer to and a SignatureAlgorithm.
|
||||
type Signer interface {
|
||||
Info(info.Req) (*info.Resp, error)
|
||||
Policy() *config.Signing
|
||||
SetDBAccessor(certdb.Accessor)
|
||||
SetPolicy(*config.Signing)
|
||||
SigAlgo() x509.SignatureAlgorithm
|
||||
Sign(req SignRequest) (cert []byte, err error)
|
||||
}
|
||||
|
||||
// Profile gets the specific profile from the signer
|
||||
func Profile(s Signer, profile string) (*config.SigningProfile, error) {
|
||||
var p *config.SigningProfile
|
||||
policy := s.Policy()
|
||||
if policy != nil && policy.Profiles != nil && profile != "" {
|
||||
p = policy.Profiles[profile]
|
||||
}
|
||||
|
||||
if p == nil && policy != nil {
|
||||
p = policy.Default
|
||||
}
|
||||
|
||||
if p == nil {
|
||||
return nil, cferr.Wrap(cferr.APIClientError, cferr.ClientHTTPError, errors.New("profile must not be nil"))
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// DefaultSigAlgo returns an appropriate X.509 signature algorithm given
|
||||
// the CA's private key.
|
||||
func DefaultSigAlgo(priv crypto.Signer) x509.SignatureAlgorithm {
|
||||
pub := priv.Public()
|
||||
switch pub := pub.(type) {
|
||||
case *rsa.PublicKey:
|
||||
keySize := pub.N.BitLen()
|
||||
switch {
|
||||
case keySize >= 4096:
|
||||
return x509.SHA512WithRSA
|
||||
case keySize >= 3072:
|
||||
return x509.SHA384WithRSA
|
||||
case keySize >= 2048:
|
||||
return x509.SHA256WithRSA
|
||||
default:
|
||||
return x509.SHA1WithRSA
|
||||
}
|
||||
case *ecdsa.PublicKey:
|
||||
switch pub.Curve {
|
||||
case elliptic.P256():
|
||||
return x509.ECDSAWithSHA256
|
||||
case elliptic.P384():
|
||||
return x509.ECDSAWithSHA384
|
||||
case elliptic.P521():
|
||||
return x509.ECDSAWithSHA512
|
||||
default:
|
||||
return x509.ECDSAWithSHA1
|
||||
}
|
||||
default:
|
||||
return x509.UnknownSignatureAlgorithm
|
||||
}
|
||||
}
|
||||
|
||||
// ParseCertificateRequest takes an incoming certificate request and
|
||||
// builds a certificate template from it.
|
||||
func ParseCertificateRequest(s Signer, csrBytes []byte) (template *x509.Certificate, err error) {
|
||||
csr, err := x509.ParseCertificateRequest(csrBytes)
|
||||
if err != nil {
|
||||
err = cferr.Wrap(cferr.CSRError, cferr.ParseFailed, err)
|
||||
return
|
||||
}
|
||||
|
||||
err = helpers.CheckSignature(csr, csr.SignatureAlgorithm, csr.RawTBSCertificateRequest, csr.Signature)
|
||||
if err != nil {
|
||||
err = cferr.Wrap(cferr.CSRError, cferr.KeyMismatch, err)
|
||||
return
|
||||
}
|
||||
|
||||
template = &x509.Certificate{
|
||||
Subject: csr.Subject,
|
||||
PublicKeyAlgorithm: csr.PublicKeyAlgorithm,
|
||||
PublicKey: csr.PublicKey,
|
||||
SignatureAlgorithm: s.SigAlgo(),
|
||||
DNSNames: csr.DNSNames,
|
||||
IPAddresses: csr.IPAddresses,
|
||||
EmailAddresses: csr.EmailAddresses,
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
type subjectPublicKeyInfo struct {
|
||||
Algorithm pkix.AlgorithmIdentifier
|
||||
SubjectPublicKey asn1.BitString
|
||||
}
|
||||
|
||||
// ComputeSKI derives an SKI from the certificate's public key in a
|
||||
// standard manner. This is done by computing the SHA-1 digest of the
|
||||
// SubjectPublicKeyInfo component of the certificate.
|
||||
func ComputeSKI(template *x509.Certificate) ([]byte, error) {
|
||||
pub := template.PublicKey
|
||||
encodedPub, err := x509.MarshalPKIXPublicKey(pub)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var subPKI subjectPublicKeyInfo
|
||||
_, err = asn1.Unmarshal(encodedPub, &subPKI)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pubHash := sha1.Sum(subPKI.SubjectPublicKey.Bytes)
|
||||
return pubHash[:], nil
|
||||
}
|
||||
|
||||
// FillTemplate is a utility function that tries to load as much of
|
||||
// the certificate template as possible from the profiles and current
|
||||
// template. It fills in the key uses, expiration, revocation URLs
|
||||
// and SKI.
|
||||
func FillTemplate(template *x509.Certificate, defaultProfile, profile *config.SigningProfile) error {
|
||||
ski, err := ComputeSKI(template)
|
||||
|
||||
var (
|
||||
eku []x509.ExtKeyUsage
|
||||
ku x509.KeyUsage
|
||||
backdate time.Duration
|
||||
expiry time.Duration
|
||||
notBefore time.Time
|
||||
notAfter time.Time
|
||||
crlURL, ocspURL string
|
||||
)
|
||||
|
||||
// The third value returned from Usages is a list of unknown key usages.
|
||||
// This should be used when validating the profile at load, and isn't used
|
||||
// here.
|
||||
ku, eku, _ = profile.Usages()
|
||||
if profile.IssuerURL == nil {
|
||||
profile.IssuerURL = defaultProfile.IssuerURL
|
||||
}
|
||||
|
||||
if ku == 0 && len(eku) == 0 {
|
||||
return cferr.New(cferr.PolicyError, cferr.NoKeyUsages)
|
||||
}
|
||||
|
||||
if expiry = profile.Expiry; expiry == 0 {
|
||||
expiry = defaultProfile.Expiry
|
||||
}
|
||||
|
||||
if crlURL = profile.CRL; crlURL == "" {
|
||||
crlURL = defaultProfile.CRL
|
||||
}
|
||||
if ocspURL = profile.OCSP; ocspURL == "" {
|
||||
ocspURL = defaultProfile.OCSP
|
||||
}
|
||||
if backdate = profile.Backdate; backdate == 0 {
|
||||
backdate = -5 * time.Minute
|
||||
} else {
|
||||
backdate = -1 * profile.Backdate
|
||||
}
|
||||
|
||||
if !profile.NotBefore.IsZero() {
|
||||
notBefore = profile.NotBefore.UTC()
|
||||
} else {
|
||||
notBefore = time.Now().Round(time.Minute).Add(backdate).UTC()
|
||||
}
|
||||
|
||||
if !profile.NotAfter.IsZero() {
|
||||
notAfter = profile.NotAfter.UTC()
|
||||
} else {
|
||||
notAfter = notBefore.Add(expiry).UTC()
|
||||
}
|
||||
|
||||
template.NotBefore = notBefore
|
||||
template.NotAfter = notAfter
|
||||
template.KeyUsage = ku
|
||||
template.ExtKeyUsage = eku
|
||||
template.BasicConstraintsValid = true
|
||||
template.IsCA = profile.CA
|
||||
template.SubjectKeyId = ski
|
||||
|
||||
if ocspURL != "" {
|
||||
template.OCSPServer = []string{ocspURL}
|
||||
}
|
||||
if crlURL != "" {
|
||||
template.CRLDistributionPoints = []string{crlURL}
|
||||
}
|
||||
|
||||
if len(profile.IssuerURL) != 0 {
|
||||
template.IssuingCertificateURL = profile.IssuerURL
|
||||
}
|
||||
if len(profile.Policies) != 0 {
|
||||
err = addPolicies(template, profile.Policies)
|
||||
if err != nil {
|
||||
return cferr.Wrap(cferr.PolicyError, cferr.InvalidPolicy, err)
|
||||
}
|
||||
}
|
||||
if profile.OCSPNoCheck {
|
||||
ocspNoCheckExtension := pkix.Extension{
|
||||
Id: asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 48, 1, 5},
|
||||
Critical: false,
|
||||
Value: []byte{0x05, 0x00},
|
||||
}
|
||||
template.ExtraExtensions = append(template.ExtraExtensions, ocspNoCheckExtension)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type policyInformation struct {
|
||||
PolicyIdentifier asn1.ObjectIdentifier
|
||||
Qualifiers []interface{} `asn1:"tag:optional,omitempty"`
|
||||
}
|
||||
|
||||
type cpsPolicyQualifier struct {
|
||||
PolicyQualifierID asn1.ObjectIdentifier
|
||||
Qualifier string `asn1:"tag:optional,ia5"`
|
||||
}
|
||||
|
||||
type userNotice struct {
|
||||
ExplicitText string `asn1:"tag:optional,utf8"`
|
||||
}
|
||||
type userNoticePolicyQualifier struct {
|
||||
PolicyQualifierID asn1.ObjectIdentifier
|
||||
Qualifier userNotice
|
||||
}
|
||||
|
||||
var (
|
||||
// Per https://tools.ietf.org/html/rfc3280.html#page-106, this represents:
|
||||
// iso(1) identified-organization(3) dod(6) internet(1) security(5)
|
||||
// mechanisms(5) pkix(7) id-qt(2) id-qt-cps(1)
|
||||
iDQTCertificationPracticeStatement = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 2, 1}
|
||||
// iso(1) identified-organization(3) dod(6) internet(1) security(5)
|
||||
// mechanisms(5) pkix(7) id-qt(2) id-qt-unotice(2)
|
||||
iDQTUserNotice = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 2, 2}
|
||||
|
||||
// CTPoisonOID is the object ID of the critical poison extension for precertificates
|
||||
// https://tools.ietf.org/html/rfc6962#page-9
|
||||
CTPoisonOID = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 11129, 2, 4, 3}
|
||||
|
||||
// SCTListOID is the object ID for the Signed Certificate Timestamp certificate extension
|
||||
// https://tools.ietf.org/html/rfc6962#page-14
|
||||
SCTListOID = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 11129, 2, 4, 2}
|
||||
)
|
||||
|
||||
// addPolicies adds Certificate Policies and optional Policy Qualifiers to a
|
||||
// certificate, based on the input config. Go's x509 library allows setting
|
||||
// Certificate Policies easily, but does not support nested Policy Qualifiers
|
||||
// under those policies. So we need to construct the ASN.1 structure ourselves.
|
||||
func addPolicies(template *x509.Certificate, policies []config.CertificatePolicy) error {
|
||||
asn1PolicyList := []policyInformation{}
|
||||
|
||||
for _, policy := range policies {
|
||||
pi := policyInformation{
|
||||
// The PolicyIdentifier is an OID assigned to a given issuer.
|
||||
PolicyIdentifier: asn1.ObjectIdentifier(policy.ID),
|
||||
}
|
||||
for _, qualifier := range policy.Qualifiers {
|
||||
switch qualifier.Type {
|
||||
case "id-qt-unotice":
|
||||
pi.Qualifiers = append(pi.Qualifiers,
|
||||
userNoticePolicyQualifier{
|
||||
PolicyQualifierID: iDQTUserNotice,
|
||||
Qualifier: userNotice{
|
||||
ExplicitText: qualifier.Value,
|
||||
},
|
||||
})
|
||||
case "id-qt-cps":
|
||||
pi.Qualifiers = append(pi.Qualifiers,
|
||||
cpsPolicyQualifier{
|
||||
PolicyQualifierID: iDQTCertificationPracticeStatement,
|
||||
Qualifier: qualifier.Value,
|
||||
})
|
||||
default:
|
||||
return errors.New("Invalid qualifier type in Policies " + qualifier.Type)
|
||||
}
|
||||
}
|
||||
asn1PolicyList = append(asn1PolicyList, pi)
|
||||
}
|
||||
|
||||
asn1Bytes, err := asn1.Marshal(asn1PolicyList)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
template.ExtraExtensions = append(template.ExtraExtensions, pkix.Extension{
|
||||
Id: asn1.ObjectIdentifier{2, 5, 29, 32},
|
||||
Critical: false,
|
||||
Value: asn1Bytes,
|
||||
})
|
||||
return nil
|
||||
}
|
Reference in New Issue
Block a user