use chrono::{Duration, Utc};
use sdjwt::{
decode, parse_yaml, sd_jwt_parts, Algorithm, Disclosure, Error, HashAlgorithm, Holder, Issuer,
Jwk, KeyForDecoding, KeyForEncoding, Validation, Verifier,
};
use std::collections::HashSet;
use common::{
compare_json_values, convert_to_pem, disclosures2vec, keys, publickey_to_jwk,
separate_jwt_and_disclosures,
};
use serde_json::value::Value;
mod common;
const TEST_CLAIMS: &str = r#"{
"sub": "user_42",
"given_name": "John",
"family_name": "Doe",
"email": "johndoe@example.com",
"phone_number": "+1-202-555-0101",
"phone_number_verified": true,
"address": {
"street_address": "123 Main St",
"locality": "Anytown",
"region": "Anystate",
"country": "US"
},
"birthdate": "1940-01-01",
"updated_at": 1570000000,
"nationalities": [
"US",
"DE"
]
}"#;
const TEST_CLAIMS_YAML: &str = r#"
sub: user_42
!sd given_name: John
!sd family_name: Doe
email: johndoe@example.com
phone_number: +1-202-555-0101
phone_number_verified: true
address:
!sd street_address: 123 Main St
!sd locality: Anytown
region: Anystate
country: US
birthdate: 1940-01-01
updated_at: 1570000000
nationalities:
- !sd US
- !sd DE
"#;
const TEST_VERIFIER_EXPECTED_CLAIMS: &str = r#"{
"sub": "user_42",
"given_name": "John",
"email": "johndoe@example.com",
"phone_number": "+1-202-555-0101",
"phone_number_verified": true,
"address": {
"locality": "Anytown",
"region": "Anystate",
"country": "US"
},
"birthdate": "1940-01-01",
"updated_at": 1570000000,
"nationalities": [
"DE"
]
}"#;
#[test]
fn test_basic_encoding_decoding() -> Result<(), Error> {
let (priv_key, pub_key) = keys();
let (issuer_private_key, issuer_public_key) = convert_to_pem(priv_key, pub_key);
let mut claims: Value = serde_json::from_str(TEST_CLAIMS).unwrap();
let now = Utc::now();
let expiration = now + Duration::minutes(5);
let exp = expiration.timestamp();
claims["exp"] = serde_json::json!(exp);
let mut issuer = Issuer::new(claims)?;
let encoded = issuer
.disclosable("/given_name")
.disclosable("/family_name")
.disclosable("/address/street_address")
.disclosable("/address/locality")
.disclosable("/nationalities/0")
.disclosable("/nationalities/1")
.encode(&crate::KeyForEncoding::from_rsa_pem(
issuer_private_key.as_bytes(),
)?)?;
println!("encoded: {:?}", encoded);
let dot_segments = encoded.split('.').count();
let disclosure_segments = encoded.split('~').count() - 2;
assert_eq!(dot_segments, 3);
assert_eq!(disclosure_segments, 6);
let issuer_jwt = encoded.split('~').next().unwrap();
let (header, claims) = decode(
issuer_jwt,
&KeyForDecoding::from_rsa_pem(issuer_public_key.as_bytes()).unwrap(),
&Validation::default(),
)?;
println!("header: {:?}", header);
println!("claims: {:?}", claims);
assert_eq!(header["alg"], "RS256");
assert_eq!(header["typ"], "sd-jwt");
assert_eq!(claims["sub"], "user_42");
assert!(claims["_sd"].is_array());
assert_eq!(claims["_sd"].as_array().unwrap().len(), 2);
assert!(claims["address"]["_sd"].is_array());
assert_eq!(claims["address"]["_sd"].as_array().unwrap().len(), 2);
assert_eq!(claims["_sd_alg"], "sha-256");
assert!(claims["nationalities"].is_array());
assert_eq!(claims["nationalities"].as_array().unwrap().len(), 2);
assert!(claims["nationalities"][0].is_object());
assert!(claims["nationalities"][1].is_object());
Ok(())
}
#[test]
fn test_presentation_verification_with_kb() -> Result<(), Error> {
let (priv_key, pub_key) = keys();
let (issuer_private_key, issuer_public_key) = convert_to_pem(priv_key, pub_key);
let (holder_private_key, holder_public_key) = keys();
let holder_jwk = publickey_to_jwk(&holder_public_key);
let (holder_private_key_pem, _) = convert_to_pem(holder_private_key, holder_public_key);
let claims: Value = serde_json::from_str(TEST_CLAIMS).unwrap();
let mut issuer = Issuer::new(claims)?;
let issuer_sd_jwt = issuer
.expires_in_seconds(60)
.disclosable("/given_name")
.disclosable("/family_name")
.disclosable("/address/street_address")
.disclosable("/address/locality")
.disclosable("/nationalities/0")
.disclosable("/nationalities/1")
.require_key_binding(Jwk::from_value(holder_jwk)?)
.encode(&KeyForEncoding::from_rsa_pem(
issuer_private_key.as_bytes(),
)?)?;
println!("issuer_sd_jwt: {:?}", issuer_sd_jwt);
let validation = Validation::default();
let decoding_key = KeyForDecoding::from_rsa_pem(issuer_public_key.as_bytes())?;
let (header, decoded_claims, disclosure_paths) =
Holder::verify(&issuer_sd_jwt, &decoding_key, &validation)?;
println!("header: {:?}", header);
println!("claims: {:?}", decoded_claims);
println!("disclosure_paths: {:?}", disclosure_paths);
let presentation = Holder::presentation(&issuer_sd_jwt)?
.redact("/family_name")?
.redact("/address/street_address")?
.redact("/nationalities/0")?
.key_binding(
"https://someone.example.com",
&KeyForEncoding::from_rsa_pem(holder_private_key_pem.as_bytes())?,
Algorithm::RS256,
)?
.build()?;
println!("presentation: {:?}", presentation);
let (issuer_jwt, disclosures, kb_jwt) = sd_jwt_parts(&presentation);
let issuer_dot_segments = issuer_jwt.split('.').count();
let kb_jwt_dot_segments = kb_jwt.as_ref().unwrap().split('.').count();
assert_eq!(issuer_dot_segments, 3);
assert_eq!(kb_jwt_dot_segments, 3);
assert_eq!(disclosures.len(), 3);
let (_, disclosure_parts) = separate_jwt_and_disclosures(&presentation);
let disclosures = disclosures2vec(&disclosure_parts);
assert_eq!(disclosures.len(), 3);
let d0 = Disclosure::from_base64(&disclosures[0], HashAlgorithm::SHA256)?;
let d1 = Disclosure::from_base64(&disclosures[1], HashAlgorithm::SHA256)?;
let d2 = Disclosure::from_base64(&disclosures[2], HashAlgorithm::SHA256)?;
assert_eq!(d0.key(), &Some("given_name".to_string()));
assert_eq!(d0.value(), &serde_json::json!("John"));
assert_eq!(d1.key(), &Some("locality".to_string()));
assert_eq!(d1.value(), &serde_json::json!("Anytown"));
assert_eq!(d2.key(), &None);
assert_eq!(d2.value(), &serde_json::json!("DE"));
let validation = Validation::default();
let mut kb_validation = Validation::default().without_expiry();
let mut audience = HashSet::new();
audience.insert("https://someone.example.com".to_string());
kb_validation.aud = Some(audience);
let decoding_key = KeyForDecoding::from_rsa_pem(issuer_public_key.as_bytes())?;
let (ver_header, ver_claims) = Verifier::verify(
&presentation,
&decoding_key,
&validation,
&Some(&kb_validation),
)?;
println!("ver_header: {:?}", ver_header);
println!("ver_claims: {:?}", ver_claims);
let mut ver_claims_without_exp = ver_claims.clone();
ver_claims_without_exp
.as_object_mut()
.unwrap()
.remove("exp");
ver_claims_without_exp
.as_object_mut()
.unwrap()
.remove("cnf");
assert!(compare_json_values(
&serde_json::from_str(TEST_VERIFIER_EXPECTED_CLAIMS)?,
&ver_claims_without_exp
));
Ok(())
}
#[test]
fn test_issue_claims_with_yaml() -> Result<(), Error> {
let (priv_key, pub_key) = keys();
let (issuer_private_key, issuer_public_key) = convert_to_pem(priv_key, pub_key);
let (holder_private_key, holder_public_key) = keys();
let holder_jwk = publickey_to_jwk(&holder_public_key);
let (holder_private_key_pem, _) = convert_to_pem(holder_private_key, holder_public_key);
let (claims, tagged_paths) = parse_yaml(TEST_CLAIMS_YAML)?;
let mut issuer = Issuer::new(claims)?;
let issuer_sd_jwt = issuer
.expires_in_seconds(60)
.require_key_binding(Jwk::from_value(holder_jwk)?)
.iter_disclosable(tagged_paths.iter())
.encode(&KeyForEncoding::from_rsa_pem(
issuer_private_key.as_bytes(),
)?)?;
println!("issuer_sd_jwt: {:?}", issuer_sd_jwt);
let validation = Validation::default();
let decoding_key = KeyForDecoding::from_rsa_pem(issuer_public_key.as_bytes())?;
let (header, decoded_claims, disclosure_paths) =
Holder::verify(&issuer_sd_jwt, &decoding_key, &validation)?;
println!("header: {:?}", header);
println!("claims: {:?}", decoded_claims);
println!("disclosure_paths: {:?}", disclosure_paths);
let presentation = Holder::presentation(&issuer_sd_jwt)?
.redact("/family_name")?
.redact("/address/street_address")?
.redact("/nationalities/0")?
.key_binding(
"https://someone.example.com",
&KeyForEncoding::from_rsa_pem(holder_private_key_pem.as_bytes())?,
Algorithm::RS256,
)?
.build()?;
println!("presentation: {:?}", presentation);
let (issuer_jwt, disclosures, kb_jwt) = sd_jwt_parts(&presentation);
let issuer_dot_segments = issuer_jwt.split('.').count();
let kb_jwt_dot_segments = kb_jwt.as_ref().unwrap().split('.').count();
assert_eq!(issuer_dot_segments, 3);
assert_eq!(kb_jwt_dot_segments, 3);
assert_eq!(disclosures.len(), 3);
let (_, disclosure_parts) = separate_jwt_and_disclosures(&presentation);
let disclosures = disclosures2vec(&disclosure_parts);
assert_eq!(disclosures.len(), 3);
let d0 = Disclosure::from_base64(&disclosures[0], HashAlgorithm::SHA256)?;
let d1 = Disclosure::from_base64(&disclosures[1], HashAlgorithm::SHA256)?;
let d2 = Disclosure::from_base64(&disclosures[2], HashAlgorithm::SHA256)?;
assert_eq!(d0.key(), &Some("given_name".to_string()));
assert_eq!(d0.value(), &serde_json::json!("John"));
assert_eq!(d1.key(), &Some("locality".to_string()));
assert_eq!(d1.value(), &serde_json::json!("Anytown"));
assert_eq!(d2.key(), &None);
assert_eq!(d2.value(), &serde_json::json!("DE"));
let validation = Validation::default();
let mut kb_validation = Validation::default().without_expiry();
let mut audience = HashSet::new();
audience.insert("https://someone.example.com".to_string());
kb_validation.aud = Some(audience);
let decoding_key = KeyForDecoding::from_rsa_pem(issuer_public_key.as_bytes())?;
let (ver_header, ver_claims) = Verifier::verify(
&presentation,
&decoding_key,
&validation,
&Some(&kb_validation),
)?;
println!("ver_header: {:?}", ver_header);
println!("ver_claims: {:?}", ver_claims);
let mut ver_claims_without_exp = ver_claims.clone();
ver_claims_without_exp
.as_object_mut()
.unwrap()
.remove("exp");
ver_claims_without_exp
.as_object_mut()
.unwrap()
.remove("cnf");
assert!(compare_json_values(
&serde_json::from_str(TEST_VERIFIER_EXPECTED_CLAIMS)?,
&ver_claims_without_exp
));
Ok(())
}