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