Passed
Push — develop ( 548a36...8ad7fd )
by nguereza
01:55
created

AttestationData::validateRelyingPartyIdHash()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * Platine Webauth
5
 *
6
 * Platine Webauthn is the implementation of webauthn specifications
7
 *
8
 * This content is released under the MIT License (MIT)
9
 *
10
 * Copyright (c) 2020 Platine Webauth
11
 * Copyright (c) Jakob Bennemann <[email protected]>
12
 *
13
 * Permission is hereby granted, free of charge, to any person obtaining a copy
14
 * of this software and associated documentation files (the "Software"), to deal
15
 * in the Software without restriction, including without limitation the rights
16
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17
 * copies of the Software, and to permit persons to whom the Software is
18
 * furnished to do so, subject to the following conditions:
19
 *
20
 * The above copyright notice and this permission notice shall be included in all
21
 * copies or substantial portions of the Software.
22
 *
23
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29
 * SOFTWARE.
30
 */
31
32
declare(strict_types=1);
33
34
namespace Platine\Webauthn\Attestation;
35
36
use JsonSerializable;
37
use Platine\Webauthn\Attestation\Format\BaseFormat;
38
use Platine\Webauthn\Attestation\Format\FidoU2F;
39
use Platine\Webauthn\Attestation\Format\None;
40
use Platine\Webauthn\Attestation\Format\Packed;
41
use Platine\Webauthn\Attestation\Format\Tpm;
42
use Platine\Webauthn\Enum\KeyFormat;
43
use Platine\Webauthn\Exception\WebauthnException;
44
use Platine\Webauthn\Helper\ByteBuffer;
45
use Platine\Webauthn\Helper\CborDecoder;
46
47
/**
48
 * @class AttestationData
49
 * @package Platine\Webauthn\Attestation
50
 */
51
class AttestationData implements JsonSerializable
52
{
53
    /**
54
     * The AuthenticatorData instance
55
     * @var AuthenticatorData
56
     */
57
    protected AuthenticatorData $authenticatorData;
58
59
    /**
60
     * The attestation format
61
     * @var BaseFormat|FidoU2F|None|Packed
62
     */
63
    protected $format;
64
65
    /**
66
     * The attestation format name
67
     * @var string
68
     */
69
    protected string $formatName;
70
71
    /**
72
     * Create new instance
73
     * @param string $binary
74
     * @param array<string> $allowedFormats
75
     */
76
    public function __construct(string $binary, array $allowedFormats)
77
    {
78
        $enc = CborDecoder::decode($binary);
79
80
        if (! is_array($enc) || ! array_key_exists('fmt', $enc) || ! is_string($enc['fmt'])) {
81
            throw new WebauthnException('Invalid attestation format provided');
82
        }
83
84
        if (! array_key_exists('attStmt', $enc) || ! is_array($enc['attStmt'])) {
85
            throw new WebauthnException('Invalid attestation format provided (attStmt not available)');
86
        }
87
88
        if (! array_key_exists('authData', $enc) || ! $enc['authData'] instanceof ByteBuffer) {
89
            throw new WebauthnException('Invalid attestation format provided (authData not available)');
90
        }
91
92
        $this->formatName = $enc['fmt'];
93
        $this->authenticatorData = new AuthenticatorData($enc['authData']->getBinaryString());
94
95
        if (! in_array($this->formatName, $allowedFormats)) {
96
            throw new WebauthnException(sprintf(
97
                'Invalid attestation format [%s], allowed [%s]',
98
                $this->formatName,
99
                implode(', ', $allowedFormats)
100
            ));
101
        }
102
103
        // Create attestation format based on the provided format name
104
        $this->createAttestationFormat($enc);
105
    }
106
107
    /**
108
     *
109
     * @return AuthenticatorData
110
     */
111
    public function getAuthenticatorData(): AuthenticatorData
112
    {
113
        return $this->authenticatorData;
114
    }
115
116
    /**
117
     *
118
     * @return BaseFormat|FidoU2F|None|Packed
119
     */
120
    public function getFormat()
121
    {
122
        return $this->format;
123
    }
124
125
    /**
126
     *
127
     * @return string
128
     */
129
    public function getFormatName(): string
130
    {
131
        return $this->formatName;
132
    }
133
134
    /**
135
     * Return the certificate chain
136
     * @return string|null
137
     */
138
    public function getCertificateChain(): ?string
139
    {
140
        return $this->format->getCertificateChain();
141
    }
142
143
    /**
144
     * Return the certificate with PEM format
145
     * @return string|null
146
     */
147
    public function getCertificatePem(): ?string
148
    {
149
        return $this->format->getCertificatePem();
150
    }
151
152
    /**
153
     * Check the validity of the signature
154
     * @param string $clientData
155
     * @return bool
156
     */
157
    public function validateAttestation(string $clientData): bool
158
    {
159
        return $this->format->validateAttestation($clientData);
160
    }
161
162
    /**
163
     * Validate the certificate against root certificates
164
     * @param array<string> $rootCertificates
165
     * @return bool
166
     */
167
    public function validateRootCertificate(array $rootCertificates): bool
168
    {
169
        return $this->format->validateRootCertificate($rootCertificates);
170
    }
171
172
    /**
173
     * Validate the relying party id hash
174
     * @param string $id
175
     * @return bool
176
     */
177
    public function validateRelyingPartyIdHash(string $id): bool
178
    {
179
        return $this->authenticatorData->getRelyingPartyIdHash() === $id;
180
    }
181
182
    /**
183
     * Return the certificate issuer
184
     * @return string
185
     */
186
    public function getCertificateIssuer(): string
187
    {
188
        return $this->getCertificateInfo('issuer');
189
    }
190
191
    /**
192
     * Return the certificate subject
193
     * @return string
194
     */
195
    public function getCertificateSubject(): string
196
    {
197
        return $this->getCertificateInfo('subject');
198
    }
199
200
    /**
201
    * {@inheritdoc}
202
    * @return mixed
203
    */
204
    public function jsonSerialize()
205
    {
206
        return get_object_vars($this);
207
    }
208
209
    /**
210
     * Return the certificate info
211
     * @param string $type can be "issuer", "subject"
212
     * @return string
213
     */
214
    protected function getCertificateInfo(string $type): string
215
    {
216
        $pem = $this->getCertificatePem();
217
        if ($pem === null) {
218
            return '';
219
        }
220
        $result = '';
221
        $certInfo = openssl_x509_parse($pem);
222
        if (is_array($certInfo) && array_key_exists($type, $certInfo) && is_array($certInfo[$type])) {
223
            $cn = $certInfo[$type]['CN'] ?? '';
224
            $o = $certInfo[$type]['O'] ?? '';
225
            $ou = $certInfo[$type]['OU'] ?? '';
226
227
            if (!empty($cn)) {
228
                $result .= $cn;
229
            }
230
231
            if (!empty($result) && (!empty($o) || !empty($ou))) {
232
                $result .= sprintf(' (%s)', trim($o . ' ' . $ou),);
233
            } else {
234
                $result .= trim($o . ' ' . $ou);
235
            }
236
        }
237
238
        return $result;
239
    }
240
241
    /**
242
     * Create the attestation format
243
     * @param array<string, mixed> $enc the encoded data
244
     * @return void
245
     */
246
    protected function createAttestationFormat(array $enc): void
247
    {
248
        switch ($this->formatName) {
249
            case KeyFormat::FIDO_U2FA:
250
                $this->format = new FidoU2F($enc, $this->authenticatorData);
251
                break;
252
            case KeyFormat::NONE:
253
                $this->format = new None($enc, $this->authenticatorData);
254
                break;
255
            case KeyFormat::PACKED:
256
                $this->format = new Packed($enc, $this->authenticatorData);
257
                break;
258
            case KeyFormat::TPM:
259
                $this->format = new Tpm($enc, $this->authenticatorData);
260
                break;
261
            default:
262
                throw new WebauthnException(sprintf(
263
                    'The attestation format [%s] is not supported yet, please implement it',
264
                    $this->formatName,
265
                ));
266
        }
267
    }
268
}
269