func (ca *CA) newCertificate(id string, pub *ecdsa.PublicKey, timestamp int64, opt ...pkix.Extension) ([]byte, error) { Trace.Println("creating certificate for " + id) notBefore := time.Now() notAfter := notBefore.Add(time.Hour * 24 * 90) isCA := ca.cert == nil tmpl := x509.Certificate{ SerialNumber: big.NewInt(1), Subject: pkix.Name{ CommonName: "OBC", Organization: []string{"IBM"}, Country: []string{"US"}, }, NotBefore: notBefore, NotAfter: notAfter, SubjectKeyId: []byte{1, 2, 3, 4}, SignatureAlgorithm: x509.ECDSAWithSHA384, KeyUsage: x509.KeyUsageDigitalSignature, BasicConstraintsValid: true, IsCA: isCA, } if len(opt) > 0 { tmpl.Extensions = opt tmpl.ExtraExtensions = opt } parent := ca.cert if isCA { parent = &tmpl } raw, err := x509.CreateCertificate( rand.Reader, &tmpl, parent, pub, ca.priv, ) if isCA && err != nil { Panic.Panicln(err) } hash := sha3.New384() hash.Write(raw) if _, err = ca.db.Exec("INSERT INTO Certificates (id, timestamp, cert, hash) VALUES (?, ?, ?, ?)", id, timestamp, raw, hash.Sum(nil)); err != nil { if isCA { Panic.Panicln(err) } else { Error.Println(err) } } return raw, err }
// 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), } switch policy.Type { case "id-qt-unotice": pi.UserNoticePolicyQualifiers = []userNoticePolicyQualifier{ userNoticePolicyQualifier{ PolicyQualifierID: iDQTUserNotice, Qualifier: userNotice{ ExplicitText: policy.Qualifier, }, }, } case "id-qt-cps": pi.PolicyQualifiers = []policyQualifier{ policyQualifier{ PolicyQualifierID: iDQTUserNotice, Qualifier: policy.Qualifier, }, } pi.PolicyQualifiers = []policyQualifier{ policyQualifier{ PolicyQualifierID: iDQTCertificationPracticeStatement, Qualifier: policy.Qualifier, }, } case "": // Empty qualifier type is fine: Include this Certificate Policy, but // don't include a Policy Qualifier. default: return errors.New("Invalid qualifier type in Policies " + policy.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 }
// NewRSAcert creates a new RSA x509 self-signed certificate func NewRSAcert(uri string, name string, priv *rsa.PrivateKey) (*tls.Certificate, error) { uri = "URI: " + uri template := x509.Certificate{ SerialNumber: new(big.Int).SetInt64(42), Subject: pkix.Name{ CommonName: name, Organization: []string{"WebID"}, // Country: []string{"US"}, }, NotBefore: notBefore, NotAfter: notAfter, BasicConstraintsValid: true, KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, } rawValues := []asn1.RawValue{ {Class: 2, Tag: 6, Bytes: []byte(uri)}, } values, err := asn1.Marshal(rawValues) if err != nil { return nil, err } template.ExtraExtensions = []pkix.Extension{{Id: subjectAltName, Value: values}} keyPEM := bytes.NewBuffer(nil) err = pem.Encode(keyPEM, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)}) if err != nil { return nil, err } derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv) if err != nil { return nil, err } certPEM := bytes.NewBuffer(nil) err = pem.Encode(certPEM, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}) if err != nil { return nil, err } cert, err := tls.X509KeyPair(certPEM.Bytes(), keyPEM.Bytes()) if err != nil { return nil, err } return &cert, nil }
func (ca *CA) newCertificateFromSpec(spec *CertificateSpec) ([]byte, error) { notBefore := spec.GetNotBefore() notAfter := spec.GetNotAfter() parent := ca.cert isCA := parent == nil tmpl := x509.Certificate{ SerialNumber: spec.GetSerialNumber(), Subject: pkix.Name{ CommonName: spec.GetCommonName(), Organization: []string{spec.GetOrganization()}, Country: []string{spec.GetCountry()}, }, NotBefore: *notBefore, NotAfter: *notAfter, SubjectKeyId: *spec.GetSubjectKeyID(), SignatureAlgorithm: spec.GetSignatureAlgorithm(), KeyUsage: spec.GetUsage(), BasicConstraintsValid: true, IsCA: isCA, } if len(*spec.GetExtensions()) > 0 { tmpl.Extensions = *spec.GetExtensions() tmpl.ExtraExtensions = *spec.GetExtensions() } if isCA { parent = &tmpl } raw, err := x509.CreateCertificate( rand.Reader, &tmpl, parent, spec.GetPublicKey(), ca.priv, ) if isCA && err != nil { caLogger.Panic(err) } return raw, err }
func (ca *CA) newCertificate(id string, pub interface{}, usage x509.KeyUsage, ext []pkix.Extension) ([]byte, error) { notBefore := time.Now() notAfter := notBefore.Add(time.Hour * 24 * 90) parent := ca.cert isCA := parent == nil tmpl := x509.Certificate{ SerialNumber: big.NewInt(1), Subject: pkix.Name{ CommonName: id, Organization: []string{"IBM"}, Country: []string{"US"}, }, NotBefore: notBefore, NotAfter: notAfter, SubjectKeyId: []byte{1, 2, 3, 4}, SignatureAlgorithm: x509.ECDSAWithSHA384, KeyUsage: usage, BasicConstraintsValid: true, IsCA: isCA, } if len(ext) > 0 { tmpl.Extensions = ext tmpl.ExtraExtensions = ext } if isCA { parent = &tmpl } raw, err := x509.CreateCertificate( rand.Reader, &tmpl, parent, pub, ca.priv, ) if isCA && err != nil { Panic.Panicln(err) } return raw, err }
// NewSPKACx509 creates a new x509 self-signed cert based on the SPKAC value func NewSPKACx509(uri string, name string, spkacBase64 string) ([]byte, error) { public, err := ParseSPKAC(spkacBase64) if err != nil { return nil, err } pubKey := public.(*rsa.PublicKey) rsaPub, err := x509.MarshalPKIXPublicKey(pubKey) if err != nil { return nil, err } h := sha1.New() pubSha1 := h.Sum(rsaPub)[:20] priv, err := rsa.GenerateKey(rand.Reader, 1024) if err != nil { return nil, err } template := x509.Certificate{ SerialNumber: new(big.Int).SetInt64(42), Subject: pkix.Name{ CommonName: name, Organization: []string{"WebID"}, // Country: []string{"US"}, }, NotBefore: notBefore, NotAfter: notAfter, SubjectKeyId: pubSha1, BasicConstraintsValid: true, } // add WebID in the subjectAltName field var rawValues []asn1.RawValue rawValues = append(rawValues, asn1.RawValue{Class: 2, Tag: 6, Bytes: []byte(uri)}) values, err := asn1.Marshal(rawValues) if err != nil { return nil, err } template.ExtraExtensions = []pkix.Extension{{Id: subjectAltName, Value: values}} template.Extensions = template.ExtraExtensions certDerBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, public, priv) return certDerBytes, nil }
// Vouch a self-signed certificate that is about to be created with an Ed25519 signature. func Vouch(signPub *[ed25519.PublicKeySize]byte, signPriv *[ed25519.PrivateKeySize]byte, cert *x509.Certificate, tlsPub interface{}) error { // note: this is so early the cert is not serialized yet, can't use those fields tlsPubDer, err := x509.MarshalPKIXPublicKey(tlsPub) if err != nil { return err } msg := make([]byte, 0, len(prefix)+8+len(tlsPubDer)) msg = append(msg, prefix...) var now [8]byte binary.LittleEndian.PutUint64(now[:], uint64(cert.NotAfter.Unix())) msg = append(msg, now[:]...) msg = append(msg, tlsPubDer...) env := make([]byte, 0, ed25519.PublicKeySize+ed25519.SignatureSize) env = append(env, signPub[:]...) sig := ed25519.Sign(signPriv, msg) env = append(env, sig[:]...) ext := pkix.Extension{Id: oid, Value: env} cert.ExtraExtensions = append(cert.ExtraExtensions, ext) return nil }
// 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 } } 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) } } } if profile.ClientProvidesSerialNumbers { if req.Serial == nil { fmt.Printf("xx %#v\n", profile) return nil, cferr.New(cferr.CertificateError, cferr.MissingSerial) } safeTemplate.SerialNumber = req.Serial } else { serialNumber, err := rand.Int(rand.Reader, new(big.Int).SetInt64(math.MaxInt64)) if err != nil { return nil, cferr.Wrap(cferr.CertificateError, cferr.Unknown, err) } safeTemplate.SerialNumber = 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) } return s.sign(&certTBS, profile) }
// 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 }
// 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 } } 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) } } } if profile.ClientProvidesSerialNumbers { if req.Serial == nil { fmt.Printf("xx %#v\n", profile) 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.db != nil { var certRecord = &certdb.CertificateRecord{ Serial: certTBS.SerialNumber.String(), CALabel: req.Label, Status: "good", Expiry: certTBS.NotAfter, PEM: string(signedCert), } err = certdb.InsertCertificate(s.db, certRecord) if err != nil { return nil, err } log.Debug("saved certificate with serial number ", certTBS.SerialNumber) } return signedCert, nil }