From a9f5328eafb85bd8f6e255cddb012df1ab314eac Mon Sep 17 00:00:00 2001 From: Shiloh Heurich Date: Fri, 16 Feb 2024 17:12:20 -0500 Subject: [PATCH] feat(challenge): add dns-account-01 support from draft rfc --- challenge_test.go | 4 ++-- misc_test.go | 17 +++++++++++++++++ types.go | 7 ++++--- utility_test.go | 45 ++++++++++++++++++++++++++++++++++++++++----- 4 files changed, 63 insertions(+), 10 deletions(-) diff --git a/challenge_test.go b/challenge_test.go index 78412c5..7597f73 100644 --- a/challenge_test.go +++ b/challenge_test.go @@ -32,8 +32,8 @@ func TestClient_UpdateChallenge(t *testing.T) { chal := auth.ChallengeMap[ChallengeTypeDNS01] - preChallenge(auth, chal) - defer postChallenge(auth, chal) + preChallenge(account, auth, chal) + defer postChallenge(account, auth, chal) updatedChal, err := testClient.UpdateChallenge(account, chal) if err != nil { diff --git a/misc_test.go b/misc_test.go index 8238d5b..4ed1bf7 100644 --- a/misc_test.go +++ b/misc_test.go @@ -18,3 +18,20 @@ func TestWildcard(t *testing.T) { t.Fatalf("error verifying hostname %s: %v", d, err) } } + +func TestWildcardDNSAccount(t *testing.T) { + d := "*." + randString() + ".com" + account, order, _ := makeOrderFinalised(t, []string{ChallengeTypeDNSAccount01}, Identifier{Type: "dns", Value: d}) + + certs, err := testClient.FetchCertificates(account, order.Certificate) + if err != nil { + t.Fatalf("error fetch cert: %v", err) + } + if len(certs) == 0 { + t.Fatal("no certs") + } + + if err := certs[0].VerifyHostname(d); err != nil { + t.Fatalf("error verifying hostname %s: %v", d, err) + } +} diff --git a/types.go b/types.go index f457105..ea79d00 100644 --- a/types.go +++ b/types.go @@ -14,9 +14,10 @@ var ErrUnsupportedKey = errors.New("acme: unknown key type; only RSA and ECDSA a // Different possible challenge types provided by an ACME server. // See https://tools.ietf.org/html/rfc8555#section-9.7.8 const ( - ChallengeTypeDNS01 = "dns-01" - ChallengeTypeHTTP01 = "http-01" - ChallengeTypeTLSALPN01 = "tls-alpn-01" + ChallengeTypeDNS01 = "dns-01" + ChallengeTypeDNSAccount01 = "dns-account-01" + ChallengeTypeHTTP01 = "http-01" + ChallengeTypeTLSALPN01 = "tls-alpn-01" // ChallengeTypeTLSSNI01 is deprecated and should not be used. // See: https://community.letsencrypt.org/t/important-what-you-need-to-know-about-tls-sni-validation-issues/50811 diff --git a/utility_test.go b/utility_test.go index dc8dc72..b4345cc 100644 --- a/utility_test.go +++ b/utility_test.go @@ -6,9 +6,11 @@ import ( "crypto/ecdsa" "crypto/elliptic" crand "crypto/rand" + "crypto/sha256" "crypto/tls" "crypto/x509" "crypto/x509/pkix" + "encoding/base32" "encoding/json" "encoding/pem" "fmt" @@ -169,7 +171,7 @@ func makeOrderFinalised(t *testing.T, supportedChalTypes []string, identifiers . chalType := supportedChalTypes[mrand.Intn(len(supportedChalTypes))] chal, ok := auth.ChallengeMap[chalType] if !ok { - t.Fatalf("No supported challenge %q (%v) in challenges: %v", chalType, supportedChalTypes, auth.ChallengeTypes) + t.Skipf("skipping, no supported challenge %q (%v) in challenges: %v", chalType, supportedChalTypes, auth.ChallengeTypes) } if chal.Status == "valid" { @@ -179,8 +181,8 @@ func makeOrderFinalised(t *testing.T, supportedChalTypes []string, identifiers . t.Fatalf("unexpected status %q on challenge: %+v", chal.Status, chal) } - preChallenge(auth, chal) - defer postChallenge(auth, chal) + preChallenge(acct, auth, chal) + defer postChallenge(acct, auth, chal) updatedChal, err := testClient.UpdateChallenge(acct, chal) if err != nil { @@ -254,7 +256,7 @@ func doPost(name string, req interface{}) { } } -func preChallenge(auth Authorization, chal Challenge) { +func preChallenge(acct Account, auth Authorization, chal Challenge) { switch chal.Type { case ChallengeTypeDNS01: setReq := struct { @@ -266,6 +268,23 @@ func preChallenge(auth Authorization, chal Challenge) { } doPost("set-txt", setReq) + case ChallengeTypeDNSAccount01: + acctHash := sha256.Sum256([]byte(acct.URL)) + acctLabel := strings.ToLower(base32.StdEncoding.EncodeToString(acctHash[0:10])) + scope := "host" + if auth.Wildcard { + scope = "wildcard" + } + setReq := struct { + Host string `json:"host"` + Value string `json:"value"` + }{ + Host: "_" + acctLabel + "._acme-" + scope + "-challenge." + + auth.Identifier.Value + ".", + Value: EncodeDNS01KeyAuthorization(chal.KeyAuthorization), + } + doPost("set-txt", setReq) + case ChallengeTypeHTTP01: addReq := struct { Token string `json:"token"` @@ -291,7 +310,7 @@ func preChallenge(auth Authorization, chal Challenge) { } } -func postChallenge(auth Authorization, chal Challenge) { +func postChallenge(acct Account, auth Authorization, chal Challenge) { switch chal.Type { case ChallengeTypeDNS01: host := "_acme-challenge." + auth.Identifier.Value + "." @@ -302,6 +321,22 @@ func postChallenge(auth Authorization, chal Challenge) { } doPost("clear-txt", clearReq) + case ChallengeTypeDNSAccount01: + acctHash := sha256.Sum256([]byte(acct.URL)) + acctLabel := strings.ToLower(base32.StdEncoding.EncodeToString(acctHash[0:10])) + scope := "host" + if auth.Wildcard { + scope = "wildcard" + } + host := "_" + acctLabel + "._acme-" + scope + "-challenge." + + auth.Identifier.Value + "." + clearReq := struct { + Host string `json:"host"` + }{ + Host: host, + } + doPost("clear-txt", clearReq) + case ChallengeTypeHTTP01: delReq := struct { Token string `json:"token"`