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::DisplayPath as _;
10
use pgp::composed::{
11
  ArmorOptions,
12
  EncryptionCaps,
13
  KeyType,
14
  SecretKeyParamsBuilder,
15
};
16
use rand::thread_rng;
17

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

            
20
use super::KEY_LOCATION_HELP;
21

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

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

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

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

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

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

            
53
    let filename_with_ext: &str = &format!("{name}.key");
54
    // Check if the file already exists
55
    let file_path = location.join(filename_with_ext);
56
    if file_path.exists() && !self.force {
57
      return Err(anyhow::anyhow!(
58
        "Key '{}' already exists at '{}'. Use --force to overwrite.",
59
        name,
60
        file_path.display_lossy()
61
      ));
62
    }
63

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

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

            
81
    Ok(())
82
  }
83
}