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 |
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() {} | 150 void WebAuthentication::Dispose() {} |
112 | 151 |
113 ScriptPromise WebAuthentication::makeCredential( | 152 ScriptPromise WebAuthentication::makeCredential( |
114 ScriptState* script_state, | 153 ScriptState* script_state, |
115 const RelyingPartyAccount& account_information, | 154 const RelyingPartyAccount& account_information, |
116 const HeapVector<ScopedCredentialParameters> crypto_parameters, | 155 const HeapVector<ScopedCredentialParameters> crypto_parameters, |
117 const BufferSource& attestation_challenge, | 156 const BufferSource& attestation_challenge, |
118 ScopedCredentialOptions& options) { | 157 ScopedCredentialOptions& options) { |
119 ExecutionContext* executionContext = script_state->GetExecutionContext(); | 158 ScriptPromise promise = rejectIfNotSupported(script_state); |
120 | 159 if (!promise.IsEmpty()) |
121 if (!m_authenticator) { | 160 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 | 161 |
132 ScriptPromiseResolver* resolver = ScriptPromiseResolver::Create(script_state); | 162 ScriptPromiseResolver* resolver = ScriptPromiseResolver::Create(script_state); |
133 ScriptPromise promise = resolver->Promise(); | |
134 | 163 |
135 // TODO(kpaulhamus) validate parameters according to spec | |
136 Vector<uint8_t> buffer = mojo::convertBufferSource(attestation_challenge); | 164 Vector<uint8_t> buffer = mojo::convertBufferSource(attestation_challenge); |
137 auto opts = mojo::convertScopedCredentialOptions(options, resolver); | 165 auto opts = mojo::convertScopedCredentialOptions(options, resolver); |
138 Vector<webauth::mojom::blink::ScopedCredentialParametersPtr> parameters; | 166 Vector<webauth::mojom::blink::ScopedCredentialParametersPtr> parameters; |
139 for (const auto& parameter : crypto_parameters) { | 167 for (const auto& parameter : crypto_parameters) { |
140 parameters.push_back( | 168 if (parameter.hasType()) { // TODO add algorithm |
141 mojo::convertScopedCredentialParameter(parameter, resolver)); | 169 parameters.push_back( |
| 170 mojo::convertScopedCredentialParameter(parameter, resolver)); |
| 171 } |
142 } | 172 } |
143 | 173 |
144 m_authenticatorRequests.insert(resolver); | 174 m_authenticatorRequests.insert(resolver); |
145 m_authenticator->makeCredential( | 175 m_authenticator->MakeCredential( |
146 account_information, std::move(parameters), buffer, std::move(opts), | 176 account_information, std::move(parameters), buffer, std::move(opts), |
147 ConvertToBaseCallback(WTF::Bind(&WebAuthentication::onMakeCredential, | 177 ConvertToBaseCallback(WTF::Bind(&WebAuthentication::onMakeCredential, |
148 WrapPersistent(this), | 178 WrapPersistent(this), |
149 WrapPersistent(resolver)))); | 179 WrapPersistent(resolver)))); |
150 return promise; | 180 return resolver->Promise(); |
151 } | 181 } |
152 | 182 |
153 ScriptPromise WebAuthentication::getAssertion( | 183 ScriptPromise WebAuthentication::getAssertion( |
154 ScriptState* script_state, | 184 ScriptState* script_state, |
155 const BufferSource& assertion_challenge, | 185 const BufferSource& assertion_challenge, |
156 const AuthenticationAssertionOptions& options) { | 186 const AuthenticationAssertionOptions& options) { |
157 NOTREACHED(); | 187 NOTREACHED(); |
158 return ScriptPromise(); | 188 return ScriptPromise(); |
159 } | 189 } |
160 | 190 |
161 void WebAuthentication::ContextDestroyed(ExecutionContext*) { | 191 void WebAuthentication::ContextDestroyed(ExecutionContext*) { |
162 m_authenticator.reset(); | 192 cleanup(); |
163 m_authenticatorRequests.Clear(); | 193 } |
| 194 |
| 195 // Step 11 of https://w3c.github.io/webauthn/#makeCredential |
| 196 void WebAuthentication::onMakeCredential( |
| 197 ScriptPromiseResolver* resolver, |
| 198 webauth::mojom::blink::AuthenticatorStatus status, |
| 199 webauth::mojom::blink::ScopedCredentialInfoPtr credential) { |
| 200 if (!markRequestComplete(resolver)) |
| 201 return; |
| 202 |
| 203 DOMException* error = mojo::createExceptionFromStatus(status); |
| 204 if (error) { |
| 205 resolver->Reject(error); |
| 206 cleanup(); |
| 207 return; |
| 208 } |
| 209 |
| 210 if (credential->client_data.IsEmpty() || credential->attestation.IsEmpty()) { |
| 211 resolver->Reject( |
| 212 DOMException::Create(kNotFoundError, "No credential returned.")); |
| 213 return; |
| 214 } |
| 215 |
| 216 DOMArrayBuffer* clientDataBuffer = DOMArrayBuffer::Create( |
| 217 static_cast<void*>(&credential->client_data.front()), |
| 218 credential->client_data.size()); |
| 219 |
| 220 DOMArrayBuffer* attestationBuffer = DOMArrayBuffer::Create( |
| 221 static_cast<void*>(&credential->attestation.front()), |
| 222 credential->attestation.size()); |
| 223 |
| 224 ScopedCredentialInfo* scopedCredential = |
| 225 ScopedCredentialInfo::Create(clientDataBuffer, attestationBuffer); |
| 226 resolver->Resolve(scopedCredential); |
| 227 } |
| 228 |
| 229 ScriptPromise WebAuthentication::rejectIfNotSupported( |
| 230 ScriptState* script_state) { |
| 231 ExecutionContext* executionContext = script_state->GetExecutionContext(); |
| 232 |
| 233 if (!m_authenticator) { |
| 234 if (!GetFrame()) { |
| 235 return ScriptPromise::RejectWithDOMException( |
| 236 script_state, DOMException::Create(kNotSupportedError)); |
| 237 } |
| 238 GetFrame()->GetInterfaceProvider()->GetInterface( |
| 239 mojo::MakeRequest(&m_authenticator)); |
| 240 |
| 241 m_authenticator.set_connection_error_handler(ConvertToBaseCallback( |
| 242 WTF::Bind(&WebAuthentication::onAuthenticatorConnectionError, |
| 243 WrapWeakPersistent(this)))); |
| 244 } |
| 245 |
| 246 String errorMessage; |
| 247 if (!executionContext->IsSecureContext(errorMessage)) { |
| 248 return ScriptPromise::RejectWithDOMException( |
| 249 script_state, DOMException::Create(kSecurityError, errorMessage)); |
| 250 } |
| 251 |
| 252 return ScriptPromise(); |
164 } | 253 } |
165 | 254 |
166 void WebAuthentication::onAuthenticatorConnectionError() { | 255 void WebAuthentication::onAuthenticatorConnectionError() { |
167 m_authenticator.reset(); | |
168 for (ScriptPromiseResolver* resolver : m_authenticatorRequests) { | 256 for (ScriptPromiseResolver* resolver : m_authenticatorRequests) { |
169 resolver->Reject( | 257 resolver->Reject( |
170 DOMException::Create(kNotFoundError, kNoAuthenticatorError)); | 258 DOMException::Create(kNotFoundError, kNoAuthenticatorError)); |
171 } | 259 } |
172 m_authenticatorRequests.Clear(); | 260 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 } | 261 } |
202 | 262 |
203 bool WebAuthentication::markRequestComplete(ScriptPromiseResolver* resolver) { | 263 bool WebAuthentication::markRequestComplete(ScriptPromiseResolver* resolver) { |
204 auto requestEntry = m_authenticatorRequests.Find(resolver); | 264 auto requestEntry = m_authenticatorRequests.Find(resolver); |
205 if (requestEntry == m_authenticatorRequests.end()) | 265 if (requestEntry == m_authenticatorRequests.end()) |
206 return false; | 266 return false; |
207 m_authenticatorRequests.erase(requestEntry); | 267 m_authenticatorRequests.erase(requestEntry); |
208 return true; | 268 return true; |
209 } | 269 } |
210 | 270 |
| 271 // Clears the promise resolver and closes the Mojo connection. |
| 272 void WebAuthentication::cleanup() { |
| 273 m_authenticator.reset(); |
| 274 m_authenticatorRequests.Clear(); |
| 275 } |
| 276 |
211 DEFINE_TRACE(WebAuthentication) { | 277 DEFINE_TRACE(WebAuthentication) { |
212 visitor->Trace(m_authenticatorRequests); | 278 visitor->Trace(m_authenticatorRequests); |
213 ContextLifecycleObserver::Trace(visitor); | 279 ContextLifecycleObserver::Trace(visitor); |
214 } | 280 } |
215 | 281 |
216 } // namespace blink | 282 } // namespace blink |
OLD | NEW |