mirror of
https://gitlab.com/fabinfra/fabaccess/bffh.git
synced 2024-12-22 03:33:48 +01:00
Add support for binary FabReader Mechanism
This commit is contained in:
parent
0380e02f3f
commit
0716a75ee6
@ -2,7 +2,7 @@ mod server;
|
||||
pub use server::FabFire;
|
||||
|
||||
use rsasl::mechname::Mechname;
|
||||
use rsasl::registry::{Mechanism, MECHANISMS, Side};
|
||||
use rsasl::registry::{Mechanism, Side, MECHANISMS};
|
||||
|
||||
const MECHNAME: &'static Mechname = &Mechname::const_new_unchecked(b"X-FABFIRE");
|
||||
|
||||
@ -10,8 +10,8 @@ const MECHNAME: &'static Mechname = &Mechname::const_new_unchecked(b"X-FABFIRE")
|
||||
pub static FABFIRE: Mechanism =
|
||||
Mechanism::build(MECHNAME, 300, None, Some(FabFire::new_server), Side::Client);
|
||||
|
||||
use std::marker::PhantomData;
|
||||
use rsasl::property::SizedProperty;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
// All Property types must implement Debug.
|
||||
#[derive(Debug)]
|
||||
|
@ -3,7 +3,10 @@ use desfire::desfire::Desfire;
|
||||
use desfire::error::Error as DesfireError;
|
||||
use desfire::iso7816_4::apduresponse::APDUResponse;
|
||||
use rsasl::callback::SessionData;
|
||||
use rsasl::mechanism::{Authentication, MechanismData, MechanismError, MechanismErrorKind, State, ThisProvider};
|
||||
use rsasl::mechanism::{
|
||||
Authentication, Demand, DemandReply, MechanismData, MechanismError, MechanismErrorKind,
|
||||
Provider, State, ThisProvider,
|
||||
};
|
||||
use rsasl::prelude::{MessageSent, SASLConfig, SASLError, SessionError};
|
||||
use rsasl::property::AuthId;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@ -62,9 +65,7 @@ impl Display for FabFireError {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for FabFireError {
|
||||
|
||||
}
|
||||
impl std::error::Error for FabFireError {}
|
||||
|
||||
impl MechanismError for FabFireError {
|
||||
fn kind(&self) -> MechanismErrorKind {
|
||||
@ -92,6 +93,7 @@ struct CardInfo {
|
||||
}
|
||||
|
||||
struct KeyInfo {
|
||||
authid: String,
|
||||
key_id: u8,
|
||||
key: Box<[u8]>,
|
||||
}
|
||||
@ -493,11 +495,20 @@ impl Authentication for FabFire {
|
||||
Ok(_) => {
|
||||
match apdu_response.body {
|
||||
Some(data) => {
|
||||
let token = String::from_utf8(data).unwrap();
|
||||
let prov =
|
||||
ThisProvider::<AuthId>::with(token.trim_matches(char::from(0)));
|
||||
let key = session.need_with::<FabFireCardKey, _, _>(&prov, |key| Ok(Box::from(key.as_slice())))?;
|
||||
self.key_info = Some(KeyInfo { key_id: 0x01, key });
|
||||
let authid = String::from_utf8(data)
|
||||
.unwrap()
|
||||
.trim_matches(char::from(0))
|
||||
.to_string();
|
||||
let prov = ThisProvider::<AuthId>::with(&authid);
|
||||
let key = session
|
||||
.need_with::<FabFireCardKey, _, _>(&prov, |key| {
|
||||
Ok(Box::from(key.as_slice()))
|
||||
})?;
|
||||
self.key_info = Some(KeyInfo {
|
||||
authid,
|
||||
key_id: 0x01,
|
||||
key,
|
||||
});
|
||||
}
|
||||
None => {
|
||||
tracing::error!("No data in response");
|
||||
@ -679,6 +690,25 @@ impl Authentication for FabFire {
|
||||
writer
|
||||
.write_all(&send_buf)
|
||||
.map_err(|e| SessionError::Io { source: e })?;
|
||||
|
||||
struct Prov<'a> {
|
||||
authid: &'a str,
|
||||
}
|
||||
impl<'a> Provider<'a> for Prov<'a> {
|
||||
fn provide(
|
||||
&self,
|
||||
req: &mut Demand<'a>,
|
||||
) -> DemandReply<()>
|
||||
{
|
||||
req.provide_ref::<AuthId>(self.authid)?
|
||||
.done()
|
||||
}
|
||||
}
|
||||
let prov = Prov {
|
||||
authid: &self.key_info.as_ref().unwrap().authid,
|
||||
};
|
||||
session.validate(&prov)?;
|
||||
|
||||
return Ok(State::Finished(MessageSent::Yes));
|
||||
}
|
||||
Err(e) => {
|
||||
|
11
bffhd/authentication/fabfire_bin/mod.rs
Normal file
11
bffhd/authentication/fabfire_bin/mod.rs
Normal file
@ -0,0 +1,11 @@
|
||||
mod server;
|
||||
pub use server::FabFire;
|
||||
|
||||
use rsasl::mechname::Mechname;
|
||||
use rsasl::registry::{Mechanism, Side, MECHANISMS};
|
||||
|
||||
const MECHNAME: &'static Mechname = &Mechname::const_new_unchecked(b"X-FABFIRE-BIN");
|
||||
|
||||
#[linkme::distributed_slice(MECHANISMS)]
|
||||
pub static FABFIRE: Mechanism =
|
||||
Mechanism::build(MECHNAME, 300, None, Some(FabFire::new_server), Side::Client);
|
526
bffhd/authentication/fabfire_bin/server.rs
Normal file
526
bffhd/authentication/fabfire_bin/server.rs
Normal file
@ -0,0 +1,526 @@
|
||||
use desfire::desfire::desfire::MAX_BYTES_PER_TRANSACTION;
|
||||
use desfire::desfire::Desfire;
|
||||
use desfire::error::Error as DesfireError;
|
||||
use desfire::iso7816_4::apduresponse::APDUResponse;
|
||||
use rsasl::callback::SessionData;
|
||||
use rsasl::mechanism::{
|
||||
Authentication, Demand, DemandReply, MechanismData, MechanismError, MechanismErrorKind,
|
||||
Provider, State, ThisProvider,
|
||||
};
|
||||
use rsasl::prelude::{MessageSent, SASLConfig, SASLError, SessionError};
|
||||
use rsasl::property::AuthId;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::convert::TryFrom;
|
||||
use std::fmt::{Debug, Display, Formatter};
|
||||
use std::io::Write;
|
||||
use std::sync::Arc;
|
||||
|
||||
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 std::error::Error for FabFireError {}
|
||||
|
||||
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 {
|
||||
authid: String,
|
||||
key_id: u8,
|
||||
key: Box<[u8]>,
|
||||
}
|
||||
|
||||
struct AuthInfo {
|
||||
rnd_a: Vec<u8>,
|
||||
rnd_b: Vec<u8>,
|
||||
iv: Vec<u8>,
|
||||
}
|
||||
|
||||
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: &SASLConfig) -> Result<Box<dyn Authentication>, SASLError> {
|
||||
Ok(Box::new(Self {
|
||||
step: Step::New,
|
||||
card_info: None,
|
||||
key_info: None,
|
||||
auth_info: None,
|
||||
app_id: 0x464142,
|
||||
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 MechanismData<'_>,
|
||||
input: Option<&[u8]>,
|
||||
writer: &mut dyn Write,
|
||||
) -> Result<State, SessionError> {
|
||||
match self.step {
|
||||
Step::New => {
|
||||
tracing::trace!("Step: New");
|
||||
//receive card info (especially card UID) from reader
|
||||
return match input {
|
||||
None => Err(SessionError::InputDataRequired),
|
||||
Some(_) => {
|
||||
//select application
|
||||
return match self.desfire.select_application_cmd(self.app_id) {
|
||||
Ok(buf) => match Vec::<u8>::try_from(buf) {
|
||||
Ok(data) => {
|
||||
self.step = Step::SelectApp;
|
||||
writer
|
||||
.write_all(&data)
|
||||
.map_err(|e| SessionError::Io { source: e })?;
|
||||
Ok(State::Running)
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!(
|
||||
"Failed to convert APDUCommand to Vec<u8>: {:?}",
|
||||
e
|
||||
);
|
||||
return Err(FabFireError::SerializationError.into());
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
tracing::error!("Failed to generate APDUCommand: {:?}", e);
|
||||
return Err(FabFireError::SerializationError.into());
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
Step::SelectApp => {
|
||||
tracing::trace!("Step: SelectApp");
|
||||
// check that we successfully selected the application
|
||||
|
||||
let apdu_response = match input {
|
||||
Some(data) => APDUResponse::new(data),
|
||||
None => return Err(SessionError::InputDataRequired),
|
||||
};
|
||||
|
||||
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;
|
||||
|
||||
return match self
|
||||
.desfire
|
||||
.read_data_chunk_cmd(MAGIC_FILE_ID, 0, MAGIC.len())
|
||||
{
|
||||
Ok(buf) => match Vec::<u8>::try_from(buf) {
|
||||
Ok(data) => {
|
||||
self.step = Step::VerifyMagic;
|
||||
writer
|
||||
.write_all(&data)
|
||||
.map_err(|e| SessionError::Io { source: e })?;
|
||||
Ok(State::Running)
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!("Failed to convert APDUCommand to Vec<u8>: {:?}", e);
|
||||
return Err(FabFireError::SerializationError.into());
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
tracing::error!("Failed to generate APDUCommand: {:?}", e);
|
||||
return Err(FabFireError::SerializationError.into());
|
||||
}
|
||||
};
|
||||
}
|
||||
Step::VerifyMagic => {
|
||||
tracing::trace!("Step: VerifyMagic");
|
||||
// verify the magic string to determine that we have a valid fabfire card
|
||||
let apdu_response = match input {
|
||||
Some(data) => APDUResponse::new(data),
|
||||
None => return Err(SessionError::InputDataRequired),
|
||||
};
|
||||
|
||||
match apdu_response.check() {
|
||||
Ok(_) => {
|
||||
match apdu_response.body {
|
||||
Some(data) => {
|
||||
if std::str::from_utf8(data.as_slice()) != Ok(MAGIC) {
|
||||
tracing::error!("Invalid magic string");
|
||||
return Err(FabFireError::ParseError.into());
|
||||
}
|
||||
}
|
||||
None => {
|
||||
tracing::error!("No data returned from card");
|
||||
return Err(FabFireError::ParseError.into());
|
||||
}
|
||||
};
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!("Got invalid APDUResponse: {:?}", e);
|
||||
return Err(FabFireError::ParseError.into());
|
||||
}
|
||||
}
|
||||
|
||||
// request the contents of the file containing the URN
|
||||
const URN_FILE_ID: u8 = 0x02;
|
||||
|
||||
return 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) => {
|
||||
self.step = Step::GetURN;
|
||||
writer
|
||||
.write_all(&data)
|
||||
.map_err(|e| SessionError::Io { source: e })?;
|
||||
Ok(State::Running)
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!("Failed to convert APDUCommand to Vec<u8>: {:?}", e);
|
||||
return Err(FabFireError::SerializationError.into());
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
tracing::error!("Failed to generate APDUCommand: {:?}", e);
|
||||
return Err(FabFireError::SerializationError.into());
|
||||
}
|
||||
};
|
||||
}
|
||||
Step::GetURN => {
|
||||
tracing::trace!("Step: GetURN");
|
||||
// parse the urn and match it to our local urn
|
||||
let apdu_response = match input {
|
||||
Some(data) => APDUResponse::new(data),
|
||||
None => return Err(SessionError::InputDataRequired),
|
||||
};
|
||||
|
||||
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 {
|
||||
tracing::error!(
|
||||
"URN mismatch: {:?} != {:?}",
|
||||
received_urn,
|
||||
self.local_urn
|
||||
);
|
||||
return Err(FabFireError::ParseError.into());
|
||||
}
|
||||
}
|
||||
None => {
|
||||
tracing::error!("No data returned from card");
|
||||
return Err(FabFireError::ParseError.into());
|
||||
}
|
||||
};
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!("Got invalid APDUResponse: {:?}", e);
|
||||
return Err(FabFireError::ParseError.into());
|
||||
}
|
||||
}
|
||||
// request the contents of the file containing the URN
|
||||
const TOKEN_FILE_ID: u8 = 0x03;
|
||||
|
||||
return 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) => {
|
||||
self.step = Step::GetToken;
|
||||
writer
|
||||
.write_all(&data)
|
||||
.map_err(|e| SessionError::Io { source: e })?;
|
||||
Ok(State::Running)
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!("Failed to convert APDUCommand to Vec<u8>: {:?}", e);
|
||||
return Err(FabFireError::SerializationError.into());
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
tracing::error!("Failed to generate APDUCommand: {:?}", e);
|
||||
return Err(FabFireError::SerializationError.into());
|
||||
}
|
||||
};
|
||||
}
|
||||
Step::GetToken => {
|
||||
// println!("Step: GetToken");
|
||||
// parse the token and select the appropriate user
|
||||
let apdu_response = match input {
|
||||
Some(data) => APDUResponse::new(data),
|
||||
None => return Err(SessionError::InputDataRequired),
|
||||
};
|
||||
|
||||
match apdu_response.check() {
|
||||
Ok(_) => {
|
||||
match apdu_response.body {
|
||||
Some(data) => {
|
||||
let authid = String::from_utf8(data)
|
||||
.unwrap()
|
||||
.trim_matches(char::from(0))
|
||||
.to_string();
|
||||
let prov = ThisProvider::<AuthId>::with(&authid);
|
||||
let key = session
|
||||
.need_with::<FabFireCardKey, _, _>(&prov, |key| {
|
||||
Ok(Box::from(key.as_slice()))
|
||||
})?;
|
||||
self.key_info = Some(KeyInfo {
|
||||
authid,
|
||||
key_id: 0x01,
|
||||
key,
|
||||
});
|
||||
}
|
||||
None => {
|
||||
tracing::error!("No data in response");
|
||||
return Err(FabFireError::ParseError.into());
|
||||
}
|
||||
};
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!("Failed to check response: {:?}", e);
|
||||
return Err(FabFireError::ParseError.into());
|
||||
}
|
||||
}
|
||||
|
||||
return 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) => {
|
||||
self.step = Step::Authenticate1;
|
||||
writer
|
||||
.write_all(&data)
|
||||
.map_err(|e| SessionError::Io { source: e })?;
|
||||
Ok(State::Running)
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!("Failed to convert to Vec<u8>: {:?}", e);
|
||||
return Err(FabFireError::SerializationError.into());
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
tracing::error!("Failed to create authenticate command: {:?}", e);
|
||||
return Err(FabFireError::SerializationError.into());
|
||||
}
|
||||
};
|
||||
}
|
||||
Step::Authenticate1 => {
|
||||
tracing::trace!("Step: Authenticate1");
|
||||
let apdu_response = match input {
|
||||
Some(data) => APDUResponse::new(data),
|
||||
None => return Err(SessionError::InputDataRequired),
|
||||
};
|
||||
|
||||
return 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();
|
||||
|
||||
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,
|
||||
});
|
||||
match Vec::<u8>::try_from(cmd_challenge_response) {
|
||||
Ok(data) => {
|
||||
self.step = Step::Authenticate2;
|
||||
writer
|
||||
.write_all(&data)
|
||||
.map_err(|e| SessionError::Io { source: e })?;
|
||||
Ok(State::Running)
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!("Failed to convert to Vec<u8>: {:?}", e);
|
||||
return Err(FabFireError::SerializationError.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
tracing::error!("Got invalid response: {:?}", apdu_response);
|
||||
Err(FabFireError::ParseError.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!("Failed to check response: {:?}", e);
|
||||
Err(FabFireError::ParseError.into())
|
||||
}
|
||||
};
|
||||
}
|
||||
Step::Authenticate2 => {
|
||||
// println!("Step: Authenticate2");
|
||||
let apdu_response = match input {
|
||||
Some(data) => APDUResponse::new(data),
|
||||
None => return Err(SessionError::InputDataRequired),
|
||||
};
|
||||
|
||||
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()
|
||||
{
|
||||
struct Prov<'a> {
|
||||
authid: &'a str,
|
||||
}
|
||||
impl<'a> Provider<'a> for Prov<'a> {
|
||||
fn provide(
|
||||
&self,
|
||||
req: &mut Demand<'a>,
|
||||
) -> DemandReply<()>
|
||||
{
|
||||
req.provide_ref::<AuthId>(self.authid)?.done()
|
||||
}
|
||||
}
|
||||
let prov = Prov {
|
||||
authid: &self.key_info.as_ref().unwrap().authid,
|
||||
};
|
||||
session.validate(&prov)?;
|
||||
return Ok(State::Finished(MessageSent::Yes));
|
||||
}
|
||||
}
|
||||
},
|
||||
None => {
|
||||
tracing::error!("got empty response");
|
||||
return Err(FabFireError::ParseError.into());
|
||||
}
|
||||
};
|
||||
}
|
||||
Err(_e) => {
|
||||
tracing::error!("Got invalid response: {:?}", apdu_response);
|
||||
return Err(
|
||||
FabFireError::InvalidCredentials(format!("{}", apdu_response)).into(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(State::Finished(MessageSent::No));
|
||||
}
|
||||
}
|
@ -1,16 +1,17 @@
|
||||
use crate::users::Users;
|
||||
use miette::{IntoDiagnostic, WrapErr};
|
||||
use std::sync::Arc;
|
||||
use rsasl::callback::{CallbackError, Request, SessionCallback, SessionData, Context};
|
||||
use rsasl::callback::{CallbackError, Context, Request, SessionCallback, SessionData};
|
||||
use rsasl::mechanism::SessionError;
|
||||
use rsasl::prelude::{Mechname, SASLConfig, SASLServer, Session, Validation};
|
||||
use rsasl::property::{AuthId, AuthzId, Password};
|
||||
use rsasl::validate::{Validate, ValidationError};
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::authentication::fabfire::FabFireCardKey;
|
||||
use crate::users::db::User;
|
||||
|
||||
mod fabfire;
|
||||
mod fabfire_bin;
|
||||
|
||||
struct Callback {
|
||||
users: Users,
|
||||
@ -23,41 +24,55 @@ impl Callback {
|
||||
}
|
||||
}
|
||||
impl SessionCallback for Callback {
|
||||
fn callback(&self, session_data: &SessionData, context: &Context, request: &mut Request) -> Result<(), SessionError> {
|
||||
fn callback(
|
||||
&self,
|
||||
session_data: &SessionData,
|
||||
context: &Context,
|
||||
request: &mut Request,
|
||||
) -> Result<(), SessionError> {
|
||||
if let Some(authid) = context.get_ref::<AuthId>() {
|
||||
request.satisfy_with::<FabFireCardKey, _>(|| {
|
||||
let user = self.users.get_user(authid).ok_or(CallbackError::NoValue)?;
|
||||
let kv = user.userdata.kv.get("cardkey").ok_or(CallbackError::NoValue)?;
|
||||
let card_key = <[u8; 16]>::try_from(
|
||||
hex::decode(kv).map_err(|_| CallbackError::NoValue)?,
|
||||
).map_err(|_| CallbackError::NoValue)?;
|
||||
let kv = user
|
||||
.userdata
|
||||
.kv
|
||||
.get("cardkey")
|
||||
.ok_or(CallbackError::NoValue)?;
|
||||
let card_key =
|
||||
<[u8; 16]>::try_from(hex::decode(kv).map_err(|_| CallbackError::NoValue)?)
|
||||
.map_err(|_| CallbackError::NoValue)?;
|
||||
Ok(card_key)
|
||||
})?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn validate(&self, session_data: &SessionData, context: &Context, validate: &mut Validate<'_>) -> Result<(), ValidationError> {
|
||||
fn validate(
|
||||
&self,
|
||||
session_data: &SessionData,
|
||||
context: &Context,
|
||||
validate: &mut Validate<'_>,
|
||||
) -> Result<(), ValidationError> {
|
||||
let span = tracing::info_span!(parent: &self.span, "validate");
|
||||
let _guard = span.enter();
|
||||
if validate.is::<V>() {
|
||||
match session_data.mechanism().mechanism.as_str() {
|
||||
"PLAIN" => {
|
||||
let authcid = context.get_ref::<AuthId>()
|
||||
let authcid = context
|
||||
.get_ref::<AuthId>()
|
||||
.ok_or(ValidationError::MissingRequiredProperty)?;
|
||||
let authzid = context.get_ref::<AuthzId>();
|
||||
let password = context.get_ref::<Password>()
|
||||
let password = context
|
||||
.get_ref::<Password>()
|
||||
.ok_or(ValidationError::MissingRequiredProperty)?;
|
||||
|
||||
if authzid.is_some() {
|
||||
return Ok(())
|
||||
}
|
||||
// if authzid.is_some() {
|
||||
// return Ok(())
|
||||
// }
|
||||
|
||||
if let Some(user) = self.users.get_user(authcid) {
|
||||
match user.check_password(password) {
|
||||
Ok(true) => {
|
||||
validate.finalize::<V>(user)
|
||||
}
|
||||
Ok(true) => validate.finalize::<V>(user),
|
||||
Ok(false) => {
|
||||
tracing::warn!(authid=%authcid, "AUTH FAILED: bad password");
|
||||
}
|
||||
@ -69,6 +84,14 @@ impl SessionCallback for Callback {
|
||||
tracing::warn!(authid=%authcid, "AUTH FAILED: no such user");
|
||||
}
|
||||
}
|
||||
"X-FABFIRE" | "X-FABFIRE-BIN" => {
|
||||
let authcid = context
|
||||
.get_ref::<AuthId>()
|
||||
.ok_or(ValidationError::MissingRequiredProperty)?;
|
||||
if let Some(user) = self.users.get_user(authcid) {
|
||||
validate.finalize::<V>(user)
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
@ -2,21 +2,21 @@ use capnp::capability::Promise;
|
||||
use capnp::Error;
|
||||
use capnp_rpc::pry;
|
||||
use rsasl::mechname::Mechname;
|
||||
use rsasl::prelude::State as SaslState;
|
||||
use rsasl::prelude::{MessageSent, Session};
|
||||
use rsasl::property::AuthId;
|
||||
use std::fmt;
|
||||
use std::fmt::{Formatter, Write};
|
||||
use std::io::Cursor;
|
||||
use rsasl::prelude::{MessageSent, Session};
|
||||
use rsasl::prelude::State as SaslState;
|
||||
use tracing::Span;
|
||||
|
||||
use crate::authentication::V;
|
||||
use crate::capnp::session::APISession;
|
||||
use crate::session::SessionManager;
|
||||
use api::authenticationsystem_capnp::authentication::{
|
||||
AbortParams, AbortResults, Server as AuthenticationSystem, StepParams, StepResults,
|
||||
};
|
||||
use api::authenticationsystem_capnp::{response, response::Error as ErrorCode};
|
||||
use crate::authentication::V;
|
||||
|
||||
const TARGET: &str = "bffh::api::authenticationsystem";
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
use super::Initiator;
|
||||
use super::InitiatorCallbacks;
|
||||
use crate::resources::modules::fabaccess::Status;
|
||||
use crate::resources::state::State;
|
||||
use crate::utils::linebuffer::LineBuffer;
|
||||
use async_process::{Child, ChildStderr, ChildStdout, Command, Stdio};
|
||||
@ -11,7 +12,6 @@ use std::future::Future;
|
||||
use std::io;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
use crate::resources::modules::fabaccess::Status;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub enum InputMessage {
|
||||
@ -63,7 +63,12 @@ struct ProcessState {
|
||||
|
||||
impl ProcessState {
|
||||
pub fn new(stdout: ChildStdout, stderr: ChildStderr, child: Child) -> Self {
|
||||
Self { stdout, stderr, stderr_closed: false, child }
|
||||
Self {
|
||||
stdout,
|
||||
stderr,
|
||||
stderr_closed: false,
|
||||
child,
|
||||
}
|
||||
}
|
||||
|
||||
fn try_process(&mut self, buffer: &[u8], callbacks: &mut InitiatorCallbacks) -> usize {
|
||||
@ -100,7 +105,9 @@ impl ProcessState {
|
||||
let InputMessage::SetState(status) = state;
|
||||
callbacks.set_status(status);
|
||||
}
|
||||
Err(error) => tracing::warn!(%error, "process initiator did not send a valid line"),
|
||||
Err(error) => {
|
||||
tracing::warn!(%error, "process initiator did not send a valid line")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -202,8 +209,8 @@ impl Future for Process {
|
||||
|
||||
impl Initiator for Process {
|
||||
fn new(params: &HashMap<String, String>, callbacks: InitiatorCallbacks) -> miette::Result<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let cmd = params
|
||||
.get("cmd")
|
||||
|
@ -1,10 +1,10 @@
|
||||
use crate::authorization::permissions::Permission;
|
||||
use crate::authorization::roles::Roles;
|
||||
use crate::resources::Resource;
|
||||
use crate::users::db::User;
|
||||
use crate::users::{db, UserRef};
|
||||
use crate::Users;
|
||||
use tracing::Span;
|
||||
use crate::users::db::User;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SessionManager {
|
||||
@ -18,7 +18,9 @@ impl SessionManager {
|
||||
}
|
||||
|
||||
pub fn try_open(&self, parent: &Span, uid: impl AsRef<str>) -> Option<SessionHandle> {
|
||||
self.users.get_user(uid.as_ref()).map(|user| self.open(parent, user))
|
||||
self.users
|
||||
.get_user(uid.as_ref())
|
||||
.map(|user| self.open(parent, user))
|
||||
}
|
||||
|
||||
// TODO: make infallible
|
||||
|
Loading…
Reference in New Issue
Block a user