Completed
Branch master (793070)
by John
01:53
created
src/LEAuthorization.php 1 patch
Indentation   +81 added lines, -81 removed lines patch added patch discarded remove patch
@@ -13,94 +13,94 @@
 block discarded – undo
13 13
  */
14 14
 class LEAuthorization
15 15
 {
16
-    private $connector;
16
+	private $connector;
17 17
 
18
-    public $authorizationURL;
19
-    public $identifier;
20
-    public $status;
21
-    public $expires;
22
-    public $challenges;
18
+	public $authorizationURL;
19
+	public $identifier;
20
+	public $status;
21
+	public $expires;
22
+	public $challenges;
23 23
 
24
-    /** @var LoggerInterface  */
25
-    private $log;
24
+	/** @var LoggerInterface  */
25
+	private $log;
26 26
 
27
-    /**
28
-     * Initiates the LetsEncrypt Authorization class. Child of a LetsEncrypt Order instance.
29
-     *
30
-     * @param LEConnector $connector The LetsEncrypt Connector instance to use for HTTP requests.
31
-     * @param LoggerInterface $log PSR-3 logger
32
-     * @param string $authorizationURL The URL of the authorization, given by a LetsEncrypt order request.
33
-     */
34
-    public function __construct($connector, LoggerInterface $log, $authorizationURL)
35
-    {
36
-        $this->connector = $connector;
37
-        $this->log = $log;
38
-        $this->authorizationURL = $authorizationURL;
27
+	/**
28
+	 * Initiates the LetsEncrypt Authorization class. Child of a LetsEncrypt Order instance.
29
+	 *
30
+	 * @param LEConnector $connector The LetsEncrypt Connector instance to use for HTTP requests.
31
+	 * @param LoggerInterface $log PSR-3 logger
32
+	 * @param string $authorizationURL The URL of the authorization, given by a LetsEncrypt order request.
33
+	 */
34
+	public function __construct($connector, LoggerInterface $log, $authorizationURL)
35
+	{
36
+		$this->connector = $connector;
37
+		$this->log = $log;
38
+		$this->authorizationURL = $authorizationURL;
39 39
 
40
-        $sign = $this->connector->signRequestKid(
41
-            null,
42
-            $this->connector->accountURL,
43
-            $this->authorizationURL
44
-        );
40
+		$sign = $this->connector->signRequestKid(
41
+			null,
42
+			$this->connector->accountURL,
43
+			$this->authorizationURL
44
+		);
45 45
 
46
-        $post = $this->connector->post($this->authorizationURL, $sign);
47
-        if ($post['status'] === 200) {
48
-            $this->identifier = $post['body']['identifier'];
49
-            $this->status = $post['body']['status'];
50
-            $this->expires = $post['body']['expires'];
51
-            $this->challenges = $post['body']['challenges'];
52
-        } else {
53
-            //@codeCoverageIgnoreStart
54
-            $this->log->error("LEAuthorization::__construct cannot find authorization $authorizationURL");
55
-            //@codeCoverageIgnoreEnd
56
-        }
57
-    }
46
+		$post = $this->connector->post($this->authorizationURL, $sign);
47
+		if ($post['status'] === 200) {
48
+			$this->identifier = $post['body']['identifier'];
49
+			$this->status = $post['body']['status'];
50
+			$this->expires = $post['body']['expires'];
51
+			$this->challenges = $post['body']['challenges'];
52
+		} else {
53
+			//@codeCoverageIgnoreStart
54
+			$this->log->error("LEAuthorization::__construct cannot find authorization $authorizationURL");
55
+			//@codeCoverageIgnoreEnd
56
+		}
57
+	}
58 58
 
59
-    /**
60
-     * Updates the data associated with the current LetsEncrypt Authorization instance.
61
-     */
59
+	/**
60
+	 * Updates the data associated with the current LetsEncrypt Authorization instance.
61
+	 */
62 62
 
63
-    public function updateData()
64
-    {
65
-        $sign = $this->connector->signRequestKid(
66
-            null,
67
-            $this->connector->accountURL,
68
-            $this->authorizationURL
69
-        );
63
+	public function updateData()
64
+	{
65
+		$sign = $this->connector->signRequestKid(
66
+			null,
67
+			$this->connector->accountURL,
68
+			$this->authorizationURL
69
+		);
70 70
 
71
-        $post = $this->connector->post($this->authorizationURL, $sign);
72
-        if ($post['status'] === 200) {
73
-            $this->identifier = $post['body']['identifier'];
74
-            $this->status = $post['body']['status'];
75
-            $this->expires = $post['body']['expires'];
76
-            $this->challenges = $post['body']['challenges'];
77
-        } else {
78
-            //@codeCoverageIgnoreStart
79
-            $this->log->error("LEAuthorization::updateData cannot find authorization " . $this->authorizationURL);
80
-            //@codeCoverageIgnoreEnd
81
-        }
82
-    }
71
+		$post = $this->connector->post($this->authorizationURL, $sign);
72
+		if ($post['status'] === 200) {
73
+			$this->identifier = $post['body']['identifier'];
74
+			$this->status = $post['body']['status'];
75
+			$this->expires = $post['body']['expires'];
76
+			$this->challenges = $post['body']['challenges'];
77
+		} else {
78
+			//@codeCoverageIgnoreStart
79
+			$this->log->error("LEAuthorization::updateData cannot find authorization " . $this->authorizationURL);
80
+			//@codeCoverageIgnoreEnd
81
+		}
82
+	}
83 83
 
84
-    /**
85
-     * Gets the challenge of the given $type for this LetsEncrypt Authorization instance.
86
-     * Throws a Runtime Exception if the given $type is not found in this LetsEncrypt Authorization instance.
87
-     *
88
-     * @param string $type The type of verification.
89
-     *                     Supporting LEOrder::CHALLENGE_TYPE_HTTP and LEOrder::CHALLENGE_TYPE_DNS.
90
-     *
91
-     * @return array Returns an array with the challenge of the requested $type.
92
-     */
93
-    public function getChallenge($type)
94
-    {
95
-        foreach ($this->challenges as $challenge) {
96
-            if ($challenge['type'] == $type) {
97
-                return $challenge;
98
-            }
99
-        }
100
-        //@codeCoverageIgnoreStart
101
-        throw new RuntimeException(
102
-            'No challenge found for type \'' . $type . '\' and identifier \'' . $this->identifier['value'] . '\'.'
103
-        );
104
-        //@codeCoverageIgnoreEnd
105
-    }
84
+	/**
85
+	 * Gets the challenge of the given $type for this LetsEncrypt Authorization instance.
86
+	 * Throws a Runtime Exception if the given $type is not found in this LetsEncrypt Authorization instance.
87
+	 *
88
+	 * @param string $type The type of verification.
89
+	 *                     Supporting LEOrder::CHALLENGE_TYPE_HTTP and LEOrder::CHALLENGE_TYPE_DNS.
90
+	 *
91
+	 * @return array Returns an array with the challenge of the requested $type.
92
+	 */
93
+	public function getChallenge($type)
94
+	{
95
+		foreach ($this->challenges as $challenge) {
96
+			if ($challenge['type'] == $type) {
97
+				return $challenge;
98
+			}
99
+		}
100
+		//@codeCoverageIgnoreStart
101
+		throw new RuntimeException(
102
+			'No challenge found for type \'' . $type . '\' and identifier \'' . $this->identifier['value'] . '\'.'
103
+		);
104
+		//@codeCoverageIgnoreEnd
105
+	}
106 106
 }
Please login to merge, or discard this patch.
src/LEOrder.php 2 patches
Spacing   +6 added lines, -6 removed lines patch added patch discarded remove patch
@@ -125,7 +125,7 @@  discard block
 block discarded – undo
125 125
 
126 126
     private function loadExistingOrder($domains)
127 127
     {
128
-        $orderUrl = $this->storage->getMetadata($this->basename.'.order.url');
128
+        $orderUrl = $this->storage->getMetadata($this->basename . '.order.url');
129 129
         $publicKey = $this->storage->getPublicKey($this->basename);
130 130
         $privateKey = $this->storage->getPrivateKey($this->basename);
131 131
 
@@ -169,7 +169,7 @@  discard block
 block discarded – undo
169 169
         }
170 170
 
171 171
         //ensure retrieved order matches our domains
172
-        $orderdomains = array_map(function ($ident) {
172
+        $orderdomains = array_map(function($ident) {
173 173
             return $ident['value'];
174 174
         }, $post['body']['identifiers']);
175 175
         $diff = array_merge(array_diff($orderdomains, $domains), array_diff($domains, $orderdomains));
@@ -198,7 +198,7 @@  discard block
 block discarded – undo
198 198
         $this->storage->setPublicKey($this->basename, null);
199 199
         $this->storage->setCertificate($this->basename, null);
200 200
         $this->storage->setFullChainCertificate($this->basename, null);
201
-        $this->storage->setMetadata($this->basename.'.order.url', null);
201
+        $this->storage->setMetadata($this->basename . '.order.url', null);
202 202
     }
203 203
 
204 204
     private function initialiseKeyTypeAndSize($keyType)
@@ -266,7 +266,7 @@  discard block
 block discarded – undo
266 266
         }
267 267
 
268 268
         $this->orderURL = trim($matches[1]);
269
-        $this->storage->setMetadata($this->basename.'.order.url', $this->orderURL);
269
+        $this->storage->setMetadata($this->basename . '.order.url', $this->orderURL);
270 270
 
271 271
         $this->generateKeys();
272 272
 
@@ -597,13 +597,13 @@  discard block
 block discarded – undo
597 597
      */
598 598
     private function generateCSR()
599 599
     {
600
-        $domains = array_map(function ($dns) {
600
+        $domains = array_map(function($dns) {
601 601
             return $dns['value'];
602 602
         }, $this->identifiers);
603 603
 
604 604
         $dn = ["commonName" => $this->calcCommonName($domains)];
605 605
 
606
-        $san = implode(",", array_map(function ($dns) {
606
+        $san = implode(",", array_map(function($dns) {
607 607
             return "DNS:" . $dns;
608 608
         }, $domains));
609 609
         $tmpConf = tmpfile();
Please login to merge, or discard this patch.
Indentation   +766 added lines, -766 removed lines patch added patch discarded remove patch
@@ -16,553 +16,553 @@  discard block
 block discarded – undo
16 16
  */
17 17
 class LEOrder
18 18
 {
19
-    const CHALLENGE_TYPE_HTTP = 'http-01';
20
-    const CHALLENGE_TYPE_DNS = 'dns-01';
21
-
22
-    /** @var string order status (pending, processing, valid) */
23
-    private $status;
24
-
25
-    /** @var string expiration date for order */
26
-    private $expires;
27
-
28
-    /** @var array containing all the domain identifiers for the order */
29
-    private $identifiers;
30
-
31
-    /** @var string[] URLs to all the authorization objects for this order */
32
-    private $authorizationURLs;
33
-
34
-    /** @var LEAuthorization[] array of authorization objects for the order */
35
-    private $authorizations;
36
-
37
-    /** @var string URL for order finalization */
38
-    private $finalizeURL;
39
-
40
-    /** @var string URL for obtaining certificate */
41
-    private $certificateURL;
42
-
43
-    /** @var string base domain name for certificate */
44
-    private $basename;
45
-
46
-    /** @var string URL referencing order */
47
-    private $orderURL;
48
-
49
-    /** @var string type of key (rsa or ec) */
50
-    private $keyType;
51
-
52
-    /** @var int size of key (typically 2048 or 4096 for rsa, 256 or 384 for ec */
53
-    private $keySize;
54
-
55
-    /** @var LEConnector ACME API connection provided to constructor */
56
-    private $connector;
57
-
58
-    /** @var LoggerInterface logger provided to constructor */
59
-    private $log;
60
-
61
-    /** @var DNSValidatorInterface dns resolution provider to constructor*/
62
-    private $dns;
63
-
64
-    /** @var Sleep sleep service provided to constructor */
65
-    private $sleep;
66
-
67
-    /** @var CertificateStorageInterface storage interface provided to constructor */
68
-    private $storage;
69
-
70
-    /**
71
-     * Initiates the LetsEncrypt Order class. If the base name is found in the $keysDir directory, the order data is
72
-     * requested. If no order was found locally, if the request is invalid or when there is a change in domain names, a
73
-     * new order is created.
74
-     *
75
-     * @param LEConnector $connector The LetsEncrypt Connector instance to use for HTTP requests.
76
-     * @param CertificateStorageInterface $storage
77
-     * @param LoggerInterface $log PSR-3 compatible logger
78
-     * @param DNSValidatorInterface $dns DNS challenge checking service
79
-     * @param Sleep $sleep Sleep service for polling
80
-     */
81
-    public function __construct(
82
-        LEConnector $connector,
83
-        CertificateStorageInterface $storage,
84
-        LoggerInterface $log,
85
-        DNSValidatorInterface $dns,
86
-        Sleep $sleep
87
-    ) {
88
-
89
-        $this->connector = $connector;
90
-        $this->log = $log;
91
-        $this->dns = $dns;
92
-        $this->sleep = $sleep;
93
-        $this->storage = $storage;
94
-    }
95
-
96
-    /**
97
-     * Loads or updates an order. If the base name is found in the $keysDir directory, the order data is
98
-     * requested. If no order was found locally, if the request is invalid or when there is a change in domain names, a
99
-     * new order is created.
100
-     *
101
-     * @param string $basename The base name for the order. Preferable the top domain (example.org).
102
-     *                                         Will be the directory in which the keys are stored. Used for the
103
-     *                                         CommonName in the certificate as well.
104
-     * @param array $domains The array of strings containing the domain names on the certificate.
105
-     * @param string $keyType Type of the key we want to use for certificate. Can be provided in
106
-     *                                         ALGO-SIZE format (ex. rsa-4096 or ec-256) or simply "rsa" and "ec"
107
-     *                                         (using default sizes)
108
-     * @param string $notBefore A date string formatted like 0000-00-00T00:00:00Z (yyyy-mm-dd hh:mm:ss)
109
-     *                                         at which the certificate becomes valid.
110
-     * @param string $notAfter A date string formatted like 0000-00-00T00:00:00Z (yyyy-mm-dd hh:mm:ss)
111
-     *                                         until which the certificate is valid.
112
-     */
113
-    public function loadOrder($basename, array $domains, $keyType, $notBefore, $notAfter)
114
-    {
115
-        $this->basename = $basename;
116
-
117
-        $this->initialiseKeyTypeAndSize($keyType ?? 'rsa-4096');
118
-
119
-        if ($this->loadExistingOrder($domains)) {
120
-            $this->updateAuthorizations();
121
-        } else {
122
-            $this->createOrder($domains, $notBefore, $notAfter);
123
-        }
124
-    }
125
-
126
-    private function loadExistingOrder($domains)
127
-    {
128
-        $orderUrl = $this->storage->getMetadata($this->basename.'.order.url');
129
-        $publicKey = $this->storage->getPublicKey($this->basename);
130
-        $privateKey = $this->storage->getPrivateKey($this->basename);
131
-
132
-        //anything to load?
133
-        if (empty($orderUrl) || empty($publicKey) || empty($privateKey)) {
134
-            $this->log->info("No order found for {$this->basename}. Creating new order.");
135
-            return false;
136
-        }
137
-
138
-        //valid URL?
139
-        $this->orderURL = $orderUrl;
140
-        if (!filter_var($this->orderURL, FILTER_VALIDATE_URL)) {
141
-            //@codeCoverageIgnoreStart
142
-            $this->log->warning("Order for {$this->basename} has invalid URL. Creating new order.");
143
-            $this->deleteOrderFiles();
144
-            return false;
145
-            //@codeCoverageIgnoreEnd
146
-        }
147
-
148
-        //retrieve the order
149
-        $sign = $this->connector->signRequestKid(
150
-            null,
151
-            $this->connector->accountURL,
152
-            $this->orderURL
153
-        );
154
-
155
-        $post = $this->connector->post($this->orderURL, $sign);
156
-        if ($post['status'] !== 200) {
157
-            //@codeCoverageIgnoreStart
158
-            $this->log->warning("Order for {$this->basename} could not be loaded. Creating new order.");
159
-            $this->deleteOrderFiles();
160
-            return false;
161
-            //@codeCoverageIgnoreEnd
162
-        }
163
-
164
-        //ensure the order is still valid
165
-        if ($post['body']['status'] === 'invalid') {
166
-            $this->log->warning("Order for {$this->basename} is 'invalid', unable to authorize. Creating new order.");
167
-            $this->deleteOrderFiles();
168
-            return false;
169
-        }
170
-
171
-        //ensure retrieved order matches our domains
172
-        $orderdomains = array_map(function ($ident) {
173
-            return $ident['value'];
174
-        }, $post['body']['identifiers']);
175
-        $diff = array_merge(array_diff($orderdomains, $domains), array_diff($domains, $orderdomains));
176
-        if (!empty($diff)) {
177
-            $this->log->warning('Domains do not match order data. Deleting and creating new order.');
178
-            $this->deleteOrderFiles();
179
-            return false;
180
-        }
181
-
182
-        //the order is good
183
-        $this->status = $post['body']['status'];
184
-        $this->expires = $post['body']['expires'];
185
-        $this->identifiers = $post['body']['identifiers'];
186
-        $this->authorizationURLs = $post['body']['authorizations'];
187
-        $this->finalizeURL = $post['body']['finalize'];
188
-        if (array_key_exists('certificate', $post['body'])) {
189
-            $this->certificateURL = $post['body']['certificate'];
190
-        }
191
-
192
-        return true;
193
-    }
194
-
195
-    private function deleteOrderFiles()
196
-    {
197
-        $this->storage->setPrivateKey($this->basename, null);
198
-        $this->storage->setPublicKey($this->basename, null);
199
-        $this->storage->setCertificate($this->basename, null);
200
-        $this->storage->setFullChainCertificate($this->basename, null);
201
-        $this->storage->setMetadata($this->basename.'.order.url', null);
202
-    }
203
-
204
-    private function initialiseKeyTypeAndSize($keyType)
205
-    {
206
-        if ($keyType == 'rsa') {
207
-            $this->keyType = 'rsa';
208
-            $this->keySize = 4096;
209
-        } elseif ($keyType == 'ec') {
210
-            $this->keyType = 'ec';
211
-            $this->keySize = 256;
212
-        } else {
213
-            preg_match_all('/^(rsa|ec)\-([0-9]{3,4})$/', $keyType, $keyTypeParts, PREG_SET_ORDER, 0);
214
-
215
-            if (!empty($keyTypeParts)) {
216
-                $this->keyType = $keyTypeParts[0][1];
217
-                $this->keySize = intval($keyTypeParts[0][2]);
218
-            } else {
219
-                throw new LogicException('Key type \'' . $keyType . '\' not supported.');
220
-            }
221
-        }
222
-    }
223
-
224
-    /**
225
-     * Creates a new LetsEncrypt order and fills this instance with its data. Subsequently creates a new RSA keypair
226
-     * for the certificate.
227
-     *
228
-     * @param array $domains The array of strings containing the domain names on the certificate.
229
-     * @param string $notBefore A date string formatted like 0000-00-00T00:00:00Z (yyyy-mm-dd hh:mm:ss)
230
-     *                          at which the certificate becomes valid.
231
-     * @param string $notAfter A date string formatted like 0000-00-00T00:00:00Z (yyyy-mm-dd hh:mm:ss)
232
-     *                          until which the certificate is valid.
233
-     */
234
-    private function createOrder($domains, $notBefore, $notAfter)
235
-    {
236
-        if (!preg_match('~(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z|^$)~', $notBefore) ||
237
-            !preg_match('~(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z|^$)~', $notAfter)
238
-        ) {
239
-            throw new LogicException("notBefore and notAfter must be blank or iso-8601 datestamp");
240
-        }
241
-
242
-        $dns = [];
243
-        foreach ($domains as $domain) {
244
-            if (preg_match_all('~(\*\.)~', $domain) > 1) {
245
-                throw new LogicException('Cannot create orders with multiple wildcards in one domain.');
246
-            }
247
-            $dns[] = ['type' => 'dns', 'value' => $domain];
248
-        }
249
-        $payload = ["identifiers" => $dns, 'notBefore' => $notBefore, 'notAfter' => $notAfter];
250
-        $sign = $this->connector->signRequestKid(
251
-            $payload,
252
-            $this->connector->accountURL,
253
-            $this->connector->newOrder
254
-        );
255
-        $post = $this->connector->post($this->connector->newOrder, $sign);
256
-        if ($post['status'] !== 201) {
257
-            //@codeCoverageIgnoreStart
258
-            throw new RuntimeException('Creating new order failed.');
259
-            //@codeCoverageIgnoreEnd
260
-        }
261
-
262
-        if (!preg_match('~Location: (\S+)~i', $post['header'], $matches)) {
263
-            //@codeCoverageIgnoreStart
264
-            throw new RuntimeException('New-order returned invalid response.');
265
-            //@codeCoverageIgnoreEnd
266
-        }
267
-
268
-        $this->orderURL = trim($matches[1]);
269
-        $this->storage->setMetadata($this->basename.'.order.url', $this->orderURL);
270
-
271
-        $this->generateKeys();
272
-
273
-        $this->status = $post['body']['status'];
274
-        $this->expires = $post['body']['expires'];
275
-        $this->identifiers = $post['body']['identifiers'];
276
-        $this->authorizationURLs = $post['body']['authorizations'];
277
-        $this->finalizeURL = $post['body']['finalize'];
278
-        if (array_key_exists('certificate', $post['body'])) {
279
-            $this->certificateURL = $post['body']['certificate'];
280
-        }
281
-        $this->updateAuthorizations();
282
-
283
-        $this->log->info('Created order for ' . $this->basename);
284
-    }
285
-
286
-    private function generateKeys()
287
-    {
288
-        if ($this->keyType == "rsa") {
289
-            $key = LEFunctions::RSAgenerateKeys($this->keySize);
290
-        } else {
291
-            $key = LEFunctions::ECgenerateKeys($this->keySize);
292
-        }
293
-
294
-        $this->storage->setPublicKey($this->basename, $key['public']);
295
-        $this->storage->setPrivateKey($this->basename, $key['private']);
296
-    }
297
-
298
-    /**
299
-     * Fetches the latest data concerning this LetsEncrypt Order instance and fills this instance with the new data.
300
-     */
301
-    private function updateOrderData()
302
-    {
303
-        $sign = $this->connector->signRequestKid(
304
-            null,
305
-            $this->connector->accountURL,
306
-            $this->orderURL
307
-        );
308
-
309
-        $post = $this->connector->post($this->orderURL, $sign);
310
-        if (strpos($post['header'], "200 OK") !== false) {
311
-            $this->status = $post['body']['status'];
312
-            $this->expires = $post['body']['expires'];
313
-            $this->identifiers = $post['body']['identifiers'];
314
-            $this->authorizationURLs = $post['body']['authorizations'];
315
-            $this->finalizeURL = $post['body']['finalize'];
316
-            if (array_key_exists('certificate', $post['body'])) {
317
-                $this->certificateURL = $post['body']['certificate'];
318
-            }
319
-            $this->updateAuthorizations();
320
-        } else {
321
-            //@codeCoverageIgnoreStart
322
-            $this->log->error("Failed to fetch order for {$this->basename}");
323
-            //@codeCoverageIgnoreEnd
324
-        }
325
-    }
326
-
327
-    /**
328
-     * Fetches the latest data concerning all authorizations connected to this LetsEncrypt Order instance and
329
-     * creates and stores a new LetsEncrypt Authorization instance for each one.
330
-     */
331
-    private function updateAuthorizations()
332
-    {
333
-        $this->authorizations = [];
334
-        foreach ($this->authorizationURLs as $authURL) {
335
-            if (filter_var($authURL, FILTER_VALIDATE_URL)) {
336
-                $auth = new LEAuthorization($this->connector, $this->log, $authURL);
337
-                if ($auth != false) {
338
-                    $this->authorizations[] = $auth;
339
-                }
340
-            }
341
-        }
342
-    }
343
-
344
-    /**
345
-     * Walks all LetsEncrypt Authorization instances and returns whether they are all valid (verified).
346
-     *
347
-     * @return boolean  Returns true if all authorizations are valid (verified), returns false if not.
348
-     */
349
-    public function allAuthorizationsValid()
350
-    {
351
-        if (count($this->authorizations) > 0) {
352
-            foreach ($this->authorizations as $auth) {
353
-                if ($auth->status != 'valid') {
354
-                    return false;
355
-                }
356
-            }
357
-            return true;
358
-        }
359
-        return false;
360
-    }
361
-
362
-    private function loadAccountKey()
363
-    {
364
-        $keydata = $this->storage->getAccountPrivateKey();
365
-        $privateKey = openssl_pkey_get_private($keydata);
366
-        if ($privateKey === false) {
367
-            //@codeCoverageIgnoreStart
368
-            throw new RuntimeException("Failed load account key");
369
-            //@codeCoverageIgnoreEnd
370
-        }
371
-        return $privateKey;
372
-    }
373
-
374
-
375
-    private function loadCertificateKey()
376
-    {
377
-        $keydata = $this->storage->getPrivateKey($this->basename);
378
-        $privateKey = openssl_pkey_get_private($keydata);
379
-        if ($privateKey === false) {
380
-            //@codeCoverageIgnoreStart
381
-            throw new RuntimeException("Failed load certificate key");
382
-            //@codeCoverageIgnoreEnd
383
-        }
384
-        return $privateKey;
385
-    }
386
-
387
-    /**
388
-     * Get all pending LetsEncrypt Authorization instances and return the necessary data for verification.
389
-     * The data in the return object depends on the $type.
390
-     *
391
-     * @param string $type The type of verification to get. Supporting http-01 and dns-01.
392
-     *                     Supporting LEOrder::CHALLENGE_TYPE_HTTP and LEOrder::CHALLENGE_TYPE_DNS. Throws a Runtime
393
-     *                     Exception when requesting an unknown $type. Keep in mind a wildcard domain authorization only
394
-     *                     accepts LEOrder::CHALLENGE_TYPE_DNS.
395
-     *
396
-     * @return array|bool Returns an array with verification data if successful, false if not pending LetsEncrypt
397
-     *                  Authorization instances were found. The return array always
398
-     *                  contains 'type' and 'identifier'. For LEOrder::CHALLENGE_TYPE_HTTP, the array contains
399
-     *                  'filename' and 'content' for necessary the authorization file.
400
-     *                  For LEOrder::CHALLENGE_TYPE_DNS, the array contains 'DNSDigest', which is the content for the
401
-     *                  necessary DNS TXT entry.
402
-     */
403
-
404
-    public function getPendingAuthorizations($type)
405
-    {
406
-        $authorizations = [];
407
-
408
-        $privateKey = $this->loadAccountKey();
409
-        $details = openssl_pkey_get_details($privateKey);
410
-
411
-        $header = [
412
-            "e" => LEFunctions::base64UrlSafeEncode($details["rsa"]["e"]),
413
-            "kty" => "RSA",
414
-            "n" => LEFunctions::base64UrlSafeEncode($details["rsa"]["n"])
415
-
416
-        ];
417
-        $digest = LEFunctions::base64UrlSafeEncode(hash('sha256', json_encode($header), true));
418
-
419
-        foreach ($this->authorizations as $auth) {
420
-            if ($auth->status == 'pending') {
421
-                $challenge = $auth->getChallenge($type);
422
-                if ($challenge['status'] == 'pending') {
423
-                    $keyAuthorization = $challenge['token'] . '.' . $digest;
424
-                    switch (strtolower($type)) {
425
-                        case LEOrder::CHALLENGE_TYPE_HTTP:
426
-                            $authorizations[] = [
427
-                                'type' => LEOrder::CHALLENGE_TYPE_HTTP,
428
-                                'identifier' => $auth->identifier['value'],
429
-                                'filename' => $challenge['token'],
430
-                                'content' => $keyAuthorization
431
-                            ];
432
-                            break;
433
-                        case LEOrder::CHALLENGE_TYPE_DNS:
434
-                            $DNSDigest = LEFunctions::base64UrlSafeEncode(
435
-                                hash('sha256', $keyAuthorization, true)
436
-                            );
437
-                            $authorizations[] = [
438
-                                'type' => LEOrder::CHALLENGE_TYPE_DNS,
439
-                                'identifier' => $auth->identifier['value'],
440
-                                'DNSDigest' => $DNSDigest
441
-                            ];
442
-                            break;
443
-                    }
444
-                }
445
-            }
446
-        }
447
-
448
-        return count($authorizations) > 0 ? $authorizations : false;
449
-    }
450
-
451
-    /**
452
-     * Sends a verification request for a given $identifier and $type. The function itself checks whether the
453
-     * verification is valid before making the request.
454
-     * Updates the LetsEncrypt Authorization instances after a successful verification.
455
-     *
456
-     * @param string $identifier The domain name to verify.
457
-     * @param int $type The type of verification. Supporting LEOrder::CHALLENGE_TYPE_HTTP and
458
-     *                           LEOrder::CHALLENGE_TYPE_DNS.
459
-     *
460
-     * @return boolean  Returns true when the verification request was successful, false if not.
461
-     */
462
-    public function verifyPendingOrderAuthorization($identifier, $type)
463
-    {
464
-        $privateKey = $this->loadAccountKey();
465
-        $details = openssl_pkey_get_details($privateKey);
466
-
467
-        $header = [
468
-            "e" => LEFunctions::base64UrlSafeEncode($details["rsa"]["e"]),
469
-            "kty" => "RSA",
470
-            "n" => LEFunctions::base64UrlSafeEncode($details["rsa"]["n"])
471
-        ];
472
-        $digest = LEFunctions::base64UrlSafeEncode(hash('sha256', json_encode($header), true));
473
-
474
-        foreach ($this->authorizations as $auth) {
475
-            if ($auth->identifier['value'] == $identifier) {
476
-                if ($auth->status == 'pending') {
477
-                    $challenge = $auth->getChallenge($type);
478
-                    if ($challenge['status'] == 'pending') {
479
-                        $keyAuthorization = $challenge['token'] . '.' . $digest;
480
-                        switch ($type) {
481
-                            case LEOrder::CHALLENGE_TYPE_HTTP:
482
-                                return $this->verifyHTTPChallenge($identifier, $challenge, $keyAuthorization, $auth);
483
-                            case LEOrder::CHALLENGE_TYPE_DNS:
484
-                                return $this->verifyDNSChallenge($identifier, $challenge, $keyAuthorization, $auth);
485
-                        }
486
-                    }
487
-                }
488
-            }
489
-        }
490
-
491
-        //f we reach here, the domain identifier given did not match any authorization object
492
-        //@codeCoverageIgnoreStart
493
-        throw new LogicException("Attempt to verify authorization for identifier $identifier not in order");
494
-        //@codeCoverageIgnoreEnd
495
-    }
496
-
497
-    private function verifyDNSChallenge($identifier, array $challenge, $keyAuthorization, LEAuthorization $auth)
498
-    {
499
-        //check it ourselves
500
-        $DNSDigest = LEFunctions::base64UrlSafeEncode(hash('sha256', $keyAuthorization, true));
501
-        if (!$this->dns->checkChallenge($identifier, $DNSDigest)) {
502
-            $this->log->warning("DNS challenge for $identifier tested, found invalid.");
503
-            return false;
504
-        }
505
-
506
-        //ask LE to check
507
-        $sign = $this->connector->signRequestKid(
508
-            ['keyAuthorization' => $keyAuthorization],
509
-            $this->connector->accountURL,
510
-            $challenge['url']
511
-        );
512
-        $post = $this->connector->post($challenge['url'], $sign);
513
-        if ($post['status'] !== 200) {
514
-            $this->log->warning("DNS challenge for $identifier valid, but failed to post to ACME service");
515
-            return false;
516
-        }
517
-
518
-        while ($auth->status == 'pending') {
519
-            $this->log->notice("DNS challenge for $identifier valid - waiting for confirmation");
520
-            $this->sleep->for(1);
521
-            $auth->updateData();
522
-        }
523
-        $this->log->notice("DNS challenge for $identifier validated");
524
-
525
-        return true;
526
-    }
527
-
528
-    private function verifyHTTPChallenge($identifier, array $challenge, $keyAuthorization, LEAuthorization $auth)
529
-    {
530
-        if (!$this->connector->checkHTTPChallenge($identifier, $challenge['token'], $keyAuthorization)) {
531
-            $this->log->warning("HTTP challenge for $identifier tested, found invalid.");
532
-            return false;
533
-        }
534
-
535
-        $sign = $this->connector->signRequestKid(
536
-            ['keyAuthorization' => $keyAuthorization],
537
-            $this->connector->accountURL,
538
-            $challenge['url']
539
-        );
540
-
541
-        $post = $this->connector->post($challenge['url'], $sign);
542
-        if ($post['status'] !== 200) {
543
-            //@codeCoverageIgnoreStart
544
-            $this->log->warning("HTTP challenge for $identifier valid, but failed to post to ACME service");
545
-            return false;
546
-            //@codeCoverageIgnoreEnd
547
-        }
548
-
549
-        while ($auth->status == 'pending') {
550
-            $this->log->notice("HTTP challenge for $identifier valid - waiting for confirmation");
551
-            $this->sleep->for(1);
552
-            $auth->updateData();
553
-        }
554
-        $this->log->notice("HTTP challenge for $identifier validated");
555
-        return true;
556
-    }
557
-
558
-    /*
19
+	const CHALLENGE_TYPE_HTTP = 'http-01';
20
+	const CHALLENGE_TYPE_DNS = 'dns-01';
21
+
22
+	/** @var string order status (pending, processing, valid) */
23
+	private $status;
24
+
25
+	/** @var string expiration date for order */
26
+	private $expires;
27
+
28
+	/** @var array containing all the domain identifiers for the order */
29
+	private $identifiers;
30
+
31
+	/** @var string[] URLs to all the authorization objects for this order */
32
+	private $authorizationURLs;
33
+
34
+	/** @var LEAuthorization[] array of authorization objects for the order */
35
+	private $authorizations;
36
+
37
+	/** @var string URL for order finalization */
38
+	private $finalizeURL;
39
+
40
+	/** @var string URL for obtaining certificate */
41
+	private $certificateURL;
42
+
43
+	/** @var string base domain name for certificate */
44
+	private $basename;
45
+
46
+	/** @var string URL referencing order */
47
+	private $orderURL;
48
+
49
+	/** @var string type of key (rsa or ec) */
50
+	private $keyType;
51
+
52
+	/** @var int size of key (typically 2048 or 4096 for rsa, 256 or 384 for ec */
53
+	private $keySize;
54
+
55
+	/** @var LEConnector ACME API connection provided to constructor */
56
+	private $connector;
57
+
58
+	/** @var LoggerInterface logger provided to constructor */
59
+	private $log;
60
+
61
+	/** @var DNSValidatorInterface dns resolution provider to constructor*/
62
+	private $dns;
63
+
64
+	/** @var Sleep sleep service provided to constructor */
65
+	private $sleep;
66
+
67
+	/** @var CertificateStorageInterface storage interface provided to constructor */
68
+	private $storage;
69
+
70
+	/**
71
+	 * Initiates the LetsEncrypt Order class. If the base name is found in the $keysDir directory, the order data is
72
+	 * requested. If no order was found locally, if the request is invalid or when there is a change in domain names, a
73
+	 * new order is created.
74
+	 *
75
+	 * @param LEConnector $connector The LetsEncrypt Connector instance to use for HTTP requests.
76
+	 * @param CertificateStorageInterface $storage
77
+	 * @param LoggerInterface $log PSR-3 compatible logger
78
+	 * @param DNSValidatorInterface $dns DNS challenge checking service
79
+	 * @param Sleep $sleep Sleep service for polling
80
+	 */
81
+	public function __construct(
82
+		LEConnector $connector,
83
+		CertificateStorageInterface $storage,
84
+		LoggerInterface $log,
85
+		DNSValidatorInterface $dns,
86
+		Sleep $sleep
87
+	) {
88
+
89
+		$this->connector = $connector;
90
+		$this->log = $log;
91
+		$this->dns = $dns;
92
+		$this->sleep = $sleep;
93
+		$this->storage = $storage;
94
+	}
95
+
96
+	/**
97
+	 * Loads or updates an order. If the base name is found in the $keysDir directory, the order data is
98
+	 * requested. If no order was found locally, if the request is invalid or when there is a change in domain names, a
99
+	 * new order is created.
100
+	 *
101
+	 * @param string $basename The base name for the order. Preferable the top domain (example.org).
102
+	 *                                         Will be the directory in which the keys are stored. Used for the
103
+	 *                                         CommonName in the certificate as well.
104
+	 * @param array $domains The array of strings containing the domain names on the certificate.
105
+	 * @param string $keyType Type of the key we want to use for certificate. Can be provided in
106
+	 *                                         ALGO-SIZE format (ex. rsa-4096 or ec-256) or simply "rsa" and "ec"
107
+	 *                                         (using default sizes)
108
+	 * @param string $notBefore A date string formatted like 0000-00-00T00:00:00Z (yyyy-mm-dd hh:mm:ss)
109
+	 *                                         at which the certificate becomes valid.
110
+	 * @param string $notAfter A date string formatted like 0000-00-00T00:00:00Z (yyyy-mm-dd hh:mm:ss)
111
+	 *                                         until which the certificate is valid.
112
+	 */
113
+	public function loadOrder($basename, array $domains, $keyType, $notBefore, $notAfter)
114
+	{
115
+		$this->basename = $basename;
116
+
117
+		$this->initialiseKeyTypeAndSize($keyType ?? 'rsa-4096');
118
+
119
+		if ($this->loadExistingOrder($domains)) {
120
+			$this->updateAuthorizations();
121
+		} else {
122
+			$this->createOrder($domains, $notBefore, $notAfter);
123
+		}
124
+	}
125
+
126
+	private function loadExistingOrder($domains)
127
+	{
128
+		$orderUrl = $this->storage->getMetadata($this->basename.'.order.url');
129
+		$publicKey = $this->storage->getPublicKey($this->basename);
130
+		$privateKey = $this->storage->getPrivateKey($this->basename);
131
+
132
+		//anything to load?
133
+		if (empty($orderUrl) || empty($publicKey) || empty($privateKey)) {
134
+			$this->log->info("No order found for {$this->basename}. Creating new order.");
135
+			return false;
136
+		}
137
+
138
+		//valid URL?
139
+		$this->orderURL = $orderUrl;
140
+		if (!filter_var($this->orderURL, FILTER_VALIDATE_URL)) {
141
+			//@codeCoverageIgnoreStart
142
+			$this->log->warning("Order for {$this->basename} has invalid URL. Creating new order.");
143
+			$this->deleteOrderFiles();
144
+			return false;
145
+			//@codeCoverageIgnoreEnd
146
+		}
147
+
148
+		//retrieve the order
149
+		$sign = $this->connector->signRequestKid(
150
+			null,
151
+			$this->connector->accountURL,
152
+			$this->orderURL
153
+		);
154
+
155
+		$post = $this->connector->post($this->orderURL, $sign);
156
+		if ($post['status'] !== 200) {
157
+			//@codeCoverageIgnoreStart
158
+			$this->log->warning("Order for {$this->basename} could not be loaded. Creating new order.");
159
+			$this->deleteOrderFiles();
160
+			return false;
161
+			//@codeCoverageIgnoreEnd
162
+		}
163
+
164
+		//ensure the order is still valid
165
+		if ($post['body']['status'] === 'invalid') {
166
+			$this->log->warning("Order for {$this->basename} is 'invalid', unable to authorize. Creating new order.");
167
+			$this->deleteOrderFiles();
168
+			return false;
169
+		}
170
+
171
+		//ensure retrieved order matches our domains
172
+		$orderdomains = array_map(function ($ident) {
173
+			return $ident['value'];
174
+		}, $post['body']['identifiers']);
175
+		$diff = array_merge(array_diff($orderdomains, $domains), array_diff($domains, $orderdomains));
176
+		if (!empty($diff)) {
177
+			$this->log->warning('Domains do not match order data. Deleting and creating new order.');
178
+			$this->deleteOrderFiles();
179
+			return false;
180
+		}
181
+
182
+		//the order is good
183
+		$this->status = $post['body']['status'];
184
+		$this->expires = $post['body']['expires'];
185
+		$this->identifiers = $post['body']['identifiers'];
186
+		$this->authorizationURLs = $post['body']['authorizations'];
187
+		$this->finalizeURL = $post['body']['finalize'];
188
+		if (array_key_exists('certificate', $post['body'])) {
189
+			$this->certificateURL = $post['body']['certificate'];
190
+		}
191
+
192
+		return true;
193
+	}
194
+
195
+	private function deleteOrderFiles()
196
+	{
197
+		$this->storage->setPrivateKey($this->basename, null);
198
+		$this->storage->setPublicKey($this->basename, null);
199
+		$this->storage->setCertificate($this->basename, null);
200
+		$this->storage->setFullChainCertificate($this->basename, null);
201
+		$this->storage->setMetadata($this->basename.'.order.url', null);
202
+	}
203
+
204
+	private function initialiseKeyTypeAndSize($keyType)
205
+	{
206
+		if ($keyType == 'rsa') {
207
+			$this->keyType = 'rsa';
208
+			$this->keySize = 4096;
209
+		} elseif ($keyType == 'ec') {
210
+			$this->keyType = 'ec';
211
+			$this->keySize = 256;
212
+		} else {
213
+			preg_match_all('/^(rsa|ec)\-([0-9]{3,4})$/', $keyType, $keyTypeParts, PREG_SET_ORDER, 0);
214
+
215
+			if (!empty($keyTypeParts)) {
216
+				$this->keyType = $keyTypeParts[0][1];
217
+				$this->keySize = intval($keyTypeParts[0][2]);
218
+			} else {
219
+				throw new LogicException('Key type \'' . $keyType . '\' not supported.');
220
+			}
221
+		}
222
+	}
223
+
224
+	/**
225
+	 * Creates a new LetsEncrypt order and fills this instance with its data. Subsequently creates a new RSA keypair
226
+	 * for the certificate.
227
+	 *
228
+	 * @param array $domains The array of strings containing the domain names on the certificate.
229
+	 * @param string $notBefore A date string formatted like 0000-00-00T00:00:00Z (yyyy-mm-dd hh:mm:ss)
230
+	 *                          at which the certificate becomes valid.
231
+	 * @param string $notAfter A date string formatted like 0000-00-00T00:00:00Z (yyyy-mm-dd hh:mm:ss)
232
+	 *                          until which the certificate is valid.
233
+	 */
234
+	private function createOrder($domains, $notBefore, $notAfter)
235
+	{
236
+		if (!preg_match('~(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z|^$)~', $notBefore) ||
237
+			!preg_match('~(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z|^$)~', $notAfter)
238
+		) {
239
+			throw new LogicException("notBefore and notAfter must be blank or iso-8601 datestamp");
240
+		}
241
+
242
+		$dns = [];
243
+		foreach ($domains as $domain) {
244
+			if (preg_match_all('~(\*\.)~', $domain) > 1) {
245
+				throw new LogicException('Cannot create orders with multiple wildcards in one domain.');
246
+			}
247
+			$dns[] = ['type' => 'dns', 'value' => $domain];
248
+		}
249
+		$payload = ["identifiers" => $dns, 'notBefore' => $notBefore, 'notAfter' => $notAfter];
250
+		$sign = $this->connector->signRequestKid(
251
+			$payload,
252
+			$this->connector->accountURL,
253
+			$this->connector->newOrder
254
+		);
255
+		$post = $this->connector->post($this->connector->newOrder, $sign);
256
+		if ($post['status'] !== 201) {
257
+			//@codeCoverageIgnoreStart
258
+			throw new RuntimeException('Creating new order failed.');
259
+			//@codeCoverageIgnoreEnd
260
+		}
261
+
262
+		if (!preg_match('~Location: (\S+)~i', $post['header'], $matches)) {
263
+			//@codeCoverageIgnoreStart
264
+			throw new RuntimeException('New-order returned invalid response.');
265
+			//@codeCoverageIgnoreEnd
266
+		}
267
+
268
+		$this->orderURL = trim($matches[1]);
269
+		$this->storage->setMetadata($this->basename.'.order.url', $this->orderURL);
270
+
271
+		$this->generateKeys();
272
+
273
+		$this->status = $post['body']['status'];
274
+		$this->expires = $post['body']['expires'];
275
+		$this->identifiers = $post['body']['identifiers'];
276
+		$this->authorizationURLs = $post['body']['authorizations'];
277
+		$this->finalizeURL = $post['body']['finalize'];
278
+		if (array_key_exists('certificate', $post['body'])) {
279
+			$this->certificateURL = $post['body']['certificate'];
280
+		}
281
+		$this->updateAuthorizations();
282
+
283
+		$this->log->info('Created order for ' . $this->basename);
284
+	}
285
+
286
+	private function generateKeys()
287
+	{
288
+		if ($this->keyType == "rsa") {
289
+			$key = LEFunctions::RSAgenerateKeys($this->keySize);
290
+		} else {
291
+			$key = LEFunctions::ECgenerateKeys($this->keySize);
292
+		}
293
+
294
+		$this->storage->setPublicKey($this->basename, $key['public']);
295
+		$this->storage->setPrivateKey($this->basename, $key['private']);
296
+	}
297
+
298
+	/**
299
+	 * Fetches the latest data concerning this LetsEncrypt Order instance and fills this instance with the new data.
300
+	 */
301
+	private function updateOrderData()
302
+	{
303
+		$sign = $this->connector->signRequestKid(
304
+			null,
305
+			$this->connector->accountURL,
306
+			$this->orderURL
307
+		);
308
+
309
+		$post = $this->connector->post($this->orderURL, $sign);
310
+		if (strpos($post['header'], "200 OK") !== false) {
311
+			$this->status = $post['body']['status'];
312
+			$this->expires = $post['body']['expires'];
313
+			$this->identifiers = $post['body']['identifiers'];
314
+			$this->authorizationURLs = $post['body']['authorizations'];
315
+			$this->finalizeURL = $post['body']['finalize'];
316
+			if (array_key_exists('certificate', $post['body'])) {
317
+				$this->certificateURL = $post['body']['certificate'];
318
+			}
319
+			$this->updateAuthorizations();
320
+		} else {
321
+			//@codeCoverageIgnoreStart
322
+			$this->log->error("Failed to fetch order for {$this->basename}");
323
+			//@codeCoverageIgnoreEnd
324
+		}
325
+	}
326
+
327
+	/**
328
+	 * Fetches the latest data concerning all authorizations connected to this LetsEncrypt Order instance and
329
+	 * creates and stores a new LetsEncrypt Authorization instance for each one.
330
+	 */
331
+	private function updateAuthorizations()
332
+	{
333
+		$this->authorizations = [];
334
+		foreach ($this->authorizationURLs as $authURL) {
335
+			if (filter_var($authURL, FILTER_VALIDATE_URL)) {
336
+				$auth = new LEAuthorization($this->connector, $this->log, $authURL);
337
+				if ($auth != false) {
338
+					$this->authorizations[] = $auth;
339
+				}
340
+			}
341
+		}
342
+	}
343
+
344
+	/**
345
+	 * Walks all LetsEncrypt Authorization instances and returns whether they are all valid (verified).
346
+	 *
347
+	 * @return boolean  Returns true if all authorizations are valid (verified), returns false if not.
348
+	 */
349
+	public function allAuthorizationsValid()
350
+	{
351
+		if (count($this->authorizations) > 0) {
352
+			foreach ($this->authorizations as $auth) {
353
+				if ($auth->status != 'valid') {
354
+					return false;
355
+				}
356
+			}
357
+			return true;
358
+		}
359
+		return false;
360
+	}
361
+
362
+	private function loadAccountKey()
363
+	{
364
+		$keydata = $this->storage->getAccountPrivateKey();
365
+		$privateKey = openssl_pkey_get_private($keydata);
366
+		if ($privateKey === false) {
367
+			//@codeCoverageIgnoreStart
368
+			throw new RuntimeException("Failed load account key");
369
+			//@codeCoverageIgnoreEnd
370
+		}
371
+		return $privateKey;
372
+	}
373
+
374
+
375
+	private function loadCertificateKey()
376
+	{
377
+		$keydata = $this->storage->getPrivateKey($this->basename);
378
+		$privateKey = openssl_pkey_get_private($keydata);
379
+		if ($privateKey === false) {
380
+			//@codeCoverageIgnoreStart
381
+			throw new RuntimeException("Failed load certificate key");
382
+			//@codeCoverageIgnoreEnd
383
+		}
384
+		return $privateKey;
385
+	}
386
+
387
+	/**
388
+	 * Get all pending LetsEncrypt Authorization instances and return the necessary data for verification.
389
+	 * The data in the return object depends on the $type.
390
+	 *
391
+	 * @param string $type The type of verification to get. Supporting http-01 and dns-01.
392
+	 *                     Supporting LEOrder::CHALLENGE_TYPE_HTTP and LEOrder::CHALLENGE_TYPE_DNS. Throws a Runtime
393
+	 *                     Exception when requesting an unknown $type. Keep in mind a wildcard domain authorization only
394
+	 *                     accepts LEOrder::CHALLENGE_TYPE_DNS.
395
+	 *
396
+	 * @return array|bool Returns an array with verification data if successful, false if not pending LetsEncrypt
397
+	 *                  Authorization instances were found. The return array always
398
+	 *                  contains 'type' and 'identifier'. For LEOrder::CHALLENGE_TYPE_HTTP, the array contains
399
+	 *                  'filename' and 'content' for necessary the authorization file.
400
+	 *                  For LEOrder::CHALLENGE_TYPE_DNS, the array contains 'DNSDigest', which is the content for the
401
+	 *                  necessary DNS TXT entry.
402
+	 */
403
+
404
+	public function getPendingAuthorizations($type)
405
+	{
406
+		$authorizations = [];
407
+
408
+		$privateKey = $this->loadAccountKey();
409
+		$details = openssl_pkey_get_details($privateKey);
410
+
411
+		$header = [
412
+			"e" => LEFunctions::base64UrlSafeEncode($details["rsa"]["e"]),
413
+			"kty" => "RSA",
414
+			"n" => LEFunctions::base64UrlSafeEncode($details["rsa"]["n"])
415
+
416
+		];
417
+		$digest = LEFunctions::base64UrlSafeEncode(hash('sha256', json_encode($header), true));
418
+
419
+		foreach ($this->authorizations as $auth) {
420
+			if ($auth->status == 'pending') {
421
+				$challenge = $auth->getChallenge($type);
422
+				if ($challenge['status'] == 'pending') {
423
+					$keyAuthorization = $challenge['token'] . '.' . $digest;
424
+					switch (strtolower($type)) {
425
+						case LEOrder::CHALLENGE_TYPE_HTTP:
426
+							$authorizations[] = [
427
+								'type' => LEOrder::CHALLENGE_TYPE_HTTP,
428
+								'identifier' => $auth->identifier['value'],
429
+								'filename' => $challenge['token'],
430
+								'content' => $keyAuthorization
431
+							];
432
+							break;
433
+						case LEOrder::CHALLENGE_TYPE_DNS:
434
+							$DNSDigest = LEFunctions::base64UrlSafeEncode(
435
+								hash('sha256', $keyAuthorization, true)
436
+							);
437
+							$authorizations[] = [
438
+								'type' => LEOrder::CHALLENGE_TYPE_DNS,
439
+								'identifier' => $auth->identifier['value'],
440
+								'DNSDigest' => $DNSDigest
441
+							];
442
+							break;
443
+					}
444
+				}
445
+			}
446
+		}
447
+
448
+		return count($authorizations) > 0 ? $authorizations : false;
449
+	}
450
+
451
+	/**
452
+	 * Sends a verification request for a given $identifier and $type. The function itself checks whether the
453
+	 * verification is valid before making the request.
454
+	 * Updates the LetsEncrypt Authorization instances after a successful verification.
455
+	 *
456
+	 * @param string $identifier The domain name to verify.
457
+	 * @param int $type The type of verification. Supporting LEOrder::CHALLENGE_TYPE_HTTP and
458
+	 *                           LEOrder::CHALLENGE_TYPE_DNS.
459
+	 *
460
+	 * @return boolean  Returns true when the verification request was successful, false if not.
461
+	 */
462
+	public function verifyPendingOrderAuthorization($identifier, $type)
463
+	{
464
+		$privateKey = $this->loadAccountKey();
465
+		$details = openssl_pkey_get_details($privateKey);
466
+
467
+		$header = [
468
+			"e" => LEFunctions::base64UrlSafeEncode($details["rsa"]["e"]),
469
+			"kty" => "RSA",
470
+			"n" => LEFunctions::base64UrlSafeEncode($details["rsa"]["n"])
471
+		];
472
+		$digest = LEFunctions::base64UrlSafeEncode(hash('sha256', json_encode($header), true));
473
+
474
+		foreach ($this->authorizations as $auth) {
475
+			if ($auth->identifier['value'] == $identifier) {
476
+				if ($auth->status == 'pending') {
477
+					$challenge = $auth->getChallenge($type);
478
+					if ($challenge['status'] == 'pending') {
479
+						$keyAuthorization = $challenge['token'] . '.' . $digest;
480
+						switch ($type) {
481
+							case LEOrder::CHALLENGE_TYPE_HTTP:
482
+								return $this->verifyHTTPChallenge($identifier, $challenge, $keyAuthorization, $auth);
483
+							case LEOrder::CHALLENGE_TYPE_DNS:
484
+								return $this->verifyDNSChallenge($identifier, $challenge, $keyAuthorization, $auth);
485
+						}
486
+					}
487
+				}
488
+			}
489
+		}
490
+
491
+		//f we reach here, the domain identifier given did not match any authorization object
492
+		//@codeCoverageIgnoreStart
493
+		throw new LogicException("Attempt to verify authorization for identifier $identifier not in order");
494
+		//@codeCoverageIgnoreEnd
495
+	}
496
+
497
+	private function verifyDNSChallenge($identifier, array $challenge, $keyAuthorization, LEAuthorization $auth)
498
+	{
499
+		//check it ourselves
500
+		$DNSDigest = LEFunctions::base64UrlSafeEncode(hash('sha256', $keyAuthorization, true));
501
+		if (!$this->dns->checkChallenge($identifier, $DNSDigest)) {
502
+			$this->log->warning("DNS challenge for $identifier tested, found invalid.");
503
+			return false;
504
+		}
505
+
506
+		//ask LE to check
507
+		$sign = $this->connector->signRequestKid(
508
+			['keyAuthorization' => $keyAuthorization],
509
+			$this->connector->accountURL,
510
+			$challenge['url']
511
+		);
512
+		$post = $this->connector->post($challenge['url'], $sign);
513
+		if ($post['status'] !== 200) {
514
+			$this->log->warning("DNS challenge for $identifier valid, but failed to post to ACME service");
515
+			return false;
516
+		}
517
+
518
+		while ($auth->status == 'pending') {
519
+			$this->log->notice("DNS challenge for $identifier valid - waiting for confirmation");
520
+			$this->sleep->for(1);
521
+			$auth->updateData();
522
+		}
523
+		$this->log->notice("DNS challenge for $identifier validated");
524
+
525
+		return true;
526
+	}
527
+
528
+	private function verifyHTTPChallenge($identifier, array $challenge, $keyAuthorization, LEAuthorization $auth)
529
+	{
530
+		if (!$this->connector->checkHTTPChallenge($identifier, $challenge['token'], $keyAuthorization)) {
531
+			$this->log->warning("HTTP challenge for $identifier tested, found invalid.");
532
+			return false;
533
+		}
534
+
535
+		$sign = $this->connector->signRequestKid(
536
+			['keyAuthorization' => $keyAuthorization],
537
+			$this->connector->accountURL,
538
+			$challenge['url']
539
+		);
540
+
541
+		$post = $this->connector->post($challenge['url'], $sign);
542
+		if ($post['status'] !== 200) {
543
+			//@codeCoverageIgnoreStart
544
+			$this->log->warning("HTTP challenge for $identifier valid, but failed to post to ACME service");
545
+			return false;
546
+			//@codeCoverageIgnoreEnd
547
+		}
548
+
549
+		while ($auth->status == 'pending') {
550
+			$this->log->notice("HTTP challenge for $identifier valid - waiting for confirmation");
551
+			$this->sleep->for(1);
552
+			$auth->updateData();
553
+		}
554
+		$this->log->notice("HTTP challenge for $identifier validated");
555
+		return true;
556
+	}
557
+
558
+	/*
559 559
      * Deactivate an LetsEncrypt Authorization instance.
560 560
      *
561 561
      * @param string $identifier The domain name for which the verification should be deactivated.
562 562
      *
563 563
      * @return boolean  Returns true is the deactivation request was successful, false if not.
564 564
      */
565
-    /*
565
+	/*
566 566
     public function deactivateOrderAuthorization($identifier)
567 567
     {
568 568
         foreach ($this->authorizations as $auth) {
@@ -587,37 +587,37 @@  discard block
 block discarded – undo
587 587
     }
588 588
     */
589 589
 
590
-    /**
591
-     * Generates a Certificate Signing Request for the identifiers in the current LetsEncrypt Order instance.
592
-     * If possible, the base name will be the certificate common name and all domain names in this LetsEncrypt Order
593
-     * instance will be added to the Subject Alternative Names entry.
594
-     *
595
-     * @return string   Returns the generated CSR as string, unprepared for LetsEncrypt. Preparation for the request
596
-     *                  happens in finalizeOrder()
597
-     */
598
-    private function generateCSR()
599
-    {
600
-        $domains = array_map(function ($dns) {
601
-            return $dns['value'];
602
-        }, $this->identifiers);
603
-
604
-        $dn = ["commonName" => $this->calcCommonName($domains)];
605
-
606
-        $san = implode(",", array_map(function ($dns) {
607
-            return "DNS:" . $dns;
608
-        }, $domains));
609
-        $tmpConf = tmpfile();
610
-        if ($tmpConf === false) {
611
-            //@codeCoverageIgnoreStart
612
-            throw new RuntimeException('LEOrder::generateCSR failed to create tmp file');
613
-            //@codeCoverageIgnoreEnd
614
-        }
615
-        $tmpConfMeta = stream_get_meta_data($tmpConf);
616
-        $tmpConfPath = $tmpConfMeta["uri"];
617
-
618
-        fwrite(
619
-            $tmpConf,
620
-            'HOME = .
590
+	/**
591
+	 * Generates a Certificate Signing Request for the identifiers in the current LetsEncrypt Order instance.
592
+	 * If possible, the base name will be the certificate common name and all domain names in this LetsEncrypt Order
593
+	 * instance will be added to the Subject Alternative Names entry.
594
+	 *
595
+	 * @return string   Returns the generated CSR as string, unprepared for LetsEncrypt. Preparation for the request
596
+	 *                  happens in finalizeOrder()
597
+	 */
598
+	private function generateCSR()
599
+	{
600
+		$domains = array_map(function ($dns) {
601
+			return $dns['value'];
602
+		}, $this->identifiers);
603
+
604
+		$dn = ["commonName" => $this->calcCommonName($domains)];
605
+
606
+		$san = implode(",", array_map(function ($dns) {
607
+			return "DNS:" . $dns;
608
+		}, $domains));
609
+		$tmpConf = tmpfile();
610
+		if ($tmpConf === false) {
611
+			//@codeCoverageIgnoreStart
612
+			throw new RuntimeException('LEOrder::generateCSR failed to create tmp file');
613
+			//@codeCoverageIgnoreEnd
614
+		}
615
+		$tmpConfMeta = stream_get_meta_data($tmpConf);
616
+		$tmpConfPath = $tmpConfMeta["uri"];
617
+
618
+		fwrite(
619
+			$tmpConf,
620
+			'HOME = .
621 621
 			RANDFILE = $ENV::HOME/.rnd
622 622
 			[ req ]
623 623
 			default_bits = 4096
@@ -630,198 +630,198 @@  discard block
 block discarded – undo
630 630
 			basicConstraints = CA:FALSE
631 631
 			subjectAltName = ' . $san . '
632 632
 			keyUsage = nonRepudiation, digitalSignature, keyEncipherment'
633
-        );
634
-
635
-        $privateKey = $this->loadCertificateKey();
636
-        $csr = openssl_csr_new($dn, $privateKey, ['config' => $tmpConfPath, 'digest_alg' => 'sha256']);
637
-        openssl_csr_export($csr, $csr);
638
-        return $csr;
639
-    }
640
-
641
-    private function calcCommonName($domains)
642
-    {
643
-        if (in_array($this->basename, $domains)) {
644
-            $CN = $this->basename;
645
-        } elseif (in_array('*.' . $this->basename, $domains)) {
646
-            $CN = '*.' . $this->basename;
647
-        } else {
648
-            $CN = $domains[0];
649
-        }
650
-        return $CN;
651
-    }
652
-
653
-    /**
654
-     * Checks, for redundancy, whether all authorizations are valid, and finalizes the order. Updates this LetsEncrypt
655
-     * Order instance with the new data.
656
-     *
657
-     * @param string $csr The Certificate Signing Request as a string. Can be a custom CSR. If empty, a CSR will
658
-     *                    be generated with the generateCSR() function.
659
-     *
660
-     * @return boolean  Returns true if the finalize request was successful, false if not.
661
-     */
662
-    public function finalizeOrder($csr = '')
663
-    {
664
-        if ($this->status == 'pending') {
665
-            if ($this->allAuthorizationsValid()) {
666
-                if (empty($csr)) {
667
-                    $csr = $this->generateCSR();
668
-                }
669
-                if (preg_match(
670
-                    '~-----BEGIN\sCERTIFICATE\sREQUEST-----(.*)-----END\sCERTIFICATE\sREQUEST-----~s',
671
-                    $csr,
672
-                    $matches
673
-                )
674
-                ) {
675
-                    $csr = $matches[1];
676
-                }
677
-                $csr = trim(LEFunctions::base64UrlSafeEncode(base64_decode($csr)));
678
-                $sign = $this->connector->signRequestKid(
679
-                    ['csr' => $csr],
680
-                    $this->connector->accountURL,
681
-                    $this->finalizeURL
682
-                );
683
-                $post = $this->connector->post($this->finalizeURL, $sign);
684
-                if (strpos($post['header'], "200 OK") !== false) {
685
-                    $this->status = $post['body']['status'];
686
-                    $this->expires = $post['body']['expires'];
687
-                    $this->identifiers = $post['body']['identifiers'];
688
-                    $this->authorizationURLs = $post['body']['authorizations'];
689
-                    $this->finalizeURL = $post['body']['finalize'];
690
-                    if (array_key_exists('certificate', $post['body'])) {
691
-                        $this->certificateURL = $post['body']['certificate'];
692
-                    }
693
-                    $this->updateAuthorizations();
694
-                    $this->log->info('Order for \'' . $this->basename . '\' finalized.');
695
-
696
-                    return true;
697
-                }
698
-            } else {
699
-                $this->log->warning(
700
-                    'Not all authorizations are valid for \'' .
701
-                    $this->basename . '\'. Cannot finalize order.'
702
-                );
703
-            }
704
-        } else {
705
-            $this->log->warning(
706
-                'Order status for \'' . $this->basename .
707
-                '\' is \'' . $this->status . '\'. Cannot finalize order.'
708
-            );
709
-        }
710
-        return false;
711
-    }
712
-
713
-    /**
714
-     * Gets whether the LetsEncrypt Order is finalized by checking whether the status is processing or valid. Keep in
715
-     * mind, a certificate is not yet available when the status still is processing.
716
-     *
717
-     * @return boolean  Returns true if finalized, false if not.
718
-     */
719
-    public function isFinalized()
720
-    {
721
-        return ($this->status == 'processing' || $this->status == 'valid');
722
-    }
723
-
724
-    /**
725
-     * Requests the certificate for this LetsEncrypt Order instance, after finalization. When the order status is still
726
-     * 'processing', the order will be polled max four times with five seconds in between. If the status becomes 'valid'
727
-     * in the meantime, the certificate will be requested. Else, the function returns false.
728
-     *
729
-     * @return boolean  Returns true if the certificate is stored successfully, false if the certificate could not be
730
-     *                  retrieved or the status remained 'processing'.
731
-     */
732
-    public function getCertificate()
733
-    {
734
-        $polling = 0;
735
-        while ($this->status == 'processing' && $polling < 4) {
736
-            $this->log->info('Certificate for ' . $this->basename . ' being processed. Retrying in 5 seconds...');
737
-
738
-            $this->sleep->for(5);
739
-            $this->updateOrderData();
740
-            $polling++;
741
-        }
742
-
743
-        if ($this->status != 'valid' || empty($this->certificateURL)) {
744
-            $this->log->warning(
745
-                'Order for ' . $this->basename . ' not valid. Cannot retrieve certificate.'
746
-            );
747
-            return false;
748
-        }
749
-
750
-        $sign = $this->connector->signRequestKid(
751
-            null,
752
-            $this->connector->accountURL,
753
-            $this->certificateURL
754
-        );
755
-
756
-        $post = $this->connector->post($this->certificateURL, $sign);
757
-        if (strpos($post['header'], "200 OK") === false) {
758
-            $this->log->warning(
759
-                'Invalid response for certificate request for \'' . $this->basename .
760
-                '\'. Cannot save certificate.'
761
-            );
762
-            return false;
763
-        }
764
-
765
-        return $this->writeCertificates($post['body']);
766
-    }
767
-
768
-    private function writeCertificates($body)
769
-    {
770
-        if (preg_match_all('~(-----BEGIN\sCERTIFICATE-----[\s\S]+?-----END\sCERTIFICATE-----)~i', $body, $matches)) {
771
-            $this->storage->setCertificate($this->basename, $matches[0][0]);
772
-
773
-            $matchCount = count($matches[0]);
774
-            if ($matchCount > 1) {
775
-                $fullchain = $matches[0][0] . "\n";
776
-
777
-                for ($i = 1; $i < $matchCount; $i++) {
778
-                    $fullchain .= $matches[0][$i] . "\n";
779
-                }
780
-                $this->storage->setFullChainCertificate($this->basename, $fullchain);
781
-            }
782
-            $this->log->info("Certificate for {$this->basename} stored");
783
-            return true;
784
-        }
785
-
786
-        $this->log->error("Received invalid certificate for {$this->basename}, cannot save");
787
-        return false;
788
-    }
789
-
790
-    /**
791
-     * Revokes the certificate in the current LetsEncrypt Order instance, if existent. Unlike stated in the ACME draft,
792
-     * the certificate revoke request cannot be signed with the account private key, and will be signed with the
793
-     * certificate private key.
794
-     *
795
-     * @param int $reason The reason to revoke the LetsEncrypt Order instance certificate. Possible reasons can be
796
-     *                        found in section 5.3.1 of RFC5280.
797
-     *
798
-     * @return boolean  Returns true if the certificate was successfully revoked, false if not.
799
-     */
800
-    public function revokeCertificate($reason = 0)
801
-    {
802
-        if ($this->status != 'valid') {
803
-            $this->log->warning("Order for {$this->basename} not valid, cannot revoke");
804
-            return false;
805
-        }
806
-
807
-        $certificate = $this->storage->getCertificate($this->basename);
808
-        if (empty($certificate)) {
809
-            $this->log->warning("Certificate for {$this->basename} not found, cannot revoke");
810
-            return false;
811
-        }
812
-
813
-        preg_match('~-----BEGIN\sCERTIFICATE-----(.*)-----END\sCERTIFICATE-----~s', $certificate, $matches);
814
-        $certificate = trim(LEFunctions::base64UrlSafeEncode(base64_decode(trim($matches[1]))));
815
-
816
-        $certificateKey = $this->storage->getPrivateKey($this->basename);
817
-        $sign = $this->connector->signRequestJWK(
818
-            ['certificate' => $certificate, 'reason' => $reason],
819
-            $this->connector->revokeCert,
820
-            $certificateKey
821
-        );
822
-        //4**/5** responses will throw an exception...
823
-        $this->connector->post($this->connector->revokeCert, $sign);
824
-        $this->log->info("Certificate for {$this->basename} successfully revoked");
825
-        return true;
826
-    }
633
+		);
634
+
635
+		$privateKey = $this->loadCertificateKey();
636
+		$csr = openssl_csr_new($dn, $privateKey, ['config' => $tmpConfPath, 'digest_alg' => 'sha256']);
637
+		openssl_csr_export($csr, $csr);
638
+		return $csr;
639
+	}
640
+
641
+	private function calcCommonName($domains)
642
+	{
643
+		if (in_array($this->basename, $domains)) {
644
+			$CN = $this->basename;
645
+		} elseif (in_array('*.' . $this->basename, $domains)) {
646
+			$CN = '*.' . $this->basename;
647
+		} else {
648
+			$CN = $domains[0];
649
+		}
650
+		return $CN;
651
+	}
652
+
653
+	/**
654
+	 * Checks, for redundancy, whether all authorizations are valid, and finalizes the order. Updates this LetsEncrypt
655
+	 * Order instance with the new data.
656
+	 *
657
+	 * @param string $csr The Certificate Signing Request as a string. Can be a custom CSR. If empty, a CSR will
658
+	 *                    be generated with the generateCSR() function.
659
+	 *
660
+	 * @return boolean  Returns true if the finalize request was successful, false if not.
661
+	 */
662
+	public function finalizeOrder($csr = '')
663
+	{
664
+		if ($this->status == 'pending') {
665
+			if ($this->allAuthorizationsValid()) {
666
+				if (empty($csr)) {
667
+					$csr = $this->generateCSR();
668
+				}
669
+				if (preg_match(
670
+					'~-----BEGIN\sCERTIFICATE\sREQUEST-----(.*)-----END\sCERTIFICATE\sREQUEST-----~s',
671
+					$csr,
672
+					$matches
673
+				)
674
+				) {
675
+					$csr = $matches[1];
676
+				}
677
+				$csr = trim(LEFunctions::base64UrlSafeEncode(base64_decode($csr)));
678
+				$sign = $this->connector->signRequestKid(
679
+					['csr' => $csr],
680
+					$this->connector->accountURL,
681
+					$this->finalizeURL
682
+				);
683
+				$post = $this->connector->post($this->finalizeURL, $sign);
684
+				if (strpos($post['header'], "200 OK") !== false) {
685
+					$this->status = $post['body']['status'];
686
+					$this->expires = $post['body']['expires'];
687
+					$this->identifiers = $post['body']['identifiers'];
688
+					$this->authorizationURLs = $post['body']['authorizations'];
689
+					$this->finalizeURL = $post['body']['finalize'];
690
+					if (array_key_exists('certificate', $post['body'])) {
691
+						$this->certificateURL = $post['body']['certificate'];
692
+					}
693
+					$this->updateAuthorizations();
694
+					$this->log->info('Order for \'' . $this->basename . '\' finalized.');
695
+
696
+					return true;
697
+				}
698
+			} else {
699
+				$this->log->warning(
700
+					'Not all authorizations are valid for \'' .
701
+					$this->basename . '\'. Cannot finalize order.'
702
+				);
703
+			}
704
+		} else {
705
+			$this->log->warning(
706
+				'Order status for \'' . $this->basename .
707
+				'\' is \'' . $this->status . '\'. Cannot finalize order.'
708
+			);
709
+		}
710
+		return false;
711
+	}
712
+
713
+	/**
714
+	 * Gets whether the LetsEncrypt Order is finalized by checking whether the status is processing or valid. Keep in
715
+	 * mind, a certificate is not yet available when the status still is processing.
716
+	 *
717
+	 * @return boolean  Returns true if finalized, false if not.
718
+	 */
719
+	public function isFinalized()
720
+	{
721
+		return ($this->status == 'processing' || $this->status == 'valid');
722
+	}
723
+
724
+	/**
725
+	 * Requests the certificate for this LetsEncrypt Order instance, after finalization. When the order status is still
726
+	 * 'processing', the order will be polled max four times with five seconds in between. If the status becomes 'valid'
727
+	 * in the meantime, the certificate will be requested. Else, the function returns false.
728
+	 *
729
+	 * @return boolean  Returns true if the certificate is stored successfully, false if the certificate could not be
730
+	 *                  retrieved or the status remained 'processing'.
731
+	 */
732
+	public function getCertificate()
733
+	{
734
+		$polling = 0;
735
+		while ($this->status == 'processing' && $polling < 4) {
736
+			$this->log->info('Certificate for ' . $this->basename . ' being processed. Retrying in 5 seconds...');
737
+
738
+			$this->sleep->for(5);
739
+			$this->updateOrderData();
740
+			$polling++;
741
+		}
742
+
743
+		if ($this->status != 'valid' || empty($this->certificateURL)) {
744
+			$this->log->warning(
745
+				'Order for ' . $this->basename . ' not valid. Cannot retrieve certificate.'
746
+			);
747
+			return false;
748
+		}
749
+
750
+		$sign = $this->connector->signRequestKid(
751
+			null,
752
+			$this->connector->accountURL,
753
+			$this->certificateURL
754
+		);
755
+
756
+		$post = $this->connector->post($this->certificateURL, $sign);
757
+		if (strpos($post['header'], "200 OK") === false) {
758
+			$this->log->warning(
759
+				'Invalid response for certificate request for \'' . $this->basename .
760
+				'\'. Cannot save certificate.'
761
+			);
762
+			return false;
763
+		}
764
+
765
+		return $this->writeCertificates($post['body']);
766
+	}
767
+
768
+	private function writeCertificates($body)
769
+	{
770
+		if (preg_match_all('~(-----BEGIN\sCERTIFICATE-----[\s\S]+?-----END\sCERTIFICATE-----)~i', $body, $matches)) {
771
+			$this->storage->setCertificate($this->basename, $matches[0][0]);
772
+
773
+			$matchCount = count($matches[0]);
774
+			if ($matchCount > 1) {
775
+				$fullchain = $matches[0][0] . "\n";
776
+
777
+				for ($i = 1; $i < $matchCount; $i++) {
778
+					$fullchain .= $matches[0][$i] . "\n";
779
+				}
780
+				$this->storage->setFullChainCertificate($this->basename, $fullchain);
781
+			}
782
+			$this->log->info("Certificate for {$this->basename} stored");
783
+			return true;
784
+		}
785
+
786
+		$this->log->error("Received invalid certificate for {$this->basename}, cannot save");
787
+		return false;
788
+	}
789
+
790
+	/**
791
+	 * Revokes the certificate in the current LetsEncrypt Order instance, if existent. Unlike stated in the ACME draft,
792
+	 * the certificate revoke request cannot be signed with the account private key, and will be signed with the
793
+	 * certificate private key.
794
+	 *
795
+	 * @param int $reason The reason to revoke the LetsEncrypt Order instance certificate. Possible reasons can be
796
+	 *                        found in section 5.3.1 of RFC5280.
797
+	 *
798
+	 * @return boolean  Returns true if the certificate was successfully revoked, false if not.
799
+	 */
800
+	public function revokeCertificate($reason = 0)
801
+	{
802
+		if ($this->status != 'valid') {
803
+			$this->log->warning("Order for {$this->basename} not valid, cannot revoke");
804
+			return false;
805
+		}
806
+
807
+		$certificate = $this->storage->getCertificate($this->basename);
808
+		if (empty($certificate)) {
809
+			$this->log->warning("Certificate for {$this->basename} not found, cannot revoke");
810
+			return false;
811
+		}
812
+
813
+		preg_match('~-----BEGIN\sCERTIFICATE-----(.*)-----END\sCERTIFICATE-----~s', $certificate, $matches);
814
+		$certificate = trim(LEFunctions::base64UrlSafeEncode(base64_decode(trim($matches[1]))));
815
+
816
+		$certificateKey = $this->storage->getPrivateKey($this->basename);
817
+		$sign = $this->connector->signRequestJWK(
818
+			['certificate' => $certificate, 'reason' => $reason],
819
+			$this->connector->revokeCert,
820
+			$certificateKey
821
+		);
822
+		//4**/5** responses will throw an exception...
823
+		$this->connector->post($this->connector->revokeCert, $sign);
824
+		$this->log->info("Certificate for {$this->basename} successfully revoked");
825
+		return true;
826
+	}
827 827
 }
Please login to merge, or discard this patch.