diff --git a/identity_credential/src/credential/jwt_serialization.rs b/identity_credential/src/credential/jwt_serialization.rs index 7a6ce49154..a943cfd325 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,9 @@ where /// One or more URIs defining the type of the `Credential`. #[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")] credential_subject: InnerCredentialSubject<'credential>, @@ -345,6 +362,7 @@ mod tests { use identity_core::convert::ToJson; use crate::credential::Credential; + use crate::Error; use super::CredentialJwtClaims; @@ -409,4 +427,247 @@ 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`, `exp`, `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() { + 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") + )); + } }