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