OLD | NEW |
---|---|
1 // Copyright 2017 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 "bindings/core/v8/ScriptPromise.h" | 7 #include "bindings/core/v8/ScriptPromise.h" |
8 #include "bindings/core/v8/ScriptPromiseResolver.h" | 8 #include "bindings/core/v8/ScriptPromiseResolver.h" |
9 #include "core/dom/DOMException.h" | 9 #include "core/dom/DOMException.h" |
10 #include "core/dom/Document.h" | 10 #include "core/dom/Document.h" |
11 #include "core/dom/ExceptionCode.h" | 11 #include "core/dom/ExceptionCode.h" |
12 #include "core/frame/LocalFrame.h" | 12 #include "core/frame/LocalFrame.h" |
13 #include "modules/webauth/ScopedCredential.h" | 13 #include "modules/webauth/ScopedCredential.h" |
14 #include "modules/webauth/ScopedCredentialOptions.h" | 14 #include "modules/webauth/ScopedCredentialOptions.h" |
15 #include "modules/webauth/ScopedCredentialParameters.h" | 15 #include "modules/webauth/ScopedCredentialParameters.h" |
16 #include "public/platform/InterfaceProvider.h" | 16 #include "public/platform/InterfaceProvider.h" |
17 | 17 |
18 namespace { | 18 namespace { |
19 const char kNoAuthenticatorError[] = "Authenticator unavailable."; | 19 const char kNoAuthenticatorError[] = "Authenticator unavailable."; |
20 // Time to wait for an authenticator to successfully complete an operation. | |
21 static const double adjustedTimeoutLower = 60; | |
22 static const double adjustedTimeoutUpper = 120; | |
20 } // anonymous namespace | 23 } // anonymous namespace |
21 | 24 |
22 namespace mojo { | 25 namespace mojo { |
23 | 26 using webauth::mojom::blink::AuthenticatorStatus; |
27 using webauth::mojom::blink::ScopedCredentialDescriptor; | |
24 using webauth::mojom::blink::ScopedCredentialOptions; | 28 using webauth::mojom::blink::ScopedCredentialOptions; |
25 using webauth::mojom::blink::ScopedCredentialOptionsPtr; | 29 using webauth::mojom::blink::ScopedCredentialOptionsPtr; |
26 using webauth::mojom::blink::ScopedCredentialParameters; | 30 using webauth::mojom::blink::ScopedCredentialParameters; |
27 using webauth::mojom::blink::ScopedCredentialParametersPtr; | 31 using webauth::mojom::blink::ScopedCredentialParametersPtr; |
28 using webauth::mojom::blink::ScopedCredentialDescriptor; | |
29 using webauth::mojom::blink::ScopedCredentialType; | 32 using webauth::mojom::blink::ScopedCredentialType; |
30 using webauth::mojom::blink::Transport; | 33 using webauth::mojom::blink::Transport; |
31 | 34 |
32 Vector<uint8_t> convertBufferSource(const blink::BufferSource& buffer) { | 35 Vector<uint8_t> convertBufferSource(const blink::BufferSource& buffer) { |
33 DCHECK(buffer.isNull()); | 36 DCHECK(!buffer.isNull()); |
34 Vector<uint8_t> vector; | 37 Vector<uint8_t> vector; |
35 if (buffer.isArrayBuffer()) { | 38 if (buffer.isArrayBuffer()) { |
36 vector.Append(static_cast<uint8_t*>(buffer.getAsArrayBuffer()->Data()), | 39 vector.Append(static_cast<uint8_t*>(buffer.getAsArrayBuffer()->Data()), |
37 buffer.getAsArrayBuffer()->ByteLength()); | 40 buffer.getAsArrayBuffer()->ByteLength()); |
38 } else { | 41 } else { |
39 vector.Append(static_cast<uint8_t*>( | 42 vector.Append(static_cast<uint8_t*>( |
40 buffer.getAsArrayBufferView().View()->BaseAddress()), | 43 buffer.getAsArrayBufferView().View()->BaseAddress()), |
41 buffer.getAsArrayBufferView().View()->byteLength()); | 44 buffer.getAsArrayBufferView().View()->byteLength()); |
42 } | 45 } |
43 return vector; | 46 return vector; |
(...skipping 14 matching lines...) Expand all Loading... | |
58 if (transport == "ble") | 61 if (transport == "ble") |
59 return Transport::BLE; | 62 return Transport::BLE; |
60 NOTREACHED(); | 63 NOTREACHED(); |
61 return Transport::USB; | 64 return Transport::USB; |
62 } | 65 } |
63 | 66 |
64 ScopedCredentialOptionsPtr convertScopedCredentialOptions( | 67 ScopedCredentialOptionsPtr convertScopedCredentialOptions( |
65 const blink::ScopedCredentialOptions options, | 68 const blink::ScopedCredentialOptions options, |
66 blink::ScriptPromiseResolver* resolver) { | 69 blink::ScriptPromiseResolver* resolver) { |
67 auto mojoOptions = ScopedCredentialOptions::New(); | 70 auto mojoOptions = ScopedCredentialOptions::New(); |
68 mojoOptions->timeout_seconds = options.timeoutSeconds(); | 71 if (options.hasRpId()) { |
69 mojoOptions->rp_id = options.rpId(); | 72 mojoOptions->rp_id = options.rpId(); |
73 } | |
70 | 74 |
71 // Adds the excludeList members (which are ScopedCredentialDescriptors) | 75 // Step 1 of https://w3c.github.io/webauthn/#makeCredential |
foolip
2017/05/12 08:00:06
This link doesn't work, should it be https://w3c.g
kpaulhamus
2017/05/24 21:08:44
Updated the link.
| |
72 for (const auto& descriptor : options.excludeList()) { | 76 if (options.hasTimeoutSeconds()) { |
73 auto mojoDescriptor = ScopedCredentialDescriptor::New(); | 77 mojoOptions->adjusted_timeout = |
74 mojoDescriptor->type = convertScopedCredentialType(descriptor.type()); | 78 static_cast<double>(options.timeoutSeconds()); |
75 mojoDescriptor->id = convertBufferSource(descriptor.id()); | 79 if (mojoOptions->adjusted_timeout > adjustedTimeoutUpper) { |
76 for (const auto& transport : descriptor.transports()) | 80 mojoOptions->adjusted_timeout = adjustedTimeoutUpper; |
77 mojoDescriptor->transports.push_back(convertTransport(transport)); | 81 } else if (mojoOptions->adjusted_timeout < adjustedTimeoutLower) { |
78 mojoOptions->exclude_list.push_back(std::move(mojoDescriptor)); | 82 mojoOptions->adjusted_timeout = adjustedTimeoutLower; |
83 } | |
84 } else { | |
85 mojoOptions->adjusted_timeout = adjustedTimeoutLower; | |
86 } | |
87 | |
88 if (options.hasExcludeList()) { | |
89 // Adds the excludeList members (which are ScopedCredentialDescriptors) | |
90 for (const auto& descriptor : options.excludeList()) { | |
91 auto mojoDescriptor = ScopedCredentialDescriptor::New(); | |
92 mojoDescriptor->type = convertScopedCredentialType(descriptor.type()); | |
93 mojoDescriptor->id = convertBufferSource(descriptor.id()); | |
94 for (const auto& transport : descriptor.transports()) | |
95 mojoDescriptor->transports.push_back(convertTransport(transport)); | |
96 mojoOptions->exclude_list.push_back(std::move(mojoDescriptor)); | |
97 } | |
79 } | 98 } |
80 // TODO (kpaulhamus) add AuthenticationExtensions; | 99 // TODO (kpaulhamus) add AuthenticationExtensions; |
81 return mojoOptions; | 100 return mojoOptions; |
82 } | 101 } |
83 | 102 |
84 ScopedCredentialParametersPtr convertScopedCredentialParameter( | 103 ScopedCredentialParametersPtr convertScopedCredentialParameter( |
85 const blink::ScopedCredentialParameters parameter, | 104 const blink::ScopedCredentialParameters parameter, |
86 blink::ScriptPromiseResolver* resolver) { | 105 blink::ScriptPromiseResolver* resolver) { |
87 auto mojoParameter = ScopedCredentialParameters::New(); | 106 auto mojoParameter = ScopedCredentialParameters::New(); |
88 mojoParameter->type = convertScopedCredentialType(parameter.type()); | 107 mojoParameter->type = convertScopedCredentialType(parameter.type()); |
89 // TODO (kpaulhamus) add AlgorithmIdentifier | 108 // TODO (kpaulhamus) add AlgorithmIdentifier |
90 return mojoParameter; | 109 return mojoParameter; |
91 } | 110 } |
111 | |
112 blink::DOMException* createExceptionFromStatus(AuthenticatorStatus status) { | |
113 switch (status) { | |
114 case AuthenticatorStatus::NOT_ALLOWED_ERROR: | |
115 return blink::DOMException::Create(blink::kNotAllowedError, | |
116 "Not allowed."); | |
117 case AuthenticatorStatus::NOT_SUPPORTED_ERROR: | |
118 return blink::DOMException::Create( | |
119 blink::kNotSupportedError, | |
120 "Parameters for this operation are not supported."); | |
121 case AuthenticatorStatus::SECURITY_ERROR: | |
122 return blink::DOMException::Create(blink::kSecurityError, | |
123 "The operation was not allowed."); | |
124 case AuthenticatorStatus::UNKNOWN_ERROR: | |
125 return blink::DOMException::Create(blink::kUnknownError, | |
126 "Request failed."); | |
127 case AuthenticatorStatus::CANCELLED: | |
128 return blink::DOMException::Create(blink::kNotAllowedError, | |
129 "User canceled the operation."); | |
130 case AuthenticatorStatus::SUCCESS: | |
131 return nullptr; | |
132 default: | |
133 NOTREACHED(); | |
134 return nullptr; | |
135 } | |
136 } | |
92 } // namespace mojo | 137 } // namespace mojo |
93 | 138 |
94 namespace blink { | 139 namespace blink { |
95 | 140 |
96 WebAuthentication::WebAuthentication(LocalFrame& frame) | 141 WebAuthentication::WebAuthentication(LocalFrame& frame) |
97 : ContextLifecycleObserver(frame.GetDocument()) { | 142 : ContextLifecycleObserver(frame.GetDocument()) {} |
98 frame.GetInterfaceProvider()->GetInterface( | |
99 mojo::MakeRequest(&m_authenticator)); | |
100 m_authenticator.set_connection_error_handler(ConvertToBaseCallback( | |
101 WTF::Bind(&WebAuthentication::onAuthenticatorConnectionError, | |
102 WrapWeakPersistent(this)))); | |
103 } | |
104 | 143 |
105 WebAuthentication::~WebAuthentication() { | 144 WebAuthentication::~WebAuthentication() { |
106 // |m_authenticator| may still be valid but there should be no more | 145 // |m_authenticator| may still be valid but there should be no more |
107 // outstanding requests because each holds a persistent handle to this object. | 146 // outstanding requests because each holds a persistent handle to this object. |
108 DCHECK(m_authenticatorRequests.IsEmpty()); | 147 DCHECK(m_authenticatorRequests.IsEmpty()); |
109 } | 148 } |
110 | 149 |
111 void WebAuthentication::Dispose() {} | |
112 | |
113 ScriptPromise WebAuthentication::makeCredential( | 150 ScriptPromise WebAuthentication::makeCredential( |
114 ScriptState* script_state, | 151 ScriptState* script_state, |
115 const RelyingPartyAccount& account_information, | 152 const RelyingPartyAccount& account_information, |
116 const HeapVector<ScopedCredentialParameters> crypto_parameters, | 153 const HeapVector<ScopedCredentialParameters> crypto_parameters, |
117 const BufferSource& attestation_challenge, | 154 const BufferSource& attestation_challenge, |
118 ScopedCredentialOptions& options) { | 155 ScopedCredentialOptions& options) { |
119 ExecutionContext* executionContext = script_state->GetExecutionContext(); | 156 ScriptPromise promise = rejectIfNotSupported(script_state); |
120 | 157 if (!promise.IsEmpty()) |
121 if (!m_authenticator) { | 158 return promise; |
122 return ScriptPromise::RejectWithDOMException( | |
123 script_state, DOMException::Create(kNotSupportedError)); | |
124 } | |
125 | |
126 String errorMessage; | |
127 if (!executionContext->IsSecureContext(errorMessage)) { | |
128 return ScriptPromise::RejectWithDOMException( | |
129 script_state, DOMException::Create(kSecurityError, errorMessage)); | |
130 } | |
131 | 159 |
132 ScriptPromiseResolver* resolver = ScriptPromiseResolver::Create(script_state); | 160 ScriptPromiseResolver* resolver = ScriptPromiseResolver::Create(script_state); |
133 ScriptPromise promise = resolver->Promise(); | |
134 | 161 |
135 // TODO(kpaulhamus) validate parameters according to spec | |
136 Vector<uint8_t> buffer = mojo::convertBufferSource(attestation_challenge); | 162 Vector<uint8_t> buffer = mojo::convertBufferSource(attestation_challenge); |
137 auto opts = mojo::convertScopedCredentialOptions(options, resolver); | 163 auto opts = mojo::convertScopedCredentialOptions(options, resolver); |
138 Vector<webauth::mojom::blink::ScopedCredentialParametersPtr> parameters; | 164 Vector<webauth::mojom::blink::ScopedCredentialParametersPtr> parameters; |
139 for (const auto& parameter : crypto_parameters) { | 165 for (const auto& parameter : crypto_parameters) { |
140 parameters.push_back( | 166 if (parameter.hasType()) { // TODO add algorithm |
141 mojo::convertScopedCredentialParameter(parameter, resolver)); | 167 parameters.push_back( |
168 mojo::convertScopedCredentialParameter(parameter, resolver)); | |
169 } | |
142 } | 170 } |
143 | 171 |
144 m_authenticatorRequests.insert(resolver); | 172 m_authenticatorRequests.insert(resolver); |
145 m_authenticator->makeCredential( | 173 m_authenticator->MakeCredential( |
146 account_information, std::move(parameters), buffer, std::move(opts), | 174 account_information, std::move(parameters), buffer, std::move(opts), |
147 ConvertToBaseCallback(WTF::Bind(&WebAuthentication::onMakeCredential, | 175 ConvertToBaseCallback(WTF::Bind(&WebAuthentication::onMakeCredential, |
148 WrapPersistent(this), | 176 WrapPersistent(this), |
149 WrapPersistent(resolver)))); | 177 WrapPersistent(resolver)))); |
150 return promise; | 178 return resolver->Promise(); |
151 } | 179 } |
152 | 180 |
153 ScriptPromise WebAuthentication::getAssertion( | 181 ScriptPromise WebAuthentication::getAssertion( |
154 ScriptState* script_state, | 182 ScriptState* script_state, |
155 const BufferSource& assertion_challenge, | 183 const BufferSource& assertion_challenge, |
156 const AuthenticationAssertionOptions& options) { | 184 const AuthenticationAssertionOptions& options) { |
157 NOTREACHED(); | 185 NOTREACHED(); |
158 return ScriptPromise(); | 186 return ScriptPromise(); |
159 } | 187 } |
160 | 188 |
161 void WebAuthentication::ContextDestroyed(ExecutionContext*) { | 189 void WebAuthentication::ContextDestroyed(ExecutionContext*) { |
162 m_authenticator.reset(); | 190 cleanup(); |
163 m_authenticatorRequests.Clear(); | 191 } |
192 | |
193 // Step 11 of https://w3c.github.io/webauthn/#makeCredential | |
194 void WebAuthentication::onMakeCredential( | |
195 ScriptPromiseResolver* resolver, | |
196 webauth::mojom::blink::AuthenticatorStatus status, | |
197 webauth::mojom::blink::ScopedCredentialInfoPtr credential) { | |
198 if (!markRequestComplete(resolver)) | |
199 return; | |
200 | |
201 DOMException* error = mojo::createExceptionFromStatus(status); | |
202 if (error) { | |
203 resolver->Reject(error); | |
204 cleanup(); | |
205 return; | |
206 } | |
207 | |
208 if (credential->client_data.IsEmpty() || credential->attestation.IsEmpty()) { | |
209 resolver->Reject( | |
210 DOMException::Create(kNotFoundError, "No credential returned.")); | |
211 return; | |
212 } | |
213 | |
214 DOMArrayBuffer* clientDataBuffer = DOMArrayBuffer::Create( | |
215 static_cast<void*>(&credential->client_data.front()), | |
216 credential->client_data.size()); | |
217 | |
218 DOMArrayBuffer* attestationBuffer = DOMArrayBuffer::Create( | |
219 static_cast<void*>(&credential->attestation.front()), | |
220 credential->attestation.size()); | |
221 | |
222 ScopedCredentialInfo* scopedCredential = | |
223 ScopedCredentialInfo::Create(clientDataBuffer, attestationBuffer); | |
224 resolver->Resolve(scopedCredential); | |
225 } | |
226 | |
227 ScriptPromise WebAuthentication::rejectIfNotSupported( | |
228 ScriptState* script_state) { | |
229 ExecutionContext* executionContext = script_state->GetExecutionContext(); | |
230 | |
231 if (!m_authenticator) { | |
232 if (!GetFrame()) { | |
233 return ScriptPromise::RejectWithDOMException( | |
234 script_state, DOMException::Create(kNotSupportedError)); | |
foolip
2017/05/12 08:00:06
The only NotSupportedError I see in the spec doesn
kpaulhamus
2017/05/24 21:08:44
This particular error is not specific to the spec
| |
235 } | |
236 GetFrame()->GetInterfaceProvider()->GetInterface( | |
237 mojo::MakeRequest(&m_authenticator)); | |
238 | |
239 m_authenticator.set_connection_error_handler(ConvertToBaseCallback( | |
240 WTF::Bind(&WebAuthentication::onAuthenticatorConnectionError, | |
241 WrapWeakPersistent(this)))); | |
242 } | |
243 | |
244 String errorMessage; | |
245 if (!executionContext->IsSecureContext(errorMessage)) { | |
246 return ScriptPromise::RejectWithDOMException( | |
247 script_state, DOMException::Create(kSecurityError, errorMessage)); | |
248 } | |
249 | |
250 return ScriptPromise(); | |
164 } | 251 } |
165 | 252 |
166 void WebAuthentication::onAuthenticatorConnectionError() { | 253 void WebAuthentication::onAuthenticatorConnectionError() { |
167 m_authenticator.reset(); | |
168 for (ScriptPromiseResolver* resolver : m_authenticatorRequests) { | 254 for (ScriptPromiseResolver* resolver : m_authenticatorRequests) { |
169 resolver->Reject( | 255 resolver->Reject( |
170 DOMException::Create(kNotFoundError, kNoAuthenticatorError)); | 256 DOMException::Create(kNotFoundError, kNoAuthenticatorError)); |
171 } | 257 } |
172 m_authenticatorRequests.Clear(); | 258 cleanup(); |
173 } | |
174 | |
175 void WebAuthentication::onMakeCredential( | |
176 ScriptPromiseResolver* resolver, | |
177 Vector<webauth::mojom::blink::ScopedCredentialInfoPtr> credentials) { | |
178 if (!markRequestComplete(resolver)) | |
179 return; | |
180 | |
181 HeapVector<Member<ScopedCredentialInfo>> scopedCredentials; | |
182 for (auto& credential : credentials) { | |
183 if (credential->client_data.IsEmpty() || | |
184 credential->attestation.IsEmpty()) { | |
185 resolver->Reject( | |
186 DOMException::Create(kNotFoundError, "No credentials returned.")); | |
187 } | |
188 DOMArrayBuffer* clientDataBuffer = DOMArrayBuffer::Create( | |
189 static_cast<void*>(&credential->client_data.front()), | |
190 credential->client_data.size()); | |
191 | |
192 DOMArrayBuffer* attestationBuffer = DOMArrayBuffer::Create( | |
193 static_cast<void*>(&credential->attestation.front()), | |
194 credential->attestation.size()); | |
195 | |
196 scopedCredentials.push_back( | |
197 ScopedCredentialInfo::Create(clientDataBuffer, attestationBuffer)); | |
198 } | |
199 resolver->Resolve(scopedCredentials); | |
200 m_authenticatorRequests.erase(resolver); | |
201 } | 259 } |
202 | 260 |
203 bool WebAuthentication::markRequestComplete(ScriptPromiseResolver* resolver) { | 261 bool WebAuthentication::markRequestComplete(ScriptPromiseResolver* resolver) { |
204 auto requestEntry = m_authenticatorRequests.Find(resolver); | 262 auto requestEntry = m_authenticatorRequests.Find(resolver); |
205 if (requestEntry == m_authenticatorRequests.end()) | 263 if (requestEntry == m_authenticatorRequests.end()) |
206 return false; | 264 return false; |
207 m_authenticatorRequests.erase(requestEntry); | 265 m_authenticatorRequests.erase(requestEntry); |
208 return true; | 266 return true; |
209 } | 267 } |
210 | 268 |
269 // Clears the promise resolver and closes the Mojo connection. | |
270 void WebAuthentication::cleanup() { | |
271 m_authenticator.reset(); | |
272 m_authenticatorRequests.Clear(); | |
273 } | |
274 | |
211 DEFINE_TRACE(WebAuthentication) { | 275 DEFINE_TRACE(WebAuthentication) { |
212 visitor->Trace(m_authenticatorRequests); | 276 visitor->Trace(m_authenticatorRequests); |
213 ContextLifecycleObserver::Trace(visitor); | 277 ContextLifecycleObserver::Trace(visitor); |
214 } | 278 } |
215 | 279 |
216 } // namespace blink | 280 } // namespace blink |
OLD | NEW |