1
use std::fs::{
2
  self,
3
  File,
4
};
5
use std::io::{
6
  self,
7
  IsTerminal,
8
  Read as _,
9
  Write as _,
10
};
11
use std::path::Path;
12

            
13
use clap::Args;
14
use mk_lib::file::ToUtf8 as _;
15
use pgp::crypto::sym::SymmetricKeyAlgorithm;
16
use pgp::types::SecretKeyTrait;
17
use pgp::{
18
  ArmorOptions,
19
  Deserializable,
20
  Message,
21
  SignedSecretKey,
22
};
23
use rand::thread_rng;
24

            
25
use crate::secrets::context::Context;
26
use crate::secrets::vault::{
27
  verify_key,
28
  verify_vault,
29
};
30

            
31
#[derive(Debug, Args)]
32
pub struct StoreSecret {
33
  #[arg(help = "The secret identifier")]
34
  path: String,
35

            
36
  #[arg(help = "The secret value")]
37
  value: Option<String>,
38

            
39
  #[arg(short, long, help = "The path to the secret vault")]
40
  vault_location: Option<String>,
41

            
42
  #[arg(long, help = "The key location")]
43
  keys_location: Option<String>,
44

            
45
  #[arg(short, long, help = "The key name")]
46
  key_name: Option<String>,
47

            
48
  /// If the secret already exists, it will be overwritten
49
  #[arg(short, long, help = "Force overwrite the secret")]
50
  force: bool,
51
}
52

            
53
impl StoreSecret {
54
  pub fn execute(&self, context: &Context) -> anyhow::Result<()> {
55
    let path: &str = &self.path.clone();
56
    let value: &str = &match &self.value {
57
      Some(value) => value.clone(),
58
      None => {
59
        let stdin = io::stdin();
60
        if stdin.is_terminal() {
61
          return Err(anyhow::anyhow!("No value provided"));
62
        }
63

            
64
        let mut buffer = String::new();
65
        let mut handle = stdin.lock();
66
        match handle.read_to_string(&mut buffer) {
67
          Ok(0) => return Err(anyhow::anyhow!("No value provided")),
68
          Ok(_) => buffer.trim().to_string(),
69
          Err(e) => return Err(anyhow::anyhow!("Failed to read from stdin: {}", e)),
70
        }
71
      },
72
    };
73

            
74
    let vault_location: &str = &self
75
      .vault_location
76
      .clone()
77
      .unwrap_or_else(|| context.vault_location());
78
    let keys_location: &str = &self
79
      .keys_location
80
      .clone()
81
      .unwrap_or_else(|| context.keys_location());
82
    let key_name: &str = &self.key_name.clone().unwrap_or_else(|| context.key_name());
83

            
84
    assert!(!path.is_empty(), "Path must be provided");
85
    assert!(!value.is_empty(), "Value must be provided");
86
    assert!(!vault_location.is_empty(), "Store location must be provided");
87
    assert!(!keys_location.is_empty(), "Keys location must be provided");
88
    assert!(!key_name.is_empty(), "Key name must be provided");
89

            
90
    verify_vault(vault_location)?;
91
    verify_key(keys_location, key_name)?;
92

            
93
    let secret_path = Path::new(vault_location).join(path);
94
    let data_path = secret_path.clone().join("data.asc");
95
    if secret_path.exists()
96
      && secret_path.is_dir()
97
      && data_path.exists()
98
      && data_path.is_file()
99
      && !self.force
100
    {
101
      println!(
102
        "Secret already exists at path {path} in {}",
103
        secret_path.to_utf8()?
104
      );
105
    } else {
106
      fs::create_dir_all(secret_path.clone())?;
107

            
108
      // Open the secret key file
109
      let key_name = format!("{}.key", key_name);
110
      let key_path = Path::new(keys_location).join(key_name);
111
      let mut secret_key_string = File::open(key_path)?;
112
      let (signed_secret_key, _) = SignedSecretKey::from_armor_single(&mut secret_key_string)?;
113
      signed_secret_key.verify()?;
114

            
115
      // Get the public key
116
      let pubkey = signed_secret_key.public_key();
117

            
118
      // Encrypt the value
119
      let message = Message::new_literal("none", value);
120
      let encrypted_message =
121
        message.encrypt_to_keys_seipdv1(&mut thread_rng(), SymmetricKeyAlgorithm::AES128, &[&pubkey])?;
122

            
123
      // Save the encrypted message to a file
124
      let mut writer = File::create(data_path)?;
125
      encrypted_message.to_armored_writer(&mut writer, ArmorOptions::default())?;
126
      writer.flush()?;
127

            
128
      println!("Secret stored at {}", secret_path.to_utf8()?);
129
    }
130
    Ok(())
131
  }
132
}