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

__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 2
dl 0
loc 4
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 U2FAuthentication\Fido2\AttestationStatement\AttestationStatementSupportManager;
17
18
class AuthenticatorAttestationResponseValidator
19
{
20
    private $attestationStatementSupportManager;
21
    private $credentialRepository;
22
23
    public function __construct(AttestationStatementSupportManager $attestationStatementSupportManager, CredentialRepository $credentialRepository)
24
    {
25
        $this->attestationStatementSupportManager = $attestationStatementSupportManager;
26
        $this->credentialRepository = $credentialRepository;
27
    }
28
29
    /**
30
     * @see https://www.w3.org/TR/webauthn/#registering-a-new-credential
31
     */
32
    public function check(AuthenticatorAttestationResponse $authenticatorAttestationResponse, PublicKeyCredentialCreationOptions $publicKeyCredentialCreationOptions, ?string $rpId = null): void
33
    {
34
        /** @see 7.1.1 */
35
        //Nothing to do
36
37
        /** @see 7.1.2 */
38
        $C = $authenticatorAttestationResponse->getClientDataJSON();
39
40
        /* @see 7.1.3 */
41
        if ('webauthn.create' !== $C->getType()) {
42
            throw new \InvalidArgumentException('The client data type is not "webauthn.create".');
43
        }
44
45
        /* @see 7.1.4 */
46
        if (hash_equals($publicKeyCredentialCreationOptions->getChallenge(), $C->getChallenge())) {
47
            throw new \InvalidArgumentException('Invalid challenge.');
48
        }
49
50
        /** @see 7.1.5 */
51
        $rpId = $rpId ?? $publicKeyCredentialCreationOptions->getRp()->getId();
52
        if (null === $rpId) {
53
            throw new \InvalidArgumentException('No rpId.');
54
        }
55
        $parsedRelyingPartyId = parse_url($C->getOrigin());
56
        if (!array_key_exists('host', $parsedRelyingPartyId) || !\is_string($parsedRelyingPartyId['host'])) {
57
            throw new \InvalidArgumentException('Invalid origin rpId.');
58
        }
59
        if ($parsedRelyingPartyId['host'] !== $rpId) {
60
            throw new \InvalidArgumentException('rpId mismatch.');
61
        }
62
63
        /* @see 7.1.6 */
64
        if ($C->getTokenBinding()) {
65
            throw new \InvalidArgumentException('Token binding not supported.');
66
        }
67
68
        /** @see 7.1.7 */
69
        $getClientDataJSONHash = hash('sha256', $authenticatorAttestationResponse->getClientDataJSON()->getRawData());
0 ignored issues
show
Unused Code introduced by
The assignment to $getClientDataJSONHash is dead and can be removed.
Loading history...
70
71
        /** @see 7.1.8 */
72
        $attestationObject = $authenticatorAttestationResponse->getAttestationObject();
73
74
        /** @see 7.1.9 */
75
        $rpIdHash = hash('sha256', $rpId);
76
        if (hash_equals($rpIdHash, $attestationObject->getAuthData()->getRpIdHash())) {
77
            throw new \InvalidArgumentException('rpId hash mismatch.');
78
        }
79
80
        /* @see 7.1.10 */
81
        if (!$attestationObject->getAuthData()->isUserPresent()) {
82
            throw new \InvalidArgumentException('User was not present');
83
        }
84
85
        /* @see 7.1.11 */
86
        if (AuthenticatorSelectionCriteria::USER_VERIFICATION_REQUIREMENT_REQUIRED === $publicKeyCredentialCreationOptions->getAuthenticatorSelection()->getUserVerification() && !$attestationObject->getAuthData()->isUserVerified()) {
87
            throw new \InvalidArgumentException('User authentication required.');
88
        }
89
90
        /* @see 7.1.12 */
91
        if (0 !== $publicKeyCredentialCreationOptions->getExtensions()->count()) {
92
            throw new \InvalidArgumentException('Extensions not supported.');
93
        }
94
95
        /** @see 7.1.13 */
96
        $fmt = $attestationObject->getAttStmt()->getFmt();
97
        if (!$this->attestationStatementSupportManager->has($fmt)) {
98
            throw new \InvalidArgumentException('Unsuppoorted attestation statement format.');
99
        }
100
101
        /** @see 7.1.14 */
102
        $attestationStatementSupport = $this->attestationStatementSupportManager->get($fmt);
103
        if (!$attestationStatementSupport->isValid($attestationObject->getAttStmt(), $attestationObject->getAuthData(), $C)) {
104
            throw new \InvalidArgumentException('Unvalid attestation statement.');
105
        }
106
107
        /** @see 7.1.15 */
108
        /** @see 7.1.16 */
109
        /** @see 7.1.17 */
110
        $credentialId = $attestationObject->getAuthData()->getAttestedCredentialData()->getCredentialId();
111
        if ($this->credentialRepository->hasCredentialId($credentialId)) {
112
            throw new \InvalidArgumentException('The credential ID already exists.');
113
        }
114
115
        /* @see 7.1.18 */
116
        /* @see 7.1.19 */
117
    }
118
}
119