Skip to content

Commit

Permalink
feat(challenge): add dns-account-01 challenge
Browse files Browse the repository at this point in the history
  • Loading branch information
sheurich committed Mar 9, 2024
1 parent c449dc1 commit 7d4bed3
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 22 deletions.
7 changes: 4 additions & 3 deletions acme/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,10 @@ const (
IdentifierDNS = "dns"
IdentifierIP = "ip"

ChallengeHTTP01 = "http-01"
ChallengeTLSALPN01 = "tls-alpn-01"
ChallengeDNS01 = "dns-01"
ChallengeHTTP01 = "http-01"
ChallengeTLSALPN01 = "tls-alpn-01"
ChallengeDNS01 = "dns-01"
ChallengeDNSAccount01 = "dns-account-01"

HTTP01BaseURL = ".well-known/acme-challenge/"

Expand Down
53 changes: 52 additions & 1 deletion va/va.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"crypto/tls"
"crypto/x509"
"encoding/asn1"
"encoding/base32"
"encoding/base64"
"fmt"
"io"
Expand Down Expand Up @@ -92,6 +93,8 @@ type vaTask struct {
Identifier acme.Identifier
Challenge *core.Challenge
Account *core.Account
AccountURL string
Wildcard bool
}

type VAImpl struct {
Expand Down Expand Up @@ -157,11 +160,13 @@ func New(
return va
}

func (va VAImpl) ValidateChallenge(ident acme.Identifier, chal *core.Challenge, acct *core.Account) {
func (va VAImpl) ValidateChallenge(ident acme.Identifier, chal *core.Challenge, acct *core.Account, acctURL string, wildcard bool) {
task := &vaTask{
Identifier: ident,
Challenge: chal,
Account: acct,
AccountURL: acctURL,
Wildcard: wildcard,
}
// Submit the task for validation
va.tasks <- task
Expand Down Expand Up @@ -299,6 +304,8 @@ func (va VAImpl) performValidation(task *vaTask, results chan<- *core.Validation
results <- va.validateTLSALPN01(task)
case acme.ChallengeDNS01:
results <- va.validateDNS01(task)
case acme.ChallengeDNSAccount01:
results <- va.validateDNSAccount01(task)
default:
va.log.Printf("Error: performValidation(): Invalid challenge type: %q", task.Challenge.Type)
}
Expand Down Expand Up @@ -342,6 +349,50 @@ func (va VAImpl) validateDNS01(task *vaTask) *core.ValidationRecord {
return result
}

func (va VAImpl) validateDNSAccount01(task *vaTask) *core.ValidationRecord {
acctHash := sha256.Sum256([]byte(task.AccountURL))
acctLabel := strings.ToLower(base32.StdEncoding.EncodeToString(acctHash[0:10]))
scope := "host"
if task.Wildcard {
scope = "wildcard"
}
challengeSubdomain := "_" + acctLabel +
"._acme-" + scope + "-challenge." + task.Identifier.Value

result := &core.ValidationRecord{
URL: challengeSubdomain,
ValidatedAt: time.Now(),
}

txts, err := va.getTXTEntry(challengeSubdomain)
if err != nil {
result.Error = acme.UnauthorizedProblem(fmt.Sprintf("Error retrieving TXT records for DNS challenge (%q)", err))
return result
}

if len(txts) == 0 {
msg := fmt.Sprintf("No TXT records found for DNS challenge")
result.Error = acme.UnauthorizedProblem(msg)
return result
}

task.Challenge.RLock()
expectedKeyAuthorization := task.Challenge.ExpectedKeyAuthorization(task.Account.Key)
h := sha256.Sum256([]byte(expectedKeyAuthorization))
task.Challenge.RUnlock()
authorizedKeysDigest := base64.RawURLEncoding.EncodeToString(h[:])

for _, element := range txts {
if subtle.ConstantTimeCompare([]byte(element), []byte(authorizedKeysDigest)) == 1 {
return result
}
}

msg := fmt.Sprintf("Correct value not found for DNS challenge")
result.Error = acme.UnauthorizedProblem(msg)
return result
}

func (va VAImpl) validateTLSALPN01(task *vaTask) *core.ValidationRecord {
portString := strconv.Itoa(va.tlsPort)

Expand Down
40 changes: 22 additions & 18 deletions wfe/wfe.go
Original file line number Diff line number Diff line change
Expand Up @@ -1597,30 +1597,27 @@ func (wfe *WebFrontEndImpl) makeChallenge(
func (wfe *WebFrontEndImpl) makeChallenges(authz *core.Authorization, request *http.Request) error {
var chals []*core.Challenge

// Authorizations for a wildcard identifier only get a DNS-01 challenges to
// match Boulder/Let's Encrypt wildcard issuance policy
// Determine which challenge types are enabled for this identifier
var enabledChallenges []string
if strings.HasPrefix(authz.Identifier.Value, "*.") {
chal, err := wfe.makeChallenge(acme.ChallengeDNS01, authz, request)
if err != nil {
return err
}
chals = []*core.Challenge{chal}
// Authorizations for a wildcard identifier get DNS-based challenges to
// match Boulder/Let's Encrypt wildcard issuance policy
enabledChallenges = []string{acme.ChallengeDNS01, acme.ChallengeDNSAccount01}
} else {
// IP addresses get HTTP-01 and TLS-ALPN challenges
var enabledChallenges []string
if authz.Identifier.Type == acme.IdentifierIP {
enabledChallenges = []string{acme.ChallengeHTTP01, acme.ChallengeTLSALPN01}
} else {
// Non-wildcard, non-IP identifier authorizations get all of the enabled challenge types
enabledChallenges = []string{acme.ChallengeHTTP01, acme.ChallengeTLSALPN01, acme.ChallengeDNS01}
enabledChallenges = []string{acme.ChallengeHTTP01, acme.ChallengeTLSALPN01, acme.ChallengeDNS01, acme.ChallengeDNSAccount01}
}
for _, chalType := range enabledChallenges {
chal, err := wfe.makeChallenge(chalType, authz, request)
if err != nil {
return err
}
chals = append(chals, chal)
}
for _, chalType := range enabledChallenges {
chal, err := wfe.makeChallenge(chalType, authz, request)
if err != nil {
return err
}
chals = append(chals, chal)
}

// Lock the authorization for writing to update the challenges
Expand Down Expand Up @@ -2377,8 +2374,12 @@ func (wfe *WebFrontEndImpl) updateChallenge(

// If the identifier value is for a wildcard domain then strip the wildcard
// prefix before dispatching the validation to ensure the base domain is
// validated.
ident.Value = strings.TrimPrefix(ident.Value, "*.")
// validated. Set a flag to indicate validation scope.
wildcard := false
if strings.HasPrefix(ident.Value, "*.") {
ident.Value = strings.TrimPrefix(ident.Value, "*.")
wildcard = true
}

// Confirm challenge status again and update it immediately before sending it to the VA
prob = nil
Expand All @@ -2395,8 +2396,11 @@ func (wfe *WebFrontEndImpl) updateChallenge(
return
}

// Reconstruct account URL for use in scoped validation methods
acctURL := wfe.relativeEndpoint(request, fmt.Sprintf("%s%s", acctPath, existingAcct.ID))

// Submit a validation job to the VA, this will be processed asynchronously
wfe.va.ValidateChallenge(ident, existingChal, existingAcct)
wfe.va.ValidateChallenge(ident, existingChal, existingAcct, acctURL, wildcard)

// Lock the challenge for reading in order to write the response
existingChal.RLock()
Expand Down

0 comments on commit 7d4bed3

Please sign in to comment.