From 4a8ca038ff44211dea1153ab93407091507449ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sosth=C3=A8ne=20Gu=C3=A9don?= Date: Fri, 30 Sep 2022 16:43:07 +0200 Subject: [PATCH 1/9] Implement AES key import --- src/command/data.rs | 37 ++++++++++++++++++++++++++++++++++++- src/state.rs | 14 +++++++++++++- 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/src/command/data.rs b/src/command/data.rs index e47063f4..a537ff5e 100644 --- a/src/command/data.rs +++ b/src/command/data.rs @@ -4,6 +4,10 @@ use heapless_bytes::Bytes; use hex_literal::hex; use iso7816::Status; +use trussed::{ + syscall, try_syscall, + types::{KeySerialization, Location, Mechanism}, +}; use crate::{ card::{Context, LoadedContext, Options}, @@ -866,9 +870,40 @@ fn put_cardholder_cert( put_arbitrary_do(ctx, to_write) } +const AES256_KEY_LEN: usize = 32; + fn put_enc_dec_key( - _ctx: LoadedContext<'_, R, T>, + ctx: LoadedContext<'_, R, T>, ) -> Result<(), Status> { + if ctx.data.len() != AES256_KEY_LEN { + warn!("Attempt at importing an AES of length not {AES256_KEY_LEN}"); + return Err(Status::ConditionsOfUseNotSatisfied); + } + + let new_key = try_syscall!(ctx.backend.client_mut().unsafe_inject_key( + Mechanism::Aes256Cbc, + ctx.data, + Location::Internal, + KeySerialization::Raw, + )) + .map_err(|_err| { + error!("Failed to import AES key: {_err:?}"); + Status::UnspecifiedNonpersistentExecutionError + })? + .key; + + let old_key = ctx + .state + .internal + .set_aes_key_id(Some(new_key), ctx.backend.client_mut()) + .map_err(|_err| { + error!("Failed to set new key: {_err:?}"); + Status::UnspecifiedNonpersistentExecutionError + })?; + if let Some(old_key) = old_key { + syscall!(ctx.backend.client_mut().delete(old_key)); + } + // TODO: implement error!("Put data in even mode not yet implemented"); Err(Status::FunctionNotSupported) diff --git a/src/state.rs b/src/state.rs index bf720744..ff633313 100644 --- a/src/state.rs +++ b/src/state.rs @@ -281,6 +281,7 @@ pub struct Internal { signing_key: Option<(KeyId, KeyOrigin)>, confidentiality_key: Option<(KeyId, KeyOrigin)>, aut_key: Option<(KeyId, KeyOrigin)>, + aes_key: Option, sign_alg: SignatureAlgorithm, dec_alg: DecryptionAlgorithm, aut_alg: AuthenticationAlgorithm, @@ -321,10 +322,11 @@ impl Internal { sign_count: 0, signing_key: None, confidentiality_key: None, + aut_key: None, + aes_key: None, sign_alg: SignatureAlgorithm::default(), dec_alg: DecryptionAlgorithm::default(), aut_alg: AuthenticationAlgorithm::default(), - aut_key: None, fingerprints: Fingerprints::default(), ca_fingerprints: CaFingerprints::default(), keygen_dates: KeyGenDates::default(), @@ -685,6 +687,16 @@ impl Internal { Ok(new) } + pub fn set_aes_key_id( + &mut self, + mut new: Option, + client: &mut impl trussed::Client, + ) -> Result, Error> { + swap(&mut self.aes_key, &mut new); + self.save(client)?; + Ok(new) + } + pub fn delete_key( &mut self, ty: KeyType, From 07ef7760291bf542a159a1377f73a5c5a89d342f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sosth=C3=A8ne=20Gu=C3=A9don?= Date: Fri, 30 Sep 2022 17:33:40 +0200 Subject: [PATCH 2/9] Add support for AES decipher --- src/command/pso.rs | 37 ++++++++++++++++++++++++++++++++++++- src/state.rs | 4 ++++ 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/src/command/pso.rs b/src/command/pso.rs index 0dd7207d..6438341c 100644 --- a/src/command/pso.rs +++ b/src/command/pso.rs @@ -3,8 +3,8 @@ use iso7816::Status; -use trussed::try_syscall; use trussed::types::*; +use trussed::{syscall, try_syscall}; use crate::card::LoadedContext; use crate::state::KeyRef; @@ -198,6 +198,34 @@ pub fn decipher_key_mecha_uif( )) } +fn decipher_aes( + mut ctx: LoadedContext<'_, R, T>, +) -> Result<(), Status> { + let key_id = ctx.state.internal.aes_key().ok_or_else(|| { + warn!("Attempt to decipher with AES and no key set"); + Status::ConditionsOfUseNotSatisfied + })?; + + if (ctx.data.len() - 1) % 16 != 0 { + warn!("Attempt to decipher with AES with length not a multiple of block size"); + return Err(Status::IncorrectDataParameter); + } + + let plaintext = syscall!(ctx.backend.client_mut().decrypt( + Mechanism::Aes256Cbc, + key_id, + &ctx.data[1..], + &[], // No AAD + &[], // Zero IV + &[] // No authentication tag + )) + .plaintext + .ok_or_else(|| { + warn!("Failed decryption"); + Status::UnspecifiedCheckingError + })?; + ctx.reply.expand(&plaintext) +} // § 7.2.11 pub fn decipher( mut ctx: LoadedContext<'_, R, T>, @@ -207,6 +235,13 @@ pub fn decipher( return Err(Status::SecurityStatusNotSatisfied); } + if ctx.data.is_empty() { + return Err(Status::IncorrectDataParameter); + } + if ctx.data[0] == 0x02 { + return decipher_aes(ctx); + } + let (key_id, mechanism, uif) = decipher_key_mecha_uif(ctx.lend())?; if uif { prompt_uif(ctx.lend())?; diff --git a/src/state.rs b/src/state.rs index ff633313..8f7390a3 100644 --- a/src/state.rs +++ b/src/state.rs @@ -687,6 +687,10 @@ impl Internal { Ok(new) } + pub fn aes_key(&self) -> &Option { + &self.aes_key + } + pub fn set_aes_key_id( &mut self, mut new: Option, From 76c4b8a8feb0fd2b07dd22df7c12adaf6f615c78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sosth=C3=A8ne=20Gu=C3=A9don?= Date: Fri, 30 Sep 2022 17:44:29 +0200 Subject: [PATCH 3/9] Implement PSO:ENCIPHER --- src/command.rs | 1 + src/command/pso.rs | 86 +++++++++++++++++++++++++++++++--------------- 2 files changed, 59 insertions(+), 28 deletions(-) diff --git a/src/command.rs b/src/command.rs index a5400191..77b85895 100644 --- a/src/command.rs +++ b/src/command.rs @@ -77,6 +77,7 @@ impl Command { Self::ComputeDigitalSignature => pso::sign(context.load_state()?), Self::InternalAuthenticate => pso::internal_authenticate(context.load_state()?), Self::Decipher => pso::decipher(context.load_state()?), + Self::Encipher => pso::encipher(context.load_state()?), Self::GenerateAsymmetricKeyPair(mode) => gen_keypair(context.load_state()?, *mode), Self::TerminateDf => terminate_df(context), Self::ActivateFile => activate_file(context), diff --git a/src/command/pso.rs b/src/command/pso.rs index 6438341c..04866e20 100644 --- a/src/command/pso.rs +++ b/src/command/pso.rs @@ -198,34 +198,6 @@ pub fn decipher_key_mecha_uif( )) } -fn decipher_aes( - mut ctx: LoadedContext<'_, R, T>, -) -> Result<(), Status> { - let key_id = ctx.state.internal.aes_key().ok_or_else(|| { - warn!("Attempt to decipher with AES and no key set"); - Status::ConditionsOfUseNotSatisfied - })?; - - if (ctx.data.len() - 1) % 16 != 0 { - warn!("Attempt to decipher with AES with length not a multiple of block size"); - return Err(Status::IncorrectDataParameter); - } - - let plaintext = syscall!(ctx.backend.client_mut().decrypt( - Mechanism::Aes256Cbc, - key_id, - &ctx.data[1..], - &[], // No AAD - &[], // Zero IV - &[] // No authentication tag - )) - .plaintext - .ok_or_else(|| { - warn!("Failed decryption"); - Status::UnspecifiedCheckingError - })?; - ctx.reply.expand(&plaintext) -} // § 7.2.11 pub fn decipher( mut ctx: LoadedContext<'_, R, T>, @@ -328,3 +300,61 @@ fn decrypt_ec( ctx.reply.expand(&data) } + +fn decipher_aes( + mut ctx: LoadedContext<'_, R, T>, +) -> Result<(), Status> { + let key_id = ctx.state.internal.aes_key().ok_or_else(|| { + warn!("Attempt to decipher with AES and no key set"); + Status::ConditionsOfUseNotSatisfied + })?; + + if (ctx.data.len() - 1) % 16 != 0 { + warn!("Attempt to decipher with AES with length not a multiple of block size"); + return Err(Status::IncorrectDataParameter); + } + + let plaintext = syscall!(ctx.backend.client_mut().decrypt( + Mechanism::Aes256Cbc, + key_id, + &ctx.data[1..], + &[], // No AAD + &[], // Zero IV + &[] // No authentication tag + )) + .plaintext + .ok_or_else(|| { + warn!("Failed decryption"); + Status::UnspecifiedCheckingError + })?; + ctx.reply.expand(&plaintext) +} + +pub fn encipher( + mut ctx: LoadedContext<'_, R, T>, +) -> Result<(), Status> { + if !ctx.state.runtime.other_verified { + warn!("Attempt to ensipher without PW1 verified"); + return Err(Status::SecurityStatusNotSatisfied); + } + + let key_id = ctx.state.internal.aes_key().ok_or_else(|| { + warn!("Attempt to decipher with AES and no key set"); + Status::ConditionsOfUseNotSatisfied + })?; + + if ctx.data.len() % 16 != 0 { + warn!("Attempt to encipher with AES with length not a multiple of block size"); + return Err(Status::IncorrectDataParameter); + } + + let plaintext = syscall!(ctx.backend.client_mut().encrypt( + Mechanism::Aes256Cbc, + key_id, + &ctx.data[..], + &[], + None + )) + .ciphertext; + ctx.reply.expand(&plaintext) +} From 5a30758556316091a4cf4199af8b57d1178427d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sosth=C3=A8ne=20Gu=C3=A9don?= Date: Tue, 4 Oct 2022 10:59:42 +0200 Subject: [PATCH 4/9] Remove unimplemented error --- src/command/data.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/command/data.rs b/src/command/data.rs index a537ff5e..85e63c14 100644 --- a/src/command/data.rs +++ b/src/command/data.rs @@ -876,7 +876,10 @@ fn put_enc_dec_key( ctx: LoadedContext<'_, R, T>, ) -> Result<(), Status> { if ctx.data.len() != AES256_KEY_LEN { - warn!("Attempt at importing an AES of length not {AES256_KEY_LEN}"); + warn!( + "Attempt at importing an AES of length not {AES256_KEY_LEN}: {}", + ctx.data.len() + ); return Err(Status::ConditionsOfUseNotSatisfied); } @@ -904,9 +907,7 @@ fn put_enc_dec_key( syscall!(ctx.backend.client_mut().delete(old_key)); } - // TODO: implement - error!("Put data in even mode not yet implemented"); - Err(Status::FunctionNotSupported) + Ok(()) } fn put_resetting_code( From 7b92f8e4a4a5e5a74c904c20de85fbe18c1c55d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sosth=C3=A8ne=20Gu=C3=A9don?= Date: Tue, 4 Oct 2022 11:01:37 +0200 Subject: [PATCH 5/9] Add tests and fix response formatting --- src/command/pso.rs | 3 ++- tests/command_response.rs | 30 ++++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/src/command/pso.rs b/src/command/pso.rs index 04866e20..7a806e8f 100644 --- a/src/command/pso.rs +++ b/src/command/pso.rs @@ -351,10 +351,11 @@ pub fn encipher( let plaintext = syscall!(ctx.backend.client_mut().encrypt( Mechanism::Aes256Cbc, key_id, - &ctx.data[..], + ctx.data, &[], None )) .ciphertext; + ctx.reply.expand(&[0x02])?; ctx.reply.expand(&plaintext) } diff --git a/tests/command_response.rs b/tests/command_response.rs index af2e74da..7ffba75e 100644 --- a/tests/command_response.rs +++ b/tests/command_response.rs @@ -28,5 +28,35 @@ fn command_response() { assert_eq!(rep.len(), 1024); // Sanity check that it's not uninitialized or something assert_ne!(rep, [0; 1024]); + rep.clear(); + + let admin_pin_cmd: iso7816::Command<32> = + iso7816::Command::try_from(hex!("00200083 08 3132333435363738").as_slice()).unwrap(); + card.handle(&admin_pin_cmd, &mut rep).unwrap(); + rep.clear(); + + let user_pin_cmd: iso7816::Command<32> = + iso7816::Command::try_from(hex!("00200082 06 313233343536").as_slice()).unwrap(); + card.handle(&user_pin_cmd, &mut rep).unwrap(); + + let mut set_aes_key = Vec::from(hex!("0C DA 00D5 20 ")); + set_aes_key.extend_from_slice(&[0; 32]); + let import_cmd: iso7816::Command<32> = iso7816::Command::try_from(&set_aes_key).unwrap(); + card.handle(&import_cmd, &mut rep).unwrap(); + + let encrypt_aes = Vec::from(hex!("00 2A 86 80 10 00112233445566778899AABBCCDDEEFF 00")); + let encrypt_cmd: iso7816::Command<16> = iso7816::Command::try_from(&encrypt_aes).unwrap(); + let mut rep: heapless::Vec = heapless::Vec::new(); + card.handle(&encrypt_cmd, &mut rep).unwrap(); + assert_eq!(rep, hex!("02 1c060f4c9e7ea8d6ca961a2d64c05c18")); + + let mut decrypt_aes = Vec::from(hex!("00 2A 80 86 11")); + decrypt_aes.extend_from_slice(&rep); + decrypt_aes.push(0x00); + + let decrypt_cmd: iso7816::Command<17> = iso7816::Command::try_from(&decrypt_aes).unwrap(); + let mut rep: heapless::Vec = heapless::Vec::new(); + card.handle(&decrypt_cmd, &mut rep).unwrap(); + assert_eq!(rep, hex!("00112233445566778899AABBCCDDEEFF")); }) } From 3da97f51dad69cb592af8b2ff8d70c6f29bf5ce8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sosth=C3=A8ne=20Gu=C3=A9don?= Date: Tue, 4 Oct 2022 11:51:12 +0200 Subject: [PATCH 6/9] Use non zero key in tests --- tests/command_response.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/command_response.rs b/tests/command_response.rs index 7ffba75e..30748819 100644 --- a/tests/command_response.rs +++ b/tests/command_response.rs @@ -39,8 +39,9 @@ fn command_response() { iso7816::Command::try_from(hex!("00200082 06 313233343536").as_slice()).unwrap(); card.handle(&user_pin_cmd, &mut rep).unwrap(); - let mut set_aes_key = Vec::from(hex!("0C DA 00D5 20 ")); - set_aes_key.extend_from_slice(&[0; 32]); + let set_aes_key = Vec::from(hex!( + "0C DA 00D5 20 FFEEDDCCBBAA00998877665544332211FFEEDDCCBBAA00998877665544332211" + )); let import_cmd: iso7816::Command<32> = iso7816::Command::try_from(&set_aes_key).unwrap(); card.handle(&import_cmd, &mut rep).unwrap(); @@ -48,7 +49,7 @@ fn command_response() { let encrypt_cmd: iso7816::Command<16> = iso7816::Command::try_from(&encrypt_aes).unwrap(); let mut rep: heapless::Vec = heapless::Vec::new(); card.handle(&encrypt_cmd, &mut rep).unwrap(); - assert_eq!(rep, hex!("02 1c060f4c9e7ea8d6ca961a2d64c05c18")); + assert_eq!(rep, hex!("02 d9d2ca17e160427aee649db6912dbfad")); let mut decrypt_aes = Vec::from(hex!("00 2A 80 86 11")); decrypt_aes.extend_from_slice(&rep); From d17b1d250f32ca133b75fd3a140abcd8e4827630 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sosth=C3=A8ne=20Gu=C3=A9don?= Date: Mon, 17 Oct 2022 09:19:19 +0200 Subject: [PATCH 7/9] Improve flexibility of command_responce tests using a serialized format --- Cargo.toml | 3 + tests/command_response.ron | 40 +++++++ tests/command_response.rs | 210 +++++++++++++++++++++++++++---------- 3 files changed, 198 insertions(+), 55 deletions(-) create mode 100644 tests/command_response.ron diff --git a/Cargo.toml b/Cargo.toml index c9948573..ee3e384c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,6 +46,9 @@ stoppable_thread = "0.2.1" test-log = "0.2.10" trussed = { version = "0.1.0", features = ["virt"] } rand = "0.8.5" +ron = "0.8" +serde_cbor = "0.11" +hex = { version = "0.4", features = ["serde"] } [features] std = [] diff --git a/tests/command_response.ron b/tests/command_response.ron new file mode 100644 index 00000000..e83f2243 --- /dev/null +++ b/tests/command_response.ron @@ -0,0 +1,40 @@ +// Copyright (C) 2022 Nitrokey GmbH +// SPDX-License-Identifier: LGPL-3.0-only + +[ + IoTest( + name: "GET CHALLENGE", + cmd_resp: [ + IoData( + input: "00 84 0000 0A", + output: And([NonZero, Len(0x0A)]) + ), + IoData( + input: "00 84 0000 00 0400", + output: And([NonZero, Len(0x0400)]) + ) + ] + ), + IoTest( + name: "AES", + cmd_resp: [ + // Verify Admin Pin + IoData(input: "00200083 08 3132333435363738"), + // Verify User Pin + IoData(input: "00200082 06 313233343536"), + // Set aes key + IoData(input: "0C DA 00D5 20 FFEEDDCCBBAA00998877665544332211FFEEDDCCBBAA00998877665544332211"), + // Encrypt with AES + IoData( + input: "00 2A 86 80 10 00112233445566778899AABBCCDDEEFF 00", + output: Data("02 d9d2ca17e160427aee649db6912dbfad"), + ), + // Decrypt with AES + IoData( + input: "00 2A 80 86 11 02 d9d2ca17e160427aee649db6912dbfad 00", + output: Data("00112233445566778899AABBCCDDEEFF"), + ), + + ] + ) +] \ No newline at end of file diff --git a/tests/command_response.rs b/tests/command_response.rs index 30748819..7a463369 100644 --- a/tests/command_response.rs +++ b/tests/command_response.rs @@ -2,62 +2,162 @@ // SPDX-License-Identifier: LGPL-3.0-only #![cfg(feature = "virtual")] -use hex_literal::hex; +use serde::Deserialize; + +// iso7816::Status doesn't support serde +#[derive(Deserialize, Debug, PartialEq)] +enum Status { + Success, + MoreAvailable(u8), + VerificationFailed, + RemainingRetries(u8), + UnspecifiedNonpersistentExecutionError, + UnspecifiedPersistentExecutionError, + WrongLength, + LogicalChannelNotSupported, + SecureMessagingNotSupported, + CommandChainingNotSupported, + SecurityStatusNotSatisfied, + ConditionsOfUseNotSatisfied, + OperationBlocked, + IncorrectDataParameter, + FunctionNotSupported, + NotFound, + NotEnoughMemory, + IncorrectP1OrP2Parameter, + KeyReferenceNotFound, + InstructionNotSupportedOrInvalid, + ClassNotSupported, + UnspecifiedCheckingError, +} + +impl TryFrom for Status { + type Error = u16; + fn try_from(sw: u16) -> Result { + Ok(match sw { + 0x6300 => Self::VerificationFailed, + sw @ 0x63c0..=0x63cf => Self::RemainingRetries((sw as u8) & 0xf), + + 0x6400 => Self::UnspecifiedNonpersistentExecutionError, + 0x6500 => Self::UnspecifiedPersistentExecutionError, + + 0x6700 => Self::WrongLength, + + 0x6881 => Self::LogicalChannelNotSupported, + 0x6882 => Self::SecureMessagingNotSupported, + 0x6884 => Self::CommandChainingNotSupported, + + 0x6982 => Self::SecurityStatusNotSatisfied, + 0x6985 => Self::ConditionsOfUseNotSatisfied, + 0x6983 => Self::OperationBlocked, + + 0x6a80 => Self::IncorrectDataParameter, + 0x6a81 => Self::FunctionNotSupported, + 0x6a82 => Self::NotFound, + 0x6a84 => Self::NotEnoughMemory, + 0x6a86 => Self::IncorrectP1OrP2Parameter, + 0x6a88 => Self::KeyReferenceNotFound, + + 0x6d00 => Self::InstructionNotSupportedOrInvalid, + 0x6e00 => Self::ClassNotSupported, + 0x6f00 => Self::UnspecifiedCheckingError, + + 0x9000 => Self::Success, + sw @ 0x6100..=0x61FF => Self::MoreAvailable(sw as u8), + other => return Err(other), + }) + } +} + +impl Default for Status { + fn default() -> Status { + Status::Success + } +} + +#[derive(Deserialize, Debug)] +struct IoTest { + name: String, + cmd_resp: Vec, +} + +#[derive(Deserialize, Debug)] +enum OutputMatcher { + And(Vec), + Or(Vec), + Len(usize), + Data(String), + NonZero, +} + +impl Default for OutputMatcher { + fn default() -> Self { + OutputMatcher::Len(0) + } +} + +fn parse_hex(data: &str) -> Vec { + let tmp: String = data.split_whitespace().collect(); + hex::decode(&tmp).unwrap() +} + +impl OutputMatcher { + fn validate(&self, data: &[u8]) -> bool { + match self { + Self::NonZero => data.iter().max() != Some(&0), + Self::Data(expected) => { + println!("Validating output with {expected}"); + data == parse_hex(expected) + } + Self::Len(len) => data.len() == *len, + Self::And(matchers) => matchers.iter().filter(|m| !m.validate(data)).count() == 0, + Self::Or(matchers) => matchers.iter().filter(|m| m.validate(data)).count() != 0, + } + } +} + +#[derive(Deserialize, Debug)] +struct IoData { + input: String, + #[serde(default)] + output: OutputMatcher, + #[serde(default)] + expected_status: Status, +} #[test_log::test] fn command_response() { - trussed::virt::with_ram_client("opcard", |client| { - let mut card = opcard::Card::new(client, opcard::Options::default()); - let reset_command: iso7816::Command<4> = - iso7816::Command::try_from(&hex!("00 44 0000")).unwrap(); - let mut rep: heapless::Vec = heapless::Vec::new(); - card.handle(&reset_command, &mut rep).unwrap(); - - let get_challenge: iso7816::Command<5> = - iso7816::Command::try_from(&hex!("00 84 0000 0A")).unwrap(); - let mut rep: heapless::Vec = heapless::Vec::new(); - card.handle(&get_challenge, &mut rep).unwrap(); - assert_eq!(rep.len(), 10); - // Sanity check that it's not uninitialized or something - assert_ne!(rep, [0; 10]); - - let get_challenge: iso7816::Command<5> = - iso7816::Command::try_from(&hex!("00 84 0000 00 0400")).unwrap(); - let mut rep: heapless::Vec = heapless::Vec::new(); - card.handle(&get_challenge, &mut rep).unwrap(); - assert_eq!(rep.len(), 1024); - // Sanity check that it's not uninitialized or something - assert_ne!(rep, [0; 1024]); - rep.clear(); - - let admin_pin_cmd: iso7816::Command<32> = - iso7816::Command::try_from(hex!("00200083 08 3132333435363738").as_slice()).unwrap(); - card.handle(&admin_pin_cmd, &mut rep).unwrap(); - rep.clear(); - - let user_pin_cmd: iso7816::Command<32> = - iso7816::Command::try_from(hex!("00200082 06 313233343536").as_slice()).unwrap(); - card.handle(&user_pin_cmd, &mut rep).unwrap(); - - let set_aes_key = Vec::from(hex!( - "0C DA 00D5 20 FFEEDDCCBBAA00998877665544332211FFEEDDCCBBAA00998877665544332211" - )); - let import_cmd: iso7816::Command<32> = iso7816::Command::try_from(&set_aes_key).unwrap(); - card.handle(&import_cmd, &mut rep).unwrap(); - - let encrypt_aes = Vec::from(hex!("00 2A 86 80 10 00112233445566778899AABBCCDDEEFF 00")); - let encrypt_cmd: iso7816::Command<16> = iso7816::Command::try_from(&encrypt_aes).unwrap(); - let mut rep: heapless::Vec = heapless::Vec::new(); - card.handle(&encrypt_cmd, &mut rep).unwrap(); - assert_eq!(rep, hex!("02 d9d2ca17e160427aee649db6912dbfad")); - - let mut decrypt_aes = Vec::from(hex!("00 2A 80 86 11")); - decrypt_aes.extend_from_slice(&rep); - decrypt_aes.push(0x00); - - let decrypt_cmd: iso7816::Command<17> = iso7816::Command::try_from(&decrypt_aes).unwrap(); - let mut rep: heapless::Vec = heapless::Vec::new(); - card.handle(&decrypt_cmd, &mut rep).unwrap(); - assert_eq!(rep, hex!("00112233445566778899AABBCCDDEEFF")); - }) + let data = std::fs::read_to_string("tests/command_response.ron").unwrap(); + let tests: Vec = ron::from_str(&data).unwrap(); + for t in tests { + println!("Running {}", t.name); + trussed::virt::with_ram_client("opcard", |client| { + let mut card = opcard::Card::new(client, opcard::Options::default()); + for io in t.cmd_resp { + println!("Command: {:?}", io.input); + let mut rep: heapless::Vec = heapless::Vec::new(); + let cmd: iso7816::Command<1024> = iso7816::Command::try_from(&parse_hex(&io.input)) + .unwrap_or_else(|err| { + panic!( + "Bad command: {err:?}, for command: {}", + hex::encode(&io.input) + ) + }); + let status: Status = card + .handle(&cmd, &mut rep) + .err() + .map(|s| TryFrom::::try_from(s.into()).unwrap()) + .unwrap_or_default(); + + println!("Output: {:?}\nStatus: {status:?}", hex::encode(&rep)); + + if !io.output.validate(&rep) { + panic!("Bad output. Expected {:?}", io.output); + } + if status != io.expected_status { + panic!("Bad status. Expected {:?}", io.expected_status); + } + } + }); + } } From 305e5dea2211d7876a941fcaf5673b98002d620b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sosth=C3=A8ne=20Gu=C3=A9don?= Date: Mon, 17 Oct 2022 16:13:02 +0200 Subject: [PATCH 8/9] Update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d273f0f9..dda8e981 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,11 +11,13 @@ SPDX-License-Identifier: CC0-1.0 - Support using authentication keys for decryption and vice-versa with MANAGE SECURITY ENVIRONMENT ([#60][]) - Support PIN resets using a resetting code ([#63][]) +- Support AES encryption/decryption ([#64][]) ### Bugfixes - Fix the length of the Digital signature counter DO 0x93 ([#76][]) +[#64]: https://github.com/Nitrokey/opcard-rs/pull/64 [#60]: https://github.com/Nitrokey/opcard-rs/pull/60 [#63]: https://github.com/Nitrokey/opcard-rs/pull/63 [#76]: https://github.com/Nitrokey/opcard-rs/pull/76 From 7e880179a20775fb44f48eac3804634f350c8f6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sosth=C3=A8ne=20Gu=C3=A9don?= Date: Mon, 17 Oct 2022 17:17:55 +0200 Subject: [PATCH 9/9] Fix typo --- src/command/pso.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/command/pso.rs b/src/command/pso.rs index 7a806e8f..d19ff64f 100644 --- a/src/command/pso.rs +++ b/src/command/pso.rs @@ -334,7 +334,7 @@ pub fn encipher( mut ctx: LoadedContext<'_, R, T>, ) -> Result<(), Status> { if !ctx.state.runtime.other_verified { - warn!("Attempt to ensipher without PW1 verified"); + warn!("Attempt to encipher without PW1 verified"); return Err(Status::SecurityStatusNotSatisfied); }