PublicKeyCredentialLoader::load()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 19
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 13
nc 1
nop 1
dl 0
loc 19
rs 9.8333
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * The MIT License (MIT)
7
 *
8
 * Copyright (c) 2014-2018 Spomky-Labs
9
 *
10
 * This software may be modified and distributed under the terms
11
 * of the MIT license.  See the LICENSE file for details.
12
 */
13
14
namespace U2FAuthentication\Fido2;
15
16
use Assert\Assertion;
17
use Base64Url\Base64Url;
18
use CBOR\Decoder;
19
use CBOR\MapObject;
20
use CBOR\StringStream;
21
use U2FAuthentication\Fido2\AttestationStatement\AttestationObjectLoader;
22
use U2FAuthentication\Fido2\AuthenticationExtensions\AuthenticationExtensionsClientOutputsLoader;
23
24
class PublicKeyCredentialLoader
25
{
26
    private $attestationObjectLoader;
27
    private $decoder;
28
29
    public function __construct(AttestationObjectLoader $attestationObjectLoader, Decoder $decoder)
30
    {
31
        $this->attestationObjectLoader = $attestationObjectLoader;
32
        $this->decoder = $decoder;
33
    }
34
35
    public function load(string $data): PublicKeyCredential
36
    {
37
        $json = \Safe\json_decode($data, true);
38
        Assertion::keyExists($json, 'id');
39
        Assertion::keyExists($json, 'rawId');
40
        Assertion::keyExists($json, 'response');
41
42
        $id = Base64Url::decode($json['id']);
43
        $rawId = Base64Url::decode($json['rawId']);
44
        Assertion::true(hash_equals($id, $rawId));
45
46
        $publicKeyCredential = new PublicKeyCredential(
47
            $json['id'],
48
            $json['type'] ?? 'public-key',
49
            $rawId,
50
            $this->createResponse($json['response'])
51
        );
52
53
        return $publicKeyCredential;
54
    }
55
56
    private function createResponse(array $response): AuthenticatorResponse
57
    {
58
        Assertion::keyExists($response, 'clientDataJSON');
59
        switch (true) {
60
            case array_key_exists('attestationObject', $response):
61
                $attestationObject = $this->attestationObjectLoader->load($response['attestationObject']);
62
63
                return new AuthenticatorAttestationResponse(CollectedClientData::createFormJson($response['clientDataJSON']), $attestationObject);
64
            case array_key_exists('authenticatorData', $response) && array_key_exists('signature', $response):
65
                $authData = Base64Url::decode($response['authenticatorData']);
66
67
                $authDataStream = new StringStream($authData);
68
                $rp_id_hash = $authDataStream->read(32);
69
                $flags = $authDataStream->read(1);
70
                $signCount = $authDataStream->read(4);
71
                $signCount = unpack('N', $signCount)[1];
72
73
                $attestedCredentialData = null;
74
                if (\ord($flags) & 0b01000000) {
75
                    $aaguid = $authDataStream->read(16);
76
                    $credentialLength = $authDataStream->read(2);
77
                    $credentialLength = unpack('n', $credentialLength)[1];
78
                    $credentialId = $authDataStream->read($credentialLength);
79
                    $credentialPublicKey = $this->decoder->decode($authDataStream);
80
                    Assertion::isInstanceOf($credentialPublicKey, MapObject::class, 'The data does not contain a valid credential public key.');
81
                    $attestedCredentialData = new AttestedCredentialData($aaguid, $credentialId, (string) $credentialPublicKey);
82
                }
83
84
                $extension = null;
85
                if (\ord($flags) & 0b10000000) {
86
                    $extension = $this->decoder->decode($authDataStream);
87
                    $extension = AuthenticationExtensionsClientOutputsLoader::load($extension);
88
                }
89
                $authenticatorData = new AuthenticatorData($authData, $rp_id_hash, $flags, $signCount, $attestedCredentialData, $extension);
90
91
                return new AuthenticatorAssertionResponse(
92
                    CollectedClientData::createFormJson($response['clientDataJSON']),
93
                    $authenticatorData,
94
                    Base64Url::decode($response['signature']),
95
                    $response['userHandle'] ?? null
96
                );
97
            default:
98
                throw new \InvalidArgumentException();
99
        }
100
    }
101
}
102