summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorDavid Lönnhager <david.l@mullvad.net>2021-03-25 13:44:07 +0100
committerDavid Lönnhager <david.l@mullvad.net>2021-03-25 13:44:07 +0100
commit72dc4d1230c3b8f749cbd0f55ebbfdb27902d2c2 (patch)
tree1e3632b999260dcb787d8a38b86f51ba25fc5d12
parentee76c83beed76c80814ddddd0e7503532efbbea2 (diff)
parentb5b92428107dd6b4be23d7d127fdfab16d00863a (diff)
downloadmullvadvpn-72dc4d1230c3b8f749cbd0f55ebbfdb27902d2c2.tar.xz
mullvadvpn-72dc4d1230c3b8f749cbd0f55ebbfdb27902d2c2.zip
Merge branch 'update-key-rotation'
-rw-r--r--CHANGELOG.md2
-rw-r--r--mullvad-cli/src/cmds/tunnel.rs32
-rw-r--r--mullvad-daemon/src/lib.rs25
-rw-r--r--mullvad-daemon/src/management_interface.rs26
-rw-r--r--mullvad-daemon/src/settings.rs7
-rw-r--r--mullvad-daemon/src/wireguard.rs25
-rw-r--r--mullvad-management-interface/proto/management_interface.proto9
-rw-r--r--mullvad-management-interface/src/lib.rs2
-rw-r--r--mullvad-types/src/settings/migrations/mod.rs12
-rw-r--r--mullvad-types/src/settings/migrations/v1.rs4
-rw-r--r--mullvad-types/src/settings/migrations/v2.rs176
-rw-r--r--mullvad-types/src/settings/mod.rs22
-rw-r--r--mullvad-types/src/wireguard.rs97
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