aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJason Baker <jason-baker@users.noreply.github.com>2022-05-17 19:35:58 +0000
committerGopher Robot <gobot@golang.org>2022-05-18 03:45:28 +0000
commit6f7dac9698988af7b704298c9fd8adf58e1d30c0 (patch)
tree89ba517c8422066babbaae299c5603c374122595
parent85d78b3ac167a1b71e0e1e6a2dbce27479d572be (diff)
downloadgo-x-crypto-6f7dac9698988af7b704298c9fd8adf58e1d30c0.tar.xz
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>
-rw-r--r--acme/rfc8555.go3
-rw-r--r--acme/rfc8555_test.go103
2 files changed, 106 insertions, 0 deletions
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 @@ import (
//
// 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 @@ import (
"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_AccountKeyRollover(t *testing.T) {
}
}
+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) {