1// Copyright 2013 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "remoting/protocol/negotiating_host_authenticator.h" 6 7#include <algorithm> 8#include <sstream> 9 10#include "base/bind.h" 11#include "base/callback.h" 12#include "base/logging.h" 13#include "base/strings/string_split.h" 14#include "remoting/base/rsa_key_pair.h" 15#include "remoting/protocol/channel_authenticator.h" 16#include "remoting/protocol/pairing_host_authenticator.h" 17#include "remoting/protocol/pairing_registry.h" 18#include "remoting/protocol/token_validator.h" 19#include "remoting/protocol/v2_authenticator.h" 20#include "third_party/webrtc/libjingle/xmllite/xmlelement.h" 21 22namespace remoting { 23namespace protocol { 24 25NegotiatingHostAuthenticator::NegotiatingHostAuthenticator( 26 const std::string& local_cert, 27 scoped_refptr<RsaKeyPair> key_pair) 28 : NegotiatingAuthenticatorBase(WAITING_MESSAGE), 29 local_cert_(local_cert), 30 local_key_pair_(key_pair) { 31} 32 33// static 34scoped_ptr<Authenticator> NegotiatingHostAuthenticator::CreateWithSharedSecret( 35 const std::string& local_cert, 36 scoped_refptr<RsaKeyPair> key_pair, 37 const std::string& shared_secret_hash, 38 AuthenticationMethod::HashFunction hash_function, 39 scoped_refptr<PairingRegistry> pairing_registry) { 40 scoped_ptr<NegotiatingHostAuthenticator> result( 41 new NegotiatingHostAuthenticator(local_cert, key_pair)); 42 result->shared_secret_hash_ = shared_secret_hash; 43 result->pairing_registry_ = pairing_registry; 44 result->AddMethod(AuthenticationMethod::Spake2(hash_function)); 45 if (pairing_registry.get()) { 46 result->AddMethod(AuthenticationMethod::Spake2Pair()); 47 } 48 return scoped_ptr<Authenticator>(result.Pass()); 49} 50 51// static 52scoped_ptr<Authenticator> 53NegotiatingHostAuthenticator::CreateWithThirdPartyAuth( 54 const std::string& local_cert, 55 scoped_refptr<RsaKeyPair> key_pair, 56 scoped_ptr<TokenValidator> token_validator) { 57 scoped_ptr<NegotiatingHostAuthenticator> result( 58 new NegotiatingHostAuthenticator(local_cert, key_pair)); 59 result->token_validator_ = token_validator.Pass(); 60 result->AddMethod(AuthenticationMethod::ThirdParty()); 61 return scoped_ptr<Authenticator>(result.Pass()); 62} 63 64NegotiatingHostAuthenticator::~NegotiatingHostAuthenticator() { 65} 66 67void NegotiatingHostAuthenticator::ProcessMessage( 68 const buzz::XmlElement* message, 69 const base::Closure& resume_callback) { 70 DCHECK_EQ(state(), WAITING_MESSAGE); 71 72 std::string method_attr = message->Attr(kMethodAttributeQName); 73 AuthenticationMethod method = AuthenticationMethod::FromString(method_attr); 74 75 // If the host has already chosen a method, it can't be changed by the client. 76 if (current_method_.is_valid() && method != current_method_) { 77 state_ = REJECTED; 78 rejection_reason_ = PROTOCOL_ERROR; 79 resume_callback.Run(); 80 return; 81 } 82 83 // If the client did not specify a preferred auth method, or specified an 84 // unknown or unsupported method, then select the first known method from 85 // the supported-methods attribute. 86 if (!method.is_valid() || 87 std::find(methods_.begin(), methods_.end(), method) == methods_.end()) { 88 method = AuthenticationMethod::Invalid(); 89 90 std::string supported_methods_attr = 91 message->Attr(kSupportedMethodsAttributeQName); 92 if (supported_methods_attr.empty()) { 93 // Message contains neither method nor supported-methods attributes. 94 state_ = REJECTED; 95 rejection_reason_ = PROTOCOL_ERROR; 96 resume_callback.Run(); 97 return; 98 } 99 100 // Find the first mutually-supported method in the client's list of 101 // supported-methods. 102 std::vector<std::string> supported_methods_strs; 103 base::SplitString(supported_methods_attr, kSupportedMethodsSeparator, 104 &supported_methods_strs); 105 for (std::vector<std::string>::iterator it = supported_methods_strs.begin(); 106 it != supported_methods_strs.end(); ++it) { 107 AuthenticationMethod list_value = AuthenticationMethod::FromString(*it); 108 if (list_value.is_valid() && 109 std::find(methods_.begin(), 110 methods_.end(), list_value) != methods_.end()) { 111 // Found common method. 112 method = list_value; 113 break; 114 } 115 } 116 117 if (!method.is_valid()) { 118 // Failed to find a common auth method. 119 state_ = REJECTED; 120 rejection_reason_ = PROTOCOL_ERROR; 121 resume_callback.Run(); 122 return; 123 } 124 125 // Drop the current message because we've chosen a different method. 126 current_method_ = method; 127 state_ = PROCESSING_MESSAGE; 128 CreateAuthenticator(MESSAGE_READY, base::Bind( 129 &NegotiatingHostAuthenticator::UpdateState, 130 base::Unretained(this), resume_callback)); 131 return; 132 } 133 134 // If the client specified a supported method, and the host hasn't chosen a 135 // method yet, use the client's preferred method and process the message. 136 if (!current_method_.is_valid()) { 137 current_method_ = method; 138 state_ = PROCESSING_MESSAGE; 139 // Copy the message since the authenticator may process it asynchronously. 140 CreateAuthenticator(WAITING_MESSAGE, base::Bind( 141 &NegotiatingAuthenticatorBase::ProcessMessageInternal, 142 base::Unretained(this), base::Owned(new buzz::XmlElement(*message)), 143 resume_callback)); 144 return; 145 } 146 147 // If the client is using the host's current method, just process the message. 148 ProcessMessageInternal(message, resume_callback); 149} 150 151scoped_ptr<buzz::XmlElement> NegotiatingHostAuthenticator::GetNextMessage() { 152 return GetNextMessageInternal(); 153} 154 155void NegotiatingHostAuthenticator::CreateAuthenticator( 156 Authenticator::State preferred_initial_state, 157 const base::Closure& resume_callback) { 158 DCHECK(current_method_.is_valid()); 159 160 if (current_method_.type() == AuthenticationMethod::THIRD_PARTY) { 161 // |ThirdPartyHostAuthenticator| takes ownership of |token_validator_|. 162 // The authentication method negotiation logic should guarantee that only 163 // one |ThirdPartyHostAuthenticator| will need to be created per session. 164 DCHECK(token_validator_); 165 current_authenticator_.reset(new ThirdPartyHostAuthenticator( 166 local_cert_, local_key_pair_, token_validator_.Pass())); 167 } else if (current_method_ == AuthenticationMethod::Spake2Pair() && 168 preferred_initial_state == WAITING_MESSAGE) { 169 // If the client requested Spake2Pair and sent an initial message, attempt 170 // the paired connection protocol. 171 current_authenticator_.reset(new PairingHostAuthenticator( 172 pairing_registry_, local_cert_, local_key_pair_, shared_secret_hash_)); 173 } else { 174 // In all other cases, use the V2 protocol. Note that this includes the 175 // case where the protocol is Spake2Pair but the client is not yet paired. 176 // In this case, the on-the-wire protocol is plain Spake2, advertised as 177 // Spake2Pair so that the client knows that the host supports pairing and 178 // that it can therefore present the option to the user when they enter 179 // the PIN. 180 DCHECK(current_method_.type() == AuthenticationMethod::SPAKE2 || 181 current_method_.type() == AuthenticationMethod::SPAKE2_PAIR); 182 current_authenticator_ = V2Authenticator::CreateForHost( 183 local_cert_, local_key_pair_, shared_secret_hash_, 184 preferred_initial_state); 185 } 186 resume_callback.Run(); 187} 188 189} // namespace protocol 190} // namespace remoting 191