]>
Commit | Line | Data |
---|---|---|
b65390eb | 1 | use anyhow::{bail, format_err, Context, Error}; |
826f309b DM |
2 | |
3 | use serde::{Deserialize, Serialize}; | |
826f309b | 4 | |
37e60ddc FG |
5 | use crate::backup::{CryptConfig, Fingerprint}; |
6 | ||
9ea4bce4 WB |
7 | use proxmox::tools::fs::{file_get_contents, replace_file, CreateOptions}; |
8 | use proxmox::try_block; | |
e18a6c9e | 9 | |
826f309b DM |
10 | #[derive(Deserialize, Serialize, Debug)] |
11 | pub 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 | ||
26 | impl 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)] | |
63 | pub 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 |
76 | pub 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 | 108 | pub 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 |
154 | pub 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 |
162 | fn 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 |
169 | pub 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 | } |