]> git.proxmox.com Git - proxmox-backup.git/blame - src/backup/key_derivation.rs
client: add 'key show' command
[proxmox-backup.git] / src / backup / key_derivation.rs
CommitLineData
b65390eb 1use anyhow::{bail, format_err, Context, Error};
826f309b
DM
2
3use serde::{Deserialize, Serialize};
826f309b 4
37e60ddc
FG
5use crate::backup::{CryptConfig, Fingerprint};
6
9ea4bce4
WB
7use proxmox::tools::fs::{file_get_contents, replace_file, CreateOptions};
8use proxmox::try_block;
e18a6c9e 9
826f309b
DM
10#[derive(Deserialize, Serialize, Debug)]
11pub enum KeyDerivationConfig {
12 Scrypt {
13 n: u64,
14 r: u64,
15 p: u64,
16 #[serde(with = "proxmox::tools::serde::bytes_as_base64")]
17 salt: Vec<u8>,
18 },
19 PBKDF2 {
20 iter: usize,
21 #[serde(with = "proxmox::tools::serde::bytes_as_base64")]
22 salt: Vec<u8>,
23 },
24}
25
26impl KeyDerivationConfig {
27
28 /// Derive a key from provided passphrase
29 pub fn derive_key(&self, passphrase: &[u8]) -> Result<[u8; 32], Error> {
30
31 let mut key = [0u8; 32];
32
33 match self {
34 KeyDerivationConfig::Scrypt { n, r, p, salt } => {
35 // estimated scrypt memory usage is 128*r*n*p
36 openssl::pkcs5::scrypt(
37 passphrase,
38 &salt,
39 *n, *r, *p,
40 1025*1024*1024,
41 &mut key,
42 )?;
43
44 Ok(key)
45 }
46 KeyDerivationConfig::PBKDF2 { iter, salt } => {
47
48 openssl::pkcs5::pbkdf2_hmac(
49 passphrase,
50 &salt,
51 *iter,
52 openssl::hash::MessageDigest::sha256(),
53 &mut key,
54 )?;
55
56 Ok(key)
57 }
58 }
59 }
60}
61
62#[derive(Deserialize, Serialize, Debug)]
63pub struct KeyConfig {
181f097a 64 pub kdf: Option<KeyDerivationConfig>,
6a7be83e
DM
65 #[serde(with = "proxmox::tools::serde::epoch_as_rfc3339")]
66 pub created: i64,
67 #[serde(with = "proxmox::tools::serde::epoch_as_rfc3339")]
68 pub modified: i64,
826f309b 69 #[serde(with = "proxmox::tools::serde::bytes_as_base64")]
181f097a 70 pub data: Vec<u8>,
37e60ddc
FG
71 #[serde(skip_serializing_if = "Option::is_none")]
72 #[serde(default)]
73 pub fingerprint: Option<Fingerprint>,
826f309b
DM
74 }
75
181f097a
DM
76pub fn store_key_config(
77 path: &std::path::Path,
78 replace: bool,
79 key_config: KeyConfig,
80) -> Result<(), Error> {
81
82 let data = serde_json::to_string(&key_config)?;
83
84 use std::io::Write;
85
86 try_block!({
87 if replace {
88 let mode = nix::sys::stat::Mode::S_IRUSR | nix::sys::stat::Mode::S_IWUSR;
feaa1ad3 89 replace_file(&path, data.as_bytes(), CreateOptions::new().perm(mode))?;
181f097a
DM
90 } else {
91 use std::os::unix::fs::OpenOptionsExt;
92
93 let mut file = std::fs::OpenOptions::new()
94 .write(true)
95 .mode(0o0600)
96 .create_new(true)
97 .open(&path)?;
98
99 file.write_all(data.as_bytes())?;
100 }
101
102 Ok(())
103 }).map_err(|err: Error| format_err!("Unable to create file {:?} - {}", path, err))?;
104
105 Ok(())
106}
826f309b 107
ab44acff 108pub fn encrypt_key_with_passphrase(
826f309b
DM
109 raw_key: &[u8],
110 passphrase: &[u8],
ab44acff 111) -> Result<KeyConfig, Error> {
826f309b
DM
112
113 let salt = proxmox::sys::linux::random_data(32)?;
114
115 let kdf = KeyDerivationConfig::Scrypt {
116 n: 65536,
117 r: 8,
118 p: 1,
119 salt,
120 };
121
122 let derived_key = kdf.derive_key(passphrase)?;
123
124 let cipher = openssl::symm::Cipher::aes_256_gcm();
125
126 let iv = proxmox::sys::linux::random_data(16)?;
127 let mut tag = [0u8; 16];
128
129 let encrypted_key = openssl::symm::encrypt_aead(
130 cipher,
131 &derived_key,
132 Some(&iv),
133 b"",
134 &raw_key,
135 &mut tag,
136 )?;
137
138 let mut enc_data = vec![];
139 enc_data.extend_from_slice(&iv);
140 enc_data.extend_from_slice(&tag);
141 enc_data.extend_from_slice(&encrypted_key);
142
6a7be83e 143 let created = proxmox::tools::time::epoch_i64();
826f309b 144
ab44acff 145 Ok(KeyConfig {
826f309b
DM
146 kdf: Some(kdf),
147 created,
ab44acff 148 modified: created,
826f309b 149 data: enc_data,
37e60ddc 150 fingerprint: None,
181f097a 151 })
826f309b
DM
152}
153
b65390eb
WB
154pub fn load_and_decrypt_key(
155 path: &std::path::Path,
156 passphrase: &dyn Fn() -> Result<Vec<u8>, Error>,
37e60ddc 157) -> Result<([u8;32], i64, Fingerprint), Error> {
b65390eb
WB
158 do_load_and_decrypt_key(path, passphrase)
159 .with_context(|| format!("failed to load decryption key from {:?}", path))
160}
826f309b 161
b65390eb
WB
162fn do_load_and_decrypt_key(
163 path: &std::path::Path,
164 passphrase: &dyn Fn() -> Result<Vec<u8>, Error>,
37e60ddc 165) -> Result<([u8;32], i64, Fingerprint), Error> {
0351f23b
WB
166 decrypt_key(&file_get_contents(&path)?, passphrase)
167}
826f309b 168
0351f23b
WB
169pub fn decrypt_key(
170 mut keydata: &[u8],
171 passphrase: &dyn Fn() -> Result<Vec<u8>, Error>,
37e60ddc 172) -> Result<([u8;32], i64, Fingerprint), Error> {
0351f23b 173 let key_config: KeyConfig = serde_json::from_reader(&mut keydata)?;
826f309b
DM
174
175 let raw_data = key_config.data;
ab44acff 176 let created = key_config.created;
826f309b 177
6d0983db 178 let key = if let Some(kdf) = key_config.kdf {
826f309b
DM
179
180 let passphrase = passphrase()?;
181 if passphrase.len() < 5 {
182 bail!("Passphrase is too short!");
183 }
184
185 let derived_key = kdf.derive_key(&passphrase)?;
186
187 if raw_data.len() < 32 {
188 bail!("Unable to encode key - short data");
189 }
190 let iv = &raw_data[0..16];
191 let tag = &raw_data[16..32];
192 let enc_data = &raw_data[32..];
193
194 let cipher = openssl::symm::Cipher::aes_256_gcm();
195
834a2f95 196 openssl::symm::decrypt_aead(
826f309b
DM
197 cipher,
198 &derived_key,
199 Some(&iv),
200 b"", //??
201 &enc_data,
202 &tag,
834a2f95 203 ).map_err(|err| format_err!("Unable to decrypt key - {}", err))?
826f309b 204
826f309b 205 } else {
6d0983db
DM
206 raw_data
207 };
208
209 let mut result = [0u8; 32];
210 result.copy_from_slice(&key);
211
37e60ddc
FG
212 let fingerprint = match key_config.fingerprint {
213 Some(fingerprint) => fingerprint,
214 None => {
215 let crypt_config = CryptConfig::new(result.clone())?;
216 crypt_config.fingerprint()
217 },
218 };
219
220 Ok((result, created, fingerprint))
826f309b 221}