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