1#![deny(warnings)]
2
3use std::ffi::{CStr, CString};
4use std::marker::PhantomData;
5use std::mem::MaybeUninit;
6use std::ptr;
7
8use apdu_core::{Command, Response};
9use openssl::{encrypt::Decrypter, hash::MessageDigest, pkey::PKey, sign::Signer};
10use rand::Rng;
11use std::error::Error;
12use std::fmt::{self, Display, Formatter, Write};
13
14pub mod ffi;
15use crate::ffi::{BaudRate, Modulation, ModulationType, NfcProperty};
16
17pub struct Nfc {
18 context: *mut ffi::context_t,
19}
20
21#[derive(Debug)]
22pub enum NfcError {
23 Unknown,
24 NonceMismatch,
26 NoResponse,
27 }
29
30impl Display for NfcError {
31 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
32 use NfcError::*;
33 match self {
34 Unknown => write!(f, "Unknown error"),
35 NonceMismatch => write!(f, "Nonce for mobile tag didn't match"),
36 NoResponse => write!(f, "Didn't get a response from the mobile tag"),
37 }
38 }
39}
40
41impl Error for NfcError {}
42
43impl Nfc {
44 pub fn new() -> Option<Self> {
45 let mut context_uninit = MaybeUninit::<*mut ffi::context_t>::uninit();
46 let context = unsafe {
47 ffi::nfc_init(context_uninit.as_mut_ptr());
48 if context_uninit.as_mut_ptr().is_null() {
49 return None;
50 }
51 context_uninit.assume_init()
52 };
53
54 Some(Nfc { context })
55 }
56
57 pub fn gatekeeper_device(&mut self, conn_str: String) -> Option<NfcDevice> {
58 self.inner_device(conn_str.clone())
59 .map(|device| NfcDevice { device, conn_str })
60 }
61 fn inner_device(&self, conn_str: String) -> Option<NfcDeviceInner> {
62 let device_string = CString::new(conn_str).unwrap();
63 let device = unsafe {
64 let device_ptr = ffi::nfc_open(self.context, device_string.as_ptr());
65 if device_ptr.is_null() {
66 return None;
67 }
68 device_ptr
69 };
70 Some(NfcDeviceInner {
71 device,
72 _context: self,
73 })
74 }
75}
76
77impl Drop for Nfc {
78 fn drop(&mut self) {
79 unsafe {
80 ffi::nfc_exit(self.context);
81 }
82 }
83}
84
85struct NfcDeviceInner<'a> {
86 device: *mut ffi::device_t,
87 _context: &'a Nfc,
88}
89
90pub struct NfcDevice<'a> {
91 device: NfcDeviceInner<'a>,
92 conn_str: String,
93}
94
95const NONCE_LENGTH: usize = 8;
96const RESPONSE_PADDING_LENGTH: usize = 2;
97
98pub struct MobileNfcTag {
99 nonce: [u8; NONCE_LENGTH],
100 _target_guard: NfcTargetGuard,
101}
102
103struct NfcTargetGuard {
104 device: *mut ffi::device_t,
105}
106
107impl Drop for NfcTargetGuard {
108 fn drop(&mut self) {
109 unsafe {
110 let ecode = ffi::nfc_initiator_deselect_target(self.device);
111 if ecode != 0 {
112 eprintln!("Couldn't deslect target!! {}", ecode);
113 let msg = CString::new("Deselect target :(").unwrap();
114 ffi::nfc_perror(self.device, msg.as_ptr());
115 }
116 }
117 }
118}
119
120impl<'b> NfcDevice<'b> {
121 pub fn authenticate_tag(&mut self, realm: &mut Realm) -> Result<Option<String>, NfcError> {
122 if let Some(mut tag) = self.first_mobile_tag(realm) {
123 tag.authenticate(self, realm).map(Some)
124 } else if let Some(mut tag) = self.first_tag() {
125 tag.authenticate(self, realm).map(Some)
126 } else {
127 Ok(None)
128 }
129 }
130 pub fn first_tag(&self) -> Option<FreefareNfcTag> {
131 let (tags, tag) = unsafe {
132 let tags = ffi::freefare_get_tags(self.device.device);
133 if tags.is_null() {
134 return None;
135 }
136
137 let tag = *tags;
138 if tag.is_null() {
139 return None;
140 }
141 (tags, tag)
142 };
143
144 Some(FreefareNfcTag {
145 tags,
146 tag,
147 _device_lifetime: PhantomData,
148 })
149 }
150 pub fn first_mobile_tag(&mut self, realm: &Realm) -> Option<MobileNfcTag> {
151 loop {
152 if unsafe { ffi::nfc_initiator_init(self.device.device) } < 0 {
153 eprintln!("Couldn't init NFC initiator!!!");
154 unsafe {
156 let msg = CString::new("Failed to init device initiator :(").unwrap();
157 ffi::nfc_perror(self.device.device, msg.as_ptr())
158 };
159 eprintln!("Resetting the device and trying again!");
160 self.device = self.device._context.inner_device(self.conn_str.clone())?;
161 continue;
162 }
163 break;
164 }
165
166 unsafe {
167 ffi::nfc_device_set_property_bool(self.device.device, NfcProperty::NP_ACTIVATE_FIELD, 1)
168 };
169 unsafe {
170 ffi::nfc_device_set_property_bool(
171 self.device.device,
172 NfcProperty::NP_INFINITE_SELECT,
173 0,
174 )
175 };
176
177 unsafe {
178 let mut nt = MaybeUninit::uninit();
179 if ffi::nfc_initiator_select_passive_target(
180 self.device.device,
181 Modulation {
182 nmt: ModulationType::NMT_ISO14443A,
183 nbr: BaudRate::NBR_106,
184 },
185 std::ptr::null(),
186 0,
187 nt.as_mut_ptr(),
188 ) <= 0
189 {
190 return None;
192 }
193 }
194 let guard = NfcTargetGuard {
195 device: self.device.device,
196 };
197
198 let response = self
199 .send(Command::new_with_payload_le(
200 0x00,
201 0xA4,
202 0x04,
203 0x00,
204 (NONCE_LENGTH + RESPONSE_PADDING_LENGTH) as u16,
205 vec![0xf0, 0x63, 0x73, 0x68, 0x72, 0x69, 0x74 + realm.slot],
206 ))
207 .ok()?;
208 if let Some(response) = response {
209 if response.payload.len() != NONCE_LENGTH {
210 return None;
211 }
212 let nonce = &response.payload[0..NONCE_LENGTH];
213 let mut nonce_arr: [u8; NONCE_LENGTH] = Default::default();
214 nonce_arr.copy_from_slice(nonce);
215 Some(MobileNfcTag {
216 nonce: nonce_arr,
217 _target_guard: guard,
218 })
219 } else {
220 None
221 }
222 }
223 pub fn send(&self, command: Command) -> Result<Option<Response>, NfcError> {
224 let mut response = vec![0u8; command.le.unwrap_or(0).into()];
225 let command: Vec<u8> = command.into();
226 let response_size = unsafe {
227 ffi::nfc_initiator_transceive_bytes(
228 self.device.device,
229 command.as_ptr(),
230 command.len(),
231 response.as_mut_ptr(),
232 response.len(),
233 2000,
234 )
235 };
236 if response_size < 0 {
237 return Err(NfcError::Unknown);
238 }
239 if response_size == 0 {
240 return Ok(None);
241 }
242 response.truncate(response_size as usize);
244
245 Ok(Some(Response::from(response)))
246 }
247}
248
249fn sign_message(realm: &Realm, message: &[u8]) -> Result<Vec<u8>, NfcError> {
250 let pkey = PKey::private_key_from_pem(
252 CString::new(realm.signing_private_key.clone())
253 .unwrap()
254 .as_bytes(),
255 )
256 .unwrap();
257 let mut signer = Signer::new(MessageDigest::sha384(), &pkey).unwrap();
258 signer.update(message).unwrap();
259 Ok(signer.sign_to_vec().unwrap())
260}
261
262fn decrypt_message(realm: &Realm, message: &[u8]) -> Result<Vec<u8>, NfcError> {
263 let pkey = PKey::private_key_from_pem(
264 CString::new(realm.asymmetric_private_key.clone())
265 .unwrap()
266 .as_bytes(),
267 )
268 .unwrap();
269 let decrypter = Decrypter::new(pkey.as_ref()).unwrap();
270 let message_len = decrypter.decrypt_len(message).unwrap();
271 let mut output = vec![0; message_len];
272 let length = decrypter.decrypt(message, output.as_mut_slice()).unwrap();
273 Ok(output[..length].into())
274}
275
276impl NfcTag for MobileNfcTag {
277 fn authenticate(
278 &mut self,
279 nfc_device: &NfcDevice<'_>,
280 realm: &mut Realm,
281 ) -> Result<String, NfcError> {
282 println!("Authenticating! Sending signed nonce");
283 println!("{} is the realm id", realm.slot);
284 let our_nonce = rand::thread_rng().gen::<[u8; NONCE_LENGTH]>();
286 let mut signature_data = [0u8; NONCE_LENGTH * 2];
287 signature_data[0..NONCE_LENGTH].copy_from_slice(&self.nonce);
288 signature_data[NONCE_LENGTH..].copy_from_slice(&our_nonce);
289 let mut signature = sign_message(realm, &signature_data)?;
290 signature.extend_from_slice(&our_nonce);
292
293 let encrypted_association = nfc_device
294 .send(Command::new_with_payload_le(
295 0xD0, 0x00, 0x00, 0x00, 512, signature,
297 ))?
298 .ok_or(NfcError::NoResponse)?;
299 let payload = encrypted_association.payload.as_slice();
300 let payload = decrypt_message(realm, payload)?;
301 let nonce = &payload[payload.len() - self.nonce.len()..payload.len()];
303 let association_id = &payload[0..payload.len() - self.nonce.len()];
305
306 if our_nonce != nonce {
307 return Err(NfcError::NonceMismatch);
308 }
309
310 Ok(association_id
311 .iter()
312 .fold(String::new(), |mut collector, id| {
313 write!(collector, "{:02x}", id).unwrap();
314 collector
315 }))
316 }
317}
318
319impl Drop for NfcDeviceInner<'_> {
320 fn drop(&mut self) {
321 unsafe {
322 ffi::nfc_close(self.device);
323 }
324 }
325}
326
327pub struct FreefareNfcTag<'a> {
328 tags: *mut *mut ffi::mifare_t,
329 tag: *mut ffi::mifare_t,
330 _device_lifetime: std::marker::PhantomData<&'a ()>,
331}
332
333pub trait NfcTag {
334 fn authenticate(
335 &mut self,
336 nfc_device: &NfcDevice<'_>,
337 realm: &mut Realm,
338 ) -> Result<String, NfcError>;
339}
340
341impl NfcTag for FreefareNfcTag<'_> {
342 fn authenticate(
344 &mut self,
345 _nfc_device: &NfcDevice<'_>,
346 realm: &mut Realm,
347 ) -> Result<String, NfcError> {
348 let mut association_id = [0u8; 37];
349 let auth_result =
350 unsafe { ffi::authenticate_tag(self.tag, realm.realm, association_id.as_mut_ptr()) };
351 if auth_result == 0 {
352 return Err(NfcError::Unknown);
353 }
354
355 let mut association_id = association_id.to_vec();
356 association_id.pop();
358
359 Ok(String::from_utf8(association_id).unwrap())
360 }
361}
362
363impl FreefareNfcTag<'_> {
364 pub fn get_uid(&mut self) -> Option<String> {
365 unsafe {
366 let tag_uid = ffi::freefare_get_tag_uid(self.tag);
367 if tag_uid.is_null() {
368 return None;
369 }
370 let tag_uid_string = CString::from_raw(tag_uid);
371 Some(tag_uid_string.to_string_lossy().to_string())
372 }
373 }
374
375 pub fn get_friendly_name(&mut self) -> Option<&str> {
376 unsafe {
377 let tag_name = ffi::freefare_get_tag_friendly_name(self.tag);
378 let tag_name_string = CStr::from_ptr(tag_name);
379 tag_name_string.to_str().ok()
380 }
381 }
382
383 pub fn format(
384 &mut self,
385 uid: Option<&str>,
386 system_secret: Option<&str>,
387 ) -> Result<(), NfcError> {
388 let uid_opt = match uid {
389 Some(uid) => CString::new(uid).ok(),
390 None => None,
391 };
392 let uid = match &uid_opt {
393 Some(uid) => uid.as_ptr(),
394 None => ptr::null(),
395 };
396 let system_secret_opt = match system_secret {
397 Some(system_secret) => CString::new(system_secret).ok(),
398 None => None,
399 };
400 let system_secret = match &system_secret_opt {
401 Some(system_secret) => system_secret.as_ptr(),
402 None => ptr::null(),
403 };
404
405 unsafe {
406 let format_result = ffi::format_tag(self.tag, uid, system_secret);
407 if format_result != 0 {
408 return Err(NfcError::Unknown);
409 }
410 Ok(())
411 }
412 }
413
414 pub fn issue(
415 &mut self,
416 system_secret: &str,
417 uid: Option<&str>,
418 in_realms: Vec<&mut Realm>,
419 ) -> Result<(), NfcError> {
420 let system_secret = CString::new(system_secret).unwrap();
421 let uid_opt = match uid {
422 Some(uid) => CString::new(uid).ok(),
423 None => None,
424 };
425 let uid = match &uid_opt {
426 Some(uid) => uid.as_ptr(),
427 None => ptr::null(),
428 };
429
430 let mut realms: Vec<*mut ffi::realm_t> = Vec::with_capacity(in_realms.len());
431 for realm in in_realms {
432 realms.push(realm.realm);
433 }
434 unsafe {
435 let issue_result = ffi::issue_tag(
436 self.tag,
437 system_secret.as_ptr(),
438 uid,
439 realms.as_mut_ptr(),
440 realms.len(),
441 );
442 if issue_result != 0 {
443 return Err(NfcError::Unknown);
444 }
445 Ok(())
446 }
447 }
448}
449
450impl Drop for FreefareNfcTag<'_> {
451 fn drop(&mut self) {
452 unsafe {
453 ffi::freefare_free_tags(self.tags);
454 }
455 }
456}
457
458pub struct Realm {
459 realm: *mut ffi::realm_t,
460 slot: u8,
461 signing_private_key: String,
462 asymmetric_private_key: String,
463}
464
465impl Realm {
469 #[allow(clippy::too_many_arguments)]
470 pub fn new(
471 slot: u8,
472 name: &str,
473 association: &str,
474 auth_key: &str,
475 read_key: &str,
476 update_key: &str,
477 public_key: &str,
478 private_key: &str,
479 mobile_private_key: &str,
480 asymmetric_private_key: &str,
481 ) -> Option<Realm> {
482 let ffi_name = CString::new(name).ok()?;
483 let ffi_association = CString::new(association).ok()?;
484 let ffi_auth_key = CString::new(auth_key).ok()?;
485 let ffi_read_key = CString::new(read_key).ok()?;
486 let ffi_update_key = CString::new(update_key).ok()?;
487 let ffi_public_key = CString::new(public_key).ok()?;
488 let ffi_private_key = CString::new(private_key).ok()?;
489
490 let realm = unsafe {
491 ffi::realm_create(
492 slot,
493 ffi_name.as_ptr(),
494 ffi_association.as_ptr(),
495 ffi_auth_key.as_ptr(),
496 ffi_read_key.as_ptr(),
497 ffi_update_key.as_ptr(),
498 ffi_public_key.as_ptr(),
499 ffi_private_key.as_ptr(),
500 )
501 };
502
503 Some(Realm {
504 realm,
505 slot,
506 signing_private_key: mobile_private_key.to_string(),
507 asymmetric_private_key: asymmetric_private_key.to_string(),
508 })
509 }
510}
511
512impl Drop for Realm {
513 fn drop(&mut self) {
514 unsafe {
515 ffi::realm_free(self.realm);
516 }
517 }
518}