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