From 538b0b28f1dafa332da8f85287d302c407de5953 Mon Sep 17 00:00:00 2001 From: Nadja Reitzenstein Date: Wed, 16 Mar 2022 19:29:36 +0100 Subject: [PATCH] Importing X-FABFIRE auth mechanism --- Cargo.lock | 89 ++- Cargo.toml | 5 +- bffhd/authentication/fabfire/mod.rs | 44 ++ bffhd/authentication/fabfire/server.rs | 595 ++++++++++++++++++ .../mod.rs} | 13 + bffhd/users/mod.rs | 2 +- 6 files changed, 745 insertions(+), 3 deletions(-) create mode 100644 bffhd/authentication/fabfire/mod.rs create mode 100644 bffhd/authentication/fabfire/server.rs rename bffhd/{authentication.rs => authentication/mod.rs} (84%) diff --git a/Cargo.lock b/Cargo.lock index 9829bc1..008d1b6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -23,6 +23,18 @@ dependencies = [ "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]] name = "ahash" version = "0.7.6" @@ -298,7 +310,7 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" dependencies = [ - "block-padding", + "block-padding 0.1.5", "byte-tools", "byteorder", "generic-array 0.12.4", @@ -322,6 +334,16 @@ dependencies = [ "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]] name = "block-padding" version = "0.1.5" @@ -331,6 +353,12 @@ dependencies = [ "byte-tools", ] +[[package]] +name = "block-padding" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" + [[package]] name = "blocking" version = "1.2.0" @@ -479,6 +507,15 @@ dependencies = [ "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]] name = "clap" version = "2.34.0" @@ -693,6 +730,33 @@ dependencies = [ "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]] name = "dhall" version = "0.10.1" @@ -743,6 +807,7 @@ dependencies = [ "capnp-rpc", "chrono", "clap 3.1.6", + "desfire", "dirs", "erased-serde", "executor", @@ -751,9 +816,11 @@ dependencies = [ "futures-signals", "futures-test", "futures-util", + "hex", "inventory", "lazy_static", "libc", + "linkme", "lmdb-rkv", "nix", "once_cell", @@ -1194,6 +1261,9 @@ name = "hex" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +dependencies = [ + "serde", +] [[package]] name = "hmac" @@ -1508,6 +1578,17 @@ dependencies = [ "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]] name = "num-integer" version = "0.1.44" @@ -2335,6 +2416,12 @@ dependencies = [ "libc", ] +[[package]] +name = "simple-error" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc47a29ce97772ca5c927f75bac34866b16d64e07f330c3248e2d7226623901b" + [[package]] name = "slab" version = "0.4.5" diff --git a/Cargo.toml b/Cargo.toml index 148e4de..56c4c97 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -74,7 +74,10 @@ capnp-rpc = "0.14.1" # API Authentication #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" async-oneshot = "0.5" diff --git a/bffhd/authentication/fabfire/mod.rs b/bffhd/authentication/fabfire/mod.rs new file mode 100644 index 0000000..bedc15d --- /dev/null +++ b/bffhd/authentication/fabfire/mod.rs @@ -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", +)); diff --git a/bffhd/authentication/fabfire/server.rs b/bffhd/authentication/fabfire/server.rs new file mode 100644 index 0000000..bc37c06 --- /dev/null +++ b/bffhd/authentication/fabfire/server.rs @@ -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>, + key_new: Option>, +} + +struct KeyInfo { + key_id: u8, + key: Box<[u8]>, +} + +struct AuthInfo { + rnd_a: Vec, + rnd_b: Vec, + iv: Vec, +} + +#[derive(Debug, Deserialize, Serialize)] +#[serde(tag = "Cmd")] +enum CardCommand { + message { + #[serde(rename = "MssgID", skip_serializing_if = "Option::is_none")] + msg_id: Option, + #[serde(rename = "ClrTxt", skip_serializing_if = "Option::is_none")] + clr_txt: Option, + #[serde(rename = "AddnTxt", skip_serializing_if = "Option::is_none")] + addn_txt: Option, + }, + sendPICC { + #[serde(deserialize_with = "hex::deserialize", serialize_with = "hex::serialize_upper")] + data: Vec + }, + readPICC { + #[serde(deserialize_with = "hex::deserialize", serialize_with = "hex::serialize_upper")] + data: Vec + }, + haltPICC, + Key { + data: String + }, + ConfirmUser, +} + +enum Step { + New, + SelectApp, + VerifyMagic, + GetURN, + GetToken, + Authenticate1, + Authenticate2, +} + +pub struct FabFire { + step: Step, + card_info: Option, + key_info: Option, + auth_info: Option, + 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, 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::::try_from(buf) { + Ok(data) => data, + Err(e) => { + // eprintln!("Failed to convert APDUCommand to Vec: {:?}", 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::::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::::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::::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::(Arc::new(token.trim_matches(char::from(0)).to_string())); + let key = match session.get_property_or_callback::() { + 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::::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::::from(rnd_a), rnd_b, iv }); + let buf = match Vec::::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)); + } +} \ No newline at end of file diff --git a/bffhd/authentication.rs b/bffhd/authentication/mod.rs similarity index 84% rename from bffhd/authentication.rs rename to bffhd/authentication/mod.rs index 531889c..2db9149 100644 --- a/bffhd/authentication.rs +++ b/bffhd/authentication/mod.rs @@ -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)), } diff --git a/bffhd/users/mod.rs b/bffhd/users/mod.rs index eb1668c..cba881a 100644 --- a/bffhd/users/mod.rs +++ b/bffhd/users/mod.rs @@ -69,7 +69,7 @@ impl UserRef { static USERDB: OnceCell = OnceCell::new(); -#[derive(Copy, Clone)] +#[derive(Copy, Clone, Debug)] #[repr(transparent)] pub struct Users { userdb: &'static UserDB,