|  | // Copyright 2013 The Chromium Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | #include "remoting/protocol/negotiating_client_authenticator.h" | 
|  |  | 
|  | #include <algorithm> | 
|  | #include <sstream> | 
|  | #include <utility> | 
|  |  | 
|  | #include "base/bind.h" | 
|  | #include "base/callback.h" | 
|  | #include "base/logging.h" | 
|  | #include "base/memory/ptr_util.h" | 
|  | #include "base/strings/string_split.h" | 
|  | #include "remoting/protocol/auth_util.h" | 
|  | #include "remoting/protocol/channel_authenticator.h" | 
|  | #include "remoting/protocol/pairing_client_authenticator.h" | 
|  | #include "remoting/protocol/spake2_authenticator.h" | 
|  | #include "remoting/protocol/v2_authenticator.h" | 
|  | #include "third_party/webrtc/libjingle/xmllite/xmlelement.h" | 
|  |  | 
|  | namespace remoting { | 
|  | namespace protocol { | 
|  |  | 
|  | NegotiatingClientAuthenticator::NegotiatingClientAuthenticator( | 
|  | const std::string& local_id, | 
|  | const std::string& remote_id, | 
|  | const ClientAuthenticationConfig& config) | 
|  | : NegotiatingAuthenticatorBase(MESSAGE_READY), | 
|  | local_id_(local_id), | 
|  | remote_id_(remote_id), | 
|  | config_(config), | 
|  | weak_factory_(this) { | 
|  | if (!config_.fetch_third_party_token_callback.is_null()) { | 
|  | AddMethod(Method::THIRD_PARTY_SPAKE2_CURVE25519); | 
|  | AddMethod(Method::THIRD_PARTY_SPAKE2_P224); | 
|  | } | 
|  |  | 
|  | AddMethod(Method::PAIRED_SPAKE2_CURVE25519); | 
|  | AddMethod(Method::PAIRED_SPAKE2_P224); | 
|  |  | 
|  | AddMethod(Method::SHARED_SECRET_SPAKE2_CURVE25519); | 
|  | AddMethod(Method::SHARED_SECRET_SPAKE2_P224); | 
|  |  | 
|  | AddMethod(Method::SHARED_SECRET_PLAIN_SPAKE2_P224); | 
|  | } | 
|  |  | 
|  | NegotiatingClientAuthenticator::~NegotiatingClientAuthenticator() {} | 
|  |  | 
|  | void NegotiatingClientAuthenticator::ProcessMessage( | 
|  | const buzz::XmlElement* message, | 
|  | const base::Closure& resume_callback) { | 
|  | DCHECK_EQ(state(), WAITING_MESSAGE); | 
|  | state_ = PROCESSING_MESSAGE; | 
|  |  | 
|  | std::string method_attr = message->Attr(kMethodAttributeQName); | 
|  | Method method = ParseMethodString(method_attr); | 
|  |  | 
|  | // The host picked a method different from the one the client had selected. | 
|  | if (method != current_method_) { | 
|  | // The host must pick a method that is valid and supported by the client, | 
|  | // and it must not change methods after it has picked one. | 
|  | if (method_set_by_host_ || method == Method::INVALID || | 
|  | std::find(methods_.begin(), methods_.end(), method) == methods_.end()) { | 
|  | state_ = REJECTED; | 
|  | rejection_reason_ = PROTOCOL_ERROR; | 
|  | resume_callback.Run(); | 
|  | return; | 
|  | } | 
|  |  | 
|  | current_method_ = method; | 
|  | method_set_by_host_ = true; | 
|  |  | 
|  | // Copy the message since the authenticator may process it asynchronously. | 
|  | base::Closure callback = base::Bind( | 
|  | &NegotiatingAuthenticatorBase::ProcessMessageInternal, | 
|  | base::Unretained(this), base::Owned(new buzz::XmlElement(*message)), | 
|  | resume_callback); | 
|  | CreateAuthenticatorForCurrentMethod(WAITING_MESSAGE, callback); | 
|  | return; | 
|  | } | 
|  | ProcessMessageInternal(message, resume_callback); | 
|  | } | 
|  |  | 
|  | std::unique_ptr<buzz::XmlElement> | 
|  | NegotiatingClientAuthenticator::GetNextMessage() { | 
|  | DCHECK_EQ(state(), MESSAGE_READY); | 
|  |  | 
|  | // This is the first message to the host, send a list of supported methods. | 
|  | if (current_method_ == Method::INVALID) { | 
|  | // If no authentication method has been chosen, see if we can optimistically | 
|  | // choose one. | 
|  | std::unique_ptr<buzz::XmlElement> result; | 
|  | CreatePreferredAuthenticator(); | 
|  | if (current_authenticator_) { | 
|  | DCHECK(current_authenticator_->state() == MESSAGE_READY); | 
|  | result = GetNextMessageInternal(); | 
|  | } else { | 
|  | result = CreateEmptyAuthenticatorMessage(); | 
|  | } | 
|  |  | 
|  | if (is_paired()) { | 
|  | // If the client is paired with the host then attach pairing client_id to | 
|  | // the message. | 
|  | buzz::XmlElement* pairing_tag = new buzz::XmlElement(kPairingInfoTag); | 
|  | result->AddElement(pairing_tag); | 
|  | pairing_tag->AddAttr(kClientIdAttribute, config_.pairing_client_id); | 
|  | } | 
|  |  | 
|  | // Include a list of supported methods. | 
|  | std::string supported_methods; | 
|  | for (Method method : methods_) { | 
|  | if (!supported_methods.empty()) | 
|  | supported_methods += kSupportedMethodsSeparator; | 
|  | supported_methods += MethodToString(method); | 
|  | } | 
|  | result->AddAttr(kSupportedMethodsAttributeQName, supported_methods); | 
|  | state_ = WAITING_MESSAGE; | 
|  | return result; | 
|  | } | 
|  | return GetNextMessageInternal(); | 
|  | } | 
|  |  | 
|  | void NegotiatingClientAuthenticator::CreateAuthenticatorForCurrentMethod( | 
|  | Authenticator::State preferred_initial_state, | 
|  | const base::Closure& resume_callback) { | 
|  | DCHECK_EQ(state(), PROCESSING_MESSAGE); | 
|  | DCHECK(current_method_ != Method::INVALID); | 
|  | switch (current_method_) { | 
|  | case Method::INVALID: | 
|  | NOTREACHED(); | 
|  | break; | 
|  |  | 
|  | case Method::THIRD_PARTY_SPAKE2_P224: | 
|  | current_authenticator_.reset(new ThirdPartyClientAuthenticator( | 
|  | base::Bind(&V2Authenticator::CreateForClient), | 
|  | config_.fetch_third_party_token_callback)); | 
|  | resume_callback.Run(); | 
|  | break; | 
|  |  | 
|  | case Method::THIRD_PARTY_SPAKE2_CURVE25519: | 
|  | current_authenticator_.reset(new ThirdPartyClientAuthenticator( | 
|  | base::Bind(&Spake2Authenticator::CreateForClient, local_id_, | 
|  | remote_id_), | 
|  | config_.fetch_third_party_token_callback)); | 
|  | resume_callback.Run(); | 
|  | break; | 
|  |  | 
|  | case Method::PAIRED_SPAKE2_P224: { | 
|  | PairingClientAuthenticator* pairing_authenticator = | 
|  | new PairingClientAuthenticator( | 
|  | config_, base::Bind(&V2Authenticator::CreateForClient)); | 
|  | current_authenticator_ = base::WrapUnique(pairing_authenticator); | 
|  | pairing_authenticator->Start(preferred_initial_state, resume_callback); | 
|  | break; | 
|  | } | 
|  |  | 
|  | case Method::PAIRED_SPAKE2_CURVE25519: { | 
|  | PairingClientAuthenticator* pairing_authenticator = | 
|  | new PairingClientAuthenticator( | 
|  | config_, base::Bind(&Spake2Authenticator::CreateForClient, | 
|  | local_id_, remote_id_)); | 
|  | current_authenticator_ = base::WrapUnique(pairing_authenticator); | 
|  | pairing_authenticator->Start(preferred_initial_state, resume_callback); | 
|  | break; | 
|  | } | 
|  |  | 
|  | case Method::SHARED_SECRET_PLAIN_SPAKE2_P224: | 
|  | case Method::SHARED_SECRET_SPAKE2_P224: | 
|  | case Method::SHARED_SECRET_SPAKE2_CURVE25519: | 
|  | config_.fetch_secret_callback.Run( | 
|  | false, | 
|  | base::Bind( | 
|  | &NegotiatingClientAuthenticator::CreateSharedSecretAuthenticator, | 
|  | weak_factory_.GetWeakPtr(), preferred_initial_state, | 
|  | resume_callback)); | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | void NegotiatingClientAuthenticator::CreatePreferredAuthenticator() { | 
|  | if (is_paired() && | 
|  | std::find(methods_.begin(), methods_.end(), Method::PAIRED_SPAKE2_P224) != | 
|  | methods_.end()) { | 
|  | PairingClientAuthenticator* pairing_authenticator = | 
|  | new PairingClientAuthenticator( | 
|  | config_, base::Bind(&V2Authenticator::CreateForClient)); | 
|  | current_authenticator_ = base::WrapUnique(pairing_authenticator); | 
|  | pairing_authenticator->StartPaired(MESSAGE_READY); | 
|  | current_method_ = Method::PAIRED_SPAKE2_P224; | 
|  | } | 
|  | } | 
|  |  | 
|  | void NegotiatingClientAuthenticator::CreateSharedSecretAuthenticator( | 
|  | Authenticator::State initial_state, | 
|  | const base::Closure& resume_callback, | 
|  | const std::string& shared_secret) { | 
|  | std::string shared_secret_hash = | 
|  | (current_method_ == Method::SHARED_SECRET_PLAIN_SPAKE2_P224) | 
|  | ? shared_secret | 
|  | : GetSharedSecretHash(config_.host_id, shared_secret); | 
|  |  | 
|  | if (current_method_ == Method::SHARED_SECRET_SPAKE2_CURVE25519) { | 
|  | current_authenticator_ = Spake2Authenticator::CreateForClient( | 
|  | local_id_, remote_id_, shared_secret_hash, initial_state); | 
|  | } else { | 
|  | current_authenticator_ = | 
|  | V2Authenticator::CreateForClient(shared_secret_hash, initial_state); | 
|  | } | 
|  | resume_callback.Run(); | 
|  | } | 
|  |  | 
|  | bool NegotiatingClientAuthenticator::is_paired() { | 
|  | return !config_.pairing_client_id.empty() && !config_.pairing_secret.empty(); | 
|  | } | 
|  |  | 
|  | }  // namespace protocol | 
|  | }  // namespace remoting |