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) {