Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions cmd/boulder-wfe2/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,14 @@ type Config struct {
// will differ in configuration for production and staging.
LegacyKeyIDPrefix string `validate:"required,url"`

// AccountURIPrefix is used to construct the "accounturi" field of
// dns-persist-01 challenges (e.g. "https://acme-v02.api.letsencrypt.org/acme/acct/").
// MUST match the first entry of the VA's AccountURIPrefixes field.
//
// TODO(#8724): Once this field has been set in Production we can make
// it required.
AccountURIPrefix string `validate:"omitempty,url,endswith=/"`

// GoodKey is an embedded config stanza for the goodkey library.
GoodKey goodkey.Config

Expand Down Expand Up @@ -414,6 +422,7 @@ func main() {
wfe.AllowOrigins = c.WFE.AllowOrigins
wfe.DirectoryWebsite = c.WFE.DirectoryWebsite
wfe.LegacyKeyIDPrefix = c.WFE.LegacyKeyIDPrefix
wfe.AccountURIPrefix = c.WFE.AccountURIPrefix

if c.WFE.ListenAddress == "" {
cmd.Fail("HTTP listen address is not configured")
Expand Down
4 changes: 4 additions & 0 deletions core/objects.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,10 @@ type Challenge struct {
// by all challenges except dns-persist-01.
Token string `json:"token,omitempty"`

// AccountURI is the account URI the client includes during dns-persist-01
// challenge validation.
AccountURI string `json:"accounturi,omitempty"`

// IssuerDomainNames contains the list of issuer domain name values accepted
// during dns-persist-01 challenge validation.
IssuerDomainNames []string `json:"issuer-domain-names,omitempty"`
Expand Down
2 changes: 1 addition & 1 deletion core/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -402,7 +402,7 @@ func Command() string {
}

// NormalizeIssuerDomainName normalizes an RFC 8659 issuer-domain-name per the
// recommended algorithm in draft-ietf-acme-dns-persist-00, Section 9.1.1:
// recommended algorithm in draft-ietf-acme-dns-persist-01, Section 9.2:
// case-fold to lowercase, apply Unicode NFC normalization, convert to A-label
// (Punycode), remove any trailing dot, and ensure the result is no more than
// 253 octets in length. If normalization fails, an error is returned.
Expand Down
2 changes: 1 addition & 1 deletion ra/ra.go
Original file line number Diff line number Diff line change
Expand Up @@ -2211,7 +2211,7 @@ func (ra *RegistrationAuthorityImpl) NewOrder(ctx context.Context, req *rapb.New
}

// Never reuse dns-persist-01 authorizations:
// draft-ietf-acme-dns-persist-00 section 7.8 caps the reuse period to
// draft-ietf-acme-dns-persist-01 section 7.8 caps the reuse period to
// the TXT record's TTL and BRs section 3.2.2.4.22 caps it at 10 days.
// Since TTLs are typically seconds to minutes, the TTL cap is likely to
// be the binding constraint; re-validating every order is simpler.
Expand Down
1 change: 1 addition & 0 deletions test/config-next/wfe2.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"directoryCAAIdentity": "happy-hacker-ca.invalid",
"directoryWebsite": "https://github.com/letsencrypt/boulder",
"legacyKeyIDPrefix": "http://boulder.service.consul:4000/reg/",
"accountURIPrefix": "http://boulder.service.consul:4001/acme/acct/",
"goodkey": {},
"maxContactsPerRegistration": 3,
"tls": {
Expand Down
7 changes: 5 additions & 2 deletions va/dns_persist.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ func parseDNSPersistRecord(record string) (string, *dnsPersistIssueValueParams,
params.persistUntil = time.Unix(persistUntilVal, 0).UTC()

default:
// Per draft-ietf-acme-dns-persist-00, "the server MUST ignore any
// Per draft-ietf-acme-dns-persist-01, "the server MUST ignore any
// parameter within the issue-value that has an unrecognized tag."
continue
}
Expand All @@ -125,10 +125,13 @@ func parseDNSPersistRecord(record string) (string, *dnsPersistIssueValueParams,
// given time. It returns nil if the record authorizes issuance, or a
// berrors.Unauthorized error for authorization failures.
func checkDNSPersistRecord(params *dnsPersistIssueValueParams, validAccountURI string, wildcardName bool, validatedAt time.Time) error {
// Per draft-ietf-acme-dns-persist-01 section 4.1, accounturi values are
// compared using Simple String Comparison per RFC 3986 section 6.2.1, with
// no case-folding or other normalization.
if params.accountURI != validAccountURI {
return berrors.UnauthorizedError("accounturi mismatch: expected %q, got %q", validAccountURI, params.accountURI)
}
// Per draft-ietf-acme-dns-persist-00, the policy parameter's tag and
// Per draft-ietf-acme-dns-persist-01 section 4.1, the policy parameter's
// defined values MUST be treated as case-insensitive. If the policy
// parameter's value is anything other than "wildcard", the CA MUST proceed
// as if the policy parameter were not present.
Expand Down
8 changes: 7 additions & 1 deletion vendor/github.com/eggsampler/acme/v3/types.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 14 additions & 2 deletions wfe2/wfe.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,11 @@ type WebFrontEndImpl struct {
// `LegacyKeyIDPrefix` for more information.
LegacyKeyIDPrefix string

// AccountURIPrefix is required to set the "accounturi" field of
// dns-persist-01 challenges. MUST match the first entry of the VA's
// AccountURIPrefixes field.
AccountURIPrefix string

// Key policy.
keyPolicy goodkey.KeyPolicy

Expand Down Expand Up @@ -1245,19 +1250,26 @@ func (wfe *WebFrontEndImpl) prepChallengeForDisplay(
}

if challenge.Type == core.ChallengeTypeDNSPersist01 {
// draft-ietf-acme-dns-persist-00 section 3.1 states, "Servers MUST NOT
// draft-ietf-acme-dns-persist-01 section 3.1 states, "Servers MUST NOT
// send more than 10 issuer domain names." Be aware of this if we ever
// support configuration of multiple CAA identities.
challenge.IssuerDomainNames = []string{wfe.DirectoryCAAIdentity}

// TODO(#8724): Once the configuration of AccountURIPrefix is required
// to be non-empty, this conditional can be removed.
if wfe.AccountURIPrefix != "" {
challenge.AccountURI = fmt.Sprintf("%s%d", wfe.AccountURIPrefix, authz.RegistrationID)
}

// dns-persist-01 does not use a token, but authorizations store a
// single token which gets unconditionally assigned to all challenge
// types during deserialization.
challenge.Token = ""
} else {
// Belt and suspenders: we don't expect this to ever be populated
// Belt and suspenders: we don't expect these to ever be populated
// outside of this function, but just in case.
challenge.IssuerDomainNames = nil
challenge.AccountURI = ""
}
}

Expand Down
3 changes: 3 additions & 0 deletions wfe2/wfe_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,7 @@ func setupWFE(t *testing.T) (WebFrontEndImpl, clock.FakeClock, requestSigner) {
test.AssertNotError(t, err, "Unable to create WFE")

wfe.SubscriberAgreementURL = agreementURL
wfe.AccountURIPrefix = "http://localhost/acme/acct/"

return wfe, fc, requestSigner{t, inmemNonceService.AsSource()}
}
Expand Down Expand Up @@ -3657,9 +3658,11 @@ func TestPrepAuthzForDisplay(t *testing.T) {
if chall.Type == core.ChallengeTypeDNSPersist01 {
test.Assert(t, chall.Token == "", fmt.Sprintf("expected %s to have no token", chall.Type))
test.AssertDeepEquals(t, chall.IssuerDomainNames, []string{"letsencrypt.org"})
test.AssertEquals(t, chall.AccountURI, "http://localhost/acme/acct/1")
} else {
test.Assert(t, chall.Token != "", fmt.Sprintf("expected %s to have a token", chall.Type))
test.Assert(t, chall.IssuerDomainNames == nil, fmt.Sprintf("expected %s to have no issuer domain names", chall.Type))
test.AssertEquals(t, chall.AccountURI, "")
}
}
}
Expand Down
Loading