Skip to content

Commit

Permalink
fetch issuer's JWK (to ease verification)
Browse files Browse the repository at this point in the history
  • Loading branch information
UMR1352 committed Sep 20, 2024
1 parent 0a1ed27 commit 8d00263
Show file tree
Hide file tree
Showing 9 changed files with 123 additions and 58 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ members = [
"identity_ecdsa_verifier",
"identity_eddsa_verifier",
"examples",
"compound_resolver",
"compound_resolver",
]

exclude = ["bindings/wasm", "bindings/grpc"]
Expand Down
74 changes: 24 additions & 50 deletions identity_credential/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,39 +12,24 @@ rust-version.workspace = true
description = "An implementation of the Verifiable Credentials standard."

[dependencies]
anyhow = { version = "1" }
async-trait = { version = "0.1.64", default-features = false }
bls12_381_plus = { workspace = true, optional = true }
flate2 = { version = "1.0.28", default-features = false, features = [
"rust_backend",
], optional = true }
flate2 = { version = "1.0.28", default-features = false, features = ["rust_backend"], optional = true }
futures = { version = "0.3", default-features = false, optional = true, features = ["alloc"] }
identity_core = { version = "=1.3.1", path = "../identity_core", default-features = false }
identity_did = { version = "=1.3.1", path = "../identity_did", default-features = false }
identity_document = { version = "=1.3.1", path = "../identity_document", default-features = false }
identity_verification = { version = "=1.3.1", path = "../identity_verification", default-features = false }
indexmap = { version = "2.0", default-features = false, features = [
"std",
"serde",
] }
itertools = { version = "0.11", default-features = false, features = [
"use_std",
], optional = true }
indexmap = { version = "2.0", default-features = false, features = ["std", "serde"] }
itertools = { version = "0.11", default-features = false, features = ["use_std"], optional = true }
json-proof-token = { workspace = true, optional = true }
jsonschema = { version = "0.19", optional = true, default-features = false }
once_cell = { version = "1.18", default-features = false, features = ["std"] }
reqwest = { version = "0.11", default-features = false, features = [
"default-tls",
"json",
"stream",
], optional = true }
roaring = { version = "0.10.2", default-features = false, features = [
"serde",
], optional = true }
sd-jwt-payload = { version = "0.2.1", default-features = false, features = [
"sha",
], optional = true }
sd-jwt-payload-rework = { package = "sd-jwt-payload", git = "https://github.com/iotaledger/sd-jwt-payload.git", branch = "feat/sd-jwt-v11", default-features = false, features = [
"sha",
], optional = true }
reqwest = { version = "0.11", default-features = false, features = ["default-tls", "json", "stream"], optional = true }
roaring = { version = "0.10.2", default-features = false, features = ["serde"], optional = true }
sd-jwt-payload = { version = "0.2.1", default-features = false, features = ["sha"], optional = true }
sd-jwt-payload-rework = { package = "sd-jwt-payload", git = "https://github.com/iotaledger/sd-jwt-payload.git", branch = "feat/sd-jwt-v11", default-features = false, features = ["sha"], optional = true }
serde.workspace = true
serde-aux = { version = "4.3.1", default-features = false }
serde_json.workspace = true
Expand All @@ -53,24 +38,13 @@ strum.workspace = true
thiserror.workspace = true
url = { version = "2.5", default-features = false }
zkryptium = { workspace = true, optional = true }
anyhow = { version = "1" }
jsonschema = { version = "0.19", optional = true, default-features = false }

[dev-dependencies]
anyhow = "1.0.62"
identity_eddsa_verifier = { path = "../identity_eddsa_verifier", default-features = false, features = [
"ed25519",
] }
iota-crypto = { version = "0.23.2", default-features = false, features = [
"ed25519",
"std",
"random",
] }
identity_eddsa_verifier = { path = "../identity_eddsa_verifier", default-features = false, features = ["ed25519"] }
iota-crypto = { version = "0.23.2", default-features = false, features = ["ed25519", "std", "random"] }
proptest = { version = "1.4.0", default-features = false, features = ["std"] }
tokio = { version = "1.35.0", default-features = false, features = [
"rt-multi-thread",
"macros",
] }
tokio = { version = "1.35.0", default-features = false, features = ["rt-multi-thread", "macros"] }

[package.metadata.docs.rs]
# To build locally:
Expand All @@ -80,13 +54,13 @@ rustdoc-args = ["--cfg", "docsrs"]

[features]
default = [
"revocation-bitmap",
"validator",
"credential",
"presentation",
"domain-linkage-fetch",
"sd-jwt",
"sd-jwt-vc",
"revocation-bitmap",
"validator",
"credential",
"presentation",
"domain-linkage-fetch",
"sd-jwt",
"sd-jwt-vc",
]
credential = []
presentation = ["credential"]
Expand All @@ -98,11 +72,11 @@ domain-linkage-fetch = ["domain-linkage", "dep:reqwest", "dep:futures"]
sd-jwt = ["credential", "validator", "dep:sd-jwt-payload"]
sd-jwt-vc = ["sd-jwt", "dep:sd-jwt-payload-rework", "dep:jsonschema"]
jpt-bbs-plus = [
"credential",
"validator",
"dep:zkryptium",
"dep:bls12_381_plus",
"dep:json-proof-token",
"credential",
"validator",
"dep:zkryptium",
"dep:bls12_381_plus",
"dep:json-proof-token",
]

[lints]
Expand Down
3 changes: 3 additions & 0 deletions identity_credential/src/sd_jwt_vc/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ pub enum Error {
/// Credential validation failed.
#[error("credential validation failed: {0}")]
Validation(#[source] anyhow::Error),
/// SD-JWT VC signature verification failed.
#[error("verification failed: {0}")]
Verification(#[source] anyhow::Error),
}

/// Either a value of type `T` or an [`Error`].
Expand Down
7 changes: 2 additions & 5 deletions identity_credential/src/sd_jwt_vc/metadata/issuer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0

use identity_core::common::Url;
use identity_verification::jwk::Jwk;
use identity_verification::jwk::JwkSet;
use serde::Deserialize;
use serde::Serialize;

Expand Down Expand Up @@ -51,10 +51,7 @@ pub enum Jwks {
Uri(Url),
/// An embedded JWK set.
#[serde(rename = "jwks")]
Object {
/// List of JWKs.
keys: Vec<Jwk>,
},
Object(JwkSet),
}

#[cfg(test)]
Expand Down
8 changes: 8 additions & 0 deletions identity_credential/src/sd_jwt_vc/metadata/vc_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,14 @@ impl TypeMetadata {
pub fn extends_integrity(&self) -> Option<&str> {
self.extends_integrity.as_ref().map(|meta| meta.as_ref())
}
/// Returns the [`ClaimMetadata`]s associated with this credential type.
pub fn claim_metadata(&self) -> &[ClaimMetadata] {
&self.claims
}
/// Returns the [`DisplayMetadata`]s associated with this credential type.
pub fn display_metadata(&self) -> &[DisplayMetadata] {
&self.display
}
/// Uses this [`TypeMetadata`] to validate JSON object `credential`. This method fails
/// if the schema is referenced instead of embedded.
/// Use [`TypeMetadata::validate_credential_with_resolver`] for such cases.
Expand Down
2 changes: 2 additions & 0 deletions identity_credential/src/sd_jwt_vc/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ mod presentation;
mod resolver;
mod status;
mod token;
mod validation;

pub use claims::*;
pub use error::Error;
Expand All @@ -18,3 +19,4 @@ pub use presentation::*;
pub use resolver::*;
pub use status::*;
pub use token::*;
pub use validation::*;
70 changes: 70 additions & 0 deletions identity_credential/src/sd_jwt_vc/token.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use std::str::FromStr;

use super::claims::SdJwtVcClaims;
use super::metadata::IssuerMetadata;
use super::metadata::Jwks;
use super::metadata::TypeMetadata;
#[allow(unused_imports)]
use super::metadata::WELL_KNOWN_VCT;
Expand All @@ -16,8 +17,11 @@ use super::Error;
use super::Resolver;
use super::Result;
use super::SdJwtVcPresentationBuilder;
use anyhow::anyhow;
use identity_core::common::StringOrUrl;
use identity_core::common::Url;
use identity_verification::jwk::Jwk;
use identity_verification::jwk::JwkSet;
use sd_jwt_payload_rework::Hasher;
use sd_jwt_payload_rework::JsonObject;
use sd_jwt_payload_rework::SdJwt;
Expand Down Expand Up @@ -121,7 +125,73 @@ impl SdJwtVc {

Ok((metadata, raw))
}

/// Resolves the issuer's public key in JWK format.
pub async fn issuer_jwk<R>(&self, resolver: &R) -> Result<Jwk>
where
R: Resolver<Url, Target = Vec<u8>>,
{
let kid = self
.header()
.get("kid")
.and_then(|value| value.as_str())
.ok_or_else(|| Error::Verification(anyhow!("missing header claim `kid`")))?;

// Try to find the key among issuer metadata jwk set.
if let jwk @ Ok(_) = self.issuer_jwk_from_iss_metadata(resolver, kid).await {
jwk
} else {
// Issuer has no metadata that can lead to its JWK. Let's see if it can be resolved directly.
let jwk_uri = kid.parse::<Url>().map_err(|e| Error::Verification(e.into()))?;
resolver
.resolve(&jwk_uri)
.await
.map_err(|e| Error::Resolution {
input: jwk_uri.to_string(),
source: e,
})
.and_then(|bytes| {
serde_json::from_slice(&bytes).map_err(|e| Error::Verification(anyhow!("invalid JWK: {}", e)))
})
}
}

async fn issuer_jwk_from_iss_metadata<R>(&self, resolver: &R, kid: &str) -> Result<Jwk>
where
R: Resolver<Url, Target = Vec<u8>>,
{
let metadata = self
.issuer_metadata(resolver)
.await?
.ok_or_else(|| Error::Verification(anyhow!("missing issuer metadata")))?;
metadata.validate(self)?;

let jwks = match metadata.jwks {
Jwks::Object(jwks) => jwks,
Jwks::Uri(jwks_uri) => resolver
.resolve(&jwks_uri)
.await
.map_err(|e| Error::Resolution {
input: jwks_uri.into_string(),
source: e,
})
.and_then(|bytes| serde_json::from_slice::<JwkSet>(&bytes).map_err(|e| Error::Verification(e.into())))?,
};
jwks
.iter()
.find(|jwk| jwk.kid() == Some(kid))
.cloned()
.ok_or_else(|| Error::Verification(anyhow!("missing key \"{kid}\" in issuer JWK set")))
}
}
// Verifies SD-JWT signature.
// pub async fn verify<R, V>(&self, resolver: &R, jws_verifier: &V) -> Result<(), SignatureVerificationError>
// where
// R: Resolver<Url, Target = Jwk>,
// V: JwsVerifier,
// {
// // Fetch the issuer's public key.
// }

/// Converts `vct` claim's URI value into the appropriate well-known URL.
/// ## Warnings
Expand Down
11 changes: 11 additions & 0 deletions identity_credential/src/sd_jwt_vc/validation.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
use crate::validator::JwtCredentialValidationOptions;

/// Options to decide which operations should be performed during SD-JWT VC validation.
#[derive(Debug, Clone)]
pub struct ValidationOptions {
/// Credential validation options.
pub credential_validation_options: JwtCredentialValidationOptions,
/// The credential will be checked using the credential type
/// specified through the `vct` claim.
pub vct: bool,
}
4 changes: 2 additions & 2 deletions identity_resolver/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,16 @@ rust-version.workspace = true

[dependencies]
anyhow = "1.0.86"
thiserror.workspace = true
iota-sdk = { version = "1.1.5" }
# This is currently necessary for the ResolutionHandler trait. This can be made an optional dependency if alternative ways of attaching handlers are introduced.
async-trait = { version = "0.1", default-features = false }
futures = { version = "0.3" }
identity_core = { version = "=1.3.1", path = "../identity_core", default-features = false }
identity_did = { version = "=1.3.1", path = "../identity_did", default-features = false }
identity_document = { version = "=1.3.1", path = "../identity_document", default-features = false }
iota-sdk = { version = "1.1.5" }
serde = { version = "1.0", default-features = false, features = ["std", "derive"] }
strum.workspace = true
thiserror.workspace = true

[dependencies.identity_iota_core]
version = "=1.3.1"
Expand Down

0 comments on commit 8d00263

Please sign in to comment.