func (m *mailer) sendNags(contacts []*core.AcmeURL, certs []*x509.Certificate) error { if len(contacts) == 0 { return nil } if len(certs) == 0 { return errors.New("no certs given to send nags for") } emails := []string{} for _, contact := range contacts { if contact.Scheme == "mailto" { emails = append(emails, contact.Opaque) } } if len(emails) == 0 { return nil } expiresIn := time.Duration(math.MaxInt64) expDate := m.clk.Now() domains := []string{} // Pick out the expiration date that is closest to being hit. for _, cert := range certs { domains = append(domains, cert.DNSNames...) possible := cert.NotAfter.Sub(m.clk.Now()) if possible < expiresIn { expiresIn = possible expDate = cert.NotAfter } } domains = core.UniqueLowerNames(domains) sort.Strings(domains) email := emailContent{ ExpirationDate: expDate.UTC().Format(time.RFC822Z), DaysToExpiration: int(expiresIn.Hours() / 24), DNSNames: strings.Join(domains, "\n"), } msgBuf := new(bytes.Buffer) err := m.emailTemplate.Execute(msgBuf, email) if err != nil { m.stats.Inc("Mailer.Expiration.Errors.SendingNag.TemplateFailure", 1, 1.0) return err } startSending := m.clk.Now() err = m.mailer.SendMail(emails, m.subject, msgBuf.String()) if err != nil { m.stats.Inc("Mailer.Expiration.Errors.SendingNag.SendFailure", 1, 1.0) return err } m.stats.TimingDuration("Mailer.Expiration.SendLatency", time.Since(startSending), 1.0) m.stats.Inc("Mailer.Expiration.Sent", int64(len(emails)), 1.0) return nil }
// normalizeCSR deduplicates and lowers the case of dNSNames and the subject CN. // If forceCNFromSAN is true it will also hoist a dNSName into the CN if it is empty. func normalizeCSR(csr *x509.CertificateRequest, forceCNFromSAN bool) { if forceCNFromSAN && csr.Subject.CommonName == "" { if len(csr.DNSNames) > 0 { csr.Subject.CommonName = csr.DNSNames[0] } } else if csr.Subject.CommonName != "" { csr.DNSNames = append(csr.DNSNames, csr.Subject.CommonName) } csr.Subject.CommonName = strings.ToLower(csr.Subject.CommonName) csr.DNSNames = core.UniqueLowerNames(csr.DNSNames) }
func (ra *RegistrationAuthorityImpl) checkCertificatesPerFQDNSetLimit(ctx context.Context, names []string, limit cmd.RateLimitPolicy, regID int64) error { count, err := ra.SA.CountFQDNSets(ctx, limit.Window.Duration, names) if err != nil { return err } names = core.UniqueLowerNames(names) if int(count) > limit.GetThreshold(strings.Join(names, ","), regID) { return core.RateLimitedError(fmt.Sprintf( "Too many certificates already issued for exact set of domains: %s", strings.Join(names, ","), )) } return nil }
// MatchesCSR tests the contents of a generated certificate to make sure // that the PublicKey, CommonName, and DNSNames match those provided in // the CSR that was used to generate the certificate. It also checks the // following fields for: // * notBefore is not more than 24 hours ago // * BasicConstraintsValid is true // * IsCA is false // * ExtKeyUsage only contains ExtKeyUsageServerAuth & ExtKeyUsageClientAuth // * Subject only contains CommonName & Names func (ra *RegistrationAuthorityImpl) MatchesCSR(cert core.Certificate, csr *x509.CertificateRequest) (err error) { parsedCertificate, err := x509.ParseCertificate([]byte(cert.DER)) if err != nil { return } // Check issued certificate matches what was expected from the CSR hostNames := make([]string, len(csr.DNSNames)) copy(hostNames, csr.DNSNames) if len(csr.Subject.CommonName) > 0 { hostNames = append(hostNames, csr.Subject.CommonName) } hostNames = core.UniqueLowerNames(hostNames) if !core.KeyDigestEquals(parsedCertificate.PublicKey, csr.PublicKey) { err = core.InternalServerError("Generated certificate public key doesn't match CSR public key") return } if len(csr.Subject.CommonName) > 0 && parsedCertificate.Subject.CommonName != strings.ToLower(csr.Subject.CommonName) { err = core.InternalServerError("Generated certificate CommonName doesn't match CSR CommonName") return } // Sort both slices of names before comparison. parsedNames := parsedCertificate.DNSNames sort.Strings(parsedNames) sort.Strings(hostNames) if !reflect.DeepEqual(parsedNames, hostNames) { err = core.InternalServerError("Generated certificate DNSNames don't match CSR DNSNames") return } if !reflect.DeepEqual(parsedCertificate.IPAddresses, csr.IPAddresses) { err = core.InternalServerError("Generated certificate IPAddresses don't match CSR IPAddresses") return } if !reflect.DeepEqual(parsedCertificate.EmailAddresses, csr.EmailAddresses) { err = core.InternalServerError("Generated certificate EmailAddresses don't match CSR EmailAddresses") return } if len(parsedCertificate.Subject.Country) > 0 || len(parsedCertificate.Subject.Organization) > 0 || len(parsedCertificate.Subject.OrganizationalUnit) > 0 || len(parsedCertificate.Subject.Locality) > 0 || len(parsedCertificate.Subject.Province) > 0 || len(parsedCertificate.Subject.StreetAddress) > 0 || len(parsedCertificate.Subject.PostalCode) > 0 || len(parsedCertificate.Subject.SerialNumber) > 0 { err = core.InternalServerError("Generated certificate Subject contains fields other than CommonName or Names") return } now := ra.clk.Now() if now.Sub(parsedCertificate.NotBefore) > time.Hour*24 { err = core.InternalServerError(fmt.Sprintf("Generated certificate is back dated %s", now.Sub(parsedCertificate.NotBefore))) return } if !parsedCertificate.BasicConstraintsValid { err = core.InternalServerError("Generated certificate doesn't have basic constraints set") return } if parsedCertificate.IsCA { err = core.InternalServerError("Generated certificate can sign other certificates") return } if !reflect.DeepEqual(parsedCertificate.ExtKeyUsage, []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}) { err = core.InternalServerError("Generated certificate doesn't have correct key usage extensions") return } return }
// IssueCertificate attempts to convert a CSR into a signed Certificate, while // enforcing all policies. Names (domains) in the CertificateRequest will be // lowercased before storage. func (ca *CertificateAuthorityImpl) IssueCertificate(csr x509.CertificateRequest, regID int64) (core.Certificate, error) { emptyCert := core.Certificate{} var err error if err := ca.checkHSMFault(); err != nil { return emptyCert, err } key, ok := csr.PublicKey.(crypto.PublicKey) if !ok { err = core.MalformedRequestError("Invalid public key in CSR.") // AUDIT[ Certificate Requests ] 11917fa4-10ef-4e0d-9105-bacbe7836a3c ca.log.AuditErr(err) return emptyCert, err } if err = ca.keyPolicy.GoodKey(key); err != nil { err = core.MalformedRequestError(fmt.Sprintf("Invalid public key in CSR: %s", err.Error())) // AUDIT[ Certificate Requests ] 11917fa4-10ef-4e0d-9105-bacbe7836a3c ca.log.AuditErr(err) return emptyCert, err } if badSignatureAlgorithms[csr.SignatureAlgorithm] { err = core.MalformedRequestError("Invalid signature algorithm in CSR") // AUDIT[ Certificate Requests ] 11917fa4-10ef-4e0d-9105-bacbe7836a3c ca.log.AuditErr(err) return emptyCert, err } // Pull hostnames from CSR // Authorization is checked by the RA commonName := "" hostNames := make([]string, len(csr.DNSNames)) copy(hostNames, csr.DNSNames) if len(csr.Subject.CommonName) > 0 { commonName = strings.ToLower(csr.Subject.CommonName) hostNames = append(hostNames, commonName) } else if len(hostNames) > 0 { commonName = strings.ToLower(hostNames[0]) } else { err = core.MalformedRequestError("Cannot issue a certificate without a hostname.") // AUDIT[ Certificate Requests ] 11917fa4-10ef-4e0d-9105-bacbe7836a3c ca.log.AuditErr(err) return emptyCert, err } // Collapse any duplicate names. Note that this operation may re-order the names hostNames = core.UniqueLowerNames(hostNames) if ca.maxNames > 0 && len(hostNames) > ca.maxNames { err = core.MalformedRequestError(fmt.Sprintf("Certificate request has %d names, maximum is %d.", len(hostNames), ca.maxNames)) ca.log.WarningErr(err) return emptyCert, err } // Verify that names are allowed by policy identifier := core.AcmeIdentifier{Type: core.IdentifierDNS, Value: commonName} if err = ca.PA.WillingToIssue(identifier, regID); err != nil { err = core.MalformedRequestError(fmt.Sprintf("Policy forbids issuing for name %s", commonName)) // AUDIT[ Certificate Requests ] 11917fa4-10ef-4e0d-9105-bacbe7836a3c ca.log.AuditErr(err) return emptyCert, err } for _, name := range hostNames { identifier = core.AcmeIdentifier{Type: core.IdentifierDNS, Value: name} if err = ca.PA.WillingToIssue(identifier, regID); err != nil { err = core.MalformedRequestError(fmt.Sprintf("Policy forbids issuing for name %s", name)) // AUDIT[ Certificate Requests ] 11917fa4-10ef-4e0d-9105-bacbe7836a3c ca.log.AuditErr(err) return emptyCert, err } } notAfter := ca.clk.Now().Add(ca.validityPeriod) if ca.notAfter.Before(notAfter) { err = core.InternalServerError("Cannot issue a certificate that expires after the intermediate certificate.") // AUDIT[ Certificate Requests ] 11917fa4-10ef-4e0d-9105-bacbe7836a3c ca.log.AuditErr(err) return emptyCert, err } // Convert the CSR to PEM csrPEM := string(pem.EncodeToMemory(&pem.Block{ Type: "CERTIFICATE REQUEST", Bytes: csr.Raw, })) // We want 136 bits of random number, plus an 8-bit instance id prefix. const randBits = 136 serialBytes := make([]byte, randBits/8+1) serialBytes[0] = byte(ca.prefix) _, err = rand.Read(serialBytes[1:]) if err != nil { err = core.InternalServerError(err.Error()) // AUDIT[ Error Conditions ] 9cc4d537-8534-4970-8665-4b382abe82f3 ca.log.Audit(fmt.Sprintf("Serial randomness failed, err=[%v]", err)) return emptyCert, err } serialHex := hex.EncodeToString(serialBytes) serialBigInt := big.NewInt(0) serialBigInt = serialBigInt.SetBytes(serialBytes) var profile string switch key.(type) { case *rsa.PublicKey: profile = ca.rsaProfile case *ecdsa.PublicKey: profile = ca.ecdsaProfile default: err = core.InternalServerError(fmt.Sprintf("unsupported key type %T", key)) // AUDIT[ Certificate Requests ] 11917fa4-10ef-4e0d-9105-bacbe7836a3c ca.log.AuditErr(err) return emptyCert, err } // Send the cert off for signing req := signer.SignRequest{ Request: csrPEM, Profile: profile, Hosts: hostNames, Subject: &signer.Subject{ CN: commonName, }, Serial: serialBigInt, } certPEM, err := ca.signer.Sign(req) ca.noteHSMFault(err) if err != nil { err = core.InternalServerError(err.Error()) // AUDIT[ Error Conditions ] 9cc4d537-8534-4970-8665-4b382abe82f3 ca.log.Audit(fmt.Sprintf("Signer failed, rolling back: serial=[%s] err=[%v]", serialHex, err)) return emptyCert, err } if len(certPEM) == 0 { err = core.InternalServerError("No certificate returned by server") // AUDIT[ Error Conditions ] 9cc4d537-8534-4970-8665-4b382abe82f3 ca.log.Audit(fmt.Sprintf("PEM empty from Signer, rolling back: serial=[%s] err=[%v]", serialHex, err)) return emptyCert, err } block, _ := pem.Decode(certPEM) if block == nil || block.Type != "CERTIFICATE" { err = core.InternalServerError("Invalid certificate value returned") // AUDIT[ Error Conditions ] 9cc4d537-8534-4970-8665-4b382abe82f3 ca.log.Audit(fmt.Sprintf("PEM decode error, aborting and rolling back issuance: pem=[%s] err=[%v]", certPEM, err)) return emptyCert, err } certDER := block.Bytes cert := core.Certificate{ DER: certDER, } // This is one last check for uncaught errors if err != nil { err = core.InternalServerError(err.Error()) // AUDIT[ Error Conditions ] 9cc4d537-8534-4970-8665-4b382abe82f3 ca.log.Audit(fmt.Sprintf("Uncaught error, aborting and rolling back issuance: pem=[%s] err=[%v]", certPEM, err)) return emptyCert, err } // Store the cert with the certificate authority, if provided _, err = ca.SA.AddCertificate(certDER, regID) if err != nil { err = core.InternalServerError(err.Error()) // AUDIT[ Error Conditions ] 9cc4d537-8534-4970-8665-4b382abe82f3 ca.log.Audit(fmt.Sprintf( "Failed RPC to store at SA, orphaning certificate: b64der=[%s] err=[%v], regID=[%d]", base64.StdEncoding.EncodeToString(certDER), err, regID, )) return emptyCert, err } // Submit the certificate to any configured CT logs go ca.Publisher.SubmitToCT(certDER) // Do not return an err at this point; caller must know that the Certificate // was issued. (Also, it should be impossible for err to be non-nil here) return cert, nil }
func hashNames(names []string) []byte { names = core.UniqueLowerNames(names) hash := sha256.Sum256([]byte(strings.Join(names, ","))) return hash[:] }
func (m *mailer) sendNags(contacts []string, certs []*x509.Certificate) error { if len(contacts) == 0 { return nil } if len(certs) == 0 { return errors.New("no certs given to send nags for") } emails := []string{} for _, contact := range contacts { parsed, err := url.Parse(contact) if err != nil { m.log.AuditErr(fmt.Sprintf("parsing contact email %s: %s", contact, err)) continue } if parsed.Scheme == "mailto" { emails = append(emails, parsed.Opaque) } } if len(emails) == 0 { return nil } expiresIn := time.Duration(math.MaxInt64) expDate := m.clk.Now() domains := []string{} serials := []string{} // Pick out the expiration date that is closest to being hit. for _, cert := range certs { domains = append(domains, cert.DNSNames...) serials = append(serials, core.SerialToString(cert.SerialNumber)) possible := cert.NotAfter.Sub(m.clk.Now()) if possible < expiresIn { expiresIn = possible expDate = cert.NotAfter } } domains = core.UniqueLowerNames(domains) sort.Strings(domains) m.log.Debug(fmt.Sprintf("Sending mail for %s (%s)", strings.Join(domains, ", "), strings.Join(serials, ", "))) var subject string if m.subject != "" { // If there is a subject from the configuration file, we should use it as-is // to preserve the "classic" behaviour before we added a domain name. subject = m.subject } else { // Otherwise, when no subject is configured we should make one using the // domain names in the expiring certificate subject = fmt.Sprintf("Certificate expiration notice for domain %q", domains[0]) if len(domains) > 1 { subject += fmt.Sprintf(" (and %d more)", len(domains)-1) } } email := emailContent{ ExpirationDate: expDate.UTC().Format(time.RFC822Z), DaysToExpiration: int(expiresIn.Hours() / 24), DNSNames: strings.Join(domains, "\n"), } msgBuf := new(bytes.Buffer) err := m.emailTemplate.Execute(msgBuf, email) if err != nil { m.stats.Inc("Errors.SendingNag.TemplateFailure", 1) return err } startSending := m.clk.Now() err = m.mailer.SendMail(emails, subject, msgBuf.String()) if err != nil { return err } finishSending := m.clk.Now() elapsed := finishSending.Sub(startSending) m.stats.TimingDuration("SendLatency", elapsed) return nil }