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 _oct_private_fields( const cjose_jwk_t *jwk, json_t *json, cjose_err *err) { json_t *field = NULL; char *k = NULL; size_t klen = 0; uint8_t *keydata = (uint8_t *)jwk->keydata; size_t keysize = jwk->keysize / 8; if (!cjose_base64url_encode(keydata, keysize, &k, &klen, err)) { return false; } field = json_stringn(k, klen); cjose_get_dealloc()(k); k = NULL; if (!field) { return false; } json_object_set(json, "k", field); json_decref(field); return true; }
/* * perform compact serialization on a JWT and return the resulting string */ char *oidc_jwt_serialize(apr_pool_t *pool, oidc_jwt_t *jwt, oidc_jose_error_t *err) { cjose_err cjose_err; const char *cser = NULL; if (strcmp(jwt->header.alg, "none") != 0) { if (cjose_jws_export(jwt->cjose_jws, &cser, &cjose_err) == FALSE) { oidc_jose_error(err, "cjose_jws_export failed: %s", oidc_cjose_e2s(pool, cjose_err)); return NULL; } } else { char *s_payload = json_dumps(jwt->payload.value.json, JSON_PRESERVE_ORDER | JSON_COMPACT); char *out = NULL; size_t out_len; if (cjose_base64url_encode((const uint8_t *)s_payload, strlen(s_payload), &out, &out_len, &cjose_err) == FALSE) return FALSE; cser = apr_pstrndup(pool, out, out_len); cjose_get_dealloc()(out); free(s_payload); cser = apr_psprintf(pool, "eyJhbGciOiJub25lIn0.%s.", cser); } return apr_pstrdup(pool, cser); }
static bool _EC_private_fields( const cjose_jwk_t *jwk, json_t *json, cjose_err *err) { ec_keydata *keydata = (ec_keydata *)jwk->keydata; const BIGNUM *bnD = EC_KEY_get0_private_key(keydata->key); uint8_t *buffer = NULL; char *b64u = NULL; size_t len = 0, offset = 0; json_t *field = NULL; bool result = false; // track expected binary data size uint8_t numsize = _ec_size_for_curve(keydata->crv, err); // short circuit if 'd' is NULL or 0 if (!bnD || BN_is_zero(bnD)) { return true; } buffer = cjose_get_alloc()(numsize); if (!buffer) { CJOSE_ERROR(err, CJOSE_ERR_NO_MEMORY); goto _ec_to_string_cleanup; } offset = numsize - BN_num_bytes(bnD); memset(buffer, 0, numsize); BN_bn2bin(bnD, (buffer + offset)); if (!cjose_base64url_encode(buffer, numsize, &b64u, &len, err)) { goto _ec_to_string_cleanup; } field = json_stringn(b64u, len); if (!field) { CJOSE_ERROR(err, CJOSE_ERR_NO_MEMORY); goto _ec_to_string_cleanup; } json_object_set(json, "d", field); json_decref(field); field = NULL; cjose_get_dealloc()(b64u); b64u = NULL; result = true; _ec_to_string_cleanup: if (buffer) { cjose_get_dealloc()(buffer); } return result; }
static inline bool _RSA_json_field( BIGNUM *param, const char *name, json_t *json, cjose_err *err) { json_t *field = NULL; uint8_t *data = NULL; char *b64u = NULL; size_t datalen = 0, b64ulen = 0; bool result = false; if (!param) { return true; } datalen = BN_num_bytes(param); data = cjose_get_alloc()(sizeof(uint8_t) * datalen); if (!data) { CJOSE_ERROR(err, CJOSE_ERR_NO_MEMORY); goto RSA_json_field_cleanup; } BN_bn2bin(param, data); if (!cjose_base64url_encode(data, datalen, &b64u, &b64ulen, err)) { goto RSA_json_field_cleanup; } field = json_stringn(b64u, b64ulen); if (!field) { CJOSE_ERROR(err, CJOSE_ERR_NO_MEMORY); goto RSA_json_field_cleanup; } json_object_set(json, name, field); json_decref(field); field = NULL; result = true; RSA_json_field_cleanup: if (b64u) { cjose_get_dealloc()(b64u); b64u = NULL; } if (data) { cjose_get_dealloc()(data); data = NULL; } return result; }
char *cjose_jwe_export( cjose_jwe_t *jwe, cjose_err *err) { char *cser = NULL; size_t cser_len = 0; if (NULL == jwe) { CJOSE_ERROR(err, CJOSE_ERR_INVALID_ARG); return NULL; } // make sure all parts are b64u encoded for (int i = 0; i < 5; ++i) { if ((NULL == jwe->part[i].b64u) && (!cjose_base64url_encode( (const uint8_t *)jwe->part[i].raw, jwe->part[i].raw_len, &jwe->part[i].b64u, &jwe->part[i].b64u_len, err))) { return NULL; } } // compute length of compact serialization cser_len = 0; for (int i = 0; i < 5; ++i) { cser_len += jwe->part[i].b64u_len + 1; } // allocate buffer for compact serialization if (!_cjose_jwe_malloc(cser_len, false, (uint8_t **)&cser, err)) { return NULL; } // build the compact serialization snprintf(cser, cser_len, "%s.%s.%s.%s.%s", jwe->part[0].b64u, jwe->part[1].b64u, jwe->part[2].b64u, jwe->part[3].b64u, jwe->part[4].b64u); return cser; }
static bool _cjose_jws_build_dat(cjose_jws_t *jws, const uint8_t *plaintext, size_t plaintext_len, cjose_err *err) { // copy plaintext data jws->dat_len = plaintext_len; jws->dat = (uint8_t *)cjose_get_alloc()(jws->dat_len); if (NULL == jws->dat) { CJOSE_ERROR(err, CJOSE_ERR_NO_MEMORY); return false; } memcpy(jws->dat, plaintext, jws->dat_len); // base64url encode data if (!cjose_base64url_encode((const uint8_t *)plaintext, plaintext_len, &jws->dat_b64u, &jws->dat_b64u_len, err)) { return false; } return true; }
/* * hash a sequence of bytes with a specific algorithm and return the result as a base64url-encoded \0 terminated string */ static apr_byte_t oidc_jose_hash_and_base64url_encode(apr_pool_t *pool, const char *openssl_hash_algo, const char *input, int input_len, char **output) { oidc_jose_error_t err; unsigned char *hashed = NULL; unsigned int hashed_len = 0; if (oidc_jose_hash_bytes(pool, openssl_hash_algo, (const unsigned char *) input, input_len, &hashed, &hashed_len, &err) == FALSE) { return FALSE; } char *out = NULL; size_t out_len; cjose_err cjose_err; if (cjose_base64url_encode(hashed, hashed_len, &out, &out_len, &cjose_err) == FALSE) return FALSE; *output = apr_pstrndup(pool, out, out_len); cjose_get_dealloc()(out); return TRUE; }
static bool _cjose_jws_build_hdr(cjose_jws_t *jws, cjose_header_t *header, cjose_err *err) { // save header object as part of the JWS (and incr. refcount) jws->hdr = (json_t *)header; json_incref(jws->hdr); // base64url encode the header char *hdr_str = json_dumps(jws->hdr, JSON_ENCODE_ANY | JSON_PRESERVE_ORDER); if (NULL == hdr_str) { CJOSE_ERROR(err, CJOSE_ERR_NO_MEMORY); return false; } if (!cjose_base64url_encode((const uint8_t *)hdr_str, strlen(hdr_str), &jws->hdr_b64u, &jws->hdr_b64u_len, err)) { free(hdr_str); return false; } free(hdr_str); return true; }
static bool _cjose_jwe_encrypt_dat_a256gcm( cjose_jwe_t *jwe, const uint8_t *plaintext, size_t plaintext_len, cjose_err *err) { EVP_CIPHER_CTX *ctx = NULL; if (NULL == plaintext) { CJOSE_ERROR(err, CJOSE_ERR_INVALID_ARG); goto _cjose_jwe_encrypt_dat_fail; } // get A256GCM cipher const EVP_CIPHER *cipher = EVP_aes_256_gcm(); if (NULL == cipher) { CJOSE_ERROR(err, CJOSE_ERR_CRYPTO); goto _cjose_jwe_encrypt_dat_fail; } // instantiate and initialize a new openssl cipher context ctx = EVP_CIPHER_CTX_new(); if (NULL == ctx) { CJOSE_ERROR(err, CJOSE_ERR_CRYPTO); goto _cjose_jwe_encrypt_dat_fail; } EVP_CIPHER_CTX_init(ctx); // initialize context for encryption using A256GCM cipher and CEK and IV if (EVP_EncryptInit_ex(ctx, cipher, NULL, jwe->cek, jwe->part[2].raw) != 1) { CJOSE_ERROR(err, CJOSE_ERR_CRYPTO); goto _cjose_jwe_encrypt_dat_fail; } // we need the header in base64url encoding as input for encryption if ((NULL == jwe->part[0].b64u) && (!cjose_base64url_encode( (const uint8_t *)jwe->part[0].raw, jwe->part[0].raw_len, &jwe->part[0].b64u, &jwe->part[0].b64u_len, err))) { goto _cjose_jwe_encrypt_dat_fail; } // set GCM mode AAD data (hdr_b64u) by setting "out" to NULL int bytes_encrypted = 0; if (EVP_EncryptUpdate(ctx, NULL, &bytes_encrypted, (unsigned char *)jwe->part[0].b64u, jwe->part[0].b64u_len) != 1 || bytes_encrypted != jwe->part[0].b64u_len) { CJOSE_ERROR(err, CJOSE_ERR_CRYPTO); goto _cjose_jwe_encrypt_dat_fail; } // allocate buffer for the ciphertext cjose_get_dealloc()(jwe->part[3].raw); jwe->part[3].raw_len = plaintext_len; if (!_cjose_jwe_malloc(jwe->part[3].raw_len, false, &jwe->part[3].raw, err)) { goto _cjose_jwe_encrypt_dat_fail; } // encrypt entire plaintext to ciphertext buffer if (EVP_EncryptUpdate(ctx, jwe->part[3].raw, &bytes_encrypted, plaintext, plaintext_len) != 1) { CJOSE_ERROR(err, CJOSE_ERR_CRYPTO); goto _cjose_jwe_encrypt_dat_fail; } jwe->part[3].raw_len = bytes_encrypted; // finalize the encryption and set the ciphertext length to correct value if (EVP_EncryptFinal_ex(ctx, NULL, &bytes_encrypted) != 1) { CJOSE_ERROR(err, CJOSE_ERR_CRYPTO); goto _cjose_jwe_encrypt_dat_fail; } // allocate buffer for the authentication tag cjose_get_dealloc()(jwe->part[4].raw); jwe->part[4].raw_len = 16; if (!_cjose_jwe_malloc(jwe->part[4].raw_len, false, &jwe->part[4].raw, err)) { goto _cjose_jwe_encrypt_dat_fail; } // get the GCM-mode authentication tag if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, jwe->part[4].raw_len, jwe->part[4].raw) != 1) { CJOSE_ERROR(err, CJOSE_ERR_CRYPTO); goto _cjose_jwe_encrypt_dat_fail; } EVP_CIPHER_CTX_free(ctx); return true; _cjose_jwe_encrypt_dat_fail: if (NULL != ctx) { EVP_CIPHER_CTX_free(ctx); } return false; }
static bool _EC_public_fields( const cjose_jwk_t *jwk, json_t *json, cjose_err *err) { ec_keydata *keydata = (ec_keydata *)jwk->keydata; const EC_GROUP *params = NULL; const EC_POINT *pub = NULL; BIGNUM *bnX = NULL, *bnY = NULL; uint8_t *buffer = NULL; char *b64u = NULL; size_t len = 0, offset = 0; json_t *field = NULL; bool result = false; // track expected binary data size uint8_t numsize = _ec_size_for_curve(keydata->crv, err); // output the curve field = json_string(_ec_name_for_curve(keydata->crv, err)); if (!field) { CJOSE_ERROR(err, CJOSE_ERR_NO_MEMORY); goto _ec_to_string_cleanup; } json_object_set(json, "crv", field); json_decref(field); field = NULL; // obtain the public key pub = EC_KEY_get0_public_key(keydata->key); params = EC_KEY_get0_group(keydata->key); if (!pub || !params) { CJOSE_ERROR(err, CJOSE_ERR_INVALID_ARG); goto _ec_to_string_cleanup; } buffer = cjose_get_alloc()(numsize); bnX = BN_new(); bnY = BN_new(); if (!buffer || !bnX || !bnY) { CJOSE_ERROR(err, CJOSE_ERR_NO_MEMORY); goto _ec_to_string_cleanup; } if (1 != EC_POINT_get_affine_coordinates_GFp(params, pub, bnX, bnY, NULL)) { CJOSE_ERROR(err, CJOSE_ERR_NO_MEMORY); goto _ec_to_string_cleanup; } // output the x coordinate offset = numsize - BN_num_bytes(bnX); memset(buffer, 0, numsize); BN_bn2bin(bnX, (buffer + offset)); if (!cjose_base64url_encode(buffer, numsize, &b64u, &len, err)) { goto _ec_to_string_cleanup; } field = json_stringn(b64u, len); if (!field) { CJOSE_ERROR(err, CJOSE_ERR_NO_MEMORY); goto _ec_to_string_cleanup; } json_object_set(json, "x", field); json_decref(field); field = NULL; cjose_get_dealloc()(b64u); b64u = NULL; // output the y coordinate offset = numsize - BN_num_bytes(bnY); memset(buffer, 0, numsize); BN_bn2bin(bnY, (buffer + offset)); if (!cjose_base64url_encode(buffer, numsize, &b64u, &len, err)) { goto _ec_to_string_cleanup; } field = json_stringn(b64u, len); if (!field) { CJOSE_ERROR(err, CJOSE_ERR_NO_MEMORY); goto _ec_to_string_cleanup; } json_object_set(json, "y", field); json_decref(field); field = NULL; cjose_get_dealloc()(b64u); b64u = NULL; result = true; _ec_to_string_cleanup: if (field) { json_decref(field); } if (bnX) { BN_free(bnX); } if (bnY) { BN_free(bnY); } if (buffer) { cjose_get_dealloc()(buffer); } if (b64u) { cjose_get_dealloc()(b64u); } return result; }
static bool _cjose_jws_build_sig_ec(cjose_jws_t *jws, const cjose_jwk_t *jwk, cjose_err *err) { bool retval = false; // ensure jwk is EC if (jwk->kty != CJOSE_JWK_KTY_EC) { CJOSE_ERROR(err, CJOSE_ERR_INVALID_ARG); return false; } ec_keydata *keydata = (ec_keydata *)jwk->keydata; EC_KEY *ec = keydata->key; ECDSA_SIG *ecdsa_sig = ECDSA_do_sign(jws->dig, jws->dig_len, ec); if (NULL == ecdsa_sig) { CJOSE_ERROR(err, CJOSE_ERR_CRYPTO); goto _cjose_jws_build_sig_ec_cleanup; } // allocate buffer for signature switch (keydata->crv) { case CJOSE_JWK_EC_P_256: jws->sig_len = 32 * 2; break; case CJOSE_JWK_EC_P_384: jws->sig_len = 48 * 2; break; case CJOSE_JWK_EC_P_521: jws->sig_len = 66 * 2; break; case CJOSE_JWK_EC_INVALID: jws->sig_len = 0; break; } jws->sig = (uint8_t *)cjose_get_alloc()(jws->sig_len); if (NULL == jws->sig) { CJOSE_ERROR(err, CJOSE_ERR_NO_MEMORY); goto _cjose_jws_build_sig_ec_cleanup; } memset(jws->sig, 0, jws->sig_len); const BIGNUM *pr, *ps; #if defined(CJOSE_OPENSSL_11X) ECDSA_SIG_get0(ecdsa_sig, &pr, &ps); #else pr = ecdsa_sig->r; ps = ecdsa_sig->s; #endif int rlen = BN_num_bytes(pr); int slen = BN_num_bytes(ps); BN_bn2bin(pr, jws->sig + jws->sig_len / 2 - rlen); BN_bn2bin(ps, jws->sig + jws->sig_len - slen); // 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); goto _cjose_jws_build_sig_ec_cleanup; } retval = true; _cjose_jws_build_sig_ec_cleanup: if (ecdsa_sig) ECDSA_SIG_free(ecdsa_sig); return retval; }
static bool _cjose_jws_build_sig_rs(cjose_jws_t *jws, const cjose_jwk_t *jwk, cjose_err *err) { // ensure jwk is private RSA if (jwk->kty != CJOSE_JWK_KTY_RSA) { CJOSE_ERROR(err, CJOSE_ERR_INVALID_ARG); return false; } RSA *rsa = (RSA *)jwk->keydata; BIGNUM *rsa_n = NULL, *rsa_e = NULL, *rsa_d = NULL; _cjose_jwk_rsa_get(rsa, &rsa_n, &rsa_e, &rsa_d); if (!rsa || !rsa_e || !rsa_n || !rsa_d) { CJOSE_ERROR(err, CJOSE_ERR_INVALID_ARG); return false; } // allocate buffer for signature jws->sig_len = RSA_size((RSA *)jwk->keydata); jws->sig = (uint8_t *)cjose_get_alloc()(jws->sig_len); if (NULL == jws->sig) { CJOSE_ERROR(err, CJOSE_ERR_NO_MEMORY); return false; } // 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-256/384/512 digest algorithm int digest_alg = -1; if (strcmp(alg, CJOSE_HDR_ALG_RS256) == 0) digest_alg = NID_sha256; else if (strcmp(alg, CJOSE_HDR_ALG_RS384) == 0) digest_alg = NID_sha384; else if (strcmp(alg, CJOSE_HDR_ALG_RS512) == 0) digest_alg = NID_sha512; if (-1 == digest_alg) { CJOSE_ERROR(err, CJOSE_ERR_CRYPTO); return false; } unsigned int siglen; if (RSA_sign(digest_alg, jws->dig, jws->dig_len, jws->sig, &siglen, (RSA *)jwk->keydata) != 1) { CJOSE_ERROR(err, CJOSE_ERR_CRYPTO); return false; } jws->sig_len = siglen; // 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_sig_ps(cjose_jws_t *jws, const cjose_jwk_t *jwk, cjose_err *err) { bool retval = false; uint8_t *em = NULL; size_t em_len = 0; // ensure jwk is private RSA if (jwk->kty != CJOSE_JWK_KTY_RSA) { CJOSE_ERROR(err, CJOSE_ERR_INVALID_ARG); goto _cjose_jws_build_sig_ps_cleanup; } RSA *rsa = (RSA *)jwk->keydata; BIGNUM *rsa_n = NULL, *rsa_e = NULL, *rsa_d = NULL; _cjose_jwk_rsa_get(rsa, &rsa_n, &rsa_e, &rsa_d); if (!rsa || !rsa_e || !rsa_n || !rsa_d) { CJOSE_ERROR(err, CJOSE_ERR_INVALID_ARG); return false; } // 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-256/384/512 digest algorithm const EVP_MD *digest_alg = NULL; if (strcmp(alg, CJOSE_HDR_ALG_PS256) == 0) digest_alg = EVP_sha256(); else if (strcmp(alg, CJOSE_HDR_ALG_PS384) == 0) digest_alg = EVP_sha384(); else if (strcmp(alg, CJOSE_HDR_ALG_PS512) == 0) digest_alg = EVP_sha512(); if (NULL == digest_alg) { CJOSE_ERROR(err, CJOSE_ERR_CRYPTO); goto _cjose_jws_build_sig_ps_cleanup; } // apply EMSA-PSS encoding (RFC-3447, 8.1.1, step 1) // (RSA_padding_add_PKCS1_PSS includes PKCS1_MGF1, -1 => saltlen = hashlen) em_len = RSA_size((RSA *)jwk->keydata); em = (uint8_t *)cjose_get_alloc()(em_len); if (NULL == em) { CJOSE_ERROR(err, CJOSE_ERR_NO_MEMORY); goto _cjose_jws_build_sig_ps_cleanup; } if (RSA_padding_add_PKCS1_PSS((RSA *)jwk->keydata, em, jws->dig, digest_alg, -1) != 1) { CJOSE_ERROR(err, CJOSE_ERR_CRYPTO); goto _cjose_jws_build_sig_ps_cleanup; } // sign the digest (RFC-3447, 8.1.1, step 2) jws->sig_len = em_len; jws->sig = (uint8_t *)cjose_get_alloc()(jws->sig_len); if (NULL == jws->sig) { CJOSE_ERROR(err, CJOSE_ERR_NO_MEMORY); goto _cjose_jws_build_sig_ps_cleanup; } if (RSA_private_encrypt(em_len, em, jws->sig, (RSA *)jwk->keydata, RSA_NO_PADDING) != jws->sig_len) { CJOSE_ERROR(err, CJOSE_ERR_CRYPTO); goto _cjose_jws_build_sig_ps_cleanup; } // base64url encode signed digest if (!cjose_base64url_encode((const uint8_t *)jws->sig, jws->sig_len, &jws->sig_b64u, &jws->sig_b64u_len, err)) { goto _cjose_jws_build_sig_ps_cleanup; } // if we got this far - success retval = true; _cjose_jws_build_sig_ps_cleanup: cjose_get_dealloc()(em); return retval; }