1 use std
::collections
::{HashSet, HashMap}
;
3 use anyhow
::{bail, Error}
;
4 use serde
::{Deserialize, Serialize}
;
6 use proxmox
::tools
::fs
::{
7 file_read_optional_string
,
19 encrypt_key_with_passphrase
,
24 use serde
::{self, Deserialize, Serializer, Deserializer}
;
29 ) -> Result
<S
::Ok
, S
::Error
>
33 let s
= proxmox
::tools
::digest_to_hex(csum
);
34 serializer
.serialize_str(&s
)
37 pub fn deserialize
<'de
, D
>(
39 ) -> Result
<[u8; 32], D
::Error
>
43 let s
= String
::deserialize(deserializer
)?
;
44 proxmox
::tools
::hex_to_digest(&s
).map_err(serde
::de
::Error
::custom
)
48 /// Store Hardware Encryption keys (private part)
49 #[derive(Deserialize, Serialize)]
50 pub struct EncryptionKeyInfo
{
51 pub fingerprint
: Fingerprint
,
52 #[serde(with = "hex_key")]
56 /// Store Hardware Encryption keys (public part)
57 #[derive(Deserialize, Serialize)]
58 pub struct EncryptionKeyConfig
{
60 pub key_config
: KeyConfig
,
63 pub fn compute_tape_key_fingerprint(key
: &[u8; 32]) -> Result
<Fingerprint
, Error
> {
64 let crypt_config
= CryptConfig
::new(*key
)?
;
65 Ok(crypt_config
.fingerprint())
68 pub fn generate_tape_encryption_key(password
: &[u8]) -> Result
<([u8; 32], KeyConfig
), Error
> {
70 let mut key
= [0u8; 32];
71 proxmox
::sys
::linux
::fill_with_random_data(&mut key
)?
;
73 let mut key_config
= encrypt_key_with_passphrase(&key
, password
, Kdf
::Scrypt
)?
;
74 key_config
.fingerprint
= Some(compute_tape_key_fingerprint(&key
)?
);
78 impl EncryptionKeyInfo
{
79 pub fn new(key
: [u8; 32], fingerprint
: Fingerprint
) -> Self {
80 Self { fingerprint, key }
84 impl EncryptionKeyConfig
{
85 pub fn new(key_config
: KeyConfig
, hint
: String
) -> Self {
86 Self { hint, key_config }
90 pub const TAPE_KEYS_FILENAME
: &str = "/etc/proxmox-backup/tape-encryption-keys.json";
91 pub const TAPE_KEY_CONFIG_FILENAME
: &str = "/etc/proxmox-backup/tape-encryption-key-config.json";
92 pub const TAPE_KEYS_LOCKFILE
: &str = "/etc/proxmox-backup/.tape-encryption-keys.lck";
94 /// Load tape encryption keys (private part)
95 pub fn load_keys() -> Result
<(HashMap
<Fingerprint
, EncryptionKeyInfo
>, [u8;32]), Error
> {
97 let content
= file_read_optional_string(TAPE_KEYS_FILENAME
)?
;
98 let content
= content
.unwrap_or_else(|| String
::from("[]"));
100 let digest
= openssl
::sha
::sha256(content
.as_bytes());
102 let key_list
: Vec
<EncryptionKeyInfo
> = serde_json
::from_str(&content
)?
;
104 let mut map
= HashMap
::new();
106 for item
in key_list
{
107 let expected_fingerprint
= compute_tape_key_fingerprint(&item
.key
)?
;
108 if item
.fingerprint
!= expected_fingerprint
{
110 "inconsistent fingerprint ({} != {})",
112 expected_fingerprint
,
116 if map
.insert(item
.fingerprint
.clone(), item
).is_some() {
117 bail
!("found duplicate fingerprint");
124 /// Load tape encryption key configurations (public part)
125 pub fn load_key_configs() -> Result
<(HashMap
<Fingerprint
, EncryptionKeyConfig
>, [u8;32]), Error
> {
127 let content
= file_read_optional_string(TAPE_KEY_CONFIG_FILENAME
)?
;
128 let content
= content
.unwrap_or_else(|| String
::from("[]"));
130 let digest
= openssl
::sha
::sha256(content
.as_bytes());
132 let key_list
: Vec
<EncryptionKeyConfig
> = serde_json
::from_str(&content
)?
;
134 let mut map
= HashMap
::new();
135 let mut hint_set
= HashSet
::new();
137 for item
in key_list
{
138 match item
.key_config
.fingerprint
{
139 Some(ref fingerprint
) => {
140 if !hint_set
.insert(item
.hint
.clone()) {
141 bail
!("found duplicate password hint '{}'", item
.hint
);
143 if map
.insert(fingerprint
.clone(), item
).is_some() {
144 bail
!("found duplicate fingerprint");
147 None
=> bail
!("missing fingerprint"),
154 pub fn save_keys(map
: HashMap
<Fingerprint
, EncryptionKeyInfo
>) -> Result
<(), Error
> {
156 let mut list
= Vec
::new();
158 for (_fp
, item
) in map
{
162 let raw
= serde_json
::to_string_pretty(&list
)?
;
164 let mode
= nix
::sys
::stat
::Mode
::from_bits_truncate(0o0600);
165 // set the correct owner/group/permissions while saving file
166 // owner(rw) = root, group(r)= root
167 let options
= CreateOptions
::new()
169 .owner(nix
::unistd
::ROOT
)
170 .group(nix
::unistd
::Gid
::from_raw(0));
172 replace_file(TAPE_KEYS_FILENAME
, raw
.as_bytes(), options
)?
;
177 pub fn save_key_configs(map
: HashMap
<Fingerprint
, EncryptionKeyConfig
>) -> Result
<(), Error
> {
179 let mut list
= Vec
::new();
181 let mut hint_set
= HashSet
::new();
183 for (_fp
, item
) in map
{
184 if !hint_set
.insert(item
.hint
.clone()) {
185 bail
!("found duplicate password hint '{}'", item
.hint
);
190 let raw
= serde_json
::to_string_pretty(&list
)?
;
192 let backup_user
= crate::backup
::backup_user()?
;
193 let mode
= nix
::sys
::stat
::Mode
::from_bits_truncate(0o0600);
194 // set the correct owner/group/permissions while saving file
195 // owner(rw) = root, group(r)= backup
196 let options
= CreateOptions
::new()
198 .owner(nix
::unistd
::ROOT
)
199 .group(backup_user
.gid
);
201 replace_file(TAPE_KEY_CONFIG_FILENAME
, raw
.as_bytes(), options
)?
;
206 pub fn insert_key(key
: [u8;32], key_config
: KeyConfig
, hint
: String
) -> Result
<(), Error
> {
208 let _lock
= open_file_locked(
210 std
::time
::Duration
::new(10, 0),
214 let (mut key_map
, _
) = load_keys()?
;
215 let (mut config_map
, _
) = load_key_configs()?
;
217 let fingerprint
= match key_config
.fingerprint
.clone() {
218 Some(fingerprint
) => fingerprint
,
219 None
=> bail
!("missing encryption key fingerprint - internal error"),
222 if let Some(_
) = config_map
.get(&fingerprint
) {
223 bail
!("encryption key '{}' already exists.", fingerprint
);
226 let item
= EncryptionKeyInfo
::new(key
, fingerprint
.clone());
227 key_map
.insert(fingerprint
.clone(), item
);
230 let item
= EncryptionKeyConfig
::new(key_config
, hint
);
231 config_map
.insert(fingerprint
, item
);
232 save_key_configs(config_map
)?
;
238 // shell completion helper
239 pub fn complete_key_fingerprint(_arg
: &str, _param
: &HashMap
<String
, String
>) -> Vec
<String
> {
240 let data
= match load_key_configs() {
241 Ok((data
, _digest
)) => data
,
242 Err(_
) => return Vec
::new(),
245 data
.keys().map(|fp
| crate::tools
::format
::as_fingerprint(fp
.bytes())).collect()