mirror of
https://gitlab.com/fabinfra/fabaccess/bffh.git
synced 2025-06-11 02:43:20 +02:00
Importing X-FABFIRE auth mechanism
This commit is contained in:
44
bffhd/authentication/fabfire/mod.rs
Normal file
44
bffhd/authentication/fabfire/mod.rs
Normal file
@ -0,0 +1,44 @@
|
||||
mod server;
|
||||
pub use server::FabFire;
|
||||
|
||||
use rsasl::mechname::Mechname;
|
||||
use rsasl::registry::{Mechanism, MECHANISMS};
|
||||
use rsasl::session::Side;
|
||||
|
||||
const MECHNAME: &'static Mechname = &Mechname::const_new_unchecked(b"X-FABFIRE");
|
||||
|
||||
#[linkme::distributed_slice(MECHANISMS)]
|
||||
pub static FABFIRE: Mechanism = Mechanism {
|
||||
mechanism: MECHNAME,
|
||||
priority: 300,
|
||||
// In this situation there's one struct for both sides, however you can just as well use
|
||||
// different types than then have different `impl Authentication` instead of checking a value
|
||||
// in self.
|
||||
client: None,
|
||||
server: Some(FabFire::new_server),
|
||||
first: Side::Client,
|
||||
};
|
||||
|
||||
use std::marker::PhantomData;
|
||||
use rsasl::property::{Property, PropertyQ, PropertyDefinition};
|
||||
// All Property types must implement Debug.
|
||||
#[derive(Debug)]
|
||||
// The `PhantomData` in the constructor is only used so external crates can't construct this type.
|
||||
pub struct FabFireCardKey(PhantomData<()>);
|
||||
impl PropertyQ for FabFireCardKey {
|
||||
// This is the type stored for this property. This could also be the struct itself if you
|
||||
// so choose
|
||||
type Item = [u8; 16];
|
||||
// You need to return the constant you define below here for things to work properly
|
||||
fn property() -> Property {
|
||||
FABFIRECARDKEY
|
||||
}
|
||||
}
|
||||
// This const is used by your mechanism to query and by your users to set your property. It
|
||||
// thus needs to be exported from your crate
|
||||
pub const FABFIRECARDKEY: Property = Property::new(&PropertyDefinition::new(
|
||||
// Short name, used in `Debug` output
|
||||
"FabFireCardKey",
|
||||
// A longer user-facing name used in `Display` output
|
||||
"A AES128 key for a FabFire card",
|
||||
));
|
595
bffhd/authentication/fabfire/server.rs
Normal file
595
bffhd/authentication/fabfire/server.rs
Normal file
@ -0,0 +1,595 @@
|
||||
use std::fmt::{Debug, Display, Formatter};
|
||||
use std::io::Write;
|
||||
use rsasl::error::{MechanismError, MechanismErrorKind, SASLError, SessionError};
|
||||
use rsasl::mechanism::Authentication;
|
||||
use rsasl::SASL;
|
||||
use rsasl::session::{SessionData, StepResult};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use desfire::desfire::Desfire;
|
||||
use desfire::iso7816_4::apduresponse::APDUResponse;
|
||||
use desfire::error::{Error as DesfireError};
|
||||
use std::convert::TryFrom;
|
||||
use std::sync::Arc;
|
||||
use desfire::desfire::desfire::MAX_BYTES_PER_TRANSACTION;
|
||||
use rsasl::property::AuthId;
|
||||
use crate::authentication::fabfire::FabFireCardKey;
|
||||
|
||||
enum FabFireError {
|
||||
ParseError,
|
||||
SerializationError,
|
||||
DeserializationError(serde_json::Error),
|
||||
CardError(DesfireError),
|
||||
InvalidMagic(String),
|
||||
InvalidToken(String),
|
||||
InvalidURN(String),
|
||||
InvalidCredentials(String),
|
||||
Session(SessionError),
|
||||
}
|
||||
|
||||
impl Debug for FabFireError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
FabFireError::ParseError => write!(f, "ParseError"),
|
||||
FabFireError::SerializationError => write!(f, "SerializationError"),
|
||||
FabFireError::DeserializationError(e) => write!(f, "DeserializationError: {}", e),
|
||||
FabFireError::CardError(err) => write!(f, "CardError: {}", err),
|
||||
FabFireError::InvalidMagic(magic) => write!(f, "InvalidMagic: {}", magic),
|
||||
FabFireError::InvalidToken(token) => write!(f, "InvalidToken: {}", token),
|
||||
FabFireError::InvalidURN(urn) => write!(f, "InvalidURN: {}", urn),
|
||||
FabFireError::InvalidCredentials(credentials) => write!(f, "InvalidCredentials: {}", credentials),
|
||||
FabFireError::Session(err) => write!(f, "Session: {}", err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for FabFireError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
FabFireError::ParseError => write!(f, "ParseError"),
|
||||
FabFireError::SerializationError => write!(f, "SerializationError"),
|
||||
FabFireError::DeserializationError(e) => write!(f, "DeserializationError: {}", e),
|
||||
FabFireError::CardError(err) => write!(f, "CardError: {}", err),
|
||||
FabFireError::InvalidMagic(magic) => write!(f, "InvalidMagic: {}", magic),
|
||||
FabFireError::InvalidToken(token) => write!(f, "InvalidToken: {}", token),
|
||||
FabFireError::InvalidURN(urn) => write!(f, "InvalidURN: {}", urn),
|
||||
FabFireError::InvalidCredentials(credentials) => write!(f, "InvalidCredentials: {}", credentials),
|
||||
FabFireError::Session(err) => write!(f, "Session: {}", err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MechanismError for FabFireError {
|
||||
fn kind(&self) -> MechanismErrorKind {
|
||||
match self {
|
||||
FabFireError::ParseError => MechanismErrorKind::Parse,
|
||||
FabFireError::SerializationError => MechanismErrorKind::Protocol,
|
||||
FabFireError::DeserializationError(_) => MechanismErrorKind::Parse,
|
||||
FabFireError::CardError(_) => MechanismErrorKind::Protocol,
|
||||
FabFireError::InvalidMagic(_) => MechanismErrorKind::Protocol,
|
||||
FabFireError::InvalidToken(_) => MechanismErrorKind::Protocol,
|
||||
FabFireError::InvalidURN(_) => MechanismErrorKind::Protocol,
|
||||
FabFireError::InvalidCredentials(_) => MechanismErrorKind::Protocol,
|
||||
FabFireError::Session(_) => MechanismErrorKind::Protocol,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct CardInfo {
|
||||
#[serde(rename = "UID", with = "hex")]
|
||||
uid: [u8; 7],
|
||||
key_old: Option<Box<[u8]>>,
|
||||
key_new: Option<Box<[u8]>>,
|
||||
}
|
||||
|
||||
struct KeyInfo {
|
||||
key_id: u8,
|
||||
key: Box<[u8]>,
|
||||
}
|
||||
|
||||
struct AuthInfo {
|
||||
rnd_a: Vec<u8>,
|
||||
rnd_b: Vec<u8>,
|
||||
iv: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[serde(tag = "Cmd")]
|
||||
enum CardCommand {
|
||||
message {
|
||||
#[serde(rename = "MssgID", skip_serializing_if = "Option::is_none")]
|
||||
msg_id: Option<u32>,
|
||||
#[serde(rename = "ClrTxt", skip_serializing_if = "Option::is_none")]
|
||||
clr_txt: Option<String>,
|
||||
#[serde(rename = "AddnTxt", skip_serializing_if = "Option::is_none")]
|
||||
addn_txt: Option<String>,
|
||||
},
|
||||
sendPICC {
|
||||
#[serde(deserialize_with = "hex::deserialize", serialize_with = "hex::serialize_upper")]
|
||||
data: Vec<u8>
|
||||
},
|
||||
readPICC {
|
||||
#[serde(deserialize_with = "hex::deserialize", serialize_with = "hex::serialize_upper")]
|
||||
data: Vec<u8>
|
||||
},
|
||||
haltPICC,
|
||||
Key {
|
||||
data: String
|
||||
},
|
||||
ConfirmUser,
|
||||
}
|
||||
|
||||
enum Step {
|
||||
New,
|
||||
SelectApp,
|
||||
VerifyMagic,
|
||||
GetURN,
|
||||
GetToken,
|
||||
Authenticate1,
|
||||
Authenticate2,
|
||||
}
|
||||
|
||||
pub struct FabFire {
|
||||
step: Step,
|
||||
card_info: Option<CardInfo>,
|
||||
key_info: Option<KeyInfo>,
|
||||
auth_info: Option<AuthInfo>,
|
||||
app_id: u32,
|
||||
local_urn: String,
|
||||
desfire: Desfire,
|
||||
}
|
||||
|
||||
const MAGIC: &'static str = "FABACCESS\0DESFIRE\01.0\0";
|
||||
|
||||
impl FabFire {
|
||||
pub fn new_server(_sasl: &SASL) -> Result<Box<dyn Authentication>, SASLError> {
|
||||
Ok(Box::new(Self { step: Step::New, card_info: None, key_info: None, auth_info: None, app_id: 1, local_urn: "urn:fabaccess:lab:innovisionlab".to_string(), desfire: Desfire { card: None, session_key: None, cbc_iv: None } }))
|
||||
}
|
||||
}
|
||||
|
||||
impl Authentication for FabFire {
|
||||
fn step(&mut self, session: &mut SessionData, input: Option<&[u8]>, writer: &mut dyn Write) -> StepResult {
|
||||
match self.step {
|
||||
Step::New => {
|
||||
// println!("Step: New");
|
||||
//receive card info (especially card UID) from reader
|
||||
return match input {
|
||||
None => { Err(SessionError::InputDataRequired) }
|
||||
Some(cardinfo) => {
|
||||
self.card_info = match serde_json::from_slice(cardinfo) {
|
||||
Ok(card_info) => Some(card_info),
|
||||
Err(e) => {
|
||||
// eprintln!("{:?}", e);
|
||||
return Err(FabFireError::DeserializationError(e).into());
|
||||
}
|
||||
};
|
||||
//select application
|
||||
let buf = match self.desfire.select_application_cmd(self.app_id) {
|
||||
Ok(buf) => match Vec::<u8>::try_from(buf) {
|
||||
Ok(data) => data,
|
||||
Err(e) => {
|
||||
// eprintln!("Failed to convert APDUCommand to Vec<u8>: {:?}", e);
|
||||
return Err(FabFireError::SerializationError.into());
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
// eprintln!("Failed to generate APDUCommand: {:?}", e);
|
||||
return Err(FabFireError::SerializationError.into());
|
||||
}
|
||||
};
|
||||
let cmd = CardCommand::sendPICC { data: buf };
|
||||
return match serde_json::to_vec(&cmd) {
|
||||
Ok(send_buf) => {
|
||||
self.step = Step::SelectApp;
|
||||
writer.write_all(&send_buf).map_err(|e| SessionError::Io { source: e })?;
|
||||
Ok(rsasl::session::Step::NeedsMore(Some(send_buf.len())))
|
||||
}
|
||||
Err(e) => {
|
||||
// eprintln!("Failed to serialize APDUCommand: {:?}", e);
|
||||
Err(FabFireError::SerializationError.into())
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
Step::SelectApp => {
|
||||
// println!("Step: SelectApp");
|
||||
// check that we successfully selected the application
|
||||
let response: CardCommand = match input {
|
||||
None => { return Err(SessionError::InputDataRequired); }
|
||||
Some(buf) => match serde_json::from_slice(buf).map_err(|e| FabFireError::DeserializationError(e)) {
|
||||
Ok(response) => response,
|
||||
Err(e) => {
|
||||
// eprintln!("{:?}", e);
|
||||
return Err(e.into());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let apdu_response = match response {
|
||||
CardCommand::readPICC { data } => { APDUResponse::new(&*data) }
|
||||
_ => {
|
||||
// eprintln!("Unexpected response: {:?}", response);
|
||||
return Err(FabFireError::ParseError.into());
|
||||
}
|
||||
};
|
||||
|
||||
apdu_response.check().map_err(|e| FabFireError::CardError(e))?;
|
||||
|
||||
// request the contents of the file containing the magic string
|
||||
const MAGIC_FILE_ID: u8 = 0x01;
|
||||
|
||||
let buf = match self.desfire.read_data_chunk_cmd(MAGIC_FILE_ID, 0, MAGIC.len()) {
|
||||
Ok(buf) => match Vec::<u8>::try_from(buf) {
|
||||
Ok(data) => data,
|
||||
Err(_) => {
|
||||
return Err(FabFireError::SerializationError.into());
|
||||
}
|
||||
},
|
||||
Err(_) => {
|
||||
return Err(FabFireError::SerializationError.into());
|
||||
}
|
||||
};
|
||||
let cmd = CardCommand::sendPICC { data: buf };
|
||||
return match serde_json::to_vec(&cmd) {
|
||||
Ok(send_buf) => {
|
||||
self.step = Step::VerifyMagic;
|
||||
writer.write_all(&send_buf).map_err(|e| SessionError::Io { source: e })?;
|
||||
Ok(rsasl::session::Step::NeedsMore(Some(send_buf.len())))
|
||||
}
|
||||
Err(_) => {
|
||||
Err(FabFireError::SerializationError.into())
|
||||
}
|
||||
};
|
||||
}
|
||||
Step::VerifyMagic => {
|
||||
// println!("Step: VerifyMagic");
|
||||
// verify the magic string to determine that we have a valid fabfire card
|
||||
let response: CardCommand = match input {
|
||||
None => { return Err(SessionError::InputDataRequired); }
|
||||
Some(buf) => match serde_json::from_slice(buf).map_err(|e| FabFireError::DeserializationError(e)) {
|
||||
Ok(response) => response,
|
||||
Err(e) => {
|
||||
// eprintln!("{:?}", e);
|
||||
return Err(e.into());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let apdu_response = match response {
|
||||
CardCommand::readPICC { data } => { APDUResponse::new(&*data) }
|
||||
_ => {
|
||||
// eprintln!("Unexpected response: {:?}", response);
|
||||
return Err(FabFireError::ParseError.into());
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
match apdu_response.check() {
|
||||
Ok(_) => {
|
||||
match apdu_response.body {
|
||||
Some(data) => {
|
||||
if std::str::from_utf8(data.as_slice()) != Ok(MAGIC) {
|
||||
return Err(FabFireError::ParseError.into());
|
||||
}
|
||||
}
|
||||
None => {
|
||||
return Err(FabFireError::ParseError.into());
|
||||
}
|
||||
};
|
||||
}
|
||||
Err(_) => {
|
||||
return Err(FabFireError::ParseError.into());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// request the contents of the file containing the URN
|
||||
const URN_FILE_ID: u8 = 0x02;
|
||||
|
||||
let buf = match self.desfire.read_data_chunk_cmd(URN_FILE_ID, 0, self.local_urn.as_bytes().len()) { // TODO: support urn longer than 47 Bytes
|
||||
Ok(buf) => match Vec::<u8>::try_from(buf) {
|
||||
Ok(data) => data,
|
||||
Err(_) => {
|
||||
return Err(FabFireError::SerializationError.into());
|
||||
}
|
||||
},
|
||||
Err(_) => {
|
||||
return Err(FabFireError::SerializationError.into());
|
||||
}
|
||||
};
|
||||
let cmd = CardCommand::sendPICC { data: buf };
|
||||
return match serde_json::to_vec(&cmd) {
|
||||
Ok(send_buf) => {
|
||||
self.step = Step::GetURN;
|
||||
writer.write_all(&send_buf).map_err(|e| SessionError::Io { source: e })?;
|
||||
Ok(rsasl::session::Step::NeedsMore(Some(send_buf.len())))
|
||||
}
|
||||
Err(_) => {
|
||||
Err(FabFireError::SerializationError.into())
|
||||
}
|
||||
};
|
||||
}
|
||||
Step::GetURN => {
|
||||
// println!("Step: GetURN");
|
||||
// parse the urn and match it to our local urn
|
||||
let response: CardCommand = match input {
|
||||
None => { return Err(SessionError::InputDataRequired); }
|
||||
Some(buf) => match serde_json::from_slice(buf).map_err(|e| FabFireError::DeserializationError(e)) {
|
||||
Ok(response) => response,
|
||||
Err(e) => {
|
||||
// eprintln!("{:?}", e);
|
||||
return Err(e.into());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let apdu_response = match response {
|
||||
CardCommand::readPICC { data } => { APDUResponse::new(&*data) }
|
||||
_ => {
|
||||
// eprintln!("Unexpected response: {:?}", response);
|
||||
return Err(FabFireError::ParseError.into());
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
match apdu_response.check() {
|
||||
Ok(_) => {
|
||||
match apdu_response.body {
|
||||
Some(data) => {
|
||||
let received_urn = String::from_utf8(data).unwrap();
|
||||
if received_urn != self.local_urn {
|
||||
// eprintln!("URN mismatch: {:?} != {:?}", received_urn, self.local_urn);
|
||||
return Err(FabFireError::ParseError.into());
|
||||
}
|
||||
}
|
||||
None => {
|
||||
// eprintln!("No data in response");
|
||||
return Err(FabFireError::ParseError.into());
|
||||
}
|
||||
};
|
||||
}
|
||||
Err(e) => {
|
||||
// eprintln!("Invalid response: {:?}", e);
|
||||
return Err(FabFireError::ParseError.into());
|
||||
}
|
||||
}
|
||||
// request the contents of the file containing the URN
|
||||
const TOKEN_FILE_ID: u8 = 0x03;
|
||||
|
||||
let buf = match self.desfire.read_data_chunk_cmd(TOKEN_FILE_ID, 0, MAX_BYTES_PER_TRANSACTION) { // TODO: support data longer than 47 Bytes
|
||||
Ok(buf) => match Vec::<u8>::try_from(buf) {
|
||||
Ok(data) => data,
|
||||
Err(_) => {
|
||||
return Err(FabFireError::SerializationError.into());
|
||||
}
|
||||
},
|
||||
Err(_) => {
|
||||
return Err(FabFireError::SerializationError.into());
|
||||
}
|
||||
};
|
||||
let cmd = CardCommand::sendPICC { data: buf };
|
||||
return match serde_json::to_vec(&cmd) {
|
||||
Ok(send_buf) => {
|
||||
self.step = Step::GetToken;
|
||||
writer.write_all(&send_buf).map_err(|e| SessionError::Io { source: e })?;
|
||||
Ok(rsasl::session::Step::NeedsMore(Some(send_buf.len())))
|
||||
}
|
||||
Err(_) => {
|
||||
Err(FabFireError::SerializationError.into())
|
||||
}
|
||||
};
|
||||
}
|
||||
Step::GetToken => {
|
||||
// println!("Step: GetToken");
|
||||
// parse the token and select the appropriate user
|
||||
let response: CardCommand = match input {
|
||||
None => { return Err(SessionError::InputDataRequired); }
|
||||
Some(buf) => match serde_json::from_slice(buf).map_err(|e| FabFireError::DeserializationError(e)) {
|
||||
Ok(response) => response,
|
||||
Err(e) => {
|
||||
// eprintln!("{:?}", e);
|
||||
return Err(e.into());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let apdu_response = match response {
|
||||
CardCommand::readPICC { data } => { APDUResponse::new(&*data) }
|
||||
_ => {
|
||||
// eprintln!("Unexpected response: {:?}", response);
|
||||
return Err(FabFireError::ParseError.into());
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
match apdu_response.check() {
|
||||
Ok(_) => {
|
||||
match apdu_response.body {
|
||||
Some(data) => {
|
||||
let token = String::from_utf8(data).unwrap();
|
||||
session.set_property::<AuthId>(Arc::new(token.trim_matches(char::from(0)).to_string()));
|
||||
let key = match session.get_property_or_callback::<FabFireCardKey>() {
|
||||
Ok(Some(key)) => Box::from(key.as_slice()),
|
||||
Ok(None) => {
|
||||
return Err(FabFireError::InvalidCredentials("No keys on file for token".to_string()).into());
|
||||
}
|
||||
Err(e) => {
|
||||
return Err(FabFireError::Session(e).into());
|
||||
}
|
||||
};
|
||||
self.key_info = Some(KeyInfo{ key_id: 0x01, key });
|
||||
}
|
||||
None => {
|
||||
// eprintln!("No data in response");
|
||||
return Err(FabFireError::ParseError.into());
|
||||
}
|
||||
};
|
||||
}
|
||||
Err(e) => {
|
||||
// eprintln!("Invalid response: {:?}", e);
|
||||
return Err(FabFireError::ParseError.into());
|
||||
}
|
||||
}
|
||||
|
||||
let buf = match self.desfire.authenticate_iso_aes_challenge_cmd(self.key_info.as_ref().unwrap().key_id) {
|
||||
Ok(buf) => match Vec::<u8>::try_from(buf) {
|
||||
Ok(data) => data,
|
||||
Err(_) => {
|
||||
return Err(FabFireError::SerializationError.into());
|
||||
}
|
||||
},
|
||||
Err(_) => {
|
||||
return Err(FabFireError::SerializationError.into());
|
||||
}
|
||||
};
|
||||
let cmd = CardCommand::sendPICC { data: buf };
|
||||
return match serde_json::to_vec(&cmd) {
|
||||
Ok(send_buf) => {
|
||||
self.step = Step::Authenticate1;
|
||||
writer.write_all(&send_buf).map_err(|e| SessionError::Io { source: e })?;
|
||||
Ok(rsasl::session::Step::NeedsMore(Some(send_buf.len())))
|
||||
}
|
||||
Err(_) => {
|
||||
Err(FabFireError::SerializationError.into())
|
||||
}
|
||||
};
|
||||
}
|
||||
Step::Authenticate1 => {
|
||||
// println!("Step: Authenticate1");
|
||||
let response: CardCommand = match input {
|
||||
None => { return Err(SessionError::InputDataRequired); }
|
||||
Some(buf) => match serde_json::from_slice(buf).map_err(|e| FabFireError::DeserializationError(e)) {
|
||||
Ok(response) => response,
|
||||
Err(e) => {
|
||||
// eprintln!("{:?}", e);
|
||||
return Err(e.into());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let apdu_response = match response {
|
||||
CardCommand::readPICC { data } => { APDUResponse::new(&*data) }
|
||||
_ => {
|
||||
// eprintln!("Unexpected response: {:?}", response);
|
||||
return Err(FabFireError::ParseError.into());
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
match apdu_response.check() {
|
||||
Ok(_) => {
|
||||
match apdu_response.body {
|
||||
Some(data) => {
|
||||
let rnd_b_enc = data.as_slice();
|
||||
|
||||
//FIXME: This is ugly, we should find a better way to make the function testable
|
||||
//TODO: Check if we need a CSPRNG here
|
||||
let rnd_a: [u8; 16] = rand::random();
|
||||
// println!("RND_A: {:x?}", rnd_a);
|
||||
|
||||
let (cmd_challenge_response,
|
||||
rnd_b,
|
||||
iv) = self.desfire.authenticate_iso_aes_response_cmd(
|
||||
rnd_b_enc,
|
||||
&*(self.key_info.as_ref().unwrap().key),
|
||||
&rnd_a).unwrap();
|
||||
self.auth_info = Some(AuthInfo { rnd_a: Vec::<u8>::from(rnd_a), rnd_b, iv });
|
||||
let buf = match Vec::<u8>::try_from(cmd_challenge_response) {
|
||||
Ok(data) => data,
|
||||
Err(_) => {
|
||||
return Err(FabFireError::SerializationError.into());
|
||||
}
|
||||
};
|
||||
let cmd = CardCommand::sendPICC { data: buf };
|
||||
return match serde_json::to_vec(&cmd) {
|
||||
Ok(send_buf) => {
|
||||
self.step = Step::Authenticate2;
|
||||
writer.write_all(&send_buf).map_err(|e| SessionError::Io { source: e })?;
|
||||
Ok(rsasl::session::Step::NeedsMore(Some(send_buf.len())))
|
||||
}
|
||||
Err(_) => {
|
||||
Err(FabFireError::SerializationError.into())
|
||||
}
|
||||
};
|
||||
}
|
||||
None => {
|
||||
return Err(FabFireError::ParseError.into());
|
||||
}
|
||||
};
|
||||
}
|
||||
Err(_) => {
|
||||
return Err(FabFireError::ParseError.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
Step::Authenticate2 => {
|
||||
// println!("Step: Authenticate2");
|
||||
let response: CardCommand = match input {
|
||||
None => { return Err(SessionError::InputDataRequired); }
|
||||
Some(buf) => match serde_json::from_slice(buf).map_err(|e| FabFireError::DeserializationError(e)) {
|
||||
Ok(response) => response,
|
||||
Err(e) => {
|
||||
// eprintln!("{:?}", e);
|
||||
return Err(e.into());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let apdu_response = match response {
|
||||
CardCommand::readPICC { data } => { APDUResponse::new(&*data) }
|
||||
_ => {
|
||||
// eprintln!("Unexpected response: {:?}", response);
|
||||
return Err(FabFireError::ParseError.into());
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
match apdu_response.check() {
|
||||
Ok(_) => {
|
||||
match apdu_response.body {
|
||||
Some(data) => {
|
||||
match self.auth_info.as_ref() {
|
||||
None => { return Err(FabFireError::ParseError.into()); }
|
||||
Some(auth_info) => {
|
||||
if self.desfire.authenticate_iso_aes_verify(
|
||||
data.as_slice(),
|
||||
auth_info.rnd_a.as_slice(),
|
||||
auth_info.rnd_b.as_slice(), &*(self.key_info.as_ref().unwrap().key),
|
||||
auth_info.iv.as_slice()).is_ok() {
|
||||
|
||||
let cmd = CardCommand::message{
|
||||
msg_id: Some(4),
|
||||
clr_txt: None,
|
||||
addn_txt: Some("".to_string()),
|
||||
};
|
||||
return match serde_json::to_vec(&cmd) {
|
||||
Ok(send_buf) => {
|
||||
self.step = Step::Authenticate1;
|
||||
writer.write_all(&send_buf).map_err(|e| SessionError::Io { source: e })?;
|
||||
return Ok(rsasl::session::Step::Done(Some(send_buf.len())))
|
||||
}
|
||||
Err(_) => {
|
||||
Err(FabFireError::SerializationError.into())
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
return Err(FabFireError::ParseError.into());
|
||||
}
|
||||
};
|
||||
}
|
||||
Err(_) => {
|
||||
return Err(FabFireError::InvalidCredentials(format!("{}", apdu_response)).into());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(rsasl::session::Step::Done(None));
|
||||
}
|
||||
}
|
@ -7,6 +7,9 @@ use rsasl::session::{Session, SessionData};
|
||||
use rsasl::validate::{validations, Validation};
|
||||
use rsasl::{SASL};
|
||||
use std::sync::Arc;
|
||||
use rsasl::registry::Mechanism;
|
||||
|
||||
mod fabfire;
|
||||
|
||||
struct Callback {
|
||||
users: Users,
|
||||
@ -66,8 +69,18 @@ pub struct AuthenticationHandle {
|
||||
|
||||
impl AuthenticationHandle {
|
||||
pub fn new(userdb: Users) -> Self {
|
||||
let span = tracing::debug_span!("authentication");
|
||||
let _guard = span.enter();
|
||||
|
||||
let mut rsasl = SASL::new();
|
||||
rsasl.install_callback(Arc::new(Callback::new(userdb)));
|
||||
|
||||
let mechs: Vec<&'static str> = rsasl.server_mech_list().into_iter()
|
||||
.map(|m| m.mechanism.as_str())
|
||||
.collect();
|
||||
tracing::info!(available_mechs=mechs.len(), "initialized sasl backend");
|
||||
tracing::debug!(?mechs, "available mechs");
|
||||
|
||||
Self {
|
||||
inner: Arc::new(Inner::new(rsasl)),
|
||||
}
|
@ -69,7 +69,7 @@ impl UserRef {
|
||||
|
||||
static USERDB: OnceCell<UserDB> = OnceCell::new();
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[repr(transparent)]
|
||||
pub struct Users {
|
||||
userdb: &'static UserDB,
|
||||
|
Reference in New Issue
Block a user