From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [212.224.123.68]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits)) (No client certificate requested) by lists.proxmox.com (Postfix) with ESMTPS id 8A243611A4 for ; Wed, 16 Dec 2020 14:42:00 +0100 (CET) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 81C2A1C9D4 for ; Wed, 16 Dec 2020 14:41:30 +0100 (CET) Received: from proxmox-new.maurer-it.com (proxmox-new.maurer-it.com [212.186.127.180]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits)) (No client certificate requested) by firstgate.proxmox.com (Proxmox) with ESMTPS id 93FFC1C9CA for ; Wed, 16 Dec 2020 14:41:29 +0100 (CET) Received: from proxmox-new.maurer-it.com (localhost.localdomain [127.0.0.1]) by proxmox-new.maurer-it.com (Proxmox) with ESMTP id 5B9A5451FF for ; Wed, 16 Dec 2020 14:41:29 +0100 (CET) From: =?UTF-8?q?Fabian=20Gr=C3=BCnbichler?= To: pbs-devel@lists.proxmox.com Date: Wed, 16 Dec 2020 14:41:08 +0100 Message-Id: <20201216134111.445581-5-f.gruenbichler@proxmox.com> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20201216134111.445581-1-f.gruenbichler@proxmox.com> References: <20201216134111.445581-1-f.gruenbichler@proxmox.com> MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit X-SPAM-LEVEL: Spam detection results: 0 AWL 0.024 Adjusted score from AWL reputation of From: address KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment RCVD_IN_DNSWL_MED -2.3 Sender listed at https://www.dnswl.org/, medium trust SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record URIBL_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to URIBL was blocked. See http://wiki.apache.org/spamassassin/DnsBlocklists#dnsbl-block for more information. [key.rs, proxmox-backup-client.rs] Subject: [pbs-devel] [PATCH proxmox-backup 4/7] client: add 'import-with-master-key' command X-BeenThere: pbs-devel@lists.proxmox.com X-Mailman-Version: 2.1.29 Precedence: list List-Id: Proxmox Backup Server development discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Wed, 16 Dec 2020 13:42:00 -0000 to import an encrypted encryption key using a master key. Signed-off-by: Fabian Grünbichler --- src/backup/key_derivation.rs | 12 ++++ src/bin/proxmox-backup-client.rs | 8 --- src/bin/proxmox_backup_client/key.rs | 95 ++++++++++++++++++++++++++++ 3 files changed, 107 insertions(+), 8 deletions(-) diff --git a/src/backup/key_derivation.rs b/src/backup/key_derivation.rs index a2aa9469..8289b86c 100644 --- a/src/backup/key_derivation.rs +++ b/src/backup/key_derivation.rs @@ -259,3 +259,15 @@ pub fn rsa_encrypt_key_config( } Ok(buffer) } + +pub fn rsa_decrypt_key_config( + rsa: openssl::rsa::Rsa, + key: &[u8], + passphrase: &dyn Fn() -> Result, Error>, +) -> Result<([u8; 32], i64, Fingerprint), Error> { + let mut buffer = vec![0u8; rsa.size() as usize]; + let decrypted = rsa + .private_decrypt(key, &mut buffer, openssl::rsa::Padding::PKCS1) + .map_err(|err| format_err!("failed to decrypt KeyConfig using RSA - {}", err))?; + decrypt_key(&mut buffer[..decrypted], passphrase) +} diff --git a/src/bin/proxmox-backup-client.rs b/src/bin/proxmox-backup-client.rs index c40bedc5..6cf81952 100644 --- a/src/bin/proxmox-backup-client.rs +++ b/src/bin/proxmox-backup-client.rs @@ -1081,14 +1081,6 @@ async fn create_backup( .await?; manifest.add_file(target.to_string(), stats.size, stats.csum, crypt_mode)?; - // openssl rsautl -decrypt -inkey master-private.pem -in rsa-encrypted.key -out t - /* - let mut buffer2 = vec![0u8; rsa.size() as usize]; - let pem_data = file_get_contents("master-private.pem")?; - let rsa = openssl::rsa::Rsa::private_key_from_pem(&pem_data)?; - let len = rsa.private_decrypt(&buffer, &mut buffer2, openssl::rsa::Padding::PKCS1)?; - println!("TEST {} {:?}", len, buffer2); - */ } // create manifest (index.json) // manifests are never encrypted, but include a signature diff --git a/src/bin/proxmox_backup_client/key.rs b/src/bin/proxmox_backup_client/key.rs index 88fa5340..e49131c1 100644 --- a/src/bin/proxmox_backup_client/key.rs +++ b/src/bin/proxmox_backup_client/key.rs @@ -21,12 +21,14 @@ use proxmox::tools::fs::{file_get_contents, replace_file, CreateOptions}; use proxmox_backup::backup::{ encrypt_key_with_passphrase, load_and_decrypt_key, + rsa_decrypt_key_config, store_key_config, CryptConfig, Kdf, KeyConfig, KeyDerivationConfig, }; + use proxmox_backup::tools; #[api()] @@ -152,6 +154,90 @@ fn create(kdf: Option, path: Option) -> Result<(), Error> { Ok(()) } +#[api( + input: { + properties: { + "master-keyfile": { + description: "(Private) master key to use.", + }, + "encrypted-keyfile": { + description: "RSA-encrypted keyfile to import.", + }, + kdf: { + type: Kdf, + optional: true, + }, + "path": { + description: + "Output file. Without this the key will become the new default encryption key.", + optional: true, + } + }, + }, +)] +/// Import an encrypted backup of an encryption key using a (private) master key. +async fn import_with_master_key( + master_keyfile: String, + encrypted_keyfile: String, + kdf: Option, + path: Option, +) -> Result<(), Error> { + let path = match path { + Some(path) => PathBuf::from(path), + None => { + let path = place_default_encryption_key()?; + if path.exists() { + bail!("Please remove default encryption key at {:?} before importing to default location (or choose a non-default one).", path); + } + println!("Importing key to default location at: {:?}", path); + path + } + }; + + let encrypted_key = file_get_contents(&encrypted_keyfile)?; + let master_key = file_get_contents(&master_keyfile)?; + let password = tty::read_password("Master Key Password: ")?; + + let master_key = + openssl::pkey::PKey::private_key_from_pem_passphrase(&master_key, &password) + .map_err(|err| format_err!("failed to read PEM-formatted private key - {}", err))? + .rsa() + .map_err(|err| format_err!("not a valid private RSA key - {}", err))?; + + let (key, created, fingerprint) = + rsa_decrypt_key_config(master_key, &encrypted_key, &get_encryption_key_password)?; + + let kdf = kdf.unwrap_or_default(); + match kdf { + Kdf::None => { + let modified = proxmox::tools::time::epoch_i64(); + + store_key_config( + &path, + true, + KeyConfig { + kdf: None, + created, // keep original value + modified, + data: key.to_vec(), + fingerprint: Some(fingerprint), + }, + )?; + } + Kdf::Scrypt | Kdf::PBKDF2 => { + let password = tty::read_and_verify_password("New Password: ")?; + + let mut new_key_config = encrypt_key_with_passphrase(&key, &password, kdf)?; + new_key_config.created = created; // keep original value + new_key_config.fingerprint = Some(fingerprint); + + store_key_config(&path, true, new_key_config)?; + } + } + + Ok(()) +} + #[api( input: { properties: { @@ -446,6 +532,14 @@ pub fn cli() -> CliCommandMap { .arg_param(&["path"]) .completion_cb("path", tools::complete_file_name); + let key_import_with_master_key_cmd_def = CliCommand::new(&API_METHOD_IMPORT_WITH_MASTER_KEY) + .arg_param(&["master-keyfile"]) + .completion_cb("master-keyfile", tools::complete_file_name) + .arg_param(&["encrypted-keyfile"]) + .completion_cb("encrypted-keyfile", tools::complete_file_name) + .arg_param(&["path"]) + .completion_cb("path", tools::complete_file_name); + let key_change_passphrase_cmd_def = CliCommand::new(&API_METHOD_CHANGE_PASSPHRASE) .arg_param(&["path"]) .completion_cb("path", tools::complete_file_name); @@ -465,6 +559,7 @@ pub fn cli() -> CliCommandMap { CliCommandMap::new() .insert("create", key_create_cmd_def) + .insert("import-with-master-key", key_import_with_master_key_cmd_def) .insert("create-master-key", key_create_master_key_cmd_def) .insert("import-master-pubkey", key_import_master_pubkey_cmd_def) .insert("change-passphrase", key_change_passphrase_cmd_def) -- 2.20.1