OLD | NEW |
---|---|
1 // Copyright 2016 The Chromium Authors. All rights reserved. | 1 // Copyright 2017 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "modules/webauth/WebAuthentication.h" | 5 #include "modules/webauth/WebAuthentication.h" |
6 | 6 |
7 #include <stdint.h> | 7 #include <stdint.h> |
8 | 8 |
9 #include "bindings/core/v8/ScriptPromise.h" | 9 #include "bindings/core/v8/ScriptPromise.h" |
10 #include "bindings/core/v8/ScriptPromiseResolver.h" | 10 #include "bindings/core/v8/ScriptPromiseResolver.h" |
11 #include "core/dom/DOMException.h" | 11 #include "core/dom/DOMException.h" |
12 #include "core/dom/Document.h" | 12 #include "core/dom/Document.h" |
13 #include "core/dom/ExceptionCode.h" | 13 #include "core/dom/ExceptionCode.h" |
14 #include "core/frame/LocalFrame.h" | 14 #include "core/frame/LocalFrame.h" |
15 #include "modules/webauth/RelyingPartyAccount.h" | 15 #include "modules/webauth/RelyingPartyAccount.h" |
16 #include "modules/webauth/ScopedCredential.h" | 16 #include "modules/webauth/ScopedCredential.h" |
17 #include "modules/webauth/ScopedCredentialOptions.h" | 17 #include "modules/webauth/ScopedCredentialOptions.h" |
18 #include "modules/webauth/ScopedCredentialParameters.h" | 18 #include "modules/webauth/ScopedCredentialParameters.h" |
19 #include "public/platform/InterfaceProvider.h" | 19 #include "public/platform/InterfaceProvider.h" |
20 | 20 |
21 namespace { | 21 namespace { |
22 const char kNoAuthenticatorError[] = "Authenticator unavailable."; | 22 const char kNoAuthenticatorError[] = "Authenticator unavailable."; |
23 // Time to wait for an authenticator to successfully complete an operation. | |
24 static const double adjustedTimeoutLower = 60; | |
25 static const double adjustedTimeoutUpper = 120; | |
Mike West
2017/06/13 21:11:23
Nit: s/double/int/ (It's not clear why these need
kpaulhamus
2017/06/15 01:27:13
It is now, but it previously was seconds. Is it ok
| |
23 } // anonymous namespace | 26 } // anonymous namespace |
24 | 27 |
25 namespace mojo { | 28 namespace mojo { |
26 | |
27 using webauth::mojom::blink::RelyingPartyAccount; | 29 using webauth::mojom::blink::RelyingPartyAccount; |
28 using webauth::mojom::blink::RelyingPartyAccountPtr; | 30 using webauth::mojom::blink::RelyingPartyAccountPtr; |
31 using webauth::mojom::blink::AuthenticatorStatus; | |
32 using webauth::mojom::blink::ScopedCredentialDescriptor; | |
29 using webauth::mojom::blink::ScopedCredentialOptions; | 33 using webauth::mojom::blink::ScopedCredentialOptions; |
30 using webauth::mojom::blink::ScopedCredentialOptionsPtr; | 34 using webauth::mojom::blink::ScopedCredentialOptionsPtr; |
31 using webauth::mojom::blink::ScopedCredentialParameters; | 35 using webauth::mojom::blink::ScopedCredentialParameters; |
32 using webauth::mojom::blink::ScopedCredentialParametersPtr; | 36 using webauth::mojom::blink::ScopedCredentialParametersPtr; |
33 using webauth::mojom::blink::ScopedCredentialDescriptor; | |
34 using webauth::mojom::blink::ScopedCredentialType; | 37 using webauth::mojom::blink::ScopedCredentialType; |
35 using webauth::mojom::blink::Transport; | 38 using webauth::mojom::blink::Transport; |
36 | 39 |
37 // TODO(kpaulhamus): Make this a TypeConverter | 40 // TODO(kpaulhamus): Make this a TypeConverter |
38 Vector<uint8_t> ConvertBufferSource(const blink::BufferSource& buffer) { | 41 Vector<uint8_t> ConvertBufferSource(const blink::BufferSource& buffer) { |
39 DCHECK(buffer.isNull()); | 42 DCHECK(!buffer.isNull()); |
40 Vector<uint8_t> vector; | 43 Vector<uint8_t> vector; |
41 if (buffer.isArrayBuffer()) { | 44 if (buffer.isArrayBuffer()) { |
42 vector.Append(static_cast<uint8_t*>(buffer.getAsArrayBuffer()->Data()), | 45 vector.Append(static_cast<uint8_t*>(buffer.getAsArrayBuffer()->Data()), |
43 buffer.getAsArrayBuffer()->ByteLength()); | 46 buffer.getAsArrayBuffer()->ByteLength()); |
44 } else { | 47 } else { |
45 vector.Append(static_cast<uint8_t*>( | 48 vector.Append(static_cast<uint8_t*>( |
46 buffer.getAsArrayBufferView().View()->BaseAddress()), | 49 buffer.getAsArrayBufferView().View()->BaseAddress()), |
47 buffer.getAsArrayBufferView().View()->byteLength()); | 50 buffer.getAsArrayBufferView().View()->byteLength()); |
48 } | 51 } |
49 return vector; | 52 return vector; |
(...skipping 12 matching lines...) Expand all Loading... | |
62 if (transport == "usb") | 65 if (transport == "usb") |
63 return Transport::USB; | 66 return Transport::USB; |
64 if (transport == "nfc") | 67 if (transport == "nfc") |
65 return Transport::NFC; | 68 return Transport::NFC; |
66 if (transport == "ble") | 69 if (transport == "ble") |
67 return Transport::BLE; | 70 return Transport::BLE; |
68 NOTREACHED(); | 71 NOTREACHED(); |
69 return Transport::USB; | 72 return Transport::USB; |
70 } | 73 } |
71 | 74 |
75 // TODO(kpaulhamus): Make this a TypeConverter | |
72 RelyingPartyAccountPtr ConvertRelyingPartyAccount( | 76 RelyingPartyAccountPtr ConvertRelyingPartyAccount( |
73 const blink::RelyingPartyAccount& account_information, | 77 const blink::RelyingPartyAccount& account_information, |
74 blink::ScriptPromiseResolver* resolver) { | 78 blink::ScriptPromiseResolver* resolver) { |
75 auto mojo_account = RelyingPartyAccount::New(); | 79 auto mojo_account = RelyingPartyAccount::New(); |
76 | 80 |
77 mojo_account->relying_party_display_name = | 81 mojo_account->relying_party_display_name = |
78 account_information.rpDisplayName(); | 82 account_information.rpDisplayName(); |
79 mojo_account->display_name = account_information.displayName(); | 83 mojo_account->display_name = account_information.displayName(); |
80 mojo_account->id = account_information.id(); | 84 mojo_account->id = account_information.id(); |
81 mojo_account->name = account_information.name(); | 85 mojo_account->name = account_information.name(); |
82 mojo_account->image_url = account_information.imageURL(); | 86 mojo_account->image_url = account_information.imageURL(); |
83 return mojo_account; | 87 return mojo_account; |
84 } | 88 } |
85 | 89 |
86 // TODO(kpaulhamus): Make this a TypeConverter | 90 // TODO(kpaulhamus): Make this a TypeConverter |
87 ScopedCredentialOptionsPtr ConvertScopedCredentialOptions( | 91 ScopedCredentialOptionsPtr ConvertScopedCredentialOptions( |
88 const blink::ScopedCredentialOptions options, | 92 const blink::ScopedCredentialOptions options, |
89 blink::ScriptPromiseResolver* resolver) { | 93 blink::ScriptPromiseResolver* resolver) { |
90 auto mojo_options = ScopedCredentialOptions::New(); | 94 auto mojo_options = ScopedCredentialOptions::New(); |
91 mojo_options->timeout_seconds = options.timeoutSeconds(); | 95 if (options.hasRpId()) { |
92 mojo_options->relying_party_id = options.rpId(); | 96 mojo_options->relying_party_id = options.rpId(); |
Mike West
2017/06/13 21:11:23
Shouldn't we set this to the origin of the page if
kpaulhamus
2017/06/15 01:27:13
I do that setting in authenticator_impl, in the br
Mike West
2017/06/19 12:19:23
Hrm. Yeah, that makes sense. Can you add a comment
kpaulhamus
2017/06/26 15:37:40
Done.
| |
97 } | |
93 | 98 |
94 // Adds the excludeList members (which are ScopedCredentialDescriptors) | 99 // Step 4 of https://w3c.github.io/webauthn/#createCredential |
95 for (const auto& descriptor : options.excludeList()) { | 100 if (options.hasTimeoutSeconds()) { |
96 auto mojo_descriptor = ScopedCredentialDescriptor::New(); | 101 mojo_options->adjusted_timeout = |
97 mojo_descriptor->type = ConvertScopedCredentialType(descriptor.type()); | 102 static_cast<double>(options.timeoutSeconds()); |
Mike West
2017/06/13 21:11:23
See milliseconds vs seconds question above.
kpaulhamus
2017/06/15 01:27:14
Acknowledged.
| |
98 mojo_descriptor->id = ConvertBufferSource(descriptor.id()); | 103 if (mojo_options->adjusted_timeout > adjustedTimeoutUpper) { |
99 for (const auto& transport : descriptor.transports()) | 104 mojo_options->adjusted_timeout = adjustedTimeoutUpper; |
100 mojo_descriptor->transports.push_back(ConvertTransport(transport)); | 105 } else if (mojo_options->adjusted_timeout < adjustedTimeoutLower) { |
101 mojo_options->exclude_list.push_back(std::move(mojo_descriptor)); | 106 mojo_options->adjusted_timeout = adjustedTimeoutLower; |
107 } | |
108 } else { | |
109 mojo_options->adjusted_timeout = adjustedTimeoutLower; | |
110 } | |
111 | |
112 if (options.hasExcludeList()) { | |
113 // Adds the excludeList members (which are ScopedCredentialDescriptors) | |
114 for (const auto& descriptor : options.excludeList()) { | |
115 auto mojo_descriptor = ScopedCredentialDescriptor::New(); | |
116 mojo_descriptor->type = ConvertScopedCredentialType(descriptor.type()); | |
117 mojo_descriptor->id = ConvertBufferSource(descriptor.id()); | |
118 for (const auto& transport : descriptor.transports()) | |
119 mojo_descriptor->transports.push_back(ConvertTransport(transport)); | |
120 mojo_options->exclude_list.push_back(std::move(mojo_descriptor)); | |
121 } | |
102 } | 122 } |
103 // TODO(kpaulhamus): add AuthenticationExtensions; | 123 // TODO(kpaulhamus): add AuthenticationExtensions; |
104 return mojo_options; | 124 return mojo_options; |
105 } | 125 } |
106 | 126 |
107 // TODO(kpaulhamus): Make this a TypeConverter | 127 // TODO(kpaulhamus): Make this a TypeConverter |
108 ScopedCredentialParametersPtr ConvertScopedCredentialParameter( | 128 ScopedCredentialParametersPtr ConvertScopedCredentialParameter( |
109 const blink::ScopedCredentialParameters parameter, | 129 const blink::ScopedCredentialParameters parameter, |
110 blink::ScriptPromiseResolver* resolver) { | 130 blink::ScriptPromiseResolver* resolver) { |
111 auto mojo_parameter = ScopedCredentialParameters::New(); | 131 auto mojo_parameter = ScopedCredentialParameters::New(); |
112 mojo_parameter->type = ConvertScopedCredentialType(parameter.type()); | 132 mojo_parameter->type = ConvertScopedCredentialType(parameter.type()); |
113 // TODO(kpaulhamus): add AlgorithmIdentifier | 133 // TODO(kpaulhamus): add AlgorithmIdentifier |
114 return mojo_parameter; | 134 return mojo_parameter; |
115 } | 135 } |
136 | |
137 blink::DOMException* CreateExceptionFromStatus(AuthenticatorStatus status) { | |
138 switch (status) { | |
139 case AuthenticatorStatus::NOT_ALLOWED_ERROR: | |
140 return blink::DOMException::Create(blink::kNotAllowedError, | |
141 "Not allowed."); | |
142 case AuthenticatorStatus::NOT_SUPPORTED_ERROR: | |
143 return blink::DOMException::Create( | |
144 blink::kNotSupportedError, | |
145 "Parameters for this operation are not supported."); | |
146 case AuthenticatorStatus::SECURITY_ERROR: | |
147 return blink::DOMException::Create(blink::kSecurityError, | |
148 "The operation was not allowed."); | |
149 case AuthenticatorStatus::UNKNOWN_ERROR: | |
150 return blink::DOMException::Create(blink::kUnknownError, | |
151 "Request failed."); | |
152 case AuthenticatorStatus::CANCELLED: | |
153 return blink::DOMException::Create(blink::kNotAllowedError, | |
154 "User canceled the operation."); | |
155 case AuthenticatorStatus::SUCCESS: | |
156 return nullptr; | |
157 default: | |
158 NOTREACHED(); | |
159 return nullptr; | |
160 } | |
161 } | |
116 } // namespace mojo | 162 } // namespace mojo |
117 | 163 |
118 namespace blink { | 164 namespace blink { |
119 | |
120 WebAuthentication::WebAuthentication(LocalFrame& frame) | 165 WebAuthentication::WebAuthentication(LocalFrame& frame) |
121 : ContextLifecycleObserver(frame.GetDocument()) { | 166 : ContextLifecycleObserver(frame.GetDocument()) {} |
122 frame.GetInterfaceProvider()->GetInterface( | |
123 mojo::MakeRequest(&authenticator_)); | |
124 authenticator_.set_connection_error_handler(ConvertToBaseCallback( | |
125 WTF::Bind(&WebAuthentication::OnAuthenticatorConnectionError, | |
126 WrapWeakPersistent(this)))); | |
127 } | |
128 | 167 |
129 WebAuthentication::~WebAuthentication() { | 168 WebAuthentication::~WebAuthentication() { |
130 // |authenticator_| may still be valid but there should be no more | 169 // |authenticator_| may still be valid but there should be no more |
131 // outstanding requests because each holds a persistent handle to this object. | 170 // outstanding requests because each holds a persistent handle to this object. |
132 DCHECK(authenticator_requests_.IsEmpty()); | 171 DCHECK(authenticator_requests_.IsEmpty()); |
133 } | 172 } |
134 | 173 |
135 void WebAuthentication::Dispose() {} | |
136 | |
137 ScriptPromise WebAuthentication::makeCredential( | 174 ScriptPromise WebAuthentication::makeCredential( |
138 ScriptState* script_state, | 175 ScriptState* script_state, |
139 const RelyingPartyAccount& account_information, | 176 const RelyingPartyAccount& account_information, |
140 const HeapVector<ScopedCredentialParameters> crypto_parameters, | 177 const HeapVector<ScopedCredentialParameters> crypto_parameters, |
141 const BufferSource& attestation_challenge, | 178 const BufferSource& attestation_challenge, |
142 ScopedCredentialOptions& options) { | 179 ScopedCredentialOptions& options) { |
143 if (!authenticator_) { | 180 ScriptPromise promise = RejectIfNotSupported(script_state); |
144 return ScriptPromise::RejectWithDOMException( | 181 if (!promise.IsEmpty()) |
145 script_state, DOMException::Create(kNotSupportedError)); | 182 return promise; |
146 } | |
147 | 183 |
148 ScriptPromiseResolver* resolver = ScriptPromiseResolver::Create(script_state); | 184 ScriptPromiseResolver* resolver = ScriptPromiseResolver::Create(script_state); |
149 ScriptPromise promise = resolver->Promise(); | |
150 | 185 |
151 // TODO(kpaulhamus) validate parameters according to spec | |
152 auto account = | |
153 mojo::ConvertRelyingPartyAccount(account_information, resolver); | |
154 Vector<uint8_t> buffer = mojo::ConvertBufferSource(attestation_challenge); | 186 Vector<uint8_t> buffer = mojo::ConvertBufferSource(attestation_challenge); |
155 auto opts = mojo::ConvertScopedCredentialOptions(options, resolver); | 187 auto opts = mojo::ConvertScopedCredentialOptions(options, resolver); |
156 Vector<webauth::mojom::blink::ScopedCredentialParametersPtr> parameters; | 188 Vector<webauth::mojom::blink::ScopedCredentialParametersPtr> parameters; |
157 for (const auto& parameter : crypto_parameters) { | 189 for (const auto& parameter : crypto_parameters) { |
158 parameters.push_back( | 190 if (parameter.hasType()) { |
159 mojo::ConvertScopedCredentialParameter(parameter, resolver)); | 191 parameters.push_back( |
192 mojo::ConvertScopedCredentialParameter(parameter, resolver)); | |
193 } | |
160 } | 194 } |
161 | 195 auto account = |
196 mojo::ConvertRelyingPartyAccount(account_information, resolver); | |
162 authenticator_requests_.insert(resolver); | 197 authenticator_requests_.insert(resolver); |
163 authenticator_->MakeCredential( | 198 authenticator_->MakeCredential( |
164 std::move(account), std::move(parameters), buffer, std::move(opts), | 199 std::move(account), std::move(parameters), buffer, std::move(opts), |
165 ConvertToBaseCallback(Bind(&WebAuthentication::OnMakeCredential, | 200 ConvertToBaseCallback(WTF::Bind(&WebAuthentication::OnMakeCredential, |
166 WrapPersistent(this), | 201 WrapPersistent(this), |
167 WrapPersistent(resolver)))); | 202 WrapPersistent(resolver)))); |
168 return promise; | 203 return resolver->Promise(); |
169 } | 204 } |
170 | 205 |
171 ScriptPromise WebAuthentication::getAssertion( | 206 ScriptPromise WebAuthentication::getAssertion( |
172 ScriptState* script_state, | 207 ScriptState* script_state, |
173 const BufferSource& assertion_challenge, | 208 const BufferSource& assertion_challenge, |
174 const AuthenticationAssertionOptions& options) { | 209 const AuthenticationAssertionOptions& options) { |
175 NOTREACHED(); | 210 NOTREACHED(); |
176 return ScriptPromise(); | 211 return ScriptPromise(); |
177 } | 212 } |
178 | 213 |
179 void WebAuthentication::ContextDestroyed(ExecutionContext*) { | 214 void WebAuthentication::ContextDestroyed(ExecutionContext*) { |
180 authenticator_.reset(); | 215 Cleanup(); |
181 authenticator_requests_.clear(); | 216 } |
217 | |
218 void WebAuthentication::OnMakeCredential( | |
219 ScriptPromiseResolver* resolver, | |
220 webauth::mojom::blink::AuthenticatorStatus status, | |
221 webauth::mojom::blink::ScopedCredentialInfoPtr credential) { | |
222 if (!MarkRequestComplete(resolver)) | |
223 return; | |
224 | |
225 DOMException* error = mojo::CreateExceptionFromStatus(status); | |
226 if (error) { | |
Mike West
2017/06/13 21:11:23
Perhaps `DCHECK(!credential)`?
kpaulhamus
2017/06/15 01:27:13
The spec is pretty specific on what types of error
Mike West
2017/06/19 12:19:23
I'm assuming that in any case where an error exist
kpaulhamus
2017/06/26 15:37:40
Done.
| |
227 resolver->Reject(error); | |
228 Cleanup(); | |
229 return; | |
230 } | |
231 | |
232 if (credential->client_data.IsEmpty() || credential->attestation.IsEmpty()) { | |
233 resolver->Reject( | |
234 DOMException::Create(kNotFoundError, "No credential returned.")); | |
235 return; | |
236 } | |
237 | |
238 DOMArrayBuffer* clientDataBuffer = DOMArrayBuffer::Create( | |
239 static_cast<void*>(&credential->client_data.front()), | |
240 credential->client_data.size()); | |
241 | |
242 DOMArrayBuffer* attestationBuffer = DOMArrayBuffer::Create( | |
243 static_cast<void*>(&credential->attestation.front()), | |
244 credential->attestation.size()); | |
245 | |
246 ScopedCredentialInfo* scopedCredential = | |
247 ScopedCredentialInfo::Create(clientDataBuffer, attestationBuffer); | |
248 resolver->Resolve(scopedCredential); | |
249 } | |
250 | |
251 ScriptPromise WebAuthentication::RejectIfNotSupported( | |
252 ScriptState* script_state) { | |
253 if (!authenticator_) { | |
254 if (!GetFrame()) { | |
255 return ScriptPromise::RejectWithDOMException( | |
256 script_state, DOMException::Create(kNotSupportedError)); | |
257 } | |
258 GetFrame()->GetInterfaceProvider()->GetInterface( | |
259 mojo::MakeRequest(&authenticator_)); | |
260 | |
261 authenticator_.set_connection_error_handler(ConvertToBaseCallback( | |
262 WTF::Bind(&WebAuthentication::OnAuthenticatorConnectionError, | |
263 WrapWeakPersistent(this)))); | |
264 } | |
265 return ScriptPromise(); | |
182 } | 266 } |
183 | 267 |
184 void WebAuthentication::OnAuthenticatorConnectionError() { | 268 void WebAuthentication::OnAuthenticatorConnectionError() { |
185 authenticator_.reset(); | |
186 for (ScriptPromiseResolver* resolver : authenticator_requests_) { | 269 for (ScriptPromiseResolver* resolver : authenticator_requests_) { |
187 resolver->Reject( | 270 resolver->Reject( |
188 DOMException::Create(kNotFoundError, kNoAuthenticatorError)); | 271 DOMException::Create(kNotFoundError, kNoAuthenticatorError)); |
189 } | 272 } |
190 authenticator_requests_.clear(); | 273 Cleanup(); |
191 } | |
192 | |
193 void WebAuthentication::OnMakeCredential( | |
194 ScriptPromiseResolver* resolver, | |
195 Vector<webauth::mojom::blink::ScopedCredentialInfoPtr> credentials) { | |
196 if (!MarkRequestComplete(resolver)) | |
197 return; | |
198 | |
199 HeapVector<Member<ScopedCredentialInfo>> scoped_credentials; | |
200 for (auto& credential : credentials) { | |
201 if (credential->client_data.IsEmpty() || | |
202 credential->attestation.IsEmpty()) { | |
203 resolver->Reject( | |
204 DOMException::Create(kNotFoundError, "No credentials returned.")); | |
205 } | |
206 DOMArrayBuffer* client_data_buffer = DOMArrayBuffer::Create( | |
207 static_cast<void*>(&credential->client_data.front()), | |
208 credential->client_data.size()); | |
209 | |
210 DOMArrayBuffer* attestation_buffer = DOMArrayBuffer::Create( | |
211 static_cast<void*>(&credential->attestation.front()), | |
212 credential->attestation.size()); | |
213 | |
214 scoped_credentials.push_back( | |
215 ScopedCredentialInfo::Create(client_data_buffer, attestation_buffer)); | |
216 } | |
217 resolver->Resolve(); | |
218 } | 274 } |
219 | 275 |
220 bool WebAuthentication::MarkRequestComplete(ScriptPromiseResolver* resolver) { | 276 bool WebAuthentication::MarkRequestComplete(ScriptPromiseResolver* resolver) { |
221 auto request_entry = authenticator_requests_.find(resolver); | 277 auto request_entry = authenticator_requests_.find(resolver); |
222 if (request_entry == authenticator_requests_.end()) | 278 if (request_entry == authenticator_requests_.end()) |
223 return false; | 279 return false; |
224 authenticator_requests_.erase(request_entry); | 280 authenticator_requests_.erase(request_entry); |
225 return true; | 281 return true; |
226 } | 282 } |
227 | 283 |
228 DEFINE_TRACE(WebAuthentication) { | 284 DEFINE_TRACE(WebAuthentication) { |
229 visitor->Trace(authenticator_requests_); | 285 visitor->Trace(authenticator_requests_); |
230 ContextLifecycleObserver::Trace(visitor); | 286 ContextLifecycleObserver::Trace(visitor); |
231 } | 287 } |
232 | 288 |
289 // Clears the promise resolver, timer, and closes the Mojo connection. | |
290 void WebAuthentication::Cleanup() { | |
291 authenticator_.reset(); | |
292 authenticator_requests_.clear(); | |
293 } | |
294 | |
233 } // namespace blink | 295 } // namespace blink |
OLD | NEW |