1 | <?php |
||
2 | |||
3 | declare(strict_types=1); |
||
4 | |||
5 | /* |
||
6 | * This file is part of the LetsEncrypt ACME client. |
||
7 | * |
||
8 | * @author Ivanov Aleksandr <[email protected]> |
||
9 | * @copyright 2019-2020 |
||
10 | * @license https://github.com/misantron/letsencrypt-client/blob/master/LICENSE MIT License |
||
11 | */ |
||
12 | |||
13 | namespace LetsEncrypt\Http; |
||
14 | |||
15 | use GuzzleHttp\Client; |
||
16 | use GuzzleHttp\ClientInterface; |
||
17 | use GuzzleHttp\Exception\ClientException; |
||
18 | use GuzzleHttp\HandlerStack; |
||
19 | use GuzzleHttp\RequestOptions; |
||
20 | use LetsEncrypt\Entity\Endpoint; |
||
21 | use LetsEncrypt\Helper\Signer; |
||
22 | use LetsEncrypt\Helper\SignerInterface; |
||
23 | use LetsEncrypt\Logger\Logger; |
||
24 | use LetsEncrypt\Logger\LogMiddleware; |
||
25 | use Psr\Http\Message\ResponseInterface; |
||
26 | |||
27 | final class Connector |
||
28 | { |
||
29 | private const STAGING_BASE_URL = 'https://acme-staging-v02.api.letsencrypt.org'; |
||
30 | private const PRODUCTION_BASE_URL = 'https://acme-v02.api.letsencrypt.org'; |
||
31 | |||
32 | private const HEADER_NONCE = 'Replay-Nonce'; |
||
33 | |||
34 | private const DIRECTORY_ENDPOINT = 'directory'; |
||
35 | |||
36 | /** |
||
37 | * @var ClientInterface |
||
38 | */ |
||
39 | private $client; |
||
40 | |||
41 | /** |
||
42 | * @var SignerInterface |
||
43 | */ |
||
44 | private $signer; |
||
45 | |||
46 | /** |
||
47 | * @var Endpoint |
||
48 | */ |
||
49 | private $endpoint; |
||
50 | |||
51 | /** |
||
52 | * @var string |
||
53 | */ |
||
54 | private $nonce; |
||
55 | |||
56 | public function __construct( |
||
57 | bool $staging, |
||
58 | Logger $logger = null, |
||
59 | ClientInterface $client = null, |
||
60 | SignerInterface $signer = null |
||
61 | ) { |
||
62 | $this->client = $client ?? $this->createClient($staging, $logger); |
||
63 | $this->signer = $signer ?? Signer::createWithBase64SafeEncoder(); |
||
64 | |||
65 | $this->getEndpoints(); |
||
66 | $this->getNewNonce(); |
||
67 | } |
||
68 | |||
69 | private function createClient(bool $staging, Logger $logger = null): ClientInterface |
||
70 | { |
||
71 | $handlerStack = HandlerStack::create(); |
||
72 | if ($logger instanceof Logger) { |
||
73 | $handlerStack->push(new LogMiddleware($logger)); |
||
74 | } |
||
75 | |||
76 | return new Client([ |
||
77 | 'base_uri' => $staging ? self::STAGING_BASE_URL : self::PRODUCTION_BASE_URL, |
||
78 | 'handler' => $handlerStack, |
||
79 | 'headers' => [ |
||
80 | 'Accept' => 'application/json', |
||
81 | ], |
||
82 | ]); |
||
83 | } |
||
84 | |||
85 | public function getSigner(): SignerInterface |
||
86 | { |
||
87 | return $this->signer; |
||
88 | } |
||
89 | |||
90 | public function getNewAccountUrl(): string |
||
91 | { |
||
92 | return $this->endpoint->getNewAccountUrl(); |
||
93 | } |
||
94 | |||
95 | public function getNewOrderUrl(): string |
||
96 | { |
||
97 | return $this->endpoint->getNewOrderUrl(); |
||
98 | } |
||
99 | |||
100 | public function getRevokeCertificateUrl(): string |
||
101 | { |
||
102 | return $this->endpoint->getRevokeCertificateUrl(); |
||
103 | } |
||
104 | |||
105 | public function getAccountKeyChangeUrl(): string |
||
106 | { |
||
107 | return $this->endpoint->getKeyChangeUrl(); |
||
108 | } |
||
109 | |||
110 | public function signedJWSRequest(string $url, array $payload, string $privateKeyPath): Response |
||
111 | { |
||
112 | $sign = $this->signer->jws($payload, $url, $this->nonce, $privateKeyPath); |
||
113 | |||
114 | return $this->request('POST', $url, $sign); |
||
115 | } |
||
116 | |||
117 | public function signedJWS(string $url, array $payload, string $privateKeyPath): array |
||
118 | { |
||
119 | return $this->signer->jws($payload, $url, $this->nonce, $privateKeyPath); |
||
120 | } |
||
121 | |||
122 | public function signedKIDRequest(string $kid, string $url, array $payload, string $privateKeyPath): Response |
||
123 | { |
||
124 | $sign = $this->signer->kid($payload, $kid, $url, $this->nonce, $privateKeyPath); |
||
125 | |||
126 | return $this->request('POST', $url, $sign); |
||
127 | } |
||
128 | |||
129 | public function get(string $uri): Response |
||
130 | { |
||
131 | return $this->request('GET', $uri); |
||
132 | } |
||
133 | |||
134 | private function getEndpoints(): void |
||
135 | { |
||
136 | $response = $this->request('GET', self::DIRECTORY_ENDPOINT); |
||
137 | |||
138 | $this->endpoint = new Endpoint($response->getDecodedContent()); |
||
139 | } |
||
140 | |||
141 | private function getNewNonce(): void |
||
142 | { |
||
143 | $this->request('HEAD', $this->endpoint->getNewNonceUrl()); |
||
144 | } |
||
145 | |||
146 | private function request(string $method, string $uri, array $data = null): Response |
||
147 | { |
||
148 | $options = [ |
||
149 | RequestOptions::HEADERS => [ |
||
150 | 'Content-Type' => $method === 'POST' ? 'application/jose+json' : 'application/json', |
||
151 | ], |
||
152 | ]; |
||
153 | |||
154 | if ($data !== null) { |
||
155 | $options[RequestOptions::JSON] = $data; |
||
156 | } |
||
157 | |||
158 | try { |
||
159 | $response = $this->client->request($method, $uri, $options); |
||
160 | |||
161 | $this->updateNonce($method, $response); |
||
162 | } catch (ClientException $e) { |
||
163 | $response = $e->getResponse(); |
||
164 | } |
||
165 | |||
166 | // var_dump([ |
||
167 | // 'method' => $method, |
||
168 | // 'uri' => $uri, |
||
169 | // 'status' => $response->getStatusCode(), |
||
170 | // 'headers' => json_encode($response->getHeaders(), JSON_PRETTY_PRINT), |
||
171 | // 'content' => $response->getBody()->getContents(), |
||
172 | // ]); |
||
173 | |||
174 | return new Response($response); |
||
0 ignored issues
–
show
Bug
introduced
by
![]() |
|||
175 | } |
||
176 | |||
177 | private function updateNonce(string $method, ResponseInterface $response): void |
||
178 | { |
||
179 | if ($response->hasHeader(self::HEADER_NONCE)) { |
||
180 | $this->nonce = $response->getHeaderLine(self::HEADER_NONCE); |
||
181 | } elseif ($method === 'POST') { |
||
182 | $this->getNewNonce(); |
||
183 | } |
||
184 | } |
||
185 | } |
||
186 |