1 | <?php |
||
2 | |||
3 | declare(strict_types=1); |
||
4 | |||
5 | namespace Yiisoft\Yii\AuthClient\Signature; |
||
6 | |||
7 | use Yiisoft\Yii\AuthClient\Exception\InvalidConfigException; |
||
8 | use Yiisoft\Yii\AuthClient\Exception\NotSupportedException; |
||
9 | |||
10 | use function function_exists; |
||
11 | use function is_int; |
||
12 | |||
13 | /** |
||
14 | * RsaSha1 represents 'SHAwithRSA' (also known as RSASSA-PKCS1-V1_5-SIGN with the SHA hash) signature method. |
||
15 | * |
||
16 | * > **Note:** This class requires PHP "OpenSSL" extension({@link https://php.net/manual/en/book.openssl.php}). |
||
17 | */ |
||
18 | final class RsaSha extends Signature |
||
19 | { |
||
20 | /** |
||
21 | * @var string path to the file, which holds private key certificate. |
||
22 | */ |
||
23 | private string $privateCertificateFile; |
||
24 | /** |
||
25 | * @var string path to the file, which holds public key certificate. |
||
26 | */ |
||
27 | private string $publicCertificateFile; |
||
28 | /** |
||
29 | * @var int|string signature hash algorithm, e.g. `OPENSSL_ALGO_SHA1`, `OPENSSL_ALGO_SHA256` and so on. |
||
30 | * |
||
31 | * @link https://php.net/manual/en/openssl.signature-algos.php |
||
32 | */ |
||
33 | private $algorithm; |
||
34 | |||
35 | /** |
||
36 | * @var string|null OpenSSL private key certificate content. |
||
37 | * This value can be fetched from file specified by {@see privateCertificateFile}. |
||
38 | */ |
||
39 | private ?string $privateCertificate = null; |
||
40 | /** |
||
41 | * @var string|null OpenSSL public key certificate content. |
||
42 | * This value can be fetched from file specified by {@see publicCertificateFile}. |
||
43 | */ |
||
44 | private ?string $publicCertificate = null; |
||
45 | |||
46 | 7 | public function __construct($algorithm = null) |
|
47 | { |
||
48 | 7 | $this->algorithm = $algorithm; |
|
49 | |||
50 | 7 | if (!function_exists('openssl_sign')) { |
|
51 | throw new NotSupportedException('PHP "OpenSSL" extension is required.'); |
||
52 | } |
||
53 | } |
||
54 | |||
55 | /** |
||
56 | * @param string $publicCertificateFile public key certificate file. |
||
57 | */ |
||
58 | 3 | public function setPublicCertificateFile(string $publicCertificateFile): void |
|
59 | { |
||
60 | 3 | $this->publicCertificateFile = $publicCertificateFile; |
|
61 | } |
||
62 | |||
63 | /** |
||
64 | * @param string $privateCertificateFile private key certificate file. |
||
65 | */ |
||
66 | 3 | public function setPrivateCertificateFile(string $privateCertificateFile): void |
|
67 | { |
||
68 | 3 | $this->privateCertificateFile = $privateCertificateFile; |
|
69 | } |
||
70 | |||
71 | 3 | public function getName(): string |
|
72 | { |
||
73 | 3 | if (is_int($this->algorithm)) { |
|
74 | 2 | $constants = get_defined_constants(true); |
|
75 | 2 | if (isset($constants['openssl'])) { |
|
76 | 2 | foreach ($constants['openssl'] as $name => $value) { |
|
77 | 2 | if (strpos($name, 'OPENSSL_ALGO_') !== 0) { |
|
78 | 2 | continue; |
|
79 | } |
||
80 | 2 | if ($value === $this->algorithm) { |
|
81 | 2 | $algorithmName = substr($name, strlen('OPENSSL_ALGO_')); |
|
82 | 2 | break; |
|
83 | } |
||
84 | } |
||
85 | } |
||
86 | |||
87 | 2 | if (!isset($algorithmName)) { |
|
88 | 2 | throw new InvalidConfigException("Unable to determine name of algorithm '{$this->algorithm}'"); |
|
89 | } |
||
90 | } else { |
||
91 | 1 | $algorithmName = strtoupper($this->algorithm); |
|
92 | } |
||
93 | 3 | return 'RSA-' . $algorithmName; |
|
94 | } |
||
95 | |||
96 | 2 | public function generateSignature(string $baseString, string $key): string |
|
97 | { |
||
98 | 2 | $privateCertificateContent = $this->getPrivateCertificate(); |
|
99 | // Pull the private key ID from the certificate |
||
100 | 2 | $privateKeyId = openssl_pkey_get_private($privateCertificateContent); |
|
101 | // Sign using the key |
||
102 | 2 | openssl_sign($baseString, $signature, $privateKeyId, $this->algorithm); |
|
103 | // Release the key resource |
||
104 | 2 | if (PHP_MAJOR_VERSION < 8) { |
|
105 | openssl_pkey_free($privateKeyId); |
||
106 | } |
||
107 | |||
108 | 2 | return base64_encode($signature); |
|
109 | } |
||
110 | |||
111 | /** |
||
112 | * @return string private key certificate content. |
||
113 | */ |
||
114 | 3 | public function getPrivateCertificate(): string |
|
115 | { |
||
116 | 3 | if ($this->privateCertificate === null) { |
|
117 | 3 | $this->privateCertificate = $this->initPrivateCertificate(); |
|
118 | } |
||
119 | |||
120 | 3 | return $this->privateCertificate; |
|
0 ignored issues
–
show
Bug
Best Practice
introduced
by
![]() |
|||
121 | } |
||
122 | |||
123 | /** |
||
124 | * Creates initial value for {@see privateCertificate}. |
||
125 | * This method will attempt to fetch the certificate value from {@see privateCertificateFile} file. |
||
126 | * |
||
127 | * @throws InvalidConfigException on failure. |
||
128 | * |
||
129 | * @return string private certificate content. |
||
130 | */ |
||
131 | 3 | protected function initPrivateCertificate(): string |
|
132 | { |
||
133 | 3 | if (!empty($this->privateCertificateFile)) { |
|
134 | 3 | if (!file_exists($this->privateCertificateFile)) { |
|
135 | throw new InvalidConfigException( |
||
136 | "Private certificate file '{$this->privateCertificateFile}' does not exist!" |
||
137 | ); |
||
138 | } |
||
139 | 3 | return file_get_contents($this->privateCertificateFile); |
|
140 | } |
||
141 | return ''; |
||
142 | } |
||
143 | |||
144 | 1 | public function verify(string $signature, string $baseString, string $key): bool |
|
145 | { |
||
146 | 1 | $decodedSignature = base64_decode($signature); |
|
147 | // Fetch the public key cert based on the request |
||
148 | 1 | $publicCertificate = $this->getPublicCertificate(); |
|
149 | // Pull the public key ID from the certificate |
||
150 | 1 | $publicKeyId = openssl_pkey_get_public($publicCertificate); |
|
151 | // Check the computed signature against the one passed in the query |
||
152 | 1 | $verificationResult = openssl_verify($baseString, $decodedSignature, $publicKeyId, $this->algorithm); |
|
153 | // Release the key resource |
||
154 | 1 | if (PHP_MAJOR_VERSION < 8) { |
|
155 | openssl_pkey_free($publicKeyId); |
||
156 | } |
||
157 | |||
158 | 1 | return $verificationResult === 1; |
|
159 | } |
||
160 | |||
161 | /** |
||
162 | * @return string public key certificate content. |
||
163 | */ |
||
164 | 2 | public function getPublicCertificate(): string |
|
165 | { |
||
166 | 2 | if ($this->publicCertificate === null) { |
|
167 | 2 | $this->publicCertificate = $this->initPublicCertificate(); |
|
168 | } |
||
169 | |||
170 | 2 | return $this->publicCertificate; |
|
0 ignored issues
–
show
|
|||
171 | } |
||
172 | |||
173 | /** |
||
174 | * Creates initial value for {@see publicCertificate}. |
||
175 | * This method will attempt to fetch the certificate value from {@see publicCertificateFile} file. |
||
176 | * |
||
177 | * @throws InvalidConfigException on failure. |
||
178 | * |
||
179 | * @return string public certificate content. |
||
180 | */ |
||
181 | 2 | protected function initPublicCertificate(): string |
|
182 | { |
||
183 | 2 | $content = ''; |
|
184 | 2 | if (!empty($this->publicCertificateFile)) { |
|
185 | 2 | if (!file_exists($this->publicCertificateFile)) { |
|
186 | throw new InvalidConfigException( |
||
187 | "Public certificate file '{$this->publicCertificateFile}' does not exist!" |
||
188 | ); |
||
189 | } |
||
190 | 2 | $fp = fopen($this->publicCertificateFile, 'rb'); |
|
191 | |||
192 | 2 | while (!feof($fp)) { |
|
193 | 2 | $content .= fgets($fp); |
|
194 | } |
||
195 | 2 | fclose($fp); |
|
196 | } |
||
197 | 2 | return $content; |
|
198 | } |
||
199 | } |
||
200 |