| // Copyright 2013 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "remoting/protocol/negotiating_client_authenticator.h" |
| |
| #include <memory> |
| #include <sstream> |
| #include <utility> |
| |
| #include "base/check_op.h" |
| #include "base/containers/contains.h" |
| #include "base/functional/bind.h" |
| #include "base/functional/callback.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/notreached.h" |
| #include "base/strings/string_split.h" |
| #include "remoting/protocol/auth_util.h" |
| #include "remoting/protocol/channel_authenticator.h" |
| #include "remoting/protocol/pairing_client_authenticator.h" |
| #include "remoting/protocol/spake2_authenticator.h" |
| #include "remoting/protocol/v2_authenticator.h" |
| #include "third_party/libjingle_xmpp/xmllite/xmlelement.h" |
| |
| namespace remoting::protocol { |
| |
| NegotiatingClientAuthenticator::NegotiatingClientAuthenticator( |
| const std::string& local_id, |
| const std::string& remote_id, |
| const ClientAuthenticationConfig& config) |
| : NegotiatingAuthenticatorBase(MESSAGE_READY), |
| local_id_(local_id), |
| remote_id_(remote_id), |
| config_(config) { |
| if (!config_.fetch_third_party_token_callback.is_null()) { |
| AddMethod(Method::THIRD_PARTY_SPAKE2_CURVE25519); |
| AddMethod(Method::THIRD_PARTY_SPAKE2_P224); |
| } |
| |
| AddMethod(Method::PAIRED_SPAKE2_CURVE25519); |
| AddMethod(Method::PAIRED_SPAKE2_P224); |
| |
| AddMethod(Method::SHARED_SECRET_SPAKE2_CURVE25519); |
| AddMethod(Method::SHARED_SECRET_SPAKE2_P224); |
| |
| AddMethod(Method::SHARED_SECRET_PLAIN_SPAKE2_P224); |
| } |
| |
| NegotiatingClientAuthenticator::~NegotiatingClientAuthenticator() = default; |
| |
| void NegotiatingClientAuthenticator::ProcessMessage( |
| const jingle_xmpp::XmlElement* message, |
| base::OnceClosure resume_callback) { |
| DCHECK_EQ(state(), WAITING_MESSAGE); |
| state_ = PROCESSING_MESSAGE; |
| |
| std::string method_attr = message->Attr(kMethodAttributeQName); |
| Method method = ParseMethodString(method_attr); |
| |
| // The host picked a method different from the one the client had selected. |
| if (method != current_method_) { |
| // The host must pick a method that is valid and supported by the client, |
| // and it must not change methods after it has picked one. |
| if (method_set_by_host_ || method == Method::INVALID || |
| !base::Contains(methods_, method)) { |
| state_ = REJECTED; |
| rejection_reason_ = RejectionReason::PROTOCOL_ERROR; |
| std::move(resume_callback).Run(); |
| return; |
| } |
| |
| current_method_ = method; |
| method_set_by_host_ = true; |
| |
| // Copy the message since the authenticator may process it asynchronously. |
| CreateAuthenticatorForCurrentMethod( |
| WAITING_MESSAGE, |
| base::BindOnce(&NegotiatingAuthenticatorBase::ProcessMessageInternal, |
| base::Unretained(this), |
| base::Owned(new jingle_xmpp::XmlElement(*message)), |
| std::move(resume_callback))); |
| return; |
| } |
| ProcessMessageInternal(message, std::move(resume_callback)); |
| } |
| |
| std::unique_ptr<jingle_xmpp::XmlElement> |
| NegotiatingClientAuthenticator::GetNextMessage() { |
| DCHECK_EQ(state(), MESSAGE_READY); |
| |
| // This is the first message to the host, send a list of supported methods. |
| if (current_method_ == Method::INVALID) { |
| // If no authentication method has been chosen, see if we can optimistically |
| // choose one. |
| std::unique_ptr<jingle_xmpp::XmlElement> result; |
| CreatePreferredAuthenticator(); |
| if (current_authenticator_) { |
| DCHECK(current_authenticator_->state() == MESSAGE_READY); |
| result = GetNextMessageInternal(); |
| } else { |
| result = CreateEmptyAuthenticatorMessage(); |
| } |
| |
| if (is_paired()) { |
| // If the client is paired with the host then attach pairing client_id to |
| // the message. |
| jingle_xmpp::XmlElement* pairing_tag = |
| new jingle_xmpp::XmlElement(kPairingInfoTag); |
| result->AddElement(pairing_tag); |
| pairing_tag->AddAttr(kClientIdAttribute, config_.pairing_client_id); |
| } |
| |
| // Include a list of supported methods. |
| std::string supported_methods; |
| for (Method method : methods_) { |
| if (!supported_methods.empty()) { |
| supported_methods += kSupportedMethodsSeparator; |
| } |
| supported_methods += MethodToString(method); |
| } |
| result->AddAttr(kSupportedMethodsAttributeQName, supported_methods); |
| state_ = WAITING_MESSAGE; |
| return result; |
| } |
| return GetNextMessageInternal(); |
| } |
| |
| void NegotiatingClientAuthenticator::CreateAuthenticatorForCurrentMethod( |
| Authenticator::State preferred_initial_state, |
| base::OnceClosure resume_callback) { |
| DCHECK_EQ(state(), PROCESSING_MESSAGE); |
| DCHECK(current_method_ != Method::INVALID); |
| switch (current_method_) { |
| case Method::INVALID: |
| NOTREACHED(); |
| break; |
| |
| case Method::THIRD_PARTY_SPAKE2_P224: |
| current_authenticator_ = std::make_unique<ThirdPartyClientAuthenticator>( |
| base::BindRepeating(&V2Authenticator::CreateForClient), |
| config_.fetch_third_party_token_callback); |
| std::move(resume_callback).Run(); |
| break; |
| |
| case Method::THIRD_PARTY_SPAKE2_CURVE25519: |
| current_authenticator_ = std::make_unique<ThirdPartyClientAuthenticator>( |
| base::BindRepeating(&Spake2Authenticator::CreateForClient, local_id_, |
| remote_id_), |
| config_.fetch_third_party_token_callback); |
| std::move(resume_callback).Run(); |
| break; |
| |
| case Method::PAIRED_SPAKE2_P224: { |
| PairingClientAuthenticator* pairing_authenticator = |
| new PairingClientAuthenticator( |
| config_, base::BindRepeating(&V2Authenticator::CreateForClient)); |
| current_authenticator_ = base::WrapUnique(pairing_authenticator); |
| pairing_authenticator->Start(preferred_initial_state, |
| std::move(resume_callback)); |
| break; |
| } |
| |
| case Method::PAIRED_SPAKE2_CURVE25519: { |
| PairingClientAuthenticator* pairing_authenticator = |
| new PairingClientAuthenticator( |
| config_, |
| base::BindRepeating(&Spake2Authenticator::CreateForClient, |
| local_id_, remote_id_)); |
| current_authenticator_ = base::WrapUnique(pairing_authenticator); |
| pairing_authenticator->Start(preferred_initial_state, |
| std::move(resume_callback)); |
| break; |
| } |
| |
| case Method::SHARED_SECRET_PLAIN_SPAKE2_P224: |
| case Method::SHARED_SECRET_SPAKE2_P224: |
| case Method::SHARED_SECRET_SPAKE2_CURVE25519: |
| config_.fetch_secret_callback.Run( |
| false, |
| base::BindRepeating( |
| &NegotiatingClientAuthenticator::CreateSharedSecretAuthenticator, |
| weak_factory_.GetWeakPtr(), preferred_initial_state, |
| base::Passed(std::move(resume_callback)))); |
| break; |
| } |
| } |
| |
| void NegotiatingClientAuthenticator::CreatePreferredAuthenticator() { |
| if (is_paired() && base::Contains(methods_, Method::PAIRED_SPAKE2_P224)) { |
| PairingClientAuthenticator* pairing_authenticator = |
| new PairingClientAuthenticator( |
| config_, base::BindRepeating(&V2Authenticator::CreateForClient)); |
| current_authenticator_ = base::WrapUnique(pairing_authenticator); |
| pairing_authenticator->StartPaired(MESSAGE_READY); |
| current_method_ = Method::PAIRED_SPAKE2_P224; |
| } |
| } |
| |
| void NegotiatingClientAuthenticator::CreateSharedSecretAuthenticator( |
| Authenticator::State initial_state, |
| base::OnceClosure resume_callback, |
| const std::string& shared_secret) { |
| std::string shared_secret_hash = |
| (current_method_ == Method::SHARED_SECRET_PLAIN_SPAKE2_P224) |
| ? shared_secret |
| : GetSharedSecretHash(config_.host_id, shared_secret); |
| |
| if (current_method_ == Method::SHARED_SECRET_SPAKE2_CURVE25519) { |
| current_authenticator_ = Spake2Authenticator::CreateForClient( |
| local_id_, remote_id_, shared_secret_hash, initial_state); |
| } else { |
| current_authenticator_ = |
| V2Authenticator::CreateForClient(shared_secret_hash, initial_state); |
| } |
| std::move(resume_callback).Run(); |
| } |
| |
| bool NegotiatingClientAuthenticator::is_paired() { |
| return !config_.pairing_client_id.empty() && !config_.pairing_secret.empty(); |
| } |
| |
| } // namespace remoting::protocol |