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