acme: make WaitAuthorization return authorization errors consistently

Fixes golang/go#37340

Change-Id: I19c4f150b8607ad4a1613cf97ad3362f4b779d7c
GitHub-Last-Rev: 4215964b4a680b135301695ccd56cff88a8ffb26
GitHub-Pull-Request: golang/crypto#121
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/220343
Reviewed-by: Filippo Valsorda <filippo@golang.org>
diff --git a/acme/acme_test.go b/acme/acme_test.go
index e2f446f..de8bea0 100644
--- a/acme/acme_test.go
+++ b/acme/acme_test.go
@@ -569,6 +569,45 @@
 			t.Errorf("err is %v (%T); want non-nil *AuthorizationError", err, err)
 		}
 	})
+	t.Run("invalid status with error returns the authorization error", func(t *testing.T) {
+		_, err := runWaitAuthorization(context.Background(), t, func(w http.ResponseWriter, r *http.Request) {
+			fmt.Fprintf(w, `{
+				"type": "dns-01",
+				"status": "invalid",
+				"error": {
+				  "type": "urn:ietf:params:acme:error:caa",
+				  "detail": "CAA record for <domain> prevents issuance",
+				  "status": 403
+				},
+				"url": "https://acme-v02.api.letsencrypt.org/acme/chall-v3/xxx/xxx",
+				"token": "xxx",
+				"validationRecord": [
+				  {
+					"hostname": "<domain>"
+				  }
+				]
+			  }`)
+		})
+
+		want := &AuthorizationError{
+			Errors: []error{
+				(&wireError{
+					Status: 403,
+					Type:   "urn:ietf:params:acme:error:caa",
+					Detail: "CAA record for <domain> prevents issuance",
+				}).error(nil),
+			},
+		}
+
+		_, ok := err.(*AuthorizationError)
+		if !ok {
+			t.Errorf("err is %T; want non-nil *AuthorizationError", err)
+		}
+
+		if err.Error() != want.Error() {
+			t.Errorf("err is %v; want %v", err, want)
+		}
+	})
 	t.Run("non-retriable error", func(t *testing.T) {
 		const code = http.StatusBadRequest
 		_, err := runWaitAuthorization(context.Background(), t, func(w http.ResponseWriter, r *http.Request) {
diff --git a/acme/types.go b/acme/types.go
index 9c59097..e959caf 100644
--- a/acme/types.go
+++ b/acme/types.go
@@ -102,7 +102,12 @@
 	for i, err := range a.Errors {
 		e[i] = err.Error()
 	}
-	return fmt.Sprintf("acme: authorization error for %s: %s", a.Identifier, strings.Join(e, "; "))
+
+	if a.Identifier != "" {
+		return fmt.Sprintf("acme: authorization error for %s: %s", a.Identifier, strings.Join(e, "; "))
+	}
+
+	return fmt.Sprintf("acme: authorization error: %s", strings.Join(e, "; "))
 }
 
 // OrderError is returned from Client's order related methods.
@@ -407,6 +412,7 @@
 	Wildcard     bool
 	Challenges   []wireChallenge
 	Combinations [][]int
+	Error        *wireError
 }
 
 func (z *wireAuthz) authorization(uri string) *Authorization {
@@ -430,11 +436,17 @@
 		URI:        uri,
 		Identifier: z.Identifier.Value,
 	}
+
+	if z.Error != nil {
+		err.Errors = append(err.Errors, z.Error.error(nil))
+	}
+
 	for _, raw := range z.Challenges {
 		if raw.Error != nil {
 			err.Errors = append(err.Errors, raw.Error.error(nil))
 		}
 	}
+
 	return err
 }
 
diff --git a/acme/types_test.go b/acme/types_test.go
index a7553e6..40ef20b 100644
--- a/acme/types_test.go
+++ b/acme/types_test.go
@@ -61,3 +61,46 @@
 		}
 	}
 }
+
+func TestAuthorizationError(t *testing.T) {
+	tests := []struct {
+		desc string
+		err  *AuthorizationError
+		msg  string
+	}{
+		{
+			desc: "when auth error identifier is set",
+			err: &AuthorizationError{
+				Identifier: "domain.com",
+				Errors: []error{
+					(&wireError{
+						Status: 403,
+						Type:   "urn:ietf:params:acme:error:caa",
+						Detail: "CAA record for domain.com prevents issuance",
+					}).error(nil),
+				},
+			},
+			msg: "acme: authorization error for domain.com: 403 urn:ietf:params:acme:error:caa: CAA record for domain.com prevents issuance",
+		},
+
+		{
+			desc: "when auth error identifier is unset",
+			err: &AuthorizationError{
+				Errors: []error{
+					(&wireError{
+						Status: 403,
+						Type:   "urn:ietf:params:acme:error:caa",
+						Detail: "CAA record for domain.com prevents issuance",
+					}).error(nil),
+				},
+			},
+			msg: "acme: authorization error: 403 urn:ietf:params:acme:error:caa: CAA record for domain.com prevents issuance",
+		},
+	}
+
+	for _, tt := range tests {
+		if tt.err.Error() != tt.msg {
+			t.Errorf("got: %s\nwant: %s", tt.err, tt.msg)
+		}
+	}
+}