From 9fbab14f903f89e23047b5971369b86380230e56 Mon Sep 17 00:00:00 2001 From: Alex Vaghin Date: Tue, 16 Aug 2016 16:28:56 +0200 Subject: [PATCH] acme: add Client.RevokeCert for certificate revocation This change adds RevokeCert method to Client, implemented as described in https://tools.ietf.org/html/draft-ietf-acme-acme-02#section-6.6 and https://tools.ietf.org/html/draft-ietf-acme-acme-03#section-6.5. Change-Id: Ib0defbc299b46268c0f36dbe0f29f0081a3598d5 Reviewed-on: https://go-review.googlesource.com/27092 Reviewed-by: Brad Fitzpatrick --- acme/internal/acme/acme.go | 34 +++++++++++++++++++++++++++++++ acme/internal/acme/acme_test.go | 36 +++++++++++++++++++++++++++++++++ acme/internal/acme/types.go | 17 ++++++++++++++++ 3 files changed, 87 insertions(+) diff --git a/acme/internal/acme/acme.go b/acme/internal/acme/acme.go index a5554ff..76324b6 100644 --- a/acme/internal/acme/acme.go +++ b/acme/internal/acme/acme.go @@ -210,6 +210,40 @@ func (c *Client) FetchCert(ctx context.Context, url string, bundle bool) ([][]by } } +// RevokeCert revokes a previously issued certificate cert, provided in DER format. +// +// The key argument, used to sign the request, must be authorized +// to revoke the certificate. It's up to the CA to decide which keys are authorized. +// For instance, the key pair of the certificate may be authorized. +// If the key is nil, c.Key is used instead. +func (c *Client) RevokeCert(ctx context.Context, key crypto.Signer, cert []byte, reason CRLReasonCode) error { + if _, err := c.Discover(ctx); err != nil { + return err + } + + body := &struct { + Resource string `json:"resource"` + Cert string `json:"certificate"` + Reason int `json:"reason"` + }{ + Resource: "revoke-cert", + Cert: base64.RawURLEncoding.EncodeToString(cert), + Reason: int(reason), + } + if key == nil { + key = c.Key + } + res, err := postJWS(ctx, c.HTTPClient, key, c.dir.RevokeURL, body) + if err != nil { + return err + } + defer res.Body.Close() + if res.StatusCode != http.StatusOK { + return responseError(res) + } + return nil +} + // AcceptTOS always returns true to indicate the acceptance of a CA's Terms of Service // during account registration. See Register method of Client for more details. func AcceptTOS(tosURL string) bool { return true } diff --git a/acme/internal/acme/acme_test.go b/acme/internal/acme/acme_test.go index 56a69ae..4568028 100644 --- a/acme/internal/acme/acme_test.go +++ b/acme/internal/acme/acme_test.go @@ -745,6 +745,42 @@ func TestFetchCertSize(t *testing.T) { } } +func TestRevokeCert(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method == "HEAD" { + w.Header().Set("replay-nonce", "nonce") + return + } + + var req struct { + Resource string + Certificate string + Reason int + } + decodeJWSRequest(t, &req, r) + if req.Resource != "revoke-cert" { + t.Errorf("req.Resource = %q; want revoke-cert", req.Resource) + } + if req.Reason != 1 { + t.Errorf("req.Reason = %d; want 1", req.Reason) + } + // echo -n cert | base64 | tr -d '=' | tr '/+' '_-' + cert := "Y2VydA" + if req.Certificate != cert { + t.Errorf("req.Certificate = %q; want %q", req.Certificate, cert) + } + })) + defer ts.Close() + client := &Client{ + Key: testKey, + dir: &Directory{RevokeURL: ts.URL}, + } + ctx := context.Background() + if err := client.RevokeCert(ctx, nil, []byte("cert"), CRLReasonKeyCompromise); err != nil { + t.Fatal(err) + } +} + func TestFetchNonce(t *testing.T) { tests := []struct { code int diff --git a/acme/internal/acme/types.go b/acme/internal/acme/types.go index fb17ed5..612cd01 100644 --- a/acme/internal/acme/types.go +++ b/acme/internal/acme/types.go @@ -16,6 +16,23 @@ const ( StatusRevoked = "revoked" ) +// CRLReasonCode identifies the reason for a certificate revocation. +type CRLReasonCode int + +// CRL reason codes as defined in RFC 5280. +const ( + CRLReasonUnspecified CRLReasonCode = 0 + CRLReasonKeyCompromise CRLReasonCode = 1 + CRLReasonCACompromise CRLReasonCode = 2 + CRLReasonAffiliationChanged CRLReasonCode = 3 + CRLReasonSuperseded CRLReasonCode = 4 + CRLReasonCessationOfOperation CRLReasonCode = 5 + CRLReasonCertificateHold CRLReasonCode = 6 + CRLReasonRemoveFromCRL CRLReasonCode = 8 + CRLReasonPrivilegeWithdrawn CRLReasonCode = 9 + CRLReasonAACompromise CRLReasonCode = 10 +) + // ErrUnsupportedKey is returned when an unsupported key type is encountered. var ErrUnsupportedKey = errors.New("acme: unknown key type; only RSA and ECDSA are supported")