Repository: go
Issue: x/crypto/acme/autocert: better error messages for failed validations
Labels: NeedsInvestigation

### What version of Go are you using (`go version`)?
### Does this issue reproduce with the latest release?

Yes, with latest golang.org/x/crypto/acme/autocert on 2023-06-01.

### What did you do?

I'm using golang.org/x/crypto/acme/autocert in https://github.com/mjl-/mox. A user of mox couldn't get a TLS certificate with autocert through Let's Encrypt. The error message only contained "no viable challenge type found". They had to try different software to see the error messages Let's Encrypt was sending back. Autocert does not expose the errors, even though they describe nicely what the problem was (CAA lookup failed in this case).

### What did you expect to see?

An error message like this:

```
unable to satisfy "https://acme-v02.api.letsencrypt.org/[...]" for domain "domain.example": no viable challenge type found (failures: tls-alpn-01: acme: authorization error for domain.example: 403 urn:ietf:params:acme:error:caa: CAA record for domain.example prevents issuance)
```

### What did you see instead?

```
unable to satisfy "https://acme-v02.api.letsencrypt.org/[...]" for domain "domain.example": no viable challenge type found 
```

### Suggested patch

```
diff --git a/vendor/golang.org/x/crypto/acme/autocert/autocert.go b/vendor/golang.org/x/crypto/acme/autocert/autocert.go
index 6b4cdf4..d3fca39 100644
--- a/vendor/golang.org/x/crypto/acme/autocert/autocert.go
+++ b/vendor/golang.org/x/crypto/acme/autocert/autocert.go
@@ -694,7 +694,8 @@ func (m *Manager) verifyRFC(ctx context.Context, client *acme.Client, domain str
 	// all order authorizations: if we've tried a challenge type once and it didn't work,
 	// it will most likely not work on another order's authorization either.
 	challengeTypes := m.supportedChallengeTypes()
-	nextTyp := 0 // challengeTypes index
+	nextTyp := 0         // challengeTypes index
+	var authErrs []error // Validation errors, possibly hinting at a solution.
 AuthorizeOrderLoop:
 	for {
 		o, err := client.AuthorizeOrder(ctx, acme.DomainIDs(domain))
@@ -735,18 +736,29 @@ AuthorizeOrderLoop:
 				nextTyp++
 			}
 			if chal == nil {
-				return nil, fmt.Errorf("acme/autocert: unable to satisfy %q for domain %q: no viable challenge type found", z.URI, domain)
+				details := ""
+				if len(authErrs) > 0 {
+					details = " (failures: " + authErrs[0].Error()
+					for _, err := range authErrs[1:] {
+						details += "; " + err.Error()
+					}
+					details += ")"
+				}
+				return nil, fmt.Errorf("acme/autocert: unable to satisfy %q for domain %q: no viable challenge type found%s", z.URI, domain, details)
 			}
 			// Respond to the challenge and wait for validation result.
 			cleanup, err := m.fulfill(ctx, client, chal, domain)
 			if err != nil {
+				authErrs = append(authErrs, fmt.Errorf("challenge %s: preparing response for validation: %v", chal.Type, err))
 				continue AuthorizeOrderLoop
 			}
 			defer cleanup()
 			if _, err := client.Accept(ctx, chal); err != nil {
+				authErrs = append(authErrs, fmt.Errorf("challenge %s: requesting validation: %v", chal.Type, err))
 				continue AuthorizeOrderLoop
 			}
 			if _, err := client.WaitAuthorization(ctx, z.URI); err != nil {
+				authErrs = append(authErrs, fmt.Errorf("challenge %s: %v", chal.Type, err))
 				continue AuthorizeOrderLoop
 			}
 		}
@@ -755,6 +767,7 @@ AuthorizeOrderLoop:
 		// Wait for the CA to update the order status.
 		o, err = client.WaitOrder(ctx, o.URI)
 		if err != nil {
+			authErrs = append(authErrs, fmt.Errorf("waiting for order: %v", err))
 			continue AuthorizeOrderLoop
 		}
 		return o, nil

```