diff options
author | Vo Minh Thu <thu@hypered.io> | 2015-12-18 07:46:48 +0100 |
---|---|---|
committer | Vo Minh Thu <thu@hypered.io> | 2015-12-18 07:46:48 +0100 |
commit | c26b53ba48e5b01c817cf2219cfecb62ee16559b (patch) | |
tree | 09ed1d6519e43d4966856f39ef383d70f0880e76 /README.md |
Initial commit.
Diffstat (limited to 'README.md')
-rw-r--r-- | README.md | 173 |
1 files changed, 173 insertions, 0 deletions
diff --git a/README.md b/README.md new file mode 100644 index 0000000..c101c43 --- /dev/null +++ b/README.md | |||
@@ -0,0 +1,173 @@ | |||
1 | # Let's Encrypt ACME protocol | ||
2 | |||
3 | This is a simple Haskell script to obtain a certificate from [Let's | ||
4 | Encrypt](https://letsencrypt.org/) using their ACME protocol. | ||
5 | |||
6 | |||
7 | - The main source of information to write this was | ||
8 | https://github.com/diafygi/letsencrypt-nosudo | ||
9 | |||
10 | - The ACME spec: https://letsencrypt.github.io/acme-spec/ | ||
11 | |||
12 | Most values are still hard-coded for my initial attempt (i.e. my email address | ||
13 | and a domain of mine). | ||
14 | |||
15 | |||
16 | ## Discover the URL for letsencrypt ACME endpoints | ||
17 | |||
18 | API endpoints are listed at https://acme-v01.api.letsencrypt.org/directory and | ||
19 | are currently hard-coded in the script. | ||
20 | |||
21 | ``` | ||
22 | > curl -s https://acme-v01.api.letsencrypt.org/directory | json_pp | ||
23 | { | ||
24 | "new-cert" : "https://acme-v01.api.letsencrypt.org/acme/new-cert", | ||
25 | "new-authz" : "https://acme-v01.api.letsencrypt.org/acme/new-authz", | ||
26 | "revoke-cert" : "https://acme-v01.api.letsencrypt.org/acme/revoke-cert", | ||
27 | "new-reg" : "https://acme-v01.api.letsencrypt.org/acme/new-reg" | ||
28 | } | ||
29 | ``` | ||
30 | |||
31 | |||
32 | ## Generate user account keys | ||
33 | |||
34 | You need an account with Let's Encrypt to ask and receive certificates for your | ||
35 | domains. The account is controlled by a public/private key pair: | ||
36 | |||
37 | ``` | ||
38 | openssl genrsa 4096 > user.key | ||
39 | openssl rsa -in user.key -pubout > user.pub | ||
40 | ``` | ||
41 | |||
42 | |||
43 | ## Generate nonces | ||
44 | |||
45 | |||
46 | Each request to the API have a nonce to prevent replays. The nonce is currently | ||
47 | hard-coded in the script. New nonces can be obtained from letsencrypt with | ||
48 | |||
49 | ``` | ||
50 | > generate-nonce.sh | ||
51 | ``` | ||
52 | |||
53 | |||
54 | ## Create user account | ||
55 | |||
56 | Generate `registration.body` by using the `acme.hs` script then POST it to | ||
57 | letsencrypt (note it assumes you agree to their subscriber agreement): | ||
58 | |||
59 | ``` | ||
60 | > curl -s -X POST --data-binary "@registration.body" \ | ||
61 | https://acme-v01.api.letsencrypt.org/acme/new-reg | json_pp | ||
62 | { | ||
63 | "agreement" : "https://letsencrypt.org/documents/LE-SA-v1.0.1-July-27-2015.pdf", | ||
64 | "contact" : [ | ||
65 | "mailto:noteed@gmail.com" | ||
66 | ], | ||
67 | "key" : { | ||
68 | "e" : "...", | ||
69 | "kty" : "RSA", | ||
70 | "n" : "..." | ||
71 | }, | ||
72 | "id" : 36009, | ||
73 | "createdAt" : "2015-12-04T14:22:08.321951547Z", | ||
74 | "initialIp" : "80.236.245.73" | ||
75 | } | ||
76 | ``` | ||
77 | |||
78 | |||
79 | ## Request a challenge | ||
80 | |||
81 | |||
82 | Let's Encrypt needs a proof that you control the claimed domain. You can | ||
83 | request a challenge with `challenge-request.body`. | ||
84 | |||
85 | ``` | ||
86 | > curl -s -X POST --data-binary "@challenge-request.body" \ | ||
87 | https://acme-v01.api.letsencrypt.org/acme/new-authz | json_pp | ||
88 | { | ||
89 | "expires" : "2015-12-21T18:44:52.331487674Z", | ||
90 | "challenges" : [ | ||
91 | { | ||
92 | "status" : "pending", | ||
93 | "uri" : "https://acme-v01.api.letsencrypt.org/acme/challenge/vXZ1UnZ-y1q7sntnf6NdOfbPAwetJFBqOtvp7FHCjaU/1844047", | ||
94 | "type" : "tls-sni-01", | ||
95 | "token" : "oielAbB7MdyCl29mqjzlqGdrCQSB8SyJaxHbAgQBA7Q" | ||
96 | }, | ||
97 | { | ||
98 | "uri" : "https://acme-v01.api.letsencrypt.org/acme/challenge/vXZ1UnZ-y1q7sntnf6NdOfbPAwetJFBqOtvp7FHCjaU/1844048", | ||
99 | "status" : "pending", | ||
100 | "type" : "http-01", | ||
101 | "token" : "DjyJpI3HVWAmsAwMT5ZFpW8dj19cel6ml6qaBUeGpCg" | ||
102 | } | ||
103 | ], | ||
104 | "identifier" : { | ||
105 | "type" : "dns", | ||
106 | "value" : "aaa.reesd.com" | ||
107 | }, | ||
108 | "combinations" : [ | ||
109 | [ | ||
110 | 0 | ||
111 | ], | ||
112 | [ | ||
113 | 1 | ||
114 | ] | ||
115 | ], | ||
116 | "status" : "pending" | ||
117 | } | ||
118 | ``` | ||
119 | |||
120 | The script assumes you'll answer the challenge by hosting a file at a location | ||
121 | chosen by Let's Encrypt. Extract the token for the `http-01` challenge and run | ||
122 | the script again. Now you have to host the file at the location reported by the | ||
123 | script. | ||
124 | |||
125 | Once this is done, you can ask Let's Encrypt to check the file. | ||
126 | |||
127 | ``` | ||
128 | > curl -s -X POST --data-binary "@challenge-response.body" \ | ||
129 | https://acme-v01.api.letsencrypt.org/acme/challenge/vXZ1UnZ-y1q7sntnf6NdOfbPAwetJFBqOtvp7FHCjaU/1844048 | json_pp | ||
130 | { | ||
131 | "token" : "DjyJpI3HVWAmsAwMT5ZFpW8dj19cel6ml6qaBUeGpCg", | ||
132 | "keyAuthorization" : "DjyJpI3HVWAmsAwMT5ZFpW8dj19cel6ml6qaBUeGpCg.EJe0KReqzCUq6leNOerMC9naZSHxP9TJzGxCcsGkNrw", | ||
133 | "type" : "http-01", | ||
134 | "uri" : "https://acme-v01.api.letsencrypt.org/acme/challenge/vXZ1UnZ-y1q7sntnf6NdOfbPAwetJFBqOtvp7FHCjaU/1844048", | ||
135 | "status" : "pending" | ||
136 | } | ||
137 | ``` | ||
138 | |||
139 | The same URL can then be polled until the status becomes valid. | ||
140 | |||
141 | |||
142 | ## Send CSR / Receive certificate | ||
143 | |||
144 | The CSR is created with: | ||
145 | |||
146 | ``` | ||
147 | > openssl genrsa 4096 > domain.key | ||
148 | > openssl req -new -sha256 -key domain.key -subj "/CN=aaa.reesd.com" > aaa.reesd.com.csr | ||
149 | > openssl req -in aaa.reesd.com.csr -outform DER > aaa.reesd.com.csr.der | ||
150 | ``` | ||
151 | |||
152 | And the signed certificate can be obtained from Let's Encrypt: | ||
153 | |||
154 | ``` | ||
155 | > curl -s -X POST --data-binary "@csr-request.body" \ | ||
156 | https://acme-v01.api.letsencrypt.org/acme/new-cert > aaa.reesd.com.cert.der | ||
157 | ``` | ||
158 | |||
159 | |||
160 | ## Create a certificate for HAProxy | ||
161 | |||
162 | Including explicit DH key exchange parameters to prevent Logjam attack | ||
163 | (https://weakdh.org/). | ||
164 | |||
165 | ``` | ||
166 | > openssl x509 -inform der -in aaa.reesd.com.cert.der \ | ||
167 | -out aaa.reesd.com.cert.pem | ||
168 | > openssl dhparam -out aaa.reesd.com-dhparams.pem 2048 | ||
169 | > cat aaa.reesd.com.cert.pem \ | ||
170 | lets-encrypt-x1-cross-signed.pem \ | ||
171 | aaa.reesd.com.key \ | ||
172 | aaa.reesd.com-dhparams.pem > aaa.reesd.com-combined.pem | ||
173 | ``` | ||