Skip to content

Commit 2303edf

Browse files
author
Stanislav Humplik
committed
adding reuse CSR option - for HPKP
1 parent 8e7f9b7 commit 2303edf

File tree

1 file changed

+50
-34
lines changed

1 file changed

+50
-34
lines changed

Lescript.php

+50-34
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,12 @@ public function __construct($certificatesDir, $webRootDir, $logger = null)
2424
$this->webRootDir = $webRootDir;
2525
$this->logger = $logger;
2626
$this->client = new Client($this->ca);
27-
$this->accountKeyPath = $certificatesDir.'/_account/private.pem';
27+
$this->accountKeyPath = $certificatesDir . '/_account/private.pem';
2828
}
2929

3030
public function initAccount()
3131
{
32-
if(!is_file($this->accountKeyPath)) {
32+
if (!is_file($this->accountKeyPath)) {
3333

3434
// generate and save new private key for account
3535
// ---------------------------------------------
@@ -46,7 +46,7 @@ public function initAccount()
4646
}
4747
}
4848

49-
public function signDomains(array $domains)
49+
public function signDomains(array $domains, $reuseCsr = false)
5050
{
5151
$this->log('Starting certificate generation process for domains');
5252

@@ -56,7 +56,7 @@ public function signDomains(array $domains)
5656
// start domains authentication
5757
// ----------------------------
5858

59-
foreach($domains as $domain) {
59+
foreach ($domains as $domain) {
6060

6161
// 1. getting available authentication options
6262
// -------------------------------------------
@@ -69,8 +69,10 @@ public function signDomains(array $domains)
6969
);
7070

7171
// 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));
7476

7577
$this->log("Got challenge token for $domain");
7678
$location = $this->client->getLastLocation();
@@ -79,10 +81,10 @@ public function signDomains(array $domains)
7981
// 2. saving authentication token for web verification
8082
// ---------------------------------------------------
8183

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'];
8486

85-
if(!file_exists($directory) && !@mkdir($directory, 0755, true)) {
87+
if (!file_exists($directory) && !@mkdir($directory, 0755, true)) {
8688
throw new \RuntimeException("Couldn't create directory to expose challenge: ${tokenPath}");
8789
}
8890

@@ -106,7 +108,7 @@ public function signDomains(array $domains)
106108
$this->log("Token for $domain saved at $tokenPath and should be available at $uri");
107109

108110
// simple self check
109-
if($payload !== trim(@file_get_contents($uri))) {
111+
if ($payload !== trim(@file_get_contents($uri))) {
110112
throw new \RuntimeException("Please check $uri - token not available");
111113
}
112114

@@ -125,12 +127,12 @@ public function signDomains(array $domains)
125127

126128
// waiting loop
127129
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));
130132
}
131133
$ended = !($result['status'] === "pending");
132134

133-
if(!$ended) {
135+
if (!$ended) {
134136
$this->log("Verification pending, sleeping 1s");
135137
sleep(1);
136138
}
@@ -148,33 +150,37 @@ public function signDomains(array $domains)
148150
$domainPath = $this->getDomainPath(reset($domains));
149151

150152
// 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')) {
152154
$this->generateKey($domainPath);
153155
}
154156

155157
// load domain key
156-
$privateDomainKey = $this->readPrivateKey($domainPath.'/private.pem');
158+
$privateDomainKey = $this->readPrivateKey($domainPath . '/private.pem');
157159

158160
$this->client->getLastLinks();
159161

162+
$csr = $reuseCsr && is_file($domainPath . "/last.csr")?
163+
$this->getCsrContent($domainPath . "/last.csr") :
164+
$this->generateCSR($privateDomainKey, $domains);
165+
160166
// request certificates creation
161167
$result = $this->signedRequest(
162168
"/acme/new-cert",
163-
array('resource' => 'new-cert', 'csr' => $this->generateCSR($privateDomainKey, $domains))
169+
array('resource' => 'new-cert', 'csr' => $csr)
164170
);
165171
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));
167173
}
168174
$location = $this->client->getLastLocation();
169175

170176
// waiting loop
171177
$certificates = array();
172-
while(1) {
178+
while (1) {
173179
$this->client->getLastLinks();
174180

175181
$result = $this->client->get($location);
176182

177-
if($this->client->getLastCode() == 202) {
183+
if ($this->client->getLastCode() == 202) {
178184

179185
$this->log("Certificate generation pending, sleeping 1s");
180186
sleep(1);
@@ -185,7 +191,7 @@ public function signDomains(array $domains)
185191
$certificates[] = $this->parsePemFromBody($result);
186192

187193

188-
foreach($this->client->getLastLinks() as $link) {
194+
foreach ($this->client->getLastLinks() as $link) {
189195
$this->log("Requesting chained cert at $link");
190196
$result = $this->client->get($link);
191197
$certificates[] = $this->parsePemFromBody($result);
@@ -194,28 +200,28 @@ public function signDomains(array $domains)
194200
break;
195201
} else {
196202

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());
198204

199205
}
200206
}
201207

202-
if(empty($certificates)) throw new \RuntimeException('No certificates generated');
208+
if (empty($certificates)) throw new \RuntimeException('No certificates generated');
203209

204210
$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));
206212

207213
$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));
209215

210216
$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));
212218

213219
$this->log("Done !!§§!");
214220
}
215221

216222
private function readPrivateKey($path)
217223
{
218-
if(($key = openssl_pkey_get_private('file://'.$path)) === FALSE) {
224+
if (($key = openssl_pkey_get_private('file://' . $path)) === FALSE) {
219225
throw new \RuntimeException(openssl_error_string());
220226
}
221227

@@ -230,10 +236,10 @@ private function parsePemFromBody($body)
230236

231237
private function getDomainPath($domain)
232238
{
233-
return $this->certificatesDir.'/'.$domain.'/';
239+
return $this->certificatesDir . '/' . $domain . '/';
234240
}
235241

236-
private function postNewReg()
242+
private function postNewReg()
237243
{
238244
$this->log('Sending registration to letsencrypt server');
239245

@@ -246,14 +252,16 @@ private function postNewReg()
246252
private function generateCSR($privateKey, array $domains)
247253
{
248254
$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));
250258
$tmpConf = tmpfile();
251-
$tmpConfMeta = stream_get_meta_data($tmpConf);
259+
$tmpConfMeta = stream_get_meta_data($tmpConf);
252260
$tmpConfPath = $tmpConfMeta["uri"];
253261

254262
// workaround to get SAN working
255263
fwrite($tmpConf,
256-
'HOME = .
264+
'HOME = .
257265
RANDFILE = $ENV::HOME/.rnd
258266
[ req ]
259267
default_bits = 2048
@@ -264,7 +272,7 @@ private function generateCSR($privateKey, array $domains)
264272
countryName = Country Name (2 letter code)
265273
[ v3_req ]
266274
basicConstraints = CA:FALSE
267-
subjectAltName = '.$san.'
275+
subjectAltName = ' . $san . '
268276
keyUsage = nonRepudiation, digitalSignature, keyEncipherment');
269277

270278
$csr = openssl_csr_new(
@@ -281,12 +289,20 @@ private function generateCSR($privateKey, array $domains)
281289
)
282290
);
283291

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());
285293

286294
openssl_csr_export($csr, $csr);
287295
fclose($tmpConf);
288296

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+
290306
preg_match('~REQUEST-----(.*)-----END~s', $csr, $matches);
291307

292308
return trim(Base64UrlSafeEncoder::encode(base64_decode($matches[1])));

0 commit comments

Comments
 (0)