]> git.proxmox.com Git - proxmox-backup.git/blame - src/config/tape_encryption_keys.rs
clippy: is_some/none/ok/err/empty
[proxmox-backup.git] / src / config / tape_encryption_keys.rs
CommitLineData
feb1645f 1use std::collections::{HashSet, HashMap};
d5a48b5c
DM
2
3use anyhow::{bail, Error};
4use serde::{Deserialize, Serialize};
d5a48b5c
DM
5
6use proxmox::tools::fs::{
7 file_read_optional_string,
8 replace_file,
feb1645f 9 open_file_locked,
d5a48b5c
DM
10 CreateOptions,
11};
12
13use crate::{
14 backup::{
15 Fingerprint,
feb1645f
DM
16 Kdf,
17 KeyConfig,
18 CryptConfig,
19 encrypt_key_with_passphrase,
d5a48b5c
DM
20 },
21};
22
23mod hex_key {
24 use serde::{self, Deserialize, Serializer, Deserializer};
25
26 pub fn serialize<S>(
27 csum: &[u8; 32],
28 serializer: S,
29 ) -> Result<S::Ok, S::Error>
30 where
31 S: Serializer,
32 {
33 let s = proxmox::tools::digest_to_hex(csum);
34 serializer.serialize_str(&s)
35 }
36
37 pub fn deserialize<'de, D>(
38 deserializer: D,
39 ) -> Result<[u8; 32], D::Error>
40 where
41 D: Deserializer<'de>,
42 {
43 let s = String::deserialize(deserializer)?;
44 proxmox::tools::hex_to_digest(&s).map_err(serde::de::Error::custom)
45 }
46}
47
feb1645f 48/// Store Hardware Encryption keys (private part)
d5a48b5c
DM
49#[derive(Deserialize, Serialize)]
50pub struct EncryptionKeyInfo {
feb1645f 51 pub fingerprint: Fingerprint,
d5a48b5c
DM
52 #[serde(with = "hex_key")]
53 pub key: [u8; 32],
feb1645f
DM
54}
55
56/// Store Hardware Encryption keys (public part)
57#[derive(Deserialize, Serialize)]
58pub struct EncryptionKeyConfig {
59 pub hint: String,
60 pub key_config: KeyConfig,
61}
62
63pub fn compute_tape_key_fingerprint(key: &[u8; 32]) -> Result<Fingerprint, Error> {
44288184 64 let crypt_config = CryptConfig::new(*key)?;
feb1645f
DM
65 Ok(crypt_config.fingerprint())
66}
67
68pub fn generate_tape_encryption_key(password: &[u8]) -> Result<([u8; 32], KeyConfig), Error> {
69
70 let mut key = [0u8; 32];
71 proxmox::sys::linux::fill_with_random_data(&mut key)?;
72
73 let mut key_config = encrypt_key_with_passphrase(&key, password, Kdf::Scrypt)?;
74 key_config.fingerprint = Some(compute_tape_key_fingerprint(&key)?);
75 Ok((key, key_config))
d5a48b5c
DM
76}
77
78impl EncryptionKeyInfo {
feb1645f
DM
79 pub fn new(key: [u8; 32], fingerprint: Fingerprint) -> Self {
80 Self { fingerprint, key }
81 }
82}
d5a48b5c 83
feb1645f
DM
84impl EncryptionKeyConfig {
85 pub fn new(key_config: KeyConfig, hint: String) -> Self {
86 Self { hint, key_config }
d5a48b5c
DM
87 }
88}
89
90pub const TAPE_KEYS_FILENAME: &str = "/etc/proxmox-backup/tape-encryption-keys.json";
feb1645f 91pub const TAPE_KEY_CONFIG_FILENAME: &str = "/etc/proxmox-backup/tape-encryption-key-config.json";
d5a48b5c
DM
92pub const TAPE_KEYS_LOCKFILE: &str = "/etc/proxmox-backup/.tape-encryption-keys.lck";
93
feb1645f 94/// Load tape encryption keys (private part)
d5a48b5c
DM
95pub fn load_keys() -> Result<(HashMap<Fingerprint, EncryptionKeyInfo>, [u8;32]), Error> {
96
97 let content = file_read_optional_string(TAPE_KEYS_FILENAME)?;
98 let content = content.unwrap_or_else(|| String::from("[]"));
99
100 let digest = openssl::sha::sha256(content.as_bytes());
101
feb1645f 102 let key_list: Vec<EncryptionKeyInfo> = serde_json::from_str(&content)?;
d5a48b5c
DM
103
104 let mut map = HashMap::new();
feb1645f
DM
105
106 for item in key_list {
107 let expected_fingerprint = compute_tape_key_fingerprint(&item.key)?;
d5a48b5c
DM
108 if item.fingerprint != expected_fingerprint {
109 bail!(
110 "inconsistent fingerprint ({} != {})",
111 item.fingerprint,
112 expected_fingerprint,
113 );
114 }
feb1645f
DM
115
116 if map.insert(item.fingerprint.clone(), item).is_some() {
117 bail!("found duplicate fingerprint");
118 }
d5a48b5c 119 }
feb1645f
DM
120
121 Ok((map, digest))
122}
123
124/// Load tape encryption key configurations (public part)
125pub fn load_key_configs() -> Result<(HashMap<Fingerprint, EncryptionKeyConfig>, [u8;32]), Error> {
126
127 let content = file_read_optional_string(TAPE_KEY_CONFIG_FILENAME)?;
128 let content = content.unwrap_or_else(|| String::from("[]"));
129
130 let digest = openssl::sha::sha256(content.as_bytes());
131
132 let key_list: Vec<EncryptionKeyConfig> = serde_json::from_str(&content)?;
133
134 let mut map = HashMap::new();
135 let mut hint_set = HashSet::new();
136
137 for item in key_list {
138 match item.key_config.fingerprint {
139 Some(ref fingerprint) => {
140 if !hint_set.insert(item.hint.clone()) {
141 bail!("found duplicate password hint '{}'", item.hint);
142 }
143 if map.insert(fingerprint.clone(), item).is_some() {
144 bail!("found duplicate fingerprint");
145 }
146 }
147 None => bail!("missing fingerprint"),
148 }
149 }
150
d5a48b5c
DM
151 Ok((map, digest))
152}
153
154pub fn save_keys(map: HashMap<Fingerprint, EncryptionKeyInfo>) -> Result<(), Error> {
155
156 let mut list = Vec::new();
157
158 for (_fp, item) in map {
159 list.push(item);
160 }
161
162 let raw = serde_json::to_string_pretty(&list)?;
feb1645f 163
d5a48b5c
DM
164 let mode = nix::sys::stat::Mode::from_bits_truncate(0o0600);
165 // set the correct owner/group/permissions while saving file
166 // owner(rw) = root, group(r)= root
167 let options = CreateOptions::new()
168 .perm(mode)
169 .owner(nix::unistd::ROOT)
170 .group(nix::unistd::Gid::from_raw(0));
171
172 replace_file(TAPE_KEYS_FILENAME, raw.as_bytes(), options)?;
feb1645f 173
d5a48b5c
DM
174 Ok(())
175}
176
feb1645f
DM
177pub fn save_key_configs(map: HashMap<Fingerprint, EncryptionKeyConfig>) -> Result<(), Error> {
178
179 let mut list = Vec::new();
180
181 let mut hint_set = HashSet::new();
182
183 for (_fp, item) in map {
184 if !hint_set.insert(item.hint.clone()) {
185 bail!("found duplicate password hint '{}'", item.hint);
186 }
187 list.push(item);
188 }
189
190 let raw = serde_json::to_string_pretty(&list)?;
191
192 let backup_user = crate::backup::backup_user()?;
193 let mode = nix::sys::stat::Mode::from_bits_truncate(0o0600);
194 // set the correct owner/group/permissions while saving file
195 // owner(rw) = root, group(r)= backup
196 let options = CreateOptions::new()
197 .perm(mode)
198 .owner(nix::unistd::ROOT)
199 .group(backup_user.gid);
200
201 replace_file(TAPE_KEY_CONFIG_FILENAME, raw.as_bytes(), options)?;
202
203 Ok(())
204}
205
206pub fn insert_key(key: [u8;32], key_config: KeyConfig, hint: String) -> Result<(), Error> {
207
208 let _lock = open_file_locked(
209 TAPE_KEYS_LOCKFILE,
210 std::time::Duration::new(10, 0),
211 true,
212 )?;
213
214 let (mut key_map, _) = load_keys()?;
215 let (mut config_map, _) = load_key_configs()?;
216
217 let fingerprint = match key_config.fingerprint.clone() {
218 Some(fingerprint) => fingerprint,
219 None => bail!("missing encryption key fingerprint - internal error"),
220 };
221
3984a5fd 222 if config_map.get(&fingerprint).is_some() {
feb1645f
DM
223 bail!("encryption key '{}' already exists.", fingerprint);
224 }
225
226 let item = EncryptionKeyInfo::new(key, fingerprint.clone());
227 key_map.insert(fingerprint.clone(), item);
228 save_keys(key_map)?;
229
230 let item = EncryptionKeyConfig::new(key_config, hint);
44288184 231 config_map.insert(fingerprint, item);
feb1645f
DM
232 save_key_configs(config_map)?;
233
234 Ok(())
235
236}
237
d5a48b5c
DM
238// shell completion helper
239pub fn complete_key_fingerprint(_arg: &str, _param: &HashMap<String, String>) -> Vec<String> {
feb1645f 240 let data = match load_key_configs() {
d5a48b5c
DM
241 Ok((data, _digest)) => data,
242 Err(_) => return Vec::new(),
243 };
244
245 data.keys().map(|fp| crate::tools::format::as_fingerprint(fp.bytes())).collect()
246}