jsonwebtoken icon indicating copy to clipboard operation
jsonwebtoken copied to clipboard

Changes to support ACME, including JWS

Open andrewbaxter opened this issue 1 year ago • 29 comments

#358

andrewbaxter avatar Jan 10 '24 12:01 andrewbaxter

One test here. Works great. Thanks @andrewbaxter!

surfingreg avatar Jan 11 '24 01:01 surfingreg

Awesome, and thanks for the comments! Sorry, I didn't quite get to this today. I'll try to do it tomorrow.

andrewbaxter avatar Jan 11 '24 15:01 andrewbaxter

It would also need a README update

I added a (very simple) example to the readme, let me know if there's other additions you think would be good.

andrewbaxter avatar Jan 12 '24 11:01 andrewbaxter

My (limited) real world testing worked.

andrewbaxter avatar Jan 14 '24 13:01 andrewbaxter

I will have to read the specs and review the PR properly as I don't really know/use JWK/JWS myself. That's going to take some time but if more people can try that would be great

Keats avatar Jan 15 '24 09:01 Keats

Can I support the work on this somehow? I don't know if I have the technical aptitude to help review, but if I can be of other assistance I would love to.

randomairborne avatar Jan 15 '24 18:01 randomairborne

Ideally maybe we could get one single PR (@andrewbaxter can change the target of other PRs to this one) so we can point at a single point for people to test

Keats avatar Jan 16 '24 08:01 Keats

Keep them separate but just change the target? Or should I combine them all? Aside from loading urlsafe base64 hmacs I don't think I have any others incoming

andrewbaxter avatar Jan 16 '24 09:01 andrewbaxter

change the target, we review them and then we can do a big review of the whole thing on this PR

Keats avatar Jan 16 '24 09:01 Keats

@randomairborne do you have any use cases you could try it with? I looked at various acme libraries and they all seemed to be doing their own jwt stuff (and didn't support EAB). I ended up hacking it into Poem's acme implementation, but I have some reservations about that

andrewbaxter avatar Jan 16 '24 09:01 andrewbaxter

Okay, great!

andrewbaxter avatar Jan 16 '24 09:01 andrewbaxter

@Keats I'm not sure I can re-target MRs in this repo to branches in my fork... sorry, my github ineptitude on full display. AFAICT it only allows me to select branches in this repo.

Edit: I'll try stacking them and recreating the MRs in my fork.

andrewbaxter avatar Jan 16 '24 09:01 andrewbaxter

I'm attempting to write a new ACME library 😅. If this has a somewhat functional public API, I'll start using it.

randomairborne avatar Jan 16 '24 16:01 randomairborne

Sorry didn't get to this yesterday. I'll do it now

andrewbaxter avatar Jan 17 '24 10:01 andrewbaxter

Okay I made https://github.com/andrewbaxter/fork-jsonwebtoken/pull/1 https://github.com/andrewbaxter/fork-jsonwebtoken/pull/2 https://github.com/andrewbaxter/fork-jsonwebtoken/pull/3

targeted at this branch, and https://github.com/andrewbaxter/fork-jsonwebtoken/pull/4 with them all merged together if you want to try stuff out.

I think this is everything needed for ACME, but I haven't read through all the different flows in ACME so it's possible some stuff is still missing.

I added you @Keats as a collaborator in case you wanted to merge them into this one. I can close the other MRs in this repo if that would be good.

andrewbaxter avatar Jan 17 '24 11:01 andrewbaxter

Ok if there are any JWS/ACME users, can they try that branch?

Keats avatar Jan 22 '24 16:01 Keats

Has anyone tried it?

Keats avatar Mar 21 '24 22:03 Keats

Aside from me I assume... :sweat_smile:

andrewbaxter avatar Mar 23 '24 13:03 andrewbaxter

I tried to test decoding of a Jws.

I added the following line in my Cargo.toml

jsonwebtoken = { git = "https://github.com/andrewbaxter/fork-jsonwebtoken.git", branch = "branch-integration" }

But I don't know how to transform the string slice containing the Jws into a Jws<C> type which is required by the decode_jws function. I can't hand over just the string slice. This is possible with the regular decode function for jwt.

let jws_token="eyJhbGciOiJSUzI1NiIsIng1dCI6IlptWXlNRFprTlRoa05Ua3lZalkyWW1VM1pEQTFNRE5oWkdSbFl6TTJNamhrTTJWaE1EUTJZUW8ifQ.eyJtZXRhZGF0YS12ZXJzaW9uIjoiMC4wLjEiLCJuYW1lIjoiY29tLmJyLWF1dG9tYXRpb24uYXV0b21hdGlvbnJ1bnRpbWUiLCJ2ZXJzaW9uIjoiNi4wLjAiLCJhcnRpZmFjdHMiOlt7ImZpbGUiOiJhci5lZWI0NGY5ZGRjNWY0MzM2YTEyODJmOGNlZWYwYWI5MzQ0NDJkZTdiNTc0NzJjZGI1YjA1OGZkMDkyNmIzNDc1LnRhciIsInNoYTI1NiI6ImM1NmI3ZGFiYmM3YWE3MzBlZWFiMDc2NjhiZGNiZDdlM2Q0MDg1NTA0N2NhOWEwY2MxYmZlZDIzYTI0ODYxMTEiLCJhcmNoaXRlY3R1cmUiOiJ4ODZfNjQifSx7ImZpbGUiOiJhci4wMTMzMDc5MTg2YzZhYTYwMmNjYjA5ODA5MTU1MzNmMDNiMDFhMDA3MDFiMWYzOWMwMGYyYmYxZjczMDYxZTM2LnRhciIsInNoYTI1NiI6IjU3OGNjODYwYmFjMGM4MTM5MmViNzAyNjJiMmVjZjNlNmM1MjFhYTY0N2JjMjBkNjcyOWNhNTE4NDZhZWEwNWUiLCJhcmNoaXRlY3R1cmUiOiJhYXJjaDY0In0seyJmaWxlIjoiY29tcG9zZTEueW1sIiwic2hhMjU2IjoiMjY2NjU5Njg2NzY3MDY5YmY2NDI5NmNiNGJkYjkxNDZkNzM3Mzg0MTM4NzVjYzEyNTcyNDgwN2M1NGZhNTFhMiIsImFyY2hpdGVjdHVyZSI6Ing4Nl82NCJ9LHsiZmlsZSI6ImNvbXBvc2UyLnltbCIsInNoYTI1NiI6IjE5YTljNTdkMWQwNjVlNGZjNTdkODMxY2IzNmI4Y2U1ZTU3ODk0MDFkM2Q1N2JhNTUyZTJkNjM0YTg4NjkwYjAiLCJhcmNoaXRlY3R1cmUiOiJhYXJjaDY0In1dfQ.PMuAg_M-IqiGY8s6KeiUra30mxUwulgrGkI4YaEMIwsOUE-cI2oqOxWkV4OUJ9NNVnUGWuuBO6E4VFCsabWL3fsIPyl_7mrGToNPAka7OxaadQXhFhZwzaGuBi879-pWVm5NNtjjCO1inDw6dDBLVj89HWeBQxgEgVwYWA1P6lbCLFrstlb_2Qp8yY44KAQDV3bVudmlZn_VaxKlsFSc8_8DOPaeFwbWjqG1XT4WrKAiKFnQCqnzaSyH4O-0bPiZ7rq04mfA6-Nm7ZNnrST3dbG6v9f5Ku9qHu6Nfs0uMGIclLN8Krzk365IuCnLp_W3zL7bTwJ3VGMj6AHjH5U4Jw";
let public_key = "-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5JX/bCT4jVCtzesSMHd8
Wu7ksIs3tFhw/8oYB+4cLik5sjvc1HiWWA50UK7TIERIIuXq0SAulV9xbHLrZjZn
iU2hbN83+7LySfzQxQRwfIVLFRsUqcm23ZZFMCHxuzEfwl/m+61AkDzARmoUVCyh
LXlomjKzN45RZZqSHVkvpJWidqKcVCtdjsGnVZeeX4TG6QI/hbzRm4y6oxLwriwJ
vl2bz+lJUN57XxhtCgYtdhUa8PfD+5mC26xlLvMfru3Ic1+NKgcZi1g5dmOMQOpY
PnLPN2VKbA9Nx+RxlQK1C+R/mmEM6lGZbSqxHUa3WuB725gVNN+tHTUfsprjax7b
PQIDAQAB
-----END PUBLIC KEY-----";

let decoding_key = DecodingKey::from_rsa_pem(public_key.as_bytes()).unwrap_or_else(|err| {
    println!("Error creating decoding key: {}", err);
    panic!("Decoding key creation failed");
});
let validation = Validation::new(Algorithm::RS256);
let decoded_token: TokenData<serde_json::Value> = match decode_jws(&jws_token, &decoding_key, &validation) {
    Ok(token) => token,
    Err(err) => {
        println!("Error decoding token: {}", err);
        panic!("Token decoding failed");
    }
};

fuch1m avatar Apr 05 '24 10:04 fuch1m

Ah yeah, so it looks like you have a JWS in the "JWS Compact Serialization" form which is .-concatenated and base64 encoded.

There are two representations discussed in the RFC: JWS JSON Serialization and JWS Compact Serialization.

The JWS support I added here doesn't provide a convenience method for the compact serialization representation. It implements Serialize and Deserialize for the "JWS JSON Serialization" representation. In ACME the JSON is embedded in another structure and de/serialized together with that.

In order to use this with the Compact Serialization I think you'd need to split it by . and put the parts in a JWS object first... something like:

let mut parts = jws_token.split(".");
let jws = JWS<serde_json::Value>{
   protected: parts.next()?,
   payload: parts.next()?,
   signature: parts.next()?,
   _pd: Default::default(),
};
decode_jws(&jws, &decoding_key, &validation)...

Assuming that works, convenience methods might be a good addition but I'm not in a great position to switch gears and add it atm.

andrewbaxter avatar Apr 05 '24 17:04 andrewbaxter

Thank you, I will try it in the next days!

fuch1m avatar Apr 06 '24 17:04 fuch1m

Decoding my JWS in compact form works now for me. I had to disable also the default validation of "exp" via validation.set_required_spec_claims(&[""]); Thanks!

let jws_token="eyJhbGciOiJSUzI1NiIsIng1dCI6IlptWXlNRFprTlRoa05Ua3lZalkyWW1VM1pEQTFNRE5oWkdSbFl6TTJNamhrTTJWaE1EUTJZUW8ifQ.eyJtZXRhZGF0YS12ZXJzaW9uIjoiMC4wLjEiLCJuYW1lIjoiY29tLmJyLWF1dG9tYXRpb24uYXV0b21hdGlvbnJ1bnRpbWUiLCJ2ZXJzaW9uIjoiNi4wLjAiLCJhcnRpZmFjdHMiOlt7ImZpbGUiOiJhci5lZWI0NGY5ZGRjNWY0MzM2YTEyODJmOGNlZWYwYWI5MzQ0NDJkZTdiNTc0NzJjZGI1YjA1OGZkMDkyNmIzNDc1LnRhciIsInNoYTI1NiI6ImM1NmI3ZGFiYmM3YWE3MzBlZWFiMDc2NjhiZGNiZDdlM2Q0MDg1NTA0N2NhOWEwY2MxYmZlZDIzYTI0ODYxMTEiLCJhcmNoaXRlY3R1cmUiOiJ4ODZfNjQifSx7ImZpbGUiOiJhci4wMTMzMDc5MTg2YzZhYTYwMmNjYjA5ODA5MTU1MzNmMDNiMDFhMDA3MDFiMWYzOWMwMGYyYmYxZjczMDYxZTM2LnRhciIsInNoYTI1NiI6IjU3OGNjODYwYmFjMGM4MTM5MmViNzAyNjJiMmVjZjNlNmM1MjFhYTY0N2JjMjBkNjcyOWNhNTE4NDZhZWEwNWUiLCJhcmNoaXRlY3R1cmUiOiJhYXJjaDY0In0seyJmaWxlIjoiY29tcG9zZTEueW1sIiwic2hhMjU2IjoiMjY2NjU5Njg2NzY3MDY5YmY2NDI5NmNiNGJkYjkxNDZkNzM3Mzg0MTM4NzVjYzEyNTcyNDgwN2M1NGZhNTFhMiIsImFyY2hpdGVjdHVyZSI6Ing4Nl82NCJ9LHsiZmlsZSI6ImNvbXBvc2UyLnltbCIsInNoYTI1NiI6IjE5YTljNTdkMWQwNjVlNGZjNTdkODMxY2IzNmI4Y2U1ZTU3ODk0MDFkM2Q1N2JhNTUyZTJkNjM0YTg4NjkwYjAiLCJhcmNoaXRlY3R1cmUiOiJhYXJjaDY0In1dfQ.PMuAg_M-IqiGY8s6KeiUra30mxUwulgrGkI4YaEMIwsOUE-cI2oqOxWkV4OUJ9NNVnUGWuuBO6E4VFCsabWL3fsIPyl_7mrGToNPAka7OxaadQXhFhZwzaGuBi879-pWVm5NNtjjCO1inDw6dDBLVj89HWeBQxgEgVwYWA1P6lbCLFrstlb_2Qp8yY44KAQDV3bVudmlZn_VaxKlsFSc8_8DOPaeFwbWjqG1XT4WrKAiKFnQCqnzaSyH4O-0bPiZ7rq04mfA6-Nm7ZNnrST3dbG6v9f5Ku9qHu6Nfs0uMGIclLN8Krzk365IuCnLp_W3zL7bTwJ3VGMj6AHjH5U4Jw";
let public_key = "-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5JX/bCT4jVCtzesSMHd8
Wu7ksIs3tFhw/8oYB+4cLik5sjvc1HiWWA50UK7TIERIIuXq0SAulV9xbHLrZjZn
iU2hbN83+7LySfzQxQRwfIVLFRsUqcm23ZZFMCHxuzEfwl/m+61AkDzARmoUVCyh
LXlomjKzN45RZZqSHVkvpJWidqKcVCtdjsGnVZeeX4TG6QI/hbzRm4y6oxLwriwJ
vl2bz+lJUN57XxhtCgYtdhUa8PfD+5mC26xlLvMfru3Ic1+NKgcZi1g5dmOMQOpY
PnLPN2VKbA9Nx+RxlQK1C+R/mmEM6lGZbSqxHUa3WuB725gVNN+tHTUfsprjax7b
PQIDAQAB
-----END PUBLIC KEY-----";

let decoding_key = DecodingKey::from_rsa_pem(public_key.as_bytes()).unwrap_or_else(|err| {
    println!("Error creating decoding key: {}", err);
    panic!("Decoding key creation failed");
});
let validation = Validation::new(Algorithm::RS256);
validation.set_required_spec_claims(&[""]);

let mut parts = jws_token.split(".");
let jws: Jws<serde_json::Value> = Jws {
    protected: parts.next().unwrap().to_string(),
    payload: parts.next().unwrap().to_string(),
    signature: parts.next().unwrap().to_string(),
    _pd: Default::default(),
};

let decoded_token: TokenData<serde_json::Value> = match decode_jws(&jws, &decoding_key, &validation) {
    Ok(token) => token,
    Err(err) => {
        println!("Error decoding token: {}", err);
        panic!("Token decoding failed");
    }
};

fuch1m avatar Apr 08 '24 08:04 fuch1m

Oh excellent!

andrewbaxter avatar Apr 09 '24 14:04 andrewbaxter

any updates on this MR?

WANG-lp avatar Apr 23 '24 15:04 WANG-lp

Just need some usage feedback, I'm not knowledgeable about it so I can't really have a good opinion on it

Keats avatar Apr 24 '24 20:04 Keats

I was thinking about this, and I wonder if it wouldn't be fine to merge as is? It's new functionality, so it won't break existing users, and if there are design or implementation issues that make it unusable making changes (even ones that are nominally breaking) it won't affect anyone because there wouldn't be any users in the first place.

The main risks are that this 1. misses some use cases and the design prevents modification to allow those use cases, but that wouldn't necessarily be found by light usage feedback anyway or 2. isn't ergonomic, but I think it's a simple enough interface that that's not a large risk.

andrewbaxter avatar Jun 12 '24 16:06 andrewbaxter