blob: aeb4e201e1e08be38a039f93f8b4de519d696273 [file] [log] [blame]
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001<?php
2
3namespace WebAuthn\Attestation;
4use WebAuthn\WebAuthnException;
5use WebAuthn\CBOR\CborDecoder;
6use WebAuthn\Binary\ByteBuffer;
7
8/**
9 * @author Lukas Buchs
10 * @license https://github.com/lbuchs/WebAuthn/blob/master/LICENSE MIT
11 */
12class AttestationObject {
13 private $_authenticatorData;
14 private $_attestationFormat;
15
16 public function __construct($binary , $allowedFormats) {
17 $enc = CborDecoder::decode($binary);
18 // validation
19 if (!\is_array($enc) || !\array_key_exists('fmt', $enc) || !is_string($enc['fmt'])) {
20 throw new WebAuthnException('invalid attestation format', WebAuthnException::INVALID_DATA);
21 }
22
23 if (!\array_key_exists('attStmt', $enc) || !\is_array($enc['attStmt'])) {
24 throw new WebAuthnException('invalid attestation format (attStmt not available)', WebAuthnException::INVALID_DATA);
25 }
26
27 if (!\array_key_exists('authData', $enc) || !\is_object($enc['authData']) || !($enc['authData'] instanceof ByteBuffer)) {
28 throw new WebAuthnException('invalid attestation format (authData not available)', WebAuthnException::INVALID_DATA);
29 }
30
31 $this->_authenticatorData = new AuthenticatorData($enc['authData']->getBinaryString());
32
33 // Format ok?
34 if (!in_array($enc['fmt'], $allowedFormats)) {
35 throw new WebAuthnException('invalid atttestation format: ' . $enc['fmt'], WebAuthnException::INVALID_DATA);
36 }
37
38 switch ($enc['fmt']) {
39 case 'android-key': $this->_attestationFormat = new Format\AndroidKey($enc, $this->_authenticatorData); break;
40 case 'android-safetynet': $this->_attestationFormat = new Format\AndroidSafetyNet($enc, $this->_authenticatorData); break;
41 case 'apple': $this->_attestationFormat = new Format\Apple($enc, $this->_authenticatorData); break;
42 case 'fido-u2f': $this->_attestationFormat = new Format\U2f($enc, $this->_authenticatorData); break;
43 case 'none': $this->_attestationFormat = new Format\None($enc, $this->_authenticatorData); break;
44 case 'packed': $this->_attestationFormat = new Format\Packed($enc, $this->_authenticatorData); break;
45 case 'tpm': $this->_attestationFormat = new Format\Tpm($enc, $this->_authenticatorData); break;
46 default: throw new WebAuthnException('invalid attestation format: ' . $enc['fmt'], WebAuthnException::INVALID_DATA);
47 }
48 }
49
50 /**
51 * returns the attestation public key in PEM format
52 * @return AuthenticatorData
53 */
54 public function getAuthenticatorData() {
55 return $this->_authenticatorData;
56 }
57
58 /**
59 * returns the certificate chain as PEM
60 * @return string|null
61 */
62 public function getCertificateChain() {
63 return $this->_attestationFormat->getCertificateChain();
64 }
65
66 /**
67 * return the certificate issuer as string
68 * @return string
69 */
70 public function getCertificateIssuer() {
71 $pem = $this->getCertificatePem();
72 $issuer = '';
73 if ($pem) {
74 $certInfo = \openssl_x509_parse($pem);
75 if (\is_array($certInfo) && \is_array($certInfo['issuer'])) {
76 if ($certInfo['issuer']['CN']) {
77 $issuer .= \trim($certInfo['issuer']['CN']);
78 }
79 if ($certInfo['issuer']['O'] || $certInfo['issuer']['OU']) {
80 if ($issuer) {
81 $issuer .= ' (' . \trim($certInfo['issuer']['O'] . ' ' . $certInfo['issuer']['OU']) . ')';
82 } else {
83 $issuer .= \trim($certInfo['issuer']['O'] . ' ' . $certInfo['issuer']['OU']);
84 }
85 }
86 }
87 }
88
89 return $issuer;
90 }
91
92 /**
93 * return the certificate subject as string
94 * @return string
95 */
96 public function getCertificateSubject() {
97 $pem = $this->getCertificatePem();
98 $subject = '';
99 if ($pem) {
100 $certInfo = \openssl_x509_parse($pem);
101 if (\is_array($certInfo) && \is_array($certInfo['subject'])) {
102 if ($certInfo['subject']['CN']) {
103 $subject .= \trim($certInfo['subject']['CN']);
104 }
105 if ($certInfo['subject']['O'] || $certInfo['subject']['OU']) {
106 if ($subject) {
107 $subject .= ' (' . \trim($certInfo['subject']['O'] . ' ' . $certInfo['subject']['OU']) . ')';
108 } else {
109 $subject .= \trim($certInfo['subject']['O'] . ' ' . $certInfo['subject']['OU']);
110 }
111 }
112 }
113 }
114
115 return $subject;
116 }
117
118 /**
119 * returns the key certificate in PEM format
120 * @return string
121 */
122 public function getCertificatePem() {
123 return $this->_attestationFormat->getCertificatePem();
124 }
125
126 /**
127 * checks validity of the signature
128 * @param string $clientDataHash
129 * @return bool
130 * @throws WebAuthnException
131 */
132 public function validateAttestation($clientDataHash) {
133 return $this->_attestationFormat->validateAttestation($clientDataHash);
134 }
135
136 /**
137 * validates the certificate against root certificates
138 * @param array $rootCas
139 * @return boolean
140 * @throws WebAuthnException
141 */
142 public function validateRootCertificate($rootCas) {
143 return $this->_attestationFormat->validateRootCertificate($rootCas);
144 }
145
146 /**
147 * checks if the RpId-Hash is valid
148 * @param string$rpIdHash
149 * @return bool
150 */
151 public function validateRpIdHash($rpIdHash) {
152 return $rpIdHash === $this->_authenticatorData->getRpIdHash();
153 }
154}