diff --git a/Cargo.lock b/Cargo.lock index bab04e9..58ba6c0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -733,6 +733,7 @@ dependencies = [ "id-contact-jwt", "id-contact-proto", "id-contact-sentry", + "irma", "josekit", "log", "reqwest", @@ -819,6 +820,18 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47be2f14c678be2fdcab04ab1171db51b2762ce6f0a8ee87c8dd4a04ed216135" +[[package]] +name = "irma" +version = "0.1.0" +source = "git+https://github.com/tweedegolf/irmars#96ee044deb0583a5424febccebcedc98f50afe42" +dependencies = [ + "reqwest", + "serde", + "serde_json", + "thiserror", + "url", +] + [[package]] name = "itoa" version = "0.4.7" @@ -1012,9 +1025,9 @@ dependencies = [ [[package]] name = "native-tls" -version = "0.2.7" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8d96b2e1c8da3957d58100b09f102c6d9cfdfced01b7ec5a8974044bb09dbd4" +checksum = "48ba9f7719b5a0f42f338907614285fb5fd70e53858141f69898a1fb7203b24d" dependencies = [ "lazy_static", "libc", @@ -1354,9 +1367,9 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.11.2" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf12057f289428dbf5c591c74bf10392e4a8003f993405a902f20117019022d4" +checksum = "66d2927ca2f685faf0fc620ac4834690d29e7abb153add10f5812eef20b5e280" dependencies = [ "base64", "bytes", @@ -1988,18 +2001,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.24" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0f4a65597094d4483ddaed134f409b2cb7c1beccf25201a9f73c719254fa98e" +checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.24" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7765189610d8241a44529806d6fd1f2e0a08734313a35d5b3a556f92b381f3c0" +checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" dependencies = [ "proc-macro2", "quote", @@ -2251,9 +2264,9 @@ checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" [[package]] name = "url" -version = "2.2.1" +version = "2.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ccd964113622c8e9322cfac19eb1004a07e636c545f325da085d5cdde6f1f8b" +checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" dependencies = [ "form_urlencoded", "idna", @@ -2313,8 +2326,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ee1280240b7c461d6a0071313e08f34a60b0365f14260362e5a2b17d1d31aa7" dependencies = [ "cfg-if", - "serde", - "serde_json", "wasm-bindgen-macro", ] diff --git a/Cargo.toml b/Cargo.toml index 4093357..436598e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,7 @@ edition = "2018" id-contact-sentry = { git = "https://github.com/id-contact/id-contact-sentry.git" } id-contact-jwt = { git = "https://github.com/id-contact/id-contact-jwt.git" } id-contact-proto = { git = "https://github.com/id-contact/id-contact-proto.git" } +irma = { git = "https://github.com/tweedegolf/irmars" } log = "0.4.14" rocket = { version = "0.5.0-rc.1", features = ["json"] } serde = "1.0.123" @@ -17,7 +18,7 @@ serde_json = "1.0.61" serde_yaml = "0.8.16" strum = "0.20.0" strum_macros = "0.20.0" -reqwest = { version = "0.11.0", features = ["json"] } +reqwest = { version = "0.11.6", features = ["json"] } urlencoding = "1.1.1" base64 = "0.13.0" josekit = "0.7.1" diff --git a/src/config.rs b/src/config.rs index f176ee5..83e33fa 100644 --- a/src/config.rs +++ b/src/config.rs @@ -8,6 +8,7 @@ type AttributeMapping = HashMap>; #[derive(Debug)] pub enum Error { + Irma(irma::Error), UnknownAttribute(String), NotMatching(&'static str), InvalidResponse(&'static str), @@ -34,6 +35,12 @@ impl From for Error { } } +impl From for Error { + fn from(e: irma::Error) -> Error { + Error::Irma(e) + } +} + impl Display for Error { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { @@ -45,6 +52,7 @@ impl Display for Error { } Error::Json(e) => e.fmt(f), Error::Jwt(e) => e.fmt(f), + Error::Irma(e) => e.fmt(f), } } } @@ -55,6 +63,7 @@ impl StdError for Error { Error::Yaml(e) => Some(e), Error::Json(e) => Some(e), Error::Jwt(e) => Some(e), + Error::Irma(e) => Some(e), _ => None, } } @@ -66,12 +75,16 @@ struct IrmaserverConfig { auth_token: Option, } -impl From for super::irma::IrmaServer { - fn from(config: IrmaserverConfig) -> Self { - match config.auth_token { - Some(token) => Self::new_with_auth(&config.url, &token), - None => Self::new(&config.url), - } +impl TryFrom for irma::IrmaClient { + type Error = Error; + + fn try_from(config: IrmaserverConfig) -> Result { + Ok(match config.auth_token { + Some(token) => irma::IrmaClientBuilder::new(&config.url)? + .token_authentication(token) + .build(), + None => irma::IrmaClient::new(&config.url)?, + }) } } @@ -95,7 +108,7 @@ pub struct Config { sentry_dsn: Option, ui_irma_url: String, attributes: AttributeMapping, - irma_server: super::irma::IrmaServer, + irma_server: irma::IrmaClient, encrypter: Box, signer: Box, } @@ -110,7 +123,7 @@ impl TryFrom for Config { sentry_dsn: config.sentry_dsn, ui_irma_url: config.ui_irma_url, attributes: config.attributes, - irma_server: super::irma::IrmaServer::from(config.irma_server), + irma_server: irma::IrmaClient::try_from(config.irma_server)?, encrypter: Box::::try_from(config.encryption_pubkey)?, signer: Box::::try_from(config.signing_privkey)?, }) @@ -118,16 +131,16 @@ impl TryFrom for Config { } impl Config { - pub fn map_attributes(&self, attributes: &[String]) -> Result { - let mut result: super::irma::ConDisCon = vec![]; + pub fn map_attributes(&self, attributes: &[String]) -> Result { + let mut result: irma::ConDisCon = vec![]; for attribute in attributes { - let mut dis: Vec> = vec![]; + let mut dis: Vec> = vec![]; for request_attribute in self .attributes .get(attribute) .ok_or_else(|| Error::UnknownAttribute(attribute.clone()))? { - dis.push(vec![super::irma::Attribute::Simple( + dis.push(vec![irma::AttributeRequest::Simple( request_attribute.clone(), )]); } @@ -139,7 +152,7 @@ impl Config { pub fn map_response( &self, attributes: &[String], - response: crate::irma::IrmaResult, + response: irma::SessionResult, ) -> Result, Error> { if attributes.len() != response.disclosed.len() { return Err(Error::NotMatching("mismatch between request and response")); @@ -157,18 +170,24 @@ impl Config { .attributes .get(attribute) .ok_or_else(|| Error::UnknownAttribute(attribute.clone()))?; - if !allowed_irma_attributes.contains(&response.disclosed[i][0].id) { + if !allowed_irma_attributes.contains(&response.disclosed[i][0].identifier) { return Err(Error::InvalidResponse( "Incorrect attribute in inner conjunction", )); } - result.insert(attribute.clone(), response.disclosed[i][0].rawvalue.clone()); + result.insert( + attribute.clone(), + response.disclosed[i][0] + .raw_value + .clone() + .unwrap_or_else(|| "".into()), + ); } Ok(result) } - pub fn irma_server(&self) -> &super::irma::IrmaServer { + pub fn irma_server(&self) -> &irma::IrmaClient { &self.irma_server } diff --git a/src/irma.rs b/src/irma.rs deleted file mode 100644 index 6c50ec1..0000000 --- a/src/irma.rs +++ /dev/null @@ -1,249 +0,0 @@ -use serde::{Deserialize, Serialize}; -use std::{convert::TryFrom, error::Error as StdError, fmt::Display}; - -#[derive(Debug)] -pub enum Error { - Reqwest(reqwest::Error), - Json(serde_json::Error), - Incomplete(), - Cancelled(), - Timeout(), - Invalid(), -} - -impl From for Error { - fn from(v: reqwest::Error) -> Error { - Error::Reqwest(v) - } -} - -impl From for Error { - fn from(v: serde_json::Error) -> Error { - Error::Json(v) - } -} - -impl Display for Error { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Error::Reqwest(e) => e.fmt(f), - Error::Json(e) => e.fmt(f), - Error::Incomplete() => f.write_str("Incomplete session"), - Error::Cancelled() => f.write_str("Cancelled session"), - Error::Timeout() => f.write_str("Session timed out"), - Error::Invalid() => f.write_str("Invalid proof"), - } - } -} -impl StdError for Error { - fn source(&self) -> Option<&(dyn StdError + 'static)> { - match self { - Error::Reqwest(e) => Some(e), - Error::Json(e) => Some(e), - _ => None, - } - } -} - -#[derive(Serialize, Debug, Clone)] -#[serde(untagged)] -pub enum Attribute { - Simple(String), -} - -pub type ConDisCon = Vec>>; - -#[derive(Serialize, Debug, Clone)] -#[serde(tag = "@context")] -pub enum IrmaRequest { - #[serde(rename = "https://irma.app/ld/request/disclosure/v2")] - Disclosure(IrmaDisclosureRequest), -} - -#[derive(Serialize, Debug, Clone)] -pub struct IrmaDisclosureRequest { - pub disclose: ConDisCon, - #[serde(rename = "clientReturnUrl")] - pub return_url: Option, - #[serde(rename = "augmentReturnUrl")] - pub augment_return: bool, -} - -#[derive(Serialize, Debug, Clone)] -struct ExtendedIrmaRequest<'a> { - #[serde(rename = "callbackUrl")] - callback_url: &'a str, - request: &'a IrmaRequest, -} - -#[derive(Deserialize, Serialize)] -#[serde(rename_all = "camelCase")] -enum SessionType { - Disclosing, - Signing, - Issuing, -} - -#[derive(Deserialize, Serialize)] -struct SessionPointer { - u: String, - #[serde(rename = "irmaqr")] - irma_qr: SessionType, -} - -#[derive(Deserialize, Serialize)] -#[serde(rename_all = "camelCase")] -struct SessionResponse { - token: String, - session_ptr: SessionPointer, -} - -#[derive(Deserialize, Serialize, PartialEq, Debug)] -#[serde(rename_all = "SCREAMING_SNAKE_CASE")] -pub enum ProofStatus { - Valid, - Invalid, - InvalidTimestamp, - UnmatchedRequest, - MissingAttributes, - Expired, -} - -#[derive( - Deserialize, Serialize, PartialEq, Debug, strum_macros::EnumString, strum_macros::ToString, -)] -#[serde(rename_all = "SCREAMING_SNAKE_CASE")] -#[strum(serialize_all = "shouty_snake_case")] -pub enum SessionStatus { - Initialized, - Connected, - Cancelled, - Done, - Timeout, -} - -#[derive(Deserialize, Serialize, Debug)] -pub struct AttributeResult { - pub id: String, - pub rawvalue: String, -} - -#[derive(Deserialize, Serialize, Debug)] -struct RawIrmaResult { - status: SessionStatus, - #[serde(rename = "proofStatus")] - proof_status: ProofStatus, - disclosed: Vec>, -} - -#[derive(Debug)] -pub struct IrmaResult { - pub disclosed: Vec>, -} - -impl TryFrom for IrmaResult { - type Error = Error; - - fn try_from(value: RawIrmaResult) -> Result { - match value.status { - SessionStatus::Cancelled => Err(Error::Cancelled()), - SessionStatus::Timeout => Err(Error::Timeout()), - SessionStatus::Done => match value.proof_status { - ProofStatus::Valid => Ok(IrmaResult { - disclosed: value.disclosed, - }), - _ => Err(Error::Invalid()), - }, - _ => Err(Error::Incomplete()), - } - } -} - -#[derive(Debug)] -pub struct IrmaSession { - pub qr: String, - pub token: String, -} - -#[derive(Debug)] -pub struct IrmaServer { - server_url: String, - auth_token: Option, -} - -impl IrmaServer { - pub fn new(server_url: &str) -> IrmaServer { - IrmaServer { - server_url: server_url.to_string(), - auth_token: None, - } - } - - pub fn new_with_auth(server_url: &str, auth_token: &str) -> IrmaServer { - IrmaServer { - server_url: server_url.to_string(), - auth_token: Some(auth_token.to_string()), - } - } - - pub async fn start(&self, request: &IrmaRequest) -> Result { - let client = reqwest::Client::new(); - - let mut session_request = client - .post(&format!("{}/session", self.server_url)) - .json(request); - - if let Some(token) = &self.auth_token { - session_request = session_request.header("Authorization", token); - } - - let session_response: SessionResponse = session_request.send().await?.json().await?; - - let qr = serde_json::to_string(&session_response.session_ptr)?; - - Ok(IrmaSession { - qr, - token: session_response.token, - }) - } - - pub async fn start_with_callback( - &self, - request: &IrmaRequest, - callback_url: &str, - ) -> Result { - let extended_request = ExtendedIrmaRequest { - callback_url, - request, - }; - let client = reqwest::Client::new(); - let mut session_request = client - .post(&format!("{}/session", self.server_url)) - .json(&extended_request); - - if let Some(token) = &self.auth_token { - session_request = session_request.header("Authorization", token); - } - - let session_response: SessionResponse = session_request.send().await?.json().await?; - - let qr = serde_json::to_string(&session_response.session_ptr)?; - - Ok(IrmaSession { - qr, - token: session_response.token, - }) - } - - pub async fn get_result(&self, token: &str) -> Result { - let client = reqwest::Client::new(); - let session_result: RawIrmaResult = client - .get(&format!("{}/session/{}/result", self.server_url, token)) - .send() - .await? - .json() - .await?; - - IrmaResult::try_from(session_result) - } -} diff --git a/src/main.rs b/src/main.rs index 7c34162..b9e5590 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,7 +2,6 @@ use askama::Template; use base64::URL_SAFE; use id_contact_jwt::sign_and_encrypt_auth_result; use id_contact_proto::{AuthResult, AuthStatus, StartAuthRequest, StartAuthResponse}; -use irma::{IrmaDisclosureRequest, IrmaRequest}; use rocket::{get, launch, post, response::Redirect, routes, serde::json::Json, State}; use serde::Deserialize; use std::{error::Error as StdError, fmt::Display, fs::File}; @@ -13,7 +12,6 @@ use josekit::{ }; mod config; -mod irma; #[derive(Debug)] enum Error { @@ -157,7 +155,10 @@ async fn decorated_continue( let attributes = base64::decode_config(attributes, URL_SAFE)?; let attributes = serde_json::from_slice::>(&attributes)?; - let session_result = config.irma_server().get_result(&token).await?; + let session_result = config + .irma_server() + .result(&irma::SessionToken(token)) + .await?; //let attributes = config.map_response(&attributes, session_result)?; let auth_result = AuthResult { @@ -198,7 +199,10 @@ async fn session_complete( let attributes = base64::decode_config(attributes, URL_SAFE)?; let attributes = serde_json::from_slice::>(&attributes)?; - let session_result = config.irma_server().get_result(&token.token).await?; + let session_result = config + .irma_server() + .result(&irma::SessionToken(token.token.clone())) + .await?; let auth_result = AuthResult { status: AuthStatus::Succes, @@ -228,11 +232,10 @@ async fn start_oob( request: &Json, attr_url: &str, ) -> Result, Error> { - let session_request = IrmaRequest::Disclosure(IrmaDisclosureRequest { - disclose: config.map_attributes(&request.attributes)?, - return_url: Some(request.continuation.clone()), - augment_return: false, - }); + let session_request = irma::DisclosureRequestBuilder::new() + .add_discons(config.map_attributes(&request.attributes)?) + .return_url(request.continuation.clone()) + .build(); log::trace!("With attr url"); @@ -245,14 +248,19 @@ async fn start_oob( let session = config .irma_server() - .start_with_callback(&session_request, &callback_url) + .request_extended(&irma::ExtendedIrmaRequest { + validity: None, + timeout: None, + callback_url: Some(callback_url), + request: session_request, + }) .await?; Ok(Json(StartAuthResponse { client_url: format!( "{}/auth/{}/{}", config.server_url(), - base64::encode_config(&session.qr, URL_SAFE), + base64::encode_config(&serde_json::to_vec(&session.qr)?, URL_SAFE), base64::encode_config(&request.continuation, URL_SAFE), ), })) @@ -272,21 +280,20 @@ async fn start_ib( log::trace!("Without attr url"); - let session_request = IrmaRequest::Disclosure(IrmaDisclosureRequest { - disclose: config.map_attributes(&request.attributes)?, - return_url: Some(continuation_url.clone()), - augment_return: true, - }); + let session_request = irma::DisclosureRequestBuilder::new() + .add_discons(config.map_attributes(&request.attributes)?) + .augmented_return_url(request.continuation.clone()) + .build(); - let session = config.irma_server().start(&session_request).await?; + let session = config.irma_server().request(&session_request).await?; Ok(Json(StartAuthResponse { client_url: format!( "{}/auth/{}/{}", config.server_url(), - base64::encode_config(&session.qr, URL_SAFE), + base64::encode_config(&serde_json::to_vec(&session.qr)?, URL_SAFE), base64::encode_config( - format!("{}?token={}", continuation_url, session.token), + format!("{}?token={}", continuation_url, session.token.0), URL_SAFE ), ),