diff --git a/Cargo.lock b/Cargo.lock index 909cc1b..33d8a7e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -958,6 +958,9 @@ name = "hex" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +dependencies = [ + "serde", +] [[package]] name = "hmac" diff --git a/Cargo.toml b/Cargo.toml index 03f35d1..ca7a144 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -77,7 +77,7 @@ async-rustls = "0.2" # Desfire desfire = { git = "https://gitlab.com/fabinfra/fabaccess/nfc_rs.git", branch = "main" } -hex = "0.4.3" +hex = { version = "0.4.3", features = ["serde"] } linkme = "0.2" [build-dependencies] diff --git a/examples/users.toml b/examples/users.toml index 719f2fb..59b267a 100644 --- a/examples/users.toml +++ b/examples/users.toml @@ -11,3 +11,4 @@ passwd = "secret" # It will get stored in the `kv` field in UserData. # This is not used for anything at the moment noot = "noot!" +cardkey = "7ab8704a61b5317e1fe4cae9e3e1fd8d" diff --git a/src/api/auth.rs b/src/api/auth.rs index 85a7c8c..22c92af 100644 --- a/src/api/auth.rs +++ b/src/api/auth.rs @@ -6,6 +6,7 @@ use std::sync::Arc; use std::rc::Rc; use std::cell::RefCell; +use std::convert::TryFrom; use std::io::Cursor; @@ -18,7 +19,7 @@ use rsasl::callback::Callback; use rsasl::error::SessionError; use rsasl::mechname::Mechname; use rsasl::property::{AuthId, Password}; -use rsasl::SASL; +use rsasl::{Property, SASL}; use rsasl::session::Step; use rsasl::validate::{Validation, validations}; @@ -32,6 +33,7 @@ use crate::db::access::AccessControl as AccessDB; mod fabfire; use fabfire::FABFIRE; +use crate::api::auth::fabfire::FabFireCardKey; pub struct AppData { userdb: Arc, @@ -73,10 +75,35 @@ impl Callback for CB { }; Err(ret) } + + fn provide_prop( + &self, + session: &mut rsasl::session::SessionData, + property: Property, + ) -> Result<(), SessionError> { + match property { + FABFIRECARDKEY => { + // Access the authentication id, i.e. the username to check the password for + let authcid = session.get_property_or_callback::()?; + println!("auth'ing user {:?}", authcid); + self.userdb.get_user(authcid.unwrap().as_ref()).map(|user| { + let kvs= user.unwrap().data.kv; + println!("kvs: {:?}", kvs); + kvs.get("cardkey").map(|key| { + session.set_property::(Arc::new(<[u8; 16]>::try_from(hex::decode(key).unwrap()).unwrap())); + }); + }).ok(); + + Ok(()) + } + _ => Err(SessionError::NoProperty { property }), + } + } } pub struct Auth { pub ctx: SASL, + sasl_session: Option, session: Rc>>, userdb: Arc, access: Arc, @@ -89,7 +116,7 @@ impl Auth { ctx.register(&FABFIRE); ctx.install_callback(Arc::new(CB::new(dbs.userdb.clone()))); - Self { log, ctx, session, userdb: dbs.userdb.clone(), access: dbs.access.clone() } + Self { log, ctx, sasl_session: None, session, userdb: dbs.userdb.clone(), access: dbs.access.clone() } } } @@ -134,6 +161,7 @@ impl authentication_system::Server for Auth { // Or fail at that and thrown an exception TODO: return Outcome let mech = pry!(req.get_mechanism()); if !((mech == "PLAIN") || (mech == "X-FABFIRE")) { + debug!(self.log, "Invalid SASL mech: {}", mech); return Promise::err(capnp::Error { kind: capnp::ErrorKind::Failed, description: format!("Invalid SASL mech"), @@ -142,8 +170,8 @@ impl authentication_system::Server for Auth { let mech = Mechname::new(mech.as_bytes()).unwrap(); - let mut session = match self.ctx.server_start(mech) { - Ok(s) => s, + self.sasl_session = match self.ctx.server_start(mech) { + Ok(s) => Some(s), Err(e) => return Promise::err(capnp::Error { kind: capnp::ErrorKind::Failed, @@ -164,10 +192,11 @@ impl authentication_system::Server for Auth { Ok(Which::None(_)) => { // FIXME: Actually this needs to indicate NO data instead of SOME data of 0 length - session.step(Option::<&[u8]>::None, &mut out) + self.sasl_session.as_mut().unwrap().step(Option::<&[u8]>::None, &mut out) } Ok(Which::Initial(data)) => { - session.step(Some(pry!(data)), &mut out) + debug!(self.log, "running step() with initial data"); + self.sasl_session.as_mut().unwrap().step(Some(pry!(data)), &mut out) } }; @@ -176,7 +205,7 @@ impl authentication_system::Server for Auth { match step_res { Ok(Step::Done(b)) => { - let user = session + let user = self.sasl_session.as_mut().unwrap() .get_property::() .and_then(|data| { self.userdb.get_user(data.as_str()).unwrap() @@ -207,8 +236,67 @@ impl authentication_system::Server for Auth { Promise::ok(()) } Err(e) => { + error!(self.log, "SASL Auth failed: {}", e); let mut outcome = pry!(res.get().get_response()).init_outcome(); - outcome.reborrow().set_result(response::Result::InvalidCredentials); + outcome.reborrow().set_result(response::Result::InvalidCredentials); //FIXME: Check if problem where invalid creds or something else + let text = format!("{}", e); + outcome.set_help_text(&text); + Promise::ok(()) + } + } + } + + fn step(&mut self, params: authentication_system::StepParams, mut res: authentication_system::StepResults) -> Promise<(), capnp::Error> { + let resp = pry!(pry!(params.get()).get_response()); + + let mut out = Cursor::new(Vec::new()); + + // Use the data the Client has provided + let step_res = self.sasl_session.as_mut().unwrap().step(Some(resp), &mut out); + + + debug!(self.log, "running step() with additional data"); + + + // The step may either return an error, a success or the need for more data + // TODO: Set the session user. Needs a lookup though <.> + + match step_res { + Ok(Step::Done(b)) => { + let user = self.sasl_session.as_mut().unwrap() + .get_property::() + .and_then(|data| { + self.userdb.get_user(data.as_str()).unwrap() + }) + .expect("Authentication returned OK but the given AuthId is invalid"); + + let perms = pry!(self.access.collect_permrules(&user.data) + .map_err(|e| capnp::Error::failed(format!("AccessDB lookup failed: {}", e)))); + self.session.replace(Some(Session::new( + self.log.new(o!()), + user.id, + "".to_string(), + user.data.roles.into_boxed_slice(), + perms.into_boxed_slice() + ))); + + let mut outcome = pry!(res.get().get_response()).init_outcome(); + outcome.reborrow().set_result(response::Result::Successful); + if b.is_some() { + outcome.init_additional_data().set_additional(&out.get_ref()); + } + Promise::ok(()) + }, + Ok(Step::NeedsMore(b)) => { + if b.is_some() { + pry!(res.get().get_response()).set_challence(&out.get_ref()); + } + Promise::ok(()) + } + Err(e) => { + error!(self.log, "SASL Auth failed: {}", e); + let mut outcome = pry!(res.get().get_response()).init_outcome(); + outcome.reborrow().set_result(response::Result::InvalidCredentials); //FIXME: Check if problem where invalid creds or something else let text = format!("{}", e); outcome.set_help_text(&text); Promise::ok(()) diff --git a/src/api/auth/fabfire.rs b/src/api/auth/fabfire.rs index 9c9c43d..bedc15d 100644 --- a/src/api/auth/fabfire.rs +++ b/src/api/auth/fabfire.rs @@ -18,3 +18,27 @@ pub static FABFIRE: Mechanism = Mechanism { 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/src/api/auth/fabfire/server.rs b/src/api/auth/fabfire/server.rs index ed76e82..32647b0 100644 --- a/src/api/auth/fabfire/server.rs +++ b/src/api/auth/fabfire/server.rs @@ -6,16 +6,24 @@ use rsasl::SASL; use rsasl::session::{SessionData, StepResult}; use serde::{Deserialize, Serialize}; use desfire::desfire::Desfire; -use desfire::iso7816_4::apducommand::APDUCommand; use desfire::iso7816_4::apduresponse::APDUResponse; -use desfire::error::{Error as DesfireError, Error}; +use desfire::error::{Error as DesfireError}; use std::convert::TryFrom; -use std::ops::Deref; +use std::sync::Arc; +use desfire::desfire::desfire::MAX_BYTES_PER_TRANSACTION; +use rsasl::property::AuthId; +use crate::api::auth::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 { @@ -23,7 +31,13 @@ impl Debug for FabFireError { 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), } } } @@ -33,7 +47,13 @@ impl Display for FabFireError { 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), } } } @@ -43,45 +63,61 @@ impl MechanismError for FabFireError { 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 { - card_uid: [u8; 7], + #[serde(rename = "UID", with = "hex")] + uid: [u8; 7], key_old: Option>, - key_new: Option> + key_new: Option>, } struct KeyInfo { key_id: u8, - key: Box<[u8]> + key: Box<[u8]>, } struct AuthInfo { rnd_a: Vec, rnd_b: Vec, - iv: 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 { - data: String + #[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 + ConfirmUser, } enum Step { @@ -92,7 +128,6 @@ enum Step { GetToken, Authenticate1, Authenticate2, - Authenticate3, } pub struct FabFire { @@ -117,52 +152,70 @@ 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) }, + None => { Err(SessionError::InputDataRequired) } Some(cardinfo) => { self.card_info = match serde_json::from_slice(cardinfo) { Ok(card_info) => Some(card_info), - Err(_) => { - return Err(FabFireError::ParseError.into()) + 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()) } }; - self.step = Step::SelectApp; - Ok(rsasl::session::Step::NeedsMore(None)) } - } + }; } Step::SelectApp => { - //select application - let buf = match self.desfire.select_application_cmd(self.app_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: hex::encode_upper(buf) }; - return match serde_json::to_writer(writer, &cmd) { - Ok(_) => { - self.step = Step::VerifyMagic; - Ok(rsasl::session::Step::NeedsMore(None)) - } - Err(_) => { - Err(FabFireError::SerializationError.into()) - } - } - } - Step::VerifyMagic => { + // println!("Step: SelectApp"); // check that we successfully selected the application - let response = match input { - None => {return Err(SessionError::InputDataRequired)}, - Some(buf) => APDUResponse::new(buf) + 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()); + } + } }; - response.check().map_err(|e| FabFireError::CardError(e))?; + + 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; @@ -171,40 +224,58 @@ impl Authentication for FabFire { Ok(buf) => match Vec::::try_from(buf) { Ok(data) => data, Err(_) => { - return Err(FabFireError::SerializationError.into()) + return Err(FabFireError::SerializationError.into()); } }, Err(_) => { - return Err(FabFireError::SerializationError.into()) + return Err(FabFireError::SerializationError.into()); } }; - let cmd = CardCommand::sendPICC { data: hex::encode_upper(buf) }; - return match serde_json::to_writer(writer, &cmd) { - Ok(_) => { - self.step = Step::GetURN; - Ok(rsasl::session::Step::NeedsMore(None)) + 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::GetURN => { - // verify the magic string to determine that we have a valid fabfire card - let response = match input { - None => {return Err(SessionError::InputDataRequired)}, - Some(buf) => APDUResponse::new(buf) }; - match response.check() { + } + 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 response.body { + 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()) + return Err(FabFireError::ParseError.into()); } }; } @@ -221,92 +292,143 @@ impl Authentication for FabFire { Ok(buf) => match Vec::::try_from(buf) { Ok(data) => data, Err(_) => { - return Err(FabFireError::SerializationError.into()) + return Err(FabFireError::SerializationError.into()); } }, Err(_) => { - return Err(FabFireError::SerializationError.into()) + return Err(FabFireError::SerializationError.into()); } }; - let cmd = CardCommand::sendPICC { data: hex::encode_upper(buf) }; - return match serde_json::to_writer(writer, &cmd) { - Ok(_) => { - self.step = Step::GetToken; - Ok(rsasl::session::Step::NeedsMore(None)) + 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::GetToken => { - // parse the urn and match it to our local urn - let response = match input { - None => {return Err(SessionError::InputDataRequired)}, - Some(buf) => APDUResponse::new(buf) }; - match response.check() { + } + 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 response.body { + match apdu_response.body { Some(data) => { - if String::from_utf8(data).unwrap() != self.local_urn { + 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 => { - return Err(FabFireError::ParseError.into()) + // eprintln!("No data in response"); + return Err(FabFireError::ParseError.into()); } }; } - Err(_) => { + 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, 47) { // TODO: support data longer than 47 Bytes + 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()) + return Err(FabFireError::SerializationError.into()); } }, Err(_) => { - return Err(FabFireError::SerializationError.into()) + return Err(FabFireError::SerializationError.into()); } }; - let cmd = CardCommand::sendPICC { data: hex::encode_upper(buf) }; - return match serde_json::to_writer(writer, &cmd) { - Ok(_) => { - self.step = Step::Authenticate1; - Ok(rsasl::session::Step::NeedsMore(None)) + 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::Authenticate1 => { - // parse the token and select the appropriate user - let response = match input { - None => {return Err(SessionError::InputDataRequired)}, - Some(buf) => APDUResponse::new(buf) }; - match response.check() { + } + 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 response.body { + match apdu_response.body { Some(data) => { - if String::from_utf8(data).unwrap() != "LoremIpsum" { // FIXME: match against user db - return Err(FabFireError::ParseError.into()); - } + 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 => { - return Err(FabFireError::ParseError.into()) + // eprintln!("No data in response"); + return Err(FabFireError::ParseError.into()); } }; } - Err(_) => { + Err(e) => { + // eprintln!("Invalid response: {:?}", e); return Err(FabFireError::ParseError.into()); } } @@ -315,62 +437,85 @@ impl Authentication for FabFire { Ok(buf) => match Vec::::try_from(buf) { Ok(data) => data, Err(_) => { - return Err(FabFireError::SerializationError.into()) + return Err(FabFireError::SerializationError.into()); } }, Err(_) => { - return Err(FabFireError::SerializationError.into()) + return Err(FabFireError::SerializationError.into()); } }; - let cmd = CardCommand::sendPICC { data: hex::encode_upper(buf) }; - return match serde_json::to_writer(writer, &cmd) { - Ok(_) => { - self.step = Step::Authenticate2; - Ok(rsasl::session::Step::NeedsMore(None)) + 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::Authenticate2 => { - let response = match input { - None => {return Err(SessionError::InputDataRequired)}, - Some(buf) => APDUResponse::new(buf) }; - match response.check() { + } + 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 response.body { + 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); + // 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 (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()) - } + Ok(data) => data, + Err(_) => { + return Err(FabFireError::SerializationError.into()); + } }; - let cmd = CardCommand::sendPICC { data: hex::encode_upper(buf) }; - return match serde_json::to_writer(writer, &cmd) { - Ok(_) => { - self.step = Step::Authenticate3; - Ok(rsasl::session::Step::NeedsMore(None)) + 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()) + return Err(FabFireError::ParseError.into()); } }; } @@ -379,36 +524,67 @@ impl Authentication for FabFire { } } } - Step::Authenticate3 => { - let response = match input { - None => {return Err(SessionError::InputDataRequired)}, - Some(buf) => APDUResponse::new(buf) + 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()); + } + } }; - match response.check() { + + 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 response.body { + match apdu_response.body { Some(data) => { match self.auth_info.as_ref() { - None => {return Err(FabFireError::ParseError.into())} + 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() { - // TODO: Do stuff with the info that we are authenticated - return Ok(rsasl::session::Step::Done(None)); + + 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()) + return Err(FabFireError::ParseError.into()); } }; } Err(_) => { - return Err(FabFireError::ParseError.into()); + return Err(FabFireError::InvalidCredentials(format!("{}", apdu_response)).into()); } } } @@ -416,5 +592,4 @@ impl Authentication for FabFire { return Ok(rsasl::session::Step::Done(None)); } - } \ No newline at end of file diff --git a/src/db/user.rs b/src/db/user.rs index 3a200dd..6d37d35 100644 --- a/src/db/user.rs +++ b/src/db/user.rs @@ -95,7 +95,7 @@ pub struct UserData { /// Additional data storage #[serde(flatten, skip_serializing_if = "HashMap::is_empty")] - kv: HashMap, + pub kv: HashMap, } impl UserData {