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.

(cherry picked from commit 1933548d41)
releases/21
George Joseph 6 months ago committed by Asterisk Development Team
parent 6e114c7869
commit fd52a4411d

@ -1038,56 +1038,109 @@
; Note: Using the same auth section for inbound and outbound
; authentication is not recommended. There is a difference in
; meaning for an empty realm setting between inbound and outbound
; authentication uses. Look to the CLI config help
; "config show help res_pjsip auth realm" or on https://docs.asterisk.org/
; for the difference.
;
;auth_type=userpass ; Authentication type. May be
; "userpass" for plain text passwords or
; "md5" for pre-hashed credentials.
; (default: "userpass")
;nonce_lifetime=32 ; Lifetime of a nonce associated with this
; authentication config (default: "32")
;md5_cred= ; 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:
; <username>:<realm>:<password>
; For incoming authentication (asterisk is the UAS),
; the realm must match either the realm set in this object
; or the default set in in the "global" object.
;
; For outgoing authentication (asterisk is the 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 generate the hash with the following shell
; command:
; $ echo -n "myname:myrealm:mypassword" | md5sum
; Note the '-n'. You don't want a newline to be part
; of the hash. (default: "")
;password= ; PlainText password used for authentication (default: "")
;realm= ; For incoming authentication (asterisk is the UAS),
; this is the realm to be sent on WWW-Authenticate
; headers. If not specified, the global object's
; "default_realm" will be used.
;
; For outgoing authentication (asterisk is the UAC), this
; must either be the realm the server is expected to send,
; or left blank or contain a single '*' to automatically
; use the realm sent by the server. If you have multiple
; auth objects for an endpoint, the realm is also used to
; match the auth object to the realm the server sent.
;
; Using the same auth section for inbound and outbound
; authentication is not recommended. There is a difference in
; meaning for an empty realm setting between inbound and outbound
; authentication uses.
; (default: "")
;type= ; Must be auth (default: "")
;username= ; Username to use for account (default: "")
; authentication uses.
;
; Note on Digest Algorithms: The currently supported digest algorithms are
; "MD5", "SHA-256" and "SHA-512-256" but availability may be limited by
; the versions of PJProject and OpenSSL installed. Run the CLI command
; `pjproject show buildopts` to see the algorithms currently available and
; see the documentation linked below for more info.
;
; Detailed discussion for this object, especially regarding hash algorithms
; and realms can be found at
; https://docs.asterisk.org/Configuration/Channel-Drivers/SIP/Configuring-res_pjsip/PJSIP-Authentication
;type= ; Must be auth (default: "")
;auth_type= ; Authentication mechanism.
; Must be one of:
; "digest" : The standard HTTP/SIP digest
; authentication. "password" and/or one or more
; "password_digest" parameters must also be specified.
; "google_oauth": Google OAuth authentication used by
; Google Voice.
; "userpass" : (deprecated). Automatically converted
; to "digest". Used to mean plain-text password but
; that is now determined automatically.
; "md5" : (deprecated) Automatically converted
; to "digest". Used to mean pre-hashed password but
; that is now determined automatically.
; (default: "digest")
;realm= ; For incoming authentication (asterisk is the UAS),
; this is the realm to be sent on WWW-Authenticate
; headers. If not specified, the global object's
; "default_realm" will be used.
;
; For outgoing authentication (asterisk is the UAC), this
; must either be the realm the server is expected to send,
; or left blank or contain a single '*' to automatically
; use the realm sent by the server. If you have multiple
; auth objects for an endpoint, the realm is also used to
; match the auth object to the realm the server sent.
;
; Using the same auth section for inbound and outbound
; authentication is not recommended. There is a difference in
; meaning for an empty realm setting between inbound and outbound
; authentication uses.
;
; If more than one auth object with the same realm or
; more than one wildcard auth object is associated to
; an endpoint, only the first one of each defined on
; the endpoint will be used.
;
; (default: "")
;username= ; Username to use for account (Required)
;password= ; PlainText password used for authentication (default: "")
;password_digest= <digest-spec>
; As an alternative to specifying a plain text password, you can
; specify pre-computed digests.
;
; <digest-spec> = <IANA_digest_algorithm>:<hashed-credential>
; <IANA_digest_algorithm>: One of the supported hash algorithms
; which currently are "MD5", "SHA-256" and "SHA-512-256" but
; see the note above.
; <hashed-credential>: The result of passing the following
; string through the selected hash algorithm:
; <username>:<realm>:<password>
; Example:
; $ echo -n "fred:asterisk:mypass" | openssl dgst -md5
; MD5(stdin)= 43a8d9be3da524f9a59ca0593d7b1b5d
; would be specified as...
;password_digest = MD5:43a8d9be3da524f9a59ca0593d7b1b5d
; You can specify this parameter once for each algorithm.
; See the documentation linked above for more info.
;md5_cred= ; (deprecated) Will be automatically converted to a
; "password_digest" parameter.
;supported_algorithms_uas= <IANA_digest_algorithm>[,<IANA_digest_algorithm>]...
; Specify the digest algorithms to offer when this auth object
; is used by Asterisk acting as a UAS. Specify one or more of
; the supported hash algorithms, which currently are "MD5",
; "SHA-256" and "SHA-512-256", but see the note above.
; The default is the value specified in the global object's
; default_auth_algorithms_uas parameter.
;supported_algorithms_uac= <IANA_digest_algorithm>[,<IANA_digest_algorithm>]...
; Specify the digest algorithms to respond with when this auth
; object is used by Asterisk acting as a UAC. Specify one or more of
; the supported hash algorithms, which currently are "MD5",
; "SHA-256" and "SHA-512-256", but see the note above.
; The default is the value specified in the global object's
; default_auth_algorithms_uac parameter.
;nonce_lifetime=32 ; Lifetime of a nonce associated with this
; authentication config (default: "32")
; For the Google OAuth authentication mechanism, the following parameters are
; required:
;refresh_token= ; OAuth 2.0 refresh token
;oauth_clientid= ; OAuth 2.0 application's client id
;oauth_secret= ; OAuth 2.0 application's secret
;==========================DOMAIN_ALIAS SECTION OPTIONS=========================
@ -1416,6 +1469,19 @@
; 183 Session Progress to the endpoint.
; (default: "no")
;default_auth_algorithms_uas = MD5
; The default list of digest algorithms to support when an
; auth object is used as a UAS. See the "supported_algorithms_uas"
; parameter in the "auth" object above.
; The default is MD5
;default_auth_algorithms_uac = MD5
; The default list of digest algorithms to support when an
; auth object is used as a UAC. See the "supported_algorithms_uac"
; parameter in the "auth" object above.
; The default is MD5
; MODULE PROVIDING BELOW SECTION(S): res_pjsip_acl
;==========================ACL SECTION OPTIONS=========================
;[acl]

115
configure vendored

@ -935,6 +935,10 @@ PBX_POPT
POPT_DIR
POPT_INCLUDE
POPT_LIB
PBX_PJSIP_AUTH_NEW_DIGESTS
PJSIP_AUTH_NEW_DIGESTS_DIR
PJSIP_AUTH_NEW_DIGESTS_INCLUDE
PJSIP_AUTH_NEW_DIGESTS_LIB
PBX_PJSIP_TLS_TRANSPORT_RESTART
PJSIP_TLS_TRANSPORT_RESTART_DIR
PJSIP_TLS_TRANSPORT_RESTART_INCLUDE
@ -22026,6 +22030,9 @@ printf "%s\n" "#define HAVE_PJPROJECT_ON_VALID_ICE_PAIR_CALLBACK 1" >>confdefs.h
printf "%s\n" "#define HAVE_PJSIP_TLS_TRANSPORT_RESTART 1" >>confdefs.h
printf "%s\n" "#define HAVE_PJSIP_AUTH_NEW_DIGESTS 1" >>confdefs.h
@ -24218,6 +24225,18 @@ PBX_PJSIP_TLS_TRANSPORT_RESTART=0
PJSIP_AUTH_NEW_DIGESTS_DESCRIP="PJSIP Auth new digests like SHA-256 and SHA-512-256"
PJSIP_AUTH_NEW_DIGESTS_OPTION=pjsip
PJSIP_AUTH_NEW_DIGESTS_DIR=${PJPROJECT_DIR}
PBX_PJSIP_AUTH_NEW_DIGESTS=0
fi
@ -39683,6 +39702,102 @@ _ACEOF
fi
if test "x${PBX_PJSIP_AUTH_NEW_DIGESTS}" != "x1" -a "${USE_PJSIP_AUTH_NEW_DIGESTS}" != "no"; then
pbxlibdir=""
# if --with-PJSIP_AUTH_NEW_DIGESTS=DIR has been specified, use it.
if test "x${PJSIP_AUTH_NEW_DIGESTS_DIR}" != "x"; then
if test -d ${PJSIP_AUTH_NEW_DIGESTS_DIR}/lib; then
pbxlibdir="-L${PJSIP_AUTH_NEW_DIGESTS_DIR}/lib"
else
pbxlibdir="-L${PJSIP_AUTH_NEW_DIGESTS_DIR}"
fi
fi
ast_ext_lib_check_save_CFLAGS="${CFLAGS}"
CFLAGS="${CFLAGS} $PJPROJECT_CFLAGS"
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for pjsip_auth_get_algorithm_by_type in -lpjsip" >&5
printf %s "checking for pjsip_auth_get_algorithm_by_type in -lpjsip... " >&6; }
if test ${ac_cv_lib_pjsip_pjsip_auth_get_algorithm_by_type+y}
then :
printf %s "(cached) " >&6
else $as_nop
ac_check_lib_save_LIBS=$LIBS
LIBS="-lpjsip ${pbxlibdir} $PJPROJECT_LIB $LIBS"
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
/* Override any GCC internal prototype to avoid an error.
Use char because int might match the return type of a GCC
builtin and then its argument prototype would still apply. */
char pjsip_auth_get_algorithm_by_type ();
int
main (void)
{
return pjsip_auth_get_algorithm_by_type ();
;
return 0;
}
_ACEOF
if ac_fn_c_try_link "$LINENO"
then :
ac_cv_lib_pjsip_pjsip_auth_get_algorithm_by_type=yes
else $as_nop
ac_cv_lib_pjsip_pjsip_auth_get_algorithm_by_type=no
fi
rm -f core conftest.err conftest.$ac_objext conftest.beam \
conftest$ac_exeext conftest.$ac_ext
LIBS=$ac_check_lib_save_LIBS
fi
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_pjsip_pjsip_auth_get_algorithm_by_type" >&5
printf "%s\n" "$ac_cv_lib_pjsip_pjsip_auth_get_algorithm_by_type" >&6; }
if test "x$ac_cv_lib_pjsip_pjsip_auth_get_algorithm_by_type" = xyes
then :
AST_PJSIP_AUTH_NEW_DIGESTS_FOUND=yes
else $as_nop
AST_PJSIP_AUTH_NEW_DIGESTS_FOUND=no
fi
CFLAGS="${ast_ext_lib_check_save_CFLAGS}"
# now check for the header.
if test "${AST_PJSIP_AUTH_NEW_DIGESTS_FOUND}" = "yes"; then
PJSIP_AUTH_NEW_DIGESTS_LIB="${pbxlibdir} -lpjsip $PJPROJECT_LIB"
# if --with-PJSIP_AUTH_NEW_DIGESTS=DIR has been specified, use it.
if test "x${PJSIP_AUTH_NEW_DIGESTS_DIR}" != "x"; then
PJSIP_AUTH_NEW_DIGESTS_INCLUDE="-I${PJSIP_AUTH_NEW_DIGESTS_DIR}/include"
fi
PJSIP_AUTH_NEW_DIGESTS_INCLUDE="${PJSIP_AUTH_NEW_DIGESTS_INCLUDE} $PJPROJECT_CFLAGS"
# check for the header
ast_ext_lib_check_saved_CPPFLAGS="${CPPFLAGS}"
CPPFLAGS="${CPPFLAGS} ${PJSIP_AUTH_NEW_DIGESTS_INCLUDE}"
ac_fn_c_check_header_compile "$LINENO" "pjsip.h" "ac_cv_header_pjsip_h" "$ac_includes_default"
if test "x$ac_cv_header_pjsip_h" = xyes
then :
PJSIP_AUTH_NEW_DIGESTS_HEADER_FOUND=1
else $as_nop
PJSIP_AUTH_NEW_DIGESTS_HEADER_FOUND=0
fi
CPPFLAGS="${ast_ext_lib_check_saved_CPPFLAGS}"
if test "x${PJSIP_AUTH_NEW_DIGESTS_HEADER_FOUND}" = "x0" ; then
PJSIP_AUTH_NEW_DIGESTS_LIB=""
PJSIP_AUTH_NEW_DIGESTS_INCLUDE=""
else
PBX_PJSIP_AUTH_NEW_DIGESTS=1
cat >>confdefs.h <<_ACEOF
#define HAVE_PJSIP_AUTH_NEW_DIGESTS 1
_ACEOF
fi
fi
fi
fi
fi

@ -620,6 +620,7 @@ AST_EXT_LIB_SETUP_OPTIONAL([PJSIP_ENDPOINT_COMPACT_FORM], [PJSIP Compact Form Su
AST_EXT_LIB_SETUP_OPTIONAL([PJSIP_TRANSPORT_DISABLE_CONNECTION_REUSE], [PJSIP Transport Connection Reuse Disabling], [PJPROJECT], [pjsip])
AST_EXT_LIB_SETUP_OPTIONAL([PJSIP_OAUTH_AUTHENTICATION], [PJSIP OAuth Authentication Support], [PJPROJECT], [pjsip])
AST_EXT_LIB_SETUP_OPTIONAL([PJSIP_TLS_TRANSPORT_RESTART], [PJSIP TLS Transport Restart Support], [PJPROJECT], [pjsip])
AST_EXT_LIB_SETUP_OPTIONAL([PJSIP_AUTH_NEW_DIGESTS], [PJSIP Auth new digests like SHA-256 and SHA-512-256], [PJPROJECT], [pjsip])
fi
AST_EXT_LIB_SETUP([POPT], [popt], [popt])
@ -2546,6 +2547,7 @@ if test "$USE_PJPROJECT" != "no" ; then
AST_EXT_LIB_CHECK([PJSIP_AUTH_CLT_DEINIT], [pjsip], [pjsip_auth_clt_deinit], [pjsip.h], [$PJPROJECT_LIB], [$PJPROJECT_CFLAGS])
AST_EXT_LIB_CHECK([PJSIP_TSX_LAYER_FIND_TSX2], [pjsip], [pjsip_tsx_layer_find_tsx2], [pjsip.h], [$PJPROJECT_LIB], [$PJPROJECT_CFLAGS])
AST_EXT_LIB_CHECK([PJSIP_TLS_TRANSPORT_RESTART], [pjsip], [pjsip_tls_transport_restart], [pjsip.h], [$PJPROJECT_LIB], [$PJPROJECT_CFLAGS])
AST_EXT_LIB_CHECK([PJSIP_AUTH_NEW_DIGESTS], [pjsip], [pjsip_auth_get_algorithm_by_type], [pjsip.h], [$PJPROJECT_LIB], [$PJPROJECT_CFLAGS])
fi
fi

@ -0,0 +1,31 @@
"""Add fields to ps_auths to support new algorithms
Revision ID: abdc9ede147d
Revises: 44bd6dd914fa
Create Date: 2024-10-27 15:26:25.165085
"""
# revision identifiers, used by Alembic.
revision = 'abdc9ede147d'
down_revision = '44bd6dd914fa'
from alembic import op
import sqlalchemy as sa
max_value_length = 1024
def upgrade():
op.add_column('ps_auths', sa.Column('password_digest', sa.String(max_value_length)))
op.add_column('ps_auths', sa.Column('supported_algorithms_uas', sa.String(max_value_length)))
op.add_column('ps_auths', sa.Column('supported_algorithms_uac', sa.String(max_value_length)))
op.add_column('ps_globals', sa.Column('default_auth_algorithms_uas', sa.String(max_value_length)))
op.add_column('ps_globals', sa.Column('default_auth_algorithms_uac', sa.String(max_value_length)))
def downgrade():
op.drop_column('ps_auths', 'password_digest')
op.drop_column('ps_auths', 'supported_algorithms_uas')
op.drop_column('ps_auths', 'supported_algorithms_uac')
op.drop_column('ps_globals', 'default_auth_algorithms_uas')
op.drop_column('ps_globals', 'default_auth_algorithms_uac')

@ -628,6 +628,10 @@
/* Define to 1 if PJPROJECT has the pjsip_auth_clt_deinit support feature. */
#undef HAVE_PJSIP_AUTH_CLT_DEINIT
/* Define to 1 if PJPROJECT has the PJSIP Auth new digests like SHA-256 and
SHA-512-256 feature. */
#undef HAVE_PJSIP_AUTH_NEW_DIGESTS
/* Define to 1 if PJPROJECT has the PJSIP Dialog Create UAS with Incremented
Lock feature. */
#undef HAVE_PJSIP_DLG_CREATE_UAS_AND_INC_LOCK

@ -72,6 +72,7 @@
#define PJSTR_PRINTF_VAR(_v) ((int)(_v).slen), ((_v).ptr)
#define AST_SIP_AUTH_MAX_REALM_LENGTH 255 /* From the auth/realm realtime column size */
#define AST_SIP_AUTH_MAX_SUPPORTED_ALGORITHMS_LENGTH (255) /* From the supported algorithms realtime column size */
/* ":12345" */
#define COLON_PORT_STRLEN 6
@ -558,25 +559,104 @@ enum ast_sip_dtmf_mode {
};
/*!
* \brief Methods of storing SIP digest authentication credentials.
* \brief Authentication methods.
*
* Note that both methods result in MD5 digest authentication being
* used. The two methods simply alter how Asterisk determines the
* credentials for a SIP authentication
* The meaning of this type has changed. It used to indicate how
* the credentials were stored, but now it indicates which authentication
* method will be used... Google Oauth, Artificial (fake auth) or Digest.
* The USER_PASS and MD5 types are still used for backwards compatibility
* but will map to DIGEST.
*/
enum ast_sip_auth_type {
/*! Credentials stored as a username and password combination */
AST_SIP_AUTH_TYPE_USER_PASS,
/*! Credentials stored as an MD5 sum */
AST_SIP_AUTH_TYPE_NONE = -1,
/*!
* Credentials stored as a username and password combination
* \deprecated Now automatically determined
*/
AST_SIP_AUTH_TYPE_USER_PASS = 0,
/*!
* Credentials stored as an MD5 sum
* \deprecated Use AST_SIP_AUTH_TYPE_DIGEST instead
*/
AST_SIP_AUTH_TYPE_MD5,
/*! Google Oauth */
AST_SIP_AUTH_TYPE_GOOGLE_OAUTH,
/*! Credentials not stored this is a fake auth */
AST_SIP_AUTH_TYPE_ARTIFICIAL
AST_SIP_AUTH_TYPE_ARTIFICIAL,
/*! Digest method will be used */
AST_SIP_AUTH_TYPE_DIGEST,
};
enum ast_sip_auth_cred_usage {
/*! The credentials used as a UAC */
AST_SIP_AUTH_CRED_USAGE_UAC,
/*! The credentials used as a UAS */
AST_SIP_AUTH_CRED_USAGE_UAS,
};
#define SIP_SORCERY_AUTH_TYPE "auth"
#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 AKAV1_MD5 but we need to specify
* it to be compatible with the pjproject definition.
*/
typedef enum pjsip_auth_algorithm_type
{
PJSIP_AUTH_ALGORITHM_NOT_SET = 0,
PJSIP_AUTH_ALGORITHM_MD5,
PJSIP_AUTH_ALGORITHM_SHA256,
PJSIP_AUTH_ALGORITHM_SHA512_256,
PJSIP_AUTH_ALGORITHM_AKAV1_MD5,
PJSIP_AUTH_ALGORITHM_COUNT,
} pjsip_auth_algorithm_type;
typedef struct pjsip_auth_algorithm
{
pjsip_auth_algorithm_type algorithm_type;
pj_str_t iana_name;
const char *openssl_name;
unsigned digest_length;
unsigned digest_str_length;
} pjsip_auth_algorithm;
#endif
/*!
* \brief Get algorithm by algorithm type
*
* \param algorithm_type The algorithm type
* \retval The algorithm or NULL if not found
*/
const pjsip_auth_algorithm *ast_sip_auth_get_algorithm_by_type(
pjsip_auth_algorithm_type algorithm_type);
/*!
* \brief Get algorithm by IANA name
*
* \param iana_name The algorithm IANA name
* \retval The algorithm or NULL if not found
*/
const pjsip_auth_algorithm *ast_sip_auth_get_algorithm_by_iana_name(
const pj_str_t *iana_name);
/*!
* \brief Is algorithm supported by OpenSSL and pjproject?
*
* \param algorithm_type The algorithm IANA name
* \retval The algorithm or NULL if not found
*/
pj_bool_t ast_sip_auth_is_algorithm_supported(
pjsip_auth_algorithm_type algorithm_type);
AST_VECTOR(pjsip_auth_algorithm_type_vector, pjsip_auth_algorithm_type);
struct ast_sip_auth_password_digest {
pjsip_auth_algorithm_type algorithm_type;
char digest[0];
};
struct ast_sip_auth {
/*! Sorcery ID of the auth is its name */
SORCERY_OBJECT(details);
@ -587,7 +667,10 @@ struct ast_sip_auth {
AST_STRING_FIELD(auth_user);
/*! Authentication password */
AST_STRING_FIELD(auth_pass);
/*! Authentication credentials in MD5 format (hash of user:realm:pass) */
/*!
* Authentication credentials in MD5 format (hash of user:realm:pass)
* \deprecated Use password_digests[PJSIP_AUTH_ALGORITHM_MD5] instead.
*/
AST_STRING_FIELD(md5_creds);
/*! Refresh token to use for OAuth authentication */
AST_STRING_FIELD(refresh_token);
@ -600,6 +683,12 @@ struct ast_sip_auth {
unsigned int nonce_lifetime;
/*! Used to determine what to use when authenticating */
enum ast_sip_auth_type type;
/*! Digest algorithms to support when UAC */
struct pjsip_auth_algorithm_type_vector supported_algorithms_uac;
/*! Digest algorithms to send challenges for when UAS */
struct pjsip_auth_algorithm_type_vector supported_algorithms_uas;
/*! Array of pre-digested passwords indexed by pjsip_auth_algorithm_type */
struct ast_sip_auth_password_digest *password_digests[PJSIP_AUTH_ALGORITHM_COUNT];
};
AST_VECTOR(ast_sip_auth_vector, const char *);
@ -1240,6 +1329,33 @@ enum ast_sip_check_auth_result {
AST_SIP_AUTHENTICATION_ERROR,
};
/*!
* \brief Populate a vector of algorithm types from a string.
*
* \param id The object id to use in error messages
* \param algorithms The vector to populate
* \param agent_type The type of agent to use in error messages ("UAC" or "UAS")
* \param value The comma-separated string to parse for algorithms
*
* \retval 0 Success
* \retval non-zero Failure
*/
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);
/*!
* \brief Dump a vector of algorithm types to a string.
*
* \param algorithms The vector to dump
* \param[out] buf Pointer to the buffer to dump the algorithms to
* Must be freed by the caller.
*
* \retval 0 Success
* \retval non-zero Failure
*/
int ast_sip_auth_digest_algorithms_vector_to_str(
const struct pjsip_auth_algorithm_type_vector *algorithms, char **buf);
/*!
* \brief An interchangeable way of handling digest authentication for SIP.
*
@ -3044,6 +3160,40 @@ const char *ast_sip_auth_type_to_str(enum ast_sip_auth_type type);
*/
int ast_sip_auths_to_str(const struct ast_sip_auth_vector *auths, char **buf);
/*!
* \brief Checks an pjsip_auth_algorithm_type_vector to see if it contains an algorithm
*
* \param auth The auth object
* \param algorithms The auth object's supported_algorithms_uac or supported_algorithms_uas
* \param algorithm_type The algorithm_type to check
*
* \retval 1 The algorithm-type is in the vector
* \retval 0 The algorithm-type is not in the vector
*/
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);
/*!
* \brief Get the plain text or digest password from an auth object
*
* \param auth The auth object
* \param algorithm_type The algorithm type to retrieve the password for
* \param cred_type [out]Pointer to an int to receive the credential type
*
* \note cred_type will contain one of the following values:
* - PJSIP_CRED_DATA_DIGEST
* - PJSIP_CRED_DATA_PLAIN_PASSWD
* If a password digest is available for the algorithm type it will
* be returned, otherwise if a plain text password is available
* that will be returned instead.
*
* \retval The plain text or digest password or NULL if not found for the algorithm type
*/
const char *ast_sip_auth_get_creds(const struct ast_sip_auth *auth,
const pjsip_auth_algorithm_type algorithm_type, int *cred_type);
/*!
* \brief AMI variable container
*/
@ -3409,6 +3559,22 @@ char *ast_sip_get_default_voicemail_extension(void);
*/
void ast_sip_get_default_realm(char *realm, size_t size);
/*!
* \brief Retrieve the global auth algorithms for UAS.
*
* \param[out] default_auth_algorithms_uas The default algorithms
* \param size The buffer size of default_auth_algorithms_uas
*/
void ast_sip_get_default_auth_algorithms_uas(char *default_auth_algorithms_uas, size_t size);
/*!
* \brief Retrieve the global auth algorithms for UAC.
*
* \param[out] default_auth_algorithms_uac The default algorithms
* \param size The buffer size of default_auth_algorithms_uac
*/
void ast_sip_get_default_auth_algorithms_uac(char *default_auth_algorithms_uac, size_t size);
/*!
* \brief Retrieve the global default from user.
*

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

@ -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));
while ((val.ptr = ast_strip(strsep(&iana_names, ",")))) {
const pjsip_auth_algorithm *algo;
if (ast_strlen_zero(val.ptr)) {
continue;
}
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;
} 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));
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";
}
break;
case AST_SIP_AUTH_TYPE_GOOGLE_OAUTH:
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);

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

@ -1556,92 +1556,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 name="username">
<synopsis>Username to use for account</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>.
<configOption name="password">
<synopsis>Plain text password used for authentication.</synopsis>
<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 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:
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>
$ echo -n "myname:myrealm:mypassword" | md5sum
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>
<para>
Note the '-n'. You don't want a newline to be part
of the hash.
</para></description>
</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>
</configOption>
<configOption name="refresh_token">
<synopsis>OAuth 2.0 refresh token</synopsis>
@ -1685,19 +1731,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>
@ -2537,6 +2583,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>

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

@ -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(&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);
}
if (pj_strcmp2(acc_name, auth->auth_user)) {
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);
}
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 (!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));
}
return PJ_SUCCESS;
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"
};
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 astobj2 callback for verifying incoming credentials
* \brief Verify 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
* \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 struct ast_sip_auth *auth, pjsip_rx_data *rdata, pj_pool_t *pool)
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);
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 (!find_challenge(rdata, auth)) {
/* Couldn't find a challenge with a sane nonce.
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 (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 (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) {
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 = {

@ -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));
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));
}
/*
* 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));
}
}
/* An empty digest is assumed to be md5 */
if (pj_strlen(&auth_hdr->challenge.digest.algorithm) == 0) {
return 1;
/*
* 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));
}
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;
/*
* 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));
}
return 0;
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;
}
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);
ast_trace(-1, "%s:%s: Retrieved %d auth objects\n", id, src_name,
(int)auth_object_count);
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 = {

@ -139,6 +139,7 @@ AC_DEFUN([_PJPROJECT_CONFIGURE],
AC_DEFINE([HAVE_PJSIP_OAUTH_AUTHENTICATION], 1, [Define if your system has HAVE_PJSIP_OAUTH_AUTHENTICATION declared])
AC_DEFINE([HAVE_PJPROJECT_ON_VALID_ICE_PAIR_CALLBACK], 1, [Define if your system has the on_valid_pair pjnath callback.])
AC_DEFINE([HAVE_PJSIP_TLS_TRANSPORT_RESTART], 1, [Define if your system has pjsip_tls_transport_restart support.])
AC_DEFINE([HAVE_PJSIP_AUTH_NEW_DIGESTS], 1, [Define if your system has pjsip new auth algorithm support.])
AC_SUBST([PJPROJECT_BUNDLED])
AC_SUBST([PJPROJECT_BUNDLED_OOT])

@ -99,7 +99,7 @@
#define PJSIP_TSX_UAS_CONTINUE_ON_TP_ERROR 0
#define PJ_SSL_SOCK_OSSL_USE_THREAD_CB 0
#define PJSIP_AUTH_ALLOW_MULTIPLE_AUTH_HEADER 1
#define PJSIP_AUTH_ALLOW_MULTIPLE_AUTH_HEADER 0
/*
* The default is 32 with 8 being used by pjproject itself.

Loading…
Cancel
Save