Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,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};
Expand Down Expand Up @@ -342,26 +343,28 @@ 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();
let format_from_query = credential_query_from_dcql.format;

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)?
Expand Down Expand Up @@ -457,12 +460,39 @@ async fn get_vp_token(
.parse::<SdJwt>()
.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(),
));
};

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());
Expand All @@ -476,21 +506,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()),
Expand Down
Loading