RustCrypto / RSA

RSA implementation in pure Rust
Apache License 2.0
536 stars 148 forks source link

Feature to import from JWK ? #382

Closed apertureless closed 10 months ago

apertureless commented 10 months ago

Hey there!

I am currently working on a small rust project with rsa to parse and decrypt a json file. Some of the fields of the json file are encrypted with RSA-OAEP with the WebCrypto API and we are using JWKs for the Keypairs. (Private key encrypted with aes-gcm)

The decryption of the base64 encoded private key works, however now I ended up with an untyped json string or my own JWK struct.

However I am stuck how to import it as a RSAPrivateKey. At first I thought ::from_components would be what I am looking for, as I have all rsa key components in the jwk. But I guess I was wrong, as the keys do not match.

tarcieri commented 10 months ago

We have some support for this in the https://docs.rs/jose-jwk/ crate

Namely if you parse your JWK to this type: https://docs.rs/jose-jwk/latest/jose_jwk/struct.Rsa.html#impl-TryFrom%3C%26Rsa%3E-for-RsaPrivateKey

You can use the TryFrom impl here to decode it to an rsa::RsaPrivateKey: https://docs.rs/jose-jwk/latest/jose_jwk/struct.Rsa.html#impl-TryFrom%3C%26Rsa%3E-for-RsaPrivateKey

Though I'm afraid there's not a good example of how to do this.

apertureless commented 10 months ago

@tarcieri

Hm, but TryFrom seems to use also from_components internally, right? So this is basically the way to go?

Here's what I have done:

pub struct JsonWebKeyRSA {
    // Algorithm
    alg: String,
    // modulus
    n: String,
    // public exponent
    e: String,
    // private exponent
    d: String,
    // prime
    p: String,
    // prime
    q: String,
    // extractable
    ext: bool,
    // key id
    kty: String,
    // key options
    key_ops: Option<Vec<KeyOperations>>,
}

impl JsonWebKeyRSA {
    pub fn get_components(self) -> (BigUint, BigUint, BigUint, Vec<BigUint>) {
        let mut n = Vec::<u8>::new();
        let mut e = Vec::<u8>::new();
        let mut d = Vec::<u8>::new();
        let mut p = Vec::<u8>::new();
        let mut q = Vec::<u8>::new();

        general_purpose::URL_SAFE_NO_PAD
            .decode_vec(&self.n, &mut n)
            .expect("Failed to base64 decode modulus n");
        general_purpose::URL_SAFE_NO_PAD
            .decode_vec(&self.e, &mut e)
            .expect("Failed to base64 decode public exponent e");
        general_purpose::URL_SAFE_NO_PAD
            .decode_vec(&self.d, &mut d)
            .expect("Failed to base64 decode private exponent d");
        general_purpose::URL_SAFE_NO_PAD
            .decode_vec(&self.p, &mut p)
            .expect("Failed to base64 decode prime 1");
        general_purpose::URL_SAFE_NO_PAD
            .decode_vec(&self.q, &mut q)
            .expect("Failed to base64 decode prime 2");

        (
            BigUint::from_bytes_le(&n),
            BigUint::from_bytes_le(&e),
            BigUint::from_bytes_le(&d),
            vec![BigUint::from_bytes_le(&p), BigUint::from_bytes_le(&q)],
        )
    }
}

And in my main

  let private_key: JsonWebKeyRSA = serde_json::from_str(jwk_private.as_str()).unwrap();
  let (n, e, d, primes) = private_key.get_components();
  let rsa_private_key = RsaPrivateKey::from_components(n, e, d, primes)
        .expect("Failed to build RSA key from components");

However decryption using this key fails. To debug I tried to export it to PEM

let pk = rsa_private_key.to_pkcs8_der().unwrap();
pk.write_pem_file("privatekey.pem", "PRIVATE KEY", rsa::pkcs8::LineEnding::LF)

And compared it to the pem which I exported with web crypto

await window.crypto.subtle.exportKey(
            'pkcs8',
            pk
        )

But they do not match. So I thought my initial idea with from_components is flawed.

tarcieri commented 10 months ago

Can you try it with the jose-jwk crate?

apertureless commented 10 months ago

Can you try it with the jose-jwk crate?

@tarcieri Not really, because it looks like jose-jwk does not support RSA-OAEP keys.

https://docs.rs/jose-jwa/0.1.2/jose_jwa/enum.Algorithm.html

Possible types of algorithms that can exist in an “alg” descriptor. Currently only signing algorithms are represented.

If I try to parse it to JWK I am getting the error:

hread 'main' panicked at src/wallet.rs:39:71:
called `Result::unwrap()` on an `Err` value: Error("data did not match any variant of untagged enum Algorithm", line: 1, column: 3206)

I guess because the alg is RSA-OAEP-256 which is part of the JWE spec.

apertureless commented 10 months ago

Okay, I found my mistake. The code I posted is basically working, I just had to change the BigUInt parsing to big endian. So after replacing

BigUint::from_bytes_le(&n),
BigUint::from_bytes_le(&e),
BigUint::from_bytes_le(&d),

with

BigUint::from_bytes_be(&n),
BigUint::from_bytes_be(&e),
BigUint::from_bytes_be(&d),

in my get_components I could properly use RsaPrivateKey::from_components