Skip to content

Commit

Permalink
JWK: Implement support for "oct" kty
Browse files Browse the repository at this point in the history
Signed-off-by: Ben Collins <[email protected]>
  • Loading branch information
benmcollins committed Jan 3, 2025
1 parent 7ea3034 commit 2da3e85
Show file tree
Hide file tree
Showing 9 changed files with 136 additions and 18 deletions.
12 changes: 10 additions & 2 deletions include/jwt.h
Original file line number Diff line number Diff line change
Expand Up @@ -114,12 +114,14 @@ typedef enum {
* Corresponds to the ``"kty"`` attribute of the JWK.
*
* @rfc{7517,4.1}
* @rfc(7518,6.1}
*/
typedef enum {
JWK_KEY_TYPE_NONE = 0, /**< Unused on valid keys */
JWK_KEY_TYPE_EC, /**< Eliptic Curve keys */
JWK_KEY_TYPE_RSA, /**< RSA keys (RSA and RSA-PSS) */
JWK_KEY_TYPE_OKP, /**< Octet Key Pair (e.g. EDDSA) */
JWK_KEY_TYPE_OCT, /**< Octet sequence (e.g. HS256) */
} jwk_key_type_t;

/** @ingroup jwks_core_grp
Expand Down Expand Up @@ -175,12 +177,18 @@ typedef enum {
* a nil terminated string of the key. The underlying crypto algorith may
* or may not support this. It's provided as a convenience.
*
* @raisewarning Decide if we need to make this an opaque object
* @raisewarning Decide if we need to make this an opaque object. Also, about that JSON...
*/
typedef struct {
char *pem; /**< If not NULL, contains PEM string of this key */
jwt_crypto_provider_t provider; /**< Crypto provider that owns this key */
void *provider_data; /**< Internal data used by the provider */
union {
void *provider_data; /**< Internal data used by the provider */
struct {
void *key;
size_t len;
} oct;
};
int is_private_key; /**< Whether this is a public or private key */
char curve[256]; /**< Curve name of an ``"EC"`` or ``"OKP"`` key */
size_t bits; /**< The number of bits in the key (may be 0) */
Expand Down
14 changes: 10 additions & 4 deletions libjwt/gnutls/sign-verify.c
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,16 @@ static int gnutls_sign_sha_hmac(jwt_t *jwt, char **out, unsigned int *len,
const char *str, unsigned int str_len)
{
int alg;
void *key;
size_t key_len;

if (jwt->config.jw_key)
return EINVAL;
if (jwt->config.jw_key) {
key = jwt->config.jw_key->oct.key;
key_len = jwt->config.jw_key->oct.len;
} else {
key = jwt->config.key;
key_len = jwt->config.key_len;
}

switch (jwt->alg) {
case JWT_ALG_HS256:
Expand All @@ -49,8 +56,7 @@ static int gnutls_sign_sha_hmac(jwt_t *jwt, char **out, unsigned int *len,
if (*out == NULL)
return ENOMEM;

if (gnutls_hmac_fast(alg, jwt->config.key, jwt->config.key_len,
str, str_len, *out)) {
if (gnutls_hmac_fast(alg, key, key_len, str, str_len, *out)) {
jwt_freemem(*out);
*out = NULL;
return EINVAL;
Expand Down
42 changes: 40 additions & 2 deletions libjwt/jwks.c
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,39 @@ static void jwk_process_values(json_t *jwk, jwk_item_t *item)
}
}

static int process_octet(json_t *jwk, jwk_item_t *item)
{
unsigned char *bin_k = NULL;
const char *str_k;
json_t *k;
int len_k = 0;

k = json_object_get(jwk, "k");
if (k == NULL || !json_is_string(k)) {
jwks_write_error(item, "Invalid JWK: missing `k`");
return -1;
}

str_k = json_string_value(k);
if (str_k == NULL || !strlen(str_k)) {
jwks_write_error(item, "Invalid JWK: invalid `k`");
return -1;
}

bin_k = jwt_base64uri_decode(str_k, &len_k);
if (bin_k == NULL) {
jwks_write_error(item, "Invalid JWK: failed to decode `k`");
return -1;
}

item->is_private_key = 1;
item->provider = JWT_CRYPTO_OPS_ANY;
item->oct.key = bin_k;
item->oct.len = len_k;

return 0;
}

static jwk_item_t *jwk_process_one(jwk_set_t *jwk_set, json_t *jwk)
{
const char *kty;
Expand Down Expand Up @@ -138,6 +171,9 @@ static jwk_item_t *jwk_process_one(jwk_set_t *jwk_set, json_t *jwk)
} else if (!jwt_strcmp(kty, "OKP")) {
item->kty = JWK_KEY_TYPE_OKP;
jwt_ops->process_eddsa(jwk, item);
} else if (!jwt_strcmp(kty, "oct")) {
item->kty = JWK_KEY_TYPE_OCT;
process_octet(jwk, item);
} else {
jwks_write_error(item, "Unknown or unsupported kty type '%s'", kty);
return item;
Expand Down Expand Up @@ -209,8 +245,10 @@ int jwks_item_free(jwk_set_t *jwk_set, size_t index)

item = todel->item;

/* Let the crypto ops clean their stuff up. */
jwt_ops->process_item_free(item);
if (item->provider == JWT_CRYPTO_OPS_ANY)
jwt_freemem(item->oct.key);
else
jwt_ops->process_item_free(item);

/* A few non-crypto specific things. */
jwt_freemem(item->kid);
Expand Down
16 changes: 11 additions & 5 deletions libjwt/openssl/sign-verify.c
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,16 @@ static int openssl_sign_sha_hmac(jwt_t *jwt, char **out, unsigned int *len,
const char *str, unsigned int str_len)
{
const EVP_MD *alg;
void *key;
size_t key_len;

if (jwt->config.jw_key)
return EINVAL;
if (jwt->config.jw_key) {
key = jwt->config.jw_key->oct.key;
key_len = jwt->config.jw_key->oct.len;
} else {
key = jwt->config.key;
key_len = jwt->config.key_len;
}

*out = NULL;

Expand All @@ -56,9 +63,8 @@ static int openssl_sign_sha_hmac(jwt_t *jwt, char **out, unsigned int *len,
if (*out == NULL)
return ENOMEM; // LCOV_EXCL_LINE

if (HMAC(alg, jwt->config.key, jwt->config.key_len,
(const unsigned char *)str, str_len, (unsigned char *)*out,
len) == NULL) {
if (HMAC(alg, key, key_len, (const unsigned char *)str, str_len,
(unsigned char *)*out, len) == NULL) {
jwt_freemem(*out);
*out = NULL;
return EINVAL;
Expand Down
28 changes: 23 additions & 5 deletions tests/jwt_jwks.c
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,12 @@ static void __jwks_check(const char *json, const char *pem)
ck_assert(!item->error);

/* Load our PEM to compare */
read_key(pem);
ret = strcmp(item->pem, t_config.key);
free_key();
ck_assert_int_eq(ret, 0);
if (item->kty != JWK_KEY_TYPE_OCT) {
read_key(pem);
ret = strcmp(item->pem, t_config.key);
free_key();
ck_assert_int_eq(ret, 0);
}

/* Should only be one key in the set */
item = jwks_item_get(jwk_set, 1);
Expand Down Expand Up @@ -99,18 +101,26 @@ JWKS_KEY_TEST(ec_key_secp384r1);
JWKS_KEY_TEST(ec_key_secp384r1_pub);
JWKS_KEY_TEST(ec_key_secp521r1);
JWKS_KEY_TEST(ec_key_secp521r1_pub);

JWKS_KEY_TEST(eddsa_key_ed25519);
JWKS_KEY_TEST(eddsa_key_ed25519_pub);

JWKS_KEY_TEST(rsa_key_2048);
JWKS_KEY_TEST(rsa_key_2048_pub);
JWKS_KEY_TEST(rsa_key_4096);
JWKS_KEY_TEST(rsa_key_4096_pub);
JWKS_KEY_TEST(rsa_key_8192);
JWKS_KEY_TEST(rsa_key_8192_pub);

JWKS_KEY_TEST(rsa_key_i37_pub);

JWKS_KEY_TEST(rsa_pss_key_2048);
JWKS_KEY_TEST(rsa_pss_key_2048_pub);

JWKS_KEY_TEST(oct_key_256);
JWKS_KEY_TEST(oct_key_384);
JWKS_KEY_TEST(oct_key_512);

START_TEST(test_jwks_keyring_load)
{
jwk_set_t *jwk_set = NULL;
Expand All @@ -129,7 +139,7 @@ START_TEST(test_jwks_keyring_load)
for (i = 0; (item = jwks_item_get(jwk_set, i)); i++)
ck_assert(!item->error);

ck_assert_int_eq(i, 19);
ck_assert_int_eq(i, 22);

ck_assert(jwks_item_free(jwk_set, 3));

Expand Down Expand Up @@ -221,18 +231,26 @@ static Suite *libjwt_suite(const char *title)
tcase_add_loop_test(tc_core, test_jwks_ec_key_secp384r1_pub, 0, i);
tcase_add_loop_test(tc_core, test_jwks_ec_key_secp521r1, 0, i);
tcase_add_loop_test(tc_core, test_jwks_ec_key_secp521r1_pub, 0, i);

tcase_add_loop_test(tc_core, test_jwks_eddsa_key_ed25519, 0, i);
tcase_add_loop_test(tc_core, test_jwks_eddsa_key_ed25519_pub, 0, i);

tcase_add_loop_test(tc_core, test_jwks_rsa_key_2048, 0, i);
tcase_add_loop_test(tc_core, test_jwks_rsa_key_2048_pub, 0, i);
tcase_add_loop_test(tc_core, test_jwks_rsa_key_4096, 0, i);
tcase_add_loop_test(tc_core, test_jwks_rsa_key_4096_pub, 0, i);
tcase_add_loop_test(tc_core, test_jwks_rsa_key_8192, 0, i);
tcase_add_loop_test(tc_core, test_jwks_rsa_key_8192_pub, 0, i);

tcase_add_loop_test(tc_core, test_jwks_rsa_key_i37_pub, 0, i);

tcase_add_loop_test(tc_core, test_jwks_rsa_pss_key_2048, 0, i);
tcase_add_loop_test(tc_core, test_jwks_rsa_pss_key_2048_pub, 0, i);

tcase_add_loop_test(tc_core, test_jwks_oct_key_256, 0, i);
tcase_add_loop_test(tc_core, test_jwks_oct_key_384, 0, i);
tcase_add_loop_test(tc_core, test_jwks_oct_key_512, 0, i);

/* Load a whole keyring of all of the above. */
tcase_add_loop_test(tc_core, test_jwks_keyring_load, 0, i);

Expand Down
15 changes: 15 additions & 0 deletions tests/keys/jwks_keyring.json
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,21 @@
"kty": "RSA",
"n": "n_7RTQX1xlllgo-aqo5zd_-wqaRZwFWp3JJIUXryJ4d153J1gEYdz4RkjFO0X8kpv8qb5hdzWhhZHSItD-07LaQXP4lSUuiK1lJAT_YW51D37nCkWm839gAqEGdsWsYQCvqJKSJr4pWCZTEx2MEfikmmnaXPR_VOVgZSj9kIoKo-kFwlw9LGVBHYAeR_W-l5DJQK6Yha8Igi36hnvZqqHcQ4gkGUqEc__Tq2nyYNwvAN3ieZvtvL71rFW26FnuA4OwDER-TYSkAr8Z4wH2EdGno1GZQAegIm-yWblleCaBVOnlk8VcDO9PecTiAFjjWAbQiaFKlkf_plD7KE4tAamQ",
"e": "AQAB"
},
{
"kty": "oct",
"alg": "HS512",
"k": "5VQGqfFoKucG-LLYFmW500OiaKIJtedo_RHjOvt4d1qJoTVi_4k95CKc_iF8xNR3NSbyndUvCPSp"
},
{
"kty": "oct",
"alg": "HS384",
"k": "Ot6zIlCpH4jwa8pYWYw2wXfwruuAlEE11C1jPCt5dlXBY6iWq-0isRy9MFK2L4Uj"
},
{
"kty": "oct",
"alg": "HS256",
"k": "0gmNspkRljssLSrldySnYUS-zhtCo5sqeqo_yl7n2XA"
}
]
}
9 changes: 9 additions & 0 deletions tests/keys/oct_key_256.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"keys": [
{
"kty": "oct",
"alg": "HS256",
"k": "0gmNspkRljssLSrldySnYUS-zhtCo5sqeqo_yl7n2XA"
}
]
}
9 changes: 9 additions & 0 deletions tests/keys/oct_key_384.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"keys": [
{
"kty": "oct",
"alg": "HS384",
"k": "Ot6zIlCpH4jwa8pYWYw2wXfwruuAlEE11C1jPCt5dlXBY6iWq-0isRy9MFK2L4Uj"
}
]
}
9 changes: 9 additions & 0 deletions tests/keys/oct_key_512.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"keys": [
{
"kty": "oct",
"alg": "HS512",
"k": "5VQGqfFoKucG-LLYFmW500OiaKIJtedo_RHjOvt4d1qJoTVi_4k95CKc_iF8xNR3NSbyndUvCPSp"
}
]
}

0 comments on commit 2da3e85

Please sign in to comment.