mirror of
https://github.com/asterisk/asterisk.git
synced 2025-10-22 12:52:33 +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