]>
Commit | Line | Data |
---|---|---|
82a103c8 | 1 | use std::collections::HashMap; |
d5a48b5c DM |
2 | |
3 | use anyhow::{bail, Error}; | |
4 | use serde::{Deserialize, Serialize}; | |
d5a48b5c DM |
5 | |
6 | use proxmox::tools::fs::{ | |
7 | file_read_optional_string, | |
8 | replace_file, | |
feb1645f | 9 | open_file_locked, |
d5a48b5c DM |
10 | CreateOptions, |
11 | }; | |
12 | ||
13 | use crate::{ | |
14 | backup::{ | |
15 | Fingerprint, | |
feb1645f | 16 | KeyConfig, |
d5a48b5c DM |
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 | ||
7acd5c56 | 45 | /// Store Hardware Encryption keys (plain, unprotected keys) |
d5a48b5c DM |
46 | #[derive(Deserialize, Serialize)] |
47 | pub struct EncryptionKeyInfo { | |
feb1645f | 48 | pub fingerprint: Fingerprint, |
d5a48b5c DM |
49 | #[serde(with = "hex_key")] |
50 | pub key: [u8; 32], | |
feb1645f DM |
51 | } |
52 | ||
d5a48b5c | 53 | impl EncryptionKeyInfo { |
feb1645f DM |
54 | pub fn new(key: [u8; 32], fingerprint: Fingerprint) -> Self { |
55 | Self { fingerprint, key } | |
56 | } | |
57 | } | |
d5a48b5c | 58 | |
d5a48b5c | 59 | pub const TAPE_KEYS_FILENAME: &str = "/etc/proxmox-backup/tape-encryption-keys.json"; |
feb1645f | 60 | pub const TAPE_KEY_CONFIG_FILENAME: &str = "/etc/proxmox-backup/tape-encryption-key-config.json"; |
d5a48b5c DM |
61 | pub const TAPE_KEYS_LOCKFILE: &str = "/etc/proxmox-backup/.tape-encryption-keys.lck"; |
62 | ||
7acd5c56 | 63 | /// Load tape encryption keys (plain, unprotected keys) |
d5a48b5c DM |
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 | ||
feb1645f | 71 | let key_list: Vec<EncryptionKeyInfo> = serde_json::from_str(&content)?; |
d5a48b5c DM |
72 | |
73 | let mut map = HashMap::new(); | |
feb1645f DM |
74 | |
75 | for item in key_list { | |
1c86893d DM |
76 | let key_config = KeyConfig::without_password(item.key)?; // to compute fingerprint |
77 | let expected_fingerprint = key_config.fingerprint.unwrap(); | |
d5a48b5c DM |
78 | if item.fingerprint != expected_fingerprint { |
79 | bail!( | |
80 | "inconsistent fingerprint ({} != {})", | |
81 | item.fingerprint, | |
82 | expected_fingerprint, | |
83 | ); | |
84 | } | |
feb1645f DM |
85 | |
86 | if map.insert(item.fingerprint.clone(), item).is_some() { | |
87 | bail!("found duplicate fingerprint"); | |
88 | } | |
d5a48b5c | 89 | } |
feb1645f DM |
90 | |
91 | Ok((map, digest)) | |
92 | } | |
93 | ||
7acd5c56 | 94 | /// Load tape encryption key configurations (password protected keys) |
82a103c8 | 95 | pub fn load_key_configs() -> Result<(HashMap<Fingerprint, KeyConfig>, [u8;32]), Error> { |
feb1645f DM |
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 | ||
82a103c8 | 102 | let key_list: Vec<KeyConfig> = serde_json::from_str(&content)?; |
feb1645f DM |
103 | |
104 | let mut map = HashMap::new(); | |
feb1645f | 105 | |
82a103c8 DM |
106 | for key_config in key_list { |
107 | match key_config.fingerprint { | |
feb1645f | 108 | Some(ref fingerprint) => { |
82a103c8 | 109 | if map.insert(fingerprint.clone(), key_config).is_some() { |
feb1645f DM |
110 | bail!("found duplicate fingerprint"); |
111 | } | |
112 | } | |
113 | None => bail!("missing fingerprint"), | |
114 | } | |
115 | } | |
116 | ||
d5a48b5c DM |
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)?; | |
feb1645f | 129 | |
d5a48b5c DM |
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)?; | |
feb1645f | 139 | |
d5a48b5c DM |
140 | Ok(()) |
141 | } | |
142 | ||
82a103c8 | 143 | pub fn save_key_configs(map: HashMap<Fingerprint, KeyConfig>) -> Result<(), Error> { |
feb1645f DM |
144 | |
145 | let mut list = Vec::new(); | |
146 | ||
feb1645f | 147 | for (_fp, item) in map { |
feb1645f DM |
148 | list.push(item); |
149 | } | |
150 | ||
151 | let raw = serde_json::to_string_pretty(&list)?; | |
152 | ||
153 | let backup_user = crate::backup::backup_user()?; | |
4dafc513 | 154 | let mode = nix::sys::stat::Mode::from_bits_truncate(0o0640); |
feb1645f DM |
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 | ||
18bd6ba1 | 167 | pub fn insert_key(key: [u8;32], key_config: KeyConfig, force: bool) -> Result<(), Error> { |
feb1645f DM |
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 | ||
18bd6ba1 | 183 | if !force { |
d543587d | 184 | if config_map.get(&fingerprint).is_some() { |
18bd6ba1 DM |
185 | bail!("encryption key '{}' already exists.", fingerprint); |
186 | } | |
feb1645f DM |
187 | } |
188 | ||
189 | let item = EncryptionKeyInfo::new(key, fingerprint.clone()); | |
190 | key_map.insert(fingerprint.clone(), item); | |
191 | save_keys(key_map)?; | |
192 | ||
82a103c8 | 193 | config_map.insert(fingerprint.clone(), key_config); |
feb1645f DM |
194 | save_key_configs(config_map)?; |
195 | ||
196 | Ok(()) | |
feb1645f DM |
197 | } |
198 | ||
d5a48b5c DM |
199 | // shell completion helper |
200 | pub fn complete_key_fingerprint(_arg: &str, _param: &HashMap<String, String>) -> Vec<String> { | |
feb1645f | 201 | let data = match load_key_configs() { |
d5a48b5c DM |
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 | } |