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 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 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 fn create_testdir(name
: &str) -> Result
<String
, Error
> {
365 let mut testdir
: PathBuf
= String
::from("./target/testout").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 serde_json
::json
;
380 use proxmox
::tools
::fs
::{replace_file, CreateOptions}
;
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())?
;
447 replace_file(&master_keypath
, &some_master_key
, CreateOptions
::default())?
;
449 // no params, no default key == no key
450 let res
= crypto_parameters(&json
!({}
));
451 assert_eq
!(res
.unwrap(), no_key_res
);
453 // keyfile param == key from keyfile
454 let res
= crypto_parameters(&json
!({"keyfile": keypath}
));
455 assert_eq
!(res
.unwrap(), some_key_res
);
457 // crypt mode none == no key
458 let res
= crypto_parameters(&json
!({"crypt-mode": "none"}
));
459 assert_eq
!(res
.unwrap(), no_key_res
);
461 // crypt mode encrypt/sign-only, no keyfile, no default key == Error
462 assert
!(crypto_parameters(&json
!({"crypt-mode": "sign-only"}
)).is_err());
463 assert
!(crypto_parameters(&json
!({"crypt-mode": "encrypt"}
)).is_err());
465 // crypt mode none with explicit key == Error
466 assert
!(crypto_parameters(&json
!({"crypt-mode": "none", "keyfile": keypath}
)).is_err());
468 // crypt mode sign-only/encrypt with keyfile == key from keyfile with correct mode
469 let res
= crypto_parameters(&json
!({"crypt-mode": "sign-only", "keyfile": keypath}
));
470 assert_eq
!(res
.unwrap(), some_key_sign_res
);
471 let res
= crypto_parameters(&json
!({"crypt-mode": "encrypt", "keyfile": keypath}
));
472 assert_eq
!(res
.unwrap(), some_key_res
);
474 // invalid keyfile parameter always errors
475 assert
!(crypto_parameters(&json
!({"keyfile": invalid_keypath}
)).is_err());
476 assert
!(crypto_parameters(&json
!({"keyfile": invalid_keypath, "crypt-mode": "none"}
)).is_err());
477 assert
!(crypto_parameters(&json
!({"keyfile": invalid_keypath, "crypt-mode": "sign-only"}
)).is_err());
478 assert
!(crypto_parameters(&json
!({"keyfile": invalid_keypath, "crypt-mode": "encrypt"}
)).is_err());
480 // now set a default key
481 unsafe { set_test_encryption_key(Ok(Some(default_key.clone()))); }
485 // no params but default key == default key
486 let res
= crypto_parameters(&json
!({}
));
487 assert_eq
!(res
.unwrap(), default_key_res
);
489 // keyfile param == key from keyfile
490 let res
= crypto_parameters(&json
!({"keyfile": keypath}
));
491 assert_eq
!(res
.unwrap(), some_key_res
);
493 // crypt mode none == no key
494 let res
= crypto_parameters(&json
!({"crypt-mode": "none"}
));
495 assert_eq
!(res
.unwrap(), no_key_res
);
497 // crypt mode encrypt/sign-only, no keyfile, default key == default key with correct mode
498 let res
= crypto_parameters(&json
!({"crypt-mode": "sign-only"}
));
499 assert_eq
!(res
.unwrap(), default_key_sign_res
);
500 let res
= crypto_parameters(&json
!({"crypt-mode": "encrypt"}
));
501 assert_eq
!(res
.unwrap(), default_key_res
);
503 // crypt mode none with explicit key == Error
504 assert
!(crypto_parameters(&json
!({"crypt-mode": "none", "keyfile": keypath}
)).is_err());
506 // crypt mode sign-only/encrypt with keyfile == key from keyfile with correct mode
507 let res
= crypto_parameters(&json
!({"crypt-mode": "sign-only", "keyfile": keypath}
));
508 assert_eq
!(res
.unwrap(), some_key_sign_res
);
509 let res
= crypto_parameters(&json
!({"crypt-mode": "encrypt", "keyfile": keypath}
));
510 assert_eq
!(res
.unwrap(), some_key_res
);
512 // invalid keyfile parameter always errors
513 assert
!(crypto_parameters(&json
!({"keyfile": invalid_keypath}
)).is_err());
514 assert
!(crypto_parameters(&json
!({"keyfile": invalid_keypath, "crypt-mode": "none"}
)).is_err());
515 assert
!(crypto_parameters(&json
!({"keyfile": invalid_keypath, "crypt-mode": "sign-only"}
)).is_err());
516 assert
!(crypto_parameters(&json
!({"keyfile": invalid_keypath, "crypt-mode": "encrypt"}
)).is_err());
518 // now make default key retrieval error
519 unsafe { set_test_encryption_key(Err(format_err!("test error"))); }
523 // no params, default key retrieval errors == Error
524 assert
!(crypto_parameters(&json
!({}
)).is_err());
526 // keyfile param == key from keyfile
527 let res
= crypto_parameters(&json
!({"keyfile": keypath}
));
528 assert_eq
!(res
.unwrap(), some_key_res
);
530 // crypt mode none == no key
531 let res
= crypto_parameters(&json
!({"crypt-mode": "none"}
));
532 assert_eq
!(res
.unwrap(), no_key_res
);
534 // crypt mode encrypt/sign-only, no keyfile, default key error == Error
535 assert
!(crypto_parameters(&json
!({"crypt-mode": "sign-only"}
)).is_err());
536 assert
!(crypto_parameters(&json
!({"crypt-mode": "encrypt"}
)).is_err());
538 // crypt mode none with explicit key == Error
539 assert
!(crypto_parameters(&json
!({"crypt-mode": "none", "keyfile": keypath}
)).is_err());
541 // crypt mode sign-only/encrypt with keyfile == key from keyfile with correct mode
542 let res
= crypto_parameters(&json
!({"crypt-mode": "sign-only", "keyfile": keypath}
));
543 assert_eq
!(res
.unwrap(), some_key_sign_res
);
544 let res
= crypto_parameters(&json
!({"crypt-mode": "encrypt", "keyfile": keypath}
));
545 assert_eq
!(res
.unwrap(), some_key_res
);
547 // invalid keyfile parameter always errors
548 assert
!(crypto_parameters(&json
!({"keyfile": invalid_keypath}
)).is_err());
549 assert
!(crypto_parameters(&json
!({"keyfile": invalid_keypath, "crypt-mode": "none"}
)).is_err());
550 assert
!(crypto_parameters(&json
!({"keyfile": invalid_keypath, "crypt-mode": "sign-only"}
)).is_err());
551 assert
!(crypto_parameters(&json
!({"keyfile": invalid_keypath, "crypt-mode": "encrypt"}
)).is_err());
553 // now remove default key again
554 unsafe { set_test_encryption_key(Ok(None)); }
555 // set a default master key
556 unsafe { set_test_default_master_pubkey(Ok(Some(default_master_key.clone()))); }
558 // and use an explicit master key
559 assert
!(crypto_parameters(&json
!({"master-pubkey-file": master_keypath}
)).is_err());
560 // just a default == no key
561 let res
= crypto_parameters(&json
!({}
));
562 assert_eq
!(res
.unwrap(), no_key_res
);
564 // keyfile param == key from keyfile
565 let res
= crypto_parameters(&json
!({"keyfile": keypath, "master-pubkey-file": master_keypath}
));
566 assert_eq
!(res
.unwrap(), some_key_some_master_res
);
567 // same with fallback to default master key
568 let res
= crypto_parameters(&json
!({"keyfile": keypath}
));
569 assert_eq
!(res
.unwrap(), some_key_default_master_res
);
571 // crypt mode none == error
572 assert
!(crypto_parameters(&json
!({"crypt-mode": "none", "master-pubkey-file": master_keypath}
)).is_err());
573 // with just default master key == no key
574 let res
= crypto_parameters(&json
!({"crypt-mode": "none"}
));
575 assert_eq
!(res
.unwrap(), no_key_res
);
577 // crypt mode encrypt without enc key == error
578 assert
!(crypto_parameters(&json
!({"crypt-mode": "encrypt", "master-pubkey-file": master_keypath}
)).is_err());
579 assert
!(crypto_parameters(&json
!({"crypt-mode": "encrypt"}
)).is_err());
581 // crypt mode none with explicit key == Error
582 assert
!(crypto_parameters(&json
!({"crypt-mode": "none", "keyfile": keypath, "master-pubkey-file": master_keypath}
)).is_err());
583 assert
!(crypto_parameters(&json
!({"crypt-mode": "none", "keyfile": keypath}
)).is_err());
585 // crypt mode encrypt with keyfile == key from keyfile with correct mode
586 let res
= crypto_parameters(&json
!({"crypt-mode": "encrypt", "keyfile": keypath, "master-pubkey-file": master_keypath}
));
587 assert_eq
!(res
.unwrap(), some_key_some_master_res
);
588 let res
= crypto_parameters(&json
!({"crypt-mode": "encrypt", "keyfile": keypath}
));
589 assert_eq
!(res
.unwrap(), some_key_default_master_res
);
591 // invalid master keyfile parameter always errors when a key is passed, even with a valid
592 // default master key
593 assert
!(crypto_parameters(&json
!({"keyfile": keypath, "master-pubkey-file": invalid_keypath}
)).is_err());
594 assert
!(crypto_parameters(&json
!({"keyfile": keypath, "master-pubkey-file": invalid_keypath,"crypt-mode": "none"}
)).is_err());
595 assert
!(crypto_parameters(&json
!({"keyfile": keypath, "master-pubkey-file": invalid_keypath,"crypt-mode": "sign-only"}
)).is_err());
596 assert
!(crypto_parameters(&json
!({"keyfile": keypath, "master-pubkey-file": invalid_keypath,"crypt-mode": "encrypt"}
)).is_err());