// addKeyUsages adds approrpiate key usages to the template given the creation // information func addKeyUsages(creationInfo *creationBundle, certTemplate *x509.Certificate) { certTemplate.KeyUsage = x509.KeyUsage(x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment | x509.KeyUsageKeyAgreement) if creationInfo.Usage&serverUsage != 0 { certTemplate.ExtKeyUsage = append(certTemplate.ExtKeyUsage, x509.ExtKeyUsageServerAuth) } if creationInfo.Usage&clientUsage != 0 { certTemplate.ExtKeyUsage = append(certTemplate.ExtKeyUsage, x509.ExtKeyUsageClientAuth) } if creationInfo.Usage&codeSigningUsage != 0 { certTemplate.ExtKeyUsage = append(certTemplate.ExtKeyUsage, x509.ExtKeyUsageCodeSigning) } if creationInfo.Usage&emailProtectionUsage != 0 { certTemplate.ExtKeyUsage = append(certTemplate.ExtKeyUsage, x509.ExtKeyUsageEmailProtection) } if creationInfo.IsCA { // Go performs validation not according to spec but according to the Windows // Crypto API, so we add all usages to CA certs certTemplate.KeyUsage = x509.KeyUsage(certTemplate.KeyUsage | x509.KeyUsageCertSign | x509.KeyUsageCRLSign) certTemplate.ExtKeyUsage = []x509.ExtKeyUsage{ x509.ExtKeyUsageAny, x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageCodeSigning, x509.ExtKeyUsageEmailProtection, x509.ExtKeyUsageTimeStamping, x509.ExtKeyUsageOCSPSigning, } } }
// keyUsage decodes/prints key usage from a certificate. func keyUsage(sKu simpleKeyUsage) []string { ku := x509.KeyUsage(sKu) out := []string{} for _, key := range keyUsages { if ku&key > 0 { out = append(out, keyUsageStrings[key]) } } return out }
// addKeyUsages adds approrpiate key usages to the template given the creation // information func addKeyUsages(creationInfo *creationBundle, certTemplate *x509.Certificate) { if creationInfo.IsCA { certTemplate.KeyUsage = x509.KeyUsage(x509.KeyUsageCertSign | x509.KeyUsageCRLSign) return } if creationInfo.Usage&serverUsage != 0 { certTemplate.ExtKeyUsage = append(certTemplate.ExtKeyUsage, x509.ExtKeyUsageServerAuth) } if creationInfo.Usage&clientUsage != 0 { certTemplate.ExtKeyUsage = append(certTemplate.ExtKeyUsage, x509.ExtKeyUsageClientAuth) } if creationInfo.Usage&codeSigningUsage != 0 { certTemplate.ExtKeyUsage = append(certTemplate.ExtKeyUsage, x509.ExtKeyUsageCodeSigning) } if creationInfo.Usage&emailProtectionUsage != 0 { certTemplate.ExtKeyUsage = append(certTemplate.ExtKeyUsage, x509.ExtKeyUsageEmailProtection) } }
func TestUpgradeCACertificate(t *testing.T) { // generated at // https://eu-west-1.console.aws.amazon.com/apigateway/home?region=eu-west-1#/client-certificates const awsApiGWCert = ` -----BEGIN CERTIFICATE----- MIIC6DCCAdCgAwIBAgIIZAgycYqDRqQwDQYJKoZIhvcNAQELBQAwNDELMAkGA1UE BhMCVVMxEDAOBgNVBAcTB1NlYXR0bGUxEzARBgNVBAMTCkFwaUdhdGV3YXkwHhcN MTYwNzEyMTkzMTMwWhcNMTcwNzEyMTkzMTMwWjA0MQswCQYDVQQGEwJVUzEQMA4G A1UEBxMHU2VhdHRsZTETMBEGA1UEAxMKQXBpR2F0ZXdheTCCASIwDQYJKoZIhvcN AQEBBQADggEPADCCAQoCggEBAM/a0LKQd/obIwcKu09EjlHP4b7QqmK/JnJfd1Eq m6We85FGu+26s7+Bpw1xiyK2jFuzQ4JFyXVkWLJH8e3Mp7P91MvJ1x6UCRk+Fz6Q Lauw5SBVmDO5CauB4NcICTYEeTT3c0m8t6sDpHf+DHZC87gq9rhBggKXfNO3ntWw Kq2uGscvnOz2/n2XIucFf2U7GI/cOapGXvIyrB5e/swSCyNkgOJ2HekzWjprxSs5 zu9JSOIzejgm8+/nnPOO9ycVrjN3qazUEXfF1QdvZeNCZ9GL6ZICAYo9xnnNLJnW 6p5d0Fw6U+V/nlNpgCB5djTwXaY51ScoW/i3ukHBZe9QIEcCAwEAATANBgkqhkiG 9w0BAQsFAAOCAQEAzwUJlSv/9XVoeCbot+3mdviZI5B7VnEKGl2Oam1fQzGZkkzB kqBgtRrHux3BRxPRqS4jM4akdplFhejHExVatOxfS+DEXzFefi+aMb7qApB1YjV/ 5FIIQdZaVOlw2KIRXCy04nxrKJmJ1T5RCkYC80dYpNfmDb5REUtp8jU78/Schsx7 0nCsrWkBSO1QtR4NnBlHbEM+imh3aCQz23SUK5Q/NTe4r2pu0zUl5b2YNgefvWle 7fe6T137rmhji9K+tYNznLGk0XmiguQPM2qJLxqeVQsA32wUbbSIFWH+KsXRPfpU n/iFVG4Y6zyXQY2RzTt+ZB2VPR72X4wqS9fBeQ== -----END CERTIFICATE-----` p, rest := pem.Decode([]byte(awsApiGWCert)) if len(rest) > 0 { t.Fatal("want only one cert") } cert, err := x509.ParseCertificate(p.Bytes) if err != nil { t.Fatal(err) } // check that cert does not have the flags set if got, want := cert.BasicConstraintsValid, false; got != want { t.Fatalf("got %v want %v", got, want) } if got, want := cert.IsCA, false; got != want { t.Fatalf("got %v want %v", got, want) } if got, want := cert.KeyUsage, x509.KeyUsage(0); got != want { t.Fatalf("got %v want %v", got, want) } // run upgrade with not-matching CN expecting no change upgradeCACertificate(cert, "no match") if got, want := cert.BasicConstraintsValid, false; got != want { t.Fatalf("got %v want %v", got, want) } if got, want := cert.IsCA, false; got != want { t.Fatalf("got %v want %v", got, want) } if got, want := cert.KeyUsage, x509.KeyUsage(0); got != want { t.Fatalf("got %v want %v", got, want) } // run upgrade with matching CN upgradeCACertificate(cert, "ApiGateway") if got, want := cert.BasicConstraintsValid, true; got != want { t.Fatalf("got %v want %v", got, want) } if got, want := cert.IsCA, true; got != want { t.Fatalf("got %v want %v", got, want) } if got, want := cert.KeyUsage, x509.KeyUsageCertSign; got != want { t.Fatalf("got %v want %v", got, want) } }
// Performs the heavy lifting of creating a certificate. Returns // a fully-filled-in ParsedCertBundle. func createCertificate(creationInfo *certCreationBundle) (*certutil.ParsedCertBundle, error) { var clientPrivKey crypto.Signer var err error result := &certutil.ParsedCertBundle{} var serialNumber *big.Int serialNumber, err = rand.Int(rand.Reader, (&big.Int{}).Exp(big.NewInt(2), big.NewInt(159), nil)) if err != nil { return nil, certutil.InternalError{Err: fmt.Sprintf("Error getting random serial number")} } switch creationInfo.KeyType { case "rsa": result.PrivateKeyType = certutil.RSAPrivateKey clientPrivKey, err = rsa.GenerateKey(rand.Reader, creationInfo.KeyBits) if err != nil { return nil, certutil.InternalError{Err: fmt.Sprintf("Error generating RSA private key")} } result.PrivateKey = clientPrivKey result.PrivateKeyBytes = x509.MarshalPKCS1PrivateKey(clientPrivKey.(*rsa.PrivateKey)) case "ec": result.PrivateKeyType = certutil.ECPrivateKey var curve elliptic.Curve switch creationInfo.KeyBits { case 224: curve = elliptic.P224() case 256: curve = elliptic.P256() case 384: curve = elliptic.P384() case 521: curve = elliptic.P521() default: return nil, certutil.UserError{Err: fmt.Sprintf("Unsupported bit length for EC key: %d", creationInfo.KeyBits)} } clientPrivKey, err = ecdsa.GenerateKey(curve, rand.Reader) if err != nil { return nil, certutil.InternalError{Err: fmt.Sprintf("Error generating EC private key")} } result.PrivateKey = clientPrivKey result.PrivateKeyBytes, err = x509.MarshalECPrivateKey(clientPrivKey.(*ecdsa.PrivateKey)) if err != nil { return nil, certutil.InternalError{Err: fmt.Sprintf("Error marshalling EC private key")} } default: return nil, certutil.UserError{Err: fmt.Sprintf("Unknown key type: %s", creationInfo.KeyType)} } subjKeyID, err := certutil.GetSubjKeyID(result.PrivateKey) if err != nil { return nil, certutil.InternalError{Err: fmt.Sprintf("Error getting subject key ID: %s", err)} } subject := pkix.Name{ Country: creationInfo.CACert.Subject.Country, Organization: creationInfo.CACert.Subject.Organization, OrganizationalUnit: creationInfo.CACert.Subject.OrganizationalUnit, Locality: creationInfo.CACert.Subject.Locality, Province: creationInfo.CACert.Subject.Province, StreetAddress: creationInfo.CACert.Subject.StreetAddress, PostalCode: creationInfo.CACert.Subject.PostalCode, SerialNumber: serialNumber.String(), CommonName: creationInfo.CommonNames[0], } certTemplate := &x509.Certificate{ SignatureAlgorithm: x509.SHA256WithRSA, SerialNumber: serialNumber, Subject: subject, NotBefore: time.Now(), NotAfter: time.Now().Add(creationInfo.Lease), KeyUsage: x509.KeyUsage(x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment | x509.KeyUsageKeyAgreement), BasicConstraintsValid: true, IsCA: false, SubjectKeyId: subjKeyID, DNSNames: creationInfo.CommonNames, IPAddresses: creationInfo.IPSANs, PermittedDNSDomainsCritical: false, PermittedDNSDomains: nil, CRLDistributionPoints: creationInfo.CACert.CRLDistributionPoints, } if creationInfo.Usage&serverUsage != 0 { certTemplate.ExtKeyUsage = append(certTemplate.ExtKeyUsage, x509.ExtKeyUsageServerAuth) } if creationInfo.Usage&clientUsage != 0 { certTemplate.ExtKeyUsage = append(certTemplate.ExtKeyUsage, x509.ExtKeyUsageClientAuth) } if creationInfo.Usage&codeSigningUsage != 0 { certTemplate.ExtKeyUsage = append(certTemplate.ExtKeyUsage, x509.ExtKeyUsageCodeSigning) } cert, err := x509.CreateCertificate(rand.Reader, certTemplate, creationInfo.CACert, clientPrivKey.Public(), creationInfo.SigningBundle.PrivateKey) if err != nil { return nil, certutil.InternalError{Err: fmt.Sprintf("Unable to create certificate: %s", err)} } result.CertificateBytes = cert result.Certificate, err = x509.ParseCertificate(cert) if err != nil { return nil, certutil.InternalError{Err: fmt.Sprintf("Unable to parse created certificate: %s", err)} } result.IssuingCABytes = creationInfo.SigningBundle.CertificateBytes result.IssuingCA = creationInfo.SigningBundle.Certificate return result, nil }
// generateCreationBundle is a shared function that reads parameters supplied // from the various endpoints and generates a creationBundle with the // parameters that can be used to issue or sign func generateCreationBundle(b *backend, role *roleEntry, signingBundle *caInfoBundle, csr *x509.CertificateRequest, req *logical.Request, data *framework.FieldData) (*creationBundle, error) { var err error var ok bool // Get the common name var cn string { if csr != nil { if role.UseCSRCommonName { cn = csr.Subject.CommonName } } if cn == "" { cn = data.Get("common_name").(string) if cn == "" { return nil, errutil.UserError{Err: `the common_name field is required, or must be provided in a CSR with "use_csr_common_name" set to true`} } } } // Read in alternate names -- DNS and email addresses dnsNames := []string{} emailAddresses := []string{} { if !data.Get("exclude_cn_from_sans").(bool) { if strings.Contains(cn, "@") { // Note: emails are not disallowed if the role's email protection // flag is false, because they may well be included for // informational purposes; it is up to the verifying party to // ensure that email addresses in a subject alternate name can be // used for the purpose for which they are presented emailAddresses = append(emailAddresses, cn) } else { dnsNames = append(dnsNames, cn) } } cnAltInt, ok := data.GetOk("alt_names") if ok { cnAlt := cnAltInt.(string) if len(cnAlt) != 0 { for _, v := range strings.Split(cnAlt, ",") { if strings.Contains(v, "@") { emailAddresses = append(emailAddresses, v) } else { dnsNames = append(dnsNames, v) } } } } // Check for bad email and/or DNS names badName, err := validateNames(req, dnsNames, role) if len(badName) != 0 { return nil, errutil.UserError{Err: fmt.Sprintf( "name %s not allowed by this role", badName)} } else if err != nil { return nil, errutil.InternalError{Err: fmt.Sprintf( "error validating name %s: %s", badName, err)} } badName, err = validateNames(req, emailAddresses, role) if len(badName) != 0 { return nil, errutil.UserError{Err: fmt.Sprintf( "email %s not allowed by this role", badName)} } else if err != nil { return nil, errutil.InternalError{Err: fmt.Sprintf( "error validating name %s: %s", badName, err)} } } // Get and verify any IP SANs ipAddresses := []net.IP{} var ipAltInt interface{} { ipAltInt, ok = data.GetOk("ip_sans") if ok { ipAlt := ipAltInt.(string) if len(ipAlt) != 0 { if !role.AllowIPSANs { return nil, errutil.UserError{Err: fmt.Sprintf( "IP Subject Alternative Names are not allowed in this role, but was provided %s", ipAlt)} } for _, v := range strings.Split(ipAlt, ",") { parsedIP := net.ParseIP(v) if parsedIP == nil { return nil, errutil.UserError{Err: fmt.Sprintf( "the value '%s' is not a valid IP address", v)} } ipAddresses = append(ipAddresses, parsedIP) } } } } // Get the TTL and very it against the max allowed var ttlField string var ttl time.Duration var maxTTL time.Duration var ttlFieldInt interface{} { ttlFieldInt, ok = data.GetOk("ttl") if !ok { ttlField = role.TTL } else { ttlField = ttlFieldInt.(string) } if len(ttlField) == 0 { ttl = b.System().DefaultLeaseTTL() } else { ttl, err = time.ParseDuration(ttlField) if err != nil { return nil, errutil.UserError{Err: fmt.Sprintf( "invalid requested ttl: %s", err)} } } if len(role.MaxTTL) == 0 { maxTTL = b.System().MaxLeaseTTL() } else { maxTTL, err = time.ParseDuration(role.MaxTTL) if err != nil { return nil, errutil.UserError{Err: fmt.Sprintf( "invalid ttl: %s", err)} } } if ttl > maxTTL { // Don't error if they were using system defaults, only error if // they specifically chose a bad TTL if len(ttlField) == 0 { ttl = maxTTL } else { return nil, errutil.UserError{Err: fmt.Sprintf( "ttl is larger than maximum allowed (%d)", maxTTL/time.Second)} } } // If it's not self-signed, verify that the issued certificate won't be // valid past the lifetime of the CA certificate if signingBundle != nil && time.Now().Add(ttl).After(signingBundle.Certificate.NotAfter) { return nil, errutil.UserError{Err: fmt.Sprintf( "cannot satisfy request, as TTL is beyond the expiration of the CA certificate")} } } // Build up usages var extUsage certExtKeyUsage { if role.ServerFlag { extUsage = extUsage | serverExtKeyUsage } if role.ClientFlag { extUsage = extUsage | clientExtKeyUsage } if role.CodeSigningFlag { extUsage = extUsage | codeSigningExtKeyUsage } if role.EmailProtectionFlag { extUsage = extUsage | emailProtectionExtKeyUsage } } creationBundle := &creationBundle{ CommonName: cn, DNSNames: dnsNames, EmailAddresses: emailAddresses, IPAddresses: ipAddresses, KeyType: role.KeyType, KeyBits: role.KeyBits, SigningBundle: signingBundle, TTL: ttl, KeyUsage: x509.KeyUsage(parseKeyUsages(role.KeyUsage)), ExtKeyUsage: extUsage, } // Don't deal with URLs or max path length if it's self-signed, as these // normally come from the signing bundle if signingBundle == nil { return creationBundle, nil } // This will have been read in from the getURLs function creationBundle.URLs = signingBundle.URLs // If the max path length in the role is not nil, it was specified at // generation time with the max_path_length parameter; otherwise derive it // from the signing certificate if role.MaxPathLength != nil { creationBundle.MaxPathLength = *role.MaxPathLength } else { switch { case signingBundle.Certificate.MaxPathLen < 0: creationBundle.MaxPathLength = -1 case signingBundle.Certificate.MaxPathLen == 0 && signingBundle.Certificate.MaxPathLenZero: // The signing function will ensure that we do not issue a CA cert creationBundle.MaxPathLength = 0 default: // If this takes it to zero, we handle this case later if // necessary creationBundle.MaxPathLength = signingBundle.Certificate.MaxPathLen - 1 } } return creationBundle, nil }
// Performs the heavy lifting of generating a certificate from a CSR. // Returns a ParsedCertBundle sans private keys. func signCertificate(creationInfo *creationBundle, csr *x509.CertificateRequest) (*certutil.ParsedCertBundle, error) { switch { case creationInfo == nil: return nil, certutil.UserError{Err: "nil creation info given to signCertificate"} case creationInfo.SigningBundle == nil: return nil, certutil.UserError{Err: "nil signing bundle given to signCertificate"} case csr == nil: return nil, certutil.UserError{Err: "nil csr given to signCertificate"} } err := csr.CheckSignature() if err != nil { return nil, certutil.UserError{Err: "request signature invalid"} } result := &certutil.ParsedCertBundle{} serialNumber, err := certutil.GenerateSerialNumber() if err != nil { return nil, err } marshaledKey, err := x509.MarshalPKIXPublicKey(csr.PublicKey) if err != nil { return nil, certutil.InternalError{Err: fmt.Sprintf("error marshalling public key: %s", err)} } subjKeyID := sha1.Sum(marshaledKey) subject := pkix.Name{ CommonName: creationInfo.CommonName, } certTemplate := &x509.Certificate{ SerialNumber: serialNumber, Subject: subject, NotBefore: time.Now(), NotAfter: time.Now().Add(creationInfo.TTL), SubjectKeyId: subjKeyID[:], } switch creationInfo.SigningBundle.PrivateKeyType { case certutil.RSAPrivateKey: certTemplate.SignatureAlgorithm = x509.SHA256WithRSA case certutil.ECPrivateKey: certTemplate.SignatureAlgorithm = x509.ECDSAWithSHA256 } if creationInfo.UseCSRValues { certTemplate.Subject = csr.Subject certTemplate.DNSNames = csr.DNSNames certTemplate.EmailAddresses = csr.EmailAddresses certTemplate.IPAddresses = csr.IPAddresses certTemplate.ExtraExtensions = csr.Extensions // Do not sign a CA certificate if they didn't go through the sign-intermediate // endpoint if !creationInfo.IsCA && oidInExtensions(oidExtensionBasicConstraints, certTemplate.ExtraExtensions) { return nil, certutil.UserError{Err: "will not sign a CSR asking for CA rights through this endpoint"} } } else { certTemplate.DNSNames = creationInfo.DNSNames certTemplate.EmailAddresses = creationInfo.EmailAddresses certTemplate.IPAddresses = creationInfo.IPAddresses certTemplate.KeyUsage = x509.KeyUsage(x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment | x509.KeyUsageKeyAgreement) if creationInfo.Usage&serverUsage != 0 { certTemplate.ExtKeyUsage = append(certTemplate.ExtKeyUsage, x509.ExtKeyUsageServerAuth) } if creationInfo.Usage&clientUsage != 0 { certTemplate.ExtKeyUsage = append(certTemplate.ExtKeyUsage, x509.ExtKeyUsageClientAuth) } if creationInfo.Usage&codeSigningUsage != 0 { certTemplate.ExtKeyUsage = append(certTemplate.ExtKeyUsage, x509.ExtKeyUsageCodeSigning) } if creationInfo.Usage&emailProtectionUsage != 0 { certTemplate.ExtKeyUsage = append(certTemplate.ExtKeyUsage, x509.ExtKeyUsageEmailProtection) } if creationInfo.IsCA { certTemplate.KeyUsage = x509.KeyUsage(certTemplate.KeyUsage | x509.KeyUsageCertSign | x509.KeyUsageCRLSign) certTemplate.ExtKeyUsage = append(certTemplate.ExtKeyUsage, x509.ExtKeyUsageOCSPSigning) } } var certBytes []byte caCert := creationInfo.SigningBundle.Certificate certTemplate.IssuingCertificateURL = creationInfo.URLs.IssuingCertificates certTemplate.CRLDistributionPoints = creationInfo.URLs.CRLDistributionPoints certTemplate.OCSPServer = creationInfo.SigningBundle.URLs.OCSPServers if creationInfo.IsCA { certTemplate.BasicConstraintsValid = true certTemplate.IsCA = true if creationInfo.SigningBundle.Certificate.MaxPathLen == 0 && creationInfo.SigningBundle.Certificate.MaxPathLenZero { return nil, certutil.UserError{Err: "signing certificate has a max path length of zero, and cannot issue further CA certificates"} } certTemplate.MaxPathLen = creationInfo.MaxPathLength if certTemplate.MaxPathLen == 0 { certTemplate.MaxPathLenZero = true } } certBytes, err = x509.CreateCertificate(rand.Reader, certTemplate, caCert, csr.PublicKey, creationInfo.SigningBundle.PrivateKey) if err != nil { return nil, certutil.InternalError{Err: fmt.Sprintf("unable to create certificate: %s", err)} } result.CertificateBytes = certBytes result.Certificate, err = x509.ParseCertificate(certBytes) if err != nil { return nil, certutil.InternalError{Err: fmt.Sprintf("unable to parse created certificate: %s", err)} } result.IssuingCABytes = creationInfo.SigningBundle.CertificateBytes result.IssuingCA = creationInfo.SigningBundle.Certificate return result, nil }
// Performs the heavy lifting of creating a certificate. Returns // a fully-filled-in ParsedCertBundle. func createCertificate(creationInfo *creationBundle) (*certutil.ParsedCertBundle, error) { var err error result := &certutil.ParsedCertBundle{} serialNumber, err := certutil.GenerateSerialNumber() if err != nil { return nil, err } if err := certutil.GeneratePrivateKey(creationInfo.KeyType, creationInfo.KeyBits, result); err != nil { return nil, err } subjKeyID, err := certutil.GetSubjKeyID(result.PrivateKey) if err != nil { return nil, certutil.InternalError{Err: fmt.Sprintf("error getting subject key ID: %s", err)} } subject := pkix.Name{ CommonName: creationInfo.CommonName, } certTemplate := &x509.Certificate{ SerialNumber: serialNumber, Subject: subject, NotBefore: time.Now(), NotAfter: time.Now().Add(creationInfo.TTL), KeyUsage: x509.KeyUsage(x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment | x509.KeyUsageKeyAgreement), IsCA: false, SubjectKeyId: subjKeyID, DNSNames: creationInfo.DNSNames, EmailAddresses: creationInfo.EmailAddresses, IPAddresses: creationInfo.IPAddresses, } if creationInfo.Usage&serverUsage != 0 { certTemplate.ExtKeyUsage = append(certTemplate.ExtKeyUsage, x509.ExtKeyUsageServerAuth) } if creationInfo.Usage&clientUsage != 0 { certTemplate.ExtKeyUsage = append(certTemplate.ExtKeyUsage, x509.ExtKeyUsageClientAuth) } if creationInfo.Usage&codeSigningUsage != 0 { certTemplate.ExtKeyUsage = append(certTemplate.ExtKeyUsage, x509.ExtKeyUsageCodeSigning) } if creationInfo.Usage&emailProtectionUsage != 0 { certTemplate.ExtKeyUsage = append(certTemplate.ExtKeyUsage, x509.ExtKeyUsageEmailProtection) } certTemplate.IssuingCertificateURL = creationInfo.URLs.IssuingCertificates certTemplate.CRLDistributionPoints = creationInfo.URLs.CRLDistributionPoints certTemplate.OCSPServer = creationInfo.URLs.OCSPServers var certBytes []byte if creationInfo.SigningBundle != nil { switch creationInfo.SigningBundle.PrivateKeyType { case certutil.RSAPrivateKey: certTemplate.SignatureAlgorithm = x509.SHA256WithRSA case certutil.ECPrivateKey: certTemplate.SignatureAlgorithm = x509.ECDSAWithSHA256 } caCert := creationInfo.SigningBundle.Certificate certBytes, err = x509.CreateCertificate(rand.Reader, certTemplate, caCert, result.PrivateKey.Public(), creationInfo.SigningBundle.PrivateKey) } else { // Creating a self-signed root if creationInfo.MaxPathLength == 0 { certTemplate.MaxPathLen = 0 certTemplate.MaxPathLenZero = true } else { certTemplate.MaxPathLen = creationInfo.MaxPathLength } switch creationInfo.KeyType { case "rsa": certTemplate.SignatureAlgorithm = x509.SHA256WithRSA case "ec": certTemplate.SignatureAlgorithm = x509.ECDSAWithSHA256 } certTemplate.BasicConstraintsValid = true certTemplate.IsCA = true certTemplate.KeyUsage = x509.KeyUsage(certTemplate.KeyUsage | x509.KeyUsageCertSign | x509.KeyUsageCRLSign) certTemplate.ExtKeyUsage = append(certTemplate.ExtKeyUsage, x509.ExtKeyUsageOCSPSigning) certBytes, err = x509.CreateCertificate(rand.Reader, certTemplate, certTemplate, result.PrivateKey.Public(), result.PrivateKey) } if err != nil { return nil, certutil.InternalError{Err: fmt.Sprintf("unable to create certificate: %s", err)} } result.CertificateBytes = certBytes result.Certificate, err = x509.ParseCertificate(certBytes) if err != nil { return nil, certutil.InternalError{Err: fmt.Sprintf("unable to parse created certificate: %s", err)} } if creationInfo.SigningBundle != nil { result.IssuingCABytes = creationInfo.SigningBundle.CertificateBytes result.IssuingCA = creationInfo.SigningBundle.Certificate } else { result.IssuingCABytes = result.CertificateBytes result.IssuingCA = result.Certificate } return result, nil }
// Base code to actually create and store a new certificate // Return the id of the new certificate(or -1) and an error (or nil) func (c App) createCertificate(id int, certificate models.FullCertificate) (*models.Certificate, error) { // Keep track of whether this is a self signed certificate var sign_cert *models.Certificate // Get the CA certificate that is signing this new cert var project *models.Project if id >= 0 { project = c.getProject(id) if project == nil { return nil, errors.New("Unable to retreive project") } cas := c.getProjectCAs(project) ca := c.Params.Values["certificate.SignedBy"][0] ca_val, _ := strconv.Atoi(ca) for _, c := range cas { if c.Id == ca_val { sign_cert = c } } } serialNumber := 1 caCount := &models.CACount{} var CRLLocation []string if sign_cert != nil { // Another Cert is CA obj, err := c.Txn.Get(models.CACount{}, sign_cert.Id) if err != nil { return nil, errors.New("Error determining Serial Number") } caCount = obj.(*models.CACount) serialNumber = caCount.SerialNumber + 1 caCount.SerialNumber = serialNumber _, err = c.Txn.Update(caCount) if err != nil { return nil, newError("Error saving ca count", err) } // Use the CRL of the cert that is signing it CRLLocation := []string{c.makeCRLLocation(caCount.Id)} if certificate.IsCA { // This certificate should now have it's own CRLLocation = append(CRLLocation, c.makeCRLLocation(caCount.Id)) } } else { crlCount := c.getCRLCount() CRLLocation = []string{c.makeCRLLocation(crlCount + 1)} } // Set up ways Key can be used certificate.KeyUses = c.Params.Values["certificate.KeyUses"] certificate.ExtKeyUses = c.Params.Values["certificate.ExtKeyUses"] //Set up primary key uses keyUsage := 0 for _, use := range certificate.KeyUses { v, _ := strconv.Atoi(use) keyUsage = keyUsage | v } // CAs need to be able to sign other certificates and Certificate Revocation Lists if certificate.IsCA { keyUsage = keyUsage | int(x509.KeyUsageCertSign) | int(x509.KeyUsageCRLSign) } // Set up extra key uses for certificate extKeyUsage := make([]x509.ExtKeyUsage, 0) for _, use := range certificate.ExtKeyUses { v, _ := strconv.Atoi(use) extKeyUsage = append(extKeyUsage, x509.ExtKeyUsage(v)) } endTime, _ := time.Parse("2006-01-02", certificate.Expires) encrypted := false template := x509.Certificate{ SerialNumber: new(big.Int).SetInt64(int64(serialNumber)), Subject: pkix.Name{ Organization: []string{certificate.Organization}, OrganizationalUnit: []string{certificate.OrganizationUnit}, Country: []string{certificate.Country}, Province: []string{certificate.State}, Locality: []string{certificate.City}, CommonName: certificate.CommonName, }, NotBefore: time.Now(), NotAfter: endTime, IsCA: certificate.IsCA, KeyUsage: x509.KeyUsage(keyUsage), ExtKeyUsage: extKeyUsage, BasicConstraintsValid: true, CRLDistributionPoints: CRLLocation, } enc := strings.Split(certificate.PrivateKeyType, " ") var privCert []byte var pubCert []byte var keyType models.KeyType var signKey interface{} var parent *x509.Certificate // Create RSA Cert if strings.HasPrefix(enc[0], "RSA") { keyType = models.RSA size, err := strconv.Atoi(enc[1]) if err != nil { return nil, newError("Failed to generate private key", err) } priv, err := rsa.GenerateKey(rand.Reader, size) if err != nil { return nil, newError("Failed to generate private key", err) } if sign_cert == nil { signKey = priv parent = &template } else { // Getting signed by CA, get CA private key bytes var ca_bytes []byte ca_block, _ := pem.Decode(sign_cert.PrivateKey) if ca_block == nil { return nil, newError("Unable to decode CA privatekey", nil) } ca_bytes = ca_block.Bytes if x509.IsEncryptedPEMBlock(ca_block) { if len(certificate.CAEncryptionKey) == 0 { return nil, errors.New("Unable to unlock CA key") } else { // CA is encrypted so decrypt it ca_bytes, err = x509.DecryptPEMBlock(ca_block, []byte(certificate.CAEncryptionKey)) if err != nil { return nil, errors.New("Unable to decrypt CA key") } } } // Obtain the key for the CA for signing if sign_cert.KeyType == models.RSA { signKey, err = x509.ParsePKCS1PrivateKey(ca_bytes) if err != nil { return nil, newError("Error parsing certificate", err) } } else { signKey, err = x509.ParseECPrivateKey(ca_bytes) if err != nil { return nil, newError("Error parsing certificate", err) } } // We need the public certificate of the CA as well block, _ := pem.Decode(sign_cert.PEM) if block == nil { return nil, newError("Unable to decode CA cert", nil) } parent, err = x509.ParseCertificate(block.Bytes) if err != nil { return nil, newError("Failed to generate private key:", err) } } pub := priv.PublicKey marshalledKey := x509.MarshalPKCS1PrivateKey(priv) derBytes, err := x509.CreateCertificate(rand.Reader, &template, parent, &pub, signKey) if err != nil { return nil, newError("Failed to create certificate \n", err) } // Convert certificate to PEM to be stored in DB pubCert = pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derBytes}) pemKeyBlock := &pem.Block{Type: "RSA PRIVATE KEY", Bytes: marshalledKey} // Encrypt the private key if an encryption key was provided if len(certificate.EncryptionKey) > 0 { pemKeyBlock, err = x509.EncryptPEMBlock(rand.Reader, pemKeyBlock.Type, pemKeyBlock.Bytes, []byte(certificate.EncryptionKey), x509.PEMCipherAES256) if err != nil { return nil, newError("Failed to encrypt key", err) } encrypted = true } privCert = pem.EncodeToMemory(pemKeyBlock) } else { // Create ECDSA Cert keyType = models.ECDSA var curve elliptic.Curve // Select the Elliptic curve to use switch enc[1] { case "224": curve = elliptic.P224() case "256": curve = elliptic.P256() case "384": curve = elliptic.P384() case "521": curve = elliptic.P521() } priv, err := ecdsa.GenerateKey(curve, rand.Reader) pub := priv.PublicKey if err != nil { return nil, newError("Failed to generate private key", err) } if sign_cert == nil { signKey = priv parent = &template } else { // Getting signed by CA, get CA private key bytes var ca_bytes []byte ca_block, _ := pem.Decode(sign_cert.PrivateKey) if ca_block == nil { return nil, newError("Unable to decode CA privatekey", nil) } ca_bytes = ca_block.Bytes if x509.IsEncryptedPEMBlock(ca_block) { if len(certificate.CAEncryptionKey) == 0 { return nil, errors.New("Unable to unlock CA key") } else { // CA is encrypted so decrypt it ca_bytes, err = x509.DecryptPEMBlock(ca_block, []byte(certificate.CAEncryptionKey)) if err != nil { return nil, errors.New("Unable to decrypt CA key") } } } // Obtain the key for the CA for signing if sign_cert.KeyType == models.RSA { signKey, err = x509.ParsePKCS1PrivateKey(ca_bytes) if err != nil { return nil, newError("Error parsing certificate", err) } } else { signKey, err = x509.ParseECPrivateKey(ca_bytes) if err != nil { return nil, newError("Error parsing certificate", err) } } // We need the public certificate of the CA as well block, _ := pem.Decode(sign_cert.PEM) if block == nil { return nil, newError("Unable to decode CA cert", nil) } parent, err = x509.ParseCertificate(block.Bytes) if err != nil { return nil, newError("Error parsing certificate", err) } } // All the parts to make a certificate are available // Create an x509 certificate derBytes, err := x509.CreateCertificate(rand.Reader, &template, parent, &pub, signKey) if err != nil { return nil, newError("Failed to create certificate", err) } // Convert certificate to PEM to be stored in DB pubCert = pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derBytes}) //marshalledKey, err := MarshalECPrivateKey(priv) marshalledKey, err := x509.MarshalECPrivateKey(priv) // To be used for go 1.2 (added to default x509 lib) if err != nil { return nil, newError("Failed Marshal EC Private Key", err) } pemKeyBlock := &pem.Block{Type: "EC PRIVATE KEY", Bytes: marshalledKey} if len(certificate.EncryptionKey) > 0 { pemKeyBlock, err = x509.EncryptPEMBlock(rand.Reader, pemKeyBlock.Type, pemKeyBlock.Bytes, []byte(certificate.EncryptionKey), x509.PEMCipherAES256) if err != nil { return nil, newError("Failed to encrypt key", err) } encrypted = true } privCert = pem.EncodeToMemory(pemKeyBlock) } // Create Certificate model for certificate table cert := models.Certificate{PEM: pubCert, PrivateKey: privCert, CommonName: certificate.CommonName, CA: certificate.IsCA, Project: project, KeyType: keyType, Encrypted: encrypted, SerialNumber: serialNumber} // Save the certificate in the database err := c.Txn.Insert(&cert) if err != nil { return nil, newError("Error saving certificate", err) } if sign_cert == nil { // Cert is self signed caCount.SerialNumber = serialNumber caCount.Certificate = &cert err := c.Txn.Insert(caCount) if err != nil { return nil, newError("Error saving ca count", err) } } return &cert, nil }