7f46cfbf3bdd00299ed320dd2d8ca5e8ac9d410a
[WebKit-https.git] / Source / WebKit / UIProcess / WebAuthentication / fido / CtapAuthenticator.cpp
1 /*
2  * Copyright (C) 2018 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #include "config.h"
27 #include "CtapAuthenticator.h"
28
29 #if ENABLE(WEB_AUTHN)
30
31 #include "CtapDriver.h"
32 #include "CtapHidDriver.h"
33 #include "U2fAuthenticator.h"
34 #include <WebCore/CryptoKeyAES.h>
35 #include <WebCore/CryptoKeyEC.h>
36 #include <WebCore/CryptoKeyHMAC.h>
37 #include <WebCore/DeviceRequestConverter.h>
38 #include <WebCore/DeviceResponseConverter.h>
39 #include <WebCore/ExceptionData.h>
40 #include <WebCore/Pin.h>
41 #include <WebCore/U2fCommandConstructor.h>
42 #include <wtf/RunLoop.h>
43 #include <wtf/text/StringConcatenateNumbers.h>
44
45 namespace WebKit {
46 using namespace WebCore;
47 using namespace fido;
48
49 namespace {
50 WebAuthenticationStatus toStatus(const CtapDeviceResponseCode& error)
51 {
52     switch (error) {
53     case CtapDeviceResponseCode::kCtap2ErrPinAuthInvalid:
54     case CtapDeviceResponseCode::kCtap2ErrPinInvalid:
55         return WebAuthenticationStatus::PinInvalid;
56     case CtapDeviceResponseCode::kCtap2ErrPinAuthBlocked:
57         return WebAuthenticationStatus::PinAuthBlocked;
58     case CtapDeviceResponseCode::kCtap2ErrPinBlocked:
59         return WebAuthenticationStatus::PinBlocked;
60     default:
61         ASSERT_NOT_REACHED();
62         return WebAuthenticationStatus::PinInvalid;
63     }
64 }
65
66 bool isPinError(const CtapDeviceResponseCode& error)
67 {
68     switch (error) {
69     case CtapDeviceResponseCode::kCtap2ErrPinAuthInvalid:
70     case CtapDeviceResponseCode::kCtap2ErrPinAuthBlocked:
71     case CtapDeviceResponseCode::kCtap2ErrPinInvalid:
72     case CtapDeviceResponseCode::kCtap2ErrPinBlocked:
73         return true;
74     default:
75         return false;
76     }
77 }
78
79 } // namespace
80
81 CtapAuthenticator::CtapAuthenticator(std::unique_ptr<CtapDriver>&& driver, AuthenticatorGetInfoResponse&& info)
82     : FidoAuthenticator(WTFMove(driver))
83     , m_info(WTFMove(info))
84 {
85 }
86
87 void CtapAuthenticator::makeCredential()
88 {
89     ASSERT(!m_isDowngraded);
90     if (processGoogleLegacyAppIdSupportExtension())
91         return;
92     Vector<uint8_t> cborCmd;
93     auto& options = WTF::get<PublicKeyCredentialCreationOptions>(requestData().options);
94     if (m_info.options().clientPinAvailability() == AuthenticatorSupportedOptions::ClientPinAvailability::kSupportedAndPinSet)
95         cborCmd = encodeMakeCredenitalRequestAsCBOR(requestData().hash, options, m_info.options().userVerificationAvailability(), PinParameters { pin::kProtocolVersion, m_pinAuth });
96     else
97         cborCmd = encodeMakeCredenitalRequestAsCBOR(requestData().hash, options, m_info.options().userVerificationAvailability());
98     driver().transact(WTFMove(cborCmd), [weakThis = makeWeakPtr(*this)](Vector<uint8_t>&& data) {
99         ASSERT(RunLoop::isMain());
100         if (!weakThis)
101             return;
102         weakThis->continueMakeCredentialAfterResponseReceived(WTFMove(data));
103     });
104 }
105
106 void CtapAuthenticator::continueMakeCredentialAfterResponseReceived(Vector<uint8_t>&& data)
107 {
108     auto response = readCTAPMakeCredentialResponse(data, WTF::get<PublicKeyCredentialCreationOptions>(requestData().options).attestation);
109     if (!response) {
110         auto error = getResponseCode(data);
111
112         if (error == CtapDeviceResponseCode::kCtap2ErrCredentialExcluded) {
113             receiveRespond(ExceptionData { InvalidStateError, "At least one credential matches an entry of the excludeCredentials list in the authenticator."_s });
114             return;
115         }
116
117         if (isPinError(error)) {
118             if (!m_pinAuth.isEmpty()) // Skip the very first command that acts like wink.
119                 observer()->authenticatorStatusUpdated(toStatus(error));
120             if (tryRestartPin(error))
121                 return;
122         }
123
124         receiveRespond(ExceptionData { UnknownError, makeString("Unknown internal error. Error code: ", static_cast<uint8_t>(error)) });
125         return;
126     }
127     receiveRespond(response.releaseNonNull());
128 }
129
130 void CtapAuthenticator::getAssertion()
131 {
132     ASSERT(!m_isDowngraded);
133     Vector<uint8_t> cborCmd;
134     auto& options = WTF::get<PublicKeyCredentialRequestOptions>(requestData().options);
135     if (m_info.options().clientPinAvailability() == AuthenticatorSupportedOptions::ClientPinAvailability::kSupportedAndPinSet && options.userVerification != UserVerificationRequirement::Discouraged)
136         cborCmd = encodeGetAssertionRequestAsCBOR(requestData().hash, options, m_info.options().userVerificationAvailability(), PinParameters { pin::kProtocolVersion, m_pinAuth });
137     else
138         cborCmd = encodeGetAssertionRequestAsCBOR(requestData().hash, options, m_info.options().userVerificationAvailability());
139     driver().transact(WTFMove(cborCmd), [weakThis = makeWeakPtr(*this)](Vector<uint8_t>&& data) {
140         ASSERT(RunLoop::isMain());
141         if (!weakThis)
142             return;
143         weakThis->continueGetAssertionAfterResponseReceived(WTFMove(data));
144     });
145 }
146
147 void CtapAuthenticator::continueGetAssertionAfterResponseReceived(Vector<uint8_t>&& data)
148 {
149     auto response = readCTAPGetAssertionResponse(data);
150     if (!response) {
151         auto error = getResponseCode(data);
152
153         if (!isPinError(error) && tryDowngrade())
154             return;
155
156         if (isPinError(error)) {
157             if (!m_pinAuth.isEmpty()) // Skip the very first command that acts like wink.
158                 observer()->authenticatorStatusUpdated(toStatus(error));
159             if (tryRestartPin(error))
160                 return;
161         }
162
163         if (error == CtapDeviceResponseCode::kCtap2ErrNoCredentials && observer())
164             observer()->authenticatorStatusUpdated(WebAuthenticationStatus::NoCredentialsFound);
165
166         receiveRespond(ExceptionData { UnknownError, makeString("Unknown internal error. Error code: ", static_cast<uint8_t>(error)) });
167         return;
168     }
169
170     if (response->numberOfCredentials() <= 1) {
171         receiveRespond(response.releaseNonNull());
172         return;
173     }
174
175     m_remainingAssertionResponses = response->numberOfCredentials() - 1;
176     m_assertionResponses.reserveInitialCapacity(response->numberOfCredentials());
177     m_assertionResponses.uncheckedAppend(response.releaseNonNull());
178     driver().transact(encodeEmptyAuthenticatorRequest(CtapRequestCommand::kAuthenticatorGetNextAssertion), [weakThis = makeWeakPtr(*this)](Vector<uint8_t>&& data) {
179         ASSERT(RunLoop::isMain());
180         if (!weakThis)
181             return;
182         weakThis->continueGetNextAssertionAfterResponseReceived(WTFMove(data));
183     });
184 }
185
186 void CtapAuthenticator::continueGetNextAssertionAfterResponseReceived(Vector<uint8_t>&& data)
187 {
188     auto response = readCTAPGetAssertionResponse(data);
189     if (!response) {
190         auto error = getResponseCode(data);
191         receiveRespond(ExceptionData { UnknownError, makeString("Unknown internal error. Error code: ", static_cast<uint8_t>(error)) });
192         return;
193     }
194     m_remainingAssertionResponses--;
195     m_assertionResponses.uncheckedAppend(response.releaseNonNull());
196
197     if (!m_remainingAssertionResponses) {
198         if (auto* observer = this->observer()) {
199             Vector<Ref<AuthenticatorAssertionResponse>> responsesCopy;
200             responsesCopy.reserveInitialCapacity(m_assertionResponses.size());
201             for (auto& response : m_assertionResponses)
202                 responsesCopy.uncheckedAppend(response.copyRef());
203
204             observer->selectAssertionResponse(WTFMove(responsesCopy), WebAuthenticationSource::External, [this, weakThis = makeWeakPtr(*this)] (AuthenticatorAssertionResponse* response) {
205                 ASSERT(RunLoop::isMain());
206                 if (!weakThis)
207                     return;
208                 auto result = m_assertionResponses.findMatching([expectedResponse = response] (auto& response) {
209                     return response.ptr() == expectedResponse;
210                 });
211                 if (result == notFound)
212                     return;
213                 receiveRespond(m_assertionResponses[result].copyRef());
214             });
215         }
216         return;
217     }
218
219     driver().transact(encodeEmptyAuthenticatorRequest(CtapRequestCommand::kAuthenticatorGetNextAssertion), [weakThis = makeWeakPtr(*this)](Vector<uint8_t>&& data) {
220         ASSERT(RunLoop::isMain());
221         if (!weakThis)
222             return;
223         weakThis->continueGetNextAssertionAfterResponseReceived(WTFMove(data));
224     });
225 }
226
227 void CtapAuthenticator::getRetries()
228 {
229     auto cborCmd = encodeAsCBOR(pin::RetriesRequest { });
230     driver().transact(WTFMove(cborCmd), [weakThis = makeWeakPtr(*this)](Vector<uint8_t>&& data) {
231         ASSERT(RunLoop::isMain());
232         if (!weakThis)
233             return;
234         weakThis->continueGetKeyAgreementAfterGetRetries(WTFMove(data));
235     });
236 }
237
238 void CtapAuthenticator::continueGetKeyAgreementAfterGetRetries(Vector<uint8_t>&& data)
239 {
240     auto retries = pin::RetriesResponse::parse(data);
241     if (!retries) {
242         auto error = getResponseCode(data);
243         receiveRespond(ExceptionData { UnknownError, makeString("Unknown internal error. Error code: ", static_cast<uint8_t>(error)) });
244         return;
245     }
246
247     auto cborCmd = encodeAsCBOR(pin::KeyAgreementRequest { });
248     driver().transact(WTFMove(cborCmd), [weakThis = makeWeakPtr(*this), retries = retries->retries] (Vector<uint8_t>&& data) {
249         ASSERT(RunLoop::isMain());
250         if (!weakThis)
251             return;
252         weakThis->continueRequestPinAfterGetKeyAgreement(WTFMove(data), retries);
253     });
254 }
255
256 void CtapAuthenticator::continueRequestPinAfterGetKeyAgreement(Vector<uint8_t>&& data, uint64_t retries)
257 {
258     auto keyAgreement = pin::KeyAgreementResponse::parse(data);
259     if (!keyAgreement) {
260         auto error = getResponseCode(data);
261         receiveRespond(ExceptionData { UnknownError, makeString("Unknown internal error. Error code: ", static_cast<uint8_t>(error)) });
262         return;
263     }
264
265     if (auto* observer = this->observer()) {
266         observer->requestPin(retries, [weakThis = makeWeakPtr(*this), keyAgreement = WTFMove(*keyAgreement)] (const String& pin) {
267             ASSERT(RunLoop::isMain());
268             if (!weakThis)
269                 return;
270             weakThis->continueGetPinTokenAfterRequestPin(pin, keyAgreement.peerKey);
271         });
272     }
273 }
274
275 void CtapAuthenticator::continueGetPinTokenAfterRequestPin(const String& pin, const CryptoKeyEC& peerKey)
276 {
277     auto pinUtf8 = pin::validateAndConvertToUTF8(pin);
278     if (!pinUtf8) {
279         receiveRespond(ExceptionData { UnknownError, makeString("Pin is not valid: ", pin) });
280         return;
281     }
282     auto tokenRequest = pin::TokenRequest::tryCreate(*pinUtf8, peerKey);
283     if (!tokenRequest) {
284         receiveRespond(ExceptionData { UnknownError, "Cannot create a TokenRequest."_s });
285         return;
286     }
287
288     auto cborCmd = encodeAsCBOR(*tokenRequest);
289     driver().transact(WTFMove(cborCmd), [weakThis = makeWeakPtr(*this), tokenRequest = WTFMove(*tokenRequest)] (Vector<uint8_t>&& data) {
290         ASSERT(RunLoop::isMain());
291         if (!weakThis)
292             return;
293         weakThis->continueRequestAfterGetPinToken(WTFMove(data), tokenRequest);
294     });
295 }
296
297 void CtapAuthenticator::continueRequestAfterGetPinToken(Vector<uint8_t>&& data, const fido::pin::TokenRequest& tokenRequest)
298 {
299     auto token = pin::TokenResponse::parse(tokenRequest.sharedKey(), data);
300     if (!token) {
301         auto error = getResponseCode(data);
302
303         if (isPinError(error)) {
304             observer()->authenticatorStatusUpdated(toStatus(error));
305             if (tryRestartPin(error))
306                 return;
307         }
308
309         receiveRespond(ExceptionData { UnknownError, makeString("Unknown internal error. Error code: ", static_cast<uint8_t>(error)) });
310         return;
311     }
312
313     m_pinAuth = token->pinAuth(requestData().hash);
314     WTF::switchOn(requestData().options, [&](const PublicKeyCredentialCreationOptions& options) {
315         makeCredential();
316     }, [&](const PublicKeyCredentialRequestOptions& options) {
317         getAssertion();
318     });
319 }
320
321 bool CtapAuthenticator::tryRestartPin(const CtapDeviceResponseCode& error)
322 {
323     switch (error) {
324     case CtapDeviceResponseCode::kCtap2ErrPinAuthInvalid:
325     case CtapDeviceResponseCode::kCtap2ErrPinInvalid:
326         getRetries();
327         return true;
328     default:
329         return false;
330     }
331 }
332
333 bool CtapAuthenticator::tryDowngrade()
334 {
335     if (m_info.versions().find(ProtocolVersion::kU2f) == m_info.versions().end())
336         return false;
337     if (!observer())
338         return false;
339
340     bool isConvertible = false;
341     WTF::switchOn(requestData().options, [&](const PublicKeyCredentialCreationOptions& options) {
342         isConvertible = isConvertibleToU2fRegisterCommand(options);
343     }, [&](const PublicKeyCredentialRequestOptions& options) {
344         isConvertible = isConvertibleToU2fSignCommand(options);
345     });
346     if (!isConvertible)
347         return false;
348
349     m_isDowngraded = true;
350     driver().setProtocol(ProtocolVersion::kU2f);
351     observer()->downgrade(this, U2fAuthenticator::create(releaseDriver()));
352     return true;
353 }
354
355 // Only U2F protocol is supported for Google legacy AppID support.
356 bool CtapAuthenticator::processGoogleLegacyAppIdSupportExtension()
357 {
358     auto& extensions = WTF::get<PublicKeyCredentialCreationOptions>(requestData().options).extensions;
359     if (!extensions) {
360         // AuthenticatorCoordinator::create should always set it.
361         ASSERT_NOT_REACHED();
362         return false;
363     }
364     if (extensions->googleLegacyAppidSupport)
365         tryDowngrade();
366     return extensions->googleLegacyAppidSupport;
367 }
368
369 } // namespace WebKit
370
371 #endif // ENABLE(WEB_AUTHN)