func BenchmarkCheckCert(b *testing.B) { dbMap, err := sa.NewDbMap(dbConnStr) if err != nil { fmt.Println("Couldn't connect to database") return } checker := newChecker(dbMap) testKey, _ := rsa.GenerateKey(rand.Reader, 1024) expiry := time.Now().AddDate(0, 0, 1) serial := big.NewInt(1337) rawCert := x509.Certificate{ Subject: pkix.Name{ CommonName: "example.com", }, NotAfter: expiry, DNSNames: []string{"example-a.com"}, SerialNumber: serial, } certDer, _ := x509.CreateCertificate(rand.Reader, &rawCert, &rawCert, &testKey.PublicKey, testKey) cert := core.Certificate{ Status: core.StatusValid, Serial: core.SerialToString(serial), Digest: core.Fingerprint256(certDer), DER: certDer, Issued: time.Now(), Expires: expiry, } b.ResetTimer() for i := 0; i < b.N; i++ { checker.checkCert(cert) } }
func TestCheckCert(t *testing.T) { testKey, _ := rsa.GenerateKey(rand.Reader, 1024) checker := newChecker(nil) fc := clock.NewFake() fc.Add(time.Hour * 24 * 90) checker.clock = fc issued := checker.clock.Now().Add(-time.Hour * 24 * 45) goodExpiry := issued.Add(checkPeriod) serial := big.NewInt(1337) // Problems // Blacklsited common name // Expiry period is too long // Basic Constraints aren't set // Wrong key usage (none) rawCert := x509.Certificate{ Subject: pkix.Name{ CommonName: "example.com", }, NotAfter: goodExpiry.AddDate(0, 0, 1), // Period too long DNSNames: []string{"example-a.com"}, SerialNumber: serial, BasicConstraintsValid: false, } brokenCertDer, err := x509.CreateCertificate(rand.Reader, &rawCert, &rawCert, &testKey.PublicKey, testKey) test.AssertNotError(t, err, "Couldn't create certificate") // Problems // Digest doesn't match // Serial doesn't match // Expiry doesn't match cert := core.Certificate{ Status: core.StatusValid, DER: brokenCertDer, Issued: issued, Expires: goodExpiry.AddDate(0, 0, 2), // Expiration doesn't match } problems := checker.checkCert(cert) test.AssertEquals(t, len(problems), 7) // Fix the problems rawCert.Subject.CommonName = "example-a.com" rawCert.NotAfter = goodExpiry rawCert.BasicConstraintsValid = true rawCert.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth} goodCertDer, err := x509.CreateCertificate(rand.Reader, &rawCert, &rawCert, &testKey.PublicKey, testKey) test.AssertNotError(t, err, "Couldn't create certificate") parsed, err := x509.ParseCertificate(goodCertDer) test.AssertNotError(t, err, "Couldn't parse created certificate") cert.Serial = core.SerialToString(serial) cert.Digest = core.Fingerprint256(goodCertDer) cert.DER = goodCertDer cert.Expires = parsed.NotAfter problems = checker.checkCert(cert) test.AssertEquals(t, len(problems), 0) }
func (c *certChecker) checkCert(cert core.Certificate) (problems []string) { // Check digests match if cert.Digest != core.Fingerprint256(cert.DER) { problems = append(problems, "Stored digest doesn't match certificate digest") } // Parse certificate parsedCert, err := x509.ParseCertificate(cert.DER) if err != nil { problems = append(problems, fmt.Sprintf("Couldn't parse stored certificate: %s", err)) } else { // Check stored serial is correct storedSerial, err := core.StringToSerial(cert.Serial) if err != nil { problems = append(problems, "Stored serial is invalid") } else if parsedCert.SerialNumber.Cmp(storedSerial) != 0 { problems = append(problems, "Stored serial doesn't match certificate serial") } // Check we have the right expiration time if !parsedCert.NotAfter.Equal(cert.Expires) { problems = append(problems, "Stored expiration doesn't match certificate NotAfter") } // Check basic constraints are set if !parsedCert.BasicConstraintsValid { problems = append(problems, "Certificate doesn't have basic constraints set") } // Check the cert isn't able to sign other certificates if parsedCert.IsCA { problems = append(problems, "Certificate can sign other certificates") } // Check the cert has the correct validity period validityPeriod := parsedCert.NotAfter.Sub(parsedCert.NotBefore) if validityPeriod > checkPeriod { problems = append(problems, fmt.Sprintf("Certificate has a validity period longer than %s", checkPeriod)) } else if validityPeriod < checkPeriod { problems = append(problems, fmt.Sprintf("Certificate has a validity period shorter than %s", checkPeriod)) } if parsedCert.NotBefore.Before(cert.Issued.Add(-6*time.Hour)) || parsedCert.NotBefore.After(cert.Issued.Add(6*time.Hour)) { problems = append(problems, "Stored issuance date is outside of 6 hour window of certificate NotBefore") } // Check that the PA is still willing to issue for each name in DNSNames + CommonName for _, name := range append(parsedCert.DNSNames, parsedCert.Subject.CommonName) { id := core.AcmeIdentifier{Type: core.IdentifierDNS, Value: name} if err = c.pa.WillingToIssue(id, cert.RegistrationID); err != nil { problems = append(problems, fmt.Sprintf("Policy Authority isn't willing to issue for %s: %s", name, err)) } } // Check the cert has the correct key usage extensions if !reflect.DeepEqual(parsedCert.ExtKeyUsage, []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}) { problems = append(problems, "Certificate has incorrect key usage extensions") } } return problems }
// AddCertificate stores an issued certificate. func (ssa *SQLStorageAuthority) AddCertificate(certDER []byte, regID int64) (digest string, err error) { var parsedCertificate *x509.Certificate parsedCertificate, err = x509.ParseCertificate(certDER) if err != nil { return } digest = core.Fingerprint256(certDER) serial := core.SerialToString(parsedCertificate.SerialNumber) cert := &core.Certificate{ RegistrationID: regID, Serial: serial, Digest: digest, DER: certDER, Issued: ssa.clk.Now(), Expires: parsedCertificate.NotAfter, } certStatus := &core.CertificateStatus{ SubscriberApproved: false, Status: core.OCSPStatus("good"), OCSPLastUpdated: time.Time{}, Serial: serial, RevokedDate: time.Time{}, RevokedReason: 0, LockCol: 0, } tx, err := ssa.dbMap.Begin() if err != nil { return } // TODO Verify that the serial number doesn't yet exist err = tx.Insert(cert) if err != nil { tx.Rollback() return } err = tx.Insert(certStatus) if err != nil { tx.Rollback() return } err = tx.Commit() return }
func (c *certChecker) checkCert(cert core.Certificate) (problems []string) { // Check digests match if cert.Digest != core.Fingerprint256(cert.DER) { problems = append(problems, "Stored digest doesn't match certificate digest") } // Parse certificate parsedCert, err := x509.ParseCertificate(cert.DER) if err != nil { problems = append(problems, fmt.Sprintf("Couldn't parse stored certificate: %s", err)) } else { // Check stored serial is correct if core.SerialToString(parsedCert.SerialNumber) != cert.Serial { problems = append(problems, "Stored serial doesn't match certificate serial") } // Check we have the right expiration time if !parsedCert.NotAfter.Equal(cert.Expires) { problems = append(problems, "Stored expiration doesn't match certificate NotAfter") } // Check basic constraints are set if !parsedCert.BasicConstraintsValid { problems = append(problems, "Certificate doesn't have basic constraints set") } // Check the cert isn't able to sign other certificates if parsedCert.IsCA { problems = append(problems, "Certificate can sign other certificates") } // Check the cert has the correct validity period +/- an hour if parsedCert.NotAfter.Sub(cert.Issued) > checkPeriod+time.Hour { problems = append(problems, "Certificate has a validity period longer than 90 days") } else if parsedCert.NotAfter.Sub(cert.Issued) < checkPeriod-time.Hour { problems = append(problems, "Certificate has a validity period shorter than 90 days") } // Check that the PA is still willing to issue for each name in DNSNames + CommonName for _, name := range append(parsedCert.DNSNames, parsedCert.Subject.CommonName) { if err = c.pa.WillingToIssue(core.AcmeIdentifier{Type: core.IdentifierDNS, Value: name}); err != nil { problems = append(problems, fmt.Sprintf("Policy Authority isn't willing to issue for %s: %s", name, err)) } } // Check the cert has the correct key usage extensions if !core.CmpExtKeyUsageSlice(parsedCert.ExtKeyUsage, []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}) { problems = append(problems, "Certificate has incorrect key usage extensions") } } return problems }
func BenchmarkCheckCert(b *testing.B) { saDbMap, err := sa.NewDbMap(vars.DBConnSA) if err != nil { fmt.Println("Couldn't connect to database") return } paDbMap, err := sa.NewDbMap(vars.DBConnPolicy) if err != nil { fmt.Println("Couldn't connect to database") return } defer func() { err = saDbMap.TruncateTables() fmt.Printf("Failed to truncate tables: %s\n", err) err = paDbMap.TruncateTables() fmt.Printf("Failed to truncate tables: %s\n", err) }() checker := newChecker(saDbMap, paDbMap, clock.Default(), false, nil) testKey, _ := rsa.GenerateKey(rand.Reader, 1024) expiry := time.Now().AddDate(0, 0, 1) serial := big.NewInt(1337) rawCert := x509.Certificate{ Subject: pkix.Name{ CommonName: "example.com", }, NotAfter: expiry, DNSNames: []string{"example-a.com"}, SerialNumber: serial, } certDer, _ := x509.CreateCertificate(rand.Reader, &rawCert, &rawCert, &testKey.PublicKey, testKey) cert := core.Certificate{ Serial: core.SerialToString(serial), Digest: core.Fingerprint256(certDer), DER: certDer, Issued: time.Now(), Expires: expiry, } b.ResetTimer() for i := 0; i < b.N; i++ { checker.checkCert(cert) } }
func TestCheckCert(t *testing.T) { saDbMap, err := sa.NewDbMap(vars.DBConnSA, 0) test.AssertNotError(t, err, "Couldn't connect to database") saCleanup := test.ResetSATestDatabase(t) defer func() { saCleanup() }() testKey, _ := rsa.GenerateKey(rand.Reader, 1024) fc := clock.NewFake() fc.Add(time.Hour * 24 * 90) checker := newChecker(saDbMap, fc, pa, expectedValidityPeriod) issued := checker.clock.Now().Add(-time.Hour * 24 * 45) goodExpiry := issued.Add(expectedValidityPeriod) serial := big.NewInt(1337) // Problems // Expiry period is too long // Basic Constraints aren't set // Wrong key usage (none) rawCert := x509.Certificate{ Subject: pkix.Name{ CommonName: "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeexample.com", }, NotBefore: issued, NotAfter: goodExpiry.AddDate(0, 0, 1), // Period too long DNSNames: []string{"example-a.com"}, SerialNumber: serial, BasicConstraintsValid: false, } brokenCertDer, err := x509.CreateCertificate(rand.Reader, &rawCert, &rawCert, &testKey.PublicKey, testKey) test.AssertNotError(t, err, "Couldn't create certificate") // Problems // Digest doesn't match // Serial doesn't match // Expiry doesn't match // Issued doesn't match cert := core.Certificate{ Serial: "8485f2687eba29ad455ae4e31c8679206fec", DER: brokenCertDer, Issued: issued.Add(12 * time.Hour), Expires: goodExpiry.AddDate(0, 0, 2), // Expiration doesn't match } problems := checker.checkCert(cert) problemsMap := map[string]int{ "Stored digest doesn't match certificate digest": 1, "Stored serial doesn't match certificate serial": 1, "Stored expiration doesn't match certificate NotAfter": 1, "Certificate doesn't have basic constraints set": 1, "Certificate has a validity period longer than 2160h0m0s": 1, "Stored issuance date is outside of 6 hour window of certificate NotBefore": 1, "Certificate has incorrect key usage extensions": 1, "Certificate has common name >64 characters long (65)": 1, } for _, p := range problems { _, ok := problemsMap[p] if !ok { t.Errorf("Found unexpected problem '%s'.", p) } delete(problemsMap, p) } for k := range problemsMap { t.Errorf("Expected problem but didn't find it: '%s'.", k) } test.AssertEquals(t, len(problems), 8) // Same settings as above, but the stored serial number in the DB is invalid. cert.Serial = "not valid" problems = checker.checkCert(cert) foundInvalidSerialProblem := false for _, p := range problems { if p == "Stored serial is invalid" { foundInvalidSerialProblem = true } } test.Assert(t, foundInvalidSerialProblem, "Invalid certificate serial number in DB did not trigger problem.") // Fix the problems rawCert.Subject.CommonName = "example-a.com" rawCert.NotAfter = goodExpiry rawCert.BasicConstraintsValid = true rawCert.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth} goodCertDer, err := x509.CreateCertificate(rand.Reader, &rawCert, &rawCert, &testKey.PublicKey, testKey) test.AssertNotError(t, err, "Couldn't create certificate") parsed, err := x509.ParseCertificate(goodCertDer) test.AssertNotError(t, err, "Couldn't parse created certificate") cert.Serial = core.SerialToString(serial) cert.Digest = core.Fingerprint256(goodCertDer) cert.DER = goodCertDer cert.Expires = parsed.NotAfter cert.Issued = parsed.NotBefore problems = checker.checkCert(cert) test.AssertEquals(t, len(problems), 0) }
// AddCertificate stores an issued certificate and returns the digest as // a string, or an error if any occurred. func (ssa *SQLStorageAuthority) AddCertificate(ctx context.Context, certDER []byte, regID int64) (string, error) { parsedCertificate, err := x509.ParseCertificate(certDER) if err != nil { return "", err } digest := core.Fingerprint256(certDER) serial := core.SerialToString(parsedCertificate.SerialNumber) cert := &core.Certificate{ RegistrationID: regID, Serial: serial, Digest: digest, DER: certDER, Issued: ssa.clk.Now(), Expires: parsedCertificate.NotAfter, } var certStatusOb interface{} if features.Enabled(features.CertStatusOptimizationsMigrated) { certStatusOb = &certStatusModelv2{ certStatusModelv1: certStatusModelv1{ SubscriberApproved: false, Status: core.OCSPStatus("good"), OCSPLastUpdated: time.Time{}, OCSPResponse: []byte{}, Serial: serial, RevokedDate: time.Time{}, RevokedReason: 0, LockCol: 0, }, NotAfter: parsedCertificate.NotAfter, } } else { certStatusOb = &certStatusModelv1{ SubscriberApproved: false, Status: core.OCSPStatus("good"), OCSPLastUpdated: time.Time{}, OCSPResponse: []byte{}, Serial: serial, RevokedDate: time.Time{}, RevokedReason: 0, LockCol: 0, } } tx, err := ssa.dbMap.Begin() if err != nil { return "", err } // Note: will fail on duplicate serials. Extremely unlikely to happen and soon // to be fixed by redesign. Reference issue // https://github.com/letsencrypt/boulder/issues/2265 for more err = tx.Insert(cert) if err != nil { return "", Rollback(tx, err) } err = tx.Insert(certStatusOb) if err != nil { return "", Rollback(tx, err) } err = addIssuedNames(tx, parsedCertificate) if err != nil { return "", Rollback(tx, err) } err = addFQDNSet( tx, parsedCertificate.DNSNames, serial, parsedCertificate.NotBefore, parsedCertificate.NotAfter, ) if err != nil { return "", Rollback(tx, err) } return digest, tx.Commit() }
func (c *certChecker) checkCert(cert core.Certificate) (problems []string) { // Check digests match if cert.Digest != core.Fingerprint256(cert.DER) { problems = append(problems, "Stored digest doesn't match certificate digest") } // Parse certificate parsedCert, err := x509.ParseCertificate(cert.DER) if err != nil { problems = append(problems, fmt.Sprintf("Couldn't parse stored certificate: %s", err)) } else { // Check stored serial is correct storedSerial, err := core.StringToSerial(cert.Serial) if err != nil { problems = append(problems, "Stored serial is invalid") } else if parsedCert.SerialNumber.Cmp(storedSerial) != 0 { problems = append(problems, "Stored serial doesn't match certificate serial") } // Check we have the right expiration time if !parsedCert.NotAfter.Equal(cert.Expires) { problems = append(problems, "Stored expiration doesn't match certificate NotAfter") } // Check basic constraints are set if !parsedCert.BasicConstraintsValid { problems = append(problems, "Certificate doesn't have basic constraints set") } // Check the cert isn't able to sign other certificates if parsedCert.IsCA { problems = append(problems, "Certificate can sign other certificates") } // Check the cert has the correct validity period validityPeriod := parsedCert.NotAfter.Sub(parsedCert.NotBefore) if validityPeriod > expectedValidityPeriod { problems = append(problems, fmt.Sprintf("Certificate has a validity period longer than %s", expectedValidityPeriod)) } else if validityPeriod < expectedValidityPeriod { problems = append(problems, fmt.Sprintf("Certificate has a validity period shorter than %s", expectedValidityPeriod)) } // Check the stored issuance time isn't too far back/forward dated if parsedCert.NotBefore.Before(cert.Issued.Add(-6*time.Hour)) || parsedCert.NotBefore.After(cert.Issued.Add(6*time.Hour)) { problems = append(problems, "Stored issuance date is outside of 6 hour window of certificate NotBefore") } // Check CommonName is <= 64 characters if len(parsedCert.Subject.CommonName) > 64 { problems = append( problems, fmt.Sprintf("Certificate has common name >64 characters long (%d)", len(parsedCert.Subject.CommonName)), ) } // Check that the PA is still willing to issue for each name in DNSNames + CommonName for _, name := range append(parsedCert.DNSNames, parsedCert.Subject.CommonName) { id := core.AcmeIdentifier{Type: core.IdentifierDNS, Value: name} if err = c.pa.WillingToIssue(id); err != nil { problems = append(problems, fmt.Sprintf("Policy Authority isn't willing to issue for '%s': %s", name, err)) } else { // For defense-in-depth, even if the PA was willing to issue for a name // we double check it against a list of forbidden domains. This way even // if the hostnamePolicyFile malfunctions we will flag the forbidden // domain matches if forbidden, pattern := isForbiddenDomain(name); forbidden { problems = append(problems, fmt.Sprintf( "Policy Authority was willing to issue but domain '%s' matches "+ "forbiddenDomains entry %q", name, pattern)) } } } // Check the cert has the correct key usage extensions if !reflect.DeepEqual(parsedCert.ExtKeyUsage, []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}) { problems = append(problems, "Certificate has incorrect key usage extensions") } } return problems }
func TestCheckCert(t *testing.T) { saDbMap, err := sa.NewDbMap(saDbConnStr) test.AssertNotError(t, err, "Couldn't connect to database") saCleanup := test.ResetTestDatabase(t, saDbMap.Db) paDbMap, err := sa.NewDbMap(paDbConnStr) test.AssertNotError(t, err, "Couldn't connect to policy database") paCleanup := test.ResetTestDatabase(t, paDbMap.Db) defer func() { saCleanup() paCleanup() }() testKey, _ := rsa.GenerateKey(rand.Reader, 1024) fc := clock.NewFake() fc.Add(time.Hour * 24 * 90) checker := newChecker(saDbMap, paDbMap, fc, false) issued := checker.clock.Now().Add(-time.Hour * 24 * 45) goodExpiry := issued.Add(checkPeriod) serial := big.NewInt(1337) // Problems // Expiry period is too long // Basic Constraints aren't set // Wrong key usage (none) rawCert := x509.Certificate{ Subject: pkix.Name{ CommonName: "example.com", }, NotBefore: issued, NotAfter: goodExpiry.AddDate(0, 0, 1), // Period too long DNSNames: []string{"example-a.com"}, SerialNumber: serial, BasicConstraintsValid: false, } brokenCertDer, err := x509.CreateCertificate(rand.Reader, &rawCert, &rawCert, &testKey.PublicKey, testKey) test.AssertNotError(t, err, "Couldn't create certificate") // Problems // Digest doesn't match // Serial doesn't match // Expiry doesn't match // Issued doesn't match cert := core.Certificate{ DER: brokenCertDer, Issued: issued.Add(12 * time.Hour), Expires: goodExpiry.AddDate(0, 0, 2), // Expiration doesn't match } problems := checker.checkCert(cert) fmt.Println(strings.Join(problems, "\n")) test.AssertEquals(t, len(problems), 7) // Fix the problems rawCert.Subject.CommonName = "example-a.com" rawCert.NotAfter = goodExpiry rawCert.BasicConstraintsValid = true rawCert.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth} goodCertDer, err := x509.CreateCertificate(rand.Reader, &rawCert, &rawCert, &testKey.PublicKey, testKey) test.AssertNotError(t, err, "Couldn't create certificate") parsed, err := x509.ParseCertificate(goodCertDer) test.AssertNotError(t, err, "Couldn't parse created certificate") cert.Serial = core.SerialToString(serial) cert.Digest = core.Fingerprint256(goodCertDer) cert.DER = goodCertDer cert.Expires = parsed.NotAfter cert.Issued = parsed.NotBefore problems = checker.checkCert(cert) test.AssertEquals(t, len(problems), 0) }