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 / CertificateToolbox.php

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

224 lines 7.7 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;
15
16 use WP_DEFENDER_VENDOR\Assert\Assertion;
17 use function count;
18 use function in_array;
19 use InvalidArgumentException;
20 use RuntimeException;
21 use WP_DEFENDER_VENDOR\Safe\Exceptions\FilesystemException;
22 use function WP_DEFENDER_VENDOR\Safe\file_put_contents;
23 use function WP_DEFENDER_VENDOR\Safe\ksort;
24 use function WP_DEFENDER_VENDOR\Safe\mkdir;
25 use function WP_DEFENDER_VENDOR\Safe\rename;
26 use function WP_DEFENDER_VENDOR\Safe\sprintf;
27 use function WP_DEFENDER_VENDOR\Safe\tempnam;
28 use function WP_DEFENDER_VENDOR\Safe\unlink;
29 use WP_DEFENDER_VENDOR\Symfony\Component\Process\Process;
30
31 class CertificateToolbox
32 {
33 /**
34 * @deprecated "This method is deprecated since v3.3 and will be removed en v4.0. Please use WP_DEFENDER_VENDOR\Webauthn\CertificateChainChecker\CertificateChainChecker instead"
35 *
36 * @param string[] $authenticatorCertificates
37 * @param string[] $trustedCertificates
38 */
39 public static function checkChain(array $authenticatorCertificates, array $trustedCertificates = []): void
40 {
41 if (0 === count($trustedCertificates)) {
42 self::checkCertificatesValidity($authenticatorCertificates, true);
43
44 return;
45 }
46 self::checkCertificatesValidity($authenticatorCertificates, false);
47
48 $processArguments = ['-no-CAfile', '-no-CApath'];
49
50 $caDirname = self::createTemporaryDirectory();
51 $processArguments[] = '--CApath';
52 $processArguments[] = $caDirname;
53
54 foreach ($trustedCertificates as $certificate) {
55 self::prepareCertificate($caDirname, $certificate, 'webauthn-trusted-', '.pem');
56 }
57
58 $rehashProcess = new Process(['openssl', 'rehash', $caDirname]);
59 $rehashProcess->run();
60 while ($rehashProcess->isRunning()) {
61 //Just wait
62 }
63 if (!$rehashProcess->isSuccessful()) {
64 throw new InvalidArgumentException('Invalid certificate or certificate chain');
65 }
66
67 $filenames = [];
68 $leafCertificate = array_shift($authenticatorCertificates);
69 $leafFilename = self::prepareCertificate(sys_get_temp_dir(), $leafCertificate, 'webauthn-leaf-', '.pem');
70 $filenames[] = $leafFilename;
71
72 foreach ($authenticatorCertificates as $certificate) {
73 $untrustedFilename = self::prepareCertificate(sys_get_temp_dir(), $certificate, 'webauthn-untrusted-', '.pem');
74 $processArguments[] = '-untrusted';
75 $processArguments[] = $untrustedFilename;
76 $filenames[] = $untrustedFilename;
77 }
78
79 $processArguments[] = $leafFilename;
80 array_unshift($processArguments, 'openssl', 'verify');
81
82 $process = new Process($processArguments);
83 $process->run();
84 while ($process->isRunning()) {
85 //Just wait
86 }
87
88 foreach ($filenames as $filename) {
89 try {
90 unlink($filename);
91 } catch (FilesystemException $e) {
92 continue;
93 }
94 }
95 self::deleteDirectory($caDirname);
96
97 if (!$process->isSuccessful()) {
98 throw new InvalidArgumentException('Invalid certificate or certificate chain');
99 }
100 }
101
102 public static function fixPEMStructure(string $certificate, string $type = 'CERTIFICATE'): string
103 {
104 $pemCert = '-----BEGIN '.$type.'-----'.PHP_EOL;
105 $pemCert .= chunk_split($certificate, 64, PHP_EOL);
106 $pemCert .= '-----END '.$type.'-----'.PHP_EOL;
107
108 return $pemCert;
109 }
110
111 public static function convertDERToPEM(string $certificate, string $type = 'CERTIFICATE'): string
112 {
113 $derCertificate = self::unusedBytesFix($certificate);
114
115 return self::fixPEMStructure(base64_encode($derCertificate), $type);
116 }
117
118 /**
119 * @param string[] $certificates
120 *
121 * @return string[]
122 */
123 public static function convertAllDERToPEM(array $certificates, string $type = 'CERTIFICATE'): array
124 {
125 $certs = [];
126 foreach ($certificates as $publicKey) {
127 $certs[] = self::convertDERToPEM($publicKey, $type);
128 }
129
130 return $certs;
131 }
132
133 private static function unusedBytesFix(string $certificate): string
134 {
135 $certificateHash = hash('sha256', $certificate);
136 if (in_array($certificateHash, self::getCertificateHashes(), true)) {
137 $certificate[mb_strlen($certificate, '8bit') - 257] = "\0";
138 }
139
140 return $certificate;
141 }
142
143 /**
144 * @param string[] $certificates
145 */
146 private static function checkCertificatesValidity(array $certificates, bool $allowRootCertificate): void
147 {
148 foreach ($certificates as $certificate) {
149 $parsed = openssl_x509_parse($certificate);
150 Assertion::isArray($parsed, 'Unable to read the certificate');
151 if (false === $allowRootCertificate) {
152 self::checkRootCertificate($parsed);
153 }
154
155 Assertion::keyExists($parsed, 'validTo_time_t', 'The certificate has no validity period');
156 Assertion::keyExists($parsed, 'validFrom_time_t', 'The certificate has no validity period');
157 Assertion::lessOrEqualThan(time(), $parsed['validTo_time_t'], 'The certificate expired');
158 Assertion::greaterOrEqualThan(time(), $parsed['validFrom_time_t'], 'The certificate is not usable yet');
159 }
160 }
161
162 /**
163 * @param array<string, mixed> $parsed
164 */
165 private static function checkRootCertificate(array $parsed): void
166 {
167 Assertion::keyExists($parsed, 'subject', 'The certificate has no subject');
168 Assertion::keyExists($parsed, 'issuer', 'The certificate has no issuer');
169 $subject = $parsed['subject'];
170 $issuer = $parsed['issuer'];
171 ksort($subject);
172 ksort($issuer);
173 Assertion::notEq($subject, $issuer, 'Root certificates are not allowed');
174 }
175
176 /**
177 * @return string[]
178 */
179 private static function getCertificateHashes(): array
180 {
181 return [
182 '349bca1031f8c82c4ceca38b9cebf1a69df9fb3b94eed99eb3fb9aa3822d26e8',
183 'dd574527df608e47ae45fbba75a2afdd5c20fd94a02419381813cd55a2a3398f',
184 '1d8764f0f7cd1352df6150045c8f638e517270e8b5dda1c63ade9c2280240cae',
185 'd0edc9a91a1677435a953390865d208c55b3183c6759c9b5a7ff494c322558eb',
186 '6073c436dcd064a48127ddbf6032ac1a66fd59a0c24434f070d4e564c124c897',
187 'ca993121846c464d666096d35f13bf44c1b05af205f9b4a1e00cf6cc10c5e511',
188 ];
189 }
190
191 private static function createTemporaryDirectory(): string
192 {
193 $caDir = tempnam(sys_get_temp_dir(), 'webauthn-ca-');
194 if (file_exists($caDir)) {
195 unlink($caDir);
196 }
197 mkdir($caDir);
198 if (!is_dir($caDir)) {
199 throw new RuntimeException(sprintf('Directory "%s" was not created', $caDir));
200 }
201
202 return $caDir;
203 }
204
205 private static function deleteDirectory(string $dirname): void
206 {
207 $rehashProcess = new Process(['rm', '-rf', $dirname]);
208 $rehashProcess->run();
209 while ($rehashProcess->isRunning()) {
210 //Just wait
211 }
212 }
213
214 private static function prepareCertificate(string $folder, string $certificate, string $prefix, string $suffix): string
215 {
216 $untrustedFilename = tempnam($folder, $prefix);
217 rename($untrustedFilename, $untrustedFilename.$suffix);
218 file_put_contents($untrustedFilename.$suffix, $certificate, FILE_APPEND);
219 file_put_contents($untrustedFilename.$suffix, PHP_EOL, FILE_APPEND);
220
221 return $untrustedFilename.$suffix;
222 }
223 }
224