From 52bcb891576490d0ce111245ac6703fd98bb2cb9 Mon Sep 17 00:00:00 2001 From: Coplat Date: Thu, 27 Aug 2026 17:07:11 +0200 Subject: [PATCH 1/2] fix: invalid holder did --- .../handle_oid4vp_authorization_request.rs | 68 +++++++++++-------- 1 file changed, 39 insertions(+), 29 deletions(-) diff --git a/identity-wallet/src/state/credentials/reducers/handle_oid4vp_authorization_request.rs b/identity-wallet/src/state/credentials/reducers/handle_oid4vp_authorization_request.rs index 2455a5334..8229efd9d 100644 --- a/identity-wallet/src/state/credentials/reducers/handle_oid4vp_authorization_request.rs +++ b/identity-wallet/src/state/credentials/reducers/handle_oid4vp_authorization_request.rs @@ -46,6 +46,7 @@ use oid4vc::oid4vp::{ use identity_credential::{credential::Jwt, presentation::Presentation}; use identity_iota::core::Url; +use identity_iota::did::DIDUrl; use jsonwebtoken::Algorithm; use jsonwebtoken::Header; use sd_jwt::{KeyBindingJwtBuilder, RequiredKeyBinding, SdJwt}; @@ -427,19 +428,6 @@ async fn get_vp_token( let dcql_query = &oid4vp_authorization_request.body.extension.dcql_query; let mut builder = VpTokenBuilder::builder_dcql_query(dcql_query.clone()); - let key_id = subject_manager - .key_id(did_method, signing_algorithm) - .await - .ok_or_else(|| AppError::Error(format!("Failed to get signing method ID for DID method {did_method}")))?; - - let full_did = subject_manager - .identifier(did_method, signing_algorithm) - .await - .map_err(|e| AppError::Error(format!("Failed to get DID identifier: {e}")))?; - - let holder_url: Url = - Url::parse(&full_did).map_err(|e| AppError::Error(format!("Failed to parse DID as URL: {e}")))?; - // TODO: Move most of this logic to `openid4vc` crates. for (credential_query_from_dcql, vc_value) in selected_verifiable_credentials { let credential_query_id = credential_query_from_dcql.id.clone(); @@ -447,6 +435,21 @@ async fn get_vp_token( let presentation_format_item = match format_from_query { Format::JwtVcJson => { + let key_id = subject_manager + .key_id(did_method, signing_algorithm) + .await + .ok_or_else(|| { + AppError::Error(format!("Failed to get signing method ID for DID method {did_method}")) + })?; + + let full_did = subject_manager + .identifier(did_method, signing_algorithm) + .await + .map_err(|e| AppError::Error(format!("Failed to get DID identifier: {e}")))?; + + let holder_url: Url = + Url::parse(&full_did).map_err(|e| AppError::Error(format!("Failed to parse DID as URL: {e}")))?; + let raw_vc_jwt_string = vc_value .as_str() .ok_or(AppError::InvalidCredentialFormatError)? @@ -542,12 +545,34 @@ async fn get_vp_token( .parse::() .map_err(|err| AppError::Error(format!("Failed to parse VCDM 2.0 SD-JWT: {err}")))?; + let Some(RequiredKeyBinding::Kid(cnf_kid)) = vcdm2_sd_jwt.claims().cnf.as_ref() else { + return Err(AppError::Error( + "Unsupported `cnf` claim in VCDM 2.0 SD-JWT".to_string(), + )); + }; + + let cnf_jwk = subject_manager + .resolve_public_key(cnf_kid) + .await + .map_err(|e| AppError::Error(format!("Failed to resolve `cnf` key from DID URL: {e}")))?; + + let algorithm = cnf_jwk + .alg() + .ok_or_else(|| AppError::Error("JWK missing `alg` parameter".to_string()))?; + + let holder_did = DIDUrl::parse(cnf_kid) + .map_err(|e| AppError::Error(format!("Failed to parse 'cnf' DID URL: {e}")))? + .did() + .to_string(); + let holder_url = + Url::parse(&holder_did).map_err(|e| AppError::Error(format!("Failed to parse holder URL: {e}")))?; + let id = VcDataUrl::parse(&format!("data:application/vc+sd-jwt,{vcdm2_sd_jwt}")) .map_err(|e| AppError::Error(format!("Failed to parse VcDataUrl: {e}")))?; let enveloped_credential = EnvelopedVc::new(id); let mut properties = IotaObject::default(); - properties.insert("iss".to_string(), full_did.clone().into()); + properties.insert("iss".to_string(), holder_did.clone().into()); properties.insert("aud".to_string(), verifier_audience.clone().into()); properties.insert("nonce".to_string(), required_nonce.clone().into()); properties.insert("iat".to_string(), Utc::now().timestamp().into()); @@ -561,21 +586,6 @@ async fn get_vp_token( .build_v2() .map_err(|e| AppError::Error(format!("Failed to build Presentation: {e}")))?; - let Some(RequiredKeyBinding::Kid(cnf_kid)) = vcdm2_sd_jwt.claims().cnf.as_ref() else { - return Err(AppError::Error( - "Unsupported `cnf` claim in VCDM 2.0 SD-JWT".to_string(), - )); - }; - - let cnf_jwk = subject_manager - .resolve_public_key(cnf_kid) - .await - .map_err(|e| AppError::Error(format!("Failed to resolve `cnf` key from DID URL: {e}")))?; - - let algorithm = cnf_jwk - .alg() - .ok_or_else(|| AppError::Error("JWK missing `alg` parameter".to_string()))?; - let jwt_header = Header { alg: Algorithm::from_str(algorithm).unwrap(), kid: Some(cnf_kid.clone()), From 1b7550b0c160be04c6aa1dc4e281b4dfc3872c37 Mon Sep 17 00:00:00 2001 From: Coplat Date: Fri, 4 Sep 2026 09:06:28 +0200 Subject: [PATCH 2/2] docs: add todo comment --- .../reducers/handle_oid4vp_authorization_request.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/identity-wallet/src/state/credentials/reducers/handle_oid4vp_authorization_request.rs b/identity-wallet/src/state/credentials/reducers/handle_oid4vp_authorization_request.rs index 8229efd9d..8d6e5598f 100644 --- a/identity-wallet/src/state/credentials/reducers/handle_oid4vp_authorization_request.rs +++ b/identity-wallet/src/state/credentials/reducers/handle_oid4vp_authorization_request.rs @@ -545,6 +545,11 @@ async fn get_vp_token( .parse::() .map_err(|err| AppError::Error(format!("Failed to parse VCDM 2.0 SD-JWT: {err}")))?; + // TODO: unify holder DID/key selection into a single helper. + // "Which DID/key do we sign with?" is answered differently at every call site: the `cnf` + // claim here, from `credentialSubject.id` when signing a DACT, and from the preferred + // DID method elsewhere. Each answer has to match the DID the credential was issued to, so they + // all need to behave identically. This should be unified into a single helper. let Some(RequiredKeyBinding::Kid(cnf_kid)) = vcdm2_sd_jwt.claims().cnf.as_ref() else { return Err(AppError::Error( "Unsupported `cnf` claim in VCDM 2.0 SD-JWT".to_string(),