| // Copyright (c) 2012 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_authenticator.h" |
| |
| #include <algorithm> |
| #include <sstream> |
| |
| #include "base/logging.h" |
| #include "base/string_split.h" |
| #include "crypto/rsa_private_key.h" |
| #include "remoting/protocol/channel_authenticator.h" |
| #include "remoting/protocol/v2_authenticator.h" |
| #include "third_party/libjingle/source/talk/xmllite/xmlelement.h" |
| |
| namespace remoting { |
| namespace protocol { |
| |
| namespace { |
| |
| const buzz::StaticQName kMethodAttributeQName = { "", "method" }; |
| const buzz::StaticQName kSupportedMethodsAttributeQName = |
| { "", "supported-methods" }; |
| |
| const char kSupportedMethodsSeparator = ','; |
| |
| } // namespace |
| |
| // static |
| bool NegotiatingAuthenticator::IsNegotiableMessage( |
| const buzz::XmlElement* message) { |
| return message->HasAttr(kSupportedMethodsAttributeQName); |
| } |
| |
| // static |
| scoped_ptr<Authenticator> NegotiatingAuthenticator::CreateForClient( |
| const std::string& authentication_tag, |
| const std::string& shared_secret, |
| const std::vector<AuthenticationMethod>& methods) { |
| scoped_ptr<NegotiatingAuthenticator> result( |
| new NegotiatingAuthenticator(MESSAGE_READY)); |
| result->authentication_tag_ = authentication_tag; |
| result->shared_secret_ = shared_secret; |
| |
| DCHECK(!methods.empty()); |
| for (std::vector<AuthenticationMethod>::const_iterator it = methods.begin(); |
| it != methods.end(); ++it) { |
| result->AddMethod(*it); |
| } |
| |
| return scoped_ptr<Authenticator>(result.Pass()); |
| } |
| |
| // static |
| scoped_ptr<Authenticator> NegotiatingAuthenticator::CreateForHost( |
| const std::string& local_cert, |
| const crypto::RSAPrivateKey& local_private_key, |
| const std::string& shared_secret_hash, |
| AuthenticationMethod::HashFunction hash_function) { |
| scoped_ptr<NegotiatingAuthenticator> result( |
| new NegotiatingAuthenticator(WAITING_MESSAGE)); |
| result->local_cert_ = local_cert; |
| result->local_private_key_.reset(local_private_key.Copy()); |
| result->shared_secret_hash_ = shared_secret_hash; |
| |
| result->AddMethod(AuthenticationMethod::Spake2(hash_function)); |
| |
| return scoped_ptr<Authenticator>(result.Pass()); |
| } |
| |
| |
| NegotiatingAuthenticator::NegotiatingAuthenticator( |
| Authenticator::State initial_state) |
| : certificate_sent_(false), |
| current_method_(AuthenticationMethod::Invalid()), |
| state_(initial_state), |
| rejection_reason_(INVALID_CREDENTIALS) { |
| } |
| |
| NegotiatingAuthenticator::~NegotiatingAuthenticator() { |
| } |
| |
| Authenticator::State NegotiatingAuthenticator::state() const { |
| return state_; |
| } |
| |
| Authenticator::RejectionReason |
| NegotiatingAuthenticator::rejection_reason() const { |
| return rejection_reason_; |
| } |
| |
| void NegotiatingAuthenticator::ProcessMessage(const buzz::XmlElement* message) { |
| DCHECK_EQ(state(), WAITING_MESSAGE); |
| |
| std::string method_attr = message->Attr(kMethodAttributeQName); |
| AuthenticationMethod method = AuthenticationMethod::FromString(method_attr); |
| |
| // Check if the remote end specified a method that is not supported locally. |
| if (method.is_valid() && |
| std::find(methods_.begin(), methods_.end(), method) == methods_.end()) { |
| method = AuthenticationMethod::Invalid(); |
| } |
| |
| // If the remote peer did not specify auth method or specified unknown |
| // method then select the first known method from the supported-methods |
| // attribute. |
| if (!method.is_valid()) { |
| 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; |
| return; |
| } |
| |
| // Find the first mutually-supported method in the peer'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; |
| return; |
| } |
| |
| // Drop the current message because we've chosen a different |
| // method. |
| state_ = MESSAGE_READY; |
| } |
| |
| DCHECK(method.is_valid()); |
| |
| // Replace current authenticator if the method has changed. |
| if (method != current_method_) { |
| current_method_ = method; |
| CreateAuthenticator(state_); |
| } |
| |
| if (state_ == WAITING_MESSAGE) { |
| current_authenticator_->ProcessMessage(message); |
| state_ = current_authenticator_->state(); |
| if (state_ == REJECTED) |
| rejection_reason_ = current_authenticator_->rejection_reason(); |
| } |
| } |
| |
| scoped_ptr<buzz::XmlElement> NegotiatingAuthenticator::GetNextMessage() { |
| DCHECK_EQ(state(), MESSAGE_READY); |
| |
| bool add_supported_methods_attr = false; |
| |
| // Initialize current method in case it is not initialized |
| // yet. Normally happens only on client. |
| if (!current_method_.is_valid()) { |
| CHECK(!methods_.empty()); |
| |
| // Initially try the first method. |
| current_method_ = methods_[0]; |
| CreateAuthenticator(MESSAGE_READY); |
| add_supported_methods_attr = true; |
| } |
| |
| scoped_ptr<buzz::XmlElement> result = |
| current_authenticator_->GetNextMessage(); |
| state_ = current_authenticator_->state(); |
| DCHECK_NE(state_, REJECTED); |
| |
| result->AddAttr(kMethodAttributeQName, current_method_.ToString()); |
| |
| if (add_supported_methods_attr) { |
| std::stringstream supported_methods(std::stringstream::out); |
| for (std::vector<AuthenticationMethod>::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()); |
| } |
| |
| return result.Pass(); |
| } |
| |
| void NegotiatingAuthenticator::AddMethod(const AuthenticationMethod& method) { |
| DCHECK(method.is_valid()); |
| methods_.push_back(method); |
| } |
| |
| scoped_ptr<ChannelAuthenticator> |
| NegotiatingAuthenticator::CreateChannelAuthenticator() const { |
| DCHECK_EQ(state(), ACCEPTED); |
| return current_authenticator_->CreateChannelAuthenticator(); |
| } |
| |
| bool NegotiatingAuthenticator::is_host_side() const { |
| return local_private_key_.get() != NULL; |
| } |
| |
| void NegotiatingAuthenticator::CreateAuthenticator(State initial_state) { |
| if (is_host_side()) { |
| current_authenticator_ = V2Authenticator::CreateForHost( |
| local_cert_, *local_private_key_.get(), |
| shared_secret_hash_, initial_state); |
| } else { |
| current_authenticator_ = V2Authenticator::CreateForClient( |
| AuthenticationMethod::ApplyHashFunction( |
| current_method_.hash_function(), |
| authentication_tag_, shared_secret_), |
| initial_state); |
| } |
| } |
| |
| } // namespace protocol |
| } // namespace remoting |