blob: b126a02cb2ba9d8d53fda4a63c7a5381d4c8f45a [file] [log] [blame]
// Copyright 2021 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "shill/vpn/wireguard_driver.h"
#include <poll.h>
#include <sys/utsname.h>
#include <set>
#include <string>
#include <utility>
#include <vector>
#include <base/base64.h>
#include <base/bind.h>
#include <base/callback_helpers.h>
#include <base/files/file_util.h>
#include <base/json/json_reader.h>
#include <base/json/json_writer.h>
#include <base/logging.h>
#include <base/stl_util.h>
#include <base/strings/strcat.h>
#include <base/strings/string_split.h>
#include <base/strings/stringprintf.h>
#include <base/time/time.h>
#include <base/version.h>
#include <chromeos/dbus/service_constants.h>
#include <crypto/random.h>
#include "shill/logging.h"
#include "shill/manager.h"
#include "shill/process_manager.h"
#include "shill/property_accessor.h"
#include "shill/store_interface.h"
#include "shill/vpn/vpn_util.h"
namespace shill {
namespace Logging {
static auto kModuleLogScope = ScopeLogger::kVPN;
static std::string ObjectID(const WireGuardDriver*) {
return "(wireguard_driver)";
}
} // namespace Logging
namespace {
constexpr char kWireGuardPath[] = "/usr/sbin/wireguard";
constexpr char kWireGuardToolsPath[] = "/usr/bin/wg";
constexpr char kDefaultInterfaceName[] = "wg0";
// The name of the property which indicates where the key pair comes from. This
// property only appears in storage but not in D-Bus API.
constexpr char kWireGuardKeyPairSource[] = "WireGuard.KeyPairSource";
// Timeout value for spawning the userspace wireguard process and configuring
// the interface via wireguard-tools.
constexpr base::TimeDelta kConnectTimeout = base::TimeDelta::FromSeconds(10);
// Key length of Curve25519.
constexpr int kWgKeyLength = 32;
constexpr int kWgBase64KeyLength = (((kWgKeyLength) + 2) / 3) * 4;
// Properties of a peer.
struct PeerProperty {
// A name will be used in 1) D-Bus API, 2) profile storage, and 3) config file
// passed to wireguard-tools.
const char* const name;
// Checked only before connecting. We allow a partially configured service
// from crosh.
const bool is_required;
};
constexpr PeerProperty kPeerProperties[] = {
{kWireGuardPeerPublicKey, true},
{kWireGuardPeerPresharedKey, false},
{kWireGuardPeerEndpoint, true},
{kWireGuardPeerAllowedIPs, false},
{kWireGuardPeerPersistentKeepalive, false},
};
// Checks the given peers object is valid for kept by WireguardDriver (it means
// this peers can be persisted in the storage but may be not ready for
// connecting). Here we checks whether each peer has a unique and non-empty
// public key.
bool ValidatePeersForStorage(const Stringmaps& peers) {
std::set<std::string> public_keys;
for (auto& peer : peers) {
const auto it = peer.find(kWireGuardPeerPublicKey);
if (it == peer.end()) {
return false;
}
const std::string& this_pubkey = it->second;
if (this_pubkey.empty()) {
return false;
}
if (public_keys.count(this_pubkey) != 0) {
return false;
}
public_keys.insert(this_pubkey);
}
return true;
}
std::string GenerateBase64PrivateKey() {
uint8_t key[kWgKeyLength];
crypto::RandBytes(key, kWgKeyLength);
// Converts the random bytes into a Curve25519 key, as per
// https://cr.yp.to/ecdh.html
key[0] &= 248;
key[31] &= 127;
key[31] |= 64;
return base::Base64Encode(base::span<uint8_t>(key, kWgKeyLength));
}
// Invokes wireguard-tools to calculates the public key based on the given
// private key. Returns an empty string on error. Note that the call to
// wireguard-tools is blocking but with a timeout (kPollTimeout below).
std::string CalculateBase64PublicKey(const std::string& base64_private_key,
ProcessManager* process_manager) {
constexpr auto kPollTimeout = base::TimeDelta::FromMilliseconds(200);
int stdin_fd = -1;
int stdout_fd = -1;
pid_t pid = process_manager->StartProcessInMinijailWithPipes(
FROM_HERE, base::FilePath(kWireGuardToolsPath), {"pubkey"},
/*environment=*/{}, VPNUtil::kVPNUser, VPNUtil::kVPNGroup, /*caps=*/0,
/*inherit_supplementary_groups=*/true, /*close_nonstd_fds=*/true,
/*exit_callback=*/base::DoNothing(),
{.stdin_fd = &stdin_fd, .stdout_fd = &stdout_fd});
if (pid == -1) {
LOG(ERROR) << "Failed to run 'wireguard-tools pubkey'";
return "";
}
base::ScopedFD scoped_stdin(stdin_fd);
base::ScopedFD scoped_stdout(stdout_fd);
if (!base::WriteFileDescriptor(scoped_stdin.get(), base64_private_key)) {
LOG(ERROR) << "Failed to send private key to wireguard-tools";
process_manager->StopProcess(pid);
return "";
}
scoped_stdin.reset();
struct pollfd pollfds[] = {{
.fd = scoped_stdout.get(),
.events = POLLIN,
}};
int ret = poll(pollfds, 1, kPollTimeout.InMilliseconds());
if (ret == -1) {
PLOG(ERROR) << "poll() failed";
process_manager->StopProcess(pid);
return "";
} else if (ret == 0) {
LOG(ERROR) << "poll() timeout";
process_manager->StopProcess(pid);
return "";
}
char buf[kWgBase64KeyLength];
ssize_t read_cnt =
HANDLE_EINTR(read(scoped_stdout.get(), buf, size_t{kWgBase64KeyLength}));
if (read_cnt == -1) {
PLOG(ERROR) << "read() failed";
process_manager->StopProcess(pid);
return "";
} else if (read_cnt != kWgBase64KeyLength) {
LOG(ERROR) << "Failed to read enough chars for a public key. read_cnt="
<< read_cnt;
process_manager->StopProcess(pid);
return "";
}
return std::string{buf, std::string::size_type{kWgBase64KeyLength}};
}
// Checks if the input string value for a property contains any invalid
// characters which can pollute the config file. Currently only '\n' is checked,
// which may generate a new parsable line.
bool ValidateInputString(const std::string& value) {
return value.find('\n') == value.npos;
}
} // namespace
// static
const VPNDriver::Property WireGuardDriver::kProperties[] = {
{kProviderHostProperty, 0},
{kProviderTypeProperty, 0},
// Properties for the interface. ListenPort is not here since we current
// only support the "client mode". Local overlay addresses on the interface,
// DNS servers, and MTU will be set via StaticIPConfig.
{kWireGuardPrivateKey, Property::kCredential | Property::kWriteOnly},
// TODO(b/177877860): This field is for software-backed keys only. May need
// to change this logic when hardware-backed keys come.
{kWireGuardPublicKey, Property::kReadOnly},
};
WireGuardDriver::WireGuardDriver(Manager* manager,
ProcessManager* process_manager)
: VPNDriver(manager, process_manager, kProperties, base::size(kProperties)),
vpn_util_(VPNUtil::New()) {}
WireGuardDriver::~WireGuardDriver() {
Cleanup();
}
base::TimeDelta WireGuardDriver::ConnectAsync(EventHandler* event_handler) {
SLOG(this, 2) << __func__;
event_handler_ = event_handler;
// To make sure the connect procedure is executed asynchronously.
dispatcher()->PostTask(
FROM_HERE,
base::BindOnce(&WireGuardDriver::CreateKernelWireGuardInterface,
weak_factory_.GetWeakPtr()));
return kConnectTimeout;
}
void WireGuardDriver::Disconnect() {
SLOG(this, 2) << __func__;
Cleanup();
event_handler_ = nullptr;
}
IPConfig::Properties WireGuardDriver::GetIPProperties() const {
return ip_properties_;
}
std::string WireGuardDriver::GetProviderType() const {
return kProviderWireGuard;
}
void WireGuardDriver::OnConnectTimeout() {
FailService(Service::kFailureConnect, "Connect timeout");
}
void WireGuardDriver::InitPropertyStore(PropertyStore* store) {
VPNDriver::InitPropertyStore(store);
store->RegisterDerivedStringmaps(
kWireGuardPeers,
StringmapsAccessor(
new CustomWriteOnlyAccessor<WireGuardDriver, Stringmaps>(
this, &WireGuardDriver::UpdatePeers, &WireGuardDriver::ClearPeers,
nullptr)));
}
KeyValueStore WireGuardDriver::GetProvider(Error* error) {
KeyValueStore props = VPNDriver::GetProvider(error);
Stringmaps copied_peers = peers_;
for (auto& peer : copied_peers) {
peer.erase(kWireGuardPeerPresharedKey);
}
props.Set<Stringmaps>(kWireGuardPeers, copied_peers);
return props;
}
bool WireGuardDriver::Load(const StoreInterface* storage,
const std::string& storage_id) {
if (!VPNDriver::Load(storage, storage_id)) {
return false;
}
peers_.clear();
std::vector<std::string> encoded_peers;
if (!storage->GetStringList(storage_id, kWireGuardPeers, &encoded_peers)) {
LOG(WARNING) << "Profile does not contain the " << kWireGuardPeers
<< " property";
return true;
}
for (const auto& peer_json : encoded_peers) {
base::Optional<base::Value> val = base::JSONReader::Read(peer_json);
if (!val || !val->is_dict()) {
LOG(ERROR) << "Failed to parse a peer. Skipped it.";
continue;
}
Stringmap peer;
for (const auto& property : kPeerProperties) {
const std::string key = property.name;
const auto* value = val->FindStringKey(key);
if (value != nullptr) {
peer[key] = *value;
} else {
peer[key] = "";
}
}
peers_.push_back(peer);
}
if (!ValidatePeersForStorage(peers_)) {
LOG(ERROR) << "Failed to load peers: missing PublicKey property or the "
"value is not unique";
peers_.clear();
return false;
}
// Loads |key_pair_source_|;
int stored_value = 0;
if (!storage->GetInt(storage_id, kWireGuardKeyPairSource, &stored_value)) {
stored_value = Metrics::kVpnWireguardKeyPairSourceUnknown;
}
if (stored_value != Metrics::kVpnWireGuardKeyPairSourceUserInput &&
stored_value != Metrics::kVpnWireGuardKeyPairSourceSoftwareGenerated) {
LOG(ERROR) << kWireGuardKeyPairSource
<< " contains an invalid value or does not exist in storage: "
<< stored_value;
stored_value = Metrics::kVpnWireguardKeyPairSourceUnknown;
}
key_pair_source_ =
static_cast<Metrics::VpnWireGuardKeyPairSource>(stored_value);
saved_private_key_ = args()->Lookup<std::string>(kWireGuardPrivateKey, "");
return true;
}
bool WireGuardDriver::Save(StoreInterface* storage,
const std::string& storage_id,
bool save_credentials) {
if (!save_credentials) {
LOG(WARNING) << "save_credentials is false when saving to the storage.";
}
// Keys should be processed before calling VPNDriver::Save().
auto private_key = args()->Lookup<std::string>(kWireGuardPrivateKey, "");
if (private_key.empty()) {
private_key = GenerateBase64PrivateKey();
args()->Set<std::string>(kWireGuardPrivateKey, private_key);
// The user cleared the private key.
key_pair_source_ = Metrics::kVpnWireGuardKeyPairSourceSoftwareGenerated;
} else if (private_key != saved_private_key_) {
// Note that this branch is different with the if statement below: if the
// private key in args() is not empty before we fill a random one in it, it
// must be changed by the user, and this code path is the only way where the
// user use its own private key.
key_pair_source_ = Metrics::kVpnWireGuardKeyPairSourceUserInput;
}
if (private_key != saved_private_key_) {
std::string public_key =
CalculateBase64PublicKey(private_key, process_manager());
if (public_key.empty()) {
LOG(ERROR) << "Failed to calculate public key in Save().";
return false;
}
args()->Set<std::string>(kWireGuardPublicKey, public_key);
saved_private_key_ = private_key;
}
// Handles peers.
std::vector<std::string> encoded_peers;
for (auto& peer : peers_) {
base::Value root(base::Value::Type::DICTIONARY);
for (const auto& property : kPeerProperties) {
const auto& key = property.name;
root.SetStringKey(key, peer[key]);
}
std::string peer_json;
if (!base::JSONWriter::Write(root, &peer_json)) {
LOG(ERROR) << "Failed to write a peer into json";
return false;
}
encoded_peers.push_back(peer_json);
}
if (!storage->SetStringList(storage_id, kWireGuardPeers, encoded_peers)) {
LOG(ERROR) << "Failed to write " << kWireGuardPeers
<< " property into profile";
return false;
}
if (!storage->SetInt(storage_id, kWireGuardKeyPairSource, key_pair_source_)) {
LOG(ERROR) << "Failed to write " << kWireGuardKeyPairSource
<< " property into profile";
return false;
}
return VPNDriver::Save(storage, storage_id, save_credentials);
}
void WireGuardDriver::UnloadCredentials() {
VPNDriver::UnloadCredentials();
for (auto& peer : peers_) {
// For a peer loaded by Load(), all properties should exist even if they are
// empty, so we only clear the value here, instead of erasing the key.
peer[kWireGuardPeerPresharedKey] = "";
}
}
void WireGuardDriver::CreateKernelWireGuardInterface() {
auto link_ready_callback = base::BindOnce(
&WireGuardDriver::ConfigureInterface, weak_factory_.GetWeakPtr(),
/*created_in_kernel=*/true);
auto failure_callback =
base::BindOnce(&WireGuardDriver::StartUserspaceWireGuardTunnel,
weak_factory_.GetWeakPtr());
if (!manager()->device_info()->CreateWireGuardInterface(
kDefaultInterfaceName, std::move(link_ready_callback),
std::move(failure_callback))) {
StartUserspaceWireGuardTunnel();
}
}
void WireGuardDriver::StartUserspaceWireGuardTunnel() {
LOG(INFO) << "Failed to create a wireguard interface in the kernel. Fallback "
"to userspace tunnel.";
// Claims the interface before the wireguard process creates it.
// TODO(b/177876632): Actually when the tunnel interface is ready, it cannot
// guarantee that the wireguard-tools can talk with the userspace wireguard
// process now. We should also wait for another event that the UAPI socket
// appears (which is a UNIX-domain socket created by the userspace wireguard
// process at a fixed path: `/var/run/wireguard/wg0.sock`).
manager()->device_info()->AddVirtualInterfaceReadyCallback(
kDefaultInterfaceName,
base::BindOnce(&WireGuardDriver::ConfigureInterface,
weak_factory_.GetWeakPtr(),
/*created_in_kernel=*/false));
if (!SpawnWireGuard()) {
FailService(Service::kFailureInternal, "Failed to spawn wireguard process");
}
}
bool WireGuardDriver::SpawnWireGuard() {
SLOG(this, 2) << __func__;
// TODO(b/177876632): Change this part after we decide the userspace binary to
// use. For wireguard-go, we need to change the way to invoke minijail; for
// wireugard-rs, we need to add `--disable-drop-privileges` or change the
// capmask.
std::vector<std::string> args = {
"--foreground",
kDefaultInterfaceName,
};
constexpr uint64_t kCapMask = CAP_TO_MASK(CAP_NET_ADMIN);
wireguard_pid_ = process_manager()->StartProcessInMinijail(
FROM_HERE, base::FilePath(kWireGuardPath), args,
/*environment=*/{}, VPNUtil::kVPNUser, VPNUtil::kVPNGroup, kCapMask,
/*inherit_supplementary_groups=*/true, /*close_nonstd_fds=*/true,
base::BindRepeating(&WireGuardDriver::WireGuardProcessExited,
weak_factory_.GetWeakPtr()));
return wireguard_pid_ > -1;
}
void WireGuardDriver::WireGuardProcessExited(int exit_code) {
wireguard_pid_ = -1;
FailService(
Service::kFailureInternal,
base::StringPrintf("wireguard process exited unexpectedly with code=%d",
exit_code));
}
std::string WireGuardDriver::GenerateConfigFileContents() {
std::vector<std::string> lines;
// [Interface] section
lines.push_back("[Interface]");
const std::string private_key =
args()->Lookup<std::string>(kWireGuardPrivateKey, "");
if (!ValidateInputString(private_key)) {
LOG(ERROR) << "PrivateKey contains invalid characters.";
return "";
}
if (private_key.empty()) {
LOG(ERROR) << "PrivateKey is required but is empty or not set.";
return "";
}
lines.push_back(base::StrCat({"PrivateKey", "=", private_key}));
// 0x4000 for bypass VPN, 0x0500 for source of host VPN.
// See patchpanel/routing_service.h for their definitions.
lines.push_back("FwMark=0x4500");
lines.push_back("");
// [Peer] sections
for (auto& peer : peers_) {
lines.push_back("[Peer]");
for (const auto& property : kPeerProperties) {
const std::string val = peer[property.name];
if (!ValidateInputString(val)) {
LOG(ERROR) << property.name << " contains invalid characters.";
return "";
}
if (!val.empty()) {
lines.push_back(base::StrCat({property.name, "=", val}));
} else if (property.is_required) {
LOG(ERROR) << property.name
<< " in a peer is required but is empty or not set.";
return "";
}
}
lines.push_back("");
}
return base::JoinString(lines, "\n");
}
void WireGuardDriver::ConfigureInterface(bool created_in_kernel,
const std::string& interface_name,
int interface_index) {
LOG(INFO) << "WireGuard interface " << interface_name << " was created "
<< (created_in_kernel ? "in kernel" : "by userspace program")
<< ". Start configuration";
kernel_interface_open_ = created_in_kernel;
if (!event_handler_) {
LOG(ERROR) << "Missing event_handler_";
Cleanup();
return;
}
interface_index_ = interface_index;
// Writes config file.
std::string config_contents = GenerateConfigFileContents();
if (config_contents.empty()) {
FailService(Service::kFailureInternal,
"Failed to generate config file contents");
return;
}
auto [fd, path] = vpn_util_->WriteAnonymousConfigFile(config_contents);
config_fd_ = std::move(fd);
if (!config_fd_.is_valid()) {
FailService(Service::kFailureInternal, "Failed to write config file");
return;
}
// Executes wireguard-tools.
std::vector<std::string> args = {"setconf", kDefaultInterfaceName,
path.value()};
constexpr uint64_t kCapMask = CAP_TO_MASK(CAP_NET_ADMIN);
pid_t pid = process_manager()->StartProcessInMinijail(
FROM_HERE, base::FilePath(kWireGuardToolsPath), args,
/*environment=*/{}, VPNUtil::kVPNUser, VPNUtil::kVPNGroup, kCapMask,
/*inherit_supplementary_groups=*/true, /*close_nonstd_fds=*/false,
base::BindRepeating(&WireGuardDriver::OnConfigurationDone,
weak_factory_.GetWeakPtr()));
if (pid == -1) {
FailService(Service::kFailureInternal, "Failed to run `wg setconf`");
return;
}
}
void WireGuardDriver::OnConfigurationDone(int exit_code) {
SLOG(this, 2) << __func__ << ": exit_code=" << exit_code;
// Closes the config file to remove it.
config_fd_.reset();
if (exit_code != 0) {
FailService(
Service::kFailureInternal,
base::StringPrintf("Failed to run `wg setconf`, code=%d", exit_code));
return;
}
if (!PopulateIPProperties()) {
FailService(Service::kFailureInternal, "Failed to populate ip properties");
return;
}
ReportConnectionMetrics();
event_handler_->OnDriverConnected(kDefaultInterfaceName, interface_index_);
}
bool WireGuardDriver::PopulateIPProperties() {
ip_properties_.default_route = false;
// When we arrive here, the value of AllowedIPs has already been validated
// by wireguard-tools. AllowedIPs is comma-separated list of CIDR-notation
// addresses (e.g., "10.8.0.1/16,192.168.1.1/24").
for (auto& peer : peers_) {
std::string allowed_ips_str = peer[kWireGuardPeerAllowedIPs];
std::vector<std::string> allowed_ip_list = base::SplitString(
allowed_ips_str, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
for (const auto& allowed_ip_str : allowed_ip_list) {
IPAddress allowed_ip;
// Currently only supports IPv4 addresses.
allowed_ip.set_family(IPAddress::kFamilyIPv4);
if (!allowed_ip.SetAddressAndPrefixFromString(allowed_ip_str)) {
LOG(DFATAL) << "Invalid allowed ip: " << allowed_ip_str;
return false;
}
// We don't need a gateway here, so use the "default" address as the
// gateways, and then RoutingTable will skip RTA_GATEWAY when installing
// this entry.
ip_properties_.routes.push_back({allowed_ip.GetNetworkPart().ToString(),
static_cast<int>(allowed_ip.prefix()),
/*gateway=*/"0.0.0.0"});
}
}
ip_properties_.method = kTypeVPN;
return true;
}
void WireGuardDriver::FailService(Service::ConnectFailure failure,
const std::string& error_details) {
LOG(ERROR) << "Driver error: " << error_details;
Cleanup();
if (event_handler_) {
event_handler_->OnDriverFailure(failure, error_details);
event_handler_ = nullptr;
}
}
void WireGuardDriver::Cleanup() {
if (wireguard_pid_ != -1) {
process_manager()->StopProcess(wireguard_pid_);
wireguard_pid_ = -1;
}
if (kernel_interface_open_) {
manager()->device_info()->DeleteInterface(interface_index_);
kernel_interface_open_ = false;
}
interface_index_ = -1;
ip_properties_ = {};
config_fd_.reset();
}
bool WireGuardDriver::UpdatePeers(const Stringmaps& new_peers, Error* error) {
if (!ValidatePeersForStorage(new_peers)) {
Error::PopulateAndLog(
FROM_HERE, error, Error::kInvalidProperty,
"Invalid peers: missing PublicKey property or the value is not unique");
return false;
}
// If the preshared key of a peer in the new peers is unspecified (the caller
// doesn't set that key), try to reset it to the old value.
Stringmap pubkey_to_psk;
for (auto& peer : peers_) {
pubkey_to_psk[peer[kWireGuardPeerPublicKey]] =
peer[kWireGuardPeerPresharedKey];
}
peers_ = new_peers;
for (auto& peer : peers_) {
if (peer.find(kWireGuardPeerPresharedKey) != peer.end()) {
continue;
}
peer[kWireGuardPeerPresharedKey] =
pubkey_to_psk[peer[kWireGuardPeerPublicKey]];
}
return true;
}
void WireGuardDriver::ClearPeers(Error* error) {
peers_.clear();
}
void WireGuardDriver::ReportConnectionMetrics() {
// VPN type.
metrics()->SendEnumToUMA(Metrics::kMetricVpnDriver,
Metrics::kVpnDriverWireGuard,
Metrics::kMetricVpnDriverMax);
// Key pair source.
metrics()->SendEnumToUMA(Metrics::kMetricVpnWireGuardKeyPairSource,
key_pair_source_,
Metrics::kMetricVpnWireGuardKeyPairSourceMax);
// Number of peers.
metrics()->SendToUMA(Metrics::kMetricVpnWireGuardPeersNum, peers_.size(),
Metrics::kMetricVpnWireGuardPeersNumMin,
Metrics::kMetricVpnWireGuardPeersNumMax,
Metrics::kMetricVpnWireGuardPeersNumNumBuckets);
// Allowed IPs type.
// TODO(b/194243702): Collect metrics for IPv6 usages in Allowed IPs.
auto allowed_ips_type = Metrics::kVpnWireGuardAllowedIPsTypeNoDefaultRoute;
for (auto peer : peers_) {
if (peer[kWireGuardPeerAllowedIPs].find("0.0.0.0/0") != std::string::npos) {
allowed_ips_type = Metrics::kVpnWireGuardAllowedIPsTypeHasDefaultRoute;
break;
}
}
metrics()->SendEnumToUMA(Metrics::kMetricVpnWireGuardAllowedIPsType,
allowed_ips_type,
Metrics::kMetricVpnWireGuardAllowedIPsTypeMax);
}
// static
bool WireGuardDriver::IsSupported() {
// WireGuard is current supported on kernel version >= 5.10
struct utsname buf;
if (uname(&buf) != 0) {
return false;
}
// Extract the numeric part of release string
std::string version = base::SplitString(
buf.release, "-", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY)[0];
base::Version kernel_version = base::Version(version);
return kernel_version.IsValid() && kernel_version >= base::Version("5.10");
}
} // namespace shill