Skip to content

Commit 2a5ba7d

Browse files
Improve flexibility of command_responce tests using a serialized format
1 parent 4454679 commit 2a5ba7d

File tree

3 files changed

+198
-55
lines changed

3 files changed

+198
-55
lines changed

Cargo.toml

+3
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ stoppable_thread = "0.2.1"
4646
test-log = "0.2.10"
4747
trussed = { version = "0.1.0", features = ["virt"] }
4848
rand = "0.8.5"
49+
ron = "0.8"
50+
serde_cbor = "0.11"
51+
hex = { version = "0.4", features = ["serde"] }
4952

5053
[features]
5154
std = []

tests/command_response.ron

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// Copyright (C) 2022 Nitrokey GmbH
2+
// SPDX-License-Identifier: LGPL-3.0-only
3+
4+
[
5+
IoTest(
6+
name: "GET CHALLENGE",
7+
cmd_resp: [
8+
IoData(
9+
input: "00 84 0000 0A",
10+
output: And([NonZero, Len(0x0A)])
11+
),
12+
IoData(
13+
input: "00 84 0000 00 0400",
14+
output: And([NonZero, Len(0x0400)])
15+
)
16+
]
17+
),
18+
IoTest(
19+
name: "AES",
20+
cmd_resp: [
21+
// Verify Admin Pin
22+
IoData(input: "00200083 08 3132333435363738"),
23+
// Verify User Pin
24+
IoData(input: "00200082 06 313233343536"),
25+
// Set aes key
26+
IoData(input: "0C DA 00D5 20 FFEEDDCCBBAA00998877665544332211FFEEDDCCBBAA00998877665544332211"),
27+
// Encrypt with AES
28+
IoData(
29+
input: "00 2A 86 80 10 00112233445566778899AABBCCDDEEFF 00",
30+
output: Data("02 d9d2ca17e160427aee649db6912dbfad"),
31+
),
32+
// Decrypt with AES
33+
IoData(
34+
input: "00 2A 80 86 11 02 d9d2ca17e160427aee649db6912dbfad 00",
35+
output: Data("00112233445566778899AABBCCDDEEFF"),
36+
),
37+
38+
]
39+
)
40+
]

tests/command_response.rs

+155-55
Original file line numberDiff line numberDiff line change
@@ -2,62 +2,162 @@
22
// SPDX-License-Identifier: LGPL-3.0-only
33
#![cfg(feature = "virtual")]
44

5-
use hex_literal::hex;
5+
use serde::Deserialize;
6+
7+
// iso7816::Status doesn't support serde
8+
#[derive(Deserialize, Debug, PartialEq)]
9+
enum Status {
10+
Success,
11+
MoreAvailable(u8),
12+
VerificationFailed,
13+
RemainingRetries(u8),
14+
UnspecifiedNonpersistentExecutionError,
15+
UnspecifiedPersistentExecutionError,
16+
WrongLength,
17+
LogicalChannelNotSupported,
18+
SecureMessagingNotSupported,
19+
CommandChainingNotSupported,
20+
SecurityStatusNotSatisfied,
21+
ConditionsOfUseNotSatisfied,
22+
OperationBlocked,
23+
IncorrectDataParameter,
24+
FunctionNotSupported,
25+
NotFound,
26+
NotEnoughMemory,
27+
IncorrectP1OrP2Parameter,
28+
KeyReferenceNotFound,
29+
InstructionNotSupportedOrInvalid,
30+
ClassNotSupported,
31+
UnspecifiedCheckingError,
32+
}
33+
34+
impl TryFrom<u16> for Status {
35+
type Error = u16;
36+
fn try_from(sw: u16) -> Result<Self, Self::Error> {
37+
Ok(match sw {
38+
0x6300 => Self::VerificationFailed,
39+
sw @ 0x63c0..=0x63cf => Self::RemainingRetries((sw as u8) & 0xf),
40+
41+
0x6400 => Self::UnspecifiedNonpersistentExecutionError,
42+
0x6500 => Self::UnspecifiedPersistentExecutionError,
43+
44+
0x6700 => Self::WrongLength,
45+
46+
0x6881 => Self::LogicalChannelNotSupported,
47+
0x6882 => Self::SecureMessagingNotSupported,
48+
0x6884 => Self::CommandChainingNotSupported,
49+
50+
0x6982 => Self::SecurityStatusNotSatisfied,
51+
0x6985 => Self::ConditionsOfUseNotSatisfied,
52+
0x6983 => Self::OperationBlocked,
53+
54+
0x6a80 => Self::IncorrectDataParameter,
55+
0x6a81 => Self::FunctionNotSupported,
56+
0x6a82 => Self::NotFound,
57+
0x6a84 => Self::NotEnoughMemory,
58+
0x6a86 => Self::IncorrectP1OrP2Parameter,
59+
0x6a88 => Self::KeyReferenceNotFound,
60+
61+
0x6d00 => Self::InstructionNotSupportedOrInvalid,
62+
0x6e00 => Self::ClassNotSupported,
63+
0x6f00 => Self::UnspecifiedCheckingError,
64+
65+
0x9000 => Self::Success,
66+
sw @ 0x6100..=0x61FF => Self::MoreAvailable(sw as u8),
67+
other => return Err(other),
68+
})
69+
}
70+
}
71+
72+
impl Default for Status {
73+
fn default() -> Status {
74+
Status::Success
75+
}
76+
}
77+
78+
#[derive(Deserialize, Debug)]
79+
struct IoTest {
80+
name: String,
81+
cmd_resp: Vec<IoData>,
82+
}
83+
84+
#[derive(Deserialize, Debug)]
85+
enum OutputMatcher {
86+
And(Vec<OutputMatcher>),
87+
Or(Vec<OutputMatcher>),
88+
Len(usize),
89+
Data(String),
90+
NonZero,
91+
}
92+
93+
impl Default for OutputMatcher {
94+
fn default() -> Self {
95+
OutputMatcher::Len(0)
96+
}
97+
}
98+
99+
fn parse_hex(data: &str) -> Vec<u8> {
100+
let tmp: String = data.split_whitespace().collect();
101+
hex::decode(&tmp).unwrap()
102+
}
103+
104+
impl OutputMatcher {
105+
fn validate(&self, data: &[u8]) -> bool {
106+
match self {
107+
Self::NonZero => data.iter().max() != Some(&0),
108+
Self::Data(expected) => {
109+
println!("Validating output with {expected}");
110+
data == parse_hex(expected)
111+
}
112+
Self::Len(len) => data.len() == *len,
113+
Self::And(matchers) => matchers.iter().filter(|m| !m.validate(data)).count() == 0,
114+
Self::Or(matchers) => matchers.iter().filter(|m| m.validate(data)).count() != 0,
115+
}
116+
}
117+
}
118+
119+
#[derive(Deserialize, Debug)]
120+
struct IoData {
121+
input: String,
122+
#[serde(default)]
123+
output: OutputMatcher,
124+
#[serde(default)]
125+
expected_status: Status,
126+
}
6127

7128
#[test_log::test]
8129
fn command_response() {
9-
trussed::virt::with_ram_client("opcard", |client| {
10-
let mut card = opcard::Card::new(client, opcard::Options::default());
11-
let reset_command: iso7816::Command<4> =
12-
iso7816::Command::try_from(&hex!("00 44 0000")).unwrap();
13-
let mut rep: heapless::Vec<u8, 0> = heapless::Vec::new();
14-
card.handle(&reset_command, &mut rep).unwrap();
15-
16-
let get_challenge: iso7816::Command<5> =
17-
iso7816::Command::try_from(&hex!("00 84 0000 0A")).unwrap();
18-
let mut rep: heapless::Vec<u8, 16> = heapless::Vec::new();
19-
card.handle(&get_challenge, &mut rep).unwrap();
20-
assert_eq!(rep.len(), 10);
21-
// Sanity check that it's not uninitialized or something
22-
assert_ne!(rep, [0; 10]);
23-
24-
let get_challenge: iso7816::Command<5> =
25-
iso7816::Command::try_from(&hex!("00 84 0000 00 0400")).unwrap();
26-
let mut rep: heapless::Vec<u8, 1024> = heapless::Vec::new();
27-
card.handle(&get_challenge, &mut rep).unwrap();
28-
assert_eq!(rep.len(), 1024);
29-
// Sanity check that it's not uninitialized or something
30-
assert_ne!(rep, [0; 1024]);
31-
rep.clear();
32-
33-
let admin_pin_cmd: iso7816::Command<32> =
34-
iso7816::Command::try_from(hex!("00200083 08 3132333435363738").as_slice()).unwrap();
35-
card.handle(&admin_pin_cmd, &mut rep).unwrap();
36-
rep.clear();
37-
38-
let user_pin_cmd: iso7816::Command<32> =
39-
iso7816::Command::try_from(hex!("00200082 06 313233343536").as_slice()).unwrap();
40-
card.handle(&user_pin_cmd, &mut rep).unwrap();
41-
42-
let set_aes_key = Vec::from(hex!(
43-
"0C DA 00D5 20 FFEEDDCCBBAA00998877665544332211FFEEDDCCBBAA00998877665544332211"
44-
));
45-
let import_cmd: iso7816::Command<32> = iso7816::Command::try_from(&set_aes_key).unwrap();
46-
card.handle(&import_cmd, &mut rep).unwrap();
47-
48-
let encrypt_aes = Vec::from(hex!("00 2A 86 80 10 00112233445566778899AABBCCDDEEFF 00"));
49-
let encrypt_cmd: iso7816::Command<16> = iso7816::Command::try_from(&encrypt_aes).unwrap();
50-
let mut rep: heapless::Vec<u8, 17> = heapless::Vec::new();
51-
card.handle(&encrypt_cmd, &mut rep).unwrap();
52-
assert_eq!(rep, hex!("02 d9d2ca17e160427aee649db6912dbfad"));
53-
54-
let mut decrypt_aes = Vec::from(hex!("00 2A 80 86 11"));
55-
decrypt_aes.extend_from_slice(&rep);
56-
decrypt_aes.push(0x00);
57-
58-
let decrypt_cmd: iso7816::Command<17> = iso7816::Command::try_from(&decrypt_aes).unwrap();
59-
let mut rep: heapless::Vec<u8, 16> = heapless::Vec::new();
60-
card.handle(&decrypt_cmd, &mut rep).unwrap();
61-
assert_eq!(rep, hex!("00112233445566778899AABBCCDDEEFF"));
62-
})
130+
let data = std::fs::read_to_string("tests/command_response.ron").unwrap();
131+
let tests: Vec<IoTest> = ron::from_str(&data).unwrap();
132+
for t in tests {
133+
println!("Running {}", t.name);
134+
trussed::virt::with_ram_client("opcard", |client| {
135+
let mut card = opcard::Card::new(client, opcard::Options::default());
136+
for io in t.cmd_resp {
137+
println!("Command: {:?}", io.input);
138+
let mut rep: heapless::Vec<u8, 1024> = heapless::Vec::new();
139+
let cmd: iso7816::Command<1024> = iso7816::Command::try_from(&parse_hex(&io.input))
140+
.unwrap_or_else(|err| {
141+
panic!(
142+
"Bad command: {err:?}, for command: {}",
143+
hex::encode(&io.input)
144+
)
145+
});
146+
let status: Status = card
147+
.handle(&cmd, &mut rep)
148+
.err()
149+
.map(|s| TryFrom::<u16>::try_from(s.into()).unwrap())
150+
.unwrap_or_default();
151+
152+
println!("Output: {:?}\nStatus: {status:?}", hex::encode(&rep));
153+
154+
if !io.output.validate(&rep) {
155+
panic!("Bad output. Expected {:?}", io.output);
156+
}
157+
if status != io.expected_status {
158+
panic!("Bad status. Expected {:?}", io.expected_status);
159+
}
160+
}
161+
});
162+
}
63163
}

0 commit comments

Comments
 (0)