@@ -73,15 +73,12 @@ function ACME.create(conf)
73
73
local resp
74
74
local err
75
75
76
- for _ , d in ipairs ({" /directory" , " /dir" , " /" }) do
77
- resp , err = http .get {url = config .ca .proxy_uri .. d }
78
-
79
- if resp and resp .status_code == 200 and resp .headers [" content-type" ]
80
- and resp .headers [" content-type" ]:match (" application/json" ) then
81
- self .resources = json .decode (resp .content )
82
- self .nonce = resp .headers [" replay-nonce" ]
83
- break
84
- end
76
+ resp , err = self :get {url = config .ca .proxy_uri .. " /directory" }
77
+
78
+ if resp and resp .status_code == 200 and resp .headers [" content-type" ]
79
+ and resp .headers [" content-type" ]:match (" application/json" ) then
80
+ self .resources = json .decode (resp .content )
81
+ self .nonce = resp .headers [" replay-nonce" ]
85
82
end
86
83
87
84
if not self .resources then
@@ -103,14 +100,31 @@ end
103
100
104
101
--- Adapt resource URLs when going through HAProxy
105
102
function ACME .proxy_url (self , url )
106
-
107
103
if url :sub (1 , # self .conf .ca .uri ) == self .conf .ca .uri then
108
104
return string.format (" %s%s" , self .conf .ca .proxy_uri , url :sub (# self .conf .ca .uri ))
109
105
else
110
106
return url
111
107
end
112
108
end
113
109
110
+ --- ACME wrapper for http.get()
111
+ --
112
+ -- @param resource ACME resource type
113
+ -- @param url Valid HTTP url (mandatory)G
114
+ --
115
+ -- @return Response object or tuple (nil, msg) on errors
116
+ function ACME .get (self , t )
117
+ local resp , err = http .get {url = self :proxy_url (t .url )}
118
+
119
+ if (not t .retry or t .retry < 5 ) and (not resp or (resp and resp .status_code == 503 )) then
120
+ t .retry = not t .retry and 1 or t .retry + 1
121
+
122
+ return self :get (t )
123
+ end
124
+
125
+ return resp , err
126
+ end
127
+
114
128
--- ACME wrapper for http.post()
115
129
--
116
130
-- @param resource ACME resource type
@@ -136,6 +150,7 @@ function ACME.post(self, t)
136
150
137
151
local resp , err = http .post {url = self :proxy_url (t .url ), data = jws ,
138
152
headers = t .headers , timeout = t .timeout }
153
+
139
154
if resp and resp .headers then
140
155
self .nonce = resp .headers [" replay-nonce" ]
141
156
@@ -151,12 +166,74 @@ function ACME.post(self, t)
151
166
return nil , err
152
167
end
153
168
154
- resp , err = http . post {url = self :proxy_url (t .url ), data = jws ,
169
+ resp , err = self : post {url = self :proxy_url (t .url ), data = jws ,
155
170
headers = t .headers }
156
171
end
157
172
end
158
173
end
159
174
175
+ if (not t .retry or t .retry < 5 ) and (not resp or (resp and resp .status_code == 503 )) then
176
+ t .retry = not t .retry and 1 or t .retry + 1
177
+
178
+ return self :post (t )
179
+ end
180
+
181
+ return resp , err
182
+ end
183
+
184
+ --- ACME wrapper for POST-as-GET
185
+ --
186
+ -- @param resource ACME resource type
187
+ -- @param url Valid HTTP url (mandatory)G
188
+ -- @param headers Lua table with request headers
189
+ -- @param data Request content
190
+ --
191
+ -- @return Response object or tuple (nil, msg) on errors
192
+ function ACME .postAsGet (self , t )
193
+ local jws , err = self :jws {url = t .url , payload = nil }
194
+
195
+ if not jws then
196
+ return nil , err
197
+ end
198
+
199
+ if not t .headers then
200
+ t .headers = {
201
+ [" content-type" ] = " application/jose+json"
202
+ }
203
+ elseif not t .headers [" content-type" ] then
204
+ t .headers [" content-type" ] = " application/jose+json"
205
+ end
206
+
207
+ local resp , err = http .post {url = self :proxy_url (t .url ), data = jws ,
208
+ headers = t .headers , timeout = t .timeout }
209
+
210
+ if resp and resp .headers then
211
+ self .nonce = resp .headers [" replay-nonce" ]
212
+
213
+ if resp .status_code == 400 then
214
+ local info = resp :json ()
215
+
216
+ if info and info .type == " urn:ietf:params:acme:error:badNonce" then
217
+
218
+ -- We need to retry once more with new nonce (hence new jws)
219
+ jws , err = self :jws {resource = t .resource , url = t .url ,
220
+ payload = " " }
221
+ if not jws then
222
+ return nil , err
223
+ end
224
+
225
+ resp , err = self :post {url = self :proxy_url (t .url ), data = jws ,
226
+ headers = t .headers }
227
+ end
228
+ end
229
+ end
230
+
231
+ if (not t .retry or t .retry < 5 ) and (not resp or (resp and resp .status_code == 503 )) then
232
+ t .retry = not t .retry and 1 or t .retry + 1
233
+
234
+ return self :postAsGet (t )
235
+ end
236
+
160
237
return resp , err
161
238
end
162
239
@@ -171,14 +248,13 @@ function ACME.refresh_nonce(self)
171
248
self .nonce = nil
172
249
if nonce then return nonce end
173
250
251
+ local resp , e = http .head {url = self :proxy_url (self .resources [" newNonce" ])}
174
252
175
- local r , e = http .head {url = self :proxy_url (self .resources [" newNonce" ])}
176
-
177
- if r and r .headers then
253
+ if resp and resp .headers then
178
254
-- TODO: Expect status code 204
179
255
-- TODO: Expect Cache-Control: no-store
180
256
-- TODO: Expect content size 0
181
- return r .headers [" replay-nonce" ]
257
+ return resp .headers [" replay-nonce" ]
182
258
else
183
259
return nil , e
184
260
end
@@ -194,9 +270,9 @@ function ACME.jws(self, t)
194
270
return nil , " ACME.jws: Account key does not exist."
195
271
end
196
272
197
- if not t or not t .resource or not t . url or not t . payload then
273
+ if not t or not t .url then
198
274
return nil ,
199
- " ACME.jws: Missing one or more parameters (resource, url, payload )"
275
+ " ACME.jws: Missing one or more parameters (url)"
200
276
end
201
277
202
278
-- if key:type() == rsaEncryption
@@ -233,7 +309,7 @@ function ACME.jws(self, t)
233
309
end
234
310
235
311
jws .protected = http .base64 .encode (json .encode (jws .protected ), base64enc )
236
- jws .payload = http .base64 .encode (json .encode (t .payload ), base64enc )
312
+ jws .payload = t . payload and http .base64 .encode (json .encode (t .payload ), base64enc ) or " "
237
313
local digest = openssl .digest .new (" SHA256" )
238
314
digest :update (jws .protected .. " ." .. jws .payload )
239
315
jws .signature = http .base64 .encode (self .account .key :sign (digest ), base64enc )
@@ -341,7 +417,7 @@ local function new_order(applet)
341
417
}
342
418
343
419
-- Get auth token
344
- local auth , err = http . get {url = acme : proxy_url ( auth ) }
420
+ local auth , err = acme : postAsGet {url = auth }
345
421
346
422
if auth then
347
423
local auth_json = auth :json ()
@@ -351,7 +427,7 @@ local function new_order(applet)
351
427
http_challenges [ch .token ] = string.format (" %s.%s" ,
352
428
ch .token , acme .account .thumbprint )
353
429
resp , err = acme :post {url = ch .url , data = ch ,
354
- resource = " challengeDone" , timeout = 1 }
430
+ resource = " challengeDone" }
355
431
challenge_token = ch .token
356
432
break
357
433
end
@@ -363,7 +439,8 @@ local function new_order(applet)
363
439
local order_status
364
440
for _ , t in pairs ({1 , 1 , 2 , 3 , 5 , 8 , 13 }) do
365
441
core .sleep (t )
366
- local resp , err = http .get {url = acme :proxy_url (order .headers [" location" ])}
442
+ local resp , err = acme :postAsGet {url = order .headers [" location" ]}
443
+
367
444
if resp then
368
445
order_status = resp :json ()
369
446
if order_status .status == " ready" then
@@ -424,7 +501,7 @@ local function new_order(applet)
424
501
return http .response .create {status_code = 500 , content = " No cert" }:send (applet )
425
502
end
426
503
427
- local resp , err = http . get {url = acme : proxy_url ( resp_json .certificate ) }
504
+ local resp , err = acme : postAsGet {url = resp_json .certificate }
428
505
local bundle = string.format (" %s%s" , resp .content , key :toPEM (" private" ))
429
506
return http .response .create {status_code = 200 , content = bundle }:send (applet )
430
507
else
@@ -476,4 +553,4 @@ local function main(applet)
476
553
end
477
554
end
478
555
479
- core .register_service (" acme" , " http" , main )
556
+ core .register_service (" acme" , " http" , main )
0 commit comments