use wecanencrypt::{
add_uid,
bytes_encrypted_for,
create_key,
create_key_simple,
decrypt_bytes,
encrypt_bytes,
encrypt_bytes_to_multiple,
get_key_cipher_details,
get_pub_key,
merge_keys,
parse_cert_bytes,
revoke_uid,
sign_bytes,
sign_bytes_cleartext,
sign_bytes_detached,
update_password,
verify_and_extract_bytes,
verify_bytes,
verify_bytes_detached,
CipherSuite,
SubkeyFlags,
};
const TEST_PASSWORD: &str = "test-password-123";
const TEST_UID: &str = "Test User <test@example.com>";
fn generate_test_key() -> (Vec<u8>, String) {
let key = create_key_simple(TEST_PASSWORD, &[TEST_UID]).unwrap();
(key.secret_key.to_vec(), key.fingerprint)
}
fn generate_test_key_with_cipher(cipher: CipherSuite) -> (Vec<u8>, String) {
let key = create_key(
TEST_PASSWORD,
&[TEST_UID],
cipher,
None,
None,
None,
SubkeyFlags::all(),
false,
true,
)
.unwrap();
(key.secret_key.to_vec(), key.fingerprint)
}
mod key_generation {
use super::*;
#[test]
fn test_create_key_simple() {
let key = create_key_simple(TEST_PASSWORD, &[TEST_UID]).unwrap();
assert!(!key.public_key.is_empty());
assert!(!key.secret_key.is_empty());
assert!(!key.fingerprint.is_empty());
assert_eq!(key.fingerprint.len(), 40); }
#[test]
fn test_create_key_cv25519() {
let key = create_key(
TEST_PASSWORD,
&[TEST_UID],
CipherSuite::Cv25519,
None,
None,
None,
SubkeyFlags::all(),
false,
true,
)
.unwrap();
assert!(!key.fingerprint.is_empty());
}
#[test]
#[ignore = "RSA4k key generation is slow (~10s release, ~200s debug)"]
fn test_create_key_rsa4k() {
let key = create_key(
TEST_PASSWORD,
&[TEST_UID],
CipherSuite::Rsa4k,
None,
None,
None,
SubkeyFlags::all(),
false,
true,
)
.unwrap();
assert!(!key.fingerprint.is_empty());
}
#[test]
fn test_create_key_multiple_uids() {
let uids = &["Alice <alice@example.com>", "Alice Work <alice@work.com>"];
let key = create_key_simple(TEST_PASSWORD, uids).unwrap();
let info = parse_cert_bytes(&key.secret_key, true).unwrap();
assert_eq!(info.user_ids.len(), 2);
assert!(info
.user_ids
.iter()
.any(|u| u.value == "Alice <alice@example.com>"));
assert!(info
.user_ids
.iter()
.any(|u| u.value == "Alice Work <alice@work.com>"));
}
#[test]
fn test_create_key_encryption_only() {
let key = create_key(
TEST_PASSWORD,
&[TEST_UID],
CipherSuite::Cv25519,
None,
None,
None,
SubkeyFlags {
encryption: true,
signing: false,
authentication: false,
},
false,
true,
)
.unwrap();
assert!(!key.fingerprint.is_empty());
}
#[test]
fn test_create_key_empty_uid_fails() {
let result = create_key_simple(TEST_PASSWORD, &[]);
assert!(result.is_err());
}
}
mod parsing {
use super::*;
#[test]
fn test_parse_cert_bytes() {
let (secret_key, fingerprint) = generate_test_key();
let info = parse_cert_bytes(&secret_key, false).unwrap();
assert_eq!(info.fingerprint, fingerprint);
assert!(info.is_secret);
assert_eq!(info.user_ids.len(), 1);
assert_eq!(info.user_ids[0].value, TEST_UID);
}
#[test]
fn test_parse_public_key() {
let (secret_key, _) = generate_test_key();
let public_key = get_pub_key(&secret_key).unwrap();
let info = parse_cert_bytes(public_key.as_bytes(), false).unwrap();
assert!(!info.is_secret);
assert_eq!(info.user_ids.len(), 1);
}
#[test]
fn test_get_key_cipher_details() {
let (secret_key, _) = generate_test_key();
let details = get_key_cipher_details(&secret_key).unwrap();
assert!(!details.is_empty());
for detail in &details {
assert!(!detail.fingerprint.is_empty());
assert!(!detail.algorithm.is_empty());
}
}
}
mod encryption {
use super::*;
#[test]
fn test_encrypt_decrypt_roundtrip() {
let (secret_key, _) = generate_test_key();
let public_key = get_pub_key(&secret_key).unwrap();
let plaintext = b"Hello, World! This is a secret message.";
let ciphertext = encrypt_bytes(public_key.as_bytes(), plaintext, true).unwrap();
assert!(!ciphertext.is_empty());
assert_ne!(&ciphertext[..], plaintext);
let decrypted = decrypt_bytes(&secret_key, &ciphertext, TEST_PASSWORD).unwrap();
assert_eq!(decrypted, plaintext);
}
#[test]
fn test_encrypt_decrypt_binary() {
let (secret_key, _) = generate_test_key();
let public_key = get_pub_key(&secret_key).unwrap();
let plaintext = b"Binary message";
let ciphertext = encrypt_bytes(public_key.as_bytes(), plaintext, false).unwrap();
assert!(!ciphertext.starts_with(b"-----BEGIN"));
let decrypted = decrypt_bytes(&secret_key, &ciphertext, TEST_PASSWORD).unwrap();
assert_eq!(decrypted, plaintext);
}
#[test]
fn test_encrypt_to_multiple_recipients() {
let (secret_key1, _) = generate_test_key();
let (secret_key2, _) = generate_test_key();
let public_key1 = get_pub_key(&secret_key1).unwrap();
let public_key2 = get_pub_key(&secret_key2).unwrap();
let plaintext = b"Message for multiple recipients";
let ciphertext = encrypt_bytes_to_multiple(
&[public_key1.as_bytes(), public_key2.as_bytes()],
plaintext,
true,
)
.unwrap();
let decrypted1 = decrypt_bytes(&secret_key1, &ciphertext, TEST_PASSWORD).unwrap();
let decrypted2 = decrypt_bytes(&secret_key2, &ciphertext, TEST_PASSWORD).unwrap();
assert_eq!(decrypted1, plaintext);
assert_eq!(decrypted2, plaintext);
}
#[test]
fn test_bytes_encrypted_for() {
let (secret_key, _) = generate_test_key();
let public_key = get_pub_key(&secret_key).unwrap();
let plaintext = b"Test message";
let ciphertext = encrypt_bytes(public_key.as_bytes(), plaintext, false).unwrap();
let key_ids = bytes_encrypted_for(&ciphertext).unwrap();
assert!(!key_ids.is_empty());
}
#[test]
fn test_decrypt_wrong_password_fails() {
let (secret_key, _) = generate_test_key();
let public_key = get_pub_key(&secret_key).unwrap();
let plaintext = b"Secret message";
let ciphertext = encrypt_bytes(public_key.as_bytes(), plaintext, true).unwrap();
let result = decrypt_bytes(&secret_key, &ciphertext, "wrong-password");
assert!(result.is_err());
}
#[test]
fn test_decrypt_wrong_key_fails() {
let (secret_key1, _) = generate_test_key();
let (secret_key2, _) = generate_test_key();
let public_key1 = get_pub_key(&secret_key1).unwrap();
let plaintext = b"Secret message";
let ciphertext = encrypt_bytes(public_key1.as_bytes(), plaintext, true).unwrap();
let result = decrypt_bytes(&secret_key2, &ciphertext, TEST_PASSWORD);
assert!(result.is_err());
}
#[test]
fn test_encrypt_rejects_insecure_algorithms() {
use wecanencrypt::{encrypt_bytes_to_multiple_with_algo, SymmetricKeyAlgorithm};
let (secret_key, _) = generate_test_key();
let public_key = get_pub_key(&secret_key).unwrap();
let plaintext = b"test message";
let result = encrypt_bytes_to_multiple_with_algo(
&[public_key.as_bytes()],
plaintext,
true,
SymmetricKeyAlgorithm::Plaintext,
);
assert!(result.is_err());
let result = encrypt_bytes_to_multiple_with_algo(
&[public_key.as_bytes()],
plaintext,
true,
SymmetricKeyAlgorithm::TripleDES,
);
assert!(result.is_err());
let result = encrypt_bytes_to_multiple_with_algo(
&[public_key.as_bytes()],
plaintext,
true,
SymmetricKeyAlgorithm::CAST5,
);
assert!(result.is_err());
let result = encrypt_bytes_to_multiple_with_algo(
&[public_key.as_bytes()],
plaintext,
true,
SymmetricKeyAlgorithm::IDEA,
);
assert!(result.is_err());
let result = encrypt_bytes_to_multiple_with_algo(
&[public_key.as_bytes()],
plaintext,
true,
SymmetricKeyAlgorithm::Blowfish,
);
assert!(result.is_err());
let ciphertext = encrypt_bytes_to_multiple_with_algo(
&[public_key.as_bytes()],
plaintext,
true,
SymmetricKeyAlgorithm::AES128,
)
.unwrap();
let decrypted = decrypt_bytes(&secret_key, &ciphertext, TEST_PASSWORD).unwrap();
assert_eq!(decrypted, plaintext);
let ciphertext = encrypt_bytes_to_multiple_with_algo(
&[public_key.as_bytes()],
plaintext,
true,
SymmetricKeyAlgorithm::AES256,
)
.unwrap();
let decrypted = decrypt_bytes(&secret_key, &ciphertext, TEST_PASSWORD).unwrap();
assert_eq!(decrypted, plaintext);
}
#[test]
fn test_encrypt_large_message() {
let (secret_key, _) = generate_test_key();
let public_key = get_pub_key(&secret_key).unwrap();
let plaintext: Vec<u8> = (0..1_000_000).map(|i| (i % 256) as u8).collect();
let ciphertext = encrypt_bytes(public_key.as_bytes(), &plaintext, false).unwrap();
let decrypted = decrypt_bytes(&secret_key, &ciphertext, TEST_PASSWORD).unwrap();
assert_eq!(decrypted, plaintext);
}
#[test]
fn test_file_encrypted_for() {
use tempfile::tempdir;
use wecanencrypt::{encrypt_file, file_encrypted_for};
let (secret_key, _) = generate_test_key();
let public_key = get_pub_key(&secret_key).unwrap();
let info = parse_cert_bytes(&secret_key, true).unwrap();
let dir = tempdir().unwrap();
let encrypted_path = dir.path().join("encrypted.pgp");
encrypt_file(public_key.as_bytes(), "Cargo.toml", &encrypted_path, false).unwrap();
let key_ids = file_encrypted_for(&encrypted_path).unwrap();
assert!(!key_ids.is_empty());
let our_subkey_ids: Vec<String> = info.subkeys.iter().map(|s| s.key_id.clone()).collect();
assert!(
key_ids.iter().any(|kid| our_subkey_ids.contains(kid)),
"Encrypted file should be for one of our subkeys, got {:?}, expected one of {:?}",
key_ids,
our_subkey_ids
);
}
#[test]
fn test_encrypt_reader_to_file_multiple_recipients() {
use std::io::Cursor;
use tempfile::tempdir;
use wecanencrypt::encrypt_reader_to_file;
let key1 = create_key_simple(TEST_PASSWORD, &["Reader1 <r1@example.com>"]).unwrap();
let key2 = create_key_simple(TEST_PASSWORD, &["Reader2 <r2@example.com>"]).unwrap();
let pub1 = get_pub_key(&key1.secret_key).unwrap();
let pub2 = get_pub_key(&key2.secret_key).unwrap();
let dir = tempdir().unwrap();
let encrypted_path = dir.path().join("encrypted.pgp");
let plaintext = b"Multi-recipient reader encryption";
let reader = Cursor::new(plaintext);
encrypt_reader_to_file(
&[pub1.as_bytes(), pub2.as_bytes()],
reader,
&encrypted_path,
false,
)
.unwrap();
let ciphertext = std::fs::read(&encrypted_path).unwrap();
let decrypted1 = decrypt_bytes(&key1.secret_key, &ciphertext, TEST_PASSWORD).unwrap();
let decrypted2 = decrypt_bytes(&key2.secret_key, &ciphertext, TEST_PASSWORD).unwrap();
assert_eq!(decrypted1, plaintext);
assert_eq!(decrypted2, plaintext);
}
}
mod signing {
use super::*;
#[test]
fn test_sign_verify_roundtrip() {
let (secret_key, _) = generate_test_key();
let public_key = get_pub_key(&secret_key).unwrap();
let message = b"This message will be signed.";
let signed = sign_bytes(&secret_key, message, TEST_PASSWORD).unwrap();
assert!(!signed.is_empty());
let valid = verify_bytes(public_key.as_bytes(), &signed).unwrap();
assert!(valid);
}
#[test]
fn test_sign_verify_and_extract() {
let (secret_key, _) = generate_test_key();
let public_key = get_pub_key(&secret_key).unwrap();
let message = b"Extract this message after verification.";
let signed = sign_bytes(&secret_key, message, TEST_PASSWORD).unwrap();
let extracted = verify_and_extract_bytes(public_key.as_bytes(), &signed).unwrap();
assert_eq!(extracted, message);
}
#[test]
fn test_sign_cleartext() {
let (secret_key, _) = generate_test_key();
let public_key = get_pub_key(&secret_key).unwrap();
let message = b"Cleartext signed message";
let signed = sign_bytes_cleartext(&secret_key, message, TEST_PASSWORD).unwrap();
let signed_str = String::from_utf8_lossy(&signed);
assert!(signed_str.contains("-----BEGIN PGP SIGNED MESSAGE-----"));
let valid = verify_bytes(public_key.as_bytes(), &signed).unwrap();
assert!(valid);
}
#[test]
fn test_sign_detached() {
let (secret_key, _) = generate_test_key();
let public_key = get_pub_key(&secret_key).unwrap();
let message = b"Message with detached signature";
let signature = sign_bytes_detached(&secret_key, message, TEST_PASSWORD).unwrap();
assert!(signature.contains("-----BEGIN PGP SIGNATURE-----"));
let valid =
verify_bytes_detached(public_key.as_bytes(), message, signature.as_bytes()).unwrap();
assert!(valid);
}
#[test]
fn test_verify_wrong_key_fails() {
let (secret_key1, _) = generate_test_key();
let (secret_key2, _) = generate_test_key();
let public_key2 = get_pub_key(&secret_key2).unwrap();
let message = b"Signed message";
let signed = sign_bytes(&secret_key1, message, TEST_PASSWORD).unwrap();
let valid = verify_bytes(public_key2.as_bytes(), &signed).unwrap();
assert!(!valid);
}
#[test]
fn test_verify_detached_tampered_message_fails() {
let (secret_key, _) = generate_test_key();
let public_key = get_pub_key(&secret_key).unwrap();
let message = b"Original message";
let signature = sign_bytes_detached(&secret_key, message, TEST_PASSWORD).unwrap();
let tampered = b"Tampered message";
let valid =
verify_bytes_detached(public_key.as_bytes(), tampered, signature.as_bytes()).unwrap();
assert!(!valid);
}
#[test]
fn test_sign_wrong_password_fails() {
let (secret_key, _) = generate_test_key();
let message = b"Message";
let result = sign_bytes(&secret_key, message, "wrong-password");
assert!(result.is_err());
}
#[test]
fn test_sign_with_primary_key_variants() {
use wecanencrypt::{
sign_bytes_cleartext_with_primary_key, sign_bytes_detached_with_primary_key,
sign_bytes_with_primary_key,
};
let (secret_key, _) = generate_test_key();
let public_key = get_pub_key(&secret_key).unwrap();
let message = b"Test primary key signing";
let signed = sign_bytes_with_primary_key(&secret_key, message, TEST_PASSWORD).unwrap();
let valid = verify_bytes(public_key.as_bytes(), &signed).unwrap();
assert!(valid);
let signed =
sign_bytes_cleartext_with_primary_key(&secret_key, message, TEST_PASSWORD).unwrap();
let valid = verify_bytes(public_key.as_bytes(), &signed).unwrap();
assert!(valid);
let signature =
sign_bytes_detached_with_primary_key(&secret_key, message, TEST_PASSWORD).unwrap();
let valid =
verify_bytes_detached(public_key.as_bytes(), message, signature.as_bytes()).unwrap();
assert!(valid);
}
#[test]
fn test_sign_prefers_signing_subkey() {
let key = create_key(
TEST_PASSWORD,
&[TEST_UID],
CipherSuite::Cv25519,
None,
None,
None,
SubkeyFlags::all(),
false, true,
)
.unwrap();
let public_key = get_pub_key(&key.secret_key).unwrap();
let message = b"Signed by subkey";
let signed = sign_bytes(&key.secret_key, message, TEST_PASSWORD).unwrap();
let valid = verify_bytes(public_key.as_bytes(), &signed).unwrap();
assert!(valid);
let sig = sign_bytes_detached(&key.secret_key, message, TEST_PASSWORD).unwrap();
let valid = verify_bytes_detached(public_key.as_bytes(), message, sig.as_bytes()).unwrap();
assert!(valid);
let signed = sign_bytes_cleartext(&key.secret_key, message, TEST_PASSWORD).unwrap();
let valid = verify_bytes(public_key.as_bytes(), &signed).unwrap();
assert!(valid);
}
#[test]
fn test_sign_fails_for_certify_only_key_without_signing_subkey() {
let key = create_key(
TEST_PASSWORD,
&[TEST_UID],
CipherSuite::Cv25519,
None,
None,
None,
SubkeyFlags::encryption_only(),
false, true,
)
.unwrap();
let message = b"Should fail to sign";
let result = sign_bytes(&key.secret_key, message, TEST_PASSWORD);
assert!(
matches!(result, Err(wecanencrypt::Error::NoSigningSubkey)),
"sign_bytes should fail with NoSigningSubkey, got {:?}",
result
);
let result = sign_bytes_detached(&key.secret_key, message, TEST_PASSWORD);
assert!(
matches!(result, Err(wecanencrypt::Error::NoSigningSubkey)),
"sign_bytes_detached should fail with NoSigningSubkey, got {:?}",
result
);
let result = sign_bytes_cleartext(&key.secret_key, message, TEST_PASSWORD);
assert!(
matches!(result, Err(wecanencrypt::Error::NoSigningSubkey)),
"sign_bytes_cleartext should fail with NoSigningSubkey, got {:?}",
result
);
}
#[test]
fn test_sign_primary_vs_subkey_produces_different_signatures() {
use wecanencrypt::sign_bytes_detached_with_primary_key;
let key = create_key(
TEST_PASSWORD,
&[TEST_UID],
CipherSuite::Cv25519,
None,
None,
None,
SubkeyFlags::all(),
true, true,
)
.unwrap();
let public_key = get_pub_key(&key.secret_key).unwrap();
let message = b"Compare signatures";
let sig_subkey = sign_bytes_detached(&key.secret_key, message, TEST_PASSWORD).unwrap();
let sig_primary =
sign_bytes_detached_with_primary_key(&key.secret_key, message, TEST_PASSWORD).unwrap();
let valid =
verify_bytes_detached(public_key.as_bytes(), message, sig_subkey.as_bytes()).unwrap();
assert!(valid, "subkey signature should verify");
let valid =
verify_bytes_detached(public_key.as_bytes(), message, sig_primary.as_bytes()).unwrap();
assert!(valid, "primary key signature should verify");
assert_ne!(sig_subkey, sig_primary);
}
}
mod key_management {
use super::*;
#[test]
fn test_add_uid() {
let (secret_key, _) = generate_test_key();
let new_uid = "New Identity <new@example.com>";
let updated_key = add_uid(&secret_key, new_uid, TEST_PASSWORD).unwrap();
let info = parse_cert_bytes(&updated_key, true).unwrap();
assert_eq!(info.user_ids.len(), 2);
assert!(info.user_ids.iter().any(|u| u.value == new_uid));
}
#[test]
fn test_revoke_uid() {
let key = create_key_simple(
TEST_PASSWORD,
&[
"Primary <primary@example.com>",
"Secondary <secondary@example.com>",
],
)
.unwrap();
let updated_key = revoke_uid(
&key.secret_key,
"Secondary <secondary@example.com>",
TEST_PASSWORD,
)
.unwrap();
let info = parse_cert_bytes(&updated_key, true).unwrap();
assert!(!info.user_ids.is_empty());
}
#[test]
fn test_update_password() {
let (secret_key, _) = generate_test_key();
let public_key = get_pub_key(&secret_key).unwrap();
let new_password = "new-password-456";
let updated_key = update_password(&secret_key, TEST_PASSWORD, new_password).unwrap();
let message = b"Test message";
let ciphertext = encrypt_bytes(public_key.as_bytes(), message, true).unwrap();
let result = decrypt_bytes(&updated_key, &ciphertext, TEST_PASSWORD);
assert!(result.is_err());
let decrypted = decrypt_bytes(&updated_key, &ciphertext, new_password).unwrap();
assert_eq!(decrypted, message);
}
#[test]
fn test_add_uid_fails_for_public_key() {
let (secret_key, _) = generate_test_key();
let public_key = get_pub_key(&secret_key).unwrap();
let result = add_uid(
public_key.as_bytes(),
"New <new@example.com>",
TEST_PASSWORD,
);
assert!(result.is_err());
}
#[test]
fn test_get_pub_key() {
let (secret_key, fingerprint) = generate_test_key();
let public_key = get_pub_key(&secret_key).unwrap();
assert!(public_key.contains("-----BEGIN PGP PUBLIC KEY BLOCK-----"));
let info = parse_cert_bytes(public_key.as_bytes(), false).unwrap();
assert_eq!(info.fingerprint, fingerprint);
assert!(!info.is_secret);
}
}
mod cross_cipher {
use super::*;
#[test]
fn test_cv25519_encrypt_decrypt() {
let (secret_key, _) = generate_test_key_with_cipher(CipherSuite::Cv25519);
let public_key = get_pub_key(&secret_key).unwrap();
let message = b"Cv25519 encrypted message";
let ciphertext = encrypt_bytes(public_key.as_bytes(), message, true).unwrap();
let decrypted = decrypt_bytes(&secret_key, &ciphertext, TEST_PASSWORD).unwrap();
assert_eq!(decrypted, message);
}
#[test]
fn test_rsa4k_encrypt_decrypt() {
let store = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/files/store");
let public_key = std::fs::read(store.join("rsa4k_public.asc")).unwrap();
let secret_key = std::fs::read(store.join("rsa4k_secret.asc")).unwrap();
let message = b"RSA4k encrypted message";
let ciphertext = encrypt_bytes(&public_key, message, true).unwrap();
let decrypted = decrypt_bytes(&secret_key, &ciphertext, "testpassword").unwrap();
assert_eq!(decrypted, message);
}
#[test]
fn test_cv25519_sign_verify() {
let (secret_key, _) = generate_test_key_with_cipher(CipherSuite::Cv25519);
let public_key = get_pub_key(&secret_key).unwrap();
let message = b"Cv25519 signed message";
let signed = sign_bytes(&secret_key, message, TEST_PASSWORD).unwrap();
let valid = verify_bytes(public_key.as_bytes(), &signed).unwrap();
assert!(valid);
}
#[test]
fn test_rsa4k_sign_verify() {
let store = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/files/store");
let public_key = std::fs::read(store.join("rsa4k_public.asc")).unwrap();
let secret_key = std::fs::read(store.join("rsa4k_secret.asc")).unwrap();
let message = b"RSA4k signed message";
let signed = sign_bytes(&secret_key, message, "testpassword").unwrap();
let valid = verify_bytes(&public_key, &signed).unwrap();
assert!(valid);
}
}
mod reader_encryption {
use std::io::Cursor;
use tempfile::tempdir;
use wecanencrypt::{
create_key_simple, decrypt_reader_to_file, encrypt_reader_to_file, get_pub_key,
};
const TEST_PASSWORD: &str = "test-password-123";
#[test]
fn test_encrypt_decrypt_reader_to_file() {
let dir = tempdir().unwrap();
let encrypted_path = dir.path().join("encrypted.pgp");
let decrypted_path = dir.path().join("decrypted.txt");
let key = create_key_simple(TEST_PASSWORD, &["Reader Test <reader@example.com>"]).unwrap();
let public_key = get_pub_key(&key.secret_key).unwrap();
let plaintext = b"Hello from reader-based encryption!";
let reader = Cursor::new(plaintext);
encrypt_reader_to_file(&[public_key.as_bytes()], reader, &encrypted_path, false).unwrap();
assert!(encrypted_path.exists());
let encrypted_data = std::fs::read(&encrypted_path).unwrap();
let encrypted_reader = Cursor::new(encrypted_data);
decrypt_reader_to_file(
&key.secret_key,
encrypted_reader,
&decrypted_path,
TEST_PASSWORD,
)
.unwrap();
let decrypted = std::fs::read(&decrypted_path).unwrap();
assert_eq!(decrypted, plaintext);
}
}
mod key_flag_policy {
use super::*;
use wecanencrypt::pgp::composed::{SignedKeyDetails, SignedSecretKey};
use wecanencrypt::pgp::packet::{
KeyFlags, PacketTrait, SignatureConfig, SignatureType, Subpacket, SubpacketData,
};
use wecanencrypt::pgp::ser::Serialize;
use wecanencrypt::pgp::types::{KeyDetails, KeyVersion, Password, SignedUser, Timestamp};
fn parse_secret(data: &[u8]) -> SignedSecretKey {
use std::io::Cursor;
use wecanencrypt::pgp::composed::Deserializable;
match SignedSecretKey::from_armor_single(Cursor::new(data)) {
Ok((key, _)) => key,
Err(_) => SignedSecretKey::from_bytes(data).unwrap(),
}
}
fn resign_uid_with_flags(
secret_data: &[u8],
password: &str,
sign_flag: bool,
certify_flag: bool,
) -> Vec<u8> {
let secret_key = parse_secret(secret_data);
let password_obj: Password = password.into();
let mut rng = rand::thread_rng();
let mut new_users = Vec::new();
for signed_user in &secret_key.details.users {
let mut flags = KeyFlags::default();
flags.set_certify(certify_flag);
flags.set_sign(sign_flag);
let hashed_subpackets = vec![
Subpacket::regular(SubpacketData::SignatureCreationTime(Timestamp::now())).unwrap(),
Subpacket::regular(SubpacketData::IssuerFingerprint(
secret_key.primary_key.fingerprint(),
))
.unwrap(),
Subpacket::regular(SubpacketData::KeyFlags(flags)).unwrap(),
];
let mut config = SignatureConfig::from_key(
&mut rng,
&secret_key.primary_key,
SignatureType::CertPositive,
)
.unwrap();
config.hashed_subpackets = hashed_subpackets;
if secret_key.primary_key.version() <= KeyVersion::V4 {
config.unhashed_subpackets =
vec![Subpacket::regular(SubpacketData::IssuerKeyId(
secret_key.primary_key.legacy_key_id(),
))
.unwrap()];
}
let sig = config
.sign_certification(
&secret_key.primary_key,
&secret_key.primary_key.public_key(),
&password_obj,
signed_user.id.tag(),
&signed_user.id,
)
.unwrap();
let mut combined_sigs = signed_user.signatures.clone();
combined_sigs.push(sig);
new_users.push(SignedUser::new(signed_user.id.clone(), combined_sigs));
}
let updated = SignedSecretKey::new(
secret_key.primary_key.clone(),
SignedKeyDetails::new(
secret_key.details.revocation_signatures.clone(),
secret_key.details.direct_signatures.clone(),
new_users,
secret_key.details.user_attributes.clone(),
),
secret_key.public_subkeys.clone(),
secret_key.secret_subkeys.clone(),
);
updated.to_bytes().unwrap()
}
#[test]
fn test_latest_self_sig_wins_for_sign_flag() {
let key = create_key(
TEST_PASSWORD,
&[TEST_UID],
CipherSuite::Cv25519,
None,
None,
None,
SubkeyFlags::all(),
true, true,
)
.unwrap();
let info = parse_cert_bytes(&key.secret_key, true).unwrap();
assert!(
info.can_primary_sign,
"Original key should have primary sign capability"
);
let updated = resign_uid_with_flags(&key.secret_key, TEST_PASSWORD, false, true);
let info2 = parse_cert_bytes(&updated, true).unwrap();
assert!(
!info2.can_primary_sign,
"After adding newer self-sig without sign flag, can_primary_sign should be false"
);
}
#[test]
fn test_latest_self_sig_wins_adding_sign_flag() {
let key = create_key(
TEST_PASSWORD,
&[TEST_UID],
CipherSuite::Cv25519,
None,
None,
None,
SubkeyFlags::all(),
false, true,
)
.unwrap();
let info = parse_cert_bytes(&key.secret_key, true).unwrap();
assert!(
!info.can_primary_sign,
"Original key should NOT have primary sign capability"
);
let updated = resign_uid_with_flags(&key.secret_key, TEST_PASSWORD, true, true);
let info2 = parse_cert_bytes(&updated, true).unwrap();
assert!(
info2.can_primary_sign,
"After adding newer self-sig with sign flag, can_primary_sign should be true"
);
}
#[test]
fn test_merge_preserves_latest_self_sig_flags() {
let key = create_key(
TEST_PASSWORD,
&[TEST_UID],
CipherSuite::Cv25519,
None,
None,
None,
SubkeyFlags::all(),
true, true,
)
.unwrap();
let updated = resign_uid_with_flags(&key.secret_key, TEST_PASSWORD, false, true);
let pub_orig = get_pub_key(&key.secret_key).unwrap();
let pub_updated = get_pub_key(&updated).unwrap();
let merged = merge_keys(pub_orig.as_bytes(), pub_updated.as_bytes(), false).unwrap();
let info = parse_cert_bytes(&merged, false).unwrap();
assert!(
!info.can_primary_sign,
"After merging cert with newer self-sig removing sign flag, can_primary_sign should be false"
);
}
#[test]
fn test_merge_older_self_sig_does_not_override_newer() {
let key_no_sign = create_key(
TEST_PASSWORD,
&[TEST_UID],
CipherSuite::Cv25519,
None,
None,
None,
SubkeyFlags::all(),
false, true,
)
.unwrap();
let key_with_sign =
resign_uid_with_flags(&key_no_sign.secret_key, TEST_PASSWORD, true, true);
let pub_with_sign = get_pub_key(&key_with_sign).unwrap();
let pub_no_sign = get_pub_key(&key_no_sign.secret_key).unwrap();
let merged = merge_keys(pub_with_sign.as_bytes(), pub_no_sign.as_bytes(), false).unwrap();
let info = parse_cert_bytes(&merged, false).unwrap();
assert!(
info.can_primary_sign,
"Merging in older self-sig without sign flag should not override newer self-sig that has it"
);
}
#[test]
fn test_certify_only_key_remains_certify_only_after_accumulation() {
let key = create_key(
TEST_PASSWORD,
&[TEST_UID],
CipherSuite::Cv25519,
None,
None,
None,
SubkeyFlags::all(),
false, true,
)
.unwrap();
let info = parse_cert_bytes(&key.secret_key, true).unwrap();
assert!(
!info.can_primary_sign,
"Certify-only key should not have sign capability"
);
let exp = chrono::Utc::now() + chrono::Duration::days(365);
let updated =
wecanencrypt::update_primary_expiry(&key.secret_key, exp, TEST_PASSWORD).unwrap();
let info2 = parse_cert_bytes(&updated, true).unwrap();
assert!(
!info2.can_primary_sign,
"After expiry update, certify-only key should still not have sign capability"
);
}
}