From 3c4949e1436473644836f0c4634b809c7526c43a Mon Sep 17 00:00:00 2001 From: Hans Zandbelt Date: Thu, 10 Jun 2021 15:32:48 +0200 Subject: [PATCH 1/3] use encrypted JWTs for storing encrypted cache contents - avoid using static AAD/IV; thanks @niebardzo - bump to 2.4.9-dev Signed-off-by: Hans Zandbelt --- ChangeLog | 4 + src/cache/common.c | 331 ++++---------------------------------- diff --git a/ChangeLog b/ChangeLog index 075f98d..b4ab0a1 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,7 @@ +06/10/2021 +- use encrypted JWTs for storing encrypted cache contents and avoid using static AAD/IV; thanks @niebardzo +- bump to 2.4.9-dev + 09/03/2020 - add SameSite attribute on cookie clearance / logout; thanks @v0gler - bump to 2.4.4.1 diff --git a/src/cache/common.c b/src/cache/common.c index c33876a..e665370 100644 --- a/src/cache/common.c +++ b/src/cache/common.c @@ -229,324 +229,59 @@ apr_byte_t oidc_cache_mutex_destroy(server_rec *s, oidc_cache_mutex_t *m) { return rv; } -#define oidc_cache_crypto_openssl_error(r, fmt, ...) \ - oidc_error(r, "%s: %s", apr_psprintf(r->pool, fmt, ##__VA_ARGS__), ERR_error_string(ERR_get_error(), NULL)) - -#define OIDC_CACHE_CIPHER EVP_aes_256_gcm() -#define OIDC_CACHE_TAG_LEN 16 - -#if (OPENSSL_VERSION_NUMBER >= 0x10100005L && !defined(LIBRESSL_VERSION_NUMBER)) -#define OIDC_CACHE_CRYPTO_GET_TAG EVP_CTRL_AEAD_GET_TAG -#define OIDC_CACHE_CRYPTO_SET_TAG EVP_CTRL_AEAD_SET_TAG -#define OIDC_CACHE_CRYPTO_SET_IVLEN EVP_CTRL_AEAD_SET_IVLEN -#else -#define OIDC_CACHE_CRYPTO_GET_TAG EVP_CTRL_GCM_GET_TAG -#define OIDC_CACHE_CRYPTO_SET_TAG EVP_CTRL_GCM_SET_TAG -#define OIDC_CACHE_CRYPTO_SET_IVLEN EVP_CTRL_GCM_SET_IVLEN -#endif +#define OIDC_CACHE_CRYPTO_JSON_KEY "c" /* - * AES GCM encrypt + * AES GCM encrypt using the crypto passphrase as symmetric key */ -static int oidc_cache_crypto_encrypt_impl(request_rec *r, - unsigned char *plaintext, int plaintext_len, const unsigned char *aad, - int aad_len, unsigned char *key, const unsigned char *iv, int iv_len, - unsigned char *ciphertext, const unsigned char *tag, int tag_len) { - EVP_CIPHER_CTX *ctx; - - int len; - - int ciphertext_len; - - /* create and initialize the context */ - if (!(ctx = EVP_CIPHER_CTX_new())) { - oidc_cache_crypto_openssl_error(r, "EVP_CIPHER_CTX_new"); - return -1; - } - - /* initialize the encryption cipher */ - if (!EVP_EncryptInit_ex(ctx, OIDC_CACHE_CIPHER, NULL, NULL, NULL)) { - oidc_cache_crypto_openssl_error(r, "EVP_EncryptInit_ex"); - return -1; - } - - /* set IV length */ - if (!EVP_CIPHER_CTX_ctrl(ctx, OIDC_CACHE_CRYPTO_SET_IVLEN, iv_len, NULL)) { - oidc_cache_crypto_openssl_error(r, "EVP_CIPHER_CTX_ctrl"); - return -1; - } - - /* initialize key and IV */ - if (!EVP_EncryptInit_ex(ctx, NULL, NULL, key, iv)) { - oidc_cache_crypto_openssl_error(r, "EVP_EncryptInit_ex"); - return -1; - } - - /* provide AAD data */ - if (!EVP_EncryptUpdate(ctx, NULL, &len, aad, aad_len)) { - oidc_cache_crypto_openssl_error(r, "EVP_DecryptUpdate aad: aad_len=%d", - aad_len); - return -1; - } - - /* provide the message to be encrypted and obtain the encrypted output */ - if (!EVP_EncryptUpdate(ctx, ciphertext, &len, plaintext, plaintext_len)) { - oidc_cache_crypto_openssl_error(r, "EVP_EncryptUpdate ciphertext"); - return -1; - } - ciphertext_len = len; - - /* - * finalize the encryption; normally ciphertext bytes may be written at - * this stage, but this does not occur in GCM mode - */ - if (!EVP_EncryptFinal_ex(ctx, ciphertext + len, &len)) { - oidc_cache_crypto_openssl_error(r, "EVP_EncryptFinal_ex"); - return -1; - } - ciphertext_len += len; - - /* get the tag */ - if (!EVP_CIPHER_CTX_ctrl(ctx, OIDC_CACHE_CRYPTO_GET_TAG, tag_len, - (void *) tag)) { - oidc_cache_crypto_openssl_error(r, "EVP_CIPHER_CTX_ctrl"); - return -1; - } - - /* clean up */ - EVP_CIPHER_CTX_free(ctx); - - return ciphertext_len; -} - -/* - * AES GCM decrypt - */ -static int oidc_cache_crypto_decrypt_impl(request_rec *r, - unsigned char *ciphertext, int ciphertext_len, const unsigned char *aad, - int aad_len, const unsigned char *tag, int tag_len, unsigned char *key, - const unsigned char *iv, int iv_len, unsigned char *plaintext) { - EVP_CIPHER_CTX *ctx; - int len; - int plaintext_len; - int ret; - - /* create and initialize the context */ - if (!(ctx = EVP_CIPHER_CTX_new())) { - oidc_cache_crypto_openssl_error(r, "EVP_CIPHER_CTX_new"); - return -1; - } - - /* initialize the decryption cipher */ - if (!EVP_DecryptInit_ex(ctx, OIDC_CACHE_CIPHER, NULL, NULL, NULL)) { - oidc_cache_crypto_openssl_error(r, "EVP_DecryptInit_ex"); - return -1; - } - - /* set IV length */ - if (!EVP_CIPHER_CTX_ctrl(ctx, OIDC_CACHE_CRYPTO_SET_IVLEN, iv_len, NULL)) { - oidc_cache_crypto_openssl_error(r, "EVP_CIPHER_CTX_ctrl"); - return -1; - } - - /* initialize key and IV */ - if (!EVP_DecryptInit_ex(ctx, NULL, NULL, key, iv)) { - oidc_cache_crypto_openssl_error(r, "EVP_DecryptInit_ex"); - return -1; - } - - /* provide AAD data */ - if (!EVP_DecryptUpdate(ctx, NULL, &len, aad, aad_len)) { - oidc_cache_crypto_openssl_error(r, "EVP_DecryptUpdate aad: aad_len=%d", - aad_len); - return -1; - } - - /* provide the message to be decrypted and obtain the plaintext output */ - if (!EVP_DecryptUpdate(ctx, plaintext, &len, ciphertext, ciphertext_len)) { - oidc_cache_crypto_openssl_error(r, "EVP_DecryptUpdate ciphertext"); - return -1; - } - plaintext_len = len; - - /* set expected tag value; works in OpenSSL 1.0.1d and later */ - if (!EVP_CIPHER_CTX_ctrl(ctx, OIDC_CACHE_CRYPTO_SET_TAG, tag_len, - (void *) tag)) { - oidc_cache_crypto_openssl_error(r, "EVP_CIPHER_CTX_ctrl"); - return -1; - } +static apr_byte_t oidc_cache_crypto_encrypt(request_rec *r, const char *plaintext, const char *key, + char **result) { + apr_byte_t rv = FALSE; + json_t *json = NULL; - /* - * finalize the decryption; a positive return value indicates success, - * anything else is a failure - the plaintext is not trustworthy - */ - ret = EVP_DecryptFinal_ex(ctx, plaintext + len, &len); + json = json_object(); + json_object_set_new(json, OIDC_CACHE_CRYPTO_JSON_KEY, json_string(plaintext)); - /* clean up */ - EVP_CIPHER_CTX_free(ctx); - - if (ret > 0) { - /* success */ - plaintext_len += len; - return plaintext_len; - } else { - /* verify failed */ - oidc_cache_crypto_openssl_error(r, "EVP_DecryptFinal_ex"); - return -1; - } -} + rv = oidc_util_jwt_create(r, (const char*) key, json, result); -/* - * static AAD value for encryption/decryption - */ -static const unsigned char OIDC_CACHE_CRYPTO_GCM_AAD[] = { 0x4d, 0x23, 0xc3, - 0xce, 0xc3, 0x34, 0xb4, 0x9b, 0xdb, 0x37, 0x0c, 0x43, 0x7f, 0xec, 0x78, - 0xde }; + if (json) + json_decref(json); -/* - * static IV value for encryption/decryption - */ -static const unsigned char OIDC_CACHE_CRYPTO_GCM_IV[] = { 0x00, 0x01, 0x02, - 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, - 0x0f }; - -/* - * AES GCM encrypt using the static AAD and IV - */ -static int oidc_cache_crypto_encrypt(request_rec *r, const char *plaintext, - unsigned char *key, char **result) { - char *encoded = NULL, *p = NULL, *e_tag = NULL; - unsigned char *ciphertext = NULL; - int plaintext_len, ciphertext_len, encoded_len, e_tag_len; - unsigned char tag[OIDC_CACHE_TAG_LEN]; - - /* allocate space for the ciphertext */ - plaintext_len = strlen(plaintext) + 1; - ciphertext = apr_pcalloc(r->pool, - (plaintext_len + EVP_CIPHER_block_size(OIDC_CACHE_CIPHER))); - - ciphertext_len = oidc_cache_crypto_encrypt_impl(r, - (unsigned char *) plaintext, plaintext_len, - OIDC_CACHE_CRYPTO_GCM_AAD, sizeof(OIDC_CACHE_CRYPTO_GCM_AAD), key, - OIDC_CACHE_CRYPTO_GCM_IV, sizeof(OIDC_CACHE_CRYPTO_GCM_IV), - ciphertext, tag, sizeof(tag)); - - /* base64url encode the resulting ciphertext */ - encoded_len = oidc_base64url_encode(r, &encoded, (const char *) ciphertext, - ciphertext_len, 1); - if (encoded_len > 0) { - p = encoded; - - /* now allocated space for the concatenated base64url encoded ciphertext and tag */ - encoded = apr_pcalloc(r->pool, - encoded_len + 1 + OIDC_CACHE_TAG_LEN + 1); - memcpy(encoded, p, encoded_len); - p = encoded + encoded_len; - *p = OIDC_CHAR_DOT; - p++; - - /* base64url encode the tag and append it in the buffer */ - e_tag_len = oidc_base64url_encode(r, &e_tag, (const char *) tag, - OIDC_CACHE_TAG_LEN, 1); - memcpy(p, e_tag, e_tag_len); - encoded_len += e_tag_len + 1; - - /* make sure the result is \0 terminated */ - encoded[encoded_len] = '\0'; - - *result = encoded; - } - - return encoded_len; + return rv; } /* - * AES GCM decrypt using the static AAD and IV + * AES GCM decrypt using the crypto passphrase as symmetric key */ -static int oidc_cache_crypto_decrypt(request_rec *r, const char *cache_value, - unsigned char *key, unsigned char **plaintext) { +static apr_byte_t oidc_cache_crypto_decrypt(request_rec *r, const char *cache_value, + const char *key, char **plaintext) { - int len = -1; + apr_byte_t rv = FALSE; + json_t *json = NULL; - /* grab the base64url-encoded tag after the "." */ - char *encoded_tag = strstr(cache_value, "."); - if (encoded_tag == NULL) { - oidc_error(r, - "corrupted cache value: no tag separator found in encrypted value"); - return FALSE; - } + rv = oidc_util_jwt_verify(r, (const char*) key, cache_value, &json); + if (rv == FALSE) + goto end; - /* make sure we don't modify the original string since it may be just a pointer into the cache (shm) */ - cache_value = apr_pstrmemdup(r->pool, cache_value, - strlen(cache_value) - strlen(encoded_tag)); - encoded_tag++; + rv = oidc_json_object_get_string(r->pool, json, OIDC_CACHE_CRYPTO_JSON_KEY, plaintext, NULL); - /* base64url decode the ciphertext */ - char *d_bytes = NULL; - int d_len = oidc_base64url_decode(r->pool, &d_bytes, cache_value); + end: - /* base64url decode the tag */ - char *t_bytes = NULL; - int t_len = oidc_base64url_decode(r->pool, &t_bytes, encoded_tag); + if (json) + json_decref(json); - /* see if we're still good to go */ - if ((d_len > 0) && (t_len > 0)) { - - /* allocated space for the plaintext */ - *plaintext = apr_pcalloc(r->pool, - (d_len + EVP_CIPHER_block_size(OIDC_CACHE_CIPHER) - 1)); - - /* decrypt the ciphertext providing the tag value */ - - len = oidc_cache_crypto_decrypt_impl(r, (unsigned char *) d_bytes, - d_len, OIDC_CACHE_CRYPTO_GCM_AAD, - sizeof(OIDC_CACHE_CRYPTO_GCM_AAD), (unsigned char *) t_bytes, - t_len, key, OIDC_CACHE_CRYPTO_GCM_IV, - sizeof(OIDC_CACHE_CRYPTO_GCM_IV), *plaintext); - - /* check the result and make sure it is \0 terminated */ - if (len > -1) { - (*plaintext)[len] = '\0'; - } else { - *plaintext = NULL; - } - - } - - return len; -} - -/* - * hash the crypto passhphrase so it has enough key length for AES GCM 256 - */ -static unsigned char *oidc_cache_hash_passphrase(request_rec *r, - const char *passphrase) { - - unsigned char *key = NULL; - unsigned int key_len = 0; - oidc_jose_error_t err; - - if (oidc_jose_hash_bytes(r->pool, OIDC_JOSE_ALG_SHA256, - (const unsigned char *) passphrase, strlen(passphrase), &key, - &key_len, &err) == FALSE) { - oidc_error(r, "oidc_jose_hash_bytes returned an error: %s", err.text); - return NULL; - } - - return key; + return rv; } /* * hash a cache key and a crypto passphrase so the result is suitable as an randomized cache key */ -static char *oidc_cache_get_hashed_key(request_rec *r, const char *passphrase, - const char *key) { +static char* oidc_cache_get_hashed_key(request_rec *r, const char *passphrase, const char *key) { char *input = apr_psprintf(r->pool, "%s:%s", passphrase, key); char *output = NULL; - if (oidc_util_hash_string_and_base64url_encode(r, OIDC_JOSE_ALG_SHA256, - input, &output) == FALSE) { - oidc_error(r, - "oidc_util_hash_string_and_base64url_encode returned an error"); + if (oidc_util_hash_string_and_base64url_encode(r, OIDC_JOSE_ALG_SHA256, input, &output) + == FALSE) { + oidc_error(r, "oidc_util_hash_string_and_base64url_encode returned an error"); return NULL; } return output; @@ -588,9 +323,7 @@ apr_byte_t oidc_cache_get(request_rec *r, const char *section, const char *key, goto out; } - rc = (oidc_cache_crypto_decrypt(r, cache_value, - oidc_cache_hash_passphrase(r, cfg->crypto_passphrase), - (unsigned char **) value) > 0); + rc = oidc_cache_crypto_decrypt(r, cache_value, cfg->crypto_passphrase, value); out: /* log the result */ @@ -634,9 +367,7 @@ apr_byte_t oidc_cache_set(request_rec *r, const char *section, const char *key, goto out; if (value != NULL) { - if (oidc_cache_crypto_encrypt(r, value, - oidc_cache_hash_passphrase(r, cfg->crypto_passphrase), - &encoded) <= 0) + if (oidc_cache_crypto_encrypt(r, value, cfg->crypto_passphrase, &encoded) == FALSE) goto out; value = encoded; }