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

@@ -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;
}