Completed
Push — master ( 234d27...d96c64 )
by Florent
02:47
created

isCredentialIdAllowed()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 4
nc 3
nop 2
dl 0
loc 9
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
class AuthenticatorAssertionResponseValidator
17
{
18
    private $credentialRepository;
19
20
    public function __construct(CredentialRepository $credentialRepository)
21
    {
22
        $this->credentialRepository = $credentialRepository;
23
    }
24
25
    /**
26
     * @see https://www.w3.org/TR/webauthn/#registering-a-new-credential
27
     */
28
    public function check(string $credentialId, AuthenticatorAssertionResponse $authenticatorAssertionResponse, PublicKeyCredentialRequestOptions $publicKeyCredentialRequestOptions, ?string $rpId = null): void
29
    {
30
        /* @see 7.2.1 */
31
        if (!$this->isCredentialIdAllowed($credentialId, $publicKeyCredentialRequestOptions->getAllowCredentials())) {
32
            throw new \InvalidArgumentException('The credential ID is not allowed.');
33
        }
34
        /* @see 7.2.2 */
35
        if (null !== $authenticatorAssertionResponse->getUserHandle()) {
36
            throw new \RuntimeException('Not supported.'); //TODO: implementation shall be done.
37
        }
38
39
        /* @see 7.2.3 */
40
        if (!$this->credentialRepository->hasCredentialPublicKey($credentialId)) {
41
            throw new \InvalidArgumentException('No credential public key available for the given credential ID.');
42
        }
43
        $credentialPublicKey = $this->credentialRepository->getCredentialPublicKey($credentialId);
0 ignored issues
show
Unused Code introduced by
The assignment to $credentialPublicKey is dead and can be removed.
Loading history...
44
45
        /** @see 7.2.4 */
46
        /** @see 7.2.5 */
47
        //Nothirg to do. Use of objets directly
48
49
        /** @see 7.2.6 */
50
        $C = $authenticatorAssertionResponse->getClientDataJSON();
51
52
        /* @see 7.2.7 */
53
        if ('webauthn.get' !== $C->getType()) {
54
            throw new \InvalidArgumentException('The client data type is not "webauthn.get".');
55
        }
56
57
        /* @see 7.2.8 */
58
        if (hash_equals($publicKeyCredentialRequestOptions->getChallenge(), $C->getChallenge())) {
59
            throw new \InvalidArgumentException('Invalid challenge.');
60
        }
61
62
        /** @see 7.2.9 */
63
        $rpId = $rpId ?? $publicKeyCredentialRequestOptions->getRpId();
64
        if (null === $rpId) {
65
            throw new \InvalidArgumentException('No rpId.');
66
        }
67
        $parsedRelyingPartyId = parse_url($C->getOrigin());
68
        if (!array_key_exists('host', $parsedRelyingPartyId) || !\is_string($parsedRelyingPartyId['host'])) {
69
            throw new \InvalidArgumentException('Invalid origin rpId.');
70
        }
71
        if ($parsedRelyingPartyId['host'] !== $rpId) {
72
            throw new \InvalidArgumentException('rpId mismatch.');
73
        }
74
75
        /* @see 7.2.10 */
76
        if ($C->getTokenBinding()) {
77
            throw new \InvalidArgumentException('Token binding not supported.');
78
        }
79
80
        /** @see 7.2.11 */
81
        $rpIdHash = hash('sha256', $rpId);
82
        if (hash_equals($rpIdHash, $authenticatorAssertionResponse->getAuthenticatorData()->getRpIdHash())) {
83
            throw new \InvalidArgumentException('rpId hash mismatch.');
84
        }
85
86
        /* @see 7.2.12 */
87
        if (!$authenticatorAssertionResponse->getAuthenticatorData()->isUserPresent()) {
88
            throw new \InvalidArgumentException('User was not present');
89
        }
90
91
        /* @see 7.2.13 */
92
        if (AuthenticatorSelectionCriteria::USER_VERIFICATION_REQUIREMENT_REQUIRED === $publicKeyCredentialRequestOptions->getUserVerification() && !$authenticatorAssertionResponse->getAuthenticatorData()->isUserVerified()) {
93
            throw new \InvalidArgumentException('User authentication required.');
94
        }
95
96
        /* @see 7.2.14 */
97
        if (0 !== $publicKeyCredentialRequestOptions->getExtensions()->count()) {
98
            throw new \InvalidArgumentException('Extensions not supported.');
99
        }
100
101
        /** @see 7.2.15 */
102
        $getClientDataJSONHash = hash('sha256', $authenticatorAssertionResponse->getClientDataJSON()->getRawData());
0 ignored issues
show
Unused Code introduced by
The assignment to $getClientDataJSONHash is dead and can be removed.
Loading history...
103
104
        /* @see 7.2.16 */
105
        //TODO: check signature
106
107
        /* @see 7.2.17 */
108
        //TODO: check counter
109
110
        /* @see 7.2.18 */
111
        //Great!
112
    }
113
114
    private function isCredentialIdAllowed(string $credentialId, array $allowedCredentials): bool
115
    {
116
        foreach ($allowedCredentials as $allowedCredential) {
117
            if (hash_equals($allowedCredential->getId(), $credentialId)) {
118
                return true;
119
            }
120
        }
121
122
        return false;
123
    }
124
}
125