// 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 #include #include "base/bind.h" #include "base/callback.h" #include "base/logging.h" #include "base/strings/string_split.h" #include "remoting/protocol/channel_authenticator.h" #include "remoting/protocol/pairing_client_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& client_pairing_id, const std::string& shared_secret, const std::string& authentication_tag, const FetchSecretCallback& fetch_secret_callback, scoped_ptr token_fetcher, const std::vector& methods) : NegotiatingAuthenticatorBase(MESSAGE_READY), client_pairing_id_(client_pairing_id), shared_secret_(shared_secret), authentication_tag_(authentication_tag), fetch_secret_callback_(fetch_secret_callback), token_fetcher_(token_fetcher.Pass()), method_set_by_host_(false), weak_factory_(this) { DCHECK(!methods.empty()); for (std::vector::const_iterator it = methods.begin(); it != methods.end(); ++it) { AddMethod(*it); } } NegotiatingClientAuthenticator::~NegotiatingClientAuthenticator() { } void NegotiatingClientAuthenticator::ProcessMessage( const buzz::XmlElement* message, const base::Closure& resume_callback) { DCHECK_EQ(state(), WAITING_MESSAGE); std::string method_attr = message->Attr(kMethodAttributeQName); AuthenticationMethod method = AuthenticationMethod::FromString(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.is_valid() || 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; state_ = PROCESSING_MESSAGE; // 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); } scoped_ptr NegotiatingClientAuthenticator::GetNextMessage() { DCHECK_EQ(state(), MESSAGE_READY); // This is the first message to the host, send a list of supported methods. if (!current_method_.is_valid()) { // If no authentication method has been chosen, see if we can optimistically // choose one. scoped_ptr result; CreatePreferredAuthenticator(); if (current_authenticator_) { DCHECK(current_authenticator_->state() == MESSAGE_READY); result = GetNextMessageInternal(); } else { result = CreateEmptyAuthenticatorMessage(); } // Include a list of supported methods. std::stringstream supported_methods(std::stringstream::out); for (std::vector::iterator it = methods_.begin(); it != methods_.end(); ++it) { if (it != methods_.begin()) supported_methods << kSupportedMethodsSeparator; supported_methods << it->ToString(); } result->AddAttr(kSupportedMethodsAttributeQName, supported_methods.str()); state_ = WAITING_MESSAGE; return result.Pass(); } return GetNextMessageInternal(); } void NegotiatingClientAuthenticator::CreateAuthenticatorForCurrentMethod( Authenticator::State preferred_initial_state, const base::Closure& resume_callback) { DCHECK(current_method_.is_valid()); if (current_method_.type() == AuthenticationMethod::THIRD_PARTY) { // |ThirdPartyClientAuthenticator| takes ownership of |token_fetcher_|. // The authentication method negotiation logic should guarantee that only // one |ThirdPartyClientAuthenticator| will need to be created per session. DCHECK(token_fetcher_); current_authenticator_.reset(new ThirdPartyClientAuthenticator( token_fetcher_.Pass())); resume_callback.Run(); } else { DCHECK(current_method_.type() == AuthenticationMethod::SPAKE2 || current_method_.type() == AuthenticationMethod::SPAKE2_PAIR); bool pairing_supported = (current_method_.type() == AuthenticationMethod::SPAKE2_PAIR); SecretFetchedCallback callback = base::Bind( &NegotiatingClientAuthenticator::CreateV2AuthenticatorWithSecret, weak_factory_.GetWeakPtr(), preferred_initial_state, resume_callback); fetch_secret_callback_.Run(pairing_supported, callback); } } void NegotiatingClientAuthenticator::CreatePreferredAuthenticator() { if (!client_pairing_id_.empty() && !shared_secret_.empty() && std::find(methods_.begin(), methods_.end(), AuthenticationMethod::Spake2Pair()) != methods_.end()) { // If the client specified a pairing id and shared secret, then create a // PairingAuthenticator. current_authenticator_.reset(new PairingClientAuthenticator( client_pairing_id_, shared_secret_, fetch_secret_callback_, authentication_tag_)); current_method_ = AuthenticationMethod::Spake2Pair(); } } void NegotiatingClientAuthenticator::CreateV2AuthenticatorWithSecret( Authenticator::State initial_state, const base::Closure& resume_callback, const std::string& shared_secret) { current_authenticator_ = V2Authenticator::CreateForClient( AuthenticationMethod::ApplyHashFunction( current_method_.hash_function(), authentication_tag_, shared_secret), initial_state); resume_callback.Run(); } } // namespace protocol } // namespace remoting