]> git.proxmox.com Git - proxmox-backup.git/blob - src/config/tape_encryption_keys.rs
42c3184dea0b50751c814156f38c37dfe50ffee2
[proxmox-backup.git] / src / config / tape_encryption_keys.rs
1 //! Store Tape encryptions keys
2 //!
3 //! This module can store 256bit encryption keys for tape backups,
4 //! indexed by key fingerprint.
5 //!
6 //! We store the plain key (unencrypted), as well as a encrypted
7 //! version protected by password (see struct `KeyConfig`)
8 //!
9 //! Tape backups store the password protected version on tape, so that
10 //! it is possible to restore the key from tape if you know the
11 //! password.
12
13 use std::collections::HashMap;
14
15 use anyhow::{bail, Error};
16 use serde::{Deserialize, Serialize};
17
18 use proxmox::tools::fs::{
19 file_read_optional_string,
20 replace_file,
21 open_file_locked,
22 CreateOptions,
23 };
24
25 use crate::{
26 backup::{
27 Fingerprint,
28 KeyConfig,
29 },
30 };
31
32 mod hex_key {
33 use serde::{self, Deserialize, Serializer, Deserializer};
34
35 pub fn serialize<S>(
36 csum: &[u8; 32],
37 serializer: S,
38 ) -> Result<S::Ok, S::Error>
39 where
40 S: Serializer,
41 {
42 let s = proxmox::tools::digest_to_hex(csum);
43 serializer.serialize_str(&s)
44 }
45
46 pub fn deserialize<'de, D>(
47 deserializer: D,
48 ) -> Result<[u8; 32], D::Error>
49 where
50 D: Deserializer<'de>,
51 {
52 let s = String::deserialize(deserializer)?;
53 proxmox::tools::hex_to_digest(&s).map_err(serde::de::Error::custom)
54 }
55 }
56
57 /// Store Hardware Encryption keys (plain, unprotected keys)
58 #[derive(Deserialize, Serialize)]
59 pub struct EncryptionKeyInfo {
60 /// Key fingerprint (we verify the fingerprint on load)
61 pub fingerprint: Fingerprint,
62 /// The plain encryption key
63 #[serde(with = "hex_key")]
64 pub key: [u8; 32],
65 }
66
67 impl EncryptionKeyInfo {
68 pub fn new(key: [u8; 32], fingerprint: Fingerprint) -> Self {
69 Self { fingerprint, key }
70 }
71 }
72
73 pub const TAPE_KEYS_FILENAME: &str = "/etc/proxmox-backup/tape-encryption-keys.json";
74 pub const TAPE_KEY_CONFIG_FILENAME: &str = "/etc/proxmox-backup/tape-encryption-key-config.json";
75 pub const TAPE_KEYS_LOCKFILE: &str = "/etc/proxmox-backup/.tape-encryption-keys.lck";
76
77 /// Load tape encryption keys (plain, unprotected keys)
78 pub fn load_keys() -> Result<(HashMap<Fingerprint, EncryptionKeyInfo>, [u8;32]), Error> {
79
80 let content = file_read_optional_string(TAPE_KEYS_FILENAME)?;
81 let content = content.unwrap_or_else(|| String::from("[]"));
82
83 let digest = openssl::sha::sha256(content.as_bytes());
84
85 let key_list: Vec<EncryptionKeyInfo> = serde_json::from_str(&content)?;
86
87 let mut map = HashMap::new();
88
89 for item in key_list {
90 let key_config = KeyConfig::without_password(item.key)?; // to compute fingerprint
91 let expected_fingerprint = key_config.fingerprint.unwrap();
92 if item.fingerprint != expected_fingerprint {
93 bail!(
94 "inconsistent fingerprint ({} != {})",
95 item.fingerprint,
96 expected_fingerprint,
97 );
98 }
99
100 if map.insert(item.fingerprint.clone(), item).is_some() {
101 bail!("found duplicate fingerprint");
102 }
103 }
104
105 Ok((map, digest))
106 }
107
108 /// Load tape encryption key configurations (password protected keys)
109 pub fn load_key_configs() -> Result<(HashMap<Fingerprint, KeyConfig>, [u8;32]), Error> {
110
111 let content = file_read_optional_string(TAPE_KEY_CONFIG_FILENAME)?;
112 let content = content.unwrap_or_else(|| String::from("[]"));
113
114 let digest = openssl::sha::sha256(content.as_bytes());
115
116 let key_list: Vec<KeyConfig> = serde_json::from_str(&content)?;
117
118 let mut map = HashMap::new();
119
120 for key_config in key_list {
121 match key_config.fingerprint {
122 Some(ref fingerprint) => {
123 if map.insert(fingerprint.clone(), key_config).is_some() {
124 bail!("found duplicate fingerprint");
125 }
126 }
127 None => bail!("missing fingerprint"),
128 }
129 }
130
131 Ok((map, digest))
132 }
133
134 /// Store tape encryption keys (plain, unprotected keys)
135 ///
136 /// The file is only accessible by user root (mode 0600).
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 /// Store tape encryption key configurations (password protected keys)
161 pub fn save_key_configs(map: HashMap<Fingerprint, KeyConfig>) -> Result<(), Error> {
162
163 let mut list = Vec::new();
164
165 for (_fp, item) in map {
166 list.push(item);
167 }
168
169 let raw = serde_json::to_string_pretty(&list)?;
170
171 let backup_user = crate::backup::backup_user()?;
172 let mode = nix::sys::stat::Mode::from_bits_truncate(0o0640);
173 // set the correct owner/group/permissions while saving file
174 // owner(rw) = root, group(r)= backup
175 let options = CreateOptions::new()
176 .perm(mode)
177 .owner(nix::unistd::ROOT)
178 .group(backup_user.gid);
179
180 replace_file(TAPE_KEY_CONFIG_FILENAME, raw.as_bytes(), options)?;
181
182 Ok(())
183 }
184
185 /// Insert a new key
186 ///
187 /// Get the lock, load both files, insert the new key, store files.
188 pub fn insert_key(key: [u8;32], key_config: KeyConfig, force: bool) -> Result<(), Error> {
189
190 let _lock = open_file_locked(
191 TAPE_KEYS_LOCKFILE,
192 std::time::Duration::new(10, 0),
193 true,
194 )?;
195
196 let (mut key_map, _) = load_keys()?;
197 let (mut config_map, _) = load_key_configs()?;
198
199 let fingerprint = match key_config.fingerprint.clone() {
200 Some(fingerprint) => fingerprint,
201 None => bail!("missing encryption key fingerprint - internal error"),
202 };
203
204 if !force {
205 if config_map.get(&fingerprint).is_some() {
206 bail!("encryption key '{}' already exists.", fingerprint);
207 }
208 }
209
210 let item = EncryptionKeyInfo::new(key, fingerprint.clone());
211 key_map.insert(fingerprint.clone(), item);
212 save_keys(key_map)?;
213
214 config_map.insert(fingerprint.clone(), key_config);
215 save_key_configs(config_map)?;
216
217 Ok(())
218 }
219
220 // shell completion helper
221 /// Complete tape encryption key fingerprints
222 pub fn complete_key_fingerprint(_arg: &str, _param: &HashMap<String, String>) -> Vec<String> {
223 let data = match load_key_configs() {
224 Ok((data, _digest)) => data,
225 Err(_) => return Vec::new(),
226 };
227
228 data.keys().map(|fp| crate::tools::format::as_fingerprint(fp.bytes())).collect()
229 }