Skip to content

x/crypto/acme/autocert: better error messages for failed validations #60554

Description

@mjl-

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    NeedsInvestigationSomeone must examine and confirm this is a valid issue and not a duplicate of an existing one.

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions