123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478 |
- // Package signer implements certificate signature functionality for CFSSL.
- package signer
- import (
- "crypto"
- "crypto/ecdsa"
- "crypto/ed25519"
- "crypto/elliptic"
- "crypto/rsa"
- "crypto/sha1"
- "crypto/x509"
- "crypto/x509/pkix"
- "encoding/asn1"
- "errors"
- "math/big"
- "net/http"
- "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"
- )
- // 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) and DelegationUsage.
- type SignRequest struct {
- Hosts []string `json:"hosts"`
- Request string `json:"certificate_request"`
- Subject *Subject `json:"subject,omitempty"`
- Profile string `json:"profile"`
- CRLOverride string `json:"crl_override"`
- Label string `json:"label"`
- Serial *big.Int `json:"serial,omitempty"`
- Extensions []Extension `json:"extensions,omitempty"`
- // If provided, NotBefore will be used without modification (except
- // for canonicalization) as the value of the notBefore field of the
- // certificate. In particular no backdating adjustment will be made
- // when NotBefore is provided.
- NotBefore time.Time
- // If provided, NotAfter will be used without modification (except
- // for canonicalization) as the value of the notAfter field of the
- // certificate.
- NotAfter time.Time
- // If ReturnPrecert is true a certificate with the CT poison extension
- // will be returned from the Signer instead of attempting to retrieve
- // SCTs and populate the tbsCert with them itself. This precert can then
- // be passed to SignFromPrecert with the SCTs in order to create a
- // valid certificate.
- ReturnPrecert bool
- // Arbitrary metadata to be stored in certdb.
- Metadata map[string]interface{} `json:"metadata"`
- }
- // 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)
- GetDBAccessor() certdb.Accessor
- SetPolicy(*config.Signing)
- SigAlgo() x509.SignatureAlgorithm
- Sign(req SignRequest) (cert []byte, err error)
- SetReqModifier(func(*http.Request, []byte))
- }
- // 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
- }
- case ed25519.PublicKey:
- return x509.PureEd25519
- default:
- return x509.UnknownSignatureAlgorithm
- }
- }
- func isCommonAttr(t []int) bool {
- return (len(t) == 4 && t[0] == 2 && t[1] == 5 && t[2] == 4 && (t[3] == 3 || (t[3] >= 5 && t[3] <= 11) || t[3] == 17))
- }
- // ParseCertificateRequest takes an incoming certificate request and
- // builds a certificate template from it.
- func ParseCertificateRequest(s Signer, p *config.SigningProfile, csrBytes []byte) (template *x509.Certificate, err error) {
- csrv, err := x509.ParseCertificateRequest(csrBytes)
- if err != nil {
- err = cferr.Wrap(cferr.CSRError, cferr.ParseFailed, err)
- return
- }
- var r pkix.RDNSequence
- _, err = asn1.Unmarshal(csrv.RawSubject, &r)
- if err != nil {
- err = cferr.Wrap(cferr.CSRError, cferr.ParseFailed, err)
- return
- }
- var subject pkix.Name
- subject.FillFromRDNSequence(&r)
- for _, v := range r {
- for _, vv := range v {
- if !isCommonAttr(vv.Type) {
- subject.ExtraNames = append(subject.ExtraNames, vv)
- }
- }
- }
- err = csrv.CheckSignature()
- if err != nil {
- err = cferr.Wrap(cferr.CSRError, cferr.KeyMismatch, err)
- return
- }
- template = &x509.Certificate{
- Subject: subject,
- PublicKeyAlgorithm: csrv.PublicKeyAlgorithm,
- PublicKey: csrv.PublicKey,
- SignatureAlgorithm: s.SigAlgo(),
- DNSNames: csrv.DNSNames,
- IPAddresses: csrv.IPAddresses,
- EmailAddresses: csrv.EmailAddresses,
- URIs: csrv.URIs,
- Extensions: csrv.Extensions,
- ExtraExtensions: []pkix.Extension{},
- }
- for _, val := range csrv.Extensions {
- // Check the CSR for the X.509 BasicConstraints (RFC 5280, 4.2.1.9)
- // extension and append to template if necessary
- if val.Id.Equal(asn1.ObjectIdentifier{2, 5, 29, 19}) {
- var constraints csr.BasicConstraints
- var rest []byte
- if rest, err = asn1.Unmarshal(val.Value, &constraints); err != nil {
- return nil, cferr.Wrap(cferr.CSRError, cferr.ParseFailed, err)
- } else if len(rest) != 0 {
- return nil, cferr.Wrap(cferr.CSRError, cferr.ParseFailed, errors.New("x509: trailing data after X.509 BasicConstraints"))
- }
- template.BasicConstraintsValid = true
- template.IsCA = constraints.IsCA
- template.MaxPathLen = constraints.MaxPathLen
- template.MaxPathLenZero = template.MaxPathLen == 0
- } else if val.Id.Equal(helpers.DelegationUsage) {
- template.ExtraExtensions = append(template.ExtraExtensions, val)
- } else {
- // If the profile has 'copy_extensions' to true then lets add it
- if p.CopyExtensions {
- template.ExtraExtensions = append(template.ExtraExtensions, val)
- }
- }
- }
- 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, notBefore time.Time, notAfter time.Time) error {
- ski, err := ComputeSKI(template)
- if err != nil {
- return err
- }
- var (
- eku []x509.ExtKeyUsage
- ku x509.KeyUsage
- backdate time.Duration
- expiry time.Duration
- crlURL, ocspURL string
- issuerURL = profile.IssuerURL
- )
- // 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 {
- 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 notBefore.IsZero() {
- if !profile.NotBefore.IsZero() {
- notBefore = profile.NotBefore
- } else {
- if backdate = profile.Backdate; backdate == 0 {
- backdate = -5 * time.Minute
- } else {
- backdate = -1 * profile.Backdate
- }
- notBefore = time.Now().Round(time.Minute).Add(backdate)
- }
- }
- notBefore = notBefore.UTC()
- if notAfter.IsZero() {
- if !profile.NotAfter.IsZero() {
- notAfter = profile.NotAfter
- } else {
- notAfter = notBefore.Add(expiry)
- }
- }
- notAfter = notAfter.UTC()
- template.NotBefore = notBefore
- template.NotAfter = notAfter
- template.KeyUsage = ku
- template.ExtKeyUsage = eku
- template.BasicConstraintsValid = true
- template.IsCA = profile.CAConstraint.IsCA
- if template.IsCA {
- template.MaxPathLen = profile.CAConstraint.MaxPathLen
- if template.MaxPathLen == 0 {
- template.MaxPathLenZero = profile.CAConstraint.MaxPathLenZero
- }
- template.DNSNames = nil
- template.EmailAddresses = nil
- template.URIs = nil
- }
- template.SubjectKeyId = ski
- if ocspURL != "" {
- template.OCSPServer = []string{ocspURL}
- }
- if crlURL != "" {
- template.CRLDistributionPoints = []string{crlURL}
- }
- if len(issuerURL) != 0 {
- template.IssuingCertificateURL = 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
- }
|