Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expand draft to use dns-scoping & create DNS-01 #34

Merged
merged 45 commits into from
Feb 10, 2024
Merged
Show file tree
Hide file tree
Changes from 44 commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
23cf066
Change how the validation label is constructed
aaomidi Sep 1, 2023
ca38d9d
Stray underline
aaomidi Sep 1, 2023
ca6ef16
prefixing -> prepending
aaomidi Sep 1, 2023
17dfe71
Review changes
aaomidi Sep 7, 2023
19e266f
Merge branch 'main' into dnsops
aaomidi Sep 7, 2023
c95a7d1
Add explanation on implementation consideration (#28)
daknob Sep 7, 2023
9a6ea88
Update Amir's Company (#30)
aaomidi Dec 15, 2023
2fe6bbd
Add security considerations for new label scheme.
Jan 9, 2024
9a0b41a
Rough draft of incorporating scoped challenges
Jan 9, 2024
399a19f
Specify how the KID value should be used
aaomidi Feb 6, 2024
9e0d3f2
Basic introduction of dns-02
aaomidi Feb 6, 2024
f7b1fa9
Rename docname
aaomidi Feb 6, 2024
4eadab4
Rewrite introduction
aaomidi Feb 6, 2024
705bb5f
More IANA things, and grammar changes
aaomidi Feb 7, 2024
a4cd68c
Merge branch 'main' into amir/expand-dns-scope
aaomidi Feb 7, 2024
488c8e8
wording
aaomidi Feb 7, 2024
cc02509
Spelling
aaomidi Feb 7, 2024
19faa97
draft-ietf-acme-scoped-dns-challenge-00 -> draft-ietf-acme-scoped-dns…
aaomidi Feb 7, 2024
573b8d6
Include errors section
aaomidi Feb 7, 2024
1a7e22b
Make paragraph easier to understand
aaomidi Feb 7, 2024
9e80b67
More wording changes
aaomidi Feb 7, 2024
67073ce
Move IANA stuff around
aaomidi Feb 7, 2024
44103a2
Update example
aaomidi Feb 7, 2024
d70c0db
dns name -> DNS name
aaomidi Feb 7, 2024
c74f709
Reword label uniqueness
aaomidi Feb 7, 2024
90edc64
Remove sentence about scoping in the intro
aaomidi Feb 7, 2024
552ea74
Reorganize the document to be a bit more clear what we're talking about
aaomidi Feb 7, 2024
431eab8
Let's try this
aaomidi Feb 7, 2024
b3e3d23
Formatting
aaomidi Feb 7, 2024
cf8cc33
Reword the introduction
aaomidi Feb 7, 2024
909e5c8
Cite CAA
aaomidi Feb 7, 2024
ef643f1
s/validation domain name/authorization domain name
aaomidi Feb 7, 2024
d42963e
Reword SHA-256
aaomidi Feb 7, 2024
914111a
Fix introduction
aaomidi Feb 7, 2024
f803417
Add build task
aaomidi Feb 7, 2024
967b7d2
Typo
aaomidi Feb 7, 2024
d8eec9c
Apply suggestions from code review
aaomidi Feb 8, 2024
d0e853f
Change [0:9] to [0:10], remove kid reference in implementation consid…
aaomidi Feb 8, 2024
3f62aa7
validation domain name -> authorization domain name
aaomidi Feb 8, 2024
7d10468
authorization label -> validation label
aaomidi Feb 8, 2024
4b2bcdc
Rename abbrev
aaomidi Feb 9, 2024
417aee4
Apply suggestions from code review
aaomidi Feb 9, 2024
a383daa
Formatting and remove a few lines
aaomidi Feb 9, 2024
81330fb
Clarify the reason for 10 bytes
aaomidi Feb 9, 2024
b27ebeb
Reference size limits
aaomidi Feb 10, 2024
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: 8 additions & 1 deletion .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,12 @@
"VARIANT": "ubuntu-22.04"
}
},
"remoteUser": "vscode"
"remoteUser": "vscode",
"customizations": {
"vscode": {
"extensions": [
"ms-vscode.live-server"
]
}
}
}
14 changes: 14 additions & 0 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "Build",
"type": "shell",
"command": "/usr/local/bin/kramdown-rfc2629 --v3 draft-ietf-acme-dns-account-01.mkd > docs/draft.xml && xml2rfc --text --html --v3 docs/draft.xml",
"group": {
"kind": "build",
"isDefault": true
}
}
]
}
242 changes: 168 additions & 74 deletions draft-ietf-acme-dns-account-01.mkd
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
---
title: "Automated Certificate Management Environment (ACME) DNS Labeled With ACME Account ID Challenge"
abbrev: "ACME-DNS-CHALLENGE"
abbrev: "ACME-SCOPED-DNS-CHALLENGES"
category: std

docname: draft-ietf-acme-dns-account-challenge-01
docname: draft-ietf-acme-scoped-dns-challenges-00
aaomidi marked this conversation as resolved.
Show resolved Hide resolved
aaomidi marked this conversation as resolved.
Show resolved Hide resolved
v: 3
area: Security
workgroup: Automated Certificate Management Environment
Expand Down Expand Up @@ -48,6 +48,7 @@ normative:
org: National Institute of Standards and Technology

informative:
I-D.draft-ietf-dnsop-domain-verification-techniques:
aaomidi marked this conversation as resolved.
Show resolved Hide resolved

--- abstract

Expand All @@ -62,44 +63,138 @@ systems or environments to handle challenge-solving for a single domain.

The `dns-01` challenge specified in section 8.4 of {{!RFC8555}} requires that
ACME clients validate the domain under the `_acme-challenge` label for the
`TXT` record. This unique label creates an impediment limiting the number of
other entities domain validation can be delegated to.

In multi-region deployments, where separate availability zones serve the same
content, and dependencies across them are avoided, operators need a way to
obtain a separate certificate per zone, for the same domain name. Similarly, in
cases of zero-downtime migration, two different setups of the infrastructure
may coexist for a long period of time, and both need access to valid
`TXT` record. This label creates several limiations in domain validation.

First, the `_acme-challenge` label does not specify if the authorization is
intended for a specific host, a wildcard domain, or a domain and all of its
subdomains. Consequently, domain owners who may be delegating or provisioning
authorization labels for a domain must concede control over the domain and all
subdomains, violating the principle of least privilege.

Furthermore, since each domain only has a single authorization label, it creates an impediment
limiting the number of other entities domain validation can be delegated to.
Delegating authorization to an entity requires the use of CNAME records, which can only used once
per DNS name (or in this case, once per authorization label). This limitation requires that
operators to pick a single ACME challenge solver for their domain name.

In multi-region deployments, where separate availability zones
serve the same content, and dependencies across them are avoided, operators need
a way to obtain a separate certificate per zone, for the same domain name.
Similarly, in cases of zero-downtime migration, two different setups of the
infrastructure may coexist for a long period of time, and both need access to valid
certificates.

Due to the uniqueness of the `_acme-challenge` label, operators today have to
pick a single ACME challenge solver for their domain name, and then add a
`CNAME` record to this infrastructure. A domain name can only have one `CNAME`
in DNS.
This document specifies two new challenge types. `dns-02` and `dns-account-01`, which aim to address these deficiencies.

This document specifies a new challenge type, `dns-account-01`. This challenge
leverages the ACME Account Resource URL to present an account-unique stable
challenge to an ACME server. This challenge allows any domain name to delegate
its domain validation to more than one service through ACME account-unique DNS
records.

As now multiple labels can be used to prove domain control, and they depend on
the ACME account, any number of them can be generated in advance, and then all
required `CNAME` records can be created statically. The dynamic part of the
label depends on the ACME account and not the account key, to allow for
seamless account key rollover without the label changing. This ensures very
long-lived labels, without any security considerations.
This work follows all recommendations set forth in "Domain Control
Validation using DNS" [I-D.draft-ietf-dnsop-domain-verification-techniques].

This RFC does not intend to deprecate the `dns-01` challenge specified in
{{!RFC8555}}. Since this new challenge does not modify or build on any
pre-existing challenges, the ability to complete the `dns-account-01` challenge
requires ACME server operators to deploy new changes to their codebase. This
makes adopting and using this challenge an opt-in process.
{{!RFC8555}}. Since these new challenges do not modify any pre-existing challenges,
the ability to complete the `dns-02` or `dns-account-01` challenge requires ACME server
operators to deploy new changes to their codebase. This makes adopting and using these
challenges an opt-in process.

## DNS-02
jdkasten marked this conversation as resolved.
Show resolved Hide resolved

The `dns-02` challenge adds onto `dns-01` by introducing a scoping mechanism
to the domain authorization label. This allows for the client to specify if the
intended domain validation is for a specific host, a wildcard domain, or a domain
and all of its subdomains.

## DNS-ACCOUNT-01

The `dns-account-01` challenge leverages the ACME Account Resource URL to present
an account-unique stable challenge to an ACME server. This challenge allows any
domain name to delegate its domain validation to more than one service through
unique per ACME account DNS records.

With this new challenge, domain validation of the same DNS name can be done through
different authorization labels. Since these authorization labels will depend on the ACME
account KID ({{!RFC8555, Section 6.2}}), any number of them can be generated in advance.
This allows all required `CNAME` records for domain validation delegation to be constructed
statically.

# Conventions and Definitions

{::boilerplate bcp14-tagged}

# DNS-02 Challenge

When the identifier being validated is a domain name, the client can prove control of that domain by provisioning a `TXT` resource record containing a designated value for a specific validation domain name.
aaomidi marked this conversation as resolved.
Show resolved Hide resolved

* type (required, string): The string "dns-02".
* token (required, string): A random value that uniquely identifies the challenge. This value MUST have at least 128 bits of entropy. It MUST NOT contain any characters outside the base64url alphabet, including padding characters ("="). See {{!RFC4086}} for additional information on additional requirements for secure randomness.

~~~
{
"type": "dns-02",
"url": "https://example.com/acme/chall/i00MGYwLWIx",
"status": "pending",
"token": "ODE4OWY4NTktYjhmYS00YmY1LTk5MDgtZTFjYTZmNjZlYTUx"
}
~~~

A client can fulfill this challenge by performing the following steps:

- Construct a key authorization {{!RFC8555, Section 8.1}} from the `token` value provided in the challenge and the client's account key
- Compute the SHA-256 digest {{FIPS180-4}} of the key authorization
- Construct the validation domain name by specifying the scope for the domain name being validated:

"_acme-" || <SCOPE> ||"-challenge"

- SCOPE is
- "host" if the associated authorization applies only to the specific host name and no labels beneath it
- "wildcard" if the associated authorization is for a wildcard domain and contains the `wildcard` field set to true ({{?RFC8555, Section 7.1.4}})
- "domain" if the authorization is for both the host and all subdomains by containing the `subdomainAuthAllowed` field set to true ({{?RFC9444, Section 4.1}}).
- The `"||"` operator indicates concatenation of strings

- Provision a DNS `TXT` record with the base64url digest value under the constructed domain validation name

For example, if the domain name being validated is the wildcard of `*.example.org` then the client would provision the following DNS record:

~~~
_acme-wildcard-challenge.example.org 300 IN TXT "LoqXcYV8...jxAjEuX0.9jg46WB3...fm21mqTI"
~~~

(In the above, "..." indicates that the token and the JWK thumbprint in the key authorization have been truncated to fit on the page.)

- Respond to the ACME server with an empty object ({}) to acknowledge that the challenge can be validated by the server

~~~
POST /acme/chall/Rg5dV14Gh1Q
Host: example.com
Content-Type: application/jose+json

{
"protected": base64url({
"alg": "ES256",
"kid": "https://example.com/acme/acct/evOfKhNU60wg",
"nonce": "SS2sSl1PtspvFZ08kNtzKd",
"url": "https://example.com/acme/chall/Rg5dV14Gh1Q"
}),
"payload": base64url({}),
"signature": "Q1bURgJoEslbD1c5...3pYdSMLio57mQNN4"
}
~~~

On receiving a response, the server constructs and stores the key authorization from the challenge `token` value.

To validate the `dns-02` challenge, the server performs the following steps:

- Compute the SHA-256 digest {{FIPS180-4}} of the stored key authorization
- Compute the validation domain name with the scope of the associated authorization similiar to the client
- Query for `TXT` records for the validation domain name
- Verify that the contents of one of the `TXT` records match the digest value

If all the above verifications succeed, then the validation is successful. If no DNS record is found, or DNS record and response payload do not pass these checks, then the server MUST fail the validation and mark the challenge as invalid.

The client SHOULD de-provision the resource record(s) provisioned for this challenge once the challenge is complete, i.e., once the "status" field of the challenge has the value "valid" or "invalid".

## Errors

The server SHOULD follow the guidelines set in {{!RFC8555, Section 6.7}} for error conditions that occur during challenge validation.

# DNS-ACCOUNT-01 Challenge

When the identifier being validated is a domain name, the client can prove control of that domain by provisioning a `TXT` resource record containing a designated value for a specific validation domain name.
Expand All @@ -120,51 +215,55 @@ A client can fulfill this challenge by performing the following steps:

- Construct a key authorization {{!RFC8555, Section 8.1}} from the `token` value provided in the challenge and the client's account key
- Compute the SHA-256 digest {{FIPS180-4}} of the key authorization
- Construct the validation domain name by prepending the following label to the domain name being validated:
- Construct the validation domain name by prepending the following two labels to the domain name being validated:

"_acme-challenge_" || base32(SHA-256(Account Resource URL)[0:9])
"_" || base32(SHA-256(<ACCOUNT_RESOURCE_URL>)[0:10]) || "._acme-" || <SCOPE> ||"-challenge"

- SHA-256 is the SHA hashing operation defined in {{!RFC6234}}
- `[0:9]` is the operation that selects the first ten bytes (bytes 0 through 9 inclusive) from the previous SHA256 operation
- `[0:10]` is the operation that selects the first ten bytes (bytes 0 through 9 inclusive) from the previous SHA-256 operation
- base32 is the operation defined in {{!RFC4648}}
- Account Resource URL is defined in {{!RFC8555, Section 7.3}} as the value in the Location header field
- ACCOUNT_RESOURCE_URL is defined in {{!RFC8555, Section 7.3}} as the value in the `Location` header field
- SCOPE is
- "host" if the associated authorization applies only to the specific host name and no labels beneath it
- "wildcard" if the associated authorization is for a wildcard domain and contains the `wildcard` field set to true ({{?RFC8555, Section 7.1.4}})
- "domain" if the authorization is for both the host and all subdomains by containing the `subdomainAuthAllowed` field set to true ({{?RFC9444, Section 4.1}}).
- The `"||"` operator indicates concatenation of strings

- Provision a DNS `TXT` record with the base64url digest value under the constructed domain validation name

For example, if the domain name being validated is "www.example.org", and the account URL of "https://example.com/acme/acct/ExampleAccount" then the client would provision the following DNS record:
For example, if the domain name being validated is `*.example.org`, and the account URL of `https://example.com/acme/acct/ExampleAccount` then the client would provision the following DNS record:

~~~
_acme-challenge_ujmmovf2vn55tgye.www.example.org 300 IN TXT "LoqXcYV8...jxAjEuX0.9jg46WB3...fm21mqTI"
~~~
~~~
_ujmmovf2vn55tgye._acme-wildcard-challenge.example.org 300 IN TXT "LoqXcYV8...jxAjEuX0.9jg46WB3...fm21mqTI"
~~~

(In the above, "..." indicates that the token and the JWK thumbprint in the key authorization have been truncated to fit on the page.)
(In the above, "..." indicates that the token and the JWK thumbprint in the key authorization have been truncated to fit on the page.)

Respond to the ACME server with an empty object ({}) to acknowledge that the challenge can be validated by the server
- Respond to the ACME server with an empty object ({}) to acknowledge that the challenge can be validated by the server

~~~
POST /acme/chall/Rg5dV14Gh1Q
Host: example.com
Content-Type: application/jose+json
~~~
POST /acme/chall/Rg5dV14Gh1Q
Host: example.com
Content-Type: application/jose+json

{
"protected": base64url({
"alg": "ES256",
"kid": "https://example.com/acme/acct/evOfKhNU60wg",
"nonce": "SS2sSl1PtspvFZ08kNtzKd",
"url": "https://example.com/acme/chall/Rg5dV14Gh1Q"
}),
"payload": base64url({}),
"signature": "Q1bURgJoEslbD1c5...3pYdSMLio57mQNN4"
}
~~~
{
"protected": base64url({
"alg": "ES256",
"kid": "https://example.com/acme/acct/evOfKhNU60wg",
"nonce": "SS2sSl1PtspvFZ08kNtzKd",
"url": "https://example.com/acme/chall/Rg5dV14Gh1Q"
}),
"payload": base64url({}),
"signature": "Q1bURgJoEslbD1c5...3pYdSMLio57mQNN4"
}
~~~

On receiving a response, the server constructs and stores the key authorization from the challenge `token` value and the current client account key.

To validate the `dns-account-01` challenge, the server performs the following steps:

- Compute the SHA-256 digest {{FIPS180-4}} of the stored key authorization
- Compute the validation domain name with the account URL of the ACME account requesting validation
- Compute the validation domain name with the account URL of the ACME account requesting validation and the associated authorization, similar to the client logic
- Query for `TXT` records for the validation domain name
- Verify that the contents of one of the `TXT` records match the digest value

Expand All @@ -182,37 +281,32 @@ If the server is unable to find a `TXT` record for the validation domain name, i

As this challenge creates strong dependency on the `kid` account identifier, the server SHOULD ensure that the account identifier is not changed during the lifetime of the account.

If this change occurs, the existing long-term `CNAME` records created by all account holders will no longer be valid. The clients will not be able to issue certificates automatically moving forward.

# Security Considerations

As this challenge that is introduced only differs in the left-most label of the domain name from the existing `dns-01` challenge, the same security considerations apply.

In terms of the construction of the label prepended to the domain name, there is no need for a cryptographic hash. The purpose of that is to create a long-lived and statistically distinctive record of minimal size.
These challenges follows the recommendations set out in "Domain Control Validation using DNS" {{I-D.draft-ietf-dnsop-domain-verification-techniques}} for supporting multiple intermediates within the context of {{!RFC8555}}.

SHA-256 was picked due to its broad adoption, hardware support, and existing need in implementations that would likely support `dns-account-01`.
In both `dns-account-01` and `dns-02`, the same security considerations apply for the integrity of authorizations ({{!RFC8555, Section 10.2}}) and DNS security ({{!RFC8555, Section 11.2}}) as in the original specification for `dns-01`. They both differ from `dns-01` in that the challenges are scoped to the particular name being validated without relying upon CAA ({{!RFC8659}}).

The first 10 bytes were picked as a tradeoff: the value needs to be short enough to not significantly impact DNS record and response size, long enough to provide sufficient probability of collision avoidance across ACME accounts, and just the right size to have Base32 require no padding. As the algorithm is used for uniform distribution of inputs, and not for integrity, we do not consider the trimming a security issue.

# IANA Considerations
## DNS-ACCOUNT-01

## DNS Parameters
To allow for seamless account key rollover without the label changing, the dynamic part of the label depends on the ACME account and not the account key. This allows for long-lived labels, without the security considerations of keeping the account key static.

The Underscored and Globally Scoped DNS Node Names is to be updated to include the following entry:
In terms of the construction of the account label prepended to the domain name, there is no need for a cryptographic hash. The goal is to simply create a long-lived and statistically distinct label of minimal size. SHA-256 was chosen due to its existing use in the `dns-01` challenge ({{!RFC8555, Section 8.1}}).

~~~
RR Type: TXT
_NODE NAME: _acme-challenge_*
Reference: This document
~~~
The first 10 bytes were picked as a tradeoff: the value needs to be short enough to not significantly impact DNS label length, long enough to provide sufficient probability of collision avoidance across ACME accounts, and just the right size to have Base32 require no padding. As the algorithm is used for a uniform distribution of inputs, and not for integrity, we do not consider the trimming a security issue.

Where `_acme-challenge_*` denotes all node names beginning with the string `_acme-challenge_`. It does NOT refer to a DNS wildcard specification.
# IANA Considerations

## ACME Validation Method

The "ACME Validation Methods" registry is to be updated to include the following entry:
The "ACME Validation Methods" registry is to be updated to include the following entries:

~~~
label: dns-02
identifier-type: dns
ACME: Y
Reference: This document

label: dns-account-01
identifier-type: dns
ACME: Y
Expand Down
Loading