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_sys
::linux
::tty
;
10 use proxmox_sys
::fs
::file_get_contents
;
11 use proxmox_schema
::*;
13 use pbs_api_types
::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 if let Some(password
) = super::get_secret_from_env("PBS_ENCRYPTION_PASSWORD")?
{
347 return Ok(password
.as_bytes().to_vec());
350 // If we're on a TTY, query the user for a password
351 if tty
::stdin_isatty() {
352 return Ok(tty
::read_password("Encryption Key Password: ")?
);
355 bail
!("no password input mechanism available");
359 fn create_testdir(name
: &str) -> Result
<String
, Error
> {
361 //let mut testdir: PathBuf = format!("{}/testout", env!("CARGO_TARGET_TMPDIR")).into();
362 let mut testdir
: PathBuf
= "./target/testout".to_string().into();
363 testdir
.push(std
::module_path
!());
366 let _
= std
::fs
::remove_dir_all(&testdir
);
367 let _
= std
::fs
::create_dir_all(&testdir
);
369 Ok(testdir
.to_str().unwrap().to_string())
373 // WARNING: there must only be one test for crypto_parameters as the default key handling is not
374 // safe w.r.t. concurrency
375 fn test_crypto_parameters_handling() -> Result
<(), Error
> {
376 use serde_json
::json
;
377 use proxmox_sys
::fs
::{replace_file, CreateOptions}
;
379 let some_key
= vec
![1;1];
380 let default_key
= vec
![2;1];
382 let some_master_key
= vec
![3;1];
383 let default_master_key
= vec
![4;1];
385 let testdir
= create_testdir("key_source")?
;
387 let keypath
= format
!("{}/keyfile.test", testdir
);
388 let master_keypath
= format
!("{}/masterkeyfile.test", testdir
);
389 let invalid_keypath
= format
!("{}/invalid_keyfile.test", testdir
);
391 let no_key_res
= CryptoParams
{
394 mode
: CryptMode
::None
,
396 let some_key_res
= CryptoParams
{
397 enc_key
: Some(KeyWithSource
::from_path(
402 mode
: CryptMode
::Encrypt
,
404 let some_key_some_master_res
= CryptoParams
{
405 enc_key
: Some(KeyWithSource
::from_path(
409 master_pubkey
: Some(KeyWithSource
::from_path(
410 master_keypath
.to_string(),
411 some_master_key
.clone(),
413 mode
: CryptMode
::Encrypt
,
415 let some_key_default_master_res
= CryptoParams
{
416 enc_key
: Some(KeyWithSource
::from_path(
420 master_pubkey
: Some(KeyWithSource
::from_default(default_master_key
.clone())),
421 mode
: CryptMode
::Encrypt
,
424 let some_key_sign_res
= CryptoParams
{
425 enc_key
: Some(KeyWithSource
::from_path(
430 mode
: CryptMode
::SignOnly
,
432 let default_key_res
= CryptoParams
{
433 enc_key
: Some(KeyWithSource
::from_default(default_key
.clone())),
435 mode
: CryptMode
::Encrypt
,
437 let default_key_sign_res
= CryptoParams
{
438 enc_key
: Some(KeyWithSource
::from_default(default_key
.clone())),
440 mode
: CryptMode
::SignOnly
,
443 replace_file(&keypath
, &some_key
, CreateOptions
::default(), false)?
;
444 replace_file(&master_keypath
, &some_master_key
, CreateOptions
::default(), false)?
;
446 // no params, no default key == no key
447 let res
= crypto_parameters(&json
!({}
));
448 assert_eq
!(res
.unwrap(), no_key_res
);
450 // keyfile param == key from keyfile
451 let res
= crypto_parameters(&json
!({"keyfile": keypath}
));
452 assert_eq
!(res
.unwrap(), some_key_res
);
454 // crypt mode none == no key
455 let res
= crypto_parameters(&json
!({"crypt-mode": "none"}
));
456 assert_eq
!(res
.unwrap(), no_key_res
);
458 // crypt mode encrypt/sign-only, no keyfile, no default key == Error
459 assert
!(crypto_parameters(&json
!({"crypt-mode": "sign-only"}
)).is_err());
460 assert
!(crypto_parameters(&json
!({"crypt-mode": "encrypt"}
)).is_err());
462 // crypt mode none with explicit key == Error
463 assert
!(crypto_parameters(&json
!({"crypt-mode": "none", "keyfile": keypath}
)).is_err());
465 // crypt mode sign-only/encrypt with keyfile == key from keyfile with correct mode
466 let res
= crypto_parameters(&json
!({"crypt-mode": "sign-only", "keyfile": keypath}
));
467 assert_eq
!(res
.unwrap(), some_key_sign_res
);
468 let res
= crypto_parameters(&json
!({"crypt-mode": "encrypt", "keyfile": keypath}
));
469 assert_eq
!(res
.unwrap(), some_key_res
);
471 // invalid keyfile parameter always errors
472 assert
!(crypto_parameters(&json
!({"keyfile": invalid_keypath}
)).is_err());
473 assert
!(crypto_parameters(&json
!({"keyfile": invalid_keypath, "crypt-mode": "none"}
)).is_err());
474 assert
!(crypto_parameters(&json
!({"keyfile": invalid_keypath, "crypt-mode": "sign-only"}
)).is_err());
475 assert
!(crypto_parameters(&json
!({"keyfile": invalid_keypath, "crypt-mode": "encrypt"}
)).is_err());
477 // now set a default key
478 unsafe { set_test_encryption_key(Ok(Some(default_key.clone()))); }
482 // no params but default key == default key
483 let res
= crypto_parameters(&json
!({}
));
484 assert_eq
!(res
.unwrap(), default_key_res
);
486 // keyfile param == key from keyfile
487 let res
= crypto_parameters(&json
!({"keyfile": keypath}
));
488 assert_eq
!(res
.unwrap(), some_key_res
);
490 // crypt mode none == no key
491 let res
= crypto_parameters(&json
!({"crypt-mode": "none"}
));
492 assert_eq
!(res
.unwrap(), no_key_res
);
494 // crypt mode encrypt/sign-only, no keyfile, default key == default key with correct mode
495 let res
= crypto_parameters(&json
!({"crypt-mode": "sign-only"}
));
496 assert_eq
!(res
.unwrap(), default_key_sign_res
);
497 let res
= crypto_parameters(&json
!({"crypt-mode": "encrypt"}
));
498 assert_eq
!(res
.unwrap(), default_key_res
);
500 // crypt mode none with explicit key == Error
501 assert
!(crypto_parameters(&json
!({"crypt-mode": "none", "keyfile": keypath}
)).is_err());
503 // crypt mode sign-only/encrypt with keyfile == key from keyfile with correct mode
504 let res
= crypto_parameters(&json
!({"crypt-mode": "sign-only", "keyfile": keypath}
));
505 assert_eq
!(res
.unwrap(), some_key_sign_res
);
506 let res
= crypto_parameters(&json
!({"crypt-mode": "encrypt", "keyfile": keypath}
));
507 assert_eq
!(res
.unwrap(), some_key_res
);
509 // invalid keyfile parameter always errors
510 assert
!(crypto_parameters(&json
!({"keyfile": invalid_keypath}
)).is_err());
511 assert
!(crypto_parameters(&json
!({"keyfile": invalid_keypath, "crypt-mode": "none"}
)).is_err());
512 assert
!(crypto_parameters(&json
!({"keyfile": invalid_keypath, "crypt-mode": "sign-only"}
)).is_err());
513 assert
!(crypto_parameters(&json
!({"keyfile": invalid_keypath, "crypt-mode": "encrypt"}
)).is_err());
515 // now make default key retrieval error
516 unsafe { set_test_encryption_key(Err(format_err!("test error"))); }
520 // no params, default key retrieval errors == Error
521 assert
!(crypto_parameters(&json
!({}
)).is_err());
523 // keyfile param == key from keyfile
524 let res
= crypto_parameters(&json
!({"keyfile": keypath}
));
525 assert_eq
!(res
.unwrap(), some_key_res
);
527 // crypt mode none == no key
528 let res
= crypto_parameters(&json
!({"crypt-mode": "none"}
));
529 assert_eq
!(res
.unwrap(), no_key_res
);
531 // crypt mode encrypt/sign-only, no keyfile, default key error == Error
532 assert
!(crypto_parameters(&json
!({"crypt-mode": "sign-only"}
)).is_err());
533 assert
!(crypto_parameters(&json
!({"crypt-mode": "encrypt"}
)).is_err());
535 // crypt mode none with explicit key == Error
536 assert
!(crypto_parameters(&json
!({"crypt-mode": "none", "keyfile": keypath}
)).is_err());
538 // crypt mode sign-only/encrypt with keyfile == key from keyfile with correct mode
539 let res
= crypto_parameters(&json
!({"crypt-mode": "sign-only", "keyfile": keypath}
));
540 assert_eq
!(res
.unwrap(), some_key_sign_res
);
541 let res
= crypto_parameters(&json
!({"crypt-mode": "encrypt", "keyfile": keypath}
));
542 assert_eq
!(res
.unwrap(), some_key_res
);
544 // invalid keyfile parameter always errors
545 assert
!(crypto_parameters(&json
!({"keyfile": invalid_keypath}
)).is_err());
546 assert
!(crypto_parameters(&json
!({"keyfile": invalid_keypath, "crypt-mode": "none"}
)).is_err());
547 assert
!(crypto_parameters(&json
!({"keyfile": invalid_keypath, "crypt-mode": "sign-only"}
)).is_err());
548 assert
!(crypto_parameters(&json
!({"keyfile": invalid_keypath, "crypt-mode": "encrypt"}
)).is_err());
550 // now remove default key again
551 unsafe { set_test_encryption_key(Ok(None)); }
552 // set a default master key
553 unsafe { set_test_default_master_pubkey(Ok(Some(default_master_key.clone()))); }
555 // and use an explicit master key
556 assert
!(crypto_parameters(&json
!({"master-pubkey-file": master_keypath}
)).is_err());
557 // just a default == no key
558 let res
= crypto_parameters(&json
!({}
));
559 assert_eq
!(res
.unwrap(), no_key_res
);
561 // keyfile param == key from keyfile
562 let res
= crypto_parameters(&json
!({"keyfile": keypath, "master-pubkey-file": master_keypath}
));
563 assert_eq
!(res
.unwrap(), some_key_some_master_res
);
564 // same with fallback to default master key
565 let res
= crypto_parameters(&json
!({"keyfile": keypath}
));
566 assert_eq
!(res
.unwrap(), some_key_default_master_res
);
568 // crypt mode none == error
569 assert
!(crypto_parameters(&json
!({"crypt-mode": "none", "master-pubkey-file": master_keypath}
)).is_err());
570 // with just default master key == no key
571 let res
= crypto_parameters(&json
!({"crypt-mode": "none"}
));
572 assert_eq
!(res
.unwrap(), no_key_res
);
574 // crypt mode encrypt without enc key == error
575 assert
!(crypto_parameters(&json
!({"crypt-mode": "encrypt", "master-pubkey-file": master_keypath}
)).is_err());
576 assert
!(crypto_parameters(&json
!({"crypt-mode": "encrypt"}
)).is_err());
578 // crypt mode none with explicit key == Error
579 assert
!(crypto_parameters(&json
!({"crypt-mode": "none", "keyfile": keypath, "master-pubkey-file": master_keypath}
)).is_err());
580 assert
!(crypto_parameters(&json
!({"crypt-mode": "none", "keyfile": keypath}
)).is_err());
582 // crypt mode encrypt with keyfile == key from keyfile with correct mode
583 let res
= crypto_parameters(&json
!({"crypt-mode": "encrypt", "keyfile": keypath, "master-pubkey-file": master_keypath}
));
584 assert_eq
!(res
.unwrap(), some_key_some_master_res
);
585 let res
= crypto_parameters(&json
!({"crypt-mode": "encrypt", "keyfile": keypath}
));
586 assert_eq
!(res
.unwrap(), some_key_default_master_res
);
588 // invalid master keyfile parameter always errors when a key is passed, even with a valid
589 // default master key
590 assert
!(crypto_parameters(&json
!({"keyfile": keypath, "master-pubkey-file": invalid_keypath}
)).is_err());
591 assert
!(crypto_parameters(&json
!({"keyfile": keypath, "master-pubkey-file": invalid_keypath,"crypt-mode": "none"}
)).is_err());
592 assert
!(crypto_parameters(&json
!({"keyfile": keypath, "master-pubkey-file": invalid_keypath,"crypt-mode": "sign-only"}
)).is_err());
593 assert
!(crypto_parameters(&json
!({"keyfile": keypath, "master-pubkey-file": invalid_keypath,"crypt-mode": "encrypt"}
)).is_err());