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( | 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 |