use super::super::decode_apdu_tree;
use super::tests::snapshot;
fn octets(text: &str) -> Vec<u8> {
let digits: String = text.split_whitespace().collect();
(0..digits.len())
.step_by(2)
.map(|at| u8::from_str_radix(&digits[at..at + 2], 16).unwrap())
.collect()
}
const SECRET: &str = "00 68756E74657232";
#[track_caller]
fn hidden(apdu: &str) -> String {
let text = snapshot(&octets(apdu));
for leaked in ["hunter2", "68756E74", "756E74"] {
assert!(!text.contains(leaked), "{leaked} in\n{text}");
}
text.trim_end().to_string()
}
#[test]
fn segmented_password_requests_hide_the_password() {
let reinit = format!("0E 05 01 00 04 14 0900 1D08 {SECRET}");
assert_eq!(
hidden(&reinit),
"\
reinitialize-device parameters @6..18
[0] 00 (0) @6..8
Password: (hidden) @8..18"
);
let dcc = format!("0E 05 01 00 04 11 0900 1900 2D08 {SECRET}");
assert_eq!(
hidden(&dcc),
"\
device-communication-control parameters @6..20
[0] 00 (0) @6..8
[1] 00 (0) @8..10
Password: (hidden) @10..20"
);
let later = format!("0E 05 01 01 04 11 2D08 {SECRET}");
assert_eq!(
hidden(&later),
"\
device-communication-control parameters @6..16
Segment data: 10 octets, hidden (may hold a password) @6..16"
);
}
#[test]
fn refused_password_requests_hide_strings_at_any_depth() {
assert_eq!(
hidden(&format!("00 05 01 11 1900 2E 7508 {SECRET} 2F")),
"\
device-communication-control parameters @4..18
[1] 00 (0) @4..6
[2] opening @6..18
Hidden: may hold a password @7..17
Not read as named fields: decoding error at offset 2: DCC: 12 trailing byte(s) @4..18"
);
let text = hidden(&format!("00 05 01 11 1900 3E 7508 {SECRET} 3F"));
assert!(
text.contains(" [3] opening @6..18\n Hidden: may hold a password @7..17"),
"{text}"
);
let text = hidden(&format!("00 05 01 11 1900 7508 {SECRET}"));
assert!(
text.contains(" Hidden: may hold a password @6..16"),
"{text}"
);
let long = "00 5A71372D68756E746572322D5A71372D68756E746572322D";
let text = snapshot(&octets(&format!("00 05 01 14 0900 1D19 {long}")));
assert!(!text.contains("5A71372D"), "{text}");
assert!(text.contains(" Password: (hidden) @6..33"), "{text}");
assert!(text.contains("exceeds 20 characters"), "{text}");
}
#[test]
fn everything_from_the_password_on_is_hidden() {
assert_eq!(
hidden("00 05 01 11 1900 2A0068 0C756E7465"),
"\
device-communication-control parameters @4..14
[1] 00 (0) @4..6
Password: (hidden) @6..9
Hidden: may hold a password @9..14
Not read as named fields: decoding error at offset 5: DCC: 5 trailing byte(s) @4..14"
);
let text = hidden("00 05 01 14 0900 1A0068 0B756E74");
assert!(
text.contains(" Password: (hidden) @6..9\n Hidden: may hold a password @9..13"),
"{text}"
);
}
#[test]
fn only_expected_top_level_fields_stay() {
let text = hidden("00 05 01 11 1900 3E0907 3F");
assert!(text.contains(" [1] 00 (0) @4..6\n"), "{text}");
assert!(
text.contains(" [3] opening @6..10\n Hidden: may hold a password @7..9"),
"{text}"
);
let text = hidden("00 05 01 11 0D05 0102030405 1900");
assert!(
text.contains(
" Hidden: may hold a password @4..11\n Hidden: may hold a password @11..13"
),
"{text}"
);
}
#[test]
fn octets_after_the_walk_stops_are_one_leaf() {
let text = snapshot(&octets("10 03 0901 1C02000005 08 1F"));
assert!(text.contains("\n Trailing octets: 1 @10..11\n"), "{text}");
let reject = decode_apdu_tree(&octets("60 01 02 AABB"));
let last = reject.children.last().unwrap();
assert_eq!(
(last.label.as_str(), last.range.clone()),
("Trailing octets: 2", 3..5)
);
}