// 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_host_authenticator.h" #include <algorithm> #include <sstream> #include "base/bind.h" #include "base/callback.h" #include "base/logging.h" #include "base/strings/string_split.h" #include "remoting/base/rsa_key_pair.h" #include "remoting/protocol/channel_authenticator.h" #include "remoting/protocol/pairing_host_authenticator.h" #include "remoting/protocol/pairing_registry.h" #include "remoting/protocol/v2_authenticator.h" #include "third_party/libjingle/source/talk/xmllite/xmlelement.h" namespace remoting { namespace protocol { NegotiatingHostAuthenticator::NegotiatingHostAuthenticator( const std::string& local_cert, scoped_refptr<RsaKeyPair> key_pair) : NegotiatingAuthenticatorBase(WAITING_MESSAGE), local_cert_(local_cert), local_key_pair_(key_pair) { } // static scoped_ptr<Authenticator> NegotiatingHostAuthenticator::CreateWithSharedSecret( const std::string& local_cert, scoped_refptr<RsaKeyPair> key_pair, const std::string& shared_secret_hash, AuthenticationMethod::HashFunction hash_function, scoped_refptr<PairingRegistry> pairing_registry) { scoped_ptr<NegotiatingHostAuthenticator> result( new NegotiatingHostAuthenticator(local_cert, key_pair)); result->shared_secret_hash_ = shared_secret_hash; result->pairing_registry_ = pairing_registry; result->AddMethod(AuthenticationMethod::Spake2(hash_function)); if (pairing_registry.get()) { result->AddMethod(AuthenticationMethod::Spake2Pair()); } return scoped_ptr<Authenticator>(result.Pass()); } // static scoped_ptr<Authenticator> NegotiatingHostAuthenticator::CreateWithThirdPartyAuth( const std::string& local_cert, scoped_refptr<RsaKeyPair> key_pair, scoped_ptr<ThirdPartyHostAuthenticator::TokenValidator> token_validator) { scoped_ptr<NegotiatingHostAuthenticator> result( new NegotiatingHostAuthenticator(local_cert, key_pair)); result->token_validator_ = token_validator.Pass(); result->AddMethod(AuthenticationMethod::ThirdParty()); return scoped_ptr<Authenticator>(result.Pass()); } NegotiatingHostAuthenticator::~NegotiatingHostAuthenticator() { } void NegotiatingHostAuthenticator::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); // If the host has already chosen a method, it can't be changed by the client. if (current_method_.is_valid() && method != current_method_) { state_ = REJECTED; rejection_reason_ = PROTOCOL_ERROR; resume_callback.Run(); return; } // If the client did not specify a preferred auth method, or specified an // unknown or unsupported method, then select the first known method from // the supported-methods attribute. if (!method.is_valid() || std::find(methods_.begin(), methods_.end(), method) == methods_.end()) { method = AuthenticationMethod::Invalid(); std::string supported_methods_attr = message->Attr(kSupportedMethodsAttributeQName); if (supported_methods_attr.empty()) { // Message contains neither method nor supported-methods attributes. state_ = REJECTED; rejection_reason_ = PROTOCOL_ERROR; resume_callback.Run(); return; } // Find the first mutually-supported method in the client's list of // supported-methods. std::vector<std::string> supported_methods_strs; base::SplitString(supported_methods_attr, kSupportedMethodsSeparator, &supported_methods_strs); for (std::vector<std::string>::iterator it = supported_methods_strs.begin(); it != supported_methods_strs.end(); ++it) { AuthenticationMethod list_value = AuthenticationMethod::FromString(*it); if (list_value.is_valid() && std::find(methods_.begin(), methods_.end(), list_value) != methods_.end()) { // Found common method. method = list_value; break; } } if (!method.is_valid()) { // Failed to find a common auth method. state_ = REJECTED; rejection_reason_ = PROTOCOL_ERROR; resume_callback.Run(); return; } // Drop the current message because we've chosen a different method. current_method_ = method; state_ = PROCESSING_MESSAGE; CreateAuthenticator(MESSAGE_READY, base::Bind( &NegotiatingHostAuthenticator::UpdateState, base::Unretained(this), resume_callback)); return; } // If the client specified a supported method, and the host hasn't chosen a // method yet, use the client's preferred method and process the message. if (!current_method_.is_valid()) { current_method_ = method; state_ = PROCESSING_MESSAGE; // Copy the message since the authenticator may process it asynchronously. CreateAuthenticator(WAITING_MESSAGE, base::Bind( &NegotiatingAuthenticatorBase::ProcessMessageInternal, base::Unretained(this), base::Owned(new buzz::XmlElement(*message)), resume_callback)); return; } // If the client is using the host's current method, just process the message. ProcessMessageInternal(message, resume_callback); } scoped_ptr<buzz::XmlElement> NegotiatingHostAuthenticator::GetNextMessage() { return GetNextMessageInternal(); } void NegotiatingHostAuthenticator::CreateAuthenticator( Authenticator::State preferred_initial_state, const base::Closure& resume_callback) { DCHECK(current_method_.is_valid()); if (current_method_.type() == AuthenticationMethod::THIRD_PARTY) { // |ThirdPartyHostAuthenticator| takes ownership of |token_validator_|. // The authentication method negotiation logic should guarantee that only // one |ThirdPartyHostAuthenticator| will need to be created per session. DCHECK(token_validator_); current_authenticator_.reset(new ThirdPartyHostAuthenticator( local_cert_, local_key_pair_, token_validator_.Pass())); } else if (current_method_ == AuthenticationMethod::Spake2Pair() && preferred_initial_state == WAITING_MESSAGE) { // If the client requested Spake2Pair and sent an initial message, attempt // the paired connection protocol. current_authenticator_.reset(new PairingHostAuthenticator( pairing_registry_, local_cert_, local_key_pair_, shared_secret_hash_)); } else { // In all other cases, use the V2 protocol. Note that this includes the // case where the protocol is Spake2Pair but the client is not yet paired. // In this case, the on-the-wire protocol is plain Spake2, advertised as // Spake2Pair so that the client knows that the host supports pairing and // that it can therefore present the option to the user when they enter // the PIN. DCHECK(current_method_.type() == AuthenticationMethod::SPAKE2 || current_method_.type() == AuthenticationMethod::SPAKE2_PAIR); current_authenticator_ = V2Authenticator::CreateForHost( local_cert_, local_key_pair_, shared_secret_hash_, preferred_initial_state); } resume_callback.Run(); } } // namespace protocol } // namespace remoting