3

I want to verify a JSON Web Tokens (JWT) with OpenSSL, but I can't get the verification to succeed. Here is my code using a token and keys from Verifying JWT signed with the RS256 algorithm using public key in C#:

QByteArray token       = "eyJraWQiOiIxZTlnZGs3IiwiYWxnIjoiUlMyNTYifQ.ewogImlzcyI6ICJodHRwOi8vc2VydmVyLmV4YW1wbGUuY29tIiwKICJzdWIiOiAiMjQ4Mjg5NzYxMDAxIiwKICJhdWQiOiAiczZCaGRSa3F0MyIsCiAibm9uY2UiOiAibi0wUzZfV3pBMk1qIiwKICJleHAiOiAxMzExMjgxOTcwLAogImlhdCI6IDEzMTEyODA5NzAsCiAiY19oYXNoIjogIkxEa3RLZG9RYWszUGswY25YeENsdEEiCn0.XW6uhdrkBgcGx6zVIrCiROpWURs-4goO1sKA4m9jhJIImiGg5muPUcNegx6sSv43c5DSn37sxCRrDZZm4ZPBKKgtYASMcE20SDgvYJdJS0cyuFw7Ijp_7WnIjcrl6B5cmoM6ylCvsLMwkoQAxVublMwH10oAxjzD6NEFsu9nipkszWhsPePf_rM4eMpkmCbTzume-fzZIi5VjdWGGEmzTg32h3jiex-r5WTHbj-u5HL7u_KP3rmbdYNzlzd1xWRYTUs4E8nOTgzAUwvwXkIQhOh5TPcSMBYy6X3E7-_gr9Ue6n4ND7hTFhtjYs3cjNKIA08qm5cpVYFMFMG6PkhzLQ);
QByteArray rsaModulus  = QByteArray::fromBase64("w7Zdfmece8iaB0kiTY8pCtiBtzbptJmP28nSWwtdjRu0f2GFpajvWE4VhfJAjEsOcwYzay7XGN0b-X84BfC8hmCTOj2b2eHT7NsZegFPKRUQzJ9wW8ipn_aDJWMGDuB1XyqT1E7DYqjUCEOD1b4FLpy_xPn6oV_TYOfQ9fZdbE5HGxJUzekuGcOKqOQ8M7wfYHhHHLxGpQVgL0apWuP2gDDOdTtpuld4D2LK1MZK99s9gaSjRHE8JDb1Z4IGhEcEyzkxswVdPndUWzfvWBBWXWxtSUvQGBRkuy1BHOa4sP6FKjWEeeF7gm7UMs2Nm2QUgNZw6xvEDGaLk4KASdIxRQ");
QByteArray rsaExponent = QByteArray::fromBase64("AQAB");

QList<QByteArray> tokenParts = token.split('.');
QByteArray header = tokenParts[0];
QByteArray body   = tokenParts[1];
QByteArray sig    = tokenParts[2];

QByteArray pad = sig.length() % 4 ? "" : QByteArray("====").left(4 - (sig.length() % 4));
QByteArray decodedSig = QByteArray::fromBase64(sig.replace('_', '/').replace('-', '+') + pad);

BIGNUM *rsaModulusBn  = BN_bin2bn((unsigned char *)rsaModulus.data(), rsaModulus.length(), NULL);
BIGNUM *rsaExponentBn = BN_bin2bn((unsigned char *)rsaExponent.data(), rsaExponent.length(), NULL);

RSA *rsa = RSA_new();
if (rsa == NULL) printf("cannot allocate RSA\n");
rsa->n = rsaModulusBn;
rsa->e = rsaExponentBn;

EVP_PKEY *key = EVP_PKEY_new();
if (1 != EVP_PKEY_set1_RSA(key, rsa)) ERR_print_errors_fp(stdout);

EVP_MD_CTX *ctx = EVP_MD_CTX_create();
if (ctx == NULL) printf("cannot allocate MD CTX\n");

if (1 != EVP_DigestVerifyInit(ctx, NULL, EVP_sha256(), NULL, key)) ERR_print_errors_fp(stdout);
if (1 != EVP_DigestVerifyUpdate(ctx, (unsigned char *)header.data(), header.length())) ERR_print_errors_fp(stdout);
if (1 != EVP_DigestVerifyUpdate(ctx, ".", 1)) ERR_print_errors_fp(stdout);
if (1 != EVP_DigestVerifyUpdate(ctx, (unsigned char *)body.data(), body.length())) ERR_print_errors_fp(stdout);
if (1 != EVP_DigestVerifyFinal(ctx, (unsigned char *)decodedSig.data(), decodedSig.length()))
{
  printf("failure\n");
  ERR_print_errors_fp(stdout);
}
else
{
  printf("success\n");
}

if (ctx) EVP_MD_CTX_destroy(ctx);
if (key) EVP_PKEY_free(key);
if (rsa) RSA_free(rsa);

In case it's helpful node jwt-to-pem says that the key above in PEM format is:

-----BEGIN RSA PUBLIC KEY-----
MIIBCgKCAQEAw7Zdfmece8iaB0kiTY8pCtiBtzbptJmP28nSWwtdjRu0f2GFpajv
WE4VhfJAjEsOcwYzay7XGN0b+X84BfC8hmCTOj2b2eHT7NsZegFPKRUQzJ9wW8ip
n/aDJWMGDuB1XyqT1E7DYqjUCEOD1b4FLpy/xPn6oV/TYOfQ9fZdbE5HGxJUzeku
GcOKqOQ8M7wfYHhHHLxGpQVgL0apWuP2gDDOdTtpuld4D2LK1MZK99s9gaSjRHE8
JDb1Z4IGhEcEyzkxswVdPndUWzfvWBBWXWxtSUvQGBRkuy1BHOa4sP6FKjWEeeF7
gm7UMs2Nm2QUgNZw6xvEDGaLk4KASdIxRQIDAQAB
-----END RSA PUBLIC KEY-----

The verification fails with:

10857314492266413637:error:04091077:rsa routines:INT_RSA_VERIFY:wrong signature length:rsa_sign.c:186:

I'd really appreciate someone with experience looking over my code as I don't know how to debug it without a working example to compare.

Community
  • 1
  • 1
helpwithhaskell
  • 558
  • 4
  • 13
  • `rsa->n = rsaModulusBn` and `rsa->e = rsaExponentBn` is going to break under OpnSSL 1.1.0 because you won't have access to the members. For 1.1.0, you will need to use [`RSA_set0_key`](https://www.openssl.org/docs/man1.1.0/crypto/RSA_set0_crt_params.html). Also see [Error: “invalid use of incomplete type ‘RSA {aka struct rsa_st}” in OpenSSL 1.1.0](http://stackoverflow.com/q/40549318). – jww Nov 12 '16 at 05:47
  • Thanks jww. I don't have that function in the target platform's openssl. I've added the comment to the code base. – helpwithhaskell Nov 12 '16 at 05:52

1 Answers1

2

It turns out that the RSA key modulus and exponent are base64 URL encoded too. Here is code that works, horaay:

QByteArray token       = "eyJraWQiOiIxZTlnZGs3IiwiYWxnIjoiUlMyNTYifQ.ewogImlzcyI6ICJodHRwOi8vc2VydmVyLmV4YW1wbGUuY29tIiwKICJzdWIiOiAiMjQ4Mjg5NzYxMDAxIiwKICJhdWQiOiAiczZCaGRSa3F0MyIsCiAibm9uY2UiOiAibi0wUzZfV3pBMk1qIiwKICJleHAiOiAxMzExMjgxOTcwLAogImlhdCI6IDEzMTEyODA5NzAsCiAiY19oYXNoIjogIkxEa3RLZG9RYWszUGswY25YeENsdEEiCn0.XW6uhdrkBgcGx6zVIrCiROpWURs-4goO1sKA4m9jhJIImiGg5muPUcNegx6sSv43c5DSn37sxCRrDZZm4ZPBKKgtYASMcE20SDgvYJdJS0cyuFw7Ijp_7WnIjcrl6B5cmoM6ylCvsLMwkoQAxVublMwH10oAxjzD6NEFsu9nipkszWhsPePf_rM4eMpkmCbTzume-fzZIi5VjdWGGEmzTg32h3jiex-r5WTHbj-u5HL7u_KP3rmbdYNzlzd1xWRYTUs4E8nOTgzAUwvwXkIQhOh5TPcSMBYy6X3E7-_gr9Ue6n4ND7hTFhtjYs3cjNKIA08qm5cpVYFMFMG6PkhzLQ";
QByteArray rsaModulus  = QByteArray::fromBase64("w7Zdfmece8iaB0kiTY8pCtiBtzbptJmP28nSWwtdjRu0f2GFpajvWE4VhfJAjEsOcwYzay7XGN0b-X84BfC8hmCTOj2b2eHT7NsZegFPKRUQzJ9wW8ipn_aDJWMGDuB1XyqT1E7DYqjUCEOD1b4FLpy_xPn6oV_TYOfQ9fZdbE5HGxJUzekuGcOKqOQ8M7wfYHhHHLxGpQVgL0apWuP2gDDOdTtpuld4D2LK1MZK99s9gaSjRHE8JDb1Z4IGhEcEyzkxswVdPndUWzfvWBBWXWxtSUvQGBRkuy1BHOa4sP6FKjWEeeF7gm7UMs2Nm2QUgNZw6xvEDGaLk4KASdIxRQ", QByteArray::Base64UrlEncoding);
QByteArray rsaExponent = QByteArray::fromBase64("AQAB", QByteArray::Base64UrlEncoding);

QList<QByteArray> tokenParts = token.split('.');
QByteArray header = tokenParts[0];
QByteArray body   = tokenParts[1];
QByteArray sig    = QByteArray::fromBase64(tokenParts[2], QByteArray::Base64UrlEncoding);

BIGNUM *rsaModulusBn  = BN_bin2bn((unsigned char *)rsaModulus.data(), rsaModulus.length(), NULL);
BIGNUM *rsaExponentBn = BN_bin2bn((unsigned char *)rsaExponent.data(), rsaExponent.length(), NULL);

RSA *rsa = RSA_new();
if (rsa == NULL) printf("cannot allocate RSA\n");

#if defined(OPENSSL_1_1_0)
if (1 != RSA_set0_key(rsa, rsaModulusBn, rsaExponentBn, NULL); ERR_print_errors_fp(stdout);
#else
rsa->n = rsaModulusBn;
rsa->e = rsaExponentBn;
#endif

EVP_PKEY *key = EVP_PKEY_new();
if (1 != EVP_PKEY_set1_RSA(key, rsa)) ERR_print_errors_fp(stdout);

EVP_MD_CTX *ctx = EVP_MD_CTX_create();
if (ctx == NULL) printf("cannot allocate MD CTX\n");

if (1 != EVP_DigestVerifyInit(ctx, NULL, EVP_sha256(), NULL, key)) ERR_print_errors_fp(stdout);
if (1 != EVP_DigestVerifyUpdate(ctx, (unsigned char *)header.data(), header.length())) ERR_print_errors_fp(stdout);
if (1 != EVP_DigestVerifyUpdate(ctx, ".", 1)) ERR_print_errors_fp(stdout);
if (1 != EVP_DigestVerifyUpdate(ctx, (unsigned char *)body.data(), body.length())) ERR_print_errors_fp(stdout);
if (1 != EVP_DigestVerifyFinal(ctx, (unsigned char *)sig.data(), sig.length()))
{
  printf("failure\n");
  ERR_print_errors_fp(stdout);
}
else
{
  printf("success\n");
}

if (ctx) EVP_MD_CTX_destroy(ctx);
if (key) EVP_PKEY_free(key);
if (rsa) RSA_free(rsa);
helpwithhaskell
  • 558
  • 4
  • 13