From 89016483a2e054ef326ec0b82d9f0f8f498e28ea Mon Sep 17 00:00:00 2001 From: Hans Zandbelt Date: Sat, 16 Jul 2016 14:10:19 +0200 Subject: [PATCH] support for symmetric key JWA HMAC algorithms HS256, HS384 and HS512 --- include/cjose/header.h | 5 ++ src/header.c | 3 + src/jws.c | 174 +++++++++++++++++++++++++++++++++++++++++ test/check_jws.c | 26 +++++- 4 files changed, 207 insertions(+), 1 deletion(-) diff --git a/include/cjose/header.h b/include/cjose/header.h index 56ea405..5b736e3 100644 --- a/include/cjose/header.h +++ b/include/cjose/header.h @@ -46,6 +46,11 @@ extern const char *CJOSE_HDR_ALG_PS256; /** The JWE algorithm attribute value for RS256. */ extern const char *CJOSE_HDR_ALG_RS256; +/** The JWE algorithm attribute values for HS256, HS384 and HS512. */ +extern const char *CJOSE_HDR_ALG_HS256; +extern const char *CJOSE_HDR_ALG_HS384; +extern const char *CJOSE_HDR_ALG_HS512; + /** The JWE algorithm attribute value for "dir". */ extern const char *CJOSE_HDR_ALG_DIR; diff --git a/src/header.c b/src/header.c index 35c99f7..2b69b80 100644 --- a/src/header.c +++ b/src/header.c @@ -17,6 +17,9 @@ const char *CJOSE_HDR_ALG_RSA_OAEP = "RSA-OAEP"; const char *CJOSE_HDR_ALG_DIR = "dir"; const char *CJOSE_HDR_ALG_PS256 = "PS256"; const char *CJOSE_HDR_ALG_RS256 = "RS256"; +const char *CJOSE_HDR_ALG_HS256 = "HS256"; +const char *CJOSE_HDR_ALG_HS384 = "HS384"; +const char *CJOSE_HDR_ALG_HS512 = "HS512"; const char *CJOSE_HDR_ENC = "enc"; const char *CJOSE_HDR_ENC_A256GCM = "A256GCM"; diff --git a/src/jws.c b/src/jws.c index d64e78f..678f1dc 100644 --- a/src/jws.c +++ b/src/jws.c @@ -17,6 +17,7 @@ #include #include #include +#include #include "include/jwk_int.h" #include "include/header_int.h" @@ -34,6 +35,11 @@ static bool _cjose_jws_build_sig_ps256( const cjose_jwk_t *jwk, cjose_err *err); +static bool _cjose_jws_build_dig_hmac_sha( + cjose_jws_t *jws, + const cjose_jwk_t *jwk, + cjose_err *err); + static bool _cjose_jws_verify_sig_ps256( cjose_jws_t *jws, const cjose_jwk_t *jwk, @@ -49,6 +55,15 @@ static bool _cjose_jws_verify_sig_rs256( const cjose_jwk_t *jwk, cjose_err *err); +static bool _cjose_jws_build_sig_hmac_sha( + cjose_jws_t *jws, + const cjose_jwk_t *jwk, + cjose_err *err); + +static bool _cjose_jws_verify_sig_hmac_sha( + cjose_jws_t *jws, + const cjose_jwk_t *jwk, + cjose_err *err); //////////////////////////////////////////////////////////////////////////////// static bool _cjose_jws_build_hdr( @@ -105,6 +120,12 @@ static bool _cjose_jws_validate_hdr( jws->fns.sign = _cjose_jws_build_sig_rs256; jws->fns.verify = _cjose_jws_verify_sig_rs256; } + else if ( (strcmp(alg, CJOSE_HDR_ALG_HS256) == 0) || (strcmp(alg, CJOSE_HDR_ALG_HS384) == 0) || (strcmp(alg, CJOSE_HDR_ALG_HS512) == 0)) + { + jws->fns.digest = _cjose_jws_build_dig_hmac_sha; + jws->fns.sign = _cjose_jws_build_sig_hmac_sha; + jws->fns.verify = _cjose_jws_verify_sig_hmac_sha; + } else { CJOSE_ERROR(err, CJOSE_ERR_INVALID_ARG); @@ -217,6 +238,96 @@ static bool _cjose_jws_build_dig_sha256( return retval; } +//////////////////////////////////////////////////////////////////////////////// +static bool _cjose_jws_build_dig_hmac_sha( + cjose_jws_t *jws, + const cjose_jwk_t *jwk, + cjose_err *err) +{ + bool retval = false; + HMAC_CTX *ctx = NULL; + + // make sure we have an alg header + json_t *alg_obj = json_object_get(jws->hdr, CJOSE_HDR_ALG); + if (NULL == alg_obj) + { + CJOSE_ERROR(err, CJOSE_ERR_INVALID_ARG); + return false; + } + const char *alg = json_string_value(alg_obj); + + // build digest using SHA-??? digest algorithm + const EVP_MD *digest_alg = NULL; + if (strcmp(alg, CJOSE_HDR_ALG_HS256) == 0) + digest_alg = EVP_sha256(); + else if (strcmp(alg, CJOSE_HDR_ALG_HS384) == 0) + digest_alg = EVP_sha384(); + else if (strcmp(alg, CJOSE_HDR_ALG_HS512) == 0) + digest_alg = EVP_sha512(); + + if (NULL == digest_alg) + { + CJOSE_ERROR(err, CJOSE_ERR_CRYPTO); + goto _cjose_jws_build_dig_hmac_sha_cleanup; + } + + // allocate buffer for digest + jws->dig_len = digest_alg->md_size; + jws->dig = (uint8_t *)cjose_get_alloc()(jws->dig_len); + if (NULL == jws->dig) + { + CJOSE_ERROR(err, CJOSE_ERR_NO_MEMORY); + goto _cjose_jws_build_dig_hmac_sha_cleanup; + } + + // instantiate and initialize a new mac digest context + ctx = cjose_get_alloc()(sizeof(HMAC_CTX)); + if (NULL == ctx) + { + CJOSE_ERROR(err, CJOSE_ERR_CRYPTO); + goto _cjose_jws_build_dig_hmac_sha_cleanup; + } + HMAC_CTX_init(ctx); + + // create digest as DIGEST(B64U(HEADER).B64U(DATA)) + if (HMAC_Init_ex(ctx, jwk->keydata, jwk->keysize / 8, digest_alg, NULL) != 1) + { + CJOSE_ERROR(err, CJOSE_ERR_CRYPTO); + goto _cjose_jws_build_dig_hmac_sha_cleanup; + } + if (HMAC_Update(ctx, (const unsigned char *)jws->hdr_b64u, jws->hdr_b64u_len) != 1) + { + CJOSE_ERROR(err, CJOSE_ERR_CRYPTO); + goto _cjose_jws_build_dig_hmac_sha_cleanup; + } + if (HMAC_Update(ctx, (const unsigned char *)".", 1) != 1) + { + CJOSE_ERROR(err, CJOSE_ERR_CRYPTO); + goto _cjose_jws_build_dig_hmac_sha_cleanup; + } + if (HMAC_Update(ctx, (const unsigned char *)jws->dat_b64u, jws->dat_b64u_len) != 1) + { + CJOSE_ERROR(err, CJOSE_ERR_CRYPTO); + goto _cjose_jws_build_dig_hmac_sha_cleanup; + } + if (HMAC_Final(ctx, jws->dig, NULL) != 1) + { + CJOSE_ERROR(err, CJOSE_ERR_CRYPTO); + goto _cjose_jws_build_dig_hmac_sha_cleanup; + } + + // if we got this far - success + retval = true; + + _cjose_jws_build_dig_hmac_sha_cleanup: + if (NULL != ctx) + { + HMAC_CTX_cleanup(ctx); + cjose_get_dealloc()(ctx); + } + + return retval; +} //////////////////////////////////////////////////////////////////////////////// static bool _cjose_jws_build_sig_ps256( @@ -333,6 +444,39 @@ static bool _cjose_jws_build_sig_rs256( return true; } +static bool _cjose_jws_build_sig_hmac_sha( + cjose_jws_t *jws, + const cjose_jwk_t *jwk, + cjose_err *err) +{ + // ensure jwk is OCT + if (jwk->kty != CJOSE_JWK_KTY_OCT) + { + CJOSE_ERROR(err, CJOSE_ERR_INVALID_ARG); + return false; + } + + // allocate buffer for signature + jws->sig_len = jws->dig_len; + jws->sig = (uint8_t *)cjose_get_alloc()(jws->sig_len); + if (NULL == jws->sig) + { + CJOSE_ERROR(err, CJOSE_ERR_NO_MEMORY); + return false; + } + + memcpy(jws->sig, jws->dig, jws->sig_len); + + // base64url encode signed digest + if (!cjose_base64url_encode((const uint8_t *)jws->sig, jws->sig_len, + &jws->sig_b64u, &jws->sig_b64u_len, err)) + { + CJOSE_ERROR(err, CJOSE_ERR_CRYPTO); + return false; + } + + return true; +} //////////////////////////////////////////////////////////////////////////////// static bool _cjose_jws_build_cser( @@ -728,6 +872,36 @@ static bool _cjose_jws_verify_sig_rs256( return retval; } +//////////////////////////////////////////////////////////////////////////////// +static bool _cjose_jws_verify_sig_hmac_sha( + cjose_jws_t *jws, + const cjose_jwk_t *jwk, + cjose_err *err) +{ + bool retval = false; + + // ensure jwk is OCT + if (jwk->kty != CJOSE_JWK_KTY_OCT) + { + CJOSE_ERROR(err, CJOSE_ERR_INVALID_ARG); + goto _cjose_jws_verify_sig_hmac_sha_cleanup; + } + + // verify decrypted digest matches computed digest + if ((_const_memcmp(jws->dig, jws->sig, jws->dig_len) != 0) || + (jws->sig_len != jws->dig_len)) + { + CJOSE_ERROR(err, CJOSE_ERR_CRYPTO); + goto _cjose_jws_verify_sig_hmac_sha_cleanup; + } + + // if we got this far - success + retval = true; + + _cjose_jws_verify_sig_hmac_sha_cleanup: + + return retval; +} //////////////////////////////////////////////////////////////////////////////// bool cjose_jws_verify( diff --git a/test/check_jws.c b/test/check_jws.c index 3fe6241..8acf35c 100644 --- a/test/check_jws.c +++ b/test/check_jws.c @@ -27,6 +27,10 @@ static const char *JWK_COMMON = "\"dq\": \"ZWB_5qJENrKO39aBW-Jf-_twihUPVi50oarRWml_iP40pVP01HDTqyiMut2tf6pUQGdF-nqulG2Mopei6Ell5wItf7s_bmnHPYysBuMrtov5PuknfVD7UqeEp25nZuZzF4aflyhovV29B-bM-_8CS0OIGb6TeTC5T5SflY17UNE\", " "\"qi\": \"RowmdelfiEBdqfBCSb3yblUKhwJsbyg6HtcugIVOC1yDxD5sZ0cjJPnXj7TJkrC0tICQ50MlPY5F650D9pvACIYnvrGEwsq757Lxg5nqshvuSC-7i1TMkv7_uPBmIxRfzqsnh_hVhxLgSUW1NI6_ncwk9vDQqpkY6qBirgvbyO0\" }"; +static const char *JWK_COMMON_OCT = + "{ \"kty\": \"oct\", " + "\"k\": \"AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow\" }"; + // a JWS encrypted with the above JWK_COMMON key static const char *JWS_COMMON = "eyAiYWxnIjogIlBTMjU2IiB9.SWYgeW91IHJldmVhbCB5b3VyIHNlY3JldHMgdG8gdGhlIHdpbmQsIHlvdSBzaG91bGQgbm90IGJsYW1lIHRoZSB3aW5kIGZvciByZXZlYWxpbmcgdGhlbSB0byB0aGUgdHJlZXMuIOKAlCBLYWhsaWwgR2licmFu.0YJo4r9gbI2nZ2_1_KLTY3i5SRcZvahRuToavqBvLbm87pN7IYx8YV9kwKQclMW2ASpbEAzKNIJfQ3FycobRwZGtqCI9sRUo0vQvkpb3HIS6HKp3Kvur57J7LcZhz7uNIxzUYNQSg4EWpwhF9FnGng7bmU8qjNPiXCWfQ-n74gopAVzd3KDJ5ai7q66voRc9pCKJVbsaIMHIqcl9OPiMdY5Hz3_PgBalR2632HOdpUlIMvnMOL3EQICvyBwxaYPbhMcCpEc3_4K-sywOGiCSp9KlaLcRq0knZtAT0ynJszaiOwfR-W18PEFLfGclpeR6e_gop9mq69t36wK7KRUjrQ"; @@ -37,10 +41,18 @@ static const char *PLAIN_COMMON = "wind for revealing them to the trees. — Kahlil Gibran"; +static const char *_self_get_jwt_by_alg(const char *alg) { + if ((strcmp(alg, CJOSE_HDR_ALG_HS256) == 0) || (strcmp(alg, CJOSE_HDR_ALG_HS384) == 0) || (strcmp(alg, CJOSE_HDR_ALG_HS512) == 0)) + return JWK_COMMON_OCT; + return JWK_COMMON; +} + static void _self_sign_self_verify( const char *plain1, const char *alg, cjose_err *err) { - cjose_jwk_t *jwk = cjose_jwk_import(JWK_COMMON, strlen(JWK_COMMON), err); + const char *s_jwk = _self_get_jwt_by_alg(alg); + cjose_jwk_t *jwk = cjose_jwk_import(s_jwk, strlen(s_jwk), err); + ck_assert_msg(NULL != jwk, "cjose_jwk_import failed: " "%s, file: %s, function: %s, line: %ld", err->message, err->file, err->function, err->line); @@ -113,6 +125,9 @@ START_TEST(test_cjose_jws_self_sign_self_verify) cjose_err err; _self_sign_self_verify(PLAIN_COMMON, CJOSE_HDR_ALG_PS256, &err); _self_sign_self_verify(PLAIN_COMMON, CJOSE_HDR_ALG_RS256, &err); + _self_sign_self_verify(PLAIN_COMMON, CJOSE_HDR_ALG_HS256, &err); + _self_sign_self_verify(PLAIN_COMMON, CJOSE_HDR_ALG_HS384, &err); + _self_sign_self_verify(PLAIN_COMMON, CJOSE_HDR_ALG_HS512, &err); } END_TEST @@ -122,6 +137,9 @@ START_TEST(test_cjose_jws_self_sign_self_verify_short) cjose_err err; _self_sign_self_verify("Setec Astronomy", CJOSE_HDR_ALG_PS256, &err); _self_sign_self_verify("Setec Astronomy", CJOSE_HDR_ALG_RS256, &err); + _self_sign_self_verify("Setec Astronomy", CJOSE_HDR_ALG_HS256, &err); + _self_sign_self_verify("Setec Astronomy", CJOSE_HDR_ALG_HS384, &err); + _self_sign_self_verify("Setec Astronomy", CJOSE_HDR_ALG_HS512, &err); } END_TEST @@ -131,6 +149,9 @@ START_TEST(test_cjose_jws_self_sign_self_verify_empty) cjose_err err; _self_sign_self_verify("", CJOSE_HDR_ALG_PS256, &err); _self_sign_self_verify("", CJOSE_HDR_ALG_RS256, &err); + _self_sign_self_verify("", CJOSE_HDR_ALG_HS256, &err); + _self_sign_self_verify("", CJOSE_HDR_ALG_HS384, &err); + _self_sign_self_verify("", CJOSE_HDR_ALG_HS512, &err); } END_TEST @@ -148,6 +169,9 @@ START_TEST(test_cjose_jws_self_sign_self_verify_many) plain[len-1] = 0; _self_sign_self_verify(plain, CJOSE_HDR_ALG_PS256, &err); _self_sign_self_verify(plain, CJOSE_HDR_ALG_RS256, &err); + _self_sign_self_verify(plain, CJOSE_HDR_ALG_HS256, &err); + _self_sign_self_verify(plain, CJOSE_HDR_ALG_HS384, &err); + _self_sign_self_verify(plain, CJOSE_HDR_ALG_HS512, &err); free(plain); } }