diff --git a/configs/samples/pjsip.conf.sample b/configs/samples/pjsip.conf.sample
index 7b4c7aac1eff4e9845e190ef31c315775e4d2e5b..874fcc390cdb7237851405bb6403deb0aa0fa95f 100644
--- a/configs/samples/pjsip.conf.sample
+++ b/configs/samples/pjsip.conf.sample
@@ -971,10 +971,11 @@
                ; The input to the hash function must be in the
                ; following format:
                ; <username>:<realm>:<password>
-               ; For incoming authentication (asterisk is the server),
+               ; For incoming authentication (asterisk is the UAS),
                ; the realm must match either the realm set in this object
                ; or the default set in in the "global" object.
-               ; For outgoing authentication (asterisk is the client),
+               ;
+               ; For outgoing authentication (asterisk is the UAC),
                ; the realm must match what the server will be sending
                ; in their WWW-Authenticate header.  It can't be blank
                ; unless you expect the server to be sending a blank
@@ -985,16 +986,22 @@
                ; Note the '-n'.  You don't want a newline to be part
                ; of the hash.  (default: "")
 ;password=     ; PlainText password used for authentication (default: "")
-;realm=        ; For incoming authentication (asterisk is the server),
+;realm=        ; For incoming authentication (asterisk is the UAS),
                ; this is the realm to be sent on WWW-Authenticate
                ; headers.  If not specified, the global object's
                ; "default_realm" will be used.
-               ; For outgoing authentication (asterisk is the client), this
+               ;
+               ; For outgoing authentication (asterisk is the UAS), this
                ; must either be the realm the server is expected to send,
-               ; or blank to automatically use the realm sent by the server.
-               ; If you have multiple auth object for an endpoint, the realm
-               ; is also used to match the auth object to the realm the
-               ; server sends.  (default: "")
+               ; or left blank or contain a single '*' to automatically
+               ; use the realm sent by the server. If you have multiple
+               ; auth object for an endpoint, the realm is also used to
+               ; match the auth object to the realm the server sent.
+               ; Using the same auth section for inbound and outbound
+               ; authentication is not recommended.  There is a difference in
+               ; meaning for an empty realm setting between inbound and outbound
+               ; authentication uses.
+               ; (default: "")
 ;type=         ; Must be auth (default: "")
 ;username=     ; Username to use for account (default: "")
 
diff --git a/include/asterisk/res_pjsip.h b/include/asterisk/res_pjsip.h
index 2020ca8782c3a9030564800602ebfcb93a7bc6cc..7083b25675abdec4c7c28f04e10df4be4fd1c2a8 100644
--- a/include/asterisk/res_pjsip.h
+++ b/include/asterisk/res_pjsip.h
@@ -2499,11 +2499,47 @@ int ast_sip_retrieve_auths(const struct ast_sip_auth_vector *auths, struct ast_s
  * Call this function once you have completed operating on auths
  * retrieved from \ref ast_sip_retrieve_auths
  *
- * \param auths An vector of auth structures to clean up
- * \param num_auths The number of auths in the vector
+ * \param auths An array of auth object pointers to clean up
+ * \param num_auths The number of auths in the array
  */
 void ast_sip_cleanup_auths(struct ast_sip_auth *auths[], size_t num_auths);
 
+/*!
+ * \brief Retrieve relevant SIP auth structures from sorcery as a vector
+ *
+ * \param auth_ids Vector of sorcery IDs of auth credentials to retrieve
+ * \param[out] auth_objects A pointer ast_sip_auth_objects_vector to hold the objects
+ *
+ * \retval 0 Success
+ * \retval -1 Number of auth objects found is less than the number of names supplied.
+ *
+ * \WARNING The number of auth objects retrieved may be less than the
+ * number of auth ids supplied if auth objects couldn't be found for
+ * some of them.
+ *
+ * \NOTE Since the ref count on all auith objects returned has been
+ * bumped, you must call ast_sip_cleanup_auth_objects_vector() to decrement
+ * the ref count on all of the auth objects in the vector,
+ * then call AST_VECTOR_FREE() on the vector itself.
+ *
+ */
+AST_VECTOR(ast_sip_auth_objects_vector, struct ast_sip_auth *);
+int ast_sip_retrieve_auths_vector(const struct ast_sip_auth_vector *auth_ids,
+	struct ast_sip_auth_objects_vector *auth_objects);
+
+/*!
+ * \brief Clean up retrieved auth objects in vector
+ *
+ * Call this function once you have completed operating on auths
+ * retrieved from \ref ast_sip_retrieve_auths_vector.  All
+ * auth objects will have their reference counts decremented and
+ * the vector size will be reset to 0.  You must still call
+ * AST_VECTOR_FREE() on the vector itself.
+ *
+ * \param auth_objects A vector of auth structures to clean up
+ */
+#define ast_sip_cleanup_auth_objects_vector(auth_objects) AST_VECTOR_RESET(auth_objects, ao2_cleanup)
+
 /*!
  * \brief Checks if the given content type matches type/subtype.
  *
diff --git a/res/res_pjsip.c b/res/res_pjsip.c
index 775b63f8d1af41bcc86c890a678144d23ee88c8e..3e9e4541ec764c87725b4d2002fba4b2dff5088d 100644
--- a/res/res_pjsip.c
+++ b/res/res_pjsip.c
@@ -1504,13 +1504,24 @@
 						This option specifies which of the password style config options should be read
 						when trying to authenticate an endpoint inbound request. If set to <literal>userpass</literal>
 						then we'll read from the 'password' option. For <literal>md5</literal> we'll read
-						from 'md5_cred'. If set to <literal>google_oauth</literal> then we'll read from the refresh_token/oauth_clientid/oauth_secret fields.
+						from 'md5_cred'. If set to <literal>google_oauth</literal> then we'll read from the
+						refresh_token/oauth_clientid/oauth_secret fields. The following values are valid:
 						</para>
 						<enumlist>
 							<enum name="md5"/>
 							<enum name="userpass"/>
 							<enum name="google_oauth"/>
 						</enumlist>
+						<para>
+						</para>
+						<note>
+							<para>
+								This setting only describes whether the password is in
+								plain text or has been pre-hashed with MD5.  It doesn't describe
+								the acceptable digest algorithms we'll accept in a received
+								challenge.
+							</para>
+						</note>
 					</description>
 				</configOption>
 				<configOption name="nonce_lifetime" default="32">
@@ -1542,11 +1553,12 @@
 						<para>
 						</para>
 						<para>
-						For outgoing authentication (asterisk is the client),
+						For outgoing authentication (asterisk is the UAC),
 						the realm must match what the server will be sending
 						in their WWW-Authenticate header.  It can't be blank
 						unless you expect the server to be sending a blank
-						realm in the header.
+						realm in the header.  You can't use pre-hashed
+						paswords with a wildcard auth object.
 						You can generate the hash with the following shell
 						command:
 						</para>
@@ -1578,7 +1590,7 @@
 				<configOption name="realm" default="">
 					<synopsis>SIP realm for endpoint</synopsis>
 					<description><para>
-						For incoming authentication (asterisk is the server),
+						For incoming authentication (asterisk is the UAS),
 						this is the realm to be sent on WWW-Authenticate
 						headers.  If not specified, the <replaceable>global</replaceable>
 						object's <variable>default_realm</variable> will be used.
@@ -1586,12 +1598,12 @@
 						<para>
 						</para>
 						<para>
-						For outgoing authentication (asterisk is the client), this
+						For outgoing authentication (asterisk is the UAS), this
 						must either be the realm the server is expected to send,
-						or blank to automatically use the realm sent by the server.
-						If you have multiple auth object for an endpoint, the realm
-						is also used to match the auth object to the realm the
-						server sent.
+						or left blank or contain a single '*' to automatically
+						use the realm sent by the server. If you have multiple
+						auth object for an endpoint, the realm is also used to
+						match the auth object to the realm the server sent.
 						</para>
 						<para>
 						</para>
@@ -1600,7 +1612,19 @@
 						Using the same auth section for inbound and outbound
 						authentication is not recommended.  There is a difference in
 						meaning for an empty realm setting between inbound and outbound
-						authentication uses.</para></note>
+						authentication uses.
+						</para>
+						</note>
+						<para>
+						</para>
+						<note>
+							<para>
+								If more than one auth object with the same realm or
+								more than one wildcard auth object associated to
+								an endpoint, we can only use the first one of
+								each defined on the endpoint.
+							</para>
+						</note>
 					</description>
 				</configOption>
 				<configOption name="type">
diff --git a/res/res_pjsip/pjsip_configuration.c b/res/res_pjsip/pjsip_configuration.c
index 6d12c6f6ec841b566e5331bc3e6ea9e0a6d8e06f..6defa7cb96a542d75ae9a13dad7b90afe52bd71c 100644
--- a/res/res_pjsip/pjsip_configuration.c
+++ b/res/res_pjsip/pjsip_configuration.c
@@ -2382,6 +2382,25 @@ void ast_sip_cleanup_auths(struct ast_sip_auth *auths[], size_t num_auths)
 	}
 }
 
+int ast_sip_retrieve_auths_vector(const struct ast_sip_auth_vector *auth_ids,
+	struct ast_sip_auth_objects_vector *auth_objects)
+{
+	int i;
+
+	for (i = 0; i < AST_VECTOR_SIZE(auth_ids); ++i) {
+		/* Using AST_VECTOR_GET is safe since the vector is immutable */
+		const char *name = AST_VECTOR_GET(auth_ids, i);
+		struct ast_sip_auth *auth_object = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), SIP_SORCERY_AUTH_TYPE, name);
+		if (!auth_object) {
+			ast_log(LOG_WARNING, "Auth object '%s' could not be found\n", name);
+		} else {
+			AST_VECTOR_APPEND(auth_objects, auth_object);
+		}
+	}
+
+	return AST_VECTOR_SIZE(auth_objects) == AST_VECTOR_SIZE(auth_ids) ? 0 : -1;
+}
+
 struct ast_sorcery *ast_sip_get_sorcery(void)
 {
 	return sip_sorcery;
diff --git a/res/res_pjsip_authenticator_digest.c b/res/res_pjsip_authenticator_digest.c
index 518ef73bb146a2ce2cf8c86c40758fbc00527455..231a7e5aabf3914dce0b8d34fb930ccd0e0cb052 100644
--- a/res/res_pjsip_authenticator_digest.c
+++ b/res/res_pjsip_authenticator_digest.c
@@ -24,6 +24,7 @@
 #include "asterisk/logger.h"
 #include "asterisk/module.h"
 #include "asterisk/strings.h"
+#include "asterisk/test.h"
 
 /*** MODULEINFO
 	<depend>pjproject</depend>
@@ -296,7 +297,7 @@ static void setup_auth_srv(pj_pool_t *pool, pjsip_auth_srv *auth_server, const c
  */
 enum digest_verify_result {
 	/*! Authentication credentials incorrect */
-	AUTH_FAIL,
+	AUTH_FAIL = 0,
 	/*! Authentication credentials correct */
 	AUTH_SUCCESS,
 	/*! Authentication credentials correct but nonce mismatch */
@@ -305,6 +306,12 @@ enum digest_verify_result {
 	AUTH_NOAUTH,
 };
 
+static char *verify_result_str[] = {
+	"FAIL",
+	"SUCCESS",
+	"STALE",
+	"NOAUTH"
+};
 /*!
  * \brief astobj2 callback for verifying incoming credentials
  *
@@ -320,6 +327,7 @@ static int verify(const struct ast_sip_auth *auth, pjsip_rx_data *rdata, pj_pool
 	int response_code;
 	pjsip_auth_srv auth_server;
 	int stale = 0;
+	int res = AUTH_FAIL;
 
 	if (!find_challenge(rdata, auth)) {
 		/* Couldn't find a challenge with a sane nonce.
@@ -336,17 +344,26 @@ static int verify(const struct ast_sip_auth *auth, pjsip_rx_data *rdata, pj_pool
 
 	if (authed == PJ_SUCCESS) {
 		if (stale) {
-			return AUTH_STALE;
+			res = AUTH_STALE;
 		} else {
-			return AUTH_SUCCESS;
+			res = AUTH_SUCCESS;
 		}
 	}
 
 	if (authed == PJSIP_EAUTHNOAUTH) {
-		return AUTH_NOAUTH;
+		res = AUTH_NOAUTH;
 	}
 
-	return AUTH_FAIL;
+	ast_debug(3, "Realm: %s  Username: %s  Result: %s\n",
+		auth->realm, auth->auth_user, verify_result_str[res]);
+
+	ast_test_suite_event_notify("INCOMING_AUTH_VERIFY_RESULT",
+		"Realm: %s\r\n"
+		"Username: %s\r\n"
+		"Status: %s",
+		auth->realm, auth->auth_user, verify_result_str[res]);
+
+	return res;
 }
 
 /*!
diff --git a/res/res_pjsip_outbound_authenticator_digest.c b/res/res_pjsip_outbound_authenticator_digest.c
index b1011b00a3b5888210a609d4930094baebbe9834..3f8732c332e09f08069e0e07292741c3439c3a1f 100644
--- a/res/res_pjsip_outbound_authenticator_digest.c
+++ b/res/res_pjsip_outbound_authenticator_digest.c
@@ -30,131 +30,498 @@
 #include "asterisk/logger.h"
 #include "asterisk/module.h"
 #include "asterisk/strings.h"
+#include "asterisk/vector.h"
 
-static pjsip_www_authenticate_hdr *get_auth_header(pjsip_rx_data *challenge,
-	const void *start)
-{
-	pjsip_hdr_e search_type;
+pj_str_t supported_digest_algorithms[] = {
+	{ "MD5", 3}
+};
 
+/*!
+ * \internal
+ * \brief Determine proper authenticate header
+ *
+ * We need to search for different headers depending on whether
+ * the response code from the UAS/Proxy was 401 or 407.
+ */
+static pjsip_hdr_e get_auth_search_type(pjsip_rx_data *challenge)
+{
 	if (challenge->msg_info.msg->line.status.code == PJSIP_SC_UNAUTHORIZED) {
-		search_type = PJSIP_H_WWW_AUTHENTICATE;
+		return PJSIP_H_WWW_AUTHENTICATE;
 	} else if (challenge->msg_info.msg->line.status.code == PJSIP_SC_PROXY_AUTHENTICATION_REQUIRED) {
-		search_type = PJSIP_H_PROXY_AUTHENTICATE;
+		return PJSIP_H_PROXY_AUTHENTICATE;
 	} else {
 		ast_log(LOG_ERROR,
 				"Status code %d was received when it should have been 401 or 407.\n",
 				challenge->msg_info.msg->line.status.code);
-		return NULL ;
+		return PJSIP_H_OTHER;
 	}
+}
 
-	return pjsip_msg_find_hdr(challenge->msg_info.msg, search_type, start);
+/*!
+ * \internal
+ * \brief Determine if digest algorithm in the header is one we support
+ *
+ * \retval 1 If we support the algorithm
+ * \retval 0 If we do not
+ *
+ */
+static int is_digest_algorithm_supported(pjsip_www_authenticate_hdr *auth_hdr)
+{
+	int digest;
 
+	/* An empty digest is assumed to be md5 */
+	if (pj_strlen(&auth_hdr->challenge.digest.algorithm) == 0) {
+		return 1;
+	}
+
+	for (digest = 0; digest < ARRAY_LEN(supported_digest_algorithms); digest++) {
+		if (pj_stricmp(&auth_hdr->challenge.digest.algorithm, &supported_digest_algorithms[digest]) == 0) {
+			return 1;
+		}
+	}
+	return 0;
 }
 
-static int set_outbound_authentication_credentials(pjsip_auth_clt_sess *auth_sess,
-		const struct ast_sip_auth_vector *auth_vector, pjsip_rx_data *challenge,
-		pjsip_www_authenticate_hdr *auth_hdr)
+/*!
+ * \internal
+ * \brief Initialize pjproject with a valid set of credentials
+ *
+ * RFC7616 and RFC8760 allow more than one WWW-Authenticate or
+ * Proxy-Authenticate header per realm, each with different digest
+ * algorithms (including new ones like SHA-256 and SHA-512-256). However,
+ * thankfully, a UAS can NOT send back multiple Authenticate headers for
+ * the same realm with the same digest algorithm.  The UAS is also
+ * supposed to send the headers in order of preference with the first one
+ * being the most preferred.
+ *
+ * We're supposed to send an Authorization header for the first one we
+ * encounter for a realm that we can support.
+ *
+ * The UAS can also send multiple realms, especially when it's a proxy
+ * that has forked the request in which case the proxy will aggregate all
+ * of the Authenticate and then them all back to the UAC.
+ *
+ * It doesn't stop there though... Each realm can require a different
+ * username from the others. There's also nothing preventing each digest
+ * algorithm from having a unique password although I'm not sure if
+ * that adds any benefit.
+ *
+ * So now... For each Authenticate header we encounter, we have to
+ * determine if we support the digest algorithm and, if not, just skip the
+ * header.  We then have to find an auth object that matches the realm AND
+ * the digest algorithm or find a wildcard object that matches the digest
+ * algorithm. If we find one, we add it to the results vector and read the
+ * next Authenticate header. If the next header is for the same realm AND
+ * we already added an auth object for that realm, we skip the header.
+ * Otherwise we repeat the process for the next header.
+ *
+ * In the end, we'll have accumulated a list of credentials we can pass to
+ * pjproject that it can use to add Authentication headers to a request.
+ *
+ * \NOTE: Neither we nor pjproject can currently handle digest algorithms
+ * other than MD5.  We don't even have a place for it in the ast_sip_auth
+ * object. For this reason, we just skip processing any Authenticate
+ * header that's not MD5.  When we support the others, we'll move the
+ * check into the loop that searches the objects.
+ */
+static pj_status_t set_outbound_authentication_credentials(pjsip_auth_clt_sess *auth_sess,
+		const struct ast_sip_auth_objects_vector *auth_objects_vector, pjsip_rx_data *challenge,
+		struct ast_str **realms)
 {
-	size_t auth_size = AST_VECTOR_SIZE(auth_vector);
-	struct ast_sip_auth **auths = ast_alloca(auth_size * sizeof(*auths));
-	pjsip_cred_info *auth_creds = ast_alloca(auth_size * sizeof(*auth_creds));
-	int res = 0;
 	int i;
+	size_t auth_object_count;
+	pjsip_www_authenticate_hdr *auth_hdr = NULL;
+	pj_status_t res = PJ_SUCCESS;
+	pjsip_hdr_e search_type;
+	size_t cred_count;
+	pjsip_cred_info *creds_array;
 
-	if (ast_sip_retrieve_auths(auth_vector, auths)) {
-		res = -1;
-		goto cleanup;
+	/*
+	 * Normally vector elements are pointers to something else, usually
+	 * structures. In this case however, the elements are the
+	 * structures themselves instead of pointers to them.  This is due
+	 * to the fact that pjsip_auth_clt_set_credentials() expects an
+	 * array of structues, not an array of pointers to structures.
+	 * Thankfully, vectors allow you to "steal" their underlying
+	 * arrays, in this case an array of pjsip_cred_info structures,
+	 * which we'll pass to pjsip_auth_clt_set_credentials() at the
+	 * end.
+	 */
+	AST_VECTOR(cred_info, pjsip_cred_info) auth_creds;
+
+	search_type = get_auth_search_type(challenge);
+	if (search_type == PJSIP_H_OTHER) {
+		/*
+		 * The status code on the response wasn't 401 or 407
+		 * so there are no WWW-Authenticate or Proxy-Authenticate
+		 * headers to process.
+		 */
+		return PJ_ENOTSUP;
 	}
 
-	for (i = 0; i < auth_size; ++i) {
-		if (ast_strlen_zero(auths[i]->realm)) {
-			auth_creds[i].realm = auth_hdr->challenge.common.realm;
+	auth_object_count = AST_VECTOR_SIZE(auth_objects_vector);
+	if (auth_object_count == 0) {
+		/* This shouldn't happen but we'll check anyway. */
+		return PJ_EINVAL;
+	}
+
+	/*
+	 * The number of pjsip_cred_infos we send to pjproject can
+	 * vary based on the number of acceptable headers received
+	 * and the number of acceptable auth objects on the endpoint
+	 * so we just use a vector to accumulate them.
+	 *
+	 * NOTE: You have to call AST_VECTOR_FREE() on the vector
+	 * but you don't have to free the elements because they're
+	 * actual structures, not pointers to structures.
+	 */
+	if (AST_VECTOR_INIT(&auth_creds, 5) != 0) {
+		return PJ_ENOMEM;
+	}
+
+	/*
+	 * It's going to be rare that we actually have more than one
+	 * WWW-Authentication header or more than one auth object to
+	 * match to it so the following nested loop should be fine.
+	 */
+	while ((auth_hdr = pjsip_msg_find_hdr(challenge->msg_info.msg,
+		search_type, auth_hdr ? auth_hdr->next : NULL))) {
+		int exact_match_index = -1;
+		int wildcard_match_index = -1;
+		int match_index = 0;
+		pjsip_cred_info auth_cred;
+		struct ast_sip_auth *auth = NULL;
+
+		memset(&auth_cred, 0, sizeof(auth_cred));
+		/*
+		 * Since we only support the MD5 algorithm at the current time,
+		 * there's no sense searching for auth objects that match the algorithm.
+		 * In fact, the auth_object structure doesn't even have a member
+		 * for it.
+		 *
+		 * When we do support more algorithms, this check will need to be
+		 * moved inside the auth object loop below.
+		 *
+		 * Note: The header may not have specified an algorithm at all in which
+		 * case it's assumed to be MD5. is_digest_algorithm_supported() returns
+		 * true for that case.
+		 */
+		if (!is_digest_algorithm_supported(auth_hdr)) {
+			ast_debug(3, "Skipping header with realm '%.*s' and unsupported '%.*s' algorithm \n",
+				(int)auth_hdr->challenge.digest.realm.slen, auth_hdr->challenge.digest.realm.ptr,
+				(int)auth_hdr->challenge.digest.algorithm.slen, auth_hdr->challenge.digest.algorithm.ptr);
+			continue;
+		}
+
+		/*
+		 * Appending the realms is strictly so digest_create_request_with_auth()
+		 * can display good error messages.  Since we only support one algorithm,
+		 * there can't be more than one header with the same realm.  No need to worry
+		 * about duplicate realms until then.
+		 */
+		if (*realms) {
+			ast_str_append(realms, 0, "%.*s, ",
+				(int)auth_hdr->challenge.digest.realm.slen, auth_hdr->challenge.digest.realm.ptr);
+		}
+
+		ast_debug(3, "Searching auths to find matching ones for header with realm '%.*s' and algorithm '%.*s'\n",
+			(int)auth_hdr->challenge.digest.realm.slen, auth_hdr->challenge.digest.realm.ptr,
+			(int)auth_hdr->challenge.digest.algorithm.slen, auth_hdr->challenge.digest.algorithm.ptr);
+
+		/*
+		 * Now that we have a valid header, we can loop over the auths available to
+		 * find either an exact realm match or, failing that, a wildcard auth (an
+		 * auth with an empty or "*" realm).
+		 *
+		 * NOTE: We never use the global default realm when we're the UAC responding
+		 * to a 401 or 407.  We only use that when we're the UAS (handled elsewhere)
+		 * and the auth object didn't have a realm.
+		 */
+		for (i = 0; i < auth_object_count; ++i) {
+			auth = AST_VECTOR_GET(auth_objects_vector, i);
+
+			/*
+			 * If this auth object's realm exactly matches the one
+			 * from the header, we can just break out and use it.
+			 *
+			 * NOTE: If there's more than one auth object for an endpoint with
+			 * a matching realm it's a misconfiguration.  We'll only use the first.
+			 */
+			if (pj_stricmp2(&auth_hdr->challenge.digest.realm, auth->realm) == 0) {
+				ast_debug(3, "Found matching auth '%s' with realm '%s'\n", ast_sorcery_object_get_id(auth),
+					auth->realm);
+				exact_match_index = i;
+				/*
+				 * If we found an exact realm match, there's no need to keep
+				 * looking for a wildcard.
+				 */
+				break;
+			}
+
+			/*
+			 * If this auth object's realm is empty or a "*", it's a wildcard
+			 * auth object.  We going to save its index but keep iterating over
+			 * the vector in case we find an exact match later.
+			 *
+			 * NOTE: If there's more than one wildcard auth object for an endpoint
+			 * it's a misconfiguration.  We'll only use the first.
+			 */
+			if (wildcard_match_index < 0
+				&& (ast_strlen_zero(auth->realm) || ast_strings_equal(auth->realm, "*"))) {
+				ast_debug(3, "Found wildcard auth '%s' for realm '%.*s'\n", ast_sorcery_object_get_id(auth),
+					(int)auth_hdr->challenge.digest.realm.slen, auth_hdr->challenge.digest.realm.ptr);
+				wildcard_match_index = i;
+			}
+		}
+
+		if (exact_match_index < 0 && wildcard_match_index < 0) {
+			/*
+			 * Didn't find either a wildcard or an exact realm match.
+			 * Move on to the next header.
+			 */
+			ast_debug(3, "No auth matching realm or no wildcard found for realm '%.*s'\n",
+				(int)auth_hdr->challenge.digest.realm.slen, auth_hdr->challenge.digest.realm.ptr);
+			continue;
+		}
+
+		if (exact_match_index >= 0) {
+			/*
+			 * If we found an exact match, we'll always prefer that.
+			 */
+			match_index = exact_match_index;
+			auth = AST_VECTOR_GET(auth_objects_vector, match_index);
+			ast_debug(3, "Using matched auth '%s' with realm '%.*s'\n", ast_sorcery_object_get_id(auth),
+				(int)auth_hdr->challenge.digest.realm.slen, auth_hdr->challenge.digest.realm.ptr);
 		} else {
-			pj_cstr(&auth_creds[i].realm, auths[i]->realm);
+			/*
+			 * We'll only use the wildcard if we didn't find an exact match.
+			 */
+			match_index = wildcard_match_index;
+			auth = AST_VECTOR_GET(auth_objects_vector, match_index);
+			ast_debug(3, "Using wildcard auth '%s' for realm '%.*s'\n", ast_sorcery_object_get_id(auth),
+				(int)auth_hdr->challenge.digest.realm.slen, auth_hdr->challenge.digest.realm.ptr);
 		}
-		pj_cstr(&auth_creds[i].username, auths[i]->auth_user);
-		pj_cstr(&auth_creds[i].scheme, "digest");
-		switch (auths[i]->type) {
+
+		/*
+		 * Copy the fields from the auth_object to the
+		 * pjsip_cred_info structure.
+		 */
+		auth_cred.realm = auth_hdr->challenge.common.realm;
+		pj_cstr(&auth_cred.username, auth->auth_user);
+		pj_cstr(&auth_cred.scheme, "digest");
+		switch (auth->type) {
 		case AST_SIP_AUTH_TYPE_USER_PASS:
-			pj_cstr(&auth_creds[i].data, auths[i]->auth_pass);
-			auth_creds[i].data_type = PJSIP_CRED_DATA_PLAIN_PASSWD;
+			pj_cstr(&auth_cred.data, auth->auth_pass);
+			auth_cred.data_type = PJSIP_CRED_DATA_PLAIN_PASSWD;
 			break;
 		case AST_SIP_AUTH_TYPE_MD5:
-			pj_cstr(&auth_creds[i].data, auths[i]->md5_creds);
-			auth_creds[i].data_type = PJSIP_CRED_DATA_DIGEST;
+			pj_cstr(&auth_cred.data, auth->md5_creds);
+			auth_cred.data_type = PJSIP_CRED_DATA_DIGEST;
 			break;
 		case AST_SIP_AUTH_TYPE_GOOGLE_OAUTH:
 			/* nothing to do. handled seperately in res_pjsip_outbound_registration */
 			break;
 		case AST_SIP_AUTH_TYPE_ARTIFICIAL:
-			ast_log(LOG_ERROR, "Trying to set artificial outbound auth credentials shouldn't happen.\n");
-			break;
+			ast_log(LOG_ERROR,
+				"Trying to set artificial outbound auth credentials shouldn't happen.\n");
+			continue;
+		} /* End auth object loop */
+
+		/*
+		 * Because the vector contains actual structures and not pointers
+		 * to structures, the call to AST_VECTOR_APPEND results in a simple
+		 * assign of one structure to another, effectively copying the auth_cred
+		 * structure contents to the array element.
+		 *
+		 * Also note that the calls to pj_cstr above set their respective
+		 * auth_cred fields to the _pointers_ of their corresponding auth
+		 * object fields.  This is safe because the call to
+		 * pjsip_auth_clt_set_credentials() below strdups them before we
+		 * return to the calling function which decrements the reference
+		 * counts.
+		 */
+		res = AST_VECTOR_APPEND(&auth_creds, auth_cred);
+		if (res != PJ_SUCCESS) {
+			res = PJ_ENOMEM;
+			goto cleanup;
 		}
+	} /* End header loop */
+
+	if (*realms && ast_str_strlen(*realms)) {
+		/*
+		 * Again, this is strictly so digest_create_request_with_auth()
+		 * can display good error messages.
+		 *
+		 * Chop off the trailing ", " on the last realm.
+		 */
+		ast_str_truncate(*realms, ast_str_strlen(*realms) - 2);
 	}
 
-	pjsip_auth_clt_set_credentials(auth_sess, auth_size, auth_creds);
+	if (AST_VECTOR_SIZE(&auth_creds) == 0) {
+		/* No matching auth objects were found. */
+		res = PJSIP_ENOCREDENTIAL;
+		goto cleanup;
+	}
+
+	/*
+	 * Here's where we steal the cred info structures from the vector.
+	 *
+	 * The steal effectively returns a pointer to the underlying
+	 * array of pjsip_cred_info structures which is exactly what we need
+	 * to pass to pjsip_auth_clt_set_credentials().
+	 *
+	 * <struct cred info><struct cred info>...<struct cred info>
+	 * ^pointer
+	 *
+	 * Since we stole the array from the vector, we have to free it ourselves.
+	 *
+	 * We also have to copy the size before we steal because stealing
+	 * resets the vector size to 0.
+	 */
+	cred_count = AST_VECTOR_SIZE(&auth_creds);
+	creds_array = AST_VECTOR_STEAL_ELEMENTS(&auth_creds);
+
+	res = pjsip_auth_clt_set_credentials(auth_sess, cred_count, creds_array);
+	ast_free(creds_array);
+	if (res == PJ_SUCCESS) {
+		ast_debug(3, "Set %"PRIu64" credentials in auth session\n", cred_count);
+	} else {
+		ast_log(LOG_ERROR, "Failed to set %"PRIu64" credentials in auth session\n", cred_count);
+	}
 
 cleanup:
-	ast_sip_cleanup_auths(auths, auth_size);
+	AST_VECTOR_FREE(&auth_creds);
 	return res;
 }
 
-static int digest_create_request_with_auth(const struct ast_sip_auth_vector *auths,
+/*!
+ * \internal
+ * \brief Create new tdata with auth based on original tdata
+ * \param auth_ids_vector  Vector of auth IDs retrieved from endpoint
+ * \param challenge rdata of the response from the UAS with challenge
+ * \param old_request tdata from the original request
+ * \param new_request tdata of the new request with the auth
+ *
+ * This function is what's registered with ast_sip_register_outbound_authenticator()
+ *
+ * \retval 0 success
+ * \retval -1 failure
+ */
+static int digest_create_request_with_auth(const struct ast_sip_auth_vector *auth_ids_vector,
 	pjsip_rx_data *challenge, pjsip_tx_data *old_request, pjsip_tx_data **new_request)
 {
 	pjsip_auth_clt_sess auth_sess;
 	pjsip_cseq_hdr *cseq;
 	pj_status_t status;
+	struct ast_sip_auth_objects_vector auth_objects_vector;
+	size_t auth_object_count = 0;
 	struct ast_sip_endpoint *endpoint;
 	char *id = NULL;
 	const char *id_type;
-	pjsip_www_authenticate_hdr *auth_hdr;
-	struct ast_str *realms;
+	struct ast_str *realms = NULL;
 	pjsip_dialog *dlg;
+	int res = -1;
+
+	/*
+	 * Some older compilers have an issue with initializing structures with
+	 * pjsip_auth_clt_sess auth_sess = { 0, };
+	 * so we'll just do it the old fashioned way.
+	 */
+	memset(&auth_sess, 0, sizeof(auth_sess));
 
 	dlg = pjsip_rdata_get_dlg(challenge);
 	if (dlg) {
+		/* The only thing we use endpoint for is to get an id for error/debug messages */
 		endpoint = ast_sip_dialog_get_endpoint(dlg);
 		id = endpoint ? ast_strdupa(ast_sorcery_object_get_id(endpoint)) : NULL;
 		ao2_cleanup(endpoint);
 		id_type = "Endpoint";
 	}
+
 	/* If there was no dialog, then this is probably a REGISTER so no endpoint */
 	if (!id) {
+		/* The only thing we use the address for is to get an id for error/debug messages */
 		id = ast_alloca(AST_SOCKADDR_BUFLEN);
 		pj_sockaddr_print(&challenge->pkt_info.src_addr, id, AST_SOCKADDR_BUFLEN, 3);
 		id_type = "Host";
 	}
 
-	auth_hdr = get_auth_header(challenge, NULL);
-	if (auth_hdr == NULL) {
-		ast_log(LOG_ERROR, "%s: '%s': Unable to find authenticate header in challenge.\n",
-			id_type, id);
+	if (!auth_ids_vector || AST_VECTOR_SIZE(auth_ids_vector) == 0) {
+		ast_log(LOG_ERROR, "%s: '%s': There were no auth ids available\n", id_type, id);
+		return -1;
+	}
+
+	if (AST_VECTOR_INIT(&auth_objects_vector, AST_VECTOR_SIZE(auth_ids_vector)) != 0) {
+		ast_log(LOG_ERROR, "%s: '%s': Couldn't initialize auth object vector\n", id_type, id);
 		return -1;
 	}
 
+	/*
+	 * We don't really care about ast_sip_retrieve_auths_vector()'s return code
+	 * because we're checking the count of objects in the vector.
+	 *
+	 * Don't forget to call
+	 * 	ast_sip_cleanup_auth_objects_vector(&auth_objects_vector);
+	 *  AST_VECTOR_FREE(&auth_objects_vector);
+	 * when you're done with the vector
+	 */
+	ast_sip_retrieve_auths_vector(auth_ids_vector, &auth_objects_vector);
+	auth_object_count = AST_VECTOR_SIZE(&auth_objects_vector);
+	if (auth_object_count == 0) {
+		/*
+		 * If none of the auth ids were found, we can't continue.
+		 * We're OK if there's at least one left.
+		 * ast_sip_retrieve_auths_vector() will print a warning for every
+		 * id that wasn't found.
+		 */
+		res = -1;
+		goto cleanup;
+	}
+
 	if (pjsip_auth_clt_init(&auth_sess, ast_sip_get_pjsip_endpoint(),
 				old_request->pool, 0) != PJ_SUCCESS) {
 		ast_log(LOG_ERROR, "%s: '%s': Failed to initialize client authentication session\n",
 			id_type, id);
-		return -1;
+		res = -1;
+		goto cleanup;
 	}
 
-	if (set_outbound_authentication_credentials(&auth_sess, auths, challenge, auth_hdr)) {
-		ast_log(LOG_WARNING, "%s: '%s': Failed to set authentication credentials\n",
-			id_type, id);
-#if defined(HAVE_PJSIP_AUTH_CLT_DEINIT)
-		/* In case it is not a noop here in the future. */
-		pjsip_auth_clt_deinit(&auth_sess);
-#endif
-		return -1;
+	/*
+	 * realms is used only for displaying good error messages.
+	 */
+	realms = ast_str_create(32);
+	if (!realms) {
+		res = -1;
+		goto cleanup;
 	}
 
+	/*
+	 * Load pjproject with the valid credentials for the Authentication headers
+	 * received on the 401 or 407 response.
+	 */
+	status = set_outbound_authentication_credentials(&auth_sess, &auth_objects_vector, challenge, &realms);
+	switch (status) {
+	case PJ_SUCCESS:
+		break;
+	case PJSIP_ENOCREDENTIAL:
+		ast_log(LOG_WARNING,
+			"%s: '%s': No auth objects matching realm(s) '%s' from challenge found.\n", id_type, id,
+			realms ? ast_str_buffer(realms) : "<none>");
+		res = -1;
+		goto cleanup;
+	default:
+		ast_log(LOG_WARNING, "%s: '%s': Failed to set authentication credentials\n", id_type, id);
+		res = -1;
+		goto cleanup;
+	}
+
+	/*
+	 * reinit_req actually creates the Authorization headers to send on
+	 * the next request.  If reinit_req already has a cached credential
+	 * from an earlier successful authorization, it'll use it. Otherwise
+	 * it'll create a new authorization and cache it.
+	 */
 	status = pjsip_auth_clt_reinit_req(&auth_sess, challenge, old_request, new_request);
-#if defined(HAVE_PJSIP_AUTH_CLT_DEINIT)
-	/* Release any cached auths */
-	pjsip_auth_clt_deinit(&auth_sess);
-#endif
 
 	switch (status) {
 	case PJ_SUCCESS:
@@ -167,22 +534,16 @@ static int digest_create_request_with_auth(const struct ast_sip_auth_vector *aut
 		cseq = pjsip_msg_find_hdr((*new_request)->msg, PJSIP_H_CSEQ, NULL);
 		ast_assert(cseq != NULL);
 		++cseq->cseq;
-		return 0;
+		res = 0;
+		goto cleanup;
 	case PJSIP_ENOCREDENTIAL:
-		realms = ast_str_create(32);
-		if (realms) {
-			ast_str_append(&realms, 0, "%.*s", (int)auth_hdr->challenge.common.realm.slen,
-				auth_hdr->challenge.common.realm.ptr);
-			while((auth_hdr = get_auth_header(challenge, auth_hdr->next))) {
-				ast_str_append(&realms, 0, ",%.*s", (int)auth_hdr->challenge.common.realm.slen,
-					auth_hdr->challenge.common.realm.ptr);
-			}
-		}
+		/*
+		 * This should be rare since set_outbound_authentication_credentials()
+		 * did the matching but you never know.
+		 */
 		ast_log(LOG_WARNING,
-			"%s: '%s': Unable to create request with auth. "
-			"No auth credentials for realm(s) '%s' in challenge.\n", id_type, id,
-			realms ? ast_str_buffer(realms) : "<unknown>");
-		ast_free(realms);
+			"%s: '%s': No auth objects matching realm(s) '%s' from challenge found.\n", id_type, id,
+			realms ? ast_str_buffer(realms) : "<none>");
 		break;
 	case PJSIP_EAUTHSTALECOUNT:
 		ast_log(LOG_WARNING,
@@ -198,8 +559,19 @@ static int digest_create_request_with_auth(const struct ast_sip_auth_vector *aut
 			id_type, id);
 		break;
 	}
+	res = -1;
+
+cleanup:
+#if defined(HAVE_PJSIP_AUTH_CLT_DEINIT)
+	/* Release any cached auths */
+	pjsip_auth_clt_deinit(&auth_sess);
+#endif
+
+	ast_sip_cleanup_auth_objects_vector(&auth_objects_vector);
+	AST_VECTOR_FREE(&auth_objects_vector);
+	ast_free(realms);
 
-	return -1;
+	return res;
 }
 
 static struct ast_sip_outbound_authenticator digest_authenticator = {
diff --git a/third-party/pjproject/patches/0090-Skip-unsupported-digest-algorithm-2408.patch b/third-party/pjproject/patches/0090-Skip-unsupported-digest-algorithm-2408.patch
new file mode 100644
index 0000000000000000000000000000000000000000..a2db2200e7c89f0708b1d71c30d6f133f98d4ad1
--- /dev/null
+++ b/third-party/pjproject/patches/0090-Skip-unsupported-digest-algorithm-2408.patch
@@ -0,0 +1,212 @@
+From bdbeb7c4b2b11efc2e59f5dee7aa4360a2bc9fff Mon Sep 17 00:00:00 2001
+From: sauwming <ming@teluu.com>
+Date: Thu, 22 Apr 2021 14:03:28 +0800
+Subject: [PATCH 90/90] Skip unsupported digest algorithm (#2408)
+
+Co-authored-by: Nanang Izzuddin <nanang@teluu.com>
+---
+ pjsip/src/pjsip/sip_auth_client.c             | 32 +++++--
+ tests/pjsua/scripts-sipp/uas-auth-two-algo.py |  7 ++
+ .../pjsua/scripts-sipp/uas-auth-two-algo.xml  | 83 +++++++++++++++++++
+ 3 files changed, 117 insertions(+), 5 deletions(-)
+ create mode 100644 tests/pjsua/scripts-sipp/uas-auth-two-algo.py
+ create mode 100644 tests/pjsua/scripts-sipp/uas-auth-two-algo.xml
+
+diff --git a/pjsip/src/pjsip/sip_auth_client.c b/pjsip/src/pjsip/sip_auth_client.c
+index 828b04db9..7eb2f5cd1 100644
+--- a/pjsip/src/pjsip/sip_auth_client.c
++++ b/pjsip/src/pjsip/sip_auth_client.c
+@@ -1042,7 +1042,7 @@ static pj_status_t process_auth( pj_pool_t *req_pool,
+     pjsip_hdr *hdr;
+     pj_status_t status;
+ 
+-    /* See if we have sent authorization header for this realm */
++    /* See if we have sent authorization header for this realm (and scheme) */
+     hdr = tdata->msg->hdr.next;
+     while (hdr != &tdata->msg->hdr) {
+ 	if ((hchal->type == PJSIP_H_WWW_AUTHENTICATE &&
+@@ -1052,7 +1052,8 @@ static pj_status_t process_auth( pj_pool_t *req_pool,
+ 	{
+ 	    sent_auth = (pjsip_authorization_hdr*) hdr;
+ 	    if (pj_stricmp(&hchal->challenge.common.realm,
+-			   &sent_auth->credential.common.realm )==0)
++			   &sent_auth->credential.common.realm)==0 &&
++		pj_stricmp(&hchal->scheme, &sent_auth->scheme)==0)
+ 	    {
+ 		/* If this authorization has empty response, remove it. */
+ 		if (pj_stricmp(&sent_auth->scheme, &pjsip_DIGEST_STR)==0 &&
+@@ -1062,6 +1063,14 @@ static pj_status_t process_auth( pj_pool_t *req_pool,
+ 		    hdr = hdr->next;
+ 		    pj_list_erase(sent_auth);
+ 		    continue;
++		} else
++		if (pj_stricmp(&sent_auth->scheme, &pjsip_DIGEST_STR)==0 &&
++		    pj_stricmp(&sent_auth->credential.digest.algorithm,
++		               &hchal->challenge.digest.algorithm)!=0)
++		{
++		    /* Same 'digest' scheme but different algo */
++		    hdr = hdr->next;
++		    continue;
+ 		} else {
+ 		    /* Found previous authorization attempt */
+ 		    break;
+@@ -1155,9 +1164,10 @@ PJ_DEF(pj_status_t) pjsip_auth_clt_reinit_req(	pjsip_auth_clt_sess *sess,
+ {
+     pjsip_tx_data *tdata;
+     const pjsip_hdr *hdr;
+-    unsigned chal_cnt;
++    unsigned chal_cnt, auth_cnt;
+     pjsip_via_hdr *via;
+     pj_status_t status;
++    pj_status_t last_auth_err;
+ 
+     PJ_ASSERT_RETURN(sess && rdata && old_request && new_request,
+ 		     PJ_EINVAL);
+@@ -1178,6 +1188,8 @@ PJ_DEF(pj_status_t) pjsip_auth_clt_reinit_req(	pjsip_auth_clt_sess *sess,
+      */
+     hdr = rdata->msg_info.msg->hdr.next;
+     chal_cnt = 0;
++    auth_cnt = 0;
++    last_auth_err = PJSIP_EAUTHNOAUTH;
+     while (hdr != &rdata->msg_info.msg->hdr) {
+ 	pjsip_cached_auth *cached_auth;
+ 	const pjsip_www_authenticate_hdr *hchal;
+@@ -1222,8 +1234,13 @@ PJ_DEF(pj_status_t) pjsip_auth_clt_reinit_req(	pjsip_auth_clt_sess *sess,
+ 	 */
+ 	status = process_auth(tdata->pool, hchal, tdata->msg->line.req.uri,
+ 			      tdata, sess, cached_auth, &hauth);
+-	if (status != PJ_SUCCESS)
+-	    return status;
++	if (status != PJ_SUCCESS) {
++	    last_auth_err = status;
++
++	    /* Process next header. */
++	    hdr = hdr->next;
++	    continue;
++	}
+ 
+ 	if (pj_pool_get_used_size(cached_auth->pool) >
+ 	    PJSIP_AUTH_CACHED_POOL_MAX_SIZE) 
+@@ -1236,12 +1253,17 @@ PJ_DEF(pj_status_t) pjsip_auth_clt_reinit_req(	pjsip_auth_clt_sess *sess,
+ 
+ 	/* Process next header. */
+ 	hdr = hdr->next;
++	auth_cnt++;
+     }
+ 
+     /* Check if challenge is present */
+     if (chal_cnt == 0)
+ 	return PJSIP_EAUTHNOCHAL;
+ 
++    /* Check if any authorization header has been created */
++    if (auth_cnt == 0)
++	return last_auth_err;
++
+     /* Remove branch param in Via header. */
+     via = (pjsip_via_hdr*) pjsip_msg_find_hdr(tdata->msg, PJSIP_H_VIA, NULL);
+     via->branch_param.slen = 0;
+diff --git a/tests/pjsua/scripts-sipp/uas-auth-two-algo.py b/tests/pjsua/scripts-sipp/uas-auth-two-algo.py
+new file mode 100644
+index 000000000..c79c9f6d3
+--- /dev/null
++++ b/tests/pjsua/scripts-sipp/uas-auth-two-algo.py
+@@ -0,0 +1,7 @@
++# $Id$
++#
++import inc_const as const
++
++PJSUA = ["--null-audio --max-calls=1 --id=sip:a@localhost --username=a --realm=* --registrar=$SIPP_URI"]
++
++PJSUA_EXPECTS = [[0, "registration success", ""]]
+diff --git a/tests/pjsua/scripts-sipp/uas-auth-two-algo.xml b/tests/pjsua/scripts-sipp/uas-auth-two-algo.xml
+new file mode 100644
+index 000000000..bd4871940
+--- /dev/null
++++ b/tests/pjsua/scripts-sipp/uas-auth-two-algo.xml
+@@ -0,0 +1,83 @@
++<?xml version="1.0" encoding="ISO-8859-1" ?>
++<!DOCTYPE scenario SYSTEM "sipp.dtd">
++
++<scenario name="Basic UAS responder">
++  <recv request="REGISTER" crlf="true">
++  </recv>
++
++  <send>
++    <![CDATA[
++      SIP/2.0 100 Trying
++      [last_Via:];received=1.1.1.1;rport=1111
++      [last_From:]
++      [last_To:];tag=[call_number]
++      [last_Call-ID:]
++      [last_CSeq:]
++      Content-Length: 0
++    ]]>
++  </send>
++
++  <send>
++    <![CDATA[
++      SIP/2.0 401 Unauthorized
++      [last_Via:];received=1.1.1.1;rport=1111
++      [last_From:]
++      [last_To:];tag=[call_number]
++      [last_Call-ID:]
++      [last_CSeq:]
++      WWW-Authenticate: Digest realm="sip.linphone.org", nonce="PARV4gAAAADgw3asAADW8zsi5BEAAAAA", opaque="+GNywA==", algorithm=SHA-256, qop="auth"
++      WWW-Authenticate: Digest realm="sip.linphone.org", nonce="PARV4gAAAADgw3asAADW8zsi5BEAAAAA", opaque="+GNywA==", algorithm=MD5, qop="auth"
++      WWW-Authenticate: Digest realm="sip.linphone.org", nonce="PARV4gAAAADgw3asAADW8zsi5BEAAAAA", opaque="+GNywA==", algorithm=MD2, qop="auth"
++      Content-Length: 0
++    ]]>
++  </send>
++
++  <recv request="REGISTER" crlf="true">
++    <action>
++      <ereg regexp=".*"
++            search_in="hdr"
++	    header="Authorization:"
++	    assign_to="have_auth" />
++    </action>
++  </recv>
++
++  <nop next="resp_okay" test="have_auth" />
++  
++  <send next="end">
++    <![CDATA[
++      SIP/2.0 403 no auth
++      [last_Via:];received=1.1.1.1;rport=1111
++      [last_From:]
++      [last_To:];tag=[call_number]
++      [last_Call-ID:]
++      [last_CSeq:]
++      [last_Contact:]
++      Content-Length: 0
++    ]]>
++  </send>
++
++  <label id="resp_okay" />
++  
++  <send>
++    <![CDATA[
++      SIP/2.0 200 OK
++      [last_Via:];received=1.1.1.1;rport=1111
++      [last_From:]
++      [last_To:];tag=[call_number]
++      [last_Call-ID:]
++      [last_CSeq:]
++      [last_Contact:]
++      Content-Length: 0
++    ]]>
++  </send>
++
++  <label id="end" />
++
++  <!-- definition of the response time repartition table (unit is ms)   -->
++  <ResponseTimeRepartition value="10, 20, 30, 40, 50, 100, 150, 200"/>
++
++  <!-- definition of the call length repartition table (unit is ms)     -->
++  <CallLengthRepartition value="10, 50, 100, 500, 1000, 5000, 10000"/>
++
++</scenario>
++
+-- 
+2.31.1
+