CVE-2021-32791: Use of Insufficiently Random Values (CWE-330)
value: 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.
CWE description: The product uses insufficiently random numbers or values in a security context that depends on unpredictable numbers.
CVSS v3 base score: 5.9
Published: 2021-07-26T17:15Z
Repository: https://github.com/zmartzone/mod_auth_openidc
Language: Other
Files changed: .gitignore, ChangeLog, Dockerfile, configure.ac, openidc.conf, src/cache/common.c, test/mod_auth_openidc.jmx
Commit date: 2021-06-10 15:32:48 +0200
Commit message: 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>
Diff: diff --git a/.gitignore b/.gitignore
index af108546..5969a412 100644
--- a/.gitignore
+++ b/.gitignore
@@ -22,5 +22,7 @@
 /.vscode/
 /configure~
 /Dockerfile-*
 /*.rpm
 
+/config.guess~
+/config.sub~
diff --git a/ChangeLog b/ChangeLog
index 7bcf0846..58686f50 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,9 @@
+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.
 
 06/02/2021
 - do not send state timeout HTML document when OIDCDefaultURL is set; this can be overridden by using e.g.:
diff --git a/Dockerfile b/Dockerfile
index b0e2024b..a85ff863 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,15 +1,16 @@
 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
 RUN dpkg -i libcjose0_0.6.1.5-1~bionic+1_amd64.deb
 RUN dpkg -i libcjose-dev_0.6.1.5-1~bionic+1_amd64.deb
@@ -33,6 +34,10 @@ WORKDIR /root
 
 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"
diff --git a/configure.ac b/configure.ac
index 07d26288..9a015960 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1,6 +1,6 @@
-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())
 
 # This section defines the --with-apxs2 option.
 AC_ARG_WITH(
diff --git a/openidc.conf b/openidc.conf
index 330d073a..ea16069e 100644
--- a/openidc.conf
+++ b/openidc.conf
@@ -22,10 +22,12 @@ OIDCCryptoPassphrase blabla1234
 #OIDCPublicKeyFiles /opt/local/etc/apache2/localhost.zmartzone.eu.pem
 #OIDCPrivateKeyFiles /opt/local/etc/apache2/localhost.zmartzone.eu.key
 
 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
 </Location>
 
diff --git a/src/cache/common.c b/src/cache/common.c
index e4ae68a5..0bd6ae2d 100644
--- a/src/cache/common.c
+++ b/src/cache/common.c
@@ -242,329 +242,63 @@ 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;
 }
 
@@ -602,13 +336,11 @@ apr_byte_t oidc_cache_get(request_rec *r, const char *section, const char *key,
 	if (encrypted == 0) {
 		*value = apr_pstrdup(r->pool, cache_value);
 		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 */
 	msg = apr_psprintf(r->pool, "from %s cache backend for %skey %s",
 			cfg->cache->name, encrypted ? "encrypted " : "", key);
@@ -648,13 +380,11 @@ apr_byte_t oidc_cache_set(request_rec *r, const char *section, const char *key,
 		key = oidc_cache_get_hashed_key(r, cfg->crypto_passphrase, key);
 		if (key == NULL)
 			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;
 		}
 	}
 
diff --git a/test/mod_auth_openidc.jmx b/test/mod_auth_openidc.jmx
index 40f96d1c..531f1557 100644
--- a/test/mod_auth_openidc.jmx
+++ b/test/mod_auth_openidc.jmx
@@ -1,16 +1,16 @@
 <?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>
       <boolProp name="TestPlan.functional_mode">false</boolProp>
       <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>
           <elementProp name="APP_PORT" elementType="Argument">
             <stringProp name="Argument.name">APP_PORT</stringProp>
@@ -55,11 +55,11 @@
             <stringProp name="Argument.desc">false</stringProp>
             <stringProp name="Argument.metadata">=</stringProp>
           </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>
       </elementProp>
       <stringProp name="TestPlan.user_define_classpath"></stringProp>
@@ -92,23 +92,22 @@
               </elementProp>
             </collectionProp>
           </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>
           <stringProp name="HTTPSampler.method">GET</stringProp>
           <boolProp name="HTTPSampler.follow_redirects">false</boolProp>
           <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">
             <stringProp name="RegexExtractor.useHeaders">true</stringProp>
             <stringProp name="RegexExtractor.refname">AuthorizationRequestPath</stringProp>
@@ -123,23 +122,22 @@
           <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
             <collectionProp name="Arguments.arguments"/>
           </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>
           <stringProp name="HTTPSampler.method">GET</stringProp>
           <boolProp name="HTTPSampler.follow_redirects">false</boolProp>
           <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">
             <stringProp name="RegexExtractor.useHeaders">false</stringProp>
             <stringProp name="RegexExtractor.refname">AuthzResumePath</stringProp>
@@ -162,23 +160,22 @@
               </elementProp>
             </collectionProp>
           </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>
           <stringProp name="HTTPSampler.method">GET</stringProp>
           <boolProp name="HTTPSampler.follow_redirects">true</boolProp>
           <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">
             <stringProp name="RegexExtractor.useHeaders">false</stringProp>
             <stringProp name="RegexExtractor.refname">ResumeParam</stringProp>
@@ -224,23 +221,22 @@
               </elementProp>
             </collectionProp>
           </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>
           <stringProp name="HTTPSampler.method">POST</stringProp>
           <boolProp name="HTTPSampler.follow_redirects">false</boolProp>
           <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">
             <stringProp name="RegexExtractor.useHeaders">true</stringProp>
             <stringProp name="RegexExtractor.refname">ResumePath</stringProp>
@@ -255,23 +251,22 @@
           <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
             <collectionProp name="Arguments.arguments"/>
           </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>
           <stringProp name="HTTPSampler.method">POST</stringProp>
           <boolProp name="HTTPSampler.follow_redirects">false</boolProp>
           <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">
             <stringProp name="RegexExtractor.useHeaders">true</stringProp>
             <stringProp name="RegexExtractor.refname">RedirectURIPath</stringProp>
@@ -319,23 +314,22 @@
               </elementProp>
             </collectionProp>
           </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>
           <stringProp name="HTTPSampler.method">POST</stringProp>
           <boolProp name="HTTPSampler.follow_redirects">false</boolProp>
           <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">
             <stringProp name="RegexExtractor.useHeaders">true</stringProp>
             <stringProp name="RegexExtractor.refname">ApplicationPath</stringProp>
@@ -365,23 +359,22 @@
               </elementProp>
             </collectionProp>
           </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>
           <stringProp name="HTTPSampler.method">POST</stringProp>
           <boolProp name="HTTPSampler.follow_redirects">true</boolProp>
           <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">
             <stringProp name="RegexExtractor.useHeaders">false</stringProp>
             <stringProp name="RegexExtractor.refname">cSRFTokenParam</stringProp>
@@ -439,23 +432,22 @@
               </elementProp>
             </collectionProp>
           </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>
           <stringProp name="HTTPSampler.method">POST</stringProp>
           <boolProp name="HTTPSampler.follow_redirects">false</boolProp>
           <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">
             <stringProp name="RegexExtractor.useHeaders">true</stringProp>
             <stringProp name="RegexExtractor.refname">LocationPath</stringProp>
@@ -470,23 +462,22 @@
           <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
             <collectionProp name="Arguments.arguments"/>
           </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>
           <stringProp name="HTTPSampler.method">GET</stringProp>
           <boolProp name="HTTPSampler.follow_redirects">false</boolProp>
           <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">
             <stringProp name="RegexExtractor.useHeaders">true</stringProp>
             <stringProp name="RegexExtractor.refname">ApplicationPath</stringProp>
@@ -515,41 +506,42 @@
             <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
               <collectionProp name="Arguments.arguments"/>
             </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>
             <stringProp name="HTTPSampler.method">GET</stringProp>
             <boolProp name="HTTPSampler.follow_redirects">false</boolProp>
             <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">
               <collectionProp name="Asserion.test_strings">
                 <stringProp name="-1682027846">\[OIDC_CLAIM_sub\] =&gt; ${USERNAME}</stringProp>
               </collectionProp>
               <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">
               <collectionProp name="Asserion.test_strings">
                 <stringProp name="-2019701846">\[OIDC_CLAIM_BOGUS\] =&gt; bogus</stringProp>
               </collectionProp>
               <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>
         </hashTree>
         <CookieManager guiclass="CookiePanel" testclass="CookieManager" testname="HTTP Cookie Manager" enabled="true">
@@ -565,22 +557,21 @@
           <boolProp name="quotedData">false</boolProp>
           <boolProp name="recycle">true</boolProp>
           <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">
         <stringProp name="ThreadGroup.on_sample_error">stoptest</stringProp>
         <elementProp name="ThreadGroup.main_controller" elementType="LoopController" guiclass="LoopControlPanel" testclass="LoopController" testname="Loop Controller" enabled="true">
@@ -629,22 +620,21 @@
               </elementProp>
             </collectionProp>
           </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>
           <stringProp name="HTTPSampler.method">POST</stringProp>
           <boolProp name="HTTPSampler.follow_redirects">false</boolProp>
           <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">
             <stringProp name="RegexExtractor.useHeaders">false</stringProp>
             <stringProp name="RegexExtractor.refname">AccessToken</stringProp>
@@ -659,10 +649,14 @@
           <collectionProp name="HeaderManager.headers">
             <elementProp name="" elementType="Header">
               <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/>
         <LoopController guiclass="LoopControlPanel" testclass="LoopController" testname="Loop Resource Access Requests" enabled="true">
           <boolProp name="LoopController.continue_forever">true</boolProp>
@@ -673,46 +667,35 @@
             <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
               <collectionProp name="Arguments.arguments"/>
             </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>
             <stringProp name="HTTPSampler.method">GET</stringProp>
             <boolProp name="HTTPSampler.follow_redirects">false</boolProp>
             <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>
         <objProp>
           <name>saveConfig</name>

Vulnerable code: #RUN apt-get update && apt-get install -y libcjose-dev

# docker run -p 443:443 -it 749d1204d189 /bin/bash -c "source /etc/apache2/envvars && valgrind --leak-check=full /usr/sbin/apache2 -X"
AC_INIT([mod_auth_openidc],[2.4.8.4],[hans.zandbelt@zmartzone.eu])
#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;
}
 * 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;
	}
	/*
	 * 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);
	/* 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;
 * AES GCM decrypt using the static AAD and IV
static int oidc_cache_crypto_decrypt(request_rec *r, const char *cache_value,
		unsigned char *key, unsigned char **plaintext) {

	int len = -1;
	/* 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;
	}
	/* 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++;
	/* base64url decode the ciphertext */
	char *d_bytes = NULL;
	int d_len = oidc_base64url_decode(r->pool, &d_bytes, cache_value);
	/* base64url decode the tag */
	char *t_bytes = NULL;
	int t_len = oidc_base64url_decode(r->pool, &t_bytes, encoded_tag);
	/* 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;
static char *oidc_cache_get_hashed_key(request_rec *r, const char *passphrase,
		const char *key) {
	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");
	rc = (oidc_cache_crypto_decrypt(r, cache_value,
			oidc_cache_hash_passphrase(r, cfg->crypto_passphrase),
			(unsigned char **) value) > 0);
			if (oidc_cache_crypto_encrypt(r, value,
					oidc_cache_hash_passphrase(r, cfg->crypto_passphrase),
					&encoded) <= 0)
<jmeterTestPlan version="1.2" properties="3.1" jmeter="3.1.20170220">
          <elementProp name="X_APP_SERVER" elementType="Argument">
            <stringProp name="Argument.name">X_APP_SERVER</stringProp>
            <stringProp name="Argument.value">/protected/api/index.php</stringProp>
          <stringProp name="HTTPSampler.connect_timeout"></stringProp>
          <stringProp name="HTTPSampler.response_timeout"></stringProp>
          <stringProp name="HTTPSampler.implementation">HttpClient4</stringProp>
          <boolProp name="HTTPSampler.monitor">false</boolProp>
          <stringProp name="HTTPSampler.connect_timeout"></stringProp>
          <stringProp name="HTTPSampler.response_timeout"></stringProp>
          <stringProp name="HTTPSampler.implementation">HttpClient4</stringProp>
          <boolProp name="HTTPSampler.monitor">false</boolProp>
          <stringProp name="HTTPSampler.connect_timeout"></stringProp>
          <stringProp name="HTTPSampler.response_timeout"></stringProp>
          <stringProp name="HTTPSampler.implementation">HttpClient4</stringProp>
          <boolProp name="HTTPSampler.monitor">false</boolProp>
          <stringProp name="HTTPSampler.connect_timeout"></stringProp>
          <stringProp name="HTTPSampler.response_timeout"></stringProp>
          <stringProp name="HTTPSampler.implementation">HttpClient4</stringProp>
          <boolProp name="HTTPSampler.monitor">false</boolProp>
          <stringProp name="HTTPSampler.connect_timeout"></stringProp>
          <stringProp name="HTTPSampler.response_timeout"></stringProp>
          <stringProp name="HTTPSampler.implementation">HttpClient4</stringProp>
          <boolProp name="HTTPSampler.monitor">false</boolProp>
          <stringProp name="HTTPSampler.connect_timeout"></stringProp>
          <stringProp name="HTTPSampler.response_timeout"></stringProp>
          <stringProp name="HTTPSampler.implementation">HttpClient4</stringProp>
          <boolProp name="HTTPSampler.monitor">false</boolProp>
          <stringProp name="HTTPSampler.connect_timeout"></stringProp>
          <stringProp name="HTTPSampler.response_timeout"></stringProp>
          <stringProp name="HTTPSampler.implementation">HttpClient4</stringProp>
          <boolProp name="HTTPSampler.monitor">false</boolProp>
          <stringProp name="HTTPSampler.connect_timeout"></stringProp>
          <stringProp name="HTTPSampler.response_timeout"></stringProp>
          <stringProp name="HTTPSampler.implementation">HttpClient4</stringProp>
          <boolProp name="HTTPSampler.monitor">false</boolProp>
          <stringProp name="HTTPSampler.connect_timeout"></stringProp>
          <stringProp name="HTTPSampler.response_timeout"></stringProp>
          <stringProp name="HTTPSampler.implementation">HttpClient4</stringProp>
          <boolProp name="HTTPSampler.monitor">false</boolProp>
            <stringProp name="HTTPSampler.connect_timeout"></stringProp>
            <stringProp name="HTTPSampler.response_timeout"></stringProp>
            <stringProp name="HTTPSampler.implementation">HttpClient4</stringProp>
            <boolProp name="HTTPSampler.monitor">false</boolProp>
        <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>
          <stringProp name="HTTPSampler.connect_timeout"></stringProp>
          <stringProp name="HTTPSampler.response_timeout"></stringProp>
          <boolProp name="HTTPSampler.monitor">false</boolProp>
            <stringProp name="HTTPSampler.connect_timeout"></stringProp>
            <stringProp name="HTTPSampler.response_timeout"></stringProp>
            <boolProp name="HTTPSampler.monitor">false</boolProp>
              <stringProp name="Assertion.test_field">Assertion.response_data_as_document</stringProp>
        <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/>
Fixed code: /config.guess~
/config.sub~
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

ENV DEBIAN_FRONTEND noninteractive

RUN apt-get update && apt-get install -y libapache2-mod-php 
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"
AC_INIT([mod_auth_openidc],[2.4.9-dev],[hans.zandbelt@zmartzone.eu])
OIDCScope "openid email profile"

#define OIDC_CACHE_CRYPTO_JSON_KEY "c"
 * AES GCM encrypt using the crypto passphrase as symmetric key
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;
	json = json_object();
	json_object_set_new(json, OIDC_CACHE_CRYPTO_JSON_KEY, json_string(plaintext));
	rv = oidc_util_jwt_create(r, (const char*) key, json, result);
	if (json)
		json_decref(json);
	return rv;
 * AES GCM decrypt using the crypto passphrase as symmetric key
static apr_byte_t oidc_cache_crypto_decrypt(request_rec *r, const char *cache_value,
		const char *key, char **plaintext) {
	apr_byte_t rv = FALSE;
	json_t *json = NULL;
	rv = oidc_util_jwt_verify(r, (const char*) key, cache_value, &json);
	if (rv == FALSE)
		goto end;
	rv = oidc_json_object_get_string(r->pool, json, OIDC_CACHE_CRYPTO_JSON_KEY, plaintext, NULL);
	end:
	if (json)
		json_decref(json);
	return rv;
static char* oidc_cache_get_hashed_key(request_rec *r, const char *passphrase, const char *key) {
	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");
	rc = oidc_cache_crypto_decrypt(r, cache_value, cfg->crypto_passphrase, value);
			if (oidc_cache_crypto_encrypt(r, value, cfg->crypto_passphrase, &encoded) == FALSE)
<jmeterTestPlan version="1.2" properties="5.0" jmeter="5.1.1.20190725">
          <elementProp name="APP_SERVER" elementType="Argument">
            <stringProp name="Argument.name">APP_SERVER</stringProp>
            <stringProp name="Argument.value">/api/index.php</stringProp>
          <stringProp name="HTTPSampler.implementation">HttpClient4</stringProp>
          <stringProp name="HTTPSampler.connect_timeout"></stringProp>
          <stringProp name="HTTPSampler.response_timeout"></stringProp>
          <stringProp name="HTTPSampler.implementation">HttpClient4</stringProp>
          <stringProp name="HTTPSampler.connect_timeout"></stringProp>
          <stringProp name="HTTPSampler.response_timeout"></stringProp>
          <stringProp name="HTTPSampler.implementation">HttpClient4</stringProp>
          <stringProp name="HTTPSampler.connect_timeout"></stringProp>
          <stringProp name="HTTPSampler.response_timeout"></stringProp>
          <stringProp name="HTTPSampler.implementation">HttpClient4</stringProp>
          <stringProp name="HTTPSampler.connect_timeout"></stringProp>
          <stringProp name="HTTPSampler.response_timeout"></stringProp>
          <stringProp name="HTTPSampler.implementation">HttpClient4</stringProp>
          <stringProp name="HTTPSampler.connect_timeout"></stringProp>
          <stringProp name="HTTPSampler.response_timeout"></stringProp>
          <stringProp name="HTTPSampler.implementation">HttpClient4</stringProp>
          <stringProp name="HTTPSampler.connect_timeout"></stringProp>
          <stringProp name="HTTPSampler.response_timeout"></stringProp>
          <stringProp name="HTTPSampler.implementation">HttpClient4</stringProp>
          <stringProp name="HTTPSampler.connect_timeout"></stringProp>
          <stringProp name="HTTPSampler.response_timeout"></stringProp>
          <stringProp name="HTTPSampler.implementation">HttpClient4</stringProp>
          <stringProp name="HTTPSampler.connect_timeout"></stringProp>
          <stringProp name="HTTPSampler.response_timeout"></stringProp>
          <stringProp name="HTTPSampler.implementation">HttpClient4</stringProp>
          <stringProp name="HTTPSampler.connect_timeout"></stringProp>
          <stringProp name="HTTPSampler.response_timeout"></stringProp>
            <stringProp name="HTTPSampler.implementation">HttpClient4</stringProp>
            <stringProp name="HTTPSampler.connect_timeout"></stringProp>
            <stringProp name="HTTPSampler.response_timeout"></stringProp>
              <stringProp name="Assertion.custom_message"></stringProp>
              <stringProp name="Assertion.custom_message"></stringProp>
          <boolProp name="ignoreFirstLine">false</boolProp>
        <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>
          <stringProp name="HTTPSampler.connect_timeout"></stringProp>
          <stringProp name="HTTPSampler.response_timeout"></stringProp>
            <elementProp name="" elementType="Header">
              <stringProp name="Header.name">Accept</stringProp>
              <stringProp name="Header.value">application/json</stringProp>
            </elementProp>
            <stringProp name="HTTPSampler.connect_timeout"></stringProp>
            <stringProp name="HTTPSampler.response_timeout"></stringProp>
              <stringProp name="Assertion.test_field">Assertion.response_data</stringProp>
              <stringProp name="Assertion.custom_message"></stringProp>