aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJames Hartig <fastest963@gmail.com>2020-12-11 22:52:14 -0500
committerFilippo Valsorda <filippo@golang.org>2021-09-21 15:51:07 +0000
commit089bfa5675191fd96a44247682f76ebca03d7916 (patch)
treed26f50954113cc7d02d918c073af488fd51a4de5
parent84f357641f6309f0bc8d2d3f445107e46d6a5101 (diff)
downloadgo-x-crypto-089bfa5675191fd96a44247682f76ebca03d7916.tar.xz
acme: implement Client.ListCertAlternates
Let's Encrypt is defaulting to a longer cross-signed chain on May 4th, 2021 but will offer the ability to download the shorter chain via an alternate URL via a link header [1]. The shorter chain can be selected to workaround a validation bug in legacy versions of OpenSSL, GnuTLS, and LibreSSL. The alternate relation is described in section 7.4.2 of RFC 8555. ListCertAlternates should be passed the original certificate chain URL and will return a list of alternate chain URLs that can be passed to FetchCert to download. Fixes golang/go#42437 [1] https://community.letsencrypt.org/t/production-chain-changes/150739 Change-Id: Iaa32e49cb1322ac79ac1a5b4b7980d5401f4b86e Reviewed-on: https://go-review.googlesource.com/c/crypto/+/277294 Trust: Filippo Valsorda <filippo@golang.org> Run-TryBot: Filippo Valsorda <filippo@golang.org> Reviewed-by: Roland Shoemaker <roland@golang.org> TryBot-Result: Go Bot <gobot@golang.org>
-rw-r--r--acme/rfc8555.go26
-rw-r--r--acme/rfc8555_test.go32
2 files changed, 58 insertions, 0 deletions
diff --git a/acme/rfc8555.go b/acme/rfc8555.go
index 073cee5..f9d3011 100644
--- a/acme/rfc8555.go
+++ b/acme/rfc8555.go
@@ -410,3 +410,29 @@ func isAlreadyRevoked(err error) bool {
e, ok := err.(*Error)
return ok && e.ProblemType == "urn:ietf:params:acme:error:alreadyRevoked"
}
+
+// ListCertAlternates retrieves any alternate certificate chain URLs for the
+// given certificate chain URL. These alternate URLs can be passed to FetchCert
+// in order to retrieve the alternate certificate chains.
+//
+// If there are no alternate issuer certificate chains, a nil slice will be
+// returned.
+func (c *Client) ListCertAlternates(ctx context.Context, url string) ([]string, error) {
+ if _, err := c.Discover(ctx); err != nil { // required by c.accountKID
+ return nil, err
+ }
+
+ res, err := c.postAsGet(ctx, url, wantStatus(http.StatusOK))
+ if err != nil {
+ return nil, err
+ }
+ defer res.Body.Close()
+
+ // We don't need the body but we need to discard it so we don't end up
+ // preventing keep-alive
+ if _, err := io.Copy(ioutil.Discard, res.Body); err != nil {
+ return nil, fmt.Errorf("acme: cert alternates response stream: %v", err)
+ }
+ alts := linkHeader(res.Header, "alternate")
+ return alts, nil
+}
diff --git a/acme/rfc8555_test.go b/acme/rfc8555_test.go
index c35118e..07e2f29 100644
--- a/acme/rfc8555_test.go
+++ b/acme/rfc8555_test.go
@@ -882,3 +882,35 @@ func TestRFC_AlreadyRevokedCert(t *testing.T) {
t.Fatalf("RevokeCert: %v", err)
}
}
+
+func TestRFC_ListCertAlternates(t *testing.T) {
+ s := newACMEServer()
+ s.handle("/crt", func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Content-Type", "application/pem-certificate-chain")
+ w.Header().Add("Link", `<https://example.com/crt/2>;rel="alternate"`)
+ w.Header().Add("Link", `<https://example.com/crt/3>; rel="alternate"`)
+ w.Header().Add("Link", `<https://example.com/acme>; rel="index"`)
+ })
+ s.handle("/crt2", func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Content-Type", "application/pem-certificate-chain")
+ })
+ s.start()
+ defer s.close()
+
+ cl := &Client{Key: testKeyEC, DirectoryURL: s.url("/")}
+ crts, err := cl.ListCertAlternates(context.Background(), s.url("/crt"))
+ if err != nil {
+ t.Fatalf("ListCertAlternates: %v", err)
+ }
+ want := []string{"https://example.com/crt/2", "https://example.com/crt/3"}
+ if !reflect.DeepEqual(crts, want) {
+ t.Errorf("ListCertAlternates(/crt): %v; want %v", crts, want)
+ }
+ crts, err = cl.ListCertAlternates(context.Background(), s.url("/crt2"))
+ if err != nil {
+ t.Fatalf("ListCertAlternates: %v", err)
+ }
+ if crts != nil {
+ t.Errorf("ListCertAlternates(/crt2): %v; want nil", crts)
+ }
+}