From 4611ed5b481661afa9c0539955b074becd62a3c3 Mon Sep 17 00:00:00 2001 From: Kai Jan Kriegel Date: Sat, 12 Mar 2022 00:45:59 +0100 Subject: [PATCH 1/4] initial integration of the X-FABFIRE mechnism Integrates the fabfire mechanism for use with the jorisdevice and desfire cards --- Cargo.lock | 94 +++++++- Cargo.toml | 9 +- Dockerfile.dev | 7 + src/api/auth.rs | 11 +- src/api/auth/fabfire.rs | 20 ++ src/api/auth/fabfire/server.rs | 420 +++++++++++++++++++++++++++++++++ 6 files changed, 555 insertions(+), 6 deletions(-) create mode 100644 Dockerfile.dev create mode 100644 src/api/auth/fabfire.rs create mode 100644 src/api/auth/fabfire/server.rs diff --git a/Cargo.lock b/Cargo.lock index 17a5241..909cc1b 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" @@ -253,7 +265,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", @@ -277,6 +289,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" @@ -286,6 +308,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" @@ -378,6 +406,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" @@ -447,6 +484,32 @@ 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.1.0" +source = "git+https://gitlab.com/fabinfra/fabaccess/nfc_rs.git?branch=main#34d1d7f3a062f007fcdc229995f9013970e460a5" +dependencies = [ + "aes", + "block-modes", + "des", + "hex", + "num-derive", + "num-traits", + "rand", + "simple-error", +] + [[package]] name = "dhall" version = "0.11.0" @@ -497,6 +560,7 @@ dependencies = [ "capnp-rpc", "capnpc", "clap", + "desfire", "easy-parallel", "flexbuffers", "futures 0.3.21", @@ -504,8 +568,10 @@ dependencies = [ "futures-test", "futures-util", "genawaiter", + "hex", "lazy_static", "libc", + "linkme", "lmdb-rkv", "rand", "rsasl", @@ -1131,6 +1197,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" @@ -1703,6 +1789,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 4a5223c..03f35d1 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.19" rustls-pemfile = "0.2" async-rustls = "0.2" +# Desfire +desfire = { git = "https://gitlab.com/fabinfra/fabaccess/nfc_rs.git", branch = "main" } +hex = "0.4.3" +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/src/api/auth.rs b/src/api/auth.rs index 662d3d5..8f260ba 100644 --- a/src/api/auth.rs +++ b/src/api/auth.rs @@ -30,6 +30,9 @@ use crate::db::Databases; use crate::db::user::{Internal as UserDB, User}; use crate::db::access::AccessControl as AccessDB; +mod fabfire; +use fabfire::FABFIRE; + pub struct AppData { userdb: Arc, } @@ -83,6 +86,7 @@ pub struct Auth { impl Auth { pub fn new(log: Logger, dbs: Databases, session: Rc>>) -> Self { let mut ctx = SASL::new(); + 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() } @@ -111,9 +115,10 @@ impl authentication_system::Server for Auth { for (i, m) in mechvec.into_iter().enumerate() { res_mechs.set(i as u32, m); }*/ - // For now, only PLAIN - let mut res_mechs = res.get().init_mechs(1); + // For now, only PLAIN and X-FABFIRE + let mut res_mechs = res.get().init_mechs(2); res_mechs.set(0, "PLAIN"); + res_mechs.set(1, "X-FABFIRE"); Promise::ok(()) } @@ -128,7 +133,7 @@ impl authentication_system::Server for Auth { // Extract the MECHANISM the client wants to use and start a session. // Or fail at that and thrown an exception TODO: return Outcome let mech = pry!(req.get_mechanism()); - if pry!(req.get_mechanism()) != "PLAIN" { + if mech != "PLAIN" || mech != "X-FABFIRE" { return Promise::err(capnp::Error { kind: capnp::ErrorKind::Failed, description: format!("Invalid SASL mech"), diff --git a/src/api/auth/fabfire.rs b/src/api/auth/fabfire.rs new file mode 100644 index 0000000..9c9c43d --- /dev/null +++ b/src/api/auth/fabfire.rs @@ -0,0 +1,20 @@ +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, +}; diff --git a/src/api/auth/fabfire/server.rs b/src/api/auth/fabfire/server.rs new file mode 100644 index 0000000..ed76e82 --- /dev/null +++ b/src/api/auth/fabfire/server.rs @@ -0,0 +1,420 @@ +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::apducommand::APDUCommand; +use desfire::iso7816_4::apduresponse::APDUResponse; +use desfire::error::{Error as DesfireError, Error}; +use std::convert::TryFrom; +use std::ops::Deref; + +enum FabFireError { + ParseError, + SerializationError, + CardError(DesfireError), +} + +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::CardError(err) => write!(f, "CardError: {}", 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::CardError(err) => write!(f, "CardError: {}", err), + } + } +} + +impl MechanismError for FabFireError { + fn kind(&self) -> MechanismErrorKind { + match self { + FabFireError::ParseError => MechanismErrorKind::Parse, + FabFireError::SerializationError => MechanismErrorKind::Protocol, + FabFireError::CardError(_) => MechanismErrorKind::Protocol, + } + } +} + +#[derive(Debug, Deserialize, Serialize)] +struct CardInfo { + card_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 { + msg_id: Option, + clr_txt: Option, + addn_txt: Option, + }, + sendPICC { + data: String + }, + haltPICC, + Key { + data: String + }, + ConfirmUser +} + +enum Step { + New, + SelectApp, + VerifyMagic, + GetURN, + GetToken, + Authenticate1, + Authenticate2, + Authenticate3, +} + +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 => { + //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(_) => { + return Err(FabFireError::ParseError.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 => { + // check that we successfully selected the application + let response = match input { + None => {return Err(SessionError::InputDataRequired)}, + Some(buf) => APDUResponse::new(buf) + }; + 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: hex::encode_upper(buf) }; + return match serde_json::to_writer(writer, &cmd) { + Ok(_) => { + self.step = Step::GetURN; + Ok(rsasl::session::Step::NeedsMore(None)) + } + 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() { + Ok(_) => { + match 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: hex::encode_upper(buf) }; + return match serde_json::to_writer(writer, &cmd) { + Ok(_) => { + self.step = Step::GetToken; + Ok(rsasl::session::Step::NeedsMore(None)) + } + 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() { + Ok(_) => { + match response.body { + Some(data) => { + if String::from_utf8(data).unwrap() != self.local_urn { + 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 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 + 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::Authenticate1; + Ok(rsasl::session::Step::NeedsMore(None)) + } + 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() { + Ok(_) => { + match response.body { + Some(data) => { + if String::from_utf8(data).unwrap() != "LoremIpsum" { // FIXME: match against user db + return Err(FabFireError::ParseError.into()); + } + } + None => { + return Err(FabFireError::ParseError.into()) + } + }; + } + Err(_) => { + 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: hex::encode_upper(buf) }; + return match serde_json::to_writer(writer, &cmd) { + Ok(_) => { + self.step = Step::Authenticate2; + Ok(rsasl::session::Step::NeedsMore(None)) + } + Err(_) => { + Err(FabFireError::SerializationError.into()) + } + } + + } + Step::Authenticate2 => { + let response = match input { + None => {return Err(SessionError::InputDataRequired)}, + Some(buf) => APDUResponse::new(buf) + }; + match response.check() { + Ok(_) => { + match 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: hex::encode_upper(buf) }; + return match serde_json::to_writer(writer, &cmd) { + Ok(_) => { + self.step = Step::Authenticate3; + Ok(rsasl::session::Step::NeedsMore(None)) + } + Err(_) => { + Err(FabFireError::SerializationError.into()) + } + } + } + None => { + return Err(FabFireError::ParseError.into()) + } + }; + } + Err(_) => { + return Err(FabFireError::ParseError.into()); + } + } + } + Step::Authenticate3 => { + let response = match input { + None => {return Err(SessionError::InputDataRequired)}, + Some(buf) => APDUResponse::new(buf) + }; + match response.check() { + Ok(_) => { + match 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() { + // TODO: Do stuff with the info that we are authenticated + return Ok(rsasl::session::Step::Done(None)); + } + } + } + } + None => { + return Err(FabFireError::ParseError.into()) + } + }; + } + Err(_) => { + return Err(FabFireError::ParseError.into()); + } + } + } + } + + return Ok(rsasl::session::Step::Done(None)); + } + +} \ No newline at end of file From 37db05a5574e2e75c919562c9bd95d0209625ac8 Mon Sep 17 00:00:00 2001 From: Kai Jan Kriegel Date: Sat, 12 Mar 2022 00:51:42 +0100 Subject: [PATCH 2/4] fix stupid logic error --- src/api/auth.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/auth.rs b/src/api/auth.rs index 8f260ba..85a7c8c 100644 --- a/src/api/auth.rs +++ b/src/api/auth.rs @@ -133,7 +133,7 @@ impl authentication_system::Server for Auth { // Extract the MECHANISM the client wants to use and start a session. // Or fail at that and thrown an exception TODO: return Outcome let mech = pry!(req.get_mechanism()); - if mech != "PLAIN" || mech != "X-FABFIRE" { + if !((mech == "PLAIN") || (mech == "X-FABFIRE")) { return Promise::err(capnp::Error { kind: capnp::ErrorKind::Failed, description: format!("Invalid SASL mech"), From 5c5c9710c5c8543fc0a5617c5fb00ca50349f6ba Mon Sep 17 00:00:00 2001 From: Kai Jan Kriegel Date: Sat, 12 Mar 2022 10:45:09 +0100 Subject: [PATCH 3/4] working Desfire auth in the api! --- Cargo.lock | 3 + Cargo.toml | 2 +- examples/users.toml | 1 + src/api/auth.rs | 104 +++++++- src/api/auth/fabfire.rs | 24 ++ src/api/auth/fabfire/server.rs | 463 +++++++++++++++++++++++---------- src/db/user.rs | 2 +- 7 files changed, 445 insertions(+), 154 deletions(-) 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 { From 926d200c938cff487a7ee05d8e2ed21d1fed2001 Mon Sep 17 00:00:00 2001 From: Kai Jan Kriegel Date: Sun, 13 Mar 2022 18:05:36 +0100 Subject: [PATCH 4/4] update desfire crate to version on crates.io --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index ca7a144..468e3b4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -76,7 +76,7 @@ rustls-pemfile = "0.2" async-rustls = "0.2" # Desfire -desfire = { git = "https://gitlab.com/fabinfra/fabaccess/nfc_rs.git", branch = "main" } +desfire = "0.2.0-alpha1" hex = { version = "0.4.3", features = ["serde"] } linkme = "0.2"