use std::time::Duration;
use bitflags::bitflags;
use bytemuck::{Pod, Zeroable, from_bytes};
use tracing::error;
use crate::{
commands::Error,
raw::{
Command, Response, request,
response::{ResponseNgFrame, errors},
},
};
use super::Proxmark;
bitflags! {
#[repr(transparent)]
#[derive(Copy, Clone, PartialEq, Eq, Pod, Zeroable)]
struct RequestFlags: u8 {
const Init = 1 << 0;
const ClearTrace = 1 << 1;
const CreditKey = 1 << 3;
const AIA = 1 << 4;
const ShallowMod = 1 << 5;
}
}
bitflags! {
#[repr(transparent)]
#[derive(Copy, Clone, PartialEq, Eq, Pod, Zeroable)]
struct ResponseFlags: u8 {
const Null = 1 << 0;
const CSN = 1 << 1;
const CC = 1 << 2;
const Conf = 1 << 3;
const AIA = 1 << 4;
}
}
mod fuses {
#![allow(missing_docs)]
bitflags::bitflags! {
#[repr(transparent)]
#[derive(Debug, Copy, Clone, PartialEq, Eq, bytemuck::Pod, bytemuck::Zeroable)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Fuses: u8 {
const RA = 1 << 0;
const FProd0 = 1 << 1;
const FProd1 = 1 << 2;
const Crypt0 = 1 << 3;
const Crypt1 = 1 << 4;
const Coding0 = 1 << 5;
const Coding1 = 1 << 6;
const FPERS = 1 << 7;
}
}
}
pub use fuses::Fuses;
pub const SZ_BLOCK: usize = 8;
#[derive(
Debug,
Copy,
Clone,
PartialEq,
Eq,
PartialOrd,
Ord,
Hash,
derive_more::From,
derive_more::Into,
derive_more::AsRef,
derive_more::AsMut,
derive_more::Deref,
derive_more::DerefMut,
derive_more::Index,
derive_more::IndexMut,
derive_more::IntoIterator,
Pod,
Zeroable,
)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[repr(C)]
pub struct Block([u8; SZ_BLOCK]);
#[repr(C, packed)]
#[derive(Copy, Clone, Pod, Zeroable)]
struct PackedPicopassConfBlock {
app_limit: u8,
otp: [u8; 2],
block_writelock: u8,
chip_config: u8,
mem_config: u8,
eas: u8,
fuses: Fuses,
}
#[repr(C, packed)]
#[derive(Copy, Clone, Pod, Zeroable)]
struct Header {
status: ResponseFlags,
csn: Block,
conf: PackedPicopassConfBlock,
}
#[repr(C, packed)]
#[derive(Copy, Clone, Pod, Zeroable)]
struct SecureBody {
epurse: Block,
key_debit: Block,
key_credit: Block,
}
#[repr(C, packed)]
#[derive(Copy, Clone, Pod, Zeroable)]
struct Footer {
app_issuer_area: Block,
}
#[derive(Debug, Copy, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Card {
pub csn: Option<Csn>,
pub conf: Option<PicopassConfBlock>,
pub aia: ApplicationIssuerArea,
pub secure_mode: Option<SecureMode>,
}
pub type Csn = Block;
pub type ApplicationIssuerArea = Block;
#[allow(missing_docs)] #[derive(Debug, Copy, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct PicopassConfBlock {
pub app_limit: u8,
pub otp: [u8; 2],
pub block_writelock: u8,
pub chip_config: u8,
pub mem_config: u8,
pub eas: u8,
pub fuses: Fuses,
}
#[allow(missing_docs)] #[derive(Debug, Copy, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct SecureMode {
pub epurse: Block,
pub key_debit: Block,
pub key_credit: Block,
}
const SZ_HEADER: usize = size_of::<Header>();
const SZ_SECURE_BODY: usize = size_of::<SecureBody>();
const SZ_FOOTER: usize = size_of::<Footer>();
const SZ_RESPONSE: usize = SZ_HEADER + SZ_SECURE_BODY + SZ_FOOTER;
impl Proxmark {
#[tracing::instrument(skip(self), level = tracing::Level::TRACE)]
pub fn read_iclass(&mut self) -> Result<Option<Card>, Error> {
let resp = self.0.request_response(
request::ng(
Command::HF_ICLASS_READER,
[(RequestFlags::Init | RequestFlags::ClearTrace).bits()],
),
Duration::from_secs(2),
)?;
if matches!(
resp,
Response::Ng(ResponseNgFrame {
status: errors::ERFTRANS,
..
})
) {
return Ok(None);
}
let payload = resp.payload();
if payload.len() < SZ_RESPONSE {
error!("payload was too short! {} < {SZ_RESPONSE}", payload.len());
return Err(Error::MalformedResponse(Box::new(resp)));
}
let header: &Header = from_bytes(&payload[..SZ_HEADER]);
if header.status == ResponseFlags::Null {
return Ok(None);
}
let csn = header
.status
.contains(ResponseFlags::CSN)
.then_some(header.csn);
let conf = header.status.contains(ResponseFlags::Conf).then_some({
let c = header.conf;
PicopassConfBlock {
app_limit: c.app_limit,
otp: c.otp,
block_writelock: c.block_writelock,
chip_config: c.chip_config,
mem_config: c.mem_config,
eas: c.eas,
fuses: c.fuses,
}
});
let pagemap = (header.conf.fuses & (Fuses::Crypt0 | Fuses::Crypt1)).bits() >> 3;
let (aia, secure_mode) = if pagemap == 0x01 {
let footer: &Footer = from_bytes(&payload[SZ_HEADER..][..SZ_FOOTER]);
(footer.app_issuer_area, None)
} else {
let body: &SecureBody = from_bytes(&payload[SZ_HEADER..][..SZ_SECURE_BODY]);
let footer: &Footer = from_bytes(&payload[SZ_HEADER + SZ_SECURE_BODY..][..SZ_FOOTER]);
(
footer.app_issuer_area,
Some({
let b = body;
SecureMode {
epurse: b.epurse,
key_debit: b.key_debit,
key_credit: b.key_credit,
}
}),
)
};
Ok(Some(Card {
csn,
conf,
aia,
secure_mode,
}))
}
}
#[cfg(feature = "sqlx")]
mod sqlx {
use sqlx::{Database, Decode, Encode, Type, encode::IsNull, error::BoxDynError};
use super::{Block, SZ_BLOCK};
impl<DB> Type<DB> for Block
where
DB: Database,
[u8]: Type<DB>,
{
fn type_info() -> <DB as Database>::TypeInfo {
<[u8]>::type_info()
}
}
impl<'r, DB> Decode<'r, DB> for Block
where
DB: Database,
&'r [u8]: Decode<'r, DB>,
{
fn decode(value: <DB as sqlx::Database>::ValueRef<'r>) -> Result<Self, BoxDynError> {
let slice = <&[u8]>::decode(value)?;
let array = <[u8; SZ_BLOCK]>::try_from(slice).map_err(|_| {
format!(
"blob had unexpected length for iClass block: {} bytes",
slice.len()
)
})?;
Ok(Block::from(array))
}
}
impl<'q, DB> Encode<'q, DB> for Block
where
DB: Database,
Vec<u8>: Encode<'q, DB>,
{
fn encode(
self,
buf: &mut <DB as Database>::ArgumentBuffer<'q>,
) -> Result<IsNull, BoxDynError> {
self.as_slice().to_vec().encode(buf)
}
fn encode_by_ref(
&self,
buf: &mut <DB as Database>::ArgumentBuffer<'q>,
) -> Result<IsNull, BoxDynError> {
(*self).encode(buf)
}
fn size_hint(&self) -> usize {
self.as_slice().len()
}
}
}