diff options
| author | Emīls Piņķis <emils@mullvad.net> | 2019-02-27 15:33:46 +0000 |
|---|---|---|
| committer | Emīls Piņķis <emils@mullvad.net> | 2019-02-27 15:33:46 +0000 |
| commit | 502bf6fa28820fcfe9f1a16bf208d09191792cd7 (patch) | |
| tree | 3060c10fb531e5687e57126318b83aaf50248180 | |
| parent | 068b0bdea9bad08c9ade30fa70d7619db89344d8 (diff) | |
| parent | a05b2329ce32aa929eed1903d8fdd63780a8127d (diff) | |
| download | mullvadvpn-502bf6fa28820fcfe9f1a16bf208d09191792cd7.tar.xz mullvadvpn-502bf6fa28820fcfe9f1a16bf208d09191792cd7.zip | |
Merge branch 'wg-key-management'
| -rw-r--r-- | Cargo.lock | 76 | ||||
| -rw-r--r-- | mullvad-cli/src/cmds/tunnel.rs | 39 | ||||
| -rw-r--r-- | mullvad-daemon/src/account_history.rs | 188 | ||||
| -rw-r--r-- | mullvad-daemon/src/lib.rs | 210 | ||||
| -rw-r--r-- | mullvad-daemon/src/management_interface.rs | 123 | ||||
| -rw-r--r-- | mullvad-ipc-client/src/lib.rs | 17 | ||||
| -rw-r--r-- | mullvad-rpc/Cargo.toml | 1 | ||||
| -rw-r--r-- | mullvad-rpc/src/lib.rs | 14 | ||||
| -rw-r--r-- | mullvad-types/Cargo.toml | 1 | ||||
| -rw-r--r-- | mullvad-types/src/lib.rs | 1 | ||||
| -rw-r--r-- | mullvad-types/src/wireguard.rs | 17 | ||||
| -rw-r--r-- | talpid-types/Cargo.toml | 2 | ||||
| -rw-r--r-- | talpid-types/src/net/wireguard.rs | 26 |
13 files changed, 577 insertions, 138 deletions
diff --git a/Cargo.lock b/Cargo.lock index 95fb30478c..d564c72f74 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -166,6 +166,14 @@ dependencies = [ ] [[package]] +name = "clear_on_drop" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cc 1.0.29 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] name = "cloudabi" version = "0.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -265,6 +273,18 @@ dependencies = [ ] [[package]] +name = "curve25519-dalek" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "clear_on_drop 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "digest 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "subtle 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] name = "dbus" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -302,6 +322,14 @@ dependencies = [ ] [[package]] +name = "digest" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "generic-array 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] name = "dirs" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -513,6 +541,14 @@ version = "0.3.55" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] +name = "generic-array" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "typenum 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] name = "globset" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -642,6 +678,14 @@ dependencies = [ [[package]] name = "ipnetwork" version = "0.14.0" +source = "git+https://github.com/mullvad/ipnetwork?branch=fix-deserialization#10d12cd94509f98cbc0d60d9f42380615faee638" +dependencies = [ + "serde 1.0.87 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "ipnetwork" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "serde 1.0.87 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1156,6 +1200,7 @@ dependencies = [ "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "mullvad-types 0.1.0", "serde_json 1.0.38 (registry+https://github.com/rust-lang/crates.io-index)", + "talpid-types 0.1.0", "tempfile 3.0.6 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-core 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-openssl 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1189,6 +1234,7 @@ version = "0.1.0" dependencies = [ "chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "error-chain 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", + "ipnetwork 0.14.0 (git+https://github.com/mullvad/ipnetwork?branch=fix-deserialization)", "lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "mullvad-paths 0.1.0", @@ -1884,6 +1930,11 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] +name = "subtle" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] name = "syn" version = "0.11.11" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2036,7 +2087,9 @@ dependencies = [ "error-chain 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", "hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "ipnetwork 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.87 (registry+https://github.com/rust-lang/crates.io-index)", + "x25519-dalek 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -2362,6 +2415,11 @@ dependencies = [ ] [[package]] +name = "typenum" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] name = "ucd-util" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2552,6 +2610,16 @@ dependencies = [ "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "x25519-dalek" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "clear_on_drop 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "curve25519-dalek 1.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [metadata] "checksum aho-corasick 0.6.9 (registry+https://github.com/rust-lang/crates.io-index)" = "1e9a933f4e58658d7b12defcf96dc5c720f20832deebe3e0a19efd3b6aaeeb9e" "checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" @@ -2574,6 +2642,7 @@ dependencies = [ "checksum cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "082bb9b28e00d3c9d39cc03e64ce4cea0f1bb9b3fde493f0cbc008472d22bdf4" "checksum chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "45912881121cb26fad7c38c17ba7daa18764771836b34fab7d3fbd93ed633878" "checksum clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b957d88f4b6a63b9d70d5f454ac8011819c6efa7727858f458ab71c756ce2d3e" +"checksum clear_on_drop 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "97276801e127ffb46b66ce23f35cc96bd454fa311294bced4bbace7baa8b1d17" "checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" "checksum colored 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6e9a455e156a4271e12fd0246238c380b1e223e3736663c7a18ed8b6362028a9" "checksum constant_time_eq 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8ff012e225ce166d4422e0e78419d901719760f62ae2b7969ca6b564d1b54a9e" @@ -2585,10 +2654,12 @@ dependencies = [ "checksum crossbeam-epoch 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "04c9e3102cc2d69cd681412141b390abd55a362afc1540965dad0ad4d34280b4" "checksum crossbeam-utils 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "f8306fcef4a7b563b76b7dd949ca48f52bc1141aa067d2ea09565f3e2652aa5c" "checksum ctrlc 3.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "630391922b1b893692c6334369ff528dcc3a9d8061ccf4c803aa8f83cb13db5e" +"checksum curve25519-dalek 1.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e1f8a6fc0376eb52dc18af94915cc04dfdf8353746c0e8c550ae683a0815e5c1" "checksum dbus 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "b9e1b39f3f6aa3d4a1522c4f0f9f1e9e9167bd93740a8690874caa7cf8ce47d7" "checksum derive-try-from-primitive 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "81dbd65eb15734b6d50dc6ac86f14f928462be0a5df6bda17761e909071ede5d" "checksum derive_builder 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c998e6ab02a828dd9735c18f154e14100e674ed08cb4e1938f0e4177543f439" "checksum derive_builder_core 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "735e24ee9e5fa8e16b86da5007856e97d592e11867e45d76e0c0d0a164a0b757" +"checksum digest 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "05f47366984d3ad862010e22c7ce81a7dbcaebbdfb37241a620f8b6596ee135c" "checksum dirs 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "88972de891f6118092b643d85a0b28e0678e0f948d7f879aa32f2d5aafe97d2a" "checksum duct 0.11.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f87f5af80601599209bff21146e4113e8c54471151049deebc37e75b78e42729" "checksum env_logger 0.5.13 (registry+https://github.com/rust-lang/crates.io-index)" = "15b0a4d2e39f8420210be8b27eeda28029729e2fd4291019455016c348240c38" @@ -2614,6 +2685,7 @@ dependencies = [ "checksum futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)" = "49e7653e374fe0d0c12de4250f0bdb60680b8c80eed558c5c7538eec9c89e21b" "checksum futures-cpupool 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "ab90cde24b3319636588d0c35fe03b1333857621051837ed769faefb4c2162e4" "checksum gcc 0.3.55 (registry+https://github.com/rust-lang/crates.io-index)" = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2" +"checksum generic-array 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3c0f28c2f5bfb5960175af447a2da7c18900693738343dc896ffbcabd9839592" "checksum globset 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "90d069fe6beb9be359ef505650b3f73228c5591a3c4b1f32be2f4f44459ffa3a" "checksum globset 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4743617a7464bbda3c8aec8558ff2f9429047e025771037df561d383337ff865" "checksum hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "805026a5d0141ffc30abb3be3173848ad46a1b1664fe632428479619a3644d77" @@ -2626,6 +2698,7 @@ dependencies = [ "checksum ioctl-sys 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5e2c4b26352496eaaa8ca7cfa9bd99e93419d3f7983dc6e99c2a35fe9e33504a" "checksum iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dbe6e417e7d0975db6512b90796e8ce223145ac4e33c377e4a42882a0e88bb08" "checksum ipnetwork 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1d1d8b990621b5b0806fac3dbf71d1833a4c0a9e25702d10bd8b2c629c7ae01c" +"checksum ipnetwork 0.14.0 (git+https://github.com/mullvad/ipnetwork?branch=fix-deserialization)" = "<none>" "checksum ipnetwork 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b3d862c86f7867f19b693ec86765e0252d82e53d4240b9b629815675a0714ad1" "checksum iproute2 0.0.2 (git+https://github.com/mullvad/netlink?branch=ignore-hw-address)" = "<none>" "checksum itoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "1306f3464951f30e30d12373d31c79fbd52d236e5e896fd92f96ec7babbbe60b" @@ -2745,6 +2818,7 @@ dependencies = [ "checksum socket2 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "c4d11a52082057d87cb5caa31ad812f4504b97ab44732cd8359df2e9ff9f48e7" "checksum stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dba1a27d3efae4351c8051072d619e3ade2820635c3958d826bfea39d59b54c8" "checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550" +"checksum subtle 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "702662512f3ddeb74a64ce2fbbf3707ee1b6bb663d28bb054e0779bbc720d926" "checksum syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d3b891b9015c88c576343b9b3e41c2c11a51c219ef067b264bd9c8aa9b441dad" "checksum syn 0.15.26 (registry+https://github.com/rust-lang/crates.io-index)" = "f92e629aa1d9c827b2bb8297046c1ccffc57c99b947a680d3ccff1f136a3bee9" "checksum synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6" @@ -2780,6 +2854,7 @@ dependencies = [ "checksum toml 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "758664fc71a3a69038656bee8b6be6477d2a6c315a6b81f7081f591bffa4111f" "checksum try-lock 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee2aa4715743892880f70885373966c83d73ef1b0838a664ef0c76fffd35e7c2" "checksum tun 0.4.2 (git+https://github.com/pinkisemils/rust-tun?branch=add-raw-fd-traits)" = "<none>" +"checksum typenum 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "612d636f949607bdf9b123b4a6f6d966dedf3ff669f7f045890d3a4a73948169" "checksum ucd-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "535c204ee4d8434478593480b8f86ab45ec9aae0e83c568ca81abf0fd0e88f86" "checksum unicase 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9d3218ea14b4edcaccfa0df0a64a3792a2c32cc706f1b336e48867f9d3147f90" "checksum unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526" @@ -2808,3 +2883,4 @@ dependencies = [ "checksum winreg 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a27a759395c1195c4cc5cda607ef6f8f6498f64e78f7900f5de0a127a424704a" "checksum winres 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "aea48d0b4b15ba195fc7250fdfb27736ce3bc327535db1c87a2fcb62371f9ecd" "checksum ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" +"checksum x25519-dalek 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "58e75de7a035f694df91838afa87faa8278bc484fa8d7dc34b5a24535cc2bb41" diff --git a/mullvad-cli/src/cmds/tunnel.rs b/mullvad-cli/src/cmds/tunnel.rs index bbd1a0e2a1..0c05c1946d 100644 --- a/mullvad-cli/src/cmds/tunnel.rs +++ b/mullvad-cli/src/cmds/tunnel.rs @@ -38,7 +38,8 @@ fn create_wireguard_subcommand() -> clap::App<'static, 'static> { let app = clap::SubCommand::with_name("wireguard") .about("Manage options for Wireguard tunnels") .setting(clap::AppSettings::SubcommandRequired) - .subcommand(create_wireguard_mtu_subcommand()); + .subcommand(create_wireguard_mtu_subcommand()) + .subcommand(create_wireguard_keys_subcommand()); if cfg!(target_os = "linux") { app.subcommand(create_wireguard_fwmark_subcommand()) } else { @@ -57,6 +58,14 @@ fn create_wireguard_mtu_subcommand() -> clap::App<'static, 'static> { ) } +fn create_wireguard_keys_subcommand() -> clap::App<'static, 'static> { + clap::SubCommand::with_name("key") + .about("Manage your wireguard keys") + .setting(clap::AppSettings::SubcommandRequired) + .subcommand(clap::SubCommand::with_name("check")) + .subcommand(clap::SubCommand::with_name("generate")) +} + fn create_wireguard_fwmark_subcommand() -> clap::App<'static, 'static> { clap::SubCommand::with_name("fwmark") .about("Configure the firewall mark used to direct traffic through Wireguard tunnel") @@ -227,6 +236,12 @@ impl Tunnel { _ => unreachable!("unhandled command"), }, + ("key", Some(matches)) => match matches.subcommand() { + ("check", _) => Self::process_wireguard_key_check(), + ("generate", _) => Self::process_wireguard_key_generate(), + _ => unreachable!("unhandled command"), + }, + #[cfg(target_os = "linux")] ("fwmark", Some(matches)) => match matches.subcommand() { ("get", _) => Self::process_wireguard_fwmark_get(), @@ -265,6 +280,28 @@ impl Tunnel { Ok(()) } + fn process_wireguard_key_check() -> Result<()> { + let mut rpc = new_rpc_client()?; + match rpc.get_wireguard_key()? { + Some(key) => { + println!("Current key: {}", key); + } + None => { + println!("No key is set"); + return Ok(()); + } + }; + + let is_valid = rpc.verify_wireguard_key()?; + println!("Key is valid for use with current account: {}", is_valid); + Ok(()) + } + + fn process_wireguard_key_generate() -> Result<()> { + let mut rpc = new_rpc_client()?; + rpc.generate_wireguard_key().map_err(|e| e.into()) + } + #[cfg(target_os = "linux")] fn process_wireguard_fwmark_get() -> Result<()> { let tunnel_options = Self::get_tunnel_options()?; diff --git a/mullvad-daemon/src/account_history.rs b/mullvad-daemon/src/account_history.rs index de57b60d3f..e79356d257 100644 --- a/mullvad-daemon/src/account_history.rs +++ b/mullvad-daemon/src/account_history.rs @@ -1,22 +1,18 @@ -use mullvad_types::account::AccountToken; +use mullvad_types::{account::AccountToken, wireguard::WireguardData}; use std::{ - fs::File, - io, - path::{Path, PathBuf}, + collections::VecDeque, + fs, + io::{self, Seek, Write}, + path::Path, }; error_chain! { errors { - ReadError(path: PathBuf) { + ReadError { description("Unable to read account history file") - display("Unable to read account history from {}", path.display()) } - WriteError(path: PathBuf) { + WriteError { description("Unable to write account history file") - display("Unable to write account history to {}", path.display()) - } - ParseError { - description("Malformed account history") } } } @@ -24,84 +20,142 @@ error_chain! { static ACCOUNT_HISTORY_FILE: &str = "account-history.json"; static ACCOUNT_HISTORY_LIMIT: usize = 3; -#[derive(Debug, Clone, Deserialize, Serialize, Default)] +/// A trivial MRU cache of account data pub struct AccountHistory { - accounts: Vec<AccountToken>, - #[serde(skip)] - cache_path: PathBuf, + file: io::BufWriter<fs::File>, + accounts: VecDeque<AccountEntry>, } + impl AccountHistory { - /// Returns a new empty `AccountHistory` ready to load from, or save to, the given cache dir. - pub fn new(cache_dir: &Path) -> AccountHistory { - AccountHistory { - accounts: Vec::new(), - cache_path: cache_dir.join(ACCOUNT_HISTORY_FILE), + pub fn new(cache_dir: &Path) -> Result<AccountHistory> { + let mut options = fs::OpenOptions::new(); + #[cfg(unix)] + { + use std::os::unix::fs::OpenOptionsExt; + options.mode(0o600); } - } + #[cfg(windows)] + { + use std::os::windows::fs::OpenOptionsExt; + // a share mode of zero ensures exclusive access to the file to *this* process + options.share_mode(0); + } + let path = cache_dir.join(ACCOUNT_HISTORY_FILE); + log::info!("Opening account history file in {}", path.display()); + let mut reader = options + .write(true) + .read(true) + .create(true) + .open(path) + .map(io::BufReader::new) + .chain_err(|| ErrorKind::ReadError)?; - /// Loads account history from file. If no file is present this does nothing. - pub fn load(&mut self) -> Result<()> { - match File::open(&self.cache_path).map(io::BufReader::new) { - Ok(mut file) => { - log::info!( - "Loading account history from {}", - &self.cache_path.display() - ); - self.accounts = Self::parse(&mut file)?.accounts; - Ok(()) + let accounts: VecDeque<AccountEntry> = match serde_json::from_reader(&mut reader) { + Err(e) => { + log::error!("Failed to read account history - {}", e); + Self::try_old_format(&mut reader)? + .into_iter() + .map(|account| AccountEntry { + account, + wireguard: None, + }) + .collect() } - Err(ref e) if e.kind() == io::ErrorKind::NotFound => { - log::info!("No account history file at {}", &self.cache_path.display()); - Ok(()) - } - Err(e) => Err(e).chain_err(|| ErrorKind::ReadError(self.cache_path.clone())), - } + Ok(accounts) => accounts, + }; + let file = io::BufWriter::new(reader.into_inner()); + Ok(AccountHistory { file, accounts }) + } + + fn try_old_format(reader: &mut io::BufReader<fs::File>) -> Result<Vec<AccountToken>> { + reader + .seek(io::SeekFrom::Start(0)) + .chain_err(|| ErrorKind::ReadError)?; + Ok(serde_json::from_reader(reader).unwrap_or(vec![])) } - fn parse(file: &mut impl io::Read) -> Result<AccountHistory> { - serde_json::from_reader(file).chain_err(|| ErrorKind::ParseError) + /// Gets account data for a certain account id and bumps it's entry to the top of the list if + /// it isn't there already. Returns None if the account entry is not available. + pub fn get(&mut self, account: &AccountToken) -> Result<Option<AccountEntry>> { + let (idx, entry) = match self + .accounts + .iter() + .enumerate() + .find(|(_idx, entry)| &entry.account == account) + { + Some((idx, entry)) => (idx, entry.clone()), + None => { + return Ok(None); + } + }; + // this account is already on top + if idx == 0 { + return Ok(Some(entry)); + } + self.insert(entry.clone())?; + Ok(Some(entry)) } - pub fn get_accounts(&self) -> &[AccountToken] { - &self.accounts + /// Bumps history of an account token. If the account token is not in history, it will be + /// added. + pub fn bump_history(&mut self, account: &AccountToken) -> Result<()> { + if let None = self.get(account)? { + let new_entry = AccountEntry { + account: account.to_string(), + wireguard: None, + }; + self.insert(new_entry)?; + } + Ok(()) } - /// Add account token to the account history removing duplicate entries - pub fn add_account_token(&mut self, account_token: AccountToken) -> Result<()> { + /// Always inserts a new entry at the start of the list + pub fn insert(&mut self, new_entry: AccountEntry) -> Result<()> { self.accounts - .retain(|existing_token| existing_token != &account_token); - self.accounts.push(account_token); + .retain(|entry| entry.account != new_entry.account); - let num_accounts = self.accounts.len(); - if num_accounts > ACCOUNT_HISTORY_LIMIT { - self.accounts = self - .accounts - .split_off(num_accounts - ACCOUNT_HISTORY_LIMIT); + self.accounts.push_front(new_entry); + if self.accounts.len() > ACCOUNT_HISTORY_LIMIT { + let _ = self.accounts.pop_back(); } - - self.save() + self.save_to_disk() } - /// Remove account token from the account history - pub fn remove_account_token(&mut self, account_token: &AccountToken) -> Result<()> { + /// Retrieve account history. + pub fn get_account_history(&self) -> Vec<AccountToken> { self.accounts - .retain(|existing_token| existing_token != account_token); - self.save() + .iter() + .map(|entry| entry.account.clone()) + .collect() } - /// Serializes the account history and saves it to the file it was loaded from. - fn save(&self) -> Result<()> { - log::debug!("Writing account history to {}", self.cache_path.display()); - let mut file = File::create(&self.cache_path) - .map(io::BufWriter::new) - .chain_err(|| ErrorKind::WriteError(self.cache_path.clone()))?; - - serde_json::to_writer_pretty(&mut file, self) - .chain_err(|| ErrorKind::WriteError(self.cache_path.clone()))?; + /// Remove account data + pub fn remove_account(&mut self, account: &str) -> Result<()> { + self.accounts.retain(|entry| entry.account != account); + self.save_to_disk() + } - file.get_mut() + fn save_to_disk(&mut self) -> Result<()> { + self.file + .get_mut() + .set_len(0) + .chain_err(|| ErrorKind::WriteError)?; + self.file + .seek(io::SeekFrom::Start(0)) + .chain_err(|| ErrorKind::WriteError)?; + serde_json::to_writer_pretty(&mut self.file, &self.accounts) + .chain_err(|| ErrorKind::WriteError)?; + self.file.flush().chain_err(|| ErrorKind::WriteError)?; + self.file + .get_mut() .sync_all() - .chain_err(|| ErrorKind::WriteError(self.cache_path.clone())) + .chain_err(|| ErrorKind::WriteError) } } + +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct AccountEntry { + pub account: AccountToken, + pub wireguard: Option<WireguardData>, +} diff --git a/mullvad-daemon/src/lib.rs b/mullvad-daemon/src/lib.rs index 0faf2b81ff..07d7cd3e4f 100644 --- a/mullvad-daemon/src/lib.rs +++ b/mullvad-daemon/src/lib.rs @@ -26,7 +26,7 @@ use futures::{ Future, Sink, }; use log::{debug, error, info, warn}; -use mullvad_rpc::{AccountsProxy, AppVersionProxy, HttpHandle}; +use mullvad_rpc::{AccountsProxy, AppVersionProxy, HttpHandle, WireguardKeyProxy}; use mullvad_types::{ account::{AccountData, AccountToken}, endpoint::MullvadEndpoint, @@ -46,7 +46,7 @@ use talpid_core::{ tunnel_state_machine::{self, TunnelCommand, TunnelParametersGenerator}, }; use talpid_types::{ - net::{openvpn, TransportProtocol, TunnelParameters}, + net::{openvpn, wireguard, TransportProtocol, TunnelParameters}, tunnel::{BlockReason, TunnelStateTransition}, }; @@ -66,9 +66,13 @@ error_chain! { description("Error in the management interface") display("Management interface error: {}", msg) } + NoKeyAvailable { + description("No wireguard private key available") + } } links { TunnelError(tunnel_state_machine::Error, tunnel_state_machine::ErrorKind); + AccountHistory(account_history::Error, account_history::ErrorKind); } } @@ -156,6 +160,8 @@ pub struct Daemon { #[cfg(unix)] management_interface_socket_path: String, settings: Settings, + account_history: account_history::AccountHistory, + wg_key_proxy: WireguardKeyProxy<HttpHandle>, accounts_proxy: AccountsProxy<HttpHandle>, version_proxy: AppVersionProxy<HttpHandle>, https_handle: mullvad_rpc::rest::RequestSender, @@ -195,6 +201,9 @@ impl Daemon { let relay_selector = relays::RelaySelector::new(rpc_handle.clone(), &resource_dir, &cache_dir); let settings = Settings::load().chain_err(|| "Unable to read settings")?; + let account_history = account_history::AccountHistory::new(&cache_dir) + .chain_err(|| "Unable to read wireguard key cache")?; + let (tx, rx) = mpsc::channel(); let tunnel_parameters_generator = MullvadTunnelParametersGenerator { tx: tx.clone() }; @@ -209,7 +218,7 @@ impl Daemon { )?; let target_state = TargetState::Unsecured; - let management_interface_result = Self::start_management_interface(tx.clone(), cache_dir)?; + let management_interface_result = Self::start_management_interface(tx.clone())?; // Attempt to download a fresh relay list relay_selector.update(); @@ -226,6 +235,8 @@ impl Daemon { #[cfg(unix)] management_interface_socket_path: management_interface_result.1, settings, + account_history, + wg_key_proxy: WireguardKeyProxy::new(rpc_handle.clone()), accounts_proxy: AccountsProxy::new(rpc_handle.clone()), version_proxy: AppVersionProxy::new(rpc_handle), https_handle, @@ -240,10 +251,9 @@ impl Daemon { // Returns a handle that allows notifying all subscribers on events. fn start_management_interface( event_tx: mpsc::Sender<DaemonEvent>, - cache_dir: PathBuf, ) -> Result<(management_interface::EventBroadcaster, String)> { let multiplex_event_tx = IntoSender::from(event_tx.clone()); - let server = Self::start_management_interface_server(multiplex_event_tx, cache_dir)?; + let server = Self::start_management_interface_server(multiplex_event_tx)?; let event_broadcaster = server.event_broadcaster(); let socket_path = server.socket_path().to_owned(); Self::spawn_management_interface_wait_thread(server, event_tx); @@ -252,9 +262,8 @@ impl Daemon { fn start_management_interface_server( event_tx: IntoSender<ManagementCommand, DaemonEvent>, - cache_dir: PathBuf, ) -> Result<ManagementInterfaceServer> { - let server = ManagementInterfaceServer::start(event_tx, cache_dir) + let server = ManagementInterfaceServer::start(event_tx) .chain_err(|| ErrorKind::ManagementInterfaceError("Failed to start server"))?; info!( "Mullvad management interface listening on {}", @@ -370,7 +379,7 @@ impl Daemon { } fn create_tunnel_parameters( - &self, + &mut self, endpoint: MullvadEndpoint, account_token: String, ) -> Result<TunnelParameters> { @@ -382,10 +391,30 @@ impl Daemon { generic_options: tunnel_options.generic, } .into()), - MullvadEndpoint::Wireguard { - peer: _, - gateway: _, - } => Err(ErrorKind::UnsupportedTunnel.into()), + MullvadEndpoint::Wireguard { peer, gateway } => { + let wg_data = self + .account_history + .get(&account_token)? + .and_then(|entry| entry.wireguard) + .ok_or(ErrorKind::NoKeyAvailable)?; + let tunnel = wireguard::TunnelConfig { + private_key: wg_data.private_key, + addresses: vec![ + wg_data.addresses.ipv4_address.ip().into(), + wg_data.addresses.ipv6_address.ip().into(), + ], + }; + Ok(wireguard::TunnelParameters { + connection: wireguard::ConnectionConfig { + tunnel, + peer, + gateway, + }, + options: tunnel_options.wireguard, + generic_options: tunnel_options.generic, + } + .into()) + } } } @@ -423,6 +452,10 @@ impl Daemon { GetRelayLocations(tx) => self.on_get_relay_locations(tx), UpdateRelayLocations => self.on_update_relay_locations(), SetAccount(tx, account_token) => self.on_set_account(tx, account_token), + GetAccountHistory(tx) => self.on_get_account_history(tx), + RemoveAccountFromHistory(tx, account_token) => { + self.on_remove_account_from_history(tx, account_token) + } UpdateRelaySettings(tx, update) => self.on_update_relay_settings(tx, update), SetAllowLan(tx, allow_lan) => self.on_set_allow_lan(tx, allow_lan), SetBlockWhenDisconnected(tx, block_when_disconnected) => { @@ -436,6 +469,9 @@ impl Daemon { SetWireguardFwmark(tx, fwmark) => self.on_set_wireguard_fwmark(tx, fwmark), SetWireguardMtu(tx, mtu) => self.on_set_wireguard_mtu(tx, mtu), GetSettings(tx) => self.on_get_settings(tx), + GenerateWireguardKey(tx) => self.on_generate_wireguard_key(tx), + GetWireguardKey(tx) => self.on_get_wireguard_key(tx), + VerifyWireguardKey(tx) => self.on_verify_wireguard_key(tx), GetVersionInfo(tx) => self.on_get_version_info(tx), GetCurrentVersion(tx) => self.on_get_current_version(tx), Shutdown => self.handle_trigger_shutdown_event(), @@ -537,8 +573,7 @@ impl Daemon { } fn on_set_account(&mut self, tx: oneshot::Sender<()>, account_token: Option<String>) { - let account_token_cleared = account_token.is_none(); - let save_result = self.settings.set_account_token(account_token); + let save_result = self.settings.set_account_token(account_token.clone()); match save_result.chain_err(|| "Unable to save settings") { Ok(account_changed) => { @@ -546,12 +581,18 @@ impl Daemon { if account_changed { self.management_interface_broadcaster .notify_settings(&self.settings); - if account_token_cleared { - info!("Disconnecting because account token was cleared"); - self.set_target_state(TargetState::Unsecured); - } else { - info!("Initiating tunnel restart because the account token changed"); - self.reconnect_tunnel(); + match account_token { + Some(token) => { + if let Err(e) = self.account_history.bump_history(&token) { + log::error!("Failed to bump account history: {}", e); + } + info!("Initiating tunnel restart because the account token changed"); + self.reconnect_tunnel(); + } + None => { + info!("Disconnecting because account token was cleared"); + self.set_target_state(TargetState::Unsecured); + } } } } @@ -559,6 +600,24 @@ impl Daemon { } } + fn on_get_account_history(&mut self, tx: oneshot::Sender<Vec<AccountToken>>) { + Self::oneshot_send( + tx, + self.account_history.get_account_history(), + "get_account_history response", + ); + } + + fn on_remove_account_from_history( + &mut self, + tx: oneshot::Sender<()>, + account_token: AccountToken, + ) { + if let Ok(_) = self.account_history.remove_account(&account_token) { + Self::oneshot_send(tx, (), "remove_account_from_history response"); + } + } + fn on_get_version_info( &mut self, tx: oneshot::Sender<BoxFuture<AppVersionInfo, mullvad_rpc::Error>>, @@ -762,6 +821,117 @@ impl Daemon { } } + fn on_generate_wireguard_key( + &mut self, + tx: oneshot::Sender<::std::result::Result<(), mullvad_rpc::Error>>, + ) { + let mut result = || -> ::std::result::Result<(), String> { + let account_token = self + .settings + .get_account_token() + .ok_or("No account token set".to_string())?; + + let mut account_entry = self + .account_history + .get(&account_token) + .map_err(|e| format!("Failed to read account entry from history: {}", e)) + .map(|data| { + data.unwrap_or_else(|| { + log::error!("Account token set in settings but not in account history"); + account_history::AccountEntry { + account: account_token.clone(), + wireguard: None, + } + }) + })?; + + let private_key = wireguard::PrivateKey::new_from_random() + .map_err(|e| format!("Failed to generate new key - {}", e))?; + + let fut = self + .wg_key_proxy + .push_wg_key(account_token, private_key.public_key()); + + let mut core = tokio_core::reactor::Core::new() + .map_err(|e| format!("Failed to spawn future for pushing wg key - {}", e))?; + + let addresses = core + .run(fut) + .map_err(|e| format!("Failed to push new wireguard key: {}", e))?; + + account_entry.wireguard = Some(mullvad_types::wireguard::WireguardData { + private_key, + addresses, + }); + + self.account_history + .insert(account_entry) + .map_err(|e| format!("Failed to add new wireguard key to account data: {}", e)) + }; + match result() { + Ok(()) => { + Self::oneshot_send(tx, Ok(()), "generate_wireguard_key response"); + } + Err(e) => { + log::error!("Failed to generate new wireguard key - {}", e); + } + } + } + + fn on_get_wireguard_key(&mut self, tx: oneshot::Sender<Option<wireguard::PublicKey>>) { + let key = self + .settings + .get_account_token() + .and_then(|account| self.account_history.get(&account).ok()?) + .and_then(|account_entry| { + account_entry + .wireguard + .map(|wg| wg.private_key.public_key()) + }); + + Self::oneshot_send(tx, key, "get_wireguard_key response"); + } + + fn on_verify_wireguard_key(&mut self, tx: oneshot::Sender<bool>) { + use futures::future::Executor; + let account = match self.settings.get_account_token() { + Some(account) => account, + None => { + Self::oneshot_send(tx, false, "verify_wireguard_key response"); + return; + } + }; + + let key = self + .account_history + .get(&account) + .map(|entry| entry.and_then(|e| e.wireguard.map(|wg| wg.private_key.public_key()))); + + let public_key = match key { + Ok(Some(public_key)) => public_key, + Ok(None) => { + Self::oneshot_send(tx, false, "verify_wireguard_key response"); + return; + } + Err(e) => { + log::error!("Failed to read key data: {}", e); + return; + } + }; + + let fut = self + .wg_key_proxy + .check_wg_key(account, public_key.clone()) + .map(|is_valid| { + Self::oneshot_send(tx, is_valid, "verify_wireguard_key response"); + () + }) + .map_err(|e| log::error!("Failed to verify wireguard key - {}", e)); + if let Err(e) = self.tokio_remote.execute(fut) { + log::error!("Failed to spawn a future to verify wireguard key: {:?}", e); + } + } + fn on_get_settings(&self, tx: oneshot::Sender<Settings>) { Self::oneshot_send(tx, self.settings.clone(), "get_settings response"); } diff --git a/mullvad-daemon/src/management_interface.rs b/mullvad-daemon/src/management_interface.rs index aeeda483a7..8b33e20171 100644 --- a/mullvad-daemon/src/management_interface.rs +++ b/mullvad-daemon/src/management_interface.rs @@ -1,4 +1,3 @@ -use crate::account_history::{AccountHistory, Error as AccountHistoryError}; use error_chain::ChainedError; use jsonrpc_core::{ futures::{ @@ -25,12 +24,14 @@ use mullvad_types::{ use serde; use std::{ collections::{hash_map::Entry, HashMap}, - path::PathBuf, sync::{Arc, Mutex, RwLock}, }; use talpid_core::mpsc::IntoSender; use talpid_ipc; -use talpid_types::{net::openvpn, tunnel::TunnelStateTransition}; +use talpid_types::{ + net::{openvpn, wireguard}, + tunnel::TunnelStateTransition, +}; use uuid; /// FIXME(linus): This is here just because the futures crate has deprecated it and jsonrpc_core @@ -133,6 +134,18 @@ build_rpc_trait! { #[rpc(meta, name = "get_settings")] fn get_settings(&self, Self::Metadata) -> BoxFuture<Settings, Error>; + /// Generates new wireguard key for current account + #[rpc(meta, name = "generate_wireguard_key")] + fn generate_wireguard_key(&self, Self::Metadata) -> BoxFuture<(), Error>; + + /// Retrieve a public key for current account if the account has one. + #[rpc(meta, name = "get_wireguard_key")] + fn get_wireguard_key(&self, Self::Metadata) -> BoxFuture<Option<wireguard::PublicKey>, Error>; + + /// Verify if current wireguard key is still valid + #[rpc(meta, name = "verify_wireguard_key")] + fn verify_wireguard_key(&self, Self::Metadata) -> BoxFuture<bool, Error>; + /// Retreive version of the app #[rpc(meta, name = "get_current_version")] fn get_current_version(&self, Self::Metadata) -> BoxFuture<String, Error>; @@ -182,6 +195,10 @@ pub enum ManagementCommand { OneshotSender<BoxFuture<AccountData, mullvad_rpc::Error>>, AccountToken, ), + /// Request account history + GetAccountHistory(OneshotSender<Vec<AccountToken>>), + /// Request account history + RemoveAccountFromHistory(OneshotSender<()>, AccountToken), /// Get the list of countries and cities where there are relays. GetRelayLocations(OneshotSender<RelayList>), /// Trigger an asynchronous relay list update. This returns before the relay list is actually @@ -213,6 +230,12 @@ pub enum ManagementCommand { SetWireguardMtu(OneshotSender<()>, Option<u16>), /// Get the daemon settings GetSettings(OneshotSender<Settings>), + /// Generate new wireguard key + GenerateWireguardKey(OneshotSender<Result<(), mullvad_rpc::Error>>), + /// Return a public key of the currently set wireguard private key, if there is one + GetWireguardKey(OneshotSender<Option<wireguard::PublicKey>>), + /// Verify if the currently set wireguard key is valid. + VerifyWireguardKey(OneshotSender<bool>), /// Get information about the currently running and latest app versions GetVersionInfo(OneshotSender<BoxFuture<version::AppVersionInfo, mullvad_rpc::Error>>), /// Get current version of the app @@ -233,14 +256,11 @@ pub struct ManagementInterfaceServer { } impl ManagementInterfaceServer { - pub fn start<T>( - tunnel_tx: IntoSender<ManagementCommand, T>, - cache_dir: PathBuf, - ) -> talpid_ipc::Result<Self> + pub fn start<T>(tunnel_tx: IntoSender<ManagementCommand, T>) -> talpid_ipc::Result<Self> where T: From<ManagementCommand> + 'static + Send, { - let rpc = ManagementInterface::new(tunnel_tx, cache_dir); + let rpc = ManagementInterface::new(tunnel_tx); let subscriptions = rpc.subscriptions.clone(); let mut io = PubSubHandler::default(); @@ -309,15 +329,13 @@ impl EventBroadcaster { struct ManagementInterface<T: From<ManagementCommand> + 'static + Send> { subscriptions: Arc<ActiveSubscriptions>, tx: Mutex<IntoSender<ManagementCommand, T>>, - cache_dir: PathBuf, } impl<T: From<ManagementCommand> + 'static + Send> ManagementInterface<T> { - pub fn new(tx: IntoSender<ManagementCommand, T>, cache_dir: PathBuf) -> Self { + pub fn new(tx: IntoSender<ManagementCommand, T>) -> Self { ManagementInterface { subscriptions: Default::default(), tx: Mutex::new(tx), - cache_dir, } } @@ -381,12 +399,6 @@ impl<T: From<ManagementCommand> + 'static + Send> ManagementInterface<T> { _ => Error::internal_error(), } } - - fn load_history(&self) -> Result<AccountHistory, AccountHistoryError> { - let mut account_history = AccountHistory::new(&self.cache_dir); - account_history.load()?; - Ok(account_history) - } } impl<T: From<ManagementCommand> + 'static + Send> ManagementInterfaceApi @@ -440,18 +452,6 @@ impl<T: From<ManagementCommand> + 'static + Send> ManagementInterfaceApi let future = self .send_command_to_daemon(ManagementCommand::SetAccount(tx, account_token.clone())) .and_then(|_| rx.map_err(|_| Error::internal_error())); - - if let Some(new_account_token) = account_token { - if let Err(e) = self.load_history().and_then(|mut account_history| { - account_history.add_account_token(new_account_token) - }) { - log::error!( - "Unable to add an account into the account history: {}", - e.display_chain() - ); - } - } - Box::new(future) } @@ -558,14 +558,11 @@ impl<T: From<ManagementCommand> + 'static + Send> ManagementInterfaceApi fn get_account_history(&self, _: Self::Metadata) -> BoxFuture<Vec<AccountToken>, Error> { log::debug!("get_account_history"); - Box::new(future::result( - self.load_history() - .map(|history| history.get_accounts().to_vec()) - .map_err(|error| { - log::error!("Unable to get account history: {}", error.display_chain()); - Error::internal_error() - }), - )) + let (tx, rx) = sync::oneshot::channel(); + let future = self + .send_command_to_daemon(ManagementCommand::GetAccountHistory(tx)) + .and_then(|_| rx.map_err(|_| Error::internal_error())); + Box::new(future) } fn remove_account_from_history( @@ -574,17 +571,14 @@ impl<T: From<ManagementCommand> + 'static + Send> ManagementInterfaceApi account_token: AccountToken, ) -> BoxFuture<(), Error> { log::debug!("remove_account_from_history"); - Box::new(future::result( - self.load_history() - .and_then(|mut history| history.remove_account_token(&account_token)) - .map_err(|error| { - log::error!( - "Unable to remove account from history: {}", - error.display_chain() - ); - Error::internal_error() - }), - )) + let (tx, rx) = sync::oneshot::channel(); + let future = self + .send_command_to_daemon(ManagementCommand::RemoveAccountFromHistory( + tx, + account_token, + )) + .and_then(|_| rx.map_err(|_| Error::internal_error())); + Box::new(future) } fn set_openvpn_mssfix(&self, _: Self::Metadata, mssfix: Option<u16>) -> BoxFuture<(), Error> { @@ -666,6 +660,39 @@ impl<T: From<ManagementCommand> + 'static + Send> ManagementInterfaceApi Box::new(future) } + fn generate_wireguard_key(&self, _: Self::Metadata) -> BoxFuture<(), Error> { + log::debug!("generate_wireguard_key"); + let (tx, rx) = sync::oneshot::channel(); + let future = self + .send_command_to_daemon(ManagementCommand::GenerateWireguardKey(tx)) + .and_then(|_| { + rx.map_err(|_| Error::internal_error()) + .and_then(|res| future::result(res.map_err(|_| Error::internal_error()))) + }); + Box::new(future) + } + + fn get_wireguard_key( + &self, + _: Self::Metadata, + ) -> BoxFuture<Option<wireguard::PublicKey>, Error> { + log::debug!("get_wireguard_key"); + let (tx, rx) = sync::oneshot::channel(); + let future = self + .send_command_to_daemon(ManagementCommand::GetWireguardKey(tx)) + .and_then(|_| rx.map_err(|_| Error::internal_error())); + Box::new(future) + } + + fn verify_wireguard_key(&self, _: Self::Metadata) -> BoxFuture<bool, Error> { + log::debug!("verify_wireguard_key"); + let (tx, rx) = sync::oneshot::channel(); + let future = self + .send_command_to_daemon(ManagementCommand::VerifyWireguardKey(tx)) + .and_then(|_| rx.map_err(|_| Error::internal_error())); + Box::new(future) + } + fn get_current_version(&self, _: Self::Metadata) -> BoxFuture<String, Error> { log::debug!("get_current_version"); let (tx, rx) = sync::oneshot::channel(); diff --git a/mullvad-ipc-client/src/lib.rs b/mullvad-ipc-client/src/lib.rs index b42dfb55fb..e207ad99fa 100644 --- a/mullvad-ipc-client/src/lib.rs +++ b/mullvad-ipc-client/src/lib.rs @@ -14,7 +14,10 @@ use mullvad_types::{ }; use serde::{Deserialize, Serialize}; use std::{path::Path, thread}; -use talpid_types::{net::openvpn, tunnel::TunnelStateTransition}; +use talpid_types::{ + net::{openvpn, wireguard}, + tunnel::TunnelStateTransition, +}; pub use jsonrpc_client_core::{Error as RpcError, ErrorKind as RpcErrorKind}; @@ -190,6 +193,18 @@ impl DaemonRpcClient { self.call("get_settings", &NO_ARGS) } + pub fn generate_wireguard_key(&mut self) -> Result<()> { + self.call("generate_wireguard_key", &NO_ARGS) + } + + pub fn get_wireguard_key(&mut self) -> Result<Option<wireguard::PublicKey>> { + self.call("get_wireguard_key", &NO_ARGS) + } + + pub fn verify_wireguard_key(&mut self) -> Result<bool> { + self.call("verify_wireguard_key", &NO_ARGS) + } + pub fn get_version_info(&mut self) -> Result<AppVersionInfo> { self.call("get_version_info", &NO_ARGS) } diff --git a/mullvad-rpc/Cargo.toml b/mullvad-rpc/Cargo.toml index ff9596d10f..2f21283c58 100644 --- a/mullvad-rpc/Cargo.toml +++ b/mullvad-rpc/Cargo.toml @@ -22,6 +22,7 @@ tokio-openssl = "0.2" log = "0.4" mullvad-types = { path = "../mullvad-types" } +talpid-types = { path = "../talpid-types" } [dev-dependencies] filetime = "0.1" diff --git a/mullvad-rpc/src/lib.rs b/mullvad-rpc/src/lib.rs index 93962dbdba..cde2de539c 100644 --- a/mullvad-rpc/src/lib.rs +++ b/mullvad-rpc/src/lib.rs @@ -21,6 +21,7 @@ use std::{ path::{Path, PathBuf}, time::Duration, }; +use talpid_types::net::wireguard; use tokio_core::reactor::Handle; pub use jsonrpc_client_core::{Error, ErrorKind}; @@ -129,3 +130,16 @@ jsonrpc_client!(pub struct AppVersionProxy { pub fn latest_app_version(&mut self) -> RpcRequest<version::LatestReleases>; pub fn is_app_version_supported(&mut self, version: &version::AppVersion) -> RpcRequest<bool>; }); + +jsonrpc_client!(pub struct WireguardKeyProxy { + pub fn push_wg_key( + &mut self, + account_token: AccountToken, + public_key: wireguard::PublicKey + ) -> RpcRequest<mullvad_types::wireguard::AssociatedAddresses>; + pub fn check_wg_key( + &mut self, + account_token: AccountToken, + public_key: wireguard::PublicKey + ) -> RpcRequest<bool>; +}); diff --git a/mullvad-types/Cargo.toml b/mullvad-types/Cargo.toml index 8239696b9b..27c59acc0f 100644 --- a/mullvad-types/Cargo.toml +++ b/mullvad-types/Cargo.toml @@ -11,6 +11,7 @@ chrono = { version = "0.4", features = ["serde"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" error-chain = "0.12" +ipnetwork = { git = "https://github.com/mullvad/ipnetwork", branch = "fix-deserialization" } log = "0.4" regex = "1" lazy_static = "1.1.0" diff --git a/mullvad-types/src/lib.rs b/mullvad-types/src/lib.rs index 11e6ac4005..b2659911e4 100644 --- a/mullvad-types/src/lib.rs +++ b/mullvad-types/src/lib.rs @@ -18,6 +18,7 @@ pub mod relay_list; pub mod settings; pub mod states; pub mod version; +pub mod wireguard; mod custom_tunnel; pub use crate::custom_tunnel::*; diff --git a/mullvad-types/src/wireguard.rs b/mullvad-types/src/wireguard.rs new file mode 100644 index 0000000000..5fc0846557 --- /dev/null +++ b/mullvad-types/src/wireguard.rs @@ -0,0 +1,17 @@ +use serde::{Deserialize, Serialize}; +use talpid_types::net::wireguard; + +/// Contains account specific wireguard data +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct WireguardData { + pub private_key: wireguard::PrivateKey, + pub addresses: AssociatedAddresses, +} + +/// Contains a pair of local link addresses that are paired with a specific wireguard +/// public/private keypair. +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct AssociatedAddresses { + pub ipv4_address: ipnetwork::Ipv4Network, + pub ipv6_address: ipnetwork::Ipv6Network, +} diff --git a/talpid-types/Cargo.toml b/talpid-types/Cargo.toml index 9ef4fa89b3..678a7832fc 100644 --- a/talpid-types/Cargo.toml +++ b/talpid-types/Cargo.toml @@ -12,3 +12,5 @@ ipnetwork = "0.14" base64 = "0.10" hex = "0.3" error-chain = "0.12" +x25519-dalek = { version = "0.4.5", features = [ "std", "u64_backend" ], default-features = false } +rand = "0.6" diff --git a/talpid-types/src/net/wireguard.rs b/talpid-types/src/net/wireguard.rs index d24ffee0e3..fe596fe475 100644 --- a/talpid-types/src/net/wireguard.rs +++ b/talpid-types/src/net/wireguard.rs @@ -6,6 +6,8 @@ use std::{ net::{IpAddr, SocketAddr}, }; +use rand::RngCore; + #[derive(Clone, Eq, PartialEq, Deserialize, Serialize, Debug)] /// Wireguard tunnel parameters @@ -67,10 +69,32 @@ impl PrivateKey { pub fn as_bytes(&self) -> &[u8; 32] { &self.0 } + + /// Normalizing a private key as per the specification - https://cr.yp.to/ecdh.html + fn normalize_key(bytes: &mut [u8; 32]) { + bytes[0] &= 248; + bytes[31] &= 127; + bytes[31] |= 64; + } + + pub fn new_from_random() -> Result<Self, rand::Error> { + let mut bytes = [0u8; 32]; + rand::rngs::OsRng::new()?.fill_bytes(&mut bytes); + Ok(Self::from(bytes)) + } + + /// Generate public key from private key + pub fn public_key(&self) -> PublicKey { + PublicKey::from(x25519_dalek::x25519( + self.0, + x25519_dalek::X25519_BASEPOINT_BYTES, + )) + } } impl From<[u8; 32]> for PrivateKey { - fn from(private_key: [u8; 32]) -> PrivateKey { + fn from(mut private_key: [u8; 32]) -> PrivateKey { + Self::normalize_key(&mut private_key); PrivateKey(private_key) } } |
