use nfc_sys::{
nfc_close, nfc_exit, nfc_init, nfc_initiator_init, nfc_open, nfc_perror,
};
use openssl::error::ErrorStack;
use openssl::pkey::{PKey, Private, Public};
use std::ffi::{c_char, CString};
use std::fmt::{self, Display, Formatter};
use std::marker::PhantomData;
use std::mem::MaybeUninit;
use std::time::Duration;
mod desfire;
mod mobile;
use crate::desfire::*;
use crate::mobile::*;
impl Display for NfcError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
Self::NonceMismatch => write!(f, "None mismatch"),
Self::NoResponse => {
write!(f, "Did not receive a response. Is the tag too far away?")
}
Self::CryptoError(err) => write!(f, "Cryptography error: {err}"),
Self::ConnectFailed => write!(f, "Failed to connect"),
Self::CommunicationError => {
write!(f, "Communication failed. Is the tag too far away?")
}
Self::InvalidSignature => {
write!(f, "Association had an invalid signature!")
}
Self::BadAssociation => write!(f, "Association ID is not valid UTF-8"),
}
}
}
impl std::error::Error for NfcError {}
#[derive(Debug)]
pub enum NfcError {
NonceMismatch,
NoResponse,
CryptoError(ErrorStack),
ConnectFailed,
CommunicationError,
InvalidSignature,
BadAssociation,
}
#[derive(Debug)]
pub(crate) struct DeviceWrapper<'device> {
_context: NfcContext<'device>,
device: *mut nfc_sys::nfc_device,
}
impl<'device> Drop for DeviceWrapper<'device> {
fn drop(&mut self) {
unsafe { nfc_close(self.device) };
}
}
#[derive(Debug)]
struct NfcContext<'context> {
context: *mut nfc_sys::nfc_context,
_lifetime: PhantomData<&'context ()>,
}
impl Drop for NfcContext<'_> {
fn drop(&mut self) {
unsafe { nfc_exit(self.context) };
}
}
impl<'a> Default for NfcContext<'a> {
fn default() -> Self {
let mut context_uninit = MaybeUninit::<*mut nfc_sys::nfc_context>::uninit();
Self {
context: unsafe {
nfc_init(context_uninit.as_mut_ptr());
if context_uninit.as_mut_ptr().is_null() {
panic!("Malloc failed");
}
context_uninit.assume_init()
},
_lifetime: PhantomData,
}
}
}
#[derive(Debug, Clone)]
pub struct RealmWriteKeys<'a> {
update_key: &'a [u8],
desfire_signing_private_key: PKey<Private>,
}
#[derive(Debug, Clone)]
pub struct Realm<'a> {
slot: RealmType,
auth_key: Vec<u8>,
read_key: Vec<u8>,
desfire_signing_public_key: PKey<Public>,
mobile_decryption_private_key: PKey<Private>,
mobile_signing_private_key: PKey<Private>,
secrets: Option<RealmWriteKeys<'a>>,
}
impl<'a> Realm<'a> {
pub fn new(
slot: RealmType,
auth_key: Vec<u8>,
read_key: Vec<u8>,
desfire_signing_public_key: &[u8],
mobile_decryption_private_key: &[u8],
mobile_signing_private_key: &[u8],
secrets: Option<RealmWriteKeys<'a>>,
) -> Self {
Self {
slot,
auth_key,
read_key,
desfire_signing_public_key: PKey::public_key_from_pem(
desfire_signing_public_key,
)
.expect("Bad key format"),
mobile_decryption_private_key: PKey::private_key_from_pem(
mobile_decryption_private_key,
)
.expect("Bad key format"),
mobile_signing_private_key: PKey::private_key_from_pem(
mobile_signing_private_key,
)
.expect("Bad key format"),
secrets,
}
}
}
#[repr(u8)]
#[derive(Debug, Clone)]
pub enum RealmType {
Door = 0,
Drink = 1,
MemberProjects = 2,
}
impl Display for UndifferentiatedTag<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
Self::Desfire(_) => write!(f, "Desfire"),
Self::Mobile(_) => write!(f, "Mobile"),
}
}
}
impl Display for RealmType {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
Self::Door => write!(f, "Door"),
Self::Drink => write!(f, "Drink"),
Self::MemberProjects => write!(f, "Member Projects"),
}
}
}
impl Copy for RealmType {}
pub struct GatekeeperReader<'device> {
device_wrapper: Option<DeviceWrapper<'device>>,
connection_string: String,
realm: Realm<'device>,
}
impl<'device> DeviceWrapper<'device> {
fn new(connection_string: String) -> Option<Self> {
let context = NfcContext::default();
let device_string = CString::new(connection_string).unwrap();
let device_string = device_string.into_bytes_with_nul();
let mut device_string_bytes: [c_char; 1024] = [0; 1024];
for (index, character) in device_string.into_iter().enumerate() {
device_string_bytes[index] = character as c_char;
}
let device_ptr = unsafe {
let device_ptr = nfc_open(context.context, &device_string_bytes);
if device_ptr.is_null() {
log::error!("Failed to open NFC device...");
return None;
}
log::debug!("Opened an NFC device!");
device_ptr
};
Some(Self {
device: device_ptr,
_context: context,
})
}
}
#[derive(Debug)]
pub enum UndifferentiatedTag<'a> {
Desfire(DesfireNfcTag<'a>),
Mobile(MobileNfcTag<'a>),
}
pub trait NfcTag {
fn authenticate(&self) -> Result<String, NfcError>;
}
impl NfcTag for UndifferentiatedTag<'_> {
fn authenticate(&self) -> Result<String, NfcError> {
match self {
Self::Desfire(desfire) => desfire.authenticate(),
Self::Mobile(mobile) => mobile.authenticate(),
}
}
}
#[must_use]
enum ReaderStatus {
Available,
Unavailable,
}
impl<'device> GatekeeperReader<'device> {
pub fn new(connection_string: String, realm: Realm<'device>) -> Option<Self> {
let device_wrapper = Some(DeviceWrapper::new(connection_string.clone())?);
Some(Self {
device_wrapper,
connection_string,
realm,
})
}
fn ensure_reader_available(&mut self) -> ReaderStatus {
for _ in 0..3 {
if self.device_wrapper.as_ref().map(|device_wrapper| unsafe {
nfc_initiator_init(device_wrapper.device)
} < 0).unwrap_or(true)
{
log::error!("Couldn't init NFC initiator!!!");
if let Some(device_wrapper) = &self.device_wrapper {
unsafe {
let msg = CString::new("Failed to init device initiator :(").unwrap();
nfc_perror(device_wrapper.device, msg.as_ptr())
};
}
log::error!("Resetting the device and trying again!");
std::thread::sleep(Duration::from_millis(500));
self.device_wrapper = None;
if let Some(device_wrapper) =
DeviceWrapper::new(self.connection_string.clone())
{
self.device_wrapper = Some(device_wrapper);
}
} else {
return ReaderStatus::Available;
}
}
ReaderStatus::Unavailable
}
pub fn get_nearby_tags(&mut self) -> Vec<UndifferentiatedTag> {
if let ReaderStatus::Unavailable = self.ensure_reader_available() {
return vec![];
}
if let Some(tag) = self.find_first_mobile_tag() {
return vec![UndifferentiatedTag::Mobile(tag)];
}
self.find_desfire_tags()
}
}