Passed
Push — master ( 6c23d7...d63c7e )
by Florent
01:40
created

PublicKeyCredentialLoader::retrieveExtensions()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 12
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 8
nc 2
nop 1
dl 0
loc 12
rs 10
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 const FLAG_AT = 0b01000000;
27
    private const FLAG_ED = 0b10000000;
28
29
    private $attestationObjectLoader;
30
    private $decoder;
31
32
    public function __construct(AttestationObjectLoader $attestationObjectLoader, Decoder $decoder)
33
    {
34
        $this->attestationObjectLoader = $attestationObjectLoader;
35
        $this->decoder = $decoder;
36
    }
37
38
    public function load(string $data): PublicKeyCredential
39
    {
40
        $json = \Safe\json_decode($data, true);
41
        Assertion::keyExists($json, 'id');
42
        Assertion::keyExists($json, 'rawId');
43
        Assertion::keyExists($json, 'response');
44
45
        $id = Base64Url::decode($json['id']);
46
        $rawId = Base64Url::decode($json['rawId']);
47
        Assertion::true(hash_equals($id, $rawId));
48
49
        $publicKeyCredential = new PublicKeyCredential(
50
            $json['id'],
51
            $json['type'] ?? 'public-key',
52
            $rawId,
53
            $this->createResponse($json['response'])
54
        );
55
56
        return $publicKeyCredential;
57
    }
58
59
    private function createResponse(array $response): AuthenticatorResponse
60
    {
61
        Assertion::keyExists($response, 'clientDataJSON');
62
        switch (true) {
63
            case array_key_exists('attestationObject', $response):
64
                $attestationObject = $this->attestationObjectLoader->load($response['attestationObject']);
65
66
                return new AuthenticatorAttestationResponse(CollectedClientData::createFormJson($response['clientDataJSON']), $attestationObject);
67
            case array_key_exists('authenticatorData', $response) && array_key_exists('signature', $response):
68
                $authData = Base64Url::decode($response['authenticatorData']);
69
70
                $authDataStream = new StringStream($authData);
71
                $rp_id_hash = $authDataStream->read(32);
72
                $flags = $authDataStream->read(1);
73
                $signCount = $authDataStream->read(4);
74
                $signCount = unpack('N', $signCount)[1];
75
76
                $attestedCredentialData = null;
77
                if (\ord($flags) & self::FLAG_AT) {
78
                    $aaguid = $authDataStream->read(16);
79
                    $credentialLength = $authDataStream->read(2);
80
                    $credentialLength = unpack('n', $credentialLength)[1];
81
                    $credentialId = $authDataStream->read($credentialLength);
82
                    $credentialPublicKey = $this->decoder->decode($authDataStream);
83
                    Assertion::isInstanceOf($credentialPublicKey, MapObject::class, 'The data does not contain a valid credential public key.');
84
                    $attestedCredentialData = new AttestedCredentialData($aaguid, $credentialId, (string) $credentialPublicKey);
85
                }
86
87
                $extension = null;
88
                if (\ord($flags) & self::FLAG_ED) {
89
                    $extension = $this->decoder->decode($authDataStream);
90
                    $extension = AuthenticationExtensionsClientOutputsLoader::load($extension);
91
                }
92
                $authenticatorData = new AuthenticatorData($authData, $rp_id_hash, $flags, $signCount, $attestedCredentialData, $extension);
93
94
                return new AuthenticatorAssertionResponse(
95
                    CollectedClientData::createFormJson($response['clientDataJSON']),
96
                    $authenticatorData,
97
                    Base64Url::decode($response['signature']),
98
                    $response['userHandle'] ?? null
99
                );
100
            default:
101
                throw new \InvalidArgumentException();
102
        }
103
    }
104
}
105