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( | 42 vector.Append( |
40 static_cast<uint8_t*>(buffer.getAsArrayBufferView()->baseAddress()), | 43 static_cast<uint8_t*>(buffer.getAsArrayBufferView()->BaseAddress()), |
41 buffer.getAsArrayBufferView()->byteLength()); | 44 buffer.getAsArrayBufferView()->byteLength()); |
42 } | 45 } |
43 return vector; | 46 return vector; |
44 } | 47 } |
45 | 48 |
46 ScopedCredentialType convertScopedCredentialType(const WTF::String& credType) { | 49 ScopedCredentialType convertScopedCredentialType(const WTF::String& credType) { |
47 if (credType == "ScopedCred") | 50 if (credType == "ScopedCred") |
48 return ScopedCredentialType::SCOPEDCRED; | 51 return ScopedCredentialType::SCOPEDCRED; |
49 NOTREACHED(); | 52 NOTREACHED(); |
50 return ScopedCredentialType::SCOPEDCRED; | 53 return ScopedCredentialType::SCOPEDCRED; |
51 } | 54 } |
52 | 55 |
53 Transport convertTransport(const WTF::String& transport) { | 56 Transport convertTransport(const WTF::String& transport) { |
54 if (transport == "usb") | 57 if (transport == "usb") |
55 return Transport::USB; | 58 return Transport::USB; |
56 if (transport == "nfc") | 59 if (transport == "nfc") |
57 return Transport::NFC; | 60 return Transport::NFC; |
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 |
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.document()) { | 142 : ContextLifecycleObserver(frame.GetDocument()) {} |
98 frame.interfaceProvider()->getInterface(mojo::MakeRequest(&m_authenticator)); | |
99 m_authenticator.set_connection_error_handler(convertToBaseCallback( | |
100 WTF::bind(&WebAuthentication::onAuthenticatorConnectionError, | |
101 wrapWeakPersistent(this)))); | |
102 } | |
103 | 143 |
104 WebAuthentication::~WebAuthentication() { | 144 WebAuthentication::~WebAuthentication() { |
105 // |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 |
106 // outstanding requests because each holds a persistent handle to this object. | 146 // outstanding requests because each holds a persistent handle to this object. |
107 DCHECK(m_authenticatorRequests.isEmpty()); | 147 DCHECK(m_authenticatorRequests.IsEmpty()); |
108 } | 148 } |
109 | 149 |
110 void WebAuthentication::Dispose() {} | 150 void WebAuthentication::Dispose() {} |
111 | 151 |
152 // With options | |
112 ScriptPromise WebAuthentication::makeCredential( | 153 ScriptPromise WebAuthentication::makeCredential( |
113 ScriptState* script_state, | 154 ScriptState* script_state, |
114 const RelyingPartyAccount& account_information, | 155 const RelyingPartyAccount& account_information, |
115 const HeapVector<ScopedCredentialParameters> crypto_parameters, | 156 const HeapVector<ScopedCredentialParameters> crypto_parameters, |
116 const BufferSource& attestation_challenge, | 157 const BufferSource& attestation_challenge, |
117 ScopedCredentialOptions& options) { | 158 ScopedCredentialOptions& options) { |
118 ExecutionContext* executionContext = scriptState->getExecutionContext(); | 159 ScriptPromise promise = rejectIfNotSupported(script_state); |
160 if (!promise.IsEmpty()) | |
161 return promise; | |
119 | 162 |
120 if (!m_authenticator) { | 163 ScriptPromiseResolver* resolver = ScriptPromiseResolver::Create(script_state); |
121 return ScriptPromise::rejectWithDOMException( | |
122 scriptState, DOMException::create(NotSupportedError)); | |
123 } | |
124 | 164 |
125 String errorMessage; | 165 Vector<uint8_t> buffer = mojo::convertBufferSource(attestation_challenge); |
126 if (!executionContext->isSecureContext(errorMessage)) { | |
127 return ScriptPromise::rejectWithDOMException( | |
128 scriptState, DOMException::create(SecurityError, errorMessage)); | |
129 } | |
130 | |
131 ScriptPromiseResolver* resolver = ScriptPromiseResolver::create(scriptState); | |
132 ScriptPromise promise = resolver->promise(); | |
133 | |
134 // TODO(kpaulhamus) validate parameters according to spec | |
135 Vector<uint8_t> buffer = mojo::convertBufferSource(attestationChallenge); | |
136 auto opts = mojo::convertScopedCredentialOptions(options, resolver); | 166 auto opts = mojo::convertScopedCredentialOptions(options, resolver); |
137 Vector<webauth::mojom::blink::ScopedCredentialParametersPtr> parameters; | 167 Vector<webauth::mojom::blink::ScopedCredentialParametersPtr> parameters; |
138 for (const auto& parameter : cryptoParameters) { | 168 for (const auto& parameter : crypto_parameters) { |
139 parameters.push_back( | 169 if (parameter.hasType()) { // TODO add algorithm |
140 mojo::convertScopedCredentialParameter(parameter, resolver)); | 170 parameters.push_back( |
171 mojo::convertScopedCredentialParameter(parameter, resolver)); | |
172 } | |
141 } | 173 } |
142 | 174 |
143 m_authenticatorRequests.insert(resolver); | 175 m_authenticatorRequests.insert(resolver); |
144 m_authenticator->makeCredential( | 176 m_authenticator->MakeCredential( |
145 accountInformation, std::move(parameters), buffer, std::move(opts), | 177 account_information, std::move(parameters), buffer, std::move(opts), |
146 convertToBaseCallback(WTF::bind(&WebAuthentication::onMakeCredential, | 178 ConvertToBaseCallback(WTF::Bind(&WebAuthentication::onMakeCredential, |
147 wrapPersistent(this), | 179 WrapPersistent(this), |
148 wrapPersistent(resolver)))); | 180 WrapPersistent(resolver)))); |
149 return promise; | 181 return resolver->Promise(); |
150 } | 182 } |
151 | 183 |
152 ScriptPromise WebAuthentication::getAssertion( | 184 ScriptPromise WebAuthentication::getAssertion( |
153 ScriptState* script_state, | 185 ScriptState* script_state, |
154 const BufferSource& assertion_challenge, | 186 const BufferSource& assertion_challenge, |
155 const AuthenticationAssertionOptions& options) { | 187 const AuthenticationAssertionOptions& options) { |
156 NOTREACHED(); | 188 NOTREACHED(); |
157 return ScriptPromise(); | 189 return ScriptPromise(); |
158 } | 190 } |
159 | 191 |
160 void WebAuthentication::contextDestroyed(ExecutionContext*) { | 192 void WebAuthentication::ContextDestroyed(ExecutionContext*) { |
161 m_authenticator.reset(); | 193 cleanup(); |
162 m_authenticatorRequests.clear(); | 194 } |
195 | |
196 // Step 11 of https://w3c.github.io/webauthn/#makeCredential | |
197 void WebAuthentication::onMakeCredential( | |
198 ScriptPromiseResolver* resolver, | |
199 webauth::mojom::blink::AuthenticatorStatus status, | |
200 webauth::mojom::blink::ScopedCredentialInfoPtr credential) { | |
201 if (!markRequestComplete(resolver)) | |
202 return; | |
203 | |
204 DOMException* error = mojo::createExceptionFromStatus(status); | |
205 if (error) { | |
206 for (ScriptPromiseResolver* resolver : m_authenticatorRequests) { | |
207 resolver->Reject(error); | |
208 } | |
209 cleanup(); | |
Zhiqiang Zhang (Slow)
2017/04/11 19:42:50
Actually, the issue is unrelated to mojo. I checke
| |
210 return; | |
211 } | |
212 | |
213 if (credential->client_data.IsEmpty() || credential->attestation.IsEmpty()) { | |
214 resolver->Reject( | |
215 DOMException::Create(kNotFoundError, "No credential returned.")); | |
216 return; | |
217 } | |
218 | |
219 DOMArrayBuffer* clientDataBuffer = DOMArrayBuffer::Create( | |
220 static_cast<void*>(&credential->client_data.front()), | |
221 credential->client_data.size()); | |
222 | |
223 DOMArrayBuffer* attestationBuffer = DOMArrayBuffer::Create( | |
224 static_cast<void*>(&credential->attestation.front()), | |
225 credential->attestation.size()); | |
226 | |
227 ScopedCredentialInfo* scopedCredential = | |
228 ScopedCredentialInfo::Create(clientDataBuffer, attestationBuffer); | |
229 resolver->Resolve(scopedCredential); | |
230 } | |
231 | |
232 ScriptPromise WebAuthentication::rejectIfNotSupported( | |
233 ScriptState* script_state) { | |
234 ExecutionContext* executionContext = script_state->GetExecutionContext(); | |
235 | |
236 if (!m_authenticator) { | |
237 if (!GetFrame()) { | |
238 return ScriptPromise::RejectWithDOMException( | |
239 script_state, DOMException::Create(kNotSupportedError)); | |
240 } | |
241 GetFrame()->GetInterfaceProvider()->GetInterface( | |
242 mojo::MakeRequest(&m_authenticator)); | |
243 | |
244 m_authenticator.set_connection_error_handler(ConvertToBaseCallback( | |
245 WTF::Bind(&WebAuthentication::onAuthenticatorConnectionError, | |
246 WrapWeakPersistent(this)))); | |
247 } | |
248 | |
249 String errorMessage; | |
250 if (!executionContext->IsSecureContext(errorMessage)) { | |
251 return ScriptPromise::RejectWithDOMException( | |
252 script_state, DOMException::Create(kSecurityError, errorMessage)); | |
253 } | |
254 | |
255 return ScriptPromise(); | |
163 } | 256 } |
164 | 257 |
165 void WebAuthentication::onAuthenticatorConnectionError() { | 258 void WebAuthentication::onAuthenticatorConnectionError() { |
166 m_authenticator.reset(); | |
167 for (ScriptPromiseResolver* resolver : m_authenticatorRequests) { | 259 for (ScriptPromiseResolver* resolver : m_authenticatorRequests) { |
168 resolver->reject( | 260 resolver->Reject( |
169 DOMException::create(NotFoundError, kNoAuthenticatorError)); | 261 DOMException::Create(kNotFoundError, kNoAuthenticatorError)); |
170 } | 262 } |
171 m_authenticatorRequests.clear(); | 263 cleanup(); |
172 } | |
173 | |
174 void WebAuthentication::onMakeCredential( | |
175 ScriptPromiseResolver* resolver, | |
176 Vector<webauth::mojom::blink::ScopedCredentialInfoPtr> credentials) { | |
177 if (!markRequestComplete(resolver)) | |
178 return; | |
179 | |
180 HeapVector<Member<ScopedCredentialInfo>> scopedCredentials; | |
181 for (auto& credential : credentials) { | |
182 if (credential->client_data.isEmpty() || | |
183 credential->attestation.isEmpty()) { | |
184 resolver->reject( | |
185 DOMException::create(NotFoundError, "No credentials returned.")); | |
186 } | |
187 DOMArrayBuffer* clientDataBuffer = DOMArrayBuffer::create( | |
188 static_cast<void*>(&credential->client_data.front()), | |
189 credential->client_data.size()); | |
190 | |
191 DOMArrayBuffer* attestationBuffer = DOMArrayBuffer::create( | |
192 static_cast<void*>(&credential->attestation.front()), | |
193 credential->attestation.size()); | |
194 | |
195 scopedCredentials.push_back( | |
196 ScopedCredentialInfo::create(clientDataBuffer, attestationBuffer)); | |
197 } | |
198 resolver->resolve(scopedCredentials); | |
199 m_authenticatorRequests.erase(resolver); | |
200 } | 264 } |
201 | 265 |
202 bool WebAuthentication::markRequestComplete(ScriptPromiseResolver* resolver) { | 266 bool WebAuthentication::markRequestComplete(ScriptPromiseResolver* resolver) { |
203 auto requestEntry = m_authenticatorRequests.find(resolver); | 267 auto requestEntry = m_authenticatorRequests.Find(resolver); |
204 if (requestEntry == m_authenticatorRequests.end()) | 268 if (requestEntry == m_authenticatorRequests.end()) |
205 return false; | 269 return false; |
206 m_authenticatorRequests.erase(requestEntry); | 270 m_authenticatorRequests.erase(requestEntry); |
207 return true; | 271 return true; |
208 } | 272 } |
209 | 273 |
274 // Clears the promise resolver and closes the Mojo connection. | |
275 void WebAuthentication::cleanup() { | |
276 m_authenticator.reset(); | |
277 m_authenticatorRequests.Clear(); | |
278 } | |
279 | |
210 DEFINE_TRACE(WebAuthentication) { | 280 DEFINE_TRACE(WebAuthentication) { |
211 visitor->trace(m_authenticatorRequests); | 281 visitor->Trace(m_authenticatorRequests); |
212 ContextLifecycleObserver::trace(visitor); | 282 ContextLifecycleObserver::Trace(visitor); |
213 } | 283 } |
214 | 284 |
215 } // namespace blink | 285 } // namespace blink |
OLD | NEW |