Passed
Push — develop ( c53a49...51ae01 )
by nguereza
01:52
created

AttestationData::createAttestationFormat()   B

Complexity

Conditions 6
Paths 6

Size

Total Lines 27
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 6
eloc 22
c 1
b 0
f 0
nc 6
nop 2
dl 0
loc 27
rs 8.9457
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
        $this->formatName = $enc['fmt'];
89
90
        // Set attestation data
91
        $this->setAuthenticatorData($enc);
92
93
        // Create attestation format based on the provided format name
94
        $this->createAttestationFormat($enc, $allowedFormats);
95
    }
96
97
    /**
98
     *
99
     * @return AuthenticatorData
100
     */
101
    public function getAuthenticatorData(): AuthenticatorData
102
    {
103
        return $this->authenticatorData;
104
    }
105
106
    /**
107
     *
108
     * @return BaseFormat|FidoU2F|None|Packed
109
     */
110
    public function getFormat()
111
    {
112
        return $this->format;
113
    }
114
115
    /**
116
     *
117
     * @return string
118
     */
119
    public function getFormatName(): string
120
    {
121
        return $this->formatName;
122
    }
123
124
    /**
125
     * Return the certificate chain
126
     * @return string|null
127
     */
128
    public function getCertificateChain(): ?string
129
    {
130
        return $this->format->getCertificateChain();
131
    }
132
133
    /**
134
     * Return the certificate with PEM format
135
     * @return string|null
136
     */
137
    public function getCertificatePem(): ?string
138
    {
139
        return $this->format->getCertificatePem();
140
    }
141
142
    /**
143
     * Check the validity of the signature
144
     * @param string $clientData
145
     * @return bool
146
     */
147
    public function validateAttestation(string $clientData): bool
148
    {
149
        return $this->format->validateAttestation($clientData);
150
    }
151
152
    /**
153
     * Validate the certificate against root certificates
154
     * @param array<string> $rootCertificates
155
     * @return bool
156
     */
157
    public function validateRootCertificate(array $rootCertificates): bool
158
    {
159
        return $this->format->validateRootCertificate($rootCertificates);
160
    }
161
162
    /**
163
     * Validate the relying party id hash
164
     * @param string $id
165
     * @return bool
166
     */
167
    public function validateRelyingPartyIdHash(string $id): bool
168
    {
169
        return $this->authenticatorData->getRelyingPartyIdHash() === $id;
170
    }
171
172
    /**
173
     * Return the certificate issuer
174
     * @return string
175
     */
176
    public function getCertificateIssuer(): string
177
    {
178
        return $this->getCertificateInfo('issuer');
179
    }
180
181
    /**
182
     * Return the certificate subject
183
     * @return string
184
     */
185
    public function getCertificateSubject(): string
186
    {
187
        return $this->getCertificateInfo('subject');
188
    }
189
190
    /**
191
    * {@inheritdoc}
192
    * @return mixed
193
    */
194
    public function jsonSerialize()
195
    {
196
        return get_object_vars($this);
197
    }
198
199
    /**
200
     * Return the certificate info
201
     * @param string $type can be "issuer", "subject"
202
     * @return string
203
     */
204
    protected function getCertificateInfo(string $type): string
205
    {
206
        $pem = $this->getCertificatePem();
207
        if ($pem === null) {
208
            return '';
209
        }
210
        $result = '';
211
        $certInfo = openssl_x509_parse($pem);
212
        if (is_array($certInfo) && array_key_exists($type, $certInfo) && is_array($certInfo[$type])) {
213
            $cn = $certInfo[$type]['CN'] ?? '';
214
            $o = $certInfo[$type]['O'] ?? '';
215
            $ou = $certInfo[$type]['OU'] ?? '';
216
217
            if (!empty($cn)) {
218
                $result .= $cn;
219
            }
220
221
            if (!empty($result) && (!empty($o) || !empty($ou))) {
222
                $result .= sprintf(' (%s)', trim($o . ' ' . $ou),);
223
            } else {
224
                $result .= trim($o . ' ' . $ou);
225
            }
226
        }
227
228
        return $result;
229
    }
230
231
    /**
232
     * Set the authenticator data
233
     * @param array<string|int, mixed> $enc
234
     * @return void
235
     */
236
    protected function setAuthenticatorData(array $enc): void
237
    {
238
        if (! array_key_exists('authData', $enc) || ! $enc['authData'] instanceof ByteBuffer) {
239
            throw new WebauthnException('Invalid attestation format provided (authData not available)');
240
        }
241
242
        $this->authenticatorData = new AuthenticatorData($enc['authData']->getBinaryString());
243
    }
244
245
    /**
246
     * Create the attestation format
247
     * @param array<string|int, mixed> $enc the encoded data
248
     * @param array<string> $allowedFormats the allowed format
249
     * @return void
250
     */
251
    protected function createAttestationFormat(array $enc, array $allowedFormats): void
252
    {
253
        if (! in_array($this->formatName, $allowedFormats)) {
254
            throw new WebauthnException(sprintf(
255
                'Invalid attestation format [%s], allowed [%s]',
256
                $this->formatName,
257
                implode(', ', $allowedFormats)
258
            ));
259
        }
260
261
        switch ($this->formatName) {
262
            case KeyFormat::FIDO_U2FA:
263
                $this->format = new FidoU2F($enc, $this->authenticatorData);
264
                break;
265
            case KeyFormat::NONE:
266
                $this->format = new None($enc, $this->authenticatorData);
267
                break;
268
            case KeyFormat::PACKED:
269
                $this->format = new Packed($enc, $this->authenticatorData);
270
                break;
271
            case KeyFormat::TPM:
272
                $this->format = new Tpm($enc, $this->authenticatorData);
273
                break;
274
            default:
275
                throw new WebauthnException(sprintf(
276
                    'The attestation format [%s] is not supported yet, please implement it',
277
                    $this->formatName,
278
                ));
279
        }
280
    }
281
}
282