blob: c2ced07575b29de1aa8457323c5f81c2bf08f60a [file] [log] [blame]
Copybara botbe50d492023-11-30 00:16:42 +01001<?php
2
3
4namespace lbuchs\WebAuthn\Attestation\Format;
5use lbuchs\WebAuthn\Attestation\AuthenticatorData;
6use lbuchs\WebAuthn\WebAuthnException;
7use lbuchs\WebAuthn\Binary\ByteBuffer;
8
9class Packed extends FormatBase {
10 private $_alg;
11 private $_signature;
12 private $_x5c;
13
14 public function __construct($AttestionObject, AuthenticatorData $authenticatorData) {
15 parent::__construct($AttestionObject, $authenticatorData);
16
17 // check packed data
18 $attStmt = $this->_attestationObject['attStmt'];
19
20 if (!\array_key_exists('alg', $attStmt) || $this->_getCoseAlgorithm($attStmt['alg']) === null) {
21 throw new WebAuthnException('unsupported alg: ' . $attStmt['alg'], WebAuthnException::INVALID_DATA);
22 }
23
24 if (!\array_key_exists('sig', $attStmt) || !\is_object($attStmt['sig']) || !($attStmt['sig'] instanceof ByteBuffer)) {
25 throw new WebAuthnException('no signature found', WebAuthnException::INVALID_DATA);
26 }
27
28 $this->_alg = $attStmt['alg'];
29 $this->_signature = $attStmt['sig']->getBinaryString();
30
31 // certificate for validation
32 if (\array_key_exists('x5c', $attStmt) && \is_array($attStmt['x5c']) && \count($attStmt['x5c']) > 0) {
33
34 // The attestation certificate attestnCert MUST be the first element in the array
35 $attestnCert = array_shift($attStmt['x5c']);
36
37 if (!($attestnCert instanceof ByteBuffer)) {
38 throw new WebAuthnException('invalid x5c certificate', WebAuthnException::INVALID_DATA);
39 }
40
41 $this->_x5c = $attestnCert->getBinaryString();
42
43 // certificate chain
44 foreach ($attStmt['x5c'] as $chain) {
45 if ($chain instanceof ByteBuffer) {
46 $this->_x5c_chain[] = $chain->getBinaryString();
47 }
48 }
49 }
50 }
51
52
53 /*
54 * returns the key certificate in PEM format
55 * @return string|null
56 */
57 public function getCertificatePem() {
58 if (!$this->_x5c) {
59 return null;
60 }
61 return $this->_createCertificatePem($this->_x5c);
62 }
63
64 /**
65 * @param string $clientDataHash
66 */
67 public function validateAttestation($clientDataHash) {
68 if ($this->_x5c) {
69 return $this->_validateOverX5c($clientDataHash);
70 } else {
71 return $this->_validateSelfAttestation($clientDataHash);
72 }
73 }
74
75 /**
76 * validates the certificate against root certificates
77 * @param array $rootCas
78 * @return boolean
79 * @throws WebAuthnException
80 */
81 public function validateRootCertificate($rootCas) {
82 if (!$this->_x5c) {
83 return false;
84 }
85
86 $chainC = $this->_createX5cChainFile();
87 if ($chainC) {
88 $rootCas[] = $chainC;
89 }
90
91 $v = \openssl_x509_checkpurpose($this->getCertificatePem(), -1, $rootCas);
92 if ($v === -1) {
93 throw new WebAuthnException('error on validating root certificate: ' . \openssl_error_string(), WebAuthnException::CERTIFICATE_NOT_TRUSTED);
94 }
95 return $v;
96 }
97
98 /**
99 * validate if x5c is present
100 * @param string $clientDataHash
101 * @return bool
102 * @throws WebAuthnException
103 */
104 protected function _validateOverX5c($clientDataHash) {
105 $publicKey = \openssl_pkey_get_public($this->getCertificatePem());
106
107 if ($publicKey === false) {
108 throw new WebAuthnException('invalid public key: ' . \openssl_error_string(), WebAuthnException::INVALID_PUBLIC_KEY);
109 }
110
111 // Verify that sig is a valid signature over the concatenation of authenticatorData and clientDataHash
112 // using the attestation public key in attestnCert with the algorithm specified in alg.
113 $dataToVerify = $this->_authenticatorData->getBinary();
114 $dataToVerify .= $clientDataHash;
115
116 $coseAlgorithm = $this->_getCoseAlgorithm($this->_alg);
117
118 // check certificate
119 return \openssl_verify($dataToVerify, $this->_signature, $publicKey, $coseAlgorithm->openssl) === 1;
120 }
121
122 /**
123 * validate if self attestation is in use
124 * @param string $clientDataHash
125 * @return bool
126 */
127 protected function _validateSelfAttestation($clientDataHash) {
128 // Verify that sig is a valid signature over the concatenation of authenticatorData and clientDataHash
129 // using the credential public key with alg.
130 $dataToVerify = $this->_authenticatorData->getBinary();
131 $dataToVerify .= $clientDataHash;
132
133 $publicKey = $this->_authenticatorData->getPublicKeyPem();
134
135 // check certificate
136 return \openssl_verify($dataToVerify, $this->_signature, $publicKey, OPENSSL_ALGO_SHA256) === 1;
137 }
138}
139