openwallet-foundation / sd-jwt-js

A JavaScript implementation of the Selective Disclosure JWT (SD-JWT) spec.
https://sdjwt.js.org/
Apache License 2.0
44 stars 13 forks source link

Fields defined in SD-JWT specification are not verified #236

Open TimoGlastra opened 6 months ago

TimoGlastra commented 6 months ago

Such as the exp, iss, et.. fields. I would expect these fields to be validated when using an SD-JWT library.

It seems that verifying an SD-JWT VC mainly focuses on verifying the signature (and now also the status list), but no the fields in the SD-JWT VC itself.

Is that intended with this library? Is the main focus SD-JWT and not SD-JWT VC?

TimoGlastra commented 6 months ago

227 Maybe this can be combine with integration of zod for validation of objects, but i think there's a lot of unexpected security issues that can arise from not validating these things. For now I've added a lot of extra validation in Credo, but ideally we handle it here.

cre8 commented 6 months ago

@TimoGlastra you are correct, at the sd-jwt is not validating any fields, just checking the integrity of the jwt and if public fields should be disclosed (defined by the spec). In sd-jwt-vc we only added the validation rules according to the status list.

According to the spec we should at least validate the nbf, iat and exp. But doing this internally, you would not be able to validate an expired credential with the current validation logic (throw an error on the first failed validation).

From my point of view validating the time based fields is fine. But we should only do this after allowing to pass the other validations (or passing a parameter allowing to validate "not in time" credentials.

Also checking the types of the fields COULD be relevant. However we need to respect all possible types an object can have.

Also what other fields beside the ones I mentioned to you think are worth to validate? How to e.g. validate iss?

TimoGlastra commented 6 months ago

I think we can look at did-jwt for a good example on configuration options, where you can pass:

In terms of issuer, i think it would be great to have a jwkResolver callback that will be called to fetch the public keys. It could be useful to also implement a default type metadata fetcher (which can be retrieved optionally during verification).

https://github.com/decentralized-identity/did-jwt/blob/8b2023c21a639b5e8d5236597dc1d7c330bb5ae0/src/JWT.ts#L30

https://github.com/decentralized-identity/did-jwt/blob/8b2023c21a639b5e8d5236597dc1d7c330bb5ae0/src/JWT.ts#L46

Really like this pattern as it gives you full contril without having to understand everything. By default everything is verified, but you can disable certain verifications (i think being strict by default is very important for security as people Expect a library to do everything that's needed. I've seen too many examples of decodeJWT being used in a place where it should have been verified!)

cre8 commented 6 months ago

I understand to validate credentials that are not in the present. But we need to be careful since statuslist does not allow to fetch older versions of a statuslist. So I can not check with this "was the credential valid 2 weeks ago".

Your mentioned policies step sounds good to me. But as mentioned before I would love that I pass my rules to the verify function and the verify function returns the decoded payload or throws an error in case something went wrong. The seems to be the normal behavior when dealing with jwt libraries.

lukasjhan commented 5 months ago

I think we can provide separate validate function for SD-JWT. User can use it optionally. How about this approach? @TimoGlastra @cre8

TimoGlastra commented 5 months ago

But as mentioned before I would love that I pass my rules to the verify function and the verify function returns the decoded payload or throws an error in case something went wrong

What if it throws a structured error where we can extract the validation result?

I'm fine with it being an error, the problem is the library knows which validations succeeded / failed. If it just throws an error message it means we have to redo all validations to find out what failed / succeeded, at which point it's easier to use a custom/different SD-JWT VC implementation.

I'm hoping we can accommodate both our requirements, but we're pretty set on the requirement that we need to know which validations succeeded/failed.

cre8 commented 5 months ago

But as mentioned before I would love that I pass my rules to the verify function and the verify function returns the decoded payload or throws an error in case something went wrong

What if it throws a structured error where we can extract the validation result?

I'm fine with it being an error, the problem is the library knows which validations succeeded / failed. If it just throws an error message it means we have to redo all validations to find out what failed / succeeded, at which point it's easier to use a custom/different SD-JWT VC implementation.

I'm hoping we can accommodate both our requirements, but we're pretty set on the requirement that we need to know which validations succeeded/failed.

According to the docs the Error.message field is of type string, so we would need to parse it as JSON back. I wold prefer to go the error way since the success way it will return an object with the payload etc. and when it fails it returns an error object.

As discussed in another issue we want to define the error message as constants, allowing you to handle errors like:

catch(e: Error) {
  if(e.message === JWT_EXPIRED) {
  }
}

This of course forces us to define good error messages so the developer knows what failed.

For use cases like API endpoint validation a parallel validation of the values in the object makes sense, so the sender can fix all problems in the next try. But I do not see this case when dealing with JWT or SD-JWTs. I am totally fine that we can pass parameters to the validation function to ignore the status check or to pass another timestamp as the present one. So going exactly like your referenced did-jwt implementation with passing a validation policy and checking everything in sequence would be my preferred way to go.

So is returning detailed and unique error messages fine for your or do you need explicit to have a report of all validations like when running a unit-test? I would not like to open the box which validations in the function should run in sequence and which in parallel. Like when the signature is wrong, I would like to stop the validation since I can not trust the payload inside it.

TimoGlastra commented 5 months ago

According to the docs the Error.message field is of type string

I was thinking a custom error class, with an instanceof check we can then check if it's a validation error and then extract e.g. validations property. Parsing error messages seems more prone to errors.

TimoGlastra commented 5 months ago

Re the validations in sequence vs parallel, maybe we should split this up:

We would use the multiple lower level functions to build our validation object, you can use the top-level validation function

So then the parallel vs sequential isn't a problem.

We just want to provide helpful error messages in Paradym (our SSI developer platform) for the SD-JWT validation. One thing we see as helpful is not just an error, but a structured error where you can use the result of every check separately.

This way users can look at the validation result as a whole (isValid) or look at why the validation failed (and this can have multiple reasons, not just one).

But I think we'll just go a level deeper and use the lower level utilities from this library to build our own validation pipeline, maybe our requirements are too custom

cre8 commented 5 months ago

Re the validations in sequence vs parallel, maybe we should split this up:

  • one high level method which fails early
  • multiple lower level functions. The high level method calls these lower level functions.

We would use the multiple lower level functions to build our validation object, you can use the top-level validation function

So then the parallel vs sequential isn't a problem.

We just want to provide helpful error messages in Paradym (our SSI developer platform) for the SD-JWT validation. One thing we see as helpful is not just an error, but a structured error where you can use the result of every check separately.

This way users can look at the validation result as a whole (isValid) or look at why the validation failed (and this can have multiple reasons, not just one).

But I think we'll just go a level deeper and use the lower level utilities from this library to build our own validation pipeline, maybe our requirements are too custom

How about adding a new verification function like verifyInParallel(). Internally this function and verify can use the same functions, but they handle the results differently. Then we also have no problem with different return types since these are two different functions.

We would also not break current implementations or have breaking changes. Could this approach work for you?

This would also allow us to compare different usages and maybe later drop the current approach when everyone is favouring yours 😉

TimoGlastra commented 5 months ago

Yes, a new method sounds good to me :)

TimoGlastra commented 5 months ago

Just wanted to also link to section 13. Of this new revocation spec, that outlines why checking revocation is a decision made on the business layer: https://peppelinux.github.io/draft-demarco-oauth-status-attestations/draft-demarco-oauth-status-attestations.html#name-considerations-on-revocatio

cre8 commented 5 months ago

Just wanted to also link to section 13. Of this new revocation spec, that outlines why checking revocation is a decision made on the business layer: https://peppelinux.github.io/draft-demarco-oauth-status-attestations/draft-demarco-oauth-status-attestations.html#name-considerations-on-revocatio

Yes, revocation check should be done via a flag, we already have this for the key binding.

We only have to decide the default value, I would suggest to validate if it's present, but it can be skipped by configuration (security by design)