From a5401df9c6d7b989dd37b626d38ea5b727f41e12 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Fri, 8 Dec 2017 09:47:51 -0800 Subject: [PATCH] acmeapi: Expose more fields on Challenge If manually using acmeapi and you get an invalid challenge, it's nice to be able to see the specific error on the challenge. Added the other fields that I see on actual challenges today as well. Field names inspired by those used in boulder. --- acmeapi/types.go | 29 +++++++++++++++++++++++++++-- acmeapi/types_test.go | 42 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 2 deletions(-) diff --git a/acmeapi/types.go b/acmeapi/types.go index cddbea5..5b8fade 100644 --- a/acmeapi/types.go +++ b/acmeapi/types.go @@ -3,9 +3,11 @@ package acmeapi import ( "encoding/json" "fmt" - denet "github.com/hlandau/goutils/net" - "gopkg.in/square/go-jose.v1" + "net" "time" + + denet "github.com/hlandau/goutils/net" + jose "gopkg.in/square/go-jose.v1" ) // Represents an account registration. @@ -28,6 +30,25 @@ type Registration struct { LatestAgreementURI string `json:"-"` } +// Represents an error that may have happened. +// https://tools.ietf.org/html/draft-ietf-appsawg-http-problem-00 +type ProblemDetails struct { + Type string `json:"type,omitempty"` + Detail string `json:"detail,omitempty"` + HTTPStatus int `json:"status,omitempty"` +} + +// Represents a single validation attempt. +type ValidationRecord struct { + Authorities []string `json:",omitempty"` + URL string `json:"url,omitempty"` + Hostname string `json:"hostname"` + Port string `json:"port"` + AddressesResolved []net.IP `json:"addressesResolved"` + AddressUsed net.IP `json:"addressUsed"` + AddressesTried []net.IP `json:"addressesTried"` +} + // Represents a Challenge which is part of an Authorization. type Challenge struct { URI string `json:"uri"` // The URI of the challenge. @@ -41,6 +62,10 @@ type Challenge struct { // proofOfPossession Certs []denet.Base64up `json:"certs,omitempty"` + Error *ProblemDetails `json:"error,omitempty"` + ProvidedKeyAuthorization string `json:"keyAuthorization,omitempty"` + ValidationRecord []ValidationRecord `json:"validationRecord,omitempty"` + retryAt time.Time } diff --git a/acmeapi/types_test.go b/acmeapi/types_test.go index 910f476..df34be8 100644 --- a/acmeapi/types_test.go +++ b/acmeapi/types_test.go @@ -1,7 +1,9 @@ package acmeapi import ( + "bytes" "encoding/json" + "net" "testing" ) @@ -19,3 +21,43 @@ func TestStatus(t *testing.T) { t.Fatal() } } + +func TestChallenge(t *testing.T) { + const cJSON = `{ + "type": "http-01", + "status": "invalid", + "error": { + "type": "urn:acme:error:caa", + "detail": "CAA record for mymonash2021.conference.monash.edu prevents issuance", + "status": 403 + }, + "uri": "https://acme-v01.api.letsencrypt.org/acme/challenge/wL4hNlUUJtGoMp6QeavoaAZjbqmBgJk2FMpOSC1aoIU/2676511905", + "token": "GMgoj5xYX7qSIfN9GdmyqhdAHYrCco_Md9kKrT8v0jE", + "keyAuthorization": "GMgoj5xYX7qSIfN9GdmyqhdAHYrCco_Md9kKrT8v0jE.QRRvz3cNxWGJObT4gl6G9ZNx-4cXE2eK81kX5lpYzmo", + "validationRecord": [ + { + "url": "http://mysite.foo.com/.well-known/acme-challenge/GMgoj5xYX7qSIfN9GdmyqHdAHYrCco_Md9kKrT8v0jE", + "hostname": "mysite.foo.com", + "port": "80", + "addressesResolved": [ + "54.85.70.226", + "52.21.26.68", + "54.210.179.160", + "52.1.9.49" + ], + "addressUsed": "54.85.70.226", + "addressesTried": [] + } + ] +}` + var c Challenge + if err := json.Unmarshal([]byte(cJSON), &c); err != nil { + t.Fatalf("%v", err) + } + if g, e := c.Error.Type, "urn:acme:error:caa"; g != e { + t.Fatalf("%v != %v", g, e) + } + if g, e := c.ValidationRecord[0].AddressesResolved[1], net.IPv4(52, 21, 26, 68); !bytes.Equal(g, e) { + t.Fatalf("%v != %v", g, e) + } +}