]> git.proxmox.com Git - proxmox-backup.git/blame - src/backup/key_derivation.rs
cleanup: factor out decrypt_key_config
[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
5e17dbf2 7use proxmox::api::api;
9ea4bce4
WB
8use proxmox::tools::fs::{file_get_contents, replace_file, CreateOptions};
9use proxmox::try_block;
e18a6c9e 10
5e17dbf2
DM
11#[api(default: "scrypt")]
12#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
13#[serde(rename_all = "lowercase")]
14/// Key derivation function for password protected encryption keys.
15pub enum Kdf {
16 /// Do not encrypt the key.
17 None,
18
19 /// Encrypt they key with a password using SCrypt.
20 Scrypt,
21
22 /// Encrtypt the Key with a password using PBKDF2
23 PBKDF2,
24}
25
26impl Default for Kdf {
27 #[inline]
28 fn default() -> Self {
29 Kdf::Scrypt
30 }
31}
32
826f309b
DM
33#[derive(Deserialize, Serialize, Debug)]
34pub enum KeyDerivationConfig {
35 Scrypt {
36 n: u64,
37 r: u64,
38 p: u64,
39 #[serde(with = "proxmox::tools::serde::bytes_as_base64")]
40 salt: Vec<u8>,
41 },
42 PBKDF2 {
43 iter: usize,
44 #[serde(with = "proxmox::tools::serde::bytes_as_base64")]
45 salt: Vec<u8>,
46 },
47}
48
49impl KeyDerivationConfig {
50
51 /// Derive a key from provided passphrase
52 pub fn derive_key(&self, passphrase: &[u8]) -> Result<[u8; 32], Error> {
53
54 let mut key = [0u8; 32];
55
56 match self {
57 KeyDerivationConfig::Scrypt { n, r, p, salt } => {
58 // estimated scrypt memory usage is 128*r*n*p
59 openssl::pkcs5::scrypt(
60 passphrase,
61 &salt,
62 *n, *r, *p,
63 1025*1024*1024,
64 &mut key,
65 )?;
66
67 Ok(key)
68 }
69 KeyDerivationConfig::PBKDF2 { iter, salt } => {
70
71 openssl::pkcs5::pbkdf2_hmac(
72 passphrase,
73 &salt,
74 *iter,
75 openssl::hash::MessageDigest::sha256(),
76 &mut key,
77 )?;
78
79 Ok(key)
80 }
81 }
82 }
83}
84
85#[derive(Deserialize, Serialize, Debug)]
86pub struct KeyConfig {
181f097a 87 pub kdf: Option<KeyDerivationConfig>,
6a7be83e
DM
88 #[serde(with = "proxmox::tools::serde::epoch_as_rfc3339")]
89 pub created: i64,
90 #[serde(with = "proxmox::tools::serde::epoch_as_rfc3339")]
91 pub modified: i64,
826f309b 92 #[serde(with = "proxmox::tools::serde::bytes_as_base64")]
181f097a 93 pub data: Vec<u8>,
37e60ddc
FG
94 #[serde(skip_serializing_if = "Option::is_none")]
95 #[serde(default)]
96 pub fingerprint: Option<Fingerprint>,
826f309b
DM
97 }
98
181f097a
DM
99pub fn store_key_config(
100 path: &std::path::Path,
101 replace: bool,
102 key_config: KeyConfig,
103) -> Result<(), Error> {
104
105 let data = serde_json::to_string(&key_config)?;
106
107 use std::io::Write;
108
109 try_block!({
110 if replace {
111 let mode = nix::sys::stat::Mode::S_IRUSR | nix::sys::stat::Mode::S_IWUSR;
feaa1ad3 112 replace_file(&path, data.as_bytes(), CreateOptions::new().perm(mode))?;
181f097a
DM
113 } else {
114 use std::os::unix::fs::OpenOptionsExt;
115
116 let mut file = std::fs::OpenOptions::new()
117 .write(true)
118 .mode(0o0600)
119 .create_new(true)
120 .open(&path)?;
121
122 file.write_all(data.as_bytes())?;
123 }
124
125 Ok(())
126 }).map_err(|err: Error| format_err!("Unable to create file {:?} - {}", path, err))?;
127
128 Ok(())
129}
826f309b 130
ab44acff 131pub fn encrypt_key_with_passphrase(
826f309b
DM
132 raw_key: &[u8],
133 passphrase: &[u8],
5e17dbf2 134 kdf: Kdf,
ab44acff 135) -> Result<KeyConfig, Error> {
826f309b
DM
136
137 let salt = proxmox::sys::linux::random_data(32)?;
138
5e17dbf2
DM
139 let kdf = match kdf {
140 Kdf::Scrypt => KeyDerivationConfig::Scrypt {
141 n: 65536,
142 r: 8,
143 p: 1,
144 salt,
145 },
146 Kdf::PBKDF2 => KeyDerivationConfig::PBKDF2 {
147 iter: 65535,
148 salt,
149 },
150 Kdf::None => {
151 bail!("No key derivation function specified");
152 }
826f309b
DM
153 };
154
155 let derived_key = kdf.derive_key(passphrase)?;
156
157 let cipher = openssl::symm::Cipher::aes_256_gcm();
158
159 let iv = proxmox::sys::linux::random_data(16)?;
160 let mut tag = [0u8; 16];
161
162 let encrypted_key = openssl::symm::encrypt_aead(
163 cipher,
164 &derived_key,
165 Some(&iv),
166 b"",
167 &raw_key,
168 &mut tag,
169 )?;
170
171 let mut enc_data = vec![];
172 enc_data.extend_from_slice(&iv);
173 enc_data.extend_from_slice(&tag);
174 enc_data.extend_from_slice(&encrypted_key);
175
6a7be83e 176 let created = proxmox::tools::time::epoch_i64();
826f309b 177
ab44acff 178 Ok(KeyConfig {
826f309b
DM
179 kdf: Some(kdf),
180 created,
ab44acff 181 modified: created,
826f309b 182 data: enc_data,
37e60ddc 183 fingerprint: None,
181f097a 184 })
826f309b
DM
185}
186
b65390eb
WB
187pub fn load_and_decrypt_key(
188 path: &std::path::Path,
189 passphrase: &dyn Fn() -> Result<Vec<u8>, Error>,
37e60ddc 190) -> Result<([u8;32], i64, Fingerprint), Error> {
0351f23b 191 decrypt_key(&file_get_contents(&path)?, passphrase)
81d10c3b 192 .with_context(|| format!("failed to load decryption key from {:?}", path))
0351f23b 193}
826f309b 194
8ca37d6a
DM
195pub fn decrypt_key_config(
196 key_config: &KeyConfig,
0351f23b 197 passphrase: &dyn Fn() -> Result<Vec<u8>, Error>,
37e60ddc 198) -> Result<([u8;32], i64, Fingerprint), Error> {
826f309b 199
8ca37d6a 200 let raw_data = &key_config.data;
826f309b 201
8ca37d6a 202 let key = if let Some(ref kdf) = key_config.kdf {
826f309b
DM
203
204 let passphrase = passphrase()?;
205 if passphrase.len() < 5 {
206 bail!("Passphrase is too short!");
207 }
208
209 let derived_key = kdf.derive_key(&passphrase)?;
210
211 if raw_data.len() < 32 {
212 bail!("Unable to encode key - short data");
213 }
214 let iv = &raw_data[0..16];
215 let tag = &raw_data[16..32];
216 let enc_data = &raw_data[32..];
217
218 let cipher = openssl::symm::Cipher::aes_256_gcm();
219
834a2f95 220 openssl::symm::decrypt_aead(
826f309b
DM
221 cipher,
222 &derived_key,
223 Some(&iv),
224 b"", //??
225 &enc_data,
226 &tag,
8ca37d6a 227 ).map_err(|err| format_err!("Unable to decrypt key (wrong password?) - {}", err))?
826f309b 228
826f309b 229 } else {
8ca37d6a 230 raw_data.clone()
6d0983db
DM
231 };
232
233 let mut result = [0u8; 32];
234 result.copy_from_slice(&key);
235
e0af222e
FG
236 let crypt_config = CryptConfig::new(result.clone())?;
237 let fingerprint = crypt_config.fingerprint();
8ca37d6a
DM
238 if let Some(ref stored_fingerprint) = key_config.fingerprint {
239 if &fingerprint != stored_fingerprint {
c0174285 240 bail!(
e0af222e
FG
241 "KeyConfig contains wrong fingerprint {}, contained key has fingerprint {}",
242 stored_fingerprint, fingerprint
243 );
244 }
245 }
8ca37d6a
DM
246
247 Ok((result, key_config.created, fingerprint))
248}
37e60ddc 249
8ca37d6a
DM
250pub fn decrypt_key(
251 mut keydata: &[u8],
252 passphrase: &dyn Fn() -> Result<Vec<u8>, Error>,
253) -> Result<([u8;32], i64, Fingerprint), Error> {
254 let key_config: KeyConfig = serde_json::from_reader(&mut keydata)?;
255 decrypt_key_config(&key_config, passphrase)
826f309b 256}
8acfd15d
FG
257
258pub fn rsa_encrypt_key_config(
259 rsa: openssl::rsa::Rsa<openssl::pkey::Public>,
260 key: &KeyConfig,
261) -> Result<Vec<u8>, Error> {
262 let data = serde_json::to_string(key)?.as_bytes().to_vec();
263
264 let mut buffer = vec![0u8; rsa.size() as usize];
265 let len = rsa.public_encrypt(&data, &mut buffer, openssl::rsa::Padding::PKCS1)?;
266 if len != buffer.len() {
267 bail!("got unexpected length from rsa.public_encrypt().");
268 }
269 Ok(buffer)
270}
7137630d
FG
271
272pub fn rsa_decrypt_key_config(
273 rsa: openssl::rsa::Rsa<openssl::pkey::Private>,
274 key: &[u8],
275 passphrase: &dyn Fn() -> Result<Vec<u8>, Error>,
276) -> Result<([u8; 32], i64, Fingerprint), Error> {
277 let mut buffer = vec![0u8; rsa.size() as usize];
278 let decrypted = rsa
279 .private_decrypt(key, &mut buffer, openssl::rsa::Padding::PKCS1)
280 .map_err(|err| format_err!("failed to decrypt KeyConfig using RSA - {}", err))?;
281 decrypt_key(&mut buffer[..decrypted], passphrase)
282}
73b50117
FG
283
284#[test]
285fn encrypt_decrypt_test() -> Result<(), Error> {
286 use openssl::bn::BigNum;
287
288 // hard-coded RSA key to avoid RNG load
289 let n = BigNum::from_dec_str("763297775503047285270249195475255390818773358873206395804367739392073195066702037300664538507287660511788773520960052020490020500131532096848837840341808263208238432840771609456175669299183585737297710099814398628316822920397690811697390531460556770185920171717205255045261184699028939408338685227311360280561223048029934992213591164033485740834987719043448066906674761591422028943934961140687347873900379335604823288576732038392698785999130614670054444889172406669687648738432457362496418067100853836965448514921778954255248154805294695304544857640397043149235605321195443660560591215396460879078079707598866525981810195613239506906019678159905700662365794059211182357495974678620101388841934629146959674859076053348229540838236896752745251734302737503775744293828247434369307467826891918526442390310055226655466835862319406221740752718258752129277114593279326227799698036058425261999904258111333276380007458144919278763944469942242338999234161147188585579934794573969834141472487673642778646170134259790130811461184848743147137198639341697548363179639042991358823669057297753206096865332303845149920379065177826748710006313272747133642274061146677367740923397358666767242901746171920401890395722806446280380164886804469750825832083").expect("converting to bignum failed");
290 let e = BigNum::from_dec_str("65537").expect("converting to bignum failed");
291 let d = BigNum::from_dec_str("19834537920284564853674022001226176519590018312725185651690468898251379391772488358073023011091610629897253174637151053464371346136136825929376853412608136964518211867003891708559549030570664609466682947037305962494828103719078802086159819263581307957743290849968728341884428605863043529798446388179368090663224786773806846388143274064254180335413340334940446739125488182098535411927937482988091512111514808559058456451259207186517021416246081401087976557460070014777577029793101223558164090029643622447657946212243306210181845486266030884899215596710196751196243890657122549917370139613045651724521564033154854414253451612565268626314358200247667906740226693180923631251719053819020017537699856142036238058103150388959616397059243552685604990510867544536282659146915388522812398795915840913802745825670833498941795568293354230962683054249223513028733221781409833526268687556063636480230666207346771664323325175723577540510559973905170578206847160551684632855673373061549848844186260938182413805301541655002820734307939021848604620517318497220269398148326924299176570233223593669359192722153811016413065311904503101005564780859010942238851216519088762587394817890851764597501374473176420295837906296738426781972820833509964922715585").expect("converting to bignum failed");
292 let p = BigNum::from_dec_str("29509637001892646371585718218450720181675215968655693119622290166463846337874978909899277049204111617901784460858811114760264767076166751445502024396748257412446297522757119324882999179307561418697097464139952930737249422485899639568595470472222197161276683797577982497955467948265299386993875583089675892019886767032750524889582030672594405810531152141432362873209548569385820623081973262550874468619670422387868884561012170536839449407663630232422905779693831681822257822783504983493794208329832510955061326579888576047912149807967610736616238778237407615015312695567289456675371922184276823263863231190560557676339").expect("converting to bignum failed");
293 let q = BigNum::from_dec_str("25866050993920799422553175902510303878636288340476152724026122959148470649546748310678170203350410878157245623372422271950639190884394436256045773535202161325882791039345330048364703416719823181485853395688815455066122599160191671526435061804017559815713791273329637690511813515454721229797045837580571003198471014420883727461348135261877384657284061678787895040009197824032371314493780688519536250146270701914875469190776765810821706480996720025323321483843112182646061748043938180130013308823672610860230340094502643614566152670758944502783858455501528490806234504795239898001698524105646533910560293336400403204897").expect("converting to bignum failed");
294 let dmp1 = BigNum::from_dec_str("21607770579166338313924278588690558922108583912962897316392792781303188398339022047518905458553289108745759383366535358272664077428797321640702979183532285223743426240475893650342331272664468275332046219832278884297711602396407401980831582724583041600551528176116883960387063733484217876666037528133838392148714866050744345765006980605100330287254053877398358630385580919903058731105447806937933747350668236714360621211130384969129674812319182867594036995223272269821421615266717078107026511273509659211002684589097654567453625356436054504001404801715927134738465685565147724902539753143706245247513141254140715042985").expect("converting to bignum failed");
295 let dmq1 = BigNum::from_dec_str("294824909477987048059069264677589712640818276551195295555907561384926187881828905626998384758270243160099828809057470393016578048898219996082612765778049262408020582364022789357590879232947921274546172186391582540158896220038500063021605980859684104892476037676079761887366292263067835858498149757735119694054623308549371262243115446856316376077501168409517640338844786525965200908293851935915491689568704919822573134038943559526432621897623477713604851434011395096458613085567264607124524187730342254186063812054159860538030670385536895853938115358646898433438472543479930479076991585011794266310458811393428158049").expect("converting to bignum failed");
296 let iqmp = BigNum::from_dec_str("19428066064824171668277167138275898936765006396600005071379051329779053619544399695639107933588871625444213173194462077344726482973273922001955114108600584475883837715007613468112455972196002915686862701860412263935895363086514864873592142686096117947515832613228762197577036084559813332497101195090727973644165586960538914545531208630624795512138060798977135902359295307626262953373309121954863020224150277262533638440848025788447039555055470985052690506486164836957350781708784380677438638580158751807723730202286612196281022183410822668814233870246463721184575820166925259871133457423401827024362448849298618281053").expect("converting to bignum failed");
297 let public =
298 openssl::rsa::Rsa::from_public_components(n.to_owned().unwrap(), e.to_owned().unwrap())
299 .expect("creating hard-coded RSA public key instance failed");
300 let private = openssl::rsa::Rsa::from_private_components(n, e, d, p, q, dmp1, dmq1, iqmp)
301 .expect("creating hard-coded RSA key instance failed");
302
303 let passphrase = || -> Result<Vec<u8>, Error> { Ok(Vec::new()) };
304
305 let key = KeyConfig {
306 kdf: None,
307 created: proxmox::tools::time::epoch_i64(),
308 modified: proxmox::tools::time::epoch_i64(),
309 data: (0u8..32u8).collect(),
310 fingerprint: Some(Fingerprint::new([
311 14, 171, 212, 70, 11, 110, 185, 202, 52, 80, 35, 222, 226, 183, 120, 199, 144, 229, 74,
312 22, 131, 185, 101, 156, 10, 87, 174, 25, 144, 144, 21, 155,
313 ])),
314 };
315
316 let encrypted = rsa_encrypt_key_config(public.clone(), &key).expect("encryption failed");
317 let (decrypted, created, fingerprint) =
318 rsa_decrypt_key_config(private.clone(), &encrypted, &passphrase)
319 .expect("decryption failed");
320
321 assert_eq!(key.created, created);
322 assert_eq!(key.data, decrypted);
323 assert_eq!(key.fingerprint, Some(fingerprint));
324
c0174285
FG
325 Ok(())
326}
327
328#[test]
329fn fingerprint_checks() -> Result<(), Error> {
e0af222e
FG
330 let key = KeyConfig {
331 kdf: None,
332 created: proxmox::tools::time::epoch_i64(),
333 modified: proxmox::tools::time::epoch_i64(),
334 data: (0u8..32u8).collect(),
335 fingerprint: Some(Fingerprint::new([0u8; 32])), // wrong FP
336 };
e0af222e 337
c0174285
FG
338 let expected_fingerprint = Fingerprint::new([
339 14, 171, 212, 70, 11, 110, 185, 202, 52, 80, 35, 222, 226, 183, 120, 199, 144, 229, 74,
340 22, 131, 185, 101, 156, 10, 87, 174, 25, 144, 144, 21, 155,
341 ]);
342
343 let mut data = serde_json::to_vec(&key).expect("encoding KeyConfig failed");
344 decrypt_key(&mut data, &{ || { Ok(Vec::new()) }}).expect_err("decoding KeyConfig with wrong fingerprint worked");
345
346 let key = KeyConfig {
347 kdf: None,
348 created: proxmox::tools::time::epoch_i64(),
349 modified: proxmox::tools::time::epoch_i64(),
350 data: (0u8..32u8).collect(),
351 fingerprint: None,
352 };
353
354
355 let mut data = serde_json::to_vec(&key).expect("encoding KeyConfig failed");
356 let (key_data, created, fingerprint) = decrypt_key(&mut data, &{ || { Ok(Vec::new()) }}).expect("decoding KeyConfig without fingerprint failed");
357
358 assert_eq!(key.data, key_data);
e0af222e 359 assert_eq!(key.created, created);
c0174285 360 assert_eq!(expected_fingerprint, fingerprint);
e0af222e 361
73b50117
FG
362 Ok(())
363}