1
use std::fs::{
2
  self,
3
  File,
4
};
5
use std::io::Write as _;
6
use std::path::Path;
7

            
8
use clap::Args;
9
use mk_lib::file::ToUtf8;
10
use pgp::{
11
  ArmorOptions,
12
  KeyType,
13
  SecretKeyParamsBuilder,
14
};
15
use rand::thread_rng;
16

            
17
use crate::secrets::context::Context;
18

            
19
use super::KEY_LOCATION_HELP;
20

            
21
#[derive(Debug, Args)]
22
pub struct GenerateKey {
23
  /// The location to store the private key
24
  #[arg(short, long, help = KEY_LOCATION_HELP)]
25
  location: Option<String>,
26

            
27
  /// If not provided, the key will be named "default"
28
  /// If the key already exists, it will not be created
29
  #[arg(short, long, help = "The key name")]
30
  name: Option<String>,
31

            
32
  /// If the key already exists, it will be overwritten
33
  #[arg(short, long, help = "Force overwrite the key")]
34
  force: bool,
35
}
36

            
37
impl GenerateKey {
38
  pub fn execute(&self, context: &Context) -> anyhow::Result<()> {
39
    let location: &str = &self.location.clone().unwrap_or_else(|| context.keys_location());
40
    let name: &str = &self.name.clone().unwrap_or_else(|| context.key_name());
41
    println!("Generating key {} at {}", name, location);
42

            
43
    assert!(!location.is_empty(), "Location must be provided");
44
    assert!(!name.is_empty(), "Key name must be provided");
45

            
46
    // Create the directory if it does not exist
47
    let location = Path::new(location);
48
    if !location.exists() {
49
      fs::create_dir_all(location)?;
50
    }
51

            
52
    let filename_with_ext: &str = &format!("{name}.key");
53
    // Check if the file already exists
54
    let file_path = location.join(filename_with_ext);
55
    if file_path.exists() && !self.force {
56
      return Err(anyhow::anyhow!(
57
        "File {} already exists. Aborting.",
58
        file_path.to_utf8()?
59
      ));
60
    }
61

            
62
    let primary_user_id = format!("Me <{name}@mk.local>");
63
    let mut key_params = SecretKeyParamsBuilder::default();
64
    key_params
65
      .key_type(KeyType::Rsa(2048))
66
      .can_certify(false)
67
      .can_encrypt(true)
68
      .can_sign(true)
69
      .primary_user_id(primary_user_id);
70
    let private_key_params = key_params.build()?;
71
    let private_key = private_key_params.generate(thread_rng())?;
72

            
73
    // Use the private key to sign itself and put empty password
74
    let signed_private_key = private_key.sign(&mut thread_rng(), String::new)?;
75

            
76
    // Save the armored private key to a file
77
    let mut file = File::create(file_path.clone())?;
78
    signed_private_key.to_armored_writer(&mut file, ArmorOptions::default())?;
79
    file.flush()?;
80
    println!("Key saved to {}", file_path.to_utf8()?);
81

            
82
    Ok(())
83
  }
84
}