-
Notifications
You must be signed in to change notification settings - Fork 276
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
Decoupled crypto backends #410
base: new-backends
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like the look of it but would like both RustCrypto and aws-lc support to make sure it works well.
I'm not too sure about the builders but I guess we can discuss that later
src/crypto/hmac.rs
Outdated
self.clone().verify_slice(signature).is_ok() | ||
impl JwtVerifier for Hs512 { | ||
fn algorithm(&self) -> Algorithm { | ||
Algorithm::HS512 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like the kind of code that could be simplified with macros
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For repeated things that that, sure it could 👍
I feel that we are only repeating it a couple times so I don't believe the complexity of a macro is worth it. I also find it easier to read normal Rust code. But maybe that is just me 🤷
validate(decoded_claims.deserialize()?, validation)?; | ||
|
||
Ok(TokenData { header, claims }) | ||
// The decoding step is unfortunately a little clunky but that seems to be how I need to do it to fit it into the `decode` function |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
how would it look like without that constraint?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, I think that comment doesn't really apply anymore (I should have removed it). There was a stage that I was less sure what was going on so I had a bit of a mess in there 🤦
Oh yeah sure thing. I'll add With regards to the builder, here was my thought process: The We could avoid the builder pattern by creating a function like the following and calling it from the current Would this work better for you? pub fn generic_encode<S: JwtSigner, T: Serialize>(
signing_provider: S,
header: &Header,
claims: T,
key: &EncodingKey
) -> Result<String> {
...
} |
I think we can have both the builder and the function? |
I just want to understand correctly as there are two functions I have talked about and I feel that we might be talking past each other.
I both cases I was visualizing that we keep the same functionality of the original functions [1], but the internal business logic of these functions would either be provided by a builder or a function [2] style interface. So in addition to the original functions [1], we would also expose a method that an arbitrary backend could be used (this would either be a builder or another function). Does this clear things up? |
I have added an Currently there are
cargo test
cargo test --no-default-features --features aws_lc_rs |
I think that's fine, as long as we can keep the keys in once_cell/lazy_static and use them without having to clone anything |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like where it's going
src/decoding.rs
Outdated
/// ``` | ||
pub struct JwtDecoder { | ||
verifying_provider: Box<dyn JwtVerifier>, | ||
validation: Validation, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should it be Option<Validator>
? The default Validation is probably not useful for a lot of cases so might as well not have one
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am currently reusing a lot of the current validation logic that makes use of fields in the Validation
struct. So in the initializer for the JwtDecoder
it is creating a "default" Validation
struct based on the algorithm of the verifying_provider
. This is then used for the validation logic. If we were to use an Option<Validator>
we would still need to provide default values to use somewhere in the code.
If someone wants to provide custom validation logic they can call the with_validation
builder style method to replace the "default" validation that is generated in the initializer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If one wanted to clean things up one could flatten the Validation
fields and methods into the JwtDecoder
, but because we want to keep the original decode
function working the Validation
code needs to be there, so might as well make use of it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should have the user pass it explicitly to the constructor then, implicit default validation is not good.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I should replace the builder with a function (see here). This would remove these smaller complexities.
src/encoding.rs
Outdated
pub fn from_boxed_signer(signing_provider: Box<dyn JwtSigner>) -> Self { | ||
// Determine a default header | ||
let mut header = Header::new(signing_provider.algorithm()); | ||
header.typ = Some("JWT".to_owned()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's the default I believe?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, you are correct. I'll fix that up.
It seems that this POC might be used. So I'll go ahead and add the rest of the algorithms, keeping in mind some of the comments above, and get everything up to scratch to pass CI. Unless there are any other concerns? |
Go ahead, we can play with the DX once it's implemented. Maybe use macros to implement them so it's easy to make changes if needed rather than copy/paste |
0ae3b87
to
78e84c1
Compare
I have converted the builder to I am about half done adding the RSA algorithms functionality. I did however recently notice that the original |
They are used by some people who only care about the crypto primitives so I would re-implement them and keep them pub |
Hi @sidrubs will you have time to finish that PR? |
Would this scope still be acceptable or do you need them all to be implemented in 1 PR? |
I think it would be easier to implement all of it in one PR |
Perfect. Thanks for verifying! |
Introduction
This is a POC at abstracting the signing and verifying algorithms behind the
JwtSigner
andJwtVerifier
traits whilst not breaking theencode
anddecode
public interfaces that people know and love.This allows any crypto backend to be used, as long as it implements the
JwtSigner
andJwtVerifier
traits. Allowing developers to provide their own backend dependent on their use case. This should also enable more flexibility for thejsonwebtoken
crate to prepackage different backends (e.g.ring
,aws_lc_rs
, andRustCrypto
).Implementation
JwtEncoder
andJwtDecoder
builder style clients were added to the public API allow the developer to use the included algorithms or provide their own.The original
encode
anddecode
public interfaces call this builder under-the-hood with a factory function choosing the correct algorithm backend.Advantages
jsonwebtoken
crate maintainers to add something for them.Disadvantages
Validation
struct). Everything works, it would just seem a little weird to someone looking at parts of the codebase without context.Wrapping Up
I understand that this a a pretty big change architecturally to the
jsonwebtoken
crate, so fully understand if you don't want to go this route. I do feel, however, that it makes the project more flexible and maintainable going forward.Any suggestions are welcome.