1 use std
::convert
::TryFrom
;
2 use std
::path
::PathBuf
;
3 use std
::os
::unix
::io
::{FromRawFd, RawFd}
;
6 use anyhow
::{bail, format_err, Error}
;
9 use proxmox
::api
::schema
::*;
10 use proxmox
::sys
::linux
::tty
;
11 use proxmox
::tools
::fs
::file_get_contents
;
13 use proxmox_backup
::backup
::CryptMode
;
15 pub const DEFAULT_ENCRYPTION_KEY_FILE_NAME
: &str = "encryption-key.json";
16 pub const DEFAULT_MASTER_PUBKEY_FILE_NAME
: &str = "master-public.pem";
18 pub const KEYFILE_SCHEMA
: Schema
=
19 StringSchema
::new("Path to encryption key. All data will be encrypted using this key.")
22 pub const KEYFD_SCHEMA
: Schema
=
23 IntegerSchema
::new("Pass an encryption key via an already opened file descriptor.")
27 pub const MASTER_PUBKEY_FILE_SCHEMA
: Schema
= StringSchema
::new(
28 "Path to master public key. The encryption key used for a backup will be encrypted using this key and appended to the backup.")
31 pub const MASTER_PUBKEY_FD_SCHEMA
: Schema
=
32 IntegerSchema
::new("Pass a master public key via an already opened file descriptor.")
36 #[derive(Clone, Debug, Eq, PartialEq)]
43 pub fn format_key_source(source
: &KeySource
, key_type
: &str) -> String
{
45 KeySource
::DefaultKey
=> format
!("Using default {} key..", key_type
),
46 KeySource
::Fd
=> format
!("Using {} key from file descriptor..", key_type
),
47 KeySource
::Path(path
) => format
!("Using {} key from '{}'..", key_type
, path
),
51 #[derive(Clone, Debug, Eq, PartialEq)]
52 pub struct KeyWithSource
{
53 pub source
: KeySource
,
58 pub fn from_fd(key
: Vec
<u8>) -> Self {
60 source
: KeySource
::Fd
,
65 pub fn from_default(key
: Vec
<u8>) -> Self {
67 source
: KeySource
::DefaultKey
,
72 pub fn from_path(path
: String
, key
: Vec
<u8>) -> Self {
74 source
: KeySource
::Path(path
),
80 #[derive(Debug, Eq, PartialEq)]
81 pub struct CryptoParams
{
83 pub enc_key
: Option
<KeyWithSource
>,
84 // FIXME switch to openssl::rsa::rsa<openssl::pkey::Public> once that is Eq?
85 pub master_pubkey
: Option
<KeyWithSource
>,
88 pub fn crypto_parameters(param
: &Value
) -> Result
<CryptoParams
, Error
> {
89 do_crypto_parameters(param
, false)
92 pub fn crypto_parameters_keep_fd(param
: &Value
) -> Result
<CryptoParams
, Error
> {
93 do_crypto_parameters(param
, true)
96 fn do_crypto_parameters(param
: &Value
, keep_keyfd_open
: bool
) -> Result
<CryptoParams
, Error
> {
97 let keyfile
= match param
.get("keyfile") {
98 Some(Value
::String(keyfile
)) => Some(keyfile
),
99 Some(_
) => bail
!("bad --keyfile parameter type"),
103 let key_fd
= match param
.get("keyfd") {
104 Some(Value
::Number(key_fd
)) => Some(
105 RawFd
::try_from(key_fd
107 .ok_or_else(|| format_err
!("bad key fd: {:?}", key_fd
))?
109 .map_err(|err
| format_err
!("bad key fd: {:?}: {}", key_fd
, err
))?
111 Some(_
) => bail
!("bad --keyfd parameter type"),
115 let master_pubkey_file
= match param
.get("master-pubkey-file") {
116 Some(Value
::String(keyfile
)) => Some(keyfile
),
117 Some(_
) => bail
!("bad --master-pubkey-file parameter type"),
121 let master_pubkey_fd
= match param
.get("master-pubkey-fd") {
122 Some(Value
::Number(key_fd
)) => Some(
123 RawFd
::try_from(key_fd
125 .ok_or_else(|| format_err
!("bad master public key fd: {:?}", key_fd
))?
127 .map_err(|err
| format_err
!("bad public master key fd: {:?}: {}", key_fd
, err
))?
129 Some(_
) => bail
!("bad --master-pubkey-fd parameter type"),
133 let mode
: Option
<CryptMode
> = match param
.get("crypt-mode") {
134 Some(mode
) => Some(serde_json
::from_value(mode
.clone())?
),
138 let key
= match (keyfile
, key_fd
) {
139 (None
, None
) => None
,
140 (Some(_
), Some(_
)) => bail
!("--keyfile and --keyfd are mutually exclusive"),
141 (Some(keyfile
), None
) => Some(KeyWithSource
::from_path(
143 file_get_contents(keyfile
)?
,
145 (None
, Some(fd
)) => {
146 let mut input
= unsafe { std::fs::File::from_raw_fd(fd) }
;
147 let mut data
= Vec
::new();
148 let _len
: usize = input
.read_to_end(&mut data
).map_err(|err
| {
149 format_err
!("error reading encryption key from fd {}: {}", fd
, err
)
152 // don't close fd if requested, and try to reset seek position
153 std
::mem
::forget(input
);
154 unsafe { libc::lseek(fd, 0, libc::SEEK_SET); }
156 Some(KeyWithSource
::from_fd(data
))
160 let master_pubkey
= match (master_pubkey_file
, master_pubkey_fd
) {
161 (None
, None
) => None
,
162 (Some(_
), Some(_
)) => bail
!("--keyfile and --keyfd are mutually exclusive"),
163 (Some(keyfile
), None
) => Some(KeyWithSource
::from_path(
165 file_get_contents(keyfile
)?
,
167 (None
, Some(fd
)) => {
168 let input
= unsafe { std::fs::File::from_raw_fd(fd) }
;
169 let mut data
= Vec
::new();
170 let _len
: usize = { input }
171 .read_to_end(&mut data
)
172 .map_err(|err
| format_err
!("error reading master key from fd {}: {}", fd
, err
))?
;
173 Some(KeyWithSource
::from_fd(data
))
177 let res
= match mode
{
178 // no crypt mode, enable encryption if keys are available
179 None
=> match (key
, master_pubkey
) {
180 // only default keys if available
181 (None
, None
) => match read_optional_default_encryption_key()?
{
182 None
=> CryptoParams { mode: CryptMode::None, enc_key: None, master_pubkey: None }
,
184 let master_pubkey
= read_optional_default_master_pubkey()?
;
186 mode
: CryptMode
::Encrypt
,
193 // explicit master key, default enc key needed
194 (None
, master_pubkey
) => match read_optional_default_encryption_key()?
{
195 None
=> bail
!("--master-pubkey-file/--master-pubkey-fd specified, but no key available"),
198 mode
: CryptMode
::Encrypt
,
205 // explicit keyfile, maybe default master key
206 (enc_key
, None
) => CryptoParams { mode: CryptMode::Encrypt, enc_key, master_pubkey: read_optional_default_master_pubkey()? }
,
208 // explicit keyfile and master key
209 (enc_key
, master_pubkey
) => CryptoParams { mode: CryptMode::Encrypt, enc_key, master_pubkey }
,
212 // explicitly disabled encryption
213 Some(CryptMode
::None
) => match (key
, master_pubkey
) {
214 // no keys => OK, no encryption
215 (None
, None
) => CryptoParams { mode: CryptMode::None, enc_key: None, master_pubkey: None }
,
217 // --keyfile and --crypt-mode=none
218 (Some(_
), _
) => bail
!("--keyfile/--keyfd and --crypt-mode=none are mutually exclusive"),
220 // --master-pubkey-file and --crypt-mode=none
221 (_
, Some(_
)) => bail
!("--master-pubkey-file/--master-pubkey-fd and --crypt-mode=none are mutually exclusive"),
224 // explicitly enabled encryption
225 Some(mode
) => match (key
, master_pubkey
) {
226 // no key, maybe master key
227 (None
, master_pubkey
) => match read_optional_default_encryption_key()?
{
228 None
=> bail
!("--crypt-mode without --keyfile and no default key file available"),
230 eprintln
!("Encrypting with default encryption key!");
231 let master_pubkey
= match master_pubkey
{
232 None
=> read_optional_default_master_pubkey()?
,
233 master_pubkey
=> master_pubkey
,
244 // --keyfile and --crypt-mode other than none
245 (enc_key
, master_pubkey
) => {
246 let master_pubkey
= match master_pubkey
{
247 None
=> read_optional_default_master_pubkey()?
,
248 master_pubkey
=> master_pubkey
,
251 CryptoParams { mode, enc_key, master_pubkey }
259 pub fn find_default_master_pubkey() -> Result
<Option
<PathBuf
>, Error
> {
260 super::find_xdg_file(
261 DEFAULT_MASTER_PUBKEY_FILE_NAME
,
262 "default master public key file",
266 pub fn place_default_master_pubkey() -> Result
<PathBuf
, Error
> {
267 super::place_xdg_file(
268 DEFAULT_MASTER_PUBKEY_FILE_NAME
,
269 "default master public key file",
273 pub fn find_default_encryption_key() -> Result
<Option
<PathBuf
>, Error
> {
274 super::find_xdg_file(
275 DEFAULT_ENCRYPTION_KEY_FILE_NAME
,
276 "default encryption key file",
280 pub fn place_default_encryption_key() -> Result
<PathBuf
, Error
> {
281 super::place_xdg_file(
282 DEFAULT_ENCRYPTION_KEY_FILE_NAME
,
283 "default encryption key file",
288 pub(crate) fn read_optional_default_encryption_key() -> Result
<Option
<KeyWithSource
>, Error
> {
289 find_default_encryption_key()?
290 .map(|path
| file_get_contents(path
).map(KeyWithSource
::from_default
))
295 pub(crate) fn read_optional_default_master_pubkey() -> Result
<Option
<KeyWithSource
>, Error
> {
296 find_default_master_pubkey()?
297 .map(|path
| file_get_contents(path
).map(KeyWithSource
::from_default
))
302 static mut TEST_DEFAULT_ENCRYPTION_KEY
: Result
<Option
<Vec
<u8>>, Error
> = Ok(None
);
305 pub(crate) fn read_optional_default_encryption_key() -> Result
<Option
<KeyWithSource
>, Error
> {
306 // not safe when multiple concurrent test cases end up here!
308 match &TEST_DEFAULT_ENCRYPTION_KEY
{
309 Ok(Some(key
)) => Ok(Some(KeyWithSource
::from_default(key
.clone()))),
310 Ok(None
) => Ok(None
),
311 Err(_
) => bail
!("test error"),
317 // not safe when multiple concurrent test cases end up here!
318 pub(crate) unsafe fn set_test_encryption_key(value
: Result
<Option
<Vec
<u8>>, Error
>) {
319 TEST_DEFAULT_ENCRYPTION_KEY
= value
;
323 static mut TEST_DEFAULT_MASTER_PUBKEY
: Result
<Option
<Vec
<u8>>, Error
> = Ok(None
);
326 pub(crate) fn read_optional_default_master_pubkey() -> Result
<Option
<KeyWithSource
>, Error
> {
327 // not safe when multiple concurrent test cases end up here!
329 match &TEST_DEFAULT_MASTER_PUBKEY
{
330 Ok(Some(key
)) => Ok(Some(KeyWithSource
::from_default(key
.clone()))),
331 Ok(None
) => Ok(None
),
332 Err(_
) => bail
!("test error"),
338 // not safe when multiple concurrent test cases end up here!
339 pub(crate) unsafe fn set_test_default_master_pubkey(value
: Result
<Option
<Vec
<u8>>, Error
>) {
340 TEST_DEFAULT_MASTER_PUBKEY
= value
;
343 pub fn get_encryption_key_password() -> Result
<Vec
<u8>, Error
> {
344 // fixme: implement other input methods
346 use std
::env
::VarError
::*;
347 match std
::env
::var("PBS_ENCRYPTION_PASSWORD") {
348 Ok(p
) => return Ok(p
.as_bytes().to_vec()),
349 Err(NotUnicode(_
)) => bail
!("PBS_ENCRYPTION_PASSWORD contains bad characters"),
351 // Try another method
355 // If we're on a TTY, query the user for a password
356 if tty
::stdin_isatty() {
357 return Ok(tty
::read_password("Encryption Key Password: ")?
);
360 bail
!("no password input mechanism available");
364 // WARNING: there must only be one test for crypto_parameters as the default key handling is not
365 // safe w.r.t. concurrency
366 fn test_crypto_parameters_handling() -> Result
<(), Error
> {
367 use serde_json
::json
;
368 use proxmox
::tools
::fs
::{replace_file, CreateOptions}
;
370 let some_key
= vec
![1;1];
371 let default_key
= vec
![2;1];
373 let some_master_key
= vec
![3;1];
374 let default_master_key
= vec
![4;1];
376 let keypath
= "./target/testout/keyfile.test";
377 let master_keypath
= "./target/testout/masterkeyfile.test";
378 let invalid_keypath
= "./target/testout/invalid_keyfile.test";
380 let no_key_res
= CryptoParams
{
383 mode
: CryptMode
::None
,
385 let some_key_res
= CryptoParams
{
386 enc_key
: Some(KeyWithSource
::from_path(
391 mode
: CryptMode
::Encrypt
,
393 let some_key_some_master_res
= CryptoParams
{
394 enc_key
: Some(KeyWithSource
::from_path(
398 master_pubkey
: Some(KeyWithSource
::from_path(
399 master_keypath
.to_string(),
400 some_master_key
.clone(),
402 mode
: CryptMode
::Encrypt
,
404 let some_key_default_master_res
= CryptoParams
{
405 enc_key
: Some(KeyWithSource
::from_path(
409 master_pubkey
: Some(KeyWithSource
::from_default(default_master_key
.clone())),
410 mode
: CryptMode
::Encrypt
,
413 let some_key_sign_res
= CryptoParams
{
414 enc_key
: Some(KeyWithSource
::from_path(
419 mode
: CryptMode
::SignOnly
,
421 let default_key_res
= CryptoParams
{
422 enc_key
: Some(KeyWithSource
::from_default(default_key
.clone())),
424 mode
: CryptMode
::Encrypt
,
426 let default_key_sign_res
= CryptoParams
{
427 enc_key
: Some(KeyWithSource
::from_default(default_key
.clone())),
429 mode
: CryptMode
::SignOnly
,
432 replace_file(&keypath
, &some_key
, CreateOptions
::default())?
;
433 replace_file(&master_keypath
, &some_master_key
, CreateOptions
::default())?
;
435 // no params, no default key == no key
436 let res
= crypto_parameters(&json
!({}
));
437 assert_eq
!(res
.unwrap(), no_key_res
);
439 // keyfile param == key from keyfile
440 let res
= crypto_parameters(&json
!({"keyfile": keypath}
));
441 assert_eq
!(res
.unwrap(), some_key_res
);
443 // crypt mode none == no key
444 let res
= crypto_parameters(&json
!({"crypt-mode": "none"}
));
445 assert_eq
!(res
.unwrap(), no_key_res
);
447 // crypt mode encrypt/sign-only, no keyfile, no default key == Error
448 assert
!(crypto_parameters(&json
!({"crypt-mode": "sign-only"}
)).is_err());
449 assert
!(crypto_parameters(&json
!({"crypt-mode": "encrypt"}
)).is_err());
451 // crypt mode none with explicit key == Error
452 assert
!(crypto_parameters(&json
!({"crypt-mode": "none", "keyfile": keypath}
)).is_err());
454 // crypt mode sign-only/encrypt with keyfile == key from keyfile with correct mode
455 let res
= crypto_parameters(&json
!({"crypt-mode": "sign-only", "keyfile": keypath}
));
456 assert_eq
!(res
.unwrap(), some_key_sign_res
);
457 let res
= crypto_parameters(&json
!({"crypt-mode": "encrypt", "keyfile": keypath}
));
458 assert_eq
!(res
.unwrap(), some_key_res
);
460 // invalid keyfile parameter always errors
461 assert
!(crypto_parameters(&json
!({"keyfile": invalid_keypath}
)).is_err());
462 assert
!(crypto_parameters(&json
!({"keyfile": invalid_keypath, "crypt-mode": "none"}
)).is_err());
463 assert
!(crypto_parameters(&json
!({"keyfile": invalid_keypath, "crypt-mode": "sign-only"}
)).is_err());
464 assert
!(crypto_parameters(&json
!({"keyfile": invalid_keypath, "crypt-mode": "encrypt"}
)).is_err());
466 // now set a default key
467 unsafe { set_test_encryption_key(Ok(Some(default_key.clone()))); }
471 // no params but default key == default key
472 let res
= crypto_parameters(&json
!({}
));
473 assert_eq
!(res
.unwrap(), default_key_res
);
475 // keyfile param == key from keyfile
476 let res
= crypto_parameters(&json
!({"keyfile": keypath}
));
477 assert_eq
!(res
.unwrap(), some_key_res
);
479 // crypt mode none == no key
480 let res
= crypto_parameters(&json
!({"crypt-mode": "none"}
));
481 assert_eq
!(res
.unwrap(), no_key_res
);
483 // crypt mode encrypt/sign-only, no keyfile, default key == default key with correct mode
484 let res
= crypto_parameters(&json
!({"crypt-mode": "sign-only"}
));
485 assert_eq
!(res
.unwrap(), default_key_sign_res
);
486 let res
= crypto_parameters(&json
!({"crypt-mode": "encrypt"}
));
487 assert_eq
!(res
.unwrap(), default_key_res
);
489 // crypt mode none with explicit key == Error
490 assert
!(crypto_parameters(&json
!({"crypt-mode": "none", "keyfile": keypath}
)).is_err());
492 // crypt mode sign-only/encrypt with keyfile == key from keyfile with correct mode
493 let res
= crypto_parameters(&json
!({"crypt-mode": "sign-only", "keyfile": keypath}
));
494 assert_eq
!(res
.unwrap(), some_key_sign_res
);
495 let res
= crypto_parameters(&json
!({"crypt-mode": "encrypt", "keyfile": keypath}
));
496 assert_eq
!(res
.unwrap(), some_key_res
);
498 // invalid keyfile parameter always errors
499 assert
!(crypto_parameters(&json
!({"keyfile": invalid_keypath}
)).is_err());
500 assert
!(crypto_parameters(&json
!({"keyfile": invalid_keypath, "crypt-mode": "none"}
)).is_err());
501 assert
!(crypto_parameters(&json
!({"keyfile": invalid_keypath, "crypt-mode": "sign-only"}
)).is_err());
502 assert
!(crypto_parameters(&json
!({"keyfile": invalid_keypath, "crypt-mode": "encrypt"}
)).is_err());
504 // now make default key retrieval error
505 unsafe { set_test_encryption_key(Err(format_err!("test error"))); }
509 // no params, default key retrieval errors == Error
510 assert
!(crypto_parameters(&json
!({}
)).is_err());
512 // keyfile param == key from keyfile
513 let res
= crypto_parameters(&json
!({"keyfile": keypath}
));
514 assert_eq
!(res
.unwrap(), some_key_res
);
516 // crypt mode none == no key
517 let res
= crypto_parameters(&json
!({"crypt-mode": "none"}
));
518 assert_eq
!(res
.unwrap(), no_key_res
);
520 // crypt mode encrypt/sign-only, no keyfile, default key error == Error
521 assert
!(crypto_parameters(&json
!({"crypt-mode": "sign-only"}
)).is_err());
522 assert
!(crypto_parameters(&json
!({"crypt-mode": "encrypt"}
)).is_err());
524 // crypt mode none with explicit key == Error
525 assert
!(crypto_parameters(&json
!({"crypt-mode": "none", "keyfile": keypath}
)).is_err());
527 // crypt mode sign-only/encrypt with keyfile == key from keyfile with correct mode
528 let res
= crypto_parameters(&json
!({"crypt-mode": "sign-only", "keyfile": keypath}
));
529 assert_eq
!(res
.unwrap(), some_key_sign_res
);
530 let res
= crypto_parameters(&json
!({"crypt-mode": "encrypt", "keyfile": keypath}
));
531 assert_eq
!(res
.unwrap(), some_key_res
);
533 // invalid keyfile parameter always errors
534 assert
!(crypto_parameters(&json
!({"keyfile": invalid_keypath}
)).is_err());
535 assert
!(crypto_parameters(&json
!({"keyfile": invalid_keypath, "crypt-mode": "none"}
)).is_err());
536 assert
!(crypto_parameters(&json
!({"keyfile": invalid_keypath, "crypt-mode": "sign-only"}
)).is_err());
537 assert
!(crypto_parameters(&json
!({"keyfile": invalid_keypath, "crypt-mode": "encrypt"}
)).is_err());
539 // now remove default key again
540 unsafe { set_test_encryption_key(Ok(None)); }
541 // set a default master key
542 unsafe { set_test_default_master_pubkey(Ok(Some(default_master_key.clone()))); }
544 // and use an explicit master key
545 assert
!(crypto_parameters(&json
!({"master-pubkey-file": master_keypath}
)).is_err());
546 // just a default == no key
547 let res
= crypto_parameters(&json
!({}
));
548 assert_eq
!(res
.unwrap(), no_key_res
);
550 // keyfile param == key from keyfile
551 let res
= crypto_parameters(&json
!({"keyfile": keypath, "master-pubkey-file": master_keypath}
));
552 assert_eq
!(res
.unwrap(), some_key_some_master_res
);
553 // same with fallback to default master key
554 let res
= crypto_parameters(&json
!({"keyfile": keypath}
));
555 assert_eq
!(res
.unwrap(), some_key_default_master_res
);
557 // crypt mode none == error
558 assert
!(crypto_parameters(&json
!({"crypt-mode": "none", "master-pubkey-file": master_keypath}
)).is_err());
559 // with just default master key == no key
560 let res
= crypto_parameters(&json
!({"crypt-mode": "none"}
));
561 assert_eq
!(res
.unwrap(), no_key_res
);
563 // crypt mode encrypt without enc key == error
564 assert
!(crypto_parameters(&json
!({"crypt-mode": "encrypt", "master-pubkey-file": master_keypath}
)).is_err());
565 assert
!(crypto_parameters(&json
!({"crypt-mode": "encrypt"}
)).is_err());
567 // crypt mode none with explicit key == Error
568 assert
!(crypto_parameters(&json
!({"crypt-mode": "none", "keyfile": keypath, "master-pubkey-file": master_keypath}
)).is_err());
569 assert
!(crypto_parameters(&json
!({"crypt-mode": "none", "keyfile": keypath}
)).is_err());
571 // crypt mode encrypt with keyfile == key from keyfile with correct mode
572 let res
= crypto_parameters(&json
!({"crypt-mode": "encrypt", "keyfile": keypath, "master-pubkey-file": master_keypath}
));
573 assert_eq
!(res
.unwrap(), some_key_some_master_res
);
574 let res
= crypto_parameters(&json
!({"crypt-mode": "encrypt", "keyfile": keypath}
));
575 assert_eq
!(res
.unwrap(), some_key_default_master_res
);
577 // invalid master keyfile parameter always errors when a key is passed, even with a valid
578 // default master key
579 assert
!(crypto_parameters(&json
!({"keyfile": keypath, "master-pubkey-file": invalid_keypath}
)).is_err());
580 assert
!(crypto_parameters(&json
!({"keyfile": keypath, "master-pubkey-file": invalid_keypath,"crypt-mode": "none"}
)).is_err());
581 assert
!(crypto_parameters(&json
!({"keyfile": keypath, "master-pubkey-file": invalid_keypath,"crypt-mode": "sign-only"}
)).is_err());
582 assert
!(crypto_parameters(&json
!({"keyfile": keypath, "master-pubkey-file": invalid_keypath,"crypt-mode": "encrypt"}
)).is_err());