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

ssi-sd-jwt implementation #529

Merged

Conversation

tristanmiller-spruceid
Copy link
Contributor

@tristanmiller-spruceid tristanmiller-spruceid commented Sep 11, 2023

This new crate implements the sd-jwt specification as found here:

https://datatracker.ietf.org/doc/draft-ietf-oauth-selective-disclosure-jwt/

This is an RFC PR to provide a base for discussion, particularly around the shape of the public API.

TODO

  • More comments on public API (examples exist in tests folder)
  • Creating sd-jwts where selectively disclosed claims aren't in the root of the payload
  • Creating sd-jwts with recursively disclosed claims (ie. sd claims that that contain additional sd claims)
  • No key binding JWT support

@tristanmiller-spruceid tristanmiller-spruceid requested a review from a team September 11, 2023 20:50
@CLAassistant
Copy link

CLAassistant commented Sep 11, 2023

CLA assistant check
All committers have signed the CLA.

@tristanmiller-spruceid tristanmiller-spruceid changed the title [WIP] Initial commit of ssi-sd-jwt [WIP] ssi-sd-jwt implementation Sep 11, 2023
Copy link
Member

@sbihel sbihel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you also expose it in the main ssi crate?

pub use verify::verify_sd_disclosures_array;

#[derive(thiserror::Error, Debug)]
pub enum Error {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ssi has a history of having massive bundles of errors, but I don't think it's necessarily nice. Could you split the errors between encoding and decoding?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can definitely do that. I trend towards that thinking generally; I just be default was matching ssi.

Ok(Disclosure { encoded, hash })
}

pub fn encode_sign<Claims: Serialize>(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not familiar with the specs, could you explain why it's not generating the disclosures from the claims struct? Is it because each disclosure can have a different encoding? If so, why not still use the values in the object and pass the encoding in some other way? It's just a bit confusing to pass an object that's barely used (at least from what I see in the examples)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The main issue is that the return value array of encoded disclosures were very difficult to match up to the various disclosures, since very complex sets of disclosures are possible including nesting. True, the object is not used much from the encode side except for the base jwt (so common claims like iss, exp, etc. that will always be disclosed), but in exchange you do get to keep the same claim struct on the decode and encode side.

pub fn decode_verify<Claims: DeserializeOwned>(
jwt: &str,
key: &JWK,
disclosures: &[&str],
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think it could improve the API if this was using the DecodedDisclosure type? To force the user to "validate" the disclosures before doing anything, and also to prevent silly mistakes like passing the jwt as a disclosure

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Practically, the main decode pathway should be going through decode_verify_string_format AFAIUI the normal use cases. That format is sort of like how jwt/jws/jwe components are separated by '.', this nests that in a structure separated by tildes to also hold the list of disclosures. At that point, I was hoping that those using the 'decode_verify' not have to call other functions just to call decode_verify when it could give them those errors pretty much immediately anyway.

Perhaps to make this clearer, would you like to see 'decode_verify_string_format' be named 'decode_verify' and the current 'decode_verify' be named something like 'decode_verify_disclosure_array' to direct downstream library users to the easiest path?

@tristanmiller-spruceid tristanmiller-spruceid changed the title [WIP] ssi-sd-jwt implementation ssi-sd-jwt implementation Oct 11, 2023
) -> Result<String, serde_json::Error> {
let disclosure = match claim_name {
Some(claim_name) => serde_json::json!([salt, claim_name, claim_value]),
None => serde_json::json!([salt, claim_value]),

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the claim_name is mandatory for a disclosure right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, array style disclosures don't require a name.

let _ = base_claims_obj.insert(claim_name.clone(), serde_json::json!([]));
}

let array = base_claims_obj.get_mut(claim_name).unwrap();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lingering unwrap

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The unwrap is because claim_name is explicitly added in all cases in the couple lines above, but this absolutely needed a comment justifying this. That has been added.

@justAnIdentity
Copy link

Am I correct thinking this implementation takes the Flat SD-JWT approach for nested data?
I think that's fine, but just want to make sure we take that decision conciously cc @cobward

@Voronar
Copy link
Contributor

Voronar commented Oct 13, 2023

Do you plan integration with ssi-vc?
Will we see new enum value here https://github.com/spruceid/ssi/blob/main/ssi-vc/src/lib.rs#L282?

@tristanmiller-spruceid
Copy link
Contributor Author

Do you plan integration with ssi-vc? Will we see new enum value here https://github.com/spruceid/ssi/blob/main/ssi-vc/src/lib.rs#L282?

I imagined that SD-JWT + VC would be a separate pull request, but I can integrate that here if that's required.

@tristanmiller-spruceid
Copy link
Contributor Author

Am I correct thinking this implementation takes the Flat SD-JWT approach for nested data? I think that's fine, but just want to make sure we take that decision conciously cc @cobward

The highlevel API only supports Flat SD-JWT, but an example of using the lower level API to parse a more complex JWT that is both nested and contains recursive disclosures is given in https://github.com/tristanmiller-spruceid/ssi/blob/5bfe8af2d9ffabe3b7b68db79f5943ffd3c4fb10/ssi-sd-jwt/tests/rfc_examples.rs#L181 and to create and parse a nested JWT is here https://github.com/tristanmiller-spruceid/ssi/blob/5bfe8af2d9ffabe3b7b68db79f5943ffd3c4fb10/ssi-sd-jwt/tests/full_pathway.rs#L119

ssi-sd-jwt/src/decode.rs Outdated Show resolved Hide resolved
ssi-sd-jwt/src/decode.rs Outdated Show resolved Hide resolved
ssi-sd-jwt/src/decode.rs Outdated Show resolved Hide resolved
ssi-sd-jwt/src/decode.rs Outdated Show resolved Hide resolved
ssi-sd-jwt/src/decode.rs Outdated Show resolved Hide resolved
ssi-sd-jwt/src/verify.rs Outdated Show resolved Hide resolved
ssi-sd-jwt/Cargo.toml Outdated Show resolved Hide resolved
ssi-sd-jwt/src/decode.rs Show resolved Hide resolved
ssi-sd-jwt/src/decode.rs Outdated Show resolved Hide resolved
ssi-sd-jwt/src/decode.rs Show resolved Hide resolved
@tristanmiller-spruceid tristanmiller-spruceid merged commit a9f593a into spruceid:main Oct 24, 2023
4 checks passed
@tristanmiller-spruceid tristanmiller-spruceid deleted the feat/ssi-sd-jwt branch October 24, 2023 15:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants