Chromium Code Reviews| 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 |