| /* | |
| * 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 "client/adb_wifi.h" | |
| #include <fstream> | |
| #include <random> | |
| #include <thread> | |
| #include <type_traits> | |
| #include <adb/crypto/key.h> | |
| #include <adb/crypto/x509_generator.h> | |
| #include <android-base/file.h> | |
| #include <android-base/parsenetaddress.h> | |
| #include "client/pairing/pairing_client.h" | |
| #include "client/transport_mdns.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; | |
| KnownWifiHostsFile known_wifi_hosts_file [[clang::no_destroy]]; | |
| struct PairingResultWaiter { | |
| std::mutex mutex_; | |
| std::condition_variable cv_; | |
| std::optional<bool> is_valid_; | |
| PeerInfo peer_info_; | |
| static void OnResult(const PeerInfo* peer_info, void* opaque) { | |
| CHECK(opaque); | |
| auto* p = reinterpret_cast<PairingResultWaiter*>(opaque); | |
| { | |
| std::lock_guard<std::mutex> lock(p->mutex_); | |
| if (peer_info) { | |
| static_assert(std::is_standard_layout<decltype(p->peer_info_)>()); | |
| memcpy(&(p->peer_info_), peer_info, sizeof(PeerInfo)); | |
| } | |
| p->is_valid_ = (peer_info != nullptr); | |
| } | |
| p->cv_.notify_one(); | |
| } | |
| }; // PairingResultWaiter | |
| static std::vector<uint8_t> stringToUint8(const std::string& str) { | |
| auto* p8 = reinterpret_cast<const uint8_t*>(str.data()); | |
| return std::vector<uint8_t>(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; | |
| } | |
| adb::proto::AdbKnownHosts KnownWifiHostsFile::Load() { | |
| adb::proto::AdbKnownHosts known_hosts; | |
| // Check for file existence. | |
| struct stat buf; | |
| if (stat(keystore_path_.c_str(), &buf) == -1) { | |
| return known_hosts; | |
| } | |
| std::ifstream file(keystore_path_, std::ios::binary); | |
| if (!file) { | |
| PLOG(ERROR) << "Unable to open [" << keystore_path_ << "]."; | |
| return known_hosts; | |
| } | |
| if (!known_hosts.ParseFromIstream(&file)) { | |
| PLOG(ERROR) << "Failed to parse [" << keystore_path_ | |
| << "]. Deleting it as it may be corrupted."; | |
| adb_unlink(keystore_path_.c_str()); | |
| return known_hosts; | |
| } | |
| return known_hosts; | |
| } | |
| void KnownWifiHostsFile::Clear() { | |
| adb_unlink(keystore_path_.c_str()); | |
| } | |
| bool KnownWifiHostsFile::AddKnownHost(const std::string& host) { | |
| auto known_hosts = Load(); | |
| auto* host_info = known_hosts.add_host_infos(); | |
| host_info->set_guid(host); | |
| std::unique_ptr<TemporaryFile> 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(keystore_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(keystore_path_.c_str(), S_IRUSR | S_IWUSR | S_IRGRP); | |
| return true; | |
| } | |
| KnownWifiHostsFile::KnownWifiHostsFile() | |
| : keystore_path_(adb_get_android_dir_path() + OS_PATH_SEPARATOR + "adb_known_hosts.pb") {} | |
| bool KnownWifiHostsFile::IsKnownHost(const std::string& host) { | |
| auto known_hosts = Load(); | |
| for (const auto& host_info : known_hosts.host_infos()) { | |
| // Android devices have a bug where they can register a service instance before | |
| // unregistering the previous one. This leads to mdns duplicates. We can mitigate this issue | |
| // by detecting them (a duplicate is automatically created by the mDNS stack with a suffix | |
| // " (X)" where X is an integer to dedup). Testing for start_with instead of == does the | |
| // trick. This workaround can be deleted once b/432299214 and b/421363980 fixes have been | |
| // out for a few years. | |
| if (host.starts_with(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) { | |
| response = "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<std::mutex> lock(waiter.mutex_); | |
| if (!client->Start(mdns_info.has_value() ? std::format("{}:{}", mdns_info->v4_address_string(), | |
| 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<const char*>(waiter.peer_info_.data); | |
| response = "Successfully paired to " + host + " [guid=" + device_guid + "]"; | |
| // Write to adb_known_hosts | |
| known_wifi_hosts_file.AddKnownHost(device_guid); | |
| // Try to auto-connect. | |
| adb_secure_connect_by_service_name(device_guid); | |
| } |