Home | History | Annotate | Download | only in protocol
      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