Skip to content

Commit

Permalink
Merge branch 'pvw-2461-implement-app2app' into 'main'
Browse files Browse the repository at this point in the history
Add DigiD app2app flow

See merge request wallet/nl-wallet!850
  • Loading branch information
Arjen committed May 17, 2024
2 parents 4a3445b + 4c47766 commit f508120
Show file tree
Hide file tree
Showing 34 changed files with 777 additions and 156 deletions.
14 changes: 5 additions & 9 deletions scripts/devenv/digid-connector/clients.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,14 @@
"${WALLET_CLIENT_ID}": {
"name": "NL Wallet",
"external_id": "87654321",
"external_ids": [
"87654321"
],
"external_ids": ["87654321"],
"token_endpoint_auth_method": "none",
"redirect_uris": [
"walletdebuginteraction://wallet.edi.rijksoverheid.nl/authentication/",
"walletdebuginteraction://wallet.edi.rijksoverheid.nl/authentication"
],
"error_page": "walletdebuginteraction://wallet.edi.rijksoverheid.nl/authentication",
"response_types": [
"code"
"walletdebuginteraction://wallet.edi.rijksoverheid.nl/return-from-digid/",
"walletdebuginteraction://wallet.edi.rijksoverheid.nl/return-from-digid"
],
"error_page": "walletdebuginteraction://wallet.edi.rijksoverheid.nl/return-from-digid",
"response_types": ["code"],
"pubkey_type": "RSA",
"client_public_key_path": "secrets/clients/test_client/test_client.crt"
}
Expand Down
5 changes: 3 additions & 2 deletions wallet_core/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion wallet_core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ trait-variant = "0.1.1"
uniffi = { version = "0.27.1", default-features = false }
url = "2.4.0"
uuid = "1.4.0"
wiremock = "0.5.19"
wiremock = "0.5.21"
x509-parser = "0.15.1"

[patch.crates-io]
Expand Down
2 changes: 1 addition & 1 deletion wallet_core/flutter_api/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ pub async fn continue_pid_issuance(uri: String) -> Result<Vec<Card>> {

let mut wallet = wallet().write().await;

let documents = wallet.continue_pid_issuance(&url).await?;
let documents = wallet.continue_pid_issuance(url).await?;

let cards = documents.into_iter().map(Card::from).collect();

Expand Down
23 changes: 13 additions & 10 deletions wallet_core/flutter_api/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ use serde::Serialize;

use wallet::{
errors::{
reqwest, AccountProviderError, DisclosureError, HistoryError, InstructionError, PidIssuanceError, ResetError,
UriIdentificationError, WalletInitError, WalletRegistrationError, WalletUnlockError,
reqwest, AccountProviderError, DigidSessionError, DisclosureError, HistoryError, InstructionError,
PidIssuanceError, ResetError, UriIdentificationError, WalletInitError, WalletRegistrationError,
WalletUnlockError,
},
openid4vc::{IssuanceSessionError, OidcError},
};
Expand Down Expand Up @@ -157,24 +158,26 @@ impl FlutterApiErrorFields for PidIssuanceError {
FlutterApiErrorType::WalletState
}

PidIssuanceError::DigidSessionFinish(OidcError::RedirectUriError(_)) => FlutterApiErrorType::RedirectUri,
PidIssuanceError::DigidSessionFinish(DigidSessionError::Oidc(OidcError::RedirectUriError(_))) => {
FlutterApiErrorType::RedirectUri
}

PidIssuanceError::PidIssuer(IssuanceSessionError::TokenRequest(_))
| PidIssuanceError::PidIssuer(IssuanceSessionError::CredentialRequest(_))
| PidIssuanceError::DigidSessionStart(OidcError::RedirectUriError(_))
| PidIssuanceError::DigidSessionStart(OidcError::RequestingAccessToken(_))
| PidIssuanceError::DigidSessionStart(OidcError::RequestingUserInfo(_))
| PidIssuanceError::DigidSessionFinish(OidcError::RequestingAccessToken(_))
| PidIssuanceError::DigidSessionFinish(OidcError::RequestingUserInfo(_)) => {
crate::errors::FlutterApiErrorType::Server
| PidIssuanceError::DigidSessionStart(DigidSessionError::Oidc(OidcError::RedirectUriError(_)))
| PidIssuanceError::DigidSessionStart(DigidSessionError::Oidc(OidcError::RequestingAccessToken(_)))
| PidIssuanceError::DigidSessionStart(DigidSessionError::Oidc(OidcError::RequestingUserInfo(_)))
| PidIssuanceError::DigidSessionFinish(DigidSessionError::Oidc(OidcError::RequestingAccessToken(_)))
| PidIssuanceError::DigidSessionFinish(DigidSessionError::Oidc(OidcError::RequestingUserInfo(_))) => {
FlutterApiErrorType::Server
}
_ => FlutterApiErrorType::Generic,
}
}

fn data(&self) -> Option<serde_json::Value> {
match self {
Self::DigidSessionFinish(OidcError::RedirectUriError(err)) => {
Self::DigidSessionFinish(DigidSessionError::Oidc(OidcError::RedirectUriError(err))) => {
[("redirect_error", format!("{:?}", &err.error))]
.into_iter()
.collect::<serde_json::Value>()
Expand Down
30 changes: 6 additions & 24 deletions wallet_core/mdoc/src/utils/serialization.rs
Original file line number Diff line number Diff line change
Expand Up @@ -473,39 +473,21 @@ impl<'de, T: DeserializeOwned> Deserialize<'de> for CborBase64<T> {
}

pub mod cbor_hex {
use std::fmt::Formatter;

use serde::{
de::{self, DeserializeOwned, Visitor},
ser::Error,
Deserializer, Serialize, Serializer,
de::{self, DeserializeOwned},
ser, Deserializer, Serialize, Serializer,
};
use serde_with::{formats::Uppercase, hex::Hex, DeserializeAs, SerializeAs};

use super::{cbor_deserialize, cbor_serialize};

pub fn serialize<S: Serializer, T: Serialize>(input: T, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(&hex::encode(cbor_serialize(&input).map_err(S::Error::custom)?))
Hex::<Uppercase>::serialize_as(&cbor_serialize(&input).map_err(ser::Error::custom)?, serializer)
}

pub fn deserialize<'de, D: Deserializer<'de>, T: DeserializeOwned>(deserializer: D) -> Result<T, D::Error> {
// based on https://github.com/marshallpierce/base64-serde/blob/master/src/lib.rs
struct HexVisitor;

impl Visitor<'_> for HexVisitor {
type Value = Vec<u8>;

fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result {
formatter.write_str("hex encoded string")
}

fn visit_str<E: de::Error>(self, v: &str) -> Result<Self::Value, E> {
hex::decode(v).map_err(E::custom)
}
}

deserializer
.deserialize_str(HexVisitor)
.map(|bts| cbor_deserialize(bts.as_slice()).map_err(de::Error::custom))?
let x: Vec<u8> = Hex::<Uppercase>::deserialize_as(deserializer)?;
cbor_deserialize(x.as_slice()).map_err(de::Error::custom)
}
}

Expand Down
9 changes: 5 additions & 4 deletions wallet_core/openid4vc/src/oidc/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ pub use josekit::{
use reqwest::header;
use url::Url;

use wallet_common::{config::wallet_config::BaseUrl, utils};
use wallet_common::{config::wallet_config::BaseUrl, reqwest::trusted_reqwest_client_builder, utils};

use crate::{
authorization::{AuthorizationErrorCode, AuthorizationRequest, AuthorizationResponse, PkceCodeChallenge},
Expand Down Expand Up @@ -64,7 +64,7 @@ const APPLICATION_JWT: &str = "application/jwt";
pub trait OidcClient {
/// Create a new instance by using OpenID discovery, and return an authorization URL.
async fn start(
http_client: reqwest::Client,
trust_anchors: Vec<reqwest::Certificate>,
issuer_url: BaseUrl,
client_id: String,
redirect_uri: Url,
Expand Down Expand Up @@ -98,11 +98,12 @@ pub struct HttpOidcClient<P = S256PkcePair> {

impl<P: PkcePair> OidcClient for HttpOidcClient<P> {
async fn start(
http_client: reqwest::Client,
trust_anchors: Vec<reqwest::Certificate>,
issuer: BaseUrl,
client_id: String,
redirect_uri: Url,
) -> Result<(Self, Url), OidcError> {
let http_client = trusted_reqwest_client_builder(trust_anchors).build()?;
let config = Config::discover(&http_client, &issuer).await?;
let jwks = config.jwks(&http_client).await?;

Expand Down Expand Up @@ -338,7 +339,7 @@ mod tests {
let (_server, server_url) = start_discovery_server().await;
let redirect_uri: Url = REDIRECT_URI.parse().unwrap();
let (client, auth_url) = HttpOidcClient::<MockPkcePair>::start(
reqwest::Client::new(),
vec![],
server_url.clone(),
CLIENT_ID.to_string(),
redirect_uri.clone(),
Expand Down
8 changes: 4 additions & 4 deletions wallet_core/tests_integration/src/bin/performance_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@ use nl_wallet_mdoc::{
verifier::{SessionType, StatusResponse},
ItemsRequest,
};
use openid4vc::{issuance_session::HttpIssuanceSession, oidc::HttpOidcClient};
use openid4vc::issuance_session::HttpIssuanceSession;
use platform_support::utils::{software::SoftwareUtilities, PlatformUtilities};
use tests_integration::{fake_digid::fake_digid_auth, logging::init_logging};
use wallet::{
mock::{default_configuration, MockStorage},
wallet_deps::{
ConfigServerConfiguration, ConfigurationRepository, HttpAccountProviderClient, HttpConfigurationRepository,
UpdateableConfigurationRepository,
HttpDigidSession, UpdateableConfigurationRepository,
},
Wallet,
};
Expand Down Expand Up @@ -61,7 +61,7 @@ async fn main() {
MockStorage,
SoftwareEcdsaKey,
HttpAccountProviderClient,
HttpOidcClient,
HttpDigidSession,
HttpIssuanceSession,
DisclosureSession<CborHttpClient, Uuid>,
> = Wallet::init_registration(
Expand Down Expand Up @@ -89,7 +89,7 @@ async fn main() {
.await;

let _unsigned_mdocs = wallet
.continue_pid_issuance(&redirect_url)
.continue_pid_issuance(redirect_url)
.await
.expect("Could not continue pid issuance");
wallet
Expand Down
8 changes: 4 additions & 4 deletions wallet_core/tests_integration/src/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@ use nl_wallet_mdoc::{server_state::SessionState, utils::x509};
use openid4vc::{
issuance_session::HttpIssuanceSession,
issuer::{AttributeService, Created},
oidc::{self, MockOidcClient},
oidc,
token::{AttestationPreview, TokenRequest},
};
use platform_support::utils::{software::SoftwareUtilities, PlatformUtilities};
use wallet::{
mock::{default_configuration, MockStorage},
mock::{default_configuration, MockDigidSession, MockStorage},
wallet_deps::{
ConfigServerConfiguration, HttpAccountProviderClient, HttpConfigurationRepository,
UpdateableConfigurationRepository,
Expand Down Expand Up @@ -79,7 +79,7 @@ pub type WalletWithMocks = Wallet<
MockStorage,
SoftwareEcdsaKey,
HttpAccountProviderClient,
MockOidcClient,
MockDigidSession,
HttpIssuanceSession,
>;

Expand Down Expand Up @@ -293,7 +293,7 @@ pub async fn do_pid_issuance(mut wallet: WalletWithMocks, pin: String) -> Wallet
.await
.expect("Could not create pid issuance auth url");
let _unsigned_mdocs = wallet
.continue_pid_issuance(&redirect_url)
.continue_pid_issuance(redirect_url)
.await
.expect("Could not continue pid issuance");
wallet
Expand Down
23 changes: 11 additions & 12 deletions wallet_core/tests_integration/tests/digid_issuance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,18 @@ use std::{net::IpAddr, process, str::FromStr};

use openid4vc::{
issuance_session::{HttpIssuanceSession, HttpOpenidMessageClient, IssuanceSession},
oidc::{HttpOidcClient, OidcClient},
pkce::S256PkcePair,
oidc::HttpOidcClient,
};

use gba_hc_converter::settings::Settings as GbaSettings;
use nl_wallet_mdoc::{holder::TrustAnchor, software_key_factory::SoftwareKeyFactory};
use tests_integration::{common::*, fake_digid::fake_digid_auth};
use wallet::{mock::default_configuration, wallet_common::WalletConfiguration};
use wallet_common::{config::wallet_config::DEFAULT_UNIVERSAL_LINK_BASE, reqwest::trusted_reqwest_client_builder};
use wallet::{
mock::default_configuration,
wallet_common::WalletConfiguration,
wallet_deps::{DigidSession, HttpDigidSession},
};
use wallet_common::config::wallet_config::DEFAULT_UNIVERSAL_LINK_BASE;
use wallet_server::pid::{attributes::BrpPidAttributeService, brp::client::HttpBrpClient};

fn gba_hc_converter_settings() -> GbaSettings {
Expand Down Expand Up @@ -67,13 +70,9 @@ async fn test_pid_issuance_digid_bridge() {
let wallet_config = default_configuration();

// Prepare DigiD flow
let (digid_session, authorization_url) = HttpOidcClient::<S256PkcePair>::start(
trusted_reqwest_client_builder(wallet_config.pid_issuance.digid_trust_anchors().clone())
.build()
.unwrap(),
settings.issuer.digid.issuer_url.clone(),
wallet_config.pid_issuance.digid_client_id.clone(),
WalletConfiguration::issuance_redirect_uri(&DEFAULT_UNIVERSAL_LINK_BASE.parse().unwrap()).into_inner(),
let (digid_session, authorization_url) = HttpDigidSession::<HttpOidcClient>::start(
wallet_config.pid_issuance.clone(),
WalletConfiguration::issuance_base_uri(&DEFAULT_UNIVERSAL_LINK_BASE.parse().unwrap()).into_inner(),
)
.await
.unwrap();
Expand All @@ -85,7 +84,7 @@ async fn test_pid_issuance_digid_bridge() {
wallet_config.pid_issuance.digid_trust_anchors(),
)
.await;
let token_request = digid_session.into_token_request(&redirect_url).unwrap();
let token_request = digid_session.into_token_request(redirect_url).await.unwrap();

let server_url = local_pid_base_url(&settings.public_url.as_ref().port().unwrap());

Expand Down
16 changes: 8 additions & 8 deletions wallet_core/tests_integration/tests/disclosure.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ use nl_wallet_mdoc::{
verifier::{DisclosedAttributes, ReturnUrlTemplate, SessionType, StatusResponse},
ItemsRequest,
};
use openid4vc::{oidc::MockOidcClient, token::TokenRequest};
use openid4vc::token::TokenRequest;
use tests_integration::common::*;
use wallet::errors::DisclosureError;
use wallet::{errors::DisclosureError, mock::MockDigidSession};
use wallet_common::utils;
use wallet_server::verifier::{StartDisclosureRequest, StartDisclosureResponse};

Expand Down Expand Up @@ -59,9 +59,9 @@ async fn test_disclosure_usecases_ok(
return_url_template: return_url,
};

let digid_context = MockOidcClient::start_context();
digid_context.expect().return_once(|_, _, _, _| {
let mut session = MockOidcClient::default();
let digid_context = MockDigidSession::start_context();
digid_context.expect().return_once(|_, _| {
let mut session = MockDigidSession::default();

session.expect_into_token_request().return_once(|_url| {
Ok(TokenRequest {
Expand Down Expand Up @@ -156,9 +156,9 @@ async fn test_disclosure_usecases_ok(
#[tokio::test]
#[serial]
async fn test_disclosure_without_pid() {
let digid_context = MockOidcClient::start_context();
digid_context.expect().return_once(|_, _, _, _| {
let session = MockOidcClient::default();
let digid_context = MockDigidSession::start_context();
digid_context.expect().return_once(|_, _| {
let session = MockDigidSession::default();
Ok((session, Url::parse("http://localhost/").unwrap()))
});

Expand Down
10 changes: 5 additions & 5 deletions wallet_core/tests_integration/tests/issuance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,16 @@ use std::sync::Arc;

use url::Url;

use openid4vc::{oidc::MockOidcClient, token::TokenRequest};
use openid4vc::token::TokenRequest;
use tests_integration::common::*;
use wallet::{AttributeValue, Document};
use wallet::{mock::MockDigidSession, AttributeValue, Document};
use wallet_common::utils;

#[tokio::test]
async fn test_pid_ok() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let digid_context = MockOidcClient::start_context();
digid_context.expect().return_once(|_, _, _, _| {
let mut session = MockOidcClient::default();
let digid_context = MockDigidSession::start_context();
digid_context.expect().return_once(|_, _| {
let mut session = MockDigidSession::default();

session.expect_into_token_request().return_once(|_url| {
Ok(TokenRequest {
Expand Down
7 changes: 4 additions & 3 deletions wallet_core/wallet/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ mime.workspace = true
once_cell = { workspace = true, features = ["parking_lot"] }
p256 = { workspace = true, features = ["ecdsa", "std"] }
parking_lot.workspace = true
regex.workspace = true
reqwest = { workspace = true, features = ["json", "rustls-tls-webpki-roots"] }
ring = { workspace = true, features = ["std"] }
sea-orm = { workspace = true, features = [
Expand Down Expand Up @@ -85,12 +86,12 @@ tokio = { workspace = true, features = [
wiremock.workspace = true

nl_wallet_mdoc = { path = "../mdoc", features = [
"generate",
"examples",
"software_keys",
"generate",
"mdocs_map",
"test",
"mock",
"software_keys",
"test",
] }
openid4vc = { path = "../openid4vc", features = ["mock"] }
# enable the "software" feature for platform_support when running tests
Expand Down
Loading

0 comments on commit f508120

Please sign in to comment.