PluginProbe
Defender Security – Malware Scanner, Login Security & Firewall / 6.3.0
Defender Security – Malware Scanner, Login Security & Firewall v6.3.0
6.3.0 6.2.3 6.2.4 6.2.0 6.2.1 6.2.2 6.1.0 5.3.1 5.4.0 5.4.1 5.5.0 5.5.1 5.6.0 5.6.1 5.6.2 5.7.0 5.7.1 5.7.2 5.8.0 5.8.1 5.9.0 6.0.0 6.0.1 3.0.1 3.1.0 All 141 releases
defender-security / lib / packages / Webauthn / CertificateChainChecker / OpenSSLCertificateChainChecker.php

OpenSSLCertificateChainChecker.php in Defender Security – Malware Scanner, Login Security & Firewall 6.3.0, at lib/packages/Webauthn/CertificateChainChecker/OpenSSLCertificateChainChecker.php

240 lines 7.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 declare(strict_types=1);
4
5 /*
6 * The MIT License (MIT)
7 *
8 * Copyright (c) 2014-2021 Spomky-Labs
9 *
10 * This software may be modified and distributed under the terms
11 * of the MIT license. See the LICENSE file for details.
12 */
13
14 namespace WP_DEFENDER_VENDOR\Webauthn\CertificateChainChecker;
15
16 use WP_DEFENDER_VENDOR\Assert\Assertion;
17 use function count;
18 use InvalidArgumentException;
19 use function is_int;
20 use WP_DEFENDER_VENDOR\Psr\Http\Client\ClientInterface;
21 use WP_DEFENDER_VENDOR\Psr\Http\Message\RequestFactoryInterface;
22 use RuntimeException;
23 use WP_DEFENDER_VENDOR\Safe\Exceptions\FilesystemException;
24 use function WP_DEFENDER_VENDOR\Safe\file_put_contents;
25 use function WP_DEFENDER_VENDOR\Safe\ksort;
26 use function WP_DEFENDER_VENDOR\Safe\mkdir;
27 use function WP_DEFENDER_VENDOR\Safe\rename;
28 use function WP_DEFENDER_VENDOR\Safe\sprintf;
29 use function WP_DEFENDER_VENDOR\Safe\tempnam;
30 use function WP_DEFENDER_VENDOR\Safe\unlink;
31 use WP_DEFENDER_VENDOR\Symfony\Component\Process\Process;
32
33 final class OpenSSLCertificateChainChecker implements CertificateChainChecker
34 {
35 /**
36 * @var ClientInterface
37 */
38 private $client;
39
40 /**
41 * @var RequestFactoryInterface
42 */
43 private $requestFactory;
44
45 /**
46 * @var string[]
47 */
48 private $rootCertificates = [];
49
50 public function __construct(ClientInterface $client, RequestFactoryInterface $requestFactory)
51 {
52 $this->client = $client;
53 $this->requestFactory = $requestFactory;
54 }
55
56 public function addRootCertificate(string $certificate): self
57 {
58 $this->rootCertificates[] = $certificate;
59
60 return $this;
61 }
62
63 /**
64 * @param string[] $authenticatorCertificates
65 * @param string[] $trustedCertificates
66 */
67 public function check(array $authenticatorCertificates, array $trustedCertificates): void
68 {
69 if (0 === count($trustedCertificates)) {
70 $this->checkCertificatesValidity($authenticatorCertificates, true);
71
72 return;
73 }
74 $this->checkCertificatesValidity($authenticatorCertificates, false);
75
76 $hasCrls = false;
77 $processArguments = ['-no-CAfile', '-no-CApath'];
78
79 $caDirname = $this->createTemporaryDirectory();
80 $processArguments[] = '--CApath';
81 $processArguments[] = $caDirname;
82
83 foreach ($trustedCertificates as $certificate) {
84 $this->saveToTemporaryFile($caDirname, $certificate, 'webauthn-trusted-', '.pem');
85 $crl = $this->getCrls($certificate);
86 if ('' !== $crl) {
87 $hasCrls = true;
88 $this->saveToTemporaryFile($caDirname, $crl, 'webauthn-trusted-crl-', '.crl');
89 }
90 }
91
92 $rehashProcess = new Process(['openssl', 'rehash', $caDirname]);
93 $rehashProcess->run();
94 while ($rehashProcess->isRunning()) {
95 //Just wait
96 }
97 if (!$rehashProcess->isSuccessful()) {
98 throw new InvalidArgumentException('Invalid certificate or certificate chain');
99 }
100
101 $filenames = [];
102 $leafCertificate = array_shift($authenticatorCertificates);
103 $leafFilename = $this->saveToTemporaryFile(sys_get_temp_dir(), $leafCertificate, 'webauthn-leaf-', '.pem');
104 $crl = $this->getCrls($leafCertificate);
105 if ('' !== $crl) {
106 $hasCrls = true;
107 $this->saveToTemporaryFile($caDirname, $crl, 'webauthn-leaf-crl-', '.pem');
108 }
109 $filenames[] = $leafFilename;
110
111 foreach ($authenticatorCertificates as $certificate) {
112 $untrustedFilename = $this->saveToTemporaryFile(sys_get_temp_dir(), $certificate, 'webauthn-untrusted-', '.pem');
113 $crl = $this->getCrls($certificate);
114 if ('' !== $crl) {
115 $hasCrls = true;
116 $this->saveToTemporaryFile($caDirname, $crl, 'webauthn-untrusted-crl-', '.pem');
117 }
118 $processArguments[] = '-untrusted';
119 $processArguments[] = $untrustedFilename;
120 $filenames[] = $untrustedFilename;
121 }
122
123 $processArguments[] = $leafFilename;
124 if ($hasCrls) {
125 array_unshift($processArguments, '-crl_check');
126 array_unshift($processArguments, '-crl_check_all');
127 //array_unshift($processArguments, '-crl_download');
128 array_unshift($processArguments, '-extended_crl');
129 }
130 array_unshift($processArguments, 'openssl', 'verify');
131
132 $process = new Process($processArguments);
133 $process->run();
134 while ($process->isRunning()) {
135 //Just wait
136 }
137
138 foreach ($filenames as $filename) {
139 try {
140 unlink($filename);
141 } catch (FilesystemException $e) {
142 continue;
143 }
144 }
145 $this->deleteDirectory($caDirname);
146
147 if (!$process->isSuccessful()) {
148 throw new InvalidArgumentException('Invalid certificate or certificate chain');
149 }
150 }
151
152 /**
153 * @param string[] $certificates
154 */
155 private function checkCertificatesValidity(array $certificates, bool $allowRootCertificate): void
156 {
157 foreach ($certificates as $certificate) {
158 $parsed = openssl_x509_parse($certificate);
159 Assertion::isArray($parsed, 'Unable to read the certificate');
160 if (false === $allowRootCertificate) {
161 $this->checkRootCertificate($parsed);
162 }
163
164 Assertion::keyExists($parsed, 'validTo_time_t', 'The certificate has no validity period');
165 Assertion::keyExists($parsed, 'validFrom_time_t', 'The certificate has no validity period');
166 Assertion::lessOrEqualThan(time(), $parsed['validTo_time_t'], 'The certificate expired');
167 Assertion::greaterOrEqualThan(time(), $parsed['validFrom_time_t'], 'The certificate is not usable yet');
168 }
169 }
170
171 /**
172 * @param array<string, mixed> $parsed
173 */
174 private function checkRootCertificate(array $parsed): void
175 {
176 Assertion::keyExists($parsed, 'subject', 'The certificate has no subject');
177 Assertion::keyExists($parsed, 'issuer', 'The certificate has no issuer');
178 $subject = $parsed['subject'];
179 $issuer = $parsed['issuer'];
180 ksort($subject);
181 ksort($issuer);
182 Assertion::notEq($subject, $issuer, 'Root certificates are not allowed');
183 }
184
185 private function createTemporaryDirectory(): string
186 {
187 $caDir = tempnam(sys_get_temp_dir(), 'webauthn-ca-');
188 if (file_exists($caDir)) {
189 unlink($caDir);
190 }
191 mkdir($caDir);
192 if (!is_dir($caDir)) {
193 throw new RuntimeException(sprintf('Directory "%s" was not created', $caDir));
194 }
195
196 return $caDir;
197 }
198
199 private function deleteDirectory(string $dirname): void
200 {
201 $rehashProcess = new Process(['rm', '-rf', $dirname]);
202 $rehashProcess->run();
203 while ($rehashProcess->isRunning()) {
204 //Just wait
205 }
206 }
207
208 private function saveToTemporaryFile(string $folder, string $certificate, string $prefix, string $suffix): string
209 {
210 $filename = tempnam($folder, $prefix);
211 rename($filename, $filename.$suffix);
212 file_put_contents($filename.$suffix, $certificate, FILE_APPEND);
213
214 return $filename.$suffix;
215 }
216
217 private function getCrls(string $certificate): string
218 {
219 $parsed = openssl_x509_parse($certificate);
220 if (false === $parsed || !isset($parsed['extensions']['crlDistributionPoints'])) {
221 return '';
222 }
223 $endpoint = $parsed['extensions']['crlDistributionPoints'];
224 $pos = mb_strpos($endpoint, 'URI:');
225 if (!is_int($pos)) {
226 return '';
227 }
228
229 $endpoint = trim(mb_substr($endpoint, $pos + 4));
230 $request = $this->requestFactory->createRequest('GET', $endpoint);
231 $response = $this->client->sendRequest($request);
232
233 if (200 !== $response->getStatusCode()) {
234 return '';
235 }
236
237 return $response->getBody()->getContents();
238 }
239 }
240