Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SD-JWT VC implementation #1413

Open
wants to merge 31 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
b7853fb
Resolver trait and CompoundResolver macro
UMR1352 Jun 11, 2024
0114b35
invert Resolver type parameters
UMR1352 Jun 11, 2024
88a538e
associated type Target instead of type parameter T
UMR1352 Jun 11, 2024
bfd5d59
fix type issue in #[resolver(..)] annotation, support for multiple re…
UMR1352 Jun 12, 2024
5671118
resolver integration
UMR1352 Jun 13, 2024
d7c2cd9
Merge branch 'main' into identity2/resolver
UMR1352 Sep 16, 2024
7c815c5
feature gate resolver-v2
UMR1352 Sep 16, 2024
b28fa9a
structures & basic operations
UMR1352 Sep 17, 2024
2f7fadf
SdJwtVc behaves as a superset of SdJwt
UMR1352 Sep 17, 2024
0d3127f
issuer's metadata fetching & validation
UMR1352 Sep 18, 2024
55319c5
type metadata & credential verification
UMR1352 Sep 19, 2024
704b395
change resolver's constraints
UMR1352 Sep 19, 2024
130af00
integrity metadata
UMR1352 Sep 19, 2024
d278060
display metadata
UMR1352 Sep 20, 2024
0a1ed27
claim metadata
UMR1352 Sep 20, 2024
8d00263
fetch issuer's JWK (to ease verification)
UMR1352 Sep 20, 2024
2111e3b
validate claim disclosability
UMR1352 Sep 24, 2024
06e31f2
add missing license header
UMR1352 Sep 24, 2024
e5e8537
resolver change, validation
UMR1352 Sep 24, 2024
9bf907f
SdJwtVcBuilder & tests
UMR1352 Sep 30, 2024
67a0abc
validation test
UMR1352 Oct 1, 2024
a510185
KB-JWT validation
UMR1352 Oct 2, 2024
654b188
review comment
UMR1352 Oct 11, 2024
72333bb
undo resolver-v2
UMR1352 Dec 11, 2024
468809e
Merge branch 'main' into feat/sd-jwt-vc
UMR1352 Dec 11, 2024
3f992c4
fix CI errors
UMR1352 Dec 11, 2024
66f7b79
make clippy happy
UMR1352 Dec 11, 2024
03f1483
add missing license headers
UMR1352 Dec 11, 2024
51e1b28
add 'SdJwtVcBuilder::from_credential' to easily convert into a SD-JWT VC
UMR1352 Dec 11, 2024
035b50f
cargo clippy
UMR1352 Dec 12, 2024
f2c0704
fix wasm compilation errors, clippy
UMR1352 Dec 12, 2024
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
1 change: 0 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ edition = "2021"
homepage = "https://www.iota.org"
license = "Apache-2.0"
repository = "https://github.com/iotaledger/identity.rs"
rust-version = "1.65"

[workspace.lints.clippy]
result_large_err = "allow"
2 changes: 1 addition & 1 deletion bindings/wasm/src/common/imported_document_lock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ impl From<&ArrayIToCoreDocument> for Vec<ImportedDocumentLock> {

pub(crate) struct ImportedDocumentReadGuard<'a>(tokio::sync::RwLockReadGuard<'a, CoreDocument>);

impl<'a> AsRef<CoreDocument> for ImportedDocumentReadGuard<'a> {
impl AsRef<CoreDocument> for ImportedDocumentReadGuard<'_> {
fn as_ref(&self) -> &CoreDocument {
self.0.as_ref()
}
Expand Down
2 changes: 1 addition & 1 deletion bindings/wasm/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ fn error_chain_fmt(e: &impl std::error::Error, f: &mut std::fmt::Formatter<'_>)

struct ErrorMessage<'a, E: std::error::Error>(&'a E);

impl<'a, E: std::error::Error> Display for ErrorMessage<'a, E> {
impl<E: std::error::Error> Display for ErrorMessage<'_, E> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
error_chain_fmt(self.0, f)
}
Expand Down
1 change: 0 additions & 1 deletion identity_core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ keywords = ["iota", "tangle", "identity"]
license.workspace = true
readme = "./README.md"
repository.workspace = true
rust-version.workspace = true
description = "The core traits and types for the identity-rs library."

[dependencies]
Expand Down
2 changes: 2 additions & 0 deletions identity_core/src/common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ pub use self::single_struct_error::*;
pub use self::timestamp::Duration;
pub use self::timestamp::Timestamp;
pub use self::url::Url;
pub use string_or_url::StringOrUrl;

mod context;
mod key_comparable;
Expand All @@ -22,5 +23,6 @@ mod one_or_many;
mod one_or_set;
mod ordered_set;
mod single_struct_error;
mod string_or_url;
mod timestamp;
mod url;
151 changes: 151 additions & 0 deletions identity_core/src/common/string_or_url.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
// Copyright 2020-2024 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

use std::convert::Infallible;
use std::fmt::Display;
use std::str::FromStr;

use serde::Deserialize;
use serde::Serialize;

use super::Url;

/// A type that represents either an arbitrary string or a URL.
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(untagged)]
pub enum StringOrUrl {
/// A well-formed URL.
Url(Url),
/// An arbitrary UTF-8 string.
String(String),
}

impl StringOrUrl {
/// Parses a [`StringOrUrl`] from a string.
pub fn parse(s: &str) -> Result<Self, Infallible> {
s.parse()
}
/// Returns a [`Url`] reference if `self` is [`StringOrUrl::Url`].
pub fn as_url(&self) -> Option<&Url> {
match self {
Self::Url(url) => Some(url),
_ => None,
}
}

/// Returns a [`str`] reference if `self` is [`StringOrUrl::String`].
pub fn as_string(&self) -> Option<&str> {
match self {
Self::String(s) => Some(s),
_ => None,
}
}

/// Returns whether `self` is a [`StringOrUrl::Url`].
pub fn is_url(&self) -> bool {
matches!(self, Self::Url(_))
}

/// Returns whether `self` is a [`StringOrUrl::String`].
pub fn is_string(&self) -> bool {
matches!(self, Self::String(_))
}
}

impl Default for StringOrUrl {
fn default() -> Self {
StringOrUrl::String(String::default())
}
}

impl Display for StringOrUrl {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Url(url) => write!(f, "{url}"),
Self::String(s) => write!(f, "{s}"),
}
}
}

impl FromStr for StringOrUrl {
// Cannot fail.
type Err = Infallible;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(
s.parse::<Url>()
.map(Self::Url)
.unwrap_or_else(|_| Self::String(s.to_string())),
)
}
}

impl AsRef<str> for StringOrUrl {
fn as_ref(&self) -> &str {
match self {
Self::String(s) => s,
Self::Url(url) => url.as_str(),
}
}
}

impl From<Url> for StringOrUrl {
fn from(value: Url) -> Self {
Self::Url(value)
}
}

impl From<String> for StringOrUrl {
fn from(value: String) -> Self {
Self::String(value)
}
}

impl From<StringOrUrl> for String {
fn from(value: StringOrUrl) -> Self {
match value {
StringOrUrl::String(s) => s,
StringOrUrl::Url(url) => url.into_string(),
}
}
}

#[cfg(test)]
mod tests {
use super::*;

#[derive(Debug, Serialize, Deserialize)]
struct TestData {
string_or_url: StringOrUrl,
}

impl Default for TestData {
fn default() -> Self {
Self {
string_or_url: StringOrUrl::Url(TEST_URL.parse().unwrap()),
}
}
}

const TEST_URL: &str = "file:///tmp/file.txt";

#[test]
fn deserialization_works() {
let test_data: TestData = serde_json::from_value(serde_json::json!({ "string_or_url": TEST_URL })).unwrap();
let target_url: Url = TEST_URL.parse().unwrap();
assert_eq!(test_data.string_or_url.as_url(), Some(&target_url));
}

#[test]
fn serialization_works() {
assert_eq!(
serde_json::to_value(TestData::default()).unwrap(),
serde_json::json!({ "string_or_url": TEST_URL })
)
}

#[test]
fn parsing_works() {
assert!(TEST_URL.parse::<StringOrUrl>().unwrap().is_url());
assert!("I'm a random string :)".parse::<StringOrUrl>().unwrap().is_string());
}
}
27 changes: 23 additions & 4 deletions identity_credential/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,27 @@ keywords = ["iota", "tangle", "identity"]
license.workspace = true
readme = "./README.md"
repository.workspace = true
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 }
futures = { version = "0.3", default-features = false, optional = true }
futures = { version = "0.3", default-features = false, features = ["alloc"], optional = true }
identity_core = { version = "=1.4.0", path = "../identity_core", default-features = false }
identity_did = { version = "=1.4.0", path = "../identity_did", default-features = false }
identity_document = { version = "=1.4.0", path = "../identity_document", default-features = false }
identity_verification = { version = "=1.4.0", 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 }
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 }
serde.workspace = true
serde-aux = { version = "4.3.1", default-features = false }
serde_json.workspace = true
Expand All @@ -40,6 +42,7 @@ zkryptium = { workspace = true, optional = true }
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"] }
josekit = "0.8"
proptest = { version = "1.4.0", default-features = false, features = ["std"] }
tokio = { version = "1.35.0", default-features = false, features = ["rt-multi-thread", "macros"] }

Expand All @@ -50,7 +53,15 @@ all-features = true
rustdoc-args = ["--cfg", "docsrs"]

[features]
default = ["revocation-bitmap", "validator", "credential", "presentation", "domain-linkage-fetch", "sd-jwt"]
default = [
"revocation-bitmap",
"validator",
"credential",
"presentation",
"domain-linkage-fetch",
"sd-jwt",
"sd-jwt-vc",
]
credential = []
presentation = ["credential"]
revocation-bitmap = ["dep:flate2", "dep:roaring"]
Expand All @@ -59,7 +70,15 @@ validator = ["dep:itertools", "dep:serde_repr", "credential", "presentation"]
domain-linkage = ["validator"]
domain-linkage-fetch = ["domain-linkage", "dep:reqwest", "dep:futures"]
sd-jwt = ["credential", "validator", "dep:sd-jwt-payload"]
jpt-bbs-plus = ["credential", "validator", "dep:zkryptium", "dep:bls12_381_plus", "dep:json-proof-token"]
sd-jwt-vc = ["sd-jwt", "dep:sd-jwt-payload-rework", "dep:jsonschema", "dep:futures"]
jpt-bbs-plus = [
"credential",
"validator",
"dep:zkryptium",
"dep:bls12_381_plus",
"dep:json-proof-token",
"dep:futures",
]

[lints]
workspace = true
4 changes: 2 additions & 2 deletions identity_credential/src/credential/jwt_serialization.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ impl<'credential, T> CredentialJwtClaims<'credential, T>
where
T: ToOwned<Owned = T> + Serialize + DeserializeOwned,
{
pub(super) fn new(credential: &'credential Credential<T>, custom: Option<Object>) -> Result<Self> {
pub(crate) fn new(credential: &'credential Credential<T>, custom: Option<Object>) -> Result<Self> {
let Credential {
context,
id,
Expand Down Expand Up @@ -118,7 +118,7 @@ where
}

#[cfg(feature = "validator")]
impl<'credential, T> CredentialJwtClaims<'credential, T>
impl<T> CredentialJwtClaims<'_, T>
where
T: ToOwned<Owned = T> + Serialize + DeserializeOwned,
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ use super::DomainLinkageValidationResult;
use crate::utils::url_only_includes_origin;

/// A validator for a Domain Linkage Configuration and Credentials.

pub struct JwtDomainLinkageValidator<V: JwsVerifier> {
validator: JwtCredentialValidator<V>,
}
Expand Down
5 changes: 5 additions & 0 deletions identity_credential/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,4 +79,9 @@ pub enum Error {
/// Cause by an invalid attribute path
#[error("Attribute Not found")]
SelectiveDisclosureError,

/// Failure of an SD-JWT VC operation.
#[cfg(feature = "sd-jwt-vc")]
#[error(transparent)]
SdJwtVc(#[from] crate::sd_jwt_vc::Error),
}
7 changes: 7 additions & 0 deletions identity_credential/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,15 @@ mod utils;
#[cfg(feature = "validator")]
pub mod validator;

/// Implementation of the SD-JWT VC token specification.
#[cfg(feature = "sd-jwt-vc")]
pub mod sd_jwt_vc;

pub use error::Error;
pub use error::Result;

#[cfg(feature = "sd-jwt")]
pub use sd_jwt_payload;

#[cfg(feature = "sd-jwt-vc")]
pub use sd_jwt_payload_rework as sd_jwt_v2;
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ where
}

#[cfg(feature = "validator")]
impl<'presentation, CRED, T> PresentationJwtClaims<'presentation, CRED, T>
impl<CRED, T> PresentationJwtClaims<'_, CRED, T>
where
CRED: ToOwned<Owned = CRED> + Serialize + DeserializeOwned + Clone,
T: ToOwned<Owned = T> + Serialize + DeserializeOwned,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ where
D: serde::Deserializer<'de>,
{
struct ExactStrVisitor(&'static str);
impl<'a> Visitor<'a> for ExactStrVisitor {
impl Visitor<'_> for ExactStrVisitor {
type Value = &'static str;
fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(formatter, "the exact string \"{}\"", self.0)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ where
D: serde::Deserializer<'de>,
{
struct ExactStrVisitor(&'static str);
impl<'a> Visitor<'a> for ExactStrVisitor {
impl Visitor<'_> for ExactStrVisitor {
type Value = &'static str;
fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(formatter, "the exact string \"{}\"", self.0)
Expand Down
Loading
Loading