Skip to content

Add PEM decoding support (#106)#107

Merged
Keats merged 24 commits intonextfrom
pem-handling
Nov 15, 2019
Merged

Add PEM decoding support (#106)#107
Keats merged 24 commits intonextfrom
pem-handling

Conversation

@Keats
Copy link
Copy Markdown
Owner

@Keats Keats commented Nov 3, 2019

  • Add PEM support with pem and simple_asn1. Documentation TODO

  • Make pkcs1 and pkcs8 versions of the RSA key, confirm they pass tests.

  • Add documentation, simplify

  • Update readme

  • Bump pem version

  • Remove extra print

* Add PEM support with pem and simple_asn1. Documentation TODO

* Make pkcs1 and pkcs8 versions of the RSA key, confirm they pass tests.

* Add documentation, simplify

* Update readme

* Bump pem version

* Remove extra print
@Keats Keats self-assigned this Nov 3, 2019
@Keats
Copy link
Copy Markdown
Owner Author

Keats commented Nov 3, 2019

@LeviSchuck

b9a3e30 removes the Option::, it's in the prelude so no need for them
06bebea runs cargo fmt, definitely something very nice to use, the original code was 2 spaces indented rather than the normal 4
caef740 does some refactoring to avoid some duplication as well as changing the names of enum members: even if they are abbreviations like RSA, it is more idiomatic to have them as Rsa

I'll start thinking about the new API now, with pem handling it can be much nicer than the current next branch.

@LeviSchuck
Copy link
Copy Markdown
Contributor

I've reviewed the 3 commits you've referenced and I'm good with them.

@Keats
Copy link
Copy Markdown
Owner Author

Keats commented Nov 4, 2019

So my thought for the api would be to take a &str again for both hmac and pem. The main issue is the Rsa(n, e) type of public key, I wonder if we can easily build a pem string from modulus/exponent with ring. @LeviSchuck do you know how to do that by any chance?

@LeviSchuck
Copy link
Copy Markdown
Contributor

It is possible.
If you drop the public key ( https://github.com/Keats/jsonwebtoken/blob/pem-handling/tests/rsa/public_rsa_key_pkcs1.pem ) into https://lapo.it/asn1js/

image

You'll see that it's encoded as a ASN1 sequence of a big endian big int followed by the modulus (similarly encoded)

The simple_asn1 library already included may be able to assist with that.

@LeviSchuck
Copy link
Copy Markdown
Contributor

In fact, it's one of the things I was looking into, using another JWK to feed these algorithms which rely on decoding an ASN1 body.

The challenge I had with the simple asn1 though is that it wants the user to calculate the size of things.
https://docs.rs/simple_asn1/0.4.0/simple_asn1/enum.ASN1Block.html

I haven't gotten to the part of actually doing it, but in theory it would be to_der(ASN1Block::Sequence(?,vec!(ASN1Block:: Integer(?,modulus), ASN1Block:: Integer(?,exponent))))

@Keats
Copy link
Copy Markdown
Owner Author

Keats commented Nov 4, 2019

Nice, so I guess the encode/decode with &str is doable, just need to have a function that takes exponent/modulus and returns a pem and users can pass that to encode/decode.

@Keats
Copy link
Copy Markdown
Owner Author

Keats commented Nov 5, 2019

@LeviSchuck are you interested in building a function that looks like (exponent: &[u8], modulus: &[u8]) -> Result<String> in pem_decoder.rs (for now). Otherwise I'll add it when I have time but you know more about asn1 than I do. I had a quick look:

pub fn ne_to_pem(modulus: &[u8], exponent: &[u8]) -> Result<String> {
    let der = to_der(&ASN1Block::Sequence(
        0,
        vec![
            ASN1Block::Integer(0, BigInt::from_signed_bytes_be(modulus)),
            ASN1Block::Integer(0, BigInt::from_signed_bytes_be(exponent)),
        ],
    ))
    .unwrap();
    let encoded = base64::encode(&der);
    let formatted = format!("-----BEGIN RSA PUBLIC KEY-----\n{}\n-----END RSA PUBLIC KEY-----", encoded);

    Ok(formatted)
}

This generated a valid key (afaik) but I could not get the test rsa_modulus_exponent test to pass, maybe because of the offset or me not having read enough about asn1/der.

@LeviSchuck
Copy link
Copy Markdown
Contributor

LeviSchuck commented Nov 5, 2019

Hey @Keats

Thanks for the example code. I've spent a few weekends at a coffee shop studying this stuff for that exact reason. I didn't use simple_asn1 for it, its much more restricted and hacky in my current implementation but I can put some time tonight into this.

For reference I was making a way to go to and back from pem and jwks

{
  "keys": [
    {
      "alg": "RSA256",
      "e": "AQAB",
      "kty": "RSA",
      "n": "xNTl_qI-XCVyJL0E8GYu0Fn1oLOECP3nb0mcnttPYIwAxU2uIHLp3K-DdbCeXVVspII7fwyVCzUKc-KeDbJnNGiHelIDBVDt5Q1KJB6gE9Yl-zzhfl6z2pPgmYz6Y5u9rtjD0Yhe9gZz8twWH5pv5o2l5UrtjpDsIgagTYkEGymwJKACvCj5Re2kc47wxCHWn632M4NQvOvLvb92uUfNRfxG_kI6E0Y6TO70wZdtmWjCXoLdFfw8ZSnsQeg_iM9ajpWSiPOrLS9fWcUcqNV3kg15Re9yviZ_bGy0I7LXq9_qzJQpBDnE591P6QhIQIzgjeXug42R2ULg-Imhsa7hKw",
      "use": "sig"
    },
    {
      "alg": "EC256",
      "crv": "PC-256",
      "kty": "EC",
      "use": "sig",
      "x": "BOKyZbG4GXq2FF1SFFN3AYpYvA8uuTn2BzGD0XrgYyI=",
      "y": "X/DBTa8Jl8D8lLohDoBPktSLuWgho0aFGcdLXlfT56c6"
    }
  ]
}

and

-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxNTl/qI+XCVyJL0E8GYu
0Fn1oLOECP3nb0mcnttPYIwAxU2uIHLp3K+DdbCeXVVspII7fwyVCzUKc+KeDbJn
NGiHelIDBVDt5Q1KJB6gE9Yl+zzhfl6z2pPgmYz6Y5u9rtjD0Yhe9gZz8twWH5pv
5o2l5UrtjpDsIgagTYkEGymwJKACvCj5Re2kc47wxCHWn632M4NQvOvLvb92uUfN
RfxG/kI6E0Y6TO70wZdtmWjCXoLdFfw8ZSnsQeg/iM9ajpWSiPOrLS9fWcUcqNV3
kg15Re9yviZ/bGy0I7LXq9/qzJQpBDnE591P6QhIQIzgjeXug42R2ULg+Imhsa7h
KwIDAQAB
-----END PUBLIC KEY-----

-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE4rJlsbgZerYUXVIUU3cBili8Dy65
OfYHMYPReuBjIl/wwU2vCZfA/JS6IQ6AT5LUi7loIaNGhRnHS15X0+enOg==
-----END PUBLIC KEY-----

@Keats
Copy link
Copy Markdown
Owner Author

Keats commented Nov 5, 2019

No hurry, take your time. If we nail that function, we can have a pretty optimal DX while supporting more formats than before.

@LeviSchuck
Copy link
Copy Markdown
Contributor

It would also mean that I can drop my inflexible jwt implementation :)

@LeviSchuck
Copy link
Copy Markdown
Contributor

The API could definitely use some refining or access via module, but I got it working for public keys. (EC(x) and RSA(n,e))

LeviSchuck@311192a

Everything is in one commit so far but I'd make a PR once I get documentation in. Then @Keats you can make the API more DX friendly.

@Keats
Copy link
Copy Markdown
Owner Author

Keats commented Nov 6, 2019

That looks very good, thanks for that. Some changes will be required (eg not exposing ring) but can be done later. Is anyone actually using EC JWK? I don't think I've seen any of that myself.
Don't worry about the documentation, it will probably change shape a lot so the documentation might be outdated.

@LeviSchuck
Copy link
Copy Markdown
Contributor

I haven’t seen it yet, but I’m wanting to use it for my own projects.
The specification says that EC is optional and RSA is expected, but also TLS 1.3 dropped RSA in the cipher suites supported.

As for not exposing ring, that’s only in the tests as a convenience to prove that I can go from private PEM to ring to public pem, else I’d need to start making “key parameters” and that’s kinda reimplementing ring. I could also hard code the keys in the tests but that’s less confidence to me than using arbitrary external key data via include str

DX wise I think the next part is going from modulus exponent to der for rsa

@LeviSchuck
Copy link
Copy Markdown
Contributor

I also replied on the commit, the specification predefined the curve used for JOSE HS* as prime256v1

@Keats
Copy link
Copy Markdown
Owner Author

Keats commented Nov 6, 2019

As for not exposing ring, that’s only in the tests as a convenience to prove that I can go from private PEM to ring to public pem, else I’d need to start making “key parameters” and that’s kinda reimplementing ring

Those tests are fine but should be implemented in the module itself.

DX wise I think the next part is going from modulus exponent to der for rsa

I am thinking into having dedicated decode functions for RSA jwk and EC jwk instead of using the default decode so the users just put n/e or x in some struct/tuple and everything happens internally (including jwk sets I guess?).

If you can do a PR with what you currently have on this branch, we can start having a look.

@LeviSchuck
Copy link
Copy Markdown
Contributor

I noticed while peeking into ring, there were functions that took the parameters directly.

But sure, I’ll submit a pr

@LeviSchuck
Copy link
Copy Markdown
Contributor

👍

@Keats
Copy link
Copy Markdown
Owner Author

Keats commented Nov 6, 2019

Important tools for Rust: clippy and fmt as shown in a6ea8c2

@LeviSchuck
Copy link
Copy Markdown
Contributor

RE: JWKs are you planning on doing verification with a JWK struct or vec of jwks?

@Keats
Copy link
Copy Markdown
Owner Author

Keats commented Nov 7, 2019

After some thoughts, I might just have an example for JWK set since they can in people own struct/hashmap etc and it is probably simpler to provider the plumbing + example on how to do it

@Keats
Copy link
Copy Markdown
Owner Author

Keats commented Nov 9, 2019

I've added not yet working JWK decoding, I'm not entirely sure why b279815#diff-d866210ac5208ba9071685e86eba4beeR117-R127 is failing to verify but that can be left for later.

@LeviSchuck
Copy link
Copy Markdown
Contributor

Its probably what I mentioned earlier

The RSA Modulus from ring doesn't have a leading zero ... but in a PEM it does have a leading zero.

I unfortunately don't have much time to poke at this.

@Keats Keats mentioned this pull request Nov 11, 2019
@Keats
Copy link
Copy Markdown
Owner Author

Keats commented Nov 11, 2019

Look at 614f361 to see the bugfix...

🤦‍♂

@LeviSchuck
Copy link
Copy Markdown
Contributor

Oh, I can see how that went wrong. Glad that's resolved.

@Keats
Copy link
Copy Markdown
Owner Author

Keats commented Nov 11, 2019

@LeviSchuck after writing the docs, I've realized I don't actually need anything from pem_decoder.rs since there is a method that takes (n, e) specifically.
Do you have a usecase for it in your own code or can I remove it for now?

@Keats
Copy link
Copy Markdown
Owner Author

Keats commented Nov 14, 2019

I think API & docs is in a good place right now, probably ready for some alpha release after I add a few more testcases from jwt.io.

@Keats Keats merged commit 1a727f7 into next Nov 15, 2019
@Keats Keats deleted the pem-handling branch November 15, 2019 19:30
@Keats
Copy link
Copy Markdown
Owner Author

Keats commented Nov 15, 2019

Thanks a lot @LeviSchuck !

I'll push an alpha release now for v7

@rokit
Copy link
Copy Markdown

rokit commented Nov 26, 2019

Hi, I'm extremely new to all this, and I'm following this guide for verifying a google token.
https://ncona.com/2015/02/consuming-a-google-id-token-from-a-server/

I'm stuck here:

This key (the kid key in the header) can’t be used directly to verify the token signature. First we need to transform it to a PEM key. I’m not going to explain how to do this because there is already a node module that can do it for us called rsa-pem-from-mod-exp.

Does this library allow verifying with PEM and/or transforming kid to PEM with a modulus and exponent?

@LeviSchuck
Copy link
Copy Markdown
Contributor

Hey @rokit

You'll need to fetch the remote JWK, find the key associated to the kid present in the JOSE header, and then use the parameters in that JWK to create a public key. You can then verify the key from there.

A constructor for a public key of that kind should be demonstrated in the test folder.

@rokit
Copy link
Copy Markdown

rokit commented Nov 26, 2019

Thanks @LeviSchuck. It looks like I needed decode_rsa_components.

JadedBlueEyes referenced this pull request in JadedBlueEyes/jsonwebtoken Apr 13, 2023
Add PEM decoding support (#106)
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