refactor MAC auth
This commit is contained in:
+2
-7
@@ -8,6 +8,7 @@ use core::time::Duration;
|
||||
|
||||
/// A card UID read by [`Pn532::read_passive_target_id`].
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct Uid {
|
||||
pub bytes: heapless::Vec<u8, 7>,
|
||||
}
|
||||
@@ -25,15 +26,9 @@ impl core::fmt::Display for Uid {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "defmt")]
|
||||
impl defmt::Format for Uid {
|
||||
fn format(&self, fmt: defmt::Formatter) {
|
||||
defmt::write!(fmt, "{}", defmt::Display2Format(self));
|
||||
}
|
||||
}
|
||||
|
||||
/// Outcome of [`Pn532::tg_init_as_target`] / [`Pn532::tg_init_as_target_default`].
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum TargetInitStatus {
|
||||
/// The PN532 entered target mode.
|
||||
Success,
|
||||
|
||||
+2
-14
@@ -1,10 +1,10 @@
|
||||
use core::fmt::Debug;
|
||||
use bitfields::bitflag;
|
||||
use defmt::Debug2Format;
|
||||
use crate::felica;
|
||||
|
||||
/// Driver-level error type, generic over the transport (I2C) error.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum Error<E> {
|
||||
/// The underlying transport (I2C) reported an error.
|
||||
#[error("transport error: {0:?}")]
|
||||
@@ -34,14 +34,8 @@ pub enum Error<E> {
|
||||
FelicaError(#[from] felica::error::Error),
|
||||
}
|
||||
|
||||
#[cfg(feature = "defmt")]
|
||||
impl<E: Debug> defmt::Format for Error<E> {
|
||||
fn format(&self, fmt: defmt::Formatter) {
|
||||
defmt::write!(fmt, "{:?}", Debug2Format(self))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[bitflag(u8)]
|
||||
pub enum StatusCode {
|
||||
#[base]
|
||||
@@ -74,9 +68,3 @@ pub enum StatusCode {
|
||||
NADMissing = 0x2E,
|
||||
Max = 0x2F
|
||||
}
|
||||
|
||||
impl defmt::Format for StatusCode {
|
||||
fn format(&self, fmt: defmt::Formatter) {
|
||||
defmt::write!(fmt, "{}", Debug2Format(self))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum Error {
|
||||
#[error("Status error: sf1:{0}, sf2:{1}")]
|
||||
Status(u8, u8)
|
||||
}
|
||||
|
||||
#[cfg(feature = "defmt")]
|
||||
impl defmt::Format for Error {
|
||||
fn format(&self, f: defmt::Formatter) {
|
||||
defmt::write!(f, "{}", defmt::Display2Format(self))
|
||||
}
|
||||
Status(u8, u8),
|
||||
#[cfg(feature = "felica-lite-s")]
|
||||
#[error("MAC mismatch")]
|
||||
MACMismatch,
|
||||
}
|
||||
@@ -1,226 +0,0 @@
|
||||
use des::cipher::{Block, BlockCipherEncrypt, Key, KeyInit};
|
||||
use des::TdesEde2;
|
||||
use rand_core::Rng;
|
||||
use crate::{Interface, Pn532};
|
||||
use crate::felica::types::{FelicaLiteSBlock, FelicaLiteSServiceCode, IDm, PMm};
|
||||
|
||||
impl<B: Interface, const N: usize> Pn532<B, N> {
|
||||
pub fn set_random_challenge(&mut self, card: &IDm, pmm: &PMm, rng: &mut impl Rng)
|
||||
-> Result<([u8; 8], [u8; 8]), crate::Error<B::TransportError>>
|
||||
{
|
||||
let mut data = [[0u8; 16]];
|
||||
rng.fill_bytes(&mut data[0]);
|
||||
self.felica_write_without_encryption(
|
||||
card, pmm,
|
||||
&[FelicaLiteSServiceCode::Write],
|
||||
&[FelicaLiteSBlock::RC],
|
||||
&data,
|
||||
)?;
|
||||
let rc1 = data[0][0..8].try_into().unwrap();
|
||||
let rc2 = data[0][8..16].try_into().unwrap();
|
||||
Ok((rc1, rc2))
|
||||
}
|
||||
|
||||
pub fn get_session_keys(
|
||||
&mut self,
|
||||
rc: &[u8; 16],
|
||||
card_key: &[u8; 16],
|
||||
) -> Result<([u8; 8], [u8; 8]), crate::Error<B::TransportError>> {
|
||||
// FeliCa byte order:
|
||||
//
|
||||
// CK1 = CK[0..8]
|
||||
// CK2 = CK[8..16]
|
||||
//
|
||||
// The DES interface receives each half reversed.
|
||||
let mut des_key = [0u8; 16];
|
||||
|
||||
des_key[0..8].copy_from_slice(&{
|
||||
let mut x = [0u8; 8];
|
||||
x.copy_from_slice(&card_key[0..8]);
|
||||
x.reverse();
|
||||
x
|
||||
});
|
||||
|
||||
des_key[8..16].copy_from_slice(&{
|
||||
let mut x = [0u8; 8];
|
||||
x.copy_from_slice(&card_key[8..16]);
|
||||
x.reverse();
|
||||
x
|
||||
});
|
||||
|
||||
// RC1 || RC2, with each 8-byte half reversed.
|
||||
let mut des_rc = [0u8; 16];
|
||||
|
||||
des_rc[0..8].copy_from_slice(&{
|
||||
let mut x = [0u8; 8];
|
||||
x.copy_from_slice(&rc[0..8]);
|
||||
x.reverse();
|
||||
x
|
||||
});
|
||||
|
||||
des_rc[8..16].copy_from_slice(&{
|
||||
let mut x = [0u8; 8];
|
||||
x.copy_from_slice(&rc[8..16]);
|
||||
x.reverse();
|
||||
x
|
||||
});
|
||||
|
||||
let key = Key::<TdesEde2>::try_from(des_key).unwrap();
|
||||
let cipher = TdesEde2::new(&key);
|
||||
|
||||
// 2-key 3DES-CBC, IV = 0.
|
||||
let mut state = Block::<TdesEde2>::default();
|
||||
|
||||
// RC1
|
||||
for i in 0..8 {
|
||||
state[i] ^= des_rc[i];
|
||||
}
|
||||
cipher.encrypt_block(&mut state);
|
||||
|
||||
let sk1_des: [u8; 8] = *state.as_array().unwrap();
|
||||
|
||||
// RC2
|
||||
for i in 0..8 {
|
||||
state[i] ^= des_rc[8 + i];
|
||||
}
|
||||
cipher.encrypt_block(&mut state);
|
||||
|
||||
let sk2_des: [u8; 8] = *state.as_array().unwrap();
|
||||
|
||||
// Convert the DES-side values back to FeliCa byte order.
|
||||
let mut sk1 = sk1_des;
|
||||
sk1.reverse();
|
||||
|
||||
let mut sk2 = sk2_des;
|
||||
sk2.reverse();
|
||||
|
||||
crate::debug!("RC: {:02X}", rc);
|
||||
crate::debug!("CK DES key: {:02X}", des_key);
|
||||
crate::debug!("RC DES input: {:02X}", des_rc);
|
||||
crate::debug!("SK1 DES: {:02X}", sk1_des);
|
||||
crate::debug!("SK1: {:02X}", sk1);
|
||||
crate::debug!("SK2 DES: {:02X}", sk2_des);
|
||||
crate::debug!("SK2: {:02X}", sk2);
|
||||
|
||||
Ok((sk1, sk2))
|
||||
}
|
||||
|
||||
pub fn get_plain_text(&mut self, card: &IDm, pmm: &PMm)
|
||||
-> Result<([u8; 56], [u8; 8]), crate::Error<B::TransportError>>
|
||||
{
|
||||
let mut data = [[0u8; 16]; 3];
|
||||
self.felica_read_without_encryption(
|
||||
card, pmm,
|
||||
&[FelicaLiteSServiceCode::Read],
|
||||
&[FelicaLiteSBlock::ID, FelicaLiteSBlock::CKV, FelicaLiteSBlock::MACA],
|
||||
&mut data
|
||||
)?;
|
||||
let id = data[0];
|
||||
let ckv = data[1];
|
||||
let maca = data[2];
|
||||
let mut data = [0u8; 56];
|
||||
data[0] = FelicaLiteSBlock::ID as u8;
|
||||
data[1] = 0x00;
|
||||
data[2] = FelicaLiteSBlock::CKV as u8;
|
||||
data[3] = 0x00;
|
||||
data[4] = FelicaLiteSBlock::MACA as u8;
|
||||
data[5] = 0x00;
|
||||
data[6] = 0xFF;
|
||||
data[7] = 0xFF;
|
||||
data[8..24].copy_from_slice(&id);
|
||||
data[24..40].copy_from_slice(&ckv);
|
||||
data[40..56].copy_from_slice(&maca);
|
||||
crate::debug!("{} {}", maca[0..8], maca[8..16]);
|
||||
let maca: [u8; 8] = maca[0..8].try_into().unwrap();
|
||||
Ok((data, maca))
|
||||
}
|
||||
|
||||
pub fn check_mac(
|
||||
&mut self,
|
||||
card: &IDm,
|
||||
pmm: &PMm,
|
||||
sk1: &[u8; 8],
|
||||
sk2: &[u8; 8],
|
||||
iv: &[u8; 8],
|
||||
) -> Result<bool, crate::Error<B::TransportError>> {
|
||||
let (data, mac_a) = self.get_plain_text(card, pmm)?;
|
||||
|
||||
// data layout from get_plain_text():
|
||||
//
|
||||
// [0..8] = block list
|
||||
// [8..24] = ID block
|
||||
// [24..40] = CKV block
|
||||
// [40..56] = MAC_A block (NOT used in MAC calculation)
|
||||
|
||||
let block_list = &data[0..8];
|
||||
let block_data = &data[8..40];
|
||||
|
||||
// FeliCa Lite-S uses the session keys with their byte order reversed
|
||||
// at the DES interface.
|
||||
let mut key_bytes = [0u8; 16];
|
||||
|
||||
for i in 0..8 {
|
||||
key_bytes[i] = sk1[7 - i];
|
||||
key_bytes[8 + i] = sk2[7 - i];
|
||||
}
|
||||
|
||||
let key = Key::<TdesEde2>::try_from(key_bytes).unwrap();
|
||||
let cipher = TdesEde2::new(&key);
|
||||
|
||||
// RC1 is also byte-reversed at the DES interface.
|
||||
let mut state_bytes = *iv;
|
||||
state_bytes.reverse();
|
||||
|
||||
// The initial plaintext for MAC_A is the block-number list.
|
||||
// Byte order is reversed before the DES operation.
|
||||
let mut first = [0u8; 8];
|
||||
first.copy_from_slice(block_list);
|
||||
first.reverse();
|
||||
|
||||
for i in 0..8 {
|
||||
state_bytes[i] ^= first[i];
|
||||
}
|
||||
|
||||
let mut state = Block::<TdesEde2>::try_from(state_bytes).unwrap();
|
||||
cipher.encrypt_block(&mut state);
|
||||
|
||||
crate::debug!(
|
||||
"MAC block list: {:02X}",
|
||||
block_list
|
||||
);
|
||||
|
||||
crate::debug!(
|
||||
"MAC first input: {:02X}",
|
||||
state_bytes
|
||||
);
|
||||
|
||||
// Process ID and CKV.
|
||||
//
|
||||
// Each 8-byte piece is byte-reversed before entering the
|
||||
// 2-key 3DES CBC operation.
|
||||
for chunk in block_data.chunks_exact(8) {
|
||||
let mut input = [0u8; 8];
|
||||
input.copy_from_slice(chunk);
|
||||
input.reverse();
|
||||
|
||||
for i in 0..8 {
|
||||
input[i] ^= state[i];
|
||||
}
|
||||
|
||||
crate::debug!("MAC input: {:02X}", input);
|
||||
|
||||
let mut block = Block::<TdesEde2>::try_from(input).unwrap();
|
||||
cipher.encrypt_block(&mut block);
|
||||
|
||||
state = block;
|
||||
}
|
||||
|
||||
// Reverse the final result back to FeliCa byte order.
|
||||
let mut mac: [u8; 8] = *state.as_array().unwrap();
|
||||
mac.reverse();
|
||||
|
||||
crate::debug!("mac: {:02X}, mac_a: {:02X}", mac, mac_a);
|
||||
|
||||
Ok(mac == mac_a[0..8])
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
use crate::{Interface, Pn532};
|
||||
|
||||
impl<B: Interface, const N: usize> Pn532<B, N> {
|
||||
pub fn setup_first_issuance(&mut self) {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
pub mod issuance;
|
||||
|
||||
use des::cipher::{Block, BlockCipherEncrypt, Key, KeyInit};
|
||||
use des::TdesEde2;
|
||||
use rand_core::Rng;
|
||||
use crate::{Interface, Pn532};
|
||||
use crate::felica::error::Error;
|
||||
use crate::felica::types::{AsBlock, CardSession, FelicaLiteSBlock, FelicaLiteSServiceCode};
|
||||
use crate::felica::types::felica_lite_s::auth::{AuthSession, CardKey, MacInput, RandomChallenge, SessionKeys};
|
||||
|
||||
impl<B: Interface, const N: usize> Pn532<B, N> {
|
||||
pub fn try_authenticate(&mut self, rng: &mut impl Rng, card: &CardSession, key: &CardKey)
|
||||
-> Result<Option<AuthSession>, crate::Error<B::TransportError>>
|
||||
{
|
||||
let rc = self.set_random_challenge(card, rng)?;
|
||||
let sk = self.get_session_keys(&rc, key)?;
|
||||
let session = AuthSession::new(sk, rc);
|
||||
let is_valid_auth = self.check_auth(card, &session)?;
|
||||
if is_valid_auth {
|
||||
Ok(Some(session))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_with_mac(
|
||||
&mut self,
|
||||
card: &CardSession,
|
||||
session: &AuthSession,
|
||||
block: impl AsBlock
|
||||
) -> Result<[u8; 16], crate::Error<B::TransportError>> {
|
||||
let mut block_data = [[0u8; 16]; 2];
|
||||
let block_list = [block.as_block(), FelicaLiteSBlock::MACA as u16];
|
||||
self.felica_read_without_encryption(
|
||||
card,
|
||||
&[FelicaLiteSServiceCode::Read],
|
||||
&block_list,
|
||||
&mut block_data
|
||||
)?;
|
||||
let input = MacInput::new(&block_data, &block_list);
|
||||
let mac = self.compute_mac(session, &input);
|
||||
if mac != input.maca() {
|
||||
return Err(crate::Error::FelicaError(Error::MACMismatch));
|
||||
}
|
||||
|
||||
Ok(block_data[0])
|
||||
}
|
||||
|
||||
fn set_random_challenge(&mut self, card: &CardSession, rng: &mut impl Rng)
|
||||
-> Result<RandomChallenge, crate::Error<B::TransportError>>
|
||||
{
|
||||
let mut data = [[0u8; 16]];
|
||||
rng.fill_bytes(&mut data[0]);
|
||||
self.felica_write_without_encryption(
|
||||
card,
|
||||
&[FelicaLiteSServiceCode::Write],
|
||||
&[FelicaLiteSBlock::RC],
|
||||
&data,
|
||||
)?;
|
||||
let rc = RandomChallenge::from(data[0]);
|
||||
Ok(rc)
|
||||
}
|
||||
|
||||
fn get_session_keys(
|
||||
&mut self,
|
||||
rc: &RandomChallenge,
|
||||
card_key: &CardKey,
|
||||
) -> Result<SessionKeys, crate::Error<B::TransportError>> {
|
||||
let des_key = card_key.as_des();
|
||||
|
||||
let key = Key::<TdesEde2>::try_from(des_key).unwrap();
|
||||
let cipher = TdesEde2::new(&key);
|
||||
|
||||
Ok(SessionKeys::new(&cipher, &rc))
|
||||
|
||||
// crate::trace!("RC: {:02X}", rc);
|
||||
// crate::trace!("CK DES key: {:02X}", des_key);
|
||||
// crate::trace!("RC DES input: {:02X}", des_rc);
|
||||
// crate::trace!("SK1 DES: {:02X}", sk1_des);
|
||||
// crate::trace!("SK1: {:02X}", sk1);
|
||||
// crate::trace!("SK2 DES: {:02X}", sk2_des);
|
||||
// crate::trace!("SK2: {:02X}", sk2);
|
||||
}
|
||||
|
||||
fn compute_mac<const U: usize>(
|
||||
&mut self,
|
||||
session: &AuthSession,
|
||||
input: &MacInput<U>
|
||||
) -> [u8; 8] {
|
||||
// data layout from get_plain_text():
|
||||
//
|
||||
// [0..8] = block list
|
||||
// [8..24] = ID block
|
||||
// [24..40] = CKV block
|
||||
// [40..56] = MAC_A block (NOT used in MAC calculation)
|
||||
|
||||
let block_list = &input.block_list();
|
||||
let block_data = &input.block_data();
|
||||
|
||||
// FeliCa Lite-S uses the session keys with their byte order reversed
|
||||
// at the DES interface.
|
||||
let key_bytes = session.sk.as_des();
|
||||
|
||||
let key = Key::<TdesEde2>::try_from(key_bytes).unwrap();
|
||||
let cipher = TdesEde2::new(&key);
|
||||
|
||||
// RC1 is also byte-reversed at the DES interface.
|
||||
let mut state_bytes = session.rc.rc1();
|
||||
state_bytes.reverse();
|
||||
|
||||
// The initial plaintext for MAC_A is the block-number list.
|
||||
// Byte order is reversed before the DES operation.
|
||||
let mut first = [0u8; 8];
|
||||
first.copy_from_slice(block_list);
|
||||
first.reverse();
|
||||
|
||||
for i in 0..8 {
|
||||
state_bytes[i] ^= first[i];
|
||||
}
|
||||
|
||||
let mut state = Block::<TdesEde2>::try_from(state_bytes).unwrap();
|
||||
cipher.encrypt_block(&mut state);
|
||||
|
||||
// Process ID and CKV.
|
||||
//
|
||||
// Each 8-byte piece is byte-reversed before entering the
|
||||
// 2-key 3DES CBC operation.
|
||||
for chunk in block_data.chunks_exact(8) {
|
||||
let mut input = [0u8; 8];
|
||||
input.copy_from_slice(chunk);
|
||||
input.reverse();
|
||||
|
||||
for i in 0..8 {
|
||||
input[i] ^= state[i];
|
||||
}
|
||||
|
||||
crate::trace!("MAC input: {:02X}", input);
|
||||
|
||||
let mut block = Block::<TdesEde2>::try_from(input).unwrap();
|
||||
cipher.encrypt_block(&mut block);
|
||||
|
||||
state = block;
|
||||
}
|
||||
|
||||
// Reverse the final result back to FeliCa byte order.
|
||||
let mut mac: [u8; 8] = state.0;
|
||||
mac.reverse();
|
||||
crate::trace!("mac: {:02X}, maca: {:02X}", mac, input.maca());
|
||||
mac
|
||||
}
|
||||
|
||||
fn check_auth(
|
||||
&mut self,
|
||||
card: &CardSession,
|
||||
session: &AuthSession,
|
||||
) -> Result<bool, crate::Error<B::TransportError>> {
|
||||
let mut data = [[0u8; 16]; 3];
|
||||
self.felica_read_without_encryption(
|
||||
card,
|
||||
&[FelicaLiteSServiceCode::Read],
|
||||
&[FelicaLiteSBlock::ID, FelicaLiteSBlock::CKV, FelicaLiteSBlock::MACA],
|
||||
&mut data
|
||||
)?;
|
||||
let block_list = &[FelicaLiteSBlock::ID, FelicaLiteSBlock::CKV, FelicaLiteSBlock::MACA];
|
||||
let input = MacInput::new(
|
||||
&data, block_list
|
||||
);
|
||||
let mac = self.compute_mac(session, &input);
|
||||
Ok(mac == input.maca())
|
||||
}
|
||||
}
|
||||
@@ -126,8 +126,7 @@ impl<B: Interface, const N: usize> Pn532<B, N> {
|
||||
/// Note: the number of blocks is limited by the 64-byte response buffer.
|
||||
pub fn felica_read_without_encryption(
|
||||
&mut self,
|
||||
card: &IDm,
|
||||
pmm: &PMm,
|
||||
card: &CardSession,
|
||||
service_code_list: &[impl AsServiceCode],
|
||||
block_list: &[impl AsBlock],
|
||||
block_data: &mut [[u8; 16]],
|
||||
@@ -160,7 +159,7 @@ impl<B: Interface, const N: usize> Pn532<B, N> {
|
||||
// command
|
||||
let mut cmd = heapless::Vec::<u8, COMMAND_SIZE>::new();
|
||||
cmd.push(FELICA_CMD_READ_WITHOUT_ENCRYPTION).unwrap();
|
||||
cmd.extend_from_slice(&card.into_bytes()).unwrap();
|
||||
cmd.extend_from_slice(&card.idm.into_bytes()).unwrap();
|
||||
cmd.push(num_service as u8).unwrap();
|
||||
for sc in service_code_list {
|
||||
let sc: [u8; 2] = sc.as_service_code().to_le_bytes();
|
||||
@@ -180,7 +179,7 @@ impl<B: Interface, const N: usize> Pn532<B, N> {
|
||||
let response_len = self.felica_send_command(
|
||||
&cmd,
|
||||
&mut response,
|
||||
pmm.get_read_timeout(num_block, Some(Duration::from_millis(50)))
|
||||
card.pmm.get_read_timeout(num_block, Some(Duration::from_millis(50)))
|
||||
)?;
|
||||
crate::trace!("Read Response: {:02X}", response);
|
||||
if response_len != 12 + 16 * num_block {
|
||||
@@ -201,8 +200,7 @@ impl<B: Interface, const N: usize> Pn532<B, N> {
|
||||
/// Send a FeliCa "Write Without Encryption" command.
|
||||
pub fn felica_write_without_encryption(
|
||||
&mut self,
|
||||
card: &IDm,
|
||||
pmm: &PMm,
|
||||
card: &CardSession,
|
||||
service_code_list: &[impl AsServiceCode],
|
||||
block_list: &[impl AsBlock],
|
||||
block_data: &[[u8; 16]],
|
||||
@@ -234,7 +232,7 @@ impl<B: Interface, const N: usize> Pn532<B, N> {
|
||||
// command
|
||||
let mut cmd = heapless::Vec::<u8, COMMAND_SIZE>::new();
|
||||
cmd.push(FELICA_CMD_WRITE_WITHOUT_ENCRYPTION).unwrap();
|
||||
cmd.extend_from_slice(&card.into_bytes()).unwrap();
|
||||
cmd.extend_from_slice(&card.idm.into_bytes()).unwrap();
|
||||
cmd.push(num_service as u8).unwrap();
|
||||
for sc in service_code_list {
|
||||
let sc = sc.as_service_code().to_le_bytes();
|
||||
@@ -254,7 +252,7 @@ impl<B: Interface, const N: usize> Pn532<B, N> {
|
||||
let response_len = self.felica_send_command(
|
||||
&cmd,
|
||||
&mut response,
|
||||
pmm.get_write_timeout(num_block, Some(Duration::from_millis(50)))
|
||||
card.pmm.get_write_timeout(num_block, Some(Duration::from_millis(50)))
|
||||
)?;
|
||||
if response_len != RESPONSE_SIZE {
|
||||
return Err(Error::InvalidFrame);
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
// use core::fmt::{Display, Formatter};
|
||||
// use bitfields::bitfield;
|
||||
//
|
||||
// #[bitfield([u8; 16])]
|
||||
// #[derive(Eq, PartialEq)]
|
||||
// pub struct ChallengeKey {
|
||||
// pub ck1: [u8; 8],
|
||||
// pub ck2: [u8; 8],
|
||||
// }
|
||||
//
|
||||
// impl ChallengeKey {
|
||||
// pub fn try_from_block(block: &[u8; 16]) -> Result<Self, <[u8; 8] as TryFrom<&[u8]>>::Error> {
|
||||
// let mut ck1: [u8; 8] = block[0..8].try_into()?;
|
||||
// ck1.reverse();
|
||||
// let mut ck2: [u8; 8] = block[8..16].try_into()?;
|
||||
// ck2.reverse();
|
||||
// let mut instance = Self::new();
|
||||
// instance.set_ck1(ck1);
|
||||
// instance.set_ck2(ck2);
|
||||
// Ok(instance)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// impl Display for ChallengeKey {
|
||||
// fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
|
||||
// #[cfg(feature = "alloc")]
|
||||
// {
|
||||
// let ck1 = hex::encode(self.ck1());
|
||||
// let ck2 = hex::encode(self.ck2());
|
||||
// write!(f, "{ck1},{ck2}")
|
||||
// }
|
||||
// #[cfg(not(feature = "alloc"))]
|
||||
// {
|
||||
// write!(f, "{:?}", self)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// #[cfg(feature = "defmt")]
|
||||
// impl defmt::Format for ChallengeKey {
|
||||
// fn format(&self, fmt: defmt::Formatter) {
|
||||
// defmt::write!(fmt, "{}", defmt::Display2Format(self))
|
||||
// }
|
||||
// }
|
||||
@@ -0,0 +1,53 @@
|
||||
use core::fmt::Display;
|
||||
use core::ops::Deref;
|
||||
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct CardKey(pub [u8; 16]);
|
||||
|
||||
impl CardKey {
|
||||
/// FeliCa byte order:
|
||||
///
|
||||
/// CK1 = CK[0..8]
|
||||
///
|
||||
/// CK2 = CK[8..16]
|
||||
///
|
||||
/// The DES interface receives each half reversed.
|
||||
pub fn as_des(&self) -> [u8; 16] {
|
||||
let card_key = self.0;
|
||||
let mut des_key = [0u8; 16];
|
||||
|
||||
for i in 0..8 {
|
||||
des_key[7-i] = card_key[i];
|
||||
des_key[15-i] = card_key[i + 8];
|
||||
}
|
||||
|
||||
des_key
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for CardKey {
|
||||
type Target = [u8; 16];
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<[u8; 16]> for CardKey {
|
||||
fn from(val: [u8; 16]) -> Self {
|
||||
CardKey(val)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for CardKey {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
#[cfg(feature = "alloc")]
|
||||
{
|
||||
write!(f, "{}", hex::encode_upper(self.0))
|
||||
}
|
||||
#[cfg(not(feature = "alloc"))]
|
||||
{
|
||||
write!(f, "{:?}", self.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
use crate::felica::types::AsBlock;
|
||||
use crate::FELICA_READ_MAX_BLOCK_NUM;
|
||||
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct MacInput<const N: usize> {
|
||||
block_data: [[u8; 16]; N],
|
||||
block_list: [u8; N]
|
||||
}
|
||||
|
||||
impl<const N: usize> MacInput<N> {
|
||||
pub fn new(block_data: &[[u8; 16]; N], blocks_list: &[impl AsBlock; N]) -> MacInput<N> {
|
||||
if N > FELICA_READ_MAX_BLOCK_NUM {
|
||||
panic!("too many blocks");
|
||||
}
|
||||
if N < 2 {
|
||||
panic!("block should have at least 2 blocks");
|
||||
}
|
||||
let mut block_nums= [0u8; N];
|
||||
for (i, block) in blocks_list.iter().enumerate(){
|
||||
block_nums[i] = block.as_block() as u8;
|
||||
}
|
||||
Self {
|
||||
block_data: *block_data,
|
||||
block_list: block_nums
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_vec(&self) -> heapless::Vec<u8, 64> {
|
||||
let mut data = heapless::Vec::new();
|
||||
data.extend(self.block_list());
|
||||
data.extend_from_slice(&self.block_data()).unwrap();
|
||||
data
|
||||
}
|
||||
|
||||
pub fn block_data(&self) -> heapless::Vec<u8, 64> {
|
||||
let mut data = heapless::Vec::<u8, 64>::new();
|
||||
for block in self.block_data.iter().take(N - 1) {
|
||||
data.extend_from_slice(block).unwrap();
|
||||
}
|
||||
data
|
||||
}
|
||||
|
||||
|
||||
pub fn block_list(&self) -> [u8; 8] {
|
||||
let mut data = [0xFFu8; 8];
|
||||
for (i, block) in self.block_list.iter().enumerate() {
|
||||
data[2 * i] = *block;
|
||||
data[1 + 2 * i] = 0x00;
|
||||
}
|
||||
data
|
||||
}
|
||||
|
||||
pub fn maca(&self) -> [u8; 8] {
|
||||
self.block_data[N - 1][0..8].try_into().unwrap()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
mod card_key;
|
||||
mod rc;
|
||||
mod sk;
|
||||
mod mac_input;
|
||||
|
||||
pub use card_key::*;
|
||||
pub use rc::*;
|
||||
pub use sk::*;
|
||||
pub use mac_input::*;
|
||||
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[readonly::make]
|
||||
pub struct AuthSession {
|
||||
pub sk: SessionKeys,
|
||||
pub rc: RandomChallenge
|
||||
}
|
||||
|
||||
impl AuthSession {
|
||||
pub fn new(sk: SessionKeys, rc: RandomChallenge) -> Self {
|
||||
AuthSession { sk, rc }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
use core::fmt::Display;
|
||||
use core::ops::Deref;
|
||||
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct RandomChallenge([u8; 16]);
|
||||
|
||||
impl Deref for RandomChallenge {
|
||||
type Target = [u8; 16];
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<[u8; 16]> for RandomChallenge {
|
||||
fn from(data: [u8; 16]) -> Self {
|
||||
RandomChallenge(data)
|
||||
}
|
||||
}
|
||||
|
||||
impl RandomChallenge {
|
||||
/// RC1 || RC2, with each 8-byte half reversed.
|
||||
pub fn as_des(&self) -> [u8; 16] {
|
||||
let rc = self.0;
|
||||
let mut des = [0u8; 16];
|
||||
|
||||
for i in 0..8 {
|
||||
des[7-i] = rc[i];
|
||||
des[15-i] = rc[i + 8];
|
||||
}
|
||||
|
||||
des
|
||||
}
|
||||
|
||||
pub fn rc1(&self) -> [u8; 8] {
|
||||
let rc = self.0;
|
||||
rc[0..8].try_into().unwrap()
|
||||
}
|
||||
|
||||
pub fn rc2(&self) -> [u8; 8] {
|
||||
let rc = self.0;
|
||||
rc[8..16].try_into().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for RandomChallenge {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
#[cfg(feature = "alloc")]
|
||||
{
|
||||
write!(f, "{},{}", hex::encode_upper(self.rc1()), hex::encode_upper(self.rc2()))
|
||||
}
|
||||
#[cfg(not(feature = "alloc"))]
|
||||
{
|
||||
write!(f, "{:?}", self.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
use cbc::cipher::{Block, BlockCipherEncrypt};
|
||||
use des::TdesEde2;
|
||||
use crate::felica::types::felica_lite_s::auth::RandomChallenge;
|
||||
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct SessionKeys {
|
||||
sk1: [u8; 8],
|
||||
sk2: [u8; 8],
|
||||
}
|
||||
|
||||
impl SessionKeys {
|
||||
pub fn new(cipher: &TdesEde2, rc: &RandomChallenge) -> Self {
|
||||
let des_rc = rc.as_des();
|
||||
// 2-key 3DES-CBC, IV = 0.
|
||||
let mut state = Block::<TdesEde2>::default();
|
||||
|
||||
// RC1
|
||||
for i in 0..8 {
|
||||
state[i] ^= des_rc[i];
|
||||
}
|
||||
cipher.encrypt_block(&mut state);
|
||||
|
||||
let sk1_des: [u8; 8] = *state.as_array().unwrap();
|
||||
|
||||
// RC2
|
||||
for i in 0..8 {
|
||||
state[i] ^= des_rc[8 + i];
|
||||
}
|
||||
cipher.encrypt_block(&mut state);
|
||||
|
||||
let sk2_des: [u8; 8] = *state.as_array().unwrap();
|
||||
|
||||
// Convert the DES-side values back to FeliCa byte order.
|
||||
let mut sk1 = sk1_des;
|
||||
sk1.reverse();
|
||||
|
||||
let mut sk2 = sk2_des;
|
||||
sk2.reverse();
|
||||
Self { sk1, sk2 }
|
||||
}
|
||||
|
||||
pub fn as_des(&self) -> [u8; 16] {
|
||||
let mut key_bytes = [0u8; 16];
|
||||
|
||||
for i in 0..8 {
|
||||
key_bytes[i] = self.sk1[7 - i];
|
||||
key_bytes[8 + i] = self.sk2[7 - i];
|
||||
}
|
||||
|
||||
key_bytes
|
||||
}
|
||||
|
||||
pub fn as_bytes(&self) -> [u8; 16] {
|
||||
let mut key_bytes = [0u8; 16];
|
||||
key_bytes[0..8].copy_from_slice(&self.sk1);
|
||||
key_bytes[8..16].copy_from_slice(&self.sk2);
|
||||
key_bytes
|
||||
}
|
||||
|
||||
pub fn sk1(&self) -> [u8; 8] {
|
||||
self.sk1
|
||||
}
|
||||
|
||||
pub fn sk2(&self) -> [u8; 8] {
|
||||
self.sk2
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
use bitfields::bitfield;
|
||||
|
||||
#[bitfield([u8; 16])]
|
||||
#[derive(Eq, PartialEq)]
|
||||
pub struct SubstractionRegisterBlock {
|
||||
pub reg_a: [u8; 4],
|
||||
pub reg_b: [u8; 4],
|
||||
pub reg_c: [u8; 8],
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
pub mod block;
|
||||
pub mod auth;
|
||||
|
||||
///! Types for Felica Lite-S (RC-S966)
|
||||
@@ -10,6 +9,7 @@ use super::AsServiceCode;
|
||||
|
||||
/// Service Code
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[bitflag(u16)]
|
||||
pub enum ServiceCode {
|
||||
#[base]
|
||||
@@ -32,6 +32,7 @@ impl AsServiceCode for ServiceCode {
|
||||
|
||||
/// FeliCa Lite system block numbers.
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[bitflag(u16)]
|
||||
pub enum Block {
|
||||
#[base]
|
||||
|
||||
@@ -2,6 +2,7 @@ use bitfields::bitflag;
|
||||
|
||||
/// https://www.sony.co.jp/en/Products/felica/business/tech-support/list.html
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[bitflag(u8)]
|
||||
pub enum CardICType {
|
||||
#[base]
|
||||
@@ -29,6 +30,7 @@ pub enum CardICType {
|
||||
|
||||
/// https://www.sony.co.jp/en/Products/felica/business/tech-support/list.html
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum MobileICType {
|
||||
/// 0x14~0x1F
|
||||
V3(u8),
|
||||
|
||||
@@ -1,8 +1,23 @@
|
||||
use core::fmt::{Display, Formatter};
|
||||
use bitfields::bitfield;
|
||||
|
||||
#[bitfield([u8; 8], from_endian = big, into_endian = big)]
|
||||
#[derive(PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct IDm {
|
||||
pub manufacturer: [u8; 2],
|
||||
pub card_id: [u8; 6]
|
||||
}
|
||||
|
||||
impl Display for IDm {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
|
||||
#[cfg(feature = "alloc")]
|
||||
{
|
||||
write!(f, "{}", hex::encode_upper(self.0))
|
||||
}
|
||||
#[cfg(not(feature = "alloc"))]
|
||||
{
|
||||
write!(f, "{:?}", self)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,12 +5,14 @@ mod idm;
|
||||
pub mod felica_lite_s;
|
||||
pub mod ic_type;
|
||||
|
||||
use core::fmt::{Display, Formatter};
|
||||
pub use idm::*;
|
||||
pub use pmm::*;
|
||||
pub use polling::PollingRequestCode as FelicaPollingRequestCode;
|
||||
pub use polling::PollingResponse as FelicaPollingResponse;
|
||||
#[cfg(feature = "felica-lite-s")]
|
||||
pub use felica_lite_s::{Block as FelicaLiteSBlock, ServiceCode as FelicaLiteSServiceCode};
|
||||
use crate::felica::types::polling::PollingResponse;
|
||||
|
||||
pub trait AsServiceCode {
|
||||
fn as_service_code(&self) -> u16;
|
||||
@@ -35,4 +37,33 @@ impl AsServiceCode for u16 {
|
||||
fn from_service_code(val: u16) -> Self {
|
||||
val
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[readonly::make]
|
||||
pub struct CardSession {
|
||||
pub idm: IDm,
|
||||
pub pmm: PMm
|
||||
}
|
||||
|
||||
impl CardSession {
|
||||
pub fn new(idm: IDm, pmm: PMm) -> Self {
|
||||
Self { idm, pmm }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PollingResponse> for CardSession {
|
||||
fn from(value: PollingResponse) -> Self {
|
||||
Self {
|
||||
idm: value.idm,
|
||||
pmm: value.pmm
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for CardSession {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
|
||||
write!(f, "IDm: {}, PMm: {}", self.idm, self.pmm)
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
use core::fmt::{Display, Formatter};
|
||||
use core::time::Duration;
|
||||
use bitfields::bitfield;
|
||||
|
||||
#[bitfield([u8; 8])]
|
||||
#[derive(PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct PMm {
|
||||
pub rom_type: u8,
|
||||
pub ic_type: u8,
|
||||
@@ -37,4 +39,17 @@ impl PMm {
|
||||
// Round up: a timeout must never underestimate the max response time.
|
||||
Duration::from_micros(us as u64 + 1)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for PMm {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
|
||||
#[cfg(feature = "alloc")]
|
||||
{
|
||||
write!(f, "{}", hex::encode_upper(self.0))
|
||||
}
|
||||
#[cfg(not(feature = "alloc"))]
|
||||
{
|
||||
write!(f, "{:?}", self)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,10 @@
|
||||
use alloc::string::ToString;
|
||||
use core::fmt::Display;
|
||||
use bitfields::bitflag;
|
||||
use super::{IDm, PMm};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Default)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[bitflag(u8)]
|
||||
pub enum PollingRequestCode {
|
||||
NoRequest = 0x00,
|
||||
@@ -11,6 +14,7 @@ pub enum PollingRequestCode {
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Default)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[bitflag(u8)]
|
||||
pub enum PollingTimeSlot {
|
||||
#[default]
|
||||
@@ -24,6 +28,7 @@ pub enum PollingTimeSlot {
|
||||
|
||||
/// Result of a successful FeliCa polling request.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct PollingResponse {
|
||||
/// The card's IDm (NFCID2).
|
||||
pub idm: IDm,
|
||||
@@ -31,4 +36,14 @@ pub struct PollingResponse {
|
||||
pub pmm: PMm,
|
||||
/// The card's system code, when returned.
|
||||
pub system_code_response: Option<u16>,
|
||||
}
|
||||
|
||||
impl Display for PollingResponse {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
|
||||
let system_code = match self.system_code_response {
|
||||
Some(system_code) => hex::encode_upper(system_code.to_be_bytes().to_vec()),
|
||||
None => "None".to_string()
|
||||
};
|
||||
write!(f, "IDm: {}, PMm, {}, System Code: {}", self.idm, self.pmm, system_code)
|
||||
}
|
||||
}
|
||||
@@ -214,11 +214,11 @@ where
|
||||
frame[idx] = (!sum).wrapping_add(1);
|
||||
frame[idx + 1] = PN532_POSTAMBLE;
|
||||
|
||||
crate::debug!("pn532: write cmd=0x{:02X} len={}", header[0], frame_len);
|
||||
crate::trace!("pn532: write cmd=0x{:02X} len={}", header[0], frame_len);
|
||||
self.i2c
|
||||
.write(PN532_I2C_ADDRESS, &frame[..frame_len])
|
||||
.map_err(Error::Transport)?;
|
||||
crate::debug!("pn532: write ACKed, reading ACK frame");
|
||||
crate::trace!("pn532: write ACKed, reading ACK frame");
|
||||
self.read_ack_frame()
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user