From a904a8bea4d36288c71e5994134e234141f4f871 Mon Sep 17 00:00:00 2001 From: Abdulrahim Al Methiab Date: Wed, 13 Sep 2023 18:07:10 +0200 Subject: [PATCH 1/4] add issuer claim check --- .../src/credential/jwt_serialization.rs | 298 ++++++++++++++++++ 1 file changed, 298 insertions(+) diff --git a/identity_credential/src/credential/jwt_serialization.rs b/identity_credential/src/credential/jwt_serialization.rs index c2fa7872e6..17d51c228a 100644 --- a/identity_credential/src/credential/jwt_serialization.rs +++ b/identity_credential/src/credential/jwt_serialization.rs @@ -97,6 +97,7 @@ where credential_subject: InnerCredentialSubject::new(subject), issuance_date: None, expiration_date: None, + issuer: None, credential_schema: Cow::Borrowed(credential_schema), credential_status: credential_status.as_ref().map(Cow::Borrowed), refresh_service: Cow::Borrowed(refresh_service), @@ -118,6 +119,18 @@ where /// Checks whether the fields that are set in the `vc` object are consistent with the corresponding values /// set for the registered claims. fn check_consistency(&self) -> Result<()> { + // Check consistency of issuer. + let issuer_from_claims: &Issuer = self.iss.as_ref(); + if !self + .vc + .issuer + .as_ref() + .map(|value| value == issuer_from_claims) + .unwrap_or(true) + { + return Err(Error::InconsistentCredentialJwtClaims("inconsistent issuer")); + }; + // Check consistency of issuanceDate let issuance_date_from_claims = self.issuance_date.to_issuance_date()?; if !self @@ -197,6 +210,7 @@ where properties, proof, issuance_date: _, + issuer: _, expiration_date: _, } = vc; @@ -302,6 +316,8 @@ where /// One or more URIs defining the type of the `Credential`. #[serde(rename = "type")] types: Cow<'credential, OneOrMany>, + /// The issuer of the `Credential`. + issuer: Option, /// One or more `Object`s representing the `Credential` subject(s). #[serde(rename = "credentialSubject")] credential_subject: InnerCredentialSubject<'credential>, @@ -345,6 +361,7 @@ mod tests { use identity_core::convert::ToJson; use crate::credential::Credential; + use crate::Error; use super::CredentialJwtClaims; @@ -409,4 +426,285 @@ mod tests { assert_eq!(credential, retrieved_credential); } + + #[test] + fn claims_duplication() { + let credential_json: &str = r#" + { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://www.w3.org/2018/credentials/examples/v1" + ], + "id": "http://example.edu/credentials/3732", + "type": ["VerifiableCredential", "UniversityDegreeCredential"], + "issuer": "https://example.edu/issuers/14", + "issuanceDate": "2010-01-01T19:23:24Z", + "expirationDate": "2025-09-13T15:56:23Z", + "credentialSubject": { + "id": "did:example:ebfeb1f712ebc6f1c276e12ec21", + "degree": { + "type": "BachelorDegree", + "name": "Bachelor of Science in Mechanical Engineering" + } + } + }"#; + + // `sub`, `jti`, `iss`, `nbf` are duplicated in `vc`. + let claims_json: &str = r#" + { + "sub": "did:example:ebfeb1f712ebc6f1c276e12ec21", + "jti": "http://example.edu/credentials/3732", + "iss": "https://example.edu/issuers/14", + "nbf": 1262373804, + "exp": 1757778983, + "vc": { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://www.w3.org/2018/credentials/examples/v1" + ], + "id": "http://example.edu/credentials/3732", + "type": ["VerifiableCredential", "UniversityDegreeCredential"], + "issuer": "https://example.edu/issuers/14", + "issuanceDate": "2010-01-01T19:23:24Z", + "expirationDate": "2025-09-13T15:56:23Z", + "credentialSubject": { + "id": "did:example:ebfeb1f712ebc6f1c276e12ec21", + "degree": { + "type": "BachelorDegree", + "name": "Bachelor of Science in Mechanical Engineering" + } + } + } + }"#; + + let credential: Credential = Credential::from_json(credential_json).unwrap(); + let credential_from_claims: Credential = CredentialJwtClaims::<'_, Object>::from_json(&claims_json) + .unwrap() + .try_into_credential() + .unwrap(); + + assert_eq!(credential, credential_from_claims); + } + + #[test] + fn inconsistent_issuer() { + // issuer is inconsistent (15 instead of 14). + let claims_json: &str = r#" + { + "sub": "did:example:ebfeb1f712ebc6f1c276e12ec21", + "jti": "http://example.edu/credentials/3732", + "iss": "https://example.edu/issuers/14", + "nbf": 1262373804, + "vc": { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://www.w3.org/2018/credentials/examples/v1" + ], + "type": ["VerifiableCredential", "UniversityDegreeCredential"], + "issuer": "https://example.edu/issuers/15", + "credentialSubject": { + "degree": { + "type": "BachelorDegree", + "name": "Bachelor of Science in Mechanical Engineering" + } + } + } + }"#; + + let credential_from_claims_result: Result = + CredentialJwtClaims::<'_, Object>::from_json(&claims_json) + .unwrap() + .try_into_credential(); + assert!(matches!( + credential_from_claims_result.unwrap_err(), + Error::InconsistentCredentialJwtClaims("inconsistent issuer") + )); + } + + #[test] + fn inconsistent_id() { + let claims_json: &str = r#" + { + "sub": "did:example:ebfeb1f712ebc6f1c276e12ec21", + "jti": "http://example.edu/credentials/3732", + "iss": "https://example.edu/issuers/14", + "nbf": 1262373804, + "vc": { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://www.w3.org/2018/credentials/examples/v1" + ], + "type": ["VerifiableCredential", "UniversityDegreeCredential"], + "id": "http://example.edu/credentials/1111", + "credentialSubject": { + "degree": { + "type": "BachelorDegree", + "name": "Bachelor of Science in Mechanical Engineering" + } + } + } + }"#; + + let credential_from_claims_result: Result = + CredentialJwtClaims::<'_, Object>::from_json(&claims_json) + .unwrap() + .try_into_credential(); + assert!(matches!( + credential_from_claims_result.unwrap_err(), + Error::InconsistentCredentialJwtClaims("inconsistent credential id") + )); + } + + #[test] + fn inconsistent_subject() { + // issuer is inconsistent (15 instead of 14). + let claims_json: &str = r#" + { + "sub": "did:example:ebfeb1f712ebc6f1c276e12ec21", + "jti": "http://example.edu/credentials/3732", + "iss": "https://example.edu/issuers/14", + "nbf": 1262373804, + "vc": { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://www.w3.org/2018/credentials/examples/v1" + ], + "id": "http://example.edu/credentials/3732", + "type": ["VerifiableCredential", "UniversityDegreeCredential"], + "issuer": "https://example.edu/issuers/14", + "issuanceDate": "2010-01-01T19:23:24Z", + "credentialSubject": { + "id": "did:example:1111111111111111111111111", + "degree": { + "type": "BachelorDegree", + "name": "Bachelor of Science in Mechanical Engineering" + } + } + } + }"#; + + let credential_from_claims_result: Result = + CredentialJwtClaims::<'_, Object>::from_json(&claims_json) + .unwrap() + .try_into_credential(); + assert!(matches!( + credential_from_claims_result.unwrap_err(), + Error::InconsistentCredentialJwtClaims("inconsistent credentialSubject: identifiers do not match") + )); + } + + #[test] + fn inconsistent_issuance_date() { + // issuer is inconsistent (15 instead of 14). + let claims_json: &str = r#" + { + "sub": "did:example:ebfeb1f712ebc6f1c276e12ec21", + "jti": "http://example.edu/credentials/3732", + "iss": "https://example.edu/issuers/14", + "nbf": 1262373804, + "vc": { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://www.w3.org/2018/credentials/examples/v1" + ], + "id": "http://example.edu/credentials/3732", + "type": ["VerifiableCredential", "UniversityDegreeCredential"], + "issuer": "https://example.edu/issuers/14", + "issuanceDate": "2020-01-01T19:23:24Z", + "credentialSubject": { + "id": "did:example:ebfeb1f712ebc6f1c276e12ec21", + "degree": { + "type": "BachelorDegree", + "name": "Bachelor of Science in Mechanical Engineering" + } + } + } + }"#; + + let credential_from_claims_result: Result = + CredentialJwtClaims::<'_, Object>::from_json(&claims_json) + .unwrap() + .try_into_credential(); + assert!(matches!( + credential_from_claims_result.unwrap_err(), + Error::InconsistentCredentialJwtClaims("inconsistent issuanceDate") + )); + } + + #[test] + fn inconsistent_expiration_date() { + // issuer is inconsistent (15 instead of 14). + let claims_json: &str = r#" + { + "sub": "did:example:ebfeb1f712ebc6f1c276e12ec21", + "jti": "http://example.edu/credentials/3732", + "iss": "https://example.edu/issuers/14", + "nbf": 1262373804, + "exp": 1757778983, + "vc": { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://www.w3.org/2018/credentials/examples/v1" + ], + "id": "http://example.edu/credentials/3732", + "type": ["VerifiableCredential", "UniversityDegreeCredential"], + "issuer": "https://example.edu/issuers/14", + "issuanceDate": "2010-01-01T19:23:24Z", + "expirationDate": "2026-09-13T15:56:23Z", + "credentialSubject": { + "id": "did:example:ebfeb1f712ebc6f1c276e12ec21", + "degree": { + "type": "BachelorDegree", + "name": "Bachelor of Science in Mechanical Engineering" + } + } + } + }"#; + + let credential_from_claims_result: Result = + CredentialJwtClaims::<'_, Object>::from_json(&claims_json) + .unwrap() + .try_into_credential(); + assert!(matches!( + credential_from_claims_result.unwrap_err(), + Error::InconsistentCredentialJwtClaims("inconsistent credential expirationDate") + )); + } + // #[test] + // fn inconsistent_id() { + // // issuer is inconsistent (15 instead of 14). + // let claims_json: &str = r#" + // { + // "sub": "did:example:ebfeb1f712ebc6f1c276e12ec21", + // "jti": "http://example.edu/credentials/3732", + // "iss": "https://example.edu/issuers/14", + // "nbf": 1262373804, + // "vc": { + // "@context": [ + // "https://www.w3.org/2018/credentials/v1", + // "https://www.w3.org/2018/credentials/examples/v1" + // ], + // "id": "http://example.edu/credentials/3732", + // "type": ["VerifiableCredential", "UniversityDegreeCredential"], + // "issuer": "https://example.edu/issuers/14", + // "issuanceDate": "2010-01-01T19:23:24Z", + // "credentialSubject": { + // "id": "did:example:ebfeb1f712ebc6f1c276e12ec21", + // "degree": { + // "type": "BachelorDegree", + // "name": "Bachelor of Science in Mechanical Engineering" + // } + // } + // } + // }"#; + // + // let credential_from_claims_result: Result = + // CredentialJwtClaims::<'_, Object>::from_json(&claims_json) + // .unwrap() + // .try_into_credential(); + // assert!(matches!( + // credential_from_claims_result.unwrap_err(), + // Error::InconsistentCredentialJwtClaims("inconsistent credential id") + // )); + // } } From 86f4c66e266837acc71035261f5c77020783a8b5 Mon Sep 17 00:00:00 2001 From: Abdulrahim Al Methiab Date: Wed, 13 Sep 2023 18:21:17 +0200 Subject: [PATCH 2/4] improvements --- .../src/credential/jwt_serialization.rs | 39 +------------------ 1 file changed, 1 insertion(+), 38 deletions(-) diff --git a/identity_credential/src/credential/jwt_serialization.rs b/identity_credential/src/credential/jwt_serialization.rs index 17d51c228a..ec61ae39a3 100644 --- a/identity_credential/src/credential/jwt_serialization.rs +++ b/identity_credential/src/credential/jwt_serialization.rs @@ -449,7 +449,7 @@ mod tests { } }"#; - // `sub`, `jti`, `iss`, `nbf` are duplicated in `vc`. + // `sub`, `exp`, `jti`, `iss`, `nbf` are duplicated in `vc`. let claims_json: &str = r#" { "sub": "did:example:ebfeb1f712ebc6f1c276e12ec21", @@ -670,41 +670,4 @@ mod tests { Error::InconsistentCredentialJwtClaims("inconsistent credential expirationDate") )); } - // #[test] - // fn inconsistent_id() { - // // issuer is inconsistent (15 instead of 14). - // let claims_json: &str = r#" - // { - // "sub": "did:example:ebfeb1f712ebc6f1c276e12ec21", - // "jti": "http://example.edu/credentials/3732", - // "iss": "https://example.edu/issuers/14", - // "nbf": 1262373804, - // "vc": { - // "@context": [ - // "https://www.w3.org/2018/credentials/v1", - // "https://www.w3.org/2018/credentials/examples/v1" - // ], - // "id": "http://example.edu/credentials/3732", - // "type": ["VerifiableCredential", "UniversityDegreeCredential"], - // "issuer": "https://example.edu/issuers/14", - // "issuanceDate": "2010-01-01T19:23:24Z", - // "credentialSubject": { - // "id": "did:example:ebfeb1f712ebc6f1c276e12ec21", - // "degree": { - // "type": "BachelorDegree", - // "name": "Bachelor of Science in Mechanical Engineering" - // } - // } - // } - // }"#; - // - // let credential_from_claims_result: Result = - // CredentialJwtClaims::<'_, Object>::from_json(&claims_json) - // .unwrap() - // .try_into_credential(); - // assert!(matches!( - // credential_from_claims_result.unwrap_err(), - // Error::InconsistentCredentialJwtClaims("inconsistent credential id") - // )); - // } } From 55ea11ff2fa09e5f8a83ce795978876552e8ddff Mon Sep 17 00:00:00 2001 From: Abdulrahim Al Methiab Date: Thu, 14 Sep 2023 10:31:21 +0200 Subject: [PATCH 3/4] skip serializing issuer if none --- identity_credential/src/credential/jwt_serialization.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/identity_credential/src/credential/jwt_serialization.rs b/identity_credential/src/credential/jwt_serialization.rs index ec61ae39a3..4b1d2a1864 100644 --- a/identity_credential/src/credential/jwt_serialization.rs +++ b/identity_credential/src/credential/jwt_serialization.rs @@ -317,6 +317,7 @@ where #[serde(rename = "type")] types: Cow<'credential, OneOrMany>, /// The issuer of the `Credential`. + #[serde(skip_serializing_if = "Option::is_none")] issuer: Option, /// One or more `Object`s representing the `Credential` subject(s). #[serde(rename = "credentialSubject")] From 626ada53ccca9f7bb4d1164040fe581059de7dff Mon Sep 17 00:00:00 2001 From: Abdulrahim Al Methiab <31316147+abdulmth@users.noreply.github.com> Date: Wed, 20 Sep 2023 09:38:17 +0200 Subject: [PATCH 4/4] small fix --- identity_credential/src/credential/jwt_serialization.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/identity_credential/src/credential/jwt_serialization.rs b/identity_credential/src/credential/jwt_serialization.rs index 4b1d2a1864..460b35fae8 100644 --- a/identity_credential/src/credential/jwt_serialization.rs +++ b/identity_credential/src/credential/jwt_serialization.rs @@ -558,7 +558,6 @@ mod tests { #[test] fn inconsistent_subject() { - // issuer is inconsistent (15 instead of 14). let claims_json: &str = r#" { "sub": "did:example:ebfeb1f712ebc6f1c276e12ec21",