blob: eed916b7e24bcbba81d5f91e92e5da890a2477b1 [file] [log] [blame]
Copybara botbe50d492023-11-30 00:16:42 +01001<?php
2
3
4namespace lbuchs\WebAuthn\Attestation\Format;
5use lbuchs\WebAuthn\WebAuthnException;
6use lbuchs\WebAuthn\Attestation\AuthenticatorData;
7
8
9abstract class FormatBase {
10 protected $_attestationObject = null;
11 protected $_authenticatorData = null;
12 protected $_x5c_chain = array();
13 protected $_x5c_tempFile = null;
14
15 /**
16 *
17 * @param Array $AttestionObject
18 * @param AuthenticatorData $authenticatorData
19 */
20 public function __construct($AttestionObject, AuthenticatorData $authenticatorData) {
21 $this->_attestationObject = $AttestionObject;
22 $this->_authenticatorData = $authenticatorData;
23 }
24
25 /**
26 *
27 */
28 public function __destruct() {
29 // delete X.509 chain certificate file after use
30 if ($this->_x5c_tempFile && \is_file($this->_x5c_tempFile)) {
31 \unlink($this->_x5c_tempFile);
32 }
33 }
34
35 /**
36 * returns the certificate chain in PEM format
37 * @return string|null
38 */
39 public function getCertificateChain() {
40 if ($this->_x5c_tempFile && \is_file($this->_x5c_tempFile)) {
41 return \file_get_contents($this->_x5c_tempFile);
42 }
43 return null;
44 }
45
46 /**
47 * returns the key X.509 certificate in PEM format
48 * @return string
49 */
50 public function getCertificatePem() {
51 // need to be overwritten
52 return null;
53 }
54
55 /**
56 * checks validity of the signature
57 * @param string $clientDataHash
58 * @return bool
59 * @throws WebAuthnException
60 */
61 public function validateAttestation($clientDataHash) {
62 // need to be overwritten
63 return false;
64 }
65
66 /**
67 * validates the certificate against root certificates
68 * @param array $rootCas
69 * @return boolean
70 * @throws WebAuthnException
71 */
72 public function validateRootCertificate($rootCas) {
73 // need to be overwritten
74 return false;
75 }
76
77
78 /**
79 * create a PEM encoded certificate with X.509 binary data
80 * @param string $x5c
81 * @return string
82 */
83 protected function _createCertificatePem($x5c) {
84 $pem = '-----BEGIN CERTIFICATE-----' . "\n";
85 $pem .= \chunk_split(\base64_encode($x5c), 64, "\n");
86 $pem .= '-----END CERTIFICATE-----' . "\n";
87 return $pem;
88 }
89
90 /**
91 * creates a PEM encoded chain file
92 * @return type
93 */
94 protected function _createX5cChainFile() {
95 $content = '';
96 if (\is_array($this->_x5c_chain) && \count($this->_x5c_chain) > 0) {
97 foreach ($this->_x5c_chain as $x5c) {
98 $certInfo = \openssl_x509_parse($this->_createCertificatePem($x5c));
99
100 // check if certificate is self signed
101 if (\is_array($certInfo) && \is_array($certInfo['issuer']) && \is_array($certInfo['subject'])) {
102 $selfSigned = false;
103
104 $subjectKeyIdentifier = $certInfo['extensions']['subjectKeyIdentifier'] ?? null;
105 $authorityKeyIdentifier = $certInfo['extensions']['authorityKeyIdentifier'] ?? null;
106
107 if ($authorityKeyIdentifier && substr($authorityKeyIdentifier, 0, 6) === 'keyid:') {
108 $authorityKeyIdentifier = substr($authorityKeyIdentifier, 6);
109 }
110 if ($subjectKeyIdentifier && substr($subjectKeyIdentifier, 0, 6) === 'keyid:') {
111 $subjectKeyIdentifier = substr($subjectKeyIdentifier, 6);
112 }
113
114 if (($subjectKeyIdentifier && !$authorityKeyIdentifier) || ($authorityKeyIdentifier && $authorityKeyIdentifier === $subjectKeyIdentifier)) {
115 $selfSigned = true;
116 }
117
118 if (!$selfSigned) {
119 $content .= "\n" . $this->_createCertificatePem($x5c) . "\n";
120 }
121 }
122 }
123 }
124
125 if ($content) {
126 $this->_x5c_tempFile = \sys_get_temp_dir() . '/x5c_chain_' . \base_convert(\rand(), 10, 36) . '.pem';
127 if (\file_put_contents($this->_x5c_tempFile, $content) !== false) {
128 return $this->_x5c_tempFile;
129 }
130 }
131
132 return null;
133 }
134
135
136 /**
137 * returns the name and openssl key for provided cose number.
138 * @param int $coseNumber
139 * @return \stdClass|null
140 */
141 protected function _getCoseAlgorithm($coseNumber) {
142 // https://www.iana.org/assignments/cose/cose.xhtml#algorithms
143 $coseAlgorithms = array(
144 array(
145 'hash' => 'SHA1',
146 'openssl' => OPENSSL_ALGO_SHA1,
147 'cose' => array(
148 -65535 // RS1
149 )),
150
151 array(
152 'hash' => 'SHA256',
153 'openssl' => OPENSSL_ALGO_SHA256,
154 'cose' => array(
155 -257, // RS256
156 -37, // PS256
157 -7, // ES256
158 5 // HMAC256
159 )),
160
161 array(
162 'hash' => 'SHA384',
163 'openssl' => OPENSSL_ALGO_SHA384,
164 'cose' => array(
165 -258, // RS384
166 -38, // PS384
167 -35, // ES384
168 6 // HMAC384
169 )),
170
171 array(
172 'hash' => 'SHA512',
173 'openssl' => OPENSSL_ALGO_SHA512,
174 'cose' => array(
175 -259, // RS512
176 -39, // PS512
177 -36, // ES512
178 7 // HMAC512
179 ))
180 );
181
182 foreach ($coseAlgorithms as $coseAlgorithm) {
183 if (\in_array($coseNumber, $coseAlgorithm['cose'], true)) {
184 $return = new \stdClass();
185 $return->hash = $coseAlgorithm['hash'];
186 $return->openssl = $coseAlgorithm['openssl'];
187 return $return;
188 }
189 }
190
191 return null;
192 }
193}