Rust implementation of the Selective Disclosure for JWTs (SD-JWT) version 12
This library supports
- Issuing SD-JWTs:
- Create a selectively disclosable JWT by choosing which properties can be concealed from a verifier. Concealable claims are replaced with their disclosure's digest.
- Adding decoys to both JSON objects and arrays.
- Requiring an holder's key-bind.
- Managing SD-JWTs
- Conceal with ease any concealable property.
- Insert a key-bind.
- Verifying SD-JWTs
- Recursively replace digests in objects and arrays with their corresponding disclosure value.
Sha-256
hash function is shipped by default, encoding/decoding with other hash functions is possible.
Include the library in your cargo.toml
.
[dependencies]
sd-jwt-payload = { version = "0.3.0" }
See sd_jwt.rs for a runnable example.
This library consists of the major structs:
SdJwtBuilder
: creates SD-JWTs.SdJwt
: handles SD-JWTs.Disclosure
: used throughout the library to represent disclosure objects.Hasher
: a trait to provide hash functions create and replace disclosures.Sha256Hasher
: implementsHasher
for theSha-256
hash function.JwsSigner
: a trait used to create JWS signatures.
Any JSON object can be used to create an SD-JWT:
let object = json!({
"sub": "user_42",
"given_name": "John",
"family_name": "Doe",
"email": "[email protected]",
"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"
]
});
let builder: SdJwtBuilder = SdJwtBuilder::new(object);
This creates a stateful builder with Sha-256
hash function by default to create disclosure digests.
Note: SdJwtBuilder
is generic over Hasher
which allows custom encoding with other hash functions.
The builder can encode any of the object's values or array elements, using the make_concealable
method. Suppose the value of street_address
in 'address' should be selectively disclosed as well as the entire value of address
and the first phone
value.
builder
.make_concealable("/email")?
.make_concealable("/phone_number")?
.make_concealable("/address/street_address")?
.make_concealable("/address")?
.make_concealable("/nationalities/0")?
Note: the make_concealable
method takes a JSON Pointer to determine the element to conceal inside the JSON object.
The builder also supports adding decoys. For instance, the amount of phone numbers and the amount of claims need to be hidden.
builder
.add_decoys("/nationalities", 1)? // Adds 1 decoys to the array `nationalities`.
.add_decoys("", 2)? // Adds 2 decoys to the top level object.
Through the builder an issuer can require a specific key-binding that will be verified upon validation:
builder
.require_key_binding(RequiredKeyBinding::Kid("key1".to_string()))
Internally, builder's object now looks like:
{
"_sd": [
"5P7JOl7w5kWrMDQ71U4ts1CHaPPNTKDqOt9OaOdGMOg",
"73rQnMSG1np-GjzaM-yHfcZAIqmeaIK9Dn9N0atxHms",
"s0UiQ41MTAPnjfKk4HEYet0ksuMo0VTArCwG5ALiC84",
"v-xRCoLxbDcL5NZGX9uRFI0hgH9gx3uX1Y1EMcWeC5k",
"z7SAFTHCOGF8vXbHyIPXH6TQvo750AdGXhvqgMTA8Mw"
],
"_sd_alg": "sha-256",
"cnf": {
"kid": "key1"
},
"sub": "user_42",
"given_name": "John",
"family_name": "Doe",
"nationalities": [
{
"...": "xYpMTpfay0Rb77IWvbJU1C4JT3kvJUftZHxZuwfiS1M"
},
"DE",
{
"...": "GqcdlPi6GUDcj9VVpm8kj29jfXCdyBx2GfWP34339hI"
}
],
"phone_number_verified": true,
"updated_at": 1570000000,
"birthdate": "1940-01-01"
}
Note: no JWT claims like exp
or iat
are added. If necessary, these need to be added and validated manually.
To create the actual SD-JWT the finish
method must be called on the builder:
let signer = MyHS256Signer::new();
let sd_jwt = builder
// ...
.finish(&signer, "ES256")
.await?;
eyJ0eXAiOiJTRC1KV1QiLCJhbGciOiJIUzI1NiJ9.eyJnaXZlbl9uYW1lIjoiSm9obiIsImZhbWlseV9uYW1lIjoiRG9lIiwicGhvbmUiOlt7Ii4uLiI6ImVaVm4wS2tRbV9UOHgteDU3VnhZdC1fTW1ORzkxU2gzNEUtYlpFbk5mV1kifSwiKzQ5IDIzNDU2NyIseyIuLi4iOiJLQWlKSXgwdGt0UVJYQnhaU0JWVmxkOTI5OGJaSXAyV2twa0RZRGEzQ1dRIn0seyIuLi4iOiJDQktBUlBoNnNkVENKeWxpWjdwQk9Zeml4N1o0QmI0eVJoMEV5a0hYMlV3In0seyIuLi4iOiJvaTFLZ3NZWGdxQkZYVVh2YlZhSFNHWVlhV2hrQjVSTDU1VDkwR2xfNXMwIn1dLCJfc2QiOlsiSmo1akJlR0Vhd1k2dlJ2bUhEZzU1RWplQUlQOEZWaFdFVjJGY3poVVhyWSIsIjhlcXBoQlBKeUNCZ1VKaE5XTlA3Y2ktWTc5TjYxNXdwWlFyeGk1RDRqdTgiLCJfaE9VNXB1SmpOelNCaEswYndoM2g4X2I2SDZuTjd2ZF83STB1VHA4ME1vIiwiR190SDcwTXJmQ2tWTTBIaHNIOVJFT2JJdDFFaTE5NDc3eTZDRXNTMFpsbyIsInpQNTZNZUgwcnlqenFoOUthZHJiNUM5WjJCRTJGV2c4bmIzZzByUjNMU0EiLCJkZ2ZWVzExaXA5T095Vmk4TTRoMVJqWEs4YWt3N0lDZU1Ra2pVd1NJNmlVIiwiQngzM21PeVRGNS13OGdSUzV5TDRZUTRkaWc0NFYzbG1IeGsxV1Jzc183VSJdLCJfc2RfYWxnIjoic2hhLTI1NiJ9.knTqw4FMCplHoMu7mfiix7dv4lIjYgRIn-tmuemAhbY~WyJHaGpUZVYwV2xlUHE1bUNrVUtPVTkzcXV4WURjTzIiLCAic3RyZWV0X2FkZHJlc3MiLCAiMTIzIE1haW4gU3QiXQ~WyJVVXVBelg5RDdFV1g0c0FRVVM5aURLYVp3cU13blUiLCAiYWRkcmVzcyIsIHsicmVnaW9uIjoiQW55c3RhdGUiLCJfc2QiOlsiaHdiX2d0eG01SnhVbzJmTTQySzc3Q194QTUxcmkwTXF0TVVLZmI0ZVByMCJdfV0~WyJHRDYzSTYwUFJjb3dvdXJUUmg4OG5aM1JNbW14YVMiLCAiKzQ5IDEyMzQ1NiJd~
Once an SD-JWT is obtained, any concealable property can be omitted from it by creating a presentation and calling the
conceal
method:
let mut sd_jwt = SdJwt::parse("...")?;
let hasher = Sha256Hasher::new();
let (presented_sd_jwt, removed_disclosures) = sd_jwt
.into_presentation(&hasher)?
.conceal("/email")?
.conceal("/nationalities/0")?
.finish()?;
To attach a key-binding JWT (KB-JWT) the KeyBindingJwtBuilder
struct can be used:
let mut sd_jwt = SdJwt::parse("...")?;
// Can be used to check which key is required - if any.
let requird_kb: Option<&RequiredKeyBinding> = sd_jwt.required_key_binding();
let signer = MyJwkSigner::new();
let hasher = Sha256Hasher::new();
let kb_jwt = KeyBindingJwtBuilder::new()
.nonce("abcd-efgh-ijkl-mnop")
.iat(time::now())
.finish(&sd_jwt, &hasher, "ES256", &signer)
.await?;
let (sd_jwt, _) = sd_jwt.into_presentation(&hasher)?
.attach_key_binding_jwt(kb_jwt)
.finish()?;
The SD-JWT can be turned into a JSON object of its disclosed values by calling the into_disclosed_object
method:
let mut sd_jwt = SdJwt::parse("...")?;
let disclosed_object = sd_jwt.into_disclosed_object(&hasher)?;
disclosed_object
:
{
"address": {
"country": "US",
"locality": "Anytown",
"region": "Anystate",
"street_address": "123 Main St"
},
"phone_number": "+1-202-555-0101",
"cnf": {
"kid": "key1"
},
"sub": "user_42",
"given_name": "John",
"family_name": "Doe",
"nationalities": [
"DE"
],
"phone_number_verified": true,
"updated_at": 1570000000,
"birthdate": "1940-01-01"
}
Note:
_sd_alg
property was removed.
Contributions are what make the open source community such an amazing place to learn, inspire, and create. Any contributions you make are greatly appreciated.
If you have a suggestion that would make this better, please fork the repo and create a pull request. You can also simply open an issue with the tag "enhancement". Don't forget to give the project a star! Thanks again!
- Fork the Project
- Create your Feature Branch (
git checkout -b feature/AmazingFeature
) - Commit your Changes (
git commit -m 'Add some AmazingFeature'
) - Push to the Branch (
git push origin feature/AmazingFeature
) - Open a Pull Request
Distributed under the Apache License. See LICENSE
for more information.