use pgp::composed::{Message, SignedSecretKey};
use pgp::types::Password;
use super::errors::CryptoError;
pub fn decrypt_armored(
armored: &str,
key: &SignedSecretKey,
key_pw: &Password,
) -> Result<Vec<u8>, CryptoError> {
let (message, _headers) =
Message::from_string(armored).map_err(|e| CryptoError::Parse(format!("message: {e}")))?;
let decrypted = message
.decrypt(key_pw, key)
.map_err(|e| CryptoError::Decrypt(e.to_string()))?;
read_plaintext(decrypted)
}
pub fn decrypt_binary(
ciphertext: &[u8],
key: &SignedSecretKey,
key_pw: &Password,
) -> Result<Vec<u8>, CryptoError> {
let message =
Message::from_bytes(ciphertext).map_err(|e| CryptoError::Parse(format!("message: {e}")))?;
let decrypted = message
.decrypt(key_pw, key)
.map_err(|e| CryptoError::Decrypt(e.to_string()))?;
read_plaintext(decrypted)
}
pub fn decrypt_armored_with_password(
armored: &str,
password: &[u8],
) -> Result<Vec<u8>, CryptoError> {
let (message, _headers) =
Message::from_string(armored).map_err(|e| CryptoError::Parse(format!("message: {e}")))?;
let decrypted = message
.decrypt_with_password(&Password::from(password))
.map_err(|e| CryptoError::Decrypt(e.to_string()))?;
read_plaintext(decrypted)
}
pub fn decrypt_armored_any(
armored: &str,
keys: &[(&SignedSecretKey, &Password)],
) -> Result<Vec<u8>, CryptoError> {
let mut last_err = CryptoError::Decrypt("no candidate keys".into());
for (key, pw) in keys {
match decrypt_armored(armored, key, pw) {
Ok(plaintext) => return Ok(plaintext),
Err(e) => last_err = e,
}
}
Err(last_err)
}
pub(crate) fn read_plaintext(mut message: Message<'_>) -> Result<Vec<u8>, CryptoError> {
while message.is_compressed() {
message = message
.decompress()
.map_err(|e| CryptoError::Decrypt(format!("decompress: {e}")))?;
}
message
.as_data_vec()
.map_err(|e| CryptoError::Decrypt(format!("read literal: {e}")))
}
#[cfg(test)]
mod tests {
use super::decrypt_armored_with_password;
use crate::crypto::derive_key_passphrase;
use pgp::composed::MessageBuilder;
use pgp::crypto::sym::SymmetricKeyAlgorithm;
use pgp::types::{Password, StringToKey};
fn encrypt_armored_with_password(plaintext: &[u8], password: &[u8]) -> String {
let mut rng = rand_08::thread_rng();
let s2k = StringToKey::new_default(&mut rng);
let mut builder = MessageBuilder::from_bytes(String::new(), plaintext.to_vec())
.seipd_v1(&mut rng, SymmetricKeyAlgorithm::AES256);
builder
.encrypt_with_password(s2k, &Password::from(password))
.expect("encrypt with password");
builder
.to_armored_string(&mut rng, Default::default())
.expect("armor")
}
#[test]
fn password_encrypted_message_round_trips() {
let password = b"link-password-and-custom".as_slice();
let salt = [7u8; 16];
let key_password = derive_key_passphrase(password, &salt).expect("derive");
let armored = encrypt_armored_with_password(b"the share passphrase", &key_password);
let plaintext = decrypt_armored_with_password(&armored, &key_password).expect("decrypt");
assert_eq!(plaintext, b"the share passphrase");
}
#[test]
fn the_wrong_password_does_not_decrypt() {
let salt = [7u8; 16];
let right = derive_key_passphrase(b"correct", &salt).expect("derive");
let wrong = derive_key_passphrase(b"incorrect", &salt).expect("derive");
let armored = encrypt_armored_with_password(b"secret", &right);
assert!(decrypt_armored_with_password(&armored, &wrong).is_err());
}
}