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