Completed
Push — master ( 56c8c1...6c23d7 )
by Florent
01:45
created

AttestationObjectLoader::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\AttestationStatement;
15
16
use Assert\Assertion;
17
use Base64Url\Base64Url;
18
use CBOR\CBORObject;
19
use CBOR\Decoder;
20
use CBOR\MapObject;
21
use CBOR\StringStream;
22
use U2FAuthentication\Fido2\AttestedCredentialData;
23
use U2FAuthentication\Fido2\AuthenticationExtensions\AuthenticationExtension;
24
use U2FAuthentication\Fido2\AuthenticationExtensions\AuthenticationExtensionsClientOutputs;
25
use U2FAuthentication\Fido2\AuthenticatorData;
26
27
class AttestationObjectLoader
28
{
29
    private const FLAG_AT = 0b01000000;
30
    private const FLAG_ED = 0b10000000;
31
32
    private $decoder;
33
34
    public function __construct(Decoder $decoder)
35
    {
36
        $this->decoder = $decoder;
37
    }
38
39
    public function load(string $data): AttestationObject
40
    {
41
        $decodedData = Base64Url::decode($data);
42
        $stream = new StringStream($decodedData);
43
        $attestationObject = $this->decoder->decode($stream)->getNormalizedData();
44
        $authData = $attestationObject['authData'];
45
46
        $authDataStream = new StringStream($authData);
47
        $rp_id_hash = $authDataStream->read(32);
48
        $flags = $authDataStream->read(1);
49
        $signCount = $authDataStream->read(4);
50
        $signCount = unpack('N', $signCount)[1];
51
52
        $attestedCredentialData = null;
53
        if (\ord($flags) & self::FLAG_AT) {
54
            $aaguid = $authDataStream->read(16);
55
            $credentialLength = $authDataStream->read(2);
56
            $credentialLength = unpack('n', $credentialLength)[1];
57
            $credentialId = $authDataStream->read($credentialLength);
58
            $credentialPublicKey = $this->decoder->decode($authDataStream);
59
            Assertion::isInstanceOf($credentialPublicKey, MapObject::class, 'The data does not contain a valid credential public key.');
60
            $attestedCredentialData = new AttestedCredentialData($aaguid, $credentialId, (string) $credentialPublicKey);
61
        }
62
63
        $extension = null;
64
        if (\ord($flags) & self::FLAG_ED) {
65
            $extension = $this->decoder->decode($authDataStream);
66
            $extension = $this->retrieveExtensions($extension);
67
        }
68
69
        $authenticatorData = new AuthenticatorData($authData, $rp_id_hash, $flags, $signCount, $attestedCredentialData, $extension);
70
71
        return new AttestationObject($data, new AttestationStatement($attestationObject['fmt'], $attestationObject['attStmt']), $authenticatorData);
72
    }
73
74
    private function retrieveExtensions(CBORObject $object): AuthenticationExtensionsClientOutputs
75
    {
76
        Assertion::isInstanceOf(MapObject::class, $object, 'Invalid extension object');
77
        $data = $object->getNormalizedData();
78
        Assertion::isArray($data, 'Invalid extension object');
79
        $extensions = new AuthenticationExtensionsClientOutputs();
80
        foreach ($data as $key => $value) {
81
            Assertion::string($key, 'Invalid extension key');
82
            $extensions->add(new AuthenticationExtension($key, $value));
83
        }
84
85
        return $extensions;
86
    }
87
}
88