|
2 | 2 | // SPDX-License-Identifier: LGPL-3.0-only
|
3 | 3 | #![cfg(feature = "virtual")]
|
4 | 4 |
|
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 | +} |
6 | 127 |
|
7 | 128 | #[test_log::test]
|
8 | 129 | 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 | + } |
63 | 163 | }
|
0 commit comments