mirror of
				https://github.com/asterisk/asterisk.git
				synced 2025-10-31 02:37:10 +00:00 
			
		
		
		
	Add SHA-256 and SHA-512-256 as authentication digest algorithms
* Refactored pjproject code to support the new algorithms and
added a patch file to third-party/pjproject/patches
* Added new parameters to the pjsip auth object:
  * password_digest = <algorithm>:<digest>
  * supported_algorithms_uac = List of algorithms to support
    when acting as a UAC.
  * supported_algorithms_uas = List of algorithms to support
    when acting as a UAS.
  See the auth object in pjsip.conf.sample for detailed info.
* Updated both res_pjsip_authenticator_digest.c (for UAS) and
res_pjsip_outbound_authentocator_digest.c (UAC) to suport the
new algorithms.
The new algorithms are only available with the bundled version
of pjproject, or an external version > 2.14.1.  OpenSSL version
1.1.1 or greater is required to support SHA-512-256.
Resolves: #948
UserNote: The SHA-256 and SHA-512-256 algorithms are now available
for authentication as both a UAS and a UAC.
			
			
This commit is contained in:
		| @@ -16,6 +16,14 @@ | ||||
|  * at the top of the source tree. | ||||
|  */ | ||||
|  | ||||
| /*! | ||||
|  * \file | ||||
|  * \brief PJSIP UAC Authentication | ||||
|  * | ||||
|  * This module handles authentication when Asterisk is the UAC. | ||||
|  * | ||||
|  */ | ||||
|  | ||||
| /*** MODULEINFO | ||||
| 	<depend>pjproject</depend> | ||||
| 	<depend>res_pjsip</depend> | ||||
| @@ -32,10 +40,6 @@ | ||||
| #include "asterisk/strings.h" | ||||
| #include "asterisk/vector.h" | ||||
|  | ||||
| pj_str_t supported_digest_algorithms[] = { | ||||
| 	{ "MD5", 3} | ||||
| }; | ||||
|  | ||||
| /*! | ||||
|  * \internal | ||||
|  * \brief Determine proper authenticate header | ||||
| @@ -59,27 +63,240 @@ static pjsip_hdr_e get_auth_search_type(pjsip_rx_data *challenge) | ||||
|  | ||||
| /*! | ||||
|  * \internal | ||||
|  * \brief Determine if digest algorithm in the header is one we support | ||||
|  * \brief Determine if digest algorithm in the header is one supported by | ||||
|  * pjproject and OpenSSL. | ||||
|  */ | ||||
| static const pjsip_auth_algorithm *get_supported_algorithm(pjsip_www_authenticate_hdr *auth_hdr) | ||||
| { | ||||
| 	const pjsip_auth_algorithm *algo = NULL; | ||||
|  | ||||
| 	algo = ast_sip_auth_get_algorithm_by_iana_name(&auth_hdr->challenge.digest.algorithm); | ||||
| 	if (!algo) { | ||||
| 		return NULL; | ||||
| 	} | ||||
|  | ||||
| 	if (ast_sip_auth_is_algorithm_supported(algo->algorithm_type)) { | ||||
| 		return algo; | ||||
| 	} | ||||
| 	return NULL; | ||||
| } | ||||
|  | ||||
| AST_VECTOR(cred_info_vector, pjsip_cred_info); | ||||
|  | ||||
| /*! | ||||
|  * \brief Get credentials (if any) from auth objects for a WWW/Proxy-Authenticate header | ||||
|  * | ||||
|  * \retval 1 If we support the algorithm | ||||
|  * \retval 0 If we do not | ||||
|  * \param id                   For logging | ||||
|  * \param src_name             For logging | ||||
|  * \param auth_hdr             The *-Authenticate header to check | ||||
|  * \param auth_object_count    The number of auth objects available | ||||
|  * \param auth_objects_vector  The vector of available auth objects | ||||
|  * \param auth_creds           The vector to store the credentials in | ||||
|  * \param realms               For logging | ||||
|  * | ||||
|  */ | ||||
| static int is_digest_algorithm_supported(pjsip_www_authenticate_hdr *auth_hdr) | ||||
| static void get_creds_for_header(const char *id, const char *src_name, | ||||
| 	pjsip_www_authenticate_hdr *auth_hdr, size_t auth_object_count, | ||||
| 	const struct ast_sip_auth_objects_vector *auth_objects_vector, | ||||
| 	struct cred_info_vector *auth_creds, struct ast_str **realms) | ||||
| { | ||||
| 	int digest; | ||||
| 	int exact_match_index = -1; | ||||
| 	int wildcard_match_index = -1; | ||||
| 	struct ast_sip_auth *found_auth = NULL; | ||||
| 	const pjsip_auth_algorithm *challenge_algorithm = | ||||
| 		get_supported_algorithm(auth_hdr); | ||||
| 	int i = 0; | ||||
| 	pjsip_cred_info auth_cred; | ||||
| 	const char *cred_data; | ||||
| 	int res = 0; | ||||
| 	SCOPE_ENTER(4, "%s:%s: Testing header realm: '" PJSTR_PRINTF_SPEC "' algorithm: '" | ||||
| 		PJSTR_PRINTF_SPEC "'\n", id, src_name, | ||||
| 		PJSTR_PRINTF_VAR(auth_hdr->challenge.digest.realm), | ||||
| 		PJSTR_PRINTF_VAR(auth_hdr->challenge.digest.algorithm)); | ||||
|  | ||||
| 	/* An empty digest is assumed to be md5 */ | ||||
| 	if (pj_strlen(&auth_hdr->challenge.digest.algorithm) == 0) { | ||||
| 		return 1; | ||||
| 	if (!challenge_algorithm) { | ||||
| 		SCOPE_EXIT_RTN("%s:%s: Skipping header with realm '" PJSTR_PRINTF_SPEC "' " | ||||
| 			"and unsupported " PJSTR_PRINTF_SPEC "' algorithm \n", id, src_name, | ||||
| 			PJSTR_PRINTF_VAR(auth_hdr->challenge.digest.realm), | ||||
| 			PJSTR_PRINTF_VAR(auth_hdr->challenge.digest.algorithm)); | ||||
| 	} | ||||
|  | ||||
| 	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; | ||||
| 	/* | ||||
| 	 * If we already have credentials for this realm, we don't need to | ||||
| 	 * process this header.  We can just skip it. | ||||
| 	 */ | ||||
| 	for (i = 0; i < AST_VECTOR_SIZE(auth_creds); i++) { | ||||
| 		pjsip_cred_info auth_cred = AST_VECTOR_GET(auth_creds, i); | ||||
| 		if (pj_stricmp(&auth_cred.realm, &auth_hdr->challenge.common.realm) == 0) { | ||||
| 			SCOPE_EXIT_RTN("%s:%s: Skipping header with realm '" PJSTR_PRINTF_SPEC "' " | ||||
| 				"because we already have credentials for it\n", id, src_name, | ||||
| 				PJSTR_PRINTF_VAR(auth_hdr->challenge.digest.realm)); | ||||
| 		} | ||||
| 	} | ||||
| 	return 0; | ||||
|  | ||||
| 	/* | ||||
| 	 * Appending "realm/agorithm" to realms is strictly so | ||||
| 	 * digest_create_request_with_auth() can display good error messages. | ||||
| 	 */ | ||||
| 	if (*realms) { | ||||
| 		ast_str_append(realms, 0, PJSTR_PRINTF_SPEC "/" PJSTR_PRINTF_SPEC ", ", | ||||
| 			PJSTR_PRINTF_VAR(auth_hdr->challenge.digest.realm), | ||||
| 			PJSTR_PRINTF_VAR(auth_hdr->challenge.digest.algorithm)); | ||||
| 	} | ||||
|  | ||||
| 	/* | ||||
| 	 * 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. | ||||
| 	 */ | ||||
| 	ast_trace(-1, "%s:%s: Searching %zu auths to find matching ones for header with realm " | ||||
| 		"'" PJSTR_PRINTF_SPEC "' and algorithm '" PJSTR_PRINTF_SPEC "'\n", | ||||
| 		id, src_name, auth_object_count, | ||||
| 		PJSTR_PRINTF_VAR(auth_hdr->challenge.digest.realm), | ||||
| 		PJSTR_PRINTF_VAR(auth_hdr->challenge.digest.algorithm)); | ||||
|  | ||||
| 	for (i = 0; i < auth_object_count; ++i) { | ||||
| 		struct ast_sip_auth *auth = AST_VECTOR_GET(auth_objects_vector, i); | ||||
| 		const char *auth_id = ast_sorcery_object_get_id(auth); | ||||
| 		SCOPE_ENTER(5, "%s:%s: Checking auth '%s' with realm '%s'\n", | ||||
| 			id, src_name, auth_id, auth->realm); | ||||
|  | ||||
| 		/* | ||||
| 		 * Is the challenge algorithm in the auth's supported_algorithms_uac | ||||
| 		 * and is there either a plain text password or a password_digest | ||||
| 		 * for the algorithm? | ||||
| 		 */ | ||||
| 		if (!ast_sip_auth_is_algorithm_available(auth, &auth->supported_algorithms_uac, | ||||
| 			challenge_algorithm->algorithm_type)) { | ||||
| 			SCOPE_EXIT_EXPR(continue, "%s:%s: Skipping auth '%s' with realm '%s' because it doesn't support " | ||||
| 				" algorithm '" PJSTR_PRINTF_SPEC "'\n", id, src_name, | ||||
| 				auth_id, auth->realm, | ||||
| 				PJSTR_PRINTF_VAR(challenge_algorithm->iana_name)); | ||||
| 		} | ||||
|  | ||||
| 		/* | ||||
| 		 * 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) { | ||||
| 			exact_match_index = i; | ||||
| 			/* | ||||
| 			 * If we found an exact realm match, there's no need to keep | ||||
| 			 * looking for a wildcard. | ||||
| 			 */ | ||||
| 			SCOPE_EXIT_EXPR(break, "%s:%s: Found matching auth '%s' with realm '%s'\n", | ||||
| 				id, src_name, auth_id, auth->realm); | ||||
| 		} | ||||
|  | ||||
| 		/* | ||||
| 		 * 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_trace(-1, "%s:%s: Found wildcard auth '%s' for realm '" PJSTR_PRINTF_SPEC "'\n", | ||||
| 				id, src_name, auth_id, | ||||
| 				PJSTR_PRINTF_VAR(auth_hdr->challenge.digest.realm)); | ||||
| 			wildcard_match_index = i; | ||||
| 		} | ||||
| 		SCOPE_EXIT("%s:%s: Done checking auth '%s' with realm '%s'. " | ||||
| 			"Found exact? %s  Found wildcard? %s\n", id, src_name, | ||||
| 			auth_id, auth->realm, exact_match_index >= 0 ? "yes" : "no", | ||||
| 				wildcard_match_index >= 0 ? "yes" : "no"); | ||||
| 	} /* End auth object loop */ | ||||
|  | ||||
| 	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. | ||||
| 		 */ | ||||
| 		SCOPE_EXIT_RTN("%s:%s: No auth matching realm or no wildcard found for realm '" PJSTR_PRINTF_SPEC "'\n", | ||||
| 			id, src_name, PJSTR_PRINTF_VAR(auth_hdr->challenge.digest.realm)); | ||||
| 	} | ||||
|  | ||||
| 	if (exact_match_index >= 0) { | ||||
| 		/* | ||||
| 		 * If we found an exact match, we'll always prefer that. | ||||
| 		 */ | ||||
| 		found_auth = AST_VECTOR_GET(auth_objects_vector, exact_match_index); | ||||
| 		ast_trace(-1, "%s:%s: Using matched auth '%s' with realm '" PJSTR_PRINTF_SPEC "'\n", | ||||
| 			id, src_name, ast_sorcery_object_get_id(found_auth), | ||||
| 			PJSTR_PRINTF_VAR(auth_hdr->challenge.digest.realm)); | ||||
| 	} else { | ||||
| 		/* | ||||
| 		 * We'll only use the wildcard if we didn't find an exact match. | ||||
| 		 */ | ||||
| 		found_auth = AST_VECTOR_GET(auth_objects_vector, wildcard_match_index); | ||||
| 		ast_trace(-1, "%s:%s: Using wildcard auth '%s' for realm '" PJSTR_PRINTF_SPEC "'\n", | ||||
| 			id, src_name, ast_sorcery_object_get_id(found_auth), | ||||
| 			PJSTR_PRINTF_VAR(auth_hdr->challenge.digest.realm)); | ||||
| 	} | ||||
|  | ||||
| 	/* | ||||
| 	 * Now that we have an auth object to use, we need to create a | ||||
| 	 * pjsip_cred_info structure for each algorithm we support. | ||||
| 	 */ | ||||
|  | ||||
| 	memset(&auth_cred, 0, sizeof(auth_cred)); | ||||
| 	/* | ||||
| 	 * 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, found_auth->auth_user); | ||||
| 	pj_cstr(&auth_cred.scheme, "digest"); | ||||
|  | ||||
| 	/* | ||||
| 	 * auth_cred.data_type tells us whether the credential is a plain text | ||||
| 	 * password or a pre-digested one. | ||||
| 	 */ | ||||
| 	cred_data = SCOPE_CALL_WITH_RESULT(-1, const char *, ast_sip_auth_get_creds, | ||||
| 		found_auth, challenge_algorithm->algorithm_type, &auth_cred.data_type); | ||||
| 	/* | ||||
| 	 * This can't really fail because we already called | ||||
| 	 * ast_sip_auth_is_algorithm_available() for the auth | ||||
| 	 * but we check anyway. | ||||
| 	 */ | ||||
| 	if (!cred_data) { | ||||
| 		SCOPE_EXIT_RTN("%s:%s: Shouldn't have happened\n", id, src_name); | ||||
| 	} | ||||
|  | ||||
| 	pj_cstr(&auth_cred.data, cred_data); | ||||
| #ifdef HAVE_PJSIP_AUTH_NEW_DIGESTS | ||||
| 	if (auth_cred.data_type == PJSIP_CRED_DATA_DIGEST) { | ||||
| 		auth_cred.algorithm_type = challenge_algorithm->algorithm_type; | ||||
| 	} | ||||
| #endif | ||||
| 	/* | ||||
| 	 * 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); | ||||
| 	SCOPE_EXIT_RTN("%s:%s: %s credential for realm: '" PJSTR_PRINTF_SPEC "' algorithm: '" | ||||
| 		PJSTR_PRINTF_SPEC "'\n", id, src_name, | ||||
| 		res == 0 ? "Added" : "Failed to add", | ||||
| 		PJSTR_PRINTF_VAR(auth_hdr->challenge.digest.realm), | ||||
| 		PJSTR_PRINTF_VAR(auth_hdr->challenge.digest.algorithm)); | ||||
| } | ||||
|  | ||||
| /*! | ||||
| @@ -89,7 +306,7 @@ static int is_digest_algorithm_supported(pjsip_www_authenticate_hdr *auth_hdr) | ||||
|  * 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 | ||||
|  * 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. | ||||
| @@ -99,14 +316,14 @@ static int is_digest_algorithm_supported(pjsip_www_authenticate_hdr *auth_hdr) | ||||
|  * | ||||
|  * 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. | ||||
|  * of the Authenticate headers into one response 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 | ||||
|  * So now... For each WWW/Proxy-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 | ||||
| @@ -115,27 +332,22 @@ static int is_digest_algorithm_supported(pjsip_www_authenticate_hdr *auth_hdr) | ||||
|  * 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. | ||||
|  * In the end, we'll have accumulated a list of credentials, one per realm, | ||||
|  * we can pass to pjproject that it can use to add Authentication headers | ||||
|  * to a request. | ||||
|  */ | ||||
| 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) | ||||
| static pj_status_t set_auth_creds(const char *id, pjsip_auth_clt_sess *auth_sess, | ||||
| 	const struct ast_sip_auth_objects_vector *auth_objects_vector, | ||||
| 	pjsip_rx_data *challenge, struct ast_str **realms) | ||||
| { | ||||
| 	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; | ||||
| 	size_t cred_count = 0; | ||||
| 	pjsip_cred_info *creds_array; | ||||
|  | ||||
| 	char *pj_err = NULL; | ||||
| 	const char *src_name = challenge->pkt_info.src_name; | ||||
| 	/* | ||||
| 	 * Normally vector elements are pointers to something else, usually | ||||
| 	 * structures. In this case however, the elements are the | ||||
| @@ -147,7 +359,8 @@ static pj_status_t set_outbound_authentication_credentials(pjsip_auth_clt_sess * | ||||
| 	 * which we'll pass to pjsip_auth_clt_set_credentials() at the | ||||
| 	 * end. | ||||
| 	 */ | ||||
| 	AST_VECTOR(cred_info, pjsip_cred_info) auth_creds; | ||||
| 	struct cred_info_vector auth_creds; | ||||
| 	SCOPE_ENTER(3, "%s:%s\n", id, src_name); | ||||
|  | ||||
| 	search_type = get_auth_search_type(challenge); | ||||
| 	if (search_type == PJSIP_H_OTHER) { | ||||
| @@ -156,13 +369,14 @@ static pj_status_t set_outbound_authentication_credentials(pjsip_auth_clt_sess * | ||||
| 		 * so there are no WWW-Authenticate or Proxy-Authenticate | ||||
| 		 * headers to process. | ||||
| 		 */ | ||||
| 		return PJ_ENOTSUP; | ||||
| 		SCOPE_EXIT_RTN_VALUE(PJ_ENOTSUP, "%s:%s: Status code %d was received when it should have been 401 or 407.\n", | ||||
| 		id, src_name, challenge->msg_info.msg->line.status.code); | ||||
| 	} | ||||
|  | ||||
| 	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; | ||||
| 		SCOPE_EXIT_RTN_VALUE(PJ_EINVAL, "%s:%s No auth objects available\n", id, src_name); | ||||
| 	} | ||||
|  | ||||
| 	/* | ||||
| @@ -176,183 +390,29 @@ static pj_status_t set_outbound_authentication_credentials(pjsip_auth_clt_sess * | ||||
| 	 * actual structures, not pointers to structures. | ||||
| 	 */ | ||||
| 	if (AST_VECTOR_INIT(&auth_creds, 5) != 0) { | ||||
| 		return PJ_ENOMEM; | ||||
| 		SCOPE_EXIT_RTN_VALUE(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. | ||||
| 	 * There may be multiple WWW/Proxy-Authenticate headers each one having | ||||
| 	 * a different realm/algorithm pair. Test each to see if we have credentials | ||||
| 	 * for it and accumulate them in the auth_creds vector. | ||||
| 	 * The code doesn't really care but just for reference, RFC-7616 says | ||||
| 	 * a UAS can't send multiple headers for the same realm with the same | ||||
| 	 * algorithm.  It also says the UAS should send the headers in order | ||||
| 	 * of preference with the first one being the most preferred. | ||||
| 	 */ | ||||
| 	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; | ||||
| 		} | ||||
| 		get_creds_for_header(id, src_name, auth_hdr, auth_object_count, | ||||
| 			auth_objects_vector, &auth_creds, realms); | ||||
|  | ||||
| 		/* | ||||
| 		 * 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 { | ||||
| 			/* | ||||
| 			 * 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); | ||||
| 		} | ||||
|  | ||||
| 		/* | ||||
| 		 * 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_cred.data, auth->auth_pass); | ||||
| 			auth_cred.data_type = PJSIP_CRED_DATA_PLAIN_PASSWD; | ||||
| 			break; | ||||
| 		case AST_SIP_AUTH_TYPE_MD5: | ||||
| 			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"); | ||||
| 			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. | ||||
| 		 * Chop off the trailing ", " on the last realm-algorithm. | ||||
| 		 */ | ||||
| 		ast_str_truncate(*realms, ast_str_strlen(*realms) - 2); | ||||
| 	} | ||||
| @@ -383,15 +443,15 @@ static pj_status_t set_outbound_authentication_credentials(pjsip_auth_clt_sess * | ||||
|  | ||||
| 	res = pjsip_auth_clt_set_credentials(auth_sess, cred_count, creds_array); | ||||
| 	ast_free(creds_array); | ||||
| 	if (res == PJ_SUCCESS) { | ||||
| 		ast_debug(3, "Set %zu credentials in auth session\n", cred_count); | ||||
| 	} else { | ||||
| 		ast_log(LOG_ERROR, "Failed to set %zu credentials in auth session\n", cred_count); | ||||
| 	} | ||||
|  | ||||
| cleanup: | ||||
| 	AST_VECTOR_FREE(&auth_creds); | ||||
| 	return res; | ||||
| 	if (res != PJ_SUCCESS) { | ||||
| 		pj_err = ast_alloca(PJ_ERR_MSG_SIZE); | ||||
| 		pj_strerror(res, pj_err, PJ_ERR_MSG_SIZE); | ||||
| 	} | ||||
| 	SCOPE_EXIT_RTN_VALUE(res, "%s:%s: Set %zu credentials in auth session: %s\n", | ||||
| 		id, src_name, cred_count, S_OR(pj_err, "success")); | ||||
| } | ||||
|  | ||||
| /*! | ||||
| @@ -415,12 +475,23 @@ static int digest_create_request_with_auth(const struct ast_sip_auth_vector *aut | ||||
| 	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_dialog *dlg = pjsip_rdata_get_dlg(challenge); | ||||
| 	struct ast_sip_endpoint *endpoint = (dlg ? ast_sip_dialog_get_endpoint(dlg) : NULL); | ||||
| 	/* | ||||
| 	 * We're ast_strdupa'ing the endpoint id because we're going to | ||||
| 	 * clean up the endpoint immediately after this. We only needed | ||||
| 	 * it to get the id for logging. | ||||
| 	 */ | ||||
| 	char *endpoint_id = endpoint ? ast_strdupa(ast_sorcery_object_get_id(endpoint)) : NULL; | ||||
| 	char *id = endpoint_id ?: "noendpoint"; | ||||
| 	char *src_name = challenge->pkt_info.src_name; | ||||
| 	struct ast_str *realms = NULL; | ||||
| 	pjsip_dialog *dlg; | ||||
| 	int res = -1; | ||||
| 	char *pj_err = NULL; | ||||
| 	SCOPE_ENTER(3, "%s:%s\n", id, src_name); | ||||
|  | ||||
| 	/* We only needed endpoint to get the id */ | ||||
| 	ao2_cleanup(endpoint); | ||||
|  | ||||
| 	/* | ||||
| 	 * Some older compilers have an issue with initializing structures with | ||||
| @@ -429,31 +500,18 @@ static int digest_create_request_with_auth(const struct ast_sip_auth_vector *aut | ||||
| 	 */ | ||||
| 	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"; | ||||
| 	} | ||||
|  | ||||
| 	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); | ||||
| 		SCOPE_EXIT_LOG_RTN_VALUE(-1, LOG_ERROR, "%s:%s: There were no auth ids available\n", | ||||
| 			id, src_name); | ||||
| 		return -1; | ||||
| 	} | ||||
|  | ||||
| 	/* | ||||
| 	 * auth_ids_vector contains only ids but we need the complete objects. | ||||
| 	 */ | ||||
| 	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; | ||||
| 		SCOPE_EXIT_LOG_RTN_VALUE(-1, LOG_ERROR, "%s:%s: Couldn't initialize auth object vector\n", | ||||
| 			id, src_name); | ||||
| 	} | ||||
|  | ||||
| 	/* | ||||
| @@ -465,6 +523,8 @@ static int digest_create_request_with_auth(const struct ast_sip_auth_vector *aut | ||||
| 	 *  AST_VECTOR_FREE(&auth_objects_vector); | ||||
| 	 * when you're done with the vector | ||||
| 	 */ | ||||
| 	ast_trace(-1, "%s:%s: Retrieving %d auth objects\n", id, src_name, | ||||
| 		(int)AST_VECTOR_SIZE(auth_ids_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) { | ||||
| @@ -475,13 +535,19 @@ static int digest_create_request_with_auth(const struct ast_sip_auth_vector *aut | ||||
| 		 * id that wasn't found. | ||||
| 		 */ | ||||
| 		res = -1; | ||||
| 		ast_trace(-1, "%s:%s: No auth objects found\n", id, src_name); | ||||
| 		goto cleanup; | ||||
| 	} | ||||
| 	ast_trace(-1, "%s:%s: Retrieved %d auth objects\n", id, src_name, | ||||
| 		(int)auth_object_count); | ||||
|  | ||||
| 	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); | ||||
| 	status = pjsip_auth_clt_init(&auth_sess, ast_sip_get_pjsip_endpoint(), | ||||
| 		old_request->pool, 0); | ||||
| 	if (status != PJ_SUCCESS) { | ||||
| 		pj_err = ast_alloca(PJ_ERR_MSG_SIZE); | ||||
| 		pj_strerror(status, pj_err, PJ_ERR_MSG_SIZE); | ||||
| 		ast_log(LOG_ERROR, "%s:%s: Failed to initialize client authentication session: %s\n", | ||||
| 			id, src_name, pj_err); | ||||
| 		res = -1; | ||||
| 		goto cleanup; | ||||
| 	} | ||||
| @@ -499,18 +565,24 @@ static int digest_create_request_with_auth(const struct ast_sip_auth_vector *aut | ||||
| 	 * 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); | ||||
| 	status = SCOPE_CALL_WITH_RESULT(-1, pj_status_t, set_auth_creds, id, &auth_sess, &auth_objects_vector, challenge, &realms); | ||||
| 	if (status != PJ_SUCCESS && status != PJSIP_ENOCREDENTIAL) { | ||||
| 		pj_err = ast_alloca(PJ_ERR_MSG_SIZE); | ||||
| 	} | ||||
|  | ||||
| 	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>"); | ||||
| 			"%s:%s: No auth objects matching realm/algorithm(s) '%s' from challenge found.\n", | ||||
| 			id, src_name, 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); | ||||
| 		pj_strerror(status, pj_err, PJ_ERR_MSG_SIZE); | ||||
| 		ast_log(LOG_WARNING, "%s:%s: Failed to set authentication credentials: %s\n", | ||||
| 			id, src_name, pj_err); | ||||
| 		res = -1; | ||||
| 		goto cleanup; | ||||
| 	} | ||||
| @@ -521,7 +593,11 @@ static int digest_create_request_with_auth(const struct ast_sip_auth_vector *aut | ||||
| 	 * 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); | ||||
| 	status = SCOPE_CALL_WITH_RESULT(-1, pj_status_t, pjsip_auth_clt_reinit_req, | ||||
| 		&auth_sess, challenge, old_request, new_request); | ||||
| 	if (status != PJ_SUCCESS) { | ||||
| 		pj_err = ast_alloca(PJ_ERR_MSG_SIZE); | ||||
| 	} | ||||
|  | ||||
| 	switch (status) { | ||||
| 	case PJ_SUCCESS: | ||||
| @@ -535,6 +611,7 @@ static int digest_create_request_with_auth(const struct ast_sip_auth_vector *aut | ||||
| 		ast_assert(cseq != NULL); | ||||
| 		++cseq->cseq; | ||||
| 		res = 0; | ||||
| 		ast_trace(-1, "%s:%s: Created new request with auth\n", id, src_name); | ||||
| 		goto cleanup; | ||||
| 	case PJSIP_ENOCREDENTIAL: | ||||
| 		/* | ||||
| @@ -542,21 +619,24 @@ static int digest_create_request_with_auth(const struct ast_sip_auth_vector *aut | ||||
| 		 * did the matching but you never know. | ||||
| 		 */ | ||||
| 		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>"); | ||||
| 			"%s:%s: No auth objects matching realm(s) '%s' from challenge found.\n", | ||||
| 			id, src_name, realms ? ast_str_buffer(realms) : "<none>"); | ||||
| 		break; | ||||
| 	case PJSIP_EAUTHSTALECOUNT: | ||||
| 		pj_strerror(status, pj_err, PJ_ERR_MSG_SIZE); | ||||
| 		ast_log(LOG_WARNING, | ||||
| 			"%s: '%s': Unable to create request with auth.  Number of stale retries exceeded.\n", | ||||
| 			id_type, id); | ||||
| 			"%s:%s: Unable to create request with auth: %s\n", | ||||
| 			id, src_name, pj_err); | ||||
| 		break; | ||||
| 	case PJSIP_EFAILEDCREDENTIAL: | ||||
| 		ast_log(LOG_WARNING, "%s: '%s': Authentication credentials not accepted by server.\n", | ||||
| 			id_type, id); | ||||
| 		pj_strerror(status, pj_err, PJ_ERR_MSG_SIZE); | ||||
| 		ast_log(LOG_WARNING, "%s:%s: Authentication credentials not accepted by server. %s\n", | ||||
| 			id, src_name, pj_err); | ||||
| 		break; | ||||
| 	default: | ||||
| 		ast_log(LOG_WARNING, "%s: '%s': Unable to create request with auth. Unknown failure.\n", | ||||
| 			id_type, id); | ||||
| 		pj_strerror(status, pj_err, PJ_ERR_MSG_SIZE); | ||||
| 		ast_log(LOG_WARNING, "%s:%s: Unable to create request with auth: %s\n", | ||||
| 			id, src_name, pj_err); | ||||
| 		break; | ||||
| 	} | ||||
| 	res = -1; | ||||
| @@ -573,7 +653,8 @@ cleanup: | ||||
| 	AST_VECTOR_FREE(&auth_objects_vector); | ||||
| 	ast_free(realms); | ||||
|  | ||||
| 	return res; | ||||
| 	SCOPE_EXIT_RTN_VALUE(res, "%s:%s: result: %s\n", id, src_name, | ||||
| 		res == 0 ? "success" : "failure"); | ||||
| } | ||||
|  | ||||
| static struct ast_sip_outbound_authenticator digest_authenticator = { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user