extern crate rand;
#[macro_use]
extern crate pretty_assertions;
extern crate serde_json;
#[macro_use]
extern crate serde;
extern crate pgp;
extern crate pretty_env_logger;
#[macro_use]
extern crate log;
use std::{collections::VecDeque, fs::File, io::BufReader};
use pgp::{
composed::{
CleartextSignedMessage, Deserializable, DetachedSignature, KeyType, Message,
MessageBuilder, PlainSessionKey, SecretKeyParamsBuilder, SignedPublicKey, SignedSecretKey,
},
crypto::{hash::HashAlgorithm, sym::SymmetricKeyAlgorithm},
packet::{LiteralData, Packet, PacketParser},
ser::Serialize,
types::{KeyDetails, KeyId, Password},
};
use rand::SeedableRng;
use rand_chacha::ChaCha8Rng;
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
struct Testcase {
typ: Option<String>,
decrypt_key: String,
passphrase: String,
verify_key: Option<String>,
filename: Option<String>,
timestamp: Option<u64>,
textcontent: Option<String>,
keyid: Option<String>,
}
fn test_parse_msg(entry: &str, base_path: &str, _is_normalized: bool) {
let _ = pretty_env_logger::try_init();
let n = format!("{base_path}/{entry}");
let mut file = File::open(&n).unwrap_or_else(|_| panic!("no file: {}", &n));
let details: Testcase = serde_json::from_reader(&mut file).unwrap();
info!(
"Testcase: {}",
serde_json::to_string_pretty(&details).unwrap()
);
let mut decrypt_key_file =
File::open(format!("{}/{}", base_path, details.decrypt_key)).unwrap();
let (decrypt_key, _headers) = SignedSecretKey::from_armor_single(&mut decrypt_key_file)
.expect("failed to read decryption key");
decrypt_key
.verify_bindings()
.expect("invalid decryption key");
let decrypt_id = hex::encode(decrypt_key.legacy_key_id());
info!("decrypt key (ID={})", &decrypt_id);
if let Some(id) = &details.keyid {
assert_eq!(id, &decrypt_id, "invalid keyid");
}
let verify_key = if let Some(verify_key_str) = details.verify_key.clone() {
let mut verify_key_file = File::open(format!("{base_path}/{verify_key_str}")).unwrap();
let (verify_key, _headers) = SignedPublicKey::from_armor_single(&mut verify_key_file)
.expect("failed to read verification key");
verify_key
.verify_bindings()
.expect("invalid verification key");
let verify_id = hex::encode(verify_key.legacy_key_id());
info!("verify key (ID={})", &verify_id);
Some(verify_key)
} else {
None
};
let file_name = entry.replace(".json", ".asc");
let cipher_file_path = format!("{base_path}/{file_name}");
let (message, _headers) =
Message::from_armor_file(cipher_file_path).expect("failed to parse message");
info!("message: {:?}", &message);
match &message {
Message::Encrypted { .. } => {
let decrypted = message
.decrypt(&details.passphrase.into(), &decrypt_key)
.expect("failed to init decryption");
let mut msg = decrypted.decompress().expect("compression");
dbg!(&msg);
let data = msg.as_data_string().unwrap();
assert_eq!(data, details.textcontent.unwrap_or_default());
if let Some(verify_key) = verify_key {
msg.verify(&verify_key.primary_key)
.expect("message verification failed");
}
}
Message::Signed { reader, .. } => {
println!("signature: {:?}", reader.signatures());
}
_ => {
panic!("this test should not have anything else?");
}
}
}
macro_rules! msg_test {
($name:ident, $pos:expr, $normalized:expr) => {
#[test]
fn $name() {
test_parse_msg(
&format!("{}.json", $pos),
"./tests/openpgp-interop/testcases/messages",
$normalized,
);
}
};
}
msg_test!(msg_gnupg_v1_001, "gnupg-v1-001", false);
msg_test!(msg_gnupg_v1_003, "gnupg-v1-003", false);
msg_test!(msg_gnupg_v1_4_11_001, "gnupg-v1-4-11-001", true);
msg_test!(msg_gnupg_v1_4_11_002, "gnupg-v1-4-11-002", false);
msg_test!(msg_gnupg_v1_4_11_003, "gnupg-v1-4-11-003", true);
msg_test!(msg_gnupg_v1_4_11_004, "gnupg-v1-4-11-004", true);
msg_test!(msg_gnupg_v1_4_11_005, "gnupg-v1-4-11-005", true);
msg_test!(msg_gnupg_v1_4_11_006, "gnupg-v1-4-11-006", false);
msg_test!(msg_gnupg_v2_0_17_001, "gnupg-v2-0-17-001", true);
msg_test!(msg_gnupg_v2_0_17_002, "gnupg-v2-0-17-002", false);
msg_test!(msg_gnupg_v2_0_17_003, "gnupg-v2-0-17-003", true);
msg_test!(msg_gnupg_v2_0_17_004, "gnupg-v2-0-17-004", true);
msg_test!(msg_gnupg_v2_0_17_005, "gnupg-v2-0-17-005", true);
msg_test!(msg_gnupg_v2_0_17_006, "gnupg-v2-0-17-006", true);
msg_test!(msg_gnupg_v2_10_001, "gnupg-v2-10-001", true);
msg_test!(msg_gnupg_v2_10_002, "gnupg-v2-10-002", true);
msg_test!(msg_gnupg_v2_10_003, "gnupg-v2-10-003", true);
msg_test!(msg_gnupg_v2_10_004, "gnupg-v2-10-004", false);
msg_test!(msg_gnupg_v2_10_005, "gnupg-v2-10-005", true);
msg_test!(msg_gnupg_v2_10_006, "gnupg-v2-10-006", true);
msg_test!(msg_gnupg_v2_10_007, "gnupg-v2-10-007", true);
msg_test!(msg_pgp_10_0_001, "pgp-10-0-001", false);
msg_test!(msg_pgp_10_0_002, "pgp-10-0-002", false);
msg_test!(msg_pgp_10_0_003, "pgp-10-0-003", false);
msg_test!(msg_pgp_10_0_004, "pgp-10-0-004", false);
msg_test!(msg_pgp_10_0_005, "pgp-10-0-005", false);
msg_test!(msg_pgp_10_0_006, "pgp-10-0-006", false);
msg_test!(msg_pgp_10_0_007, "pgp-10-0-007", false);
msg_test!(msg_camellia128_001, "camellia128-001", false);
msg_test!(msg_camellia192_001, "camellia192-001", false);
msg_test!(msg_camellia256_001, "camellia256-001", false);
msg_test!(msg_openpgp_001, "openpgp-001", false);
macro_rules! msg_test_js {
($name:ident, $pos:expr, $normalized:expr) => {
#[test]
fn $name() {
test_parse_msg(&format!("{}.json", $pos), "./tests/openpgpjs", $normalized);
}
};
}
msg_test_js!(msg_openpgpjs_x25519, "x25519", true);
#[test]
fn msg_partial_body_len() {
let msg_file = "./tests/partial.asc";
Message::from_armor_file(msg_file).expect("failed to parse message");
}
#[test]
fn msg_regression_01() {
let msg_file = "./tests/regression-01.asc";
Message::from_armor_file(msg_file).expect("failed to parse message");
}
#[test]
fn msg_large_indeterminate_len() {
let _ = pretty_env_logger::try_init();
let msg_file = "./tests/indeterminate.asc";
let (message, _headers) = Message::from_armor_file(msg_file).expect("failed to parse message");
let mut key_file = File::open("./tests/openpgpjs/x25519.sec.asc").unwrap();
let (decrypt_key, _headers) =
SignedSecretKey::from_armor_single(&mut key_file).expect("failed to parse key");
let decrypted = message
.decrypt(&"moon".into(), &decrypt_key)
.expect("failed to decrypt message");
let mut msg = decrypted.decompress().unwrap();
let raw = msg.as_data_string().unwrap();
assert_eq!(
raw,
"Content-Type: text/plain; charset=us-ascii
Autocrypt-Gossip: addr=deltabot@codespeak.net; keydata=
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
I=
Autocrypt-Gossip: addr=holger@merlinux.eu; keydata=
mQENBFHjpUYBCADtXtH0nIjMpuaWgOvcg6/bBJKhDW9mosTOYH1XaArGG2REhgTh8CyU27qPG+1NKO
qm5VT4JWfG91TgvBQdx37ejiLxK9pkqkDMSSHCd5+6lPpgYOTueejToVHTRcHLp2fv7DOJ1s+G05TX
T6gesTVvCyNXpGJN/RXbfF5XOBb4Q+5rp7t9ygjb9F97zkeT6YKAAtYqnZNUvamfmNK+vKFyhwhWJX
0Fb6qP3cvlxh4kXbeVdRjlf1Bg17OVcS1uUTI51W67x7vKgOWSUx1gpArq/YYg43o0kcnzj1mEUdjw
gu7qAOwoq3b9tHefG971/3/zbPC6lpli7oUV7cfdmSZPABEBAAG0ImhvbGdlciBrcmVrZWwgPGhvbG
dlckBtZXJsaW51eC5ldT6JATsEEwECACUCGwMGCwkIBwMCBhUIAgkKCwQWAgMBAh4BAheABQJR5XTc
AhkBAAoJEI47A6J5t3LWGFYH/iG8e2Rn6D/Z5q7vAF00SCkRYzhDqVEx7bX/YazmfiUQImjBnbZZa5
zCQZSDYjAZdwNKBUpdG8Xlc+TI5qLBNEiapOPUYUaaJuG6GtaRF0E36yqvh//VDnCpeeurpn4EhyFB
2SeoMqNxVhv0gdzUi8jp9fHlWNvvYgeTU2y3+9EXGLgayoDPEoUSSF8AOSa3SkgzDnTWNTOVrHJ5UV
j2mZTW6HBYPfnKmu/3aERlDH0pOYHBT1bzT6JRBvADZsEln8OM2ODyMjFNiUb7IHbpQb2JETFdMY54
E6gT7pCwleE/K3yovWsUdrJo6YruU2xdlCIWf3qfUQ5xcXUsTitOjky0H2hvbGdlciBrcmVrZWwgPG
hwa0B0cmlsbGtlLm5ldD6JATgEEwECACIFAlHlXhICGwMGCwkIBwMCBhUIAgkKCwQWAgMBAh4BAheA
AAoJEI47A6J5t3LWYKsIAOU6h2W9lQIKJVgRQMXRjk6vS6QIl3t0we/N9u52YBcE2iGYiyC9a5+VTv
Z4OTDWV6gx8KYFnK6V5PYL6+CZJ/qfsImWwnb6Rp0nGulPjxEhiVjNakQryVZhcXKE8lhMhWYPRxUG
gEb3VtOI7HUFVVnhLiakfr8ULe7b5O4EWiYPFxO+5kr44Xvxc3mHrKbfHGuJUxKlAiiQeoiCA/E2cD
SMq3qEcrzE9UeW/1qn1pIxx/tGhMSSR7TKQkzTBUyEepY/wh1JHGXIsd7L0bmowG0YF+I5tG4FOZjj
kzDPayR5zYyvu/A8L3ynP9lwloJCkyKGVQv9c/nCJCNgimgTiWe5AQ0EUeOlRgEIANjZCj/cBHinl1
8SLdY8VsruEEiFBTgOZn7lWOFcF4bSoJm6bzXckBgPp8yd77MEn7HsfMe9tJuriNvAVl8Ybxqum543
+KtJg1oZ9qv8RQ8OCXRjwNl7dxh41lKmyomFSKhyhmCxLkIwoh+XD2vTiD/w7j9QCtBzQ+UsHLWG4w
XHkZ7SfOkVE8EVN/ygqOFeOVRmozckm7pv71JOYlVGO+Gk265ZO3hlstPJgWIbe28S46lDX4wmyJw7
tIuu7zeKTbINztMOUV79S7N2uNE5dt18EtlQb+k4l6JWvpZM+URiPGfLSgCi51njVkSELORW/OrMAJ
JImPt7eY/7dtVL6ekAEQEAAYkBHwQYAQIACQUCUeOlRgIbDAAKCRCOOwOiebdy1pp6B/9mMHozAVOS
oVhnj4QmlTGlRJxs6tHgTkJ47RlqmRRjYpY4G36rs21KPH++w5E8eLFpQwI6EZ+3yBiNQ7lpRhPmAo
8jP38zvvmT3a1WmvVIBbmwDcGpVvlE6kk3djiJ2jOPfvpwPG42A4trOyvuZtJ38nvzyyuwtg3OhHfX
dhjEPzJDSJeUZuRgz+aE7+38edwFi3jwb8gOB3QhrrKo4fL1nMHrrgZK4+n8so5Np4OhX0RBkfy8Jj
idxg9xawubYJDHcjc242Wl/gcAIUcnQZ4tEFOL55SCgih1LtlQLsrdnkJgnGI7VepNL1MwMXnAvfIb
1CvHBWNRmnPMaFMeSpgJ
test1
"
);
}
#[test]
fn msg_literal_signature() {
let (pkey, _) = SignedPublicKey::from_armor_single(
File::open("./tests/autocrypt/alice@autocrypt.example.pub.asc").unwrap(),
)
.unwrap();
let (msg, _) = Message::from_armor_file("./tests/literal-text-signed.asc")
.expect("failed to parse message");
let mut msg = msg.decompress().unwrap();
msg.verify_read(&pkey).unwrap();
}
#[test]
fn binary_msg_password() {
let message = Message::from_file("./tests/binary_password.pgp").unwrap();
let decrypted = message.decrypt_with_password(&"1234".into()).unwrap();
let decompressed = decrypted.decompress().unwrap();
assert!(decompressed.is_literal());
assert_eq!(
decompressed.literal_data_header().unwrap().file_name(),
"README.md"
);
}
#[test]
fn wildcard_id_decrypt() {
let (skey, _headers) = SignedSecretKey::from_armor_single(
std::fs::File::open("./tests/draft-bre-openpgp-samples-00/bob.sec.asc").unwrap(),
)
.unwrap();
let (msg, _) = Message::from_armor_file("./tests/wildcard.msg").expect("msg");
let mut dec = msg.decrypt(&Password::empty(), &skey).expect("decrypt");
let decrypted = dec.as_data_string().unwrap();
assert_eq!(&decrypted, "Hello World :)");
}
#[test]
fn skesk_decrypt() {
let (msg, _) = Message::from_armor_file("./tests/sym-password.msg").expect("msg");
let mut dec = msg
.decrypt_with_password(&Password::from("password"))
.expect("decrypt_with_password");
let decrypted = dec.as_data_string().unwrap();
assert_eq!(&decrypted, "hello world");
}
#[test]
fn pgp6_decrypt() {
let (skey, _headers) = SignedSecretKey::from_armor_single(
std::fs::File::open("./tests/pgp6/alice.sec.asc").unwrap(),
)
.unwrap();
let (msg, _) = Message::from_armor_file("./tests/pgp6/hello.msg").expect("msg");
dbg!(&msg);
let dec = msg
.decrypt_legacy(&Password::empty(), &skey)
.expect("decrypt");
let mut dec = dec.decompress().expect("decompress");
let decrypted = dec.as_data_string().unwrap();
assert_eq!(&decrypted, "hello world\n");
}
#[test]
fn test_compression_quine() {
let (skey, _headers) = SignedSecretKey::from_armor_single(
std::fs::File::open("./tests/autocrypt/alice@autocrypt.example.sec.asc").unwrap(),
)
.unwrap();
let pkey = skey.public_key();
let msg = Message::from_file("./tests/quine.out").unwrap();
let mut msg = msg.decompress().unwrap();
let res = msg.as_data_vec().unwrap();
assert_eq!(res.len(), 176);
let msg = Message::from_file("./tests/quine.out").unwrap();
assert!(msg.verify(pkey).is_err());
}
#[test]
fn test_text_signature_normalization() {
let (mut signed_msg, _header) =
Message::from_armor_file("./tests/unit-tests/text_signature_normalization.msg").unwrap();
let (skey, _headers) = SignedSecretKey::from_armor_single(
std::fs::File::open("./tests/unit-tests/text_signature_normalization_alice.key").unwrap(),
)
.unwrap();
let signing = skey
.secret_subkeys
.iter()
.find(|key| {
key.legacy_key_id() == KeyId::from([0x64, 0x35, 0x7E, 0xB6, 0xBB, 0x55, 0xDE, 0x12])
})
.unwrap();
let verify = signing.public_key();
signed_msg
.verify_read(&verify)
.expect("signature seems bad");
}
const ANNEX_A_3: &str = "-----BEGIN PGP PUBLIC KEY BLOCK-----
xioGY4d/4xsAAAAg+U2nu0jWCmHlZ3BqZYfQMxmZu52JGggkLq2EVD34laPCsQYf
GwoAAABCBYJjh3/jAwsJBwUVCg4IDAIWAAKbAwIeCSIhBssYbE8GCaaX5NUt+mxy
KwwfHifBilZwj2Ul7Ce62azJBScJAgcCAAAAAK0oIBA+LX0ifsDm185Ecds2v8lw
gyU2kCcUmKfvBXbAf6rhRYWzuQOwEn7E/aLwIwRaLsdry0+VcallHhSu4RN6HWaE
QsiPlR4zxP/TP7mhfVEe7XWPxtnMUMtf15OyA51YBM4qBmOHf+MZAAAAIIaTJINn
+eUBXbki+PSAld2nhJh/LVmFsS+60WyvXkQ1wpsGGBsKAAAALAWCY4d/4wKbDCIh
BssYbE8GCaaX5NUt+mxyKwwfHifBilZwj2Ul7Ce62azJAAAAAAQBIKbpGG2dWTX8
j+VjFM21J0hqWlEg+bdiojWnKfA5AQpWUWtnNwDEM0g12vYxoWM8Y81W+bHBw805
I8kWVkXU6vFOi+HWvv/ira7ofJu16NnoUkhclkUrk0mXubZvyl4GBg==
-----END PGP PUBLIC KEY BLOCK-----";
const ANNEX_A_4: &str = "-----BEGIN PGP PRIVATE KEY BLOCK-----
xUsGY4d/4xsAAAAg+U2nu0jWCmHlZ3BqZYfQMxmZu52JGggkLq2EVD34laMAGXKB
exK+cH6NX1hs5hNhIB00TrJmosgv3mg1ditlsLfCsQYfGwoAAABCBYJjh3/jAwsJ
BwUVCg4IDAIWAAKbAwIeCSIhBssYbE8GCaaX5NUt+mxyKwwfHifBilZwj2Ul7Ce6
2azJBScJAgcCAAAAAK0oIBA+LX0ifsDm185Ecds2v8lwgyU2kCcUmKfvBXbAf6rh
RYWzuQOwEn7E/aLwIwRaLsdry0+VcallHhSu4RN6HWaEQsiPlR4zxP/TP7mhfVEe
7XWPxtnMUMtf15OyA51YBMdLBmOHf+MZAAAAIIaTJINn+eUBXbki+PSAld2nhJh/
LVmFsS+60WyvXkQ1AE1gCk95TUR3XFeibg/u/tVY6a//1q0NWC1X+yui3O24wpsG
GBsKAAAALAWCY4d/4wKbDCIhBssYbE8GCaaX5NUt+mxyKwwfHifBilZwj2Ul7Ce6
2azJAAAAAAQBIKbpGG2dWTX8j+VjFM21J0hqWlEg+bdiojWnKfA5AQpWUWtnNwDE
M0g12vYxoWM8Y81W+bHBw805I8kWVkXU6vFOi+HWvv/ira7ofJu16NnoUkhclkUr
k0mXubZvyl4GBg==
-----END PGP PRIVATE KEY BLOCK-----";
#[test]
fn test_v6_annex_a_6() {
let (ssk, _) = SignedPublicKey::from_string(ANNEX_A_3).expect("SSK from armor");
let msg = "-----BEGIN PGP SIGNED MESSAGE-----
What we need from the grocery store:
- - tofu
- - vegetables
- - noodles
-----BEGIN PGP SIGNATURE-----
wpgGARsKAAAAKQWCY5ijYyIhBssYbE8GCaaX5NUt+mxyKwwfHifBilZwj2Ul7Ce6
2azJAAAAAGk2IHZJX1AhiJD39eLuPBgiUU9wUA9VHYblySHkBONKU/usJ9BvuAqo
/FvLFuGWMbKAdA+epq7V4HOtAPlBWmU8QOd6aud+aSunHQaaEJ+iTFjP2OMW0KBr
NK2ay45cX1IVAQ==
-----END PGP SIGNATURE-----";
let (msg, _) = CleartextSignedMessage::from_string(msg).unwrap();
msg.verify(&ssk).expect("verify");
}
#[test]
fn test_v6_annex_a_7() {
pretty_env_logger::try_init().ok();
let (ssk, _) = SignedPublicKey::from_string(ANNEX_A_3).expect("SSK from armor");
assert_eq!(ssk.details.direct_signatures.len(), 1);
let msg = "-----BEGIN PGP MESSAGE-----
xEYGAQobIHZJX1AhiJD39eLuPBgiUU9wUA9VHYblySHkBONKU/usyxhsTwYJppfk
1S36bHIrDB8eJ8GKVnCPZSXsJ7rZrMkBy0p1AAAAAABXaGF0IHdlIG5lZWQgZnJv
bSB0aGUgZ3JvY2VyeSBzdG9yZToKCi0gdG9mdQotIHZlZ2V0YWJsZXMKLSBub29k
bGVzCsKYBgEbCgAAACkFgmOYo2MiIQbLGGxPBgmml+TVLfpscisMHx4nwYpWcI9l
JewnutmsyQAAAABpNiB2SV9QIYiQ9/Xi7jwYIlFPcFAPVR2G5ckh5ATjSlP7rCfQ
b7gKqPxbyxbhljGygHQPnqau1eBzrQD5QVplPEDnemrnfmkrpx0GmhCfokxYz9jj
FtCgazStmsuOXF9SFQE=
-----END PGP MESSAGE-----";
let (mut msg, _) = Message::from_string(msg).unwrap();
dbg!(&msg);
msg.verify_read(&ssk).expect("verify");
}
#[test]
fn test_v6_annex_a_8() {
let (ssk, _) = SignedSecretKey::from_string(ANNEX_A_4).expect("SSK from armor");
let msg = "-----BEGIN PGP MESSAGE-----
wV0GIQYSyD8ecG9jCP4VGkF3Q6HwM3kOk+mXhIjR2zeNqZMIhRmHzxjV8bU/gXzO
WgBM85PMiVi93AZfJfhK9QmxfdNnZBjeo1VDeVZheQHgaVf7yopqR6W1FT6NOrfS
aQIHAgZhZBZTW+CwcW1g4FKlbExAf56zaw76/prQoN+bAzxpohup69LA7JW/Vp0l
yZnuSj3hcFj0DfqLTGgr4/u717J+sPWbtQBfgMfG9AOIwwrUBqsFE9zW+f1zdlYo
bhF30A+IitsxxA==
-----END PGP MESSAGE-----";
let (message, _) = Message::from_string(msg).expect("ok");
let mut dec = message.decrypt(&Password::empty(), &ssk).expect("decrypt");
let decrypted = dec.as_data_string().unwrap();
assert_eq!(&decrypted, "Hello, world!");
}
#[test]
fn test_invalid_partial_messages() {
pretty_env_logger::try_init().ok();
let (ssk, _headers) =
SignedSecretKey::from_armor_file("./tests/draft-bre-openpgp-samples-00/bob.sec.asc")
.expect("ssk");
let (message, _) =
Message::from_armor_file("./tests/partial_invalid_two_fixed.asc").expect("ok");
dbg!(&message);
let mut msg = message.decrypt(&Password::empty(), &ssk).expect("decrypt");
let err = msg.as_data_vec().unwrap_err();
dbg!(&err);
assert!(
err.to_string().contains("unexpected trailing"),
"found error: {err}"
);
let (message, _) =
Message::from_armor_file("./tests/partial_invalid_two_fixed_empty.asc").expect("ok");
dbg!(&message);
let mut msg = message.decrypt(&Password::empty(), &ssk).expect("decrypt");
let err = msg.as_data_vec().unwrap_err();
dbg!(&err);
assert!(
err.to_string().contains("unexpected trailing"),
"found error: {err}"
);
let (message, _) =
Message::from_armor_file("./tests/partial_invalid_short_last.asc").expect("ok");
dbg!(&message);
let mut msg = message.decrypt(&Password::empty(), &ssk).expect("decrypt");
let err = msg.as_data_vec().unwrap_err();
dbg!(&err);
assert!(
err.to_string()
.contains("Fixed chunk was shorter than expected"),
"found error: {err}"
);
}
#[test]
fn test_invalid_multi_message() {
pretty_env_logger::try_init().ok();
let (ssk, _headers) =
SignedSecretKey::from_armor_file("./tests/draft-bre-openpgp-samples-00/bob.sec.asc")
.expect("ssk");
let (message, _) = Message::from_armor_file("./tests/multi_message_1.asc").expect("ok");
dbg!(&message);
let mut msg = message.decrypt(&Password::empty(), &ssk).expect("decrypt");
dbg!(&msg);
let err = msg.as_data_vec().unwrap_err();
dbg!(&err);
let err_string = err.to_string();
assert!(
err_string.contains("unexpected trailing") && err_string.contains("LiteralData"),
"found error: {err_string}"
);
}
#[test]
fn test_packet_excess_data() {
pretty_env_logger::try_init().ok();
let (ssk, _headers) =
SignedSecretKey::from_armor_file("./tests/draft-bre-openpgp-samples-00/bob.sec.asc")
.expect("ssk");
let (message, _) = Message::from_armor_file("./tests/tests/excess_100k.msg").expect("ok");
dbg!(&message);
let msg = message.decrypt(&Password::empty(), &ssk).expect("decrypt");
let mut msg = msg.decompress().unwrap();
dbg!(&msg);
let data = msg.as_data_vec().unwrap();
assert_eq!(&data, b"Hello World :)");
}
#[test]
fn test_two_messages() {
pretty_env_logger::try_init().ok();
let (ssk, _headers) =
SignedSecretKey::from_armor_file("./tests/draft-bre-openpgp-samples-00/bob.sec.asc")
.expect("ssk");
let (message, _) = Message::from_armor_file("./tests/two_messages.asc").expect("ok");
dbg!(&message);
let mut msg = message.decrypt(&Password::empty(), &ssk).expect("decrypt");
let res = msg.as_data_string();
dbg!(&res);
let err = res.unwrap_err();
assert!(
err.to_string().contains("unexpected trailing"),
"found error: {err}"
);
}
#[test]
fn test_two_literals_first_compressed_no_decompression() {
pretty_env_logger::try_init().ok();
let (ssk, _headers) =
SignedSecretKey::from_armor_file("./tests/draft-bre-openpgp-samples-00/bob.sec.asc")
.expect("ssk");
let (message, _) =
Message::from_armor_file("./tests/two_literals_first_compressed.asc").expect("ok");
dbg!(&message);
let mut msg = message.decrypt(&Password::empty(), &ssk).expect("decrypt");
let err = msg.as_data_vec().unwrap_err();
dbg!(&err);
assert!(
err.to_string().contains("unexpected trailing"),
"found error: {err}"
);
}
#[test]
fn test_two_literals_first_compressed_two_times() {
pretty_env_logger::try_init().ok();
let (ssk, _headers) =
SignedSecretKey::from_armor_file("./tests/draft-bre-openpgp-samples-00/bob.sec.asc")
.expect("ssk");
let (message, _) =
Message::from_armor_file("./tests/two_literals_first_compressed_two_times.asc")
.expect("ok");
dbg!(&message);
let mut msg = message.decrypt(&Password::empty(), &ssk).expect("decrypt");
let err = msg.as_data_vec().unwrap_err();
dbg!(&err);
assert!(
err.to_string().contains("unexpected trailing"),
"found error: {err}"
);
}
#[test]
fn test_two_literals_first_compressed_explicit_decompression() {
pretty_env_logger::try_init().ok();
let (ssk, _headers) =
SignedSecretKey::from_armor_file("./tests/draft-bre-openpgp-samples-00/bob.sec.asc")
.expect("ssk");
let (message, _) =
Message::from_armor_file("./tests/two_literals_first_compressed.asc").expect("ok");
dbg!(&message);
let msg = message.decrypt(&Password::empty(), &ssk).expect("decrypt");
let mut msg = msg.decompress().unwrap();
let err = msg.as_data_string().unwrap_err();
dbg!(&err);
assert!(
err.to_string().contains("unexpected trailing"),
"found error: {err}"
);
}
#[test]
fn test_two_literals_first_compressed_two_times_explicit_decompression() {
pretty_env_logger::try_init().ok();
let (ssk, _headers) =
SignedSecretKey::from_armor_file("./tests/draft-bre-openpgp-samples-00/bob.sec.asc")
.expect("ssk");
let (message, _) =
Message::from_armor_file("./tests/two_literals_first_compressed_two_times.asc")
.expect("ok");
dbg!(&message);
let msg = message.decrypt(&Password::empty(), &ssk).expect("decrypt");
let msg = msg.decompress().unwrap();
dbg!(&msg);
let mut msg = msg.decompress().unwrap();
dbg!(&msg);
let res = msg.as_data_string();
dbg!(&res);
let err = res.unwrap_err();
assert!(
err.to_string().contains("unexpected trailing"),
"found error: {err}"
);
}
#[test]
fn test_literal_eating_mdc() {
pretty_env_logger::try_init().ok();
let (ssk, _headers) =
SignedSecretKey::from_armor_file("./tests/draft-bre-openpgp-samples-00/bob.sec.asc")
.expect("ssk");
let (message, _) = Message::from_armor_file("./tests/literal_eating_mdc.asc").expect("ok");
dbg!(&message);
let mut msg = message.decrypt(&Password::empty(), &ssk).expect("decrypt");
let res = msg.as_data_vec();
dbg!(&res);
let err = res.unwrap_err();
assert!(
err.to_string()
.contains("Fixed chunk was shorter than expected"),
"found error: {err}"
);
}
#[test]
fn test_unknown_hash() {
pretty_env_logger::try_init().ok();
let (msg, _) = Message::from_armor_file("tests/sigs/unknown_hash.sig.asc").unwrap();
dbg!(&msg);
let mut msg = msg
.decrypt_with_session_key(PlainSessionKey::V3_4 {
sym_alg: SymmetricKeyAlgorithm::AES256,
key: hex::decode("0A62FC3D10FA134E8C3C915C68AA4B6C6E081D68A9ED1578735AC4743D0381F8")
.unwrap()
.into(),
})
.expect("failed to decrypt");
dbg!(&msg);
let content = msg.as_data_string().expect("failed to read");
assert_eq!(content, "Encrypted, signed message.");
}
#[test]
fn test_unknown_one_pass() {
pretty_env_logger::try_init().ok();
let (ssk, _headers) =
SignedSecretKey::from_armor_file("./tests/draft-bre-openpgp-samples-00/bob.sec.asc")
.expect("ssk");
let (msg, _) = Message::from_armor_file("tests/sigs/unknown_one_pass.sig.asc").unwrap();
dbg!(&msg);
let mut msg = msg
.decrypt(&Password::empty(), &ssk)
.expect("failed to decrypt");
dbg!(&msg);
let content = msg.as_data_string().expect("failed to read");
assert_eq!(content, "Encrypted, signed message.");
dbg!(&msg);
}
#[test]
fn test_signature_leniency() {
pretty_env_logger::try_init().ok();
let (ssk, _headers) =
SignedSecretKey::from_armor_file("./tests/draft-bre-openpgp-samples-00/bob.sec.asc")
.expect("ssk");
let (message, _) = Message::from_armor_file("./tests/message_other_hash.asc").expect("ok");
dbg!(&message);
let mut msg = message.decrypt(&Password::empty(), &ssk).expect("decrypt");
let res = msg.as_data_vec();
dbg!(&res);
assert!(res.is_ok());
let (message, _) = Message::from_armor_file("./tests/message_other_pub_algo.asc").expect("ok");
dbg!(&message);
let mut msg = message.decrypt(&Password::empty(), &ssk).expect("decrypt");
let res = msg.as_data_vec();
dbg!(&res);
assert!(res.is_ok());
let (message, _) =
Message::from_armor_file("./tests/message_future_signature.asc").expect("ok");
dbg!(&message);
let mut msg = message.decrypt(&Password::empty(), &ssk).expect("decrypt");
let res = msg.as_data_vec();
dbg!(&res);
assert!(res.is_ok());
}
#[test]
fn test_packet_leniency() {
pretty_env_logger::try_init().ok();
let (key, _) = SignedPublicKey::from_armor_file("./tests/perturbed.pub.asc").unwrap();
dbg!(&key);
}
#[test]
fn test_mock_pq_cert_leniency_unkown_algo_mpi() {
pretty_env_logger::try_init().ok();
let (key, _) =
SignedPublicKey::from_armor_file("./tests/mock_pq/unknown_algo_mpi.pub.asc").unwrap();
dbg!(&key);
}
#[test]
fn test_mock_pq_cert_leniency_ecdsa_opaque() {
pretty_env_logger::try_init().ok();
let (key, _) =
SignedPublicKey::from_armor_file("./tests/mock_pq/ecdsa_opaque_small.pub.asc").unwrap();
dbg!(&key);
}
#[test]
fn test_mock_pq_cert_leniency_eddsa_opaque() {
pretty_env_logger::try_init().ok();
let (key, _) =
SignedPublicKey::from_armor_file("./tests/mock_pq/eddsa_opaque_small.pub.asc").unwrap();
dbg!(&key);
}
#[test]
fn test_mock_pq_cert_leniency_ecdh_opaque() {
pretty_env_logger::try_init().ok();
let (key, _) =
SignedPublicKey::from_armor_file("./tests/mock_pq/ecdh_opaque_small.pub.asc").unwrap();
dbg!(key);
}
#[test]
fn fuzz_msg_reader() {
pretty_env_logger::try_init().ok();
for file in [
"./tests/fuzz/minimized-from-7585e756306047aba2218ebf70b24c6373e82e2a",
"./tests/fuzz/minimized-from-82b02bbac39a10c7b98d020f78153ffb75c94607",
] {
let _ = Message::from_file(file).unwrap().as_data_vec();
}
}
#[test]
fn fuzz_msg_reader_fail() {
pretty_env_logger::try_init().ok();
for file in [
"./tests/fuzz/crash-1b1482d11c52075aabfc75256626a56c607787f3",
"./tests/fuzz/minimized-from-e2a02fea22523e47a4d74b66bda8f455533bcfbb",
] {
let _ = Message::from_file(file).unwrap_err();
}
}
#[test]
fn message_parsing_pqc_pkesk() {
pretty_env_logger::try_init().ok();
let (message, _) = Message::from_armor_file("./tests/message_pqc.asc").expect("ok");
let Message::Encrypted { esk, .. } = message else {
panic!("destructure encrypted message")
};
assert_eq!(esk.len(), 2);
}
#[test]
fn message_many_one_pass_signatures() {
pretty_env_logger::try_init().ok();
const PLAIN: &str = "hello";
let mut rng = ChaCha8Rng::seed_from_u64(0);
const NUM: usize = 2000;
let keys = make_signing_keys(&mut rng, NUM);
let mut builder = MessageBuilder::from_bytes("", PLAIN.as_bytes());
warn!("signing message");
for skey in &keys {
builder.sign(&skey.primary_key, Password::empty(), HashAlgorithm::Sha256);
}
let signed = builder.to_vec(&mut rng).expect("writing");
warn!("parsing message");
let mut message = Message::from_bytes(&signed[..]).expect("reading");
let plain = message.as_data_string().unwrap();
assert_eq!(plain, PLAIN);
warn!("verifying message");
assert!(message.is_one_pass_signed());
let Message::Signed { reader, .. } = &message else {
panic!("invalid message type");
};
assert_eq!(reader.num_signatures(), keys.len());
assert_eq!(reader.num_one_pass_signatures(), keys.len());
assert_eq!(reader.num_regular_signatures(), 0);
for (i, key) in keys.iter().enumerate() {
message
.verify_nested_explicit(i, key.primary_key.public_key())
.expect("signed");
}
}
#[test]
fn message_many_prefix_signatures() {
pretty_env_logger::try_init().ok();
let mut rng = ChaCha8Rng::seed_from_u64(0);
const NUM: usize = 2000;
let keys = make_signing_keys(&mut rng, NUM);
const PLAIN: &str = "hello";
let sigs: Vec<_> = keys
.iter()
.map(|k| {
DetachedSignature::sign_binary_data(
&mut rng,
&k.primary_key,
&Password::empty(),
HashAlgorithm::Sha256,
PLAIN.as_bytes(),
)
.expect("sign")
})
.collect();
let mut packets: Vec<Packet> = sigs.into_iter().map(|ds| ds.signature.into()).collect();
let lit = LiteralData::from_bytes(&[][..], PLAIN.as_bytes().into()).expect("literal");
packets.push(lit.into());
let signed = packets.to_bytes().expect("bytes");
warn!("parsing message");
let mut message = Message::from_bytes(&signed[..]).expect("reading");
let plain = message.as_data_string().unwrap();
assert_eq!(plain, PLAIN);
warn!("verifying message");
assert!(message.is_signed());
let Message::Signed { reader, .. } = &message else {
panic!("invalid message type");
};
assert_eq!(reader.num_signatures(), keys.len());
assert_eq!(reader.num_one_pass_signatures(), 0);
assert_eq!(reader.num_regular_signatures(), keys.len());
for (i, key) in keys.iter().enumerate() {
message
.verify_nested_explicit(i, key.primary_key.public_key())
.expect("signed");
}
}
#[test]
fn message_many_mixed_signatures() {
pretty_env_logger::try_init().ok();
let mut rng = ChaCha8Rng::seed_from_u64(0);
const NUM: usize = 2000;
let keys = make_signing_keys(&mut rng, NUM);
for order in 0..=1 {
let mut packets: Vec<Packet> = Vec::new();
const PLAIN: &str = "hello";
let lit = LiteralData::from_bytes(&[][..], PLAIN.as_bytes().into()).expect("literal");
let mut regular_sigs: VecDeque<Packet> = VecDeque::new();
let mut ops: VecDeque<Packet> = VecDeque::new();
let mut op_sigs: VecDeque<Packet> = VecDeque::new();
for (i, k) in keys.iter().enumerate() {
if i % 2 == order {
println!("prefixed sig: {i}");
let sig = DetachedSignature::sign_binary_data(
&mut rng,
&k.primary_key,
&Password::empty(),
HashAlgorithm::Sha256,
PLAIN.as_bytes(),
)
.expect("sign");
regular_sigs.push_back(sig.signature.into());
} else {
println!("ops sig: {i}");
let mut builder = MessageBuilder::from_bytes("", PLAIN.as_bytes());
warn!("signing message");
builder.sign(&k.primary_key, Password::empty(), HashAlgorithm::Sha256);
let signed = builder.to_vec(&mut rng).expect("writing");
let mut pp = PacketParser::new(BufReader::new(signed.as_slice()));
let Ok(Packet::OnePassSignature(op)) = pp.next().unwrap() else {
panic!("expected OPS");
};
let _ = pp.next().unwrap();
let Ok(Packet::Signature(sig)) = pp.next().unwrap() else {
panic!("expected Signature");
};
ops.push_back(op.into());
op_sigs.push_back(sig.into());
}
}
let ops_sigs = ops.len();
let prefix_sigs = regular_sigs.len();
assert_eq!(ops_sigs, op_sigs.len());
for i in 0..keys.len() {
if i % 2 == order {
println!("insert regular sig: {i}");
packets.push(regular_sigs.pop_front().unwrap());
} else {
println!("insert ops: {i}");
packets.push(ops.pop_front().unwrap());
}
}
packets.push(lit.into());
for (i, sig) in op_sigs.into_iter().enumerate().rev() {
println!("insert ops sig: {i}");
packets.push(sig);
}
let signed = packets.to_bytes().expect("bytes");
warn!("parsing message");
let mut message = Message::from_bytes(&signed[..]).expect("reading");
let plain = message.as_data_string().unwrap();
assert_eq!(plain, PLAIN);
warn!("verifying message");
assert!(message.is_signed());
let Message::Signed { reader, .. } = &message else {
panic!("invalid message type");
};
assert_eq!(reader.num_signatures(), keys.len());
assert_eq!(reader.num_one_pass_signatures(), ops_sigs);
assert_eq!(reader.num_regular_signatures(), prefix_sigs);
for (i, key) in keys.iter().enumerate() {
println!("-- verifying key {i}");
message
.verify_nested_explicit(i, key.primary_key.public_key())
.expect("signed");
}
}
}
fn make_signing_keys(mut rng: &mut ChaCha8Rng, num: usize) -> Vec<SignedSecretKey> {
(0..num)
.map(|count| {
let key_params = SecretKeyParamsBuilder::default()
.key_type(KeyType::Ed25519)
.can_sign(true)
.primary_user_id(format!("test{}", count))
.build()
.unwrap();
key_params
.generate(&mut rng)
.expect("failed to generate secret key")
})
.collect()
}
#[test]
fn test_two_signed_messages() {
pretty_env_logger::try_init().ok();
let (mut message, _) = Message::from_armor_file("./tests/two_signed.asc").expect("ok");
dbg!(&message);
let err = message.as_data_string().unwrap_err();
dbg!(&err);
assert!(
err.to_string().contains("unexpected trailing"),
"found error: {err}"
);
}