2 use std
::os
::unix
::io
::{FromRawFd, RawFd}
;
3 use std
::path
::PathBuf
;
5 use anyhow
::{bail, format_err, Error}
;
9 use proxmox_sys
::fs
::file_get_contents
;
10 use proxmox_sys
::linux
::tty
;
12 use pbs_api_types
::CryptMode
;
14 pub const DEFAULT_ENCRYPTION_KEY_FILE_NAME
: &str = "encryption-key.json";
15 pub const DEFAULT_MASTER_PUBKEY_FILE_NAME
: &str = "master-public.pem";
17 pub const KEYFILE_SCHEMA
: Schema
=
18 StringSchema
::new("Path to encryption key. All data will be encrypted using this key.")
21 pub const KEYFD_SCHEMA
: Schema
=
22 IntegerSchema
::new("Pass an encryption key via an already opened file descriptor.")
26 pub const MASTER_PUBKEY_FILE_SCHEMA
: Schema
= StringSchema
::new(
27 "Path to master public key. The encryption key used for a backup will be encrypted using this key and appended to the backup.")
30 pub const MASTER_PUBKEY_FD_SCHEMA
: Schema
=
31 IntegerSchema
::new("Pass a master public key via an already opened file descriptor.")
35 #[derive(Clone, Debug, Eq, PartialEq)]
42 pub fn format_key_source(source
: &KeySource
, key_type
: &str) -> String
{
44 KeySource
::DefaultKey
=> format
!("Using default {} key..", key_type
),
45 KeySource
::Fd
=> format
!("Using {} key from file descriptor..", key_type
),
46 KeySource
::Path(path
) => format
!("Using {} key from '{}'..", key_type
, path
),
50 #[derive(Clone, Debug, Eq, PartialEq)]
51 pub struct KeyWithSource
{
52 pub source
: KeySource
,
57 pub fn from_fd(key
: Vec
<u8>) -> Self {
59 source
: KeySource
::Fd
,
64 pub fn from_default(key
: Vec
<u8>) -> Self {
66 source
: KeySource
::DefaultKey
,
71 pub fn from_path(path
: String
, key
: Vec
<u8>) -> Self {
73 source
: KeySource
::Path(path
),
79 #[derive(Debug, Eq, PartialEq)]
80 pub struct CryptoParams
{
82 pub enc_key
: Option
<KeyWithSource
>,
83 // FIXME switch to openssl::rsa::rsa<openssl::pkey::Public> once that is Eq?
84 pub master_pubkey
: Option
<KeyWithSource
>,
87 pub fn crypto_parameters(param
: &Value
) -> Result
<CryptoParams
, Error
> {
88 do_crypto_parameters(param
, false)
91 pub fn crypto_parameters_keep_fd(param
: &Value
) -> Result
<CryptoParams
, Error
> {
92 do_crypto_parameters(param
, true)
95 fn do_crypto_parameters(param
: &Value
, keep_keyfd_open
: bool
) -> Result
<CryptoParams
, Error
> {
96 let keyfile
= match param
.get("keyfile") {
97 Some(Value
::String(keyfile
)) => Some(keyfile
),
98 Some(_
) => bail
!("bad --keyfile parameter type"),
102 let key_fd
= match param
.get("keyfd") {
103 Some(Value
::Number(key_fd
)) => Some(
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(
126 .ok_or_else(|| format_err
!("bad master public key fd: {:?}", key_fd
))?
,
128 .map_err(|err
| format_err
!("bad public master key fd: {:?}: {}", key_fd
, err
))?
,
130 Some(_
) => bail
!("bad --master-pubkey-fd parameter type"),
134 let mode
: Option
<CryptMode
> = match param
.get("crypt-mode") {
135 Some(mode
) => Some(serde
::Deserialize
::deserialize(mode
)?
),
139 let key
= match (keyfile
, key_fd
) {
140 (None
, None
) => None
,
141 (Some(_
), Some(_
)) => bail
!("--keyfile and --keyfd are mutually exclusive"),
142 (Some(keyfile
), None
) => Some(KeyWithSource
::from_path(
144 file_get_contents(keyfile
)?
,
146 (None
, Some(fd
)) => {
147 let mut input
= unsafe { std::fs::File::from_raw_fd(fd) }
;
148 let mut data
= Vec
::new();
149 let _len
: usize = input
.read_to_end(&mut data
).map_err(|err
| {
150 format_err
!("error reading encryption key from fd {}: {}", fd
, err
)
153 // don't close fd if requested, and try to reset seek position
154 std
::mem
::forget(input
);
156 libc
::lseek(fd
, 0, libc
::SEEK_SET
);
159 Some(KeyWithSource
::from_fd(data
))
163 let master_pubkey
= match (master_pubkey_file
, master_pubkey_fd
) {
164 (None
, None
) => None
,
165 (Some(_
), Some(_
)) => bail
!("--keyfile and --keyfd are mutually exclusive"),
166 (Some(keyfile
), None
) => Some(KeyWithSource
::from_path(
168 file_get_contents(keyfile
)?
,
170 (None
, Some(fd
)) => {
171 let input
= unsafe { std::fs::File::from_raw_fd(fd) }
;
172 let mut data
= Vec
::new();
173 let _len
: usize = { input }
174 .read_to_end(&mut data
)
175 .map_err(|err
| format_err
!("error reading master key from fd {}: {}", fd
, err
))?
;
176 Some(KeyWithSource
::from_fd(data
))
180 let res
= match mode
{
181 // no crypt mode, enable encryption if keys are available
182 None
=> match (key
, master_pubkey
) {
183 // only default keys if available
184 (None
, None
) => match read_optional_default_encryption_key()?
{
185 None
=> CryptoParams { mode: CryptMode::None, enc_key: None, master_pubkey: None }
,
187 let master_pubkey
= read_optional_default_master_pubkey()?
;
189 mode
: CryptMode
::Encrypt
,
196 // explicit master key, default enc key needed
197 (None
, master_pubkey
) => match read_optional_default_encryption_key()?
{
198 None
=> bail
!("--master-pubkey-file/--master-pubkey-fd specified, but no key available"),
201 mode
: CryptMode
::Encrypt
,
208 // explicit keyfile, maybe default master key
209 (enc_key
, None
) => CryptoParams { mode: CryptMode::Encrypt, enc_key, master_pubkey: read_optional_default_master_pubkey()? }
,
211 // explicit keyfile and master key
212 (enc_key
, master_pubkey
) => CryptoParams { mode: CryptMode::Encrypt, enc_key, master_pubkey }
,
215 // explicitly disabled encryption
216 Some(CryptMode
::None
) => match (key
, master_pubkey
) {
217 // no keys => OK, no encryption
218 (None
, None
) => CryptoParams { mode: CryptMode::None, enc_key: None, master_pubkey: None }
,
220 // --keyfile and --crypt-mode=none
221 (Some(_
), _
) => bail
!("--keyfile/--keyfd and --crypt-mode=none are mutually exclusive"),
223 // --master-pubkey-file and --crypt-mode=none
224 (_
, Some(_
)) => bail
!("--master-pubkey-file/--master-pubkey-fd and --crypt-mode=none are mutually exclusive"),
227 // explicitly enabled encryption
228 Some(mode
) => match (key
, master_pubkey
) {
229 // no key, maybe master key
230 (None
, master_pubkey
) => match read_optional_default_encryption_key()?
{
231 None
=> bail
!("--crypt-mode without --keyfile and no default key file available"),
233 log
::info
!("Encrypting with default encryption key!");
234 let master_pubkey
= match master_pubkey
{
235 None
=> read_optional_default_master_pubkey()?
,
236 master_pubkey
=> master_pubkey
,
247 // --keyfile and --crypt-mode other than none
248 (enc_key
, master_pubkey
) => {
249 let master_pubkey
= match master_pubkey
{
250 None
=> read_optional_default_master_pubkey()?
,
251 master_pubkey
=> master_pubkey
,
254 CryptoParams { mode, enc_key, master_pubkey }
262 pub fn find_default_master_pubkey() -> Result
<Option
<PathBuf
>, Error
> {
263 super::find_xdg_file(
264 DEFAULT_MASTER_PUBKEY_FILE_NAME
,
265 "default master public key file",
269 pub fn place_default_master_pubkey() -> Result
<PathBuf
, Error
> {
270 super::place_xdg_file(
271 DEFAULT_MASTER_PUBKEY_FILE_NAME
,
272 "default master public key file",
276 pub fn find_default_encryption_key() -> Result
<Option
<PathBuf
>, Error
> {
277 super::find_xdg_file(
278 DEFAULT_ENCRYPTION_KEY_FILE_NAME
,
279 "default encryption key file",
283 pub fn place_default_encryption_key() -> Result
<PathBuf
, Error
> {
284 super::place_xdg_file(
285 DEFAULT_ENCRYPTION_KEY_FILE_NAME
,
286 "default encryption key file",
291 pub(crate) fn read_optional_default_encryption_key() -> Result
<Option
<KeyWithSource
>, Error
> {
292 find_default_encryption_key()?
293 .map(|path
| file_get_contents(path
).map(KeyWithSource
::from_default
))
298 pub(crate) fn read_optional_default_master_pubkey() -> Result
<Option
<KeyWithSource
>, Error
> {
299 find_default_master_pubkey()?
300 .map(|path
| file_get_contents(path
).map(KeyWithSource
::from_default
))
305 static mut TEST_DEFAULT_ENCRYPTION_KEY
: Result
<Option
<Vec
<u8>>, Error
> = Ok(None
);
308 pub(crate) fn read_optional_default_encryption_key() -> Result
<Option
<KeyWithSource
>, Error
> {
309 // not safe when multiple concurrent test cases end up here!
311 match &TEST_DEFAULT_ENCRYPTION_KEY
{
312 Ok(Some(key
)) => Ok(Some(KeyWithSource
::from_default(key
.clone()))),
313 Ok(None
) => Ok(None
),
314 Err(_
) => bail
!("test error"),
320 // not safe when multiple concurrent test cases end up here!
321 pub(crate) unsafe fn set_test_encryption_key(value
: Result
<Option
<Vec
<u8>>, Error
>) {
322 TEST_DEFAULT_ENCRYPTION_KEY
= value
;
326 static mut TEST_DEFAULT_MASTER_PUBKEY
: Result
<Option
<Vec
<u8>>, Error
> = Ok(None
);
329 pub(crate) fn read_optional_default_master_pubkey() -> Result
<Option
<KeyWithSource
>, Error
> {
330 // not safe when multiple concurrent test cases end up here!
332 match &TEST_DEFAULT_MASTER_PUBKEY
{
333 Ok(Some(key
)) => Ok(Some(KeyWithSource
::from_default(key
.clone()))),
334 Ok(None
) => Ok(None
),
335 Err(_
) => bail
!("test error"),
341 // not safe when multiple concurrent test cases end up here!
342 pub(crate) unsafe fn set_test_default_master_pubkey(value
: Result
<Option
<Vec
<u8>>, Error
>) {
343 TEST_DEFAULT_MASTER_PUBKEY
= value
;
346 pub fn get_encryption_key_password() -> Result
<Vec
<u8>, Error
> {
347 // fixme: implement other input methods
349 if let Some(password
) = super::get_secret_from_env("PBS_ENCRYPTION_PASSWORD")?
{
350 return Ok(password
.as_bytes().to_vec());
353 // If we're on a TTY, query the user for a password
354 if tty
::stdin_isatty() {
355 return tty
::read_password("Encryption Key Password: ");
358 bail
!("no password input mechanism available");
362 fn create_testdir(name
: &str) -> Result
<String
, Error
> {
364 //let mut testdir: PathBuf = format!("{}/testout", env!("CARGO_TARGET_TMPDIR")).into();
365 let mut testdir
: PathBuf
= "./target/testout".to_string().into();
366 testdir
.push(std
::module_path
!());
369 let _
= std
::fs
::remove_dir_all(&testdir
);
370 let _
= std
::fs
::create_dir_all(&testdir
);
372 Ok(testdir
.to_str().unwrap().to_string())
376 // WARNING: there must only be one test for crypto_parameters as the default key handling is not
377 // safe w.r.t. concurrency
378 fn test_crypto_parameters_handling() -> Result
<(), Error
> {
379 use proxmox_sys
::fs
::{replace_file, CreateOptions}
;
380 use serde_json
::json
;
382 let some_key
= vec
![1; 1];
383 let default_key
= vec
![2; 1];
385 let some_master_key
= vec
![3; 1];
386 let default_master_key
= vec
![4; 1];
388 let testdir
= create_testdir("key_source")?
;
390 let keypath
= format
!("{}/keyfile.test", testdir
);
391 let master_keypath
= format
!("{}/masterkeyfile.test", testdir
);
392 let invalid_keypath
= format
!("{}/invalid_keyfile.test", testdir
);
394 let no_key_res
= CryptoParams
{
397 mode
: CryptMode
::None
,
399 let some_key_res
= CryptoParams
{
400 enc_key
: Some(KeyWithSource
::from_path(
405 mode
: CryptMode
::Encrypt
,
407 let some_key_some_master_res
= CryptoParams
{
408 enc_key
: Some(KeyWithSource
::from_path(
412 master_pubkey
: Some(KeyWithSource
::from_path(
413 master_keypath
.to_string(),
414 some_master_key
.clone(),
416 mode
: CryptMode
::Encrypt
,
418 let some_key_default_master_res
= CryptoParams
{
419 enc_key
: Some(KeyWithSource
::from_path(
423 master_pubkey
: Some(KeyWithSource
::from_default(default_master_key
.clone())),
424 mode
: CryptMode
::Encrypt
,
427 let some_key_sign_res
= CryptoParams
{
428 enc_key
: Some(KeyWithSource
::from_path(
433 mode
: CryptMode
::SignOnly
,
435 let default_key_res
= CryptoParams
{
436 enc_key
: Some(KeyWithSource
::from_default(default_key
.clone())),
438 mode
: CryptMode
::Encrypt
,
440 let default_key_sign_res
= CryptoParams
{
441 enc_key
: Some(KeyWithSource
::from_default(default_key
.clone())),
443 mode
: CryptMode
::SignOnly
,
446 replace_file(&keypath
, &some_key
, CreateOptions
::default(), false)?
;
450 CreateOptions
::default(),
454 // no params, no default key == no key
455 let res
= crypto_parameters(&json
!({}
));
456 assert_eq
!(res
.unwrap(), no_key_res
);
458 // keyfile param == key from keyfile
459 let res
= crypto_parameters(&json
!({ "keyfile": keypath }
));
460 assert_eq
!(res
.unwrap(), some_key_res
);
462 // crypt mode none == no key
463 let res
= crypto_parameters(&json
!({"crypt-mode": "none"}
));
464 assert_eq
!(res
.unwrap(), no_key_res
);
466 // crypt mode encrypt/sign-only, no keyfile, no default key == Error
467 assert
!(crypto_parameters(&json
!({"crypt-mode": "sign-only"}
)).is_err());
468 assert
!(crypto_parameters(&json
!({"crypt-mode": "encrypt"}
)).is_err());
470 // crypt mode none with explicit key == Error
471 assert
!(crypto_parameters(&json
!({"crypt-mode": "none", "keyfile": keypath}
)).is_err());
473 // crypt mode sign-only/encrypt with keyfile == key from keyfile with correct mode
474 let res
= crypto_parameters(&json
!({"crypt-mode": "sign-only", "keyfile": keypath}
));
475 assert_eq
!(res
.unwrap(), some_key_sign_res
);
476 let res
= crypto_parameters(&json
!({"crypt-mode": "encrypt", "keyfile": keypath}
));
477 assert_eq
!(res
.unwrap(), some_key_res
);
479 // invalid keyfile parameter always errors
480 assert
!(crypto_parameters(&json
!({ "keyfile": invalid_keypath }
)).is_err());
481 assert
!(crypto_parameters(&json
!({"keyfile": invalid_keypath, "crypt-mode": "none"}
)).is_err());
483 crypto_parameters(&json
!({"keyfile": invalid_keypath, "crypt-mode": "sign-only"}
)).is_err()
486 crypto_parameters(&json
!({"keyfile": invalid_keypath, "crypt-mode": "encrypt"}
)).is_err()
489 // now set a default key
491 set_test_encryption_key(Ok(Some(default_key
)));
496 // no params but default key == default key
497 let res
= crypto_parameters(&json
!({}
));
498 assert_eq
!(res
.unwrap(), default_key_res
);
500 // keyfile param == key from keyfile
501 let res
= crypto_parameters(&json
!({ "keyfile": keypath }
));
502 assert_eq
!(res
.unwrap(), some_key_res
);
504 // crypt mode none == no key
505 let res
= crypto_parameters(&json
!({"crypt-mode": "none"}
));
506 assert_eq
!(res
.unwrap(), no_key_res
);
508 // crypt mode encrypt/sign-only, no keyfile, default key == default key with correct mode
509 let res
= crypto_parameters(&json
!({"crypt-mode": "sign-only"}
));
510 assert_eq
!(res
.unwrap(), default_key_sign_res
);
511 let res
= crypto_parameters(&json
!({"crypt-mode": "encrypt"}
));
512 assert_eq
!(res
.unwrap(), default_key_res
);
514 // crypt mode none with explicit key == Error
515 assert
!(crypto_parameters(&json
!({"crypt-mode": "none", "keyfile": keypath}
)).is_err());
517 // crypt mode sign-only/encrypt with keyfile == key from keyfile with correct mode
518 let res
= crypto_parameters(&json
!({"crypt-mode": "sign-only", "keyfile": keypath}
));
519 assert_eq
!(res
.unwrap(), some_key_sign_res
);
520 let res
= crypto_parameters(&json
!({"crypt-mode": "encrypt", "keyfile": keypath}
));
521 assert_eq
!(res
.unwrap(), some_key_res
);
523 // invalid keyfile parameter always errors
524 assert
!(crypto_parameters(&json
!({ "keyfile": invalid_keypath }
)).is_err());
525 assert
!(crypto_parameters(&json
!({"keyfile": invalid_keypath, "crypt-mode": "none"}
)).is_err());
527 crypto_parameters(&json
!({"keyfile": invalid_keypath, "crypt-mode": "sign-only"}
)).is_err()
530 crypto_parameters(&json
!({"keyfile": invalid_keypath, "crypt-mode": "encrypt"}
)).is_err()
533 // now make default key retrieval error
535 set_test_encryption_key(Err(format_err
!("test error")));
540 // no params, default key retrieval errors == Error
541 assert
!(crypto_parameters(&json
!({}
)).is_err());
543 // keyfile param == key from keyfile
544 let res
= crypto_parameters(&json
!({ "keyfile": keypath }
));
545 assert_eq
!(res
.unwrap(), some_key_res
);
547 // crypt mode none == no key
548 let res
= crypto_parameters(&json
!({"crypt-mode": "none"}
));
549 assert_eq
!(res
.unwrap(), no_key_res
);
551 // crypt mode encrypt/sign-only, no keyfile, default key error == Error
552 assert
!(crypto_parameters(&json
!({"crypt-mode": "sign-only"}
)).is_err());
553 assert
!(crypto_parameters(&json
!({"crypt-mode": "encrypt"}
)).is_err());
555 // crypt mode none with explicit key == Error
556 assert
!(crypto_parameters(&json
!({"crypt-mode": "none", "keyfile": keypath}
)).is_err());
558 // crypt mode sign-only/encrypt with keyfile == key from keyfile with correct mode
559 let res
= crypto_parameters(&json
!({"crypt-mode": "sign-only", "keyfile": keypath}
));
560 assert_eq
!(res
.unwrap(), some_key_sign_res
);
561 let res
= crypto_parameters(&json
!({"crypt-mode": "encrypt", "keyfile": keypath}
));
562 assert_eq
!(res
.unwrap(), some_key_res
);
564 // invalid keyfile parameter always errors
565 assert
!(crypto_parameters(&json
!({ "keyfile": invalid_keypath }
)).is_err());
566 assert
!(crypto_parameters(&json
!({"keyfile": invalid_keypath, "crypt-mode": "none"}
)).is_err());
568 crypto_parameters(&json
!({"keyfile": invalid_keypath, "crypt-mode": "sign-only"}
)).is_err()
571 crypto_parameters(&json
!({"keyfile": invalid_keypath, "crypt-mode": "encrypt"}
)).is_err()
574 // now remove default key again
576 set_test_encryption_key(Ok(None
));
578 // set a default master key
580 set_test_default_master_pubkey(Ok(Some(default_master_key
)));
583 // and use an explicit master key
584 assert
!(crypto_parameters(&json
!({ "master-pubkey-file": master_keypath }
)).is_err());
585 // just a default == no key
586 let res
= crypto_parameters(&json
!({}
));
587 assert_eq
!(res
.unwrap(), no_key_res
);
589 // keyfile param == key from keyfile
590 let res
= crypto_parameters(&json
!({"keyfile": keypath, "master-pubkey-file": master_keypath}
));
591 assert_eq
!(res
.unwrap(), some_key_some_master_res
);
592 // same with fallback to default master key
593 let res
= crypto_parameters(&json
!({ "keyfile": keypath }
));
594 assert_eq
!(res
.unwrap(), some_key_default_master_res
);
596 // crypt mode none == error
597 assert
!(crypto_parameters(
598 &json
!({"crypt-mode": "none", "master-pubkey-file": master_keypath}
)
601 // with just default master key == no key
602 let res
= crypto_parameters(&json
!({"crypt-mode": "none"}
));
603 assert_eq
!(res
.unwrap(), no_key_res
);
605 // crypt mode encrypt without enc key == error
606 assert
!(crypto_parameters(
607 &json
!({"crypt-mode": "encrypt", "master-pubkey-file": master_keypath}
)
610 assert
!(crypto_parameters(&json
!({"crypt-mode": "encrypt"}
)).is_err());
612 // crypt mode none with explicit key == Error
613 assert
!(crypto_parameters(
614 &json
!({"crypt-mode": "none", "keyfile": keypath, "master-pubkey-file": master_keypath}
)
617 assert
!(crypto_parameters(&json
!({"crypt-mode": "none", "keyfile": keypath}
)).is_err());
619 // crypt mode encrypt with keyfile == key from keyfile with correct mode
620 let res
= crypto_parameters(
621 &json
!({"crypt-mode": "encrypt", "keyfile": keypath, "master-pubkey-file": master_keypath}
),
623 assert_eq
!(res
.unwrap(), some_key_some_master_res
);
624 let res
= crypto_parameters(&json
!({"crypt-mode": "encrypt", "keyfile": keypath}
));
625 assert_eq
!(res
.unwrap(), some_key_default_master_res
);
627 // invalid master keyfile parameter always errors when a key is passed, even with a valid
628 // default master key
630 crypto_parameters(&json
!({"keyfile": keypath, "master-pubkey-file": invalid_keypath}
))
633 assert
!(crypto_parameters(
634 &json
!({"keyfile": keypath, "master-pubkey-file": invalid_keypath,"crypt-mode": "none"}
)
637 assert
!(crypto_parameters(&json
!({"keyfile": keypath, "master-pubkey-file": invalid_keypath,"crypt-mode": "sign-only"}
)).is_err());
638 assert
!(crypto_parameters(
639 &json
!({"keyfile": keypath, "master-pubkey-file": invalid_keypath,"crypt-mode": "encrypt"}
)