[WebAuthN] Implement PublicKeyCredential’s [[DiscoverFromExternalSource]] with a...
[WebKit-https.git] / Source / WebCore / Modules / webauthn / PublicKeyCredential.cpp
1 /*
2  * Copyright (C) 2017 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 "PublicKeyCredential.h"
28
29 #include "Authenticator.h"
30 #include "AuthenticatorResponse.h"
31 #include "JSDOMPromiseDeferred.h"
32 #include "PublicKeyCredentialCreationOptions.h"
33 #include "PublicKeyCredentialRequestOptions.h"
34 #include "SecurityOrigin.h"
35 #include <pal/crypto/CryptoDigest.h>
36 #include <wtf/CurrentTime.h>
37 #include <wtf/JSONValues.h>
38 #include <wtf/text/Base64.h>
39
40 namespace WebCore {
41
42 namespace PublicKeyCredentialInternal {
43
44 // The layout of attestation object: https://www.w3.org/TR/webauthn/#attestation-object as of 5 December 2017.
45 // Here is a summary before CredentialID in the layout. All lengths are fixed.
46 // RP ID hash (32) || FLAGS (1) || COUNTER (4) || AAGUID (16) || L (2) || CREDENTIAL ID (?) || ...
47 static constexpr size_t CredentialIdLengthOffset = 43;
48
49 enum class ClientDataType {
50     Create,
51     Get
52 };
53
54 // FIXME(181948): Add token binding ID and extensions.
55 static Ref<ArrayBuffer> produceClientDataJson(ClientDataType type, const BufferSource& challenge, const SecurityOrigin& origin)
56 {
57     auto object = JSON::Object::create();
58     switch (type) {
59     case ClientDataType::Create:
60         object->setString(ASCIILiteral("type"), ASCIILiteral("webauthn.create"));
61         break;
62     case ClientDataType::Get:
63         object->setString(ASCIILiteral("type"), ASCIILiteral("webauthn.get"));
64         break;
65     }
66     object->setString(ASCIILiteral("challenge"), WTF::base64URLEncode(challenge.data(), challenge.length()));
67     object->setString(ASCIILiteral("origin"), origin.toRawString());
68     // FIXME: This might be platform dependent.
69     object->setString(ASCIILiteral("hashAlgorithm"), ASCIILiteral("SHA-256"));
70
71     auto utf8JSONString = object->toJSONString().utf8();
72     return ArrayBuffer::create(utf8JSONString.data(), utf8JSONString.length());
73 }
74
75 static Vector<uint8_t> produceClientDataJsonHash(const Ref<ArrayBuffer>& clientDataJson)
76 {
77     auto crypto = PAL::CryptoDigest::create(PAL::CryptoDigest::Algorithm::SHA_256);
78     crypto->addBytes(clientDataJson->data(), clientDataJson->byteLength());
79     return crypto->computeHash();
80 }
81
82 static RefPtr<ArrayBuffer> getIdFromAttestationObject(const Vector<uint8_t>& attestationObject)
83 {
84     // The byte length of L is 2.
85     if (attestationObject.size() < CredentialIdLengthOffset + 2)
86         return nullptr;
87     size_t length = (attestationObject[CredentialIdLengthOffset] << 8) + attestationObject[CredentialIdLengthOffset + 1];
88     if (attestationObject.size() < CredentialIdLengthOffset + 2 + length)
89         return nullptr;
90     return ArrayBuffer::create(attestationObject.data() + CredentialIdLengthOffset + 2, length);
91 }
92
93 } // namespace PublicKeyCredentialInternal
94
95 PublicKeyCredential::PublicKeyCredential(RefPtr<ArrayBuffer>&& id, RefPtr<AuthenticatorResponse>&& response)
96     : BasicCredential(WTF::base64URLEncode(id->data(), id->byteLength()), Type::PublicKey, Discovery::Remote)
97     , m_rawId(WTFMove(id))
98     , m_response(WTFMove(response))
99 {
100 }
101
102 Vector<Ref<BasicCredential>> PublicKeyCredential::collectFromCredentialStore(PublicKeyCredentialRequestOptions&&, bool)
103 {
104     return { };
105 }
106
107 ExceptionOr<RefPtr<BasicCredential>> PublicKeyCredential::discoverFromExternalSource(const SecurityOrigin& callerOrigin, const PublicKeyCredentialRequestOptions& options, bool sameOriginWithAncestors)
108 {
109     using namespace PublicKeyCredentialInternal;
110
111     // The following implements https://www.w3.org/TR/webauthn/#createCredential as of 5 December 2017.
112     // FIXME: Extensions are not supported yet. Skip Step 8-9.
113     // Step 1, 3-4, 13, 16 are handled by the caller, including options sanitizing, timer and abort signal.
114     // Step 2.
115     if (!sameOriginWithAncestors)
116         return Exception { NotAllowedError };
117
118     // Step 5-7.
119     // FIXME(181950): We lack fundamental support from SecurityOrigin to determine if a host is a valid domain or not.
120     // Step 6 is therefore skipped. Also, we lack the support to determine whether a domain is a registrable
121     // domain suffix of another domain. Hence restrict the comparison to equal in Step 7.
122     if (!options.rpId.isEmpty() && !(callerOrigin.host() == options.rpId))
123         return Exception { SecurityError };
124     if (options.rpId.isEmpty())
125         options.rpId = callerOrigin.host();
126
127     // Step 10-12.
128     auto clientDataJson = produceClientDataJson(ClientDataType::Get, options.challenge, callerOrigin);
129     auto clientDataJsonHash = produceClientDataJsonHash(clientDataJson);
130
131     // Step 14-15, 17-19.
132     // Only platform attachments will be supported at this stage. Assuming one authenticator per device.
133     // Also, resident keys, user verifications and direct attestation are enforced at this tage.
134     // For better performance, no filtering is done here regarding to options.excludeCredentials.
135     // What's more, user cancellations effectively means NotAllowedError. Therefore, the below call
136     // will only returns either an exception or a PublicKeyCredential ref.
137     // FIXME(181946): The following operation might need to perform async.
138     auto result = Authenticator::singleton().getAssertion(options.rpId, clientDataJsonHash, options.allowCredentials);
139     if (result.hasException())
140         return result.releaseException();
141
142     auto bundle = result.releaseReturnValue();
143     return ExceptionOr<RefPtr<BasicCredential>>(PublicKeyCredential::create(WTFMove(bundle.credentialID), AuthenticatorAssertionResponse::create(WTFMove(clientDataJson), WTFMove(bundle.authenticatorData), WTFMove(bundle.signature), WTFMove(bundle.userHandle))));
144 }
145
146 RefPtr<BasicCredential> PublicKeyCredential::store(RefPtr<BasicCredential>&&, bool)
147 {
148     return nullptr;
149 }
150
151 ExceptionOr<RefPtr<BasicCredential>> PublicKeyCredential::create(const SecurityOrigin& callerOrigin, const PublicKeyCredentialCreationOptions& options, bool sameOriginWithAncestors)
152 {
153     using namespace PublicKeyCredentialInternal;
154
155     // The following implements https://www.w3.org/TR/webauthn/#createCredential as of 5 December 2017.
156     // FIXME: Extensions are not supported yet. Skip Step 11-12.
157     // Step 1, 3-4, 16-17 are handled by the caller, including options sanitizing, timer and abort signal.
158     // Step 2.
159     if (!sameOriginWithAncestors)
160         return Exception { NotAllowedError };
161
162     // Step 5-7.
163     // FIXME(181950): We lack fundamental support from SecurityOrigin to determine if a host is a valid domain or not.
164     // Step 6 is therefore skipped. Also, we lack the support to determine whether a domain is a registrable
165     // domain suffix of another domain. Hence restrict the comparison to equal in Step 7.
166     if (!options.rp.id.isEmpty() && !(callerOrigin.host() == options.rp.id))
167         return Exception { SecurityError };
168     if (options.rp.id.isEmpty())
169         options.rp.id = callerOrigin.host();
170
171     // Step 8-10.
172     // Most of the jobs are done by bindings. However, we can't know if the JSValue of options.pubKeyCredParams
173     // is empty or not. Return NotSupportedError as long as it is empty.
174     if (options.pubKeyCredParams.isEmpty())
175         return Exception { NotSupportedError };
176
177     // Step 13-15.
178     auto clientDataJson = produceClientDataJson(ClientDataType::Create, options.challenge, callerOrigin);
179     auto clientDataJsonHash = produceClientDataJsonHash(clientDataJson);
180
181     // Step 18-21.
182     // Only platform attachments will be supported at this stage. Assuming one authenticator per device.
183     // Also, resident keys, user verifications and direct attestation are enforced at this tage.
184     // For better performance, no filtering is done here regarding to options.excludeCredentials.
185     // What's more, user cancellations effectively means NotAllowedError. Therefore, the below call
186     // will only returns either an exception or a PublicKeyCredential ref.
187     // FIXME(181946): The following operation might need to perform async.
188     auto result = Authenticator::singleton().makeCredential(clientDataJsonHash, options.rp, options.user, options.pubKeyCredParams, options.excludeCredentials);
189     if (result.hasException())
190         return result.releaseException();
191
192     auto attestationObject = result.releaseReturnValue();
193     return ExceptionOr<RefPtr<BasicCredential>>(PublicKeyCredential::create(getIdFromAttestationObject(attestationObject), AuthenticatorAttestationResponse::create(WTFMove(clientDataJson), ArrayBuffer::create(attestationObject.data(), attestationObject.size()))));
194 }
195
196 ArrayBuffer* PublicKeyCredential::rawId() const
197 {
198     return m_rawId.get();
199 }
200
201 AuthenticatorResponse* PublicKeyCredential::response() const
202 {
203     return m_response.get();
204 }
205
206 ExceptionOr<bool> PublicKeyCredential::getClientExtensionResults() const
207 {
208     return Exception { NotSupportedError };
209 }
210
211 void PublicKeyCredential::isUserVerifyingPlatformAuthenticatorAvailable(Ref<DeferredPromise>&& promise)
212 {
213     promise->reject(Exception { NotSupportedError });
214 }
215
216 } // namespace WebCore