use super::*;
use crate::signing::{KeyEntry, KeyError};
use x509_cert::der::{DecodePem, Encode};
fn p12_password() -> Vec<u8> {
std::fs::read(
std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
.join("../../tests/corpus/pki/p12-password"),
)
.expect("the test .p12 password")
}
fn file(name: &str) -> Vec<u8> {
std::fs::read(
std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
.join("../../tests/corpus")
.join(name),
)
.expect("test file")
}
fn trust() -> Trust {
Trust::with_roots(&[&file("tqsl/pki/root.pem")]).expect("root")
}
fn tlv(tag: u8, value: &[u8]) -> Zeroizing<Vec<u8>> {
let mut out = Zeroizing::new(Vec::with_capacity(value.len() + 6));
out.push(tag);
if value.len() < 128 {
out.push(value.len() as u8);
} else {
let len = (value.len() as u32).to_be_bytes();
let start = len.iter().position(|b| *b != 0).expect("length");
out.push(128 | (4 - start) as u8);
out.extend_from_slice(&len[start..]);
}
out.extend_from_slice(value);
out
}
fn concat(pieces: &[&[u8]]) -> Zeroizing<Vec<u8>> {
let mut out = Zeroizing::new(Vec::with_capacity(pieces.iter().map(|p| p.len()).sum()));
for piece in pieces {
out.extend_from_slice(piece);
}
out
}
fn seq(pieces: &[&[u8]]) -> Zeroizing<Vec<u8>> {
tlv(0x30, &concat(pieces))
}
fn data(bytes: &[u8]) -> Zeroizing<Vec<u8>> {
seq(&[&tlv(6, DATA), &tlv(0xa0, &tlv(4, bytes))])
}
fn bag(oid: &[u8], value: &[u8]) -> Zeroizing<Vec<u8>> {
seq(&[&tlv(6, oid), &tlv(0xa0, value)])
}
fn cert_bag() -> Zeroizing<Vec<u8>> {
let der = x509_cert::Certificate::from_pem(file("tqsl/1024/tqsl/certs/user"))
.expect("cert")
.to_der()
.expect("DER");
bag(
CERT_BAG,
&seq(&[&tlv(6, X509_BAG), &tlv(0xa0, &tlv(4, &der))]),
)
}
fn authority_bag() -> Zeroizing<Vec<u8>> {
let der = x509_cert::Certificate::from_pem(file("tqsl/pki/ca.pem"))
.expect("CA")
.to_der()
.expect("DER");
bag(
CERT_BAG,
&seq(&[&tlv(6, X509_BAG), &tlv(0xa0, &tlv(4, &der))]),
)
}
fn pfx(auth: &[u8]) -> Zeroizing<Vec<u8>> {
let password = bmp(&p12_password()).expect("password");
let key = kdf(ImportDigest::Sha256, &password, b"test-salt", 2048, 3, 32);
let digest = qso_graph_trust_awslc::import_hmac(ImportDigest::Sha256, &key, auth);
let info = seq(&[&seq(&[&tlv(6, SHA256), &tlv(5, &[])]), &tlv(4, &digest)]);
let mac = seq(&[&info, &tlv(4, b"test-salt"), &tlv(2, &[8, 0])]);
seq(&[&tlv(2, &[3]), &data(auth), &mac])
}
fn key_bag(name: &str, password: Option<&[u8]>) -> Zeroizing<Vec<u8>> {
let key = KeyEntry::read_key_file(&file(name)).expect("key").remove(0);
bag(KEY_BAG, &key.pkcs8_der(password).expect("DER"))
}
pub(crate) fn with_certificate_issuer(issuer: x509_cert::name::Name) -> Zeroizing<Vec<u8>> {
let mut cert =
x509_cert::Certificate::from_pem(file("tqsl/1024/tqsl/certs/user")).expect("cert");
cert.tbs_certificate.issuer = issuer;
let cert = bag(
CERT_BAG,
&seq(&[
&tlv(6, X509_BAG),
&tlv(0xa0, &tlv(4, &cert.to_der().expect("DER"))),
]),
);
pfx(&seq(&[&data(&seq(&[
&cert,
&key_bag("tqsl/1024/tqsl/keys/QG0TEST", None),
]))]))
}
#[test]
fn pkcs12_sha1_3des_pbe_loads_and_matches_the_corpus_key() {
let bytes = file("tqsl/1024/QG0TEST-weak.p12");
let mut outer = sequence(&bytes).expect("PFX");
outer.uint().expect("version");
let mut safe = outer.sequence().expect("safe");
safe.value(6).expect("OID");
let mut auth =
sequence(one(safe.value(0xa0).expect("content"), 4).expect("octets")).expect("auth");
auth.sequence().expect("RC2 content");
let key_content = auth.0;
let public = seq(&[&cert_bag(), &authority_bag()]);
let auth = seq(&[&data(&public), key_content]);
let imported = read(&pfx(&auth), &p12_password(), &trust()).expect("3DES import");
let expected = KeyEntry::read_key_file(&file("tqsl/1024/tqsl/keys/QG0TEST"))
.expect("key")
.remove(0)
.unlock(None)
.expect("unlock")
.sign(b"test")
.expect("sign");
assert_eq!(
signing::TqslKey::from_pkcs8_der(&imported.key, imported.certificate.public_key_der())
.expect("key")
.sign(b"test")
.expect("sign"),
expected
);
}
#[test]
fn authenticated_key_certificate_mismatch_is_refused() {
let safe = seq(&[
&cert_bag(),
&authority_bag(),
&key_bag(
"standard/tqsl/keys/W4W",
Some(&file("standard/keys/W4W.password")),
),
]);
let auth = seq(&[&data(&safe)]);
assert!(matches!(
read(&pfx(&auth), &p12_password(), &trust()),
Err(ImportError::Key(KeyError::PublicKeyMismatch))
));
}
#[test]
fn nested_bags_counts_and_der_sizes_are_bounded() {
let mut safe = seq(&[
&cert_bag(),
&authority_bag(),
&key_bag("tqsl/1024/tqsl/keys/QG0TEST", None),
]);
for _ in 0..MAX_DEPTH {
safe = seq(&[&bag(SAFE_BAG, &safe)]);
}
let accepted = pfx(&seq(&[&data(&safe)]));
assert!(read(&accepted, &p12_password(), &trust()).is_ok());
safe = seq(&[&bag(SAFE_BAG, &safe)]);
assert!(matches!(
read(&pfx(&seq(&[&data(&safe)])), &p12_password(), &trust()),
Err(ImportError::Limit)
));
let certs = concat(
&(0..=MAX_IMPORT_ITEMS)
.map(|_| cert_bag())
.collect::<Vec<_>>()
.iter()
.map(|b| b.as_slice())
.collect::<Vec<_>>(),
);
assert!(matches!(
read(
&pfx(&seq(&[&data(&tlv(0x30, &certs))])),
&p12_password(),
&trust()
),
Err(ImportError::Limit)
));
assert!(matches!(
read(&vec![0; MAX_IMPORT_LEN + 1], b"", &trust()),
Err(ImportError::Limit)
));
for der in [
&[0x30, 0x80, 0, 0][..],
&[0x30, 0x84, 0x7f, 0xff, 0xff, 0xff][..],
&[0x30, 0x81, 1, 0][..],
] {
assert!(sequence(der).is_err());
}
let attrs = tlv(0x30, &tlv(0x30, &[0x30, 0]));
assert!(attributes(&attrs, MAX_DEPTH - 3, &mut 0).is_ok());
assert!(matches!(
attributes(&attrs, MAX_DEPTH - 2, &mut 0),
Err(ImportError::Limit)
));
}
#[test]
fn absurd_kdf_counts_are_refused_before_deriving() {
let mut budget = MAX_WORK;
assert!(work(MAX_ITERATIONS, 1, &mut budget).is_ok());
for count in [0, MAX_ITERATIONS + 1, u32::MAX] {
assert!(matches!(
work(count, 1, &mut budget),
Err(ImportError::Limit)
));
}
let mac = concat(&[
&seq(&[&seq(&[&tlv(6, SHA1)]), &tlv(4, &[0; 20])]),
&tlv(4, b"salt"),
&tlv(2, &[0x7f, 0xff, 0xff, 0xff]),
]);
assert!(matches!(
verify_mac(&mac, b"test", b"", &mut budget),
Err(ImportError::Limit)
));
let algorithm = concat(&[
&tlv(6, PBE_3DES),
&seq(&[&tlv(4, b"salt"), &tlv(2, &[0x7f, 0xff, 0xff, 0xff])]),
]);
assert!(matches!(
decrypt(&algorithm, &[0; 8], b"", b"", &mut budget),
Err(ImportError::Limit)
));
let pbkdf2 = &[42, 134, 72, 134, 247, 13, 1, 5, 12];
let aes256 = &[96, 134, 72, 1, 101, 3, 4, 1, 42];
let params = seq(&[
&seq(&[
&tlv(6, pbkdf2),
&seq(&[&tlv(4, b"salt"), &tlv(2, &[0x7f, 0xff, 0xff, 0xff])]),
]),
&seq(&[&tlv(6, aes256), &tlv(4, &[0; 16])]),
]);
let algorithm = concat(&[&tlv(6, PBES2), ¶ms]);
assert!(matches!(
decrypt(&algorithm, &[0; 16], b"", b"", &mut budget),
Err(ImportError::Limit)
));
assert!(matches!(work(1, 1, &mut 0), Err(ImportError::Limit)));
}
#[test]
fn password_encoding_and_missing_mac_are_explicit() {
assert_eq!(&*bmp(b"").expect("empty password"), &[0, 0]);
assert_eq!(&*bmp("aé".as_bytes()).expect("BMP"), &[0, 97, 0, 233, 0, 0]);
assert!(bmp(&[255]).is_err());
assert!(bmp("🔑".as_bytes()).is_err());
let password: Vec<u8> = p12_password()
.into_iter()
.cycle()
.take(MAX_PASSWORD_LEN + 1)
.collect();
assert!(matches!(bmp(&password), Err(ImportError::Limit)));
let missing = seq(&[&tlv(2, &[3]), &data(&seq(&[]))]);
assert!(matches!(
read(&missing, b"", &trust()),
Err(ImportError::Authentication)
));
}