diff --git a/Cargo.lock b/Cargo.lock index 3a48440..338e283 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 = "annotate-snippets" version = "0.9.1" @@ -242,7 +254,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", @@ -266,6 +278,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" @@ -275,6 +297,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.1.0" @@ -367,6 +395,15 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[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" @@ -436,6 +473,33 @@ dependencies = [ "typenum", ] +[[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.11.0" @@ -485,6 +549,7 @@ dependencies = [ "capnp-rpc", "capnpc", "clap", + "desfire", "easy-parallel", "flexbuffers", "futures 0.3.21", @@ -493,8 +558,10 @@ dependencies = [ "futures-test", "futures-util", "genawaiter", + "hex", "lazy_static", "libc", + "linkme", "lmdb-rkv", "rand", "rsasl", @@ -892,6 +959,9 @@ name = "hex" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +dependencies = [ + "serde", +] [[package]] name = "hmac" @@ -1131,6 +1201,26 @@ 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-traits" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +dependencies = [ + "autocfg", +] + [[package]] name = "num_cpus" version = "1.13.1" @@ -1725,6 +1815,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 535308b..a60fa87 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,8 +47,8 @@ uuid = { version = "0.8.2", features = ["serde", "v4"] } clap = "2.33.3" # TODO update this if bindgen breaks (again) -rsasl = "2.0.0-preview2" -#rsasl = { path = "../../rsasl" } +rsasl = { version = "2.0.0-preview2", features = ["unstable_custom_mechanism", "registry_static"] } +#rsasl = { path = "../../rsasl", features = ["unstable_custom_mechanism", "registry_static"] } rumqttc = { version = "0.10", features = ["url"] } async-compat = "0.2.1" @@ -75,6 +75,11 @@ rustls = "0.20" rustls-pemfile = "0.2" futures-rustls = "0.22.0" +# Desfire +desfire = "0.2.0-alpha1" +hex = { version = "0.4.3", features = ["serde"] } +linkme = "0.2" + [build-dependencies] capnpc = "0.14.4" # Used in build.rs to iterate over all files in schema/ diff --git a/Dockerfile.dev b/Dockerfile.dev new file mode 100644 index 0000000..9a52f58 --- /dev/null +++ b/Dockerfile.dev @@ -0,0 +1,7 @@ +# Setup build image for multistage build +FROM rust:latest +# install build deps +RUN apt-get update && apt-get upgrade -y +RUN apt-get install -yqq --no-install-recommends capnproto build-essential cmake clang libclang-dev libgsasl7-dev + +COPY ../nfc_rs /nfc_rs \ No newline at end of file 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.rs b/src/api.rs index c9ae961..a928171 100644 --- a/src/api.rs +++ b/src/api.rs @@ -41,6 +41,7 @@ impl Bootstrap { pub fn new(log: Logger, db: Databases, nw: Arc) -> Self { info!(log, "Created Bootstrap"); let mut ctx = SASL::new(); + ctx.register(&FABFIRE); ctx.install_callback(Arc::new(auth::CB::new(db.userdb.clone()))); Self { db, nw, log, ctx } } @@ -48,7 +49,7 @@ impl Bootstrap { use connection_capnp::{API_VERSION_MAJOR, API_VERSION_MINOR, API_VERSION_PATCH}; use connection_capnp::bootstrap::*; -use crate::api::auth::Auth; +use crate::api::auth::{Auth, FABFIRE}; use crate::RELEASE; impl connection_capnp::bootstrap::Server for Bootstrap { diff --git a/src/api/auth.rs b/src/api/auth.rs index 0a01038..ccc2a2b 100644 --- a/src/api/auth.rs +++ b/src/api/auth.rs @@ -4,6 +4,7 @@ //! Authentication using SASL use std::cell::RefCell; +use std::convert::TryFrom; use std::io::Cursor; use std::rc::Rc; use std::sync::Arc; @@ -18,10 +19,10 @@ use rsasl::callback::Callback; use rsasl::error::SessionError; use rsasl::mechname::Mechname; use rsasl::property::{AuthId, Password}; +use rsasl::{Property, SASL}; use rsasl::session::Session as RsaslSession; use rsasl::session::Step; use rsasl::validate::{validations, Validation}; -use rsasl::SASL; use crate::api::users::Users; use crate::api::Session; @@ -33,6 +34,10 @@ use crate::db::access::AccessControl as AccessDB; use crate::db::user::{Internal as UserDB, User, UserId}; use crate::network::Network; +mod fabfire; +pub use fabfire::FABFIRE; +use crate::api::auth::fabfire::FabFireCardKey; + pub struct AppData { userdb: Arc, } @@ -58,6 +63,7 @@ impl Callback for CB { ) -> Result<(), SessionError> { let ret = match validation { validations::SIMPLE => { + let authid = session .get_property::() .ok_or(SessionError::no_property::())?; @@ -81,6 +87,30 @@ impl Callback for CB { }; Err(ret) } + + fn provide_prop( + &self, + session: &mut rsasl::session::SessionData, + property: Property, + ) -> Result<(), SessionError> { + match property { + fabfire::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 enum State { diff --git a/src/api/auth/fabfire.rs b/src/api/auth/fabfire.rs new file mode 100644 index 0000000..bedc15d --- /dev/null +++ b/src/api/auth/fabfire.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/src/api/auth/fabfire/server.rs b/src/api/auth/fabfire/server.rs new file mode 100644 index 0000000..32647b0 --- /dev/null +++ b/src/api/auth/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::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 { + 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/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 {