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 template.MaxPathLenZero = signer.MaxPathLenZero } 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 }
func (s *Signer) sign(template *x509.Certificate, profile *config.SigningProfile) (cert []byte, err error) { pub := template.PublicKey encodedpub, err := x509.MarshalPKIXPublicKey(pub) if err != nil { return } pubhash := sha1.New() pubhash.Write(encodedpub) if profile == nil { profile = s.Policy.Default } var ( eku []x509.ExtKeyUsage ku x509.KeyUsage expiry time.Duration 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() expiry = profile.Expiry if profile.IssuerURL == nil { profile.IssuerURL = s.Policy.Default.IssuerURL } if ku == 0 && len(eku) == 0 { err = cferr.New(cferr.PolicyError, cferr.NoKeyUsages, errors.New("no key usage available")) return } if expiry == 0 { expiry = s.Policy.Default.Expiry } if crlURL = profile.CRL; crlURL == "" { crlURL = s.Policy.Default.CRL } if ocspURL = profile.OCSP; ocspURL == "" { ocspURL = s.Policy.Default.OCSP } now := time.Now() serialNumber, err := rand.Int(rand.Reader, new(big.Int).SetInt64(math.MaxInt64)) if err != nil { err = cferr.New(cferr.CertificateError, cferr.Unknown, err) } template.SerialNumber = serialNumber template.NotBefore = now.Add(-5 * time.Minute).UTC() template.NotAfter = now.Add(expiry).UTC() template.KeyUsage = ku template.ExtKeyUsage = eku template.BasicConstraintsValid = true template.IsCA = profile.CA template.SubjectKeyId = pubhash.Sum(nil) if ocspURL != "" { template.OCSPServer = []string{ocspURL} } if crlURL != "" { template.CRLDistributionPoints = []string{crlURL} } if len(profile.IssuerURL) != 0 { template.IssuingCertificateURL = profile.IssuerURL } var initRoot bool if s.CA == nil { if !template.IsCA { err = cferr.New(cferr.PolicyError, cferr.InvalidRequest, nil) return } template.DNSNames = nil s.CA = template initRoot = true template.MaxPathLen = 2 } else if template.IsCA { template.MaxPathLen = 1 template.DNSNames = nil } derBytes, err := x509.CreateCertificate(rand.Reader, template, s.CA, pub, s.Priv) if err != nil { return } if initRoot { s.CA, err = x509.ParseCertificate(derBytes) if err != nil { err = cferr.New(cferr.CertificateError, cferr.ParseFailed, err) return } } cert = pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derBytes}) return }
// 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 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 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.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.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 }
// GenerateKeyPair generates or reloads an RSA keypair with key usages // determined by `purpose`. The disk files that are generated or reused are // named `name`-key.pem and `name`-cert.pem for the private and public halves, // respectively. `commonName` and `hosts` are the corresponding fields in the // certificate. // // cc.Serial is incremented for each key that is freshly generated. func (cc *CertCreator) GenerateKeyPair(purpose Purpose, parent *KeyPair, name string, commonName string, hosts ...string) (*KeyPair, error) { if pair, err := LoadKeyPairFromDisk(name); err == nil { return pair, nil } keyFile, certFile := name+"-key.pem", name+"-cert.pem" if _, err := os.Stat(certFile); !os.IsNotExist(err) { return nil, fmt.Errorf("Cert file %q already exists", certFile) } var extUsages []x509.ExtKeyUsage if purpose == CA { extUsages = []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth} } else if purpose == SERVER { extUsages = []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth} } else { extUsages = []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth} } serial := new(big.Int).SetInt64(cc.Serial) cc.Serial++ template := x509.Certificate{ Subject: pkix.Name{ Country: []string{cc.Country}, Province: []string{cc.State}, Locality: []string{cc.City}, Organization: []string{cc.Organization}, CommonName: commonName, SerialNumber: serial.String(), }, NotBefore: cc.NotBefore, NotAfter: cc.NotAfter, SerialNumber: serial, KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, ExtKeyUsage: extUsages, BasicConstraintsValid: true, // enforces IsCA and KeyUsage } if purpose == CA { template.IsCA = true template.KeyUsage = x509.KeyUsageCertSign } else { template.MaxPathLen = 1 } // generate IP and DNS SANs for _, h := range hosts { if ip := net.ParseIP(h); ip != nil { template.IPAddresses = append(template.IPAddresses, ip) } else { template.DNSNames = append(template.DNSNames, h) } } var privKey *rsa.PrivateKey var reusedKey bool if keyBytes, err := ioutil.ReadFile(keyFile); err == nil { // reuse existing key keyDERBlock, _ := pem.Decode(keyBytes) if keyDERBlock == nil { return nil, errors.New("failed to parse key PEM data") } if keyDERBlock.Type != "RSA PRIVATE KEY" { return nil, fmt.Errorf("key is not a RSA PRIVATE KEY: %s", keyDERBlock.Type) } privKey, err = x509.ParsePKCS1PrivateKey(keyDERBlock.Bytes) if err != nil { return nil, err } reusedKey = true vlog.VLogf("Reusing key %q\n", keyFile) } else if os.IsNotExist(err) { // generate a new key privKey, err = rsa.GenerateKey(rand.Reader, cc.KeySize) if err != nil { return nil, fmt.Errorf("Failed to generate private key: %s", err) } } else { return nil, err } origParent := parent if parent == nil { // CA signs itself parent = &KeyPair{&template, privKey} } // sign the key derBytes, err := x509.CreateCertificate(rand.Reader, &template, parent.Cert, &privKey.PublicKey, parent.PrivKey) if err != nil { return nil, err } // check that the cert can be parsed cert, err := x509.ParseCertificate(derBytes) if err != nil { return nil, fmt.Errorf("Cert doesn't verify against its CA: %s", err) } // check that the cert verifies against its own CA roots := x509.NewCertPool() if origParent == nil { roots.AddCert(cert) } else { roots.AddCert(parent.Cert) } _, err = cert.Verify(x509.VerifyOptions{Roots: roots, KeyUsages: extUsages}) if err != nil { return nil, fmt.Errorf("Couldn't verify %q against its own CA: %s", name, err) } // write key and cert to disk if !reusedKey { keyOut, err := os.OpenFile(keyFile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) if err != nil { return nil, fmt.Errorf("Failed to open %q for writing: %s", keyFile, err) } pem.Encode(keyOut, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(privKey)}) keyOut.Close() vlog.VLogf("Wrote key %q\n", keyFile) } certOut, err := os.Create(certFile) if err != nil { return nil, fmt.Errorf("Failed to open %q for writing: %s", certFile, err) } pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}) certOut.Close() vlog.VLogf("Wrote cert %q\n", certFile) return LoadKeyPairFromDisk(name) }