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/pairing_authenticator_base.h"
6
7#include "base/bind.h"
8#include "remoting/base/constants.h"
9#include "remoting/protocol/channel_authenticator.h"
10
11namespace remoting {
12namespace protocol {
13
14const buzz::StaticQName PairingAuthenticatorBase::kPairingInfoTag =
15    { kChromotingXmlNamespace, "pairing-info" };
16const buzz::StaticQName PairingAuthenticatorBase::kClientIdAttribute =
17    { "", "client-id" };
18
19namespace {
20const buzz::StaticQName kPairingFailedTag =
21    { kChromotingXmlNamespace, "pairing-failed" };
22const buzz::StaticQName kPairingErrorAttribute = { "", "error" };
23}  // namespace
24
25PairingAuthenticatorBase::PairingAuthenticatorBase()
26    : using_paired_secret_(false),
27      waiting_for_authenticator_(false),
28      weak_factory_(this) {
29}
30
31PairingAuthenticatorBase::~PairingAuthenticatorBase() {
32}
33
34Authenticator::State PairingAuthenticatorBase::state() const {
35  if (waiting_for_authenticator_) {
36    return PROCESSING_MESSAGE;
37  }
38  return v2_authenticator_->state();
39}
40
41Authenticator::RejectionReason
42PairingAuthenticatorBase::rejection_reason() const {
43  if (!v2_authenticator_) {
44    return PROTOCOL_ERROR;
45  }
46  return v2_authenticator_->rejection_reason();
47}
48
49void PairingAuthenticatorBase::ProcessMessage(
50    const buzz::XmlElement* message,
51    const base::Closure& resume_callback) {
52  DCHECK_EQ(state(), WAITING_MESSAGE);
53
54  // The client authenticator creates the underlying authenticator in the ctor
55  // and the host creates it in response to the first message before deferring
56  // to this class to process it. Either way, it should exist here.
57  DCHECK(v2_authenticator_);
58
59  // If pairing failed, and we haven't already done so, try again with the PIN.
60  if (using_paired_secret_ && HasErrorMessage(message)) {
61    using_paired_secret_ = false;
62    waiting_for_authenticator_ = true;
63    v2_authenticator_.reset();
64    SetAuthenticatorCallback set_authenticator = base::Bind(
65        &PairingAuthenticatorBase::SetAuthenticatorAndProcessMessage,
66        weak_factory_.GetWeakPtr(), base::Owned(new buzz::XmlElement(*message)),
67        resume_callback);
68    CreateV2AuthenticatorWithPIN(WAITING_MESSAGE, set_authenticator);
69    return;
70  }
71
72  // Pass the message to the underlying authenticator for processing, but
73  // check for a failed SPAKE exchange if we're using the paired secret. In
74  // this case the pairing protocol can continue by communicating the error
75  // to the peer and retrying with the PIN.
76  v2_authenticator_->ProcessMessage(
77      message,
78      base::Bind(&PairingAuthenticatorBase::CheckForFailedSpakeExchange,
79                 weak_factory_.GetWeakPtr(), resume_callback));
80}
81
82scoped_ptr<buzz::XmlElement> PairingAuthenticatorBase::GetNextMessage() {
83  DCHECK_EQ(state(), MESSAGE_READY);
84  scoped_ptr<buzz::XmlElement> result = v2_authenticator_->GetNextMessage();
85  AddPairingElements(result.get());
86  MaybeAddErrorMessage(result.get());
87  return result.Pass();
88}
89
90scoped_ptr<ChannelAuthenticator>
91PairingAuthenticatorBase::CreateChannelAuthenticator() const {
92  return v2_authenticator_->CreateChannelAuthenticator();
93}
94
95void PairingAuthenticatorBase::MaybeAddErrorMessage(buzz::XmlElement* message) {
96  if (!error_message_.empty()) {
97    buzz::XmlElement* pairing_failed_tag =
98        new buzz::XmlElement(kPairingFailedTag);
99    pairing_failed_tag->AddAttr(kPairingErrorAttribute, error_message_);
100    message->AddElement(pairing_failed_tag);
101    error_message_.clear();
102  }
103}
104
105bool PairingAuthenticatorBase::HasErrorMessage(
106    const buzz::XmlElement* message) const {
107  const buzz::XmlElement* pairing_failed_tag =
108      message->FirstNamed(kPairingFailedTag);
109  if (pairing_failed_tag) {
110    std::string error = pairing_failed_tag->Attr(kPairingErrorAttribute);
111    LOG(ERROR) << "Pairing failed: " << error;
112  }
113  return pairing_failed_tag != NULL;
114}
115
116void PairingAuthenticatorBase::CheckForFailedSpakeExchange(
117    const base::Closure& resume_callback) {
118  // If the SPAKE exchange failed due to invalid credentials, and those
119  // credentials were the paired secret, then notify the peer that the
120  // PIN-less connection failed and retry using the PIN.
121  if (v2_authenticator_->state() == REJECTED &&
122      v2_authenticator_->rejection_reason() == INVALID_CREDENTIALS &&
123      using_paired_secret_) {
124    using_paired_secret_ = false;
125    error_message_ = "invalid-shared-secret";
126    v2_authenticator_.reset();
127    buzz::XmlElement* no_message = NULL;
128    SetAuthenticatorCallback set_authenticator = base::Bind(
129        &PairingAuthenticatorBase::SetAuthenticatorAndProcessMessage,
130        weak_factory_.GetWeakPtr(), no_message, resume_callback);
131    CreateV2AuthenticatorWithPIN(MESSAGE_READY, set_authenticator);
132    return;
133  }
134
135  resume_callback.Run();
136}
137
138void PairingAuthenticatorBase::SetAuthenticatorAndProcessMessage(
139    const buzz::XmlElement* message,
140    const base::Closure& resume_callback,
141    scoped_ptr<Authenticator> authenticator) {
142  DCHECK(!v2_authenticator_);
143  DCHECK(authenticator);
144  waiting_for_authenticator_ = false;
145  v2_authenticator_ = authenticator.Pass();
146  if (message) {
147    ProcessMessage(message, resume_callback);
148  } else {
149    resume_callback.Run();
150  }
151}
152
153}  // namespace protocol
154}  // namespace remoting
155