]>
Commit | Line | Data |
---|---|---|
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 | 13 | use std::collections::HashMap; |
d5a48b5c DM |
14 | |
15 | use anyhow::{bail, Error}; | |
16 | use serde::{Deserialize, Serialize}; | |
d5a48b5c DM |
17 | |
18 | use proxmox::tools::fs::{ | |
19 | file_read_optional_string, | |
20 | replace_file, | |
21 | CreateOptions, | |
22 | }; | |
23 | ||
24 | use crate::{ | |
25 | backup::{ | |
7526d864 | 26 | open_backup_lockfile, |
d5a48b5c | 27 | Fingerprint, |
feb1645f | 28 | KeyConfig, |
d5a48b5c DM |
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 | ||
7acd5c56 | 57 | /// Store Hardware Encryption keys (plain, unprotected keys) |
d5a48b5c DM |
58 | #[derive(Deserialize, Serialize)] |
59 | pub 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 | 67 | impl EncryptionKeyInfo { |
feb1645f DM |
68 | pub fn new(key: [u8; 32], fingerprint: Fingerprint) -> Self { |
69 | Self { fingerprint, key } | |
70 | } | |
71 | } | |
d5a48b5c | 72 | |
d5a48b5c | 73 | pub const TAPE_KEYS_FILENAME: &str = "/etc/proxmox-backup/tape-encryption-keys.json"; |
feb1645f | 74 | pub const TAPE_KEY_CONFIG_FILENAME: &str = "/etc/proxmox-backup/tape-encryption-key-config.json"; |
d5a48b5c DM |
75 | pub const TAPE_KEYS_LOCKFILE: &str = "/etc/proxmox-backup/.tape-encryption-keys.lck"; |
76 | ||
7acd5c56 | 77 | /// Load tape encryption keys (plain, unprotected keys) |
d5a48b5c DM |
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 | ||
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 | 109 | pub 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 |
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)?; | |
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 | 161 | pub 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 | 188 | pub fn insert_key(key: [u8;32], key_config: KeyConfig, force: bool) -> Result<(), Error> { |
feb1645f | 189 | |
7526d864 | 190 | let _lock = open_backup_lockfile(TAPE_KEYS_LOCKFILE, None, true)?; |
feb1645f DM |
191 | |
192 | let (mut key_map, _) = load_keys()?; | |
193 | let (mut config_map, _) = load_key_configs()?; | |
194 | ||
195 | let fingerprint = match key_config.fingerprint.clone() { | |
196 | Some(fingerprint) => fingerprint, | |
197 | None => bail!("missing encryption key fingerprint - internal error"), | |
198 | }; | |
199 | ||
18bd6ba1 | 200 | if !force { |
d543587d | 201 | if config_map.get(&fingerprint).is_some() { |
18bd6ba1 DM |
202 | bail!("encryption key '{}' already exists.", fingerprint); |
203 | } | |
feb1645f DM |
204 | } |
205 | ||
206 | let item = EncryptionKeyInfo::new(key, fingerprint.clone()); | |
207 | key_map.insert(fingerprint.clone(), item); | |
208 | save_keys(key_map)?; | |
209 | ||
82a103c8 | 210 | config_map.insert(fingerprint.clone(), key_config); |
feb1645f DM |
211 | save_key_configs(config_map)?; |
212 | ||
213 | Ok(()) | |
feb1645f DM |
214 | } |
215 | ||
d5a48b5c | 216 | // shell completion helper |
aff3e161 | 217 | /// Complete tape encryption key fingerprints |
d5a48b5c | 218 | pub fn complete_key_fingerprint(_arg: &str, _param: &HashMap<String, String>) -> Vec<String> { |
feb1645f | 219 | let data = match load_key_configs() { |
d5a48b5c DM |
220 | Ok((data, _digest)) => data, |
221 | Err(_) => return Vec::new(), | |
222 | }; | |
223 | ||
770a36e5 | 224 | data.keys().map(|fp| pbs_tools::format::as_fingerprint(fp.bytes())).collect() |
d5a48b5c | 225 | } |