2022-05-23 21:18:34 +02:00

190 lines
7.2 KiB
Rust

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 = "0x464142")]
app_id: u32,
/// Masterkey for the PICC
#[clap(long)]
picc_masterkey: Option<String>,
/// Masterkey for the Application
#[clap(long)]
app_masterkey: Option<String>,
/// user authentication key
#[clap(long)]
app_authkey: Option<String>,
/// 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<String>,
/// BFFHd Instance for the space
#[clap(short, long, required_unless_present = "format")]
instance: Option<String>,
/// Contact option for lost cards
#[clap(short, long, required_unless_present = "format")]
contact: Option<String>,
/// User token, currently this should be set to the Username (will be generated for you if not given)
#[clap(short, long)]
token: Option<String>,
/// Whether to format the card
#[clap(short, long)]
format: bool,
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
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(())
}
}