Skip to content

Conversation

@einrobin
Copy link

@einrobin einrobin commented Dec 1, 2025

Adds support for asymmetric JWKs to the Rust implementation.

I've added the "kty" parameter to the JWK as per RFC (https://datatracker.ietf.org/doc/html/rfc7517#section-4.1), this was not present before but is required now (as stated in the RFC "This member MUST be present in a JWK."). This is a breaking change though as all previously generated tokens would have to be updated, I'm not sure whether that is a big deal? If so this would require "oct" to be the default value.

Also the TypeScript part is not implemented yet.

> cargo run --features jwk-rsa,jwk-ec --bin moq-token -- --key private.jwk generate --algorithm RS256 --public public.jwk
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.31s
     Running `target\debug\moq-token.exe --key private.jwk generate --algorithm RS256 --public public.jwk`
> cat private.jwk
eyJhbGciOiJSUzI1NiIsImtleV9vcHMiOlsic2lnbiIsInZlcmlmeSJdLCJrdHkiOiJSU0EiLCJuIjoiMXVwNkVPclNtRjFXN1ZLTnROQmpLTFRyN21FR0NOMkEzbHVRZzc1djF2cnd4UFA5NkEyOE1VYzVqdVlzTGhja1ZiR0ZOQXJvRDhZc2NwakUyRnpYZHFzNUJBanNXd0JZeV9mdTJmR3ZkTjRVZE45V1pkRXJhbXJna3gxTGlZOGh5Xy16RzJ4dDV6VlMybVN0ME5RYmZFMTBMQnZoSk1HVTl3QlZJU3pzdmdSNkVseDJFeWxTc1piZUV4QmE5RGtTVlZBVE9falBaS0RwMnZRTWZqMnJtamRsd3ZCamM1azkwV0s4OS1tdG9nTkI1cFJZNWdvSDNPZlFfTDU5dTJpTjRseno1Q1VqTW5sNWc5OExFcElUaDVKZGYwaFpsSklRa21ZVk5vWEVxV1I5X015OTB3eTNWN2paZ1plclg3VzlaUy1qZ0lhRXRrQU1hc3BUWFk5dmF3IiwiZSI6IkFRQUIiLCJkIjoiVDVrLXBwOWlhbjRfb054dmthV1E1RlhHQUY4OURmR0VObGJ4ZW1vQklEMkhDVnRaRWlac0tsQTMtRDQxNU1nN2MtV3c4U3FNOHJLeWFhczlWT2xtQUJUQXBFYnR5M0tnY01NZVd1aFIxTllmLTMxd2tYdGNPaWpsam5kT0w5LXNZOU1Mc2otQm9SMjExeDlzNVNoNkdaclNTVGYyTmxmZ1pXOWhHRTE4VFlfSzJJMWlDQnZ6TldZc3dGalhpTEIyMjA4YmJ5Sk9yaUtvcWVJdHlUcGpuUmhxYU9PXzdNZDhmendOUXBCNkFxSUZXWHY2cUYtMjFiNUkyQTJwNW1NQjl3V2dIa1ZLNXI5dUdtVGN4bjIyQ1M2eV9UQzg5NnlyTGxUdXh3U1FTTXNqSmY1RmpCQmtMdTNiRUotYTlLT2dXaVBoYzV0UG5FQzlKMFh6akxfM2NRIiwicCI6IjNNTl9RWGRMbHdJdlFydWhUcWZSbHFXR2Z0VklXVFBiQzhvNW9HX3k3ZGdQeG1fblhMWWNrbG9XQXNuQ1RYQkZmclROOU5aWU1kbzh6bC1HRTVyT3l4Y05TRWJYcVdfY3pGbkswSW5JVnNqblBIdkdEY1NrbU9qSGRyTm1fWHpLSU1GNWJkYk5DcFh4ZjlDTjItdFlMS3RfRnR1UXVobnpkam9rbk5veG5UayIsInEiOiItVGdKNHIxOVBQRWJTaEFzaldmUXE0OS1hb1NFb2E0dzBDWUdEbEt5TEtRcmtuR1E5dlR0T0tMeVc2SVlzRTVOSjlOZzZMYkZnNF9qRkxBSHcxekg4NlZyMXRDWThpdml1cXAtLUZHRXFWR2p0bnVRbmV5cElGaXhqMVlFMmQ2b2o0Y09qWVdUODIxUFR0TEtqa05KR3RZNV80NVp1c3VMY05LczFYWFhGY00ifQ
> cat public.jwk
eyJhbGciOiJSUzI1NiIsImtleV9vcHMiOlsidmVyaWZ5Il0sImt0eSI6IlJTQSIsIm4iOiIxdXA2RU9yU21GMVc3VktOdE5CaktMVHI3bUVHQ04yQTNsdVFnNzV2MXZyd3hQUDk2QTI4TVVjNWp1WXNMaGNrVmJHRk5Bcm9EOFlzY3BqRTJGelhkcXM1QkFqc1d3Qll5X2Z1MmZHdmRONFVkTjlXWmRFcmFtcmdreDFMaVk4aHlfLXpHMnh0NXpWUzJtU3QwTlFiZkUxMExCdmhKTUdVOXdCVklTenN2Z1I2RWx4MkV5bFNzWmJlRXhCYTlEa1NWVkFUT19qUFpLRHAydlFNZmoycm1qZGx3dkJqYzVrOTBXSzg5LW10b2dOQjVwUlk1Z29IM09mUV9MNTl1MmlONGx6ejVDVWpNbmw1Zzk4TEVwSVRoNUpkZjBoWmxKSVFrbVlWTm9YRXFXUjlfTXk5MHd5M1Y3alpnWmVyWDdXOVpTLWpnSWFFdGtBTWFzcFRYWTl2YXciLCJlIjoiQVFBQiJ9
> $token = cargo run --features jwk-rsa,jwk-ec --bin moq-token -- --key private.jwk sign --root test --publish pub                
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.30s
     Running `target\debug\moq-token.exe --key private.jwk sign --root test --publish pub`
> $token
eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJyb290IjoidGVzdCIsInB1dCI6WyJwdWIiXSwiZXhwIjpudWxsLCJpYXQiOm51bGx9.sufElGn-PAMqU3uSsW5WFo7UUUObmLnGsrJ3XWNB_Soncmrkuh_lT8oZ_kCKzSNmoq7bHBowILyWsWtjrw6kQx8e-LLf7uKm8sjM-L_yiblORlwrYQ9oxQd3J_qn-1NyBHyabBxbVVH-J6ltT_OTBedNT1Wr7n6vsSc-8psPiVM9PUaSGwFlO9a7cFY7oUOr2zEDJacY2YAztWJG5EgdytI557pYJIufK9wF8BbSTlCej5iKCm9Pge57z2HGFrwUVmfO9YIqI3Os1Y0Ui7N8ftrY2Qz2z0UAOP38Wrfk4i9BsfvrkLNpHgxCnzrGT5gYCwDii0fCospi1110FQriYg
> $token | cargo run --features jwk-rsa,jwk-ec --bin moq-token -- --key public.jwk verify                          
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.30s
     Running `target\debug\moq-token.exe --key public.jwk verify`
Claims {
    root: "test",
    publish: [
        "pub",
    ],
    cluster: false,
    subscribe: [],
    expires: None,
    issued: None,
}

Copy link
Owner

@kixelated kixelated left a comment

Choose a reason for hiding this comment

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

Super cool.

Very fast review on my phone. I would use aws-lc-rs for the crypto operations and remove the feature flags.

serde_json = "1"
serde_with = { version = "3", features = ["base64"] }

rsa = { version = "0.9.9", optional = true }
Copy link
Owner

@kixelated kixelated Dec 1, 2025

Choose a reason for hiding this comment

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

I'd use ring or aws-lc-rs. The family of Rust crypto crates are pretty bad for usability and performance.

Copy link
Author

Choose a reason for hiding this comment

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

I tried it with aws-lc-rs at first but unfortunately it seems like the KeyPair doesn't expose the private_key components as in the rsa crate, please correct me if I'm missing something. I also didn't really want to bother parsing the DER that I could export from the KeyPair. ring doesn't seem much different. I've implemented the RngCore trait using aws-lc-rs and then use it as the RNG for rsa.
image

}

#[cfg(feature = "jwk-ec")]
fn generate_ec_key(curve: EllipticCurve) -> Key {
Copy link
Owner

Choose a reason for hiding this comment

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

There's no easier way to do this?

Copy link
Author

Choose a reason for hiding this comment

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

Updated the function

Copy link
Owner

@kixelated kixelated left a comment

Choose a reason for hiding this comment

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

Ideally we can reuse the same key files so it's not a breaking relay change.

decode: Default::default(),
encode: Default::default(),
};
match key {
Copy link
Owner

Choose a reason for hiding this comment

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

key? is simpler.


let x = point
.x()
.ok_or_else(|| anyhow::anyhow!("Missing x() point in EC key"))?
Copy link
Owner

Choose a reason for hiding this comment

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

use anyhow::Context;

Suggested change
.ok_or_else(|| anyhow::anyhow!("Missing x() point in EC key"))?
.context("Missing x() point in EC key")?

#[derive(Clone)]
pub struct Auth {
key: Option<Arc<moq_token::Key>>,
key: Option<Arc<moq_token::JWK>>,
Copy link
Owner

Choose a reason for hiding this comment

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

Maybe just keep this as Key? It's fine if it's not backwards compatible.

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.

2 participants