Browse Source

acme: add StatusValid to expected values for Authorize

Let's Encrypt may respond with the "valid" value in status
field to authorization requests:
https://github.com/letsencrypt/boulder/blob/0543691d9e2cbcc1b94baf3b9fab4e64ca2750ef/ra/ra.go#L373-L402

The spec isn't clear on this:
https://tools.ietf.org/html/draft-ietf-acme-acme-02#section-6.4

Either way, this change follows common sense and should not produce
unexpected results.

Change-Id: Iac55aa1fb17d458f6d62e8da875ed86e3e4afbc3
Reviewed-on: https://go-review.googlesource.com/27432
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
Alex Vaghin 9 years ago
parent
commit
1ba5ec0bf2
3 changed files with 27 additions and 1 deletions
  1. 5 0
      acme/autocert/autocert.go
  2. 5 1
      acme/internal/acme/acme.go
  3. 17 0
      acme/internal/acme/acme_test.go

+ 5 - 0
acme/autocert/autocert.go

@@ -404,6 +404,11 @@ func (m *Manager) verify(ctx context.Context, domain string) error {
 	if err != nil {
 		return err
 	}
+	// maybe don't need to at all
+	if authz.Status == acme.StatusValid {
+		return nil
+	}
+
 	// pick a challenge: prefer tls-sni-02 over tls-sni-01
 	// TODO: consider authz.Combinations
 	var chal *acme.Challenge

+ 5 - 1
acme/internal/acme/acme.go

@@ -302,6 +302,10 @@ func (c *Client) UpdateReg(ctx context.Context, a *Account) (*Account, error) {
 // Authorize performs the initial step in an authorization flow.
 // The caller will then need to choose from and perform a set of returned
 // challenges using c.Accept in order to successfully complete authorization.
+//
+// If an authorization has been previously granted, the CA may return
+// a valid authorization (Authorization.Status is StatusValid). If so, the caller
+// need not fulfill any challenge and can proceed to requesting a certificate.
 func (c *Client) Authorize(ctx context.Context, domain string) (*Authorization, error) {
 	if _, err := c.Discover(ctx); err != nil {
 		return nil, err
@@ -331,7 +335,7 @@ func (c *Client) Authorize(ctx context.Context, domain string) (*Authorization,
 	if err := json.NewDecoder(res.Body).Decode(&v); err != nil {
 		return nil, fmt.Errorf("acme: invalid response: %v", err)
 	}
-	if v.Status != StatusPending {
+	if v.Status != StatusPending && v.Status != StatusValid {
 		return nil, fmt.Errorf("acme: unexpected status: %s", v.Status)
 	}
 	return v.authorization(res.Header.Get("Location")), nil

+ 17 - 0
acme/internal/acme/acme_test.go

@@ -368,6 +368,23 @@ func TestAuthorize(t *testing.T) {
 	}
 }
 
+func TestAuthorizeValid(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
+		}
+		w.WriteHeader(http.StatusCreated)
+		w.Write([]byte(`{"status":"valid"}`))
+	}))
+	defer ts.Close()
+	client := Client{Key: testKey, dir: &Directory{AuthzURL: ts.URL}}
+	_, err := client.Authorize(context.Background(), "example.com")
+	if err != nil {
+		t.Errorf("err = %v", err)
+	}
+}
+
 func TestPollAuthz(t *testing.T) {
 	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 		if r.Method != "GET" {