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