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