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