mirror of
https://github.com/dani-garcia/vaultwarden.git
synced 2026-05-08 20:58:06 +02:00
add optional encryption to rsa_key.pem
This commit is contained in:
parent
f21a3adae2
commit
18a1b00aa7
3 changed files with 58 additions and 6 deletions
55
src/auth.rs
55
src/auth.rs
|
|
@ -69,14 +69,59 @@ pub async fn initialize_keys() -> Result<(), Error> {
|
||||||
Err(e) => return Err(e.into()),
|
Err(e) => return Err(e.into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
let (priv_key, priv_key_buffer) = if let Some(priv_key_buffer) = priv_key_buffer {
|
let passphrase = CONFIG.rsa_key_passphrase();
|
||||||
(Rsa::private_key_from_pem(priv_key_buffer.to_vec().as_slice())?, priv_key_buffer.to_vec())
|
|
||||||
|
let (priv_key, priv_key_buffer) = if let Some(stored_buffer) = priv_key_buffer {
|
||||||
|
let mut passphrase_error: Option<Error> = None;
|
||||||
|
let mut key_is_unencrypted = true;
|
||||||
|
|
||||||
|
// The callback is only invoked if the key file is encrypted. For unencrypted keys it is never called.
|
||||||
|
let rsa = Rsa::private_key_from_pem_callback(stored_buffer.to_vec().as_slice(), |buf| {
|
||||||
|
key_is_unencrypted = false;
|
||||||
|
if passphrase.is_empty() {
|
||||||
|
// Only reached when key is encrypted. Return any error to abort the callback;
|
||||||
|
// the actual error message is stored in passphrase_error above.
|
||||||
|
passphrase_error = Some(Error::other(
|
||||||
|
"Private RSA key is encrypted but RSA_KEY_PASSPHRASE is not configured",
|
||||||
|
));
|
||||||
|
return Err(openssl::error::ErrorStack::get());
|
||||||
|
}
|
||||||
|
let bytes = passphrase.as_bytes();
|
||||||
|
buf[..bytes.len()].copy_from_slice(bytes);
|
||||||
|
Ok(bytes.len())
|
||||||
|
});
|
||||||
|
|
||||||
|
let rsa = match rsa {
|
||||||
|
Ok(r) => r,
|
||||||
|
Err(_) if passphrase_error.is_some() => return Err(passphrase_error.unwrap().into()),
|
||||||
|
Err(e) => return Err(e.into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
if key_is_unencrypted && !passphrase.is_empty() {
|
||||||
|
warn!(
|
||||||
|
"RSA key passphrase is configured but the existing private key '{}' is not encrypted.",
|
||||||
|
CONFIG.private_rsa_key()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodingKey requires an unencrypted PEM, so export the in-memory key regardless of how it was stored on disk.
|
||||||
|
let unencrypted_pem = rsa.private_key_to_pem()?;
|
||||||
|
(rsa, unencrypted_pem)
|
||||||
} else {
|
} else {
|
||||||
let rsa_key = Rsa::generate(2048)?;
|
let rsa_key = Rsa::generate(2048)?;
|
||||||
let priv_key_buffer = rsa_key.private_key_to_pem()?;
|
|
||||||
operator.write(&rsa_key_filename, priv_key_buffer.clone()).await?;
|
// Store encrypted on disk if a passphrase is configured, otherwise store plaintext.
|
||||||
|
let stored_pem = if passphrase.is_empty() {
|
||||||
|
rsa_key.private_key_to_pem()?
|
||||||
|
} else {
|
||||||
|
use openssl::symm::Cipher;
|
||||||
|
rsa_key.private_key_to_pem_passphrase(Cipher::aes_256_cbc(), passphrase.as_bytes())?
|
||||||
|
};
|
||||||
|
operator.write(&rsa_key_filename, stored_pem).await?;
|
||||||
info!("Private key '{}' created correctly", CONFIG.private_rsa_key());
|
info!("Private key '{}' created correctly", CONFIG.private_rsa_key());
|
||||||
(rsa_key, priv_key_buffer)
|
|
||||||
|
let unencrypted_pem = rsa_key.private_key_to_pem()?;
|
||||||
|
(rsa_key, unencrypted_pem)
|
||||||
};
|
};
|
||||||
let pub_key_buffer = priv_key.public_key_to_pem()?;
|
let pub_key_buffer = priv_key.public_key_to_pem()?;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -520,6 +520,8 @@ make_config! {
|
||||||
templates_folder: String, false, auto, |c| format!("{}/templates", c.data_folder);
|
templates_folder: String, false, auto, |c| format!("{}/templates", c.data_folder);
|
||||||
/// Session JWT key
|
/// Session JWT key
|
||||||
rsa_key_filename: String, false, auto, |c| format!("{}/rsa_key", c.data_folder);
|
rsa_key_filename: String, false, auto, |c| format!("{}/rsa_key", c.data_folder);
|
||||||
|
/// RSA key passphrase |> Passphrase used to encrypt the private RSA key file on disk (leave empty for unencrypted)
|
||||||
|
rsa_key_passphrase: String, false, def, String::new();
|
||||||
/// Web vault folder
|
/// Web vault folder
|
||||||
web_vault_folder: String, false, def, "web-vault/".to_string();
|
web_vault_folder: String, false, def, "web-vault/".to_string();
|
||||||
},
|
},
|
||||||
|
|
@ -959,6 +961,11 @@ fn validate_config(cfg: &ConfigItems, on_update: bool) -> Result<(), Error> {
|
||||||
err!(format!("`DATABASE_MIN_CONNS` must be smaller than or equal to `DATABASE_MAX_CONNS`.",));
|
err!(format!("`DATABASE_MIN_CONNS` must be smaller than or equal to `DATABASE_MAX_CONNS`.",));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 1024 = OpenSSL's PEM_BUFSIZE, the size of the buffer passed to the passphrase callback
|
||||||
|
if cfg.rsa_key_passphrase.as_bytes().len() > 1024 {
|
||||||
|
err!("`RSA_KEY_PASSPHRASE` must not exceed 1024 bytes");
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(log_file) = &cfg.log_file {
|
if let Some(log_file) = &cfg.log_file {
|
||||||
if std::fs::OpenOptions::new().append(true).create(true).open(log_file).is_err() {
|
if std::fs::OpenOptions::new().append(true).create(true).open(log_file).is_err() {
|
||||||
err!("Unable to write to log file", log_file);
|
err!("Unable to write to log file", log_file);
|
||||||
|
|
|
||||||
|
|
@ -77,7 +77,7 @@ async fn main() -> Result<(), Error> {
|
||||||
|
|
||||||
check_data_folder().await;
|
check_data_folder().await;
|
||||||
auth::initialize_keys().await.unwrap_or_else(|e| {
|
auth::initialize_keys().await.unwrap_or_else(|e| {
|
||||||
error!("Error creating private key '{}'\n{e:?}\nExiting Vaultwarden!", CONFIG.private_rsa_key());
|
error!("Error creating or loading private key '{}'\n{e:?}\nExiting Vaultwarden!", CONFIG.private_rsa_key());
|
||||||
exit(1);
|
exit(1);
|
||||||
});
|
});
|
||||||
check_web_vault();
|
check_web_vault();
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue