Connector::getSigner()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 1
c 0
b 0
f 0
dl 0
loc 3
rs 10
cc 1
nc 1
nop 0
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
It seems like $response can also be of type null; however, parameter $origin of LetsEncrypt\Http\Response::__construct() does only seem to accept Psr\Http\Message\ResponseInterface, maybe add an additional type check? ( Ignorable by Annotation )

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

174
        return new Response(/** @scrutinizer ignore-type */ $response);
Loading history...
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