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:
George Joseph
2024-10-17 08:02:08 -06:00
parent dd1e5065ba
commit a0987672f0
15 changed files with 1784 additions and 571 deletions

View File

@@ -315,6 +315,26 @@ static char *handle_pjproject_show_buildopts(struct ast_cli_entry *e, int cmd, s
ast_cli(a->fd, "%s\n", AST_VECTOR_GET(&buildopts, i));
}
#ifdef HAVE_PJSIP_AUTH_NEW_DIGESTS
{
struct ast_str *buf = ast_str_alloca(256);
for (i = PJSIP_AUTH_ALGORITHM_NOT_SET + 1; i < PJSIP_AUTH_ALGORITHM_COUNT; i++) {
const pjsip_auth_algorithm *algorithm = pjsip_auth_get_algorithm_by_type(i);
if (!ast_strlen_zero(algorithm->openssl_name)) {
if (pjsip_auth_is_algorithm_supported(i)) {
ast_str_append(&buf, 0, "%.*s/%s, ", (int)algorithm->iana_name.slen,
algorithm->iana_name.ptr, algorithm->openssl_name);
}
}
}
/* Trim off the trailing ", " */
ast_str_truncate(buf, -2);
ast_cli(a->fd, "Supported Digest Algorithms (IANA name/OpenmSSL name): %s\n", ast_str_buffer(buf));
}
#else
ast_cli(a->fd, "Supported Digest Algorithms (IANA name/OpenmSSL name): MD5/MD5\n");
#endif
return CLI_SUCCESS;
}

View File

@@ -24,13 +24,106 @@
#include "asterisk/logger.h"
#include "asterisk/sorcery.h"
#include "asterisk/cli.h"
#include "asterisk/vector.h"
#include "include/res_pjsip_private.h"
#include "asterisk/res_pjsip_cli.h"
#ifndef HAVE_PJSIP_AUTH_NEW_DIGESTS
/*
* These are needed if the version of pjproject in use
* does not have the new digests.
* NOTE: We don't support AKA but we need to specify
* it to be compatible with the pjproject definition.
*/
#ifdef HAVE_OPENSSL
#include "openssl/md5.h"
#include "openssl/sha.h"
#else
#define MD5_DIGEST_LENGTH 16
#define SHA256_DIGEST_LENGTH 32
#endif
const pjsip_auth_algorithm pjsip_auth_algorithms[] = {
/* TYPE IANA name OpenSSL name */
/* Raw digest byte length Hex representation length */
{ PJSIP_AUTH_ALGORITHM_NOT_SET, {"", 0}, "",
0, 0},
{ PJSIP_AUTH_ALGORITHM_MD5, {"MD5", 3}, "MD5",
MD5_DIGEST_LENGTH, MD5_DIGEST_LENGTH * 2},
{ PJSIP_AUTH_ALGORITHM_SHA256, {"SHA-256", 7}, "SHA256",
SHA256_DIGEST_LENGTH, SHA256_DIGEST_LENGTH * 2},
{ PJSIP_AUTH_ALGORITHM_SHA512_256, {"SHA-512-256", 11}, "SHA512-256",
SHA256_DIGEST_LENGTH, SHA256_DIGEST_LENGTH * 2},
{ PJSIP_AUTH_ALGORITHM_AKAV1_MD5, {"AKAv1-MD5", 9}, "",
MD5_DIGEST_LENGTH, MD5_DIGEST_LENGTH * 2},
{ PJSIP_AUTH_ALGORITHM_AKAV1_MD5, {"AKAv2-MD5", 9}, "",
MD5_DIGEST_LENGTH, MD5_DIGEST_LENGTH * 2},
{ PJSIP_AUTH_ALGORITHM_COUNT, {"", 0}, "",
0, 0},
};
#endif
const pjsip_auth_algorithm *ast_sip_auth_get_algorithm_by_type(
pjsip_auth_algorithm_type algorithm_type)
{
#ifdef HAVE_PJSIP_AUTH_NEW_DIGESTS
return pjsip_auth_get_algorithm_by_type(algorithm_type);
#else
/*
* If we don't have a pjproject with the new algorithms, the
* only one we support is MD5.
*/
if (algorithm_type == PJSIP_AUTH_ALGORITHM_MD5) {
return &pjsip_auth_algorithms[algorithm_type];
}
return NULL;
#endif
}
const pjsip_auth_algorithm *ast_sip_auth_get_algorithm_by_iana_name(
const pj_str_t *iana_name)
{
#ifdef HAVE_PJSIP_AUTH_NEW_DIGESTS
return pjsip_auth_get_algorithm_by_iana_name(iana_name);
#else
if (!iana_name) {
return NULL;
}
/*
* If we don't have a pjproject with the new algorithms, the
* only one we support is MD5. If iana_name is empty (but not NULL),
* the default is MD5.
*/
if (iana_name->slen == 0 || pj_stricmp2(iana_name, "MD5") == 0) {
return &pjsip_auth_algorithms[PJSIP_AUTH_ALGORITHM_MD5];
}
return NULL;
#endif
}
pj_bool_t ast_sip_auth_is_algorithm_supported(
pjsip_auth_algorithm_type algorithm_type)
{
#ifdef HAVE_PJSIP_AUTH_NEW_DIGESTS
return pjsip_auth_is_algorithm_supported(algorithm_type);
#else
return algorithm_type == PJSIP_AUTH_ALGORITHM_MD5;
#endif
}
static void auth_destroy(void *obj)
{
struct ast_sip_auth *auth = obj;
int i = 0;
ast_string_field_free_memory(auth);
for (i = PJSIP_AUTH_ALGORITHM_NOT_SET + 1; i < PJSIP_AUTH_ALGORITHM_COUNT; i++) {
ast_free(auth->password_digests[i]);
}
AST_VECTOR_FREE(&auth->supported_algorithms_uac);
AST_VECTOR_FREE(&auth->supported_algorithms_uas);
}
static void *auth_alloc(const char *name)
@@ -56,6 +149,8 @@ static int auth_type_handler(const struct aco_option *opt, struct ast_variable *
auth->type = AST_SIP_AUTH_TYPE_USER_PASS;
} else if (!strcasecmp(var->value, "md5")) {
auth->type = AST_SIP_AUTH_TYPE_MD5;
} else if (!strcasecmp(var->value, "digest")) {
auth->type = AST_SIP_AUTH_TYPE_DIGEST;
} else if (!strcasecmp(var->value, "google_oauth")) {
#ifdef HAVE_PJSIP_OAUTH_AUTHENTICATION
auth->type = AST_SIP_AUTH_TYPE_GOOGLE_OAUTH;
@@ -74,6 +169,7 @@ static int auth_type_handler(const struct aco_option *opt, struct ast_variable *
static const char *auth_types_map[] = {
[AST_SIP_AUTH_TYPE_USER_PASS] = "userpass",
[AST_SIP_AUTH_TYPE_MD5] = "md5",
[AST_SIP_AUTH_TYPE_DIGEST] = "digest",
[AST_SIP_AUTH_TYPE_GOOGLE_OAUTH] = "google_oauth"
};
@@ -90,43 +186,300 @@ static int auth_type_to_str(const void *obj, const intptr_t *args, char **buf)
return 0;
}
static int auth_apply(const struct ast_sorcery *sorcery, void *obj)
int ast_sip_auth_digest_algorithms_vector_init(const char *id,
struct pjsip_auth_algorithm_type_vector *algorithms, const char *agent_type,
const char *value)
{
struct ast_sip_auth *auth = obj;
char *iana_names = ast_strdupa(value);
pj_str_t val;
int res = 0;
if (ast_strlen_zero(auth->auth_user)) {
ast_log(LOG_ERROR, "No authentication username for auth '%s'\n",
ast_sorcery_object_get_id(auth));
ast_assert(algorithms != NULL);
if (AST_VECTOR_SIZE(algorithms)) {
AST_VECTOR_FREE(algorithms);
}
if (AST_VECTOR_INIT(algorithms, 4)) {
return -1;
}
switch (auth->type) {
case AST_SIP_AUTH_TYPE_MD5:
if (ast_strlen_zero(auth->md5_creds)) {
ast_log(LOG_ERROR, "'md5' authentication specified but no md5_cred "
"specified for auth '%s'\n", ast_sorcery_object_get_id(auth));
res = -1;
} else if (strlen(auth->md5_creds) != PJSIP_MD5STRLEN) {
ast_log(LOG_ERROR, "'md5' authentication requires digest of size '%d', but "
"digest is '%d' in size for auth '%s'\n", PJSIP_MD5STRLEN, (int)strlen(auth->md5_creds),
ast_sorcery_object_get_id(auth));
res = -1;
while ((val.ptr = ast_strip(strsep(&iana_names, ",")))) {
const pjsip_auth_algorithm *algo;
if (ast_strlen_zero(val.ptr)) {
continue;
}
break;
case AST_SIP_AUTH_TYPE_GOOGLE_OAUTH:
val.slen = strlen(val.ptr);
algo = ast_sip_auth_get_algorithm_by_iana_name(&val);
if (!algo) {
ast_log(LOG_WARNING, "%s: Unknown %s digest algorithm '%s' specified\n",
id, agent_type, val.ptr);
res = -1;
continue;
}
if (!ast_sip_auth_is_algorithm_supported(algo->algorithm_type)) {
ast_log(LOG_WARNING, "%s: %s digest algorithm '%s' is not supported by the version of OpenSSL in use\n",
id, agent_type, val.ptr);
res = -1;
continue;
}
if (AST_VECTOR_APPEND(algorithms, algo->algorithm_type)) {
AST_VECTOR_FREE(algorithms);
return -1;
}
}
return res;
}
static int uac_algorithms_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
{
struct ast_sip_auth *auth = obj;
return ast_sip_auth_digest_algorithms_vector_init(ast_sorcery_object_get_id(auth),
&auth->supported_algorithms_uac, "UAC", var->value);
}
static int uas_algorithms_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
{
struct ast_sip_auth *auth = obj;
return ast_sip_auth_digest_algorithms_vector_init(ast_sorcery_object_get_id(auth),
&auth->supported_algorithms_uas, "UAS", var->value);
}
int ast_sip_auth_digest_algorithms_vector_to_str(
const struct pjsip_auth_algorithm_type_vector *algorithms, char **buf)
{
struct ast_str *str = NULL;
int i = 0;
if (!algorithms || !AST_VECTOR_SIZE(algorithms)) {
return 0;
}
str = ast_str_alloca(256);
if (!str) {
return -1;
}
for (i = 0; i < AST_VECTOR_SIZE(algorithms); ++i) {
const pjsip_auth_algorithm *algo = ast_sip_auth_get_algorithm_by_type(
AST_VECTOR_GET(algorithms, i));
ast_str_append(&str, 0, "%s" PJSTR_PRINTF_SPEC, i > 0 ? "," : "",
PJSTR_PRINTF_VAR(algo->iana_name));
}
*buf = ast_strdup(ast_str_buffer(str));
return *buf ? 0 : -1;
}
static int uac_algorithms_to_str(const void *obj, const intptr_t *args, char **buf)
{
const struct ast_sip_auth *auth = obj;
return ast_sip_auth_digest_algorithms_vector_to_str(&auth->supported_algorithms_uac, buf);
}
static int uas_algorithms_to_str(const void *obj, const intptr_t *args, char **buf)
{
const struct ast_sip_auth *auth = obj;
return ast_sip_auth_digest_algorithms_vector_to_str(&auth->supported_algorithms_uas, buf);
}
static int password_digest_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
{
struct ast_sip_auth *auth = obj;
const char *auth_name = ast_sorcery_object_get_id(auth);
char *value = ast_strdupa(var->value);
char *unparsed_digest = NULL;
while ((unparsed_digest = ast_strsep(&value, ',', AST_STRSEP_TRIM))) {
const pjsip_auth_algorithm *algo;
char *iana_name;
char *digest;
struct ast_sip_auth_password_digest *pw;
pj_str_t pj_iana_name;
if (ast_strlen_zero(unparsed_digest)) {
continue;
}
if (strchr(unparsed_digest, ':') != NULL) {
iana_name = ast_strsep(&unparsed_digest, ':', AST_STRSEP_TRIM);
} else {
/*
* md5_cred doesn't have the algorithm name in front
* so we need to force it.
*/
iana_name = "MD5";
}
digest = unparsed_digest;
pj_iana_name = pj_str(iana_name);
algo = ast_sip_auth_get_algorithm_by_iana_name(&pj_iana_name);
if (!algo) {
ast_log(LOG_WARNING, "%s: Unknown password_digest algorithm '%s' specified\n",
auth_name, iana_name);
return -1;
}
if (!ast_sip_auth_is_algorithm_supported(algo->algorithm_type)) {
ast_log(LOG_WARNING, "%s: password_digest algorithm '%s' is not supported by the version of OpenSSL in use\n",
auth_name, iana_name);
return -1;
}
if (strlen(digest) != algo->digest_str_length) {
ast_log(LOG_WARNING, "%s: password_digest algorithm '%s' length (%d) must be %d\n",
auth_name, iana_name, (int)strlen(digest), (int)algo->digest_str_length);
return -1;
}
pw = ast_calloc(1, sizeof(*pw) + strlen(digest) + 1);
if (!pw) {
return -1;
}
pw->algorithm_type = algo->algorithm_type;
strcpy(pw->digest, digest); /* Safe */
auth->password_digests[pw->algorithm_type] = pw;
}
return 0;
}
static int password_digest_to_str(const void *obj, const intptr_t *args, char **buf)
{
const struct ast_sip_auth *auth = obj;
struct ast_str *str = ast_str_alloca(256);
int i = 0;
int count = 0;
for (i = PJSIP_AUTH_ALGORITHM_NOT_SET + 1; i < PJSIP_AUTH_ALGORITHM_COUNT; i++) {
struct ast_sip_auth_password_digest *pw =
auth->password_digests[i];
const pjsip_auth_algorithm *algorithm;
if (!pw) {
continue;
}
algorithm = ast_sip_auth_get_algorithm_by_type(pw->algorithm_type);
ast_str_append(&str, 0, "%s" PJSTR_PRINTF_SPEC ":%s", count > 0 ? "," : "",
PJSTR_PRINTF_VAR(algorithm->iana_name), pw->digest);
count++;
}
*buf = ast_strdup(ast_str_buffer(str));
return 0;
}
static int md5cred_to_str(const void *obj, const intptr_t *args, char **buf)
{
const struct ast_sip_auth *auth = obj;
if (auth->password_digests[PJSIP_AUTH_ALGORITHM_MD5]) {
*buf = ast_strdup(auth->password_digests[PJSIP_AUTH_ALGORITHM_MD5]->digest);
}
return 0;
}
int ast_sip_auth_is_algorithm_available(const struct ast_sip_auth *auth,
const struct pjsip_auth_algorithm_type_vector *algorithms,
pjsip_auth_algorithm_type algorithm_type)
{
int i;
if (!algorithms) {
return 0;
}
for (i = 0; i < AST_VECTOR_SIZE(algorithms); ++i) {
if (AST_VECTOR_GET(algorithms, i) == algorithm_type) {
if (auth->password_digests[algorithm_type] || !ast_strlen_zero(auth->auth_pass)) {
return 1;
}
}
}
return 0;
}
const char *ast_sip_auth_get_creds(const struct ast_sip_auth *auth,
const pjsip_auth_algorithm_type algorithm_type, int *cred_type)
{
struct ast_sip_auth_password_digest *pw_digest =
auth->password_digests[algorithm_type];
if (pw_digest) {
*cred_type = PJSIP_CRED_DATA_DIGEST;
return pw_digest->digest;
}
*cred_type = PJSIP_CRED_DATA_PLAIN_PASSWD;
return auth->auth_pass;
}
static int check_algorithm(const struct ast_sip_auth *auth,
const pjsip_auth_algorithm_type algorithm_type, const char *which_supported)
{
const pjsip_auth_algorithm *algo = ast_sip_auth_get_algorithm_by_type(algorithm_type);
struct ast_sip_auth_password_digest *pw_digest =
auth->password_digests[algorithm_type];
if (!pw_digest && ast_strlen_zero(auth->auth_pass)) {
ast_log(LOG_ERROR, "%s: No plain text or digest password found for algorithm "
PJSTR_PRINTF_SPEC " in supported_algorithms_%s\n",
ast_sorcery_object_get_id(auth), PJSTR_PRINTF_VAR(algo->iana_name), which_supported);
return -1;
}
return 0;
}
static int auth_apply(const struct ast_sorcery *sorcery, void *obj)
{
struct ast_sip_auth *auth = obj;
const char *id = ast_sorcery_object_get_id(auth);
int i = 0;
int res = 0;
if (ast_strlen_zero(auth->auth_user)) {
ast_log(LOG_ERROR, "%s: No authentication username\n", id);
return -1;
}
if (auth->type == AST_SIP_AUTH_TYPE_GOOGLE_OAUTH) {
if (ast_strlen_zero(auth->refresh_token)
|| ast_strlen_zero(auth->oauth_clientid)
|| ast_strlen_zero(auth->oauth_secret)) {
ast_log(LOG_ERROR, "'google_oauth' authentication specified but refresh_token,"
" oauth_clientid, or oauth_secret not specified for auth '%s'\n",
ast_sorcery_object_get_id(auth));
ast_log(LOG_ERROR, "%s: 'google_oauth' authentication specified but refresh_token,"
" oauth_clientid, or oauth_secret not specified\n", id);
res = -1;
}
break;
case AST_SIP_AUTH_TYPE_USER_PASS:
case AST_SIP_AUTH_TYPE_ARTIFICIAL:
break;
return res;
}
if (AST_VECTOR_SIZE(&auth->supported_algorithms_uas) == 0) {
char *default_algo_uas = ast_alloca(AST_SIP_AUTH_MAX_SUPPORTED_ALGORITHMS_LENGTH + 1);
ast_sip_get_default_auth_algorithms_uas(default_algo_uas, AST_SIP_AUTH_MAX_SUPPORTED_ALGORITHMS_LENGTH);
ast_sip_auth_digest_algorithms_vector_init(id, &auth->supported_algorithms_uas, "UAS", default_algo_uas);
}
if (AST_VECTOR_SIZE(&auth->supported_algorithms_uac) == 0) {
char *default_algo_uac = ast_alloca(AST_SIP_AUTH_MAX_SUPPORTED_ALGORITHMS_LENGTH + 1);
ast_sip_get_default_auth_algorithms_uac(default_algo_uac, AST_SIP_AUTH_MAX_SUPPORTED_ALGORITHMS_LENGTH);
ast_sip_auth_digest_algorithms_vector_init(id, &auth->supported_algorithms_uac, "UAC", default_algo_uac);
}
for (i = 0; i < AST_VECTOR_SIZE(&auth->supported_algorithms_uas); i++) {
res += check_algorithm(auth, AST_VECTOR_GET(&auth->supported_algorithms_uas, i), "uas");
}
for (i = 0; i < AST_VECTOR_SIZE(&auth->supported_algorithms_uac); i++) {
res += check_algorithm(auth, AST_VECTOR_GET(&auth->supported_algorithms_uac, i), "uac");
}
return res;
@@ -366,6 +719,18 @@ static struct ast_cli_entry cli_commands[] = {
static struct ast_sip_cli_formatter_entry *cli_formatter;
#if 1
static void global_loaded(const char *object_type)
{
ast_sorcery_force_reload_object(ast_sip_get_sorcery(), "auth");
}
/*! \brief Observer which is used to update our interval and default_realm when the global setting changes */
static struct ast_sorcery_observer global_observer = {
.loaded = global_loaded,
};
#endif
/*! \brief Initialize sorcery with auth support */
int ast_sip_initialize_sorcery_auth(void)
{
@@ -389,14 +754,20 @@ int ast_sip_initialize_sorcery_auth(void)
"", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_auth, oauth_clientid));
ast_sorcery_object_field_register(sorcery, SIP_SORCERY_AUTH_TYPE, "oauth_secret",
"", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_auth, oauth_secret));
ast_sorcery_object_field_register(sorcery, SIP_SORCERY_AUTH_TYPE, "md5_cred",
"", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_auth, md5_creds));
ast_sorcery_object_field_register_custom(sorcery, SIP_SORCERY_AUTH_TYPE, "md5_cred",
NULL, password_digest_handler, md5cred_to_str, NULL, 0, 0);
ast_sorcery_object_field_register(sorcery, SIP_SORCERY_AUTH_TYPE, "realm",
"", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_auth, realm));
ast_sorcery_object_field_register(sorcery, SIP_SORCERY_AUTH_TYPE, "nonce_lifetime",
"32", OPT_UINT_T, 0, FLDSET(struct ast_sip_auth, nonce_lifetime));
ast_sorcery_object_field_register_custom(sorcery, SIP_SORCERY_AUTH_TYPE, "auth_type",
"userpass", auth_type_handler, auth_type_to_str, NULL, 0, 0);
ast_sorcery_object_field_register_custom(sorcery, SIP_SORCERY_AUTH_TYPE, "password_digest",
NULL, password_digest_handler, password_digest_to_str, NULL, 0, 0);
ast_sorcery_object_field_register_custom(sorcery, SIP_SORCERY_AUTH_TYPE, "supported_algorithms_uac",
"", uac_algorithms_handler, uac_algorithms_to_str, NULL, 0, 0);
ast_sorcery_object_field_register_custom(sorcery, SIP_SORCERY_AUTH_TYPE, "supported_algorithms_uas",
"", uas_algorithms_handler, uas_algorithms_to_str, NULL, 0, 0);
ast_sip_register_endpoint_formatter(&endpoint_auth_formatter);
@@ -420,11 +791,14 @@ int ast_sip_initialize_sorcery_auth(void)
return -1;
}
ast_sorcery_observer_add(sorcery, "global", &global_observer);
return 0;
}
int ast_sip_destroy_sorcery_auth(void)
{
ast_sorcery_observer_remove(ast_sip_get_sorcery(), "global", &global_observer);
ast_cli_unregister_multiple(cli_commands, ARRAY_LEN(cli_commands));
ast_sip_unregister_cli_formatter(cli_formatter);
ast_sip_unregister_endpoint_formatter(&endpoint_auth_formatter);

View File

@@ -55,6 +55,8 @@
#define DEFAULT_TASKPROCESSOR_OVERLOAD_TRIGGER TASKPROCESSOR_OVERLOAD_TRIGGER_GLOBAL
#define DEFAULT_NOREFERSUB 1
#define DEFAULT_ALL_CODECS_ON_EMPTY_REINVITE 0
#define DEFAULT_AUTH_ALGORITHMS_UAS "MD5"
#define DEFAULT_AUTH_ALGORITHMS_UAC "MD5"
/*!
* \brief Cached global config object
@@ -83,6 +85,10 @@ struct global_config {
AST_STRING_FIELD(default_voicemail_extension);
/*! Realm to use in challenges before an endpoint is identified */
AST_STRING_FIELD(default_realm);
/*! Default authentication algorithms for UAS */
AST_STRING_FIELD(default_auth_algorithms_uas);
/*! Default authentication algorithms for UAC */
AST_STRING_FIELD(default_auth_algorithms_uac);
);
/*! Value to put in Max-Forwards header */
unsigned int max_forwards;
@@ -188,6 +194,8 @@ static int global_apply(const struct ast_sorcery *sorcery, void *obj)
{
struct global_config *cfg = obj;
char max_forwards[10];
struct pjsip_auth_algorithm_type_vector algorithms;
int res = 0;
if (ast_strlen_zero(cfg->debug)) {
ast_log(LOG_ERROR,
@@ -211,6 +219,25 @@ static int global_apply(const struct ast_sorcery *sorcery, void *obj)
return -1;
}
AST_VECTOR_INIT(&algorithms, 4);
res = ast_sip_auth_digest_algorithms_vector_init("global",
&algorithms, "UAS", cfg->default_auth_algorithms_uas);
AST_VECTOR_FREE(&algorithms);
if (res) {
ast_log(LOG_WARNING, "global: Invalid values in default_auth_algorithms_uas. "
"Defaulting to %s\n", DEFAULT_AUTH_ALGORITHMS_UAS);
ast_string_field_set(cfg, default_auth_algorithms_uas, DEFAULT_AUTH_ALGORITHMS_UAS);
}
AST_VECTOR_INIT(&algorithms, 4);
res = ast_sip_auth_digest_algorithms_vector_init("global",
&algorithms, "UAC", cfg->default_auth_algorithms_uac);
AST_VECTOR_FREE(&algorithms);
if (res) {
ast_log(LOG_WARNING, "global: Invalid values in default_auth_algorithms_uac. "
"Defaulting to %s\n", DEFAULT_AUTH_ALGORITHMS_UAC);
ast_string_field_set(cfg, default_auth_algorithms_uac, DEFAULT_AUTH_ALGORITHMS_UAC);
}
ao2_t_global_obj_replace_unref(global_cfg, cfg, "Applying global settings");
return 0;
}
@@ -391,6 +418,32 @@ void ast_sip_get_default_realm(char *realm, size_t size)
}
}
void ast_sip_get_default_auth_algorithms_uas(char *default_auth_algorithms_uas, size_t size)
{
struct global_config *cfg;
cfg = get_global_cfg();
if (!cfg) {
ast_copy_string(default_auth_algorithms_uas, DEFAULT_AUTH_ALGORITHMS_UAS, size);
} else {
ast_copy_string(default_auth_algorithms_uas, cfg->default_auth_algorithms_uas, size);
ao2_ref(cfg, -1);
}
}
void ast_sip_get_default_auth_algorithms_uac(char *default_auth_algorithms_uac, size_t size)
{
struct global_config *cfg;
cfg = get_global_cfg();
if (!cfg) {
ast_copy_string(default_auth_algorithms_uac, DEFAULT_AUTH_ALGORITHMS_UAC, size);
} else {
ast_copy_string(default_auth_algorithms_uac, cfg->default_auth_algorithms_uac, size);
ao2_ref(cfg, -1);
}
}
void ast_sip_get_default_from_user(char *from_user, size_t size)
{
struct global_config *cfg;
@@ -765,10 +818,17 @@ int ast_sip_initialize_sorcery_global(void)
ast_sorcery_object_field_register(sorcery, "global", "all_codecs_on_empty_reinvite",
DEFAULT_ALL_CODECS_ON_EMPTY_REINVITE ? "yes" : "no",
OPT_BOOL_T, 1, FLDSET(struct global_config, all_codecs_on_empty_reinvite));
ast_sorcery_object_field_register(sorcery, "global", "default_auth_algorithms_uas",
DEFAULT_AUTH_ALGORITHMS_UAS, OPT_STRINGFIELD_T, 0,
STRFLDSET(struct global_config, default_auth_algorithms_uas));
ast_sorcery_object_field_register(sorcery, "global", "default_auth_algorithms_uac",
DEFAULT_AUTH_ALGORITHMS_UAC, OPT_STRINGFIELD_T, 0,
STRFLDSET(struct global_config, default_auth_algorithms_uac));
if (ast_sorcery_instance_observer_add(sorcery, &observer_callbacks_global)) {
return -1;
}
ast_sorcery_load_object(ast_sip_get_sorcery(), "global");
return 0;
}

View File

@@ -1558,92 +1558,138 @@
</configOption>
</configObject>
<configObject name="auth">
<!--
Be sure to update the following documentation page when making changes to this object:
https://docs.asterisk.org/Configuration/Channel-Drivers/SIP/Configuring-res_pjsip/PJSIP-Authentication
-->
<synopsis>Authentication type</synopsis>
<description><para>
Authentication objects hold the authentication information for use
by other objects such as <literal>endpoints</literal> or <literal>registrations</literal>.
This also allows for multiple objects to use a single auth object. See
the <literal>auth_type</literal> config option for password style choices.
</para></description>
<configOption name="auth_type" default="userpass">
the <literal>auth_type</literal> config option for security mechanism choices.
</para>
<note><para>
See the link below for detailed discussion of this object especially concerning
realms and digest hash algorithms.
</para>
<para>
https://docs.asterisk.org/Configuration/Channel-Drivers/SIP/Configuring-res_pjsip/PJSIP-Authentication
</para>
</note>
</description>
<see-also>
<ref type="link">https://docs.asterisk.org/Configuration/Channel-Drivers/SIP/Configuring-res_pjsip/PJSIP-Authentication</ref>
</see-also>
<configOption name="auth_type" default="digest">
<synopsis>Authentication type</synopsis>
<description><para>
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. The following values are valid:
If set to <literal>google_oauth</literal> then we'll read from the
refresh_token/oauth_clientid/oauth_secret parameters.
If set to <literal>digest</literal> then we'll read from the
<literal>password</literal> and/or <literal>password_digest</literal>
parameters. The older <literal>md5</literal> and <literal>userpass</literal>
values are deprecated and converted to <literal>digest</literal>.
</para>
<enumlist>
<enum name="md5"/>
<enum name="userpass"/>
<enum name="google_oauth"/>
<enum name="userpass"><para>Deprecated. Use <literal>digest</literal>.</para></enum>
<enum name="md5"><para>Deprecated. Use <literal>digest</literal>.</para></enum>
<enum name="google_oauth"><para>If selected, the <literal>refresh_token</literal>,
<literal>oauth_clientid</literal> and <literal>oauth_secret</literal>
parameters must be provided.</para></enum>
<enum name="digest"><para>If selected, the <literal>password</literal>
and/or one or more <literal>password_digest</literal>
parameters must be provided.</para></enum>
</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">
<synopsis>Lifetime of a nonce associated with this authentication config.</synopsis>
</configOption>
<configOption name="md5_cred" default="">
<synopsis>MD5 Hash used for authentication.</synopsis>
<description><para>
Only used when auth_type is <literal>md5</literal>.
As an alternative to specifying a plain text password,
you can hash the username, realm and password
together one time and place the hash value here.
The input to the hash function must be in the
following format:
</para>
<para>
</para>
<para>
&lt;username&gt;:&lt;realm&gt;:&lt;password&gt;
</para>
<para>
</para>
<para>
For incoming authentication (asterisk is the server),
the realm must match either the realm set in this object
or the <variable>default_realm</variable> set in in the
<replaceable>global</replaceable> object.
</para>
<para>
</para>
<para>
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. You can't use pre-hashed
passwords with a wildcard auth object.
You can generate the hash with the following shell
command:
</para>
<para>
</para>
<para>
$ echo -n "myname:myrealm:mypassword" | md5sum
</para>
<para>
</para>
<para>
Note the '-n'. You don't want a newline to be part
of the hash.
</para></description>
<configOption name="username">
<synopsis>Username to use for account</synopsis>
</configOption>
<configOption name="password">
<synopsis>Plain text password used for authentication.</synopsis>
<description><para>Only used when auth_type is <literal>userpass</literal>.</para></description>
<description><para>Only used when auth_type is <literal>digest</literal>.</para></description>
</configOption>
<configOption name="password_digest" default="">
<synopsis>One or more pre-computed hashes used for authentication.</synopsis>
<description><para>Only used when auth_type is <literal>digest</literal>.
As an alternative to specifying a plain text password,
you can specify one or more pre-computed digests separated by
commas.
</para>
<para>
<literal>password_digest= &lt;digest-spec&gt;[,&lt;digest_spec&gt;]...</literal>
</para>
<enumlist>
<enum name="&lt;digest-spec&gt;"><para>&lt;hash-algorithm&gt;:&lt;hashed-credential&gt;</para></enum>
<enum name="&lt;hash-algorithm&gt;"><para>One of the supported hash algorithms
which currently are</para>
<enumlist>
<enum name="MD5"><para>Supported by all versions of OpenSSL and pjproject</para></enum>
<enum name="SHA-256"><para>Supported by OpenSSL versions &gt;> 1.0.0 and pjproject versions &gt;= 2.15.1</para></enum>
<enum name="SHA-512-256"><para>Supported by OpenSSL versions &gt;= 1.1.1 and pjproject versions &gt;= 2.15.1</para></enum>
</enumlist>
<para>You can see the current list by running the CLI command
<literal>pjproject show buildopts</literal>.
</para></enum>
<enum name="&lt;hashed-credential&gt;">
<para>The result of passing the following string through
the selected hash algorithm:
<literal>&lt;username&gt;:&lt;realm&gt;:&lt;password&gt;</literal>
</para>
</enum>
</enumlist>
<para>You can create the hash by piping the string into the appropriate
hash/checksum program. See the description for the <literal>realm</literal>
parameter for info on how to set it.</para>
<example>
$ echo -n "myname:myrealm:mypassword" | openssl dgst -md5
MD5(stdin)= dce9ccd0a69e3ef90d8b9bf725053e78
</example>
<para>You would then set:</para>
<example>
password_digest = md5:dce9ccd0a69e3ef90d8b9bf725053e78
</example>
</description>
</configOption>
<configOption name="md5_cred" default="">
<synopsis>MD5 Hash used for authentication. (deprecated)</synopsis>
<description><para>Use the <literal>password_digest</literal> parameter instead.
If supplied, a <literal>password_digest</literal> parameter will be created
for it.
</para></description>
</configOption>
<configOption name="supported_algorithms_uac">
<synopsis>Comma separated list of algorithms to support when this auth is used as a UAC</synopsis>
<description><para>Valid values:</para>
<enumlist>
<enum name="md5"><para>Supported by all versions of OpenSSL and pjproject</para></enum>
<enum name="sha-256"><para>Supported by all versions of OpenSSL but only pjproject versions &gt; 2.14.1</para></enum>
<enum name="sha-512-256"><para>Supported by OpenSSL versions &gt;= 1.1.1 and pjproject versions &gt; 2.14.1</para></enum>
</enumlist>
<para>
The default may be specified by the
<literal>default_auth_algorithms_uac</literal> parameter in
the global object. If that's not specified, the default is "MD5".
</para>
</description>
</configOption>
<configOption name="supported_algorithms_uas">
<synopsis>Comma separated list of algorithms to support when this auth is used as a UAS</synopsis>
<description><para>Valid values:</para>
<enumlist>
<enum name="md5"><para>Supported by all versions of OpenSSL and pjproject</para></enum>
<enum name="sha-256"><para>Supported by all versions of OpenSSL but only pjproject versions &gt; 2.14.1</para></enum>
<enum name="sha-512-256"><para>Supported by OpenSSL versions &gt;= 1.1.1 and pjproject versions &gt; 2.14.1</para></enum>
</enumlist>
<para>
The default may be specified by the
<literal>default_auth_algorithms_uas</literal> parameter in
the global object. If that's not specified, the default is "MD5".
</para>
</description>
</configOption>
<configOption name="refresh_token">
<synopsis>OAuth 2.0 refresh token</synopsis>
@@ -1687,19 +1733,19 @@
<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.
more than one wildcard auth object is associated to
an endpoint, only the first one of each defined on
the endpoint will be used.
</para>
</note>
</description>
</configOption>
<configOption name="nonce_lifetime" default="32">
<synopsis>Lifetime of a nonce associated with this authentication config.</synopsis>
</configOption>
<configOption name="type">
<synopsis>Must be 'auth'</synopsis>
</configOption>
<configOption name="username">
<synopsis>Username to use for account</synopsis>
</configOption>
</configObject>
<configObject name="domain_alias">
<synopsis>Domain Alias</synopsis>
@@ -2539,6 +2585,28 @@
RFC 3261 specifies this as a SHOULD requirement.
</para></description>
</configOption>
<configOption name="default_auth_algorithms_uas" default="no">
<synopsis>List of default authentication algorithms to support when Asterisk is UAS</synopsis>
<description><para>Valid values:</para>
<enumlist>
<enum name="md5"><para>Supported by all versions of OpenSSL and pjproject</para></enum>
<enum name="sha-256"><para>Supported by all versions of OpenSSL but only pjproject versions > 2.14.1</para></enum>
<enum name="sha-512-256"><para>Supported by OpenSSL versions >= 1.1.1 and pjproject versions > 2.14.1</para></enum>
</enumlist>
<para>If not specified, the default is <literal>MD5</literal> only.</para>
</description>
</configOption>
<configOption name="default_auth_algorithms_uac" default="no">
<synopsis>List of default authentication algorithms to support when Asterisk is UAC</synopsis>
<description><para>Valid values:</para>
<enumlist>
<enum name="md5"><para>Supported by all versions of OpenSSL and pjproject</para></enum>
<enum name="sha-256"><para>Supported by all versions of OpenSSL but only pjproject versions > 2.14.1</para></enum>
<enum name="sha-512-256"><para>Supported by OpenSSL versions >= 1.1.1 and pjproject versions > 2.14.1</para></enum>
</enumlist>
<para>If not specified, the default is <literal>MD5</literal> only.</para>
</description>
</configOption>
</configObject>
</configFile>
</configInfo>

View File

@@ -588,7 +588,8 @@ static pj_bool_t distributor(pjsip_rx_data *rdata)
return PJ_TRUE;
}
static struct ast_sip_auth *alloc_artificial_auth(char *default_realm)
static struct ast_sip_auth *alloc_artificial_auth(char *default_realm,
char *default_algos_uac, char *default_algos_uas)
{
struct ast_sip_auth *fake_auth;
@@ -601,6 +602,13 @@ static struct ast_sip_auth *alloc_artificial_auth(char *default_realm)
ast_string_field_set(fake_auth, realm, default_realm);
ast_string_field_set(fake_auth, auth_user, "");
ast_string_field_set(fake_auth, auth_pass, "");
ast_sip_auth_digest_algorithms_vector_init("artificial",
&fake_auth->supported_algorithms_uac, "UAC", default_algos_uac);
ast_sip_auth_digest_algorithms_vector_init("artificial",
&fake_auth->supported_algorithms_uas, "UAS", default_algos_uas);
fake_auth->type = AST_SIP_AUTH_TYPE_ARTIFICIAL;
return fake_auth;
@@ -608,20 +616,48 @@ static struct ast_sip_auth *alloc_artificial_auth(char *default_realm)
static AO2_GLOBAL_OBJ_STATIC(artificial_auth);
static int create_artificial_auth(void)
static int create_artificial_auth(int reload)
{
char default_realm[AST_SIP_AUTH_MAX_REALM_LENGTH + 1];
struct ast_sip_auth *fake_auth;
char default_algos_uac[AST_SIP_AUTH_MAX_SUPPORTED_ALGORITHMS_LENGTH + 1];
char default_algos_uas[AST_SIP_AUTH_MAX_SUPPORTED_ALGORITHMS_LENGTH + 1];
int need_update = 1;
ast_sip_get_default_realm(default_realm, sizeof(default_realm));
fake_auth = alloc_artificial_auth(default_realm);
if (!fake_auth) {
ast_log(LOG_ERROR, "Unable to create artificial auth\n");
return -1;
ast_sip_get_default_auth_algorithms_uac(default_algos_uac,
sizeof(default_algos_uac));
ast_sip_get_default_auth_algorithms_uas(default_algos_uas,
sizeof(default_algos_uas));
fake_auth = ast_sip_get_artificial_auth();
if (fake_auth && reload) {
char *fake_algorithms_uac = NULL;
char *fake_algorithms_uas = NULL;
ast_sip_auth_digest_algorithms_vector_to_str(
&fake_auth->supported_algorithms_uac, &fake_algorithms_uac);
ast_sip_auth_digest_algorithms_vector_to_str(
&fake_auth->supported_algorithms_uas, &fake_algorithms_uas);
if (strcmp(fake_auth->realm, default_realm) == 0
&& strcmp(fake_algorithms_uac, default_algos_uac) == 0
&& strcmp(fake_algorithms_uas, default_algos_uas) == 0) {
need_update = 0;
}
ast_free(fake_algorithms_uac);
ast_free(fake_algorithms_uas);
}
ao2_global_obj_replace_unref(artificial_auth, fake_auth);
ao2_ref(fake_auth, -1);
ao2_cleanup(fake_auth);
if (!need_update) {
return 0;
}
fake_auth = alloc_artificial_auth(default_realm, default_algos_uac,
default_algos_uas);
if (fake_auth) {
ao2_global_obj_replace_unref(artificial_auth, fake_auth);
}
return 0;
}
@@ -1161,8 +1197,6 @@ static int clean_task(const void *data)
static void global_loaded(const char *object_type)
{
char default_realm[AST_SIP_AUTH_MAX_REALM_LENGTH + 1];
struct ast_sip_auth *fake_auth;
char *identifier_order;
/* Update using_auth_username */
@@ -1182,18 +1216,7 @@ static void global_loaded(const char *object_type)
using_auth_username = new_using;
}
/* Update default_realm of artificial_auth */
ast_sip_get_default_realm(default_realm, sizeof(default_realm));
fake_auth = ast_sip_get_artificial_auth();
if (!fake_auth || strcmp(fake_auth->realm, default_realm)) {
ao2_cleanup(fake_auth);
fake_auth = alloc_artificial_auth(default_realm);
if (fake_auth) {
ao2_global_obj_replace_unref(artificial_auth, fake_auth);
}
}
ao2_cleanup(fake_auth);
create_artificial_auth(1);
ast_sip_get_unidentified_request_thresholds(&unidentified_count, &unidentified_period, &unidentified_prune_interval);
@@ -1287,7 +1310,7 @@ int ast_sip_initialize_distributor(void)
ast_sorcery_observer_add(ast_sip_get_sorcery(), "global", &global_observer);
ast_sorcery_reload_object(ast_sip_get_sorcery(), "global");
if (create_artificial_endpoint() || create_artificial_auth()) {
if (create_artificial_endpoint() || create_artificial_auth(0)) {
ast_sip_destroy_distributor();
return -1;
}

View File

@@ -26,6 +26,14 @@
#include "asterisk/strings.h"
#include "asterisk/test.h"
/*!
* \file
* \brief PJSIP UAS Authentication
*
* This module handles authentication when Asterisk is the UAS.
*
*/
/*** MODULEINFO
<depend>pjproject</depend>
<depend>res_pjsip</depend>
@@ -131,58 +139,132 @@ static const struct ast_sip_auth *get_auth(void)
return NULL;
}
static struct pjsip_authorization_hdr *get_authorization_hdr(
const char *auth_id, const char *realm, const pjsip_rx_data *rdata)
{
const char *src_name = rdata->pkt_info.src_name;
struct pjsip_authorization_hdr *auth_hdr =
(pjsip_authorization_hdr *) &rdata->msg_info.msg->hdr;
SCOPE_ENTER(3, "%s:%s: realm: %s\n", auth_id, src_name, realm);
while ((auth_hdr = pjsip_msg_find_hdr(rdata->msg_info.msg,
PJSIP_H_AUTHORIZATION, auth_hdr ? auth_hdr->next : NULL))) {
if (pj_strcmp2(&auth_hdr->credential.common.realm, realm) == 0) {
SCOPE_EXIT_RTN_VALUE(auth_hdr, "%s:%s: realm: %s Found header\n",
auth_id, src_name, realm);
}
}
SCOPE_EXIT_RTN_VALUE(NULL, "%s:%s: realm: %s No auth header found\n",
auth_id, src_name, realm);
}
/*!
* \brief Lookup callback for authentication verification
*
* This function is called when we call pjsip_auth_srv_verify(). It
* expects us to verify that the realm and account name from the
* Authorization header is correct. We are then supposed to supply
* a password or MD5 sum of credentials.
* Authorization header are correct and that we can support the digest
* algorithm specified. We are then supposed to supply a password or
* password_digest for the algorithm.
*
* The auth object must have previously been saved to thread-local storage.
*
* \param pool A memory pool we can use for allocations
* \param realm The realm from the Authorization header
* \param acc_name the user from the Authorization header
* \param[out] info The credentials we need to fill in
* \param param Contains the realm, username, rdata and auth header
* \param cred_info The credentials we need to fill in
* \retval PJ_SUCCESS Successful authentication
* \retval other Unsuccessful
*/
static pj_status_t digest_lookup(pj_pool_t *pool, const pj_str_t *realm,
const pj_str_t *acc_name, pjsip_cred_info *info)
static pj_status_t digest_lookup(pj_pool_t *pool,
const pjsip_auth_lookup_cred_param *param,
pjsip_cred_info *cred_info)
{
const struct ast_sip_auth *auth;
const struct ast_sip_auth *auth = get_auth();
const char *realm = S_OR(auth->realm, default_realm);
const char *creds;
const char *auth_name = (auth ? ast_sorcery_object_get_id(auth) : "none");
struct pjsip_authorization_hdr *auth_hdr = get_authorization_hdr(auth_name, realm, param->rdata);
const pjsip_auth_algorithm *algorithm =
ast_sip_auth_get_algorithm_by_iana_name(&auth_hdr->credential.digest.algorithm);
const char *src_name = param->rdata->pkt_info.src_name;
SCOPE_ENTER(4, "%s:%s:"
" srv realm: " PJSTR_PRINTF_SPEC
" auth realm: %s"
" hdr realm: " PJSTR_PRINTF_SPEC
" auth user: %s"
" hdr user: " PJSTR_PRINTF_SPEC
" algorithm: " PJSTR_PRINTF_SPEC
"\n",
auth_name, src_name,
PJSTR_PRINTF_VAR(param->realm),
realm,
PJSTR_PRINTF_VAR(auth_hdr->credential.common.realm),
auth->auth_user,
PJSTR_PRINTF_VAR(param->acc_name),
PJSTR_PRINTF_VAR(algorithm->iana_name));
auth = get_auth();
if (!auth) {
return PJSIP_SC_FORBIDDEN;
/* This can only happen if the auth object was not saved to thread-local storage */
SCOPE_EXIT_RTN_VALUE(PJSIP_SC_FORBIDDEN, "%s:%s: No auth object found\n",
auth_name, src_name);
}
if (auth->type == AST_SIP_AUTH_TYPE_ARTIFICIAL) {
return PJSIP_SC_FORBIDDEN;
/*
* This shouldn't happen because this function can only be invoked
* if there was an Authorization header in the incoming request.
*/
SCOPE_EXIT_RTN_VALUE(PJSIP_SC_FORBIDDEN, "%s:%s: Artificial auth object\n",
auth_name, src_name);
}
if (pj_strcmp2(realm, auth->realm)) {
return PJSIP_SC_FORBIDDEN;
}
if (pj_strcmp2(acc_name, auth->auth_user)) {
return PJSIP_SC_FORBIDDEN;
if (pj_strcmp2(&param->realm, realm) != 0) {
/*
* This shouldn't happen because param->realm was passed in from the auth
* when we called pjsip_auth_srv_init2.
*/
SCOPE_EXIT_RTN_VALUE(PJSIP_SC_FORBIDDEN, "%s:%s: Realm '%s' mismatch\n",
auth_name, src_name, realm);
}
pj_strdup2(pool, &info->realm, auth->realm);
pj_strdup2(pool, &info->username, auth->auth_user);
switch (auth->type) {
case AST_SIP_AUTH_TYPE_USER_PASS:
pj_strdup2(pool, &info->data, auth->auth_pass);
info->data_type = PJSIP_CRED_DATA_PLAIN_PASSWD;
break;
case AST_SIP_AUTH_TYPE_MD5:
pj_strdup2(pool, &info->data, auth->md5_creds);
info->data_type = PJSIP_CRED_DATA_DIGEST;
break;
default:
return PJSIP_SC_FORBIDDEN;
if (pj_strcmp2(&param->acc_name, auth->auth_user) != 0) {
SCOPE_EXIT_RTN_VALUE(PJSIP_SC_FORBIDDEN, "%s:%s: Username '%s' mismatch\n",
auth_name, src_name, auth->auth_user);
}
return PJ_SUCCESS;
if (!ast_sip_auth_is_algorithm_available(auth, &auth->supported_algorithms_uas,
algorithm->algorithm_type)) {
/*
* This shouldn't happen because we shouldn't have sent a challenge for
* an unsupported algorithm.
*/
SCOPE_EXIT_RTN_VALUE(PJSIP_SC_FORBIDDEN, "%s:%s: Algorithm '" PJSTR_PRINTF_SPEC
"' not supported or auth doesn't contain appropriate credentials\n",
auth_name, src_name, PJSTR_PRINTF_VAR(algorithm->iana_name));
}
pj_strdup2(pool, &cred_info->realm, realm);
pj_strdup2(pool, &cred_info->username, auth->auth_user);
creds = ast_sip_auth_get_creds(auth, algorithm->algorithm_type, &cred_info->data_type);
if (!creds) {
/*
* This shouldn't happen because we checked the auth object when we
* loaded it to make sure it had the appropriate credentials for each
* algorithm in supported_algorithms_uas.
*/
SCOPE_EXIT_RTN_VALUE(PJSIP_SC_FORBIDDEN, "%s:%s: No plain text or digest password found for algorithm '" PJSTR_PRINTF_SPEC "'\n",
auth_name, src_name, PJSTR_PRINTF_VAR(algorithm->iana_name));
}
pj_strdup2(pool, &cred_info->data, creds);
#ifdef HAVE_PJSIP_AUTH_NEW_DIGESTS
if (cred_info->data_type == PJSIP_CRED_DATA_DIGEST) {
cred_info->algorithm_type = algorithm->algorithm_type;
}
#endif
SCOPE_EXIT_RTN_VALUE(PJ_SUCCESS, "%s:%s: Success. Data type: %s Algorithm '" PJSTR_PRINTF_SPEC "'\n",
auth_name, src_name, cred_info->data_type ? "digest" : "plain text", PJSTR_PRINTF_VAR(algorithm->iana_name));
}
/*!
@@ -202,7 +284,8 @@ static pj_status_t digest_lookup(pj_pool_t *pool, const pj_str_t *realm,
* \param rdata The incoming request
* \param realm The realm for which authentication should occur
*/
static int build_nonce(struct ast_str **nonce, const char *timestamp, const pjsip_rx_data *rdata, const char *realm)
static int build_nonce(struct ast_str **nonce, const char *timestamp,
const pjsip_rx_data *rdata, const char *realm)
{
struct ast_str *str = ast_str_alloca(256);
RAII_VAR(char *, eid, ao2_global_obj_ref(entity_id), ao2_cleanup);
@@ -255,7 +338,7 @@ static int check_nonce(const char *candidate, const pjsip_rx_data *rdata, const
return 0;
}
build_nonce(&calculated, timestamp, rdata, auth->realm);
build_nonce(&calculated, timestamp, rdata, S_OR(auth->realm, default_realm));
ast_debug(3, "Calculated nonce %s. Actual nonce is %s\n", ast_str_buffer(calculated), candidate);
if (strcmp(ast_str_buffer(calculated), candidate)) {
return 0;
@@ -263,34 +346,6 @@ static int check_nonce(const char *candidate, const pjsip_rx_data *rdata, const
return 1;
}
static int find_challenge(const pjsip_rx_data *rdata, const struct ast_sip_auth *auth)
{
struct pjsip_authorization_hdr *auth_hdr = (pjsip_authorization_hdr *) &rdata->msg_info.msg->hdr;
int challenge_found = 0;
char nonce[64];
while ((auth_hdr = (pjsip_authorization_hdr *) pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_AUTHORIZATION, auth_hdr->next))) {
ast_copy_pj_str(nonce, &auth_hdr->credential.digest.nonce, sizeof(nonce));
if (check_nonce(nonce, rdata, auth) && !pj_strcmp2(&auth_hdr->credential.digest.realm, auth->realm)) {
challenge_found = 1;
break;
}
}
return challenge_found;
}
/*!
* \brief Common code for initializing a pjsip_auth_srv
*/
static void setup_auth_srv(pj_pool_t *pool, pjsip_auth_srv *auth_server, const char *realm)
{
pj_str_t realm_str;
pj_cstr(&realm_str, realm);
pjsip_auth_srv_init(pool, auth_server, &realm_str, digest_lookup, 0);
}
/*!
* \brief Result of digest verification
*/
@@ -311,69 +366,147 @@ static char *verify_result_str[] = {
"STALE",
"NOAUTH"
};
/*!
* \brief astobj2 callback for verifying incoming credentials
*
* \param auth The ast_sip_auth to check against
* \param rdata The incoming request
* \param pool A pool to use for the auth server
* \return CMP_MATCH on successful authentication
* \return 0 on failed authentication
*/
static int verify(const struct ast_sip_auth *auth, pjsip_rx_data *rdata, pj_pool_t *pool)
static enum digest_verify_result find_authorization(const char *endpoint_id,
const struct ast_sip_auth *auth, const pjsip_rx_data *rdata)
{
const char *auth_id = ast_sorcery_object_get_id(auth);
const char *src_name = rdata->pkt_info.src_name;
const char *realm = S_OR(auth->realm, default_realm);
struct pjsip_authorization_hdr *auth_hdr =
(pjsip_authorization_hdr *) &rdata->msg_info.msg->hdr;
enum digest_verify_result res = AUTH_NOAUTH;
int authorization_found = 0;
char nonce[64];
SCOPE_ENTER(3, "%s:%s:%s: realm: %s\n",
endpoint_id, auth_id, src_name, realm);
while ((auth_hdr = pjsip_msg_find_hdr(rdata->msg_info.msg,
PJSIP_H_AUTHORIZATION, auth_hdr ? auth_hdr->next : NULL))) {
ast_copy_pj_str(nonce, &auth_hdr->credential.digest.nonce, sizeof(nonce));
ast_trace(-1, "%s:%s:%s: Checking nonce %s hdr-realm: " PJSTR_PRINTF_SPEC " hdr-algo: " PJSTR_PRINTF_SPEC " \n",
endpoint_id, auth_id, src_name, nonce,
PJSTR_PRINTF_VAR(auth_hdr->credential.digest.realm),
PJSTR_PRINTF_VAR(auth_hdr->credential.digest.algorithm));
authorization_found++;
if (check_nonce(nonce, rdata, auth)
&& pj_strcmp2(&auth_hdr->credential.digest.realm, realm) == 0) {
res = AUTH_SUCCESS;
break;
} else {
res = AUTH_STALE;
}
}
if (!authorization_found) {
ast_trace(-1, "%s:%s:%s: No Authorization header found\n",
endpoint_id, auth_id, src_name);
res = AUTH_NOAUTH;
}
SCOPE_EXIT_RTN_VALUE(res, "%s:%s:%s: realm: %s Result %s\n",
endpoint_id, auth_id, src_name, realm, verify_result_str[res]);
}
/*!
* \brief Common code for initializing a pjsip_auth_srv
*/
static void setup_auth_srv(pj_pool_t *pool, pjsip_auth_srv *auth_server, const char *realm)
{
pjsip_auth_srv_init_param *param = pj_pool_alloc(pool, sizeof(*param));
pj_str_t *pj_realm = pj_pool_alloc(pool, sizeof(*pj_realm));
pj_cstr(pj_realm, realm);
param->realm = pj_realm;
param->lookup2 = digest_lookup;
param->options = 0;
pjsip_auth_srv_init2(pool, auth_server, param);
}
/*!
* \brief Verify incoming credentials
*
* \param endpoint_id For logging
* \param auth The ast_sip_auth to check against
* \param rdata The incoming request
* \param pool A pool to use for the auth server
* \return One of digest_verify_result
*/
static int verify(const char *endpoint_id, const struct ast_sip_auth *auth,
pjsip_rx_data *rdata, pj_pool_t *pool)
{
const char *auth_id = ast_sorcery_object_get_id(auth);
const char *realm = S_OR(auth->realm, default_realm);
const char *src_name = rdata->pkt_info.src_name;
pj_status_t authed;
int response_code;
pjsip_auth_srv auth_server;
int stale = 0;
int res = AUTH_FAIL;
enum digest_verify_result res = AUTH_FAIL;
SCOPE_ENTER(3, "%s:%s:%s: realm: %s\n",
endpoint_id, auth_id, src_name, realm);
if (!find_challenge(rdata, auth)) {
/* Couldn't find a challenge with a sane nonce.
res = find_authorization(endpoint_id, auth, rdata);
if (res == AUTH_NOAUTH)
{
ast_test_suite_event_notify("INCOMING_AUTH_VERIFY_RESULT",
"Realm: %s\r\n"
"Username: %s\r\n"
"Status: %s",
realm, auth->auth_user, verify_result_str[res]);
SCOPE_EXIT_RTN_VALUE(res, "%s:%s:%s: No Authorization header found\n",
endpoint_id, auth_id, src_name);
}
if (res == AUTH_STALE) {
/* Couldn't find an authorization with a sane nonce.
* Nonce mismatch may just be due to staleness.
*/
stale = 1;
}
setup_auth_srv(pool, &auth_server, auth->realm);
setup_auth_srv(pool, &auth_server, realm);
store_auth(auth);
authed = pjsip_auth_srv_verify(&auth_server, rdata, &response_code);
/* pjsip_auth_srv_verify will invoke digest_lookup */
authed = SCOPE_CALL_WITH_RESULT(-1, pj_status_t, pjsip_auth_srv_verify, &auth_server, rdata, &response_code);
remove_auth();
if (authed == PJ_SUCCESS) {
if (stale) {
res = AUTH_STALE;
} else {
res = AUTH_SUCCESS;
}
} else {
char err[256];
res = AUTH_FAIL;
pj_strerror(authed, err, sizeof(err));
ast_trace(-1, "%s:%s:%s: authed: %s\n", endpoint_id, auth_id, src_name, err);
}
if (authed == PJSIP_EAUTHNOAUTH) {
res = AUTH_NOAUTH;
}
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]);
realm, auth->auth_user, verify_result_str[res]);
return res;
SCOPE_EXIT_RTN_VALUE(res, "%s:%s:%s: Realm: %s Username: %s Result: %s\n",
endpoint_id, auth_id, src_name, realm,
auth->auth_user, verify_result_str[res]);
}
/*!
* \brief astobj2 callback for adding digest challenges to responses
* \brief Send a WWW-Authenticate challenge
*
* \param realm An auth's realm to build a challenge from
* \param endpoint_id For logging
* \param auth The auth object to use for the challenge
* \param tdata The response to add the challenge to
* \param rdata The request the challenge is in response to
* \param is_stale Indicates whether nonce on incoming request was stale
* \param algorithm_type The algorithm to use for the challenge
*/
static void challenge(const char *realm, pjsip_tx_data *tdata, const pjsip_rx_data *rdata, int is_stale)
static void challenge(const char *endpoint_id, struct ast_sip_auth *auth,
pjsip_tx_data *tdata, const pjsip_rx_data *rdata, int is_stale,
const pjsip_auth_algorithm *algorithm)
{
pj_str_t qop;
pj_str_t pj_nonce;
@@ -381,6 +514,14 @@ static void challenge(const char *realm, pjsip_tx_data *tdata, const pjsip_rx_da
struct ast_str *nonce = ast_str_alloca(256);
char time_buf[32];
time_t timestamp = time(NULL);
pj_status_t res;
const char *realm = S_OR(auth->realm, default_realm);
const char *auth_id = ast_sorcery_object_get_id(auth);
const char *src_name = rdata->pkt_info.src_name;
SCOPE_ENTER(5, "%s:%s:%s: realm: %s time: %d algorithm: " PJSTR_PRINTF_SPEC " stale? %s\n",
endpoint_id, auth_id, src_name, realm, (int)timestamp,
PJSTR_PRINTF_VAR(algorithm->iana_name), is_stale ? "yes" : "no");
snprintf(time_buf, sizeof(time_buf), "%d", (int) timestamp);
build_nonce(&nonce, time_buf, rdata, realm);
@@ -389,9 +530,27 @@ static void challenge(const char *realm, pjsip_tx_data *tdata, const pjsip_rx_da
pj_cstr(&pj_nonce, ast_str_buffer(nonce));
pj_cstr(&qop, "auth");
pjsip_auth_srv_challenge(&auth_server, &qop, &pj_nonce, NULL, is_stale ? PJ_TRUE : PJ_FALSE, tdata);
#ifdef HAVE_PJSIP_AUTH_NEW_DIGESTS
res = pjsip_auth_srv_challenge2(&auth_server, &qop, &pj_nonce,
NULL, is_stale ? PJ_TRUE : PJ_FALSE, tdata, algorithm->algorithm_type);
#else
res = pjsip_auth_srv_challenge(&auth_server, &qop, &pj_nonce,
NULL, is_stale ? PJ_TRUE : PJ_FALSE, tdata);
#endif
SCOPE_EXIT_RTN("%s:%s:%s: Sending challenge for realm: %s algorithm: " PJSTR_PRINTF_SPEC
" %s\n",
endpoint_id, auth_id, src_name, realm, PJSTR_PRINTF_VAR(algorithm->iana_name),
res == PJ_SUCCESS ? "succeeded" : "failed");
}
static char *check_auth_result_str[] = {
"CHALLENGE",
"SUCCESS",
"FAILED",
"ERROR",
};
/*!
* \brief Check authentication using Digest scheme
*
@@ -405,7 +564,6 @@ static enum ast_sip_check_auth_result digest_check_auth(struct ast_sip_endpoint
pjsip_rx_data *rdata, pjsip_tx_data *tdata)
{
struct ast_sip_auth **auths;
struct ast_sip_auth **auths_shallow;
enum digest_verify_result *verify_res;
struct ast_sip_endpoint *artificial_endpoint;
enum ast_sip_check_auth_result res;
@@ -413,6 +571,9 @@ static enum ast_sip_check_auth_result digest_check_auth(struct ast_sip_endpoint
int is_artificial;
int failures = 0;
size_t auth_size;
const char *endpoint_id = ast_sorcery_object_get_id(endpoint);
char *src_name = rdata->pkt_info.src_name;
SCOPE_ENTER(3, "%s:%s\n", endpoint_id, src_name);
auth_size = AST_VECTOR_SIZE(&endpoint->inbound_auths);
ast_assert(0 < auth_size);
@@ -423,81 +584,122 @@ static enum ast_sip_check_auth_result digest_check_auth(struct ast_sip_endpoint
artificial_endpoint = ast_sip_get_artificial_endpoint();
if (!artificial_endpoint) {
/* Should not happen except possibly if we are shutting down. */
return AST_SIP_AUTHENTICATION_ERROR;
SCOPE_EXIT_RTN_VALUE(AST_SIP_AUTHENTICATION_ERROR);
}
is_artificial = endpoint == artificial_endpoint;
ao2_ref(artificial_endpoint, -1);
if (is_artificial) {
ast_trace(3, "%s:%s: Using artificial endpoint for authentication\n",
endpoint_id, src_name);
ast_assert(auth_size == 1);
auths[0] = ast_sip_get_artificial_auth();
if (!auths[0]) {
/* Should not happen except possibly if we are shutting down. */
return AST_SIP_AUTHENTICATION_ERROR;
SCOPE_EXIT_RTN_VALUE(AST_SIP_AUTHENTICATION_ERROR);
}
} else {
ast_trace(3, "%s:%s: Using endpoint for authentication\n",
endpoint_id, src_name);
memset(auths, 0, auth_size * sizeof(*auths));
/*
* If ast_sip_retrieve_auths returns a failure we still need
* to cleanup the auths array because it may have been partially
* filled in.
*/
if (ast_sip_retrieve_auths(&endpoint->inbound_auths, auths)) {
res = AST_SIP_AUTHENTICATION_ERROR;
goto cleanup;
ast_sip_cleanup_auths(auths, auth_size);
SCOPE_EXIT_RTN_VALUE(AST_SIP_AUTHENTICATION_ERROR,
"%s:%s: Failed to retrieve some or all auth objects from endpoint\n",
endpoint_id, src_name);
}
}
/* Setup shallow copy of auths */
if (ast_strlen_zero(default_realm)) {
auths_shallow = auths;
} else {
/*
* NOTE: The only reason to use multiple auth objects as a UAS might
* be to send challenges for multiple realms however we currently don't
* know of anyone actually doing this.
*/
for (idx = 0; idx < auth_size; ++idx) {
int i = 0;
struct ast_sip_auth *auth = auths[idx];
const char *realm = S_OR(auth->realm, default_realm);
const char *auth_id = ast_sorcery_object_get_id(auth);
SCOPE_ENTER(4, "%s:%s:%s: Verifying\n", endpoint_id, auth_id, src_name);
/*
* Set default realm on a shallow copy of the authentication
* objects that don't have a realm set.
* Artificial auth objects are used for the purpose of
* sending challenges. We don't need to verify them.
*/
auths_shallow = ast_alloca(auth_size * sizeof(*auths_shallow));
for (idx = 0; idx < auth_size; ++idx) {
if (ast_strlen_zero(auths[idx]->realm)) {
/*
* Make a shallow copy and set the default realm on it.
*
* The stack allocation is OK here. Normally this will
* loop one time. If you have multiple auths then you
* shouldn't need more auths than the normal complement
* of fingers and toes. Otherwise, you should check
* your sanity for setting up your system up that way.
*/
auths_shallow[idx] = ast_alloca(sizeof(**auths_shallow));
memcpy(auths_shallow[idx], auths[idx], sizeof(**auths_shallow));
*((char **) (&auths_shallow[idx]->realm)) = default_realm;
ast_debug(3, "Using default realm '%s' on incoming auth '%s'.\n",
default_realm, ast_sorcery_object_get_id(auths_shallow[idx]));
} else {
auths_shallow[idx] = auths[idx];
if (auth->type == AST_SIP_AUTH_TYPE_ARTIFICIAL) {
ast_trace(-1, "%s:%s:%s: Skipping verification on artificial endpoint\n", endpoint_id, auth_id, src_name )
verify_res[idx] = AUTH_NOAUTH;
} else {
verify_res[idx] = SCOPE_CALL_WITH_RESULT(-1, int, verify, endpoint_id, auth, rdata, tdata->pool);
if (verify_res[idx] == AUTH_SUCCESS) {
res = AST_SIP_AUTHENTICATION_SUCCESS;
SCOPE_EXIT_EXPR(break, "%s:%s:%s: success\n", endpoint_id, auth_id, src_name);
}
if (verify_res[idx] == AUTH_FAIL) {
ast_trace(-1, "%s:%s:%s: fail\n", endpoint_id, auth_id, src_name);
failures++;
}
}
for (i = 0; i < AST_VECTOR_SIZE(&auth->supported_algorithms_uas); i++) {
pjsip_auth_algorithm_type algorithm_type = AST_VECTOR_GET(&auth->supported_algorithms_uas, i);
const pjsip_auth_algorithm *algorithm = ast_sip_auth_get_algorithm_by_type(algorithm_type);
pjsip_www_authenticate_hdr *auth_hdr = NULL;
int already_sent_challenge = 0;
SCOPE_ENTER(5, "%s:%s:%s: Challenging with " PJSTR_PRINTF_SPEC "\n",
endpoint_id, auth_id, src_name, PJSTR_PRINTF_VAR(algorithm->iana_name));
/*
* Per RFC 7616, if we've already sent a challenge for this realm
* and algorithm, we must not send another.
*/
while ((auth_hdr = pjsip_msg_find_hdr(tdata->msg,
PJSIP_H_WWW_AUTHENTICATE, auth_hdr ? auth_hdr->next : NULL))) {
if (pj_strcmp2(&auth_hdr->challenge.common.realm, realm) == 0 &&
!pj_stricmp(&auth_hdr->challenge.digest.algorithm, &algorithm->iana_name)) {
ast_trace(-1, "%s:%s:%s: Not sending duplicate challenge for realm: %s algorithm: "
PJSTR_PRINTF_SPEC "\n",
endpoint_id, auth_id, src_name, realm, PJSTR_PRINTF_VAR(algorithm->iana_name));
already_sent_challenge = 1;
}
}
if (already_sent_challenge) {
SCOPE_EXIT_EXPR(continue);
}
SCOPE_CALL(5, challenge, endpoint_id, auth, tdata, rdata,
verify_res[idx] == AUTH_STALE, algorithm);
SCOPE_EXIT("%s:%s:%s: Challenged with " PJSTR_PRINTF_SPEC "\n",
endpoint_id, auth_id, src_name, PJSTR_PRINTF_VAR(algorithm->iana_name));
}
SCOPE_EXIT("%s:%s:%s: Done with auth challenge\n", endpoint_id, auth_id, src_name);
}
for (idx = 0; idx < auth_size; ++idx) {
verify_res[idx] = verify(auths_shallow[idx], rdata, tdata->pool);
if (verify_res[idx] == AUTH_SUCCESS) {
res = AST_SIP_AUTHENTICATION_SUCCESS;
goto cleanup;
}
if (verify_res[idx] == AUTH_FAIL) {
failures++;
}
}
for (idx = 0; idx < auth_size; ++idx) {
challenge(auths_shallow[idx]->realm, tdata, rdata, verify_res[idx] == AUTH_STALE);
}
/*
* If we've sent challenges for multiple auth objects, we currently
* return SUCCESS when the first one succeeds. We may want to change
* this in the future to require that all succeed but as stated above,
* currently we don't have a use case for even using more than one
* auth object as a UAS.
*/
if (failures == auth_size) {
res = AST_SIP_AUTHENTICATION_FAILED;
} else {
} else if (res != AST_SIP_AUTHENTICATION_SUCCESS){
res = AST_SIP_AUTHENTICATION_CHALLENGE;
}
cleanup:
ast_sip_cleanup_auths(auths, auth_size);
return res;
SCOPE_EXIT_RTN_VALUE(res, "%s:%s: Result: %s\n",
endpoint_id, src_name,
check_auth_result_str[res]);
}
static struct ast_sip_authenticator digest_authenticator = {

View File

@@ -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 = {