Importing X-FABFIRE auth mechanism

This commit is contained in:
Nadja Reitzenstein 2022-03-16 19:29:36 +01:00
parent cb8cda39cd
commit 538b0b28f1
6 changed files with 745 additions and 3 deletions

89
Cargo.lock generated
View File

@ -23,6 +23,18 @@ dependencies = [
"pretty", "pretty",
] ]
[[package]]
name = "aes"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8"
dependencies = [
"cfg-if",
"cipher",
"cpufeatures",
"opaque-debug 0.3.0",
]
[[package]] [[package]]
name = "ahash" name = "ahash"
version = "0.7.6" version = "0.7.6"
@ -298,7 +310,7 @@ version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b"
dependencies = [ dependencies = [
"block-padding", "block-padding 0.1.5",
"byte-tools", "byte-tools",
"byteorder", "byteorder",
"generic-array 0.12.4", "generic-array 0.12.4",
@ -322,6 +334,16 @@ dependencies = [
"generic-array 0.14.5", "generic-array 0.14.5",
] ]
[[package]]
name = "block-modes"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2cb03d1bed155d89dce0f845b7899b18a9a163e148fd004e1c28421a783e2d8e"
dependencies = [
"block-padding 0.2.1",
"cipher",
]
[[package]] [[package]]
name = "block-padding" name = "block-padding"
version = "0.1.5" version = "0.1.5"
@ -331,6 +353,12 @@ dependencies = [
"byte-tools", "byte-tools",
] ]
[[package]]
name = "block-padding"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae"
[[package]] [[package]]
name = "blocking" name = "blocking"
version = "1.2.0" version = "1.2.0"
@ -479,6 +507,15 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "cipher"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7"
dependencies = [
"generic-array 0.14.5",
]
[[package]] [[package]]
name = "clap" name = "clap"
version = "2.34.0" version = "2.34.0"
@ -693,6 +730,33 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "des"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac41dd49fb554432020d52c875fc290e110113f864c6b1b525cd62c7e7747a5d"
dependencies = [
"byteorder",
"cipher",
"opaque-debug 0.3.0",
]
[[package]]
name = "desfire"
version = "0.2.0-alpha1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83dddd3136b4dfc80f46dc6441cd3f16f99317e645bedc61eabc1452d24bfb3f"
dependencies = [
"aes",
"block-modes",
"des",
"hex",
"num-derive",
"num-traits",
"rand",
"simple-error",
]
[[package]] [[package]]
name = "dhall" name = "dhall"
version = "0.10.1" version = "0.10.1"
@ -743,6 +807,7 @@ dependencies = [
"capnp-rpc", "capnp-rpc",
"chrono", "chrono",
"clap 3.1.6", "clap 3.1.6",
"desfire",
"dirs", "dirs",
"erased-serde", "erased-serde",
"executor", "executor",
@ -751,9 +816,11 @@ dependencies = [
"futures-signals", "futures-signals",
"futures-test", "futures-test",
"futures-util", "futures-util",
"hex",
"inventory", "inventory",
"lazy_static", "lazy_static",
"libc", "libc",
"linkme",
"lmdb-rkv", "lmdb-rkv",
"nix", "nix",
"once_cell", "once_cell",
@ -1194,6 +1261,9 @@ name = "hex"
version = "0.4.3" version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "hmac" name = "hmac"
@ -1508,6 +1578,17 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "num-derive"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "num-integer" name = "num-integer"
version = "0.1.44" version = "0.1.44"
@ -2335,6 +2416,12 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "simple-error"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc47a29ce97772ca5c927f75bac34866b16d64e07f330c3248e2d7226623901b"
[[package]] [[package]]
name = "slab" name = "slab"
version = "0.4.5" version = "0.4.5"

View File

@ -74,7 +74,10 @@ capnp-rpc = "0.14.1"
# API Authentication # API Authentication
#rsasl = "2.0.0-preview3" #rsasl = "2.0.0-preview3"
rsasl = { path = "../../rsasl" } rsasl = { path = "../../rsasl", features = ["unstable_custom_mechanism"] }
desfire = "0.2.0-alpha1"
hex = { version = "0.4.3", features = ["serde"] }
linkme = "0.2.10"
futures-signals = "0.3.22" futures-signals = "0.3.22"
async-oneshot = "0.5" async-oneshot = "0.5"

View 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",
));

View 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));
}
}

View File

@ -7,6 +7,9 @@ use rsasl::session::{Session, SessionData};
use rsasl::validate::{validations, Validation}; use rsasl::validate::{validations, Validation};
use rsasl::{SASL}; use rsasl::{SASL};
use std::sync::Arc; use std::sync::Arc;
use rsasl::registry::Mechanism;
mod fabfire;
struct Callback { struct Callback {
users: Users, users: Users,
@ -66,8 +69,18 @@ pub struct AuthenticationHandle {
impl AuthenticationHandle { impl AuthenticationHandle {
pub fn new(userdb: Users) -> Self { pub fn new(userdb: Users) -> Self {
let span = tracing::debug_span!("authentication");
let _guard = span.enter();
let mut rsasl = SASL::new(); let mut rsasl = SASL::new();
rsasl.install_callback(Arc::new(Callback::new(userdb))); 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 { Self {
inner: Arc::new(Inner::new(rsasl)), inner: Arc::new(Inner::new(rsasl)),
} }

View File

@ -69,7 +69,7 @@ impl UserRef {
static USERDB: OnceCell<UserDB> = OnceCell::new(); static USERDB: OnceCell<UserDB> = OnceCell::new();
#[derive(Copy, Clone)] #[derive(Copy, Clone, Debug)]
#[repr(transparent)] #[repr(transparent)]
pub struct Users { pub struct Users {
userdb: &'static UserDB, userdb: &'static UserDB,