Skip to content

Conversation

valkrypton
Copy link

@valkrypton valkrypton commented Jul 31, 2025

working on #621.

@GlenDC I've written the conversions in rama-crypto/src/jose/jwa.rs file. Let me know if they are correct. However, I'd like some guidance on what changes needs to be made in the rama-crypto/src/jose/jwk.rs file.

@GlenDC GlenDC requested review from soundofspace and GlenDC July 31, 2025 10:00
@GlenDC
Copy link
Member

GlenDC commented Jul 31, 2025

I've asked @soundofspace to do the initial reviews and guidance here. He'll come back to you.

@soundofspace
Copy link
Collaborator

For changes in rama-crypto/src/jose/jwk.rs I would start with RSA as it behaves quite similar to EC curves.

Places that need updating:

  • Add JWK::new_from_rsa_keypair using RsaKeyPair from awc_lc_rs. Here you create key_type JWKType::RSA with base64 encoded values for n, e (using RsaKeyPair.public_key().modules/exponent)
  • In JWK::unparsed_public_key implement the reverse so we can get an UnparsedPublicKey
  • Add RsaKey very similar to EcdsaKey but modify where needed to use RSA stuff
  • Rename EcdsaKeySigningHeaders to SigningHeaders so you can also use it to sign for RSA
  • Implement Signer for RsaKey, this logic can probably be copied from the implementation for EcdsaKey but might need small tweaks
  • Add some tests to verify that everything works like expected. These can be hardcoded to some expected output for a given input, there are lots of other projects online that have already a very nice test suite for this if you need inspiration
  • If you see opportunities to extend the current (very minimal) test suite please also do, it would appreciated a lot

Also don't be afraid to look at the jwk rfc it's a decently sized one but it's split up in a lot of chapters and it also contains some examples

And thanks for looking into this!

}
}

impl TryFrom<JWA> for Box<&dyn RsaEncoding> {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Any specific reason to box this and not use &'static RsaSignatureEncoding instead?

Copy link
Author

@valkrypton valkrypton Aug 18, 2025

Choose a reason for hiding this comment

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

RsaSignatureEncoding is not exposed outside of the crate.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Missed that it was not exposed, in that case this is indeed a good approach

Copy link
Author

Choose a reason for hiding this comment

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

Btw my i've been busy with my day job so this issue slipped my mind. I'll make a complete PR in a few days

Copy link
Collaborator

Choose a reason for hiding this comment

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

No worries, no rush here

Copy link
Member

Choose a reason for hiding this comment

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

Missed that it was not exposed, in that case this is indeed a good approach

@soundofspace do you think that's a mistake of that crate? If so can you perhaps check with those crate authors to get that improved so we can do perhaps better here in future?

Copy link
Collaborator

Choose a reason for hiding this comment

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

@GlenDC already did that a few hours ago

@valkrypton valkrypton force-pushed the implement-jwa-algorithms branch 2 times, most recently from ba45afd to 4377157 Compare August 31, 2025 15:35
result.extend_from_slice(&final_len);
result.extend_from_slice(&final_sequence);

Ok(signature::UnparsedPublicKey::new(self.alg.try_into()?, result))
Copy link
Author

@valkrypton valkrypton Aug 31, 2025

Choose a reason for hiding this comment

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

@soundofspace
The UnparsedPublicKey::new takes trait objects of type VerificationAlgorithm as argument. And JWA.alg is of type dyn RsaEncoding. So I couldn't figure out a way to convert our alg field to that type. Any guidance would be appreciated.

.sign(*alg, self.rng(), data.as_bytes(), &mut sig)
.context("sign protected data")?;
// problem here
Ok(sig.into())
Copy link
Author

@valkrypton valkrypton Aug 31, 2025

Choose a reason for hiding this comment

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

The sign method for RsaKeyPair does not return the Signature and the Signature type does not expose it's new method. So there is no way to construct that type. What should we do here?

Copy link
Collaborator

@soundofspace soundofspace left a comment

Choose a reason for hiding this comment

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

Nice progress, and thanks for looking into this!

}
}

impl TryFrom<JWA> for Box<&dyn RsaEncoding> {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can you change this to &'static dyn RsaEncoding so no allocations are needed

)),
}
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

If you also add these it should solve the issue you have with converting

impl TryFrom<JWA> for &'static dyn VerificationAlgorithm {
    type Error = OpaqueError;

    fn try_from(value: JWA) -> Result<Self, Self::Error> {
        match value {
            JWA::RS256 => Ok(&RSA_PKCS1_2048_8192_SHA256),

Copy link
Collaborator

Choose a reason for hiding this comment

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

And once that is is added, the logic from this one, should also be placed in there
impl TryFrom<JWA> for &'static EcdsaVerificationAlgorithm {

Something along these lines:

           JWA::PS512 => Ok(&RSA_PSS_2048_8192_SHA512),
            JWA::ES256 | JWA::ES384 ... => {
                let signing_algo: &'static EcdsaSigningAlgorithm = value.try_into()?;
                Ok(signing_algo.deref())
            }

Otherwise there might be issues with try_into overlapping for the use case of verification

}

impl Signer for RsaKey {
type Signature = Signature;
Copy link
Collaborator

Choose a reason for hiding this comment

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

For Rsa signing this is a different signature

Suggested change
type Signature = Signature;
type Signature = Vec<u8>;

So no need to do extra try_into in the sign method

@soundofspace soundofspace self-assigned this Sep 2, 2025
@valkrypton valkrypton force-pushed the implement-jwa-algorithms branch from 4377157 to e1c1ade Compare September 15, 2025 15:28
Copy link
Collaborator

@soundofspace soundofspace left a comment

Choose a reason for hiding this comment

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

Nice progress, some minor remarks but looks like it's almost ready

let n_der_encoded = utils::encode_integer(n_bytes);
let e_der_encoded = utils::encode_integer(e_bytes);

let mut rsa_public_key_sequence = Vec::new();
Copy link
Collaborator

Choose a reason for hiding this comment

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

It should be possible to create all the vec's in this block with Vec::with_capacity to prevent extra allocations and moves

let e_der_encoded = utils::encode_integer(e_bytes);

let mut rsa_public_key_sequence = Vec::new();
rsa_public_key_sequence.extend_from_slice(&n_der_encoded);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Since you don't need these "original" vectors later you can you extend instead which takes ownership and is more optimized

Comment on lines 238 to 239
n: String::try_from(n.big_endian_without_leading_zero().to_vec())
.map_err(|e| OpaqueError::from_display(format!("{:?}", e)))?,
Copy link
Collaborator

Choose a reason for hiding this comment

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

n and e should be base64 encoded bytes which is not the same as String::try_from, that one does utf8 encoding, instead use

 BASE64_URL_SAFE_NO_PAD.encode(n)

Copy link
Collaborator

Choose a reason for hiding this comment

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

Can you also try to add some unit tests which should detect mistakes like this

}
}

pub struct RsaKey {
Copy link
Collaborator

Choose a reason for hiding this comment

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

This one will also have to import/exported in mod.rs

When you run linting/clippy/tests

just qa

or only linting/clippy without tests

just qq

It should also detect these things, same for unused imports

}

/// Create pkcs8 der for the current [`RsaKeyPair`]
pub fn pkcs8_der(&self) -> Result<(JWA, Pkcs8V1Der<'_>), OpaqueError> {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
pub fn pkcs8_der(&self) -> Result<(JWA, Pkcs8V1Der<'_>), OpaqueError> {
pub fn pkcs8_der(&self) -> Result<(JWA, Pkcs8V1Der<'static>), OpaqueError> {

'static is more explicit here and its also what happens internally


fn sign(&self, data: &str) -> Result<Self::Signature, Self::Error> {
let mut sig: Vec<u8> = Vec::new();
let alg = <&'static dyn RsaEncoding as TryFrom<JWA>>::try_from(self.alg)?;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
let alg = <&'static dyn RsaEncoding as TryFrom<JWA>>::try_from(self.alg)?;
let alg = self.alg.try_into()?;

Should be possible to just do this?

}
}

pub(super) fn encode_integer(mut bytes: Vec<u8>) -> Vec<u8> {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can you add comment that this function should only be used for parsing JWK encoded rsa values. This function does not work for general asn1 encoding which is fine for this use case, but a comment should prevent other people from later extending this module and using this function for wrong use cases.

Some things that this function does not handle:

  • empty bytes vec
  • negative numbers
  • assumes input is already in minimal format, which is not always the case in general asn 1

Again fine for this use case, but warrants a comment

Copy link
Author

@valkrypton valkrypton Sep 16, 2025

Choose a reason for hiding this comment

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

Could you suggest me some resources where I could learn more about these specs?

@valkrypton valkrypton force-pushed the implement-jwa-algorithms branch 2 times, most recently from 8bfdb48 to 71b1713 Compare September 16, 2025 08:36
@valkrypton valkrypton force-pushed the implement-jwa-algorithms branch from 71b1713 to a839ce0 Compare September 16, 2025 10:04
@valkrypton valkrypton marked this pull request as ready for review September 18, 2025 10:48
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.

3 participants