RegistrationVerifier::verify()   A
last analyzed

Complexity

Conditions 2
Paths 3

Size

Total Lines 57
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 2.0078

Importance

Changes 0
Metric Value
cc 2
eloc 16
c 0
b 0
f 0
nc 3
nop 2
dl 0
loc 57
ccs 14
cts 16
cp 0.875
crap 2.0078
rs 9.7333

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace MadWizard\WebAuthn\Server\Registration;
4
5
use MadWizard\WebAuthn\Attestation\AttestationObject;
6
use MadWizard\WebAuthn\Attestation\AuthenticatorData;
7
use MadWizard\WebAuthn\Attestation\Registry\AttestationFormatRegistryInterface;
8
use MadWizard\WebAuthn\Credential\CredentialId;
9
use MadWizard\WebAuthn\Dom\CollectedClientData;
10
use MadWizard\WebAuthn\Dom\PublicKeyCredentialInterface;
11
use MadWizard\WebAuthn\Dom\TokenBindingStatus;
12
use MadWizard\WebAuthn\Exception\FormatNotSupportedException;
13
use MadWizard\WebAuthn\Exception\UnsupportedException;
14
use MadWizard\WebAuthn\Exception\VerificationException;
15
use MadWizard\WebAuthn\Extension\ExtensionInterface;
16
use MadWizard\WebAuthn\Extension\ExtensionProcessingContext;
17
use MadWizard\WebAuthn\Extension\ExtensionRegistryInterface;
18
use MadWizard\WebAuthn\Server\AbstractVerifier;
19
20
final class RegistrationVerifier extends AbstractVerifier
21
{
22
    /**
23
     * @var AttestationFormatRegistryInterface
24
     */
25
    private $formatRegistry;
26
27 1
    public function __construct(AttestationFormatRegistryInterface $registry, ExtensionRegistryInterface $extensionRegistry)
28
    {
29 1
        parent::__construct($extensionRegistry);
30 1
        $this->formatRegistry = $registry;
31 1
    }
32
33
    /**
34
     * @throws VerificationException
35
     * @throws \MadWizard\WebAuthn\Exception\ParseException
36
     * @throws \MadWizard\WebAuthn\Exception\WebAuthnException
37
     */
38 1
    public function verify(PublicKeyCredentialInterface $credential, RegistrationContext $context): RegistrationResult
39
    {
40
        // SPEC 7.1 Registering a new credential
41 1
        $response = $credential->getResponse()->asAttestationResponse();
42
43
        // 1. Let JSONtext be the result of running UTF-8 decode on the value of response.clientDataJSON.
44
        // 2. Let C, the client data claimed as collected during the credential creation, be the result of running an
45
        //    implementation-specific JSON parser on JSONtext.
46
        // 3 - 6
47 1
        $clientData = $response->getParsedClientData();
48 1
        $this->checkClientData($clientData, $context);
49
50
        // 7. Compute the hash of response.clientDataJSON using SHA-256.
51 1
        $clientDataHash = $this->getClientDataHash($response);
52
53
        // 8. Perform CBOR decoding on the attestationObject field of the AuthenticatorAttestationResponse structure to
54
        //    obtain the attestation statement format fmt, the authenticator data authData, and the attestation
55
        //    statement attStmt.
56
57 1
        $attestation = AttestationObject::parse($response->getAttestationObject());
58 1
        $authData = new AuthenticatorData($attestation->getAuthenticatorData());
59
60 1
        $extensionContext = $this->processExtensions($credential, $authData, $context, ExtensionInterface::OPERATION_REGISTRATION);
61
62
        // 9 - 11
63 1
        $this->checkAuthenticatorData($authData, $context, $extensionContext);
64
65
        // 12. Verify that the values of the client extension outputs in clientExtensionResults and the authenticator
66
        //     extension outputs in the extensions in authData are as expected, considering the client extension input
67
        //     values that were given as the extensions option in the create() call. In particular, any extension
68
        //     identifier values in the clientExtensionResults and the extensions in authData MUST be also be present
69
        //     as extension identifier values in the extensions member of options, i.e., no extensions are present that
70
        //     were not requested. In the general case, the meaning of "are as expected" is specific to the
71
        //     Relying Party and which extensions are in use.
72
        //     Note: Since all extensions are OPTIONAL for both the client and the authenticator, the Relying Party MUST
73
        //     be prepared to handle cases where none or not all of the requested extensions were acted upon.
74
75
        // -> This is already checked in processExtensions above, the extensions need to be processed earlier because
76
        // extensions such as appid affect the effective rpId
77
78
        // 13. Determine the attestation statement format by performing a USASCII case-sensitive match on fmt against
79
        //     the set of supported WebAuthn Attestation Statement Format Identifier values.
80 1
        $format = $attestation->getFormat();
81
82
        try {
83 1
            $statement = $this->formatRegistry->createStatement($attestation);
84 1
            $verifier = $this->formatRegistry->getVerifier($attestation->getFormat());
85
        } catch (FormatNotSupportedException $e) {
86
            throw new VerificationException(sprintf("Attestation format '%s' not supported", $format), 0, $e);
87
        }
88
89
        // 14. Verify that attStmt is a correct attestation statement, conveying a valid attestation signature, by
90
        //     using the attestation statement format fmt’s verification procedure given attStmt, authData and the hash
91
        //     of the serialized client data computed in step 7.
92 1
        $verificationResult = $verifier->verify($statement, $authData, $clientDataHash);
93
94 1
        return new RegistrationResult(CredentialId::fromBuffer($credential->getRawId()), $authData, $attestation, $verificationResult, $context->getUserHandle());
95
    }
96
97 1
    private function checkClientData(CollectedClientData $clientData, RegistrationContext $context): void
98
    {
99
        // 3. Verify that the value of C.type is webauthn.create.
100 1
        if ($clientData->getType() !== 'webauthn.create') {
101
            throw new VerificationException('Expecting type in clientDataJSON to be webauthn.create.');
102
        }
103
104
        // 4. Verify that the value of C.challenge matches the challenge that was sent to the authenticator
105
        //    in the create() call.
106 1
        if (!hash_equals($context->getChallenge()->getBase64Url(), $clientData->getChallenge())) {
107
            throw new VerificationException('Challenge in clientDataJSON does not match the challenge in the request.');
108
        }
109
110
        // 5. Verify that the value of C.origin matches the Relying Party's origin.
111 1
        if (!$this->verifyOrigin($clientData->getOrigin(), $context->getOrigin())) {
112
            throw new VerificationException(sprintf("Origin '%s' does not match relying party origin.", $clientData->getOrigin()));
113
        }
114
115
        // 6. Verify that the value of C.tokenBinding.status matches the state of Token Binding for the TLS connection
116
        //    over which the assertion was obtained. If Token Binding was used on that TLS connection, also verify that
117
        //    C.tokenBinding.id matches the base64url encoding of the Token Binding ID for the connection.
118 1
        $tokenBinding = $clientData->getTokenBinding();
119 1
        if ($tokenBinding !== null && $tokenBinding->getStatus() === TokenBindingStatus::PRESENT) {
120
            throw new UnsupportedException('Token binding is not yet supported by this library.');
121
        }
122 1
    }
123
124 1
    private function checkAuthenticatorData(AuthenticatorData $authData, RegistrationContext $context, ExtensionProcessingContext $extensionContext): void
125
    {
126 1
        if (!$authData->hasAttestedCredentialData()) {
127
            throw new VerificationException('Authenticator data does not contain attested credential.');
128
        }
129
130
        // 9. Verify that the RP ID hash in authData is indeed the SHA-256 hash of the RP ID expected by the RP.
131 1
        if (!$this->verifyRpIdHash($authData, $context, $extensionContext)) {
132
            throw new VerificationException('RP ID hash in authData does not match.');
133
        }
134
135
        // 10 and 11
136 1
        if (!$this->verifyUser($authData, $context)) {
137
            throw new VerificationException('User verification failed');
138
        }
139 1
    }
140
}
141