Thalhammer / jwt-cpp

A header only library for creating and validating json web tokens in c++
https://thalhammer.github.io/jwt-cpp/
MIT License
855 stars 233 forks source link

Exception: failed to load key: bio read failed #280

Closed jpl-vitac closed 9 months ago

jpl-vitac commented 1 year ago

What's your question?

Trying to solve Exception: failed to load key: bio read failed

Additional Context

Code and a sample run are shown below. The problem is that this line is throwing an exception:

    jwt::algorithm::rs512 publicAlgo( publicKey );

But note that this line is perfectly happy:

    jwt::algorithm::rs512 privateAlgo( "", privateKey );

This is on Ubuntu 20.04. OpenSSL 1.1.1f 31 Mar 2020.

At this point, I'm dead in the water validating my JWTs.

At this point, I think I'm probably generating my keys improperly. If I cut & paste keys from some of your example files, those work. But I don't have a clue where to go from here.

Here's my code:

#define JWT_DISABLE_PICOJSON
#include <iostream>

#include <jwt-cpp/jwt.h>
#include <jwt-cpp/traits/nlohmann-json/defaults.h>
#include <jwt-cpp/traits/nlohmann-json/traits.h>

#include <openssl/bio.h>
#include <openssl/pem.h>

int main(int, const char *) {
    int bits = 2048;

    try {
        // This first part creates new keys.
        RSA * rsa = RSA_new();
        BIGNUM * bne = BN_new();
        BN_set_word(bne, RSA_F4);
        RSA_generate_key_ex(rsa, bits, bne, NULL);

        BIO * bioPrivate = BIO_new( BIO_s_mem() );
        BIO * bioPublic = BIO_new( BIO_s_mem() );
        PEM_write_bio_RSAPrivateKey( bioPrivate, rsa, nullptr, nullptr, 0, nullptr, nullptr );
        PEM_write_bio_RSAPublicKey( bioPublic, rsa );

        size_t privateLen = BIO_pending( bioPrivate );
        size_t publicLen = BIO_pending( bioPublic );

        // Oversized buffers.
        char privateBuffer[10000];
        char publicBuffer[10000];

        BIO_read(bioPrivate, privateBuffer, privateLen);
        BIO_read(bioPublic, publicBuffer, publicLen);

        privateBuffer[privateLen] = 0;
        publicBuffer[publicLen] = 0;

        string privateKey = privateBuffer;
        string publicKey = publicBuffer;

        cout << "Keys:\n"
             << privateKey << endl << endl
             << publicKey << endl << endl;

        cout << "Create the algorithms. Private:\n";
        jwt::algorithm::rs512 privateAlgo( "", privateKey );

        cout << "Create the algorithms. Public:\n";
        jwt::algorithm::rs512 publicAlgo( publicKey );
    }
    catch (const std::exception &e) {
        cerr << "Exception: " << e.what() << endl;
    }

    cout << "Done.\n";
}

And here's a sample run:

Keys: -----BEGIN RSA PRIVATE KEY----- MIIEogIBAAKCAQEAxW6ZXPS+iV+c5fTdvp2p/ahdcK/rLH1+1zxmnv7erZrq2lM8 po2dlWaz8M/0aBd4ycl6OkVpqvVZXNf/EKyZ3x4JkbjDJ6Jqun26pXiDzHWSnALL Un11YQzhI5Tz9fQFng7GVmdC5uJtM4HeenuCdTpzfUuzODqApkd1RX2GRZETjMsD owh98BY9yTtaJJkHWiOUxheOXONUCwT4dEfNHNvJKQvTjAK/359O7LXBuTuFfPNo lET//1x4M9KjmAFlVs7C0kxib5JyDFfIySpNcMDJty8Jh5ZVFxGzIkWTaIU7i7Vk IOyQlyjgHR6vq32jg71xzFqItxk/Tf1GatdR4wIDAQABAoIBAHFfm433tVqpny2m OZf2beGNx2qoesHnpujudHelIDZNGcQZvGBgPjfG7FnC8TS9Fc26dwfzSES184UP PBdVJl8rno8hrF+cMvG4tW/EDttgQLp6GEvcY4VBoh+Or7FzWa50CY0gaxiuucq0 Lw/i0P2EfUFcMSR+49WYf01SUcKsE7NBreq1BRErMNd0BOq6CcATwJYKgkWj9qji BMEK/mG0UIYjZ8Ai4E74sumcPG87/j/rVq1iyw2xWUGTReIlMl4pR9I1YKgBwnCn WSkgt+kbl3MxejnIhgTCrrA+OpzeG6dlmOzbEG3qD3EafBg9lAEVrKM7G2kWI5yz HzhmVXECgYEA8wvx/1FMw2zhgohRGj/ZmVY8QNfjON2Ocq9u0WsgI3NLx16YGghU 60xQ4C49sc3D+b6u2sAk/uPlHBkpSUGNr8Cl6WW/EwfAGNjvrK2pMKaUA2lo/IMn gLkrrO2u7SikfBWgFJKW59s1vRnvJKvDWopG7WEEiHhzfBFQIJG//pcCgYEAz/RN NPCd0DX05eZloNXweLUwqpgNSUr+G3wm0F3RneczZeshjYwKqnvDa5zifdl6h2Bw V/LxfJ8I5YhhR1zynFOz8a9J4RpJGdq7iQKN2l9okEYtB2dSDpKZM79rG8XykQgf E6a91dc8zA6IVTNxAiTPTbVjXrpppvlsWV9RfJUCgYBvxY+N5hqSSPFQcVYy5ygv 3zuoamAhl6pZzZn+DvDhvLUdw7/ZQPmkmB0da8aNjV5R7UJGcVTSL4X+rQeSm+YH 7GWgd10u6EKMar+WPuru57xr8T8J2VsCVKXkSg5HHIjAU8WhkGR7zhDhnRqgV6lo au0BX7uQ8yvQ47lgsQaFkwKBgHUNRTwMSDg/dsbFReUfJwk6q9cKWUgtDxU3b72P YYLo9ZRjonJDJmnJ2jjDiPVfqclzrijDFcyY0/AnMOJzXhhCQSJEEWjEW+tSpIa9 Sk1lsLys04G8VYLgX52yHD74z8107dEo5OfuDEmr0G9s0iprv8g01mcmtHuJH8S6 k2p1AoGAPOR2aIAUam9WOFFkPdihNxpVgke4jwy//k2S4ZXz4GJsBT202N0dHPf9 4MjB9tE8RRbSF/v4a/LXe1FpEaSI9j4ql4KlQhnOyKmXwWRe2IyJJVGr3yQmYMZW tM/96paeMmPcSXx39HijDV8qUp+ZuIUcUktwAcyAg/z3Qd/X4Kc= -----END RSA PRIVATE KEY-----

-----BEGIN RSA PUBLIC KEY----- MIIBCgKCAQEAxW6ZXPS+iV+c5fTdvp2p/ahdcK/rLH1+1zxmnv7erZrq2lM8po2d lWaz8M/0aBd4ycl6OkVpqvVZXNf/EKyZ3x4JkbjDJ6Jqun26pXiDzHWSnALLUn11 YQzhI5Tz9fQFng7GVmdC5uJtM4HeenuCdTpzfUuzODqApkd1RX2GRZETjMsDowh9 8BY9yTtaJJkHWiOUxheOXONUCwT4dEfNHNvJKQvTjAK/359O7LXBuTuFfPNolET/ /1x4M9KjmAFlVs7C0kxib5JyDFfIySpNcMDJty8Jh5ZVFxGzIkWTaIU7i7VkIOyQ lyjgHR6vq32jg71xzFqItxk/Tf1GatdR4wIDAQAB -----END RSA PUBLIC KEY-----

Create the algorithms. Private: Create the algorithms. Public: Exception: failed to load key: bio read failed Done.

Thalhammer commented 1 year ago

Whenever you have a known good data and want to replicate it using your own code its a good idea to look for differences in the output. In this particular case your code generates a "BEGIN RSA PUBLIC KEY" whereas jwt-cpp (or more precisely OpenSSL underneath) expects a "BEGIN PUBLIC KEY". Those two are somewhat similar, however the non rsa format includes an additional objectidentifier in addition to the public key, which is likely whats tripping OpenSSL.

Jwt-cpp tries to load the key using PEM_read_bio_PUBKEY, which according to OpenSSL "process a public key [...] encoded as a SubjectPublicKeyInfo structure.", whereas the "The RSAPublicKey functions process an RSA public key [...] encoded using a PKCS#1 RSAPublicKey structure.".

To solve this you either have to wrap your RSA structure in a EVP_PKEY and use PEM_write_bio_PUBKEY, or (untested) use PEM_write_bio_RSA_PUBKEY, which according to the docs also encode the key as a SubjectPublicKeyInfo, so they should in theory work (I've never used the later though).

jpl-vitac commented 1 year ago

Okay, that's interesting. I changed to do this:

    PEM_write_bio_RSAPrivateKey( bioPrivate, rsa, nullptr, nullptr, 0, nullptr, nullptr );
    PEM_write_bio_RSA_PUBKEY( bioPublic, rsa );

I'm not sure why the private key method is correct but the public key version is not.

I'll put this back into my real code and make sure I'm good. Thank you, Thalhammer.

Thalhammer commented 1 year ago

I'm not sure why the private key method is correct but the public key version is not.

The private key method should in theory have the same issue, however in this case you got lucky because OpenSSL seems to support both formats for some reason (probably backwards compatibility). I'd still recommend switching both in order to make it easier to understand for other people (if its a shared codebase) or future yourself.

The RSAPrivateKey functions process an RSA private key using an RSA structure.
The write routines uses traditional format. The read routines handles the same formats
as the PrivateKey functions but an error occurs if the private key is not RSA.

Thank you, Thalhammer.

No problem ;)

jpl-vitac commented 1 year ago

Thanks again.