Keats / jsonwebtoken

JWT lib in rust
MIT License
1.64k stars 260 forks source link

Failure to Verify EdDSA Signature from DER Public Key, Succeeds in PEM #398

Open naftulikay opened 1 month ago

naftulikay commented 1 month ago

I'm generating ed25519 keys using ed25519-dalek and the rand crates, and while I can sign and verify using public key PEM encoding, verification fails when using public key DER encoding.

Cargo.toml:

# ...
[dependencies]
ed25519-dalek = { version = "2", features = ["pkcs8", "pem", "rand_core"] }
jsonwebtoken = "9"
rand = "0.8"

Here is a test case which generates a keypair, converts it into jsonwebtoken types, and successfully signs and verifies a signature:

tests/test_alpha.rs:

use ed25519::pkcs8::{EncodePrivateKey, EncodePublicKey};
use ed25519::pkcs8::spki::der::pem::LineEnding;
use ed25519_dalek::SigningKey as Ed25519SigningKey;
use jsonwebtoken::{Algorithm, DecodingKey, EncodingKey};

/// Tests that we can generate an ed25519 keypair via [ed25519_dalek], import it into
/// [jsonwebtoken], generate a signature, and verify that signature.
#[test]
fn test_ed25519_roundtrip() {
    use jsonwebtoken::crypto::{sign, verify};

    // first, generate an ed25519 private key using the thread rng
    let dalek_private = Ed25519SigningKey::generate(&mut rand::thread_rng());
    let dalek_public = dalek_private.verifying_key();

    // next, in order for it to work with jsonwebtoken, we convert it into pkcs8
    let (public_pem, private_der) = (
        // FIXME this line succeeds when using pem, fails when using der
        dalek_public.to_public_key_pem(LineEnding::LF).expect("unable to convert public to der"),
        dalek_private.to_pkcs8_der().expect("unable to convert private to der")
    );

    // create the jsonwebtoken key structure
    let (encoding_key, decoding_key) = (
        EncodingKey::from_ed_der(private_der.as_bytes()),
        // FIXME this line succeeds when using pem, fails when using der
        DecodingKey::from_ed_pem(public_pem.as_bytes()).expect("unable to parse public pem"),
    );

    // create a signature
    let message = "Hello World";
    let signature = sign(message.as_bytes(), &encoding_key, Algorithm::EdDSA).unwrap();

    let valid = verify(signature.as_str(), message.as_bytes(), &decoding_key, Algorithm::EdDSA)
        .unwrap();

    assert!(valid, "failed to verify signature");
}

The above code does pass the test, but note that I'm converting my public ed25519 key to PEM format, and creating a DecodingKey using DecodingKey::from_ed_pem:

let (public_pem, private_der) = (
    // FIXME this line succeeds when using pem, fails when using der
    dalek_public.to_public_key_pem(LineEnding::LF).expect("unable to convert public to der"),
    dalek_private.to_pkcs8_der().expect("unable to convert private to der")
);

let (encoding_key, decoding_key) = (
    EncodingKey::from_ed_der(private_der.as_bytes()),
    DecodingKey::from_ed_pem(public_pem.as_bytes()).expect("unable to parse public pem"),
);

My private key converts just fine when using DER encoding, but the public key seems to be the problem. If I change the above code to use DER for the public key, the test fails:

let (public_der, private_der) = (
    // FIXME this line succeeds when using pem, fails when using der
    dalek_public.to_public_key_der().expect("unable to convert public to der"),
    dalek_private.to_pkcs8_der().expect("unable to convert private to der")
);

let (encoding_key, decoding_key) = (
    EncodingKey::from_ed_der(private_der.as_bytes()),
    DecodingKey::from_ed_der(public_der.as_bytes()),
);

Just changing the DecodingKey from PEM format to DER format causes the verification to fail.

Am I doing something wrong here when trying to work with DER format? Is it possible that the DER format emitted by ed25519-dalek is incompatible with this library, and if so, what can I do to determine where the incompatibility is coming from?

naftulikay commented 1 month ago

I don't think this is a problem within ed25519-dalek, as I have verified things externally using the openssl CLI. I generated a key-pair, wrote the PEM format to public.pem, the DER format to public.der, and then ran the following to convert the PEM to DER:

openssl pkey -in public.pem -pubin -outform der -out public.openssl.der

I then compared the contents, and they are the same:

$ sha256sum public.der public.openssl.der
a17fcf0a2f50e2d495e4f90ce263410edc183add6c62699a2facbccf60410f74  public.der
a17fcf0a2f50e2d495e4f90ce263410edc183add6c62699a2facbccf60410f74  public.openssl.der

Therefore it seems that there is a problem within the DecodingKey::from_ed_der method.

naftulikay commented 1 month ago

I also just attempted to use the private key DER bytes to create a DecodingKey:

let private_der = dalek_private.to_pkcs8_der().unwrap();
let decoding_key = DecodingKey::from_ed_der(private_der.as_bytes());

This also fails verification. It seems the only thing that I can do is use PEM. I may try some of the other options as well.