acme: DeactivateReg fix panic
Currently discover is not called which results in a panic if just a key
is added to an ACME client and then deactivation is attempted.
This patch adds a discover call as well as missing unit tests for the
API.
Change-Id: I0719e5376eb2fccf62182e5f91e5b5eaa7bdd518
GitHub-Last-Rev: 501d7c6c1b75a3069dcad4254b4d4a0d2ccb02c8
GitHub-Pull-Request: golang/crypto#217
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/406734
TryBot-Result: Gopher Robot <gobot@golang.org>
Auto-Submit: Roland Shoemaker <roland@golang.org>
Reviewed-by: Roland Shoemaker <roland@golang.org>
Run-TryBot: Roland Shoemaker <roland@golang.org>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
diff --git a/acme/rfc8555.go b/acme/rfc8555.go
index 320d83b..940e70b 100644
--- a/acme/rfc8555.go
+++ b/acme/rfc8555.go
@@ -24,6 +24,9 @@
//
// It only works with CAs implementing RFC 8555.
func (c *Client) DeactivateReg(ctx context.Context) error {
+ if _, err := c.Discover(ctx); err != nil { // required by c.accountKID
+ return err
+ }
url := string(c.accountKID(ctx))
if url == "" {
return ErrNoAccount
diff --git a/acme/rfc8555_test.go b/acme/rfc8555_test.go
index 7a53608..6ea77df 100644
--- a/acme/rfc8555_test.go
+++ b/acme/rfc8555_test.go
@@ -15,12 +15,14 @@
"encoding/base64"
"encoding/json"
"encoding/pem"
+ "errors"
"fmt"
"io/ioutil"
"math/big"
"net/http"
"net/http/httptest"
"reflect"
+ "strings"
"sync"
"testing"
"time"
@@ -644,6 +646,107 @@
}
}
+func TestRFC_DeactivateReg(t *testing.T) {
+ const email = "mailto:user@example.org"
+ curStatus := StatusValid
+
+ type account struct {
+ Status string `json:"status"`
+ Contact []string `json:"contact"`
+ AcceptTOS bool `json:"termsOfServiceAgreed"`
+ Orders string `json:"orders"`
+ }
+
+ s := newACMEServer()
+ s.handle("/acme/new-account", func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Location", s.url("/accounts/1"))
+ w.WriteHeader(http.StatusOK) // 200 means existing account
+ json.NewEncoder(w).Encode(account{
+ Status: curStatus,
+ Contact: []string{email},
+ AcceptTOS: true,
+ Orders: s.url("/accounts/1/orders"),
+ })
+
+ b, _ := ioutil.ReadAll(r.Body) // check err later in decodeJWSxxx
+ head, err := decodeJWSHead(bytes.NewReader(b))
+ if err != nil {
+ t.Errorf("decodeJWSHead: %v", err)
+ return
+ }
+ if len(head.JWK) == 0 {
+ t.Error("head.JWK is empty")
+ }
+
+ var req struct {
+ Status string `json:"status"`
+ Contact []string `json:"contact"`
+ AcceptTOS bool `json:"termsOfServiceAgreed"`
+ OnlyExisting bool `json:"onlyReturnExisting"`
+ }
+ decodeJWSRequest(t, &req, bytes.NewReader(b))
+ if !req.OnlyExisting {
+ t.Errorf("req.OnlyReturnExisting = %t; want = %t", req.OnlyExisting, true)
+ }
+ })
+ s.handle("/accounts/1", func(w http.ResponseWriter, r *http.Request) {
+ if curStatus == StatusValid {
+ curStatus = StatusDeactivated
+ w.WriteHeader(http.StatusOK)
+ } else {
+ s.error(w, &wireError{
+ Status: http.StatusUnauthorized,
+ Type: "urn:ietf:params:acme:error:unauthorized",
+ })
+ }
+ var req account
+ b, _ := ioutil.ReadAll(r.Body) // check err later in decodeJWSxxx
+ head, err := decodeJWSHead(bytes.NewReader(b))
+ if err != nil {
+ t.Errorf("decodeJWSHead: %v", err)
+ return
+ }
+ if len(head.JWK) != 0 {
+ t.Error("head.JWK is not empty")
+ }
+ if !strings.HasSuffix(head.KID, "/accounts/1") {
+ t.Errorf("head.KID = %q; want suffix /accounts/1", head.KID)
+ }
+
+ decodeJWSRequest(t, &req, bytes.NewReader(b))
+ if req.Status != StatusDeactivated {
+ t.Errorf("req.Status = %q; want = %q", req.Status, StatusDeactivated)
+ }
+ })
+ s.start()
+ defer s.close()
+
+ cl := &Client{Key: testKeyEC, DirectoryURL: s.url("/")}
+ if err := cl.DeactivateReg(context.Background()); err != nil {
+ t.Errorf("DeactivateReg: %v, wanted no error", err)
+ }
+ if err := cl.DeactivateReg(context.Background()); err == nil {
+ t.Errorf("DeactivateReg: %v, wanted error for unauthorized", err)
+ }
+}
+
+func TestRF_DeactivateRegNoAccount(t *testing.T) {
+ s := newACMEServer()
+ s.handle("/acme/new-account", func(w http.ResponseWriter, r *http.Request) {
+ s.error(w, &wireError{
+ Status: http.StatusBadRequest,
+ Type: "urn:ietf:params:acme:error:accountDoesNotExist",
+ })
+ })
+ s.start()
+ defer s.close()
+
+ cl := &Client{Key: testKeyEC, DirectoryURL: s.url("/")}
+ if err := cl.DeactivateReg(context.Background()); !errors.Is(err, ErrNoAccount) {
+ t.Errorf("DeactivateReg: %v, wanted ErrNoAccount", err)
+ }
+}
+
func TestRFC_AuthorizeOrder(t *testing.T) {
s := newACMEServer()
s.handle("/acme/new-account", func(w http.ResponseWriter, r *http.Request) {