Skip to content

Commit 0411856

Browse files
jamesmcmsid-code
authored andcommitted
Remove Mullvad API usage for Wireguard keys
Mullvad has unfortunately deprecated the API for handling Wireguard keys See issue 243 - jamesmcm#243 This change removes that code and relies on the user uploading their Wireguard public key and recording the return IPv4 and IPv6 ranges directly.
1 parent 950d3c4 commit 0411856

File tree

4 files changed

+47
-131
lines changed

4 files changed

+47
-131
lines changed

vopono_core/src/config/providers/mozilla/wireguard.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,6 @@ struct WireguardRelay {
280280
ipv6_addr_in: std::net::Ipv6Addr,
281281
pubkey: String,
282282
multihop_port: u16,
283-
socks_name: String,
284283
}
285284

286285
#[derive(Serialize, Debug)]

vopono_core/src/config/providers/mullvad/mod.rs

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,6 @@ use crate::util::wireguard::WgPeer;
1010
use anyhow::anyhow;
1111
use serde::Deserialize;
1212

13-
#[derive(Deserialize, Debug)]
14-
struct AuthToken {
15-
auth_token: String,
16-
}
17-
1813
#[allow(dead_code)]
1914
#[derive(Deserialize, Debug, Clone)]
2015
struct UserInfo {
@@ -25,12 +20,6 @@ struct UserInfo {
2520
wg_peers: Vec<WgPeer>,
2621
}
2722

28-
// TODO: use Json::Value to remove this?
29-
#[derive(Deserialize, Debug)]
30-
struct UserResponse {
31-
account: UserInfo,
32-
}
33-
3423
pub struct Mullvad {}
3524

3625
impl Provider for Mullvad {

vopono_core/src/config/providers/mullvad/openvpn.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,8 @@ impl OpenVpnProvider for Mullvad {
120120
relay.ipv4_addr_in, port, relay.hostname
121121
)
122122
} else {
123-
format!("remote {}.mullvad.net {}", relay.hostname, port)
123+
// TODO: Use fqdn here?
124+
format!("remote {}.relays.mullvad.net {}", relay.hostname, port)
124125
};
125126

126127
file_set.entry(file_name).or_default().push(remote_string);

vopono_core/src/config/providers/mullvad/wireguard.rs

Lines changed: 45 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,20 @@
11
use super::Mullvad;
2-
use super::{AuthToken, UserInfo, UserResponse, WireguardProvider};
3-
use crate::config::providers::{BoolChoice, ConfigurationChoice, Input, InputNumericu16, UiClient};
2+
use super::WireguardProvider;
3+
use crate::config::providers::{ConfigurationChoice, Input, InputNumericu16, UiClient};
44
use crate::network::wireguard::{WireguardConfig, WireguardInterface, WireguardPeer};
55
use crate::util::delete_all_files_in_dir;
6-
use crate::util::wireguard::{generate_keypair, generate_public_key, WgKey, WgPeer};
7-
use anyhow::{anyhow, Context};
6+
use crate::util::wireguard::{generate_public_key, WgKey, WgPeer};
7+
use anyhow::Context;
88
use ipnet::IpNet;
99
use log::{debug, info};
1010
use regex::Regex;
1111
use reqwest::blocking::Client;
12-
use reqwest::header::AUTHORIZATION;
1312
use serde::Deserialize;
14-
use std::collections::HashMap;
1513
use std::fs::create_dir_all;
1614
use std::io::Write;
1715
use std::net::{IpAddr, SocketAddr};
1816
use std::str::FromStr;
1917

20-
impl Mullvad {
21-
fn upload_wg_key(client: &Client, auth_token: &str, keypair: &WgKey) -> anyhow::Result<()> {
22-
let mut map = HashMap::new();
23-
map.insert("pubkey", keypair.public.clone());
24-
client
25-
.post("https://api.mullvad.net/www/wg-pubkeys/add/")
26-
.header(AUTHORIZATION, format!("Token {auth_token}"))
27-
.json(&map)
28-
.send()?
29-
.error_for_status()
30-
.context("Failed to upload keypair to Mullvad")?;
31-
info!(
32-
"Public key submitted to Mullvad. Private key will be saved in generated config files."
33-
);
34-
Ok(())
35-
}
36-
}
37-
3818
impl WireguardProvider for Mullvad {
3919
fn create_wireguard_config(&self, uiclient: &dyn UiClient) -> anyhow::Result<()> {
4020
let wireguard_dir = self.wireguard_dir()?;
@@ -47,46 +27,15 @@ impl WireguardProvider for Mullvad {
4727
.send()?
4828
.json().with_context(|| "Failed to parse Mullvad relays response - try again after a few minutes or report an issue if it is persistent")?;
4929

50-
let username = self.request_mullvad_username(uiclient)?;
51-
let auth: AuthToken = client
52-
.get(format!("https://api.mullvad.net/www/accounts/{username}/"))
53-
.send()?
54-
.json()?;
55-
56-
let user_info: UserResponse = client
57-
.get("https://api.mullvad.net/www/me/")
58-
.header(AUTHORIZATION, format!("Token {}", auth.auth_token))
59-
.send()?
60-
.json()?;
61-
62-
let user_info = user_info.account;
63-
debug!("Received user info: {:?}", user_info);
64-
65-
let keypair: WgKey = prompt_for_wg_key(user_info, &client, &auth.auth_token, uiclient)?;
30+
let (keypair, ipv4_net, ipv6_net) = prompt_for_wg_key(uiclient)?;
6631

6732
debug!("Chosen keypair: {:?}", keypair);
68-
// Get user info again in case we uploaded new key
69-
let user_info: UserResponse = client
70-
.get("https://api.mullvad.net/www/me/")
71-
.header(AUTHORIZATION, format!("Token {}", auth.auth_token))
72-
.send()?
73-
.json()?;
74-
75-
let user_info = user_info.account;
76-
let wg_peer = user_info
77-
.wg_peers
78-
.iter()
79-
.find(|x| x.key.public == keypair.public)
80-
.ok_or_else(|| anyhow!("Did not find key: {} in Mullvad account", keypair.public))?;
8133

8234
// TODO: Hardcoded IP - can we scrape this anywhere?
8335
let dns = std::net::Ipv4Addr::new(193, 138, 218, 74);
8436
let interface = WireguardInterface {
8537
private_key: keypair.private.clone(),
86-
address: vec![
87-
IpNet::from(wg_peer.ipv4_address),
88-
IpNet::from(wg_peer.ipv6_address),
89-
],
38+
address: vec![ipv4_net, ipv6_net],
9039
dns: Some(vec![IpAddr::from(dns)]),
9140
};
9241

@@ -162,7 +111,6 @@ struct WireguardRelay {
162111
ipv6_addr_in: std::net::Ipv6Addr,
163112
pubkey: String,
164113
multihop_port: u16,
165-
socks_name: String,
166114
}
167115

168116
struct Devices {
@@ -188,69 +136,48 @@ impl ConfigurationChoice for Devices {
188136
}
189137
}
190138

191-
fn prompt_for_wg_key(
192-
user_info: UserInfo,
193-
client: &Client,
194-
auth_token: &str,
195-
uiclient: &dyn UiClient,
196-
) -> anyhow::Result<WgKey> {
197-
if !user_info.wg_peers.is_empty() {
198-
let existing = Devices { devices: user_info.wg_peers.clone()};
199-
200-
let selection = uiclient.get_configuration_choice(&existing)?;
201-
202-
if selection >= user_info.wg_peers.len() {
203-
if user_info.wg_peers.len() >= user_info.max_wg_peers as usize
204-
|| !user_info.can_add_wg_peers
205-
{
206-
return Err(anyhow!("Cannot add more Wireguard keypairs to this account. Try to delete existing keypairs."));
207-
}
208-
let keypair = generate_keypair()?;
209-
Mullvad::upload_wg_key(client, auth_token, &keypair)?;
210-
Ok(keypair)
211-
} else {
212-
let pubkey_clone = user_info.wg_peers[selection].key.public.clone();
213-
let private_key = uiclient.get_input(Input{
214-
prompt: format!("Private key for {}",
215-
&user_info.wg_peers[selection].key.public
216-
),
217-
validator: Some(Box::new(move |private_key: &String| -> Result<(), String> {
218-
219-
let private_key = private_key.trim();
220-
221-
if private_key.len() != 44 {
222-
return Err("Expected private key length of 44 characters".to_string()
223-
);
224-
}
225-
226-
match generate_public_key(private_key) {
227-
Ok(public_key) => {
228-
if public_key != pubkey_clone {
229-
return Err("Private key does not match public key".to_string());
230-
}
231-
Ok(())
139+
fn prompt_for_wg_key(uiclient: &dyn UiClient) -> anyhow::Result<(WgKey, IpNet, IpNet)> {
140+
// TODO: We could also generate new private key first - generate_keypair()
141+
let private_key = uiclient.get_input(Input {
142+
prompt: "Enter your Wireguard Private key and upload the Public Key as a Mullvad device"
143+
.to_owned(),
144+
validator: Some(Box::new(
145+
move |private_key: &String| -> Result<(), String> {
146+
let private_key = private_key.trim();
147+
148+
if private_key.len() != 44 {
149+
Err("Expected private key length of 44 characters".to_string())
150+
} else {
151+
Ok(())
232152
}
233-
Err(_) => Err("Failed to generate public key".to_string())
234-
}}))})?;
153+
},
154+
)),
155+
})?;
235156

157+
let ipv4_address = IpNet::from_str(&uiclient.get_input(Input {
158+
prompt: "Enter the IPv4 address range Mullvad returned after adding the device".to_owned(),
159+
validator: Some(Box::new(move |_ip: &String| -> Result<(), String> {
160+
// TODO: Ipv4 range validator
161+
Ok(())
162+
})),
163+
})?)?;
236164

237-
Ok(WgKey {
238-
public: user_info.wg_peers[selection].key.public.clone(),
239-
private: private_key,
240-
})
241-
}
242-
} else if uiclient.get_bool_choice(BoolChoice{
243-
prompt:
244-
"No Wireguard keys currently exist on your Mullvad account, would you like to generate a new keypair?".to_string(),
245-
default: true,
246-
})?
247-
{
248-
let keypair = generate_keypair()?;
249-
Mullvad::upload_wg_key(client, auth_token, &keypair)?;
250-
Ok(keypair)
251-
} else {
252-
Err(anyhow!("Wireguard requires a keypair, either upload one to Mullvad or let vopono generate one"))
253-
}
165+
let ipv6_address = IpNet::from_str(&uiclient.get_input(Input {
166+
prompt: "Enter the IPv6 address range Mullvad returned after adding the device".to_owned(),
167+
validator: Some(Box::new(move |_ip: &String| -> Result<(), String> {
168+
// TODO: Ipv4 range validator
169+
Ok(())
170+
})),
171+
})?)?;
172+
173+
Ok((
174+
WgKey {
175+
public: generate_public_key(&private_key).expect("Failed to generate public key"),
176+
private: private_key,
177+
},
178+
ipv4_address,
179+
ipv6_address,
180+
))
254181
}
255182

256183
fn request_port(uiclient: &dyn UiClient) -> anyhow::Result<u16> {

0 commit comments

Comments
 (0)