mod card; use std::ffi::CString; use std::ops::Deref; use clap::Parser; use desfire::Card; use desfire::crypto::cipher_key::CipherKey; use desfire::crypto::cipher_type::CipherType; use desfire::desfire::{ChangeApplicationKey, ChangeMasterKey, ChangeMasterKeySettings, CreateDeleteFile, CryptoOperationsType, Desfire, FileAccessRights, FileCommunication, FileDirectoryAccess, FileIdentifiers}; use desfire::desfire::desfire::{generate_file_access_rights, generate_keysetting1, generate_keysetting2, MAX_BYTES_PER_TRANSACTION}; use pcsc::{Context, Scope}; use uriparse::{Authority, Path, Scheme, Segment, UnregisteredScheme, URI}; use urn::UrnBuilder; use uuid::Uuid; use crate::card::PCSCCard; /// Simple program to greet a person #[derive(Parser, Debug)] #[clap(author, version, about, long_about = None)] struct Args { /// Application id to use #[clap(long = "id", default_value = "1")] app_id: u32, /// Masterkey for the PICC #[clap(long)] picc_masterkey: Option, /// Masterkey for the Application #[clap(long)] app_masterkey: Option, /// user authentication key #[clap(long)] app_authkey: Option, /// Magic string to identify cards #[clap(short, long, default_value = "FABACCESS\0DESFIRE\01.0\0")] magic: String, /// Name of the issuing space #[clap(short, long, required_unless_present = "format")] space: Option, /// BFFHd Instance for the space #[clap(short, long, required_unless_present = "format")] instance: Option, /// Contact option for lost cards #[clap(short, long, required_unless_present = "format")] contact: Option, /// User token, currently this should be set to the Username (will be generated for you if not given) #[clap(short, long)] token: Option, /// Whether to format the card #[clap(short, long)] format: bool, } fn main() -> Result<(), Box> { let args = Args::parse(); // connect to the card let mut card = PCSCCard::new()?; card.connect()?; let mut desfire = Desfire { card: Some(Box::new(card)), cbc_iv: None, session_key: None }; // store the provided keys or generate new ones let master_key = match args.picc_masterkey { Some(key) => CipherKey::new_from_str(&key, CipherType::TDES, 0x00)?, None => CipherKey::new_empty(CipherType::TDES)?, }; let app_key = match args.app_masterkey { Some(key) => CipherKey::new_from_str(&key, CipherType::AES, 0x10)?, None => CipherKey::new_empty(CipherType::AES)?, }; let user_key = match args.app_authkey { Some(key) => CipherKey::new_from_str(&key, CipherType::AES, 0x10)?, None => CipherKey::new(&rand::random::<[u8; 16]>(), CipherType::AES, 0x10)?, }; // format the card if requested if args.format { desfire.select_application(0x000000); desfire.authenticate_iso_des(0x00, master_key.key.as_ref(), None)?; desfire.format_picc()?; println!("Card formatted"); return Ok(()) } else { let space = match args.space { Some(ref space) => space, None => { return Err(Box::new(std::io::Error::new(std::io::ErrorKind::Other, "No space name provided"))) } }; let instance = match args.instance { Some(ref instance) => instance, None => { return Err(Box::new(std::io::Error::new(std::io::ErrorKind::Other, "No instance name provided"))) } }; let contact = match args.contact { Some(ref contact) => contact, None => { return Err(Box::new(std::io::Error::new(std::io::ErrorKind::Other, "No contact info provided"))) } }; // encode the space info let space_urn = UrnBuilder::new("fabaccess", &format!("lab:{}", urlencoding::encode(space))) .build()?; let instance_uri = URI::builder() .with_scheme(Scheme::Unregistered(UnregisteredScheme::try_from("fabaccess")?)) .with_authority(Some(Authority::try_from(instance.deref())?)) .with_path(Path::try_from("")?) .build()?; let contact_uri = URI::try_from(contact.deref())?; let token = match args.token { Some(token) => { if token.as_bytes().len() >= MAX_BYTES_PER_TRANSACTION { return Err("Token is too long".into()) } token }, None => { Uuid::new_v4().to_string() } }; // authenticate against picc desfire.authenticate_iso_des(0x00, master_key.key.as_ref(), None)?; // generate a new application let ks1 = generate_keysetting1(ChangeApplicationKey::MASTERKEY as u8, ChangeMasterKeySettings::WITHMASTERKEY, CreateDeleteFile::ONLYMASTERKEY, FileDirectoryAccess::NOKEY, ChangeMasterKey::CHANGEABLE)?; let ks2 = generate_keysetting2(CryptoOperationsType::AES, FileIdentifiers::NOTUSED, 0x02)?; desfire.create_application(args.app_id, ks1, ks2)?; // select the application desfire.select_application(args.app_id); // change the application master key desfire.authenticate_iso_aes(0x00, CipherKey::new_empty(CipherType::AES)?.key.as_ref(), None)?; desfire.change_key_aes(0x00, app_key.key.as_ref(), app_key.key_version)?; // authenticate with new application master key desfire.authenticate_iso_aes(0x00, app_key.key.as_ref(), None)?; // set the user authentication key desfire.change_other_key_aes(0x01, user_key.key.as_ref(), CipherKey::new_empty(CipherType::AES)?.key.as_ref(), user_key.key_version)?; // create file with magic let magic_accessrights = generate_file_access_rights(FileAccessRights::FREE as u8, 0x00, 0x00, 0x00)?; desfire.create_file_standard(0x01, FileCommunication::PLAIN, magic_accessrights, args.magic.as_bytes().len() as u32)?; desfire.write_data(0x01, 0x00, args.magic.as_bytes())?; // create file with space info let space_accessrights = generate_file_access_rights(FileAccessRights::FREE as u8, 0x00, 0x00, 0x00)?; desfire.create_file_standard(0x02, FileCommunication::PLAIN, space_accessrights, (MAX_BYTES_PER_TRANSACTION * 3) as u32)?; desfire.write_data(0x02, 0x00, space_urn.as_bytes())?; desfire.write_data(0x02, MAX_BYTES_PER_TRANSACTION as u32, instance_uri.to_string().as_bytes())?; desfire.write_data(0x02, (MAX_BYTES_PER_TRANSACTION * 2) as u32, contact_uri.to_string().as_bytes())?; // create file with token let token_accessrights = generate_file_access_rights(FileAccessRights::FREE as u8, 0x00, 0x00, 0x00)?; desfire.create_file_standard(0x03, FileCommunication::PLAIN, token_accessrights, MAX_BYTES_PER_TRANSACTION as u32)?; // Max desfire.write_data(0x03, 0x00, token.as_bytes())?; println!("Card provisioned! Add the following to the users entry in 'users.toml': cardkey = \"{}\"", hex::encode(user_key.key)); Ok(()) } }