mirror of
https://github.com/asterisk/asterisk.git
synced 2025-09-02 11:06:31 +00:00
Add SHA-256 and SHA-512-256 as authentication digest algorithms
* Refactored pjproject code to support the new algorithms and added a patch file to third-party/pjproject/patches * Added new parameters to the pjsip auth object: * password_digest = <algorithm>:<digest> * supported_algorithms_uac = List of algorithms to support when acting as a UAC. * supported_algorithms_uas = List of algorithms to support when acting as a UAS. See the auth object in pjsip.conf.sample for detailed info. * Updated both res_pjsip_authenticator_digest.c (for UAS) and res_pjsip_outbound_authentocator_digest.c (UAC) to suport the new algorithms. The new algorithms are only available with the bundled version of pjproject, or an external version > 2.14.1. OpenSSL version 1.1.1 or greater is required to support SHA-512-256. Resolves: #948 UserNote: The SHA-256 and SHA-512-256 algorithms are now available for authentication as both a UAS and a UAC.
This commit is contained in:
@@ -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.
|
||||
; authentication uses.
|
||||
;
|
||||
;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: "")
|
||||
; 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
115
configure
vendored
@@ -937,6 +937,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
|
||||
@@ -22040,6 +22044,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
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -24298,6 +24305,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
|
||||
|
||||
|
||||
@@ -39870,6 +39889,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
|
||||
|
||||
|
@@ -622,6 +622,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])
|
||||
@@ -2550,6 +2551,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')
|
@@ -637,6 +637,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));
|
||||
res = -1;
|
||||
} else if (strlen(auth->md5_creds) != PJSIP_MD5STRLEN) {
|
||||
ast_log(LOG_ERROR, "'md5' authentication requires digest of size '%d', but "
|
||||
"digest is '%d' in size for auth '%s'\n", PJSIP_MD5STRLEN, (int)strlen(auth->md5_creds),
|
||||
ast_sorcery_object_get_id(auth));
|
||||
res = -1;
|
||||
while ((val.ptr = ast_strip(strsep(&iana_names, ",")))) {
|
||||
const pjsip_auth_algorithm *algo;
|
||||
|
||||
if (ast_strlen_zero(val.ptr)) {
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
case AST_SIP_AUTH_TYPE_GOOGLE_OAUTH:
|
||||
val.slen = strlen(val.ptr);
|
||||
|
||||
algo = ast_sip_auth_get_algorithm_by_iana_name(&val);
|
||||
if (!algo) {
|
||||
ast_log(LOG_WARNING, "%s: Unknown %s digest algorithm '%s' specified\n",
|
||||
id, agent_type, val.ptr);
|
||||
res = -1;
|
||||
continue;
|
||||
}
|
||||
if (!ast_sip_auth_is_algorithm_supported(algo->algorithm_type)) {
|
||||
ast_log(LOG_WARNING, "%s: %s digest algorithm '%s' is not supported by the version of OpenSSL in use\n",
|
||||
id, agent_type, val.ptr);
|
||||
res = -1;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (AST_VECTOR_APPEND(algorithms, algo->algorithm_type)) {
|
||||
AST_VECTOR_FREE(algorithms);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
static int uac_algorithms_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
|
||||
{
|
||||
struct ast_sip_auth *auth = obj;
|
||||
|
||||
return ast_sip_auth_digest_algorithms_vector_init(ast_sorcery_object_get_id(auth),
|
||||
&auth->supported_algorithms_uac, "UAC", var->value);
|
||||
}
|
||||
|
||||
static int uas_algorithms_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
|
||||
{
|
||||
struct ast_sip_auth *auth = obj;
|
||||
|
||||
return ast_sip_auth_digest_algorithms_vector_init(ast_sorcery_object_get_id(auth),
|
||||
&auth->supported_algorithms_uas, "UAS", var->value);
|
||||
}
|
||||
|
||||
int ast_sip_auth_digest_algorithms_vector_to_str(
|
||||
const struct pjsip_auth_algorithm_type_vector *algorithms, char **buf)
|
||||
{
|
||||
struct ast_str *str = NULL;
|
||||
int i = 0;
|
||||
|
||||
if (!algorithms || !AST_VECTOR_SIZE(algorithms)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
str = ast_str_alloca(256);
|
||||
if (!str) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (i = 0; i < AST_VECTOR_SIZE(algorithms); ++i) {
|
||||
const pjsip_auth_algorithm *algo = ast_sip_auth_get_algorithm_by_type(
|
||||
AST_VECTOR_GET(algorithms, i));
|
||||
ast_str_append(&str, 0, "%s" PJSTR_PRINTF_SPEC, i > 0 ? "," : "",
|
||||
PJSTR_PRINTF_VAR(algo->iana_name));
|
||||
}
|
||||
|
||||
*buf = ast_strdup(ast_str_buffer(str));
|
||||
|
||||
return *buf ? 0 : -1;
|
||||
}
|
||||
|
||||
static int uac_algorithms_to_str(const void *obj, const intptr_t *args, char **buf)
|
||||
{
|
||||
const struct ast_sip_auth *auth = obj;
|
||||
return ast_sip_auth_digest_algorithms_vector_to_str(&auth->supported_algorithms_uac, buf);
|
||||
}
|
||||
|
||||
static int uas_algorithms_to_str(const void *obj, const intptr_t *args, char **buf)
|
||||
{
|
||||
const struct ast_sip_auth *auth = obj;
|
||||
return ast_sip_auth_digest_algorithms_vector_to_str(&auth->supported_algorithms_uas, buf);
|
||||
}
|
||||
|
||||
static int password_digest_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
|
||||
{
|
||||
struct ast_sip_auth *auth = obj;
|
||||
const char *auth_name = ast_sorcery_object_get_id(auth);
|
||||
char *value = ast_strdupa(var->value);
|
||||
char *unparsed_digest = NULL;
|
||||
|
||||
while ((unparsed_digest = ast_strsep(&value, ',', AST_STRSEP_TRIM))) {
|
||||
const pjsip_auth_algorithm *algo;
|
||||
char *iana_name;
|
||||
char *digest;
|
||||
struct ast_sip_auth_password_digest *pw;
|
||||
pj_str_t pj_iana_name;
|
||||
|
||||
if (ast_strlen_zero(unparsed_digest)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (strchr(unparsed_digest, ':') != NULL) {
|
||||
iana_name = ast_strsep(&unparsed_digest, ':', AST_STRSEP_TRIM);
|
||||
} else {
|
||||
/*
|
||||
* md5_cred doesn't have the algorithm name in front
|
||||
* so we need to force it.
|
||||
*/
|
||||
iana_name = "MD5";
|
||||
}
|
||||
digest = unparsed_digest;
|
||||
|
||||
pj_iana_name = pj_str(iana_name);
|
||||
|
||||
algo = ast_sip_auth_get_algorithm_by_iana_name(&pj_iana_name);
|
||||
if (!algo) {
|
||||
ast_log(LOG_WARNING, "%s: Unknown password_digest algorithm '%s' specified\n",
|
||||
auth_name, iana_name);
|
||||
return -1;
|
||||
}
|
||||
if (!ast_sip_auth_is_algorithm_supported(algo->algorithm_type)) {
|
||||
ast_log(LOG_WARNING, "%s: password_digest algorithm '%s' is not supported by the version of OpenSSL in use\n",
|
||||
auth_name, iana_name);
|
||||
return -1;
|
||||
}
|
||||
if (strlen(digest) != algo->digest_str_length) {
|
||||
ast_log(LOG_WARNING, "%s: password_digest algorithm '%s' length (%d) must be %d\n",
|
||||
auth_name, iana_name, (int)strlen(digest), (int)algo->digest_str_length);
|
||||
return -1;
|
||||
}
|
||||
|
||||
pw = ast_calloc(1, sizeof(*pw) + strlen(digest) + 1);
|
||||
if (!pw) {
|
||||
return -1;
|
||||
}
|
||||
pw->algorithm_type = algo->algorithm_type;
|
||||
strcpy(pw->digest, digest); /* Safe */
|
||||
auth->password_digests[pw->algorithm_type] = pw;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int password_digest_to_str(const void *obj, const intptr_t *args, char **buf)
|
||||
{
|
||||
const struct ast_sip_auth *auth = obj;
|
||||
struct ast_str *str = ast_str_alloca(256);
|
||||
int i = 0;
|
||||
int count = 0;
|
||||
|
||||
for (i = PJSIP_AUTH_ALGORITHM_NOT_SET + 1; i < PJSIP_AUTH_ALGORITHM_COUNT; i++) {
|
||||
struct ast_sip_auth_password_digest *pw =
|
||||
auth->password_digests[i];
|
||||
const pjsip_auth_algorithm *algorithm;
|
||||
|
||||
if (!pw) {
|
||||
continue;
|
||||
}
|
||||
|
||||
algorithm = ast_sip_auth_get_algorithm_by_type(pw->algorithm_type);
|
||||
|
||||
ast_str_append(&str, 0, "%s" PJSTR_PRINTF_SPEC ":%s", count > 0 ? "," : "",
|
||||
PJSTR_PRINTF_VAR(algorithm->iana_name), pw->digest);
|
||||
count++;
|
||||
}
|
||||
|
||||
*buf = ast_strdup(ast_str_buffer(str));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int md5cred_to_str(const void *obj, const intptr_t *args, char **buf)
|
||||
{
|
||||
const struct ast_sip_auth *auth = obj;
|
||||
|
||||
if (auth->password_digests[PJSIP_AUTH_ALGORITHM_MD5]) {
|
||||
*buf = ast_strdup(auth->password_digests[PJSIP_AUTH_ALGORITHM_MD5]->digest);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ast_sip_auth_is_algorithm_available(const struct ast_sip_auth *auth,
|
||||
const struct pjsip_auth_algorithm_type_vector *algorithms,
|
||||
pjsip_auth_algorithm_type algorithm_type)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (!algorithms) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (i = 0; i < AST_VECTOR_SIZE(algorithms); ++i) {
|
||||
if (AST_VECTOR_GET(algorithms, i) == algorithm_type) {
|
||||
if (auth->password_digests[algorithm_type] || !ast_strlen_zero(auth->auth_pass)) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char *ast_sip_auth_get_creds(const struct ast_sip_auth *auth,
|
||||
const pjsip_auth_algorithm_type algorithm_type, int *cred_type)
|
||||
{
|
||||
struct ast_sip_auth_password_digest *pw_digest =
|
||||
auth->password_digests[algorithm_type];
|
||||
|
||||
if (pw_digest) {
|
||||
*cred_type = PJSIP_CRED_DATA_DIGEST;
|
||||
return pw_digest->digest;
|
||||
}
|
||||
|
||||
*cred_type = PJSIP_CRED_DATA_PLAIN_PASSWD;
|
||||
return auth->auth_pass;
|
||||
}
|
||||
|
||||
static int check_algorithm(const struct ast_sip_auth *auth,
|
||||
const pjsip_auth_algorithm_type algorithm_type, const char *which_supported)
|
||||
{
|
||||
const pjsip_auth_algorithm *algo = ast_sip_auth_get_algorithm_by_type(algorithm_type);
|
||||
struct ast_sip_auth_password_digest *pw_digest =
|
||||
auth->password_digests[algorithm_type];
|
||||
|
||||
if (!pw_digest && ast_strlen_zero(auth->auth_pass)) {
|
||||
ast_log(LOG_ERROR, "%s: No plain text or digest password found for algorithm "
|
||||
PJSTR_PRINTF_SPEC " in supported_algorithms_%s\n",
|
||||
ast_sorcery_object_get_id(auth), PJSTR_PRINTF_VAR(algo->iana_name), which_supported);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int auth_apply(const struct ast_sorcery *sorcery, void *obj)
|
||||
{
|
||||
struct ast_sip_auth *auth = obj;
|
||||
const char *id = ast_sorcery_object_get_id(auth);
|
||||
int i = 0;
|
||||
int res = 0;
|
||||
|
||||
if (ast_strlen_zero(auth->auth_user)) {
|
||||
ast_log(LOG_ERROR, "%s: No authentication username\n", id);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (auth->type == AST_SIP_AUTH_TYPE_GOOGLE_OAUTH) {
|
||||
if (ast_strlen_zero(auth->refresh_token)
|
||||
|| ast_strlen_zero(auth->oauth_clientid)
|
||||
|| ast_strlen_zero(auth->oauth_secret)) {
|
||||
ast_log(LOG_ERROR, "'google_oauth' authentication specified but refresh_token,"
|
||||
" oauth_clientid, or oauth_secret not specified for auth '%s'\n",
|
||||
ast_sorcery_object_get_id(auth));
|
||||
ast_log(LOG_ERROR, "%s: 'google_oauth' authentication specified but refresh_token,"
|
||||
" oauth_clientid, or oauth_secret not specified\n", id);
|
||||
res = -1;
|
||||
}
|
||||
break;
|
||||
case AST_SIP_AUTH_TYPE_USER_PASS:
|
||||
case AST_SIP_AUTH_TYPE_ARTIFICIAL:
|
||||
break;
|
||||
return res;
|
||||
}
|
||||
|
||||
if (AST_VECTOR_SIZE(&auth->supported_algorithms_uas) == 0) {
|
||||
char *default_algo_uas = ast_alloca(AST_SIP_AUTH_MAX_SUPPORTED_ALGORITHMS_LENGTH + 1);
|
||||
ast_sip_get_default_auth_algorithms_uas(default_algo_uas, AST_SIP_AUTH_MAX_SUPPORTED_ALGORITHMS_LENGTH);
|
||||
ast_sip_auth_digest_algorithms_vector_init(id, &auth->supported_algorithms_uas, "UAS", default_algo_uas);
|
||||
}
|
||||
if (AST_VECTOR_SIZE(&auth->supported_algorithms_uac) == 0) {
|
||||
char *default_algo_uac = ast_alloca(AST_SIP_AUTH_MAX_SUPPORTED_ALGORITHMS_LENGTH + 1);
|
||||
ast_sip_get_default_auth_algorithms_uac(default_algo_uac, AST_SIP_AUTH_MAX_SUPPORTED_ALGORITHMS_LENGTH);
|
||||
ast_sip_auth_digest_algorithms_vector_init(id, &auth->supported_algorithms_uac, "UAC", default_algo_uac);
|
||||
}
|
||||
|
||||
for (i = 0; i < AST_VECTOR_SIZE(&auth->supported_algorithms_uas); i++) {
|
||||
res += check_algorithm(auth, AST_VECTOR_GET(&auth->supported_algorithms_uas, i), "uas");
|
||||
}
|
||||
|
||||
for (i = 0; i < AST_VECTOR_SIZE(&auth->supported_algorithms_uac); i++) {
|
||||
res += check_algorithm(auth, AST_VECTOR_GET(&auth->supported_algorithms_uac, i), "uac");
|
||||
}
|
||||
|
||||
return res;
|
||||
@@ -366,6 +719,18 @@ static struct ast_cli_entry cli_commands[] = {
|
||||
|
||||
static struct ast_sip_cli_formatter_entry *cli_formatter;
|
||||
|
||||
#if 1
|
||||
static void global_loaded(const char *object_type)
|
||||
{
|
||||
ast_sorcery_force_reload_object(ast_sip_get_sorcery(), "auth");
|
||||
}
|
||||
|
||||
/*! \brief Observer which is used to update our interval and default_realm when the global setting changes */
|
||||
static struct ast_sorcery_observer global_observer = {
|
||||
.loaded = global_loaded,
|
||||
};
|
||||
#endif
|
||||
|
||||
/*! \brief Initialize sorcery with auth support */
|
||||
int ast_sip_initialize_sorcery_auth(void)
|
||||
{
|
||||
@@ -389,14 +754,20 @@ int ast_sip_initialize_sorcery_auth(void)
|
||||
"", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_auth, oauth_clientid));
|
||||
ast_sorcery_object_field_register(sorcery, SIP_SORCERY_AUTH_TYPE, "oauth_secret",
|
||||
"", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_auth, oauth_secret));
|
||||
ast_sorcery_object_field_register(sorcery, SIP_SORCERY_AUTH_TYPE, "md5_cred",
|
||||
"", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_auth, md5_creds));
|
||||
ast_sorcery_object_field_register_custom(sorcery, SIP_SORCERY_AUTH_TYPE, "md5_cred",
|
||||
NULL, password_digest_handler, md5cred_to_str, NULL, 0, 0);
|
||||
ast_sorcery_object_field_register(sorcery, SIP_SORCERY_AUTH_TYPE, "realm",
|
||||
"", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_auth, realm));
|
||||
ast_sorcery_object_field_register(sorcery, SIP_SORCERY_AUTH_TYPE, "nonce_lifetime",
|
||||
"32", OPT_UINT_T, 0, FLDSET(struct ast_sip_auth, nonce_lifetime));
|
||||
ast_sorcery_object_field_register_custom(sorcery, SIP_SORCERY_AUTH_TYPE, "auth_type",
|
||||
"userpass", auth_type_handler, auth_type_to_str, NULL, 0, 0);
|
||||
ast_sorcery_object_field_register_custom(sorcery, SIP_SORCERY_AUTH_TYPE, "password_digest",
|
||||
NULL, password_digest_handler, password_digest_to_str, NULL, 0, 0);
|
||||
ast_sorcery_object_field_register_custom(sorcery, SIP_SORCERY_AUTH_TYPE, "supported_algorithms_uac",
|
||||
"", uac_algorithms_handler, uac_algorithms_to_str, NULL, 0, 0);
|
||||
ast_sorcery_object_field_register_custom(sorcery, SIP_SORCERY_AUTH_TYPE, "supported_algorithms_uas",
|
||||
"", uas_algorithms_handler, uas_algorithms_to_str, NULL, 0, 0);
|
||||
|
||||
ast_sip_register_endpoint_formatter(&endpoint_auth_formatter);
|
||||
|
||||
@@ -420,11 +791,14 @@ int ast_sip_initialize_sorcery_auth(void)
|
||||
return -1;
|
||||
}
|
||||
|
||||
ast_sorcery_observer_add(sorcery, "global", &global_observer);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ast_sip_destroy_sorcery_auth(void)
|
||||
{
|
||||
ast_sorcery_observer_remove(ast_sip_get_sorcery(), "global", &global_observer);
|
||||
|
||||
ast_cli_unregister_multiple(cli_commands, ARRAY_LEN(cli_commands));
|
||||
ast_sip_unregister_cli_formatter(cli_formatter);
|
||||
ast_sip_unregister_endpoint_formatter(&endpoint_auth_formatter);
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -1558,92 +1558,138 @@
|
||||
</configOption>
|
||||
</configObject>
|
||||
<configObject name="auth">
|
||||
<!--
|
||||
Be sure to update the following documentation page when making changes to this object:
|
||||
https://docs.asterisk.org/Configuration/Channel-Drivers/SIP/Configuring-res_pjsip/PJSIP-Authentication
|
||||
-->
|
||||
<synopsis>Authentication type</synopsis>
|
||||
<description><para>
|
||||
Authentication objects hold the authentication information for use
|
||||
by other objects such as <literal>endpoints</literal> or <literal>registrations</literal>.
|
||||
This also allows for multiple objects to use a single auth object. See
|
||||
the <literal>auth_type</literal> config option for password style choices.
|
||||
</para></description>
|
||||
<configOption name="auth_type" default="userpass">
|
||||
the <literal>auth_type</literal> config option for security mechanism choices.
|
||||
</para>
|
||||
<note><para>
|
||||
See the link below for detailed discussion of this object especially concerning
|
||||
realms and digest hash algorithms.
|
||||
</para>
|
||||
<para>
|
||||
https://docs.asterisk.org/Configuration/Channel-Drivers/SIP/Configuring-res_pjsip/PJSIP-Authentication
|
||||
</para>
|
||||
</note>
|
||||
</description>
|
||||
<see-also>
|
||||
<ref type="link">https://docs.asterisk.org/Configuration/Channel-Drivers/SIP/Configuring-res_pjsip/PJSIP-Authentication</ref>
|
||||
</see-also>
|
||||
<configOption name="auth_type" default="digest">
|
||||
<synopsis>Authentication type</synopsis>
|
||||
<description><para>
|
||||
This option specifies which of the password style config options should be read
|
||||
when trying to authenticate an endpoint inbound request. If set to <literal>userpass</literal>
|
||||
then we'll read from the 'password' option. For <literal>md5</literal> we'll read
|
||||
from 'md5_cred'. If set to <literal>google_oauth</literal> then we'll read from the
|
||||
refresh_token/oauth_clientid/oauth_secret fields. The following values are valid:
|
||||
If set to <literal>google_oauth</literal> then we'll read from the
|
||||
refresh_token/oauth_clientid/oauth_secret parameters.
|
||||
If set to <literal>digest</literal> then we'll read from the
|
||||
<literal>password</literal> and/or <literal>password_digest</literal>
|
||||
parameters. The older <literal>md5</literal> and <literal>userpass</literal>
|
||||
values are deprecated and converted to <literal>digest</literal>.
|
||||
</para>
|
||||
<enumlist>
|
||||
<enum name="md5"/>
|
||||
<enum name="userpass"/>
|
||||
<enum name="google_oauth"/>
|
||||
<enum name="userpass"><para>Deprecated. Use <literal>digest</literal>.</para></enum>
|
||||
<enum name="md5"><para>Deprecated. Use <literal>digest</literal>.</para></enum>
|
||||
<enum name="google_oauth"><para>If selected, the <literal>refresh_token</literal>,
|
||||
<literal>oauth_clientid</literal> and <literal>oauth_secret</literal>
|
||||
parameters must be provided.</para></enum>
|
||||
<enum name="digest"><para>If selected, the <literal>password</literal>
|
||||
and/or one or more <literal>password_digest</literal>
|
||||
parameters must be provided.</para></enum>
|
||||
</enumlist>
|
||||
<para>
|
||||
</para>
|
||||
<note>
|
||||
<para>
|
||||
This setting only describes whether the password is in
|
||||
plain text or has been pre-hashed with MD5. It doesn't describe
|
||||
the acceptable digest algorithms we'll accept in a received
|
||||
challenge.
|
||||
</para>
|
||||
</note>
|
||||
</description>
|
||||
</configOption>
|
||||
<configOption name="nonce_lifetime" default="32">
|
||||
<synopsis>Lifetime of a nonce associated with this authentication config.</synopsis>
|
||||
</configOption>
|
||||
<configOption name="md5_cred" default="">
|
||||
<synopsis>MD5 Hash used for authentication.</synopsis>
|
||||
<description><para>
|
||||
Only used when auth_type is <literal>md5</literal>.
|
||||
As an alternative to specifying a plain text password,
|
||||
you can hash the username, realm and password
|
||||
together one time and place the hash value here.
|
||||
The input to the hash function must be in the
|
||||
following format:
|
||||
</para>
|
||||
<para>
|
||||
</para>
|
||||
<para>
|
||||
<username>:<realm>:<password>
|
||||
</para>
|
||||
<para>
|
||||
</para>
|
||||
<para>
|
||||
For incoming authentication (asterisk is the server),
|
||||
the realm must match either the realm set in this object
|
||||
or the <variable>default_realm</variable> set in in the
|
||||
<replaceable>global</replaceable> object.
|
||||
</para>
|
||||
<para>
|
||||
</para>
|
||||
<para>
|
||||
For outgoing authentication (asterisk is the UAC),
|
||||
the realm must match what the server will be sending
|
||||
in their WWW-Authenticate header. It can't be blank
|
||||
unless you expect the server to be sending a blank
|
||||
realm in the header. You can't use pre-hashed
|
||||
passwords with a wildcard auth object.
|
||||
You can generate the hash with the following shell
|
||||
command:
|
||||
</para>
|
||||
<para>
|
||||
</para>
|
||||
<para>
|
||||
$ echo -n "myname:myrealm:mypassword" | md5sum
|
||||
</para>
|
||||
<para>
|
||||
</para>
|
||||
<para>
|
||||
Note the '-n'. You don't want a newline to be part
|
||||
of the hash.
|
||||
</para></description>
|
||||
<configOption name="username">
|
||||
<synopsis>Username to use for account</synopsis>
|
||||
</configOption>
|
||||
<configOption name="password">
|
||||
<synopsis>Plain text password used for authentication.</synopsis>
|
||||
<description><para>Only used when auth_type is <literal>userpass</literal>.</para></description>
|
||||
<description><para>Only used when auth_type is <literal>digest</literal>.</para></description>
|
||||
</configOption>
|
||||
<configOption name="password_digest" default="">
|
||||
<synopsis>One or more pre-computed hashes used for authentication.</synopsis>
|
||||
<description><para>Only used when auth_type is <literal>digest</literal>.
|
||||
As an alternative to specifying a plain text password,
|
||||
you can specify one or more pre-computed digests separated by
|
||||
commas.
|
||||
</para>
|
||||
<para>
|
||||
<literal>password_digest= <digest-spec>[,<digest_spec>]...</literal>
|
||||
</para>
|
||||
<enumlist>
|
||||
<enum name="<digest-spec>"><para><hash-algorithm>:<hashed-credential></para></enum>
|
||||
<enum name="<hash-algorithm>"><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 >> 1.0.0 and pjproject versions >= 2.15.1</para></enum>
|
||||
<enum name="SHA-512-256"><para>Supported by OpenSSL versions >= 1.1.1 and pjproject versions >= 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="<hashed-credential>">
|
||||
<para>The result of passing the following string through
|
||||
the selected hash algorithm:
|
||||
<literal><username>:<realm>:<password></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 > 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>
|
||||
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 > 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>
|
||||
The default may be specified by the
|
||||
<literal>default_auth_algorithms_uas</literal> parameter in
|
||||
the global object. If that's not specified, the default is "MD5".
|
||||
</para>
|
||||
</description>
|
||||
</configOption>
|
||||
<configOption name="refresh_token">
|
||||
<synopsis>OAuth 2.0 refresh token</synopsis>
|
||||
@@ -1687,19 +1733,19 @@
|
||||
<note>
|
||||
<para>
|
||||
If more than one auth object with the same realm or
|
||||
more than one wildcard auth object associated to
|
||||
an endpoint, we can only use the first one of
|
||||
each defined on the endpoint.
|
||||
more than one wildcard auth object is associated to
|
||||
an endpoint, only the first one of each defined on
|
||||
the endpoint will be used.
|
||||
</para>
|
||||
</note>
|
||||
</description>
|
||||
</configOption>
|
||||
<configOption name="nonce_lifetime" default="32">
|
||||
<synopsis>Lifetime of a nonce associated with this authentication config.</synopsis>
|
||||
</configOption>
|
||||
<configOption name="type">
|
||||
<synopsis>Must be 'auth'</synopsis>
|
||||
</configOption>
|
||||
<configOption name="username">
|
||||
<synopsis>Username to use for account</synopsis>
|
||||
</configOption>
|
||||
</configObject>
|
||||
<configObject name="domain_alias">
|
||||
<synopsis>Domain Alias</synopsis>
|
||||
@@ -2539,6 +2585,28 @@
|
||||
RFC 3261 specifies this as a SHOULD requirement.
|
||||
</para></description>
|
||||
</configOption>
|
||||
<configOption name="default_auth_algorithms_uas" default="no">
|
||||
<synopsis>List of default authentication algorithms to support when Asterisk is UAS</synopsis>
|
||||
<description><para>Valid values:</para>
|
||||
<enumlist>
|
||||
<enum name="md5"><para>Supported by all versions of OpenSSL and pjproject</para></enum>
|
||||
<enum name="sha-256"><para>Supported by all versions of OpenSSL but only pjproject versions > 2.14.1</para></enum>
|
||||
<enum name="sha-512-256"><para>Supported by OpenSSL versions >= 1.1.1 and pjproject versions > 2.14.1</para></enum>
|
||||
</enumlist>
|
||||
<para>If not specified, the default is <literal>MD5</literal> only.</para>
|
||||
</description>
|
||||
</configOption>
|
||||
<configOption name="default_auth_algorithms_uac" default="no">
|
||||
<synopsis>List of default authentication algorithms to support when Asterisk is UAC</synopsis>
|
||||
<description><para>Valid values:</para>
|
||||
<enumlist>
|
||||
<enum name="md5"><para>Supported by all versions of OpenSSL and pjproject</para></enum>
|
||||
<enum name="sha-256"><para>Supported by all versions of OpenSSL but only pjproject versions > 2.14.1</para></enum>
|
||||
<enum name="sha-512-256"><para>Supported by OpenSSL versions >= 1.1.1 and pjproject versions > 2.14.1</para></enum>
|
||||
</enumlist>
|
||||
<para>If not specified, the default is <literal>MD5</literal> only.</para>
|
||||
</description>
|
||||
</configOption>
|
||||
</configObject>
|
||||
</configFile>
|
||||
</configInfo>
|
||||
|
@@ -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(acc_name, auth->auth_user)) {
|
||||
return PJSIP_SC_FORBIDDEN;
|
||||
if (pj_strcmp2(¶m->realm, realm) != 0) {
|
||||
/*
|
||||
* This shouldn't happen because param->realm was passed in from the auth
|
||||
* when we called pjsip_auth_srv_init2.
|
||||
*/
|
||||
SCOPE_EXIT_RTN_VALUE(PJSIP_SC_FORBIDDEN, "%s:%s: Realm '%s' mismatch\n",
|
||||
auth_name, src_name, realm);
|
||||
}
|
||||
|
||||
pj_strdup2(pool, &info->realm, auth->realm);
|
||||
pj_strdup2(pool, &info->username, auth->auth_user);
|
||||
|
||||
switch (auth->type) {
|
||||
case AST_SIP_AUTH_TYPE_USER_PASS:
|
||||
pj_strdup2(pool, &info->data, auth->auth_pass);
|
||||
info->data_type = PJSIP_CRED_DATA_PLAIN_PASSWD;
|
||||
break;
|
||||
case AST_SIP_AUTH_TYPE_MD5:
|
||||
pj_strdup2(pool, &info->data, auth->md5_creds);
|
||||
info->data_type = PJSIP_CRED_DATA_DIGEST;
|
||||
break;
|
||||
default:
|
||||
return PJSIP_SC_FORBIDDEN;
|
||||
if (pj_strcmp2(¶m->acc_name, auth->auth_user) != 0) {
|
||||
SCOPE_EXIT_RTN_VALUE(PJSIP_SC_FORBIDDEN, "%s:%s: Username '%s' mismatch\n",
|
||||
auth_name, src_name, auth->auth_user);
|
||||
}
|
||||
return PJ_SUCCESS;
|
||||
|
||||
if (!ast_sip_auth_is_algorithm_available(auth, &auth->supported_algorithms_uas,
|
||||
algorithm->algorithm_type)) {
|
||||
/*
|
||||
* This shouldn't happen because we shouldn't have sent a challenge for
|
||||
* an unsupported algorithm.
|
||||
*/
|
||||
SCOPE_EXIT_RTN_VALUE(PJSIP_SC_FORBIDDEN, "%s:%s: Algorithm '" PJSTR_PRINTF_SPEC
|
||||
"' not supported or auth doesn't contain appropriate credentials\n",
|
||||
auth_name, src_name, PJSTR_PRINTF_VAR(algorithm->iana_name));
|
||||
}
|
||||
|
||||
pj_strdup2(pool, &cred_info->realm, realm);
|
||||
pj_strdup2(pool, &cred_info->username, auth->auth_user);
|
||||
|
||||
creds = ast_sip_auth_get_creds(auth, algorithm->algorithm_type, &cred_info->data_type);
|
||||
if (!creds) {
|
||||
/*
|
||||
* This shouldn't happen because we checked the auth object when we
|
||||
* loaded it to make sure it had the appropriate credentials for each
|
||||
* algorithm in supported_algorithms_uas.
|
||||
*/
|
||||
SCOPE_EXIT_RTN_VALUE(PJSIP_SC_FORBIDDEN, "%s:%s: No plain text or digest password found for algorithm '" PJSTR_PRINTF_SPEC "'\n",
|
||||
auth_name, src_name, PJSTR_PRINTF_VAR(algorithm->iana_name));
|
||||
}
|
||||
pj_strdup2(pool, &cred_info->data, creds);
|
||||
#ifdef HAVE_PJSIP_AUTH_NEW_DIGESTS
|
||||
if (cred_info->data_type == PJSIP_CRED_DATA_DIGEST) {
|
||||
cred_info->algorithm_type = algorithm->algorithm_type;
|
||||
}
|
||||
#endif
|
||||
|
||||
SCOPE_EXIT_RTN_VALUE(PJ_SUCCESS, "%s:%s: Success. Data type: %s Algorithm '" PJSTR_PRINTF_SPEC "'\n",
|
||||
auth_name, src_name, cred_info->data_type ? "digest" : "plain text", PJSTR_PRINTF_VAR(algorithm->iana_name));
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -202,7 +284,8 @@ static pj_status_t digest_lookup(pj_pool_t *pool, const pj_str_t *realm,
|
||||
* \param rdata The incoming request
|
||||
* \param realm The realm for which authentication should occur
|
||||
*/
|
||||
static int build_nonce(struct ast_str **nonce, const char *timestamp, const pjsip_rx_data *rdata, const char *realm)
|
||||
static int build_nonce(struct ast_str **nonce, const char *timestamp,
|
||||
const pjsip_rx_data *rdata, const char *realm)
|
||||
{
|
||||
struct ast_str *str = ast_str_alloca(256);
|
||||
RAII_VAR(char *, eid, ao2_global_obj_ref(entity_id), ao2_cleanup);
|
||||
@@ -255,7 +338,7 @@ static int check_nonce(const char *candidate, const pjsip_rx_data *rdata, const
|
||||
return 0;
|
||||
}
|
||||
|
||||
build_nonce(&calculated, timestamp, rdata, auth->realm);
|
||||
build_nonce(&calculated, timestamp, rdata, S_OR(auth->realm, default_realm));
|
||||
ast_debug(3, "Calculated nonce %s. Actual nonce is %s\n", ast_str_buffer(calculated), candidate);
|
||||
if (strcmp(ast_str_buffer(calculated), candidate)) {
|
||||
return 0;
|
||||
@@ -263,34 +346,6 @@ static int check_nonce(const char *candidate, const pjsip_rx_data *rdata, const
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int find_challenge(const pjsip_rx_data *rdata, const struct ast_sip_auth *auth)
|
||||
{
|
||||
struct pjsip_authorization_hdr *auth_hdr = (pjsip_authorization_hdr *) &rdata->msg_info.msg->hdr;
|
||||
int challenge_found = 0;
|
||||
char nonce[64];
|
||||
|
||||
while ((auth_hdr = (pjsip_authorization_hdr *) pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_AUTHORIZATION, auth_hdr->next))) {
|
||||
ast_copy_pj_str(nonce, &auth_hdr->credential.digest.nonce, sizeof(nonce));
|
||||
if (check_nonce(nonce, rdata, auth) && !pj_strcmp2(&auth_hdr->credential.digest.realm, auth->realm)) {
|
||||
challenge_found = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return challenge_found;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Common code for initializing a pjsip_auth_srv
|
||||
*/
|
||||
static void setup_auth_srv(pj_pool_t *pool, pjsip_auth_srv *auth_server, const char *realm)
|
||||
{
|
||||
pj_str_t realm_str;
|
||||
pj_cstr(&realm_str, realm);
|
||||
|
||||
pjsip_auth_srv_init(pool, auth_server, &realm_str, digest_lookup, 0);
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Result of digest verification
|
||||
*/
|
||||
@@ -311,69 +366,147 @@ static char *verify_result_str[] = {
|
||||
"STALE",
|
||||
"NOAUTH"
|
||||
};
|
||||
/*!
|
||||
* \brief astobj2 callback for verifying incoming credentials
|
||||
*
|
||||
* \param auth The ast_sip_auth to check against
|
||||
* \param rdata The incoming request
|
||||
* \param pool A pool to use for the auth server
|
||||
* \return CMP_MATCH on successful authentication
|
||||
* \return 0 on failed authentication
|
||||
*/
|
||||
static int verify(const struct ast_sip_auth *auth, pjsip_rx_data *rdata, pj_pool_t *pool)
|
||||
|
||||
static enum digest_verify_result find_authorization(const char *endpoint_id,
|
||||
const struct ast_sip_auth *auth, const pjsip_rx_data *rdata)
|
||||
{
|
||||
const char *auth_id = ast_sorcery_object_get_id(auth);
|
||||
const char *src_name = rdata->pkt_info.src_name;
|
||||
const char *realm = S_OR(auth->realm, default_realm);
|
||||
struct pjsip_authorization_hdr *auth_hdr =
|
||||
(pjsip_authorization_hdr *) &rdata->msg_info.msg->hdr;
|
||||
enum digest_verify_result res = AUTH_NOAUTH;
|
||||
int authorization_found = 0;
|
||||
char nonce[64];
|
||||
SCOPE_ENTER(3, "%s:%s:%s: realm: %s\n",
|
||||
endpoint_id, auth_id, src_name, realm);
|
||||
|
||||
while ((auth_hdr = pjsip_msg_find_hdr(rdata->msg_info.msg,
|
||||
PJSIP_H_AUTHORIZATION, auth_hdr ? auth_hdr->next : NULL))) {
|
||||
ast_copy_pj_str(nonce, &auth_hdr->credential.digest.nonce, sizeof(nonce));
|
||||
ast_trace(-1, "%s:%s:%s: Checking nonce %s hdr-realm: " PJSTR_PRINTF_SPEC " hdr-algo: " PJSTR_PRINTF_SPEC " \n",
|
||||
endpoint_id, auth_id, src_name, nonce,
|
||||
PJSTR_PRINTF_VAR(auth_hdr->credential.digest.realm),
|
||||
PJSTR_PRINTF_VAR(auth_hdr->credential.digest.algorithm));
|
||||
authorization_found++;
|
||||
if (check_nonce(nonce, rdata, auth)
|
||||
&& pj_strcmp2(&auth_hdr->credential.digest.realm, realm) == 0) {
|
||||
res = AUTH_SUCCESS;
|
||||
break;
|
||||
} else {
|
||||
res = AUTH_STALE;
|
||||
}
|
||||
}
|
||||
if (!authorization_found) {
|
||||
ast_trace(-1, "%s:%s:%s: No Authorization header found\n",
|
||||
endpoint_id, auth_id, src_name);
|
||||
res = AUTH_NOAUTH;
|
||||
}
|
||||
|
||||
SCOPE_EXIT_RTN_VALUE(res, "%s:%s:%s: realm: %s Result %s\n",
|
||||
endpoint_id, auth_id, src_name, realm, verify_result_str[res]);
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Common code for initializing a pjsip_auth_srv
|
||||
*/
|
||||
static void setup_auth_srv(pj_pool_t *pool, pjsip_auth_srv *auth_server, const char *realm)
|
||||
{
|
||||
pjsip_auth_srv_init_param *param = pj_pool_alloc(pool, sizeof(*param));
|
||||
pj_str_t *pj_realm = pj_pool_alloc(pool, sizeof(*pj_realm));
|
||||
|
||||
pj_cstr(pj_realm, realm);
|
||||
param->realm = pj_realm;
|
||||
param->lookup2 = digest_lookup;
|
||||
param->options = 0;
|
||||
|
||||
pjsip_auth_srv_init2(pool, auth_server, param);
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Verify incoming credentials
|
||||
*
|
||||
* \param endpoint_id For logging
|
||||
* \param auth The ast_sip_auth to check against
|
||||
* \param rdata The incoming request
|
||||
* \param pool A pool to use for the auth server
|
||||
* \return One of digest_verify_result
|
||||
*/
|
||||
static int verify(const char *endpoint_id, const struct ast_sip_auth *auth,
|
||||
pjsip_rx_data *rdata, pj_pool_t *pool)
|
||||
{
|
||||
const char *auth_id = ast_sorcery_object_get_id(auth);
|
||||
const char *realm = S_OR(auth->realm, default_realm);
|
||||
const char *src_name = rdata->pkt_info.src_name;
|
||||
pj_status_t authed;
|
||||
int response_code;
|
||||
pjsip_auth_srv auth_server;
|
||||
int stale = 0;
|
||||
int res = AUTH_FAIL;
|
||||
enum digest_verify_result res = AUTH_FAIL;
|
||||
SCOPE_ENTER(3, "%s:%s:%s: realm: %s\n",
|
||||
endpoint_id, auth_id, src_name, realm);
|
||||
|
||||
if (!find_challenge(rdata, auth)) {
|
||||
/* Couldn't find a challenge with a sane nonce.
|
||||
res = find_authorization(endpoint_id, auth, rdata);
|
||||
if (res == AUTH_NOAUTH)
|
||||
{
|
||||
ast_test_suite_event_notify("INCOMING_AUTH_VERIFY_RESULT",
|
||||
"Realm: %s\r\n"
|
||||
"Username: %s\r\n"
|
||||
"Status: %s",
|
||||
realm, auth->auth_user, verify_result_str[res]);
|
||||
SCOPE_EXIT_RTN_VALUE(res, "%s:%s:%s: No Authorization header found\n",
|
||||
endpoint_id, auth_id, src_name);
|
||||
}
|
||||
|
||||
if (res == AUTH_STALE) {
|
||||
/* Couldn't find an authorization with a sane nonce.
|
||||
* Nonce mismatch may just be due to staleness.
|
||||
*/
|
||||
stale = 1;
|
||||
}
|
||||
|
||||
setup_auth_srv(pool, &auth_server, auth->realm);
|
||||
|
||||
setup_auth_srv(pool, &auth_server, realm);
|
||||
store_auth(auth);
|
||||
authed = pjsip_auth_srv_verify(&auth_server, rdata, &response_code);
|
||||
/* pjsip_auth_srv_verify will invoke digest_lookup */
|
||||
authed = SCOPE_CALL_WITH_RESULT(-1, pj_status_t, pjsip_auth_srv_verify, &auth_server, rdata, &response_code);
|
||||
remove_auth();
|
||||
|
||||
if (authed == PJ_SUCCESS) {
|
||||
if (stale) {
|
||||
res = AUTH_STALE;
|
||||
} else {
|
||||
res = AUTH_SUCCESS;
|
||||
}
|
||||
} else {
|
||||
char err[256];
|
||||
res = AUTH_FAIL;
|
||||
pj_strerror(authed, err, sizeof(err));
|
||||
ast_trace(-1, "%s:%s:%s: authed: %s\n", endpoint_id, auth_id, src_name, err);
|
||||
}
|
||||
|
||||
if (authed == PJSIP_EAUTHNOAUTH) {
|
||||
res = AUTH_NOAUTH;
|
||||
}
|
||||
|
||||
ast_debug(3, "Realm: %s Username: %s Result: %s\n",
|
||||
auth->realm, auth->auth_user, verify_result_str[res]);
|
||||
|
||||
ast_test_suite_event_notify("INCOMING_AUTH_VERIFY_RESULT",
|
||||
"Realm: %s\r\n"
|
||||
"Username: %s\r\n"
|
||||
"Status: %s",
|
||||
auth->realm, auth->auth_user, verify_result_str[res]);
|
||||
realm, auth->auth_user, verify_result_str[res]);
|
||||
|
||||
return res;
|
||||
SCOPE_EXIT_RTN_VALUE(res, "%s:%s:%s: Realm: %s Username: %s Result: %s\n",
|
||||
endpoint_id, auth_id, src_name, realm,
|
||||
auth->auth_user, verify_result_str[res]);
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief astobj2 callback for adding digest challenges to responses
|
||||
* \brief Send a WWW-Authenticate challenge
|
||||
*
|
||||
* \param realm An auth's realm to build a challenge from
|
||||
* \param endpoint_id For logging
|
||||
* \param auth The auth object to use for the challenge
|
||||
* \param tdata The response to add the challenge to
|
||||
* \param rdata The request the challenge is in response to
|
||||
* \param is_stale Indicates whether nonce on incoming request was stale
|
||||
* \param algorithm_type The algorithm to use for the challenge
|
||||
*/
|
||||
static void challenge(const char *realm, pjsip_tx_data *tdata, const pjsip_rx_data *rdata, int is_stale)
|
||||
static void challenge(const char *endpoint_id, struct ast_sip_auth *auth,
|
||||
pjsip_tx_data *tdata, const pjsip_rx_data *rdata, int is_stale,
|
||||
const pjsip_auth_algorithm *algorithm)
|
||||
{
|
||||
pj_str_t qop;
|
||||
pj_str_t pj_nonce;
|
||||
@@ -381,6 +514,14 @@ static void challenge(const char *realm, pjsip_tx_data *tdata, const pjsip_rx_da
|
||||
struct ast_str *nonce = ast_str_alloca(256);
|
||||
char time_buf[32];
|
||||
time_t timestamp = time(NULL);
|
||||
pj_status_t res;
|
||||
const char *realm = S_OR(auth->realm, default_realm);
|
||||
const char *auth_id = ast_sorcery_object_get_id(auth);
|
||||
const char *src_name = rdata->pkt_info.src_name;
|
||||
SCOPE_ENTER(5, "%s:%s:%s: realm: %s time: %d algorithm: " PJSTR_PRINTF_SPEC " stale? %s\n",
|
||||
endpoint_id, auth_id, src_name, realm, (int)timestamp,
|
||||
PJSTR_PRINTF_VAR(algorithm->iana_name), is_stale ? "yes" : "no");
|
||||
|
||||
snprintf(time_buf, sizeof(time_buf), "%d", (int) timestamp);
|
||||
|
||||
build_nonce(&nonce, time_buf, rdata, realm);
|
||||
@@ -389,9 +530,27 @@ static void challenge(const char *realm, pjsip_tx_data *tdata, const pjsip_rx_da
|
||||
|
||||
pj_cstr(&pj_nonce, ast_str_buffer(nonce));
|
||||
pj_cstr(&qop, "auth");
|
||||
pjsip_auth_srv_challenge(&auth_server, &qop, &pj_nonce, NULL, is_stale ? PJ_TRUE : PJ_FALSE, tdata);
|
||||
#ifdef HAVE_PJSIP_AUTH_NEW_DIGESTS
|
||||
res = pjsip_auth_srv_challenge2(&auth_server, &qop, &pj_nonce,
|
||||
NULL, is_stale ? PJ_TRUE : PJ_FALSE, tdata, algorithm->algorithm_type);
|
||||
#else
|
||||
res = pjsip_auth_srv_challenge(&auth_server, &qop, &pj_nonce,
|
||||
NULL, is_stale ? PJ_TRUE : PJ_FALSE, tdata);
|
||||
#endif
|
||||
SCOPE_EXIT_RTN("%s:%s:%s: Sending challenge for realm: %s algorithm: " PJSTR_PRINTF_SPEC
|
||||
" %s\n",
|
||||
endpoint_id, auth_id, src_name, realm, PJSTR_PRINTF_VAR(algorithm->iana_name),
|
||||
res == PJ_SUCCESS ? "succeeded" : "failed");
|
||||
}
|
||||
|
||||
static char *check_auth_result_str[] = {
|
||||
"CHALLENGE",
|
||||
"SUCCESS",
|
||||
"FAILED",
|
||||
"ERROR",
|
||||
};
|
||||
|
||||
|
||||
/*!
|
||||
* \brief Check authentication using Digest scheme
|
||||
*
|
||||
@@ -405,7 +564,6 @@ static enum ast_sip_check_auth_result digest_check_auth(struct ast_sip_endpoint
|
||||
pjsip_rx_data *rdata, pjsip_tx_data *tdata)
|
||||
{
|
||||
struct ast_sip_auth **auths;
|
||||
struct ast_sip_auth **auths_shallow;
|
||||
enum digest_verify_result *verify_res;
|
||||
struct ast_sip_endpoint *artificial_endpoint;
|
||||
enum ast_sip_check_auth_result res;
|
||||
@@ -413,6 +571,9 @@ static enum ast_sip_check_auth_result digest_check_auth(struct ast_sip_endpoint
|
||||
int is_artificial;
|
||||
int failures = 0;
|
||||
size_t auth_size;
|
||||
const char *endpoint_id = ast_sorcery_object_get_id(endpoint);
|
||||
char *src_name = rdata->pkt_info.src_name;
|
||||
SCOPE_ENTER(3, "%s:%s\n", endpoint_id, src_name);
|
||||
|
||||
auth_size = AST_VECTOR_SIZE(&endpoint->inbound_auths);
|
||||
ast_assert(0 < auth_size);
|
||||
@@ -423,81 +584,122 @@ static enum ast_sip_check_auth_result digest_check_auth(struct ast_sip_endpoint
|
||||
artificial_endpoint = ast_sip_get_artificial_endpoint();
|
||||
if (!artificial_endpoint) {
|
||||
/* Should not happen except possibly if we are shutting down. */
|
||||
return AST_SIP_AUTHENTICATION_ERROR;
|
||||
SCOPE_EXIT_RTN_VALUE(AST_SIP_AUTHENTICATION_ERROR);
|
||||
}
|
||||
|
||||
is_artificial = endpoint == artificial_endpoint;
|
||||
ao2_ref(artificial_endpoint, -1);
|
||||
if (is_artificial) {
|
||||
ast_trace(3, "%s:%s: Using artificial endpoint for authentication\n",
|
||||
endpoint_id, src_name);
|
||||
ast_assert(auth_size == 1);
|
||||
auths[0] = ast_sip_get_artificial_auth();
|
||||
if (!auths[0]) {
|
||||
/* Should not happen except possibly if we are shutting down. */
|
||||
return AST_SIP_AUTHENTICATION_ERROR;
|
||||
SCOPE_EXIT_RTN_VALUE(AST_SIP_AUTHENTICATION_ERROR);
|
||||
}
|
||||
} else {
|
||||
ast_trace(3, "%s:%s: Using endpoint for authentication\n",
|
||||
endpoint_id, src_name);
|
||||
memset(auths, 0, auth_size * sizeof(*auths));
|
||||
/*
|
||||
* If ast_sip_retrieve_auths returns a failure we still need
|
||||
* to cleanup the auths array because it may have been partially
|
||||
* filled in.
|
||||
*/
|
||||
if (ast_sip_retrieve_auths(&endpoint->inbound_auths, auths)) {
|
||||
res = AST_SIP_AUTHENTICATION_ERROR;
|
||||
goto cleanup;
|
||||
ast_sip_cleanup_auths(auths, auth_size);
|
||||
SCOPE_EXIT_RTN_VALUE(AST_SIP_AUTHENTICATION_ERROR,
|
||||
"%s:%s: Failed to retrieve some or all auth objects from endpoint\n",
|
||||
endpoint_id, src_name);
|
||||
}
|
||||
}
|
||||
|
||||
/* Setup shallow copy of auths */
|
||||
if (ast_strlen_zero(default_realm)) {
|
||||
auths_shallow = auths;
|
||||
} else {
|
||||
/*
|
||||
* NOTE: The only reason to use multiple auth objects as a UAS might
|
||||
* be to send challenges for multiple realms however we currently don't
|
||||
* know of anyone actually doing this.
|
||||
*/
|
||||
for (idx = 0; idx < auth_size; ++idx) {
|
||||
int i = 0;
|
||||
struct ast_sip_auth *auth = auths[idx];
|
||||
const char *realm = S_OR(auth->realm, default_realm);
|
||||
const char *auth_id = ast_sorcery_object_get_id(auth);
|
||||
SCOPE_ENTER(4, "%s:%s:%s: Verifying\n", endpoint_id, auth_id, src_name);
|
||||
|
||||
/*
|
||||
* Set default realm on a shallow copy of the authentication
|
||||
* objects that don't have a realm set.
|
||||
* Artificial auth objects are used for the purpose of
|
||||
* sending challenges. We don't need to verify them.
|
||||
*/
|
||||
auths_shallow = ast_alloca(auth_size * sizeof(*auths_shallow));
|
||||
for (idx = 0; idx < auth_size; ++idx) {
|
||||
if (ast_strlen_zero(auths[idx]->realm)) {
|
||||
/*
|
||||
* Make a shallow copy and set the default realm on it.
|
||||
*
|
||||
* The stack allocation is OK here. Normally this will
|
||||
* loop one time. If you have multiple auths then you
|
||||
* shouldn't need more auths than the normal complement
|
||||
* of fingers and toes. Otherwise, you should check
|
||||
* your sanity for setting up your system up that way.
|
||||
*/
|
||||
auths_shallow[idx] = ast_alloca(sizeof(**auths_shallow));
|
||||
memcpy(auths_shallow[idx], auths[idx], sizeof(**auths_shallow));
|
||||
*((char **) (&auths_shallow[idx]->realm)) = default_realm;
|
||||
ast_debug(3, "Using default realm '%s' on incoming auth '%s'.\n",
|
||||
default_realm, ast_sorcery_object_get_id(auths_shallow[idx]));
|
||||
} else {
|
||||
auths_shallow[idx] = auths[idx];
|
||||
if (auth->type == AST_SIP_AUTH_TYPE_ARTIFICIAL) {
|
||||
ast_trace(-1, "%s:%s:%s: Skipping verification on artificial endpoint\n", endpoint_id, auth_id, src_name )
|
||||
verify_res[idx] = AUTH_NOAUTH;
|
||||
} else {
|
||||
verify_res[idx] = SCOPE_CALL_WITH_RESULT(-1, int, verify, endpoint_id, auth, rdata, tdata->pool);
|
||||
if (verify_res[idx] == AUTH_SUCCESS) {
|
||||
res = AST_SIP_AUTHENTICATION_SUCCESS;
|
||||
SCOPE_EXIT_EXPR(break, "%s:%s:%s: success\n", endpoint_id, auth_id, src_name);
|
||||
}
|
||||
if (verify_res[idx] == AUTH_FAIL) {
|
||||
ast_trace(-1, "%s:%s:%s: fail\n", endpoint_id, auth_id, src_name);
|
||||
failures++;
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < AST_VECTOR_SIZE(&auth->supported_algorithms_uas); i++) {
|
||||
pjsip_auth_algorithm_type algorithm_type = AST_VECTOR_GET(&auth->supported_algorithms_uas, i);
|
||||
const pjsip_auth_algorithm *algorithm = ast_sip_auth_get_algorithm_by_type(algorithm_type);
|
||||
pjsip_www_authenticate_hdr *auth_hdr = NULL;
|
||||
int already_sent_challenge = 0;
|
||||
SCOPE_ENTER(5, "%s:%s:%s: Challenging with " PJSTR_PRINTF_SPEC "\n",
|
||||
endpoint_id, auth_id, src_name, PJSTR_PRINTF_VAR(algorithm->iana_name));
|
||||
|
||||
/*
|
||||
* Per RFC 7616, if we've already sent a challenge for this realm
|
||||
* and algorithm, we must not send another.
|
||||
*/
|
||||
while ((auth_hdr = pjsip_msg_find_hdr(tdata->msg,
|
||||
PJSIP_H_WWW_AUTHENTICATE, auth_hdr ? auth_hdr->next : NULL))) {
|
||||
if (pj_strcmp2(&auth_hdr->challenge.common.realm, realm) == 0 &&
|
||||
!pj_stricmp(&auth_hdr->challenge.digest.algorithm, &algorithm->iana_name)) {
|
||||
ast_trace(-1, "%s:%s:%s: Not sending duplicate challenge for realm: %s algorithm: "
|
||||
PJSTR_PRINTF_SPEC "\n",
|
||||
endpoint_id, auth_id, src_name, realm, PJSTR_PRINTF_VAR(algorithm->iana_name));
|
||||
already_sent_challenge = 1;
|
||||
}
|
||||
}
|
||||
if (already_sent_challenge) {
|
||||
SCOPE_EXIT_EXPR(continue);
|
||||
}
|
||||
|
||||
SCOPE_CALL(5, challenge, endpoint_id, auth, tdata, rdata,
|
||||
verify_res[idx] == AUTH_STALE, algorithm);
|
||||
|
||||
SCOPE_EXIT("%s:%s:%s: Challenged with " PJSTR_PRINTF_SPEC "\n",
|
||||
endpoint_id, auth_id, src_name, PJSTR_PRINTF_VAR(algorithm->iana_name));
|
||||
}
|
||||
SCOPE_EXIT("%s:%s:%s: Done with auth challenge\n", endpoint_id, auth_id, src_name);
|
||||
}
|
||||
|
||||
for (idx = 0; idx < auth_size; ++idx) {
|
||||
verify_res[idx] = verify(auths_shallow[idx], rdata, tdata->pool);
|
||||
if (verify_res[idx] == AUTH_SUCCESS) {
|
||||
res = AST_SIP_AUTHENTICATION_SUCCESS;
|
||||
goto cleanup;
|
||||
}
|
||||
if (verify_res[idx] == AUTH_FAIL) {
|
||||
failures++;
|
||||
}
|
||||
}
|
||||
|
||||
for (idx = 0; idx < auth_size; ++idx) {
|
||||
challenge(auths_shallow[idx]->realm, tdata, rdata, verify_res[idx] == AUTH_STALE);
|
||||
}
|
||||
/*
|
||||
* If we've sent challenges for multiple auth objects, we currently
|
||||
* return SUCCESS when the first one succeeds. We may want to change
|
||||
* this in the future to require that all succeed but as stated above,
|
||||
* currently we don't have a use case for even using more than one
|
||||
* auth object as a UAS.
|
||||
*/
|
||||
|
||||
if (failures == auth_size) {
|
||||
res = AST_SIP_AUTHENTICATION_FAILED;
|
||||
} else {
|
||||
} else if (res != AST_SIP_AUTHENTICATION_SUCCESS){
|
||||
res = AST_SIP_AUTHENTICATION_CHALLENGE;
|
||||
}
|
||||
|
||||
cleanup:
|
||||
ast_sip_cleanup_auths(auths, auth_size);
|
||||
return res;
|
||||
SCOPE_EXIT_RTN_VALUE(res, "%s:%s: Result: %s\n",
|
||||
endpoint_id, src_name,
|
||||
check_auth_result_str[res]);
|
||||
|
||||
}
|
||||
|
||||
static struct ast_sip_authenticator digest_authenticator = {
|
||||
|
@@ -16,6 +16,14 @@
|
||||
* at the top of the source tree.
|
||||
*/
|
||||
|
||||
/*!
|
||||
* \file
|
||||
* \brief PJSIP UAC Authentication
|
||||
*
|
||||
* This module handles authentication when Asterisk is the UAC.
|
||||
*
|
||||
*/
|
||||
|
||||
/*** MODULEINFO
|
||||
<depend>pjproject</depend>
|
||||
<depend>res_pjsip</depend>
|
||||
@@ -32,10 +40,6 @@
|
||||
#include "asterisk/strings.h"
|
||||
#include "asterisk/vector.h"
|
||||
|
||||
pj_str_t supported_digest_algorithms[] = {
|
||||
{ "MD5", 3}
|
||||
};
|
||||
|
||||
/*!
|
||||
* \internal
|
||||
* \brief Determine proper authenticate header
|
||||
@@ -59,27 +63,240 @@ static pjsip_hdr_e get_auth_search_type(pjsip_rx_data *challenge)
|
||||
|
||||
/*!
|
||||
* \internal
|
||||
* \brief Determine if digest algorithm in the header is one we support
|
||||
* \brief Determine if digest algorithm in the header is one supported by
|
||||
* pjproject and OpenSSL.
|
||||
*/
|
||||
static const pjsip_auth_algorithm *get_supported_algorithm(pjsip_www_authenticate_hdr *auth_hdr)
|
||||
{
|
||||
const pjsip_auth_algorithm *algo = NULL;
|
||||
|
||||
algo = ast_sip_auth_get_algorithm_by_iana_name(&auth_hdr->challenge.digest.algorithm);
|
||||
if (!algo) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (ast_sip_auth_is_algorithm_supported(algo->algorithm_type)) {
|
||||
return algo;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
AST_VECTOR(cred_info_vector, pjsip_cred_info);
|
||||
|
||||
/*!
|
||||
* \brief Get credentials (if any) from auth objects for a WWW/Proxy-Authenticate header
|
||||
*
|
||||
* \retval 1 If we support the algorithm
|
||||
* \retval 0 If we do not
|
||||
* \param id For logging
|
||||
* \param src_name For logging
|
||||
* \param auth_hdr The *-Authenticate header to check
|
||||
* \param auth_object_count The number of auth objects available
|
||||
* \param auth_objects_vector The vector of available auth objects
|
||||
* \param auth_creds The vector to store the credentials in
|
||||
* \param realms For logging
|
||||
*
|
||||
*/
|
||||
static int is_digest_algorithm_supported(pjsip_www_authenticate_hdr *auth_hdr)
|
||||
static void get_creds_for_header(const char *id, const char *src_name,
|
||||
pjsip_www_authenticate_hdr *auth_hdr, size_t auth_object_count,
|
||||
const struct ast_sip_auth_objects_vector *auth_objects_vector,
|
||||
struct cred_info_vector *auth_creds, struct ast_str **realms)
|
||||
{
|
||||
int digest;
|
||||
int exact_match_index = -1;
|
||||
int wildcard_match_index = -1;
|
||||
struct ast_sip_auth *found_auth = NULL;
|
||||
const pjsip_auth_algorithm *challenge_algorithm =
|
||||
get_supported_algorithm(auth_hdr);
|
||||
int i = 0;
|
||||
pjsip_cred_info auth_cred;
|
||||
const char *cred_data;
|
||||
int res = 0;
|
||||
SCOPE_ENTER(4, "%s:%s: Testing header realm: '" PJSTR_PRINTF_SPEC "' algorithm: '"
|
||||
PJSTR_PRINTF_SPEC "'\n", id, src_name,
|
||||
PJSTR_PRINTF_VAR(auth_hdr->challenge.digest.realm),
|
||||
PJSTR_PRINTF_VAR(auth_hdr->challenge.digest.algorithm));
|
||||
|
||||
/* An empty digest is assumed to be md5 */
|
||||
if (pj_strlen(&auth_hdr->challenge.digest.algorithm) == 0) {
|
||||
return 1;
|
||||
if (!challenge_algorithm) {
|
||||
SCOPE_EXIT_RTN("%s:%s: Skipping header with realm '" PJSTR_PRINTF_SPEC "' "
|
||||
"and unsupported " PJSTR_PRINTF_SPEC "' algorithm \n", id, src_name,
|
||||
PJSTR_PRINTF_VAR(auth_hdr->challenge.digest.realm),
|
||||
PJSTR_PRINTF_VAR(auth_hdr->challenge.digest.algorithm));
|
||||
}
|
||||
|
||||
for (digest = 0; digest < ARRAY_LEN(supported_digest_algorithms); digest++) {
|
||||
if (pj_stricmp(&auth_hdr->challenge.digest.algorithm, &supported_digest_algorithms[digest]) == 0) {
|
||||
return 1;
|
||||
/*
|
||||
* If we already have credentials for this realm, we don't need to
|
||||
* process this header. We can just skip it.
|
||||
*/
|
||||
for (i = 0; i < AST_VECTOR_SIZE(auth_creds); i++) {
|
||||
pjsip_cred_info auth_cred = AST_VECTOR_GET(auth_creds, i);
|
||||
if (pj_stricmp(&auth_cred.realm, &auth_hdr->challenge.common.realm) == 0) {
|
||||
SCOPE_EXIT_RTN("%s:%s: Skipping header with realm '" PJSTR_PRINTF_SPEC "' "
|
||||
"because we already have credentials for it\n", id, src_name,
|
||||
PJSTR_PRINTF_VAR(auth_hdr->challenge.digest.realm));
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* Appending "realm/agorithm" to realms is strictly so
|
||||
* digest_create_request_with_auth() can display good error messages.
|
||||
*/
|
||||
if (*realms) {
|
||||
ast_str_append(realms, 0, PJSTR_PRINTF_SPEC "/" PJSTR_PRINTF_SPEC ", ",
|
||||
PJSTR_PRINTF_VAR(auth_hdr->challenge.digest.realm),
|
||||
PJSTR_PRINTF_VAR(auth_hdr->challenge.digest.algorithm));
|
||||
}
|
||||
|
||||
/*
|
||||
* Now that we have a valid header, we can loop over the auths available to
|
||||
* find either an exact realm match or, failing that, a wildcard auth (an
|
||||
* auth with an empty or "*" realm).
|
||||
*
|
||||
* NOTE: We never use the global default realm when we're the UAC responding
|
||||
* to a 401 or 407. We only use that when we're the UAS (handled elsewhere)
|
||||
* and the auth object didn't have a realm.
|
||||
*/
|
||||
ast_trace(-1, "%s:%s: Searching %zu auths to find matching ones for header with realm "
|
||||
"'" PJSTR_PRINTF_SPEC "' and algorithm '" PJSTR_PRINTF_SPEC "'\n",
|
||||
id, src_name, auth_object_count,
|
||||
PJSTR_PRINTF_VAR(auth_hdr->challenge.digest.realm),
|
||||
PJSTR_PRINTF_VAR(auth_hdr->challenge.digest.algorithm));
|
||||
|
||||
for (i = 0; i < auth_object_count; ++i) {
|
||||
struct ast_sip_auth *auth = AST_VECTOR_GET(auth_objects_vector, i);
|
||||
const char *auth_id = ast_sorcery_object_get_id(auth);
|
||||
SCOPE_ENTER(5, "%s:%s: Checking auth '%s' with realm '%s'\n",
|
||||
id, src_name, auth_id, auth->realm);
|
||||
|
||||
/*
|
||||
* Is the challenge algorithm in the auth's supported_algorithms_uac
|
||||
* and is there either a plain text password or a password_digest
|
||||
* for the algorithm?
|
||||
*/
|
||||
if (!ast_sip_auth_is_algorithm_available(auth, &auth->supported_algorithms_uac,
|
||||
challenge_algorithm->algorithm_type)) {
|
||||
SCOPE_EXIT_EXPR(continue, "%s:%s: Skipping auth '%s' with realm '%s' because it doesn't support "
|
||||
" algorithm '" PJSTR_PRINTF_SPEC "'\n", id, src_name,
|
||||
auth_id, auth->realm,
|
||||
PJSTR_PRINTF_VAR(challenge_algorithm->iana_name));
|
||||
}
|
||||
|
||||
/*
|
||||
* If this auth object's realm exactly matches the one
|
||||
* from the header, we can just break out and use it.
|
||||
*
|
||||
* NOTE: If there's more than one auth object for an endpoint with
|
||||
* a matching realm it's a misconfiguration. We'll only use the first.
|
||||
*/
|
||||
if (pj_stricmp2(&auth_hdr->challenge.digest.realm, auth->realm) == 0) {
|
||||
exact_match_index = i;
|
||||
/*
|
||||
* If we found an exact realm match, there's no need to keep
|
||||
* looking for a wildcard.
|
||||
*/
|
||||
SCOPE_EXIT_EXPR(break, "%s:%s: Found matching auth '%s' with realm '%s'\n",
|
||||
id, src_name, auth_id, auth->realm);
|
||||
}
|
||||
|
||||
/*
|
||||
* If this auth object's realm is empty or a "*", it's a wildcard
|
||||
* auth object. We going to save its index but keep iterating over
|
||||
* the vector in case we find an exact match later.
|
||||
*
|
||||
* NOTE: If there's more than one wildcard auth object for an endpoint
|
||||
* it's a misconfiguration. We'll only use the first.
|
||||
*/
|
||||
if (wildcard_match_index < 0
|
||||
&& (ast_strlen_zero(auth->realm) || ast_strings_equal(auth->realm, "*"))) {
|
||||
ast_trace(-1, "%s:%s: Found wildcard auth '%s' for realm '" PJSTR_PRINTF_SPEC "'\n",
|
||||
id, src_name, auth_id,
|
||||
PJSTR_PRINTF_VAR(auth_hdr->challenge.digest.realm));
|
||||
wildcard_match_index = i;
|
||||
}
|
||||
SCOPE_EXIT("%s:%s: Done checking auth '%s' with realm '%s'. "
|
||||
"Found exact? %s Found wildcard? %s\n", id, src_name,
|
||||
auth_id, auth->realm, exact_match_index >= 0 ? "yes" : "no",
|
||||
wildcard_match_index >= 0 ? "yes" : "no");
|
||||
} /* End auth object loop */
|
||||
|
||||
if (exact_match_index < 0 && wildcard_match_index < 0) {
|
||||
/*
|
||||
* Didn't find either a wildcard or an exact realm match.
|
||||
* Move on to the next header.
|
||||
*/
|
||||
SCOPE_EXIT_RTN("%s:%s: No auth matching realm or no wildcard found for realm '" PJSTR_PRINTF_SPEC "'\n",
|
||||
id, src_name, PJSTR_PRINTF_VAR(auth_hdr->challenge.digest.realm));
|
||||
}
|
||||
|
||||
if (exact_match_index >= 0) {
|
||||
/*
|
||||
* If we found an exact match, we'll always prefer that.
|
||||
*/
|
||||
found_auth = AST_VECTOR_GET(auth_objects_vector, exact_match_index);
|
||||
ast_trace(-1, "%s:%s: Using matched auth '%s' with realm '" PJSTR_PRINTF_SPEC "'\n",
|
||||
id, src_name, ast_sorcery_object_get_id(found_auth),
|
||||
PJSTR_PRINTF_VAR(auth_hdr->challenge.digest.realm));
|
||||
} else {
|
||||
/*
|
||||
* We'll only use the wildcard if we didn't find an exact match.
|
||||
*/
|
||||
found_auth = AST_VECTOR_GET(auth_objects_vector, wildcard_match_index);
|
||||
ast_trace(-1, "%s:%s: Using wildcard auth '%s' for realm '" PJSTR_PRINTF_SPEC "'\n",
|
||||
id, src_name, ast_sorcery_object_get_id(found_auth),
|
||||
PJSTR_PRINTF_VAR(auth_hdr->challenge.digest.realm));
|
||||
}
|
||||
|
||||
/*
|
||||
* Now that we have an auth object to use, we need to create a
|
||||
* pjsip_cred_info structure for each algorithm we support.
|
||||
*/
|
||||
|
||||
memset(&auth_cred, 0, sizeof(auth_cred));
|
||||
/*
|
||||
* Copy the fields from the auth_object to the
|
||||
* pjsip_cred_info structure.
|
||||
*/
|
||||
auth_cred.realm = auth_hdr->challenge.common.realm;
|
||||
pj_cstr(&auth_cred.username, found_auth->auth_user);
|
||||
pj_cstr(&auth_cred.scheme, "digest");
|
||||
|
||||
/*
|
||||
* auth_cred.data_type tells us whether the credential is a plain text
|
||||
* password or a pre-digested one.
|
||||
*/
|
||||
cred_data = SCOPE_CALL_WITH_RESULT(-1, const char *, ast_sip_auth_get_creds,
|
||||
found_auth, challenge_algorithm->algorithm_type, &auth_cred.data_type);
|
||||
/*
|
||||
* This can't really fail because we already called
|
||||
* ast_sip_auth_is_algorithm_available() for the auth
|
||||
* but we check anyway.
|
||||
*/
|
||||
if (!cred_data) {
|
||||
SCOPE_EXIT_RTN("%s:%s: Shouldn't have happened\n", id, src_name);
|
||||
}
|
||||
|
||||
pj_cstr(&auth_cred.data, cred_data);
|
||||
#ifdef HAVE_PJSIP_AUTH_NEW_DIGESTS
|
||||
if (auth_cred.data_type == PJSIP_CRED_DATA_DIGEST) {
|
||||
auth_cred.algorithm_type = challenge_algorithm->algorithm_type;
|
||||
}
|
||||
#endif
|
||||
/*
|
||||
* Because the vector contains actual structures and not pointers
|
||||
* to structures, the call to AST_VECTOR_APPEND results in a simple
|
||||
* assign of one structure to another, effectively copying the auth_cred
|
||||
* structure contents to the array element.
|
||||
*
|
||||
* Also note that the calls to pj_cstr above set their respective
|
||||
* auth_cred fields to the _pointers_ of their corresponding auth
|
||||
* object fields. This is safe because the call to
|
||||
* pjsip_auth_clt_set_credentials() below strdups them before we
|
||||
* return to the calling function which decrements the reference
|
||||
* counts.
|
||||
*/
|
||||
res = AST_VECTOR_APPEND(auth_creds, auth_cred);
|
||||
SCOPE_EXIT_RTN("%s:%s: %s credential for realm: '" PJSTR_PRINTF_SPEC "' algorithm: '"
|
||||
PJSTR_PRINTF_SPEC "'\n", id, src_name,
|
||||
res == 0 ? "Added" : "Failed to add",
|
||||
PJSTR_PRINTF_VAR(auth_hdr->challenge.digest.realm),
|
||||
PJSTR_PRINTF_VAR(auth_hdr->challenge.digest.algorithm));
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -89,7 +306,7 @@ static int is_digest_algorithm_supported(pjsip_www_authenticate_hdr *auth_hdr)
|
||||
* RFC7616 and RFC8760 allow more than one WWW-Authenticate or
|
||||
* Proxy-Authenticate header per realm, each with different digest
|
||||
* algorithms (including new ones like SHA-256 and SHA-512-256). However,
|
||||
* thankfully, a UAS can NOT send back multiple Authenticate headers for
|
||||
* a UAS can NOT send back multiple Authenticate headers for
|
||||
* the same realm with the same digest algorithm. The UAS is also
|
||||
* supposed to send the headers in order of preference with the first one
|
||||
* being the most preferred.
|
||||
@@ -99,14 +316,14 @@ static int is_digest_algorithm_supported(pjsip_www_authenticate_hdr *auth_hdr)
|
||||
*
|
||||
* The UAS can also send multiple realms, especially when it's a proxy
|
||||
* that has forked the request in which case the proxy will aggregate all
|
||||
* of the Authenticate and then them all back to the UAC.
|
||||
* of the Authenticate headers into one response back to the UAC.
|
||||
*
|
||||
* It doesn't stop there though... Each realm can require a different
|
||||
* username from the others. There's also nothing preventing each digest
|
||||
* algorithm from having a unique password although I'm not sure if
|
||||
* that adds any benefit.
|
||||
*
|
||||
* So now... For each Authenticate header we encounter, we have to
|
||||
* So now... For each WWW/Proxy-Authenticate header we encounter, we have to
|
||||
* determine if we support the digest algorithm and, if not, just skip the
|
||||
* header. We then have to find an auth object that matches the realm AND
|
||||
* the digest algorithm or find a wildcard object that matches the digest
|
||||
@@ -115,27 +332,22 @@ static int is_digest_algorithm_supported(pjsip_www_authenticate_hdr *auth_hdr)
|
||||
* we already added an auth object for that realm, we skip the header.
|
||||
* Otherwise we repeat the process for the next header.
|
||||
*
|
||||
* In the end, we'll have accumulated a list of credentials we can pass to
|
||||
* pjproject that it can use to add Authentication headers to a request.
|
||||
*
|
||||
* \note: Neither we nor pjproject can currently handle digest algorithms
|
||||
* other than MD5. We don't even have a place for it in the ast_sip_auth
|
||||
* object. For this reason, we just skip processing any Authenticate
|
||||
* header that's not MD5. When we support the others, we'll move the
|
||||
* check into the loop that searches the objects.
|
||||
* In the end, we'll have accumulated a list of credentials, one per realm,
|
||||
* we can pass to pjproject that it can use to add Authentication headers
|
||||
* to a request.
|
||||
*/
|
||||
static pj_status_t set_outbound_authentication_credentials(pjsip_auth_clt_sess *auth_sess,
|
||||
const struct ast_sip_auth_objects_vector *auth_objects_vector, pjsip_rx_data *challenge,
|
||||
struct ast_str **realms)
|
||||
static pj_status_t set_auth_creds(const char *id, pjsip_auth_clt_sess *auth_sess,
|
||||
const struct ast_sip_auth_objects_vector *auth_objects_vector,
|
||||
pjsip_rx_data *challenge, struct ast_str **realms)
|
||||
{
|
||||
int i;
|
||||
size_t auth_object_count;
|
||||
pjsip_www_authenticate_hdr *auth_hdr = NULL;
|
||||
pj_status_t res = PJ_SUCCESS;
|
||||
pjsip_hdr_e search_type;
|
||||
size_t cred_count;
|
||||
size_t cred_count = 0;
|
||||
pjsip_cred_info *creds_array;
|
||||
|
||||
char *pj_err = NULL;
|
||||
const char *src_name = challenge->pkt_info.src_name;
|
||||
/*
|
||||
* Normally vector elements are pointers to something else, usually
|
||||
* structures. In this case however, the elements are the
|
||||
@@ -147,7 +359,8 @@ static pj_status_t set_outbound_authentication_credentials(pjsip_auth_clt_sess *
|
||||
* which we'll pass to pjsip_auth_clt_set_credentials() at the
|
||||
* end.
|
||||
*/
|
||||
AST_VECTOR(cred_info, pjsip_cred_info) auth_creds;
|
||||
struct cred_info_vector auth_creds;
|
||||
SCOPE_ENTER(3, "%s:%s\n", id, src_name);
|
||||
|
||||
search_type = get_auth_search_type(challenge);
|
||||
if (search_type == PJSIP_H_OTHER) {
|
||||
@@ -156,13 +369,14 @@ static pj_status_t set_outbound_authentication_credentials(pjsip_auth_clt_sess *
|
||||
* so there are no WWW-Authenticate or Proxy-Authenticate
|
||||
* headers to process.
|
||||
*/
|
||||
return PJ_ENOTSUP;
|
||||
SCOPE_EXIT_RTN_VALUE(PJ_ENOTSUP, "%s:%s: Status code %d was received when it should have been 401 or 407.\n",
|
||||
id, src_name, challenge->msg_info.msg->line.status.code);
|
||||
}
|
||||
|
||||
auth_object_count = AST_VECTOR_SIZE(auth_objects_vector);
|
||||
if (auth_object_count == 0) {
|
||||
/* This shouldn't happen but we'll check anyway. */
|
||||
return PJ_EINVAL;
|
||||
SCOPE_EXIT_RTN_VALUE(PJ_EINVAL, "%s:%s No auth objects available\n", id, src_name);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -176,183 +390,29 @@ static pj_status_t set_outbound_authentication_credentials(pjsip_auth_clt_sess *
|
||||
* actual structures, not pointers to structures.
|
||||
*/
|
||||
if (AST_VECTOR_INIT(&auth_creds, 5) != 0) {
|
||||
return PJ_ENOMEM;
|
||||
SCOPE_EXIT_RTN_VALUE(PJ_ENOMEM);
|
||||
}
|
||||
|
||||
/*
|
||||
* It's going to be rare that we actually have more than one
|
||||
* WWW-Authentication header or more than one auth object to
|
||||
* match to it so the following nested loop should be fine.
|
||||
* There may be multiple WWW/Proxy-Authenticate headers each one having
|
||||
* a different realm/algorithm pair. Test each to see if we have credentials
|
||||
* for it and accumulate them in the auth_creds vector.
|
||||
* The code doesn't really care but just for reference, RFC-7616 says
|
||||
* a UAS can't send multiple headers for the same realm with the same
|
||||
* algorithm. It also says the UAS should send the headers in order
|
||||
* of preference with the first one being the most preferred.
|
||||
*/
|
||||
while ((auth_hdr = pjsip_msg_find_hdr(challenge->msg_info.msg,
|
||||
search_type, auth_hdr ? auth_hdr->next : NULL))) {
|
||||
int exact_match_index = -1;
|
||||
int wildcard_match_index = -1;
|
||||
int match_index = 0;
|
||||
pjsip_cred_info auth_cred;
|
||||
struct ast_sip_auth *auth = NULL;
|
||||
|
||||
memset(&auth_cred, 0, sizeof(auth_cred));
|
||||
/*
|
||||
* Since we only support the MD5 algorithm at the current time,
|
||||
* there's no sense searching for auth objects that match the algorithm.
|
||||
* In fact, the auth_object structure doesn't even have a member
|
||||
* for it.
|
||||
*
|
||||
* When we do support more algorithms, this check will need to be
|
||||
* moved inside the auth object loop below.
|
||||
*
|
||||
* Note: The header may not have specified an algorithm at all in which
|
||||
* case it's assumed to be MD5. is_digest_algorithm_supported() returns
|
||||
* true for that case.
|
||||
*/
|
||||
if (!is_digest_algorithm_supported(auth_hdr)) {
|
||||
ast_debug(3, "Skipping header with realm '%.*s' and unsupported '%.*s' algorithm \n",
|
||||
(int)auth_hdr->challenge.digest.realm.slen, auth_hdr->challenge.digest.realm.ptr,
|
||||
(int)auth_hdr->challenge.digest.algorithm.slen, auth_hdr->challenge.digest.algorithm.ptr);
|
||||
continue;
|
||||
}
|
||||
get_creds_for_header(id, src_name, auth_hdr, auth_object_count,
|
||||
auth_objects_vector, &auth_creds, realms);
|
||||
|
||||
/*
|
||||
* Appending the realms is strictly so digest_create_request_with_auth()
|
||||
* can display good error messages. Since we only support one algorithm,
|
||||
* there can't be more than one header with the same realm. No need to worry
|
||||
* about duplicate realms until then.
|
||||
*/
|
||||
if (*realms) {
|
||||
ast_str_append(realms, 0, "%.*s, ",
|
||||
(int)auth_hdr->challenge.digest.realm.slen, auth_hdr->challenge.digest.realm.ptr);
|
||||
}
|
||||
|
||||
ast_debug(3, "Searching auths to find matching ones for header with realm '%.*s' and algorithm '%.*s'\n",
|
||||
(int)auth_hdr->challenge.digest.realm.slen, auth_hdr->challenge.digest.realm.ptr,
|
||||
(int)auth_hdr->challenge.digest.algorithm.slen, auth_hdr->challenge.digest.algorithm.ptr);
|
||||
|
||||
/*
|
||||
* Now that we have a valid header, we can loop over the auths available to
|
||||
* find either an exact realm match or, failing that, a wildcard auth (an
|
||||
* auth with an empty or "*" realm).
|
||||
*
|
||||
* NOTE: We never use the global default realm when we're the UAC responding
|
||||
* to a 401 or 407. We only use that when we're the UAS (handled elsewhere)
|
||||
* and the auth object didn't have a realm.
|
||||
*/
|
||||
for (i = 0; i < auth_object_count; ++i) {
|
||||
auth = AST_VECTOR_GET(auth_objects_vector, i);
|
||||
|
||||
/*
|
||||
* If this auth object's realm exactly matches the one
|
||||
* from the header, we can just break out and use it.
|
||||
*
|
||||
* NOTE: If there's more than one auth object for an endpoint with
|
||||
* a matching realm it's a misconfiguration. We'll only use the first.
|
||||
*/
|
||||
if (pj_stricmp2(&auth_hdr->challenge.digest.realm, auth->realm) == 0) {
|
||||
ast_debug(3, "Found matching auth '%s' with realm '%s'\n", ast_sorcery_object_get_id(auth),
|
||||
auth->realm);
|
||||
exact_match_index = i;
|
||||
/*
|
||||
* If we found an exact realm match, there's no need to keep
|
||||
* looking for a wildcard.
|
||||
*/
|
||||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
* If this auth object's realm is empty or a "*", it's a wildcard
|
||||
* auth object. We going to save its index but keep iterating over
|
||||
* the vector in case we find an exact match later.
|
||||
*
|
||||
* NOTE: If there's more than one wildcard auth object for an endpoint
|
||||
* it's a misconfiguration. We'll only use the first.
|
||||
*/
|
||||
if (wildcard_match_index < 0
|
||||
&& (ast_strlen_zero(auth->realm) || ast_strings_equal(auth->realm, "*"))) {
|
||||
ast_debug(3, "Found wildcard auth '%s' for realm '%.*s'\n", ast_sorcery_object_get_id(auth),
|
||||
(int)auth_hdr->challenge.digest.realm.slen, auth_hdr->challenge.digest.realm.ptr);
|
||||
wildcard_match_index = i;
|
||||
}
|
||||
}
|
||||
|
||||
if (exact_match_index < 0 && wildcard_match_index < 0) {
|
||||
/*
|
||||
* Didn't find either a wildcard or an exact realm match.
|
||||
* Move on to the next header.
|
||||
*/
|
||||
ast_debug(3, "No auth matching realm or no wildcard found for realm '%.*s'\n",
|
||||
(int)auth_hdr->challenge.digest.realm.slen, auth_hdr->challenge.digest.realm.ptr);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (exact_match_index >= 0) {
|
||||
/*
|
||||
* If we found an exact match, we'll always prefer that.
|
||||
*/
|
||||
match_index = exact_match_index;
|
||||
auth = AST_VECTOR_GET(auth_objects_vector, match_index);
|
||||
ast_debug(3, "Using matched auth '%s' with realm '%.*s'\n", ast_sorcery_object_get_id(auth),
|
||||
(int)auth_hdr->challenge.digest.realm.slen, auth_hdr->challenge.digest.realm.ptr);
|
||||
} else {
|
||||
/*
|
||||
* We'll only use the wildcard if we didn't find an exact match.
|
||||
*/
|
||||
match_index = wildcard_match_index;
|
||||
auth = AST_VECTOR_GET(auth_objects_vector, match_index);
|
||||
ast_debug(3, "Using wildcard auth '%s' for realm '%.*s'\n", ast_sorcery_object_get_id(auth),
|
||||
(int)auth_hdr->challenge.digest.realm.slen, auth_hdr->challenge.digest.realm.ptr);
|
||||
}
|
||||
|
||||
/*
|
||||
* Copy the fields from the auth_object to the
|
||||
* pjsip_cred_info structure.
|
||||
*/
|
||||
auth_cred.realm = auth_hdr->challenge.common.realm;
|
||||
pj_cstr(&auth_cred.username, auth->auth_user);
|
||||
pj_cstr(&auth_cred.scheme, "digest");
|
||||
switch (auth->type) {
|
||||
case AST_SIP_AUTH_TYPE_USER_PASS:
|
||||
pj_cstr(&auth_cred.data, auth->auth_pass);
|
||||
auth_cred.data_type = PJSIP_CRED_DATA_PLAIN_PASSWD;
|
||||
break;
|
||||
case AST_SIP_AUTH_TYPE_MD5:
|
||||
pj_cstr(&auth_cred.data, auth->md5_creds);
|
||||
auth_cred.data_type = PJSIP_CRED_DATA_DIGEST;
|
||||
break;
|
||||
case AST_SIP_AUTH_TYPE_GOOGLE_OAUTH:
|
||||
/* nothing to do. handled seperately in res_pjsip_outbound_registration */
|
||||
break;
|
||||
case AST_SIP_AUTH_TYPE_ARTIFICIAL:
|
||||
ast_log(LOG_ERROR,
|
||||
"Trying to set artificial outbound auth credentials shouldn't happen.\n");
|
||||
continue;
|
||||
} /* End auth object loop */
|
||||
|
||||
/*
|
||||
* Because the vector contains actual structures and not pointers
|
||||
* to structures, the call to AST_VECTOR_APPEND results in a simple
|
||||
* assign of one structure to another, effectively copying the auth_cred
|
||||
* structure contents to the array element.
|
||||
*
|
||||
* Also note that the calls to pj_cstr above set their respective
|
||||
* auth_cred fields to the _pointers_ of their corresponding auth
|
||||
* object fields. This is safe because the call to
|
||||
* pjsip_auth_clt_set_credentials() below strdups them before we
|
||||
* return to the calling function which decrements the reference
|
||||
* counts.
|
||||
*/
|
||||
res = AST_VECTOR_APPEND(&auth_creds, auth_cred);
|
||||
if (res != PJ_SUCCESS) {
|
||||
res = PJ_ENOMEM;
|
||||
goto cleanup;
|
||||
}
|
||||
} /* End header loop */
|
||||
|
||||
if (*realms && ast_str_strlen(*realms)) {
|
||||
/*
|
||||
* Again, this is strictly so digest_create_request_with_auth()
|
||||
* can display good error messages.
|
||||
*
|
||||
* Chop off the trailing ", " on the last realm.
|
||||
* Chop off the trailing ", " on the last realm-algorithm.
|
||||
*/
|
||||
ast_str_truncate(*realms, ast_str_strlen(*realms) - 2);
|
||||
}
|
||||
@@ -383,15 +443,15 @@ static pj_status_t set_outbound_authentication_credentials(pjsip_auth_clt_sess *
|
||||
|
||||
res = pjsip_auth_clt_set_credentials(auth_sess, cred_count, creds_array);
|
||||
ast_free(creds_array);
|
||||
if (res == PJ_SUCCESS) {
|
||||
ast_debug(3, "Set %zu credentials in auth session\n", cred_count);
|
||||
} else {
|
||||
ast_log(LOG_ERROR, "Failed to set %zu credentials in auth session\n", cred_count);
|
||||
}
|
||||
|
||||
cleanup:
|
||||
AST_VECTOR_FREE(&auth_creds);
|
||||
return res;
|
||||
if (res != PJ_SUCCESS) {
|
||||
pj_err = ast_alloca(PJ_ERR_MSG_SIZE);
|
||||
pj_strerror(res, pj_err, PJ_ERR_MSG_SIZE);
|
||||
}
|
||||
SCOPE_EXIT_RTN_VALUE(res, "%s:%s: Set %zu credentials in auth session: %s\n",
|
||||
id, src_name, cred_count, S_OR(pj_err, "success"));
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -415,12 +475,23 @@ static int digest_create_request_with_auth(const struct ast_sip_auth_vector *aut
|
||||
pj_status_t status;
|
||||
struct ast_sip_auth_objects_vector auth_objects_vector;
|
||||
size_t auth_object_count = 0;
|
||||
struct ast_sip_endpoint *endpoint;
|
||||
char *id = NULL;
|
||||
const char *id_type;
|
||||
pjsip_dialog *dlg = pjsip_rdata_get_dlg(challenge);
|
||||
struct ast_sip_endpoint *endpoint = (dlg ? ast_sip_dialog_get_endpoint(dlg) : NULL);
|
||||
/*
|
||||
* We're ast_strdupa'ing the endpoint id because we're going to
|
||||
* clean up the endpoint immediately after this. We only needed
|
||||
* it to get the id for logging.
|
||||
*/
|
||||
char *endpoint_id = endpoint ? ast_strdupa(ast_sorcery_object_get_id(endpoint)) : NULL;
|
||||
char *id = endpoint_id ?: "noendpoint";
|
||||
char *src_name = challenge->pkt_info.src_name;
|
||||
struct ast_str *realms = NULL;
|
||||
pjsip_dialog *dlg;
|
||||
int res = -1;
|
||||
char *pj_err = NULL;
|
||||
SCOPE_ENTER(3, "%s:%s\n", id, src_name);
|
||||
|
||||
/* We only needed endpoint to get the id */
|
||||
ao2_cleanup(endpoint);
|
||||
|
||||
/*
|
||||
* Some older compilers have an issue with initializing structures with
|
||||
@@ -429,31 +500,18 @@ static int digest_create_request_with_auth(const struct ast_sip_auth_vector *aut
|
||||
*/
|
||||
memset(&auth_sess, 0, sizeof(auth_sess));
|
||||
|
||||
dlg = pjsip_rdata_get_dlg(challenge);
|
||||
if (dlg) {
|
||||
/* The only thing we use endpoint for is to get an id for error/debug messages */
|
||||
endpoint = ast_sip_dialog_get_endpoint(dlg);
|
||||
id = endpoint ? ast_strdupa(ast_sorcery_object_get_id(endpoint)) : NULL;
|
||||
ao2_cleanup(endpoint);
|
||||
id_type = "Endpoint";
|
||||
}
|
||||
|
||||
/* If there was no dialog, then this is probably a REGISTER so no endpoint */
|
||||
if (!id) {
|
||||
/* The only thing we use the address for is to get an id for error/debug messages */
|
||||
id = ast_alloca(AST_SOCKADDR_BUFLEN);
|
||||
pj_sockaddr_print(&challenge->pkt_info.src_addr, id, AST_SOCKADDR_BUFLEN, 3);
|
||||
id_type = "Host";
|
||||
}
|
||||
|
||||
if (!auth_ids_vector || AST_VECTOR_SIZE(auth_ids_vector) == 0) {
|
||||
ast_log(LOG_ERROR, "%s: '%s': There were no auth ids available\n", id_type, id);
|
||||
SCOPE_EXIT_LOG_RTN_VALUE(-1, LOG_ERROR, "%s:%s: There were no auth ids available\n",
|
||||
id, src_name);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* auth_ids_vector contains only ids but we need the complete objects.
|
||||
*/
|
||||
if (AST_VECTOR_INIT(&auth_objects_vector, AST_VECTOR_SIZE(auth_ids_vector)) != 0) {
|
||||
ast_log(LOG_ERROR, "%s: '%s': Couldn't initialize auth object vector\n", id_type, id);
|
||||
return -1;
|
||||
SCOPE_EXIT_LOG_RTN_VALUE(-1, LOG_ERROR, "%s:%s: Couldn't initialize auth object vector\n",
|
||||
id, src_name);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -465,6 +523,8 @@ static int digest_create_request_with_auth(const struct ast_sip_auth_vector *aut
|
||||
* AST_VECTOR_FREE(&auth_objects_vector);
|
||||
* when you're done with the vector
|
||||
*/
|
||||
ast_trace(-1, "%s:%s: Retrieving %d auth objects\n", id, src_name,
|
||||
(int)AST_VECTOR_SIZE(auth_ids_vector));
|
||||
ast_sip_retrieve_auths_vector(auth_ids_vector, &auth_objects_vector);
|
||||
auth_object_count = AST_VECTOR_SIZE(&auth_objects_vector);
|
||||
if (auth_object_count == 0) {
|
||||
@@ -475,13 +535,19 @@ static int digest_create_request_with_auth(const struct ast_sip_auth_vector *aut
|
||||
* id that wasn't found.
|
||||
*/
|
||||
res = -1;
|
||||
ast_trace(-1, "%s:%s: No auth objects found\n", id, src_name);
|
||||
goto cleanup;
|
||||
}
|
||||
ast_trace(-1, "%s:%s: Retrieved %d auth objects\n", id, src_name,
|
||||
(int)auth_object_count);
|
||||
|
||||
if (pjsip_auth_clt_init(&auth_sess, ast_sip_get_pjsip_endpoint(),
|
||||
old_request->pool, 0) != PJ_SUCCESS) {
|
||||
ast_log(LOG_ERROR, "%s: '%s': Failed to initialize client authentication session\n",
|
||||
id_type, id);
|
||||
status = pjsip_auth_clt_init(&auth_sess, ast_sip_get_pjsip_endpoint(),
|
||||
old_request->pool, 0);
|
||||
if (status != PJ_SUCCESS) {
|
||||
pj_err = ast_alloca(PJ_ERR_MSG_SIZE);
|
||||
pj_strerror(status, pj_err, PJ_ERR_MSG_SIZE);
|
||||
ast_log(LOG_ERROR, "%s:%s: Failed to initialize client authentication session: %s\n",
|
||||
id, src_name, pj_err);
|
||||
res = -1;
|
||||
goto cleanup;
|
||||
}
|
||||
@@ -499,18 +565,24 @@ static int digest_create_request_with_auth(const struct ast_sip_auth_vector *aut
|
||||
* Load pjproject with the valid credentials for the Authentication headers
|
||||
* received on the 401 or 407 response.
|
||||
*/
|
||||
status = set_outbound_authentication_credentials(&auth_sess, &auth_objects_vector, challenge, &realms);
|
||||
status = SCOPE_CALL_WITH_RESULT(-1, pj_status_t, set_auth_creds, id, &auth_sess, &auth_objects_vector, challenge, &realms);
|
||||
if (status != PJ_SUCCESS && status != PJSIP_ENOCREDENTIAL) {
|
||||
pj_err = ast_alloca(PJ_ERR_MSG_SIZE);
|
||||
}
|
||||
|
||||
switch (status) {
|
||||
case PJ_SUCCESS:
|
||||
break;
|
||||
case PJSIP_ENOCREDENTIAL:
|
||||
ast_log(LOG_WARNING,
|
||||
"%s: '%s': No auth objects matching realm(s) '%s' from challenge found.\n", id_type, id,
|
||||
realms ? ast_str_buffer(realms) : "<none>");
|
||||
"%s:%s: No auth objects matching realm/algorithm(s) '%s' from challenge found.\n",
|
||||
id, src_name, realms ? ast_str_buffer(realms) : "<none>");
|
||||
res = -1;
|
||||
goto cleanup;
|
||||
default:
|
||||
ast_log(LOG_WARNING, "%s: '%s': Failed to set authentication credentials\n", id_type, id);
|
||||
pj_strerror(status, pj_err, PJ_ERR_MSG_SIZE);
|
||||
ast_log(LOG_WARNING, "%s:%s: Failed to set authentication credentials: %s\n",
|
||||
id, src_name, pj_err);
|
||||
res = -1;
|
||||
goto cleanup;
|
||||
}
|
||||
@@ -521,7 +593,11 @@ static int digest_create_request_with_auth(const struct ast_sip_auth_vector *aut
|
||||
* from an earlier successful authorization, it'll use it. Otherwise
|
||||
* it'll create a new authorization and cache it.
|
||||
*/
|
||||
status = pjsip_auth_clt_reinit_req(&auth_sess, challenge, old_request, new_request);
|
||||
status = SCOPE_CALL_WITH_RESULT(-1, pj_status_t, pjsip_auth_clt_reinit_req,
|
||||
&auth_sess, challenge, old_request, new_request);
|
||||
if (status != PJ_SUCCESS) {
|
||||
pj_err = ast_alloca(PJ_ERR_MSG_SIZE);
|
||||
}
|
||||
|
||||
switch (status) {
|
||||
case PJ_SUCCESS:
|
||||
@@ -535,6 +611,7 @@ static int digest_create_request_with_auth(const struct ast_sip_auth_vector *aut
|
||||
ast_assert(cseq != NULL);
|
||||
++cseq->cseq;
|
||||
res = 0;
|
||||
ast_trace(-1, "%s:%s: Created new request with auth\n", id, src_name);
|
||||
goto cleanup;
|
||||
case PJSIP_ENOCREDENTIAL:
|
||||
/*
|
||||
@@ -542,21 +619,24 @@ static int digest_create_request_with_auth(const struct ast_sip_auth_vector *aut
|
||||
* did the matching but you never know.
|
||||
*/
|
||||
ast_log(LOG_WARNING,
|
||||
"%s: '%s': No auth objects matching realm(s) '%s' from challenge found.\n", id_type, id,
|
||||
realms ? ast_str_buffer(realms) : "<none>");
|
||||
"%s:%s: No auth objects matching realm(s) '%s' from challenge found.\n",
|
||||
id, src_name, realms ? ast_str_buffer(realms) : "<none>");
|
||||
break;
|
||||
case PJSIP_EAUTHSTALECOUNT:
|
||||
pj_strerror(status, pj_err, PJ_ERR_MSG_SIZE);
|
||||
ast_log(LOG_WARNING,
|
||||
"%s: '%s': Unable to create request with auth. Number of stale retries exceeded.\n",
|
||||
id_type, id);
|
||||
"%s:%s: Unable to create request with auth: %s\n",
|
||||
id, src_name, pj_err);
|
||||
break;
|
||||
case PJSIP_EFAILEDCREDENTIAL:
|
||||
ast_log(LOG_WARNING, "%s: '%s': Authentication credentials not accepted by server.\n",
|
||||
id_type, id);
|
||||
pj_strerror(status, pj_err, PJ_ERR_MSG_SIZE);
|
||||
ast_log(LOG_WARNING, "%s:%s: Authentication credentials not accepted by server. %s\n",
|
||||
id, src_name, pj_err);
|
||||
break;
|
||||
default:
|
||||
ast_log(LOG_WARNING, "%s: '%s': Unable to create request with auth. Unknown failure.\n",
|
||||
id_type, id);
|
||||
pj_strerror(status, pj_err, PJ_ERR_MSG_SIZE);
|
||||
ast_log(LOG_WARNING, "%s:%s: Unable to create request with auth: %s\n",
|
||||
id, src_name, pj_err);
|
||||
break;
|
||||
}
|
||||
res = -1;
|
||||
@@ -573,7 +653,8 @@ cleanup:
|
||||
AST_VECTOR_FREE(&auth_objects_vector);
|
||||
ast_free(realms);
|
||||
|
||||
return res;
|
||||
SCOPE_EXIT_RTN_VALUE(res, "%s:%s: result: %s\n", id, src_name,
|
||||
res == 0 ? "success" : "failure");
|
||||
}
|
||||
|
||||
static struct ast_sip_outbound_authenticator digest_authenticator = {
|
||||
|
1
third-party/pjproject/configure.m4
vendored
1
third-party/pjproject/configure.m4
vendored
@@ -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])
|
||||
|
2
third-party/pjproject/patches/config_site.h
vendored
2
third-party/pjproject/patches/config_site.h
vendored
@@ -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.
|
||||
|
Reference in New Issue
Block a user