use bitflags::bitflags;
use hex_literal::hex;
use iso7816::Status;
use trussed_auth::AuthClient;
use trussed_chunked::ChunkedClient;
use trussed_core::{
reset_signal::{ResetSignal, ResetSignalAllocation},
types::Location,
CryptoClient, FilesystemClient, UiClient,
};
pub(crate) mod reply;
use crate::state::{LoadedState, State};
use crate::utils::InspectErr;
use crate::{backend::Backend, command::Command};
use reply::Reply;
pub const RID: [u8; 5] = [0xD2, 0x76, 0x00, 0x01, 0x24];
pub const PIX_APPLICATION: [u8; 1] = [0x01];
pub const PIX_RFU: [u8; 2] = [0x00, 0x00];
pub const PGP_SMARTCARD_VERSION: [u8; 2] = [3, 4];
#[derive(Clone, Debug)]
pub struct Card<T: Client> {
backend: Backend<T>,
options: Options,
state: State,
}
impl<T: Client> Card<T> {
pub fn new(client: T, options: Options) -> Self {
let state = State::default();
Self {
backend: Backend::new(client),
options,
state,
}
}
fn ack_factory_reset(&mut self, reset_signal: &ResetSignalAllocation) -> bool {
self.state = State::default();
reset_signal.ack_factory_reset()
}
pub fn handle(
&mut self,
command: iso7816::command::CommandView<'_>,
reply: &mut heapless::VecView<u8>,
) -> Result<(), Status> {
if let Some(reset_signal) = self.options.reset_signal {
match reset_signal.load() {
ResetSignal::None => {}
ResetSignal::ConfigChanged => {
return Err(Status::SelectedFileInTerminationState);
}
ResetSignal::FactoryReset => {
if !self.ack_factory_reset(reset_signal) {
return Err(Status::SelectedFileInTerminationState);
}
}
}
}
trace!("Received APDU {:?}", command);
let card_command = Command::try_from(command).inspect_err_stable(|_err| {
warn!("Failed to parse command: {command:x?} {_err:?}");
})?;
info!("Executing command {:x?}", card_command);
let context = Context {
backend: &mut self.backend,
state: &mut self.state,
options: &self.options,
data: command.data(),
reply: Reply(reply),
};
card_command.exec(context)
}
pub fn reset(&mut self) {
if let Some(reset_signal) = self.options.reset_signal {
match reset_signal.load() {
ResetSignal::None => {}
ResetSignal::ConfigChanged => {
debug!("Attempt to reset opcard with reset signal active");
return;
}
ResetSignal::FactoryReset => {
self.ack_factory_reset(reset_signal);
return;
}
}
}
self.state.volatile.clear(self.backend.client_mut());
let state = State::default();
self.state = state;
}
}
impl<T: Client> Drop for Card<T> {
fn drop(&mut self) {
self.reset()
}
}
impl<T: Client> iso7816::App for Card<T> {
fn aid(&self) -> iso7816::Aid {
iso7816::Aid::new_truncatable(&self.options.aid(), RID.len())
}
}
#[cfg(feature = "apdu-dispatch")]
impl<T: Client> apdu_app::App for Card<T> {
fn select(
&mut self,
interface: apdu_app::Interface,
command: iso7816::command::CommandView<'_>,
reply: &mut heapless::VecView<u8>,
) -> Result<(), Status> {
if interface != apdu_app::Interface::Contact {
return Err(Status::ConditionsOfUseNotSatisfied);
}
self.handle(command, reply)
}
fn call(
&mut self,
interface: apdu_app::Interface,
command: iso7816::command::CommandView<'_>,
reply: &mut heapless::VecView<u8>,
) -> Result<(), Status> {
if interface != apdu_app::Interface::Contact {
return Err(Status::ConditionsOfUseNotSatisfied);
}
self.handle(command, reply)
}
fn deselect(&mut self) {
self.reset()
}
}
bitflags! {
#[derive(Clone, Copy, Debug)]
pub struct AllowedAlgorithms: u32 {
const P_256 = 1;
const P_384 = 1 << 1;
const P_521 = 1 << 2;
const RSA_2048 = 1 << 3;
const RSA_3072 = 1 << 4;
const RSA_4096 = 1 << 5;
const X_25519 = 1 << 6;
const ED_25519 = 1 << 7;
const BRAINPOOL_P256R1 = 1 << 8;
const BRAINPOOL_P384R1 = 1 << 9;
const BRAINPOOL_P512R1 = 1 << 10;
const SECP256K1 = 1 << 11;
}
}
impl AllowedAlgorithms {
fn default_gen() -> Self {
[
Self::P_256,
Self::P_384,
Self::P_521,
#[cfg(feature = "rsa2048-gen")]
Self::RSA_2048,
#[cfg(feature = "rsa3072-gen")]
Self::RSA_3072,
#[cfg(feature = "rsa4096-gen")]
Self::RSA_4096,
Self::X_25519,
Self::ED_25519,
Self::SECP256K1,
]
.into_iter()
.fold(Self::empty(), |acc, value| acc | value)
}
fn default_import() -> Self {
[
Self::P_256,
Self::P_384,
Self::P_521,
#[cfg(feature = "rsa2048")]
Self::RSA_2048,
#[cfg(feature = "rsa3072")]
Self::RSA_3072,
#[cfg(feature = "rsa4096")]
Self::RSA_4096,
Self::X_25519,
Self::ED_25519,
Self::SECP256K1,
]
.into_iter()
.fold(Self::empty(), |acc, value| acc | value)
}
}
#[derive(Clone, Debug)]
#[non_exhaustive]
pub struct Options {
pub manufacturer: [u8; 2],
pub serial: [u8; 4],
pub(crate) historical_bytes: heapless::Vec<u8, 15>,
pub button_available: bool,
pub storage: Location,
pub allowed_imports: AllowedAlgorithms,
pub allowed_generation: AllowedAlgorithms,
pub reset_signal: Option<&'static ResetSignalAllocation>,
}
impl Options {
pub fn aid(&self) -> [u8; 16] {
[
RID[0],
RID[1],
RID[2],
RID[3],
RID[4],
PIX_APPLICATION[0],
PGP_SMARTCARD_VERSION[0],
PGP_SMARTCARD_VERSION[1],
self.manufacturer[0],
self.manufacturer[1],
self.serial[0],
self.serial[1],
self.serial[2],
self.serial[3],
PIX_RFU[0],
PIX_RFU[1],
]
}
}
impl Default for Options {
fn default() -> Self {
#[allow(clippy::unwrap_used)]
Self {
manufacturer: Default::default(),
serial: Default::default(),
historical_bytes: heapless::Vec::from_slice(&hex!("0031F573C00160009000")).unwrap(),
button_available: true,
storage: Location::External,
allowed_imports: AllowedAlgorithms::default_import(),
allowed_generation: AllowedAlgorithms::default_gen(),
reset_signal: None,
}
}
}
#[derive(Debug)]
pub struct Context<'a, T: Client> {
pub backend: &'a mut Backend<T>,
pub options: &'a Options,
pub state: &'a mut State,
pub data: &'a [u8],
pub reply: Reply<'a>,
}
impl<T: Client> Context<'_, T> {
pub fn load_state(&mut self) -> Result<LoadedContext<'_, T>, Status> {
Ok(LoadedContext {
state: self
.state
.load(self.backend.client_mut(), self.options.storage)
.map_err(|_| Status::UnspecifiedNonpersistentExecutionError)?,
options: self.options,
backend: self.backend,
data: self.data,
reply: self.reply.lend(),
})
}
pub fn lend(&mut self) -> Context<'_, T> {
Context {
reply: Reply(self.reply.0),
backend: self.backend,
options: self.options,
state: self.state,
data: self.data,
}
}
}
#[derive(Debug)]
pub struct LoadedContext<'a, T: Client> {
pub backend: &'a mut Backend<T>,
pub options: &'a Options,
pub state: LoadedState<'a>,
pub data: &'a [u8],
pub reply: Reply<'a>,
}
impl<T: Client> LoadedContext<'_, T> {
pub fn lend(&mut self) -> LoadedContext<'_, T> {
LoadedContext {
reply: Reply(self.reply.0),
backend: self.backend,
options: self.options,
state: self.state.lend(),
data: self.data,
}
}
}
use trussed_wrap_key_to_file::WrapKeyToFileClient;
pub trait Client:
CryptoClient + FilesystemClient + UiClient + AuthClient + WrapKeyToFileClient + ChunkedClient
{
}
impl<
C: CryptoClient
+ FilesystemClient
+ UiClient
+ WrapKeyToFileClient
+ AuthClient
+ ChunkedClient,
> Client for C
{
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn aid() {
assert_eq!(
Options::default().aid(),
hex!("D2 76 00 01 24 01 03 04 00 00 00 00 00 00 00 00"),
)
}
}