From fe11207fd715e74a994f8a29936b2df63208e8df Mon Sep 17 00:00:00 2001 From: Helen Qin Date: Tue, 21 Jul 2026 02:34:57 +0000 Subject: [PATCH 1/2] Support OpenID4VP multi-signed request in Rust matcher --- matcher-rs/src/openid4vp.rs | 78 +++++++++- .../TC42_ParseV1Multisigned_expected.json | 139 ++++++++++++++++++ .../TC42_ParseV1Multisigned_request.json | 10 ++ 3 files changed, 225 insertions(+), 2 deletions(-) create mode 100644 matcher-rs/testdata/TC42_ParseV1Multisigned_expected.json create mode 100644 matcher-rs/testdata/TC42_ParseV1Multisigned_request.json diff --git a/matcher-rs/src/openid4vp.rs b/matcher-rs/src/openid4vp.rs index 06405f0..7206336 100644 --- a/matcher-rs/src/openid4vp.rs +++ b/matcher-rs/src/openid4vp.rs @@ -1,10 +1,51 @@ use crate::base64url::decode_base64url; use crate::credman::CredmanApi; +use crate::json_value::JsonValue; pub use crate::openid4vp_models::*; use crate::reporter::report_match_result; use nanoserde::DeJson; use std::borrow::Cow; +fn extract_multisigned_payload<'a>( + pr: &'a ProtocolRequest, +) -> Result> { + let json_str: &str = if let Some(data) = &pr.data { + match data { + ProtocolRequestData::String(s) => s.as_str(), + ProtocolRequestData::Object(obj) => obj.request.as_str(), + } + } else if !pr.request.is_empty() { + pr.request.as_str() + } else { + return Err("Missing multisigned request data".into()); + }; + + let parsed: JsonValue = DeJson::deserialize_json(json_str)?; + + let payload = match &parsed { + JsonValue::Object(map) => { + if let Some(JsonValue::Object(req_map)) = map.get("request") { + if let Some(JsonValue::String(p)) = req_map.get("payload") { + p.clone() + } else { + return Err("Missing 'payload' in 'request' object".into()); + } + } else if let Some(JsonValue::String(p)) = map.get("payload") { + p.clone() + } else { + return Err("Missing 'payload' field in multisigned request".into()); + } + } + _ => return Err("Multisigned request must be a JSON object".into()), + }; + + if payload.is_empty() { + return Err("Empty payload in multisigned request".into()); + } + + Ok(payload) +} + fn parse_protocol_request_data<'a>( pr: &'a ProtocolRequest, ) -> Result, Box> { @@ -36,7 +77,14 @@ fn parse_protocol_request_data<'a>( return Ok(Cow::Owned(DeJson::deserialize_json(std::str::from_utf8( &decoded, )?)?)); - } + } else if pr.protocol == "openid4vp-v1-multisigned" { + log::debug!("Handling multisigned OpenID4VP request"); + let payload_str = extract_multisigned_payload(pr)?; + let decoded = decode_base64url(&payload_str)?; + return Ok(Cow::Owned(DeJson::deserialize_json(std::str::from_utf8( + &decoded, + )?)?)); + } log::debug!("Handling unsigned OpenID4VP request"); if let Some(data) = &pr.data { @@ -110,7 +158,10 @@ pub fn openid4vp_main(credman: &mut impl CredmanApi) -> Result<(), Box Date: Wed, 22 Jul 2026 23:57:45 +0000 Subject: [PATCH 2/2] address reviewer feedback --- matcher-rs/src/openid4vp.rs | 74 ++++++++++++++++++------------------- 1 file changed, 36 insertions(+), 38 deletions(-) diff --git a/matcher-rs/src/openid4vp.rs b/matcher-rs/src/openid4vp.rs index 7206336..b3ec3e8 100644 --- a/matcher-rs/src/openid4vp.rs +++ b/matcher-rs/src/openid4vp.rs @@ -6,37 +6,49 @@ use crate::reporter::report_match_result; use nanoserde::DeJson; use std::borrow::Cow; -fn extract_multisigned_payload<'a>( +fn extract_request_str<'a>( pr: &'a ProtocolRequest, -) -> Result> { - let json_str: &str = if let Some(data) = &pr.data { +) -> Result<&'a str, Box> { + if let Some(data) = &pr.data { match data { - ProtocolRequestData::String(s) => s.as_str(), - ProtocolRequestData::Object(obj) => obj.request.as_str(), + ProtocolRequestData::String(s) => Ok(s.as_str()), + ProtocolRequestData::Object(obj) => { + if obj.request.is_empty() { + return Err("Missing 'request' field in data object".into()); + } + Ok(obj.request.as_str()) + } } } else if !pr.request.is_empty() { - pr.request.as_str() + Ok(pr.request.as_str()) } else { - return Err("Missing multisigned request data".into()); - }; + Err("Missing request data".into()) + } +} +fn extract_multisigned_payload( + pr: &ProtocolRequest, +) -> Result> { + let json_str = extract_request_str(pr)?; let parsed: JsonValue = DeJson::deserialize_json(json_str)?; - let payload = match &parsed { - JsonValue::Object(map) => { - if let Some(JsonValue::Object(req_map)) = map.get("request") { - if let Some(JsonValue::String(p)) = req_map.get("payload") { - p.clone() - } else { - return Err("Missing 'payload' in 'request' object".into()); - } - } else if let Some(JsonValue::String(p)) = map.get("payload") { - p.clone() - } else { - return Err("Missing 'payload' field in multisigned request".into()); - } - } - _ => return Err("Multisigned request must be a JSON object".into()), + let JsonValue::Object(mut map) = parsed else { + return Err("Multisigned request must be a JSON object".into()); + }; + + let payload = if let Some(req_val) = map.shift_remove("request") { + let JsonValue::Object(mut req_map) = req_val else { + return Err("Missing 'payload' in 'request' object".into()); + }; + let Some(JsonValue::String(p)) = req_map.shift_remove("payload") else { + return Err("Missing 'payload' in 'request' object".into()); + }; + p + } else { + let Some(JsonValue::String(p)) = map.shift_remove("payload") else { + return Err("Missing 'payload' field in multisigned request".into()); + }; + p }; if payload.is_empty() { @@ -51,21 +63,7 @@ fn parse_protocol_request_data<'a>( ) -> Result, Box> { if pr.protocol == "openid4vp-v1-signed" { log::debug!("Handling signed OpenID4VP request"); - let jws: &'a str = if let Some(data) = &pr.data { - match data { - ProtocolRequestData::String(s) => s, - ProtocolRequestData::Object(obj) => { - if obj.request.is_empty() { - return Err("Missing 'request' field in signed data object".into()); - } - &obj.request - } - } - } else if !pr.request.is_empty() { - &pr.request - } else { - return Err("Missing signed request data".into()); - }; + let jws = extract_request_str(pr)?; let parts: Vec<&str> = jws.split('.').collect(); if parts.len() < 2 {