[WebAuthn] Provide a _WKWebAuthenticationPanelUpdatePINInvalid update to UI clients...
[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() && observer()) // 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() && observer()) // 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     if (pin.isNull()) {
278         receiveRespond(ExceptionData { UnknownError, "Pin is null."_s });
279         return;
280     }
281
282     auto pinUtf8 = pin::validateAndConvertToUTF8(pin);
283     if (!pinUtf8) {
284         // Fake a pin invalid response from the authenticator such that clients could show some error to the user.
285         if (auto* observer = this->observer())
286             observer->authenticatorStatusUpdated(WebAuthenticationStatus::PinInvalid);
287         tryRestartPin(CtapDeviceResponseCode::kCtap2ErrPinInvalid);
288         return;
289     }
290     auto tokenRequest = pin::TokenRequest::tryCreate(*pinUtf8, peerKey);
291     if (!tokenRequest) {
292         receiveRespond(ExceptionData { UnknownError, "Cannot create a TokenRequest."_s });
293         return;
294     }
295
296     auto cborCmd = encodeAsCBOR(*tokenRequest);
297     driver().transact(WTFMove(cborCmd), [weakThis = makeWeakPtr(*this), tokenRequest = WTFMove(*tokenRequest)] (Vector<uint8_t>&& data) {
298         ASSERT(RunLoop::isMain());
299         if (!weakThis)
300             return;
301         weakThis->continueRequestAfterGetPinToken(WTFMove(data), tokenRequest);
302     });
303 }
304
305 void CtapAuthenticator::continueRequestAfterGetPinToken(Vector<uint8_t>&& data, const fido::pin::TokenRequest& tokenRequest)
306 {
307     auto token = pin::TokenResponse::parse(tokenRequest.sharedKey(), data);
308     if (!token) {
309         auto error = getResponseCode(data);
310
311         if (isPinError(error)) {
312             if (auto* observer = this->observer())
313                 observer->authenticatorStatusUpdated(toStatus(error));
314             if (tryRestartPin(error))
315                 return;
316         }
317
318         receiveRespond(ExceptionData { UnknownError, makeString("Unknown internal error. Error code: ", static_cast<uint8_t>(error)) });
319         return;
320     }
321
322     m_pinAuth = token->pinAuth(requestData().hash);
323     WTF::switchOn(requestData().options, [&](const PublicKeyCredentialCreationOptions& options) {
324         makeCredential();
325     }, [&](const PublicKeyCredentialRequestOptions& options) {
326         getAssertion();
327     });
328 }
329
330 bool CtapAuthenticator::tryRestartPin(const CtapDeviceResponseCode& error)
331 {
332     switch (error) {
333     case CtapDeviceResponseCode::kCtap2ErrPinAuthInvalid:
334     case CtapDeviceResponseCode::kCtap2ErrPinInvalid:
335         getRetries();
336         return true;
337     default:
338         return false;
339     }
340 }
341
342 bool CtapAuthenticator::tryDowngrade()
343 {
344     if (m_info.versions().find(ProtocolVersion::kU2f) == m_info.versions().end())
345         return false;
346     if (!observer())
347         return false;
348
349     bool isConvertible = false;
350     WTF::switchOn(requestData().options, [&](const PublicKeyCredentialCreationOptions& options) {
351         isConvertible = isConvertibleToU2fRegisterCommand(options);
352     }, [&](const PublicKeyCredentialRequestOptions& options) {
353         isConvertible = isConvertibleToU2fSignCommand(options);
354     });
355     if (!isConvertible)
356         return false;
357
358     m_isDowngraded = true;
359     driver().setProtocol(ProtocolVersion::kU2f);
360     observer()->downgrade(this, U2fAuthenticator::create(releaseDriver()));
361     return true;
362 }
363
364 // Only U2F protocol is supported for Google legacy AppID support.
365 bool CtapAuthenticator::processGoogleLegacyAppIdSupportExtension()
366 {
367     auto& extensions = WTF::get<PublicKeyCredentialCreationOptions>(requestData().options).extensions;
368     if (!extensions) {
369         // AuthenticatorCoordinator::create should always set it.
370         ASSERT_NOT_REACHED();
371         return false;
372     }
373     if (extensions->googleLegacyAppidSupport)
374         tryDowngrade();
375     return extensions->googleLegacyAppidSupport;
376 }
377
378 } // namespace WebKit
379
380 #endif // ENABLE(WEB_AUTHN)