// 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 }