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