]> git.proxmox.com Git - proxmox-backup.git/blame - src/config/tape_encryption_keys.rs
add pbs-tools subcrate
[proxmox-backup.git] / src / config / tape_encryption_keys.rs
CommitLineData
aff3e161
DM
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
d1d74c43 7//! version protected by password (see struct `KeyConfig`)
aff3e161
DM
8//!
9//! Tape backups store the password protected version on tape, so that
d1d74c43 10//! it is possible to restore the key from tape if you know the
aff3e161
DM
11//! password.
12
82a103c8 13use std::collections::HashMap;
d5a48b5c
DM
14
15use anyhow::{bail, Error};
16use serde::{Deserialize, Serialize};
d5a48b5c
DM
17
18use proxmox::tools::fs::{
19 file_read_optional_string,
20 replace_file,
feb1645f 21 open_file_locked,
d5a48b5c
DM
22 CreateOptions,
23};
24
25use crate::{
26 backup::{
27 Fingerprint,
feb1645f 28 KeyConfig,
d5a48b5c
DM
29 },
30};
31
32mod 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
7acd5c56 57/// Store Hardware Encryption keys (plain, unprotected keys)
d5a48b5c
DM
58#[derive(Deserialize, Serialize)]
59pub struct EncryptionKeyInfo {
aff3e161 60 /// Key fingerprint (we verify the fingerprint on load)
feb1645f 61 pub fingerprint: Fingerprint,
aff3e161 62 /// The plain encryption key
d5a48b5c
DM
63 #[serde(with = "hex_key")]
64 pub key: [u8; 32],
feb1645f
DM
65}
66
d5a48b5c 67impl EncryptionKeyInfo {
feb1645f
DM
68 pub fn new(key: [u8; 32], fingerprint: Fingerprint) -> Self {
69 Self { fingerprint, key }
70 }
71}
d5a48b5c 72
d5a48b5c 73pub const TAPE_KEYS_FILENAME: &str = "/etc/proxmox-backup/tape-encryption-keys.json";
feb1645f 74pub const TAPE_KEY_CONFIG_FILENAME: &str = "/etc/proxmox-backup/tape-encryption-key-config.json";
d5a48b5c
DM
75pub const TAPE_KEYS_LOCKFILE: &str = "/etc/proxmox-backup/.tape-encryption-keys.lck";
76
7acd5c56 77/// Load tape encryption keys (plain, unprotected keys)
d5a48b5c
DM
78pub 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
feb1645f 85 let key_list: Vec<EncryptionKeyInfo> = serde_json::from_str(&content)?;
d5a48b5c
DM
86
87 let mut map = HashMap::new();
feb1645f
DM
88
89 for item in key_list {
1c86893d
DM
90 let key_config = KeyConfig::without_password(item.key)?; // to compute fingerprint
91 let expected_fingerprint = key_config.fingerprint.unwrap();
d5a48b5c
DM
92 if item.fingerprint != expected_fingerprint {
93 bail!(
94 "inconsistent fingerprint ({} != {})",
95 item.fingerprint,
96 expected_fingerprint,
97 );
98 }
feb1645f
DM
99
100 if map.insert(item.fingerprint.clone(), item).is_some() {
101 bail!("found duplicate fingerprint");
102 }
d5a48b5c 103 }
feb1645f
DM
104
105 Ok((map, digest))
106}
107
7acd5c56 108/// Load tape encryption key configurations (password protected keys)
82a103c8 109pub fn load_key_configs() -> Result<(HashMap<Fingerprint, KeyConfig>, [u8;32]), Error> {
feb1645f
DM
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
82a103c8 116 let key_list: Vec<KeyConfig> = serde_json::from_str(&content)?;
feb1645f
DM
117
118 let mut map = HashMap::new();
feb1645f 119
82a103c8
DM
120 for key_config in key_list {
121 match key_config.fingerprint {
feb1645f 122 Some(ref fingerprint) => {
82a103c8 123 if map.insert(fingerprint.clone(), key_config).is_some() {
feb1645f
DM
124 bail!("found duplicate fingerprint");
125 }
126 }
127 None => bail!("missing fingerprint"),
128 }
129 }
130
d5a48b5c
DM
131 Ok((map, digest))
132}
133
aff3e161
DM
134/// Store tape encryption keys (plain, unprotected keys)
135///
136/// The file is only accessible by user root (mode 0600).
d5a48b5c
DM
137pub 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)?;
feb1645f 146
d5a48b5c
DM
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)?;
feb1645f 156
d5a48b5c
DM
157 Ok(())
158}
159
aff3e161 160/// Store tape encryption key configurations (password protected keys)
82a103c8 161pub fn save_key_configs(map: HashMap<Fingerprint, KeyConfig>) -> Result<(), Error> {
feb1645f
DM
162
163 let mut list = Vec::new();
164
feb1645f 165 for (_fp, item) in map {
feb1645f
DM
166 list.push(item);
167 }
168
169 let raw = serde_json::to_string_pretty(&list)?;
170
171 let backup_user = crate::backup::backup_user()?;
4dafc513 172 let mode = nix::sys::stat::Mode::from_bits_truncate(0o0640);
feb1645f
DM
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
aff3e161
DM
185/// Insert a new key
186///
187/// Get the lock, load both files, insert the new key, store files.
18bd6ba1 188pub fn insert_key(key: [u8;32], key_config: KeyConfig, force: bool) -> Result<(), Error> {
feb1645f
DM
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
18bd6ba1 204 if !force {
d543587d 205 if config_map.get(&fingerprint).is_some() {
18bd6ba1
DM
206 bail!("encryption key '{}' already exists.", fingerprint);
207 }
feb1645f
DM
208 }
209
210 let item = EncryptionKeyInfo::new(key, fingerprint.clone());
211 key_map.insert(fingerprint.clone(), item);
212 save_keys(key_map)?;
213
82a103c8 214 config_map.insert(fingerprint.clone(), key_config);
feb1645f
DM
215 save_key_configs(config_map)?;
216
217 Ok(())
feb1645f
DM
218}
219
d5a48b5c 220// shell completion helper
aff3e161 221/// Complete tape encryption key fingerprints
d5a48b5c 222pub fn complete_key_fingerprint(_arg: &str, _param: &HashMap<String, String>) -> Vec<String> {
feb1645f 223 let data = match load_key_configs() {
d5a48b5c
DM
224 Ok((data, _digest)) => data,
225 Err(_) => return Vec::new(),
226 };
227
770a36e5 228 data.keys().map(|fp| pbs_tools::format::as_fingerprint(fp.bytes())).collect()
d5a48b5c 229}