Confirm proper thread in UserGestureIndicator constructor
[WebKit-https.git] / Source / WebCore / Modules / webauthn / AuthenticatorManager.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 "AuthenticatorManager.h"
28
29 #if ENABLE(WEB_AUTHN)
30
31 #include "AbortSignal.h"
32 #include "AuthenticatorAssertionResponse.h"
33 #include "AuthenticatorAttestationResponse.h"
34 #include "CredentialsMessenger.h"
35 #include "JSBasicCredential.h"
36 #include "JSDOMPromiseDeferred.h"
37 #include "PublicKeyCredential.h"
38 #include "PublicKeyCredentialCreationOptions.h"
39 #include "PublicKeyCredentialRequestOptions.h"
40 #include "SecurityOrigin.h"
41 #include "Timer.h"
42 #include <pal/crypto/CryptoDigest.h>
43 #include <wtf/JSONValues.h>
44 #include <wtf/NeverDestroyed.h>
45 #include <wtf/text/Base64.h>
46
47 namespace WebCore {
48
49 namespace AuthenticatorManagerInternal {
50
51 enum class ClientDataType {
52     Create,
53     Get
54 };
55
56 // FIXME(181948): Add token binding ID and extensions.
57 static Ref<ArrayBuffer> produceClientDataJson(ClientDataType type, const BufferSource& challenge, const SecurityOrigin& origin)
58 {
59     auto object = JSON::Object::create();
60     switch (type) {
61     case ClientDataType::Create:
62         object->setString(ASCIILiteral("type"), ASCIILiteral("webauthn.create"));
63         break;
64     case ClientDataType::Get:
65         object->setString(ASCIILiteral("type"), ASCIILiteral("webauthn.get"));
66         break;
67     }
68     object->setString(ASCIILiteral("challenge"), WTF::base64URLEncode(challenge.data(), challenge.length()));
69     object->setString(ASCIILiteral("origin"), origin.toRawString());
70     // FIXME: This might be platform dependent.
71     object->setString(ASCIILiteral("hashAlgorithm"), ASCIILiteral("SHA-256"));
72
73     auto utf8JSONString = object->toJSONString().utf8();
74     return ArrayBuffer::create(utf8JSONString.data(), utf8JSONString.length());
75 }
76
77 static Vector<uint8_t> produceClientDataJsonHash(const ArrayBuffer& clientDataJson)
78 {
79     // FIXME: This might be platform dependent.
80     auto crypto = PAL::CryptoDigest::create(PAL::CryptoDigest::Algorithm::SHA_256);
81     crypto->addBytes(clientDataJson.data(), clientDataJson.byteLength());
82     return crypto->computeHash();
83 }
84
85 // FIXME(181947): We should probably trim timeOutInMs to some max allowable number.
86 static std::unique_ptr<Timer> initTimeoutTimer(std::optional<unsigned long> timeOutInMs, const Ref<DeferredPromise>& promise)
87 {
88     if (!timeOutInMs)
89         return nullptr;
90
91     auto timer = std::make_unique<Timer>([promise = promise.copyRef()] () {
92         promise->reject(Exception { NotAllowedError, ASCIILiteral("Operation timed out.") });
93     });
94     timer->startOneShot(Seconds::fromMilliseconds(*timeOutInMs));
95     return timer;
96 }
97
98 static bool didTimeoutTimerFire(Timer* timer)
99 {
100     if (!timer)
101         return false;
102     if (!timer->isActive())
103         return true;
104     timer->stop();
105     return false;
106 }
107
108 } // namespace AuthenticatorManagerInternal
109
110 AuthenticatorManager& AuthenticatorManager::singleton()
111 {
112     ASSERT(isMainThread());
113     static NeverDestroyed<AuthenticatorManager> authenticator;
114     return authenticator;
115 }
116
117 void AuthenticatorManager::setMessenger(CredentialsMessenger& messenger)
118 {
119     m_messenger = makeWeakPtr(messenger);
120 }
121
122 void AuthenticatorManager::create(const SecurityOrigin& callerOrigin, const PublicKeyCredentialCreationOptions& options, bool sameOriginWithAncestors, RefPtr<AbortSignal>&& abortSignal, Ref<DeferredPromise>&& promise) const
123 {
124     using namespace AuthenticatorManagerInternal;
125
126     // The following implements https://www.w3.org/TR/webauthn/#createCredential as of 5 December 2017.
127     // FIXME: Extensions are not supported yet. Skip Step 11-12.
128     // Step 1, 3, 16 are handled by the caller.
129     // Step 2.
130     if (!sameOriginWithAncestors) {
131         promise->reject(Exception { NotAllowedError, ASCIILiteral("The origin of the document is not the same as its ancestors.") });
132         return;
133     }
134
135     // Step 4 & 17.
136     std::unique_ptr<Timer> timeoutTimer = initTimeoutTimer(options.timeout, promise);
137
138     // Step 5-7.
139     // FIXME(181950): We lack fundamental support from SecurityOrigin to determine if a host is a valid domain or not.
140     // Step 6 is therefore skipped. Also, we lack the support to determine whether a domain is a registrable
141     // domain suffix of another domain. Hence restrict the comparison to equal in Step 7.
142     if (!options.rp.id.isEmpty() && callerOrigin.host() != options.rp.id) {
143         promise->reject(Exception { SecurityError, ASCIILiteral("The origin of the document is not a registrable domain suffix of the provided RP ID.") });
144         return;
145     }
146     if (options.rp.id.isEmpty())
147         options.rp.id = callerOrigin.host();
148
149     // Step 8-10.
150     // Most of the jobs are done by bindings. However, we can't know if the JSValue of options.pubKeyCredParams
151     // is empty or not. Return NotSupportedError as long as it is empty.
152     if (options.pubKeyCredParams.isEmpty()) {
153         promise->reject(Exception { NotSupportedError, ASCIILiteral("No desired properties of the to be created credential are provided.") });
154         return;
155     }
156
157     // Step 13-15.
158     auto clientDataJson = produceClientDataJson(ClientDataType::Create, options.challenge, callerOrigin);
159     auto clientDataJsonHash = produceClientDataJsonHash(clientDataJson);
160
161     // Step 18-21.
162     // Only platform attachments will be supported at this stage. Assuming one authenticator per device.
163     // Also, resident keys, user verifications and direct attestation are enforced at this tage.
164     // For better performance, no filtering is done here regarding to options.excludeCredentials.
165     if (!m_messenger)  {
166         promise->reject(Exception { UnknownError, ASCIILiteral("Unknown internal error.") });
167         return;
168     }
169
170     auto completionHandler = [clientDataJson = WTFMove(clientDataJson), promise = WTFMove(promise), timeoutTimer = WTFMove(timeoutTimer), abortSignal = WTFMove(abortSignal)] (ExceptionOr<CreationReturnBundle>&& result) mutable {
171         if (didTimeoutTimerFire(timeoutTimer.get()))
172             return;
173         if (abortSignal && abortSignal->aborted()) {
174             promise->reject(Exception { AbortError, ASCIILiteral("Aborted by AbortSignal.") });
175             return;
176         }
177         if (result.hasException()) {
178             promise->reject(result.exception());
179             return;
180         }
181
182         auto bundle = result.releaseReturnValue();
183         promise->resolve<IDLNullable<IDLInterface<BasicCredential>>>(PublicKeyCredential::create(WTFMove(bundle.credentialId), AuthenticatorAttestationResponse::create(WTFMove(clientDataJson), ArrayBuffer::create(WTFMove(bundle.attestationObject)))).ptr());
184     };
185     // Async operations are dispatched and handled in the messenger.
186     m_messenger->makeCredential(clientDataJsonHash, options, WTFMove(completionHandler));
187 }
188
189 void AuthenticatorManager::discoverFromExternalSource(const SecurityOrigin& callerOrigin, const PublicKeyCredentialRequestOptions& options, bool sameOriginWithAncestors, RefPtr<AbortSignal>&& abortSignal, Ref<DeferredPromise>&& promise) const
190 {
191     using namespace AuthenticatorManagerInternal;
192
193     // The following implements https://www.w3.org/TR/webauthn/#createCredential as of 5 December 2017.
194     // FIXME: Extensions are not supported yet. Skip Step 8-9.
195     // Step 1, 3, 13 are handled by the caller.
196     // Step 2.
197     if (!sameOriginWithAncestors) {
198         promise->reject(Exception { NotAllowedError, ASCIILiteral("The origin of the document is not the same as its ancestors.") });
199         return;
200     }
201
202     // Step 4 & 16.
203     std::unique_ptr<Timer> timeoutTimer = initTimeoutTimer(options.timeout, promise);
204
205     // Step 5-7.
206     // FIXME(181950): We lack fundamental support from SecurityOrigin to determine if a host is a valid domain or not.
207     // Step 6 is therefore skipped. Also, we lack the support to determine whether a domain is a registrable
208     // domain suffix of another domain. Hence restrict the comparison to equal in Step 7.
209     if (!options.rpId.isEmpty() && callerOrigin.host() != options.rpId) {
210         promise->reject(Exception { SecurityError, ASCIILiteral("The origin of the document is not a registrable domain suffix of the provided RP ID.") });
211         return;
212     }
213     if (options.rpId.isEmpty())
214         options.rpId = callerOrigin.host();
215
216     // Step 10-12.
217     auto clientDataJson = produceClientDataJson(ClientDataType::Get, options.challenge, callerOrigin);
218     auto clientDataJsonHash = produceClientDataJsonHash(clientDataJson);
219
220     // Step 14-15, 17-19.
221     // Only platform attachments will be supported at this stage. Assuming one authenticator per device.
222     // Also, resident keys, user verifications and direct attestation are enforced at this tage.
223     // For better performance, no filtering is done here regarding to options.allowCredentials.
224     if (!m_messenger)  {
225         promise->reject(Exception { UnknownError, ASCIILiteral("Unknown internal error.") });
226         return;
227     }
228
229     auto completionHandler = [clientDataJson = WTFMove(clientDataJson), promise = WTFMove(promise), timeoutTimer = WTFMove(timeoutTimer), abortSignal = WTFMove(abortSignal)] (ExceptionOr<AssertionReturnBundle>&& result) mutable {
230         if (didTimeoutTimerFire(timeoutTimer.get()))
231             return;
232         if (abortSignal && abortSignal->aborted()) {
233             promise->reject(Exception { AbortError, ASCIILiteral("Aborted by AbortSignal.") });
234             return;
235         }
236         if (result.hasException()) {
237             promise->reject(result.exception());
238             return;
239         }
240
241         auto bundle = result.releaseReturnValue();
242         promise->resolve<IDLNullable<IDLInterface<BasicCredential>>>(PublicKeyCredential::create(WTFMove(bundle.credentialId), AuthenticatorAssertionResponse::create(WTFMove(clientDataJson), WTFMove(bundle.authenticatorData), WTFMove(bundle.signature), WTFMove(bundle.userHandle))).ptr());
243     };
244     // Async operations are dispatched and handled in the messenger.
245     m_messenger->getAssertion(clientDataJsonHash, options, WTFMove(completionHandler));
246 }
247
248 } // namespace WebCore
249
250 #endif // ENABLE(WEB_AUTHN)