1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
|
//! See [Opt].
use anyhow::Context;
use clap::Parser;
use std::io::Read;
use tokio::{fs, io};
use mullvad_update::format::{self, key};
#[allow(dead_code)]
const DEFAULT_EXPIRY_MONTHS: u32 = 6;
/// A tool that generates signed Mullvad version metadata.
#[derive(Parser)]
pub enum Opt {
/// Generate an ed25519 secret key
GenerateKey,
/// Sign a JSON payload using an ed25519 key and output the signed metadata
/// This data is typically generated by 'generate-unsigned-metadata'
Sign {
/// File to sign. Use "-" to read from stdin.
#[clap(short, long)]
file: String,
/// Secret ed25519 key used for signing, as hexadecimal string
#[clap(short, long)]
secret: key::SecretKey,
},
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let opt = Opt::parse();
match opt {
Opt::GenerateKey => {
println!("{}", key::SecretKey::generate());
Ok(())
}
Opt::Sign { file, secret } => sign(file, secret).await,
}
}
async fn sign(file: String, secret: key::SecretKey) -> anyhow::Result<()> {
// Read unsigned JSON data
let data = if file == "-" {
get_stdin().await?
} else {
fs::read(file).await?
};
// Deserialize version data
let response: format::Response =
serde_json::from_slice(&data).context("Failed to deserialize version metadata")?;
// Sign it
let signed_response = format::SignedResponse::sign(secret, response)?;
// Print it
println!(
"{}",
serde_json::to_string_pretty(&signed_response)
.context("Failed to serialize signed version")?
);
Ok(())
}
async fn get_stdin() -> io::Result<Vec<u8>> {
tokio::task::spawn_blocking(|| {
let mut buf = vec![];
std::io::stdin().read_to_end(&mut buf)?;
Ok(buf)
})
.await
.unwrap()
}
|