RFC 8555
Automatic Certificate Management Environment (ACME)
RFC 8555: Automatic Certificate Management Environment (ACME) 💖
okay bestie, this is RFC 8555, and it's giving main character energy 🦋 It's the spec for ACME, the protocol that lets a certificate authority and an applicant automate the whole certificate dance ✨
Title: Automatic Certificate Management Environment (ACME) Number: RFC 8555 Authors: R. Barnes (Cisco), J. Hoffman-Andrews (EFF), D. McCarney (Let's Encrypt), J. Kasten (University of Michigan) Category: Standards Track Date: March 2019 ISSN: 2070-1721 Obsoletes / Updates: the title block doesn't list anything here, so this one doesn't obsolete or update any other RFC 🌸
Abstract ✨
so the vibe here is about PKIX certificates (that's X.509 under the Public Key Infrastructure umbrella) 🔐 The biggest thing they're used for is proving who owns a domain name. That's why certification authorities (CAs) in the Web PKI are trusted to check that someone asking for a certificate actually represents the domain names inside it 💅
The tea? As of writing, that checking happens through a bunch of ad hoc mechanisms, which is very messy 🙈 So this document describes a protocol that a CA and an applicant can use to automate the verification and the issuance. It also brings along facilities for other certificate management stuff, like revocation 🧚
Status of This Memo 📜
this one is an Internet Standards Track document 🎀
It's a product of the Internet Engineering Task Force (IETF), and it represents the consensus of the IETF community 🫶 It went through public review and got approved for publication by the Internet Engineering Steering Group (IESG) 👑 If you want more on Internet Standards, check out Section 2 of RFC 7841 💜
For the latest status, any errata, or to send feedback, head to https://www.rfc-editor.org/info/rfc8555 🌈
Copyright Notice 🔐
Copyright (c) 2019 IETF Trust and the persons identified as the document authors. All rights reserved 💖
This document is subject to BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents (https://trustee.ietf.org/license-info), as they stand on the date of publication. Please read those carefully, because they spell out your rights and restrictions for this document 📖 Any Code Components pulled out of this document must include Simplified BSD License text, as described in Section 4.e of the Trust Legal Provisions. They're provided without warranty, as described in the Simplified BSD License 🫶
Table of Contents 🗂️
1. Introduction ....................................................4
2. Deployment Model and Operator Experience ........................5
3. Terminology .....................................................7
4. Protocol Overview ...............................................7
5. Character Encoding .............................................10
6. Message Transport ..............................................10
6.1. HTTPS Requests ............................................10
6.2. Request Authentication ....................................11
6.3. GET and POST-as-GET Requests ..............................13
6.4. Request URL Integrity .....................................13
6.4.1. "url" (URL) JWS Header Parameter ...................14
6.5. Replay Protection .........................................14
6.5.1. Replay-Nonce .......................................15
6.5.2. "nonce" (Nonce) JWS Header Parameter ...............16
6.6. Rate Limits ...............................................16
6.7. Errors ....................................................16
6.7.1. Subproblems ........................................18
7. Certificate Management .........................................20
7.1. Resources .................................................20
7.1.1. Directory ..........................................23
7.1.2. Account Objects ....................................24
7.1.3. Order Objects ......................................26
7.1.4. Authorization Objects ..............................28
7.1.5. Challenge Objects ..................................30
7.1.6. Status Changes .....................................30
7.2. Getting a Nonce ...........................................34
7.3. Account Management ........................................34
7.3.1. Finding an Account URL Given a Key .................36
7.3.2. Account Update .....................................37
7.3.3. Changes of Terms of Service ........................38
7.3.4. External Account Binding ...........................38
7.3.5. Account Key Rollover ...............................40
7.3.6. Account Deactivation ...............................43
7.4. Applying for Certificate Issuance .........................44
7.4.1. Pre-authorization ..................................49
7.4.2. Downloading the Certificate ........................51
7.5. Identifier Authorization ..................................53
7.5.1. Responding to Challenges ...........................54
7.5.2. Deactivating an Authorization ......................57
7.6. Certificate Revocation ....................................58
8. Identifier Validation Challenges ...............................60
8.1. Key Authorizations ........................................62
8.2. Retrying Challenges .......................................63
8.3. HTTP Challenge ............................................63
8.4. DNS Challenge .............................................66
9. IANA Considerations ............................................68
9.1. Media Type: application/pem-certificate-chain .............68
9.2. Well-Known URI for the HTTP Challenge .....................69
9.3. Replay-Nonce HTTP Header ..................................69
9.4. "url" JWS Header Parameter ................................70
9.5. "nonce" JWS Header Parameter ..............................70
9.6. URN Sub-namespace for ACME (urn:ietf:params:acme) .........70
9.7. New Registries ............................................71
9.7.1. Fields in Account Objects ..........................71
9.7.2. Fields in Order Objects ............................72
9.7.3. Fields in Authorization Objects ....................73
9.7.4. Error Types ........................................74
9.7.5. Resource Types .....................................74
9.7.6. Fields in the "meta" Object within a
Directory Object ...................................75
9.7.7. Identifier Types ...................................76
9.7.8. Validation Methods .................................76
10. Security Considerations .......................................78
10.1. Threat Model .............................................78
10.2. Integrity of Authorizations ..............................80
10.3. Denial-of-Service Considerations .........................83
10.4. Server-Side Request Forgery ..............................84
10.5. CA Policy Considerations .................................84
11. Operational Considerations ....................................86
11.1. Key Selection ............................................86
11.2. DNS Security .............................................87
11.3. Token Entropy ............................................88
11.4. Malformed Certificate Chains .............................88
12. References ....................................................88
12.1. Normative References .....................................88
12.2. Informative References ...................................92
Acknowledgements ..................................................94
Authors' Addresses ................................................95
to be continued in part 2 🎀 we're just getting started, bestie 🦋
1. Introduction ✨
okay so, certificates [RFC5280] in the Web PKI are mostly used to authenticate domain names 🌸. That's why certification authorities (CAs) in the Web PKI are trusted to verify that an applicant for a certificate legitimately represents the domain name(s) in that certificate 💅.
Different types of certificates reflect different kinds of CA verification about the certificate subject. "Domain Validation" (DV) certificates are by far the most common type 👑. The only validation the CA is required to perform for DV issuance is verifying that the requester has effective control of the domain [CABFBR]. The CA is not required to try verifying the requester's real-world identity. (This is different from "Organization Validation" (OV) and "Extended Validation" (EV) certificates, where the process is also meant to verify the real-world identity of the requester 🫶.)
Existing Web PKI certification authorities tend to use a set of ad hoc protocols for certificate issuance and identity verification 🙈. For DV certificates, the typical user experience goes something like this:
- Generate a PKCS#10 [RFC2986] Certificate Signing Request (CSR).
- Cut and paste the CSR into a CA's web page.
- Prove ownership of the domain(s) in the CSR by one of the following methods:
- Put a CA-provided challenge at a specific place on the web server.
- Put a CA-provided challenge in a DNS record corresponding to the target domain.
- Receive a CA-provided challenge at (hopefully) an administrator-controlled email address corresponding to the domain, and then respond to it on the CA's web page.
- Download the issued certificate and install it on the user's Web Server.
With the exception of the CSR itself and the certificates that get issued, these are all completely ad hoc procedures 💀. They rely on a human user following interactive natural-language instructions from the CA, rather than on machine-implemented published protocols. In many cases the instructions are difficult to follow and cause significant frustration and confusion 😭. Informal usability tests by the authors indicate that webmasters often need 1-3 hours to obtain and install a certificate for a domain 🫠. Even in the best case, the lack of published, standardized mechanisms is an obstacle to the wide deployment of HTTPS and other PKIX-dependent systems, because it inhibits mechanization of tasks related to certificate issuance, deployment, and revocation 🚫.
This document describes an extensible framework for automating the issuance and domain validation procedure ✨. That lets servers and infrastructure software obtain certificates without user interaction 🤖. Using this protocol should radically simplify the deployment of HTTPS, and make PKIX-based authentication practical for other protocols based on Transport Layer Security (TLS) [RFC8446] 🔥.
It should be noted that while the focus of this document is on validating domain names for issuing certificates in the Web PKI, ACME supports extensions for use with other identifiers in other PKI contexts 🦋. For example, as of this writing, there is ongoing work to use ACME for issuing Web PKI certificates attesting to IP addresses [ACME-IP], and Secure Telephone Identity Revisited (STIR) certificates attesting to telephone numbers [ACME-TELEPHONE] 📞.
ACME can also automate some aspects of certificate management even where non-automated processes are still needed 🧚. For example, the external account binding feature (see Section 7.3.4) can allow an ACME account to use authorizations that have been granted to an external, non-ACME account. This lets ACME handle issuance scenarios that can't yet be fully automated, such as issuing "Extended Validation" certificates 💜.
2. Deployment Model and Operator Experience 🎀
The guiding use case for ACME is obtaining certificates for websites (HTTPS [RFC2818]) 🌈. In this case, a web server is intended to speak for one or more domains, and the certificate issuance process is intended to verify that this web server actually speaks for those domain(s) 👀.
DV certificate validation commonly checks claims about properties related to control of a domain name. These are properties that the certificate issuer can observe in an interactive process conducted purely online 💻. So under typical circumstances, every step in the request, verification, and issuance process can be represented and performed by Internet protocols, with no out-of-band human intervention ✨.
Before ACME, when deploying an HTTPS server, a server operator typically got a prompt to generate a self-signed certificate 🥲. If the operator were instead deploying an HTTPS server using ACME, the experience would be something like this:
- The operator's ACME client prompts the operator for the intended domain name(s) that the web server is to stand for.
- The ACME client presents the operator with a list of CAs from which it could get a certificate. (This list will change over time based on the capabilities of CAs and updates to ACME configuration.) The ACME client might prompt the operator for payment information at this point 💸.
- The operator selects a CA.
- In the background, the ACME client contacts the CA and requests that it issue a certificate for the intended domain name(s) 🏃♀️.
- The CA verifies that the client controls the requested domain name(s) by having the ACME client perform some action(s) that can only be done with control of the domain name(s). For example, the CA might require a client requesting
example.comto provision a DNS record underexample.com, or an HTTP resource underhttp://example.com🔐. - Once the CA is satisfied, it issues the certificate, and the ACME client automatically downloads and installs it, potentially notifying the operator via email, SMS, etc. 📬
- The ACME client periodically contacts the CA to get updated certificates, stapled Online Certificate Status Protocol (OCSP) responses [RFC6960], or whatever else would be required to keep the web server functional and its credentials up to date 🔄.
In this way, it would be nearly as easy to deploy with a CA-issued certificate as with a self-signed certificate 🥹. Furthermore, maintaining that CA-issued certificate would require minimal manual intervention. This close integration of ACME with HTTPS servers allows certificates to be deployed immediately and automatically as they are issued, sparing the human administrator from much of the time-consuming work described in the previous section 🙌.
3. Terminology 📖
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all capitals, as shown here 🧐.
The two main roles in ACME are "client" and "server" 🤝. The ACME client uses the protocol to request certificate management actions, such as issuance or revocation. An ACME client may run on a web server, a mail server, or some other server system that requires valid X.509 certificates. Or it may run on a separate server that doesn't consume the certificate but is authorized to respond to a CA-provided challenge 🧩. The ACME server runs at a certification authority and responds to client requests, performing the requested actions if the client is authorized 🏛️.
An ACME client authenticates to the server by means of an "account key pair" 🔑. The client uses the private key of this key pair to sign all messages sent to the server. The server uses the public key to verify the authenticity and integrity of messages from the client 🫶.
4. Protocol Overview ✨
okay bestie, so ACME lets a client ask for certificate management stuff by sending little JSON messages 📦 ([RFC8259]) over HTTPS ([RFC2818]). Getting a cert this way is giving traditional CA energy: you make an account, ask for a cert, and prove you control the domain(s) in it so the CA will actually issue it 💅
Phase one is the account. The client generates an asymmetric key pair and requests a new account. It can optionally include contact info, agree to the terms of service (ToS), and/or link the account to an existing account in another system. The creation request gets signed with the generated private key to prove the client controls it 🔐
Client Server
[Contact Information]
[ToS Agreement]
[Additional Data]
Signature ------->
Account URL
<------- Account Object
[] Information covered by request signatures
Account Creation
Once the account exists, there are four major steps the client needs to take to get a certificate 🎀:
- Submit an order for a certificate to be issued
- Prove control of any identifiers requested in the certificate
- Finalize the order by submitting a CSR
- Await issuance and download the issued certificate
The order describes the identifiers you want, plus a few extra fields that capture semantics the CSR format can't express. If the server is willing to consider issuing the cert, it replies with a list of requirements the client has to satisfy first 📋
For example, in most cases the server will make you prove you control the identifiers in the cert. There are lots of ways to validate different kinds of identifiers, so the server picks from an extensible set of challenges that fit the identifier being claimed. The client responds with a set of responses that tell the server which challenges it has completed, and then the server checks that they're actually done ✅
Once validation is complete and the server is happy that the client has met its requirements, the client finalizes the order by submitting a PKCS#10 Certificate Signing Request (CSR). The server issues the requested certificate and makes it available to the client 🦋
Client Server
[Order]
Signature ------->
<------- Required Authorizations
[Responses]
Signature ------->
<~~~~~~~~Validation~~~~~~~~>
[CSR]
Signature ------->
<------- Acknowledgement
<~~~~~~Await issuance~~~~~~>
[POST-as-GET request]
Signature ------->
<------- Certificate
[] Information covered by request signatures
Certificate Issuance
To revoke a certificate, the client sends a signed revocation request that says which certificate to revoke 🗑️
Client Server
[Revocation request]
Signature -------->
<-------- Result
[] Information covered by request signatures
Certificate Revocation
Note that ACME is designed flexibly enough to handle different types of identifiers in principle, but the primary use case this document covers is domain names as identifiers 🌐. For example, all of the identifier validation challenges in Section 8 address validation of domain names. Using ACME for other identifiers will need further specification, to describe how those identifiers are encoded in the protocol and what types of validation challenges the server might require 🧚
5. Character Encoding
okay this one's a must-know 🤓 All requests and responses sent via HTTP by ACME clients, ACME servers, and validation servers, plus any inputs for digest computations, MUST be encoded using the UTF-8 character set [RFC3629]. Identifiers that show up in certificates can have their own encoding considerations, though. For example, DNS names with non-ASCII characters are expressed as A-labels rather than U-labels. Any of those encoding considerations get applied before the UTF-8 encoding I just mentioned 🌸
6. Message Transport
ACME clients and servers talk over HTTPS 🔒, and they use JSON Web Signature (JWS) [RFC7515] to add some extra security for messages going from the client to the server. HTTPS handles server authentication and confidentiality. With a few ACME-specific extras, JWS adds authentication of the client's request payloads, anti-replay protection, and integrity for the HTTPS request URL. Two layers of protection, no cap 💅
6.1. HTTPS Requests
Each ACME function happens when the client sends a sequence of HTTPS requests to the server [RFC2818], carrying JSON messages [RFC8259]. Use of HTTPS is REQUIRED. Each subsection of Section 7 below describes the message formats used by that function and the order the messages get sent in 📬
In most HTTPS transactions used by ACME, the ACME client is the HTTPS client and the ACME server is the HTTPS server. The ACME server flips roles when it validates challenges: it acts as an HTTP client when validating an http-01 challenge, as a DNS client with dns-01, and so on 🔄
ACME servers SHOULD follow the recommendations of [RFC7525] when configuring their TLS implementations. ACME servers that support TLS 1.3 MAY allow clients to send early data (0-RTT). This is safe because the ACME protocol itself includes anti-replay protections (see Section 6.5) in all cases where they are required. For this reason, there are no restrictions on what ACME data can be carried in 0-RTT 🚀
ACME clients MUST send a User-Agent header field, in accordance with [RFC7231]. This header field SHOULD include the name and version of the ACME software, in addition to the name and version of the underlying HTTP client software 🤖
ACME clients SHOULD send an Accept-Language header field in accordance with [RFC7231] so error messages can be localized 🌍
ACME servers that are meant to be generally accessible need to use Cross-Origin Resource Sharing (CORS) so they can be reached from browser-based clients [W3C.REC-cors-20140116]. Such servers SHOULD set the Access-Control-Allow-Origin header field to the value "*" 💜
Binary fields in the JSON objects used by ACME are encoded using base64url encoding, described in Section 5 of [RFC4648], following the profile specified in JSON Web Signature in Section 2 of [RFC7515]. This encoding uses a URL safe character set. Trailing '=' characters MUST be stripped. Encoded values that include trailing '=' characters MUST be rejected as improperly encoded 🙈
6.2. Request Authentication
All ACME requests with a non-empty body MUST encapsulate their payload in a JSON Web Signature (JWS) [RFC7515] object, signed using the account's private key unless otherwise specified. The server MUST verify the JWS before processing the request. Wrapping request bodies in JWS is what gives you request authentication 🔏
A JWS object sent as the body of an ACME request MUST meet the following additional criteria:
- The JWS MUST be in the Flattened JSON Serialization [RFC7515]
- The JWS MUST NOT have multiple signatures
- The JWS Unencoded Payload Option [RFC7797] MUST NOT be used
- The JWS Unprotected Header [RFC7515] MUST NOT be used
- The JWS Payload MUST NOT be detached
- The JWS Protected Header MUST include the following fields:
"alg"(Algorithm)- This field MUST NOT contain
"none"or a Message Authentication Code (MAC) algorithm (e.g. one in which the algorithm registry description mentions MAC/HMAC).
- This field MUST NOT contain
"nonce"(defined in Section 6.5)"url"(defined in Section 6.4)- Either
"jwk"(JSON Web Key) or"kid"(Key ID) as specified below
An ACME server MUST implement the "ES256" signature algorithm [RFC7518] and SHOULD implement the "EdDSA" signature algorithm using the "Ed25519" variant (indicated by "crv") [RFC8037] 🔑
The "jwk" and "kid" fields are mutually exclusive. Servers MUST reject requests that contain both 🚫
For newAccount requests, and for revokeCert requests authenticated by a certificate key, there MUST be a "jwk" field. This field MUST contain the public key corresponding to the private key used to sign the JWS 🗝️
For all other requests, the request is signed using an existing account, and there MUST be a "kid" field. This field MUST contain the account URL received by POSTing to the newAccount resource 🎟️
If the client sends a JWS signed with an algorithm that the server does not support, then the server MUST return an error with status code 400 (Bad Request) and type "urn:ietf:params:acme:error:badSignatureAlgorithm". The problem document returned with the error MUST include an "algorithms" field with an array of supported "alg" values. See Section 6.7 for more details on the structure of error responses 💀
If the server supports the signature algorithm "alg" but either does not support or chooses to reject the public key "jwk", then the server MUST return an error with status code 400 (Bad Request) and type "urn:ietf:params:acme:error:badPublicKey". The problem document detail SHOULD describe the reason for rejecting the public key. Some example reasons are:
"alg"is"RS256"but the modulus"n"is too small (e.g., 512-bit)"alg"is"ES256"but"jwk"does not contain a valid P-256 public key"alg"is"EdDSA"and"crv"is"Ed448", but the server only supports"EdDSA"with"Ed25519"- the corresponding private key is known to have been compromised
Because client requests in ACME carry JWS objects in the Flattened JSON Serialization, they must have theContent-Typeheader field set to"application/jose+json". If a request does not meet this requirement, then the server MUST return a response with status code 415 (Unsupported Media Type). 🚨
6.3. GET and POST-as-GET Requests
okay so heads up 👀 signed JWS request bodies mean requests without a body aren't authenticated, and that includes GET requests 🙈 which is why this section matters so much. Except for the cases described here, if the server gets a GET request, it MUST return an error with status code 405 (Method Not Allowed) and type "malformed". not it 💀
if a client wants to fetch a resource (the thing that would normally be a GET) 🧚, it MUST send a POST request with a JWS body like we covered above, where the payload of the JWS is a zero-length octet string. so basically the "payload" field of the JWS object MUST be present and set to the empty string ("") ✨
we're calling these "POST-as-GET" requests 💖 when the server gets one with a zero-length (and therefore non-JSON) payload, it MUST authenticate the sender and verify any access control rules. otherwise, the server MUST treat the request as having the same semantics as a GET request for the same resource. the vibe is giving "same result, different outfit" 👗
the server MUST allow GET requests for the directory and newNonce resources (see Section 7.1), on top of POST-as-GET requests for those same resources. this is how clients get their foot in the door and bootstrap into the ACME authentication system 🚪🌸
6.4. Request URL Integrity
it's super common in deployment for the entity terminating TLS for HTTPS to be a different entity from the one running the logical HTTPS server 🌐 so there's a "request routing" layer in the middle. for example, an ACME CA might have a content delivery network terminate TLS connections from clients so it can inspect requests for denial-of-service (DoS) protection 🛡️
these middlemen can also change values in the request that aren't signed in the HTTPS request, like the request URL and header fields 😬 ACME uses JWS to provide an integrity mechanism, and that protects against an intermediary swapping the request URL for a different ACME URL. no sneaky switcheroos allowed 🙅♀️
as noted in Section 6.2, every ACME request object carries a "url" header parameter in its protected header. this header parameter encodes the URL the client is sending the request to. when the server receives one of these objects in an HTTP request, it MUST compare the "url" header parameter to the request URL. if the two don't match, the server MUST reject the request as unauthorized 🚫
except for the directory resource, every ACME resource is addressed with URLs the server hands to the client. for POST requests sent to these resources, the client MUST set the "url" header parameter to the exact string the server provided (no re-encoding the URL, bestie 🙅♀️). the server SHOULD do the matching string equality check, configuring each resource with the URL string given to clients and having the resource check that requests carry the same string in their "url" header parameter. and if that string equality check fails, the server MUST reject the request as unauthorized 🔒
6.4.1. "url" (URL) JWS Header Parameter
the "url" header parameter specifies the URL [RFC3986] that this JWS object is aimed at 🎯 the "url" header parameter MUST be carried in the protected header of the JWS, and its value MUST be a string representing the target URL 💜
6.5. Replay Protection
to keep ACME resources safe from replay attacks 🔁, ACME POST requests come with a mandatory anti-replay mechanism. the server keeps a list of nonces it has issued, and any signed request from the client has to carry one of those nonces. no nonce, no entry 🎟️
an ACME server hands nonces to clients using the HTTP Replay-Nonce header field, as specified in Section 6.5.1. the server MUST include a Replay-Nonce header field in every successful response to a POST request, and it SHOULD include one in error responses too 💅
every JWS an ACME client sends MUST include the "nonce" header parameter in its protected header, with contents as defined in Section 6.5.2 🌈 during JWS verification, the ACME server MUST verify that the "nonce" value is one it previously gave out in a Replay-Nonce header field. once a nonce shows up in an ACME request, the server MUST consider it invalid, same as a value it never issued. it's a one-time pass, periodt 🦋
when a server rejects a request because the nonce was unacceptable (or missing), it MUST provide HTTP status code 400 (Bad Request) and indicate the ACME error type "urn:ietf:params:acme:error:badNonce" 🙈 an error response with the "badNonce" error type MUST include a Replay-Nonce header field with a fresh nonce that the server will accept if the original request is retried (and maybe in other requests too, depending on the server's nonce scoping policy) ✨ when a client gets that response, it SHOULD retry the request using the new nonce 🔁
how servers generate and track nonces is totally up to them 🧁 for example, the server could make a random 128-bit value for each response, keep a list of issued nonces, and strike each nonce from the list once it's used.
other than the rule above about nonces issued in "badNonce" responses, ACME doesn't dictate how servers scope nonces. clients MAY assume nonces have broad scope, like having one big pool of nonces for every request 🌊 but when retrying after a "badNonce" error, the client MUST use the nonce from the error response. servers should scope nonces broadly enough that retries don't happen very often 🫶
6.5.1. Replay-Nonce
the Replay-Nonce HTTP header field carries a server-generated value that the server can use to spot unauthorized replays in future client requests 🕵️♀️ the server MUST generate Replay-Nonce values so they're unique to each message (with high probability) and unpredictable to everyone except the server. generating them randomly is fine, for instance 🎲
the value of the Replay-Nonce header field MUST be an octet string encoded with the base64url encoding described in Section 2 of [RFC7515] 🧩 clients MUST ignore invalid Replay-Nonce values. here's the ABNF [RFC5234] for the Replay-Nonce header field:
base64url = ALPHA / DIGIT / "-" / "_"
Replay-Nonce = 1*base64url
the Replay-Nonce header field SHOULD NOT be included in HTTP request messages 🙅♀️ it's a server-to-client thing only
6.5.2. "nonce" (Nonce) JWS Header Parameter
the "nonce" header parameter gives a unique value so the verifier of a JWS can tell when replay has happened 🔍 the "nonce" header parameter MUST be carried in the protected header of the JWS.
the value of the "nonce" header parameter MUST be an octet string, encoded with the base64url encoding described in Section 2 of [RFC7515]. if a "nonce" value isn't valid under that encoding, the verifier MUST reject the JWS as malformed 💀 no mystery nonces
6.6. Rate Limits
creation of resources can be rate limited by ACME servers to keep usage fair and stop abuse 🚦 once the rate limit is exceeded, the server MUST respond with an error of the type "urn:ietf:params:acme:error:rateLimited" 🙃 the server SHOULD also send a Retry-After header field [RFC7231] saying when the current request might succeed again. if multiple rate limits are in place, that's the time when all of them allow the current request again with exactly the same parameters ⏰
in addition to the human-readable "detail" field of the error response, the server MAY send one or more link relations in the Link header field [RFC8288] pointing to documentation about the specific rate limit that was hit, using the help link relation type 📚🎀
6.7. Errors ✨
Okay so errors in ACME can show up in two places: at the HTTP layer and inside challenge objects (that's Section 8, bestie 🧚). ACME servers can send back an HTTP error response code, which is anything in the 4XX or 5XX range. Like, if a client sends a method this document doesn't allow, the server MAY send back status code 405 (Method Not Allowed) 🙈
When the server responds with an error status, it SHOULD include extra info in a problem document [RFC7807] 💅 To make it easy for clients to auto-handle errors, this document defines the standard tokens below for the "type" field. They all live in the ACME URN namespace, which is urn:ietf:params:acme:error: 🎀
Type Description
---------------------- ----------------------------------------
accountDoesNotExist The request specified an account that
does not exist
alreadyRevoked The request specified a certificate to
be revoked that has already been
revoked
badCSR The CSR is unacceptable (e.g., due to a
short key)
badNonce The client sent an unacceptable anti-
replay nonce
badPublicKey The JWS was signed by a public key the
server does not support
badRevocationReason The revocation reason provided is not
allowed by the server
badSignatureAlgorithm The JWS was signed with an algorithm
the server does not support
caa Certification Authority Authorization
(CAA) records forbid the CA from
issuing a certificate
compound Specific error conditions are indicated
in the "subproblems" array
connection The server could not connect to
validation target
dns There was a problem with a DNS query
during identifier validation
externalAccountRequired The request must include a value for
the "externalAccountBinding" field
incorrectResponse Response received didn't match the
challenge's requirements
invalidContact A contact URL for an account was
invalid
malformed The request message was malformed
orderNotReady The request attempted to finalize an
order that is not ready to be finalized
rateLimited The request exceeds a rate limit
rejectedIdentifier The server will not issue certificates
for the identifier
serverInternal The server experienced an internal
error
tls The server received a TLS error during
validation
unauthorized The client lacks sufficient
authorization
unsupportedContact A contact URL for an account used an
unsupported protocol scheme
unsupportedIdentifier An identifier is of an unsupported type
userActionRequired Visit the "instance" URL and take
actions specified there
That list isn't the whole story, periodt 💅 The server MAY return errors whose "type" is some other URI that isn't on the list above. BUT servers MUST NOT use the ACME URN namespace for errors that aren't listed in the matching IANA registry (see Section 9.6), so no making up your own URNs in there 🙅♀️ And clients SHOULD display the "detail" field of every error, because that's where the good tea is 🫶
From here on out, we'll use the short tokens from the table instead of the full URNs. So when you see "error of type 'badCSR'", that just means an error document whose "type" value is urn:ietf:params:acme:error:badCSR ✨
6.7.1. Subproblems 🌸
Sometimes a CA needs to send back more than one error for a single request. Also, the CA might need to pin each error to a specific identifier. For example, a newOrder request could include several identifiers that the CA can't issue certificates for. In that case, an ACME problem document MAY include a "subproblems" field, which is a JSON array of problem documents. Each of those MAY include an "identifier" field. If the "identifier" field is there, it MUST contain an ACME identifier (Section 9.7.7) 🧚
The "identifier" field MUST NOT show up at the top level of an ACME problem document. It can only live inside subproblems. And the subproblems don't all have to share the same type, nor do they have to match the top-level type. Mix and match, it's giving variety 🦋
ACME clients can treat the "identifier" field of a subproblem as a hint. It suggests that the operation might work if that identifier got left out. For instance, say an order has ten DNS identifiers, and the newOrder response is a problem document with two subproblems pointing at two of them. The client can then place a new order with only the eight identifiers that weren't flagged 🍓
Here's what that looks like on the wire:
HTTP/1.1 403 Forbidden
Content-Type: application/problem+json
Link: <https://example.com/acme/directory>;rel="index"
{
"type": "urn:ietf:params:acme:error:malformed",
"detail": "Some of the identifiers requested were rejected",
"subproblems": [
{
"type": "urn:ietf:params:acme:error:malformed",
"detail": "Invalid underscore in DNS name \"_example.org\"",
"identifier": {
"type": "dns",
"value": "_example.org"
}
},
{
"type": "urn:ietf:params:acme:error:rejectedIdentifier",
"detail": "This CA will not issue for \"example.net\"",
"identifier": {
"type": "dns",
"value": "example.net"
}
}
]
}
7. Certificate Management 🎀
In this section, we get into the certificate management stuff that ACME makes possible. There are five functions, and they're all giving main character energy 👑
- Account Creation 🧚
- Ordering a Certificate 🌈
- Identifier Authorization 💜
- Certificate Issuance 🦋
- Certificate Revocation 🔥
7.1. Resources 🎀
okay so bestie, ACME is built as an HTTP-based app, and it's made of a few different types of resources 💅 Here's the squad:
- Account resources, representing information about an account (Section 7.1.2, Section 7.3)
- Order resources, representing an account's requests to issue certificates (Section 7.1.3)
- Authorization resources, representing an account's authorization to act for an identifier (Section 7.1.4)
- Challenge resources, representing a challenge to prove control of an identifier (Section 7.5, Section 8)
- Certificate resources, representing issued certificates (Section 7.4.2)
- A
directoryresource (Section 7.1.1) - A
newNonceresource (Section 7.2) - A
newAccountresource (Section 7.3) - A
newOrderresource (Section 7.4) - A
revokeCertresource (Section 7.6) - A
keyChangeresource (Section 7.3.5)
The server MUST provide directory and newNonce resources. 🔥 Those two are non-negotiable, no cap.
ACME uses a bunch of different URLs for different management jobs 🦋 Each job is listed in a directory next to its URL, so clients only need to be set up with the directory URL. Lowkey genius. These URLs are linked together with a few different link relations [RFC8288].
The "up" link relation is used with challenge resources to point at the authorization resource that a challenge belongs to. It's also used, with some media types, from certificate resources to point at a resource where the client can grab a chain of CA certificates that could be used to validate the certificate in the original resource 🌸
The "index" link relation shows up on every resource except the directory, and it points at the URL of the directory. ✨
Here's a diagram of how the resources connect on an ACME server 🧚 Most of these relations are expressed by URLs given as strings in the resources' JSON representations. The labels in quotes are the HTTP link relations.
directory
|
+--> newNonce
|
+----------+----------+-----+-----+------------+
| | | | |
| | | | |
V V V V V
newAccount newAuthz newOrder revokeCert keyChange
| | |
| | |
V | V
account | order --+--> finalize
| | |
| | +--> cert
| V
+---> authorization
| ^
| | "up"
V |
challenge
ACME Resources and Relationships
Next up is a typical sequence of requests. This is the whole journey: making a new account, proving you control an identifier, getting a cert, and then fetching an updated cert some time after it's issued 🦋 The "->" is a mnemonic for a Location header field pointing to a created resource.
+-------------------+--------------------------------+--------------+
| Action | Request | Response |
+-------------------+--------------------------------+--------------+
| Get directory | GET directory | 200 |
| | | |
| Get nonce | HEAD newNonce | 200 |
| | | |
| Create account | POST newAccount | 201 -> |
| | | account |
| | | |
| Submit order | POST newOrder | 201 -> order |
| | | |
| Fetch challenges | POST-as-GET order's | 200 |
| | authorization urls | |
| | | |
| Respond to | POST authorization challenge | 200 |
| challenges | urls | |
| | | |
| Poll for status | POST-as-GET order | 200 |
| | | |
| Finalize order | POST order's finalize url | 200 |
| | | |
| Poll for status | POST-as-GET order | 200 |
| | | |
| Download | POST-as-GET order's | 200 |
| certificate | certificate url | |
+-------------------+--------------------------------+--------------+
The rest of this section gets into the details of how these resources are built and how ACME uses them 💜
7.1.1. Directory 🗺️
So ACME servers give clients a directory object to help them set up the right URLs for each ACME operation 💖 This should be the only URL clients need to get configured. It's a JSON object. Its field names come from the resource registry (Section 9.7.5), and its values are the matching URLs.
+------------+--------------------+
| Field | URL in Value |
+------------+--------------------+
| newNonce | New nonce |
| | |
| newAccount | New account |
| | |
| newOrder | New order |
| | |
| newAuthz | New authorization |
| | |
| revokeCert | Revoke certificate |
| | |
| keyChange | Key change |
+------------+--------------------+
The directory URL has no real constraints, except that it should be different from the other ACME server resources' URLs and shouldn't clash with other services 🌈 For example:
- a host that functions as both an ACME and a Web server may want to keep the root path "/" for an HTML "front page" and place the ACME directory under the path "/acme".
- a host that only functions as an ACME server could place the directory under the path "/".
🚨 If the ACME server does not implement pre-authorization (Section 7.4.1), it MUST omit the "newAuthz" field of the directory. No exceptions, bestie.
The object MAY additionally contain a "meta" field. If it's there, it MUST be a JSON object, and each field in that object is an item of metadata about the service the ACME server provides ✨
The following metadata items are defined (Section 9.7.6), and all of them are OPTIONAL 🌸
termsOfService(optional, string): A URL identifying the current terms of service.website(optional, string): An HTTP or HTTPS URL locating a website providing more information about the ACME server.caaIdentities(optional, array of string): The hostnames that the ACME server recognizes as referring to itself for the purposes of CAA record validation as defined in [RFC6844]. Each string MUST represent the same sequence of ASCII code points that the server will expect to see as the "Issuer Domain Name" in a CAA issue or issuewild property tag. This lets clients figure out the correct issuer domain name to use when they configure CAA records 🧚externalAccountRequired(optional, boolean): If this field is present and set to "true", then the CA requires that all newAccount requests include an"externalAccountBinding"field associating the new account with an external account 🔐
Clients get the directory by sending a GET request to the directory URL 📥
HTTP/1.1 200 OK
Content-Type: application/json
{
"newNonce": "https://example.com/acme/new-nonce",
"newAccount": "https://example.com/acme/new-account",
"newOrder": "https://example.com/acme/new-order",
"newAuthz": "https://example.com/acme/new-authz",
"revokeCert": "https://example.com/acme/revoke-cert",
"keyChange": "https://example.com/acme/key-change",
"meta": {
"termsOfService": "https://example.com/acme/terms/2017-5-30",
"website": "https://www.example.com/",
"caaIdentities": ["example.com"],
"externalAccountRequired": false
}
}
7.1.2. Account Objects 🎀
An ACME account resource is basically a bundle of metadata tied to an account 💅 Account resources have the following structure:
status(required, string): The status of this account. Possible values are "valid", "deactivated", and "revoked". The value "deactivated" should be used to indicate client-initiated deactivation, whereas "revoked" should be used to indicate server-initiated deactivation. See Section 7.1.6.contact(optional, array of string): An array of URLs that the server can use to contact the client about issues related to this account. For example, the server may wish to notify the client about server-initiated revocation or certificate expiration. For information on supported URL schemes, see Section 7.3. 📧termsOfServiceAgreed(optional, boolean): Including this field in a newAccount request, with a value of true, indicates the client's agreement with the terms of service. This field cannot be updated by the client 🫶externalAccountBinding(optional, object): Including this field in a newAccount request indicates approval by the holder of an existing non-ACME account to bind that account to this ACME account. This field is not updateable by the client (see Section 7.3.4).orders(required, string): A URL from which a list of orders submitted by this account can be fetched via a POST-as-GET request, as described in Section 7.1.2.1 🍓
{
"status": "valid",
"contact": [
"mailto:cert-admin@example.org",
"mailto:admin@example.org"
],
"termsOfServiceAgreed": true,
"orders": "https://example.com/acme/orders/rzGoeA"
}
7.1.2.1. Orders List 📋✨
okay so the orders list is literally how a client finds every order its account has ever made 🫶 each account object has an "orders" URL, and you grab the list with a POST-as-GET request 💖 the result MUST be a JSON object whose "orders" field is an array of URLs, one per order that belongs to the account 🎀 the server SHOULD include pending orders 🌸 and SHOULD NOT include invalid ones in that array 🙈 the server MAY return an incomplete list too 🦋 if it does, it sends a Link header with a "next" link relation pointing at where the rest lives 💅
HTTP/1.1 200 OK
Content-Type: application/json
Link: <https://example.com/acme/directory>;rel="index"
Link: <https://example.com/acme/orders/rzGoeA?cursor=2>;rel="next"
{
"orders": [
"https://example.com/acme/order/TOlocE8rfgo",
"https://example.com/acme/order/4E16bbL5iSw",
/* more URLs not shown for example brevity */
"https://example.com/acme/order/neBHYLfw0mg"
]
}
7.1.3. Order Objects 🧾💜
an ACME order object is the client's request for a certificate, and it's how you track that request all the way to issuance 🚀 so it holds info about the certificate you asked for, the authorizations the server needs you to finish, and any certificates that came out of the order 🎁
status (required, string): the status of this order 💅 possible values are "pending", "ready", "processing", "valid", and "invalid" ✨ see Section 7.1.6.
expires (optional, string): the timestamp after which the server will consider this order invalid 🕰️ it's encoded in the format from [RFC3339] 🌈 this field is REQUIRED for objects with "pending" or "valid" in the status field 🫶
identifiers (required, array of object): an array of identifier objects the order is about 🦋
type (required, string): the type of identifier 💖 this document defines the "dns" identifier type 🌸 see the registry defined in Section 9.7.7 for any others 🔥
value (required, string): the identifier itself 💅
notBefore (optional, string): the requested value of the notBefore field in the certificate, in the date format from [RFC3339] 📅
notAfter (optional, string): the requested value of the notAfter field in the certificate, in the date format from [RFC3339] 📅
error (optional, object): the error that happened while processing the order, if there was one 😭 this field is structured as a problem document [RFC7807] 🎀
authorizations (required, array of string): for pending orders, these are the authorizations the client still needs to complete before the certificate can be issued (see Section 7.5) 🌷 this includes unexpired authorizations the client already finished in the past for identifiers in the order ✨ the authorizations required are dictated by server policy, so there may not be a 1:1 relationship between order identifiers and authorizations 🙃 for final orders (in the "valid" or "invalid" state), it's the authorizations that were completed 🏁 each entry is a URL you can fetch with a POST-as-GET request 💜
finalize (required, string): a URL that a CSR MUST be POSTed to once all of the order's authorizations are satisfied, to finalize the order 🔥 a successful finalization populates the certificate URL for the order 👑
certificate (optional, string): a URL for the certificate that was issued in response to this order 🎀
{
"status": "valid",
"expires": "2016-01-20T14:09:07.99Z",
"identifiers": [
{ "type": "dns", "value": "www.example.org" },
{ "type": "dns", "value": "example.org" }
],
"notBefore": "2016-01-01T00:00:00Z",
"notAfter": "2016-01-08T00:00:00Z",
"authorizations": [
"https://example.com/acme/authz/PAniVnsZcis",
"https://example.com/acme/authz/r4HqLzrSrpI"
],
"finalize": "https://example.com/acme/order/TOlocE8rfgo/finalize",
"certificate": "https://example.com/acme/cert/mAt3xBGaobw"
}
any identifier of type "dns" in a newOrder request MAY have a wildcard domain name as its value 🌈 a wildcard domain name is a single asterisk followed by a single full stop (".") and then a domain name, as defined for the Subject Alternate Name Extension by [RFC5280] 🦋 an authorization the server returns for a wildcard domain name identifier MUST NOT include that "." prefix in the authorization identifier value 🙈 and the returned authorization MUST include the optional "wildcard" field, with a value of true 💖
⚠️ the elements of the "authorizations" and "identifiers" arrays are immutable once set 😤 the server MUST NOT change the contents of either array after they're created 🔒 if a client sees a change in the contents of either array, it SHOULD consider the order invalid 🚫
the "authorizations" array of the order SHOULD reflect all authorizations the CA takes into account when deciding to issue 💅 even if some were fulfilled in earlier orders or in pre-authorization transactions 🌸 for example, if a CA lets multiple orders be fulfilled from one authorization transaction, then it SHOULD reflect that authorization in all of those orders 🧚
just because an authorization URL is listed in the "authorizations" array doesn't mean the client has to do anything 🫠 there are a few reasons the referenced authorizations may already be valid:
- the client completed the authorization as part of a previous order 🎀
- the client previously pre-authorized the identifier (see Section 7.4.1) ✨
- the server granted the client authorization based on an external account 🌷
clients SHOULD check the "status" field of an order to figure out whether they need to take any action 👀💅
7.1.4. Authorization Objects 🔑🦋
an ACME authorization object is the server's authorization for an account to represent an identifier 💖 along with the identifier, it has some metadata, like the status of the authorization (e.g., "pending", "valid", or "revoked") and which challenges were used to validate possession of the identifier 🌈
the structure of an ACME authorization resource looks like this 👇
identifier (required, object): the identifier the account is authorized to represent 🫶
type (required, string): the type of identifier (see below and Section 9.7.7) 🌸
value (required, string): the identifier itself 💅
status (required, string): the status of this authorization 💜 possible values are "pending", "valid", "invalid", "deactivated", "expired", and "revoked" ✨ see Section 7.1.6.
expires (optional, string): the timestamp after which the server will consider this authorization invalid 🕰️ encoded in the format from [RFC3339] 🌈 this field is REQUIRED for objects with "valid" in the "status" field 🫶
challenges (required, array of objects): for pending authorizations, the challenges the client can fulfill to prove possession of the identifier 🎯 for valid authorizations, the challenge that was validated ✅ for invalid authorizations, the challenge that was attempted and failed 😭 each array entry is an object with the parameters needed to validate the challenge 🧩 a client SHOULD attempt to fulfill one of these challenges, and a server SHOULD consider any one of them sufficient to make the authorization valid 💅
wildcard (optional, boolean): this field MUST be present and true for authorizations created from a newOrder request containing a DNS identifier whose value was a wildcard domain name 🌟 for other authorizations, it MUST be absent 🙅 wildcard domain names are described in Section 7.1.3 🎀
the only identifier type this specification defines is a fully qualified domain name (type: "dns") 🌍 the domain name MUST be encoded in the form it would appear in a certificate 📜 that means it MUST be encoded according to the rules in Section 7 of [RFC5280] 💖 servers MUST verify that any identifier values beginning with the ASCII-Compatible Encoding prefix "xn--" as defined in [RFC5890] are properly encoded 🔍 wildcard domain names (with "*" as the first label) MUST NOT be included in authorization objects 🚫 if an authorization object conveys authorization for the base domain of a newOrder DNS identifier containing a wildcard domain name, then the optional authorizations "wildcard" field MUST be present with a value of true 🌸
Section 8 describes a set of challenges for domain name validation 🧁✨
{
"status": "valid",
"expires": "2015-03-01T14:09:07.99Z",
"identifier": {
"type": "dns",
"value": "www.example.org"
},
"challenges": [
{
"url": "https://example.com/acme/chall/prV_B7yEyA4",
"type": "http-01",
"status": "valid",
"token": "DGyRejmCefe7v4NfDGDKfA",
"validated": "2014-12-01T12:05:58.16Z"
}
],
"wildcard": false
}
7.1.5. Challenge Objects 🎯💜
an ACME challenge object is the server's offer to validate your possession of an identifier in a specific way 🦋 unlike the other objects above, there isn't a single standard structure for a challenge object 🙃 the contents depend on the validation method being used 🌈 the general structure of challenge objects and an initial set of validation methods are described in Section 8 💅👑
7.1.6. Status Changes ✨
okay so every ACME object has its own little life story, and it runs on a state machine 🧚 The status field is basically the object's current mood, telling you which state it's in right now 💅
Challenge objects are born in "pending". They hop over to "processing" once the client responds to the challenge (see Section 7.5.1) and the server starts checking that the client actually finished it 🔥 While they're in "processing", the server might validate the challenge more than once (see Section 8.2). Retry requests from the client don't change the state either, so asking again doesn't reset anything 🫶 If validation works out, the challenge goes "valid". If something goes wrong, it goes "invalid" 😭
pending
|
| Receive
| response
V
processing <-+
| | | Server retry or
| | | client retry request
| +----+
|
|
Successful | Failed
validation | validation
+---------+---------+
| |
V V
valid invalid
State Transitions for Challenge Objects
Authorization objects start out "pending" too 🦋 If any challenge inside the authorization reaches "valid", the authorization is "valid" as well. If the client tries to fulfill a challenge and fails, or an error shows up while the authorization is still pending, it goes "invalid" 💀 Once it's "valid", it can "expire" ("expired"), get deactivated by the client ("deactivated", see Section 7.5.2), or get revoked by the server ("revoked"). Iconic glow-up, then a few different exits 🌸
pending --------------------+
| |
Challenge failure | |
or | |
Error | Challenge valid |
+---------+---------+ |
| | |
V V |
invalid valid |
| |
| |
| |
+--------------+--------------+
| | |
| | |
Server | Client | Time after |
revoke | deactivate | "expires" |
V V V
revoked deactivated expired
State Transitions for Authorization Objects
Order objects are created "pending" 🍓 When every authorization on the order is "valid", the order becomes "ready". It moves to "processing" after the client submits a request to the order's "finalize" URL and the CA starts the issuance process for the certificate 🔥 Once the certificate is issued, the order is "valid". If an error happens at any of these stages, it goes "invalid". It also goes "invalid" if it expires, or if one of its authorizations lands in a final state other than "valid" ("expired", "revoked", or "deactivated"). Drama, but make it organized 👑
pending --------------+
| |
| All authz |
| "valid" |
V |
ready ---------------+
| |
| Receive |
| finalize |
| request |
V |
processing ------------+
| |
| Certificate | Error or
| issued | Authorization failure
V V
valid invalid
State Transitions for Order Objects
Account objects are created "valid", because once a successful newAccount request goes through, there's nothing else to do 💖 If the client deactivates the account or the server revokes it, it moves into the matching state 🌈
valid
|
|
+-----------+-----------+
Client | Server |
deactiv.| revoke |
V V
deactivated revoked
State Transitions for Account Objects
Heads up, though: some of these states may never show up in a "status" field, depending on how the server behaves 🙈 For example, a server that issues synchronously will never show an order in the "processing" state. A server that deletes expired authorizations right away will never show an authorization in the "expired" state. So don't panic if you don't see every single one 🧚
7.2. Getting a Nonce 💜
Before the client sends a POST request to the server, it needs a fresh anti-replay nonce to put in the "nonce" header of the JWS 🎀 Usually the client already has one from a previous request. Sometimes, though, it needs a brand-new one, like on its very first request to the server or when an existing nonce stops being valid 😭
To grab a fresh nonce, the client sends a HEAD request to the newNonce resource on the server 🦋 The server's response MUST include a Replay-Nonce header field with a fresh nonce, and SHOULD have status code 200 (OK). The server MUST also respond to GET requests for this resource. Those come back with an empty body (but still include a Replay-Nonce header) and a status code of 204 (No Content) ✨
HEAD /acme/new-nonce HTTP/1.1
Host: example.com
HTTP/1.1 200 OK
Replay-Nonce: oFvnlFP1wIhRlYS2jTaXbA
Cache-Control: no-store
Link: <https://example.com/acme/directory>;rel="index"
Proxy caching is sneaky 🙈 If a proxy caches responses from the newNonce resource, clients can keep getting the same nonce over and over, which leads to "badNonce" errors. So the server MUST include a Cache-Control header field with the "no-store" directive in responses for the newNonce resource, to stop that caching from happening 🔥
7.3. Account Management 🎀
In this part, we walk through how an ACME client can create an account on an ACME server, and how it can tweak the account after it's been made 💅
To create a new account, the client sends a POST request to the server's newAccount URL 🌸 The body is a stub account object with some subset of these fields:
contact(optional, array of string): Same meaning as the corresponding server field defined in Section 7.1.2.termsOfServiceAgreed(optional, boolean): Same meaning as the corresponding server field defined in Section 7.1.2.onlyReturnExisting(optional, boolean): If this field is present with the value "true", then the server MUST NOT create a new account if one does not already exist. This lets a client look up an account URL based on an account key (see Section 7.3.1).externalAccountBinding(optional, object): Same meaning as the corresponding server field defined in Section 7.1.2
Here's what a full request looks like 🧚
POST /acme/new-account HTTP/1.1
Host: example.com
Content-Type: application/jose+json
{
"protected": base64url({
"alg": "ES256",
"jwk": {...},
"nonce": "6S8IqOGY7eL2lsGoTZYifg",
"url": "https://example.com/acme/new-account"
}),
"payload": base64url({
"termsOfServiceAgreed": true,
"contact": [
"mailto:cert-admin@example.org",
"mailto:admin@example.org"
]
}),
"signature": "RZPOnYoPs1PhjszF...-nh6X1qtOFPB519I"
}
The server MUST ignore any values the client sends in the "orders" fields of account objects, plus any other fields it doesn't recognize 🙈 If new fields get specified in the future, their specification MUST describe whether the client is allowed to provide them. The server MUST NOT reflect the "onlyReturnExisting" field or any unrecognized fields in the resulting account object. This is the clever part: it lets clients figure out when a server doesn't support an extension field 👑
The server SHOULD validate that the contact URLs in the "contact" field are valid and supported by the server. If it does validate them, it MUST support the "mailto" scheme. Clients MUST NOT give a "mailto" URL in the "contact" field that contains "hfields" [RFC6068] or more than one "addr-spec" in the "to" component. If the server runs into a "mailto" contact URL that doesn't meet these rules, it SHOULD reject it as invalid 💀
If the server rejects a contact URL because the scheme isn't supported, it MUST return an error of type "unsupportedContact", with a description of the error and what types of contact URLs it considers acceptable. If the server rejects a contact URL that uses a supported scheme but has an invalid value, the server MUST return an error of type "invalidContact" 🫶
If the server wants the client to agree to terms before using the ACME service, it MUST indicate the URL for those terms in the "termsOfService" subfield of the "meta" field in the directory object. The server MUST also reject newAccount requests that don't have "termsOfServiceAgreed" set to "true" 🔥 Clients SHOULD NOT automatically agree to terms by default. Instead, they SHOULD ask the user for some interaction before agreeing to the terms. No sneaky auto-yes, bestie 🙅♀️
When everything checks out, the server creates the account and stores the public key used to verify the JWS (that's the "jwk" element in the JWS header) so it can authenticate future requests from that account 🦋 It sends this account object back in a 201 (Created) response, with the account URL in a Location header field. That account URL becomes the "kid" value in the JWS for every later request from the account (see Section 6.2). It's also how you make management changes to the account, as described below ✨
HTTP/1.1 201 Created
Content-Type: application/json
Replay-Nonce: D8s4D2mLs8Vn-goWuPQeKA
Link: <https://example.com/acme/directory>;rel="index"
Location: https://example.com/acme/acct/evOfKhNU60wg
{
"status": "valid",
"contact": [
"mailto:cert-admin@example.org",
"mailto:admin@example.org"
],
"orders": "https://example.com/acme/acct/evOfKhNU60wg/orders"
}
7.3.1. Finding an Account URL Given a Key 🔑
okay so if the server gets a newAccount request signed with a key that already has an account registered to it, it MUST return a response with status code 200 (OK) and hand over that account's URL in the Location header field. The body is the account object exactly as it looked on the server before this request, and any fields in the request object MUST be ignored. This is giving recovery mode ✨ A client that has its account key but lost the account URL can get that URL back.
If a client wants to find the URL for an existing account and does not want a brand new one created when none exists, it SHOULD send a POST request to the newAccount URL with a JWS whose payload has an onlyReturnExisting field set to true ({"onlyReturnExisting": true}) 🧚 If the client sends that and no account exists, the server MUST return an error response with status code 400 (Bad Request) and type urn:ietf:params:acme:error:accountDoesNotExist 💀
7.3.2. Account Update 🎀
If the client wants to update its info later, it sends a POST request with the new details to the account URL. The server MUST ignore any updates to the orders field, the termsOfServiceAgreed field (see Section 7.3.3), the status field (except as allowed by Section 7.3.6), or any other fields it does not recognize. If the server accepts the update, it MUST return a response with a 200 (OK) status code and the resulting account object.
Like, to update the contact info on the account from before, the client could send this request 💅
POST /acme/acct/evOfKhNU60wg HTTP/1.1
Host: example.com
Content-Type: application/jose+json
{
"protected": base64url({
"alg": "ES256",
"kid": "https://example.com/acme/acct/evOfKhNU60wg",
"nonce": "ax5RnthDqp_Yf4_HZnFLmA",
"url": "https://example.com/acme/acct/evOfKhNU60wg"
}),
"payload": base64url({
"contact": [
"mailto:certificates@example.org",
"mailto:admin@example.org"
]
}),
"signature": "hDXzvcj8T6fbFbmn...rDzXzzvzpRy64N0o"
}
7.3.3. Changes of Terms of Service 📜
Like we talked about above, a client can say yes to the CA's terms of service by setting the termsOfServiceAgreed field in its account object to true.
If the server has changed its terms since a client first agreed, and it won't process a request without a fresh yes to the new ones, it MUST return an error response with status code 403 (Forbidden) and type urn:ietf:params:acme:error:userActionRequired. This response MUST include a Link header field with link relation terms-of-service and the latest terms-of-service URL. 🙅
The problem document returned with the error MUST also include an instance field, indicating a URL that the client should direct a human user to visit in order for instructions on how to agree to the terms. 🫶
HTTP/1.1 403 Forbidden
Replay-Nonce: T81bdZroZ2ITWSondpTmAw
Link: <https://example.com/acme/directory>;rel="index"
Link: <https://example.com/acme/terms/2017-6-02>;rel="terms-of-service"
Content-Type: application/problem+json
Content-Language: en
{
"type": "urn:ietf:params:acme:error:userActionRequired",
"detail": "Terms of service have changed",
"instance": "https://example.com/acme/agreement/?token=W8Ih3PswD-8"
}
7.3.4. External Account Binding 🌈
The server MAY require a value for the externalAccountBinding field to be present in newAccount requests. This can be used to associate an ACME account with an existing account in a non-ACME system, such as a CA customer database.
To turn on ACME account binding, the CA running the ACME server has to give the ACME client a MAC key and a key identifier, using some mechanism outside of ACME. The key identifier MUST be an ASCII string. The MAC key SHOULD be provided in base64url-encoded form, to maximize compatibility between non-ACME provisioning systems and ACME clients. 🔐
Then the ACME client computes a binding JWS to show the external account holder approves the ACME account key. The payload of this JWS is the ACME account key being registered, in JWK form. The protected header of the JWS MUST meet the following criteria:
- The
algfield MUST indicate a MAC-based algorithm - The
kidfield MUST contain the key identifier provided by the CA - The
noncefield MUST NOT be present - The
urlfield MUST be set to the same value as the outer JWS
The signature field of the JWS will contain the MAC value computed with the MAC key provided by the CA. 🔏
POST /acme/new-account HTTP/1.1
Host: example.com
Content-Type: application/jose+json
{
"protected": base64url({
"alg": "ES256",
"jwk": /* account key */,
"nonce": "K60BWPrMQG9SDxBDS_xtSw",
"url": "https://example.com/acme/new-account"
}),
"payload": base64url({
"contact": [
"mailto:cert-admin@example.org",
"mailto:admin@example.org"
],
"termsOfServiceAgreed": true,
"externalAccountBinding": {
"protected": base64url({
"alg": "HS256",
"kid": /* key identifier from CA */,
"url": "https://example.com/acme/new-account"
}),
"payload": base64url(/* same as in "jwk" above */),
"signature": /* MAC using MAC key from CA */
}
}),
"signature": "5TWiqIYQfIDfALQv...x9C2mg8JGPxl5bI4"
}
If such a CA requires that newAccount requests contain an externalAccountBinding field, then it MUST provide the value true in the externalAccountRequired subfield of the meta field in the directory object. If the CA receives a newAccount request without an externalAccountBinding field, then it SHOULD reply with an error of type externalAccountRequired. 🚨
When a CA receives a newAccount request containing an externalAccountBinding field, it decides whether or not to verify the binding. If the CA does not verify the binding, then it MUST NOT reflect the externalAccountBinding field in the resulting account object (if any). To verify the account binding, the CA MUST take the following steps:
- Verify that the value of the field is a well-formed JWS
- Verify that the JWS protected field meets the above criteria
- Retrieve the MAC key corresponding to the key identifier in the
kidfield - Verify that the MAC on the JWS verifies using that MAC key
- Verify that the payload of the JWS represents the same key as was used to verify the outer JWS (i.e., the
jwkfield of the outer JWS)
If all of these checks pass and the CA creates a new account, then the CA may consider the new account associated with the external account corresponding to the MAC key. The account object the CA returns MUST include an externalAccountBinding field with the same value as the field in the request. If any of these checks fail, then the CA MUST reject the newAccount request. 🎉 that's the whole account-binding flow, bestie 🎉
7.3.5. Account Key Rollover ✨
okay so, sometimes a client wants to swap the public key attached to their account 🔑 maybe to recover from a key compromise, or just to get ahead of a compromise nobody has noticed yet. it's giving responsible key hygiene 💅
to change the key, the client sends a request to the server with signatures from both the old and new keys 🫶. the new key's signature covers the account URL and the old key. that basically says "hi, I'm the new key holder and I want to take over this account from the old key holder." the old key's signature covers that whole request, including its signature, and shows the old key holder agrees to the rollover. two keys, one vibe, no drama 🌸
first, the client builds a keyChange object that describes the account and its current account key:
account(required, string): The URL for the account being modified. The content of this field MUST be the exact string provided in the Location header field in response to the newAccount request that created the account.oldKey(required, JWK): The JWK representation of the old key.
then the client wraps that keyChange object in an "inner" JWS, signed with the requested new account key 🦋. this "inner" JWS becomes the payload of the "outer" JWS, which is the body of the ACME request. so it's JWS inside JWS, a little matryoshka of signatures 🪆
The outer JWS MUST meet the normal requirements for an ACME JWS request body (see Section 6.2). The inner JWS MUST meet the normal requirements too, with these differences:
- The inner JWS MUST have a
jwkheader parameter, containing the public key of the new key pair. - The inner JWS MUST have the same
urlheader parameter as the outer JWS. - The inner JWS MUST omit the
nonceheader parameter.
why both signatures? so the server can check that the holders of both keys actually agree to the change 🤝 the signatures are nested so that every signature on a POST message is still made by exactly one key, which keeps things clean and consistent ✨ the inner JWS is basically the new key holder saying "I want this account," and the outer JWS is the current account holder saying "yep, approved" 💜
Here's what the request looks like:
POST /acme/key-change HTTP/1.1
Host: example.com
Content-Type: application/jose+json
{
"protected": base64url({
"alg": "ES256",
"kid": "https://example.com/acme/acct/evOfKhNU60wg",
"nonce": "S9XaOcxP5McpnTcWPIhYuB",
"url": "https://example.com/acme/key-change"
}),
"payload": base64url({
"protected": base64url({
"alg": "ES256",
"jwk": /* new key */,
"url": "https://example.com/acme/key-change"
}),
"payload": base64url({
"account": "https://example.com/acme/acct/evOfKhNU60wg",
"oldKey": /* old key */
}),
"signature": "Xe8B94RD30Azj2ea...8BmZIRtcSKPSd8gU"
}),
"signature": "5TWiqIYQfIDfALQv...x9C2mg8JGPxl5bI4"
}
when a keyChange request lands, the server MUST do the following steps on top of the usual JWS validation 🧐
- Validate the POST request belongs to a currently active account, as described in Section 6.
- Check that the payload of the JWS is a well-formed JWS object (the "inner JWS").
- Check that the JWS protected header of the inner JWS has a
jwkfield. - Check that the inner JWS verifies using the key in its
jwkfield. - Check that the payload of the inner JWS is a well-formed keyChange object (as described above).
- Check that the
urlparameters of the inner and outer JWSs are the same. - Check that the
accountfield of the keyChange object contains the URL for the account matching the old key (i.e., thekidfield in the outer JWS). - Check that the
oldKeyfield of the keyChange object is the same as the account key for the account in question. - Check that no account exists whose account key is the same as the key in the
jwkheader parameter of the inner JWS.
if every check passes, the server swaps the old account key for the new public key and returns status code 200 (OK) 🎉 if anything fails, the server sends back an error status code and a problem document explaining what went wrong 🙈 and if there's already an account using the new key, the server SHOULD use status code 409 (Conflict) and put that account's URL in the Location header field. very helpful, very polite ✨
Changing the account key SHOULD NOT have any other impact on the account 🚫 for example, the server MUST NOT invalidate pending orders or authorization transactions just because the account key changed. the rest of your stuff stays put, bestie 💖
7.3.6. Account Deactivation 💀
a client can deactivate an account by posting a signed update to the account URL with a status field set to "deactivated" 🪦 clients might do this when the account key is compromised or it's being retired. once an account is deactivated, it can't request certificate issuance or touch its resources anymore, like orders or authorizations. if the server gets a POST or POST-as-GET from a deactivated account, it MUST return an error response with status code 401 (Unauthorized) and the type "urn:ietf:params:acme:error:unauthorized" 🚪
POST /acme/acct/evOfKhNU60wg HTTP/1.1
Host: example.com
Content-Type: application/jose+json
{
"protected": base64url({
"alg": "ES256",
"kid": "https://example.com/acme/acct/evOfKhNU60wg",
"nonce": "ntuJWWSic4WVNSqeUmshgg",
"url": "https://example.com/acme/acct/evOfKhNU60wg"
}),
"payload": base64url({
"status": "deactivated"
}),
"signature": "earzVLd3m5M4xJzR...bVTqn7R08AKOVf3Y"
}
the server MUST verify that the request is signed by the account key 🔐 if it accepts the deactivation, it replies with a 200 (OK) status code and the current contents of the account object 🫧
once an account is deactivated, the server MUST NOT accept any more requests authorized by that account's key 🛑 the server SHOULD cancel any pending operations tied to that key, like certificate orders. a server can take all sorts of actions after a deactivation, e.g., deleting data related to that account or emailing the account's contacts 📬 and servers SHOULD NOT revoke certificates issued by the deactivated account, because that could cause operational chaos for the servers using those certs 😭 also, ACME has no way to reactivate a deactivated account, so this is a one-way door, babe 🚪✨
7.4. Applying for Certificate Issuance ✨
okay so bestie, this is the moment the client actually asks for a cert 🎀 it kicks things off by POSTing to the server's newOrder resource. The body is a JWS object, and its JSON payload is basically a slice of the order object from Section 7.1.3, just the bits that describe the cert being requested 💅
identifiers (required, array of object): an array of identifier objects the client wants an order for 🍓
type (required, string): the type of identifier 🌸
value (required, string): the identifier itself 💖
notBefore (optional, string): the requested value of the notBefore field in the certificate, in the date format defined in [RFC3339] ✨
notAfter (optional, string): the requested value of the notAfter field in the certificate, in the date format defined in [RFC3339] 🦋
POST /acme/new-order HTTP/1.1
Host: example.com
Content-Type: application/jose+json
{
"protected": base64url({
"alg": "ES256",
"kid": "https://example.com/acme/acct/evOfKhNU60wg",
"nonce": "5XJ1L3lEkMG7tR6pA00clA",
"url": "https://example.com/acme/new-order"
}),
"payload": base64url({
"identifiers": [
{ "type": "dns", "value": "www.example.org" },
{ "type": "dns", "value": "example.org" }
],
"notBefore": "2016-01-01T00:04:00+04:00",
"notAfter": "2016-01-08T00:04:00+04:00"
}),
"signature": "H6ZXtGjTZyUnPeKn...wEA4TklBdh3e454g"
}
The server MUST return an error if it can't fulfill the request as specified, and it MUST NOT issue a cert with contents other than what was requested 🙈 If the server needs the request changed in some way, it SHOULD say what needs to change using an appropriate error type and description 💅
If the server is down to issue the requested cert, it responds with a 201 (Created) 🎉 The body is an order object reflecting the client's request, plus any authorizations the client still has to complete before the cert gets issued ✨
HTTP/1.1 201 Created
Replay-Nonce: MYAuvOpaoIiywTezizk5vw
Link: <https://example.com/acme/directory>;rel="index"
Location: https://example.com/acme/order/TOlocE8rfgo
{
"status": "pending",
"expires": "2016-01-05T14:09:07.99Z",
"notBefore": "2016-01-01T00:00:00Z",
"notAfter": "2016-01-08T00:00:00Z",
"identifiers": [
{ "type": "dns", "value": "www.example.org" },
{ "type": "dns", "value": "example.org" }
],
"authorizations": [
"https://example.com/acme/authz/PAniVnsZcis",
"https://example.com/acme/authz/r4HqLzrSrpI"
],
"finalize": "https://example.com/acme/order/TOlocE8rfgo/finalize"
}
The order object the server sends back is a promise 🤞 If the client meets the server's requirements before the "expires" time, the server will be willing to finalize the order on request and issue the requested cert 👑 In the order object, any authorization in the "authorizations" array whose status is "pending" is an authorization transaction the client must complete before the server issues the cert (see Section 7.5) 🔥 If the client fails to finish those actions before "expires", the server SHOULD change the order status to "invalid" and MAY delete the order resource 💀 Clients MUST NOT make any assumptions about the sort order of the "identifiers" or "authorizations" elements in the returned order object 🙈
Once the client thinks it has met the server's requirements, it SHOULD send a POST request to the order resource's finalize URL 🌈 The POST body MUST include a CSR:
csr (required, string): a CSR encoding the parameters for the cert being requested [RFC2986] 🧚 It's sent in the base64url-encoded version of the DER format. (Note: because this field uses base64url and doesn't include headers, it's different from PEM ✨)
POST /acme/order/TOlocE8rfgo/finalize HTTP/1.1
Host: example.com
Content-Type: application/jose+json
{
"protected": base64url({
"alg": "ES256",
"kid": "https://example.com/acme/acct/evOfKhNU60wg",
"nonce": "MSF2j2nawWHPxxkE3ZJtKQ",
"url": "https://example.com/acme/order/TOlocE8rfgo/finalize"
}),
"payload": base64url({
"csr": "MIIBPTCBxAIBADBFMQ...FS6aKdZeGsysoCo4H9P",
}),
"signature": "uOrUfIIk5RyQ...nw62Ay1cl6AB"
}
The CSR is where the client spells out what it wants in the cert 📜 The CSR MUST list the exact same set of requested identifiers as the initial newOrder request 💖 Identifiers of type "dns" MUST appear either in the commonName part of the requested subject name, or in an extensionRequest attribute [RFC2985] asking for a subjectAltName extension, or both 🎀 (These identifiers can show up in any sort order 🦋) Specs that define new identifier types MUST say where in the CSR those identifiers can appear 🫶
A finalize request will throw an error if the CA is unwilling to issue a cert matching the submitted CSR 😭 For example:
- If the CSR and order identifiers differ
- If the account isn't authorized for the identifiers in the CSR
- If the CSR asks for extensions the CA isn't willing to include
In those cases, the problem document the server sends back SHOULD use error code "badCSR" and describe the specific reasons the CSR got rejected in its "detail" field 📝 After sending that error, the server SHOULD leave the order in the "ready" state, so the client can submit a new finalize request with an amended CSR 🌸
A finalize request also errors if the order isn't in the "ready" state 💅 In that case the server MUST return a 403 (Forbidden) error with a problem document of type "orderNotReady" 🙈 The client should then send a POST-as-GET request to the order resource to get its current state ✨ The order's status tells the client what to do next (see below) 👑
If a finalize request succeeds, the server returns a 200 (OK) with an updated order object 🎉 The order's status tells the client what action to take:
- "invalid": the cert won't be issued. Consider this order process abandoned 💀
- "pending": the server doesn't think the client has met the requirements. Check the "authorizations" array for entries that are still pending 🥺
- "ready": the server agrees the requirements are fulfilled and is waiting for finalization. Submit a finalization request 🦋
- "processing": the cert is being issued. Send a POST-as-GET request after the time in the Retry-After header field of the response, if there is one ⏰
- "valid": the server has issued the cert and put its URL in the "certificate" field of the order. Download the cert 🏆
HTTP/1.1 200 OK
Replay-Nonce: CGf81JWBsq8QyIgPCi9Q9X
Link: <https://example.com/acme/directory>;rel="index"
Location: https://example.com/acme/order/TOlocE8rfgo
{
"status": "valid",
"expires": "2016-01-20T14:09:07.99Z",
"notBefore": "2016-01-01T00:00:00Z",
"notAfter": "2016-01-08T00:00:00Z",
"identifiers": [
{ "type": "dns", "value": "www.example.org" },
{ "type": "dns", "value": "example.org" }
],
"authorizations": [
"https://example.com/acme/authz/PAniVnsZcis",
"https://example.com/acme/authz/r4HqLzrSrpI"
],
"finalize": "https://example.com/acme/order/TOlocE8rfgo/finalize",
"certificate": "https://example.com/acme/cert/mAt3xBGaobw"
}
7.4.1. Pre-authorization 🦋
okay so the order process we just went through assumes authorization objects get created reactively, as a response to a cert order 🌸 But some servers want clients to be able to get authorization for an identifier proactively, outside of any specific issuance ✨ For example, a client hosting virtual servers for a bunch of names might want authorization before any virtual servers exist, and only make a cert when a virtual server starts up 💖
In some cases, a CA running an ACME server might have a totally external, non-ACME process for authorizing a client to issue certs for an identifier 👑 In those cases, the CA SHOULD provision its ACME server with authorization objects matching those authorizations, and show them as already valid in any orders the client submits 🔥
If a CA wants to allow pre-authorization within ACME, it can offer a "new authorization" resource in its directory by adding the field "newAuthz" with a URL for the newAuthz resource 🎀
To request authorization for an identifier, the client sends a POST request to the newAuthz resource, specifying the identifier it wants authorized 🫶
identifier (required, object): the identifier to appear in the resulting authorization object (see Section 7.1.4) 🧚
type (required, string): the type of identifier 🍓
value (required, string): the identifier itself 💜
POST /acme/new-authz HTTP/1.1
Host: example.com
Content-Type: application/jose+json
{
"protected": base64url({
"alg": "ES256",
"kid": "https://example.com/acme/acct/evOfKhNU60wg",
"nonce": "uQpSjlRb4vQVCjVYAyyUWg",
"url": "https://example.com/acme/new-authz"
}),
"payload": base64url({
"identifier": {
"type": "dns",
"value": "example.org"
}
}),
"signature": "nuSDISbWG8mMgE7H...QyVUL68yzf3Zawps"
}
Heads up bestie: because the identifier in a pre-authorization request is the exact identifier that goes into the authorization object, pre-authorization cannot be used to authorize issuance of certs containing wildcard domain names 🙈
Before processing the authorization request, the server SHOULD figure out whether it's willing to issue certs for the identifier 🔍 For example, it should check that the identifier is of a supported type. Servers might also check names against a blacklist of known high-value identifiers 🚫 If the server won't issue for the identifier, it SHOULD return an error with status code 403 (Forbidden), along with a problem document describing why it said no 💅
If the server is willing to proceed, it builds a pending authorization object from the client's inputs:
- "identifier" the identifier the client submitted 🌸
- "status" MUST be "pending" unless the server has out-of-band information about the client's authorization status 💖
- "challenges" as selected by the server's policy for this identifier 🦋
The server allocates a new URL for this authorization and returns a 201 (Created) response, with the authorization URL in the Location header field and the JSON authorization object in the body 🎉 The client then follows the process in Section 7.5 to finish the authorization 💅
7.4.2. Downloading the Certificate ✨
okay bestie, so getting your cert is literally the easiest part 💅 you just send a POST-as-GET request to the certificate URL. no body, no drama.
the default format is application/pem-certificate-chain (see Section 9) 🎀
the server MAY give you one or more link relation header fields [RFC8288] with relation "alternate" 🦋. each of those SHOULD express an alternative certificate chain that starts with the same end-entity certificate. this is how you can point to different trust anchors. clients can grab these alternates and use their own heuristics to pick the best one. main character energy for certificate chains 👑
POST /acme/cert/mAt3xBGaobw HTTP/1.1
Host: example.com
Content-Type: application/jose+json
Accept: application/pem-certificate-chain
{
"protected": base64url({
"alg": "ES256",
"kid": "https://example.com/acme/acct/evOfKhNU60wg",
"nonce": "uQpSjlRb4vQVCjVYAyyUWg",
"url": "https://example.com/acme/cert/mAt3xBGaobw"
}),
"payload": "",
"signature": "nuSDISbWG8mMgE7H...QyVUL68yzf3Zawps"
}
HTTP/1.1 200 OK
Content-Type: application/pem-certificate-chain
Link: <https://example.com/acme/directory>;rel="index"
-----BEGIN CERTIFICATE-----
[End-entity certificate contents]
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
[Issuer certificate contents]
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
[Other certificate contents]
-----END CERTIFICATE-----
a certificate resource is a single, immutable certificate 🧚. so if you want a renewed one, you start a brand new order process. no editing the old one, bestie 🙈
because certificate resources are immutable once issuance is complete, the server MAY turn on caching for the resource by adding Expires and Cache-Control header fields that point to a time way out in the future 🌈. but these header fields have nothing to do with the certificate's validity period, so don't mix them up 💀
the ACME client MAY ask for other formats by including an Accept header field [RFC7231] in its request. for example, the client could use the media type "application/pkix-cert" [RFC2585] or "application/pkcs7-mime" [RFC5751] to get the end-entity certificate in DER format 🍓. server support for alternate formats is OPTIONAL 🫶. for formats that can only hold a single certificate, the server SHOULD give you one or more "Link: rel="up"" header fields pointing at an issuer (or issuers), so ACME clients can build a certificate chain the way TLS defines it (see Section 4.4.2 of [RFC8446]) ✨
7.5. Identifier Authorization 🔐
okay so identifier authorization is how the server gets assurance about two things 💖
- that the client controls the private key of the account key pair, and
- that the client controls the identifier in question.
you can repeat this process to attach multiple identifiers to one account (say, to request a cert with several names) or to attach multiple accounts to one identifier (say, so several people can manage certs together) 👯♀️
authorization resources get created by the server in response to newOrder or newAuthz requests from an account key holder 🦋. their URLs come back to the client in the responses to those requests. the authorization object is implicitly tied to the account key that signed the request, so it's a whole vibe 🔑
when a client gets an order from the server in reply to a newOrder request, it downloads the authorization resources by sending POST-as-GET requests to the indicated URLs 📥. if the client starts authorization with a request to the newAuthz resource, it already has the pending authorization object from that response, so no need to fetch it again 💅
POST /acme/authz/PAniVnsZcis HTTP/1.1
Host: example.com
Content-Type: application/jose+json
{
"protected": base64url({
"alg": "ES256",
"kid": "https://example.com/acme/acct/evOfKhNU60wg",
"nonce": "uQpSjlRb4vQVCjVYAyyUWg",
"url": "https://example.com/acme/authz/PAniVnsZcis"
}),
"payload": "",
"signature": "nuSDISbWG8mMgE7H...QyVUL68yzf3Zawps"
}
HTTP/1.1 200 OK
Content-Type: application/json
Link: <https://example.com/acme/directory>;rel="index"
{
"status": "pending",
"expires": "2016-01-02T14:09:30Z",
"identifier": {
"type": "dns",
"value": "www.example.org"
},
"challenges": [
{
"type": "http-01",
"url": "https://example.com/acme/chall/prV_B7yEyA4",
"token": "DGyRejmCefe7v4NfDGDKfA"
},
{
"type": "dns-01",
"url": "https://example.com/acme/chall/Rg5dV14Gh1Q",
"token": "DGyRejmCefe7v4NfDGDKfA"
}
]
}
7.5.1. Responding to Challenges 🎯
to prove you control the identifier and get authorized, the client has to set up the required challenge response for that challenge type, then tell the server it's ready for validation to be attempted 🙌
the client says "I'm ready!" by sending an empty JSON body ("{}") in a POST request to the challenge URL (not the authorization URL, bestie, pay attention 🙈).
for example, if the client were responding to the "http-01" challenge from the authorization above, it would send this request:
POST /acme/chall/prV_B7yEyA4 HTTP/1.1
Host: example.com
Content-Type: application/jose+json
{
"protected": base64url({
"alg": "ES256",
"kid": "https://example.com/acme/acct/evOfKhNU60wg",
"nonce": "Q_s3MWoqT05TrdkM2MTDcw",
"url": "https://example.com/acme/chall/prV_B7yEyA4"
}),
"payload": base64url({}),
"signature": "9cbg5JO1Gf5YLjjz...SpkUfcdPai9uVYYQ"
}
the server updates the authorization document by updating its copy of the challenge with the response object the client sent 📝. the server MUST ignore any fields in the response object that aren't specified as response fields for this challenge type 🚫. heads up: the challenges in this document don't define any response fields, but future specs might 👀. the server replies with a 200 (OK) response and the updated challenge object as the body ✨
if the client's response is invalid for any reason, or doesn't give the server the info it needs to validate the challenge, the server MUST return an HTTP error 😭. when the client gets an error like that, it SHOULD undo any actions it took to fulfill the challenge, like removing files it put on a web server 🧹
the server is said to "finalize" the authorization once it has completed one of the validations 🏁. it does this by giving the authorization a status of "valid" or "invalid", depending on whether it considers the account authorized for the identifier. if the final state is "valid", the server MUST include an "expires" field 📅. when finalizing, the server MAY remove challenges other than the one that got completed, and it may change the "expires" field too. the server SHOULD NOT remove challenges with status "invalid" 🙅♀️
validation usually takes some time, so the client will need to poll the authorization resource to see when it's finalized ⏳. for challenges where the client can tell when the server has validated it (like seeing an HTTP or DNS request come in from the server), the client SHOULD NOT start polling until it has seen that validation request 👀
to check the status of an authorization, the client sends a POST-as-GET request to the authorization URL, and the server responds with the current authorization object 🔍. when validation is still in progress and the server is answering these poll requests, the server MUST return a 200 (OK) response, and it MAY include a Retry-After header field to suggest a polling interval to the client ⏰
POST /acme/authz/PAniVnsZcis HTTP/1.1
Host: example.com
Content-Type: application/jose+json
{
"protected": base64url({
"alg": "ES256",
"kid": "https://example.com/acme/acct/evOfKhNU60wg",
"nonce": "uQpSjlRb4vQVCjVYAyyUWg",
"url": "https://example.com/acme/authz/PAniVnsZcis"
}),
"payload": "",
"signature": "nuSDISbWG8mMgE7H...QyVUL68yzf3Zawps"
}
HTTP/1.1 200 OK
Content-Type: application/json
Link: <https://example.com/acme/directory>;rel="index"
{
"status": "valid",
"expires": "2018-09-09T14:09:01.13Z",
"identifier": {
"type": "dns",
"value": "www.example.org"
},
"challenges": [
{
"type": "http-01",
"url": "https://example.com/acme/chall/prV_B7yEyA4",
"status": "valid",
"validated": "2014-12-01T12:05:13.72Z",
"token": "IlirfxKKXAsHtmzK29Pj8A"
}
]
}
7.5.2. Deactivating an Authorization 🫧
if a client wants to give up its authorization to issue certificates for an identifier, it can ask the server to deactivate each authorization tied to it. it does this by sending POST requests with the static object {"status": "deactivated"} to each authorization URL 💜
POST /acme/authz/PAniVnsZcis HTTP/1.1
Host: example.com
Content-Type: application/jose+json
{
"protected": base64url({
"alg": "ES256",
"kid": "https://example.com/acme/acct/evOfKhNU60wg",
"nonce": "xWCM9lGbIyCgue8di6ueWQ",
"url": "https://example.com/acme/authz/PAniVnsZcis"
}),
"payload": base64url({
"status": "deactivated"
}),
"signature": "srX9Ji7Le9bjszhu...WTFdtujObzMtZcx4"
}
the server MUST verify that the request is signed by the account key for the account that owns the authorization 🔐. if the server accepts the deactivation, it SHOULD reply with a 200 (OK) status code and the updated contents of the authorization object 🎀
the server MUST NOT treat deactivated authorization objects as enough to issue certificates 🚫💀
7.6. Certificate Revocation 💀🔥
Okay so, if a client wants a certificate revoked, it sends a POST to the ACME server's revokeCert URL 📮. The body is a JWS object, and its JSON payload holds the certificate being revoked:
certificate(required, string): The certificate to be revoked, in the base64url-encoded version of the DER format. (Note: because this field uses base64url and doesn't include headers, it's different from PEM 🙈)reason(optional, int): One of the revocationreasonCodevalues defined in Section 5.3.1 of [RFC5280], used when generating OCSP responses and CRLs. If this field isn't set, the server SHOULD omit thereasonCodeCRL entry extension when generating OCSP responses and CRLs. The server MAY disallow a subset ofreasonCodevalues from being used by the user. If a request contains a disallowedreasonCode, the server MUST reject it with the error typeurn:ietf:params:acme:error:badRevocationReason. The problem document detail SHOULD indicate whichreasonCodevalues are allowed 🌸
Revocation requests are different from other ACME requests because they can be signed with either an account key pair or the key pair in the certificate. Iconic flex 👑
Example using an account key pair for the signature:
POST /acme/revoke-cert HTTP/1.1
Host: example.com
Content-Type: application/jose+json
{
"protected": base64url({
"alg": "ES256",
"kid": "https://example.com/acme/acct/evOfKhNU60wg",
"nonce": "JHb54aT_KTXBWQOzGYkt9A",
"url": "https://example.com/acme/revoke-cert"
}),
"payload": base64url({
"certificate": "MIIEDTCCAvegAwIBAgIRAP8...",
"reason": 4
}),
"signature": "Q1bURgJoEslbD1c5...3pYdSMLio57mQNN4"
}
Example using the certificate key pair for the signature:
POST /acme/revoke-cert HTTP/1.1
Host: example.com
Content-Type: application/jose+json
{
"protected": base64url({
"alg": "RS256",
"jwk": /* certificate's public key */,
"nonce": "JHb54aT_KTXBWQOzGYkt9A",
"url": "https://example.com/acme/revoke-cert"
}),
"payload": base64url({
"certificate": "MIIEDTCCAvegAwIBAgIRAP8...",
"reason": 1
}),
"signature": "Q1bURgJoEslbD1c5...3pYdSMLio57mQNN4"
}
Before revoking a certificate, the server MUST verify that the key used to sign the request is authorized to revoke it. This is the bit where security gets serious 🔒 The server MUST consider at least the following accounts authorized for a given certificate:
- the account that issued the certificate.
- an account that holds authorizations for all of the identifiers in the certificate.
The server MUST also consider a revocation request valid if it's signed with the private key corresponding to the public key in the certificate 🗝️
If the revocation succeeds, the server responds with status code 200 (OK) ✨ If it fails, the server returns an error. For example, if the certificate has already been revoked, the server returns an error response with status code 400 (Bad Request) and type urn:ietf:params:acme:error:alreadyRevoked 💀
HTTP/1.1 200 OK
Replay-Nonce: IXVHDyxIRGcTE0VSblhPzw
Content-Length: 0
Link: <https://example.com/acme/directory>;rel="index"
--- or ---
HTTP/1.1 403 Forbidden
Replay-Nonce: lXfyFzi6238tfPQRwgfmPU
Content-Type: application/problem+json
Content-Language: en
Link: <https://example.com/acme/directory>;rel="index"
{
"type": "urn:ietf:params:acme:error:unauthorized",
"detail": "No authorization provided for name example.org"
}
8. Identifier Validation Challenges 🦋
Okay, so there are very few types of identifiers out there with a standardized way to prove you actually own them 🙈 In practice, CAs rely on a whole bunch of different methods to check whether someone applying for a certificate for an identifier really controls it.
Challenges give the server confidence that the account holder is also the entity that controls the identifier. For each type of challenge, an entity can only complete it successfully if it both:
- Holds the private key of the account key pair used to respond to the challenge, and
- Controls the identifier in question.
Section 10 documents how the challenges defined in this document meet these requirements. New challenges will need to document how they do so 📝
ACME uses an extensible challenge/response framework for identifier validation 🧩 The server presents a set of challenges in the authorization object it sends to a client (as objects in the challenges array), and the client responds by sending a response object in a POST request to a challenge URL.
This section describes an initial set of challenge types. The definition of a challenge type includes:
- Content of challenge objects
- Content of response objects
- How the server uses the challenge and response to verify control of an identifier
Challenge objects all contain these basic fields:
type(required, string): The type of challenge encoded in the object.url(required, string): The URL to which a response can be posted.status(required, string): The status of this challenge. Possible values are"pending","processing","valid", and"invalid"(see Section 7.1.6).validated(optional, string): The time at which the server validated this challenge, encoded in the format specified in [RFC3339]. This field is REQUIRED if thestatusfield is"valid".error(optional, object): The error that occurred while the server was validating the challenge, if any, structured as a problem document [RFC7807]. Multiple errors can be indicated by using subproblems Section 6.7.1. A challenge object with an error MUST havestatusequal to"invalid".
All additional fields are specified by the challenge type. If the server sets a challenge's status to "invalid", it SHOULD also include the error field to help the client figure out why the challenge failed 🕵️♀️
Different challenges let the server get proof of different aspects of control over an identifier. In some challenges, like HTTP and DNS, the client directly proves it can do certain things related to the identifier. Which challenges get offered to a client, and under which circumstances, is a matter of server policy 🎀
The identifier validation challenges described in this section all relate to validation of domain names. If ACME is extended in the future to support other types of identifiers, there will need to be new challenge types, and they will need to specify which types of identifier they apply to 🌈
8.1. Key Authorizations 🔑
All challenges defined in this document make use of a key authorization string. A key authorization is a string that joins the token for the challenge with a key fingerprint, separated by a "." character:
keyAuthorization = token || '.' || base64url(Thumbprint(accountKey))
The "Thumbprint" step is the computation specified in [RFC7638], using the SHA-256 digest [FIPS180-4]. As noted in [RFC7518], any prepended zero octets in the fields of a JWK object MUST be stripped before doing the computation 🧼
As specified in the individual challenges below, the token for a challenge is a string made up entirely of characters from the URL-safe base64 alphabet. The "||" operator means concatenation of strings 💜
8.2. Retrying Challenges 🔁
ACME challenges usually require the client to set up some network-accessible resource that the server can query to validate that the client controls an identifier. In practice, it's pretty common for the server's queries to fail while a resource is still being set up, e.g., because information is still propagating across a cluster or firewall rules aren't in place yet 😭
Clients SHOULD NOT respond to challenges until they believe the server's queries will succeed. If a server's initial validation query fails, the server SHOULD retry the query after some time, to account for delay in setting up responses such as DNS records or HTTP resources. The precise retry schedule is up to the server, but server operators should keep in mind the operational scenarios the schedule is trying to accommodate. Since retries are meant to handle things like propagation delays in HTTP or DNS provisioning, there should not usually be any reason to retry more often than every 5 or 10 seconds ⏳ While the server is still trying, the challenge's status stays "processing"; it only becomes "invalid" once the server has given up.
The server MUST provide information about its retry state to the client via the error field in the challenge and the Retry-After HTTP header field in responses to requests to the challenge resource. The server MUST add an entry to the error field in the challenge after each failed validation query. The server SHOULD set the Retry-After header field to a time after the server's next validation query, since the challenge's status won't change until then 🗓️
Clients can explicitly ask for a retry by re-sending their response to a challenge in a new POST request (with a new nonce, etc.). This lets clients request a retry once the state has changed (e.g., after firewall rules have been updated) 🙌 Servers SHOULD retry a request immediately on receiving such a POST request. To avoid denial-of-service attacks via client-initiated retries, servers SHOULD rate-limit such requests 🛡️
Let's keep the momentum going, bestie. 💖
8.3. HTTP Challenge ✨
okay bestie, let's talk about the HTTP challenge 🌸 this is the one where the client proves it owns a domain by being able to drop a file onto a web server under that domain 💅 the ACME server basically says "hey, put this exact string at this exact path," and if it shows up, you're verified 🦋
so a domain can resolve to a bunch of IPv4 and IPv6 addresses, and the server gets to pick at least one of the hosts from the DNS A and AAAA records, whichever it likes 🧚 and here's the tea: the challenge has to be done over HTTP, not HTTPS 🙈 because a lot of web servers hand a default HTTPS virtual host to a low-privilege tenant user in a sneaky, not-obvious way, so it's just not reliable 😭
the fields are giving: type is required and is the string "http-01" 💖 and token is required too, a random value that uniquely identifies the challenge 🎀 it MUST have at least 128 bits of entropy 🔥 it MUST NOT contain any characters outside the base64url alphabet, and it MUST NOT include base64 padding characters ("=") 💀 check out [RFC4086] if you want more on randomness requirements 🌈
here's what the challenge object looks like:
{
"type": "http-01",
"url": "https://example.com/acme/chall/prV_B7yEyA4",
"status": "pending",
"token": "LoqXcYV8q5ONbJQxbmR7SCTNo3tiAXDfowyjxAjEuX0"
}
so the client makes a key authorization from the "token" value and its account key 👑 then it provisions that key authorization as a resource on the HTTP server for the domain in question 🦋
the path is the fixed prefix "/.well-known/acme-challenge/" followed by the "token" value 🌸 and the value of the resource MUST be the ASCII representation of the key authorization 💅
here's the request and response, bestie:
GET /.well-known/acme-challenge/LoqXcYV8...jxAjEuX0
Host: example.org
HTTP/1.1 200 OK
Content-Type: application/octet-stream
LoqXcYV8...jxAjEuX0.9jg46WB3...fm21mqTI
(the "..." just means the token and the JWK thumbprint in the key authorization got chopped so it fits on the page 🙈)
then the client responds with an empty object ({}) to say "yep, you can validate this now" ✨
POST /acme/chall/prV_B7yEyA4
Host: example.com
Content-Type: application/jose+json
{
"protected": base64url({
"alg": "ES256",
"kid": "https://example.com/acme/acct/evOfKhNU60wg",
"nonce": "UQI1PoRi5OuXzxuX7V7wL0",
"url": "https://example.com/acme/chall/prV_B7yEyA4"
}),
"payload": base64url({}),
"signature": "Q1bURgJoEslbD1c5...3pYdSMLio57mQNN4"
}
when the response comes in, the server builds and stores the key authorization from the challenge "token" and the current client account key 💜
now the server's side of the verification, bestie 👑 it checks that the client actually controls the domain by checking the resource was provisioned like it should be:
- construct a URL from the URL template [RFC6570] "http://{domain}/.well-known/acme-challenge/{token}", where the domain field is the domain name being verified and the token field is the token in the challenge 🌈
- verify that the resulting URL is well-formed ✨
- dereference the URL with an HTTP GET request, which MUST be sent to TCP port 80 on the HTTP server 🔥
- verify that the body of the response is a well-formed key authorization, and the server SHOULD ignore whitespace characters at the end of the body 💅
- verify that the key authorization the HTTP server gave back matches the one the server stored 🦋
the server SHOULD follow redirects when dereferencing the URL 🧚 clients might use redirects, like so the response can come from a centralized certificate management server 🌸 see Section 10.2 for the security considerations on redirects, and yes, you should read them 😭
if all of those checks pass, the validation is successful 🎀 if the request fails, or the body doesn't pass the checks, then it failed 💀
and the client SHOULD de-provision the resource it put up for this challenge once the challenge is complete, i.e., once the "status" field of the challenge is "valid" or "invalid" 🫶
ngl this part is important: because the token shows up in both the server's request and the key authorization in the response, it's possible to build clients that copy the token from request to response 😭 clients should avoid that because it can lead to cross-site scripting vulnerabilities 💀 instead, clients should be explicitly configured on a per-challenge basis 🔥 and a client that DOES copy tokens from requests to responses MUST validate that the token in the request matches the token syntax above, e.g., that it includes only characters from the base64url alphabet 🛡️
8.4. DNS Challenge 🍓
bestie, now we're switching to DNS 🌈 when the identifier is a domain name, the client can prove it controls that domain by provisioning a TXT resource record with a designated value for a specific validation domain name 💖
same deal with the token: type is required and is the string "dns-01" 🦋 and token is required, a random value that uniquely identifies the challenge 🎀 it MUST have at least 128 bits of entropy ✨ it MUST NOT contain any characters outside the base64url alphabet, including padding characters ("=") 🙈 see [RFC4086] for more on randomness 🌸
{
"type": "dns-01",
"url": "https://example.com/acme/chall/Rg5dV14Gh1Q",
"status": "pending",
"token": "evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA"
}
the client builds a key authorization from the "token" and its account key 👑 then it computes the SHA-256 digest [FIPS180-4] of that key authorization 💅
the record provisioned in DNS contains the base64url encoding of this digest 🧚 the client makes the validation domain name by prepending the label "_acme-challenge" to the domain name being validated, then provisions a TXT record with the digest value under that name 🔥 for example, if the domain being validated is "www.example.org", the client would provision this DNS record:
_acme-challenge.www.example.org. 300 IN TXT "gfj9Xq...Rg85nM"
then the client responds with an empty object ({}) to acknowledge 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"
}
when the response lands, the server builds and stores the key authorization from the challenge "token" and the current client account key 💜
to validate a DNS challenge, the server does these steps, bestie:
- compute the SHA-256 digest [FIPS180-4] of the stored key authorization 🔥
- 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 of those pass, the validation is successful 🎀 if no DNS record is found, or the DNS record and response payload don't pass these checks, then the validation fails 💀
and the client SHOULD de-provision the resource record(s) it provisioned for this challenge once the challenge is complete, i.e., once the "status" field of the challenge is "valid" or "invalid" 🦋
9. IANA Considerations 🌈
okay, this section is all about registrations, and I'm obsessed 💅
9.1. Media Type: application/pem-certificate-chain 🎀
a file of this type contains one or more certificates encoded with the PEM textual encoding, according to [RFC7468] 🧚 the textual encoding of certificates in this file MUST use the strict encoding and MUST NOT include explanatory text 🔥 the ABNF for this format is below, where "stricttextualmsg" and "eol" are as defined in Section 3 of RFC 7468 🌸
certchain = stricttextualmsg *(eol stricttextualmsg)
for easy interop with TLS, the first certificate MUST be an end-entity certificate 👑 each following certificate SHOULD directly certify the one before it 💖 because certificate validation requires that trust anchors be distributed independently, a certificate that represents a trust anchor MAY be left out of the chain, as long as supported peers are known to already have any omitted certificates 🦋
here's what got registered in the "Media Types" registry:
Type name: application
Subtype name: pem-certificate-chain
Required parameters: None
Optional parameters: None
Encoding considerations: 7bit
Security considerations: Carries a cryptographic certificate and its
associated certificate chain. This media type carries no active
content.
Interoperability considerations: None
Published specification: RFC 8555
Applications that use this media type: ACME clients and servers, HTTP
servers, other applications that need to be configured with a
certificate chain
Additional information:
Deprecated alias names for this type: n/a
Magic number(s): n/a
File extension(s): .pem
Macintosh file type code(s): n/a
Person & email address to contact for further information: See
Authors' Addresses section.
Intended usage: COMMON
Restrictions on usage: n/a
Author: See Authors' Addresses section.
Change controller: IETF <iesg@ietf.org>
9.2. Well-Known URI for the HTTP Challenge 🦋
the following value got registered in the "Well-Known URIs" registry (using the template from [RFC5785]) ✨
URI suffix: acme-challenge
Change controller: IETF
Specification document(s): RFC 8555, Section 8.3
Related information: N/A
9.3. Replay-Nonce HTTP Header 💖
the following value got registered in the "Message Headers" registry 🌸
+-------------------+----------+----------+-------------------------+
| Header Field Name | Protocol | Status | Reference |
+-------------------+----------+----------+-------------------------+
| Replay-Nonce | http | standard | RFC 8555, Section 6.5.1 |
+-------------------+----------+----------+-------------------------+
9.4. "url" JWS Header Parameter 🧚
the following value got registered in the "JSON Web Signature and Encryption Header Parameters" registry 🔥
o Header Parameter Name: "url"
o Header Parameter Description: URL
o Header Parameter Usage Location(s): JWE, JWS
o Change Controller: IESG
o Specification Document(s): RFC 8555, Section 6.4.1
9.5. "nonce" JWS Header Parameter 🎀
the following value got registered in the "JSON Web Signature and Encryption Header Parameters" registry 💅
o Header Parameter Name: "nonce"
o Header Parameter Description: Nonce
o Header Parameter Usage Location(s): JWE, JWS
o Change Controller: IESG
o Specification Document(s): RFC 8555, Section 6.5.2
9.6. URN Sub-namespace for ACME (urn:ietf:params:acme) 👑
the following value got registered in the "IETF URN Sub-namespace for Registered Protocol Parameter Identifiers" registry, following the template in [RFC3553] 🌈
Registry name: acme
Specification: RFC 8555
Repository: http://www.iana.org/assignments/acme
Index value: No transformation needed.
9.7. New Registries
Okay bestie, IANA is getting eight brand new registries 🎀 Here they are:
- ACME Account Object Fields (Section 9.7.1)
- ACME Order Object Fields (Section 9.7.2)
- ACME Authorization Object Fields (Section 9.7.3)
- ACME Error Types (Section 9.7.4)
- ACME Resource Types (Section 9.7.5)
- ACME Directory Metadata Fields (Section 9.7.6)
- ACME Identifier Types (Section 9.7.7)
- ACME Validation Methods (Section 9.7.8)
All of these sit under the heading "Automated Certificate Management Environment (ACME) Protocol" 🌸 and they're administered under a Specification Required policy [RFC8126] 💅 iconic organisation honestly.
9.7.1. Fields in Account Objects
The "ACME Account Object Fields" registry lists the field names that are defined for use in ACME account objects ✨ Fields marked as "configurable" may be included in a newAccount request 🦋
Template:
- Field name: The string to be used as a field name in the JSON object
- Field type: The type of value to be provided, e.g., string, boolean, array of string
- Requests: Either the value "none" or a list of types of requests where the field is allowed in a request object, taken from the following values:
- "new" - Requests to the "newAccount" URL
- "account" - Requests to an account URL
- Reference: Where this field is defined
Initial contents: The fields and descriptions defined in Section 7.1.2 💖
+------------------------+---------------+--------------+-----------+
| Field Name | Field Type | Requests | Reference |
+------------------------+---------------+--------------+-----------+
| status | string | new, account | RFC 8555 |
| | | | |
| contact | array of | new, account | RFC 8555 |
| | string | | |
| | | | |
| externalAccountBinding | object | new | RFC 8555 |
| | | | |
| termsOfServiceAgreed | boolean | new | RFC 8555 |
| | | | |
| onlyReturnExisting | boolean | new | RFC 8555 |
| | | | |
| orders | string | none | RFC 8555 |
+------------------------+---------------+--------------+-----------+
9.7.2. Fields in Order Objects
The "ACME Order Object Fields" registry lists the field names that are defined for use in ACME order objects 🍓 Fields marked as "configurable" may be included in a newOrder request 🌈
Template:
- Field name: The string to be used as a field name in the JSON object
- Field type: The type of value to be provided, e.g., string, boolean, array of string
- Configurable: Boolean indicating whether the server should accept values provided by the client
- Reference: Where this field is defined
Initial contents: The fields and descriptions defined in Section 7.1.3 ✨
+----------------+-----------------+--------------+-----------+
| Field Name | Field Type | Configurable | Reference |
+----------------+-----------------+--------------+-----------+
| status | string | false | RFC 8555 |
| | | | |
| expires | string | false | RFC 8555 |
| | | | |
| identifiers | array of object | true | RFC 8555 |
| | | | |
| notBefore | string | true | RFC 8555 |
| | | | |
| notAfter | string | true | RFC 8555 |
| | | | |
| error | string | false | RFC 8555 |
| | | | |
| authorizations | array of string | false | RFC 8555 |
| | | | |
| finalize | string | false | RFC 8555 |
| | | | |
| certificate | string | false | RFC 8555 |
+----------------+-----------------+--------------+-----------+
9.7.3. Fields in Authorization Objects
The "ACME Authorization Object Fields" registry lists the field names that are defined for use in ACME authorization objects 🔥 Fields marked as "configurable" may be included in a newAuthz request 💜
Template:
- Field name: The string to be used as a field name in the JSON object
- Field type: The type of value to be provided, e.g., string, boolean, array of string
- Configurable: Boolean indicating whether the server should accept values provided by the client
- Reference: Where this field is defined
Initial contents: The fields and descriptions defined in Section 7.1.4 🦋
+------------+-----------------+--------------+-----------+
| Field Name | Field Type | Configurable | Reference |
+------------+-----------------+--------------+-----------+
| identifier | object | true | RFC 8555 |
| | | | |
| status | string | false | RFC 8555 |
| | | | |
| expires | string | false | RFC 8555 |
| | | | |
| challenges | array of object | false | RFC 8555 |
| | | | |
| wildcard | boolean | false | RFC 8555 |
+------------+-----------------+--------------+-----------+
9.7.4. Error Types
The "ACME Error Types" registry lists the values that are used within the URN values provided in the "type" field of problem documents in ACME 🧚 It's the little label bank for all those error URNs 🙈
Template:
- Type: The label to be included in the URN for this error, following "urn:ietf:params:acme:error:"
- Description: A human-readable description of the error
- Reference: Where the error is defined
Initial contents: The types and descriptions in the table in Section 6.7 above, with the Reference field set to point to this specification ⭐
9.7.5. Resource Types
The "ACME Resource Types" registry lists the types of resources that ACME servers may list in their directory objects 🌸
Template:
- Field name: The value to be used as a field name in the directory object
- Resource type: The type of resource labeled by the field
- Reference: Where the resource type is defined
Initial contents:
+------------+--------------------+-----------+
| Field Name | Resource Type | Reference |
+------------+--------------------+-----------+
| newNonce | New nonce | RFC 8555 |
| | | |
| newAccount | New account | RFC 8555 |
| | | |
| newOrder | New order | RFC 8555 |
| | | |
| newAuthz | New authorization | RFC 8555 |
| | | |
| revokeCert | Revoke certificate | RFC 8555 |
| | | |
| keyChange | Key change | RFC 8555 |
| | | |
| meta | Metadata object | RFC 8555 |
+------------+--------------------+-----------+
9.7.6. Fields in the "meta" Object within a Directory Object
The "ACME Directory Metadata Fields" registry lists the field names that are defined for use in the JSON object included in the "meta" field of an ACME directory object 💅
Template:
- Field name: The string to be used as a field name in the JSON object
- Field type: The type of value to be provided, e.g., string, boolean, array of string
- Reference: Where this field is defined
Initial contents: The fields and descriptions defined in Section 7.1.1 🎀
+-------------------------+-----------------+-----------+
| Field Name | Field Type | Reference |
+-------------------------+-----------------+-----------+
| termsOfService | string | RFC 8555 |
| | | |
| website | string | RFC 8555 |
| | | |
| caaIdentities | array of string | RFC 8555 |
| | | |
| externalAccountRequired | boolean | RFC 8555 |
+-------------------------+-----------------+-----------+
9.7.7. Identifier Types
The "ACME Identifier Types" registry lists the types of identifiers that can be present in ACME authorization objects 🦋
Template:
- Label: The value to be put in the "type" field of the identifier object
- Reference: Where the identifier type is defined
Initial contents:
+-------+-----------+
| Label | Reference |
+-------+-----------+
| dns | RFC 8555 |
+-------+-----------+
9.7.8. Validation Methods 🔐
okay so the "ACME Validation Methods" registry is basically the list of all the ways a CA can check that you actually control an identifier 🦋 Every entry MUST say whether it matches an ACME challenge type. And the "Identifier Type" field MUST be listed in the Label column of the "ACME Identifier Types" registry, so everything lines up 💅
Here's the template:
- Label: The identifier for this validation method
- Identifier Type: The type of identifier that this method applies to
- ACME: "Y" if the validation method corresponds to an ACME challenge type; "N" otherwise
- Reference: Where the validation method is defined
This registry can also hold reserved entries (for example, to stop collisions) 🙈 Those SHOULD have the "ACME" field set to "N" and the "Identifier Type" set to "RESERVED".
Initial Contents:
+------------+-----------------+------+-----------+
| Label | Identifier Type | ACME | Reference |
+------------+-----------------+------+-----------+
| http-01 | dns | Y | RFC 8555 |
| | | | |
| dns-01 | dns | Y | RFC 8555 |
| | | | |
| tls-sni-01 | RESERVED | N | RFC 8555 |
| | | | |
| tls-sni-02 | RESERVED | N | RFC 8555 |
+------------+-----------------+------+-----------+
When someone asks to add something to this registry, the designated expert SHOULD make sure the new method has a clear, interoperable definition and doesn't overlap with the existing ones ✨ The big rule is that it should not be possible for a client and server to follow the same set of actions and end up fulfilling two different validation methods. That would be chaos 💀
Also, "tls-sni-01" and "tls-sni-02" are reserved on purpose 🌸 They were used in pre-RFC versions of this spec, but they got removed because they weren't secure in some cases, so nobody gets to reuse those names.
Validation methods don't have to be ACME-compatible to get registered, btw 🎀 For example, a CA might want to register a method to use with the ACME extensions to CAA [ACME-CAA]. Iconic flexibility, honestly 👑
10. Security Considerations 🛡️
okay bestie, this part is SERIOUS but also kind of iconic 😭 ACME is a protocol for managing certificates that attest to identifier/key bindings. So the number one security goal is making sure this whole process has integrity: the bindings that certificates vouch for are correct, and only authorized entities can manage those certificates 🔥 ACME identifies clients by their account keys, so that big goal breaks down into two more precise goals:
- Only an entity that controls an identifier can get an authorization for that identifier
- Once authorized, an account key's authorizations cannot be improperly used by another account
In this section we'll go over the threat model ACME is built around, how ACME hits those security goals inside that model, and the denial-of-service risks ACME servers have to deal with, plus a few other misc considerations 🧚
10.1. Threat Model 🕵️♀️
ACME is a service on the Internet, so it broadly lives inside the Internet threat model [RFC3552]. When you're analyzing ACME, it helps to picture the ACME server talking to other Internet hosts over two "channels":
- An ACME channel, over which the ACME HTTPS requests are exchanged
- A validation channel, over which the ACME server performs additional requests to validate a client's control of an identifier
+------------+
| ACME | ACME Channel
| Client |--------------------+
+------------+ |
V
+------------+
| ACME |
| Server |
+------------+
+------------+ |
| Validation |<-------------------+
| Server | Validation Channel
+------------+
Communications Channels Used by ACME
In practice the risks to these two channels aren't totally separate, but they're different in most cases 🦋 Each channel uses a different communication pattern: the ACME channel is made of inbound HTTPS connections to the ACME server, and the validation channel is made of outbound HTTP or DNS requests.
Broadly speaking, ACME aims to stay secure against active and passive attackers on any single channel 💖 Some vulnerabilities do show up (I'll call them out below) when an attacker can exploit both the ACME channel and one of the others, and that's the scary combo 😭
On the ACME channel, we have to think about network-layer attackers, but also man-in-the-middle (MitM) attacks at the application layer, plus abusive use of the protocol itself 🙈 Protection against application-layer MitM covers stuff like Content Distribution Networks (CDNs) and middleboxes that do a TLS MitM function. Stopping abusive use means making sure an attacker who has access to the validation channel can't get an illegitimate authorization by acting as an ACME client (legitimately, in terms of the protocol) 💅
ACME does NOT protect against every kind of abuse by a MitM on the ACME channel, tho ⚠️ For example, that kind of attacker could send a bogus "badSignatureAlgorithm" error response to downgrade a client to the lowest-quality signature algorithm the server supports. And a MitM that's on every connection (like a CDN) can cause denial-of-service conditions in a bunch of different ways 💀
10.2. Integrity of Authorizations ✨
okay so ACME lets anyone ask for challenges for an identifier 💅 you just register an account key and send a newOrder request signed with that key. 🔑 That means the whole authorization process is only safe if the identifier validation challenges make sure the challenge can only be completed by someone who (1) holds the private key of the account key pair AND (2) controls the identifier in question. no cap, that's the entire vibe. 🫶
Validation responses need to be bound to an account key pair, bestie, because otherwise a MitM on ACME HTTPS requests could swap a legit domain holder's account key for one they pick themselves 😭 This can happen if a CA puts a CDN or third-party reverse proxy in front of its ACME interface. Here's how that attack could go down:
- Legitimate domain holder registers account key pair A
- MitM registers account key pair B
- Legitimate domain holder sends a newOrder request signed using account key A
- MitM suppresses the legitimate request but sends the same request signed using account key B
- ACME server issues challenges and MitM forwards them to the legitimate domain holder
- Legitimate domain holder provisions the validation response
- ACME server performs validation query and sees the response provisioned by the legitimate domain holder
- Because the challenges were issued in response to a message signed with account key B, the ACME server grants authorization to account key B (the MitM) instead of account key A (the legitimate domain holder)
Here's the whole thing as a diagram, so you can see the sneaky part 👀
Domain ACME
Holder MitM Server
| | |
| newAccount(A) | |
|--------------------->|--------------------->|
| | |
| | newAccount(B) |
| |--------------------->|
| newOrder(domain, A) | |
|--------------------->| |
| | newOrder(domain, B) |
| |--------------------->|
| | |
| authz, challenges | authz, challenges |
|<---------------------|<---------------------|
| | |
| response(chall, A) | response(chall, B) |
|--------------------->|--------------------->|
| | |
| validation request | |
|<--------------------------------------------|
| | |
| validation response | |
|-------------------------------------------->|
| | |
| | | Considers challenge
| | | fulfilled by B
| | |
Man-in-the-Middle Attack Exploiting a Validation
Method without Account Key Binding
so to stop this 🙅♀️ all of the challenges in this document bind the account private key to the validation query the server makes, through the key authorization. ✨ The key authorization reflects the account public key and gets handed to the server in the validation response over the validation channel. iconic design, honestly. 👑
Challenges are tied to identifiers by making the client do some action that only someone who effectively controls the identifier can do. For the challenges in this document, those actions are:
- HTTP: Provision files under .well-known on a web server for the domain
- DNS: Provision DNS resource records for the domain
There are several ways these assumptions can get broken, both by misconfiguration and by attacks 🙈 For example, on a web server that lets non-administrative users write to .well-known, any user can claim to own that web server's hostname just by responding to an HTTP challenge. Similarly, if a server that can be used for ACME validation gets compromised by a malicious actor, that actor can use the access to get certificates via ACME. 💀
Hosting providers are a particular risk for ACME validation, not gonna lie. If the domain owner has outsourced DNS or web services to a hosting provider, there's nothing that can be done about tampering by that hosting provider. As far as the outside world is concerned, the zone or website the hosting provider serves is the real thing. 😭
More limited forms of delegation can also let an unintended party successfully complete a validation transaction. For example, say an ACME server follows HTTP redirects during HTTP validation, and a website operator sets up a catch-all redirect rule that sends requests for unknown resources to a different domain. Then the target of that redirect could use it to get a certificate through HTTP validation, since the primary server won't know the validation path. 🦋
The DNS is a common point of vulnerability for all of these challenges, bestie. An entity that can provision false DNS records for a domain can attack the DNS challenge directly, and can provision false A/AAAA records to point the ACME server's HTTP validation query at a remote server of the attacker's choosing. 🚩 There are a few different mitigations ACME servers can apply:
- Always querying the DNS using a DNSSEC-validating resolver (enhancing security for zones that are DNSSEC-enabled)
- Querying the DNS from multiple vantage points to address local attackers
- Applying mitigations against DNS off-path attackers, e.g., adding entropy to requests [DNS0x20] or only using TCP
Given all of this, the ACME validation process makes it impossible for any attacker on the ACME channel, or a passive attacker on the validation channel, to hijack the authorization process and authorize a key of the attacker's choice. 🛡️✨
An attacker who can only see the ACME channel would need to convince the validation server to give a response that authorizes the attacker's account key. That's blocked by binding the validation response to the account key that requested the challenges. 🔒 A passive attacker on the validation channel can observe the correct validation response and even replay it, but that response can only be used with the account key it was generated for. so, no slay for them. 🙅♀️
An active attacker on the validation channel can subvert the ACME process by running normal ACME transactions and providing a validation response for their own account key. 😤 The hosting provider risks noted above are a particular case of this.
Attackers can also exploit vulnerabilities in Internet routing protocols to get access to the validation channel (see, e.g., [RFC7132]). To make these attacks harder, it is RECOMMENDED that the server perform DNS queries and make HTTP connections from multiple points in the network. 🌍 Routing attacks are often localized or depend on where the attacker sits, so forcing the attacker to hit multiple points (the server's validation vantage points) or one specific point (the DNS / HTTP server) makes it way harder to subvert ACME validation with routing attacks. main character defense energy 💜
10.3. Denial-of-Service Considerations 🛡️
ACME runs over HTTPS, so the standard considerations for TCP-based and HTTP-based DoS mitigation apply to ACME too, obviously 🙃
At the application layer, ACME requires the server to do a few potentially expensive operations. Identifier validation transactions make the ACME server open outbound connections to potentially attacker-controlled servers, and certificate issuance can require interactions with cryptographic hardware. 🥵
On top of that, an attacker can also make the ACME server send validation requests to a domain of its choosing, just by submitting authorization requests for the victim domain. 😬
All of these attacks can be handled by applying appropriate rate limits. Front-end stuff, like POST body validation, can be dealt with using HTTP request limiting. For validation and certificate requests, there are other identifiers the rate limits can be keyed on. For example, the server might limit how fast any individual account key can issue certificates, or how fast validation can be requested within a given subtree of the DNS. 🌸 And to stop attackers from getting around these limits just by minting new accounts, servers would need to limit how fast accounts can be registered. 🚫
10.4. Server-Side Request Forgery 🕵️♀️
Server-Side Request Forgery (SSRF) attacks can happen when an attacker can make a server send HTTP requests to an attacker-chosen URL. In the ACME HTTP challenge validation process, the ACME server does an HTTP GET request to a URL where the attacker gets to choose the domain. This request happens before the server has verified that the client controls the domain, so any client can make a query to any domain. 😱
Some ACME server implementations include information from the validation server's response (to help with debugging). Those implementations let an attacker extract this information from any web server the ACME server can reach, even if the ACME client can't reach it. For example, the ACME server might be able to access servers behind a firewall that would block the ACME client. 🧱💀
You might think the risk of SSRF through this channel is limited, since the attacker can only control the domain of the URL and not the path. 🤔 But nope: if the attacker first points the domain at one they control, they can send the server an HTTP redirect (e.g., a 302 response), which makes the server query an arbitrary URL. oof. 🙈
To limit the SSRF risk even further, ACME server operators should make sure validation queries can only be sent to servers on the public Internet, and not, say, web services inside the server operator's internal network. Since the attacker could make requests to those public servers themselves, they can't gain anything extra through an SSRF attack on ACME except a layer of anonymization. 🎭✨
10.5. CA Policy Considerations ✨
Okay bestie, so ACME's issuance controls are all about checking that the person asking for a cert actually controls the identifier they claim 🧐. But before a CA hands one over, it might need to run a bunch of other checks too, like:
- Did the client agree to a subscriber agreement? 📝
- Is the claimed identifier syntactically valid? 🔍
- For domain names:
- If the leftmost label is a
*, have the right checks been applied? 🌟 - Is the name on the Public Suffix List?
- Is the name a high-value name? 💎
- Is the name a known phishing domain? 🎣
- If the leftmost label is a
- Is the key in the CSR strong enough? 💪
- Is the CSR signed with an acceptable algorithm? 🔐
- Has issuance been authorized or forbidden by a Certification Authority Authorization (CAA) record ([RFC6844])? 🚫
CAs that use ACME to automate issuance need to make sure their servers run all the necessary checks before they issue anything 🤖.
CAs using ACME to let clients agree to terms of service should keep in mind that ACME clients can automate that agreement, and a human user might not be involved at all 🙈.
ACME doesn't say how the server builds the URLs it uses to address resources 🔗. If the server operator uses URLs that third parties can predict, that can leak info about which URLs exist on the server, because an attacker can probe whether a POST-as-GET request to the URL returns 404 (Not Found) or 401 (Unauthorized) 👀.
Say the CA uses really structured URLs with guessable fields:
- Accounts:
https://example.com/:accountID - Orders:
https://example.com/:accountID/:domainName - Authorizations:
https://example.com/:accountID/:domainName - Certificates:
https://example.com/:accountID/:domainName
In that setup, an attacker could figure out which domain names go with which accounts 😬. That could let them correlate ownership between domain names, if the CA doesn't otherwise allow it.
To stop these correlations from leaking, CAs SHOULD give their URLs an unpredictable component 🙅♀️. For example, a CA might give each resource type its own independent namespace, with unpredictable IDs for each resource:
- Accounts:
https://example.com/acct/:accountID - Orders:
https://example.com/order/:orderID - Authorizations:
https://example.com/authz/:authorizationID - Certificates:
https://example.com/cert/:certID
A scheme like that only leaks the type of resource, and it hides the extra correlations the first example gave away 🦋.
11. Operational Considerations ⚙️
Real-world operations bring up some factors that operators of ACME-based CAs have to keep in mind when they configure their services 🛠️. Check the subsections below for examples.
11.1. Key Selection 🔑
ACME relies on two different classes of key pair:
- Account key pairs, which authenticate account holders
- Certificate key pairs, which sign and verify CSRs (and whose public keys are included in certificates)
Compromising the private key of an account key pair is way worse than compromising a certificate's private key 😱. A certificate key pair compromise lets the attacker impersonate the entities named in the certificate for as long as the certificate lasts. An account key pair compromise lets the attacker take full control of the victim's ACME account and do anything the legitimate account holder could do within ACME:
- Issue certificates using existing authorizations
- Revoke existing certificates
- Access and change account information (e.g., contacts)
- Change the account key pair for the account, locking out the legitimate account holder
Because of this, it is RECOMMENDED that each account key pair be used only for authenticating a single ACME account 💅. For example, the public key of an account key pair MUST NOT be included in a certificate 🚫. If an ACME client gets a request from a user to create an account or roll over a key, using an account key the client knows is used somewhere else, then the client MUST return an error 🙅. Clients MUST generate a fresh account key for every account creation or rollover operation 🆕. Also, given the requirements of Section 7.3.1, servers won't create accounts with reused keys anyway 🙌.
ACME clients and servers MUST verify that a CSR submitted in a finalize request does not contain a public key for any known account key pair 🕵️♀️. Specifically, when a server gets a finalize request, it MUST verify that the public key in the CSR is not the same as the public key of the account key pair used to authenticate that request. This keeps things safe even if the protocols the certificate gets used with have vulnerabilities (for example, signing oracles in TLS [JSS15]), so they can't lead to compromise of the ACME account 🛡️. Because ACME accounts are uniquely identified by their account key pair (see Section 7.3.1), the server MUST not allow account key pair reuse across multiple accounts 🚷.
11.2. DNS Security 🌐
As mentioned above, DNS forgery attacks against the ACME server can get the server making wrong calls about domain control, which means mis-issued certificates 😵. Servers SHOULD perform DNS queries over TCP, which resists some forgery attacks better than DNS over UDP 💪.
An ACME-based CA will often need to make DNS queries, like to validate control of DNS names 🔎. Since the security of those validations depends on DNS data being authentic, every possible precaution should be taken to secure the DNS queries the CA makes 🔒. So it is RECOMMENDED that ACME-based CAs make all DNS queries through DNSSEC-validating stub or recursive resolvers ✅. That gives extra protection to domains that choose to use DNSSEC 🫶.
An ACME-based CA must only use a resolver if it trusts the resolver and every component of the network route it's accessed through 🛤️. So it is RECOMMENDED that ACME-based CAs run their own DNSSEC-validating resolvers inside their trusted network, and use them for both CAA record lookups and all record lookups for a challenge scheme (A, AAAA, TXT, etc.) 🏰.
11.3. Token Entropy 🎲
The http-01 and dns-01 validation methods mandate using a random token value to uniquely identify the challenge ✨. The token value is required to contain at least 128 bits of entropy, for two security properties. First, the ACME client shouldn't be able to influence the ACME server's choice of token, because that could let an attacker reuse a domain owner's previous challenge responses for a new validation request 🕵️. Second, the entropy requirement makes it harder for ACME clients to build a "naive" validation server that automatically answers challenges without being configured per challenge 🤏.
11.4. Malformed Certificate Chains 📜
ACME provides certificate chains in the widely used format colloquially called PEM (though it may differ from the actual Privacy Enhanced Mail specification [RFC1421], as noted in [RFC7468]) 🧾. Some current software lets you configure a private key and a certificate in one PEM file by concatenating the text encodings of the two objects 🤯. In the ACME context, that kind of software might be vulnerable to key replacement attacks 😱. A malicious ACME server could make a client use a private key of the server's choosing, by including that key in the PEM file it returns for a certificate URL 👿.
When a client processes a file of type application/pem-certificate-chain, it SHOULD verify that the file contains only encoded certificates 🔍. If anything other than a certificate turns up (that is, if the string -----BEGIN is ever followed by anything other than CERTIFICATE), then the client MUST reject the file as invalid 🚨.
12. References 📚
12.1. Normative References 📚✨
okay so these are the must-read docs bestie, the ones the spec leans on so hard you literally can't follow it without them 💅
[FIPS180-4]
National Institute of Standards and Technology (NIST),
"Secure Hash Standard (SHS)", FIPS PUB 180-4,
DOI 10.6028/NIST.FIPS.180-4, August 2015,
<http://csrc.nist.gov/publications/fips/fips180-4/
fips-180-4.pdf>.
[JSS15] Somorovsky, J., Schwenk, J., and J. Somorovsky, "On the
Security of TLS 1.3 and QUIC Against Weaknesses in PKCS#1
v1.5 Encryption", CSS '15 Proceedings of the 22nd ACM
SIGSAC Conference on Computer and Communications
Security Pages 1185-1196, DOI 10.1145/2810103.2813657,
<https://dl.acm.org/citation.cfm?id=2813657>.
[RFC2119] Bradner, S., "Key words for use in RFCs to Indicate
Requirement Levels", BCP 14, RFC 2119,
DOI 10.17487/RFC2119, March 1997,
<https://www.rfc-editor.org/info/rfc2119>.
[RFC2585] Housley, R. and P. Hoffman, "Internet X.509 Public Key
Infrastructure Operational Protocols: FTP and HTTP",
RFC 2585, DOI 10.17487/RFC2585, May 1999,
<https://www.rfc-editor.org/info/rfc2585>.
[RFC2818] Rescorla, E., "HTTP Over TLS", RFC 2818,
DOI 10.17487/RFC2818, May 2000,
<https://www.rfc-editor.org/info/rfc2818>.
[RFC2985] Nystrom, M. and B. Kaliski, "PKCS #9: Selected Object
Classes and Attribute Types Version 2.0", RFC 2985,
DOI 10.17487/RFC2985, November 2000,
<https://www.rfc-editor.org/info/rfc2985>.
[RFC2986] Nystrom, M. and B. Kaliski, "PKCS #10: Certification
Request Syntax Specification Version 1.7", RFC 2986,
DOI 10.17487/RFC2986, November 2000,
<https://www.rfc-editor.org/info/rfc2986>.
[RFC3339] Klyne, G. and C. Newman, "Date and Time on the Internet:
Timestamps", RFC 3339, DOI 10.17487/RFC3339, July 2002,
<https://www.rfc-editor.org/info/rfc3339>.
[RFC3629] Yergeau, F., "UTF-8, a transformation format of ISO
10646", STD 63, RFC 3629, DOI 10.17487/RFC3629, November
2003, <https://www.rfc-editor.org/info/rfc3629>.
[RFC3986] Berners-Lee, T., Fielding, R., and L. Masinter, "Uniform
Resource Identifier (URI): Generic Syntax", STD 66,
RFC 3986, DOI 10.17487/RFC3986, January 2005,
<https://www.rfc-editor.org/info/rfc3986>.
[RFC4086] Eastlake 3rd, D., Schiller, J., and S. Crocker,
"Randomness Requirements for Security", BCP 106, RFC 4086,
DOI 10.17487/RFC4086, June 2005,
<https://www.rfc-editor.org/info/rfc4086>.
[RFC4648] Josefsson, S., "The Base16, Base32, and Base64 Data
Encodings", RFC 4648, DOI 10.17487/RFC4648, October 2006,
<https://www.rfc-editor.org/info/rfc4648>.
[RFC5234] Crocker, D., Ed. and P. Overell, "Augmented BNF for Syntax
Specifications: ABNF", STD 68, RFC 5234,
DOI 10.17487/RFC5234, January 2008,
<https://www.rfc-editor.org/info/rfc5234>.
[RFC5280] Cooper, D., Santesson, S., Farrell, S., Boeyen, S.,
Housley, R., and W. Polk, "Internet X.509 Public Key
Infrastructure Certificate and Certificate Revocation List
(CRL) Profile", RFC 5280, DOI 10.17487/RFC5280, May 2008,
<https://www.rfc-editor.org/info/rfc5280>.
[RFC5751] Ramsdell, B. and S. Turner, "Secure/Multipurpose Internet
Mail Extensions (S/MIME) Version 3.2 Message
Specification", RFC 5751, DOI 10.17487/RFC5751, January
2010, <https://www.rfc-editor.org/info/rfc5751>.
[RFC5890] Klensin, J., "Internationalized Domain Names for
Applications (IDNA): Definitions and Document Framework",
RFC 5890, DOI 10.17487/RFC5890, August 2010,
<https://www.rfc-editor.org/info/rfc5890>.
[RFC6068] Duerst, M., Masinter, L., and J. Zawinski, "The 'mailto'
URI Scheme", RFC 6068, DOI 10.17487/RFC6068, October 2010,
<https://www.rfc-editor.org/info/rfc6068>.
[RFC6570] Gregorio, J., Fielding, R., Hadley, M., Nottingham, M.,
and D. Orchard, "URI Template", RFC 6570,
DOI 10.17487/RFC6570, March 2012,
<https://www.rfc-editor.org/info/rfc6570>.
[RFC6844] Hallam-Baker, P. and R. Stradling, "DNS Certification
Authority Authorization (CAA) Resource Record", RFC 6844,
DOI 10.17487/RFC6844, January 2013,
<https://www.rfc-editor.org/info/rfc6844>.
[RFC7231] Fielding, R., Ed. and J. Reschke, Ed., "Hypertext Transfer
Protocol (HTTP/1.1): Semantics and Content", RFC 7231,
DOI 10.17487/RFC7231, June 2014,
<https://www.rfc-editor.org/info/rfc7231>.
[RFC7468] Josefsson, S. and S. Leonard, "Textual Encodings of PKIX,
PKCS, and CMS Structures", RFC 7468, DOI 10.17487/RFC7468,
April 2015, <https://www.rfc-editor.org/info/rfc7468>.
[RFC7515] Jones, M., Bradley, J., and N. Sakimura, "JSON Web
Signature (JWS)", RFC 7515, DOI 10.17487/RFC7515, May
2015, <https://www.rfc-editor.org/info/rfc7515>.
[RFC7518] Jones, M., "JSON Web Algorithms (JWA)", RFC 7518,
DOI 10.17487/RFC7518, May 2015,
<https://www.rfc-editor.org/info/rfc7518>.
[RFC7638] Jones, M. and N. Sakimura, "JSON Web Key (JWK)
Thumbprint", RFC 7638, DOI 10.17487/RFC7638, September
2015, <https://www.rfc-editor.org/info/rfc7638>.
[RFC7797] Jones, M., "JSON Web Signature (JWS) Unencoded Payload
Option", RFC 7797, DOI 10.17487/RFC7797, February 2016,
<https://www.rfc-editor.org/info/rfc7797>.
[RFC7807] Nottingham, M. and E. Wilde, "Problem Details for HTTP
APIs", RFC 7807, DOI 10.17487/RFC7807, March 2016,
<https://www.rfc-editor.org/info/rfc7807>.
[RFC8037] Liusvaara, I., "CFRG Elliptic Curve Diffie-Hellman (ECDH)
and Signatures in JSON Object Signing and Encryption
(JOSE)", RFC 8037, DOI 10.17487/RFC8037, January 2017,
<https://www.rfc-editor.org/info/rfc8037>.
[RFC8126] Cotton, M., Leiba, B., and T. Narten, "Guidelines for
Writing an IANA Considerations Section in RFCs", BCP 26,
RFC 8126, DOI 10.17487/RFC8126, June 2017,
<https://www.rfc-editor.org/info/rfc8126>.
[RFC8174] Leiba, B., "Ambiguity of Uppercase vs Lowercase in RFC
2119 Key Words", BCP 14, RFC 8174, DOI 10.17487/RFC8174,
May 2017, <https://www.rfc-editor.org/info/rfc8174>.
[RFC8259] Bray, T., Ed., "The JavaScript Object Notation (JSON) Data
Interchange Format", STD 90, RFC 8259,
DOI 10.17487/RFC8259, December 2017,
<https://www.rfc-editor.org/info/rfc8259>.
[RFC8288] Nottingham, M., "Web Linking", RFC 8288,
DOI 10.17487/RFC8288, October 2017,
<https://www.rfc-editor.org/info/rfc8288>.
[RFC8446] Rescorla, E., "The Transport Layer Security (TLS) Protocol
Version 1.3", RFC 8446, DOI 10.17487/RFC8446, August 2018,
<https://www.rfc-editor.org/info/rfc8446>.
12.2. Informative References 🌸
these ones are the extra-credit reading, the background tea that helps everything click even though the spec doesn't strictly need it 🫶
[ACME-CAA]
Landau, H., "CAA Record Extensions for Account URI and
ACME Method Binding", Work in Progress, draft-ietf-acme-
caa-06, January 2019.
[ACME-IP] Shoemaker, R., "ACME IP Identifier Validation Extension",
Work in Progress, draft-ietf-acme-ip-05, February 2019.
[ACME-TELEPHONE]
Peterson, J. and R. Barnes, "ACME Identifiers and
Challenges for Telephone Numbers", Work in Progress,
draft-ietf-acme-telephone-01, October 2017.
[CABFBR] CA/Browser Forum, "CA/Browser Forum Baseline
Requirements", September 2018,
<https://cabforum.org/baseline-requirements-documents/>.
[DNS0x20] Vixie, P. and D. Dagon, "Use of Bit 0x20 in DNS Labels to
Improve Transaction Identity", Work in Progress,
draft-vixie-dnsext-dns0x20-00, March 2008.
[RFC1421] Linn, J., "Privacy Enhancement for Internet Electronic
Mail: Part I: Message Encryption and Authentication
Procedures", RFC 1421, DOI 10.17487/RFC1421, February
1993, <https://www.rfc-editor.org/info/rfc1421>.
[RFC3552] Rescorla, E. and B. Korver, "Guidelines for Writing RFC
Text on Security Considerations", BCP 72, RFC 3552,
DOI 10.17487/RFC3552, July 2003,
<https://www.rfc-editor.org/info/rfc3552>.
[RFC3553] Mealling, M., Masinter, L., Hardie, T., and G. Klyne, "An
IETF URN Sub-namespace for Registered Protocol
Parameters", BCP 73, RFC 3553, DOI 10.17487/RFC3553, June
2003, <https://www.rfc-editor.org/info/rfc3553>.
[RFC5785] Nottingham, M. and E. Hammer-Lahav, "Defining Well-Known
Uniform Resource Identifiers (URIs)", RFC 5785,
DOI 10.17487/RFC5785, April 2010,
<https://www.rfc-editor.org/info/rfc5785>.
[RFC6960] Santesson, S., Myers, M., Ankney, R., Malpani, A.,
Galperin, S., and C. Adams, "X.509 Internet Public Key
Infrastructure Online Certificate Status Protocol - OCSP",
RFC 6960, DOI 10.17487/RFC6960, June 2013,
<https://www.rfc-editor.org/info/rfc6960>.
[RFC7132] Kent, S. and A. Chi, "Threat Model for BGP Path Security",
RFC 7132, DOI 10.17487/RFC7132, February 2014,
<https://www.rfc-editor.org/info/rfc7132>.
[RFC7525] Sheffer, Y., Holz, R., and P. Saint-Andre,
"Recommendations for Secure Use of Transport Layer
Security (TLS) and Datagram Transport Layer Security
(DTLS)", BCP 195, RFC 7525, DOI 10.17487/RFC7525, May
2015, <https://www.rfc-editor.org/info/rfc7525>.
[W3C.REC-cors-20140116]
Kesteren, A., Ed., "Cross-Origin Resource Sharing", W3C
Recommendation REC-cors-20140116, January 2014,
<http://www.w3.org/TR/2014/REC-cors-20140116>.
Acknowledgements 🫶
okay the people who helped make this happen, and honestly they deserve all the flowers 💐 the spec is a group project and these are the MVPs who were in the chat from the very beginning 👑
this document has been helped by a huge crew of contributors, going all the way back to when it was just an idea:
- Andrew Ayer, SSLMate
- Karthik Bhargavan, INRIA
- Peter Eckersley, EFF
- Alex Halderman, University of Michigan
- Sophie Herold, Hemio
- Tim Hollebeek, DigiCert
- Eric Rescorla, Mozilla
- Seth Schoen, EFF
- Roland Shoemaker, Let's Encrypt
- Rob Stradling, Sectigo
- Martin Thomson, Mozilla
- Jakub Warmuz, University of Oxford
this doc also draws on a lot of ideas from Eric Rescorla's "Automated Certificate Issuance Protocol" draft, and Martin Thomson gave super helpful guidance on using HTTP 🙈✨
Authors' Addresses 🎀
and finally, the people who wrote it all down and to whom you can send your questions, your praise, and your "wait, what does this line mean" 💌
Richard Barnes
Cisco
Email: rlb@ipv.sx
Jacob Hoffman-Andrews
EFF
Email: jsha@eff.org
Daniel McCarney
Let's Encrypt
Email: cpu@letsencrypt.org
James Kasten
University of Michigan
Email: jdkasten@umich.edu
📦 where this page comes from
- ietf: rfc8555 2019-03
- 💖 girlified by claude-haiku-5-5 on : a language model's rewrite of the original, under its terms