CVE-2021-32791 (published 2021-07-26T17:15Z)

mod_auth_openidc is an authentication/authorization module for the Apache 2.x HTTP server that functions as an OpenID Connect Relying Party, authenticating users against an OpenID Connect Provider. In mod_auth_openidc before version 2.4.9, the AES GCM encryption in mod_auth_openidc uses a static IV and AAD. It is important to fix because this creates a static nonce and since aes-gcm is a stream cipher, this can lead to known cryptographic issues, since the same key is being reused. From 2.4.9 onwards this has been patched to use dynamic values through usage of cjose AES encryption routines.

Severity: MEDIUM
CVSS v2 base score: 4.3 (AV:N/AC:M/Au:N/C:P/I:N/A:N)
CVSS v3 base score: 5.9, MEDIUM (CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N)

Weakness information:
cwe_id: CWE-330
cwe_name: Use of Insufficiently Random Values
description: The product uses insufficiently random numbers or values in a security context that depends on unpredictable numbers.
extended_description: When product generates predictable values in a context requiring unpredictability, it may be possible for an attacker to guess the next value that will be generated, and use this guess to impersonate another user or access sensitive information.
         
cwe_id: CWE-323
cwe_name: Reusing a Nonce, Key Pair in Encryption
description: Nonces should be used for the present occasion and only once.
extended_description: 
            
         
         

Fix information:
repo_url: https://github.com/zmartzone/mod_auth_openidc
commit_details: repo_url: https://github.com/zmartzone/mod_auth_openidc
author: Hans Zandbelt
author_date: 2021-06-10 15:32:48+02:00
msg: 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 <hans.zandbelt@zmartzone.eu>
repository: repo_url: https://github.com/zmartzone/mod_auth_openidc
repo_name: OpenIDC/mod_auth_openidc
description: OpenID Certified™ OpenID Connect Relying Party implementation for Apache HTTP Server 2.x
repo_language: C
owner: zmartzone
file_changes: filename: .gitignore
change_type: ModificationType.MODIFY
diff: @@ -24,3 +24,5 @@
 /Dockerfile-*
 /*.rpm
 
+/config.guess~
+/config.sub~

programming_language: Scala
filename: ChangeLog
change_type: ModificationType.MODIFY
diff: @@ -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
+
 06/04/2021
 - fix a problem where the host and port are calculated incorrectly, when you use literal ipv6 address.
 

programming_language: Markdown
filename: Dockerfile
change_type: ModificationType.MODIFY
diff: @@ -1,13 +1,14 @@
 FROM ubuntu:bionic
 MAINTAINER hans.zandbelt@zmartzone.eu
 
+ENV DEBIAN_FRONTEND noninteractive
+
 RUN apt-get update && apt-get install -y pkg-config make gcc gdb lcov valgrind vim curl iputils-ping wget
 RUN apt-get update && apt-get install -y autoconf automake libtool
 RUN apt-get update && apt-get install -y libssl-dev libjansson-dev libcurl4-openssl-dev check
-#RUN apt-get update && apt-get install -y libcjose-dev
 RUN apt-get update && apt-get install -y apache2 apache2-dev
-
 RUN apt-get update && apt-get install -y libpcre3-dev zlib1g-dev
+RUN apt-get update && apt-get install -y libapache2-mod-php 
 
 RUN wget https://mod-auth-openidc.org/download/libcjose0_0.6.1.5-1~bionic+1_amd64.deb
 RUN wget https://mod-auth-openidc.org/download/libcjose-dev_0.6.1.5-1~bionic+1_amd64.deb
@@ -35,4 +36,8 @@ ADD openidc.conf /etc/apache2/conf-available
 RUN a2enconf openidc
 RUN /usr/sbin/apache2ctl start
 
-# docker run -p 443:443 -it 749d1204d189 /bin/bash -c "source /etc/apache2/envvars && valgrind --leak-check=full /usr/sbin/apache2 -X"
+RUN mkdir -p /var/www/html/protected
+RUN echo "<html><body><h1>Hello, <?php echo($_SERVER['REMOTE_USER']) ?></h1><pre><?php print_r(array_map(\"htmlentities\", apache_request_headers())); ?></pre><a href=\"/protected/?logout=https%3A%2F%2Flocalhost.zmartzone.eu%2Floggedout.html\">Logout</a></body></html>" >  /var/www/html/protected/index.php
+RUN mkdir -p /var/www/html/api && cp /var/www/html/protected/index.php /var/www/html/api
+
+# docker run -p 443:443 -it mod_auth_openidc /bin/bash -c "source /etc/apache2/envvars && valgrind --leak-check=full /usr/sbin/apache2 -X"

programming_language: Shell
filename: configure.ac
change_type: ModificationType.MODIFY
diff: @@ -1,4 +1,4 @@
-AC_INIT([mod_auth_openidc],[2.4.8.4],[hans.zandbelt@zmartzone.eu])
+AC_INIT([mod_auth_openidc],[2.4.9-dev],[hans.zandbelt@zmartzone.eu])
 
 AC_SUBST(NAMEVER, AC_PACKAGE_TARNAME()-AC_PACKAGE_VERSION())
 

programming_language: Shell
filename: openidc.conf
change_type: ModificationType.MODIFY
diff: @@ -24,6 +24,8 @@ OIDCCryptoPassphrase blabla1234
 
 OIDCInfoHook iat access_token access_token_expires id_token userinfo refresh_token session
 
+OIDCScope "openid email profile"
+
 <Location /protected>
   AuthType openid-connect
   Require valid-user

programming_language: Ruby
filename: common.c
change_type: ModificationType.MODIFY
diff: @@ -244,325 +244,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
-
-/*
- * AES GCM encrypt
- */
-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;
-}
+#define OIDC_CACHE_CRYPTO_JSON_KEY "c"
 
 /*
- * AES GCM decrypt
+ * AES GCM encrypt using the crypto passphrase as symmetric key
  */
-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;
-	}
+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;
 
-	/* 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;
-	}
+	json = json_object();
+	json_object_set_new(json, OIDC_CACHE_CRYPTO_JSON_KEY, json_string(plaintext));
 
-	/* 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;
-	}
+	rv = oidc_util_jwt_create(r, (const char*) key, json, result);
 
-	/*
-	 * 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);
+	if (json)
+		json_decref(json);
 
-	/* 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;
-	}
-}
-
-/*
- * 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 };
-
-/*
- * 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;
-
-		/* base64url encode the tag */
-		e_tag_len = oidc_base64url_encode(r, &e_tag, (const char *) tag,
-				OIDC_CACHE_TAG_LEN, 1);
-
-		/* now allocated space for the concatenated base64url encoded ciphertext and tag */
-		encoded = apr_pcalloc(r->pool, encoded_len + 1 + e_tag_len + 1);
-		memcpy(encoded, p, encoded_len);
-		p = encoded + encoded_len;
-		*p = OIDC_CHAR_DOT;
-		p++;
-
-		/* append the tag in the buffer */
-		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) {
-
-	int len = -1;
+static apr_byte_t oidc_cache_crypto_decrypt(request_rec *r, const char *cache_value,
+		const char *key, char **plaintext) {
 
-	/* 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;
-	}
+	apr_byte_t rv = FALSE;
+	json_t *json = NULL;
 
-	/* 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_util_jwt_verify(r, (const char*) key, cache_value, &json);
+	if (rv == FALSE)
+		goto end;
 
-	/* base64url decode the ciphertext */
-	char *d_bytes = NULL;
-	int d_len = oidc_base64url_decode(r->pool, &d_bytes, cache_value);
+	rv = oidc_json_object_get_string(r->pool, json, OIDC_CACHE_CRYPTO_JSON_KEY, plaintext, NULL);
 
-	/* base64url decode the tag */
-	char *t_bytes = NULL;
-	int t_len = oidc_base64url_decode(r->pool, &t_bytes, encoded_tag);
+	end:
 
-	/* see if we're still good to go */
-	if ((d_len > 0) && (t_len > 0)) {
+	if (json)
+		json_decref(json);
 
-		/* 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;
@@ -604,9 +338,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 */
@@ -650,9 +382,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;
 		}

programming_language: C
filename: mod_auth_openidc.jmx
change_type: ModificationType.MODIFY
diff: @@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<jmeterTestPlan version="1.2" properties="3.1" jmeter="3.1.20170220">
+<jmeterTestPlan version="1.2" properties="5.0" jmeter="5.1.1.20190725">
   <hashTree>
     <TestPlan guiclass="TestPlanGui" testclass="TestPlan" testname="OpenID Connect Apache Module Test Plan" enabled="true">
       <stringProp name="TestPlan.comments"></stringProp>
@@ -7,8 +7,8 @@
       <boolProp name="TestPlan.serialize_threadgroups">false</boolProp>
       <elementProp name="TestPlan.user_defined_variables" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
         <collectionProp name="Arguments.arguments">
-          <elementProp name="X_APP_SERVER" elementType="Argument">
-            <stringProp name="Argument.name">X_APP_SERVER</stringProp>
+          <elementProp name="APP_SERVER" elementType="Argument">
+            <stringProp name="Argument.name">APP_SERVER</stringProp>
             <stringProp name="Argument.value">localhost.zmartzone.eu</stringProp>
             <stringProp name="Argument.metadata">=</stringProp>
           </elementProp>
@@ -57,7 +57,7 @@
           </elementProp>
           <elementProp name="APP_PATH_ACCESS" elementType="Argument">
             <stringProp name="Argument.name">APP_PATH_ACCESS</stringProp>
-            <stringProp name="Argument.value">/protected/api/index.php</stringProp>
+            <stringProp name="Argument.value">/api/index.php</stringProp>
             <stringProp name="Argument.metadata">=</stringProp>
           </elementProp>
         </collectionProp>
@@ -94,8 +94,6 @@
           </elementProp>
           <stringProp name="HTTPSampler.domain">${APP_SERVER}</stringProp>
           <stringProp name="HTTPSampler.port">${APP_PORT}</stringProp>
-          <stringProp name="HTTPSampler.connect_timeout"></stringProp>
-          <stringProp name="HTTPSampler.response_timeout"></stringProp>
           <stringProp name="HTTPSampler.protocol">https</stringProp>
           <stringProp name="HTTPSampler.contentEncoding"></stringProp>
           <stringProp name="HTTPSampler.path">${APP_PATH_USER}</stringProp>
@@ -104,9 +102,10 @@
           <boolProp name="HTTPSampler.auto_redirects">false</boolProp>
           <boolProp name="HTTPSampler.use_keepalive">false</boolProp>
           <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
-          <stringProp name="HTTPSampler.implementation">HttpClient4</stringProp>
-          <boolProp name="HTTPSampler.monitor">false</boolProp>
           <stringProp name="HTTPSampler.embedded_url_re"></stringProp>
+          <stringProp name="HTTPSampler.implementation">HttpClient4</stringProp>
+          <stringProp name="HTTPSampler.connect_timeout"></stringProp>
+          <stringProp name="HTTPSampler.response_timeout"></stringProp>
         </HTTPSamplerProxy>
         <hashTree>
           <RegexExtractor guiclass="RegexExtractorGui" testclass="RegexExtractor" testname="Grab AuthorizationRequestPath" enabled="true">
@@ -125,8 +124,6 @@
           </elementProp>
           <stringProp name="HTTPSampler.domain">${AS_HOST}</stringProp>
           <stringProp name="HTTPSampler.port">${AS_PORT}</stringProp>
-          <stringProp name="HTTPSampler.connect_timeout"></stringProp>
-          <stringProp name="HTTPSampler.response_timeout"></stringProp>
           <stringProp name="HTTPSampler.protocol">https</stringProp>
           <stringProp name="HTTPSampler.contentEncoding"></stringProp>
           <stringProp name="HTTPSampler.path">/${AuthorizationRequestPath}</stringProp>
@@ -135,9 +132,10 @@
           <boolProp name="HTTPSampler.auto_redirects">false</boolProp>
           <boolProp name="HTTPSampler.use_keepalive">false</boolProp>
           <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
-          <stringProp name="HTTPSampler.implementation">HttpClient4</stringProp>
-          <boolProp name="HTTPSampler.monitor">false</boolProp>
           <stringProp name="HTTPSampler.embedded_url_re"></stringProp>
+          <stringProp name="HTTPSampler.implementation">HttpClient4</stringProp>
+          <stringProp name="HTTPSampler.connect_timeout"></stringProp>
+          <stringProp name="HTTPSampler.response_timeout"></stringProp>
         </HTTPSamplerProxy>
         <hashTree>
           <RegexExtractor guiclass="RegexExtractorGui" testclass="RegexExtractor" testname="Grab AuthzResumePath" enabled="true">
@@ -164,8 +162,6 @@
           </elementProp>
           <stringProp name="HTTPSampler.domain">${AS_HOST}</stringProp>
           <stringProp name="HTTPSampler.port">${AS_PORT}</stringProp>
-          <stringProp name="HTTPSampler.connect_timeout"></stringProp>
-          <stringProp name="HTTPSampler.response_timeout"></stringProp>
           <stringProp name="HTTPSampler.protocol">https</stringProp>
           <stringProp name="HTTPSampler.contentEncoding"></stringProp>
           <stringProp name="HTTPSampler.path">${AuthzResumePath}</stringProp>
@@ -174,9 +170,10 @@
           <boolProp name="HTTPSampler.auto_redirects">false</boolProp>
           <boolProp name="HTTPSampler.use_keepalive">false</boolProp>
           <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
-          <stringProp name="HTTPSampler.implementation">HttpClient4</stringProp>
-          <boolProp name="HTTPSampler.monitor">false</boolProp>
           <stringProp name="HTTPSampler.embedded_url_re"></stringProp>
+          <stringProp name="HTTPSampler.implementation">HttpClient4</stringProp>
+          <stringProp name="HTTPSampler.connect_timeout"></stringProp>
+          <stringProp name="HTTPSampler.response_timeout"></stringProp>
         </HTTPSamplerProxy>
         <hashTree>
           <RegexExtractor guiclass="RegexExtractorGui" testclass="RegexExtractor" testname="Grab ResumeParam" enabled="true">
@@ -226,8 +223,6 @@
           </elementProp>
           <stringProp name="HTTPSampler.domain">${AS_HOST}</stringProp>
           <stringProp name="HTTPSampler.port">${AS_PORT}</stringProp>
-          <stringProp name="HTTPSampler.connect_timeout"></stringProp>
-          <stringProp name="HTTPSampler.response_timeout"></stringProp>
           <stringProp name="HTTPSampler.protocol">https</stringProp>
           <stringProp name="HTTPSampler.contentEncoding"></stringProp>
           <stringProp name="HTTPSampler.path">/IdpSample/MainPage?cmd=login</stringProp>
@@ -236,9 +231,10 @@
           <boolProp name="HTTPSampler.auto_redirects">false</boolProp>
           <boolProp name="HTTPSampler.use_keepalive">false</boolProp>
           <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
-          <stringProp name="HTTPSampler.implementation">HttpClient4</stringProp>
-          <boolProp name="HTTPSampler.monitor">false</boolProp>
           <stringProp name="HTTPSampler.embedded_url_re"></stringProp>
+          <stringProp name="HTTPSampler.implementation">HttpClient4</stringProp>
+          <stringProp name="HTTPSampler.connect_timeout"></stringProp>
+          <stringProp name="HTTPSampler.response_timeout"></stringProp>
         </HTTPSamplerProxy>
         <hashTree>
           <RegexExtractor guiclass="RegexExtractorGui" testclass="RegexExtractor" testname="Grab ResumePath" enabled="true">
@@ -257,8 +253,6 @@
           </elementProp>
           <stringProp name="HTTPSampler.domain">${AS_HOST}</stringProp>
           <stringProp name="HTTPSampler.port">${AS_PORT}</stringProp>
-          <stringProp name="HTTPSampler.connect_timeout"></stringProp>
-          <stringProp name="HTTPSampler.response_timeout"></stringProp>
           <stringProp name="HTTPSampler.protocol">https</stringProp>
           <stringProp name="HTTPSampler.contentEncoding"></stringProp>
           <stringProp name="HTTPSampler.path">${ResumePath}</stringProp>
@@ -267,9 +261,10 @@
           <boolProp name="HTTPSampler.auto_redirects">false</boolProp>
           <boolProp name="HTTPSampler.use_keepalive">false</boolProp>
           <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
-          <stringProp name="HTTPSampler.implementation">HttpClient4</stringProp>
-          <boolProp name="HTTPSampler.monitor">false</boolProp>
           <stringProp name="HTTPSampler.embedded_url_re"></stringProp>
+          <stringProp name="HTTPSampler.implementation">HttpClient4</stringProp>
+          <stringProp name="HTTPSampler.connect_timeout"></stringProp>
+          <stringProp name="HTTPSampler.response_timeout"></stringProp>
         </HTTPSamplerProxy>
         <hashTree>
           <RegexExtractor guiclass="RegexExtractorGui" testclass="RegexExtractor" testname="Grab RedirectURIPath" enabled="true">
@@ -321,8 +316,6 @@
           </elementProp>
           <stringProp name="HTTPSampler.domain">${APP_SERVER}</stringProp>
           <stringProp name="HTTPSampler.port">${APP_PORT}</stringProp>
-          <stringProp name="HTTPSampler.connect_timeout"></stringProp>
-          <stringProp name="HTTPSampler.response_timeout"></stringProp>
           <stringProp name="HTTPSampler.protocol">https</stringProp>
           <stringProp name="HTTPSampler.contentEncoding"></stringProp>
           <stringProp name="HTTPSampler.path">/${RedirectURIPath}</stringProp>
@@ -331,9 +324,10 @@
           <boolProp name="HTTPSampler.auto_redirects">false</boolProp>
           <boolProp name="HTTPSampler.use_keepalive">false</boolProp>
           <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
-          <stringProp name="HTTPSampler.implementation">HttpClient4</stringProp>
-          <boolProp name="HTTPSampler.monitor">false</boolProp>
           <stringProp name="HTTPSampler.embedded_url_re"></stringProp>
+          <stringProp name="HTTPSampler.implementation">HttpClient4</stringProp>
+          <stringProp name="HTTPSampler.connect_timeout"></stringProp>
+          <stringProp name="HTTPSampler.response_timeout"></stringProp>
         </HTTPSamplerProxy>
         <hashTree>
           <RegexExtractor guiclass="RegexExtractorGui" testclass="RegexExtractor" testname="Grab ApplicationPath" enabled="true">
@@ -367,8 +361,6 @@
           </elementProp>
           <stringProp name="HTTPSampler.domain">${AS_HOST}</stringProp>
           <stringProp name="HTTPSampler.port">${AS_PORT}</stringProp>
-          <stringProp name="HTTPSampler.connect_timeout"></stringProp>
-          <stringProp name="HTTPSampler.response_timeout"></stringProp>
           <stringProp name="HTTPSampler.protocol">https</stringProp>
           <stringProp name="HTTPSampler.contentEncoding"></stringProp>
           <stringProp name="HTTPSampler.path">${AuthzResumePath}</stringProp>
@@ -377,9 +369,10 @@
           <boolProp name="HTTPSampler.auto_redirects">false</boolProp>
           <boolProp name="HTTPSampler.use_keepalive">false</boolProp>
           <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
-          <stringProp name="HTTPSampler.implementation">HttpClient4</stringProp>
-          <boolProp name="HTTPSampler.monitor">false</boolProp>
           <stringProp name="HTTPSampler.embedded_url_re"></stringProp>
+          <stringProp name="HTTPSampler.implementation">HttpClient4</stringProp>
+          <stringProp name="HTTPSampler.connect_timeout"></stringProp>
+          <stringProp name="HTTPSampler.response_timeout"></stringProp>
         </HTTPSamplerProxy>
         <hashTree>
           <RegexExtractor guiclass="RegexExtractorGui" testclass="RegexExtractor" testname="Grab cSRFToken Parameter" enabled="true">
@@ -441,8 +434,6 @@
           </elementProp>
           <stringProp name="HTTPSampler.domain">${AS_HOST}</stringProp>
           <stringProp name="HTTPSampler.port">${AS_PORT}</stringProp>
-          <stringProp name="HTTPSampler.connect_timeout"></stringProp>
-          <stringProp name="HTTPSampler.response_timeout"></stringProp>
           <stringProp name="HTTPSampler.protocol">https</stringProp>
           <stringProp name="HTTPSampler.contentEncoding"></stringProp>
           <stringProp name="HTTPSampler.path">${AuthzResumePath}</stringProp>
@@ -451,9 +442,10 @@
           <boolProp name="HTTPSampler.auto_redirects">false</boolProp>
           <boolProp name="HTTPSampler.use_keepalive">false</boolProp>
           <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
-          <stringProp name="HTTPSampler.implementation">HttpClient4</stringProp>
-          <boolProp name="HTTPSampler.monitor">false</boolProp>
           <stringProp name="HTTPSampler.embedded_url_re"></stringProp>
+          <stringProp name="HTTPSampler.implementation">HttpClient4</stringProp>
+          <stringProp name="HTTPSampler.connect_timeout"></stringProp>
+          <stringProp name="HTTPSampler.response_timeout"></stringProp>
         </HTTPSamplerProxy>
         <hashTree>
           <RegexExtractor guiclass="RegexExtractorGui" testclass="RegexExtractor" testname="Grab LocationPath" enabled="true">
@@ -472,8 +464,6 @@
           </elementProp>
           <stringProp name="HTTPSampler.domain">${APP_SERVER}</stringProp>
           <stringProp name="HTTPSampler.port">${APP_PORT}</stringProp>
-          <stringProp name="HTTPSampler.connect_timeout"></stringProp>
-          <stringProp name="HTTPSampler.response_timeout"></stringProp>
           <stringProp name="HTTPSampler.protocol">https</stringProp>
           <stringProp name="HTTPSampler.contentEncoding"></stringProp>
           <stringProp name="HTTPSampler.path">/${LocationPath}</stringProp>
@@ -482,9 +472,10 @@
           <boolProp name="HTTPSampler.auto_redirects">false</boolProp>
           <boolProp name="HTTPSampler.use_keepalive">false</boolProp>
           <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
-          <stringProp name="HTTPSampler.implementation">HttpClient4</stringProp>
-          <boolProp name="HTTPSampler.monitor">false</boolProp>
           <stringProp name="HTTPSampler.embedded_url_re"></stringProp>
+          <stringProp name="HTTPSampler.implementation">HttpClient4</stringProp>
+          <stringProp name="HTTPSampler.connect_timeout"></stringProp>
+          <stringProp name="HTTPSampler.response_timeout"></stringProp>
         </HTTPSamplerProxy>
         <hashTree>
           <RegexExtractor guiclass="RegexExtractorGui" testclass="RegexExtractor" testname="Grab ApplicationPath" enabled="true">
@@ -517,8 +508,6 @@
             </elementProp>
             <stringProp name="HTTPSampler.domain">${APP_SERVER}</stringProp>
             <stringProp name="HTTPSampler.port">${APP_PORT}</stringProp>
-            <stringProp name="HTTPSampler.connect_timeout"></stringProp>
-            <stringProp name="HTTPSampler.response_timeout"></stringProp>
             <stringProp name="HTTPSampler.protocol">https</stringProp>
             <stringProp name="HTTPSampler.contentEncoding"></stringProp>
             <stringProp name="HTTPSampler.path">/${ApplicationPath}</stringProp>
@@ -527,9 +516,10 @@
             <boolProp name="HTTPSampler.auto_redirects">false</boolProp>
             <boolProp name="HTTPSampler.use_keepalive">false</boolProp>
             <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
-            <stringProp name="HTTPSampler.implementation">HttpClient4</stringProp>
-            <boolProp name="HTTPSampler.monitor">false</boolProp>
             <stringProp name="HTTPSampler.embedded_url_re"></stringProp>
+            <stringProp name="HTTPSampler.implementation">HttpClient4</stringProp>
+            <stringProp name="HTTPSampler.connect_timeout"></stringProp>
+            <stringProp name="HTTPSampler.response_timeout"></stringProp>
           </HTTPSamplerProxy>
           <hashTree>
             <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Assert User Logged In" enabled="true">
@@ -539,6 +529,7 @@
               <stringProp name="Assertion.test_field">Assertion.response_data</stringProp>
               <boolProp name="Assertion.assume_success">false</boolProp>
               <intProp name="Assertion.test_type">2</intProp>
+              <stringProp name="Assertion.custom_message"></stringProp>
             </ResponseAssertion>
             <hashTree/>
             <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Assert Fake Header Removal" enabled="true">
@@ -548,6 +539,7 @@
               <stringProp name="Assertion.test_field">Assertion.response_data</stringProp>
               <boolProp name="Assertion.assume_success">false</boolProp>
               <intProp name="Assertion.test_type">6</intProp>
+              <stringProp name="Assertion.custom_message"></stringProp>
             </ResponseAssertion>
             <hashTree/>
           </hashTree>
@@ -567,18 +559,17 @@
           <stringProp name="shareMode">shareMode.all</stringProp>
           <boolProp name="stopThread">false</boolProp>
           <stringProp name="variableNames">USERNAME,PASSWORD</stringProp>
+          <boolProp name="ignoreFirstLine">false</boolProp>
         </CSVDataSet>
         <hashTree/>
-        <CSVDataSet guiclass="TestBeanGUI" testclass="CSVDataSet" testname="Hosts Data Set" enabled="true">
-          <stringProp name="filename">hosts.txt</stringProp>
-          <stringProp name="fileEncoding"></stringProp>
-          <stringProp name="variableNames">APP_SERVER</stringProp>
-          <stringProp name="delimiter">,</stringProp>
-          <boolProp name="quotedData">false</boolProp>
-          <boolProp name="recycle">true</boolProp>
-          <boolProp name="stopThread">false</boolProp>
-          <stringProp name="shareMode">shareMode.all</stringProp>
-        </CSVDataSet>
+        <HeaderManager guiclass="HeaderPanel" testclass="HeaderManager" testname="HTTP Header Manager" enabled="true">
+          <collectionProp name="HeaderManager.headers">
+            <elementProp name="" elementType="Header">
+              <stringProp name="Header.name">Accept</stringProp>
+              <stringProp name="Header.value">*/*</stringProp>
+            </elementProp>
+          </collectionProp>
+        </HeaderManager>
         <hashTree/>
       </hashTree>
       <ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="OAuth Resource Access" enabled="true">
@@ -631,8 +622,6 @@
           </elementProp>
           <stringProp name="HTTPSampler.domain">${AS_HOST}</stringProp>
           <stringProp name="HTTPSampler.port">${AS_PORT}</stringProp>
-          <stringProp name="HTTPSampler.connect_timeout"></stringProp>
-          <stringProp name="HTTPSampler.response_timeout"></stringProp>
           <stringProp name="HTTPSampler.protocol">https</stringProp>
           <stringProp name="HTTPSampler.contentEncoding"></stringProp>
           <stringProp name="HTTPSampler.path">${AS_PATH_TOKEN}</stringProp>
@@ -641,8 +630,9 @@
           <boolProp name="HTTPSampler.auto_redirects">false</boolProp>
           <boolProp name="HTTPSampler.use_keepalive">false</boolProp>
           <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
-          <boolProp name="HTTPSampler.monitor">false</boolProp>
           <stringProp name="HTTPSampler.embedded_url_re"></stringProp>
+          <stringProp name="HTTPSampler.connect_timeout"></stringProp>
+          <stringProp name="HTTPSampler.response_timeout"></stringProp>
         </HTTPSamplerProxy>
         <hashTree>
           <RegexExtractor guiclass="RegexExtractorGui" testclass="RegexExtractor" testname="Grab AccessToken" enabled="true">
@@ -661,6 +651,10 @@
               <stringProp name="Header.name">Authorization</stringProp>
               <stringProp name="Header.value">bearer ${AccessToken}</stringProp>
             </elementProp>
+            <elementProp name="" elementType="Header">
+              <stringProp name="Header.name">Accept</stringProp>
+              <stringProp name="Header.value">application/json</stringProp>
+            </elementProp>
           </collectionProp>
         </HeaderManager>
         <hashTree/>
@@ -675,8 +669,6 @@
             </elementProp>
             <stringProp name="HTTPSampler.domain">${APP_SERVER}</stringProp>
             <stringProp name="HTTPSampler.port">${APP_PORT}</stringProp>
-            <stringProp name="HTTPSampler.connect_timeout"></stringProp>
-            <stringProp name="HTTPSampler.response_timeout"></stringProp>
             <stringProp name="HTTPSampler.protocol">https</stringProp>
             <stringProp name="HTTPSampler.contentEncoding"></stringProp>
             <stringProp name="HTTPSampler.path">${APP_PATH_ACCESS}</stringProp>
@@ -685,32 +677,23 @@
             <boolProp name="HTTPSampler.auto_redirects">false</boolProp>
             <boolProp name="HTTPSampler.use_keepalive">false</boolProp>
             <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
-            <boolProp name="HTTPSampler.monitor">false</boolProp>
             <stringProp name="HTTPSampler.embedded_url_re"></stringProp>
+            <stringProp name="HTTPSampler.connect_timeout"></stringProp>
+            <stringProp name="HTTPSampler.response_timeout"></stringProp>
           </HTTPSamplerProxy>
           <hashTree>
             <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Assert AS_USERNAME" enabled="true">
               <collectionProp name="Asserion.test_strings">
                 <stringProp name="240033137">\[OIDC_CLAIM_Username\] =&gt; ${AS_USERNAME}</stringProp>
               </collectionProp>
-              <stringProp name="Assertion.test_field">Assertion.response_data_as_document</stringProp>
+              <stringProp name="Assertion.test_field">Assertion.response_data</stringProp>
               <boolProp name="Assertion.assume_success">false</boolProp>
               <intProp name="Assertion.test_type">2</intProp>
+              <stringProp name="Assertion.custom_message"></stringProp>
             </ResponseAssertion>
             <hashTree/>
           </hashTree>
         </hashTree>
-        <CSVDataSet guiclass="TestBeanGUI" testclass="CSVDataSet" testname="Hosts Data Set" enabled="true">
-          <stringProp name="filename">hosts.txt</stringProp>
-          <stringProp name="fileEncoding"></stringProp>
-          <stringProp name="variableNames">APP_SERVER</stringProp>
-          <stringProp name="delimiter">,</stringProp>
-          <boolProp name="quotedData">false</boolProp>
-          <boolProp name="recycle">true</boolProp>
-          <boolProp name="stopThread">false</boolProp>
-          <stringProp name="shareMode">shareMode.all</stringProp>
-        </CSVDataSet>
-        <hashTree/>
       </hashTree>
       <ResultCollector guiclass="ViewResultsFullVisualizer" testclass="ResultCollector" testname="View Results Tree" enabled="true">
         <boolProp name="ResultCollector.error_logging">true</boolProp>

programming_language: SQL