├── lib ├── addstyle.sed └── style.css ├── .gitignore ├── .travis.yml ├── SUBMITTING.md ├── README.md ├── CONTRIBUTING.md ├── Makefile └── draft-barnes-acme.md /lib/addstyle.sed: -------------------------------------------------------------------------------- 1 | \~~ { a\ 2 | 6 | } 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.redxml 2 | *.txt 3 | *.html 4 | *.pdf 5 | *~ 6 | *.swp 7 | /*-[0-9][0-9].xml 8 | .refcache 9 | .i-d-template 10 | 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | 3 | before_install: 4 | - sudo apt-get update -qq 5 | - sudo apt-get install -y python-lxml python-pip 6 | install: 7 | # here we don't use the travis default virtualenv because that 8 | # causes us to install lxml again below, doubling the build time 9 | # when it compiles the native modules from source 10 | - deactivate 11 | - gem install kramdown-rfc2629 12 | - sudo pip install xml2rfc 13 | 14 | script: make ghpages 15 | 16 | env: 17 | global: 18 | - secure: "" 19 | -------------------------------------------------------------------------------- /SUBMITTING.md: -------------------------------------------------------------------------------- 1 | # Submitting Drafts 2 | 3 | Occasionally, you will want to submit versions of your draft to the official 4 | IETF repository. The following process makes this easy. 5 | 6 | Make a submission version of your draft. The makefile uses git tags to work out 7 | what version to create. It looks for the last version number you have tagged 8 | the draft with and calculates the next version. When there are no tags, it 9 | generates a `-00` version. 10 | 11 | ```sh 12 | $ make submit 13 | ``` 14 | 15 | [Submit the .txt and .xml files](https://datatracker.ietf.org/submit/) 16 | that this produces. 17 | 18 | Then you can tag your repository and upload the tags. The tag you should 19 | use is your draft name with the usual number in place of `-latest`. 20 | 21 | ```sh 22 | $ git tag draft-ietf-unicorn-protocol-03 23 | $ git push --tags 24 | ``` 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Automated Certificate Management Environment (ACME) 2 | 3 | [![Build Status](https://travis-ci.org/letsencrypt/acme-spec.svg)] 4 | (https://travis-ci.org/letsencrypt/acme-spec) 5 | 6 | [Latest Version](https://letsencrypt.github.io/acme-spec/) 7 | 8 | ACME is a protocol for automating the management of domain-validation certificates, based on a simple JSON-over-HTTPS interface. This repository contains the specification for ACME. 9 | 10 | We're using the IETF toolchain and formats for this specification. The "source" version of the specification is the markdown version, `draft-barnes-acme.md`. Other versions are generated from that, and the versions in the repo may be out of date. 11 | 12 | This spec is a work in progress. Eventually, we hope to move it to the IETF process to become an RFC, but for now -- pull requests welcome! 13 | 14 | ## Quickstart 15 | 16 | Just open `draft-barnes-acme.md` in a text editor. 17 | 18 | If you want to reproduce the other files, type `make`. 19 | 20 | You need to install some tools (see the Makefile for more information). 21 | ``` 22 | > sudo pip install xml2rfc 23 | > gem install kramdown-rfc2629 24 | ``` 25 | 26 | You can also use a prototype [web version](http://ipv.sx/draftr/) of these tools. 27 | -------------------------------------------------------------------------------- /lib/style.css: -------------------------------------------------------------------------------- 1 | @viewport { 2 | zoom: 1.0; 3 | width: extend-to-zoom; 4 | } 5 | 6 | @-ms-viewport { 7 | width: extend-to-zoom; 8 | zoom: 1.0; 9 | } 10 | 11 | body { 12 | font: 11pt cambria, helvetica, arial, sans-serif; 13 | font-size-adjust: 0.5; 14 | line-height: 130%; 15 | margin: 1em auto; 16 | max-width: 700px; 17 | } 18 | 19 | .title, .filename, h1, h2, h3, h4 { 20 | font-family: candara, helvetica, arial, sans-serif; 21 | font-size-adjust: 0.5; 22 | } 23 | .title { font-size: 150%; } 24 | h1 { font-size: 130%; } 25 | h2 { font-size: 120%; } 26 | h3, h4 { font-size: 110%; } 27 | ul.toc >li { font-size: 95%; } 28 | ul.toc >li >ul, .figure, caption { font-size: 90%; } 29 | 30 | table { 31 | margin-left: 0em; 32 | } 33 | table.header { 34 | width: 100%; 35 | } 36 | 37 | table.header td { 38 | background-color: inherit; 39 | color: black; 40 | } 41 | 42 | samp, tt, code, pre { 43 | font: 11pt consolas, monospace; 44 | font-size-adjust: none; 45 | } 46 | 47 | pre.text, pre.text2 { 48 | width: 90%; 49 | } 50 | 51 | dt { 52 | float: left; clear: left; 53 | margin: 0.5em 0.5em 0 0; 54 | } 55 | dt:first-child { 56 | margin-top: 0; 57 | } 58 | dd { 59 | margin: 0.5em 0 0 2em; 60 | } 61 | dd p, dd ul { 62 | margin-top: 0; margin-bottom: 0; 63 | } 64 | dd *+p { 65 | margin-top: 0.5em; 66 | } 67 | 68 | ol, ul { 69 | padding: 0; 70 | margin: 0.5em 0 0.5em 2em; 71 | } 72 | ul.toc, ul.toc ul { 73 | margin: 0 0 0 1.5em; 74 | } 75 | ul.toc a:first-child { 76 | display: inline-block; 77 | min-width: 1.2em; 78 | } -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Before submitting feedback, please familiarize yourself with our current issues 4 | list and review the [working group home page](|WG-HOMEPAGE|). If you're 5 | new to this, you may also want to read the [Tao of the 6 | IETF](https://www.ietf.org/tao.html). 7 | 8 | Be aware that all contributions to the specification fall under the "NOTE WELL" 9 | terms outlined below. 10 | 11 | 1. The best way to provide feedback (editorial or design) and ask questions is 12 | sending an e-mail to [our mailing 13 | list](https://www.ietf.org/mailman/listinfo/|WG-NAME|). This will ensure that 14 | the entire Working Group sees your input in a timely fashion. 15 | 16 | 2. If you have **editorial** suggestions (i.e., those that do not change the 17 | meaning of the specification), you can either: 18 | 19 | a) Fork this repository and submit a pull request; this is the lowest 20 | friction way to get editorial changes in. 21 | 22 | b) Submit a new issue to Github, and mention that you believe it is editorial 23 | in the issue body. It is not necessary to notify the mailing list for 24 | editorial issues. 25 | 26 | c) Make comments on individual commits in Github. Note that this feedback is 27 | processed only with best effort by the editors, so it should only be used for 28 | quick editorial suggestions or questions. 29 | 30 | 3. For non-editorial (i.e., **design**) issues, you can also create an issue on 31 | Github. However, you **must notify the mailing list** when creating such issues, 32 | providing a link to the issue in the message body. 33 | 34 | Note that **github issues are not for substantial discussions**; the only 35 | appropriate place to discuss design issues is on the mailing list itself. 36 | 37 | 38 | ## NOTE WELL 39 | 40 | Any submission to the [IETF](https://www.ietf.org/) intended by the Contributor 41 | for publication as all or part of an IETF Internet-Draft or RFC and any 42 | statement made within the context of an IETF activity is considered an "IETF 43 | Contribution". Such statements include oral statements in IETF sessions, as 44 | well as written and electronic communications made at any time or place, which 45 | are addressed to: 46 | 47 | * The IETF plenary session 48 | * The IESG, or any member thereof on behalf of the IESG 49 | * Any IETF mailing list, including the IETF list itself, any working group 50 | or design team list, or any other list functioning under IETF auspices 51 | * Any IETF working group or portion thereof 52 | * Any Birds of a Feather (BOF) session 53 | * The IAB or any member thereof on behalf of the IAB 54 | * The RFC Editor or the Internet-Drafts function 55 | * All IETF Contributions are subject to the rules of 56 | [RFC 5378](https://tools.ietf.org/html/rfc5378) and 57 | [RFC 3979](https://tools.ietf.org/html/rfc3979) 58 | (updated by [RFC 4879](https://tools.ietf.org/html/rfc4879)). 59 | 60 | Statements made outside of an IETF session, mailing list or other function, 61 | that are clearly not intended to be input to an IETF activity, group or 62 | function, are not IETF Contributions in the context of this notice. 63 | 64 | Please consult [RFC 5378](https://tools.ietf.org/html/rfc5378) and [RFC 65 | 3979](https://tools.ietf.org/html/rfc3979) for details. 66 | 67 | A participant in any IETF activity is deemed to accept all IETF rules of 68 | process, as documented in Best Current Practices RFCs and IESG Statements. 69 | 70 | A participant in any IETF activity acknowledges that written, audio and video 71 | records of meetings may be made and may be available to the public. 72 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Original makefile from https://github.com/martinthomson/i-d-template 2 | 3 | # The following tools are used by this file. 4 | # All are assumed to be on the path, but you can override these 5 | # in the environment, or command line. 6 | 7 | # Mandatory: 8 | # https://pypi.python.org/pypi/xml2rfc 9 | xml2rfc ?= xml2rfc 10 | 11 | # If you are using markdown files: 12 | # https://github.com/cabo/kramdown-rfc2629 13 | kramdown-rfc2629 ?= kramdown-rfc2629 14 | 15 | # If you are using outline files: 16 | # https://github.com/Juniper/libslax/tree/master/doc/oxtradoc 17 | oxtradoc ?= oxtradoc.in 18 | 19 | # For sanity checkout your draft: 20 | # https://tools.ietf.org/tools/idnits/ 21 | idnits ?= idnits 22 | 23 | # For diff: 24 | # https://tools.ietf.org/tools/rfcdiff/ 25 | rfcdiff ?= rfcdiff --browse 26 | 27 | # For generating PDF: 28 | # https://www.gnu.org/software/enscript/ 29 | enscript ?= enscript 30 | # http://www.ghostscript.com/ 31 | ps2pdf ?= ps2pdf 32 | 33 | 34 | ## Work out what to build 35 | 36 | draft := $(basename $(lastword $(sort $(wildcard draft-*.xml)) $(sort $(wildcard draft-*.org)) $(sort $(wildcard draft-*.md)))) 37 | 38 | ifeq (,$(draft)) 39 | $(warning No file named draft-*.md or draft-*.xml or draft-*.org) 40 | $(error Read README.md for setup instructions) 41 | endif 42 | 43 | draft_type := $(suffix $(firstword $(wildcard $(draft).md $(draft).org $(draft).xml))) 44 | 45 | current_ver := $(shell git tag | grep '$(draft)-[0-9][0-9]' | tail -1 | sed -e"s/.*-//") 46 | ifeq (,$(current_ver)) 47 | next_ver ?= 00 48 | else 49 | next_ver ?= $(shell printf "%.2d" $$((1$(current_ver)-99))) 50 | endif 51 | next := $(draft)-$(next_ver) 52 | diff_ver := $(draft)-$(current_ver) 53 | 54 | 55 | ## Targets 56 | 57 | .PHONY: latest txt html pdf submit diff clean update ghpages 58 | 59 | latest: txt html 60 | txt: $(draft).txt 61 | html: $(draft).html 62 | pdf: $(draft).pdf 63 | 64 | submit: $(next).txt 65 | 66 | idnits: $(next).txt 67 | $(idnits) $< 68 | 69 | ## If you'd like the main github page to show the draft text. 70 | readme: $(next).txt 71 | @echo '```' > README.md 72 | @cat $(next).txt >> README.md 73 | @echo '```' >> README.md 74 | 75 | clean: 76 | -rm -f $(draft).{txt,html,pdf} index.html 77 | -rm -f $(draft)-[0-9][0-9].{xml,md,org,txt,html,pdf} 78 | -rm -f *.diff.html 79 | ifneq (.xml,$(draft_type)) 80 | -rm -f $(draft).xml 81 | endif 82 | 83 | ## diff 84 | 85 | $(next).xml: $(draft).xml 86 | sed -e"s/$(basename $<)-latest/$(basename $@)/" $< > $@ 87 | 88 | ifneq (,$(current_ver)) 89 | .INTERMEDIATE: $(addprefix $(draft)-$(current_ver),.txt $(draft_type)) 90 | diff: $(draft).txt $(draft)-$(current_ver).txt 91 | -$(rfcdiff) $^ 92 | 93 | $(draft)-$(current_ver)$(draft_type): 94 | git show $(draft)-$(current_ver):$(draft)$(draft_type) > $@ 95 | endif 96 | 97 | ## Recipes 98 | 99 | .INTERMEDIATE: $(draft).xml 100 | %.xml: %.md 101 | $(kramdown-rfc2629) $< > $@ 102 | 103 | %.xml: %.org 104 | $(oxtradoc) -m outline-to-xml -n "$@" $< > $@ 105 | 106 | %.txt: %.xml 107 | $(xml2rfc) $< -o $@ --text 108 | 109 | %.htmltmp: %.xml 110 | $(xml2rfc) $< -o $@ --html 111 | %.html: %.htmltmp 112 | sed -f lib/addstyle.sed $< > $@ 113 | 114 | %.pdf: %.txt 115 | $(enscript) --margins 76::76: -B -q -p - $^ | $(ps2pdf) - $@ 116 | 117 | ## Update this Makefile 118 | 119 | # The prerequisites here are what is updated 120 | .INTERMEDIATE: .i-d-template.diff 121 | update: Makefile lib .gitignore SUBMITTING.md 122 | git diff --quiet -- $^ || \ 123 | (echo "You have uncommitted changes to:" $^ 1>&2; exit 1) 124 | -if [ -f .i-d-template ]; then \ 125 | git diff --exit-code $$(cat .i-d-template) -- $^ > .i-d-template.diff && \ 126 | rm -f .i-d-template.diff; \ 127 | fi 128 | git remote | grep i-d-template > /dev/null || \ 129 | git remote add i-d-template https://github.com/martinthomson/i-d-template.git 130 | git fetch i-d-template 131 | [ -f .i-d-template ] && [ $$(git rev-parse i-d-template/master) = $$(cat .i-d-template) ] || \ 132 | git checkout i-d-template/master $^ 133 | git diff --quiet -- $^ && rm -f .i-d-template.diff || \ 134 | git commit -m "Update of $^ from i-d-template/$$(git rev-parse i-d-template/master)" $^ 135 | if [ -f .i-d-template.diff ]; then \ 136 | git apply .i-d-template.diff && \ 137 | git commit -m "Restoring local changes to $$(git diff --name-only $^ | paste -s -d ' ' -)" $^; \ 138 | fi 139 | git rev-parse i-d-template/master > .i-d-template 140 | 141 | ## Update the gh-pages branch with useful files 142 | 143 | GHPAGES_TMP := /tmp/ghpages$(shell echo $$$$) 144 | .INTERMEDIATE: $(GHPAGES_TMP) 145 | ifeq (,$(TRAVIS_COMMIT)) 146 | GIT_ORIG := $(shell git branch | grep '*' | cut -c 3-) 147 | else 148 | GIT_ORIG := $(TRAVIS_COMMIT) 149 | endif 150 | 151 | # Only run upload if we are local or on the master branch 152 | IS_LOCAL := $(if $(TRAVIS),,true) 153 | ifeq (master,$(TRAVIS_BRANCH)) 154 | IS_MASTER := $(findstring false,$(TRAVIS_PULL_REQUEST)) 155 | else 156 | IS_MASTER := 157 | endif 158 | 159 | index.html: $(draft).html 160 | cp $< $@ 161 | 162 | ghpages: index.html $(draft).txt 163 | ifneq (,$(or $(IS_LOCAL),$(IS_MASTER))) 164 | mkdir $(GHPAGES_TMP) 165 | cp -f $^ $(GHPAGES_TMP) 166 | git clean -qfdX 167 | ifeq (true,$(TRAVIS)) 168 | git config user.email "ci-bot@example.com" 169 | git config user.name "Travis CI Bot" 170 | git checkout -q --orphan gh-pages 171 | git rm -qr --cached . 172 | git clean -qfd 173 | git pull -qf origin gh-pages --depth=5 174 | else 175 | git checkout gh-pages 176 | git pull 177 | endif 178 | mv -f $(GHPAGES_TMP)/* $(CURDIR) 179 | git add $^ 180 | if test `git status -s | wc -l` -gt 0; then git commit -m "Script updating gh-pages."; fi 181 | ifneq (,$(GH_TOKEN)) 182 | @echo git push https://github.com/$(TRAVIS_REPO_SLUG).git gh-pages 183 | @git push https://$(GH_TOKEN)@github.com/$(TRAVIS_REPO_SLUG).git gh-pages 184 | endif 185 | -git checkout -qf "$(GIT_ORIG)" 186 | -rm -rf $(GHPAGES_TMP) 187 | endif 188 | -------------------------------------------------------------------------------- /draft-barnes-acme.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Automatic Certificate Management Environment (ACME)" 3 | abbrev: ACME 4 | docname: draft-barnes-acme-02 5 | date: 2014-09-01 6 | category: std 7 | ipr: trust200902 8 | 9 | stand_alone: yes 10 | pi: [toc, sortrefs, symrefs] 11 | 12 | author: 13 | - 14 | ins: R. Barnes 15 | name: Richard Barnes 16 | org: Mozilla 17 | email: rlb@ipv.sx 18 | - 19 | ins: P. Eckersley 20 | name: Peter Eckersley 21 | org: EFF 22 | email: pde@eff.org 23 | - 24 | ins: S. Schoen 25 | name: Seth Schoen 26 | org: EFF 27 | email: schoen@eff.org 28 | - 29 | ins: A. Halderman 30 | name: Alex Halderman 31 | org: University of Michigan 32 | email: jhalderm@eecs.umich.edu 33 | - 34 | ins: J. Kasten 35 | name: James Kasten 36 | org: University of Michigan 37 | email: jdkasten@umich.edu 38 | 39 | 40 | normative: 41 | RFC2119: 42 | RFC2314: 43 | RFC2985: 44 | RFC2986: 45 | RFC3339: 46 | RFC3986: 47 | RFC4514: 48 | RFC5226: 49 | RFC5246: 50 | RFC5280: 51 | RFC5988: 52 | RFC6570: 53 | RFC7159: 54 | RFC7386: 55 | I-D.ietf-appsawg-http-problem: 56 | I-D.ietf-jose-json-web-algorithms: 57 | I-D.ietf-jose-json-web-key: 58 | I-D.ietf-jose-json-web-signature: 59 | 60 | informative: 61 | RFC2818: 62 | 63 | 64 | --- abstract 65 | 66 | Certificates in the Web's X.509 PKI (PKIX) are used for a number of purposes, the most significant of which is the authentication of domain names. Thus, certificate authorities in the Web PKI are trusted to verify that an applicant for a certificate legitimately represents the domain name(s) in the certificate. Today, this verification is done through a collection of ad hoc mechanisms. This document describes a protocol that a certificate authority (CA) and an applicant can use to automate the process of verification and certificate issuance. The protocol also provides facilities for other certificate management functions, such as certificate revocation. 67 | 68 | 69 | --- middle 70 | 71 | # Introduction 72 | 73 | Certificates in the Web PKI are most commonly used to authenticate domain names. Thus, certificate authorities in the Web PKI are trusted to verify that an applicant for a certificate legitimately represents the domain name(s) in the certificate. 74 | 75 | Existing Web PKI certificate authorities tend to run on a set of ad hoc protocols for certificate issuance and identity verification. A typical user experience is something like: 76 | 77 | * Generate a PKCS#10 {{RFC2314}} Certificate Signing Request (CSR). 78 | * Cut-and-paste the CSR into a CA web page. 79 | * Prove ownership of the domain by one of the following methods: 80 | * Put a CA-provided challenge at a specific place on the web server. 81 | * Put a CA-provided challenge at a DNS location corresponding to the target domain. 82 | * Receive CA challenge at a (hopefully) administrator-controlled e-mail address corresponding to the domain and then respond to it on the CA's web page. 83 | * Download the issued certificate and install it on their Web Server. 84 | 85 | With the exception of the CSR itself and the certificates that are issued, these are all completely ad hoc procedures and are accomplished by getting the human user to follow interactive natural-language instructions from the CA rather than by machine-implemented published protocols. In many cases, the instructions are difficult to follow and cause significant 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 presents 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. 86 | 87 | This document describes an extensible framework for automating the issuance and domain validation procedure, thereby allowing servers and infrastructural software to obtain certificates without user interaction. Use of this protocol should radically simplify the deployment of HTTPS and the practicality of PKIX authentication for other TLS based protocols. 88 | 89 | # Deployment Model and Operator Experience 90 | 91 | The major guiding use case for ACME is obtaining certificates for Web sites (HTTPS {{RFC2818}}). In that case, the server is intended to speak for one or more domains, and the process of certificate issuance is intended to verify that the server actually speaks for the domain. 92 | 93 | Different types of certificates reflect different kinds of CA verification of information about the certificate subject. "Domain Validation" (DV) certificates are by far the most common type. For DV validation, the CA merely verifies that the requester has effective control of the web server and/or DNS server for the domain, but does not explicitly attempt to verify their real-world identity. (This is as opposed to "Organization Validation" (OV) and "Extended Validation" (EV) certificates, where the process is intended to also verify the real-world identity of the requester.) 94 | 95 | DV certificate validation commonly checks claims about properties related to control of a domain name -- properties that can be observed by the issuing authority in an interactive process that can be conducted purely online. That means that under typical circumstances, all steps in the request, verification, and issuance process can be represented and performed by Internet protocols with no out-of-band human intervention. 96 | 97 | When an operator deploys a current HTTPS server, it generally prompts him to generate a self-signed certificate. When an operator deploys an ACME-compatible web server, the experience would be something like this: 98 | 99 | * The ACME client prompts the operator for the intended domain name(s) 100 | that the web server is to stand for. 101 | * The ACME client presents the operator with a list of CAs from which it could 102 | get a certificate. 103 | (This list will change over time based on the capabilities of CAs and updates to ACME configuration.) 104 | The ACME client might prompt the operator for 105 | payment information at this point. 106 | * The operator selects a CA. 107 | * In the background, the ACME client contacts the CA and requests that 108 | a certificate be issued for the intended domain name(s). 109 | * Once the CA is satisfied, the certificate is issued and the ACME client 110 | automatically downloads and installs it, potentially notifying the 111 | operator via e-mail, SMS, etc. 112 | * The ACME client periodically contacts the CA to get updated 113 | certificates, stapled OCSP responses, or whatever else would be 114 | required to keep the server functional and its credentials up-to-date. 115 | 116 | The overall idea is that it's nearly as easy to deploy with a CA-issued certificate as a self-signed certificate, and that once the operator has done so, the process is self-sustaining with minimal manual intervention. Close integration of ACME with HTTPS servers, for example, can allow the immediate and automated deployment of certificates as they are issued, optionally sparing the human administrator from additional configuration work. 117 | 118 | 119 | # Terminology 120 | The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 {{RFC2119}}. 121 | 122 | 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 therefore typically runs on a web server, mail server, or some other server system which requires valid TLS certificates. The ACME server runs at a certificate authority, and responds to client requests, performing the requested actions if the client is authorized. 123 | 124 | For simplicity, in the HTTPS transactions used by ACME, the ACME client is the HTTPS client and the ACME server is the HTTPS server. 125 | 126 | In the discussion below, we will refer to three different types of keys / key pairs: 127 | 128 | Subject Public Key: 129 | : A public key to be included in a certificate. 130 | 131 | Account Key Pair: 132 | : A key pair for which the ACME server considers the holder of the private key authorized to manage certificates for a given identifier. The same key pair may be authorized for multiple identifiers. 133 | 134 | Recovery Token: 135 | : A secret value that can be used to demonstrate prior authorization for an identifier, in a situation where all Subject Private Keys and Account Keys are lost. 136 | 137 | ACME messaging is based on HTTPS {{RFC2818}} and JSON {{RFC7159}}. Since JSON is a text-based format, binary fields are Base64-encoded. For Base64 encoding, we use the variant defined in {{I-D.ietf-jose-json-web-signature}}. The important features of this encoding are (1) that it uses the URL-safe character set, and (2) that "=" padding characters are stripped. 138 | 139 | Some HTTPS bodies in ACME are authenticated and integrity-protected by being encapsulated in a JSON Web Signature (JWS) object {{I-D.ietf-jose-json-web-signature}}. ACME uses a profile of JWS, with the following restrictions: 140 | 141 | * The JWS MUST use the JSON or Flattened JSON Serialization 142 | * If the JWS is in the JSON Serialization, it MUST NOT include more than one signature in the "signatures" array 143 | * The JWS Header MUST include "alg" and "jwk" fields 144 | 145 | 146 | # Protocol Overview 147 | 148 | ACME allows a client to request certificate management actions using a set of JSON messages carried over HTTPS. In some ways, ACME functions much like a traditional CA, in which a user creates an account, adds identifiers to that account (proving control of the domains), and requests certificate issuance for those domains while logged in to the account. 149 | 150 | In ACME, the account is represented by an account key pair. The "add a domain" function is accomplished by authorizing the key pair for a given domain. Certificate issuance and revocation are authorized by a signature with the key pair. 151 | 152 | The first phase of ACME is for the client to register with the ACME server. The client generates an asymmetric key pair and associates this key pair with a set of contact information by signing the contact information. The server acknowledges the 153 | registration by replying with a recovery token that the client can provide later to associate a new account key pair in the event that the first account key pair is lost. 154 | 155 | ~~~~~~~~~~ 156 | 157 | Client Server 158 | 159 | Contact Information 160 | Signature -------> 161 | 162 | <------- Recovery Token 163 | 164 | ~~~~~~~~~~ 165 | 166 | Before a client can issue certificates, it must establish an authorization with the server for an account key pair to act for the identifier(s) that it wishes to include in the certificate. To do this, the client must demonstrate to the server both (1) that it holds the private key of the account key pair, and (2) that it has authority over the identifier being claimed. 167 | 168 | Proof of possession of the account key is built into the ACME protocol. All messages from the client to the server are signed by the client, and the server verifies them using the public key of the account key pair. 169 | 170 | To verify that the client controls the identifier being claimed, the server issues the client a set of challenges. Because there are many different ways to validate possession of different types of identifiers, the server will choose from an extensible set of challenges that are appropriate for the identifier being claimed. The client responds with a set of responses that tell the server which challenges the client has completed. The server then validates the challenges to check that the client has accomplished the challenge. 171 | 172 | For example, if the client requests a domain name, the server might challenge the client to provision a record in the DNS under that name, or to provision a file on a web server referenced by an A or AAAA record under that name. The server would then query the DNS for the record in question, or send an HTTP request for the file. If the client provisioned the DNS or the web server as expected, then the server considers the client authorized for the domain name. 173 | 174 | ~~~~~~~~~~ 175 | 176 | Client Server 177 | 178 | Identifier 179 | Signature -------> 180 | 181 | <------- Challenges 182 | 183 | Responses 184 | Signature -------> 185 | 186 | <------- Updated Challenge 187 | 188 | Poll -------> 189 | 190 | <------- Authorization 191 | 192 | ~~~~~~~~~~ 193 | 194 | Once the client has authorized an account key pair for an identifier, it can use the key pair to authorize the issuance of certificates for the identifier. To do this, the client sends a PKCS#10 Certificate Signing Request (CSR) to the server (indicating the identifier(s) to be included in the issued certificate), a set of links to any required authorizations, and a signature over the CSR by the private key of the account key pair. 195 | 196 | If the server agrees to issue the certificate, then it creates the certificate and provides it in its response. The certificate is assigned a URI, which the client can use to fetch updated versions of the certificate. 197 | 198 | ~~~~~~~~~~ 199 | 200 | Client Server 201 | 202 | CSR 203 | Authorization URI(s) 204 | Signature --------> 205 | 206 | <-------- Certificate 207 | 208 | ~~~~~~~~~~ 209 | 210 | To revoke a certificate, the client simply sends a revocation request, signed with an authorized key pair, and the server indicates whether the request has succeeded. 211 | 212 | ~~~~~~~~~~ 213 | 214 | Client Server 215 | 216 | Revocation request 217 | Signature --------> 218 | 219 | <-------- Result 220 | 221 | ~~~~~~~~~~ 222 | 223 | 224 | Note that while ACME is defined with enough flexibility to handle different types of identifiers in principle, the primary use case addressed by this document is the case where domain names are used as identifiers. For example, all of the identifier validation challenges described in Section {identifier-validation-challenges} below address validation of domain names. The use of ACME for other protocols will require further specification, in order to describe how these identifiers are encoded in the protocol, and what types of validation challenges the server might require. 225 | 226 | 227 | # Certificate Management 228 | 229 | In this section, we describe the certificate management functions that ACME enables: 230 | 231 | * Registration 232 | * Key Authorization 233 | * Certificate Issuance 234 | * Certificate Revocation 235 | 236 | Each of these functions is accomplished by the client sending a sequence of HTTPS requests to the server, carrying JSON messages. Each subsection below describes the message formats used by the function, and the order in which messages are sent. 237 | 238 | ## Resources and Requests 239 | 240 | ACME is structured as a REST application with a few types of resources: 241 | 242 | * Registration resources, representing information about an account key 243 | * Authorization resources, representing an account key's authorization to act for an identifier 244 | * Challenge resources, representing a challenge to prove control of an identifier 245 | * Certificate resources, representing issued certificates 246 | * A "new-registration" resource 247 | * A "new-authorization" resource 248 | * A "new-certificate" resource 249 | 250 | In general, the intent is for authorization and certificate resources to contain only public information, so that CAs may publish these resources to document what certificates have been issued and how they were authorized. Non-public information, such as 251 | contact information, is stored in registration resources. 252 | 253 | In order to accomplish ACME transactions, a client needs to have the server's new-registration, new-authorization, and new-certificate URIs; the remaining URIs are provided to the client as a result of requests to these URIs. To simplify 254 | configuration, ACME uses the "next" link relation to indicate URI to contact for the next step in processing: From registration to authorization, and from authorization to certificate issuance. In this way, a client need only be configured with the registration URI. 255 | 256 | The "up" link relation is used with challenge resources to indicate the authorization resource to which a challenge belongs. It is also used from certificate resources to indicate a resource from which the client may fetch a chain of CA certificates that could be used to validate the certificate in the original resource. 257 | 258 | The following diagram illustrates the relations between resources on an ACME server. The solid lines indicate link relations, and the dotted lines correspond to relationships expressed in other ways, e.g., the Location header in a 201 (Created) response. 259 | 260 | ~~~~~~~~~~ 261 | "next" "next" 262 | new-reg ---+----> new-authz ---+----> new-cert cert-chain 263 | . | . | . ^ 264 | . | . | . | "up" 265 | V | V | V | 266 | reg* ----+ authz -----+ cert-----------+ 267 | . ^ 268 | . | "up" 269 | V | 270 | challenge 271 | 272 | ~~~~~~~~~~ 273 | 274 | 275 | The remainder of this section provides the details of how these resources are structured and how the ACME protocol makes use of them. 276 | 277 | All ACME requests with a non-empty body MUST encapsulate the body in a JWS object, signed using the account key pair. The server MUST verify the JWS before processing the request. (For readability, however, the examples below omit this encapsulation.) Encapsulating request bodies in JWS provides a simple authentication of requests by way of key continuity. 278 | 279 | Note that this implies that GET requests are not authenticated. Servers MUST NOT respond to GET requests for resources that might be considered sensitive. 280 | 281 | The following table illustrates a typical sequence of requests required to establish a new account with the server, prove control of an identifier, issue a certificate, and fetch an updated certificate some time after issuance. The "->" is a mnemonic for 282 | a Location header pointing to a created resource. 283 | 284 | | Action | Request | Response | 285 | |:-------------------|:---------------|:-------------| 286 | | Register | POST new-reg | 201 -> reg | 287 | | Request challenges | POST new-authz | 201 -> authz | 288 | | Answer challenges | POST challenge | 200 | 289 | | Poll for status | GET authz | 200 | 290 | | Request issuance | POST new-cert | 201 -> cert | 291 | | Check for new cert | GET cert | 200 | 292 | 293 | 294 | ## Errors 295 | 296 | Errors can be reported in ACME both at the HTTP layer and within ACME payloads. ACME servers can return responses with an HTTP error response code (4XX or 5XX). For example: If the client submits a request using a method not allowed in this document, then the server MAY return status code 405 (Method Not Allowed). 297 | 298 | When the server responds with an error status, it SHOULD provide additional information using problem document {{I-D.ietf-appsawg-http-problem}}. The "type", "detail", and "instance" fields MUST be populated. To facilitate automatic response to errors, this document defines the following standard tokens for use in the "type" field (within the "urn:acme:" namespace): 299 | 300 | | Code | Semantic | 301 | |:----------------|:---------------------------------------------------------| 302 | | malformed | The request message was malformed | 303 | | unauthorized | The client lacks sufficient authorization | 304 | | serverInternal | The server experienced an internal error | 305 | | badCSR | The CSR is unacceptable (e.g., due to a short key) | 306 | 307 | Authorization and challenge objects can also contain error information to indicate why the server was unable to validate authorization. 308 | 309 | TODO: Flesh out errors and syntax for them 310 | 311 | ## Registration 312 | 313 | An ACME registration resource represents a set of metadata associated to an account key pair, most importantly contact information and a recovery token. Registration resources have the following structure: 314 | 315 | key (required, dictionary): 316 | : The public key of the account key pair, encoded as a JSON Web Key object {{I-D.ietf-jose-json-web-key}}. 317 | 318 | contact (optional, array of string): 319 | : An array of URIs that the server can use to contact the client for issues related to this authorization. For example, the server may wish to notify the client about server-initiated revocation, or check with the client on future authorizations (see the "recoveryContact" challenge type). 320 | 321 | recoveryToken (optional, string): 322 | : An opaque token that the client can present to demonstrate that it participated in a prior authorization transaction. 323 | 324 | agreement (optional, string): 325 | : A URI referring to a subscriber agreement or terms of service provided by the server (see below). Including this field indicates the client's agreement with these terms. 326 | 327 | A client creates a new account with the server by sending a POST request to the server's new-registration URI. The body of the request is a registration object containing only the "contact" field. 328 | 329 | ~~~~~~~~~~ 330 | 331 | POST /acme/new-registration HTTP/1.1 332 | Host: example.com 333 | 334 | { 335 | "contact": [ 336 | "mailto:cert-admin@example.com", 337 | "tel:+12025551212" 338 | ], 339 | } 340 | /* Signed as JWS */ 341 | 342 | ~~~~~~~~~~ 343 | 344 | The server MUST ignore any values provided in the "key" or "recoveryToken" fields, as well as any other fields that it does not recognize. If new fields are specified in the future, the specification of those fields MUST describe whether they may be 345 | provided by the client. 346 | 347 | The server creates a registration object with the included contact information. The "key" element of the registration is set to the public key used to verify the JWS (i.e., the "jwk" element of the JWS header). The server also provides a random 348 | recovery token. The server returns this registration object in a 201 (Created) response, with the registration URI in a Location header field. The server may also indicate its new-authorization URI using the "next" link relation. 349 | 350 | If the server wishes to present the client with terms under which the ACME service is to be used, it may indicate the URI where such terms can be accessed in a Link header with link relation "terms-of-service". As noted above, the client may indicate its 351 | agreement with these terms by updating its registration to include the "agreement" field, with the terms URI as its value. 352 | 353 | ~~~~~~~~~~ 354 | 355 | HTTP/1.1 201 Created 356 | Content-Type: application/json 357 | Location: https://example.com/reg/asdf 358 | Link: ;rel="next" 359 | Link: ;rel="terms-of-service" 360 | 361 | { 362 | "key": { /* JWK from JWS header */ }, 363 | 364 | "contact": [ 365 | "mailto:cert-admin@example.com", 366 | "tel:+12025551212" 367 | ], 368 | 369 | "recoveryToken": "uV2Aph7-sghuCcFVmvsiZw" 370 | } 371 | 372 | ~~~~~~~~~~ 373 | 374 | If the client wishes to update this information in the future, it sends a POST request with updated information to the registration URI. The server MUST ignore any updates to the "key" or "recoveryToken" fields, and MUST verify that the request is signed 375 | with the private key corresponding to the "key" field of the request before updating the registration. 376 | 377 | Servers SHOULD NOT respond to GET requests for registration resources as these requests are not authenticated. 378 | 379 | 380 | ## Authorization Resources 381 | 382 | An ACME authorization resource represents server's authorization for an account key pair to represent an identifier. In addition to a public key and identifier, an authorization includes several metadata fields, such as the status of the authorization (e.g., "pending", "valid", or "revoked") and which challenges were used to validate possession of the identifier. 383 | 384 | The structure of an ACME authorization resource is as follows: 385 | 386 | identifier (required, dictionary of string): 387 | : The identifier that the account key is authorized to represent 388 | 389 | type (required, string): 390 | : The type of identifier. 391 | 392 | value (required, string): 393 | : The identifier itself. 394 | 395 | key (required, dictionary): 396 | : The public key of the account key pair, encoded as a JSON Web Key object {{I-D.ietf-jose-json-web-key}}. 397 | 398 | status (optional, string): 399 | : The status of this authorization. Possible values are: "pending", "valid", and "invalid". If this field is missing, then the default value is "pending". 400 | 401 | expires (optional, string): 402 | : The date after which the server will consider this authorization invalid, encoded in the format specified in RFC 3339 {{RFC3339}}. 403 | 404 | challenges (required, dictionary): 405 | : The challenges that the client needs to fulfill in order to prove possession of the identifier (for pending authorizations). For final authorizations, the challenges that were used. Each key in the dictionary is a type of challenge, and the value is a dictionary with parameters required to validate the challenge, as specified in Section {identifier-validation-challenges}. 406 | 407 | combinations (optional, array of arrays of integers): 408 | : A collection of sets of challenges, each of which would be sufficient to prove possession of the identifier. Clients complete a set of challenges that that covers at least one set in this array. Challenges are identified by their indices in the challenges array. If no "combinations" element is included in an authorization object, the client completes all challenges. 409 | 410 | 411 | The only type of identifier defined by this specification is a fully-qualified domain name (type: "dns"). The value of the identifier MUST be the ASCII representation of the domain name. 412 | 413 | ~~~~~~~~~~ 414 | 415 | { 416 | "status": "valid", 417 | "expires": "2015-03-01", 418 | 419 | "identifier": { 420 | "type": "dns", 421 | "value": "example.org" 422 | }, 423 | 424 | "key": { /* JWK */ }, 425 | 426 | "challenges": [ 427 | { 428 | "type": "simpleHttps", 429 | "status": "valid", 430 | "validated": "2014-12-01T12:05Z", 431 | "token": "IlirfxKKXAsHtmzK29Pj8A" 432 | "path": "Hf5GrX4Q7EBax9hc2jJnfw" 433 | }, 434 | { 435 | "type": "recoveryToken", 436 | "status": "valid", 437 | "validated": "2014-12-01T12:07Z", 438 | "token": "23029d88d9e123e" 439 | } 440 | ], 441 | } 442 | 443 | ~~~~~~~~~~ 444 | 445 | 446 | ## Key Authorization 447 | 448 | The key authorization process establishes the authorization of an account key pair to manage certificates for a given identifier. This process must assure the server of two things: First, that the client controls the private key of the key pair, and second, that the client holds the identifier in question. This process may be repeated to associate multiple identifiers to a key pair (e.g., to request certificates with multiple identifiers), or to associate multiple key pairs with an identifier (e.g., to allow multiple entities to manage certificates). 449 | 450 | As illustrated by the figure in the overview section above, the authorization process proceeds in two phases. The client first requests a new authorization, and then the server issues challenges that the client responds to. 451 | 452 | To begin the key authorization process, the client sends a POST request to the server's new-authorization resource. The body of the POST request MUST contain a JWS object, whose payload is a partial authorization object. This JWS object MUST contain only the "identifier" field, so that the server knows what identifier is being authorized. The client MAY provide contact information in the "contact" field in this or any subsequent request. 453 | 454 | ~~~~~~~~~~ 455 | 456 | POST /acme/new-authorization HTTP/1.1 457 | Host: example.com 458 | 459 | { 460 | "identifier": { 461 | "type": "dns", 462 | "value": "example.org" 463 | } 464 | } 465 | /* Signed as JWS */ 466 | 467 | ~~~~~~~~~~ 468 | 469 | Before processing the authorization further, the server SHOULD determine whether it is willing to issue certificates for the identifier. For example, the server 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 is unwilling to issue for the identifier, it SHOULD return a 403 (Forbidden) error, with a problem document describing the reason for the rejection. 470 | 471 | If the server is willing to proceed, it builds a pending authorization object from the initial authorization object submitted by the client. 472 | 473 | * "identifier" the identifier submitted by the client. 474 | * "key": the key used to verify the client's JWS request (i.e., the contents of the "jwk" field in the JWS header) 475 | * "status": SHOULD be "pending" (MAY be omitted) 476 | * "challenges" and "combinations": As selected by the server's policy for this identifier 477 | * The "expires" field MUST be absent. 478 | 479 | The server allocates a new URI for this authorization, and returns a 201 (Created) response, with the authorization URI in a Location header field, and the JSON authorization object in the body. 480 | 481 | ~~~~~~~~~~ 482 | 483 | HTTP/1.1 201 Created 484 | Content-Type: application/json 485 | Location: https://example.com/authz/asdf 486 | Link: ;rel="next" 487 | 488 | { 489 | "status": "pending", 490 | 491 | "identifier": { 492 | "type": "dns", 493 | "value": "example.org" 494 | }, 495 | 496 | "key": { /* JWK from JWS header */ }, 497 | 498 | "challenges": [ 499 | { 500 | "type": "simpleHttps", 501 | "uri": "https://example.com/authz/asdf/0", 502 | "token": "IlirfxKKXAsHtmzK29Pj8A" 503 | }, 504 | { 505 | "type": "dns", 506 | "uri": "https://example.com/authz/asdf/1" 507 | "token": "DGyRejmCefe7v4NfDGDKfA" 508 | }, 509 | { 510 | "type": "recoveryToken", 511 | "uri": "https://example.com/authz/asdf/2" 512 | } 513 | }, 514 | 515 | "combinations": [ 516 | [0, 2], 517 | [1, 2] 518 | ] 519 | } 520 | 521 | ~~~~~~~~~~ 522 | 523 | The client needs to respond with information to complete the challenges. To do this, the client updates the authorization object received from the server by filling in any required information in the elements of the "challenges" dictionary. For example, if the client wishes to complete the "simpleHttps" challenge, it needs to provide the "path" component. (This is also the stage where the client should perform any actions required by the challenge.) 524 | 525 | The client sends these updates back to the server in the form of a JSON object with the response fields required by the challenge type, carried in a POST request to the challenge URI (not authorization URI or the new-authorization URI). This allows the client to send information only for challenges it is responding to. 526 | 527 | For example, if the client were to respond to the "simpleHttps" challenge in the above authorization, it would send the following request: 528 | 529 | ~~~~~~~~~~ 530 | 531 | POST /acme/authz/asdf/0 HTTP/1.1 532 | Host: example.com 533 | 534 | { 535 | "path": "Hf5GrX4Q7EBax9hc2jJnfw" 536 | } 537 | /* Signed as JWS */ 538 | 539 | ~~~~~~~~~~ 540 | 541 | The server updates the authorization document by updating its representation of the challenge with the response fields provided by the client. The server MUST ignore any fields in the response object that are not specified as response fields for this type of challenge. The server provides a 200 response including the updated challenge. 542 | 543 | Presumably, the client's responses provide the server with enough information to validate one or more challenges. The server is said to "finalize" the authorization when it has completed all the validations it is going to complete, and assigns the authorization a status of "valid" or "invalid", corresponding to whether it considers the account key authorized for the identifier. If the final state is "valid", the server MUST add an "expires" field to the authorization. When finalizing an authorization, the server MAY remove the "combinations" field (if present), remove any unfulfilled challenges, or add a "recoveryToken" field. 544 | 545 | Usually, the validation process will take some time, so the client will need to poll the authorization resource to see when it is finalized. For challenges where the client can tell when the server has validated the challenge (e.g., by seeing an HTTP or DNS request from the server), the client SHOULD NOT begin polling until it has seen the validation request from the server. 546 | 547 | To check on the status of an authorization, the client sends a GET request to the authorization URI, and the server responds with the current authorization object. To provide some degree of control over polling, the server MAY provide a Retry-After header field to indicate how long it expect to take in finalizing the response. 548 | 549 | ~~~~~~~~~~ 550 | 551 | GET /acme/authz/asdf HTTP/1.1 552 | Host: example.com 553 | 554 | HTTP/1.1 200 OK 555 | 556 | { 557 | "status": "valid", 558 | "expires": "2015-03-01", 559 | 560 | "identifier": { 561 | "type": "dns", 562 | "value": "example.org" 563 | }, 564 | 565 | "key": { /* JWK */ }, 566 | 567 | "contact": [ 568 | "mailto:cert-admin@example.com", 569 | "tel:+12025551212" 570 | ], 571 | 572 | "challenges": [ 573 | "simpleHttps": { 574 | "status": "valid", 575 | "validated": "2014-12-01T12:05Z", 576 | "token": "IlirfxKKXAsHtmzK29Pj8A" 577 | "path": "Hf5GrX4Q7EBax9hc2jJnfw" 578 | }, 579 | "recoveryToken": { 580 | "status": "valid", 581 | "validated": "2014-12-01T12:07Z", 582 | "token": "23029d88d9e123e" 583 | } 584 | ] 585 | } 586 | 587 | ~~~~~~~~~~ 588 | 589 | 590 | ### Recovery Tokens 591 | 592 | A recovery token is a fallback authentication mechanism. In the event that a client loses all other state, including authorized key pairs and key pairs bound to certificates, the client can use the recovery token to prove that it was previously authorized for the identifier in question. 593 | 594 | This mechanism is necessary because once an ACME server has issued an Authorization Key for a given identifier, that identifier enters a higher-security state, at least with respect to the ACME server. That state exists to protect against attacks such as DNS hijacking and router compromise which tend to inherently defeat all forms of Domain Validation. So once a domain has begun using ACME, new DV-only authorization will not be performed without proof of continuity via possession of an Authorized Private Key or potentially a Subject Private Key for that domain. 595 | 596 | This higher state of security poses some risks. From time to time, the administrators and owners of domains may lose access to keys they have previously had issued or certified, including Authorized private keys and Subject private keys. For instance, the disks on which this key material is stored may suffer failures, or passphrases for these keys may be forgotten. In some cases, the security measures that are taken to protect this sensitive data may contribute to its loss. 597 | 598 | Recovery Tokens and Recovery Challenges exist to provide a fallback mechanism to restore the state of the domain to the server-side administrative security state it was in prior to the use of ACME, such that fresh Domain Validation is sufficient for reauthorization. 599 | 600 | Recovery tokens are therefore only useful to an attacker who can also perform Domain Validation against a target domain, and as a result client administrators may choose to handle them with somewhat fewer security precautions than Authorized and Subject private keys, decreasing the risk of their loss. 601 | 602 | Recovery tokens come in several types, including high-entropy passcodes (which need to be safely preserved by the client admin) and email addresses (which are inherently hard to lose, and which can be used for verification, though they may be a little less secure). 603 | 604 | Recovery tokens are employed in response to Recovery Challenges. Such challenges will be available if the server has issued Recovery Tokens for a given domain, and the combination of a Recovery Challenge and a domain validation Challenge is a plausible alternative to other challenge sets for domains that already have extant Authorized keys. 605 | 606 | ## Certificate Issuance 607 | 608 | The holder of an authorized key pair for an identifier may use ACME to request that a certificate be issued for that identifier. The client makes this request by sending a POST request to the server's new-certificate resource. The body of the POST is a JWS object whose JSON payload contains a Certificate Signing Request (CSR) {{RFC2986}} and set of authorization URIs. The CSR encodes the parameters of the requested certificate; authority to issue is demonstrated by the JWS signature and the linked authorizations. 609 | 610 | csr (required, string): 611 | : A CSR encoding the parameters for the certificate being requested. The CSR is sent in Base64-encoded version of the DER format. (Note: This field uses the same modified Base64-encoding rules used elsewhere in this document, so it is different from PEM.) 612 | 613 | authorizations (required, array of string): 614 | : An array of URIs for authorization resources. 615 | 616 | ~~~~~~~~~~ 617 | 618 | POST /acme/new-cert HTTP/1.1 619 | Host: example.com 620 | Accept: application/pkix-cert 621 | 622 | { 623 | "csr": "5jNudRx6Ye4HzKEqT5...FS6aKdZeGsysoCo4H9P", 624 | "authorizations": [ 625 | "https://example.com/acme/authz/asdf" 626 | ] 627 | } 628 | /* Signed as JWS */ 629 | 630 | ~~~~~~~~~~ 631 | 632 | The CSR encodes the client's requests with regard to the content of the certificate to be issued. The CSR MUST contain at least one extensionRequest attribute {{RFC2985}} requesting a subjectAltName extension, containing the requested identifiers. 633 | 634 | The values provided in the CSR are only a request, and are not guaranteed. The server or CA may alter any fields in the certificate before issuance. For example, the CA may remove identifiers that are not authorized for the key indicated in the "authorization" field. 635 | 636 | If the CA decides to issue a certificate, then the server returns the certificate in a response with status code 201 (Created). The server MUST indicate a URL for this certificate in a Location header field. 637 | 638 | The default format of the certificate is DER (application/pkix-cert). The client may request other formats by including an Accept header in its request. 639 | 640 | The server can provide metadata about the certificate in HTTP headers. For example, the server can include a Link relation header field {{RFC5988}} with relation "up" to provide a certificate under which this certificate was issued. Or the server can include an Expires header as a hint to the client about when to re-query to refresh the certificate. (Of course, the real expiration of the certificate is controlled by the notAfter time in the certificate itself.) 641 | 642 | ~~~~~~~~~~ 643 | 644 | HTTP/1.1 201 Created 645 | Content-Type: application/pkix-cert 646 | Link: ;rel="up";title="issuer" 647 | Location: https://example.com/acme/cert/asdf 648 | 649 | [DER-encoded certificate] 650 | 651 | ~~~~~~~~~~ 652 | 653 | ## Certificate Refresh 654 | 655 | The certificate URL (provided in the Location header of the server's response) is used to refresh or revoke the certificate. To refresh the certificate, the client simply sends a GET request to the certificate URL. This allows the server to provide the client with updated certificates with the same content and different validity intervals, for as long as all of the authorization objects underlying the certificate are valid. 656 | 657 | If a client sends a refresh request and the server is not willing to refresh the certificate, the server MUST respond with status code 403 (Forbidden). If the client still wishes to obtain a certificate, it can re-initiate the authorization process for any expired authorizations related to the certificate. 658 | 659 | ## Certificate Revocation 660 | 661 | To request that a certificate be revoked, the client sends a POST request to the certificate URL. The body of the POST is a JWS object whose JSON payload contains an indication when the client would like the certificate to be revoked: 662 | 663 | revoke (required, string): 664 | : The time at which the certificate should be revoked. The value of this field MUST be either the literal string "now", or a date in RFC 3339 format {{RFC3339}}. 665 | 666 | authorizations (required, array of string): 667 | : An array of URIs for authorization resources. 668 | 669 | ~~~~~~~~~~ 670 | 671 | POST /acme/cert/asdf HTTP/1.1 672 | Host: example.com 673 | 674 | { 675 | "revoke": "now", 676 | "authorizations": [ 677 | "https://example.com/acme/authz/asdf" 678 | ] 679 | } 680 | /* Signed as JWS */ 681 | 682 | ~~~~~~~~~~ 683 | 684 | Before revoking a certificate, the server MUST verify that the account key pair used to sign the request is authorized to act for all of the identifier(s) in the certificate. The server MAY also accept a signature by the private key corresponding to the public key in the certificate. 685 | 686 | If the revocation succeeds, the server responds with status code 200 (OK). If the revocation fails, the server returns an error. 687 | 688 | ~~~~~~~~~~ 689 | 690 | HTTP/1.1 200 OK 691 | Content-Length: 0 692 | 693 | --- or --- 694 | 695 | HTTP/1.1 403 Forbidden 696 | Content-Type: application/problem+json 697 | Content-Language: en 698 | 699 | { 700 | "type": "urn:acme:error:unauthorized" 701 | "detail": "No authorization provided for name example.net" 702 | "instance": "http://example.com/doc/unauthorized" 703 | } 704 | 705 | ~~~~~~~~~~ 706 | 707 | 708 | # Identifier Validation Challenges 709 | 710 | There are few types of identifier in the world for which there is a standardized mechanism to prove possession of a given identifier. In all practical cases, CAs rely on a variety of means to test whether an entity applying for a certificate with a given identifier actually controls that identifier. 711 | 712 | To accommodate this reality, ACME includes an extensible challenge/response framework for identifier validation. This section describes an initial set of Challenge types. Each challenge must describe: 713 | 714 | * Content of Challenge payloads (in Challenge messages) 715 | * Content of Response payloads (in authorizationRequest messages) 716 | * How the server uses the Challenge and Response to verify control of an identifier 717 | 718 | The only general requirement for Challenge and Response payloads is that they MUST be structured as a JSON object, and they MUST contain a parameter "type" that specifies the type of Challenge or Response encoded in the object. 719 | 720 | Different challenges allow the server to obtain proof of different aspects of control over an identifier. In some challenges, like Simple HTTPS and DVSNI, the client directly proves control of an identifier. In other challenges, such as Recovery or Proof of Possession, the client proves historical control of the identifier, by reference to a prior authorization transaction or certificate. 721 | 722 | The choice of which Challenges to offer to a client under which circumstances is a matter of server policy. A server may choose different sets of challenges depending on whether it has interacted with a domain before, and how. For example: 723 | 724 | | Domain status | Challenges typically sufficient for (re)Authorization | 725 | |:----------------------------------------------|:------------------------------------------------------| 726 | | No known prior certificates or ACME usage | Domain Validation (DVSNI or Simple HTTPS) | 727 | | Existing valid certs, first use of ACME | DV + Proof of Possession of previous CA-signed key | 728 | | Ongoing ACME usage | PoP of previous Authorized key | 729 | | Ongoing ACME usage, lost Authorized key | DV + (Recovery or PoP of ACME-certified Subject key) | 730 | | ACME usage, all keys and recovery tokens lost | Recertification by another CA + PoP of that key | 731 | 732 | 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 identifier, there will need to be new Challenge types, and they will need to specify which types of identifier they apply to. 733 | 734 | ## Simple HTTPS 735 | 736 | With Simple HTTPS validation, the client in an ACME transaction proves its control over a domain name by proving that it can provision resources on an HTTPS server that responds for that domain name. The ACME server challenges the client to provision a file with a specific string as its contents. 737 | 738 | type (required, string): 739 | : The string "simpleHttps" 740 | 741 | token (required, string): 742 | : The value to be provisioned in the file. This value MUST have at least 128 bits of entropy, in order to prevent an attacker from guessing it. It MUST NOT contain any non-ASCII characters. 743 | 744 | ~~~~~~~~~~ 745 | 746 | { 747 | "type": "simpleHttps", 748 | "token": "evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ+PCt92wr+oA" 749 | } 750 | 751 | ~~~~~~~~~~ 752 | 753 | A client responds to this Challenge by provisioning the nonce as a resource on the HTTPS server for the domain in question. The path at which the resource is provisioned is determined by the client, but MUST begin with ".well-known/acme-challenge/". The content type of the resource MUST be "text/plain". The client returns the part of the path coming after that prefix in its Response message. 754 | 755 | type (required, string): 756 | : The string "simpleHttps" 757 | 758 | path (required, string): 759 | : The string to be appended to the standard prefix ".well-known/acme-challenge" in order to form the path at which the nonce resource is provisioned. The result of concatenating the prefix with this value MUST match the "path" production in the standard URI format {{RFC3986}} 760 | 761 | ~~~~~~~~~~ 762 | 763 | { 764 | "type": "simpleHttps", 765 | "path": "6tbIMBC5Anhl5bOlWT5ZFA" 766 | } 767 | 768 | ~~~~~~~~~~ 769 | 770 | Given a Challenge/Response pair, the server verifies the client's control of the domain by verifying that the resource was provisioned as expected. 771 | 772 | 1. Form a URI by populating the URI template "https://{domain}/.well-known/acme-challenge/{path}", where the domain field is set to the domain name being verified and the path field is the path provided in the challenge {{RFC6570}}. 773 | 2. Verify that the resulting URI is well-formed. 774 | 3. Dereference the URI using an HTTPS GET request. 775 | 4. Verify that the certificate presented by the HTTPS server is a valid self-signed certificate, and contains the domain name being validated as well as the public key of the key pair being authorized. 776 | 5. Verify that the Content-Type header of the response is either absent, or has the value "text/plain" 777 | 6. Compare the entity body of the response with the nonce. This comparison MUST be performed in terms of Unicode code points, taking into account the encodings of the stored nonce and the body of the request. 778 | 779 | If the GET request succeeds and the entity body is equal to the nonce, then the validation is successful. If the request fails, or the body does not match the nonce, then it has failed. 780 | 781 | 782 | ## Domain Validation with Server Name Indication 783 | 784 | The Domain Validation with Server Name Indication (DVSNI) validation method aims to ensure that the ACME client has administrative access to the web server at the domain name being validated, and possession of the private key being authorized. The ACME server verifies that the operator can reconfigure the web server by having the client create a new self-signed challenge certificate and respond to TLS connections from the ACME server with it. 785 | 786 | The challenge proceeds as follows: The ACME server sends the client a random value R and a nonce used to identify the transaction. The client responds with another random value S. The server initiates a TLS connection on port 443 to a host with the domain name being validated. In the handshake, the ACME server sets the Server Name Indication extension set to "\.acme.invalid". The TLS server (i.e., the ACME client) should respond with a valid self-signed certificate containing both the domain name being validated and the domain name "\.acme.invalid", where Z = SHA-256(R || S). 787 | 788 | The ACME server's Challenge provides its random value R, and a random nonce used to identify the transaction: 789 | 790 | type (required, string): 791 | : The string "dvsni" 792 | 793 | r (required, string): 794 | : A random 32-byte octet, Base64-encoded 795 | 796 | nonce (required, string): 797 | : A random 16-byte octet string, hex-encoded (so that it can be used as a DNS label) 798 | 799 | ~~~~~~~~~~ 800 | 801 | { 802 | "type": "dvsni", 803 | "r": "Tyq0La3slT7tqQ0wlOiXnCY2vyez7Zo5blgPJ1xt5xI", 804 | "nonce": "a82d5ff8ef740d12881f6d3c2277ab2e" 805 | } 806 | 807 | ~~~~~~~~~~ 808 | 809 | The ACME server MAY re-use nonce values, but SHOULD periodically refresh them. ACME clients MUST NOT rely on nonce values being stable over time. 810 | 811 | The client responds to this Challenge by configuring a TLS server on port 443 of a server with the domain name being validated: 812 | 813 | 1. Decode the server's random value R 814 | 2. Generate a random 32-byte octet string S 815 | 3. Compute Z = SHA-256(R || S) (where || denotes concatenation of octet strings) 816 | 4. Generate a self-signed certificate with a subjectAltName extension containing two dNSName values: 817 | 1. The domain name being validated 818 | 2. A name formed by hex-encoding Z and appending the suffix ".acme.invalid" 819 | 5. Compute a nonce domain name by appending the suffix ".acme.invalid" to the nonce provided by the server. 820 | 6. Configure the TLS server such that when a client presents the nonce domain name in the SNI field, the server presents the generated certificate. 821 | 822 | The client's response provides its random value S: 823 | 824 | type (required, string): 825 | : The string "dvsni" 826 | 827 | s (required, string): 828 | : A random 32-byte secret octet string, Base64-encoded 829 | 830 | ~~~~~~~~~~ 831 | 832 | { 833 | "type": "dvsni", 834 | "s": "9dbjsl3gTAtOnEtKFEmhS6Mj-ajNjDcOmRkp3Lfzm3c" 835 | } 836 | 837 | ~~~~~~~~~~ 838 | 839 | Given a Challenge/Response pair, the ACME server verifies the client's control of the domain by verifying that the TLS server was configured as expected: 840 | 841 | 1. Compute the value Z = SHA-256(R || S) 842 | 2. Open a TLS connection to the domain name being validated on port 443, presenting the value "\.acme.invalid" in the SNI field. 843 | 3. Verify the following properties of the certificate provided by the TLS server: 844 | * It is a valid self-signed certificate 845 | * The public key is the public key for the key pair being authorized 846 | * It contains the domain name being validated as a subjectAltName 847 | * It contains a subjectAltName matching the hex-encoding of Z, with the suffix ".acme.invalid" 848 | 849 | It is RECOMMENDED that the ACME server verify the challenge certificate using multi-path probing techniques to reduce the risk of DNS hijacking attacks. 850 | 851 | If the server presents a certificate matching all of the above criteria, then the validation is successful. Otherwise, the validation fails. 852 | 853 | ## Recovery Contact 854 | 855 | A server may issue a recovery contact challenge to verify that the client is the same as the entity that previously requested authorization, using contact information provided by the client in a prior authorizationRequest message. 856 | 857 | The server's message to the client may request action in-band or out-of-band to ACME. The server can provide a token in the message that the client provides in its response. Or the server could provide some out-of-band response channel in its message, such as a URL to click in an email. 858 | 859 | type (required, string): 860 | : The string "recoveryContact" 861 | 862 | activationURL (optional, string): 863 | : A URL the client can visit to cause a recovery message to be sent to client's contact address. 864 | 865 | successURL (optional, string): 866 | : A URL the client may poll to determine if the user has successfully clicked a link or completed other tasks specified by the recovery message. This URL will return a 200 success code if the required tasks have been completed. The client SHOULD NOT poll the URL more than once every three seconds. 867 | 868 | contact (optional, string) 869 | : A full or partly obfuscated version of the contact URI that the server will use to contact the client. Client software may present this to a user in order to suggest what contact point the user should check (e.g., an email address). 870 | 871 | ~~~~~~~~~~ 872 | 873 | { 874 | "type": "recoveryContact", 875 | "activationURL" : "https://example.ca/sendrecovery/a5bd99383fb0", 876 | "successURL" : "https://example.ca/confirmrecovery/bb1b9928932", 877 | "contact" : "c********n@example.com" 878 | } 879 | 880 | ~~~~~~~~~~ 881 | 882 | type (required, string): 883 | : The string "recoveryContact" 884 | 885 | token (optional, string): 886 | : If the user transferred a token from a contact email or call into the client software, the client sends it here. If it the client has received a 200 success response while polling the RecoveryContact Challenge's successURL, this field SHOULD be omitted. 887 | 888 | ~~~~~~~~~~ 889 | 890 | { 891 | "type": "recoveryContact", 892 | "token": "23029d88d9e123e" 893 | } 894 | 895 | ~~~~~~~~~~ 896 | 897 | If the value of the "token" field matches the value provided in the out-of-band message to the client, or if the client has completed the required out-of-band action, then the validation succeeds. Otherwise, the validation fails. 898 | 899 | 900 | ## Recovery Token 901 | 902 | A recovery token is a simple way for the server to verify that the client was previously authorized for a domain. The client simply provides the recovery token that was provided in the authorize message. 903 | 904 | type (required, string): 905 | : The string "recoveryToken" 906 | 907 | ~~~~~~~~~~ 908 | 909 | { 910 | "type": "recoveryToken" 911 | } 912 | 913 | ~~~~~~~~~~ 914 | 915 | The response to a recovery token challenge is simple; the client sends the requested token that it was provided by the server earlier. 916 | 917 | type (required, string): 918 | : The string "recoveryToken" 919 | 920 | token (optional, string): 921 | : The recovery token provided by the server. 922 | 923 | ~~~~~~~~~~ 924 | 925 | { 926 | "type": "recoveryToken", 927 | "token": "23029d88d9e123e" 928 | } 929 | 930 | ~~~~~~~~~~ 931 | 932 | If the value of the "token" field matches a recovery token that the server previously provided for this domain, then the validation succeeds. Otherwise, the validation fails. 933 | 934 | 935 | ## Proof of Possession of a Prior Key 936 | 937 | The Proof of Possession challenge verifies that a client possesses a private key corresponding to a server-specified public key, as demonstrated by its ability to correctly sign server-provided data with that key. 938 | 939 | This method is useful if a server policy calls for issuing a certificate only to an entity that already possesses the subject private key of a particular prior related certificate (perhaps issued by a different CA). It may also help enable other kinds of server policy that are related to authenticating a client's identity using digital signatures. 940 | 941 | This challenge proceeds in much the same way as the proof of possession of the authorized key pair in the main ACME flow (challenge + authorizationRequest). The server provides a nonce and the client signs over the nonce. The main difference is that rather than signing with the private key of the key pair being authorized, the client signs with a private key specified by the server. The server can specify which key pair(s) are acceptable directly (by indicating a public key), or by asking for the key corresponding to a certificate. 942 | 943 | The server provides the following fields as part of the challenge: 944 | 945 | type (required, string): 946 | : The string "proofOfPossession" 947 | 948 | alg (required, string): 949 | : A token indicating the cryptographic algorithm that should be used by the client to compute the signature {{I-D.ietf-jose-json-web-algorithms}}. (MAC algorithms such as "HS*" MUST NOT be used.) The client MUST verify that this algorithm is supported for the indicated key before responding to this challenge. 950 | 951 | nonce (required, string): 952 | : A random 16-byte octet string, Base64-encoded 953 | 954 | hints (required, object): 955 | : A JSON object that contains various clues for the client about what the requested key is, such that the client can find it. Entries in the hints object may include: 956 | 957 | jwk (required, object): 958 | : A JSON Web Key object describing the public key whose corresponding private key should be used to generate the signature {{I-D.ietf-jose-json-web-key}} 959 | 960 | certFingerprints (optional, array): 961 | : An array of certificate fingerprints, hex-encoded SHA1 hashes of DER-encoded certificates that are known to contain this key 962 | 963 | certs (optional, array): 964 | : An array of certificates, in Base64-encoded DER format, that contain acceptable public keys. 965 | 966 | subjectKeyIdentifiers (optional, array): 967 | : An array of hex-encoded Subject Key Identifiers (SKIDs) from certificate(s) that contain the key. Because of divergences in the way that SKIDs are calculated {{RFC5280}}, there may conceivably be more than one of these. 968 | 969 | serialNumbers (optional, array of numbers): 970 | : An array of serial numbers of certificates that are known to contain the requested key 971 | 972 | issuers (optional, array): 973 | : An array of X.509 Distinguished Names {{RFC5280}} of CAs that have been observed to issue certificates for this key, in text form {{RFC4514}} 974 | 975 | authorizedFor (optional, array): 976 | : An array of domain names, if any, for which this server regards the key as an ACME Authorized key. 977 | 978 | ~~~~~~~~~~ 979 | 980 | { 981 | "type": "proofOfPossession", 982 | "alg": "RS256", 983 | "nonce": "eET5udtV7aoX8Xl8gYiZIA", 984 | "hints" : { 985 | "jwk": { 986 | "kty": "RSA", 987 | "e": "AQAB", 988 | "n": "KxITJ0rNlfDMAtfDr8eAw...fSSoehDFNZKQKzTZPtQ" 989 | }, 990 | "certFingerprints": [ 991 | "93416768eb85e33adc4277f4c9acd63e7418fcfe", 992 | "16d95b7b63f1972b980b14c20291f3c0d1855d95", 993 | "48b46570d9fc6358108af43ad1649484def0debf" 994 | ], 995 | "subjectKeyIdentifiers": [ 996 | "d0083162dcc4c8a23ecb8aecbd86120e56fd24e5" 997 | ], 998 | "serialNumbers": [34234239832, 23993939911, 17], 999 | "issuers": [ 1000 | "C=US, O=SuperT LLC, CN=SuperTrustworthy Public CA", 1001 | "O=LessTrustworthy CA Inc, CN=LessTrustworthy But StillSecure" 1002 | ], 1003 | "authorizedFor": ["www.example.com", "example.net"] 1004 | } 1005 | } 1006 | 1007 | ~~~~~~~~~~ 1008 | 1009 | In this case the server is challenging the client to prove its control over the private key that corresponds to the public key specified in the jwk object. The signing algorithm is specified by the alg field. The nonce value is used by the server to identify this challenge and is also used, also with a client-provided signature nonce, as part of the signature input. 1010 | 1011 | ~~~~~~~~~~ 1012 | 1013 | signature-input = signature-nonce || server-nonce 1014 | 1015 | ~~~~~~~~~~ 1016 | 1017 | The client's response includes the server-provided nonce, together with a signature over that nonce by one of the private keys requested by the server. 1018 | 1019 | type (required, string): 1020 | : The string "proofOfPossession" 1021 | 1022 | nonce (required, string): 1023 | : The server nonce that the server previously associated with this challenge 1024 | 1025 | signature (required, object): 1026 | : The ACME signature computed over the signature-input using the server-specified algorithm 1027 | 1028 | 1029 | ~~~~~~~~~~ 1030 | 1031 | { 1032 | "type": "proofOfPossession", 1033 | "nonce": "eET5udtV7aoX8Xl8gYiZIA", 1034 | "signature": { 1035 | "alg": "RS256", 1036 | "nonce": "eET5udtV7aoX8Xl8gYiZIA", 1037 | "sig": "KxITJ0rNlfDMAtfDr8eAw...fSSoehDFNZKQKzTZPtQ", 1038 | "jwk": { 1039 | "kty": "RSA", 1040 | "e": "AQAB", 1041 | "n": "KxITJ0rNlfDMAtfDr8eAw...fSSoehDFNZKQKzTZPtQ" 1042 | } 1043 | } 1044 | } 1045 | 1046 | ~~~~~~~~~~ 1047 | 1048 | Note that just as in the authorizationRequest message, there are two nonces here, once provided by the client (inside the signature object) and one provided by the server in its challenge (outside the signature object). The signature covers the concatenation of these two nonces (as specified in the signature-input above). 1049 | 1050 | If the server is able to validate the signature and confirm that the jwk and alg objects are unchanged from the original challenge, the server can conclude that the client is in control of the private key that corresponds to the specified public key. The server can use this evidence in support of its authorization and certificate issuance policies. 1051 | 1052 | 1053 | ## DNS 1054 | 1055 | When the identifier being validated is a domain name, the client can prove control of that domain by provisioning records under it. The DNS challenge requires the client to provision a TXT record containing a validation token under a specific validation domain name. 1056 | 1057 | type (required, string): 1058 | : The string "dns" 1059 | 1060 | token (required, string): 1061 | : An ASCII string that is to be provisioned in the TXT record. This string SHOULD be randomly generated, with at least 128 bits of entropy (e.g., a hex-encoded random octet string). 1062 | 1063 | ~~~~~~~~~~ 1064 | 1065 | { 1066 | "type": "dns", 1067 | "token": "17817c66b60ce2e4012dfad92657527a" 1068 | } 1069 | 1070 | ~~~~~~~~~~ 1071 | 1072 | In response to this challenge, the client first MUST verify that the token contains only ASCII characters. If so, the client constructs the validation domain name by appending the label "_acme-challenge" to the domain name being validated. For example, if the domain name being validated is "example.com", then the client would provision the following DNS record: 1073 | 1074 | ~~~~~~~~~~ 1075 | 1076 | _acme-challenge.example.com. IN TXT "17817c66b60ce2e4012dfad92657527a" 1077 | 1078 | ~~~~~~~~~~ 1079 | 1080 | The response to a DNS challenge is simply an acknowledgement that the relevant record has been provisioned. 1081 | 1082 | type (required, string): 1083 | : The string "dns" 1084 | 1085 | ~~~~~~~~~~ 1086 | 1087 | { 1088 | "type": "dns" 1089 | } 1090 | 1091 | ~~~~~~~~~~ 1092 | 1093 | To validate a DNS challenge, the server queries for TXT records under the validation domain name. If it receives a record whose contents match the token in the challenge, then the validation succeeds. Otherwise, the validation fails. 1094 | 1095 | 1096 | ## Other possibilities 1097 | 1098 | For future work: 1099 | 1100 | * Email 1101 | * DNSSEC 1102 | * WHOIS 1103 | 1104 | # IANA Considerations 1105 | 1106 | TODO 1107 | 1108 | * Register .well-known path 1109 | * Create identifier validation method registry 1110 | * Registries of syntax tokens, e.g., message types / error types? 1111 | 1112 | # Security Considerations 1113 | 1114 | TODO 1115 | 1116 | * General authorization story 1117 | * PoP nonce entropy 1118 | * ToC/ToU; duration of key authorization 1119 | * Clients need to protect recovery key 1120 | * CA needs to perform a very wide range of issuance policy enforcement and sanity-check steps 1121 | * Parser safety (for JSON, JWK, ASN.1, and any other formats that can be parsed by the ACME server) 1122 | 1123 | 1124 | # Acknowledgements 1125 | 1126 | This document draws on many concepts established by Eric Rescorla's "Automated Certificate Issuance Protocol" draft. Martin Thomson provided helpful guidance in the use of HTTP. 1127 | --------------------------------------------------------------------------------