/* * Copyright (C) 2019 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_wifi.h" #include #include #include #include #include #include #include #include "client/pairing/pairing_client.h" #include "adb_auth.h" #include "adb_known_hosts.pb.h" #include "adb_mdns.h" #include "adb_utils.h" #include "client/adb_client.h" #include "sysdeps.h" using adbwifi::pairing::PairingClient; using namespace adb::crypto; struct PairingResultWaiter { std::mutex mutex_; std::condition_variable cv_; std::optional is_valid_; PeerInfo peer_info_; static void OnResult(const PeerInfo* peer_info, void* opaque) { CHECK(opaque); auto* p = reinterpret_cast(opaque); { std::lock_guard lock(p->mutex_); if (peer_info) { memcpy(&(p->peer_info_), peer_info, sizeof(PeerInfo)); } p->is_valid_ = (peer_info != nullptr); } p->cv_.notify_one(); } }; // PairingResultWaiter void adb_wifi_init() {} static std::vector stringToUint8(const std::string& str) { auto* p8 = reinterpret_cast(str.data()); return std::vector(p8, p8 + str.length()); } // Tries to replace the |old_file| with |new_file|. // On success, then |old_file| has been removed and replaced with the // contents of |new_file|, |new_file| will be removed, and only |old_file| will // remain. // On failure, both files will be unchanged. // |new_file| must exist, but |old_file| does not need to exist. bool SafeReplaceFile(std::string_view old_file, std::string_view new_file) { std::string to_be_deleted(old_file); to_be_deleted += ".tbd"; bool old_renamed = true; if (adb_rename(old_file.data(), to_be_deleted.c_str()) != 0) { // Don't exit here. This is not necessarily an error, because |old_file| // may not exist. PLOG(INFO) << "Failed to rename " << old_file; old_renamed = false; } if (adb_rename(new_file.data(), old_file.data()) != 0) { PLOG(ERROR) << "Unable to rename file (" << new_file << " => " << old_file << ")"; if (old_renamed) { // Rename the .tbd file back to it's original name adb_rename(to_be_deleted.c_str(), old_file.data()); } return false; } adb_unlink(to_be_deleted.c_str()); return true; } static std::string get_user_known_hosts_path() { return adb_get_android_dir_path() + OS_PATH_SEPARATOR + "adb_known_hosts.pb"; } bool load_known_hosts_from_file(const std::string& path, adb::proto::AdbKnownHosts& known_hosts) { // Check for file existence. struct stat buf; if (stat(path.c_str(), &buf) == -1) { LOG(INFO) << "Known hosts file [" << path << "] does not exist..."; return false; } std::ifstream file(path, std::ios::binary); if (!file) { PLOG(ERROR) << "Unable to open [" << path << "]."; return false; } if (!known_hosts.ParseFromIstream(&file)) { PLOG(ERROR) << "Failed to parse [" << path << "]. Deleting it as it may be corrupted."; adb_unlink(path.c_str()); return false; } return true; } static bool write_known_host_to_file(std::string& known_host) { std::string path = get_user_known_hosts_path(); if (path.empty()) { PLOG(ERROR) << "Error getting user known hosts filename"; return false; } adb::proto::AdbKnownHosts known_hosts; load_known_hosts_from_file(path, known_hosts); auto* host_info = known_hosts.add_host_infos(); host_info->set_guid(known_host); std::unique_ptr temp_file(new TemporaryFile(adb_get_android_dir_path())); if (temp_file->fd == -1) { PLOG(ERROR) << "Failed to open [" << temp_file->path << "] for writing"; return false; } if (!known_hosts.SerializeToFileDescriptor(temp_file->fd)) { LOG(ERROR) << "Unable to write out adb_knowns_hosts"; return false; } temp_file->DoNotRemove(); std::string temp_file_name(temp_file->path); temp_file.reset(); // Replace the existing adb_known_hosts with the new one if (!SafeReplaceFile(path, temp_file_name.c_str())) { LOG(ERROR) << "Failed to replace old adb_known_hosts"; adb_unlink(temp_file_name.c_str()); return false; } chmod(path.c_str(), S_IRUSR | S_IWUSR | S_IRGRP); return true; } bool adb_wifi_is_known_host(const std::string& host) { std::string path = get_user_known_hosts_path(); if (path.empty()) { PLOG(ERROR) << "Error getting user known hosts filename"; return false; } adb::proto::AdbKnownHosts known_hosts; if (!load_known_hosts_from_file(path, known_hosts)) { return false; } for (const auto& host_info : known_hosts.host_infos()) { if (host == host_info.guid()) { return true; } } return false; } void adb_wifi_pair_device(const std::string& host, const std::string& password, std::string& response) { auto mdns_info = mdns_get_pairing_service_info(host); if (!mdns_info.has_value()) { // Check the address for a valid address and port. std::string parsed_host; std::string err; int port = -1; if (!android::base::ParseNetAddress(host, &parsed_host, &port, nullptr, &err)) { response = "Failed to parse address for pairing: " + err; return; } if (port <= 0 || port > 65535) { response = "Invalid port while parsing address [" + host + "]"; return; } } auto priv_key = adb_auth_get_user_privkey(); auto x509_cert = GenerateX509Certificate(priv_key.get()); if (!x509_cert) { LOG(ERROR) << "Unable to create X509 certificate for pairing"; return; } auto cert_str = X509ToPEMString(x509_cert.get()); auto priv_str = Key::ToPEMString(priv_key.get()); // Send our public key on pairing success PeerInfo system_info = {}; system_info.type = ADB_RSA_PUB_KEY; std::string public_key = adb_auth_get_userkey(); CHECK_LE(public_key.size(), sizeof(system_info.data) - 1); // -1 for null byte memcpy(system_info.data, public_key.data(), public_key.size()); auto pswd8 = stringToUint8(password); auto cert8 = stringToUint8(cert_str); auto priv8 = stringToUint8(priv_str); auto client = PairingClient::Create(pswd8, system_info, cert8, priv8); if (client == nullptr) { response = "Failed: unable to create pairing client."; return; } PairingResultWaiter waiter; std::unique_lock lock(waiter.mutex_); if (!client->Start(mdns_info.has_value() ? android::base::StringPrintf("%s:%d", mdns_info->addr.c_str(), mdns_info->port) : host, waiter.OnResult, &waiter)) { response = "Failed: Unable to start pairing client."; return; } waiter.cv_.wait(lock, [&]() { return waiter.is_valid_.has_value(); }); if (!*(waiter.is_valid_)) { response = "Failed: Wrong password or connection was dropped."; return; } if (waiter.peer_info_.type != ADB_DEVICE_GUID) { response = "Failed: Successfully paired but server returned unknown response="; response += waiter.peer_info_.type; return; } std::string device_guid = reinterpret_cast(waiter.peer_info_.data); response = "Successfully paired to " + host + " [guid=" + device_guid + "]"; // Write to adb_known_hosts write_known_host_to_file(device_guid); // Try to auto-connect. adb_secure_connect_by_service_name(device_guid); }