@@ -24,12 +24,12 @@ public function __construct($certificatesDir, $webRootDir, $logger = null)
24
24
$ this ->webRootDir = $ webRootDir ;
25
25
$ this ->logger = $ logger ;
26
26
$ this ->client = new Client ($ this ->ca );
27
- $ this ->accountKeyPath = $ certificatesDir. '/_account/private.pem ' ;
27
+ $ this ->accountKeyPath = $ certificatesDir . '/_account/private.pem ' ;
28
28
}
29
29
30
30
public function initAccount ()
31
31
{
32
- if (!is_file ($ this ->accountKeyPath )) {
32
+ if (!is_file ($ this ->accountKeyPath )) {
33
33
34
34
// generate and save new private key for account
35
35
// ---------------------------------------------
@@ -46,7 +46,7 @@ public function initAccount()
46
46
}
47
47
}
48
48
49
- public function signDomains (array $ domains )
49
+ public function signDomains (array $ domains, $ reuseCsr = false )
50
50
{
51
51
$ this ->log ('Starting certificate generation process for domains ' );
52
52
@@ -56,7 +56,7 @@ public function signDomains(array $domains)
56
56
// start domains authentication
57
57
// ----------------------------
58
58
59
- foreach ($ domains as $ domain ) {
59
+ foreach ($ domains as $ domain ) {
60
60
61
61
// 1. getting available authentication options
62
62
// -------------------------------------------
@@ -69,8 +69,10 @@ public function signDomains(array $domains)
69
69
);
70
70
71
71
// choose http-01 challange only
72
- $ challenge = array_reduce ($ response ['challenges ' ], function ($ v , $ w ) { return $ v ? $ v : ($ w ['type ' ] == 'http-01 ' ? $ w : false ); });
73
- if (!$ challenge ) throw new \RuntimeException ("HTTP Challenge for $ domain is not available. Whole response: " .json_encode ($ response ));
72
+ $ challenge = array_reduce ($ response ['challenges ' ], function ($ v , $ w ) {
73
+ return $ v ? $ v : ($ w ['type ' ] == 'http-01 ' ? $ w : false );
74
+ });
75
+ if (!$ challenge ) throw new \RuntimeException ("HTTP Challenge for $ domain is not available. Whole response: " . json_encode ($ response ));
74
76
75
77
$ this ->log ("Got challenge token for $ domain " );
76
78
$ location = $ this ->client ->getLastLocation ();
@@ -79,10 +81,10 @@ public function signDomains(array $domains)
79
81
// 2. saving authentication token for web verification
80
82
// ---------------------------------------------------
81
83
82
- $ directory = $ this ->webRootDir . '/.well-known/acme-challenge ' ;
83
- $ tokenPath = $ directory. '/ ' . $ challenge ['token ' ];
84
+ $ directory = $ this ->webRootDir . '/.well-known/acme-challenge ' ;
85
+ $ tokenPath = $ directory . '/ ' . $ challenge ['token ' ];
84
86
85
- if (!file_exists ($ directory ) && !@mkdir ($ directory , 0755 , true )) {
87
+ if (!file_exists ($ directory ) && !@mkdir ($ directory , 0755 , true )) {
86
88
throw new \RuntimeException ("Couldn't create directory to expose challenge: $ {tokenPath}" );
87
89
}
88
90
@@ -106,7 +108,7 @@ public function signDomains(array $domains)
106
108
$ this ->log ("Token for $ domain saved at $ tokenPath and should be available at $ uri " );
107
109
108
110
// simple self check
109
- if ($ payload !== trim (@file_get_contents ($ uri ))) {
111
+ if ($ payload !== trim (@file_get_contents ($ uri ))) {
110
112
throw new \RuntimeException ("Please check $ uri - token not available " );
111
113
}
112
114
@@ -125,12 +127,12 @@ public function signDomains(array $domains)
125
127
126
128
// waiting loop
127
129
do {
128
- if (empty ($ result ['status ' ]) || $ result ['status ' ] == "invalid " ) {
129
- throw new \RuntimeException ("Verification ended with error: " . json_encode ($ result ));
130
+ if (empty ($ result ['status ' ]) || $ result ['status ' ] == "invalid " ) {
131
+ throw new \RuntimeException ("Verification ended with error: " . json_encode ($ result ));
130
132
}
131
133
$ ended = !($ result ['status ' ] === "pending " );
132
134
133
- if (!$ ended ) {
135
+ if (!$ ended ) {
134
136
$ this ->log ("Verification pending, sleeping 1s " );
135
137
sleep (1 );
136
138
}
@@ -148,33 +150,37 @@ public function signDomains(array $domains)
148
150
$ domainPath = $ this ->getDomainPath (reset ($ domains ));
149
151
150
152
// generate private key for domain if not exist
151
- if (!is_dir ($ domainPath ) || !is_file ($ domainPath. '/private.pem ' )) {
153
+ if (!is_dir ($ domainPath ) || !is_file ($ domainPath . '/private.pem ' )) {
152
154
$ this ->generateKey ($ domainPath );
153
155
}
154
156
155
157
// load domain key
156
- $ privateDomainKey = $ this ->readPrivateKey ($ domainPath. '/private.pem ' );
158
+ $ privateDomainKey = $ this ->readPrivateKey ($ domainPath . '/private.pem ' );
157
159
158
160
$ this ->client ->getLastLinks ();
159
161
162
+ $ csr = $ reuseCsr && is_file ($ domainPath . "/last.csr " )?
163
+ $ this ->getCsrContent ($ domainPath . "/last.csr " ) :
164
+ $ this ->generateCSR ($ privateDomainKey , $ domains );
165
+
160
166
// request certificates creation
161
167
$ result = $ this ->signedRequest (
162
168
"/acme/new-cert " ,
163
- array ('resource ' => 'new-cert ' , 'csr ' => $ this -> generateCSR ( $ privateDomainKey , $ domains ) )
169
+ array ('resource ' => 'new-cert ' , 'csr ' => $ csr )
164
170
);
165
171
if ($ this ->client ->getLastCode () !== 201 ) {
166
- throw new \RuntimeException ("Invalid response code: " . $ this ->client ->getLastCode (). ", " . json_encode ($ result ));
172
+ throw new \RuntimeException ("Invalid response code: " . $ this ->client ->getLastCode () . ", " . json_encode ($ result ));
167
173
}
168
174
$ location = $ this ->client ->getLastLocation ();
169
175
170
176
// waiting loop
171
177
$ certificates = array ();
172
- while (1 ) {
178
+ while (1 ) {
173
179
$ this ->client ->getLastLinks ();
174
180
175
181
$ result = $ this ->client ->get ($ location );
176
182
177
- if ($ this ->client ->getLastCode () == 202 ) {
183
+ if ($ this ->client ->getLastCode () == 202 ) {
178
184
179
185
$ this ->log ("Certificate generation pending, sleeping 1s " );
180
186
sleep (1 );
@@ -185,7 +191,7 @@ public function signDomains(array $domains)
185
191
$ certificates [] = $ this ->parsePemFromBody ($ result );
186
192
187
193
188
- foreach ($ this ->client ->getLastLinks () as $ link ) {
194
+ foreach ($ this ->client ->getLastLinks () as $ link ) {
189
195
$ this ->log ("Requesting chained cert at $ link " );
190
196
$ result = $ this ->client ->get ($ link );
191
197
$ certificates [] = $ this ->parsePemFromBody ($ result );
@@ -194,28 +200,28 @@ public function signDomains(array $domains)
194
200
break ;
195
201
} else {
196
202
197
- throw new \RuntimeException ("Can't get certificate: HTTP code " . $ this ->client ->getLastCode ());
203
+ throw new \RuntimeException ("Can't get certificate: HTTP code " . $ this ->client ->getLastCode ());
198
204
199
205
}
200
206
}
201
207
202
- if (empty ($ certificates )) throw new \RuntimeException ('No certificates generated ' );
208
+ if (empty ($ certificates )) throw new \RuntimeException ('No certificates generated ' );
203
209
204
210
$ this ->log ("Saving fullchain.pem " );
205
- file_put_contents ($ domainPath. '/fullchain.pem ' , implode ("\n" , $ certificates ));
211
+ file_put_contents ($ domainPath . '/fullchain.pem ' , implode ("\n" , $ certificates ));
206
212
207
213
$ this ->log ("Saving cert.pem " );
208
- file_put_contents ($ domainPath. '/cert.pem ' , array_shift ($ certificates ));
214
+ file_put_contents ($ domainPath . '/cert.pem ' , array_shift ($ certificates ));
209
215
210
216
$ this ->log ("Saving chain.pem " );
211
- file_put_contents ($ domainPath. "/chain.pem " , implode ("\n" , $ certificates ));
217
+ file_put_contents ($ domainPath . "/chain.pem " , implode ("\n" , $ certificates ));
212
218
213
219
$ this ->log ("Done !!§§! " );
214
220
}
215
221
216
222
private function readPrivateKey ($ path )
217
223
{
218
- if (($ key = openssl_pkey_get_private ('file:// ' . $ path )) === FALSE ) {
224
+ if (($ key = openssl_pkey_get_private ('file:// ' . $ path )) === FALSE ) {
219
225
throw new \RuntimeException (openssl_error_string ());
220
226
}
221
227
@@ -230,10 +236,10 @@ private function parsePemFromBody($body)
230
236
231
237
private function getDomainPath ($ domain )
232
238
{
233
- return $ this ->certificatesDir . '/ ' . $ domain. '/ ' ;
239
+ return $ this ->certificatesDir . '/ ' . $ domain . '/ ' ;
234
240
}
235
241
236
- private function postNewReg ()
242
+ private function postNewReg ()
237
243
{
238
244
$ this ->log ('Sending registration to letsencrypt server ' );
239
245
@@ -246,14 +252,16 @@ private function postNewReg()
246
252
private function generateCSR ($ privateKey , array $ domains )
247
253
{
248
254
$ domain = reset ($ domains );
249
- $ san = implode (", " , array_map (function ($ dns ) { return "DNS: " . $ dns ; }, $ domains ));
255
+ $ san = implode (", " , array_map (function ($ dns ) {
256
+ return "DNS: " . $ dns ;
257
+ }, $ domains ));
250
258
$ tmpConf = tmpfile ();
251
- $ tmpConfMeta = stream_get_meta_data ($ tmpConf );
259
+ $ tmpConfMeta = stream_get_meta_data ($ tmpConf );
252
260
$ tmpConfPath = $ tmpConfMeta ["uri " ];
253
261
254
262
// workaround to get SAN working
255
263
fwrite ($ tmpConf ,
256
- 'HOME = .
264
+ 'HOME = .
257
265
RANDFILE = $ENV::HOME/.rnd
258
266
[ req ]
259
267
default_bits = 2048
@@ -264,7 +272,7 @@ private function generateCSR($privateKey, array $domains)
264
272
countryName = Country Name (2 letter code)
265
273
[ v3_req ]
266
274
basicConstraints = CA:FALSE
267
- subjectAltName = '. $ san. '
275
+ subjectAltName = ' . $ san . '
268
276
keyUsage = nonRepudiation, digitalSignature, keyEncipherment ' );
269
277
270
278
$ csr = openssl_csr_new (
@@ -281,12 +289,20 @@ private function generateCSR($privateKey, array $domains)
281
289
)
282
290
);
283
291
284
- if (!$ csr ) throw new \RuntimeException ("CSR couldn't be generated! " . openssl_error_string ());
292
+ if (!$ csr ) throw new \RuntimeException ("CSR couldn't be generated! " . openssl_error_string ());
285
293
286
294
openssl_csr_export ($ csr , $ csr );
287
295
fclose ($ tmpConf );
288
296
289
- file_put_contents ($ this ->getDomainPath ($ domain )."/last.csr " , $ csr );
297
+ $ csrPath = $ this ->getDomainPath ($ domain ) . "/last.csr " ;
298
+ file_put_contents ($ csrPath , $ csr );
299
+
300
+ return $ this ->getCsrContent ($ csrPath );
301
+ }
302
+
303
+ private function getCsrContent ($ csrPath ) {
304
+ $ csr = file_get_contents ($ csrPath );
305
+
290
306
preg_match ('~REQUEST-----(.*)-----END~s ' , $ csr , $ matches );
291
307
292
308
return trim (Base64UrlSafeEncoder::encode (base64_decode ($ matches [1 ])));
0 commit comments