use super::*;
use std::time::Duration;
#[test]
fn boundary_collision_is_retried_and_random_failure_is_returned() {
let payload = format!("binary\0qgt-{}\r\n", "00".repeat(16));
let mut attempts = 0;
let boundary = boundary_with(payload.as_bytes(), |out| {
out.fill(attempts);
attempts += 1;
Ok(())
})
.unwrap();
assert_eq!(attempts, 2);
assert_eq!(boundary, format!("qgt-{}", "01".repeat(16)));
assert_eq!(
boundary_with(b"", |_| Err(Error::Random)),
Err(Error::Random)
);
}
#[test]
fn filename_uses_tqsls_utc_format() {
for (seconds, expected) in [
(0, "19700101-0000"),
(951_827_696, "20000229-1234"),
(4_107_542_400, "21000301-0000"),
] {
assert_eq!(
filename(UNIX_EPOCH + Duration::from_secs(seconds), "log.tq8"),
format!("<TQSLUpl {expected}> log.tq8")
);
}
assert_eq!(
filename(UNIX_EPOCH - Duration::from_secs(1), "log.tq8"),
filename(UNIX_EPOCH, "log.tq8")
);
}
#[test]
fn an_upload_is_named_by_its_file_and_never_its_folder() {
assert_eq!(upload_name("us12223.tq8"), "us12223.tq8");
assert_eq!(upload_name("/Users/someone/logs/us7831.tq8"), "us7831.tq8");
assert_eq!(upload_name("C:\\Logs\\Field Day.TQ8"), "Field_Day.TQ8");
assert_eq!(upload_name("contest.adi"), "contest.adi.tq8");
assert_eq!(upload_name("a\"b\r\nX: y.tq8"), "a_b__X__y.tq8");
assert_eq!(upload_name(""), "log.tq8");
assert_eq!(upload_name("/logs/"), "log.tq8");
assert_eq!(upload_name(".."), "log.tq8");
assert_eq!(upload_name(&"x".repeat(200)).len(), 64 + 4);
}
#[test]
fn a_named_upload_sends_that_name() {
let request =
super::UploadRequest::try_named(b"signed", "/home/someone/us12223.tq8").expect("request");
let head = String::from_utf8_lossy(request.body()).to_string();
assert!(head.contains("> us12223.tq8\"\r\n"), "{head}");
assert!(!head.contains("someone"), "{head}");
}
#[test]
fn only_a_signed_log_is_one_to_upload() {
use super::{NotASignedLog, check_signed_log};
let corpus = |f: &str| {
std::fs::read(
std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
.join("../../tests/corpus/standard")
.join(f),
)
.expect(f)
};
let tq7 = corpus("signed/W4W-EVENT.tq7");
let tq8 = crate::signed_log::tq8(&tq7);
assert_eq!(check_signed_log(&tq8), Ok(tq8.clone()));
let mut commented = tq8[..10].to_vec();
commented[3] |= 0x10; commented.extend_from_slice(b"-----BEGIN PRIVATE KEY-----MIIC-----END PRIVATE KEY-----\0");
commented.extend_from_slice(&tq8[10..]);
let sent = check_signed_log(&commented).expect("the log itself is a signed log");
assert_eq!(sent, tq8);
assert!(!sent.windows(11).any(|w| w == b"PRIVATE KEY"));
let mut trailing = tq8.clone();
trailing.extend_from_slice(b"-----BEGIN PRIVATE KEY-----");
if let Ok(sent) = check_signed_log(&trailing) {
assert_eq!(sent, tq8);
}
assert_eq!(
check_signed_log(&corpus("standard.tbk")),
Err(NotASignedLog)
);
assert_eq!(check_signed_log(&tq7), Err(NotASignedLog));
assert_eq!(
check_signed_log(&crate::signed_log::tq8(&corpus("adif/W4W-EVENT.adi"))),
Err(NotASignedLog)
);
let text = String::from_utf8(tq7.clone()).expect("text");
let header: String = text
.split("<Rec_Type:8>tCONTACT")
.next()
.expect("head")
.into();
assert_eq!(
check_signed_log(&crate::signed_log::tq8(header.as_bytes())),
Err(NotASignedLog)
);
let planted = |text: String| check_signed_log(&crate::signed_log::tq8(text.as_bytes()));
assert_eq!(
planted(text.replacen(
"<Rec_Type:8>tCONTACT",
"<Rec_Type:8>tCONTACT\n<PRIVATE_KEY:4>TEST",
1
)),
Err(NotASignedLog)
);
assert_eq!(
planted(format!(
"{text}\n-----BEGIN PRIVATE KEY-----\nMIIC\n-----END PRIVATE KEY-----\n"
)),
Err(NotASignedLog)
);
let start = text.find("<CERTIFICATE:").expect("certificate");
let end = start + text[start..].find("<eor>").expect("end");
assert_eq!(
planted(format!(
"{}<CERTIFICATE:8>TUlJQ2Rn{}",
&text[..start],
&text[end..]
)),
Err(NotASignedLog)
);
let pem = String::from_utf8(
std::fs::read(
std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
.join("../../tests/corpus/pki/user-1024.key.pem"),
)
.expect("test key"),
)
.expect("text");
let sd = text.find("<SIGNDATA:").expect("signdata");
let sd_end = sd + text[sd..].find('\n').expect("line");
assert_eq!(
planted(format!(
"{}<SIGNDATA:{}>{pem}{}",
&text[..sd],
pem.len(),
&text[sd_end..]
)),
Err(NotASignedLog)
);
assert_eq!(
planted(format!(
"<TQSL_IDENT:4>TEST\n<Rec_Type:8>tCONTACT\n<SIGNDATA:{}>{pem}<eor>\n",
pem.len()
)),
Err(NotASignedLog)
);
let head = &text[..text.find("<Rec_Type:8>tCONTACT").expect("contact")];
let contact = &text[text.find("<Rec_Type:8>tCONTACT").expect("contact")..];
let contact = &contact[..contact.find("<eor>").expect("end") + 5];
let mut big = String::from(head);
for _ in 0..(crate::records::MAX_RECORDS + 1) {
big.push_str(contact);
big.push('\n');
}
assert!(planted(big).is_ok());
}
#[test]
fn every_signature_is_nonempty_base64_of_its_own_certificates_modulus_length() {
use pkcs8::der::{
Decode, Encode, Tag,
asn1::{AnyRef, BitString, UintRef},
};
let tq7 = std::fs::read(
std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
.join("../../tests/corpus/tqsl/1024/qg0test-small.tq7"),
)
.unwrap();
let text = String::from_utf8(tq7).unwrap();
let check = |text: &str| check_signed_log(&crate::gzip::compress(text.as_bytes()));
assert!(check(&text).is_ok());
fn replace_field(text: &str, name: &str, value: &str) -> String {
let start = text.find(&format!("<{name}:")).unwrap();
let close = start + text[start..].find('>').unwrap();
let len: usize = text[start + 1..close]
.split(':')
.nth(1)
.unwrap()
.parse()
.unwrap();
let mut result = text.to_owned();
result.replace_range(
start..close + 1 + len,
&format!("<{name}:{}>{value}", value.len()),
);
result
}
for bad in [
String::new(),
" \n\r".to_owned(),
"!".repeat(172),
Base64::encode_string(&[0; 127]),
Base64::encode_string(&[0; 129]),
"A".repeat(172),
"AA=A".repeat(43),
] {
assert!(check(&replace_field(&text, lotw::SIGNATURE_FIELD, &bad)).is_err());
}
let records = crate::records::parse(text.as_bytes()).unwrap();
let cert_text = records[0].text("CERTIFICATE").unwrap();
let der = Base64::decode_vec(
&cert_text
.chars()
.filter(|c| !c.is_ascii_whitespace())
.collect::<String>(),
)
.unwrap();
let mut cert = x509_cert::Certificate::from_der(&der).unwrap();
let n = [0x81; 256];
let integers = [
UintRef::new(&n).unwrap().to_der().unwrap(),
UintRef::new(&[1, 0, 1]).unwrap().to_der().unwrap(),
]
.concat();
let public = AnyRef::new(Tag::Sequence, &integers)
.unwrap()
.to_der()
.unwrap();
cert.tbs_certificate
.subject_public_key_info
.subject_public_key = BitString::from_bytes(&public).unwrap();
let cert_b64 = crate::signing::tqsl_base64(&cert.to_der().unwrap());
assert_eq!(rsa_signature_len(cert_b64.as_bytes()).unwrap(), 256);
let cert_at = text.find("<Rec_Type:5>tCERT").unwrap();
let cert_end = cert_at + text[cert_at..].find("<eor>").unwrap() + 5;
let repeated = format!(
"{}{}{}",
&text[..cert_end],
&text[cert_at..cert_end],
&text[cert_end..]
);
assert!(
check(&repeated).is_err(),
"certificate identifiers must identify one certificate"
);
assert!(check(&replace_field(&text, "CERT_UID", "")).is_err());
let at = text.find("<Rec_Type:8>tSTATION").unwrap();
let second_cert = format!(
"<Rec_Type:5>tCERT<CERT_UID:1>2<CERTIFICATE:{}>{cert_b64}<eor>\n",
cert_b64.len()
);
let changed = format!(
"{}{}{}",
&text[..at],
second_cert,
replace_field(&text[at..], "CERT_UID", "2")
);
assert!(
check(&changed).is_err(),
"128-byte signatures cannot name the 256-byte certificate"
);
let mut changed = changed;
let mut start = 0;
while let Some(relative) = changed[start..].find("<SIGN_LOTW_V2.0:") {
let at = start + relative;
let value = crate::signing::tqsl_base64(&[0; 256]);
let suffix = replace_field(&changed[at..], lotw::SIGNATURE_FIELD, &value);
changed.replace_range(at.., &suffix);
start = at + format!("<SIGN_LOTW_V2.0:{}>", value.len()).len() + value.len();
}
assert!(
check(&changed).is_ok(),
"length is read from the signing certificate, not hardcoded"
);
let at = changed.find("<Rec_Type:8>tSTATION").unwrap();
let unknown = format!(
"{}{}",
&changed[..at],
replace_field(&changed[at..], "CERT_UID", "9")
);
assert!(
check(&unknown).is_err(),
"the station must name an existing certificate"
);
}