//! 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> { tokio::task::spawn_blocking(|| { let mut buf = vec![]; std::io::stdin().read_to_end(&mut buf)?; Ok(buf) }) .await .unwrap() }