Skip to content

Commit

Permalink
Add support for ES256K
Browse files Browse the repository at this point in the history
This comes with a lot of other things:
- Check bit sizes for EC keys vs ALG
- For ES256K also check that the curve is secp256k1
- Rework tests for above stuff (no tests for ES256K, yet)
- Added extra tests in for jwt_exception_str()

WARNING!: ES256K does not work on GnuTLS because it does not support the
	  secp256k1 curve.

FIX: Fixed a bug in GnuTLS that should have (but apparently didn't) show
any errors. Somewhere in the EC key handling.

Signed-off-by: Ben Collins <[email protected]>
  • Loading branch information
benmcollins committed Dec 2, 2024
1 parent 549dda4 commit e12bba8
Show file tree
Hide file tree
Showing 13 changed files with 149 additions and 76 deletions.
1 change: 1 addition & 0 deletions include/jwt.h
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ typedef enum jwt_alg {
JWT_ALG_RS384,
JWT_ALG_RS512,
JWT_ALG_ES256,
JWT_ALG_ES256K,
JWT_ALG_ES384,
JWT_ALG_ES512,
JWT_ALG_PS256,
Expand Down
11 changes: 7 additions & 4 deletions libjwt/jwt-gnutls.c
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ int jwt_sign_sha_pem(jwt_t *jwt, char **out, unsigned int *len, const char *str,

/* EC */
case JWT_ALG_ES256:
/* XXX case JWT_ALG_ES256K: */
alg = GNUTLS_DIG_SHA256;
pk_alg = GNUTLS_PK_EC;
break;
Expand Down Expand Up @@ -221,11 +222,11 @@ int jwt_sign_sha_pem(jwt_t *jwt, char **out, unsigned int *len, const char *str,
}

/* Check r and s size */
if (JWT_ALG_ES256)
if (jwt->alg == JWT_ALG_ES256 || jwt->alg == JWT_ALG_ES256K)
adj = 32;
if (JWT_ALG_ES384)
if (jwt->alg == JWT_ALG_ES384)
adj = 48;
if (JWT_ALG_ES512)
if (jwt->alg == JWT_ALG_ES512)
adj = 66;

if (r.size > adj)
Expand Down Expand Up @@ -315,6 +316,7 @@ int jwt_verify_sha_pem(jwt_t *jwt, const char *head, unsigned int head_len, cons

/* EC */
case JWT_ALG_ES256:
/* XXX case JWT_ALG_ES256K: */
alg = GNUTLS_SIGN_ECDSA_SHA256;
break;
case JWT_ALG_ES384:
Expand All @@ -337,7 +339,7 @@ int jwt_verify_sha_pem(jwt_t *jwt, const char *head, unsigned int head_len, cons
goto verify_clean_sig;
}

if (gnutls_pubkey_import(pubkey, &cert_dat, GNUTLS_X509_FMT_PEM)) {
if ((ret = gnutls_pubkey_import(pubkey, &cert_dat, GNUTLS_X509_FMT_PEM))) {
ret = EINVAL;
goto verify_clean_pubkey;
}
Expand All @@ -346,6 +348,7 @@ int jwt_verify_sha_pem(jwt_t *jwt, const char *head, unsigned int head_len, cons
* is ESxxx. */
switch (jwt->alg) {
case JWT_ALG_ES256:
case JWT_ALG_ES256K:
case JWT_ALG_ES384:
case JWT_ALG_ES512:
/* XXX Gotta be a better way. */
Expand Down
44 changes: 37 additions & 7 deletions libjwt/jwt-openssl.c
Original file line number Diff line number Diff line change
Expand Up @@ -154,21 +154,29 @@ int jwt_verify_sha_hmac(jwt_t *jwt, const char *head, unsigned int head_len, con

#define SIGN_ERROR(__err) { ret = __err; goto jwt_sign_sha_pem_done; }

static int jwt_degree_for_key(EVP_PKEY *pkey)
static int jwt_degree_for_key(EVP_PKEY *pkey, jwt_t *jwt)
{
int degree = 0;
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
/* OpenSSL 3.0.0 and later has a new API for this. */
char groupNameBuffer[24] = {0};
size_t groupNameBufferLen = 0;
int curve_nid;
EC_GROUP *group;

if (!EVP_PKEY_get_group_name(pkey, groupNameBuffer, sizeof(groupNameBuffer), &groupNameBufferLen))
return -EINVAL;

groupNameBuffer[groupNameBufferLen] = '\0';

/* We only perform this check for ES256K. All others we just check
* the degree (bits). */
if (jwt->alg == JWT_ALG_ES256K) {
if (strcmp(groupNameBuffer, "secp256k1"))
return -EINVAL;
}

#if OPENSSL_VERSION_NUMBER >= 0x30000000L
int curve_nid;
EC_GROUP *group;
/* OpenSSL 3.0.0 and later has a new API for this. */

curve_nid = OBJ_txt2nid(groupNameBuffer);
if (curve_nid == NID_undef)
return -EINVAL;
Expand Down Expand Up @@ -197,6 +205,26 @@ static int jwt_degree_for_key(EVP_PKEY *pkey)
EC_KEY_free(ec_key);
#endif

/* Final check for matching degree */
switch (jwt->alg) {
case JWT_ALG_ES256:
case JWT_ALG_ES256K:
if (degree != 256)
return -EINVAL;
break;
case JWT_ALG_ES384:
if (degree != 384)
return -EINVAL;
break;
case JWT_ALG_ES512:
/* This is not a typo. ES512 uses secp521r1 */
if (degree != 521)
return -EINVAL;
break;
default:
return -EINVAL;
}

return degree;
}

Expand Down Expand Up @@ -251,6 +279,7 @@ int jwt_sign_sha_pem(jwt_t *jwt, char **out, unsigned int *len,

/* ECC */
case JWT_ALG_ES256:
case JWT_ALG_ES256K:
alg = EVP_sha256();
type = EVP_PKEY_EC;
break;
Expand Down Expand Up @@ -322,7 +351,7 @@ int jwt_sign_sha_pem(jwt_t *jwt, char **out, unsigned int *len,
unsigned char *raw_buf;

/* For EC we need to convert to a raw format of R/S. */
int degree = jwt_degree_for_key(pkey);
int degree = jwt_degree_for_key(pkey, jwt);
if (degree < 0)
SIGN_ERROR(-degree);

Expand Down Expand Up @@ -421,6 +450,7 @@ int jwt_verify_sha_pem(jwt_t *jwt, const char *head, unsigned int head_len, cons

/* ECC */
case JWT_ALG_ES256:
case JWT_ALG_ES256K:
alg = EVP_sha256();
type = EVP_PKEY_EC;
break;
Expand Down Expand Up @@ -466,7 +496,7 @@ int jwt_verify_sha_pem(jwt_t *jwt, const char *head, unsigned int head_len, cons
if (ec_sig == NULL)
VERIFY_ERROR(ENOMEM);

degree = jwt_degree_for_key(pkey);
degree = jwt_degree_for_key(pkey, jwt);
if (degree < 0)
VERIFY_ERROR(-degree);

Expand Down
2 changes: 2 additions & 0 deletions libjwt/jwt-wincrypt.c
Original file line number Diff line number Diff line change
Expand Up @@ -678,6 +678,7 @@ int jwt_sign_sha_pem(jwt_t *jwt, char **out, unsigned int *len,

/* ECC */
case JWT_ALG_ES256:
case JWT_ALG_ES256K:
alg = BCRYPT_SHA256_ALGORITHM;
isECDSA = 1;
break;
Expand Down Expand Up @@ -874,6 +875,7 @@ int jwt_verify_sha_pem(jwt_t *jwt, const char *head, const char *sig_b64)

/* ECC */
case JWT_ALG_ES256:
case JWT_ALG_ES256K:
alg = BCRYPT_SHA256_ALGORITHM;
isECDSA = 1;
break;
Expand Down
6 changes: 6 additions & 0 deletions libjwt/jwt.c
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,8 @@ const char *jwt_alg_str(jwt_alg_t alg)
return "RS512";
case JWT_ALG_ES256:
return "ES256";
case JWT_ALG_ES256K:
return "ES256K";
case JWT_ALG_ES384:
return "ES384";
case JWT_ALG_ES512:
Expand Down Expand Up @@ -163,6 +165,8 @@ jwt_alg_t jwt_str_alg(const char *alg)
return JWT_ALG_RS512;
else if (!strcmp(alg, "ES256"))
return JWT_ALG_ES256;
else if (!strcmp(alg, "ES256K"))
return JWT_ALG_ES256K;
else if (!strcmp(alg, "ES384"))
return JWT_ALG_ES384;
else if (!strcmp(alg, "ES512"))
Expand Down Expand Up @@ -488,6 +492,7 @@ static int jwt_sign(jwt_t *jwt, char **out, unsigned int *len, const char *str,

/* ECC */
case JWT_ALG_ES256:
case JWT_ALG_ES256K:
case JWT_ALG_ES384:
case JWT_ALG_ES512:
return jwt_sign_sha_pem(jwt, out, len, str, str_len);
Expand Down Expand Up @@ -522,6 +527,7 @@ static int jwt_verify(jwt_t *jwt, const char *head, unsigned int head_len, const

/* ECC */
case JWT_ALG_ES256:
case JWT_ALG_ES256K:
case JWT_ALG_ES384:
case JWT_ALG_ES512:
return jwt_verify_sha_pem(jwt, head, head_len, sig);
Expand Down
39 changes: 20 additions & 19 deletions tests/jwt_ec.c
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,11 @@ static size_t key_len;
/* NOTE: ES signing will generate a different signature every time, so can't
* be simply string compared for verification like we do with RS. */

static const char jwt_es256[] = "eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9.eyJpYXQ"
"iOjE0NzU5ODA1NDUsImlzcyI6ImZpbGVzLmN5cGhyZS5jb20iLCJyZWYiOiJYWFhYLVl"
"ZWVktWlpaWi1BQUFBLUNDQ0MiLCJzdWIiOiJ1c2VyMCJ9.3AA32Mn5dMuJXxe03mxJcT"
"fmif1eiv_doUCSVuMgny4DLKIZ3956SIGjeJpj3BSx2Lul7Zwy-PPuxyBwnL1jiWp7iw"
"PN9G9tV75ylfWvcwkF20bQA9m1vDbUIl8PIK8Q";
static const char jwt_es256[] = "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQ"
"iOjE0NzU5ODA1NDUsImlzcyI6ImZpbGVzLm1hY2xhcmEtbGxjLmNvbSIsInJlZiI6Ilh"
"YWFgtWVlZWS1aWlpaLUFBQUEtQ0NDQyIsInN1YiI6InVzZXIwIn0.IONoUPo6QhHwcx1"
"N1TD4DnrjvmB-9lSX6qrn_WPrh3DBum-qKP66MIF9tgymy7hCoU6dvUW8zKK0AyVH3iD"
"1uA";

static const char jwt_es384[] = "eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzM4NCJ9.eyJpYXQ"
"iOjE0NzU5ODA1NDUsImlzcyI6ImZpbGVzLmN5cGhyZS5jb20iLCJyZWYiOiJYWFhYLVl"
Expand All @@ -50,9 +50,10 @@ static const char jwt_es384[] = "eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzM4NCJ9.eyJpYXQ"

static const char jwt_es512[] = "eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzUxMiJ9.eyJpYXQ"
"iOjE0NzU5ODA1NDUsImlzcyI6ImZpbGVzLmN5cGhyZS5jb20iLCJyZWYiOiJYWFhYLVl"
"ZWVktWlpaWi1BQUFBLUNDQ0MiLCJzdWIiOiJ1c2VyMCJ9._i6CCfwqgk9IEFbKjNL8Ki"
"tPT9NEnXn2-qCSq0UgqkZ3sY-R0cnzD-WzpsEA8QWC882Y-SWwN7qVxK9e45pHUy4jye"
"YKXJj3agq9tZ61V3TM-BjcnMkERsV37nDQcfom";
"ZWVktWlpaWi1BQUFBLUNDQ0MiLCJzdWIiOiJ1c2VyMCJ9.Abs-SriTqd9NAO-bJb-B3U"
"zF1W8JmoutfHQpMqJnkPHyasVVuKN-I-6RibSv-qxgTxuzlo0u5dCt4mOw7w8mgEnMAS"
"zsjm-NlOPUBjIUD9T592lse9OOF6TjPOQbijqeMc6qFZ8q5YhxvxBXHO6PuImkJpEWj4"
"Zda8lNTxqHol7vorg9";

static const char jwt_es_invalid[] = "eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9.eyJpYXQ"
"iOjE0NzU5ODA1IAmCornholio6ImZpbGVzLmN5cGhyZS5jb20iLCJyZWYiOiJYWFhYLVl"
Expand Down Expand Up @@ -82,12 +83,12 @@ static void read_key(const char *key_file)
key[key_len] = '\0';
}

static void __verify_jwt(const char *jwt_str, const jwt_alg_t alg)
static void __verify_jwt(const char *jwt_str, const jwt_alg_t alg, const char *file)
{
jwt_t *jwt = NULL;
int ret = 0;

read_key("ec_key_secp384r1-pub.pem");
read_key(file);

ret = jwt_decode(&jwt, jwt_str, key, key_len);
ck_assert_int_eq(ret, 0);
Expand All @@ -98,15 +99,15 @@ static void __verify_jwt(const char *jwt_str, const jwt_alg_t alg)
jwt_free(jwt);
}

static void __test_alg_key(const jwt_alg_t alg)
static void __test_alg_key(const jwt_alg_t alg, const char *file, const char *pub)
{
jwt_t *jwt = NULL;
int ret = 0;
char *out;

ALLOC_JWT(&jwt);

read_key("ec_key_secp384r1.pem");
read_key(file);

ret = jwt_add_grant(jwt, "iss", "files.maclara-llc.com");
ck_assert_int_eq(ret, 0);
Expand All @@ -126,45 +127,45 @@ static void __test_alg_key(const jwt_alg_t alg)
out = jwt_encode_str(jwt);
ck_assert_ptr_ne(out, NULL);

__verify_jwt(out, alg);
__verify_jwt(out, alg, pub);

jwt_free_str(out);
jwt_free(jwt);
}

START_TEST(test_jwt_encode_es256)
{
__test_alg_key(JWT_ALG_ES256);
__test_alg_key(JWT_ALG_ES256, "ec_key_prime256v1.pem", "ec_key_prime256v1-pub.pem");
}
END_TEST

START_TEST(test_jwt_verify_es256)
{
__verify_jwt(jwt_es256, JWT_ALG_ES256);
__verify_jwt(jwt_es256, JWT_ALG_ES256, "ec_key_prime256v1-pub.pem");
}
END_TEST

START_TEST(test_jwt_encode_es384)
{
__test_alg_key(JWT_ALG_ES384);
__test_alg_key(JWT_ALG_ES384, "ec_key_secp384r1.pem", "ec_key_secp384r1-pub.pem");
}
END_TEST

START_TEST(test_jwt_verify_es384)
{
__verify_jwt(jwt_es384, JWT_ALG_ES384);
__verify_jwt(jwt_es384, JWT_ALG_ES384, "ec_key_secp384r1-pub.pem");
}
END_TEST

START_TEST(test_jwt_encode_es512)
{
__test_alg_key(JWT_ALG_ES512);
__test_alg_key(JWT_ALG_ES512, "ec_key_secp521r1.pem", "ec_key_secp521r1-pub.pem");
}
END_TEST

START_TEST(test_jwt_verify_es512)
{
__verify_jwt(jwt_es512, JWT_ALG_ES512);
__verify_jwt(jwt_es512, JWT_ALG_ES512, "ec_key_secp521r1-pub.pem");
}
END_TEST

Expand Down
Loading

0 comments on commit e12bba8

Please sign in to comment.