diff options
| author | David Lönnhager <david.l@mullvad.net> | 2021-03-25 13:44:07 +0100 |
|---|---|---|
| committer | David Lönnhager <david.l@mullvad.net> | 2021-03-25 13:44:07 +0100 |
| commit | 72dc4d1230c3b8f749cbd0f55ebbfdb27902d2c2 (patch) | |
| tree | 1e3632b999260dcb787d8a38b86f51ba25fc5d12 | |
| parent | ee76c83beed76c80814ddddd0e7503532efbbea2 (diff) | |
| parent | b5b92428107dd6b4be23d7d127fdfab16d00863a (diff) | |
| download | mullvadvpn-72dc4d1230c3b8f749cbd0f55ebbfdb27902d2c2.tar.xz mullvadvpn-72dc4d1230c3b8f749cbd0f55ebbfdb27902d2c2.zip | |
Merge branch 'update-key-rotation'
| -rw-r--r-- | CHANGELOG.md | 2 | ||||
| -rw-r--r-- | mullvad-cli/src/cmds/tunnel.rs | 32 | ||||
| -rw-r--r-- | mullvad-daemon/src/lib.rs | 25 | ||||
| -rw-r--r-- | mullvad-daemon/src/management_interface.rs | 26 | ||||
| -rw-r--r-- | mullvad-daemon/src/settings.rs | 7 | ||||
| -rw-r--r-- | mullvad-daemon/src/wireguard.rs | 25 | ||||
| -rw-r--r-- | mullvad-management-interface/proto/management_interface.proto | 9 | ||||
| -rw-r--r-- | mullvad-management-interface/src/lib.rs | 2 | ||||
| -rw-r--r-- | mullvad-types/src/settings/migrations/mod.rs | 12 | ||||
| -rw-r--r-- | mullvad-types/src/settings/migrations/v1.rs | 4 | ||||
| -rw-r--r-- | mullvad-types/src/settings/migrations/v2.rs | 176 | ||||
| -rw-r--r-- | mullvad-types/src/settings/mod.rs | 22 | ||||
| -rw-r--r-- | mullvad-types/src/wireguard.rs | 97 |
13 files changed, 355 insertions, 84 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index bca02638aa..90cb3cc56d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,6 +49,8 @@ Line wrap the file at 100 chars. Th - Shrink account history capactity from 3 account entries to 1. - Allow whitespace in account token in CLI. - Read account token from standard input unless given as an argument in CLI. +- Make WireGuard automatic key rotation interval mandatory and between 1 and 7 days. +- Show default, minimum, and maximum key rotation intervals in CLI. #### Android - WireGuard key is now rotated sooner: every four days instead of seven. diff --git a/mullvad-cli/src/cmds/tunnel.rs b/mullvad-cli/src/cmds/tunnel.rs index 4af0864ddf..08306b70eb 100644 --- a/mullvad-cli/src/cmds/tunnel.rs +++ b/mullvad-cli/src/cmds/tunnel.rs @@ -1,6 +1,8 @@ use crate::{format::print_keygen_event, new_rpc_client, Command, Error, Result}; use clap::value_t; -use mullvad_management_interface::types::{Timestamp, TunnelOptions}; +use mullvad_management_interface::types::{self, Timestamp, TunnelOptions}; +use mullvad_types::wireguard::DEFAULT_ROTATION_INTERVAL; +use std::{convert::TryFrom, time::Duration}; pub struct Tunnel; @@ -61,7 +63,7 @@ fn create_wireguard_keys_subcommand() -> clap::App<'static, 'static> { fn create_wireguard_keys_rotation_interval_subcommand() -> clap::App<'static, 'static> { clap::SubCommand::with_name("rotation-interval") - .about("Manage automatic key rotation (specified in hours; 0 = disabled)") + .about("Manage automatic key rotation (given in hours)") .setting(clap::AppSettings::SubcommandRequiredElseHelp) .subcommand(clap::SubCommand::with_name("get")) .subcommand(clap::SubCommand::with_name("reset").about("Use the default rotation interval")) @@ -220,20 +222,27 @@ impl Tunnel { async fn process_wireguard_rotation_interval_get() -> Result<()> { let tunnel_options = Self::get_tunnel_options().await?; - match tunnel_options.wireguard.unwrap().automatic_rotation { + match tunnel_options.wireguard.unwrap().rotation_interval { Some(interval) => { - println!("Rotation interval: {} hour(s)", interval.interval); + let hours = duration_hours(&Duration::try_from(interval).unwrap()); + println!("Rotation interval: {} hour(s)", hours); } - None => println!("Rotation interval: default"), + None => println!( + "Rotation interval: default ({} hours)", + duration_hours(&DEFAULT_ROTATION_INTERVAL) + ), } Ok(()) } async fn process_wireguard_rotation_interval_set(matches: &clap::ArgMatches<'_>) -> Result<()> { let rotate_interval = - value_t!(matches.value_of("interval"), u32).unwrap_or_else(|e| e.exit()); + value_t!(matches.value_of("interval"), u64).unwrap_or_else(|e| e.exit()); let mut rpc = new_rpc_client().await?; - rpc.set_wireguard_rotation_interval(rotate_interval).await?; + rpc.set_wireguard_rotation_interval(types::Duration::from(Duration::from_secs( + 60 * 60 * rotate_interval, + ))) + .await?; println!("Set key rotation interval: {} hour(s)", rotate_interval); Ok(()) } @@ -241,7 +250,10 @@ impl Tunnel { async fn process_wireguard_rotation_interval_reset() -> Result<()> { let mut rpc = new_rpc_client().await?; rpc.reset_wireguard_rotation_interval(()).await?; - println!("Set key rotation interval: default"); + println!( + "Set key rotation interval: default ({} hours)", + duration_hours(&DEFAULT_ROTATION_INTERVAL) + ); Ok(()) } @@ -326,3 +338,7 @@ impl Tunnel { utc.with_timezone(&chrono::Local).to_string() } } + +fn duration_hours(duration: &Duration) -> u64 { + duration.as_secs() / 60 / 60 +} diff --git a/mullvad-daemon/src/lib.rs b/mullvad-daemon/src/lib.rs index dd297e9802..73f3daf170 100644 --- a/mullvad-daemon/src/lib.rs +++ b/mullvad-daemon/src/lib.rs @@ -38,7 +38,7 @@ use mullvad_types::{ settings::{DnsOptions, Settings}, states::{TargetState, TunnelState}, version::{AppVersion, AppVersionInfo}, - wireguard::KeygenEvent, + wireguard::{KeygenEvent, RotationInterval}, }; use settings::SettingsPersister; #[cfg(target_os = "android")] @@ -223,7 +223,7 @@ pub enum DaemonCommand { /// Set MTU for wireguard tunnels SetWireguardMtu(ResponseTx<(), settings::Error>, Option<u16>), /// Set automatic key rotation interval for wireguard tunnels - SetWireguardRotationInterval(ResponseTx<(), settings::Error>, Option<u32>), + SetWireguardRotationInterval(ResponseTx<(), settings::Error>, Option<RotationInterval>), /// Get the daemon settings GetSettings(oneshot::Sender<Settings>), /// Generate new wireguard key @@ -1231,15 +1231,12 @@ where async fn ensure_key_rotation(&mut self) { if let Some(token) = self.settings.get_account_token() { - let rotation_interval = self - .settings - .tunnel_options - .wireguard - .automatic_rotation - .map(|hours| Duration::from_secs(60u64 * 60u64 * hours as u64)); - self.wireguard_key_manager - .set_rotation_interval(&mut self.account_history, token, rotation_interval) + .set_rotation_interval( + &mut self.account_history, + token, + self.settings.tunnel_options.wireguard.rotation_interval, + ) .await; } } @@ -1882,7 +1879,7 @@ where async fn on_set_wireguard_rotation_interval( &mut self, tx: ResponseTx<(), settings::Error>, - interval: Option<u32>, + interval: Option<RotationInterval>, ) { let save_result = self.settings.set_wireguard_rotation_interval(interval); match save_result { @@ -1989,11 +1986,7 @@ where .set_rotation_interval( &mut self.account_history, account_token, - self.settings - .tunnel_options - .wireguard - .automatic_rotation - .map(|hours| Duration::from_secs(60u64 * 60u64 * hours as u64)), + self.settings.tunnel_options.wireguard.rotation_interval, ) .await; diff --git a/mullvad-daemon/src/management_interface.rs b/mullvad-daemon/src/management_interface.rs index 0cd1ddd1cf..dff318cece 100644 --- a/mullvad-daemon/src/management_interface.rs +++ b/mullvad-daemon/src/management_interface.rs @@ -19,12 +19,16 @@ use mullvad_types::{ relay_list::{Relay, RelayList, RelayListCountry}, settings::{Settings, TunnelOptions}, states::{TargetState, TunnelState}, - version, wireguard, ConnectionConfig, + version, + wireguard::{self, RotationInterval, RotationIntervalError}, + ConnectionConfig, }; use parking_lot::RwLock; use std::{ cmp, + convert::{TryFrom, TryInto}, sync::{mpsc, Arc}, + time::Duration, }; use talpid_types::{ net::{IpVersion, TransportProtocol, TunnelType}, @@ -595,8 +599,16 @@ impl ManagementService for ManagementServiceImpl { // WireGuard key management // - async fn set_wireguard_rotation_interval(&self, request: Request<u32>) -> ServiceResult<()> { - let interval = request.into_inner(); + async fn set_wireguard_rotation_interval( + &self, + request: Request<types::Duration>, + ) -> ServiceResult<()> { + let interval: RotationInterval = Duration::try_from(request.into_inner()) + .map_err(|_| Status::invalid_argument("unexpected negative rotation interval"))? + .try_into() + .map_err(|error: RotationIntervalError| { + Status::invalid_argument(error.display_chain()) + })?; log::debug!("set_wireguard_rotation_interval({:?})", interval); let (tx, rx) = oneshot::channel(); @@ -1218,18 +1230,16 @@ fn convert_bridge_state(state: &BridgeState) -> types::BridgeState { } fn convert_tunnel_options(options: &TunnelOptions) -> types::TunnelOptions { - use types::tunnel_options::wireguard_options::RotationInterval; - types::TunnelOptions { openvpn: Some(types::tunnel_options::OpenvpnOptions { mssfix: u32::from(options.openvpn.mssfix.unwrap_or_default()), }), wireguard: Some(types::tunnel_options::WireguardOptions { mtu: u32::from(options.wireguard.options.mtu.unwrap_or_default()), - automatic_rotation: options + rotation_interval: options .wireguard - .automatic_rotation - .map(|interval| RotationInterval { interval }), + .rotation_interval + .map(|ivl| types::Duration::from(Duration::from(ivl))), }), generic: Some(types::tunnel_options::GenericOptions { enable_ipv6: options.generic.enable_ipv6, diff --git a/mullvad-daemon/src/settings.rs b/mullvad-daemon/src/settings.rs index 0888121363..abdd06127c 100644 --- a/mullvad-daemon/src/settings.rs +++ b/mullvad-daemon/src/settings.rs @@ -2,6 +2,7 @@ use log::{debug, error, info}; use mullvad_types::{ relay_constraints::{BridgeSettings, BridgeState, RelaySettingsUpdate}, settings::{DnsOptions, Settings}, + wireguard::RotationInterval, }; use std::{ fs::{self, File}, @@ -224,11 +225,11 @@ impl SettingsPersister { pub fn set_wireguard_rotation_interval( &mut self, - automatic_rotation: Option<u32>, + interval: Option<RotationInterval>, ) -> Result<bool, Error> { let should_save = Self::update_field( - &mut self.settings.tunnel_options.wireguard.automatic_rotation, - automatic_rotation, + &mut self.settings.tunnel_options.wireguard.rotation_interval, + interval, ); self.update(should_save) } diff --git a/mullvad-daemon/src/wireguard.rs b/mullvad-daemon/src/wireguard.rs index b77a779b7d..b12c61ec56 100644 --- a/mullvad-daemon/src/wireguard.rs +++ b/mullvad-daemon/src/wireguard.rs @@ -16,13 +16,6 @@ pub use talpid_types::net::wireguard::{ }; use talpid_types::ErrorExt; -/// Default automatic key rotation interval -const DEFAULT_KEY_ROTATION: Duration = if cfg!(target_os = "android") { - Duration::from_secs(4 * 24 * 60 * 60) -} else { - Duration::from_secs(7 * 24 * 60 * 60) -}; - /// How long to wait before starting key rotation const ROTATION_START_DELAY: Duration = Duration::from_secs(60 * 3); @@ -50,7 +43,7 @@ pub struct KeyManager { current_job: Option<AbortHandle>, abort_scheduler_tx: Option<AbortHandle>, - auto_rotation_interval: Duration, + auto_rotation_interval: RotationInterval, } impl KeyManager { @@ -60,7 +53,7 @@ impl KeyManager { http_handle, current_job: None, abort_scheduler_tx: None, - auto_rotation_interval: Duration::new(0, 0), + auto_rotation_interval: RotationInterval::default(), } } @@ -88,15 +81,14 @@ impl KeyManager { } /// Update automatic key rotation interval - /// Passing `None` for the interval will use the default value. - /// A duration of `0` disables automatic key rotation. + /// Passing `None` for the interval will cause the default value to be used. pub async fn set_rotation_interval( &mut self, account_history: &mut AccountHistory, account_token: AccountToken, - auto_rotation_interval: Option<Duration>, + auto_rotation_interval: Option<RotationInterval>, ) { - self.auto_rotation_interval = auto_rotation_interval.unwrap_or(DEFAULT_KEY_ROTATION); + self.auto_rotation_interval = auto_rotation_interval.unwrap_or_default(); self.reset_rotation(account_history, account_token).await; } @@ -411,18 +403,13 @@ impl KeyManager { async fn run_automatic_rotation(&mut self, account_token: AccountToken, public_key: PublicKey) { self.stop_automatic_rotation(); - if self.auto_rotation_interval == Duration::new(0, 0) { - log::debug!("Not running key rotation because it's disabled"); - return; - } - log::debug!("Starting automatic key rotation job"); // Schedule cancellable series of repeating rotation tasks let fut = Self::create_automatic_rotation( self.daemon_tx.clone(), self.http_handle.clone(), public_key, - self.auto_rotation_interval.as_secs(), + self.auto_rotation_interval.as_duration().as_secs(), account_token, ); let (request, abort_handle) = abortable(Box::pin(fut)); diff --git a/mullvad-management-interface/proto/management_interface.proto b/mullvad-management-interface/proto/management_interface.proto index 83ea64a2ac..9404baaaf3 100644 --- a/mullvad-management-interface/proto/management_interface.proto +++ b/mullvad-management-interface/proto/management_interface.proto @@ -5,6 +5,7 @@ package mullvad_daemon.management_interface; import "google/protobuf/empty.proto"; import "google/protobuf/timestamp.proto"; import "google/protobuf/wrappers.proto"; +import "google/protobuf/duration.proto"; service ManagementService { // Control and get tunnel state @@ -52,7 +53,7 @@ service ManagementService { rpc SubmitVoucher(google.protobuf.StringValue) returns (VoucherSubmission) {} // WireGuard key management - rpc SetWireguardRotationInterval(google.protobuf.UInt32Value) returns (google.protobuf.Empty) {} + rpc SetWireguardRotationInterval(google.protobuf.Duration) returns (google.protobuf.Empty) {} rpc ResetWireguardRotationInterval(google.protobuf.Empty) returns (google.protobuf.Empty) {} rpc GenerateWireguardKey(google.protobuf.Empty) returns (KeygenEvent) {} rpc GetWireguardKey(google.protobuf.Empty) returns (PublicKey) {} @@ -360,13 +361,9 @@ message TunnelOptions { uint32 mssfix = 1; } message WireguardOptions { - message RotationInterval { - uint32 interval = 1; - } - // NOTE: optional uint32 mtu = 1; - RotationInterval automatic_rotation = 2; + google.protobuf.Duration rotation_interval = 2; } message GenericOptions { bool enable_ipv6 = 1; diff --git a/mullvad-management-interface/src/lib.rs b/mullvad-management-interface/src/lib.rs index c527764f19..8b16aff46e 100644 --- a/mullvad-management-interface/src/lib.rs +++ b/mullvad-management-interface/src/lib.rs @@ -1,7 +1,7 @@ pub mod types { tonic::include_proto!("mullvad_daemon.management_interface"); - pub use prost_types::Timestamp; + pub use prost_types::{Duration, Timestamp}; } use parity_tokio_ipc::Endpoint as IpcEndpoint; diff --git a/mullvad-types/src/settings/migrations/mod.rs b/mullvad-types/src/settings/migrations/mod.rs index a732848a7b..0d40e1e564 100644 --- a/mullvad-types/src/settings/migrations/mod.rs +++ b/mullvad-types/src/settings/migrations/mod.rs @@ -1,15 +1,17 @@ use super::{Error, Result}; use serde::{Deserialize, Deserializer, Serialize, Serializer}; mod v1; +mod v2; #[derive(Debug, PartialEq, PartialOrd, Clone, Copy)] #[repr(u32)] pub enum SettingsVersion { V2 = 2, + V3 = 3, } -pub const CURRENT_SETTINGS_VERSION: SettingsVersion = SettingsVersion::V2; +pub const CURRENT_SETTINGS_VERSION: SettingsVersion = SettingsVersion::V3; impl<'de> Deserialize<'de> for SettingsVersion { fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error> @@ -18,6 +20,7 @@ impl<'de> Deserialize<'de> for SettingsVersion { { match <u32>::deserialize(deserializer)? { v if v == SettingsVersion::V2 as u32 => Ok(SettingsVersion::V2), + v if v == SettingsVersion::V3 as u32 => Ok(SettingsVersion::V3), v => Err(serde::de::Error::custom(format!( "{} is not a valid SettingsVersion", v @@ -49,7 +52,10 @@ pub fn try_migrate_settings(mut settings_file: &[u8]) -> Result<crate::settings: return Err(Error::NoMatchingVersion); } - for migration in &[Box::new(v1::Migration)] { + let migrations: Vec<Box<dyn SettingsMigration>> = + vec![Box::new(v1::Migration), Box::new(v2::Migration)]; + + for migration in &migrations { if !migration.version_matches(&mut settings) { continue; } @@ -73,7 +79,7 @@ mod test { #[test] #[should_panic] fn test_deserialization_failure_version_too_big() { - let _version: SettingsVersion = serde_json::from_str("3").expect("Version too big"); + let _version: SettingsVersion = serde_json::from_str("4").expect("Version too big"); } #[test] diff --git a/mullvad-types/src/settings/migrations/v1.rs b/mullvad-types/src/settings/migrations/v1.rs index 033c25a580..15ce57f0f5 100644 --- a/mullvad-types/src/settings/migrations/v1.rs +++ b/mullvad-types/src/settings/migrations/v1.rs @@ -18,6 +18,8 @@ impl super::SettingsMigration for Migration { } fn migrate(&self, settings: &mut serde_json::Value) -> Result<()> { + log::info!("Migrating settings format to V2"); + let old_relay_settings: RelaySettings = serde_json::from_value(settings["relay_settings"].clone()) .map_err(Error::ParseError)?; @@ -126,7 +128,7 @@ mod test { "enable_ipv6": false } }, - "settings_version": 2 + "settings_version": 3 } "#; diff --git a/mullvad-types/src/settings/migrations/v2.rs b/mullvad-types/src/settings/migrations/v2.rs new file mode 100644 index 0000000000..393da1827c --- /dev/null +++ b/mullvad-types/src/settings/migrations/v2.rs @@ -0,0 +1,176 @@ +use super::{Error, Result, SettingsVersion}; +use crate::wireguard::{MAX_ROTATION_INTERVAL, MIN_ROTATION_INTERVAL}; +use std::time::Duration; + + +pub(super) struct Migration; + +impl super::SettingsMigration for Migration { + fn version_matches(&self, settings: &mut serde_json::Value) -> bool { + settings + .get("settings_version") + .map(|version| version == SettingsVersion::V2 as u64) + .unwrap_or(false) + } + + fn migrate(&self, settings: &mut serde_json::Value) -> Result<()> { + log::info!("Migrating settings format to V3"); + + // `show_beta_releases` used to be nullable + if settings + .get_mut("show_beta_releases") + .map(|val| val.is_null()) + .unwrap_or(false) + { + settings + .as_object_mut() + .ok_or(Error::NoMatchingVersion)? + .remove("show_beta_releases"); + } + + let automatic_rotation = || -> Option<u64> { + settings + .get("tunnel_options")? + .get("wireguard")? + .get("automatic_rotation") + .map(|ivl| ivl.as_u64())? + }(); + + if let Some(interval) = automatic_rotation { + let new_ivl = match Duration::from_secs(60 * 60 * interval) { + ivl if ivl < MIN_ROTATION_INTERVAL => { + log::warn!("Increasing key rotation interval since it is below minimum"); + MIN_ROTATION_INTERVAL + } + ivl if ivl > MAX_ROTATION_INTERVAL => { + log::warn!("Decreasing key rotation interval since it is above maximum"); + MAX_ROTATION_INTERVAL + } + ivl => ivl, + }; + + settings["tunnel_options"]["wireguard"]["rotation_interval"] = + serde_json::json!(new_ivl); + } + + settings["settings_version"] = serde_json::json!(SettingsVersion::V3); + + Ok(()) + } +} + +#[cfg(test)] +mod test { + use super::super::try_migrate_settings; + use serde_json; + + const V2_SETTINGS: &str = r#" +{ + "account_token": "1234", + "relay_settings": { + "normal": { + "location": { + "only": { + "country": "se" + } + }, + "tunnel": { + "only": { + "openvpn": { + "port": { + "only": 53 + }, + "protocol": { + "only": "udp" + } + } + } + } + } + }, + "bridge_settings": { + "normal": { + "location": "any" + } + }, + "bridge_state": "auto", + "allow_lan": true, + "block_when_disconnected": false, + "auto_connect": false, + "tunnel_options": { + "openvpn": { + "mssfix": null + }, + "wireguard": { + "mtu": null, + "automatic_rotation": 24 + }, + "generic": { + "enable_ipv6": false + } + } +} +"#; + + pub const NEW_SETTINGS: &str = r#" +{ + "account_token": "1234", + "relay_settings": { + "normal": { + "location": { + "only": { + "country": "se" + } + }, + "tunnel_protocol": "any", + "wireguard_constraints": { + "port": "any" + }, + "openvpn_constraints": { + "port": { + "only": 53 + }, + "protocol": { + "only": "udp" + } + } + } + }, + "bridge_settings": { + "normal": { + "location": "any" + } + }, + "bridge_state": "auto", + "allow_lan": true, + "block_when_disconnected": false, + "auto_connect": false, + "tunnel_options": { + "openvpn": { + "mssfix": null + }, + "wireguard": { + "mtu": null, + "rotation_interval": { + "secs": 86400, + "nanos": 0 + } + }, + "generic": { + "enable_ipv6": false + } + }, + "settings_version": 3 +} +"#; + + + #[test] + fn test_v2_migration() { + let migrated_settings = + try_migrate_settings(V2_SETTINGS.as_bytes()).expect("Migration failed"); + let new_settings = serde_json::from_str(NEW_SETTINGS).unwrap(); + + assert_eq!(&migrated_settings, &new_settings); + } +} diff --git a/mullvad-types/src/settings/mod.rs b/mullvad-types/src/settings/mod.rs index fa5d2dfb8c..9ea32fdb3b 100644 --- a/mullvad-types/src/settings/mod.rs +++ b/mullvad-types/src/settings/mod.rs @@ -55,7 +55,6 @@ pub struct Settings { /// might be located. pub tunnel_options: TunnelOptions, /// Whether to notify users of beta updates. - #[serde(deserialize_with = "deserialize_show_beta_releases")] pub show_beta_releases: bool, /// Specifies settings schema version #[cfg_attr(target_os = "android", jnix(skip))] @@ -197,7 +196,7 @@ impl Default for TunnelOptions { openvpn: openvpn::TunnelOptions::default(), wireguard: wireguard::TunnelOptions { options: net::wireguard::TunnelOptions::default(), - automatic_rotation: None, + rotation_interval: None, }, generic: GenericTunnelOptions { // Enable IPv6 be default on Android @@ -208,21 +207,14 @@ impl Default for TunnelOptions { } } -/// Used to deserialize the `show_beta_releases` field in the settings struct, as it used to be -/// a nullable field, but it is no longer. -fn deserialize_show_beta_releases<'de, D: serde::de::Deserializer<'de>>( - field: D, -) -> std::result::Result<bool, D::Error> { - Option::deserialize(field).map(|value| value.unwrap_or(false)) -} #[cfg(test)] mod test { use super::*; #[test] - fn test_deserialization_of_2020_4_format() { - let old_settings = br#"{ + fn test_deserialization() { + let settings = br#"{ "account_token": "0000000000000000", "relay_settings": { "normal": { @@ -258,16 +250,16 @@ mod test { }, "wireguard": { "mtu": null, - "automatic_rotation": null + "rotation_interval": null }, "generic": { "enable_ipv6": true } }, - "settings_version": 2, - "show_beta_releases": null + "settings_version": 3, + "show_beta_releases": false }"#; - let _ = Settings::load_from_bytes(old_settings).unwrap(); + let _ = Settings::load_from_bytes(settings).unwrap(); } } diff --git a/mullvad-types/src/wireguard.rs b/mullvad-types/src/wireguard.rs index 32d152ec4d..cbe05363ec 100644 --- a/mullvad-types/src/wireguard.rs +++ b/mullvad-types/src/wireguard.rs @@ -1,10 +1,18 @@ use chrono::{offset::Utc, DateTime}; #[cfg(target_os = "android")] use jnix::IntoJava; -use serde::{Deserialize, Serialize}; -use std::fmt; +use serde::{Deserialize, Deserializer, Serialize}; +use std::{convert::TryFrom, fmt, time::Duration}; use talpid_types::net::wireguard; +pub const MIN_ROTATION_INTERVAL: Duration = Duration::from_secs(1 * 24 * 60 * 60); +pub const MAX_ROTATION_INTERVAL: Duration = Duration::from_secs(7 * 24 * 60 * 60); +pub const DEFAULT_ROTATION_INTERVAL: Duration = if cfg!(target_os = "android") { + Duration::from_secs(4 * 24 * 60 * 60) +} else { + Duration::from_secs(7 * 24 * 60 * 60) +}; + /// Contains account specific wireguard data #[derive(Serialize, Deserialize, Clone, Debug)] pub struct WireguardData { @@ -24,6 +32,87 @@ impl WireguardData { } } +#[derive(Debug, Clone)] +pub enum RotationIntervalError { + TooSmall, + TooLarge, +} + +impl fmt::Display for RotationIntervalError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use RotationIntervalError::*; + + match *self { + TooSmall => write!( + f, + "Rotation interval must be at least {} hours", + MIN_ROTATION_INTERVAL.as_secs() / 60 / 60 + ), + TooLarge => write!( + f, + "Rotation interval must be at most {} hours", + MAX_ROTATION_INTERVAL.as_secs() / 60 / 60 + ), + } + } +} + +impl std::error::Error for RotationIntervalError {} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)] +pub struct RotationInterval(Duration); + +impl RotationInterval { + pub fn new(interval: Duration) -> Result<RotationInterval, RotationIntervalError> { + if interval < MIN_ROTATION_INTERVAL { + Err(RotationIntervalError::TooSmall) + } else if interval > MAX_ROTATION_INTERVAL { + Err(RotationIntervalError::TooLarge) + } else { + Ok(RotationInterval(interval)) + } + } + + pub fn as_duration(&self) -> &Duration { + &self.0 + } +} + +impl<'de> Deserialize<'de> for RotationInterval { + fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error> + where + D: Deserializer<'de>, + { + let ivl = <Duration>::deserialize(deserializer)?; + RotationInterval::new(ivl).map_err(|_error| { + serde::de::Error::invalid_value( + serde::de::Unexpected::Other("Duration"), + &"interval within allowed range", + ) + }) + } +} + +impl TryFrom<Duration> for RotationInterval { + type Error = RotationIntervalError; + + fn try_from(duration: Duration) -> Result<RotationInterval, RotationIntervalError> { + RotationInterval::new(duration) + } +} + +impl From<RotationInterval> for Duration { + fn from(interval: RotationInterval) -> Duration { + *interval.as_duration() + } +} + +impl Default for RotationInterval { + fn default() -> RotationInterval { + RotationInterval::new(DEFAULT_ROTATION_INTERVAL).unwrap() + } +} + #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[cfg_attr(target_os = "android", derive(IntoJava))] #[cfg_attr( @@ -33,9 +122,9 @@ impl WireguardData { pub struct TunnelOptions { #[serde(flatten)] pub options: wireguard::TunnelOptions, - /// Interval used for automatic key rotation, in hours + /// Interval used for automatic key rotation #[cfg_attr(target_os = "android", jnix(skip))] - pub automatic_rotation: Option<u32>, + pub rotation_interval: Option<RotationInterval>, } /// Represents a published public key |
