Completed
Pull Request — master (#8)
by John
02:04
created

LEOrder::finalizeOrder()   C

Complexity

Conditions 13
Paths 34

Size

Total Lines 47
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 13

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 13
eloc 29
c 3
b 0
f 0
nc 34
nop 1
dl 0
loc 47
rs 6.6166
ccs 9
cts 9
cp 1
crap 13

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace LEClient;
4
5
use LEClient\Exceptions\LEOrderException;
6
7
/**
8
 * LetsEncrypt Order class, containing the functions and data associated with a specific LetsEncrypt order.
9
 *
10
 * PHP version 5.2.0
11
 *
12
 * MIT License
13
 *
14
 * Copyright (c) 2018 Youri van Weegberg
15
 *
16
 * Permission is hereby granted, free of charge, to any person obtaining a copy
17
 * of this software and associated documentation files (the "Software"), to deal
18
 * in the Software without restriction, including without limitation the rights
19
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
20
 * copies of the Software, and to permit persons to whom the Software is
21
 * furnished to do so, subject to the following conditions:
22
 *
23
 * The above copyright notice and this permission notice shall be included in all
24
 * copies or substantial portions of the Software.
25
 *
26
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
27
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
28
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
29
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
30
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
31
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
32
 * SOFTWARE.
33
 *
34
 * @author     Youri van Weegberg <[email protected]>
35
 * @copyright  2018 Youri van Weegberg
36
 * @license    https://opensource.org/licenses/mit-license.php  MIT License
37
 * @link       https://github.com/yourivw/LEClient
38
 * @since      Class available since Release 1.0.0
39
 */
40
class LEOrder
41
{
42
	private $connector;
43
44
	private $basename;
45
	private $certificateKeys;
46
	private $orderURL;
47
	private $keyType;
48
	private $keySize;
49
50
	public $status;
51
	public $expires;
52
	public $identifiers;
53
	private $authorizationURLs;
54
	public $authorizations;
55
	public $finalizeURL;
56
	public $certificateURL;
57
58
	private $log;
59
60
61
	const CHALLENGE_TYPE_HTTP = 'http-01';
62
	const CHALLENGE_TYPE_DNS = 'dns-01';
63
64
    /**
65
     * Initiates the LetsEncrypt Order class. If the base name is found in the $keysDir directory, the order data is requested. If no order was found locally, if the request is invalid or when there is a change in domain names, a new order is created.
66
     *
67
     * @param LEConnector	$connector			The LetsEncrypt Connector instance to use for HTTP requests.
68
     * @param int 			$log 				The level of logging. Defaults to no logging. LOG_OFF, LOG_STATUS, LOG_DEBUG accepted.
69
     * @param array 		$certificateKeys 	Array containing location of certificate keys files.
70
     * @param string 		$basename 			The base name for the order. Preferable the top domain (example.org). Will be the directory in which the keys are stored. Used for the CommonName in the certificate as well.
71
     * @param array 		$domains 			The array of strings containing the domain names on the certificate.
72
     * @param string 		$keyType 			Type of the key we want to use for certificate. Can be provided in ALGO-SIZE format (ex. rsa-4096 or ec-256) or simple "rsa" and "ec" (using default sizes)
73
     * @param string 		$notBefore 			A date string formatted like 0000-00-00T00:00:00Z (yyyy-mm-dd hh:mm:ss) at which the certificate becomes valid.
74
     * @param string 		$notAfter 			A date string formatted like 0000-00-00T00:00:00Z (yyyy-mm-dd hh:mm:ss) until which the certificate is valid.
75
     */
76
	public function __construct($connector, $log, $certificateKeys, $basename, $domains, $keyType = 'rsa-4096', $notBefore, $notAfter)
77
	{
78
		$this->connector = $connector;
79
		$this->basename = $basename;
80
		$this->log = $log;
81
82
		if ($keyType == 'rsa')
83
		{
84
			$this->keyType = 'rsa';
85 72
			$this->keySize = 4096;
86
		}
87
		elseif ($keyType == 'ec')
88
		{
89
			$this->keyType = 'ec';
90
			$this->keySize = 256;
91
		}
92
		else
93
		{
94 72
			preg_match_all('/^(rsa|ec)\-([0-9]{3,4})$/', $keyType, $keyTypeParts, PREG_SET_ORDER, 0);
95 72
96 72
			if (!empty($keyTypeParts))
97 72
			{
98 72
				$this->keyType = $keyTypeParts[0][1];
99 72
				$this->keySize = intval($keyTypeParts[0][2]);
100 72
			}
101
			else throw LEOrderException::InvalidKeyTypeException($keyType);
102
		}
103
		
104
		if(preg_match('~(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z|^$)~', $notBefore) == false OR preg_match('~(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z|^$)~', $notAfter) == false)
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing preg_match('~(\d{4}-\d{2...{2}Z|^$)~', $notBefore) of type integer to the boolean false. If you are specifically checking for 0, consider using something more explicit like === 0 instead.
Loading history...
Bug Best Practice introduced by
It seems like you are loosely comparing preg_match('~(\d{4}-\d{2...d{2}Z|^$)~', $notAfter) of type integer to the boolean false. If you are specifically checking for 0, consider using something more explicit like === 0 instead.
Loading history...
105
		{
106
			throw LEOrderException::InvalidArgumentException('notBefore and notAfter fields must be empty or be a string similar to 0000-00-00T00:00:00Z');
107
		}
108
109
		$this->certificateKeys = $certificateKeys;
110
111
		if(file_exists($this->certificateKeys['private_key']) AND file_exists($this->certificateKeys['order']) AND file_exists($this->certificateKeys['public_key']))
112
		{
113
			$this->orderURL = file_get_contents($this->certificateKeys['order']);
114
			if (filter_var($this->orderURL, FILTER_VALIDATE_URL) !== false)
115
			{
116
				try 
117
				{
118
					$sign = $this->connector->signRequestKid('', $this->connector->accountURL, $this->orderURL);
0 ignored issues
show
Bug introduced by
'' of type string is incompatible with the type array expected by parameter $payload of LEClient\LEConnector::signRequestKid(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

118
					$sign = $this->connector->signRequestKid(/** @scrutinizer ignore-type */ '', $this->connector->accountURL, $this->orderURL);
Loading history...
119 72
					$post = $this->connector->post($this->orderURL, $sign);
0 ignored issues
show
Bug introduced by
$sign of type string is incompatible with the type object expected by parameter $data of LEClient\LEConnector::post(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

119
					$post = $this->connector->post($this->orderURL, /** @scrutinizer ignore-type */ $sign);
Loading history...
120
					if($post['body']['status'] == "invalid")
121 72
					{
122
						throw LEOrderException::InvalidOrderStatusException();
123 72
					}
124
					
125 68
					$orderdomains = array_map(function($ident) { return $ident['value']; }, $post['body']['identifiers']);
126
					$diff = array_merge(array_diff($orderdomains, $domains), array_diff($domains, $orderdomains));
127
					if(!empty($diff))
128 68
					{
129
						foreach ($this->certificateKeys as $file)
130 60
						{
131
							if (is_file($file)) rename($file, $file.'.old');
132 68
						}
133
						if($this->log instanceof \Psr\Log\LoggerInterface) 
0 ignored issues
show
introduced by
$this->log is never a sub-type of Psr\Log\LoggerInterface.
Loading history...
134 68
						{
135 68
							$this->log->info('Domains do not match order data. Renaming current files and creating new order.');
136 68
						}
137
						elseif($this->log >= LEClient::LOG_STATUS) LEFunctions::log('Domains do not match order data. Renaming current files and creating new order.', 'function LEOrder __construct');
0 ignored issues
show
Bug introduced by
'Domains do not match or...nd creating new order.' of type string is incompatible with the type object expected by parameter $data of LEClient\LEFunctions::log(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

137
						elseif($this->log >= LEClient::LOG_STATUS) LEFunctions::log(/** @scrutinizer ignore-type */ 'Domains do not match order data. Renaming current files and creating new order.', 'function LEOrder __construct');
Loading history...
138
						$this->createOrder($domains, $notBefore, $notAfter, $keyType);
0 ignored issues
show
Unused Code introduced by
The call to LEClient\LEOrder::createOrder() has too many arguments starting with $keyType. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

138
						$this->/** @scrutinizer ignore-call */ 
139
             createOrder($domains, $notBefore, $notAfter, $keyType);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
139 68
					}
140 68
					else
141 68
					{
142
						$this->status = $post['body']['status'];
143
						$this->expires = $post['body']['expires'];
144
						$this->identifiers = $post['body']['identifiers'];
145
						$this->authorizationURLs = $post['body']['authorizations'];
146
						$this->finalizeURL = $post['body']['finalize'];
147
						if(array_key_exists('certificate', $post['body'])) $this->certificateURL = $post['body']['certificate'];
0 ignored issues
show
Bug introduced by
It seems like $post['body'] can also be of type string; however, parameter $array of array_key_exists() does only seem to accept ArrayObject|array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

147
						if(array_key_exists('certificate', /** @scrutinizer ignore-type */ $post['body'])) $this->certificateURL = $post['body']['certificate'];
Loading history...
148
						$this->updateAuthorizations();
149
					}
150
				}
151
				catch (\Exception $e)
152
				{
153
					foreach ($this->certificateKeys as $file)
154
					{
155
						if (is_file($file)) unlink($file);
156
					}
157
					if($this->log instanceof \Psr\Log\LoggerInterface) 
0 ignored issues
show
introduced by
$this->log is never a sub-type of Psr\Log\LoggerInterface.
Loading history...
158
					{
159
						$this->log->info('Order data for \'' . $this->basename . '\' invalid. Deleting order data and creating new order.');
160
					}
161
					elseif($this->log >= LEClient::LOG_STATUS) LEFunctions::log('Order data for \'' . $this->basename . '\' invalid. Deleting order data and creating new order.', 'function LEOrder __construct');
162
					$this->createOrder($domains, $notBefore, $notAfter);
163
				}
164
			}
165
			else
166
			{
167
168
				foreach ($this->certificateKeys as $file)
169
				{
170
					if (is_file($file)) unlink($file);
171
				}
172
				if($this->log instanceof \Psr\Log\LoggerInterface) 
0 ignored issues
show
introduced by
$this->log is never a sub-type of Psr\Log\LoggerInterface.
Loading history...
173
				{
174
					$this->log->info('Order data for \'' . $this->basename . '\' invalid. Deleting order data and creating new order.');
175
				}
176
				elseif($this->log >= LEClient::LOG_STATUS) LEFunctions::log('Order data for \'' . $this->basename . '\' invalid. Deleting order data and creating new order.', 'function LEOrder __construct');
177
178
				$this->createOrder($domains, $notBefore, $notAfter);
179
			}
180
		}
181
		else
182
		{
183
			if($this->log instanceof \Psr\Log\LoggerInterface) 
0 ignored issues
show
introduced by
$this->log is never a sub-type of Psr\Log\LoggerInterface.
Loading history...
184
			{
185
				$this->log->info('No order found for \'' . $this->basename . '\'. Creating new order.');
186
			}
187
			elseif($this->log >= LEClient::LOG_STATUS) LEFunctions::log('No order found for \'' . $this->basename . '\'. Creating new order.', 'function LEOrder __construct');
188
			$this->createOrder($domains, $notBefore, $notAfter);
189
		}
190
	}
191
192
    /**
193
     * Creates a new LetsEncrypt order and fills this instance with its data. Subsequently creates a new RSA keypair for the certificate.
194
     *
195
     * @param array		$domains 	The array of strings containing the domain names on the certificate.
196
     * @param string 	$notBefore 	A date string formatted like 0000-00-00T00:00:00Z (yyyy-mm-dd hh:mm:ss) at which the certificate becomes valid.
197
     * @param string 	$notAfter 	A date string formatted like 0000-00-00T00:00:00Z (yyyy-mm-dd hh:mm:ss) until which the certificate is valid.
198
     */
199
	private function createOrder($domains, $notBefore, $notAfter)
200
	{
201
		$dns = array();
202
		foreach($domains as $domain)
203
		{
204
			if(preg_match_all('~(\*\.)~', $domain) > 1) throw LEOrderException::InvalidArgumentException('Cannot create orders with multiple wildcards in one domain.');
205
			$dns[] = array('type' => 'dns', 'value' => $domain);
206
		}
207
		$payload = array("identifiers" => $dns, 'notBefore' => $notBefore, 'notAfter' => $notAfter);
208
		$sign = $this->connector->signRequestKid($payload, $this->connector->accountURL, $this->connector->newOrder);
209
		$post = $this->connector->post($this->connector->newOrder, $sign);
0 ignored issues
show
Bug introduced by
$sign of type string is incompatible with the type object expected by parameter $data of LEClient\LEConnector::post(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

209
		$post = $this->connector->post($this->connector->newOrder, /** @scrutinizer ignore-type */ $sign);
Loading history...
210 72
211
		if($post['status'] === 201)
212 72
		{
213 20
			if(preg_match('~Location: (\S+)~i', $post['header'], $matches))
214 20
			{
215 52
				$this->orderURL = trim($matches[1]);
216 32
				file_put_contents($this->certificateKeys['order'], $this->orderURL);
217 32
				if ($this->keyType == "rsa")
218
				{
219 20
					LEFunctions::RSAgenerateKeys(null, $this->certificateKeys['private_key'], $this->certificateKeys['public_key'], $this->keySize);
220
				}
221 20
				elseif ($this->keyType == "ec")
222 16
				{
223 16
					LEFunctions::ECgenerateKeys(null, $this->certificateKeys['private_key'], $this->certificateKeys['public_key'], $this->keySize);
224
				}
225 4
				else
226
				{
227
					throw LEOrderException::InvalidKeyTypeException($this->keyType);
228 68
				}
229
230
				$this->status = $post['body']['status'];
231
				$this->expires = $post['body']['expires'];
232
				$this->identifiers = $post['body']['identifiers'];
233
				$this->authorizationURLs = $post['body']['authorizations'];
234
				$this->finalizeURL = $post['body']['finalize'];
235
				if(array_key_exists('certificate', $post['body'])) $this->certificateURL = $post['body']['certificate'];
0 ignored issues
show
Bug introduced by
It seems like $post['body'] can also be of type string; however, parameter $array of array_key_exists() does only seem to accept ArrayObject|array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

235
				if(array_key_exists('certificate', /** @scrutinizer ignore-type */ $post['body'])) $this->certificateURL = $post['body']['certificate'];
Loading history...
236
				$this->updateAuthorizations();
237
238
				if($this->log instanceof \Psr\Log\LoggerInterface) 
0 ignored issues
show
introduced by
$this->log is never a sub-type of Psr\Log\LoggerInterface.
Loading history...
239
				{
240 68
					$this->log->info('Created order for \'' . $this->basename . '\'.');
241
				}
242 68
				elseif($this->log >= LEClient::LOG_STATUS) LEFunctions::log('Created order for \'' . $this->basename . '\'.', 'function createOrder (function LEOrder __construct)');
0 ignored issues
show
Bug introduced by
'Created order for '' . $this->basename . ''.' of type string is incompatible with the type object expected by parameter $data of LEClient\LEFunctions::log(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

242
				elseif($this->log >= LEClient::LOG_STATUS) LEFunctions::log(/** @scrutinizer ignore-type */ 'Created order for \'' . $this->basename . '\'.', 'function createOrder (function LEOrder __construct)');
Loading history...
243 68
			}
244
			else
245 4
			{
246
				throw LEOrderException::CreateFailedException('New-order returned invalid response.');
247
			}
248 64
		}
249 64
		else
250 64
		{
251 4
			throw LEOrderException::CreateFailedException('Creating new order failed.');
252
		}
253 60
	}
254
255 60
    /**
256 60
     * Fetches the latest data concerning this LetsEncrypt Order instance and fills this instance with the new data.
257 60
     */
258 60
	private function updateOrderData()
259 60
	{
260
		$sign = $this->connector->signRequestKid('', $this->connector->accountURL, $this->orderURL);
0 ignored issues
show
Bug introduced by
'' of type string is incompatible with the type array expected by parameter $payload of LEClient\LEConnector::signRequestKid(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

260
		$sign = $this->connector->signRequestKid(/** @scrutinizer ignore-type */ '', $this->connector->accountURL, $this->orderURL);
Loading history...
261 60
		$post = $this->connector->post($this->orderURL, $sign);
0 ignored issues
show
Bug introduced by
$sign of type string is incompatible with the type object expected by parameter $data of LEClient\LEConnector::post(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

261
		$post = $this->connector->post($this->orderURL, /** @scrutinizer ignore-type */ $sign);
Loading history...
262 60
		if($post['status'] === 200)
263
		{
264
			$this->status = $post['body']['status'];
265
			$this->expires = $post['body']['expires'];
266
			$this->identifiers = $post['body']['identifiers'];
267
			$this->authorizationURLs = $post['body']['authorizations'];
268 60
			$this->finalizeURL = $post['body']['finalize'];
269
			if(array_key_exists('certificate', $post['body'])) $this->certificateURL = $post['body']['certificate'];
0 ignored issues
show
Bug introduced by
It seems like $post['body'] can also be of type string; however, parameter $array of array_key_exists() does only seem to accept ArrayObject|array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

269
			if(array_key_exists('certificate', /** @scrutinizer ignore-type */ $post['body'])) $this->certificateURL = $post['body']['certificate'];
Loading history...
270
			$this->updateAuthorizations();
271
		}
272
		else
273
		{
274 60
			if($this->log instanceof \Psr\Log\LoggerInterface) 
0 ignored issues
show
introduced by
$this->log is never a sub-type of Psr\Log\LoggerInterface.
Loading history...
275 60
			{
276
				$this->log->info('Cannot update data for order \'' . $this->basename . '\'.');
277 60
			}
278
			elseif($this->log >= LEClient::LOG_STATUS) LEFunctions::log('Cannot update data for order \'' . $this->basename . '\'.', 'function updateOrderData');
0 ignored issues
show
Bug introduced by
'Cannot update data for ... $this->basename . ''.' of type string is incompatible with the type object expected by parameter $data of LEClient\LEFunctions::log(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

278
			elseif($this->log >= LEClient::LOG_STATUS) LEFunctions::log(/** @scrutinizer ignore-type */ 'Cannot update data for order \'' . $this->basename . '\'.', 'function updateOrderData');
Loading history...
279 60
		}
280 60
	}
281 60
282 60
	/**
283 60
     * Fetches the latest data concerning all authorizations connected to this LetsEncrypt Order instance and creates and stores a new LetsEncrypt Authorization instance for each one.
284 60
     */
285 40
	private function updateAuthorizations()
286
	{
287 60
		$this->authorizations = array();
288
		foreach($this->authorizationURLs as $authURL)
289 60
		{
290 60
			if (filter_var($authURL, FILTER_VALIDATE_URL))
291
			{
292 60
				$auth = new LEAuthorization($this->connector, $this->log, $authURL);
293
				if($auth != false) $this->authorizations[] = $auth;
294 60
			}
295 32
		}
296
	}
297 28
298
    /**
299
     * Walks all LetsEncrypt Authorization instances and returns whether they are all valid (verified).
300 60
     *
301 60
     * @return boolean	Returns true if all authorizations are valid (verified), returns false if not.
302 60
     */
303
	public function allAuthorizationsValid()
304
	{
305
		if(count($this->authorizations) > 0)
306
		{
307 28
			foreach($this->authorizations as $auth)
308
			{
309 28
				if($auth->status != 'valid') return false;
310 28
			}
311 28
			return true;
312 28
		}
313
		return false;
314
	}
315 28
316 28
    /**
317 28
     * Get all pending LetsEncrypt Authorization instances and return the necessary data for verification. The data in the return object depends on the $type.
318 28
     *
319 28
     * @param int	$type	The type of verification to get. Supporting http-01 and dns-01. Supporting LEOrder::CHALLENGE_TYPE_HTTP and LEOrder::CHALLENGE_TYPE_DNS. Throws
320 28
	 *						a Runtime Exception when requesting an unknown $type. Keep in mind a wildcard domain authorization only accepts LEOrder::CHALLENGE_TYPE_DNS.
321 28
     *
322 28
     * @return object	Returns an array with verification data if successful, false if not pending LetsEncrypt Authorization instances were found. The return array always
323 28
	 *					contains 'type' and 'identifier'. For LEOrder::CHALLENGE_TYPE_HTTP, the array contains 'filename' and 'content' for necessary the authorization file.
324
	 *					For LEOrder::CHALLENGE_TYPE_DNS, the array contains 'DNSDigest', which is the content for the necessary DNS TXT entry.
325 28
     */
326
327
	public function getPendingAuthorizations($type)
328
	{
329
		$authorizations = array();
330
331 28
		$privateKey = openssl_pkey_get_private(file_get_contents($this->connector->accountKeys['private_key']));
332
		$details = openssl_pkey_get_details($privateKey);
333
334
		$header = array(
335
			"e" => LEFunctions::Base64UrlSafeEncode($details["rsa"]["e"]),
336
			"kty" => "RSA",
337 60
			"n" => LEFunctions::Base64UrlSafeEncode($details["rsa"]["n"])
338
339 60
		);
340 60
		$digest = LEFunctions::Base64UrlSafeEncode(hash('sha256', json_encode($header), true));
341 56
342 56
		foreach($this->authorizations as $auth)
343 56
		{
344 56
			if($auth->status == 'pending')
345
			{
346
				$challenge = $auth->getChallenge($type);
347
				if($challenge['status'] == 'pending')
348 60
				{
349
					$keyAuthorization = $challenge['token'] . '.' . $digest;
350
					switch(strtolower($type))
351
					{
352
						case LEOrder::CHALLENGE_TYPE_HTTP:
353
							$authorizations[] = array('type' => LEOrder::CHALLENGE_TYPE_HTTP, 'identifier' => $auth->identifier['value'], 'filename' => $challenge['token'], 'content' => $keyAuthorization);
354
							break;
355 6
						case LEOrder::CHALLENGE_TYPE_DNS:
356
							$DNSDigest = LEFunctions::Base64UrlSafeEncode(hash('sha256', $keyAuthorization, true));
357 6
							$authorizations[] = array('type' => LEOrder::CHALLENGE_TYPE_DNS, 'identifier' => $auth->identifier['value'], 'DNSDigest' => $DNSDigest);
358 2
							break;
359 2
					}
360 2
				}
361
			}
362
		}
363 2
364
		return count($authorizations) > 0 ? $authorizations : false;
365 4
	}
366
367
    /**
368 6
     * Sends a verification request for a given $identifier and $type. The function itself checks whether the verification is valid before making the request.
369
	 * Updates the LetsEncrypt Authorization instances after a successful verification.
370 6
     *
371 6
     * @param string	$identifier	The domain name to verify.
372 6
     * @param int 		$type 		The type of verification. Supporting LEOrder::CHALLENGE_TYPE_HTTP and LEOrder::CHALLENGE_TYPE_DNS.
373
	 * @param boolean	$localcheck	Whether to verify the authorization locally before making the authorization request to LE. Optional, default to true.
374
     *
375
     * @return boolean	Returns true when the verification request was successful, false if not.
376
     */
377 6
	public function verifyPendingOrderAuthorization($identifier, $type, $localcheck = true)
378
	{
379
		$privateKey = openssl_pkey_get_private(file_get_contents($this->connector->accountKeys['private_key']));
380
		$details = openssl_pkey_get_details($privateKey);
381 2
382
		$header = array(
383 2
			"e" => LEFunctions::Base64UrlSafeEncode($details["rsa"]["e"]),
384 2
			"kty" => "RSA",
385 2
			"n" => LEFunctions::Base64UrlSafeEncode($details["rsa"]["n"])
386
387
		);
388
		$digest = LEFunctions::Base64UrlSafeEncode(hash('sha256', json_encode($header), true));
389
390 2
		foreach($this->authorizations as $auth)
391
		{
392
			if($auth->identifier['value'] == $identifier)
393
			{
394
				if($auth->status == 'pending')
395
				{
396
					$challenge = $auth->getChallenge($type);
397
					if($challenge['status'] == 'pending')
398
					{
399
						$keyAuthorization = $challenge['token'] . '.' . $digest;
400
						switch($type)
401
						{
402
							case LEOrder::CHALLENGE_TYPE_HTTP:
403
								if($localcheck == false OR LEFunctions::checkHTTPChallenge($identifier, $challenge['token'], $keyAuthorization))
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
404
								{
405
									$sign = $this->connector->signRequestKid(array('keyAuthorization' => $keyAuthorization), $this->connector->accountURL, $challenge['url']);
406
									$post = $this->connector->post($challenge['url'], $sign);
0 ignored issues
show
Bug introduced by
$sign of type string is incompatible with the type object expected by parameter $data of LEClient\LEConnector::post(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

406
									$post = $this->connector->post($challenge['url'], /** @scrutinizer ignore-type */ $sign);
Loading history...
407
									if($post['status'] === 200)
408
									{
409
										if($localcheck)
410 6
										{
411
											if($this->log instanceof \Psr\Log\LoggerInterface) 
412 6
											{
413
												$this->log->info('HTTP challenge for \'' . $identifier . '\' valid.');
414 6
											}
415 6
											elseif($this->log >= LEClient::LOG_STATUS) LEFunctions::log('HTTP challenge for \'' . $identifier . '\' valid.', 'function verifyPendingOrderAuthorization');
0 ignored issues
show
Bug introduced by
'HTTP challenge for '' . $identifier . '' valid.' of type string is incompatible with the type object expected by parameter $data of LEClient\LEFunctions::log(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

415
											elseif($this->log >= LEClient::LOG_STATUS) LEFunctions::log(/** @scrutinizer ignore-type */ 'HTTP challenge for \'' . $identifier . '\' valid.', 'function verifyPendingOrderAuthorization');
Loading history...
416
										}
417
										while($auth->status == 'pending')
418 6
										{
419 6
											sleep(1);
420 6
											$auth->updateData();
421
										}
422
										return true;
423 6
									}
424
								}
425 6
								else
426 6
								{
427 6
									if($this->log instanceof \Psr\Log\LoggerInterface) 
428 6
									{
429 6
										$this->log->info('HTTP challenge for \'' . $identifier . '\' tested locally, found invalid.');
430 6
									}
431
									elseif($this->log >= LEClient::LOG_STATUS) LEFunctions::log('HTTP challenge for \'' . $identifier . '\' tested locally, found invalid.', 'function verifyPendingOrderAuthorization');
432 4
								}
433 4
								break;
434 4
							case LEOrder::CHALLENGE_TYPE_DNS:
435 4
								$DNSDigest = LEFunctions::Base64UrlSafeEncode(hash('sha256', $keyAuthorization, true));
436 4
								if($localcheck == false OR LEFunctions::checkDNSChallenge($identifier, $DNSDigest))
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
437
								{
438 4
									$sign = $this->connector->signRequestKid(array('keyAuthorization' => $keyAuthorization), $this->connector->accountURL, $challenge['url']);
439
									$post = $this->connector->post($challenge['url'], $sign);
440 2
									if($post['status'] === 200)
441 2
									{
442
										if($localcheck)
443 2
										{
444 2
											if($this->log instanceof \Psr\Log\LoggerInterface) 
445 2
											{
446 2
												$this->log->info('DNS challenge for \'' . $identifier . '\' valid.');
447
											}
448 2
											elseif($this->log >= LEClient::LOG_STATUS) LEFunctions::log('DNS challenge for \'' . $identifier . '\' valid.', 'function verifyPendingOrderAuthorization');
449
										}
450
										while($auth->status == 'pending')
451
										{
452
											sleep(1);
453
											$auth->updateData();
454 6
										}
455
										return true;
456
									}
457
								}
458
								else
459
								{
460
									if($this->log instanceof \Psr\Log\LoggerInterface) 
461
									{
462
										$this->log->info('DNS challenge for \'' . $identifier . '\' tested locally, found invalid.');
463
									}
464
									elseif($this->log >= LEClient::LOG_STATUS) LEFunctions::log('DNS challenge for \'' . $identifier . '\' tested locally, found invalid.', 'function verifyPendingOrderAuthorization');
465
								}
466
								break;
467
						}
468 2
					}
469
				}
470 2
			}
471 2
		}
472
		return false;
473
	}
474 2
475 2
    /**
476 2
     * Deactivate an LetsEncrypt Authorization instance.
477
     *
478 2
     * @param string	$identifier The domain name for which the verification should be deactivated.
479
     *
480 2
     * @return boolean	Returns true is the deactivation request was successful, false if not.
481 2
     */
482 2
	public function deactivateOrderAuthorization($identifier)
483 2
	{
484 2
		foreach($this->authorizations as $auth)
485 2
		{
486 2
			if($auth->identifier['value'] == $identifier)
487
			{
488
				$sign = $this->connector->signRequestKid(array('status' => 'deactivated'), $this->connector->accountURL, $auth->authorizationURL);
489
				$post = $this->connector->post($auth->authorizationURL, $sign);
0 ignored issues
show
Bug introduced by
$sign of type string is incompatible with the type object expected by parameter $data of LEClient\LEConnector::post(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

489
				$post = $this->connector->post($auth->authorizationURL, /** @scrutinizer ignore-type */ $sign);
Loading history...
490 2
				if($post['status'] === 200)
491
				{
492
					if($this->log instanceof \Psr\Log\LoggerInterface) 
493
					{
494
						$this->log->info('Authorization for \'' . $identifier . '\' deactivated.');
495
					}
496
					elseif($this->log >= LEClient::LOG_STATUS) LEFunctions::log('Authorization for \'' . $identifier . '\' deactivated.', 'function deactivateOrderAuthorization');
0 ignored issues
show
Bug introduced by
'Authorization for '' . ...fier . '' deactivated.' of type string is incompatible with the type object expected by parameter $data of LEClient\LEFunctions::log(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

496
					elseif($this->log >= LEClient::LOG_STATUS) LEFunctions::log(/** @scrutinizer ignore-type */ 'Authorization for \'' . $identifier . '\' deactivated.', 'function deactivateOrderAuthorization');
Loading history...
497
					$this->updateAuthorizations();
498
					return true;
499
				}
500
			}
501
		}
502
		if($this->log instanceof \Psr\Log\LoggerInterface) 
0 ignored issues
show
introduced by
$this->log is never a sub-type of Psr\Log\LoggerInterface.
Loading history...
503 2
		{
504
			$this->log->info('No authorization found for \'' . $identifier . '\', cannot deactivate.');
505
		}
506 2
		elseif($this->log >= LEClient::LOG_STATUS) LEFunctions::log('No authorization found for \'' . $identifier . '\', cannot deactivate.', 'function deactivateOrderAuthorization');
507 2
		return false;
508
	}
509
510
    /**
511
     * Generates a Certificate Signing Request for the identifiers in the current LetsEncrypt Order instance. If possible, the base name will be the certificate
512
	 * common name and all domain names in this LetsEncrypt Order instance will be added to the Subject Alternative Names entry.
513 2
     *
514 2
     * @return string	Returns the generated CSR as string, unprepared for LetsEncrypt. Preparation for the request happens in finalizeOrder()
515 2
     */
516 2
	public function generateCSR()
517
	{
518 2
		$domains = array_map(function ($dns) { return $dns['value']; }, $this->identifiers);
519 2
		if(in_array($this->basename, $domains))
520
		{
521
			$CN = $this->basename;
522
		}
523
		elseif(in_array('*.' . $this->basename, $domains))
524 2
		{
525 2
			$CN = '*.' . $this->basename;
526 2
		}
527 2
		else
528
		{
529 2
			$CN = $domains[0];
530
		}
531 2
532
		$dn = array(
533
			"commonName" => $CN
534
		);
535
536
		$san = implode(",", array_map(function ($dns) {
537
            return "DNS:" . $dns;
538
        }, $domains));
539
        $tmpConf = tmpfile();
540
        $tmpConfMeta = stream_get_meta_data($tmpConf);
541
        $tmpConfPath = $tmpConfMeta["uri"];
542
543
        fwrite($tmpConf,
544
            'HOME = .
545
			RANDFILE = $ENV::HOME/.rnd
546
			[ req ]
547
			default_bits = ' . $this->keySize . '
548
			default_keyfile = privkey.pem
549
			distinguished_name = req_distinguished_name
550
			req_extensions = v3_req
551
			[ req_distinguished_name ]
552
			countryName = Country Name (2 letter code)
553
			[ v3_req ]
554
			basicConstraints = CA:FALSE
555
			subjectAltName = ' . $san . '
556
			keyUsage = nonRepudiation, digitalSignature, keyEncipherment');
557
558
		$privateKey = openssl_pkey_get_private(file_get_contents($this->certificateKeys['private_key']));
559
		$csr = openssl_csr_new($dn, $privateKey, array('config' => $tmpConfPath, 'digest_alg' => 'sha256'));
0 ignored issues
show
Bug introduced by
It seems like $privateKey can also be of type resource; however, parameter $private_key of openssl_csr_new() does only seem to accept OpenSSLAsymmetricKey, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

559
		$csr = openssl_csr_new($dn, /** @scrutinizer ignore-type */ $privateKey, array('config' => $tmpConfPath, 'digest_alg' => 'sha256'));
Loading history...
560
		openssl_csr_export ($csr, $csr);
0 ignored issues
show
Bug introduced by
$csr of type OpenSSLCertificateSigningRequest|resource is incompatible with the type string expected by parameter $output of openssl_csr_export(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

560
		openssl_csr_export ($csr, /** @scrutinizer ignore-type */ $csr);
Loading history...
561
		return $csr;
562
	}
563
564
    /**
565
     * Checks, for redundancy, whether all authorizations are valid, and finalizes the order. Updates this LetsEncrypt Order instance with the new data.
566
     *
567
     * @param string	$csr	The Certificate Signing Request as a string. Can be a custom CSR. If empty, a CSR will be generated with the generateCSR() function.
568
     *
569
     * @return boolean	Returns true if the finalize request was successful, false if not.
570
     */
571
	public function finalizeOrder($csr = '')
572
	{
573
		$this->updateOrderData();
574
		if($this->status == 'ready')
575
		{
576
			if($this->allAuthorizationsValid())
577
			{
578
				if(empty($csr)) $csr = $this->generateCSR();
579
				if(preg_match('~-----BEGIN\sCERTIFICATE\sREQUEST-----(.*)-----END\sCERTIFICATE\sREQUEST-----~s', $csr, $matches)) $csr = $matches[1];
580
				$csr = trim(LEFunctions::Base64UrlSafeEncode(base64_decode($csr)));
581
				$sign = $this->connector->signRequestKid(array('csr' => $csr), $this->connector->accountURL, $this->finalizeURL);
582
				$post = $this->connector->post($this->finalizeURL, $sign);
0 ignored issues
show
Bug introduced by
$sign of type string is incompatible with the type object expected by parameter $data of LEClient\LEConnector::post(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

582
				$post = $this->connector->post($this->finalizeURL, /** @scrutinizer ignore-type */ $sign);
Loading history...
583
				if($post['status'] === 200)
584
				{
585
					$this->status = $post['body']['status'];
586
					$this->expires = $post['body']['expires'];
587
					$this->identifiers = $post['body']['identifiers'];
588
					$this->authorizationURLs = $post['body']['authorizations'];
589
					$this->finalizeURL = $post['body']['finalize'];
590
					if(array_key_exists('certificate', $post['body'])) $this->certificateURL = $post['body']['certificate'];
0 ignored issues
show
Bug introduced by
It seems like $post['body'] can also be of type string; however, parameter $array of array_key_exists() does only seem to accept ArrayObject|array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

590
					if(array_key_exists('certificate', /** @scrutinizer ignore-type */ $post['body'])) $this->certificateURL = $post['body']['certificate'];
Loading history...
591
					$this->updateAuthorizations();
592
					if($this->log instanceof \Psr\Log\LoggerInterface) 
0 ignored issues
show
introduced by
$this->log is never a sub-type of Psr\Log\LoggerInterface.
Loading history...
593
					{
594
						$this->log->info('Order for \'' . $this->basename . '\' finalized.');
595
					}
596
					elseif($this->log >= LEClient::LOG_STATUS) LEFunctions::log('Order for \'' . $this->basename . '\' finalized.', 'function finalizeOrder');
0 ignored issues
show
Bug introduced by
'Order for '' . $this->basename . '' finalized.' of type string is incompatible with the type object expected by parameter $data of LEClient\LEFunctions::log(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

596
					elseif($this->log >= LEClient::LOG_STATUS) LEFunctions::log(/** @scrutinizer ignore-type */ 'Order for \'' . $this->basename . '\' finalized.', 'function finalizeOrder');
Loading history...
597
					return true;
598
				}
599
			}
600
			else
601
			{
602
				if($this->log instanceof \Psr\Log\LoggerInterface) 
0 ignored issues
show
introduced by
$this->log is never a sub-type of Psr\Log\LoggerInterface.
Loading history...
603
				{
604
					$this->log->info('Not all authorizations are valid for \'' . $this->basename . '\'. Cannot finalize order.');
605
				}
606 2
				elseif($this->log >= LEClient::LOG_STATUS) LEFunctions::log('Not all authorizations are valid for \'' . $this->basename . '\'. Cannot finalize order.', 'function finalizeOrder');
607 2
			}
608 2
		}
609
		else
610 2
		{
611
			if($this->log instanceof \Psr\Log\LoggerInterface) 
0 ignored issues
show
introduced by
$this->log is never a sub-type of Psr\Log\LoggerInterface.
Loading history...
612 2
			{
613 2
				$this->log->info('Order status for \'' . $this->basename . '\' is \'' . $this->status . '\'. Cannot finalize order.');
614 2
			}
615 2
			elseif($this->log >= LEClient::LOG_STATUS) LEFunctions::log('Order status for \'' . $this->basename . '\' is \'' . $this->status . '\'. Cannot finalize order.', 'function finalizeOrder');
616 2
		}
617
		return false;
618
	}
619
620
    /**
621 2
     * Gets whether the LetsEncrypt Order is finalized by checking whether the status is processing or valid. Keep in mind, a certificate is not yet available when the status still is processing.
622 2
     *
623
     * @return boolean	Returns true if finalized, false if not.
624 2
     */
625 2
	public function isFinalized()
626
	{
627
		return ($this->status == 'processing' || $this->status == 'valid');
628
	}
629
630
    /**
631
     * Requests the certificate for this LetsEncrypt Order instance, after finalization. When the order status is still 'processing', the order will be polled max
632
	 * four times with five seconds in between. If the status becomes 'valid' in the meantime, the certificate will be requested. Else, the function returns false.
633
     *
634
     * @return boolean	Returns true if the certificate is stored successfully, false if the certificate could not be retrieved or the status remained 'processing'.
635
     */
636
	public function getCertificate()
637 2
	{
638
		$polling = 0;
639
		while($this->status == 'processing' && $polling < 4)
640
		{
641 2
			if($this->log instanceof \Psr\Log\LoggerInterface) 
642 2
			{
643 2
				$this->log->info('Certificate for \'' . $this->basename . '\' being processed. Retrying in 5 seconds...');
644 2
			}
645
			elseif($this->log >= LEClient::LOG_STATUS) LEFunctions::log('Certificate for \'' . $this->basename . '\' being processed. Retrying in 5 seconds...', 'function getCertificate');
0 ignored issues
show
Bug introduced by
'Certificate for '' . $t...trying in 5 seconds...' of type string is incompatible with the type object expected by parameter $data of LEClient\LEFunctions::log(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

645
			elseif($this->log >= LEClient::LOG_STATUS) LEFunctions::log(/** @scrutinizer ignore-type */ 'Certificate for \'' . $this->basename . '\' being processed. Retrying in 5 seconds...', 'function getCertificate');
Loading history...
646
			sleep(5);
647 2
			$this->updateOrderData();
648
			$polling++;
649 2
		}
650 2
		if($this->status == 'valid')
651
		{
652
			if(!empty($this->certificateURL))
653
			{
654
				$sign = $this->connector->signRequestKid('', $this->connector->accountURL, $this->certificateURL);
0 ignored issues
show
Bug introduced by
'' of type string is incompatible with the type array expected by parameter $payload of LEClient\LEConnector::signRequestKid(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

654
				$sign = $this->connector->signRequestKid(/** @scrutinizer ignore-type */ '', $this->connector->accountURL, $this->certificateURL);
Loading history...
655
				$post = $this->connector->post($this->certificateURL, $sign);
0 ignored issues
show
Bug introduced by
$sign of type string is incompatible with the type object expected by parameter $data of LEClient\LEConnector::post(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

655
				$post = $this->connector->post($this->certificateURL, /** @scrutinizer ignore-type */ $sign);
Loading history...
656 2
				if($post['status'] === 200)
657
				{
658
					if(preg_match_all('~(-----BEGIN\sCERTIFICATE-----[\s\S]+?-----END\sCERTIFICATE-----)~i', $post['body'], $matches))
659
					{
660
						if (isset($this->certificateKeys['certificate'])) file_put_contents($this->certificateKeys['certificate'],  $matches[0][0]);
661
662
						if(count($matches[0]) > 1 && isset($this->certificateKeys['fullchain_certificate']))
663
						{
664
							$fullchain = $matches[0][0]."\n";
665
							for($i=1;$i<count($matches[0]);$i++)
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
666
							{
667
								$fullchain .= $matches[0][$i]."\n";
668 2
							}
669
							file_put_contents(trim($this->certificateKeys['fullchain_certificate']), $fullchain);
670 2
						}
671 2
						if($this->log instanceof \Psr\Log\LoggerInterface) 
0 ignored issues
show
introduced by
$this->log is never a sub-type of Psr\Log\LoggerInterface.
Loading history...
672 2
						{
673 2
							$this->log->info('Certificate for \'' . $this->basename . '\' saved');
674
						}
675 2
						elseif($this->log >= LEClient::LOG_STATUS) LEFunctions::log('Certificate for \'' . $this->basename . '\' saved', 'function getCertificate');
676 2
						return true;
677 1
					}
678 1
					else
679
					{
680
						if($this->log instanceof \Psr\Log\LoggerInterface) 
0 ignored issues
show
introduced by
$this->log is never a sub-type of Psr\Log\LoggerInterface.
Loading history...
681 2
						{
682
							$this->log->info('Received invalid certificate for \'' . $this->basename . '\'. Cannot save certificate.');
683 2
						}
684 2
						elseif($this->log >= LEClient::LOG_STATUS) LEFunctions::log('Received invalid certificate for \'' . $this->basename . '\'. Cannot save certificate.', 'function getCertificate');
685 2
					}
686 2
				}
687 2
				else
688
				{
689 2
					if($this->log instanceof \Psr\Log\LoggerInterface) 
0 ignored issues
show
introduced by
$this->log is never a sub-type of Psr\Log\LoggerInterface.
Loading history...
690 2
					{
691 2
						$this->log->info('Invalid response for certificate request for \'' . $this->basename . '\'. Cannot save certificate.');
692 2
					}
693 2
					elseif($this->log >= LEClient::LOG_STATUS) LEFunctions::log('Invalid response for certificate request for \'' . $this->basename . '\'. Cannot save certificate.', 'function getCertificate');
694 2
				}
695 2
			}
696 2
			else
697 2
			{
698
				if($this->log instanceof \Psr\Log\LoggerInterface) 
0 ignored issues
show
introduced by
$this->log is never a sub-type of Psr\Log\LoggerInterface.
Loading history...
699 2
				{
700 2
					$this->log->info('Order for \'' . $this->basename . '\' not valid. Cannot find certificate URL.');
701
				}
702 2
				elseif($this->log >= LEClient::LOG_STATUS) LEFunctions::log('Order for \'' . $this->basename . '\' not valid. Cannot find certificate URL.', 'function getCertificate');
703
			}
704
		}
705
		else
706
		{
707
			if($this->log instanceof \Psr\Log\LoggerInterface) 
0 ignored issues
show
introduced by
$this->log is never a sub-type of Psr\Log\LoggerInterface.
Loading history...
708
			{
709
				$this->log->info('Order for \'' . $this->basename . '\' not valid. Cannot retrieve certificate.');
710
			}
711
			elseif($this->log >= LEClient::LOG_STATUS) LEFunctions::log('Order for \'' . $this->basename . '\' not valid. Cannot retrieve certificate.', 'function getCertificate');
712
		}
713
		return false;
714
	}
715
716
    /**
717
     * Revokes the certificate in the current LetsEncrypt Order instance, if existent. Unlike stated in the ACME draft, the certificate revoke request cannot be signed
718
	 * with the account private key, and will be signed with the certificate private key.
719
     *
720
     * @param int	$reason   The reason to revoke the LetsEncrypt Order instance certificate. Possible reasons can be found in section 5.3.1 of RFC5280.
721
     *
722
     * @return boolean	Returns true if the certificate was successfully revoked, false if not.
723
     */
724
	public function revokeCertificate($reason = 0)
725 2
	{
726
		if($this->status == 'valid' || $this->status == 'ready')
727 2
		{
728
			if (isset($this->certificateKeys['certificate'])) $certFile = $this->certificateKeys['certificate'];
729
			elseif (isset($this->certificateKeys['fullchain_certificate']))  $certFile = $this->certificateKeys['fullchain_certificate'];
730
			else throw LEOrderException::InvalidConfigurationException('certificateKeys[certificate] or certificateKeys[fullchain_certificate] required');
731
732
			if(file_exists($certFile) && file_exists($this->certificateKeys['private_key']))
733
			{
734
				$certificate = file_get_contents($this->certificateKeys['certificate']);
735
				preg_match('~-----BEGIN\sCERTIFICATE-----(.*)-----END\sCERTIFICATE-----~s', $certificate, $matches);
736
				$certificate = trim(LEFunctions::Base64UrlSafeEncode(base64_decode(trim($matches[1]))));
737
738 30
				$sign = $this->connector->signRequestJWK(array('certificate' => $certificate, 'reason' => $reason), $this->connector->revokeCert, $this->certificateKeys['private_key']);
739
				$post = $this->connector->post($this->connector->revokeCert, $sign);
0 ignored issues
show
Bug introduced by
$sign of type string is incompatible with the type object expected by parameter $data of LEClient\LEConnector::post(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

739
				$post = $this->connector->post($this->connector->revokeCert, /** @scrutinizer ignore-type */ $sign);
Loading history...
740 30
				if($post['status'] === 200)
741 30
				{
742 28
					if($this->log instanceof \Psr\Log\LoggerInterface) 
0 ignored issues
show
introduced by
$this->log is never a sub-type of Psr\Log\LoggerInterface.
Loading history...
743
					{
744 28
						$this->log->info('Certificate for order \'' . $this->basename . '\' revoked.');
745 28
					}
746 28
					elseif($this->log >= LEClient::LOG_STATUS) LEFunctions::log('Certificate for order \'' . $this->basename . '\' revoked.', 'function revokeCertificate');
0 ignored issues
show
Bug introduced by
'Certificate for order '...basename . '' revoked.' of type string is incompatible with the type object expected by parameter $data of LEClient\LEFunctions::log(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

746
					elseif($this->log >= LEClient::LOG_STATUS) LEFunctions::log(/** @scrutinizer ignore-type */ 'Certificate for order \'' . $this->basename . '\' revoked.', 'function revokeCertificate');
Loading history...
747
					return true;
748
				}
749 30
				else
750 4
				{
751 4
					if($this->log instanceof \Psr\Log\LoggerInterface) 
0 ignored issues
show
introduced by
$this->log is never a sub-type of Psr\Log\LoggerInterface.
Loading history...
752
					{
753 4
						$this->log->info('Certificate for order \'' . $this->basename . '\' cannot be revoked.');
754
					}
755
					elseif($this->log >= LEClient::LOG_STATUS) LEFunctions::log('Certificate for order \'' . $this->basename . '\' cannot be revoked.', 'function revokeCertificate');
756 26
				}
757 26
			}
758 26
			else
759 26
			{
760
				if($this->log instanceof \Psr\Log\LoggerInterface) 
0 ignored issues
show
introduced by
$this->log is never a sub-type of Psr\Log\LoggerInterface.
Loading history...
761
				{
762 26
					$this->log->info('Certificate for order \'' . $this->basename . '\' not found. Cannot revoke certificate.');
763 26
				}
764 4
				elseif($this->log >= LEClient::LOG_STATUS) LEFunctions::log('Certificate for order \'' . $this->basename . '\' not found. Cannot revoke certificate.', 'function revokeCertificate');
765 4
			}
766 4
		}
767
		else
768 4
		{
769
			if($this->log instanceof \Psr\Log\LoggerInterface) 
0 ignored issues
show
introduced by
$this->log is never a sub-type of Psr\Log\LoggerInterface.
Loading history...
770
			{
771 22
				$this->log->info('Order for \'' . $this->basename . '\' not valid. Cannot revoke certificate.');
772
			}
773
			elseif($this->log >= LEClient::LOG_STATUS) LEFunctions::log('Order for \'' . $this->basename . '\' not valid. Cannot revoke certificate.', 'function revokeCertificate');
774 22
		}
775
		return false;
776 22
	}
777
}
778