From 494f4b92eb1103ec1e785c2e712425d1b2564fd4 Mon Sep 17 00:00:00 2001 From: fromost Date: Fri, 28 Aug 2026 21:56:37 +0800 Subject: [PATCH] implement MAC auth for felica --- .cargo/config.toml | 6 - .gitignore | 2 + Cargo.toml | 1 + build.rs | 11 + pn532/Cargo.toml | 1 + pn532/src/commands.rs | 38 +-- pn532/src/driver.rs | 11 +- pn532/src/error.rs | 5 +- pn532/src/felica/error.rs | 12 + pn532/src/felica/felica_lite_s.rs | 226 ++++++++++++++++++ pn532/src/felica/mod.rs | 118 ++++----- pn532/src/felica/types/felica_lite_s/auth.rs | 44 ++++ pn532/src/felica/types/felica_lite_s/block.rs | 9 + pn532/src/felica/types/felica_lite_s/mod.rs | 77 ++++++ pn532/src/felica/types/ic_type.rs | 39 +++ pn532/src/felica/types/idm.rs | 6 +- pn532/src/felica/types/mod.rs | 107 +-------- pn532/src/lib.rs | 3 +- src/components/card_reader.rs | 76 ++++-- src/components/lora.rs | 3 - 20 files changed, 562 insertions(+), 233 deletions(-) create mode 100644 pn532/src/felica/error.rs create mode 100644 pn532/src/felica/felica_lite_s.rs create mode 100644 pn532/src/felica/types/felica_lite_s/auth.rs create mode 100644 pn532/src/felica/types/felica_lite_s/block.rs create mode 100644 pn532/src/felica/types/felica_lite_s/mod.rs create mode 100644 pn532/src/felica/types/ic_type.rs diff --git a/.cargo/config.toml b/.cargo/config.toml index 39e0735..0b9a9ef 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -5,12 +5,6 @@ linker = "/home/fromost/.rustup/toolchains/esp/xtensa-esp-elf/esp-15.2.0_2025092 [env] DEFMT_LOG = "info,lora=trace,eeprom24x=debug,pn532=trace" -DEV_EUI = "000000DD44F30FAE" -APP_KEY = "00000000000000000000000040000000" -APP_EUI = "0000000000000040" - -CARD_KEY = "00000000000000000000000000000000" - [build] rustflags = [ "-C", "link-arg=-nostartfiles", diff --git a/.gitignore b/.gitignore index 1a95b90..185d7c4 100644 --- a/.gitignore +++ b/.gitignore @@ -26,3 +26,5 @@ Cargo.lock # Ignore .DS_Store file in mac **/.DS_Store + +**/*.env \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index e9ec0c7..2b99cc2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -60,6 +60,7 @@ crossbeam = { version = "0.8.4", default-features = false, features = ["alloc"] [build-dependencies] patch-crate = "0.1.13" +dotenvy = "0.15.7" [package.metadata.patch] crates = ["lorawan-device"] diff --git a/build.rs b/build.rs index c54d3c7..a7f6f8b 100644 --- a/build.rs +++ b/build.rs @@ -2,6 +2,17 @@ fn main() { println!("cargo:rerun-if-changed=Cargo.toml"); patch_crate::run().expect("Failed while patching"); + let iter = dotenvy::from_path_iter(".env") + .expect("failed to read .env"); + + for item in iter { + let (key, value) = item.expect("invalid .env entry"); + + println!("cargo::rustc-env={key}={value}"); + } + + println!("cargo::rerun-if-changed=.env"); + linker_be_nice(); println!("cargo:rustc-link-arg=-Tdefmt.x"); // make sure linkall.x is the last linker script (otherwise might cause problems with flip-link) diff --git a/pn532/Cargo.toml b/pn532/Cargo.toml index b01c135..70ec41d 100644 --- a/pn532/Cargo.toml +++ b/pn532/Cargo.toml @@ -22,6 +22,7 @@ cbc = { version = "~0.2", default-features = false, features = ["block-padding"] heapless = "~0.9" thiserror = { version = "~2", default-features = false } bitfields = "3.1.0" +rand_core = "0.10.1" [features] default = [] diff --git a/pn532/src/commands.rs b/pn532/src/commands.rs index 5eb6642..8a0670e 100644 --- a/pn532/src/commands.rs +++ b/pn532/src/commands.rs @@ -107,41 +107,11 @@ pub const PN532_GPIO_P33: u8 = 3; pub const PN532_GPIO_P34: u8 = 4; pub const PN532_GPIO_P35: u8 = 5; -// FeliCa limits. -pub const fn felica_read_max_service_num() -> usize { - if cfg!(feature = "felica-lite-s") { - 8 - } else { - 16 - } -} -pub const fn felica_read_max_block_num() -> usize { - if cfg!(feature = "felica-lite-s") { - 4 - } else { - 12 - } -} -pub const fn felica_write_max_service_num() -> usize { - if cfg!(feature = "felica-lite-s") { - 4 - } else { - 16 - } -} -pub const fn felica_write_max_block_num() -> usize { - if cfg!(feature = "felica-lite-s") { - 2 - } else { - 10 - } -} +pub const FELICA_READ_MAX_SERVICE_NUM: usize = 3; +pub const FELICA_READ_MAX_BLOCK_NUM: usize = 3; -pub const FELICA_READ_MAX_SERVICE_NUM: usize = felica_read_max_service_num(); -pub const FELICA_READ_MAX_BLOCK_NUM: usize = felica_read_max_block_num(); - -pub const FELICA_WRITE_MAX_SERVICE_NUM: usize = felica_write_max_service_num(); -pub const FELICA_WRITE_MAX_BLOCK_NUM: usize = felica_write_max_block_num(); +pub const FELICA_WRITE_MAX_SERVICE_NUM: usize = 3; +pub const FELICA_WRITE_MAX_BLOCK_NUM: usize = 3; pub const FELICA_REQ_SERVICE_MAX_NODE_NUM: usize = 32; diff --git a/pn532/src/driver.rs b/pn532/src/driver.rs index db035c6..63c864b 100644 --- a/pn532/src/driver.rs +++ b/pn532/src/driver.rs @@ -15,15 +15,12 @@ pub struct Uid { impl core::fmt::Display for Uid { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { #[cfg(feature = "alloc")] - return write!(f, "{}", hex::encode(&self.bytes)); + { + write!(f, "{}", hex::encode(&self.bytes)) + } #[cfg(not(feature = "alloc"))] { - let mut buf: heapless::Vec = heapless::Vec::new(); - hex::encode_to_slice(&self.bytes, &mut buf) - .map_err(|_| core::fmt::Error)?; - let s = heapless::String::from_utf8(buf) - .map_err(|_| core::fmt::Error)?; - write!(f, "{}", s) + write!(f, "{:?}", self) } } } diff --git a/pn532/src/error.rs b/pn532/src/error.rs index 5753d66..bbbd764 100644 --- a/pn532/src/error.rs +++ b/pn532/src/error.rs @@ -1,6 +1,7 @@ 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)] @@ -29,8 +30,8 @@ pub enum Error { #[error("Invalid baud rate")] InvalidBaudRate, #[cfg(feature = "felica")] - #[error("Felica error")] - FelicaError + #[error(transparent)] + FelicaError(#[from] felica::error::Error), } #[cfg(feature = "defmt")] diff --git a/pn532/src/felica/error.rs b/pn532/src/felica/error.rs new file mode 100644 index 0000000..d4d4e96 --- /dev/null +++ b/pn532/src/felica/error.rs @@ -0,0 +1,12 @@ +#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)] +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)) + } +} \ No newline at end of file diff --git a/pn532/src/felica/felica_lite_s.rs b/pn532/src/felica/felica_lite_s.rs new file mode 100644 index 0000000..a9e89b2 --- /dev/null +++ b/pn532/src/felica/felica_lite_s.rs @@ -0,0 +1,226 @@ +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 Pn532 { + pub fn set_random_challenge(&mut self, card: &IDm, pmm: &PMm, rng: &mut impl Rng) + -> Result<([u8; 8], [u8; 8]), crate::Error> + { + 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> { + // 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::::try_from(des_key).unwrap(); + let cipher = TdesEde2::new(&key); + + // 2-key 3DES-CBC, IV = 0. + let mut state = Block::::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> + { + 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> { + 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::::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::::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::::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]) + } +} \ No newline at end of file diff --git a/pn532/src/felica/mod.rs b/pn532/src/felica/mod.rs index 51ee979..5f00e7a 100644 --- a/pn532/src/felica/mod.rs +++ b/pn532/src/felica/mod.rs @@ -1,14 +1,12 @@ -mod types; - -pub(crate) use types::AsBlock; -pub use types::Block as FelicaBlock; -pub use types::PollingRequestCode as FelicaPollingRequestCode; -pub use types::PollingResponse as FelicaPollingResponse; -pub use types::ServiceCode as FelicaServiceCode; +pub mod types; +#[cfg(feature="felica-lite-s")] +mod felica_lite_s; +pub mod error; +use error::Error as FelicaError; use crate::commands::*; use crate::driver::Pn532; -use crate::error::{Error, StatusCode}; +use crate::error::Error; use crate::interface::Interface; use crate::BaudRate; use core::time::Duration; @@ -20,9 +18,9 @@ impl Pn532 { &mut self, system_code: Option, baud_rate: BaudRate, - request_code: PollingRequestCode, + request_code: FelicaPollingRequestCode, timeout: Duration, - ) -> Result, Error> { + ) -> Result, Error> { if !matches!(baud_rate, BaudRate::Felica212kbps | BaudRate::Felica424kbps) { return Err(Error::InvalidBaudRate); } @@ -55,8 +53,9 @@ impl Pn532 { if response_length != 18 && response_length != 20 { return Err(Error::InvalidFrame); } - + let mut idm = [0u8; 8]; + idm.copy_from_slice(&self.buffer[4..12]); let idm = IDm::from(idm); @@ -70,7 +69,7 @@ impl Pn532 { None }; - Ok(Some(PollingResponse { + Ok(Some(FelicaPollingResponse { idm, pmm, system_code_response, @@ -108,49 +107,8 @@ impl Pn532 { Ok(response_len) } - /// Send a FeliCa "Request Service" command. - pub fn felica_request_service( - &mut self, - card: &IDm, - node_code_list: &[u16], - key_versions: &mut [u16], - ) -> Result<(), Error> { - let num_node = node_code_list.len(); - if num_node > FELICA_REQ_SERVICE_MAX_NODE_NUM || num_node > key_versions.len() { - return Err(Error::InvalidParam); - } - - let mut cmd = [0u8; 1 + 8 + 1 + 2 * FELICA_REQ_SERVICE_MAX_NODE_NUM]; - let mut j = 0; - cmd[j] = FELICA_CMD_REQUEST_SERVICE; - j += 1; - cmd[j..j + 8].copy_from_slice(&card.into_be_bytes()); - j += 8; - cmd[j] = num_node as u8; - j += 1; - for &code in node_code_list { - cmd[j] = code as u8; - cmd[j + 1] = (code >> 8) as u8; - j += 2; - } - - let mut response = [0u8; 10 + 2 * FELICA_REQ_SERVICE_MAX_NODE_NUM]; - let response_len = self.felica_send_command( - &cmd[..j], - &mut response, - Duration::from_millis(200) - )?; - if response_len != 10 + 2 * num_node { - return Err(Error::InvalidFrame); - } - for (i, kv) in key_versions.iter_mut().enumerate().take(num_node) { - *kv = u16::from_le_bytes([response[10 + i * 2], response[10 + i * 2 + 1]]); - } - Ok(()) - } - /// Send a FeliCa "Request Response" command, returning the card's mode. - pub fn felica_request_response(&mut self, card: &PollingResponse) -> Result> { + pub fn felica_request_response(&mut self, card: &FelicaPollingResponse) -> Result> { let mut cmd = [0u8; 9]; cmd[0] = FELICA_CMD_REQUEST_RESPONSE; cmd[1..9].copy_from_slice(&card.idm.into_be_bytes()); @@ -202,7 +160,7 @@ impl Pn532 { // command let mut cmd = heapless::Vec::::new(); cmd.push(FELICA_CMD_READ_WITHOUT_ENCRYPTION).unwrap(); - cmd.extend_from_slice(&card.into_be_bytes()).unwrap(); + cmd.extend_from_slice(&card.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(); @@ -224,11 +182,12 @@ impl Pn532 { &mut response, pmm.get_read_timeout(num_block, Some(Duration::from_millis(50))) )?; + crate::trace!("Read Response: {:02X}", response); if response_len != 12 + 16 * num_block { return Err(Error::InvalidFrame); } if response[9] != 0 || response[10] != 0 { - return Err(Error::Status(StatusCode::from_bits(response[9]))); + return Err(Error::FelicaError(FelicaError::Status(response[9], response[10]))); } let mut k = 12; @@ -275,7 +234,7 @@ impl Pn532 { // command let mut cmd = heapless::Vec::::new(); cmd.push(FELICA_CMD_WRITE_WITHOUT_ENCRYPTION).unwrap(); - cmd.extend_from_slice(&card.into_be_bytes()).unwrap(); + cmd.extend_from_slice(&card.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(); @@ -301,7 +260,7 @@ impl Pn532 { return Err(Error::InvalidFrame); } if response[9] != 0 || response[10] != 0 { - return Err(Error::Status(StatusCode::from_bits(response[9]))); + return Err(Error::FelicaError(FelicaError::Status(response[9], response[10]))); } Ok(()) } @@ -309,6 +268,7 @@ impl Pn532 { /// Send a FeliCa "Request System Code" command. /// /// Returns the number of system codes written to `system_code_list`. + #[cfg(not(feature = "felica-lite-s"))] pub fn felica_request_system_code( &mut self, card: &IDm, @@ -337,4 +297,46 @@ impl Pn532 { } Ok(num_system_code) } + + /// Send a FeliCa "Request Service" command. + #[cfg(not(feature = "felica-lite-s"))] + pub fn felica_request_service( + &mut self, + card: &IDm, + node_code_list: &[u16], + key_versions: &mut [u16], + ) -> Result<(), Error> { + let num_node = node_code_list.len(); + if num_node > FELICA_REQ_SERVICE_MAX_NODE_NUM || num_node > key_versions.len() { + return Err(Error::InvalidParam); + } + + let mut cmd = [0u8; 1 + 8 + 1 + 2 * FELICA_REQ_SERVICE_MAX_NODE_NUM]; + let mut j = 0; + cmd[j] = FELICA_CMD_REQUEST_SERVICE; + j += 1; + cmd[j..j + 8].copy_from_slice(&card.into_be_bytes()); + j += 8; + cmd[j] = num_node as u8; + j += 1; + for &code in node_code_list { + cmd[j] = code as u8; + cmd[j + 1] = (code >> 8) as u8; + j += 2; + } + + let mut response = [0u8; 10 + 2 * FELICA_REQ_SERVICE_MAX_NODE_NUM]; + let response_len = self.felica_send_command( + &cmd[..j], + &mut response, + Duration::from_millis(200) + )?; + if response_len != 10 + 2 * num_node { + return Err(Error::InvalidFrame); + } + for (i, kv) in key_versions.iter_mut().enumerate().take(num_node) { + *kv = u16::from_le_bytes([response[10 + i * 2], response[10 + i * 2 + 1]]); + } + Ok(()) + } } diff --git a/pn532/src/felica/types/felica_lite_s/auth.rs b/pn532/src/felica/types/felica_lite_s/auth.rs new file mode 100644 index 0000000..f6a14e2 --- /dev/null +++ b/pn532/src/felica/types/felica_lite_s/auth.rs @@ -0,0 +1,44 @@ +// 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>::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)) +// } +// } \ No newline at end of file diff --git a/pn532/src/felica/types/felica_lite_s/block.rs b/pn532/src/felica/types/felica_lite_s/block.rs new file mode 100644 index 0000000..49ef885 --- /dev/null +++ b/pn532/src/felica/types/felica_lite_s/block.rs @@ -0,0 +1,9 @@ +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], +} \ No newline at end of file diff --git a/pn532/src/felica/types/felica_lite_s/mod.rs b/pn532/src/felica/types/felica_lite_s/mod.rs new file mode 100644 index 0000000..4624ad7 --- /dev/null +++ b/pn532/src/felica/types/felica_lite_s/mod.rs @@ -0,0 +1,77 @@ +pub mod block; +pub mod auth; + +///! Types for Felica Lite-S (RC-S966) +///! User Manual: https://www.sony.net/Products/felica/business/tech-support/data/fls_usmnl_1.4e.pdf + +use bitfields::bitflag; +use super::AsBlock; +use super::AsServiceCode; + +/// Service Code +#[derive(Debug, PartialEq, Eq)] +#[bitflag(u16)] +pub enum ServiceCode { + #[base] + Unknown = 0, + /// FeliCa Lite read service code (read without key). + Read = 0x000B, + /// FeliCa Lite write service code (write without key). + Write = 0x0009 +} + +impl AsServiceCode for ServiceCode { + fn as_service_code(&self) -> u16 { + *self as u16 + } + + fn from_service_code(val: u16) -> Self { + Self::from_bits(val) + } +} + +/// FeliCa Lite system block numbers. +#[derive(Debug, PartialEq, Eq)] +#[bitflag(u16)] +pub enum Block { + #[base] + Unknown = 0, + PAD0 = 0x8000, + PAD1 = 0x8001, + PAD2 = 0x8002, + PAD3 = 0x8003, + PAD4 = 0x8004, + PAD5 = 0x8005, + PAD6 = 0x8006, + PAD7 = 0x8007, + PAD8 = 0x8008, + PAD9 = 0x8009, + PAD10 = 0x800A, + PAD11 = 0x800B, + PAD12 = 0x800C, + PAD13 = 0x800D, + /// Subtraction Register Block + REG = 0x800E, + /// Random challenge block. + RC = 0x8080, + /// MAC block. + MAC = 0x8081, + /// ID block. + ID = 0x8082, + /// Card key version block. + CKV = 0x8086, + /// Card key block. + CK= 0x8087, + /// Memory configuration block. + MemoryConfig = 0x8088, + /// Write counter block. + WriteCounter = 0x8090, + /// MAC_A block. + MACA = 0x8091 +} + +impl AsBlock for Block { + fn as_block(&self) -> u16 { + *self as u16 + } +} \ No newline at end of file diff --git a/pn532/src/felica/types/ic_type.rs b/pn532/src/felica/types/ic_type.rs new file mode 100644 index 0000000..5761450 --- /dev/null +++ b/pn532/src/felica/types/ic_type.rs @@ -0,0 +1,39 @@ +use bitfields::bitflag; + +/// https://www.sony.co.jp/en/Products/felica/business/tech-support/list.html +#[derive(Debug, PartialEq, Eq)] +#[bitflag(u8)] +pub enum CardICType { + #[base] + Unknown = 0x00, + S140 = 0x50, + SA40_2P = 0x51, + SA41_2C = 0x52, + SA21_2 = 0x46, + SA20_2 = 0x45, + SA20_1 = 0x44, + SA01_2 = 0x35, + SA00_1 = 0x32, + S962 = 0x20, + S960 = 0x0D, + S953 = 0x09, + S952 = 0x08, + S915 = 0x01, + S982 = 0xF1, + S978F = 0xF0, + S967LiteS = 0xF2, + S967Plug = 0xE1, + S967NFC = 0xFF, + S926 = 0xE0 +} + +/// https://www.sony.co.jp/en/Products/felica/business/tech-support/list.html +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum MobileICType { + /// 0x14~0x1F + V3(u8), + /// 0x10~0x13 + V2(u8), + /// 0x06~0x07 + V1(u8) +} \ No newline at end of file diff --git a/pn532/src/felica/types/idm.rs b/pn532/src/felica/types/idm.rs index e17bfa3..5c0e9fc 100644 --- a/pn532/src/felica/types/idm.rs +++ b/pn532/src/felica/types/idm.rs @@ -1,8 +1,8 @@ use bitfields::bitfield; -#[bitfield([u8; 8])] +#[bitfield([u8; 8], from_endian = big, into_endian = big)] #[derive(PartialEq, Eq)] pub struct IDm { - pub manufacturer: u8, - pub card_id: [u8; 7] + pub manufacturer: [u8; 2], + pub card_id: [u8; 6] } \ No newline at end of file diff --git a/pn532/src/felica/types/mod.rs b/pn532/src/felica/types/mod.rs index 2e08047..18dd6ea 100644 --- a/pn532/src/felica/types/mod.rs +++ b/pn532/src/felica/types/mod.rs @@ -1,35 +1,29 @@ mod polling; mod pmm; mod idm; +#[cfg(feature = "felica-lite-s")] +pub mod felica_lite_s; +pub mod ic_type; -use bitfields::bitflag; pub use idm::*; pub use pmm::*; -pub use polling::*; +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}; pub trait AsServiceCode { fn as_service_code(&self) -> u16; fn from_service_code(val: u16) -> Self; } -#[derive(Debug, PartialEq, Eq)] -#[bitflag(u16)] -pub enum ServiceCode { - #[base] - Unknown = 0, - /// FeliCa Lite read service code (read without key). - Read = 0x000B, - /// FeliCa Lite write service code (write without key). - Write = 0x0009 +pub trait AsBlock { + fn as_block(&self) -> u16; } -impl AsServiceCode for ServiceCode { - fn as_service_code(&self) -> u16 { - *self as u16 - } - - fn from_service_code(val: u16) -> Self { - Self::from_bits(val) +impl AsBlock for u16 { + fn as_block(&self) -> u16 { + *self } } @@ -41,81 +35,4 @@ impl AsServiceCode for u16 { fn from_service_code(val: u16) -> Self { val } -} - -pub trait AsBlock { - fn as_block(&self) -> u16; -} - -/// FeliCa Lite system block numbers. -#[derive(Debug, PartialEq, Eq)] -#[bitflag(u16)] -pub enum Block { - #[base] - Unknown = 0, - /// Random challenge block. - RC = 0x8080, - /// MAC block. - MAC = 0x8081, - /// ID block. - ID = 0x8082, - /// Card key version block. - CKV = 0x8086, - /// Card key block. - CK= 0x8087, - /// Memory configuration block. - MemoryConfig = 0x8088, - /// Write counter block. - WriteCounter = 0x8090, - /// MAC_A block. - MACA = 0x8091 -} - -impl AsBlock for Block { - fn as_block(&self) -> u16 { - *self as u16 - } -} - -impl AsBlock for u16 { - fn as_block(&self) -> u16 { - // A 2-byte block list element is `0x80 | block_number`: the MSB marks a - // 2-byte element (access mode 0 = standard area, service list order 0). - *self | 0x8000 - } -} - - -/// https://www.sony.co.jp/en/Products/felica/business/tech-support/list.html -#[derive(Debug, PartialEq, Eq)] -#[bitflag(u8)] -pub enum CardICType { - #[base] - Unknown = 0x00, - S140 = 0x50, - SA40_2P = 0x51, - SA41_2C = 0x52, - SA21_2 = 0x46, - SA20_2 = 0x45, - SA20_1 = 0x44, - SA01_2 = 0x35, - SA00_1 = 0x32, - S962 = 0x20, - S960 = 0x0D, - S953 = 0x09, - S952 = 0x08, - S915 = 0x01, - S982 = 0xF1, - S978F = 0xF0, - S967LiteS = 0xF2, - S967Plug = 0xE1, - S967NFC = 0xFF, - S926 = 0xE0 -} - -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub enum MobileICType { - V3(u8), // 0x14~0x1F - V2(u8), // 0x10~0x13 - V1(u8) // 0x06~0x07 } \ No newline at end of file diff --git a/pn532/src/lib.rs b/pn532/src/lib.rs index bfc1197..390fbbb 100644 --- a/pn532/src/lib.rs +++ b/pn532/src/lib.rs @@ -63,7 +63,8 @@ pub use error::*; pub use interface::*; #[cfg(feature = "defmt")] -use defmt::debug; +#[allow(unused_imports)] +use defmt::{error, warn, info, debug, trace}; #[cfg(not(feature = "defmt"))] #[macro_export] diff --git a/src/components/card_reader.rs b/src/components/card_reader.rs index 9f104b2..d2066b5 100644 --- a/src/components/card_reader.rs +++ b/src/components/card_reader.rs @@ -1,22 +1,20 @@ use crate::types::{LoraPublisher, SharedI2c}; -use core::sync::atomic::{AtomicPtr, Ordering}; use defmt::Debug2Format; use embassy_sync::once_lock::OnceLock; use embassy_time::{Delay, Timer}; use esp_hal::gpio; use esp_hal::rng::Trng; -use lorawan_device::RngCore; -use pn532::felica::{FelicaBlock, FelicaPollingRequestCode, FelicaServiceCode}; +use pn532::felica::types::{FelicaPollingRequestCode}; use pn532::{BaudRate, Pn532}; -static CARD_KEY: OnceLock> = OnceLock::new(); +static CARD_KEY: OnceLock<[u8; 16]> = OnceLock::new(); +const CARD_KEY_STR: &str = env!("CARD_KEY"); -fn get_card_key() -> &'static heapless::Vec { +fn get_card_key() -> &'static [u8; 16] { CARD_KEY.get_or_init(|| { - let hex_string = env!("CARD_KEY"); let mut buffer = [0u8; 16]; - hex::decode_to_slice(hex_string, &mut buffer).unwrap(); - heapless::Vec::from_array(buffer) + hex::decode_to_slice(CARD_KEY_STR, &mut buffer).unwrap(); + buffer }) } @@ -41,19 +39,17 @@ impl<'a> CardReader<'a> { #[embassy_executor::task] pub async fn start_card_reader( mut card_reader: CardReader<'static>, - mut publisher: LoraPublisher<'static>, + _publisher: LoraPublisher<'static>, mut trng: Trng ) -> () { let card_key = get_card_key(); - let mut rc = [0u8; 16]; - trng.fill_bytes(&mut rc); defmt::trace!("card_key: {}", hex::encode_upper(card_key)); loop { Timer::after_millis(50).await; let Some(response) = (match card_reader.driver.felica_polling( None, - BaudRate::Felica424kbps, + BaudRate::Felica212kbps, FelicaPollingRequestCode::SystemCode, core::time::Duration::from_secs(1), ) { @@ -67,25 +63,57 @@ pub async fn start_card_reader( continue; }; defmt::info!("Polling card: {}", Debug2Format(&response)); + + // if let Err(e) = card_reader.driver.felica_write_without_encryption( + // &response.idm, &response.pmm, + // &[FelicaLiteSServiceCode::Write], + // &[FelicaLiteSBlock::CK], + // &[*card_key] + // ) { + // defmt::error!("card_reader write ck error: {}", e); + // continue; + // } - let mut data = [[0u8; 16]; 3]; - // user blocks 0x0000..0x000D via the raw u16 AsBlock impl; a single read - // is capped at 3 blocks by the 64-byte packet buffer (response = 14 + 16n). - let blocks: [u16; 3] = [0x0000, 0x0001, 0x0002]; - match card_reader.driver.felica_read_without_encryption( + let (rc1, rc2) = match card_reader.driver.set_random_challenge( &response.idm, &response.pmm, - &[FelicaServiceCode::Read], - &blocks, - &mut data, + &mut trng ) { - Ok(_) => {}, + Ok(rc) => rc, Err(e) => { - defmt::error!("unable to read blocks: {}", e); + defmt::error!("card_reader rc error: {}", e); continue; } - } - defmt::debug!("Blocks: {:?}", data); + }; + defmt::info!("RC: rc1: {:02X}, rc2: {:02X}", rc1, rc2); + + let mut rc = [0u8; 16]; + rc[..8].copy_from_slice(&rc1); + rc[8..].copy_from_slice(&rc2); + + let (sk1, sk2) = match card_reader.driver.get_session_keys(&rc, card_key) { + Ok(v) => v, + Err(e) => { + defmt::error!("card_reader sk error: {}", e); + continue; + } + }; + defmt::info!("sk1: {:02X} sk2: {:02X}", sk1, sk2); + + let mac = match card_reader.driver.check_mac( + &response.idm, + &response.pmm, + &sk1, + &sk2, + &rc1 + ) { + Ok(v) => v, + Err(e) => { + defmt::error!("card_driver mac check error: {}", e); + continue; + } + }; + defmt::info!("mac check: {}", mac); match card_reader.driver.release() { Ok(_) => {}, diff --git a/src/components/lora.rs b/src/components/lora.rs index 185d6da..09d52c1 100644 --- a/src/components/lora.rs +++ b/src/components/lora.rs @@ -3,10 +3,7 @@ use crate::types::{LoraPayload, LoraPublisher, LoraSubscriber, SharedAsyncSpi}; use alloc::vec::Vec; use chrono::{DateTime, NaiveDateTime, Utc}; use core::str::FromStr; -use core::sync::atomic::{AtomicPtr, Ordering}; use defmt::Formatter; -use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; -use embassy_sync::pubsub::{Publisher, Subscriber}; use embassy_time::{Delay, Duration, Instant, Timer}; use esp_hal::gpio; use esp_hal::rng::Rng;