Failed Conditions
Push — master ( bbfade...32fb37 )
by Florent
02:44
created

loadAttestationObject()   B

Complexity

Conditions 4
Paths 8

Size

Total Lines 47
Code Lines 33

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 33
nc 8
nop 1
dl 0
loc 47
rs 8.6845
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) 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 Base64Url\Base64Url;
17
use CBOR\Decoder;
18
19
class AuthenticatorAttestationResponseChecker
20
{
21
    private const CERTIFICATES_HASHES = [
22
        '349bca1031f8c82c4ceca38b9cebf1a69df9fb3b94eed99eb3fb9aa3822d26e8',
23
        'dd574527df608e47ae45fbba75a2afdd5c20fd94a02419381813cd55a2a3398f',
24
        '1d8764f0f7cd1352df6150045c8f638e517270e8b5dda1c63ade9c2280240cae',
25
        'd0edc9a91a1677435a953390865d208c55b3183c6759c9b5a7ff494c322558eb',
26
        '6073c436dcd064a48127ddbf6032ac1a66fd59a0c24434f070d4e564c124c897',
27
        'ca993121846c464d666096d35f13bf44c1b05af205f9b4a1e00cf6cc10c5e511',
28
    ];
29
    private const FLAG_AT   = 0b01000000;
30
    private const FLAG_ED   = 0b10000000;
31
32
    /**
33
     * @var Decoder
34
     */
35
    private $decoder;
36
37
    /**
38
     * AttestationObjectParser constructor.
39
     *
40
     * @param Decoder $decoder
41
     */
42
    public function __construct(Decoder $decoder)
43
    {
44
        $this->decoder = $decoder;
45
    }
46
47
    /**
48
     * @param PublicKeyCredentialCreationOptions $publicKeyCredentialCreationOptions
49
     * @param AuthenticatorAttestationResponse   $authenticatorAttestationResponse
50
     */
51
    public function check(PublicKeyCredentialCreationOptions $publicKeyCredentialCreationOptions, AuthenticatorAttestationResponse $authenticatorAttestationResponse)
52
    {
53
        $this->checkClientData($publicKeyCredentialCreationOptions, $authenticatorAttestationResponse->getClientDataJSON());
54
    }
55
56
    private function checkClientData(PublicKeyCredentialCreationOptions $publicKeyCredentialCreationOptions, string $clientData): void
57
    {
58
        $decoded = Base64Url::decode($clientData);
59
        $json = json_decode($decoded, true);
60
61
        if (!hash_equals($publicKeyCredentialCreationOptions->getChallenge(), Base64Url::decode($json['challenge']))) {
62
            throw new \InvalidArgumentException();
63
        }
64
        if ($publicKeyCredentialCreationOptions->getRp()->getId()) {
65
66
        }
67
    }
68
69
70
    /**
71
     * @param string $attestationObject
72
     *
73
     * @return AuthenticatorData
74
     */
75
    public function loadAttestationObject(string $attestationObject): AuthenticatorData
76
    {
77
        $decodedAttestationObject = Base64Url::decode($attestationObject);
78
        $stream = new StringStream($decodedAttestationObject);
79
        $data = $this->decoder->decode($stream);
80
81
        dump($data);
82
        $normalized = $data->getNormalizedData();
83
        foreach ($normalized['attStmt']['x5c'] as $cert) {
84
            dump($this->getPublicKeyAsPem($cert));
85
86
        }
87
        dump(base64_encode($normalized['attStmt']['sig']));
88
        $authDataStream = new StringStream($normalized['authData']);
89
90
        $rp_id_hash = $authDataStream->read(32);
91
        $flags = $authDataStream->read(1);
92
        $signCount = $authDataStream->read(4);
93
        $signCount = unpack('l', $signCount)[1];
94
95
        if (ord($flags) & self::FLAG_AT) {
96
            $aaguid = $authDataStream->read(16);
97
            $credentialLength = $authDataStream->read(2);
98
            $credentialLength = unpack('n', $credentialLength)[1];
99
            $credentialId = $authDataStream->read($credentialLength);
100
            $credentialPublicKey = $this->decoder->decode($authDataStream);
101
            //TODO: should be converted into a COSE Key
102
            $attestedCredentialData  = new AttestedCredentialData($aaguid, $credentialId, $credentialPublicKey);
103
        } else {
104
            $attestedCredentialData = null;
105
        }
106
107
        if (ord($flags) & self::FLAG_ED) {
108
            $extension = $this->decoder->decode($authDataStream);
109
            //TODO: should be correctly handled
110
        } else {
111
            $extension = null;
112
        }
113
        $authenticatorData = new AuthenticatorData(
114
            $rp_id_hash,
115
            $flags,
116
            $signCount,
117
            $attestedCredentialData,
118
            $extension
119
        );
120
121
        return $authenticatorData;
122
    }
123
124
    /**
125
     * @param string $publicKey
126
     *
127
     * @return string
128
     */
129
    private function getPublicKeyAsPem(string $publicKey): string
130
    {
131
132
        $derCertificate = $this->unusedBytesFix($publicKey);
133
        $pemCert = '-----BEGIN CERTIFICATE-----'.PHP_EOL;
134
        $pemCert .= chunk_split(base64_encode($derCertificate), 64, PHP_EOL);
135
        $pemCert .= '-----END CERTIFICATE-----'.PHP_EOL;
136
137
        return $pemCert;
138
    }
139
140
    /**
141
     * @param string $derCertificate
142
     *
143
     * @return string
144
     */
145
    private function unusedBytesFix(string $derCertificate): string
146
    {
147
        $certificateHash = hash('sha256', $derCertificate);
148
        if (in_array($certificateHash, self::CERTIFICATES_HASHES)) {
149
            $derCertificate[mb_strlen($derCertificate, '8bit') - 257] = "\0";
150
        }
151
152
        return $derCertificate;
153
    }
154
}
155