U2fService::verifyRegistration()   B
last analyzed

Complexity

Conditions 8
Paths 8

Size

Total Lines 47

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 47
rs 7.9119
c 0
b 0
f 0
cc 8
nc 8
nop 2
1
<?php
2
3
/**
4
 * Copyright 2014 SURFnet bv
5
 *
6
 * Licensed under the Apache License, Version 2.0 (the "License");
7
 * you may not use this file except in compliance with the License.
8
 * You may obtain a copy of the License at
9
 *
10
 *     http://www.apache.org/licenses/LICENSE-2.0
11
 *
12
 * Unless required by applicable law or agreed to in writing, software
13
 * distributed under the License is distributed on an "AS IS" BASIS,
14
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
 * See the License for the specific language governing permissions and
16
 * limitations under the License.
17
 */
18
19
namespace Surfnet\StepupU2fBundle\Service;
20
21
use stdClass;
22
use Surfnet\StepupU2fBundle\Dto\RegisterRequest;
23
use Surfnet\StepupU2fBundle\Dto\RegisterResponse;
24
use Surfnet\StepupU2fBundle\Dto\Registration;
25
use Surfnet\StepupU2fBundle\Dto\SignRequest;
26
use Surfnet\StepupU2fBundle\Dto\SignResponse;
27
use Surfnet\StepupU2fBundle\Exception\LogicException;
28
use Surfnet\StepupU2fBundle\Value\AppId;
29
use u2flib_server\Error;
30
use u2flib_server\RegisterRequest as YubicoRegisterRequest;
31
use u2flib_server\Registration as YubicoRegistration;
32
use u2flib_server\SignRequest as YubicoSignRequest;
33
use u2flib_server\U2F;
34
35
/**
36
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects) — Mainly due to the DTOs.
37
 */
38
class U2fService
39
{
40
    /**
41
     * @var \u2flib_server\U2F
42
     */
43
    private $u2fService;
44
45
    /**
46
     * @var AppId
47
     */
48
    private $appId;
49
50
    /**
51
     * @param AppId $appId
52
     * @param U2F    $u2fService
53
     */
54
    public function __construct(AppId $appId, U2F $u2fService)
55
    {
56
        $this->appId = $appId;
57
        $this->u2fService = $u2fService;
58
    }
59
60
    /**
61
     * @return RegisterRequest
62
     */
63
    public function createRegistrationRequest()
64
    {
65
        /** @var YubicoRegisterRequest $yubicoRequest */
66
        list($yubicoRequest) = $this->u2fService->getRegisterData();
67
68
        $request = new RegisterRequest();
69
        $request->version   = $yubicoRequest->version;
70
        $request->challenge = $yubicoRequest->challenge;
71
        $request->appId     = $yubicoRequest->appId;
72
73
        return $request;
74
    }
75
76
    /**
77
     * @param RegisterRequest  $request The register request that you requested earlier and was used to query the U2F
78
     *     device.
79
     * @param RegisterResponse $response The response that the U2F device gave in response to the register request.
80
     * @return RegistrationVerificationResult
81
     */
82
    public function verifyRegistration(RegisterRequest $request, RegisterResponse $response)
83
    {
84
        if (!$this->appId->equals(new AppId($request->appId))) {
85
            return RegistrationVerificationResult::appIdMismatch();
86
        }
87
88
        if ($response->errorCode > 0) {
89
            return RegistrationVerificationResult::deviceReportedError($response->errorCode);
90
        }
91
92
        $yubicoRequest = new YubicoRegisterRequest($request->challenge, $request->appId);
93
94
        $yubicoResponse = new stdClass;
95
        $yubicoResponse->clientData = $response->clientData;
96
        $yubicoResponse->registrationData = $response->registrationData;
97
98
        try {
99
            $yubicoRegistration = $this->u2fService->doRegister($yubicoRequest, $yubicoResponse);
100
        } catch (Error $error) {
101
            switch ($error->getCode()) {
102
                case \u2flib_server\ERR_UNMATCHED_CHALLENGE:
103
                    return RegistrationVerificationResult::responseChallengeDidNotMatchRequestChallenge();
104
                case \u2flib_server\ERR_ATTESTATION_SIGNATURE:
105
                    return RegistrationVerificationResult::responseWasNotSignedByDevice();
106
                case \u2flib_server\ERR_ATTESTATION_VERIFICATION:
107
                    return RegistrationVerificationResult::deviceCannotBeTrusted();
108
                case \u2flib_server\ERR_PUBKEY_DECODE:
109
                    return RegistrationVerificationResult::publicKeyDecodingFailed();
110
                default:
111
                    throw new LogicException(
112
                        sprintf(
113
                            'The Yubico U2F service threw an exception with error code %d that should not occur ("%s")',
114
                            $error->getCode(),
115
                            $error->getMessage()
116
                        ),
117
                        $error
118
                    );
119
            }
120
        }
121
122
        $registration = new Registration();
123
        $registration->keyHandle   = $yubicoRegistration->keyHandle;
124
        $registration->publicKey   = $yubicoRegistration->publicKey;
125
        $registration->signCounter = $yubicoRegistration->counter;
126
127
        return RegistrationVerificationResult::success($registration);
128
    }
129
130
    /**
131
     * @param Registration $registration
132
     * @return SignRequest
133
     */
134
    public function createSignRequest(Registration $registration)
135
    {
136
        $yubicoRegistration = new YubicoRegistration();
137
        $yubicoRegistration->keyHandle = $registration->keyHandle;
138
        $yubicoRegistration->publicKey = $registration->publicKey;
139
        $yubicoRegistration->counter   = $registration->signCounter;
140
141
        /** @var YubicoSignRequest $yubicoRequest */
142
        list($yubicoRequest) = $this->u2fService->getAuthenticateData([$yubicoRegistration]);
143
144
        $request = new SignRequest();
145
        $request->version   = $yubicoRequest->version;
146
        $request->challenge = $yubicoRequest->challenge;
147
        $request->appId     = $yubicoRequest->appId;
148
        $request->keyHandle = $yubicoRequest->keyHandle;
149
150
        return $request;
151
    }
152
153
    /**
154
     * Request signing of a sign request. Does not support U2F's sign counter system.
155
     *
156
     * @param Registration $registration The registration that is to be signed.
157
     * @param SignRequest  $request The sign request that you requested earlier and was used to query the U2F device.
158
     * @param SignResponse $response The response that the U2F device gave in response to the sign request.
159
     * @return AuthenticationVerificationResult
160
     */
161
    public function verifyAuthentication(Registration $registration, SignRequest $request, SignResponse $response)
162
    {
163
        if (!$this->appId->equals(new AppId($request->appId))) {
164
            return AuthenticationVerificationResult::appIdMismatch();
165
        }
166
167
        if ($response->errorCode > 0) {
168
            return AuthenticationVerificationResult::deviceReportedError($response->errorCode);
169
        }
170
171
        $yubicoRegistration = new YubicoRegistration();
172
        $yubicoRegistration->keyHandle = $registration->keyHandle;
173
        $yubicoRegistration->publicKey = $registration->publicKey;
174
        $yubicoRegistration->counter   = $registration->signCounter;
175
176
        $yubicoRequest = new YubicoSignRequest();
177
        $yubicoRequest->version   = $request->version;
178
        $yubicoRequest->challenge = $request->challenge;
179
        $yubicoRequest->appId     = $request->appId;
180
        $yubicoRequest->keyHandle = $request->keyHandle;
181
182
        $yubicoResponse = new stdClass;
183
        $yubicoResponse->keyHandle     = $response->keyHandle;
184
        $yubicoResponse->signatureData = $response->signatureData;
185
        $yubicoResponse->clientData    = $response->clientData;
186
187
        try {
188
            $yubicoRegistration = $this->u2fService->doAuthenticate(
189
                [$yubicoRequest],
190
                [$yubicoRegistration],
191
                $yubicoResponse
192
            );
193
        } catch (Error $error) {
194
            switch ($error->getCode()) {
195
                case \u2flib_server\ERR_NO_MATCHING_REQUEST:
196
                    return AuthenticationVerificationResult::requestResponseMismatch();
197
                case \u2flib_server\ERR_NO_MATCHING_REGISTRATION:
198
                    return AuthenticationVerificationResult::responseRegistrationMismatch();
199
                case \u2flib_server\ERR_PUBKEY_DECODE:
200
                    return AuthenticationVerificationResult::publicKeyDecodingFailed();
201
                case \u2flib_server\ERR_AUTHENTICATION_FAILURE:
202
                    return AuthenticationVerificationResult::responseWasNotSignedByDevice();
203
                case \u2flib_server\ERR_COUNTER_TOO_LOW:
204
                    return AuthenticationVerificationResult::signCounterTooLow();
205
                default:
206
                    throw new LogicException(
207
                        sprintf(
208
                            'The Yubico U2F service threw an exception with error code %d that should not occur ("%s")',
209
                            $error->getCode(),
210
                            $error->getMessage()
211
                        ),
212
                        $error
213
                    );
214
            }
215
        }
216
217
        $registration = new Registration();
218
        $registration->keyHandle   = $yubicoRegistration->keyHandle;
219
        $registration->publicKey   = $yubicoRegistration->publicKey;
220
        $registration->signCounter = $yubicoRegistration->counter;
221
222
        return AuthenticationVerificationResult::success($registration);
223
    }
224
}
225