diff options
author | Joshua Duong <joshuaduong@google.com> | 2020-02-07 11:06:16 -0800 |
---|---|---|
committer | Joshua Duong <joshuaduong@google.com> | 2020-02-21 21:06:12 +0000 |
commit | c7a1fb8fd970c6fa41843f48cc7bcc02fd07800d (patch) | |
tree | 802bc92dc2935c5a13681a117f17168aed96d3e1 | |
parent | df8f1217d09de155420371dee30ebf5d884d1cd7 (diff) |
[adbwifi] Add pairing_connection library.
Bug: 111434128
Bug: 119494503
Test: atest adb_pairing_connection_test
Change-Id: I54d68c65067809832266d6c3043b63222c98a9cd
Exempt-From-Owner-Approval: approved already
-rw-r--r-- | adb/pairing_connection/Android.bp | 185 | ||||
-rw-r--r-- | adb/pairing_connection/include/adb/pairing/pairing_connection.h | 130 | ||||
-rw-r--r-- | adb/pairing_connection/include/adb/pairing/pairing_server.h | 111 | ||||
-rw-r--r-- | adb/pairing_connection/internal/constants.h | 34 | ||||
-rw-r--r-- | adb/pairing_connection/libadb_pairing_connection.map.txt | 10 | ||||
-rw-r--r-- | adb/pairing_connection/libadb_pairing_server.map.txt | 10 | ||||
-rw-r--r-- | adb/pairing_connection/pairing_connection.cpp | 491 | ||||
-rw-r--r-- | adb/pairing_connection/pairing_server.cpp | 466 | ||||
-rw-r--r-- | adb/pairing_connection/tests/Android.bp | 47 | ||||
-rw-r--r-- | adb/pairing_connection/tests/pairing_client.cpp | 201 | ||||
-rw-r--r-- | adb/pairing_connection/tests/pairing_client.h | 68 | ||||
-rw-r--r-- | adb/pairing_connection/tests/pairing_connection_test.cpp | 500 |
12 files changed, 2253 insertions, 0 deletions
diff --git a/adb/pairing_connection/Android.bp b/adb/pairing_connection/Android.bp new file mode 100644 index 000000000..c05385475 --- /dev/null +++ b/adb/pairing_connection/Android.bp @@ -0,0 +1,185 @@ +// Copyright (C) 2020 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +cc_defaults { + name: "libadb_pairing_connection_defaults", + cflags: [ + "-Wall", + "-Wextra", + "-Wthread-safety", + "-Werror", + ], + + compile_multilib: "both", + + srcs: [ + "pairing_connection.cpp", + ], + target: { + android: { + version_script: "libadb_pairing_connection.map.txt", + }, + windows: { + compile_multilib: "first", + enabled: true, + }, + }, + export_include_dirs: ["include"], + + visibility: [ + "//art:__subpackages__", + "//system/core/adb:__subpackages__", + "//frameworks/base/services:__subpackages__", + ], + apex_available: [ + "com.android.adbd", + ], + + // libadb_pairing_connection doesn't need an embedded build number. + use_version_lib: false, + + stl: "libc++_static", + + host_supported: true, + recovery_available: true, + + static_libs: [ + "libbase", + "libssl", + ], + shared_libs: [ + "libcrypto", + "liblog", + "libadb_pairing_auth", + ], +} + +cc_library { + name: "libadb_pairing_connection", + defaults: ["libadb_pairing_connection_defaults"], + + apex_available: [ + "com.android.adbd", + ], + + stubs: { + symbol_file: "libadb_pairing_connection.map.txt", + versions: ["30"], + }, + + static_libs: [ + "libadb_protos", + // Statically link libadb_tls_connection because it is not + // ABI-stable. + "libadb_tls_connection", + "libprotobuf-cpp-lite", + ], +} + +// For running atest (b/147158681) +cc_library_static { + name: "libadb_pairing_connection_static", + defaults: ["libadb_pairing_connection_defaults"], + + apex_available: [ + "//apex_available:platform", + ], + + static_libs: [ + "libadb_protos_static", + "libprotobuf-cpp-lite", + "libadb_tls_connection_static", + ], +} + +cc_defaults { + name: "libadb_pairing_server_defaults", + cflags: [ + "-Wall", + "-Wextra", + "-Wthread-safety", + "-Werror", + ], + + compile_multilib: "both", + + srcs: [ + "pairing_server.cpp", + ], + target: { + android: { + version_script: "libadb_pairing_server.map.txt", + }, + }, + export_include_dirs: ["include"], + + visibility: [ + "//art:__subpackages__", + "//system/core/adb:__subpackages__", + "//frameworks/base/services:__subpackages__", + ], + + host_supported: true, + recovery_available: true, + + stl: "libc++_static", + + static_libs: [ + "libbase", + ], + shared_libs: [ + "libcrypto", + "libcrypto_utils", + "libcutils", + "liblog", + "libadb_pairing_auth", + "libadb_pairing_connection", + ], +} + +cc_library { + name: "libadb_pairing_server", + defaults: ["libadb_pairing_server_defaults"], + + apex_available: [ + "com.android.adbd", + ], + + stubs: { + symbol_file: "libadb_pairing_server.map.txt", + versions: ["30"], + }, + + static_libs: [ + // Statically link libadb_crypto because it is not + // ABI-stable. + "libadb_crypto", + "libadb_protos", + ], +} + +// For running atest (b/147158681) +cc_library_static { + name: "libadb_pairing_server_static", + defaults: ["libadb_pairing_server_defaults"], + + apex_available: [ + "//apex_available:platform", + ], + + static_libs: [ + "libadb_crypto_static", + "libadb_protos_static", + ], +} diff --git a/adb/pairing_connection/include/adb/pairing/pairing_connection.h b/adb/pairing_connection/include/adb/pairing/pairing_connection.h new file mode 100644 index 000000000..3543b8738 --- /dev/null +++ b/adb/pairing_connection/include/adb/pairing/pairing_connection.h @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include <stddef.h> +#include <stdint.h> +#include <sys/cdefs.h> + +#if !defined(__INTRODUCED_IN) +#define __INTRODUCED_IN(__api_level) /* nothing */ +#endif + +// These APIs are for the Adb pairing protocol. This protocol requires both +// sides to possess a shared secret to authenticate each other. The connection +// is over TLS, and requires that both the client and server have a valid +// certificate. +// +// This protocol is one-to-one, i.e., one PairingConnectionCtx server instance +// interacts with only one PairingConnectionCtx client instance. In other words, +// every new client instance must be bound to a new server instance. +// +// If both sides have authenticated, they will exchange their peer information +// (see #PeerInfo). +__BEGIN_DECLS +#if !defined(__ANDROID__) || __ANDROID_API__ >= 30 + +const uint32_t kMaxPeerInfoSize = 8192; +struct PeerInfo { + uint8_t type; + uint8_t data[kMaxPeerInfoSize - 1]; +} __attribute__((packed)); +typedef struct PeerInfo PeerInfo; +static_assert(sizeof(PeerInfo) == kMaxPeerInfoSize, "PeerInfo has weird size"); + +enum PeerInfoType : uint8_t { + ADB_RSA_PUB_KEY = 0, + ADB_DEVICE_GUID = 1, +}; + +struct PairingConnectionCtx; +typedef struct PairingConnectionCtx PairingConnectionCtx; +typedef void (*pairing_result_cb)(const PeerInfo*, int, void*); + +// Starts the pairing connection on a separate thread. +// +// Upon completion, if the pairing was successful, +// |cb| will be called with the peer information and certificate. +// Otherwise, |cb| will be called with empty data. |fd| should already +// be opened. PairingConnectionCtx will take ownership of the |fd|. +// +// Pairing is successful if both server/client uses the same non-empty +// |pswd|, and they are able to exchange the information. |pswd| and +// |certificate| must be non-empty. start() can only be called once in the +// lifetime of this object. +// +// @param ctx the PairingConnectionCtx instance. Will abort if null. +// @param fd the fd connecting the peers. This will take ownership of fd. +// @param cb the user-provided callback that is called with the result of the +// pairing. The callback will be called on a different thread from the +// caller. +// @param opaque opaque userdata. +// @return true if the thread was successfully started, false otherwise. To stop +// the connection process, destroy the instance (see +// #pairing_connection_destroy). If false is returned, cb will not be +// invoked. Otherwise, cb is guaranteed to be invoked, even if you +// destroy the ctx while in the pairing process. +bool pairing_connection_start(PairingConnectionCtx* ctx, int fd, pairing_result_cb cb, void* opaque) + __INTRODUCED_IN(30); + +// Creates a new PairingConnectionCtx instance as the client. +// +// @param pswd the password to authenticate both peers. Will abort if null. +// @param pswd_len the length of pswd. Will abort if 0. +// @param peer_info the PeerInfo struct that is exchanged between peers if the +// pairing was successful. Will abort if null. +// @param x509_cert_pem the X.509 certificate in PEM format. Will abort if null. +// @param x509_size the size of x509_cert_pem. Will abort if 0. +// @param priv_key_pem the private key corresponding to the given X.509 +// certificate, in PEM format. Will abort if null. +// @param priv_size the size of priv_key_pem. Will abort if 0. +// @return a new PairingConnectionCtx client instance. The caller is responsible +// for destroying the context via #pairing_connection_destroy. +PairingConnectionCtx* pairing_connection_client_new(const uint8_t* pswd, size_t pswd_len, + const PeerInfo* peer_info, + const uint8_t* x509_cert_pem, size_t x509_size, + const uint8_t* priv_key_pem, size_t priv_size) + __INTRODUCED_IN(30); + +// Creates a new PairingConnectionCtx instance as the server. +// +// @param pswd the password to authenticate both peers. Will abort if null. +// @param pswd_len the length of pswd. Will abort if 0. +// @param peer_info the PeerInfo struct that is exchanged between peers if the +// pairing was successful. Will abort if null. +// @param x509_cert_pem the X.509 certificate in PEM format. Will abort if null. +// @param x509_size the size of x509_cert_pem. Will abort if 0. +// @param priv_key_pem the private key corresponding to the given X.509 +// certificate, in PEM format. Will abort if null. +// @param priv_size the size of priv_key_pem. Will abort if 0. +// @return a new PairingConnectionCtx server instance. The caller is responsible +// for destroying the context via #pairing_connection_destroy. +PairingConnectionCtx* pairing_connection_server_new(const uint8_t* pswd, size_t pswd_len, + const PeerInfo* peer_info, + const uint8_t* x509_cert_pem, size_t x509_size, + const uint8_t* priv_key_pem, size_t priv_size) + __INTRODUCED_IN(30); + +// Destroys the PairingConnectionCtx instance. +// +// It is safe to destroy the instance at any point in the pairing process. +// +// @param ctx the PairingConnectionCtx instance to destroy. Will abort if null. +void pairing_connection_destroy(PairingConnectionCtx* ctx) __INTRODUCED_IN(30); + +#endif //!__ANDROID__ || __ANDROID_API__ >= 30 +__END_DECLS diff --git a/adb/pairing_connection/include/adb/pairing/pairing_server.h b/adb/pairing_connection/include/adb/pairing/pairing_server.h new file mode 100644 index 000000000..178a174bd --- /dev/null +++ b/adb/pairing_connection/include/adb/pairing/pairing_server.h @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include <stddef.h> +#include <stdint.h> +#include <sys/cdefs.h> + +#include <functional> +#include <memory> +#include <string_view> +#include <vector> + +#include "adb/pairing/pairing_connection.h" + +#if !defined(__INTRODUCED_IN) +#define __INTRODUCED_IN(__api_level) /* nothing */ +#endif + +__BEGIN_DECLS +#if !defined(__ANDROID__) || __ANDROID_API__ >= 30 + +// PairingServerCtx is a wrapper around the #PairingConnectionCtx APIs, +// which handles multiple client connections. +// +// See pairing_connection_test.cpp for example usage. +// +struct PairingServerCtx; +typedef struct PairingServerCtx PairingServerCtx; + +// Callback containing the result of the pairing. If #PeerInfo is null, +// then the pairing failed. Otherwise, pairing succeeded and #PeerInfo +// contains information about the peer. +typedef void (*pairing_server_result_cb)(const PeerInfo*, void*) __INTRODUCED_IN(30); + +// Starts the pairing server. +// +// This call is non-blocking. Upon completion, if the pairing was successful, +// then |cb| will be called with the PeerInfo +// containing the info of the trusted peer. Otherwise, |cb| will be +// called with an empty value. Start can only be called once in the lifetime +// of this object. +// +// @param ctx the PairingServerCtx instance. +// @param cb the user-provided callback to notify the result of the pairing. See +// #pairing_server_result_cb. +// @param opaque the opaque userdata. +// @return the port number the server is listening on. Returns 0 on failure. +uint16_t pairing_server_start(PairingServerCtx* ctx, pairing_server_result_cb cb, void* opaque) + __INTRODUCED_IN(30); + +// Creates a new PairingServerCtx instance. +// +// @param pswd the password used to authenticate the client and server. +// @param pswd_len the length of pswd. +// @param peer_info the #PeerInfo struct passed to the client on successful +// pairing. +// @param x509_cert_pem the X.509 certificate in PEM format. Cannot be empty. +// @param x509_size the size of x509_cert_pem. +// @param priv_key_pem the private key corresponding to the given X.509 +// certificate, in PEM format. Cannot be empty. +// @param priv_size the size of priv_key_pem. +// @param port the port number the server should listen on. Must be within the +// valid port range [0, 65535]. If port is 0, then the server will +// find an open port to listen on. See #pairing_server_start to +// obtain the port used. +// @return a new PairingServerCtx instance The caller is responsible +// for destroying the context via #pairing_server_destroy. +PairingServerCtx* pairing_server_new(const uint8_t* pswd, size_t pswd_len, + const PeerInfo* peer_info, const uint8_t* x509_cert_pem, + size_t x509_size, const uint8_t* priv_key_pem, + size_t priv_size, uint16_t port) __INTRODUCED_IN(30); + +// Same as #pairing_server_new, except that the x509 certificate and private key +// is generated internally. +// +// @param pswd the password used to authenticate the client and server. +// @param pswd_len the length of pswd. +// @param peer_info the #PeerInfo struct passed to the client on successful +// pairing. +// @param port the port number the server should listen on. Must be within the +// valid port range [0, 65535]. If port is 0, then the server will +// find an open port to listen on. See #pairing_server_start to +// obtain the port used. +// @return a new PairingServerCtx instance The caller is responsible +// for destroying the context via #pairing_server_destroy. +PairingServerCtx* pairing_server_new_no_cert(const uint8_t* pswd, size_t pswd_len, + const PeerInfo* peer_info, uint16_t port) + __INTRODUCED_IN(30); + +// Destroys the PairingServerCtx instance. +// +// @param ctx the PairingServerCtx instance to destroy. +void pairing_server_destroy(PairingServerCtx* ctx) __INTRODUCED_IN(30); + +#endif //!__ANDROID__ || __ANDROID_API__ >= 30 +__END_DECLS diff --git a/adb/pairing_connection/internal/constants.h b/adb/pairing_connection/internal/constants.h new file mode 100644 index 000000000..9a04f174e --- /dev/null +++ b/adb/pairing_connection/internal/constants.h @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +// This file contains constants that can be used both in the pairing_connection +// code and tested in the pairing_connection_test code. +namespace adb { +namespace pairing { +namespace internal { + +// The maximum number of connections the PairingServer can handle at once. +constexpr int kMaxConnections = 10; +// The maximum number of attempts the PairingServer will take before quitting. +// This is to prevent someone malicious from quickly brute-forcing every +// combination. +constexpr int kMaxPairingAttempts = 20; + +} // namespace internal +} // namespace pairing +} // namespace adb diff --git a/adb/pairing_connection/libadb_pairing_connection.map.txt b/adb/pairing_connection/libadb_pairing_connection.map.txt new file mode 100644 index 000000000..abd5f16d4 --- /dev/null +++ b/adb/pairing_connection/libadb_pairing_connection.map.txt @@ -0,0 +1,10 @@ +LIBADB_PAIRING_CONNECTION { + global: + pairing_connection_client_new; # apex introduced=30 + pairing_connection_server_new; # apex introduced=30 + pairing_connection_start; # apex introduced=30 + pairing_connection_destroy; # apex introduced=30 + + local: + *; +}; diff --git a/adb/pairing_connection/libadb_pairing_server.map.txt b/adb/pairing_connection/libadb_pairing_server.map.txt new file mode 100644 index 000000000..dc0dc89a3 --- /dev/null +++ b/adb/pairing_connection/libadb_pairing_server.map.txt @@ -0,0 +1,10 @@ +LIBADB_PAIRING_SERVER { + global: + pairing_server_start; # apex introduced=30 + pairing_server_new; # apex introduced=30 + pairing_server_new_no_cert; # apex introduced=30 + pairing_server_destroy; # apex introduced=30 + + local: + *; +}; diff --git a/adb/pairing_connection/pairing_connection.cpp b/adb/pairing_connection/pairing_connection.cpp new file mode 100644 index 000000000..a26a6b4d2 --- /dev/null +++ b/adb/pairing_connection/pairing_connection.cpp @@ -0,0 +1,491 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "adb/pairing/pairing_connection.h" + +#include <stddef.h> +#include <stdint.h> + +#include <functional> +#include <memory> +#include <string_view> +#include <thread> +#include <vector> + +#include <adb/pairing/pairing_auth.h> +#include <adb/tls/tls_connection.h> +#include <android-base/endian.h> +#include <android-base/logging.h> +#include <android-base/macros.h> +#include <android-base/unique_fd.h> + +#include "pairing.pb.h" + +using namespace adb; +using android::base::unique_fd; +using TlsError = tls::TlsConnection::TlsError; + +const uint8_t kCurrentKeyHeaderVersion = 1; +const uint8_t kMinSupportedKeyHeaderVersion = 1; +const uint8_t kMaxSupportedKeyHeaderVersion = 1; +const uint32_t kMaxPayloadSize = kMaxPeerInfoSize * 2; + +struct PairingPacketHeader { + uint8_t version; // PairingPacket version + uint8_t type; // the type of packet (PairingPacket.Type) + uint32_t payload; // Size of the payload in bytes +} __attribute__((packed)); + +struct PairingAuthDeleter { + void operator()(PairingAuthCtx* p) { pairing_auth_destroy(p); } +}; // PairingAuthDeleter +using PairingAuthPtr = std::unique_ptr<PairingAuthCtx, PairingAuthDeleter>; + +// PairingConnectionCtx encapsulates the protocol to authenticate two peers with +// each other. This class will open the tcp sockets and handle the pairing +// process. On completion, both sides will have each other's public key +// (certificate) if successful, otherwise, the pairing failed. The tcp port +// number is hardcoded (see pairing_connection.cpp). +// +// Each PairingConnectionCtx instance represents a different device trying to +// pair. So for the device, we can have multiple PairingConnectionCtxs while the +// host may have only one (unless host has a PairingServer). +// +// See pairing_connection_test.cpp for example usage. +// +struct PairingConnectionCtx { + public: + using Data = std::vector<uint8_t>; + using ResultCallback = pairing_result_cb; + enum class Role { + Client, + Server, + }; + + explicit PairingConnectionCtx(Role role, const Data& pswd, const PeerInfo& peer_info, + const Data& certificate, const Data& priv_key); + virtual ~PairingConnectionCtx(); + + // Starts the pairing connection on a separate thread. + // Upon completion, if the pairing was successful, + // |cb| will be called with the peer information and certificate. + // Otherwise, |cb| will be called with empty data. |fd| should already + // be opened. PairingConnectionCtx will take ownership of the |fd|. + // + // Pairing is successful if both server/client uses the same non-empty + // |pswd|, and they are able to exchange the information. |pswd| and + // |certificate| must be non-empty. Start() can only be called once in the + // lifetime of this object. + // + // Returns true if the thread was successfully started, false otherwise. + bool Start(int fd, ResultCallback cb, void* opaque); + + private: + // Setup the tls connection. + bool SetupTlsConnection(); + + /************ PairingPacketHeader methods ****************/ + // Tries to write out the header and payload. + bool WriteHeader(const PairingPacketHeader* header, std::string_view payload); + // Tries to parse incoming data into the |header|. Returns true if header + // is valid and header version is supported. |header| is filled on success. + // |header| may contain garbage if unsuccessful. + bool ReadHeader(PairingPacketHeader* header); + // Creates a PairingPacketHeader. + void CreateHeader(PairingPacketHeader* header, adb::proto::PairingPacket::Type type, + uint32_t payload_size); + // Checks if actual matches expected. + bool CheckHeaderType(adb::proto::PairingPacket::Type expected, uint8_t actual); + + /*********** State related methods **************/ + // Handles the State::ExchangingMsgs state. + bool DoExchangeMsgs(); + // Handles the State::ExchangingPeerInfo state. + bool DoExchangePeerInfo(); + + // The background task to do the pairing. + void StartWorker(); + + // Calls |cb_| and sets the state to Stopped. + void NotifyResult(const PeerInfo* p); + + static PairingAuthPtr CreatePairingAuthPtr(Role role, const Data& pswd); + + enum class State { + Ready, + ExchangingMsgs, + ExchangingPeerInfo, + Stopped, + }; + + std::atomic<State> state_{State::Ready}; + Role role_; + Data pswd_; + PeerInfo peer_info_; + Data cert_; + Data priv_key_; + + // Peer's info + PeerInfo their_info_; + + ResultCallback cb_; + void* opaque_ = nullptr; + std::unique_ptr<tls::TlsConnection> tls_; + PairingAuthPtr auth_; + unique_fd fd_; + std::thread thread_; + static constexpr size_t kExportedKeySize = 64; +}; // PairingConnectionCtx + +PairingConnectionCtx::PairingConnectionCtx(Role role, const Data& pswd, const PeerInfo& peer_info, + const Data& cert, const Data& priv_key) + : role_(role), pswd_(pswd), peer_info_(peer_info), cert_(cert), priv_key_(priv_key) { + CHECK(!pswd_.empty() && !cert_.empty() && !priv_key_.empty()); +} + +PairingConnectionCtx::~PairingConnectionCtx() { + // Force close the fd and wait for the worker thread to finish. + fd_.reset(); + if (thread_.joinable()) { + thread_.join(); + } +} + +bool PairingConnectionCtx::SetupTlsConnection() { + tls_ = tls::TlsConnection::Create( + role_ == Role::Server ? tls::TlsConnection::Role::Server + : tls::TlsConnection::Role::Client, + std::string_view(reinterpret_cast<const char*>(cert_.data()), cert_.size()), + std::string_view(reinterpret_cast<const char*>(priv_key_.data()), priv_key_.size()), + fd_); + + if (tls_ == nullptr) { + LOG(ERROR) << "Unable to start TlsConnection. Unable to pair fd=" << fd_.get(); + return false; + } + + // Allow any peer certificate + tls_->SetCertVerifyCallback([](X509_STORE_CTX*) { return 1; }); + + // SSL doesn't seem to behave correctly with fdevents so just do a blocking + // read for the pairing data. + if (tls_->DoHandshake() != TlsError::Success) { + LOG(ERROR) << "Failed to handshake with the peer fd=" << fd_.get(); + return false; + } + + // To ensure the connection is not stolen while we do the PAKE, append the + // exported key material from the tls connection to the password. + std::vector<uint8_t> exportedKeyMaterial = tls_->ExportKeyingMaterial(kExportedKeySize); + if (exportedKeyMaterial.empty()) { + LOG(ERROR) << "Failed to export key material"; + return false; + } + pswd_.insert(pswd_.end(), std::make_move_iterator(exportedKeyMaterial.begin()), + std::make_move_iterator(exportedKeyMaterial.end())); + auth_ = CreatePairingAuthPtr(role_, pswd_); + + return true; +} + +bool PairingConnectionCtx::WriteHeader(const PairingPacketHeader* header, + std::string_view payload) { + PairingPacketHeader network_header = *header; + network_header.payload = htonl(network_header.payload); + if (!tls_->WriteFully(std::string_view(reinterpret_cast<const char*>(&network_header), + sizeof(PairingPacketHeader))) || + !tls_->WriteFully(payload)) { + LOG(ERROR) << "Failed to write out PairingPacketHeader"; + state_ = State::Stopped; + return false; + } + return true; +} + +bool PairingConnectionCtx::ReadHeader(PairingPacketHeader* header) { + auto data = tls_->ReadFully(sizeof(PairingPacketHeader)); + if (data.empty()) { + return false; + } + + uint8_t* p = data.data(); + // First byte is always PairingPacketHeader version + header->version = *p; + ++p; + if (header->version < kMinSupportedKeyHeaderVersion || + header->version > kMaxSupportedKeyHeaderVersion) { + LOG(ERROR) << "PairingPacketHeader version mismatch (us=" << kCurrentKeyHeaderVersion + << " them=" << header->version << ")"; + return false; + } + // Next byte is the PairingPacket::Type + if (!adb::proto::PairingPacket::Type_IsValid(*p)) { + LOG(ERROR) << "Unknown PairingPacket type=" << static_cast<uint32_t>(*p); + return false; + } + header->type = *p; + ++p; + // Last, the payload size + header->payload = ntohl(*(reinterpret_cast<uint32_t*>(p))); + if (header->payload == 0 || header->payload > kMaxPayloadSize) { + LOG(ERROR) << "header payload not within a safe payload size (size=" << header->payload + << ")"; + return false; + } + + return true; +} + +void PairingConnectionCtx::CreateHeader(PairingPacketHeader* header, + adb::proto::PairingPacket::Type type, + uint32_t payload_size) { + header->version = kCurrentKeyHeaderVersion; + uint8_t type8 = static_cast<uint8_t>(static_cast<int>(type)); + header->type = type8; + header->payload = payload_size; +} + +bool PairingConnectionCtx::CheckHeaderType(adb::proto::PairingPacket::Type expected_type, + uint8_t actual) { + uint8_t expected = *reinterpret_cast<uint8_t*>(&expected_type); + if (actual != expected) { + LOG(ERROR) << "Unexpected header type (expected=" << static_cast<uint32_t>(expected) + << " actual=" << static_cast<uint32_t>(actual) << ")"; + return false; + } + return true; +} + +void PairingConnectionCtx::NotifyResult(const PeerInfo* p) { + cb_(p, fd_.get(), opaque_); + state_ = State::Stopped; +} + +bool PairingConnectionCtx::Start(int fd, ResultCallback cb, void* opaque) { + if (fd < 0) { + return false; + } + + State expected = State::Ready; + if (!state_.compare_exchange_strong(expected, State::ExchangingMsgs)) { + return false; + } + + fd_.reset(fd); + cb_ = cb; + opaque_ = opaque; + + thread_ = std::thread([this] { StartWorker(); }); + return true; +} + +bool PairingConnectionCtx::DoExchangeMsgs() { + uint32_t payload = pairing_auth_msg_size(auth_.get()); + std::vector<uint8_t> msg(payload); + pairing_auth_get_spake2_msg(auth_.get(), msg.data()); + + PairingPacketHeader header; + CreateHeader(&header, adb::proto::PairingPacket::SPAKE2_MSG, payload); + + // Write our SPAKE2 msg + if (!WriteHeader(&header, + std::string_view(reinterpret_cast<const char*>(msg.data()), msg.size()))) { + LOG(ERROR) << "Failed to write SPAKE2 msg."; + return false; + } + + // Read the peer's SPAKE2 msg header + if (!ReadHeader(&header)) { + LOG(ERROR) << "Invalid PairingPacketHeader."; + return false; + } + if (!CheckHeaderType(adb::proto::PairingPacket::SPAKE2_MSG, header.type)) { + return false; + } + + // Read the SPAKE2 msg payload and initialize the cipher for + // encrypting the PeerInfo and certificate. + auto their_msg = tls_->ReadFully(header.payload); + if (their_msg.empty() || + !pairing_auth_init_cipher(auth_.get(), their_msg.data(), their_msg.size())) { + LOG(ERROR) << "Unable to initialize pairing cipher [their_msg.size=" << their_msg.size() + << "]"; + return false; + } + + return true; +} + +bool PairingConnectionCtx::DoExchangePeerInfo() { + // Encrypt PeerInfo + std::vector<uint8_t> buf; + uint8_t* p = reinterpret_cast<uint8_t*>(&peer_info_); + buf.assign(p, p + sizeof(peer_info_)); + std::vector<uint8_t> outbuf(pairing_auth_safe_encrypted_size(auth_.get(), buf.size())); + CHECK(!outbuf.empty()); + size_t outsize; + if (!pairing_auth_encrypt(auth_.get(), buf.data(), buf.size(), outbuf.data(), &outsize)) { + LOG(ERROR) << "Failed to encrypt peer info"; + return false; + } + outbuf.resize(outsize); + + // Write out the packet header + PairingPacketHeader out_header; + out_header.version = kCurrentKeyHeaderVersion; + out_header.type = static_cast<uint8_t>(static_cast<int>(adb::proto::PairingPacket::PEER_INFO)); + out_header.payload = htonl(outbuf.size()); + if (!tls_->WriteFully( + std::string_view(reinterpret_cast<const char*>(&out_header), sizeof(out_header)))) { + LOG(ERROR) << "Unable to write PairingPacketHeader"; + return false; + } + + // Write out the encrypted payload + if (!tls_->WriteFully( + std::string_view(reinterpret_cast<const char*>(outbuf.data()), outbuf.size()))) { + LOG(ERROR) << "Unable to write encrypted peer info"; + return false; + } + + // Read in the peer's packet header + PairingPacketHeader header; + if (!ReadHeader(&header)) { + LOG(ERROR) << "Invalid PairingPacketHeader."; + return false; + } + + if (!CheckHeaderType(adb::proto::PairingPacket::PEER_INFO, header.type)) { + return false; + } + + // Read in the encrypted peer certificate + buf = tls_->ReadFully(header.payload); + if (buf.empty()) { + return false; + } + + // Try to decrypt the certificate + outbuf.resize(pairing_auth_safe_decrypted_size(auth_.get(), buf.data(), buf.size())); + if (outbuf.empty()) { + LOG(ERROR) << "Unsupported payload while decrypting peer info."; + return false; + } + + if (!pairing_auth_decrypt(auth_.get(), buf.data(), buf.size(), outbuf.data(), &outsize)) { + LOG(ERROR) << "Failed to decrypt"; + return false; + } + outbuf.resize(outsize); + + // The decrypted message should contain the PeerInfo. + if (outbuf.size() != sizeof(PeerInfo)) { + LOG(ERROR) << "Got size=" << outbuf.size() << "PeerInfo.size=" << sizeof(PeerInfo); + return false; + } + + p = outbuf.data(); + ::memcpy(&their_info_, p, sizeof(PeerInfo)); + p += sizeof(PeerInfo); + + return true; +} + +void PairingConnectionCtx::StartWorker() { + // Setup the secure transport + if (!SetupTlsConnection()) { + NotifyResult(nullptr); + return; + } + + for (;;) { + switch (state_) { + case State::ExchangingMsgs: + if (!DoExchangeMsgs()) { + NotifyResult(nullptr); + return; + } + state_ = State::ExchangingPeerInfo; + break; + case State::ExchangingPeerInfo: + if (!DoExchangePeerInfo()) { + NotifyResult(nullptr); + return; + } + NotifyResult(&their_info_); + return; + case State::Ready: + case State::Stopped: + LOG(FATAL) << __func__ << ": Got invalid state"; + return; + } + } +} + +// static +PairingAuthPtr PairingConnectionCtx::CreatePairingAuthPtr(Role role, const Data& pswd) { + switch (role) { + case Role::Client: + return PairingAuthPtr(pairing_auth_client_new(pswd.data(), pswd.size())); + break; + case Role::Server: + return PairingAuthPtr(pairing_auth_server_new(pswd.data(), pswd.size())); + break; + } +} + +static PairingConnectionCtx* CreateConnection(PairingConnectionCtx::Role role, const uint8_t* pswd, + size_t pswd_len, const PeerInfo* peer_info, + const uint8_t* x509_cert_pem, size_t x509_size, + const uint8_t* priv_key_pem, size_t priv_size) { + CHECK(pswd); + CHECK_GT(pswd_len, 0U); + CHECK(x509_cert_pem); + CHECK_GT(x509_size, 0U); + CHECK(priv_key_pem); + CHECK_GT(priv_size, 0U); + CHECK(peer_info); + std::vector<uint8_t> vec_pswd(pswd, pswd + pswd_len); + std::vector<uint8_t> vec_x509_cert(x509_cert_pem, x509_cert_pem + x509_size); + std::vector<uint8_t> vec_priv_key(priv_key_pem, priv_key_pem + priv_size); + return new PairingConnectionCtx(role, vec_pswd, *peer_info, vec_x509_cert, vec_priv_key); +} + +PairingConnectionCtx* pairing_connection_client_new(const uint8_t* pswd, size_t pswd_len, + const PeerInfo* peer_info, + const uint8_t* x509_cert_pem, size_t x509_size, + const uint8_t* priv_key_pem, size_t priv_size) { + return CreateConnection(PairingConnectionCtx::Role::Client, pswd, pswd_len, peer_info, + x509_cert_pem, x509_size, priv_key_pem, priv_size); +} + +PairingConnectionCtx* pairing_connection_server_new(const uint8_t* pswd, size_t pswd_len, + const PeerInfo* peer_info, + const uint8_t* x509_cert_pem, size_t x509_size, + const uint8_t* priv_key_pem, size_t priv_size) { + return CreateConnection(PairingConnectionCtx::Role::Server, pswd, pswd_len, peer_info, + x509_cert_pem, x509_size, priv_key_pem, priv_size); +} + +void pairing_connection_destroy(PairingConnectionCtx* ctx) { + CHECK(ctx); + delete ctx; +} + +bool pairing_connection_start(PairingConnectionCtx* ctx, int fd, pairing_result_cb cb, + void* opaque) { + return ctx->Start(fd, cb, opaque); +} diff --git a/adb/pairing_connection/pairing_server.cpp b/adb/pairing_connection/pairing_server.cpp new file mode 100644 index 000000000..7218eacf2 --- /dev/null +++ b/adb/pairing_connection/pairing_server.cpp @@ -0,0 +1,466 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "adb/pairing/pairing_server.h" + +#include <sys/epoll.h> +#include <sys/eventfd.h> + +#include <atomic> +#include <deque> +#include <iomanip> +#include <mutex> +#include <sstream> +#include <thread> +#include <tuple> +#include <unordered_map> +#include <variant> +#include <vector> + +#include <adb/crypto/rsa_2048_key.h> +#include <adb/crypto/x509_generator.h> +#include <adb/pairing/pairing_connection.h> +#include <android-base/logging.h> +#include <android-base/parsenetaddress.h> +#include <android-base/thread_annotations.h> +#include <android-base/unique_fd.h> +#include <cutils/sockets.h> + +#include "internal/constants.h" + +using android::base::ScopedLockAssertion; +using android::base::unique_fd; +using namespace adb::crypto; +using namespace adb::pairing; + +// The implementation has two background threads running: one to handle and +// accept any new pairing connection requests (socket accept), and the other to +// handle connection events (connection started, connection finished). +struct PairingServerCtx { + public: + using Data = std::vector<uint8_t>; + + virtual ~PairingServerCtx(); + + // All parameters must be non-empty. + explicit PairingServerCtx(const Data& pswd, const PeerInfo& peer_info, const Data& cert, + const Data& priv_key, uint16_t port); + + // Starts the pairing server. This call is non-blocking. Upon completion, + // if the pairing was successful, then |cb| will be called with the PublicKeyHeader + // containing the info of the trusted peer. Otherwise, |cb| will be + // called with an empty value. Start can only be called once in the lifetime + // of this object. + // + // Returns the port number if PairingServerCtx was successfully started. Otherwise, + // returns 0. + uint16_t Start(pairing_server_result_cb cb, void* opaque); + + private: + // Setup the server socket to accept incoming connections. Returns the + // server port number (> 0 on success). + uint16_t SetupServer(); + // Force stop the server thread. + void StopServer(); + + // handles a new pairing client connection + bool HandleNewClientConnection(int fd) EXCLUDES(conn_mutex_); + + // ======== connection events thread ============= + std::mutex conn_mutex_; + std::condition_variable conn_cv_; + + using FdVal = int; + struct ConnectionDeleter { + void operator()(PairingConnectionCtx* p) { pairing_connection_destroy(p); } + }; + using ConnectionPtr = std::unique_ptr<PairingConnectionCtx, ConnectionDeleter>; + static ConnectionPtr CreatePairingConnection(const Data& pswd, const PeerInfo& peer_info, + const Data& cert, const Data& priv_key); + using NewConnectionEvent = std::tuple<unique_fd, ConnectionPtr>; + // <fd, PeerInfo.type, PeerInfo.data> + using ConnectionFinishedEvent = std::tuple<FdVal, uint8_t, std::optional<std::string>>; + using ConnectionEvent = std::variant<NewConnectionEvent, ConnectionFinishedEvent>; + // Queue for connections to write into. We have a separate queue to read + // from, in order to minimize the time the server thread is blocked. + std::deque<ConnectionEvent> conn_write_queue_ GUARDED_BY(conn_mutex_); + std::deque<ConnectionEvent> conn_read_queue_; + // Map of fds to their PairingConnections currently running. + std::unordered_map<FdVal, ConnectionPtr> connections_; + + // Two threads launched when starting the pairing server: + // 1) A server thread that waits for incoming client connections, and + // 2) A connection events thread that synchonizes events from all of the + // clients, since each PairingConnection is running in it's own thread. + void StartConnectionEventsThread(); + void StartServerThread(); + + static void PairingConnectionCallback(const PeerInfo* peer_info, int fd, void* opaque); + + std::thread conn_events_thread_; + void ConnectionEventsWorker(); + std::thread server_thread_; + void ServerWorker(); + bool is_terminate_ GUARDED_BY(conn_mutex_) = false; + + enum class State { + Ready, + Running, + Stopped, + }; + State state_ = State::Ready; + Data pswd_; + PeerInfo peer_info_; + Data cert_; + Data priv_key_; + uint16_t port_; + + pairing_server_result_cb cb_; + void* opaque_ = nullptr; + bool got_valid_pairing_ = false; + + static const int kEpollConstSocket = 0; + // Used to break the server thread from epoll_wait + static const int kEpollConstEventFd = 1; + unique_fd epoll_fd_; + unique_fd server_fd_; + unique_fd event_fd_; +}; // PairingServerCtx + +// static +PairingServerCtx::ConnectionPtr PairingServerCtx::CreatePairingConnection(const Data& pswd, + const PeerInfo& peer_info, + const Data& cert, + const Data& priv_key) { + return ConnectionPtr(pairing_connection_server_new(pswd.data(), pswd.size(), &peer_info, + cert.data(), cert.size(), priv_key.data(), + priv_key.size())); +} + +PairingServerCtx::PairingServerCtx(const Data& pswd, const PeerInfo& peer_info, const Data& cert, + const Data& priv_key, uint16_t port) + : pswd_(pswd), peer_info_(peer_info), cert_(cert), priv_key_(priv_key), port_(port) { + CHECK(!pswd_.empty() && !cert_.empty() && !priv_key_.empty()); +} + +PairingServerCtx::~PairingServerCtx() { + // Since these connections have references to us, let's make sure they + // destruct before us. + if (server_thread_.joinable()) { + StopServer(); + server_thread_.join(); + } + + { + std::lock_guard<std::mutex> lock(conn_mutex_); + is_terminate_ = true; + } + conn_cv_.notify_one(); + if (conn_events_thread_.joinable()) { + conn_events_thread_.join(); + } + + // Notify the cb_ if it hasn't already. + if (!got_valid_pairing_ && cb_ != nullptr) { + cb_(nullptr, opaque_); + } +} + +uint16_t PairingServerCtx::Start(pairing_server_result_cb cb, void* opaque) { + cb_ = cb; + opaque_ = opaque; + + if (state_ != State::Ready) { + LOG(ERROR) << "PairingServerCtx already running or stopped"; + return 0; + } + + port_ = SetupServer(); + if (port_ == 0) { + LOG(ERROR) << "Unable to start PairingServer"; + state_ = State::Stopped; + return 0; + } + LOG(INFO) << "Pairing server started on port " << port_; + + state_ = State::Running; + return port_; +} + +void PairingServerCtx::StopServer() { + if (event_fd_.get() == -1) { + return; + } + uint64_t value = 1; + ssize_t rc = write(event_fd_.get(), &value, sizeof(value)); + if (rc == -1) { + // This can happen if the server didn't start. + PLOG(ERROR) << "write to eventfd failed"; + } else if (rc != sizeof(value)) { + LOG(FATAL) << "write to event returned short (" << rc << ")"; + } +} + +uint16_t PairingServerCtx::SetupServer() { + epoll_fd_.reset(epoll_create1(EPOLL_CLOEXEC)); + if (epoll_fd_ == -1) { + PLOG(ERROR) << "failed to create epoll fd"; + return 0; + } + + event_fd_.reset(eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK)); + if (event_fd_ == -1) { + PLOG(ERROR) << "failed to create eventfd"; + return 0; + } + + server_fd_.reset(socket_inaddr_any_server(port_, SOCK_STREAM)); + if (server_fd_.get() == -1) { + PLOG(ERROR) << "Failed to start pairing connection server"; + return 0; + } else if (fcntl(server_fd_.get(), F_SETFD, FD_CLOEXEC) != 0) { + PLOG(ERROR) << "Failed to make server socket cloexec"; + return 0; + } else if (fcntl(server_fd_.get(), F_SETFD, O_NONBLOCK) != 0) { + PLOG(ERROR) << "Failed to make server socket nonblocking"; + return 0; + } + + StartConnectionEventsThread(); + StartServerThread(); + int port = socket_get_local_port(server_fd_.get()); + return (port <= 0 ? 0 : port); +} + +void PairingServerCtx::StartServerThread() { + server_thread_ = std::thread([this]() { ServerWorker(); }); +} + +void PairingServerCtx::StartConnectionEventsThread() { + conn_events_thread_ = std::thread([this]() { ConnectionEventsWorker(); }); +} + +void PairingServerCtx::ServerWorker() { + { + struct epoll_event event; + event.events = EPOLLIN; + event.data.u64 = kEpollConstSocket; + CHECK_EQ(0, epoll_ctl(epoll_fd_.get(), EPOLL_CTL_ADD, server_fd_.get(), &event)); + } + + { + struct epoll_event event; + event.events = EPOLLIN; + event.data.u64 = kEpollConstEventFd; + CHECK_EQ(0, epoll_ctl(epoll_fd_.get(), EPOLL_CTL_ADD, event_fd_.get(), &event)); + } + + while (true) { + struct epoll_event events[2]; + int rc = TEMP_FAILURE_RETRY(epoll_wait(epoll_fd_.get(), events, 2, -1)); + if (rc == -1) { + PLOG(ERROR) << "epoll_wait failed"; + return; + } else if (rc == 0) { + LOG(ERROR) << "epoll_wait returned 0"; + return; + } + + for (int i = 0; i < rc; ++i) { + struct epoll_event& event = events[i]; + switch (event.data.u64) { + case kEpollConstSocket: + HandleNewClientConnection(server_fd_.get()); + break; + case kEpollConstEventFd: + uint64_t dummy; + int rc = TEMP_FAILURE_RETRY(read(event_fd_.get(), &dummy, sizeof(dummy))); + if (rc != sizeof(dummy)) { + PLOG(FATAL) << "failed to read from eventfd (rc=" << rc << ")"; + } + return; + } + } + } +} + +// static +void PairingServerCtx::PairingConnectionCallback(const PeerInfo* peer_info, int fd, void* opaque) { + auto* p = reinterpret_cast<PairingServerCtx*>(opaque); + + ConnectionFinishedEvent event; + if (peer_info != nullptr) { + if (peer_info->type == ADB_RSA_PUB_KEY) { + event = std::make_tuple(fd, peer_info->type, + std::string(reinterpret_cast<const char*>(peer_info->data))); + } else { + LOG(WARNING) << "Ignoring successful pairing because of unknown " + << "PeerInfo type=" << peer_info->type; + } + } else { + event = std::make_tuple(fd, 0, std::nullopt); + } + { + std::lock_guard<std::mutex> lock(p->conn_mutex_); + p->conn_write_queue_.push_back(std::move(event)); + } + p->conn_cv_.notify_one(); +} + +void PairingServerCtx::ConnectionEventsWorker() { + uint8_t num_tries = 0; + for (;;) { + // Transfer the write queue to the read queue. + { + std::unique_lock<std::mutex> lock(conn_mutex_); + ScopedLockAssertion assume_locked(conn_mutex_); + + if (is_terminate_) { + // We check |is_terminate_| twice because condition_variable's + // notify() only wakes up a thread if it is in the wait state + // prior to notify(). Furthermore, we aren't holding the mutex + // when processing the events in |conn_read_queue_|. + return; + } + if (conn_write_queue_.empty()) { + // We need to wait for new events, or the termination signal. + conn_cv_.wait(lock, [this]() REQUIRES(conn_mutex_) { + return (is_terminate_ || !conn_write_queue_.empty()); + }); + } + if (is_terminate_) { + // We're done. + return; + } + // Move all events into the read queue. + conn_read_queue_ = std::move(conn_write_queue_); + conn_write_queue_.clear(); + } + + // Process all events in the read queue. + while (conn_read_queue_.size() > 0) { + auto& event = conn_read_queue_.front(); + if (auto* p = std::get_if<NewConnectionEvent>(&event)) { + // Ignore if we are already at the max number of connections + if (connections_.size() >= internal::kMaxConnections) { + conn_read_queue_.pop_front(); + continue; + } + auto [ufd, connection] = std::move(*p); + int fd = ufd.release(); + bool started = pairing_connection_start(connection.get(), fd, + PairingConnectionCallback, this); + if (!started) { + LOG(ERROR) << "PairingServer unable to start a PairingConnection fd=" << fd; + ufd.reset(fd); + } else { + connections_[fd] = std::move(connection); + } + } else if (auto* p = std::get_if<ConnectionFinishedEvent>(&event)) { + auto [fd, info_type, public_key] = std::move(*p); + if (public_key.has_value() && !public_key->empty()) { + // Valid pairing. Let's shutdown the server and close any + // pairing connections in progress. + StopServer(); + connections_.clear(); + + PeerInfo info = {}; + info.type = info_type; + strncpy(reinterpret_cast<char*>(info.data), public_key->data(), + public_key->size()); + + cb_(&info, opaque_); + + got_valid_pairing_ = true; + return; + } + // Invalid pairing. Close the invalid connection. + if (connections_.find(fd) != connections_.end()) { + connections_.erase(fd); + } + + if (++num_tries >= internal::kMaxPairingAttempts) { + cb_(nullptr, opaque_); + // To prevent the destructor from calling it again. + cb_ = nullptr; + return; + } + } + conn_read_queue_.pop_front(); + } + } +} + +bool PairingServerCtx::HandleNewClientConnection(int fd) { + unique_fd ufd(TEMP_FAILURE_RETRY(accept4(fd, nullptr, nullptr, SOCK_CLOEXEC))); + if (ufd == -1) { + PLOG(WARNING) << "adb_socket_accept failed fd=" << fd; + return false; + } + auto connection = CreatePairingConnection(pswd_, peer_info_, cert_, priv_key_); + if (connection == nullptr) { + LOG(ERROR) << "PairingServer unable to create a PairingConnection fd=" << fd; + return false; + } + // send the new connection to the connection thread for further processing + NewConnectionEvent event = std::make_tuple(std::move(ufd), std::move(connection)); + { + std::lock_guard<std::mutex> lock(conn_mutex_); + conn_write_queue_.push_back(std::move(event)); + } + conn_cv_.notify_one(); + + return true; +} + +uint16_t pairing_server_start(PairingServerCtx* ctx, pairing_server_result_cb cb, void* opaque) { + return ctx->Start(cb, opaque); +} + +PairingServerCtx* pairing_server_new(const uint8_t* pswd, size_t pswd_len, + const PeerInfo* peer_info, const uint8_t* x509_cert_pem, + size_t x509_size, const uint8_t* priv_key_pem, + size_t priv_size, uint16_t port) { + CHECK(pswd); + CHECK_GT(pswd_len, 0U); + CHECK(x509_cert_pem); + CHECK_GT(x509_size, 0U); + CHECK(priv_key_pem); + CHECK_GT(priv_size, 0U); + CHECK(peer_info); + std::vector<uint8_t> vec_pswd(pswd, pswd + pswd_len); + std::vector<uint8_t> vec_x509_cert(x509_cert_pem, x509_cert_pem + x509_size); + std::vector<uint8_t> vec_priv_key(priv_key_pem, priv_key_pem + priv_size); + return new PairingServerCtx(vec_pswd, *peer_info, vec_x509_cert, vec_priv_key, port); +} + +PairingServerCtx* pairing_server_new_no_cert(const uint8_t* pswd, size_t pswd_len, + const PeerInfo* peer_info, uint16_t port) { + auto rsa_2048 = CreateRSA2048Key(); + auto x509_cert = GenerateX509Certificate(rsa_2048->GetEvpPkey()); + std::string pkey_pem = Key::ToPEMString(rsa_2048->GetEvpPkey()); + std::string cert_pem = X509ToPEMString(x509_cert.get()); + + return pairing_server_new(pswd, pswd_len, peer_info, + reinterpret_cast<const uint8_t*>(cert_pem.data()), cert_pem.size(), + reinterpret_cast<const uint8_t*>(pkey_pem.data()), pkey_pem.size(), + port); +} + +void pairing_server_destroy(PairingServerCtx* ctx) { + CHECK(ctx); + delete ctx; +} diff --git a/adb/pairing_connection/tests/Android.bp b/adb/pairing_connection/tests/Android.bp new file mode 100644 index 000000000..bf075bcb9 --- /dev/null +++ b/adb/pairing_connection/tests/Android.bp @@ -0,0 +1,47 @@ +// +// Copyright (C) 2020 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +cc_test { + name: "adb_pairing_connection_test", + srcs: [ + "pairing_client.cpp", + "pairing_connection_test.cpp", + ], + + compile_multilib: "first", + + shared_libs: [ + "libbase", + "libcutils", + "libcrypto", + "libcrypto_utils", + "libprotobuf-cpp-lite", + "libssl", + ], + + // Let's statically link them so we don't have to install it onto the + // system image for testing. + static_libs: [ + "libadb_pairing_auth_static", + "libadb_pairing_connection_static", + "libadb_pairing_server_static", + "libadb_crypto_static", + "libadb_protos_static", + "libadb_tls_connection_static", + ], + + test_suites: ["device-tests"], +} diff --git a/adb/pairing_connection/tests/pairing_client.cpp b/adb/pairing_connection/tests/pairing_client.cpp new file mode 100644 index 000000000..1f3ef5a34 --- /dev/null +++ b/adb/pairing_connection/tests/pairing_client.cpp @@ -0,0 +1,201 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "pairing_client.h" + +#include <netdb.h> +#include <netinet/tcp.h> + +#include <atomic> +#include <iomanip> +#include <mutex> +#include <sstream> +#include <thread> +#include <vector> + +#include <android-base/logging.h> +#include <android-base/parsenetaddress.h> +#include <android-base/stringprintf.h> +#include <android-base/thread_annotations.h> +#include <android-base/unique_fd.h> +#include <cutils/sockets.h> + +namespace adb { +namespace pairing { + +using android::base::unique_fd; + +static void ConnectionDeleter(PairingConnectionCtx* p) { + pairing_connection_destroy(p); +} +using ConnectionPtr = std::unique_ptr<PairingConnectionCtx, decltype(&ConnectionDeleter)>; + +namespace { + +class PairingClientImpl : public PairingClient { + public: + explicit PairingClientImpl(const Data& pswd, const PeerInfo& peer_info, const Data& cert, + const Data& priv_key); + + // Starts the pairing client. This call is non-blocking. Upon pairing + // completion, |cb| will be called with the PeerInfo on success, + // or an empty value on failure. + // + // Returns true if PairingClient was successfully started. Otherwise, + // return false. + virtual bool Start(std::string_view ip_addr, pairing_client_result_cb cb, + void* opaque) override; + + private: + static ConnectionPtr CreatePairingConnection(const Data& pswd, const PeerInfo& peer_info, + const Data& cert, const Data& priv_key); + + static void PairingResultCallback(const PeerInfo* peer_info, int fd, void* opaque); + // Setup and start the PairingConnection + bool StartConnection(); + + enum class State { + Ready, + Running, + Stopped, + }; + + State state_ = State::Ready; + Data pswd_; + PeerInfo peer_info_; + Data cert_; + Data priv_key_; + std::string host_; + int port_; + + ConnectionPtr connection_; + pairing_client_result_cb cb_; + void* opaque_ = nullptr; +}; // PairingClientImpl + +// static +ConnectionPtr PairingClientImpl::CreatePairingConnection(const Data& pswd, + const PeerInfo& peer_info, + const Data& cert, const Data& priv_key) { + return ConnectionPtr( + pairing_connection_client_new(pswd.data(), pswd.size(), &peer_info, cert.data(), + cert.size(), priv_key.data(), priv_key.size()), + ConnectionDeleter); +} + +PairingClientImpl::PairingClientImpl(const Data& pswd, const PeerInfo& peer_info, const Data& cert, + const Data& priv_key) + : pswd_(pswd), + peer_info_(peer_info), + cert_(cert), + priv_key_(priv_key), + connection_(nullptr, ConnectionDeleter) { + CHECK(!pswd_.empty() && !cert_.empty() && !priv_key_.empty()); + + state_ = State::Ready; +} + +bool PairingClientImpl::Start(std::string_view ip_addr, pairing_client_result_cb cb, void* opaque) { + CHECK(!ip_addr.empty()); + cb_ = cb; + opaque_ = opaque; + + if (state_ != State::Ready) { + LOG(ERROR) << "PairingClient already running or finished"; + return false; + } + + // Try to parse the host address + std::string err; + CHECK(android::base::ParseNetAddress(std::string(ip_addr), &host_, &port_, nullptr, &err)); + CHECK(port_ > 0 && port_ <= 65535); + + if (!StartConnection()) { + LOG(ERROR) << "Unable to start PairingClient connection"; + state_ = State::Stopped; + return false; + } + + state_ = State::Running; + return true; +} + +static int network_connect(const std::string& host, int port, int type, int timeout, + std::string* error) { + int getaddrinfo_error = 0; + int fd = socket_network_client_timeout(host.c_str(), port, type, timeout, &getaddrinfo_error); + if (fd != -1) { + return fd; + } + if (getaddrinfo_error != 0) { + *error = android::base::StringPrintf("failed to resolve host: '%s': %s", host.c_str(), + gai_strerror(getaddrinfo_error)); + LOG(WARNING) << *error; + } else { + *error = android::base::StringPrintf("failed to connect to '%s:%d': %s", host.c_str(), port, + strerror(errno)); + LOG(WARNING) << *error; + } + return -1; +} + +// static +void PairingClientImpl::PairingResultCallback(const PeerInfo* peer_info, int /* fd */, + void* opaque) { + auto* p = reinterpret_cast<PairingClientImpl*>(opaque); + p->cb_(peer_info, p->opaque_); +} + +bool PairingClientImpl::StartConnection() { + std::string err; + const int timeout = 10; // seconds + unique_fd fd(network_connect(host_, port_, SOCK_STREAM, timeout, &err)); + if (fd.get() == -1) { + LOG(ERROR) << "Failed to start pairing connection client [" << err << "]"; + return false; + } + int off = 1; + setsockopt(fd.get(), IPPROTO_TCP, TCP_NODELAY, &off, sizeof(off)); + + connection_ = CreatePairingConnection(pswd_, peer_info_, cert_, priv_key_); + if (connection_ == nullptr) { + LOG(ERROR) << "PairingClient unable to create a PairingConnection"; + return false; + } + + if (!pairing_connection_start(connection_.get(), fd.release(), PairingResultCallback, this)) { + LOG(ERROR) << "PairingClient failed to start the PairingConnection"; + state_ = State::Stopped; + return false; + } + + return true; +} + +} // namespace + +// static +std::unique_ptr<PairingClient> PairingClient::Create(const Data& pswd, const PeerInfo& peer_info, + const Data& cert, const Data& priv_key) { + CHECK(!pswd.empty()); + CHECK(!cert.empty()); + CHECK(!priv_key.empty()); + + return std::unique_ptr<PairingClient>(new PairingClientImpl(pswd, peer_info, cert, priv_key)); +} + +} // namespace pairing +} // namespace adb diff --git a/adb/pairing_connection/tests/pairing_client.h b/adb/pairing_connection/tests/pairing_client.h new file mode 100644 index 000000000..be0db5ce4 --- /dev/null +++ b/adb/pairing_connection/tests/pairing_client.h @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include <stddef.h> +#include <stdint.h> + +#include <functional> +#include <memory> +#include <string_view> +#include <vector> + +#include "adb/pairing/pairing_connection.h" + +typedef void (*pairing_client_result_cb)(const PeerInfo*, void*); + +namespace adb { +namespace pairing { + +// PairingClient is the client side of the PairingConnection protocol. It will +// attempt to connect to a PairingServer specified at |host| and |port|, and +// allocate a new PairingConnection for processing. +// +// See pairing_connection_test.cpp for example usage. +// +class PairingClient { + public: + using Data = std::vector<uint8_t>; + + virtual ~PairingClient() = default; + + // Starts the pairing client. This call is non-blocking. Upon completion, + // if the pairing was successful, then |cb| will be called with the PeerInfo + // containing the info of the trusted peer. Otherwise, |cb| will be + // called with an empty value. Start can only be called once in the lifetime + // of this object. |ip_addr| requires a port to be specified. + // + // Returns true if PairingClient was successfully started. Otherwise, + // returns false. + virtual bool Start(std::string_view ip_addr, pairing_client_result_cb cb, void* opaque) = 0; + + // Creates a new PairingClient instance. May return null if unable + // to create an instance. |pswd|, |certificate|, |priv_key| and + // |ip_addr| cannot be empty. |peer_info| must contain non-empty strings for + // the guid and name fields. + static std::unique_ptr<PairingClient> Create(const Data& pswd, const PeerInfo& peer_info, + const Data& certificate, const Data& priv_key); + + protected: + PairingClient() = default; +}; // class PairingClient + +} // namespace pairing +} // namespace adb diff --git a/adb/pairing_connection/tests/pairing_connection_test.cpp b/adb/pairing_connection/tests/pairing_connection_test.cpp new file mode 100644 index 000000000..b6e09f190 --- /dev/null +++ b/adb/pairing_connection/tests/pairing_connection_test.cpp @@ -0,0 +1,500 @@ +/* + * Copyright 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "AdbPairingConnectionTest" + +#include <chrono> +#include <condition_variable> +#include <mutex> +#include <thread> + +#include <adb/pairing/pairing_server.h> +#include <android-base/logging.h> +#include <android-base/strings.h> +#include <gtest/gtest.h> + +#include "../internal/constants.h" +#include "pairing_client.h" + +using namespace std::chrono_literals; + +namespace adb { +namespace pairing { + +// Test X.509 certificates (RSA 2048) +static const std::string kTestRsa2048ServerCert = + "-----BEGIN CERTIFICATE-----\n" + "MIIDFzCCAf+gAwIBAgIBATANBgkqhkiG9w0BAQsFADAtMQswCQYDVQQGEwJVUzEQ\n" + "MA4GA1UECgwHQW5kcm9pZDEMMAoGA1UEAwwDQWRiMB4XDTIwMDEyMTIyMjU1NVoX\n" + "DTMwMDExODIyMjU1NVowLTELMAkGA1UEBhMCVVMxEDAOBgNVBAoMB0FuZHJvaWQx\n" + "DDAKBgNVBAMMA0FkYjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK8E\n" + "2Ck9TfuKlz7wqWdMfknjZ1luFDp2IHxAUZzh/F6jeI2dOFGAjpeloSnGOE86FIaT\n" + "d1EvpyTh7nBwbrLZAA6XFZTo7Bl6BdNOQdqb2d2+cLEN0inFxqUIycevRtohUE1Y\n" + "FHM9fg442X1jOTWXjDZWeiqFWo95paAPhzm6pWqfJK1+YKfT1LsWZpYqJGGQE5pi\n" + "C3qOBYYgFpoXMxTYJNoZo3uOYEdM6upc8/vh15nMgIxX/ymJxEY5BHPpZPPWjXLg\n" + "BfzVaV9fUfv0JT4HQ4t2WvxC3cD/UsjWp2a6p454uUp2ENrANa+jRdRJepepg9D2\n" + "DKsx9L8zjc5Obqexrt0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8B\n" + "Af8EBAMCAYYwHQYDVR0OBBYEFDFW+8GTErwoZN5Uu9KyY4QdGYKpMA0GCSqGSIb3\n" + "DQEBCwUAA4IBAQBCDEn6SHXGlq5TU7J8cg1kRPd9bsJW+0hDuKSq0REXDkl0PcBf\n" + "fy282Agg9enKPPKmnpeQjM1dmnxdM8tT8LIUbMl779i3fn6v9HJVB+yG4gmRFThW\n" + "c+AGlBnrIT820cX/gU3h3R3FTahfsq+1rrSJkEgHyuC0HYeRyveSckBdaEOLvx0S\n" + "toun+32JJl5hWydpUUZhE9Mbb3KHBRM2YYZZU9JeJ08Apjl+3lRUeMAUwI5fkAAu\n" + "z/1SqnuGL96bd8P5ixdkA1+rF8FPhodGcq9mQOuUGP9g5HOXjaNoJYvwVRUdLeGh\n" + "cP/ReOTwQIzM1K5a83p8cX8AGGYmM7dQp7ec\n" + "-----END CERTIFICATE-----\n"; + +static const std::string kTestRsa2048ServerPrivKey = + "-----BEGIN PRIVATE KEY-----\n" + "MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCvBNgpPU37ipc+\n" + "8KlnTH5J42dZbhQ6diB8QFGc4fxeo3iNnThRgI6XpaEpxjhPOhSGk3dRL6ck4e5w\n" + "cG6y2QAOlxWU6OwZegXTTkHam9ndvnCxDdIpxcalCMnHr0baIVBNWBRzPX4OONl9\n" + "Yzk1l4w2VnoqhVqPeaWgD4c5uqVqnyStfmCn09S7FmaWKiRhkBOaYgt6jgWGIBaa\n" + "FzMU2CTaGaN7jmBHTOrqXPP74deZzICMV/8picRGOQRz6WTz1o1y4AX81WlfX1H7\n" + "9CU+B0OLdlr8Qt3A/1LI1qdmuqeOeLlKdhDawDWvo0XUSXqXqYPQ9gyrMfS/M43O\n" + "Tm6nsa7dAgMBAAECggEAFCS2bPdUKIgjbzLgtHW+hT+J2hD20rcHdyAp+dNH/2vI\n" + "yLfDJHJA4chGMRondKA704oDw2bSJxxlG9t83326lB35yxPhye7cM8fqgWrK8PVl\n" + "tU22FhO1ZgeJvb9OeXWNxKZyDW9oOOJ8eazNXVMuEo+dFj7B6l3MXQyHJPL2mJDm\n" + "u9ofFLdypX+gJncVO0oW0FNJnEUn2MMwHDNlo7gc4WdQuidPkuZItKRGcB8TTGF3\n" + "Ka1/2taYdTQ4Aq//Z84LlFvE0zD3T4c8LwYYzOzD4gGGTXvft7vSHzIun1S8YLRS\n" + "dEKXdVjtaFhgH3uUe4j+1b/vMvSHeoGBNX/G88GD+wKBgQDWUYVlMVqc9HD2IeYi\n" + "EfBcNwAJFJkh51yAl5QbUBgFYgFJVkkS/EDxEGFPvEmI3/pAeQFHFY13BI466EPs\n" + "o8Z8UUwWDp+Z1MFHHKQKnFakbsZbZlbqjJ9VJsqpezbpWhMHTOmcG0dmE7rf0lyM\n" + "eQv9slBB8qp2NEUs5Of7f2C2bwKBgQDRDq4nUuMQF1hbjM05tGKSIwkobmGsLspv\n" + "TMhkM7fq4RpbFHmbNgsFqMhcqYZ8gY6/scv5KCuAZ4yHUkbqwf5h+QCwrJ4uJeUJ\n" + "ZgJfHus2mmcNSo8FwSkNoojIQtzcbJav7bs2K9VTuertk/i7IJLApU4FOZZ5pghN\n" + "EXu0CZF1cwKBgDWFGhjRIF29tU/h20R60llU6s9Zs3wB+NmsALJpZ/ZAKS4VPB5f\n" + "nCAXBRYSYRKrTCU5kpYbzb4BBzuysPOxWmnFK4j+keCqfrGxd02nCQP7HdHJVr8v\n" + "6sIq88UrHeVcNxBFprjzHvtgxfQK5k22FMZ/9wbhAKyQFQ5HA5+MiaxFAoGAIcZZ\n" + "ZIkDninnYIMS9OursShv5lRO+15j3i9tgKLKZ+wOMgDQ1L6acUOfezj4PU1BHr8+\n" + "0PYocQpJreMhCfRlgLaV4fVBaPs+UZJld7CrF5tCYudUy/01ALrtlk0XGZWBktK5\n" + "mDrksC4tQkzRtonAq9cJD9cJ9IVaefkFH0UcdvkCgYBpZj50VLeGhnHHBnkJRlV1\n" + "fV+/P6PAq6RtqjA6O9Qdaoj5V3w2d63aQcQXQLJjH2BBmtCIy47r04rFvZpbCxP7\n" + "NH/OnK9NHpk2ucRTe8TAnVbvF/TZzPJoIxAO/D3OWaW6df4R8en8u6GYzWFglAyT\n" + "sydGT8yfWD1FYUWgfrVRbg==\n" + "-----END PRIVATE KEY-----\n"; + +static const std::string kTestRsa2048ClientCert = + "-----BEGIN CERTIFICATE-----\n" + "MIIDFzCCAf+gAwIBAgIBATANBgkqhkiG9w0BAQsFADAtMQswCQYDVQQGEwJVUzEQ\n" + "MA4GA1UECgwHQW5kcm9pZDEMMAoGA1UEAwwDQWRiMB4XDTIwMDEyMTIyMjU1NloX\n" + "DTMwMDExODIyMjU1NlowLTELMAkGA1UEBhMCVVMxEDAOBgNVBAoMB0FuZHJvaWQx\n" + "DDAKBgNVBAMMA0FkYjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAI3a\n" + "EXh1S5FTbet7JVONswffRPaekdIK53cb8SnAbSO9X5OLA4zGwdkrBvDTsd96SKrp\n" + "JxmoNOE1DhbZh05KPlWAPkGKacjGWaz+S7biDOL0I6aaLbTlU/il1Ub9olPSBVUx\n" + "0nhdtEFgIOzddnP6/1KmyIIeRxS5lTKeg4avqUkZNXkz/wL1dHBFL7FNFf0SCcbo\n" + "tsub/deFbjZ27LTDN+SIBgFttTNqC5NTvoBAoMdyCOAgNYwaHO+fKiK3edfJieaw\n" + "7HD8qqmQxcpCtRlA8CUPj7GfR+WHiCJmlevhnkFXCo56R1BS0F4wuD4KPdSWt8gc\n" + "27ejH/9/z2cKo/6SLJMCAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8B\n" + "Af8EBAMCAYYwHQYDVR0OBBYEFO/Mr5ygqqpyU/EHM9v7RDvcqaOkMA0GCSqGSIb3\n" + "DQEBCwUAA4IBAQAH33KMouzF2DYbjg90KDrDQr4rq3WfNb6P743knxdUFuvb+40U\n" + "QjC2OJZHkSexH7wfG/y6ic7vfCfF4clNs3QvU1lEjOZC57St8Fk7mdNdsWLwxEMD\n" + "uePFz0dvclSxNUHyCVMqNxddzQYzxiDWQRmXWrUBliMduQqEQelcxW2yDtg8bj+s\n" + "aMpR1ra9scaD4jzIZIIxLoOS9zBMuNRbgP217sZrniyGMhzoI1pZ/izN4oXpyH7O\n" + "THuaCzzRT3ph2f8EgmHSodz3ttgSf2DHzi/Ez1xUkk7NOlgNtmsxEdrM47+cC5ae\n" + "fIf2V+1o1JW8J7D11RmRbNPh3vfisueB4f88\n" + "-----END CERTIFICATE-----\n"; + +static const std::string kTestRsa2048ClientPrivKey = + "-----BEGIN PRIVATE KEY-----\n" + "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCN2hF4dUuRU23r\n" + "eyVTjbMH30T2npHSCud3G/EpwG0jvV+TiwOMxsHZKwbw07Hfekiq6ScZqDThNQ4W\n" + "2YdOSj5VgD5BimnIxlms/ku24gzi9COmmi205VP4pdVG/aJT0gVVMdJ4XbRBYCDs\n" + "3XZz+v9SpsiCHkcUuZUynoOGr6lJGTV5M/8C9XRwRS+xTRX9EgnG6LbLm/3XhW42\n" + "duy0wzfkiAYBbbUzaguTU76AQKDHcgjgIDWMGhzvnyoit3nXyYnmsOxw/KqpkMXK\n" + "QrUZQPAlD4+xn0flh4giZpXr4Z5BVwqOekdQUtBeMLg+Cj3UlrfIHNu3ox//f89n\n" + "CqP+kiyTAgMBAAECggEAAa64eP6ggCob1P3c73oayYPIbvRqiQdAFOrr7Vwu7zbr\n" + "z0rde+n6RU0mrpc+4NuzyPMtrOGQiatLbidJB5Cx3z8U00ovqbCl7PtcgorOhFKe\n" + "VEzihebCcYyQqbWQcKtpDMhOgBxRwFoXieJb6VGXfa96FAZalCWvXgOrTl7/BF2X\n" + "qMqIm9nJi+yS5tIO8VdOsOmrMWRH/b/ENUcef4WpLoxTXr0EEgyKWraeZ/hhXo1e\n" + "z29dZKqdr9wMsq11NPsRddwS94jnDkXTo+EQyWVTfB7gb6yyp07s8jysaDb21tVv\n" + "UXB9MRhDV1mOv0ncXfXZ4/+4A2UahmZaLDAVLaat4QKBgQDAVRredhGRGl2Nkic3\n" + "KvZCAfyxug788CgasBdEiouz19iCCwcgMIDwnq0s3/WM7h/laCamT2x38riYDnpq\n" + "rkYMfuVtU9CjEL9pTrdfwbIRhTwYNqADaPz2mXwQUhRXutE5TIdgxxC/a+ZTh0qN\n" + "S+vhTj/4hf0IZhMh5Nqj7IPExQKBgQC8zxEzhmSGjys0GuE6Wl6Doo2TpiR6vwvi\n" + "xPLU9lmIz5eca/Rd/eERioFQqeoIWDLzx52DXuz6rUoQhbJWz9hP3yqCwXD+pbNP\n" + "oDJqDDbCC4IMYEb0IK/PEPH+gIpnTjoFcW+ecKDFG7W5Lt05J8WsJsfOaJvMrOU+\n" + "dLXq3IgxdwKBgQC5RAFq0v6e8G+3hFaEHL0z3igkpt3zJf7rnj37hx2FMmDa+3Z0\n" + "umQp5B9af61PgL12xLmeMBmC/Wp1BlVDV/Yf6Uhk5Hyv5t0KuomHEtTNbbLyfAPs\n" + "5P/vJu/L5NS1oT4S3LX3MineyjgGs+bLbpub3z1dzutrYLADUSiPCK/xJQKBgBQt\n" + "nQ0Ao+Wtj1R2OvPdjJRM3wyUiPmFSWPm4HzaBx+T8AQLlYYmB9O0FbXlMtnJc0iS\n" + "YMcVcgYoVu4FG9YjSF7g3s4yljzgwJUV7c1fmMqMKE3iTDLy+1cJ3JLycdgwiArk\n" + "4KTyLHxkRbuQwpvFIF8RlfD9RQlOwQE3v+llwDhpAoGBAL6XG6Rp6mBoD2Ds5c9R\n" + "943yYgSUes3ji1SI9zFqeJtj8Ml/enuK1xu+8E/BxB0//+vgZsH6i3i8GFwygKey\n" + "CGJF8CbiHc3EJc3NQIIRXcni/CGacf0HwC6m+PGFDBIpA4H2iDpVvCSofxttQiq0\n" + "/Z7HXmXUvZHVyYi/QzX2Gahj\n" + "-----END PRIVATE KEY-----\n"; + +struct ServerDeleter { + void operator()(PairingServerCtx* p) { pairing_server_destroy(p); } +}; +using ServerPtr = std::unique_ptr<PairingServerCtx, ServerDeleter>; + +struct ResultWaiter { + std::mutex mutex_; + std::condition_variable cv_; + std::optional<bool> is_valid_; + PeerInfo peer_info_; + + static void ResultCallback(const PeerInfo* peer_info, void* opaque) { + auto* p = reinterpret_cast<ResultWaiter*>(opaque); + { + std::unique_lock<std::mutex> lock(p->mutex_); + if (peer_info) { + memcpy(&(p->peer_info_), peer_info, sizeof(PeerInfo)); + } + p->is_valid_ = (peer_info != nullptr); + } + p->cv_.notify_one(); + } +}; + +class AdbPairingConnectionTest : public testing::Test { + protected: + virtual void SetUp() override {} + + virtual void TearDown() override {} + + void InitPairing(const std::vector<uint8_t>& server_pswd, + const std::vector<uint8_t>& client_pswd) { + server_ = CreateServer(server_pswd); + client_ = CreateClient(client_pswd); + } + + ServerPtr CreateServer(const std::vector<uint8_t>& pswd) { + return CreateServer(pswd, &server_info_, kTestRsa2048ServerCert, kTestRsa2048ServerPrivKey, + 0); + } + + std::unique_ptr<PairingClient> CreateClient(const std::vector<uint8_t> pswd) { + std::vector<uint8_t> cert; + std::vector<uint8_t> key; + // Include the null-byte as well. + cert.assign(reinterpret_cast<const uint8_t*>(kTestRsa2048ClientCert.data()), + reinterpret_cast<const uint8_t*>(kTestRsa2048ClientCert.data()) + + kTestRsa2048ClientCert.size() + 1); + key.assign(reinterpret_cast<const uint8_t*>(kTestRsa2048ClientPrivKey.data()), + reinterpret_cast<const uint8_t*>(kTestRsa2048ClientPrivKey.data()) + + kTestRsa2048ClientPrivKey.size() + 1); + return PairingClient::Create(pswd, client_info_, cert, key); + } + + static ServerPtr CreateServer(const std::vector<uint8_t>& pswd, const PeerInfo* peer_info, + const std::string_view cert, const std::string_view priv_key, + int port) { + return ServerPtr(pairing_server_new( + pswd.data(), pswd.size(), peer_info, reinterpret_cast<const uint8_t*>(cert.data()), + cert.size(), reinterpret_cast<const uint8_t*>(priv_key.data()), priv_key.size(), + port)); + } + + ServerPtr server_; + const PeerInfo server_info_ = { + .type = ADB_DEVICE_GUID, + .data = "my_server_info", + }; + std::unique_ptr<PairingClient> client_; + const PeerInfo client_info_ = { + .type = ADB_RSA_PUB_KEY, + .data = "my_client_info", + }; + std::string ip_addr_ = "127.0.0.1:"; +}; + +TEST_F(AdbPairingConnectionTest, ServerCreation) { + // All parameters bad + ASSERT_DEATH({ auto server = CreateServer({}, nullptr, "", "", 0); }, ""); + // Bad password + ASSERT_DEATH( + { + auto server = CreateServer({}, &server_info_, kTestRsa2048ServerCert, + kTestRsa2048ServerPrivKey, 0); + }, + ""); + // Bad peer_info + ASSERT_DEATH( + { + auto server = CreateServer({0x01}, nullptr, kTestRsa2048ServerCert, + kTestRsa2048ServerPrivKey, 0); + }, + ""); + // Bad certificate + ASSERT_DEATH( + { + auto server = CreateServer({0x01}, &server_info_, "", kTestRsa2048ServerPrivKey, 0); + }, + ""); + // Bad private key + ASSERT_DEATH( + { auto server = CreateServer({0x01}, &server_info_, kTestRsa2048ServerCert, "", 0); }, + ""); + // Valid params + auto server = CreateServer({0x01}, &server_info_, kTestRsa2048ServerCert, + kTestRsa2048ServerPrivKey, 0); + EXPECT_NE(nullptr, server); +} + +TEST_F(AdbPairingConnectionTest, ClientCreation) { + std::vector<uint8_t> pswd{0x01, 0x03, 0x05, 0x07}; + // Bad password + ASSERT_DEATH( + { + pairing_connection_client_new( + nullptr, pswd.size(), &client_info_, + reinterpret_cast<const uint8_t*>(kTestRsa2048ClientCert.data()), + kTestRsa2048ClientCert.size(), + reinterpret_cast<const uint8_t*>(kTestRsa2048ClientPrivKey.data()), + kTestRsa2048ClientPrivKey.size()); + }, + ""); + ASSERT_DEATH( + { + pairing_connection_client_new( + pswd.data(), 0, &client_info_, + reinterpret_cast<const uint8_t*>(kTestRsa2048ClientCert.data()), + kTestRsa2048ClientCert.size(), + reinterpret_cast<const uint8_t*>(kTestRsa2048ClientPrivKey.data()), + kTestRsa2048ClientPrivKey.size()); + }, + ""); + + // Bad peer_info + ASSERT_DEATH( + { + pairing_connection_client_new( + pswd.data(), pswd.size(), nullptr, + reinterpret_cast<const uint8_t*>(kTestRsa2048ClientCert.data()), + kTestRsa2048ClientCert.size(), + reinterpret_cast<const uint8_t*>(kTestRsa2048ClientPrivKey.data()), + kTestRsa2048ClientPrivKey.size()); + }, + ""); + + // Bad certificate + ASSERT_DEATH( + { + pairing_connection_client_new( + pswd.data(), pswd.size(), &client_info_, nullptr, + kTestRsa2048ClientCert.size(), + reinterpret_cast<const uint8_t*>(kTestRsa2048ClientPrivKey.data()), + kTestRsa2048ClientPrivKey.size()); + }, + ""); + ASSERT_DEATH( + { + pairing_connection_client_new( + pswd.data(), pswd.size(), &client_info_, + reinterpret_cast<const uint8_t*>(kTestRsa2048ClientCert.data()), 0, + reinterpret_cast<const uint8_t*>(kTestRsa2048ClientPrivKey.data()), + kTestRsa2048ClientPrivKey.size()); + }, + ""); + + // Bad private key + ASSERT_DEATH( + { + pairing_connection_client_new( + pswd.data(), pswd.size(), &client_info_, + reinterpret_cast<const uint8_t*>(kTestRsa2048ClientCert.data()), + kTestRsa2048ClientCert.size(), nullptr, kTestRsa2048ClientPrivKey.size()); + }, + ""); + ASSERT_DEATH( + { + pairing_connection_client_new( + pswd.data(), pswd.size(), &client_info_, + reinterpret_cast<const uint8_t*>(kTestRsa2048ClientCert.data()), + kTestRsa2048ClientCert.size(), + reinterpret_cast<const uint8_t*>(kTestRsa2048ClientPrivKey.data()), 0); + }, + ""); + + // Valid params + auto client = pairing_connection_client_new( + pswd.data(), pswd.size(), &client_info_, + reinterpret_cast<const uint8_t*>(kTestRsa2048ClientCert.data()), + kTestRsa2048ClientCert.size(), + reinterpret_cast<const uint8_t*>(kTestRsa2048ClientPrivKey.data()), + kTestRsa2048ClientPrivKey.size()); + EXPECT_NE(nullptr, client); +} + +TEST_F(AdbPairingConnectionTest, SmokeValidPairing) { + std::vector<uint8_t> pswd{0x01, 0x03, 0x05, 0x07}; + InitPairing(pswd, pswd); + + // Start the server + ResultWaiter server_waiter; + std::unique_lock<std::mutex> server_lock(server_waiter.mutex_); + auto port = pairing_server_start(server_.get(), server_waiter.ResultCallback, &server_waiter); + ASSERT_GT(port, 0); + ip_addr_ += std::to_string(port); + + // Start the client + ResultWaiter client_waiter; + std::unique_lock<std::mutex> client_lock(client_waiter.mutex_); + ASSERT_TRUE(client_->Start(ip_addr_, client_waiter.ResultCallback, &client_waiter)); + client_waiter.cv_.wait(client_lock, [&]() { return client_waiter.is_valid_.has_value(); }); + ASSERT_TRUE(*(client_waiter.is_valid_)); + ASSERT_EQ(strlen(reinterpret_cast<const char*>(client_waiter.peer_info_.data)), + strlen(reinterpret_cast<const char*>(server_info_.data))); + EXPECT_EQ(memcmp(client_waiter.peer_info_.data, server_info_.data, sizeof(server_info_.data)), + 0); + + // Kill server if the pairing failed, since server only shuts down when + // it gets a valid pairing. + if (!client_waiter.is_valid_) { + server_lock.unlock(); + server_.reset(); + } else { + server_waiter.cv_.wait(server_lock, [&]() { return server_waiter.is_valid_.has_value(); }); + ASSERT_TRUE(*(server_waiter.is_valid_)); + ASSERT_EQ(strlen(reinterpret_cast<const char*>(server_waiter.peer_info_.data)), + strlen(reinterpret_cast<const char*>(client_info_.data))); + EXPECT_EQ( + memcmp(server_waiter.peer_info_.data, client_info_.data, sizeof(client_info_.data)), + 0); + } +} + +TEST_F(AdbPairingConnectionTest, CancelPairing) { + std::vector<uint8_t> pswd{0x01, 0x03, 0x05, 0x07}; + std::vector<uint8_t> pswd2{0x01, 0x03, 0x05, 0x06}; + InitPairing(pswd, pswd2); + + // Start the server + ResultWaiter server_waiter; + std::unique_lock<std::mutex> server_lock(server_waiter.mutex_); + auto port = pairing_server_start(server_.get(), server_waiter.ResultCallback, &server_waiter); + ASSERT_GT(port, 0); + ip_addr_ += std::to_string(port); + + // Start the client. Client should fail to pair + ResultWaiter client_waiter; + std::unique_lock<std::mutex> client_lock(client_waiter.mutex_); + ASSERT_TRUE(client_->Start(ip_addr_, client_waiter.ResultCallback, &client_waiter)); + client_waiter.cv_.wait(client_lock, [&]() { return client_waiter.is_valid_.has_value(); }); + ASSERT_FALSE(*(client_waiter.is_valid_)); + + // Kill the server. We should still receive the callback with no valid + // pairing. + server_lock.unlock(); + server_.reset(); + server_lock.lock(); + ASSERT_TRUE(server_waiter.is_valid_.has_value()); + EXPECT_FALSE(*(server_waiter.is_valid_)); +} + +TEST_F(AdbPairingConnectionTest, MultipleClientsAllFail) { + std::vector<uint8_t> pswd{0x01, 0x03, 0x05, 0x07}; + std::vector<uint8_t> pswd2{0x01, 0x03, 0x05, 0x06}; + + // Start the server + auto server = CreateServer(pswd); + ResultWaiter server_waiter; + std::unique_lock<std::mutex> server_lock(server_waiter.mutex_); + auto port = pairing_server_start(server.get(), server_waiter.ResultCallback, &server_waiter); + ASSERT_GT(port, 0); + ip_addr_ += std::to_string(port); + + // Start multiple clients, all with bad passwords + int test_num_clients = 5; + int num_clients_done = 0; + std::mutex global_clients_mutex; + std::unique_lock<std::mutex> global_clients_lock(global_clients_mutex); + std::condition_variable global_cv_; + for (int i = 0; i < test_num_clients; ++i) { + std::thread([&]() { + auto client = CreateClient(pswd2); + ResultWaiter client_waiter; + std::unique_lock<std::mutex> client_lock(client_waiter.mutex_); + ASSERT_TRUE(client->Start(ip_addr_, client_waiter.ResultCallback, &client_waiter)); + client_waiter.cv_.wait(client_lock, + [&]() { return client_waiter.is_valid_.has_value(); }); + ASSERT_FALSE(*(client_waiter.is_valid_)); + { + std::lock_guard<std::mutex> global_lock(global_clients_mutex); + ++num_clients_done; + } + global_cv_.notify_one(); + }).detach(); + } + + global_cv_.wait(global_clients_lock, [&]() { return num_clients_done == test_num_clients; }); + server_lock.unlock(); + server.reset(); + server_lock.lock(); + ASSERT_TRUE(server_waiter.is_valid_.has_value()); + EXPECT_FALSE(*(server_waiter.is_valid_)); +} + +TEST_F(AdbPairingConnectionTest, MultipleClientsOnePass) { + // Send multiple clients with bad passwords, but send the last one with the + // correct password. + std::vector<uint8_t> pswd{0x01, 0x03, 0x05, 0x07}; + std::vector<uint8_t> pswd2{0x01, 0x03, 0x05, 0x06}; + + // Start the server + auto server = CreateServer(pswd); + ResultWaiter server_waiter; + std::unique_lock<std::mutex> server_lock(server_waiter.mutex_); + auto port = pairing_server_start(server.get(), server_waiter.ResultCallback, &server_waiter); + ASSERT_GT(port, 0); + ip_addr_ += std::to_string(port); + + // Start multiple clients, all with bad passwords + int test_num_clients = 5; + int num_clients_done = 0; + std::mutex global_clients_mutex; + std::unique_lock<std::mutex> global_clients_lock(global_clients_mutex); + std::condition_variable global_cv_; + for (int i = 0; i < test_num_clients; ++i) { + std::thread([&, i]() { + bool good_client = (i == (test_num_clients - 1)); + auto client = CreateClient((good_client ? pswd : pswd2)); + ResultWaiter client_waiter; + std::unique_lock<std::mutex> client_lock(client_waiter.mutex_); + ASSERT_TRUE(client->Start(ip_addr_, client_waiter.ResultCallback, &client_waiter)); + client_waiter.cv_.wait(client_lock, + [&]() { return client_waiter.is_valid_.has_value(); }); + if (good_client) { + ASSERT_TRUE(*(client_waiter.is_valid_)); + ASSERT_EQ(strlen(reinterpret_cast<const char*>(client_waiter.peer_info_.data)), + strlen(reinterpret_cast<const char*>(server_info_.data))); + EXPECT_EQ(memcmp(client_waiter.peer_info_.data, server_info_.data, + sizeof(server_info_.data)), + 0); + } else { + ASSERT_FALSE(*(client_waiter.is_valid_)); + } + { + std::lock_guard<std::mutex> global_lock(global_clients_mutex); + ++num_clients_done; + } + global_cv_.notify_one(); + }).detach(); + } + + global_cv_.wait(global_clients_lock, [&]() { return num_clients_done == test_num_clients; }); + server_waiter.cv_.wait(server_lock, [&]() { return server_waiter.is_valid_.has_value(); }); + ASSERT_TRUE(*(server_waiter.is_valid_)); + ASSERT_EQ(strlen(reinterpret_cast<const char*>(server_waiter.peer_info_.data)), + strlen(reinterpret_cast<const char*>(client_info_.data))); + EXPECT_EQ(memcmp(server_waiter.peer_info_.data, client_info_.data, sizeof(client_info_.data)), + 0); +} + +} // namespace pairing +} // namespace adb |