acme/autocert: renew Let's Encrypt certificates likely to get revoked Let's Encrypt is revoking all certificates verified with TLS-ALPN-01 beofre January 26th due to a compliance issue. Detect them and force a renewal. Also, fix the tests which were not testing if expired certificates were renewed anymore, as the test certificates were always invalid due to not having SANs. Change-Id: If9d0632b2edfe0b7fb70f6cfd7e65e46e2d047dc Reviewed-on: https://go-review.googlesource.com/c/crypto/+/381114 Trust: Filippo Valsorda <filippo@golang.org> Run-TryBot: Filippo Valsorda <filippo@golang.org> TryBot-Result: Gopher Robot <gobot@golang.org> Reviewed-by: Roland Shoemaker <roland@golang.org>
diff --git a/acme/autocert/autocert.go b/acme/autocert/autocert.go index c7fbc54..37923f4 100644 --- a/acme/autocert/autocert.go +++ b/acme/autocert/autocert.go
@@ -1200,6 +1200,10 @@ if err := leaf.VerifyHostname(ck.domain); err != nil { return nil, err } + // renew certificates revoked by Let's Encrypt in January 2022 + if isRevokedLetsEncrypt(leaf) { + return nil, errors.New("acme/autocert: certificate was probably revoked by Let's Encrypt") + } // ensure the leaf corresponds to the private key and matches the certKey type switch pub := leaf.PublicKey.(type) { case *rsa.PublicKey: @@ -1230,6 +1234,18 @@ return leaf, nil } +// https://community.letsencrypt.org/t/2022-01-25-issue-with-tls-alpn-01-validation-method/170450 +var letsEncryptFixDeployTime = time.Date(2022, time.January, 26, 00, 48, 0, 0, time.UTC) + +// isRevokedLetsEncrypt returns whether the certificate is likely to be part of +// a batch of certificates revoked by Let's Encrypt in January 2022. This check +// can be safely removed from May 2022. +func isRevokedLetsEncrypt(cert *x509.Certificate) bool { + O := cert.Issuer.Organization + return len(O) == 1 && O[0] == "Let's Encrypt" && + cert.NotBefore.Before(letsEncryptFixDeployTime) +} + type lockedMathRand struct { sync.Mutex rnd *mathrand.Rand
diff --git a/acme/autocert/autocert_test.go b/acme/autocert/autocert_test.go index 927efb9..e4663e9 100644 --- a/acme/autocert/autocert_test.go +++ b/acme/autocert/autocert_test.go
@@ -154,7 +154,7 @@ return nil, err } t := &x509.Certificate{ - SerialNumber: big.NewInt(1), + SerialNumber: randomSerial(), NotBefore: start, NotAfter: end, BasicConstraintsValid: true, @@ -167,6 +167,14 @@ return x509.CreateCertificate(rand.Reader, t, t, pub, key) } +func randomSerial() *big.Int { + serial, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 32)) + if err != nil { + panic(err) + } + return serial +} + func decodePayload(v interface{}, r io.Reader) error { var req struct{ Payload string } if err := json.NewDecoder(r).Decode(&req); err != nil { @@ -276,15 +284,54 @@ } } +func TestGetCertificate_goodCache(t *testing.T) { + // Make a valid cert and cache it. + pk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + t.Fatal(err) + } + serial := randomSerial() + tmpl := &x509.Certificate{ + SerialNumber: serial, + DNSNames: []string{exampleDomain}, + // Use a time before the Let's Encrypt revocation cutoff to also test + // that non-Let's Encrypt certificates are not renewed. + NotBefore: time.Date(2022, time.January, 1, 0, 0, 0, 0, time.UTC), + NotAfter: time.Date(2122, time.January, 1, 0, 0, 0, 0, time.UTC), + } + pub, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, &pk.PublicKey, pk) + if err != nil { + t.Fatal(err) + } + tlscert := &tls.Certificate{ + Certificate: [][]byte{pub}, + PrivateKey: pk, + } + + man := &Manager{Prompt: AcceptTOS, Cache: newMemCache(t)} + defer man.stopRenew() + if err := man.cachePut(context.Background(), exampleCertKey, tlscert); err != nil { + t.Fatalf("man.cachePut: %v", err) + } + + hello := clientHelloInfo(exampleDomain, algECDSA) + gotCert := testGetCertificate(t, man, exampleDomain, hello) + if gotCert.SerialNumber.Cmp(serial) != 0 { + t.Error("good certificate was replaced") + } +} + func TestGetCertificate_expiredCache(t *testing.T) { // Make an expired cert and cache it. pk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) if err != nil { t.Fatal(err) } + serial := randomSerial() tmpl := &x509.Certificate{ - SerialNumber: big.NewInt(1), - Subject: pkix.Name{CommonName: exampleDomain}, + SerialNumber: serial, + DNSNames: []string{exampleDomain}, + NotBefore: time.Now().Add(-1 * time.Minute), NotAfter: time.Now(), } pub, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, &pk.PublicKey, pk) @@ -305,7 +352,89 @@ // The expired cached cert should trigger a new cert issuance // and return without an error. hello := clientHelloInfo(exampleDomain, algECDSA) - testGetCertificate(t, man, exampleDomain, hello) + gotCert := testGetCertificate(t, man, exampleDomain, hello) + if gotCert.SerialNumber.Cmp(serial) == 0 { + t.Error("expired certificate was not replaced") + } +} + +func TestGetCertificate_goodLetsEncrypt(t *testing.T) { + pk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + t.Fatal(err) + } + issuer := &x509.Certificate{ + Subject: pkix.Name{Country: []string{"US"}, + Organization: []string{"Let's Encrypt"}, CommonName: "R3"}, + } + serial := randomSerial() + tmpl := &x509.Certificate{ + SerialNumber: serial, + DNSNames: []string{exampleDomain}, + NotBefore: time.Date(2022, time.January, 26, 12, 0, 0, 0, time.UTC), + NotAfter: time.Date(2122, time.January, 1, 0, 0, 0, 0, time.UTC), + } + pub, err := x509.CreateCertificate(rand.Reader, tmpl, issuer, &pk.PublicKey, pk) + if err != nil { + t.Fatal(err) + } + tlscert := &tls.Certificate{ + Certificate: [][]byte{pub}, + PrivateKey: pk, + } + + man := &Manager{Prompt: AcceptTOS, Cache: newMemCache(t)} + defer man.stopRenew() + if err := man.cachePut(context.Background(), exampleCertKey, tlscert); err != nil { + t.Fatalf("man.cachePut: %v", err) + } + + hello := clientHelloInfo(exampleDomain, algECDSA) + gotCert := testGetCertificate(t, man, exampleDomain, hello) + if gotCert.SerialNumber.Cmp(serial) != 0 { + t.Error("good certificate was replaced") + } +} + +func TestGetCertificate_revokedLetsEncrypt(t *testing.T) { + // Make a presumably revoked Let's Encrypt cert and cache it. + pk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + t.Fatal(err) + } + issuer := &x509.Certificate{ + Subject: pkix.Name{Country: []string{"US"}, + Organization: []string{"Let's Encrypt"}, CommonName: "R3"}, + } + serial := randomSerial() + tmpl := &x509.Certificate{ + SerialNumber: serial, + DNSNames: []string{exampleDomain}, + NotBefore: time.Date(2022, time.January, 1, 0, 0, 0, 0, time.UTC), + NotAfter: time.Date(2122, time.January, 1, 0, 0, 0, 0, time.UTC), + } + pub, err := x509.CreateCertificate(rand.Reader, tmpl, issuer, &pk.PublicKey, pk) + if err != nil { + t.Fatal(err) + } + tlscert := &tls.Certificate{ + Certificate: [][]byte{pub}, + PrivateKey: pk, + } + + man := &Manager{Prompt: AcceptTOS, Cache: newMemCache(t)} + defer man.stopRenew() + if err := man.cachePut(context.Background(), exampleCertKey, tlscert); err != nil { + t.Fatalf("man.cachePut: %v", err) + } + + // The presumably revoked cached cert should trigger a new cert issuance + // and return without an error. + hello := clientHelloInfo(exampleDomain, algECDSA) + gotCert := testGetCertificate(t, man, exampleDomain, hello) + if gotCert.SerialNumber.Cmp(serial) == 0 { + t.Error("certificate was not replaced") + } } func TestGetCertificate_failedAttempt(t *testing.T) { @@ -441,7 +570,7 @@ } tmpl := &x509.Certificate{ SerialNumber: big.NewInt(1), - Subject: pkix.Name{CommonName: exampleDomain}, + DNSNames: []string{exampleDomain}, NotAfter: time.Now().Add(90 * 24 * time.Hour), } pub, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, &pk.PublicKey, pk) @@ -599,7 +728,7 @@ // tests man.GetCertificate flow using the provided hello argument. // The domain argument is the expected domain name of a certificate request. -func testGetCertificate(t *testing.T, man *Manager, domain string, hello *tls.ClientHelloInfo) { +func testGetCertificate(t *testing.T, man *Manager, domain string, hello *tls.ClientHelloInfo) *x509.Certificate { url, finish := startACMEServerStub(t, tokenCertFn(man, algECDSA), domain) defer finish() man.Client = &acme.Client{DirectoryURL: url} @@ -633,6 +762,7 @@ t.Errorf("cert.DNSNames = %v; want %q", cert.DNSNames, domain) } + return cert } func TestVerifyHTTP01(t *testing.T) {