babelouest / rhonabwy

Javascript Object Signing and Encryption (JOSE) library - JWK, JWKS, JWS, JWE and JWT
https://babelouest.github.io/rhonabwy/
GNU Lesser General Public License v2.1
45 stars 21 forks source link

Pubkey not set but signature verification possible #26

Open DedieuPY opened 1 year ago

DedieuPY commented 1 year ago

Hello, I have a problem (or rather a strange behavior) with the lib rhonabwy.

First, here is my code :

const char token[] = "eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCJ9."                // header
                     "eyJleHBpcmF0aW9uRGF0ZSI6MTIzNDU2Nzg5MCwibXNnQ2hrQSI6MTIsIm1zZ0Noa0IiOjM0LCJ0b2tlblR5cGUiOiJTIn0."   // payload
                     "AOyZC-Jf2rt6BpSjzNHk0hyLBS96DpBWDol58gORVjTMpJNoU_ICE6ePLCBq24kW4CgN_XDV3cjtSl8CjG4HZ3zAAAAOForo1kNG5PjBzItBsf9AS5fq_E8DBi99o8xZvZBTqTunYLNXE048WG5r88AfnNLCnYCioiSIg47774r760Eu"; // signature

const unsigned char pubKey[] = "-----BEGIN PUBLIC KEY-----\n"
            "MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBgc4HZz+/fBbC7lmEww0AO3NK9wVZ\n"
            "PDZ0VEnsaUFLEYpTzb90nITtJUcPUbvOsdZIZ1Q8fnbquAYgxXL5UgHMoywAib47\n"
            "6MkyyYgPk0BXZq3mq4zImTRNuaU9slj9TVJ3ScT3L1bXwVuPJDzpr5GOFpaj+WwM\n"
            "Al8G7CqwoJOsW7Kddns=\n"
            "-----END PUBLIC KEY-----\n";

const unsigned char privKey[] = "-----BEGIN PRIVATE KEY-----\n"
            "MIHuAgEAMBAGByqGSM49AgEGBSuBBAAjBIHWMIHTAgEBBEIBiyAa7aRHFDCh2qga\n"
            "9sTUGINE5jHAFnmM8xWeT/uni5I4tNqhV5Xx0pDrmCV9mbroFtfEa0XVfKuMAxxf\n"
            "Z6LM/yKhgYkDgYYABAGBzgdnP798FsLuWYTDDQA7c0r3BVk8NnRUSexpQUsRilPN\n"
            "v3SchO0lRw9Ru86x1khnVDx+duq4BiDFcvlSAcyjLACJvjvoyTLJiA+TQFdmrear\n"
            "jMiZNE25pT2yWP1NUndJxPcvVtfBW48kPOmvkY4WlqP5bAwCXwbsKrCgk6xbsp12\n"
            "ew==\n"
            "-----END PRIVATE KEY-----\n";

jwt_t * jwt;

r_global_init();
r_jwt_init(&jwt);

r_jwt_add_sign_keys_pem_der(jwt, R_FORMAT_PEM, privKey, sizeof(privKey), pubKey, sizeof(pubKey));
r_jwt_advanced_parse(jwt, token,R_PARSE_NONE, R_FLAG_IGNORE_SERVER_CERTIFICATE);
r_jwt_set_sign_alg(jwt, R_JWA_ALG_ES512);
printf("PRIV : %s\n", r_jwks_export_to_json_str(jwt->jwks_privkey_sign, 0));
printf("PUB : %s\n", r_jwks_export_to_json_str(jwt->jwks_pubkey_sign, 0));

r_jwt_parse(jwt, token, 0);
r_jwt_verify_signature(jwt, NULL, 0);

printf("CLAIM = %s\n", r_jwt_get_full_claims_str(jwt));

And here is an execution result :

PRIV : {"keys":[{"kty":"EC","x":"AYHOB2c_v3wWwu5ZhMMNADtzSvcFWTw2dFRJ7GlBSxGKU82_dJyE7SVHD1G7zrHWSGdUPH526rgGIMVy-VIBzKMs","y":"ib476MkyyYgPk0BXZq3mq4zImTRNuaU9slj9TVJ3ScT3L1bXwVuPJDzpr5GOFpaj-WwMAl8G7CqwoJOsW7Kddns","d":"AYsgGu2kRxQwodqoGvbE1BiDROYxwBZ5jPMVnk_7p4uSOLTaoVeV8dKQ65glfZm66BbXxGtF1XyrjAMcX2eizP8i","crv":"P-521","kid":"CatmrLCuBa_kI3VMfembQnugtVauN35XuHIRRGZuXzY"}]}
PUB : {"keys":[]}
CLAIM = {"expirationDate":1234567890,"msgChkA":12,"msgChkB":34,"tokenType":"S"}

As you can see, apparently my public key was not set correctly but my token signature was verifiy.

Do you have an explanation please?

Thanks in advance

babelouest commented 1 year ago

Hello,

I can't reproduce your issue as-is, my output is:

PRIV : {"keys":[{"kty":"EC","x":"AYHOB2c_v3wWwu5ZhMMNADtzSvcFWTw2dFRJ7GlBSxGKU82_dJyE7SVHD1G7zrHWSGdUPH526rgGIMVy-VIBzKMs","y":"ib476MkyyYgPk0BXZq3mq4zImTRNuaU9slj9TVJ3ScT3L1bXwVuPJDzpr5GOFpaj-WwMAl8G7CqwoJOsW7Kddns","d":"AYsgGu2kRxQwodqoGvbE1BiDROYxwBZ5jPMVnk_7p4uSOLTaoVeV8dKQ65glfZm66BbXxGtF1XyrjAMcX2eizP8i","crv":"P-521","kid":"CatmrLCuBa_kI3VMfembQnugtVauN35XuHIRRGZuXzY"}]}
PUB : {"keys":[{"kty":"EC","x":"AYHOB2c_v3wWwu5ZhMMNADtzSvcFWTw2dFRJ7GlBSxGKU82_dJyE7SVHD1G7zrHWSGdUPH526rgGIMVy-VIBzKMs","y":"ib476MkyyYgPk0BXZq3mq4zImTRNuaU9slj9TVJ3ScT3L1bXwVuPJDzpr5GOFpaj-WwMAl8G7CqwoJOsW7Kddns","crv":"P-521","kid":"CatmrLCuBa_kI3VMfembQnugtVauN35XuHIRRGZuXzY"}]}
CLAIM = {"expirationDate":1234567890,"msgChkA":12,"msgChkB":34,"tokenType":"S"}

Although, if you add a private key to a JWT private keyset, its intention is to generate a new signed token. If you just want to verify a signature, you can only add the public key into the jwt's keyset, or even better, specify the public key when calling r_jwt_verify_signature.

DedieuPY commented 1 year ago

Hi @babelouest ,

First of all, thank you for your quick response. I have changed my code this way :


                     "eyJleHBpcmF0aW9uRGF0ZSI6MTIzNDU2Nzg5MCwibXNnQ2hrQSI6MTIsIm1zZ0Noa0IiOjM0LCJ0b2tlblR5cGUiOiJTIn0."   // payload
                     "AOyZC-Jf2rt6BpSjzNHk0hyLBS96DpBWDol58gORVjTMpJNoU_ICE6ePLCBq24kW4CgN_XDV3cjtSl8CjG4HZ3zAAAAOForo1kNG5PjBzItBsf9AS5fq_E8DBi99o8xZvZBTqTunYLNXE048WG5r88AfnNLCnYCioiSIg47774r760Eu"; // signature

const unsigned char pubKey[] = "-----BEGIN PUBLIC KEY-----\n"
            "MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBgc4HZz+/fBbC7lmEww0AO3NK9wVZ\n"
            "PDZ0VEnsaUFLEYpTzb90nITtJUcPUbvOsdZIZ1Q8fnbquAYgxXL5UgHMoywAib47\n"
            "6MkyyYgPk0BXZq3mq4zImTRNuaU9slj9TVJ3ScT3L1bXwVuPJDzpr5GOFpaj+WwM\n"
            "Al8G7CqwoJOsW7Kddns=\n"
            "-----END PUBLIC KEY-----\n";

const unsigned char privKey[] = "-----BEGIN PRIVATE KEY-----\n"
            "MIHuAgEAMBAGByqGSM49AgEGBSuBBAAjBIHWMIHTAgEBBEIBiyAa7aRHFDCh2qga\n"
            "9sTUGINE5jHAFnmM8xWeT/uni5I4tNqhV5Xx0pDrmCV9mbroFtfEa0XVfKuMAxxf\n"
            "Z6LM/yKhgYkDgYYABAGBzgdnP798FsLuWYTDDQA7c0r3BVk8NnRUSexpQUsRilPN\n"
            "v3SchO0lRw9Ru86x1khnVDx+duq4BiDFcvlSAcyjLACJvjvoyTLJiA+TQFdmrear\n"
            "jMiZNE25pT2yWP1NUndJxPcvVtfBW48kPOmvkY4WlqP5bAwCXwbsKrCgk6xbsp12\n"
            "ew==\n"
            "-----END PRIVATE KEY-----\n";

 r_global_init();

/* jwt_t * jwt;
r_jwt_init(&jwt); */
int iterations = 5000;
double sum = 0;
clock_t start, stop;

jwt_t * jwt;
jwk_t * jwk;
r_jwt_init(&jwt);    
r_jwk_init(&jwk);

printf("=================== IS VERIFY WORKING ? ===================\n");
printf("ADD PEM : %d\n", r_jwt_add_sign_keys_pem_der(jwt, R_FORMAT_PEM, privKey, sizeof(privKey), pubKey, sizeof(pubKey)));
r_jwt_set_sign_alg(jwt, R_JWA_ALG_ES512);
//r_jwt_set_sign_alg(jwt, R_JWA_ALG_ES256);

printf("PRIV : %s\n", r_jwks_export_to_json_str(jwt->jwks_privkey_sign, 0));
printf("PUB : %s\n", r_jwks_export_to_json_str(jwt->jwks_pubkey_sign, 0));

printf("JWK ? %d\n", r_jwk_append_x5c(jwk, R_FORMAT_PEM, pubKey, sizeof(pubKey)));

r_jwt_parse(jwt, token, 0);
r_jwt_verify_signature(jwt, jwk, 0);

and the output :


ADD PEM : 1
PRIV : {"keys":[{"kty":"EC","x":"AYHOB2c_v3wWwu5ZhMMNADtzSvcFWTw2dFRJ7GlBSxGKU82_dJyE7SVHD1G7zrHWSGdUPH526rgGIMVy-VIBzKMs","y":"ib476MkyyYgPk0BXZq3mq4zImTRNuaU9slj9TVJ3ScT3L1bXwVuPJDzpr5GOFpaj-WwMAl8G7CqwoJOsW7Kddns","d":"AYsgGu2kRxQwodqoGvbE1BiDROYxwBZ5jPMVnk_7p4uSOLTaoVeV8dKQ65glfZm66BbXxGtF1XyrjAMcX2eizP8i","crv":"P-521","kid":"CatmrLCuBa_kI3VMfembQnugtVauN35XuHIRRGZuXzY"}]}
PUB : {"keys":[]}
JWK ? 3
CLAIM = {"expirationDate":1234567890,"msgChkA":12,"msgChkB":34,"tokenType":"S"}

So I have apparently an error 3 (so RHN_ERROR_PARAM) but I don't really see why.

EDIT :

I added the log display (thanks yder) and I have this:

2023-03-28T12:29:11Z - Yder Tests ERROR: r_jwk_import_from_gnutls_pubkey ecdsa - Error curve
2023-03-28T12:29:11Z - Yder Tests ERROR: r_jwt_add_sign_keys_pem_der - Error parsing pubkey
ADD PEM : 1
PRIV : {"keys":[{"kty":"EC","x":"EVs_o5-uQbTjL3chynL4wXgUg2R9q9UU8I5mEovUf84","y":"kGe5DgSIycKp8w9aJmoHhB1sB3QTugfnRWm5nU_TzsY","d":"evZzL1gdAFr88hb2OF_2NxApJCzGCEDdfSp6VQO30hw","crv":"P-256","kid":"pA8PLil03HaoLfb_4I7Q15lVx5560MINl66Pkkat4cE"}]}
PUB : {"keys":[]}
2023-03-28T12:29:11Z - Yder Tests ERROR: r_jwk_append_x5c - Error gnutls_x509_crt_import: Base64 unexpected header error.
JWK ? 3
2023-03-28T12:29:11Z - Yder Tests ERROR: r_jwk_is_valid - Missing kty
2023-03-28T12:29:11Z - Yder Tests ERROR: r_jwk_is_valid - Invalid kty
CLAIM = {"expirationDate":1234567890,"msgChkA":789456123,"msgChkB":147852369,"tokenType":"S"}
babelouest commented 1 year ago

You're using r_jwk_append_x5c to import a public key, this function expects a x5 certificate, tha's why I also have the RHN_ERROR_PARAM.

Nevertheless, you should test all return values to make sure RHN_OK is always returned, otherwise you should stop the process.

You also shouldn't call r_jwt_set_sign_alg, the alg is set during the token parse. But you can verify that the alg set is the expected one: if (r_jwt_get_sign_alg(jwt) == R_JWA_ALG_ES512).

Are you using the last rhonabwy release? If not, you also should, that would explain our differences.

I refactored your code to make it more readable and so it won't continue after an error. Note that calling r_jwt_add_sign_keys_pem_der and r_jwt_set_sign_alg are useless and could lead to error in this case, so you should skip them.

#include <stdio.h>
#include <yder.h>
#include <rhonabwy.h>

int main() {
  const char token[] = "eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCJ9."                // header
                       "eyJleHBpcmF0aW9uRGF0ZSI6MTIzNDU2Nzg5MCwibXNnQ2hrQSI6MTIsIm1zZ0Noa0IiOjM0LCJ0b2tlblR5cGUiOiJTIn0."   // payload
                       "AOyZC-Jf2rt6BpSjzNHk0hyLBS96DpBWDol58gORVjTMpJNoU_ICE6ePLCBq24kW4CgN_XDV3cjtSl8CjG4HZ3zAAAAOForo1kNG5PjBzItBsf9AS5fq_E8DBi99o8xZvZBTqTunYLNXE048WG5r88AfnNLCnYCioiSIg47774r760Eu"; // signature

  const unsigned char pubKey[] = "-----BEGIN PUBLIC KEY-----\n"
              "MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBgc4HZz+/fBbC7lmEww0AO3NK9wVZ\n"
              "PDZ0VEnsaUFLEYpTzb90nITtJUcPUbvOsdZIZ1Q8fnbquAYgxXL5UgHMoywAib47\n"
              "6MkyyYgPk0BXZq3mq4zImTRNuaU9slj9TVJ3ScT3L1bXwVuPJDzpr5GOFpaj+WwM\n"
              "Al8G7CqwoJOsW7Kddns=\n"
              "-----END PUBLIC KEY-----\n";

  const unsigned char privKey[] = "-----BEGIN PRIVATE KEY-----\n"
              "MIHuAgEAMBAGByqGSM49AgEGBSuBBAAjBIHWMIHTAgEBBEIBiyAa7aRHFDCh2qga\n"
              "9sTUGINE5jHAFnmM8xWeT/uni5I4tNqhV5Xx0pDrmCV9mbroFtfEa0XVfKuMAxxf\n"
              "Z6LM/yKhgYkDgYYABAGBzgdnP798FsLuWYTDDQA7c0r3BVk8NnRUSexpQUsRilPN\n"
              "v3SchO0lRw9Ru86x1khnVDx+duq4BiDFcvlSAcyjLACJvjvoyTLJiA+TQFdmrear\n"
              "jMiZNE25pT2yWP1NUndJxPcvVtfBW48kPOmvkY4WlqP5bAwCXwbsKrCgk6xbsp12\n"
              "ew==\n"
              "-----END PRIVATE KEY-----\n";

  r_global_init();
  y_init_logs("issue 26", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting issue 26 tester");

  jwt_t * jwt = NULL;
  jwk_t * jwk = NULL;
  int ret;

  do {
    if ((ret = r_jwt_init(&jwt)) != RHN_OK) {
      printf("r_jwt_init error: %d\n", ret);
      break;
    }

    if ((ret = r_jwk_init(&jwk)) != RHN_OK) {
      printf("r_jwk_init error: %d\n", ret);
      break;
    }

    if ((ret = r_jwt_add_sign_keys_pem_der(jwt, R_FORMAT_PEM, privKey, sizeof(privKey), pubKey, sizeof(pubKey))) != RHN_OK) {
      printf("r_jwt_add_sign_keys_pem_der error: %d\n", ret);
      break;
    }

    if ((ret = r_jwt_set_sign_alg(jwt, R_JWA_ALG_ES512)) != RHN_OK) {
      printf("r_jwt_set_sign_alg error: %d\n", ret);
      break;
    }

    printf("PRIV : %s\n", r_jwks_export_to_json_str(jwt->jwks_privkey_sign, 0));
    printf("PUB : %s\n", r_jwks_export_to_json_str(jwt->jwks_pubkey_sign, 0));

    if ((ret = r_jwk_import_from_pem_der(jwk, R_X509_TYPE_PUBKEY, R_FORMAT_PEM, pubKey, sizeof(pubKey))) != RHN_OK) {
      printf("r_jwk_append_x5c error: %d\n", ret);
      break;
    }

    if ((ret = r_jwt_parse(jwt, token, 0)) != RHN_OK) {
      printf("r_jwk_init error: %d\n", ret);
      break;
    }

    if ((ret = r_jwt_verify_signature(jwt, jwk, 0)) != RHN_OK) {
      printf("r_jwt_verify_signature error: %d\n", ret);
      break;
    } else {
      printf("r_jwt_verify_signature ok\n");
    }
  } while (0);
  r_jwk_free(jwk);
  r_jwt_free(jwt);
  y_close_logs();
}

you can compile it with the following command: gcc -o test test.c -lrhonabwy -lyder.

With this code, I have the following output:

2021-07-09T02:11:00Z - issue 26 INFO: Starting issue 26 tester
PRIV : {"keys":[{"kty":"EC","x":"AYHOB2c_v3wWwu5ZhMMNADtzSvcFWTw2dFRJ7GlBSxGKU82_dJyE7SVHD1G7zrHWSGdUPH526rgGIMVy-VIBzKMs","y":"ib476MkyyYgPk0BXZq3mq4zImTRNuaU9slj9TVJ3ScT3L1bXwVuPJDzpr5GOFpaj-WwMAl8G7CqwoJOsW7Kddns","d":"AYsgGu2kRxQwodqoGvbE1BiDROYxwBZ5jPMVnk_7p4uSOLTaoVeV8dKQ65glfZm66BbXxGtF1XyrjAMcX2eizP8i","crv":"P-521","kid":"CatmrLCuBa_kI3VMfembQnugtVauN35XuHIRRGZuXzY"}]}
PUB : {"keys":[{"kty":"EC","x":"AYHOB2c_v3wWwu5ZhMMNADtzSvcFWTw2dFRJ7GlBSxGKU82_dJyE7SVHD1G7zrHWSGdUPH526rgGIMVy-VIBzKMs","y":"ib476MkyyYgPk0BXZq3mq4zImTRNuaU9slj9TVJ3ScT3L1bXwVuPJDzpr5GOFpaj-WwMAl8G7CqwoJOsW7Kddns","crv":"P-521","kid":"CatmrLCuBa_kI3VMfembQnugtVauN35XuHIRRGZuXzY"}]}
r_jwt_verify_signature ok
DedieuPY commented 1 year ago

With your code, I have the following output :


2023-03-28T12:44:25Z - issue 26 INFO: Starting issue 26 tester
2023-03-28T12:44:25Z - issue 26 ERROR: r_jwk_import_from_gnutls_pubkey ecdsa - Error curve
2023-03-28T12:44:25Z - issue 26 ERROR: r_jwt_add_sign_keys_pem_der - Error parsing pubkey
r_jwt_add_sign_keys_pem_der error: 1

Are you using the last rhonabwy release? If not, you also should, that would explain our differences.

I took the code of the branch master

babelouest commented 1 year ago

What system/version are you using? What version of GnuTLS is installed?

DedieuPY commented 1 year ago

I'm using Yocto 2.5, with GnuTLS 3.6.1 (which is supposed to be the minimum version required for ECDSA if I understand correctly)

However, i tried this code but with the RS512 algo and it works correctly. So it seems to be a sepcific problem with ECDSA (or at least depending on the algo used)

babelouest commented 1 year ago

I'm using Yocto 2.5, with GnuTLS 3.6.1 (which is supposed to be the minimum version required for ECDSA if I understand correctly)

Maybe that's the problem with rhonabwy, I assumed ECDSA was supported since GnuTLS 3.6 but it's probably from a later version.

DedieuPY commented 1 year ago

Perhaps If I can find out which version of GnuTLS ECDSA works, I'll tell you.

But thanks for taking the time to help me!