diff --git a/configs/samples/pjsip.conf.sample b/configs/samples/pjsip.conf.sample index a808694045e..bef1310ffa4 100644 --- a/configs/samples/pjsip.conf.sample +++ b/configs/samples/pjsip.conf.sample @@ -1028,52 +1028,122 @@ ; "config show help res_pjsip auth realm" or on the wiki for the ; difference. ; -;auth_type=userpass ; Authentication type. May be - ; "userpass" for plain text passwords or - ; "md5" for pre-hashed credentials. - ; (default: "userpass") -;nonce_lifetime=32 ; Lifetime of a nonce associated with this - ; authentication config (default: "32") -;md5_cred= ; As an alternative to specifying a plain text password, - ; you can hash the username, realm and password - ; together one time and place the hash value here. - ; The input to the hash function must be in the - ; following format: - ; :: - ; 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: "") +;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 (default: "") + +;password= ; PlainText password used for authentication (default: "") + +;password_digest= [,]... + ; As an alternative to specifying a plain text password, you can + ; specify one or more pre-computed digests separated by commas. + ; + ; = : + ; : One of the supported hash algorithms + ; which currently are "MD5", "SHA-256" and + ; "SHA-512-256". (case-insensitive) + ; If you are NOT using the bundled pjproject or a system + ; pjproject version > 2.14.1, only "MD5" is supported. + ; Also, OpenSSL versions < 1.1.1 do not support "SHA-512-256". + ; You can see the current list by running the CLI command + ; "pjproject show buildopts". + ; : The result of passing the following + ; string through the selected hash algorithm: + ; :: + ; Example: + ; $ echo -n "fred:asterisk:mypass" | openssl dgst -md5 + ; MD5(stdin)= 43a8d9be3da524f9a59ca0593d7b1b5d + ; would be specified as... +;password_digest = MD5:43a8d9be3da524f9a59ca0593d7b1b5d + ; Ensure you don't accidentally include a trailing '\n' + ; in the input to the hash function and see the notes above + ; concerning 'realm". + ; Also note that the name used by OpenSSL is NOT the official + ; IANA name which is required by Asterisk and SIP. + ; You may specify multiple password_digest parameters or + ; specify multiple digest-specs separated by commas in one + ; password_digest entry. + +;md5_cred= ; (deprecated) Will be automatically converted to a + ; "password_digest" parameter. + +;supported_algorithms_uas= [,]... + ; Specify the digest algorithms to offer when this auth object + ; is used by Asterisk acting as a UAS. + ; A WWW-Authenticate header will be created for each algorithm + ; in the order the algorithms appear here. If a plain text + ; password isn't specified in this object, then a + ; password_digest parameter must be specified for each + ; algorithm. If neither condition is met, validation will fail + ; and the object will not load. The default may be specified by the + ; "default_auth_algorithms_uas" parameter in the "global" + ; object. If that's not specified, the default is "MD5". + +;supported_algorithms_uac= [,]... + ; Specify the digest algorithms to respond with when this auth + ; object is used by Asterisk acting as a UAC. + ; The algorithm specified in each received + ; WWW/Proxy-Authenticate header in a 401/407 response will be + ; checked against this list. If the algorithm received doesn't + ; appear in the list, that header will be ignored. Order + ; doesn't matter as the UAS is supposed to send the headers + ; in their preferred order. As with supported_algorithms_uas, + ; If a plain text password isn't specified in this object, + ; then a password_digest parameter must be specified for each + ; algorithm. If neither condition is met, the algorithm + ; will automatically be removed from the list. If no + ; algorithms remain, the object will fail to load. + ; The default may be specified by the + ; "default_auth_algorithms_uac" parameter in the "global" + ; object. If that's not specified, the default is "MD5". + +;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========================= @@ -1400,6 +1470,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] diff --git a/configure b/configure index 95f3d06a8b1..60882dde9ae 100755 --- a/configure +++ b/configure @@ -941,6 +941,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 @@ -11231,6 +11235,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 + + @@ -13414,6 +13421,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 @@ -28291,6 +28310,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 diff --git a/configure.ac b/configure.ac index 24f9ba219dc..643a515dda3 100644 --- a/configure.ac +++ b/configure.ac @@ -601,6 +601,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]) @@ -2527,6 +2528,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 diff --git a/contrib/ast-db-manage/config/versions/abdc9ede147d_add_fields_to_ps_auths_to_support_new_.py b/contrib/ast-db-manage/config/versions/abdc9ede147d_add_fields_to_ps_auths_to_support_new_.py new file mode 100644 index 00000000000..a7add98b858 --- /dev/null +++ b/contrib/ast-db-manage/config/versions/abdc9ede147d_add_fields_to_ps_auths_to_support_new_.py @@ -0,0 +1,31 @@ +"""Add fields to ps_auths to support new algorithms + +Revision ID: abdc9ede147d +Revises: 801b9fced8b7 +Create Date: 2024-10-27 15:26:25.165085 + +""" + +# revision identifiers, used by Alembic. +revision = 'abdc9ede147d' +down_revision = '801b9fced8b7' + +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') diff --git a/include/asterisk/autoconfig.h.in b/include/asterisk/autoconfig.h.in index debf540c3b8..7df98f8d833 100644 --- a/include/asterisk/autoconfig.h.in +++ b/include/asterisk/autoconfig.h.in @@ -613,6 +613,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 diff --git a/include/asterisk/res_pjsip.h b/include/asterisk/res_pjsip.h index dc66a40184f..e61bbf3554b 100644 --- a/include/asterisk/res_pjsip.h +++ b/include/asterisk/res_pjsip.h @@ -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 @@ -557,25 +558,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); @@ -586,7 +666,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); @@ -599,6 +682,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 *); @@ -1237,6 +1326,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. * @@ -3041,6 +3157,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 */ @@ -3406,6 +3556,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. * diff --git a/res/res_pjproject.c b/res/res_pjproject.c index 865f06278fc..eeaf954039c 100644 --- a/res/res_pjproject.c +++ b/res/res_pjproject.c @@ -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; } diff --git a/res/res_pjsip/config_auth.c b/res/res_pjsip/config_auth.c index 2350140f533..9c82a21bdec 100644 --- a/res/res_pjsip/config_auth.c +++ b/res/res_pjsip/config_auth.c @@ -24,13 +24,101 @@ #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. + */ +#include "openssl/md5.h" +#include "openssl/sha.h" + +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 +144,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 +164,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 +181,300 @@ static int auth_type_to_str(const void *obj, const intptr_t *args, char **buf) return 0; } -static int auth_apply(const struct ast_sorcery *sorcery, void *obj) +int ast_sip_auth_digest_algorithms_vector_init(const char *id, + struct pjsip_auth_algorithm_type_vector *algorithms, const char *agent_type, + const char *value) { - struct ast_sip_auth *auth = obj; + char *iana_names = ast_strdupa(value); + pj_str_t val; int res = 0; - if (ast_strlen_zero(auth->auth_user)) { - ast_log(LOG_ERROR, "No authentication username for auth '%s'\n", - ast_sorcery_object_get_id(auth)); + ast_assert(algorithms != NULL); + + if (AST_VECTOR_SIZE(algorithms)) { + AST_VECTOR_FREE(algorithms); + } + if (AST_VECTOR_INIT(algorithms, 4)) { return -1; } - switch (auth->type) { - case AST_SIP_AUTH_TYPE_MD5: - if (ast_strlen_zero(auth->md5_creds)) { - ast_log(LOG_ERROR, "'md5' authentication specified but no md5_cred " - "specified for auth '%s'\n", ast_sorcery_object_get_id(auth)); + while ((val.ptr = ast_strip(strsep(&iana_names, ",")))) { + const pjsip_auth_algorithm *algo; + + if (ast_strlen_zero(val.ptr)) { + continue; + } + val.slen = strlen(val.ptr); + + algo = ast_sip_auth_get_algorithm_by_iana_name(&val); + if (!algo) { + ast_log(LOG_WARNING, "%s: Unknown %s digest algorithm '%s' specified\n", + id, agent_type, val.ptr); res = -1; - } else if (strlen(auth->md5_creds) != PJSIP_MD5STRLEN) { - ast_log(LOG_ERROR, "'md5' authentication requires digest of size '%d', but " - "digest is '%d' in size for auth '%s'\n", PJSIP_MD5STRLEN, (int)strlen(auth->md5_creds), - ast_sorcery_object_get_id(auth)); + continue; + } + if (!ast_sip_auth_is_algorithm_supported(algo->algorithm_type)) { + ast_log(LOG_WARNING, "%s: %s digest algorithm '%s' is not supported by the version of OpenSSL in use\n", + id, agent_type, val.ptr); res = -1; + continue; + } + + if (AST_VECTOR_APPEND(algorithms, algo->algorithm_type)) { + AST_VECTOR_FREE(algorithms); + return -1; + } + } + return res; +} + +static int uac_algorithms_handler(const struct aco_option *opt, struct ast_variable *var, void *obj) +{ + struct ast_sip_auth *auth = obj; + + return ast_sip_auth_digest_algorithms_vector_init(ast_sorcery_object_get_id(auth), + &auth->supported_algorithms_uac, "UAC", var->value); +} + +static int uas_algorithms_handler(const struct aco_option *opt, struct ast_variable *var, void *obj) +{ + struct ast_sip_auth *auth = obj; + + return ast_sip_auth_digest_algorithms_vector_init(ast_sorcery_object_get_id(auth), + &auth->supported_algorithms_uas, "UAS", var->value); +} + +int ast_sip_auth_digest_algorithms_vector_to_str( + const struct pjsip_auth_algorithm_type_vector *algorithms, char **buf) +{ + struct ast_str *str = NULL; + int i = 0; + + if (!algorithms || !AST_VECTOR_SIZE(algorithms)) { + return 0; + } + + str = ast_str_alloca(256); + if (!str) { + return -1; + } + + for (i = 0; i < AST_VECTOR_SIZE(algorithms); ++i) { + const pjsip_auth_algorithm *algo = ast_sip_auth_get_algorithm_by_type( + AST_VECTOR_GET(algorithms, i)); + ast_str_append(&str, 0, "%s" PJSTR_PRINTF_SPEC, i > 0 ? "," : "", + PJSTR_PRINTF_VAR(algo->iana_name)); + } + + *buf = ast_strdup(ast_str_buffer(str)); + + return *buf ? 0 : -1; +} + +static int uac_algorithms_to_str(const void *obj, const intptr_t *args, char **buf) +{ + const struct ast_sip_auth *auth = obj; + return ast_sip_auth_digest_algorithms_vector_to_str(&auth->supported_algorithms_uac, buf); +} + +static int uas_algorithms_to_str(const void *obj, const intptr_t *args, char **buf) +{ + const struct ast_sip_auth *auth = obj; + return ast_sip_auth_digest_algorithms_vector_to_str(&auth->supported_algorithms_uas, buf); +} + +static int password_digest_handler(const struct aco_option *opt, struct ast_variable *var, void *obj) +{ + struct ast_sip_auth *auth = obj; + const char *auth_name = ast_sorcery_object_get_id(auth); + char *value = ast_strdupa(var->value); + char *unparsed_digest = NULL; + + while ((unparsed_digest = ast_strsep(&value, ',', AST_STRSEP_TRIM))) { + const pjsip_auth_algorithm *algo; + char *iana_name; + char *digest; + struct ast_sip_auth_password_digest *pw; + pj_str_t pj_iana_name; + + if (ast_strlen_zero(unparsed_digest)) { + continue; + } + + if (strchr(unparsed_digest, ':') != NULL) { + iana_name = ast_strsep(&unparsed_digest, ':', AST_STRSEP_TRIM); + } else { + /* + * md5_cred doesn't have the algorithm name in front + * so we need to force it. + */ + iana_name = "MD5"; + } + 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; } - break; - case AST_SIP_AUTH_TYPE_GOOGLE_OAUTH: + + 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 +714,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 +749,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 +786,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); diff --git a/res/res_pjsip/config_global.c b/res/res_pjsip/config_global.c index 5a71b485b41..17b3f0f8e06 100644 --- a/res/res_pjsip/config_global.c +++ b/res/res_pjsip/config_global.c @@ -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; } diff --git a/res/res_pjsip/pjsip_config.xml b/res/res_pjsip/pjsip_config.xml index ab1c6332f38..b15007847fe 100644 --- a/res/res_pjsip/pjsip_config.xml +++ b/res/res_pjsip/pjsip_config.xml @@ -1540,87 +1540,160 @@ Authentication objects hold the authentication information for use by other objects such as endpoints or registrations. This also allows for multiple objects to use a single auth object. See - the auth_type config option for password style choices. + the auth_type config option for security mechanism choices. - + Authentication type - This option specifies which of the password style config options should be read - when trying to authenticate an endpoint inbound request. If set to userpass - then we'll read from the 'password' option. For md5 we'll read - from 'md5_cred'. If set to google_oauth then we'll read from the - refresh_token/oauth_clientid/oauth_secret fields. The following values are valid: + If set to google_oauth then we'll read from the + refresh_token/oauth_clientid/oauth_secret parameters. + If set to digest then we'll read from the + password and/or password_digest + parameters. The older md5 and userpass + values are deprecated and converted to digest. - - - + Deprecated. Use digest. + Deprecated. Use digest. + If selected, the refresh_token, + oauth_clientid and oauth_secret + parameters must be provided. + If selected, the password + and/or one or more password_digest + parameters must be provided. - - - 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. - - - - Lifetime of a nonce associated with this authentication config. + + Username to use for account - - MD5 Hash used for authentication. - - Only used when auth_type is md5. + + Plain text password used for authentication. + Only used when auth_type is digest. + + + One or more pre-computed hashes used for authentication. + Only used when auth_type is digest. 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: - - + you can specify one or more pre-computed digests separated by + commas. - <username>:<realm>:<password> + password_digest= <digest-spec>[,<digest_spec>]... - + + <hash-algorithm>:<hashed-credential> + One of the supported hash algorithms + which currently are + + Supported by all versions of OpenSSL and pjproject + Supported by all versions of OpenSSL but only pjproject versions > 2.14.1 + Supported by OpenSSL versions >= 1.1.1 and pjproject versions > 2.14.1 + + You can see the current list by running the CLI command + pjproject show buildopts. + + + The result of passing the following string through + the selected hash algorithm: + <username>:<realm>:<password> + + + + You can create the hash by piping the string into the appropriate + hash/checksum program. See the description for the realm + parameter for info on how to set it. + + $ echo -n "myname:myrealm:mypassword" | openssl dgst -md5 + MD5(stdin)= dce9ccd0a69e3ef90d8b9bf725053e78 + + You would then set: + + password_digest = md5:dce9ccd0a69e3ef90d8b9bf725053e78 + + If you also wanted to support the sha-512-256 algorithm, you would + add another entry: + + $ echo -n "myname:myrealm:mypassword" | openssl dgst -sha512-256 + SHA2-512/256(stdin)= 2b644472039d4548eee26839985bc69767c4b27d36ccdc12cd2d118f13eac07e + + Note that OpenSSL uses different designations for the hash names than the IANA. + The password_digest parameters must use the IANA name. + + password_digest = md5:dce9ccd0a69e3ef90d8b9bf725053e78 + password_digest = sha-512-256:2b644472039d4548eee26839985bc69767c4b27d36ccdc12cd2d118f13eac07e + + In this example, we used two different occurrences of the + password_digest parameter instead of a comma-separated list. + The sha-512-256 entry may show as wrapped in the example, but it + must be on a single line in the configuration file. + + + + MD5 Hash used for authentication. (deprecated) + Use the password_digest parameter instead. + If supplied, a password_digest parameter will be created + for it. + + + + Comma separated list of algorithms to support when this auth is used as a UAC + Valid values: + + Supported by all versions of OpenSSL and pjproject + Supported by all versions of OpenSSL but only pjproject versions > 2.14.1 + Supported by OpenSSL versions >= 1.1.1 and pjproject versions > 2.14.1 + - For incoming authentication (asterisk is the server), - the realm must match either the realm set in this object - or the default_realm set in in the - global object. + The algorithm specified in each received + WWW/Proxy-Authenticate header in a 401/407 response will be + checked against this list. If the algorithm received doesn't + appear in the list, that header will be ignored. Order + doesn't matter as the UAS is supposed to send the headers + in their preferred order. + If a plain text password isn't specified in this object, then a + password_digest parameter must be specified for each + algorithm. If neither condition is met or if an invalid + algorithm is entered, validation will fail and the object + will not load. - 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: + The default may be specified by the + default_auth_algorithms_uac parameter in + the global object. If that's not specified, the default is "MD5". + + + + Comma separated list of algorithms to support when this auth is used as a UAS + Valid values: + + Supported by all versions of OpenSSL and pjproject + Supported by all versions of OpenSSL but only pjproject versions > 2.14.1 + Supported by OpenSSL versions >= 1.1.1 and pjproject versions > 2.14.1 + + A WWW-Authenticate header will be created for each algorithm + in the order the algorithms appear here. - $ echo -n "myname:myrealm:mypassword" | md5sum + If a plain text password isn't specified in this object, then a + password_digest parameter must be specified for each + algorithm. If neither condition is met or if an invalid + algorithm is entered, validation will fail and the object + will not load. + The default may be specified by the + default_auth_algorithms_uas parameter in + the global object. If that's not specified, the default is "MD5". - - Note the '-n'. You don't want a newline to be part - of the hash. - - - - Plain text password used for authentication. - Only used when auth_type is userpass. + OAuth 2.0 refresh token @@ -1664,19 +1737,19 @@ 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. + + Lifetime of a nonce associated with this authentication config. + Must be 'auth' - - Username to use for account - Domain Alias @@ -2500,6 +2573,28 @@ RFC 3261 specifies this as a SHOULD requirement. + + List of default authentication algorithms to support when Asterisk is UAS + Valid values: + + Supported by all versions of OpenSSL and pjproject + Supported by all versions of OpenSSL but only pjproject versions > 2.14.1 + Supported by OpenSSL versions >= 1.1.1 and pjproject versions > 2.14.1 + + If not specified, the default is MD5 only. + + + + List of default authentication algorithms to support when Asterisk is UAC + Valid values: + + Supported by all versions of OpenSSL and pjproject + Supported by all versions of OpenSSL but only pjproject versions > 2.14.1 + Supported by OpenSSL versions >= 1.1.1 and pjproject versions > 2.14.1 + + If not specified, the default is MD5 only. + + diff --git a/res/res_pjsip/pjsip_distributor.c b/res/res_pjsip/pjsip_distributor.c index 909f99a8f98..50608679cbe 100644 --- a/res/res_pjsip/pjsip_distributor.c +++ b/res/res_pjsip/pjsip_distributor.c @@ -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; } diff --git a/res/res_pjsip_authenticator_digest.c b/res/res_pjsip_authenticator_digest.c index 0b9424e0b34..a97052562a4 100644 --- a/res/res_pjsip_authenticator_digest.c +++ b/res/res_pjsip_authenticator_digest.c @@ -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 pjproject res_pjsip @@ -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(¶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); } - if (pj_strcmp2(acc_name, auth->auth_user)) { - 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); } - pj_strdup2(pool, &info->realm, auth->realm); - pj_strdup2(pool, &info->username, auth->auth_user); - - switch (auth->type) { - case AST_SIP_AUTH_TYPE_USER_PASS: - pj_strdup2(pool, &info->data, auth->auth_pass); - info->data_type = PJSIP_CRED_DATA_PLAIN_PASSWD; - break; - case AST_SIP_AUTH_TYPE_MD5: - pj_strdup2(pool, &info->data, auth->md5_creds); - info->data_type = PJSIP_CRED_DATA_DIGEST; - break; - default: - return PJSIP_SC_FORBIDDEN; + if (!ast_sip_auth_is_algorithm_available(auth, &auth->supported_algorithms_uas, + algorithm->algorithm_type)) { + /* + * This shouldn't happen because we shouldn't have sent a challenge for + * an unsupported algorithm. + */ + SCOPE_EXIT_RTN_VALUE(PJSIP_SC_FORBIDDEN, "%s:%s: Algorithm '" PJSTR_PRINTF_SPEC + "' not supported or auth doesn't contain appropriate credentials\n", + auth_name, src_name, PJSTR_PRINTF_VAR(algorithm->iana_name)); } - return PJ_SUCCESS; + + pj_strdup2(pool, &cred_info->realm, realm); + pj_strdup2(pool, &cred_info->username, auth->auth_user); + + creds = ast_sip_auth_get_creds(auth, algorithm->algorithm_type, &cred_info->data_type); + if (!creds) { + /* + * This shouldn't happen because we checked the auth object when we + * loaded it to make sure it had the appropriate credentials for each + * algorithm in supported_algorithms_uas. + */ + SCOPE_EXIT_RTN_VALUE(PJSIP_SC_FORBIDDEN, "%s:%s: No plain text or digest password found for algorithm '" PJSTR_PRINTF_SPEC "'\n", + auth_name, src_name, PJSTR_PRINTF_VAR(algorithm->iana_name)); + } + pj_strdup2(pool, &cred_info->data, creds); +#ifdef HAVE_PJSIP_AUTH_NEW_DIGESTS + if (cred_info->data_type == PJSIP_CRED_DATA_DIGEST) { + cred_info->algorithm_type = algorithm->algorithm_type; + } +#endif + + SCOPE_EXIT_RTN_VALUE(PJ_SUCCESS, "%s:%s: Success. Data type: %s Algorithm '" PJSTR_PRINTF_SPEC "'\n", + auth_name, src_name, cred_info->data_type ? "digest" : "plain text", PJSTR_PRINTF_VAR(algorithm->iana_name)); } /*! @@ -202,7 +284,8 @@ static pj_status_t digest_lookup(pj_pool_t *pool, const pj_str_t *realm, * \param rdata The incoming request * \param realm The realm for which authentication should occur */ -static int build_nonce(struct ast_str **nonce, const char *timestamp, const pjsip_rx_data *rdata, const char *realm) +static int build_nonce(struct ast_str **nonce, const char *timestamp, + const pjsip_rx_data *rdata, const char *realm) { struct ast_str *str = ast_str_alloca(256); RAII_VAR(char *, eid, ao2_global_obj_ref(entity_id), ao2_cleanup); @@ -255,7 +338,7 @@ static int check_nonce(const char *candidate, const pjsip_rx_data *rdata, const return 0; } - build_nonce(&calculated, timestamp, rdata, auth->realm); + build_nonce(&calculated, timestamp, rdata, S_OR(auth->realm, default_realm)); ast_debug(3, "Calculated nonce %s. Actual nonce is %s\n", ast_str_buffer(calculated), candidate); if (strcmp(ast_str_buffer(calculated), candidate)) { return 0; @@ -263,34 +346,6 @@ static int check_nonce(const char *candidate, const pjsip_rx_data *rdata, const return 1; } -static int find_challenge(const pjsip_rx_data *rdata, const struct ast_sip_auth *auth) -{ - struct pjsip_authorization_hdr *auth_hdr = (pjsip_authorization_hdr *) &rdata->msg_info.msg->hdr; - int challenge_found = 0; - char nonce[64]; - - while ((auth_hdr = (pjsip_authorization_hdr *) pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_AUTHORIZATION, auth_hdr->next))) { - ast_copy_pj_str(nonce, &auth_hdr->credential.digest.nonce, sizeof(nonce)); - if (check_nonce(nonce, rdata, auth) && !pj_strcmp2(&auth_hdr->credential.digest.realm, auth->realm)) { - challenge_found = 1; - break; - } - } - - return challenge_found; -} - -/*! - * \brief Common code for initializing a pjsip_auth_srv - */ -static void setup_auth_srv(pj_pool_t *pool, pjsip_auth_srv *auth_server, const char *realm) -{ - pj_str_t realm_str; - pj_cstr(&realm_str, realm); - - pjsip_auth_srv_init(pool, auth_server, &realm_str, digest_lookup, 0); -} - /*! * \brief Result of digest verification */ @@ -311,69 +366,147 @@ static char *verify_result_str[] = { "STALE", "NOAUTH" }; + +static enum digest_verify_result find_authorization(const char *endpoint_id, + const struct ast_sip_auth *auth, const pjsip_rx_data *rdata) +{ + const char *auth_id = ast_sorcery_object_get_id(auth); + const char *src_name = rdata->pkt_info.src_name; + const char *realm = S_OR(auth->realm, default_realm); + struct pjsip_authorization_hdr *auth_hdr = + (pjsip_authorization_hdr *) &rdata->msg_info.msg->hdr; + enum digest_verify_result res = AUTH_NOAUTH; + int authorization_found = 0; + char nonce[64]; + SCOPE_ENTER(3, "%s:%s:%s: realm: %s\n", + endpoint_id, auth_id, src_name, realm); + + while ((auth_hdr = pjsip_msg_find_hdr(rdata->msg_info.msg, + PJSIP_H_AUTHORIZATION, auth_hdr ? auth_hdr->next : NULL))) { + ast_copy_pj_str(nonce, &auth_hdr->credential.digest.nonce, sizeof(nonce)); + ast_trace(-1, "%s:%s:%s: Checking nonce %s hdr-realm: " PJSTR_PRINTF_SPEC " hdr-algo: " PJSTR_PRINTF_SPEC " \n", + endpoint_id, auth_id, src_name, nonce, + PJSTR_PRINTF_VAR(auth_hdr->credential.digest.realm), + PJSTR_PRINTF_VAR(auth_hdr->credential.digest.algorithm)); + authorization_found++; + if (check_nonce(nonce, rdata, auth) + && pj_strcmp2(&auth_hdr->credential.digest.realm, realm) == 0) { + res = AUTH_SUCCESS; + break; + } else { + res = AUTH_STALE; + } + } + if (!authorization_found) { + ast_trace(-1, "%s:%s:%s: No Authorization header found\n", + endpoint_id, auth_id, src_name); + res = AUTH_NOAUTH; + } + + SCOPE_EXIT_RTN_VALUE(res, "%s:%s:%s: realm: %s Result %s\n", + endpoint_id, auth_id, src_name, realm, verify_result_str[res]); +} + +/*! + * \brief Common code for initializing a pjsip_auth_srv + */ +static void setup_auth_srv(pj_pool_t *pool, pjsip_auth_srv *auth_server, const char *realm) +{ + pjsip_auth_srv_init_param *param = pj_pool_alloc(pool, sizeof(*param)); + pj_str_t *pj_realm = pj_pool_alloc(pool, sizeof(*pj_realm)); + + pj_cstr(pj_realm, realm); + param->realm = pj_realm; + param->lookup2 = digest_lookup; + param->options = 0; + + pjsip_auth_srv_init2(pool, auth_server, param); +} + /*! - * \brief astobj2 callback for verifying incoming credentials + * \brief Verify incoming credentials * - * \param auth The ast_sip_auth to check against - * \param rdata The incoming request - * \param pool A pool to use for the auth server - * \return CMP_MATCH on successful authentication - * \return 0 on failed authentication + * \param endpoint_id For logging + * \param auth The ast_sip_auth to check against + * \param rdata The incoming request + * \param pool A pool to use for the auth server + * \return One of digest_verify_result */ -static int verify(const struct ast_sip_auth *auth, pjsip_rx_data *rdata, pj_pool_t *pool) +static int verify(const char *endpoint_id, const struct ast_sip_auth *auth, + pjsip_rx_data *rdata, pj_pool_t *pool) { + const char *auth_id = ast_sorcery_object_get_id(auth); + const char *realm = S_OR(auth->realm, default_realm); + const char *src_name = rdata->pkt_info.src_name; pj_status_t authed; int response_code; pjsip_auth_srv auth_server; int stale = 0; - int res = AUTH_FAIL; + enum digest_verify_result res = AUTH_FAIL; + SCOPE_ENTER(3, "%s:%s:%s: realm: %s\n", + endpoint_id, auth_id, src_name, realm); + + res = find_authorization(endpoint_id, auth, rdata); + if (res == AUTH_NOAUTH) + { + ast_test_suite_event_notify("INCOMING_AUTH_VERIFY_RESULT", + "Realm: %s\r\n" + "Username: %s\r\n" + "Status: %s", + realm, auth->auth_user, verify_result_str[res]); + SCOPE_EXIT_RTN_VALUE(res, "%s:%s:%s: No Authorization header found\n", + endpoint_id, auth_id, src_name); + } - if (!find_challenge(rdata, auth)) { - /* Couldn't find a challenge with a sane nonce. + if (res == AUTH_STALE) { + /* Couldn't find an authorization with a sane nonce. * Nonce mismatch may just be due to staleness. */ stale = 1; } - setup_auth_srv(pool, &auth_server, auth->realm); - + setup_auth_srv(pool, &auth_server, realm); store_auth(auth); - authed = pjsip_auth_srv_verify(&auth_server, rdata, &response_code); + /* pjsip_auth_srv_verify will invoke digest_lookup */ + authed = SCOPE_CALL_WITH_RESULT(-1, pj_status_t, pjsip_auth_srv_verify, &auth_server, rdata, &response_code); remove_auth(); - if (authed == PJ_SUCCESS) { if (stale) { res = AUTH_STALE; } else { res = AUTH_SUCCESS; } + } else { + char err[256]; + res = AUTH_FAIL; + pj_strerror(authed, err, sizeof(err)); + ast_trace(-1, "%s:%s:%s: authed: %s\n", endpoint_id, auth_id, src_name, err); } - if (authed == PJSIP_EAUTHNOAUTH) { - res = AUTH_NOAUTH; - } - - ast_debug(3, "Realm: %s Username: %s Result: %s\n", - auth->realm, auth->auth_user, verify_result_str[res]); - ast_test_suite_event_notify("INCOMING_AUTH_VERIFY_RESULT", "Realm: %s\r\n" "Username: %s\r\n" "Status: %s", - auth->realm, auth->auth_user, verify_result_str[res]); + realm, auth->auth_user, verify_result_str[res]); - return res; + SCOPE_EXIT_RTN_VALUE(res, "%s:%s:%s: Realm: %s Username: %s Result: %s\n", + endpoint_id, auth_id, src_name, realm, + auth->auth_user, verify_result_str[res]); } /*! - * \brief astobj2 callback for adding digest challenges to responses + * \brief Send a WWW-Authenticate challenge * - * \param realm An auth's realm to build a challenge from + * \param endpoint_id For logging + * \param auth The auth object to use for the challenge * \param tdata The response to add the challenge to * \param rdata The request the challenge is in response to * \param is_stale Indicates whether nonce on incoming request was stale + * \param algorithm_type The algorithm to use for the challenge */ -static void challenge(const char *realm, pjsip_tx_data *tdata, const pjsip_rx_data *rdata, int is_stale) +static void challenge(const char *endpoint_id, struct ast_sip_auth *auth, + pjsip_tx_data *tdata, const pjsip_rx_data *rdata, int is_stale, + const pjsip_auth_algorithm *algorithm) { pj_str_t qop; pj_str_t pj_nonce; @@ -381,6 +514,14 @@ static void challenge(const char *realm, pjsip_tx_data *tdata, const pjsip_rx_da struct ast_str *nonce = ast_str_alloca(256); char time_buf[32]; time_t timestamp = time(NULL); + pj_status_t res; + const char *realm = S_OR(auth->realm, default_realm); + const char *auth_id = ast_sorcery_object_get_id(auth); + const char *src_name = rdata->pkt_info.src_name; + SCOPE_ENTER(5, "%s:%s:%s: realm: %s time: %d algorithm: " PJSTR_PRINTF_SPEC " stale? %s\n", + endpoint_id, auth_id, src_name, realm, (int)timestamp, + PJSTR_PRINTF_VAR(algorithm->iana_name), is_stale ? "yes" : "no"); + snprintf(time_buf, sizeof(time_buf), "%d", (int) timestamp); build_nonce(&nonce, time_buf, rdata, realm); @@ -389,9 +530,27 @@ static void challenge(const char *realm, pjsip_tx_data *tdata, const pjsip_rx_da pj_cstr(&pj_nonce, ast_str_buffer(nonce)); pj_cstr(&qop, "auth"); - pjsip_auth_srv_challenge(&auth_server, &qop, &pj_nonce, NULL, is_stale ? PJ_TRUE : PJ_FALSE, tdata); +#ifdef HAVE_PJSIP_AUTH_NEW_DIGESTS + res = pjsip_auth_srv_challenge2(&auth_server, &qop, &pj_nonce, + NULL, is_stale ? PJ_TRUE : PJ_FALSE, tdata, algorithm->algorithm_type); +#else + res = pjsip_auth_srv_challenge(&auth_server, &qop, &pj_nonce, + NULL, is_stale ? PJ_TRUE : PJ_FALSE, tdata); +#endif + SCOPE_EXIT_RTN("%s:%s:%s: Sending challenge for realm: %s algorithm: " PJSTR_PRINTF_SPEC + " %s\n", + endpoint_id, auth_id, src_name, realm, PJSTR_PRINTF_VAR(algorithm->iana_name), + res == PJ_SUCCESS ? "succeeded" : "failed"); } +static char *check_auth_result_str[] = { + "CHALLENGE", + "SUCCESS", + "FAILED", + "ERROR", +}; + + /*! * \brief Check authentication using Digest scheme * @@ -405,7 +564,6 @@ static enum ast_sip_check_auth_result digest_check_auth(struct ast_sip_endpoint pjsip_rx_data *rdata, pjsip_tx_data *tdata) { struct ast_sip_auth **auths; - struct ast_sip_auth **auths_shallow; enum digest_verify_result *verify_res; struct ast_sip_endpoint *artificial_endpoint; enum ast_sip_check_auth_result res; @@ -413,6 +571,9 @@ static enum ast_sip_check_auth_result digest_check_auth(struct ast_sip_endpoint int is_artificial; int failures = 0; size_t auth_size; + const char *endpoint_id = ast_sorcery_object_get_id(endpoint); + char *src_name = rdata->pkt_info.src_name; + SCOPE_ENTER(3, "%s:%s\n", endpoint_id, src_name); auth_size = AST_VECTOR_SIZE(&endpoint->inbound_auths); ast_assert(0 < auth_size); @@ -423,81 +584,122 @@ static enum ast_sip_check_auth_result digest_check_auth(struct ast_sip_endpoint artificial_endpoint = ast_sip_get_artificial_endpoint(); if (!artificial_endpoint) { /* Should not happen except possibly if we are shutting down. */ - return AST_SIP_AUTHENTICATION_ERROR; + SCOPE_EXIT_RTN_VALUE(AST_SIP_AUTHENTICATION_ERROR); } is_artificial = endpoint == artificial_endpoint; ao2_ref(artificial_endpoint, -1); if (is_artificial) { + ast_trace(3, "%s:%s: Using artificial endpoint for authentication\n", + endpoint_id, src_name); ast_assert(auth_size == 1); auths[0] = ast_sip_get_artificial_auth(); if (!auths[0]) { /* Should not happen except possibly if we are shutting down. */ - return AST_SIP_AUTHENTICATION_ERROR; + SCOPE_EXIT_RTN_VALUE(AST_SIP_AUTHENTICATION_ERROR); } } else { + ast_trace(3, "%s:%s: Using endpoint for authentication\n", + endpoint_id, src_name); memset(auths, 0, auth_size * sizeof(*auths)); + /* + * If ast_sip_retrieve_auths returns a failure we still need + * to cleanup the auths array because it may have been partially + * filled in. + */ if (ast_sip_retrieve_auths(&endpoint->inbound_auths, auths)) { - res = AST_SIP_AUTHENTICATION_ERROR; - goto cleanup; + ast_sip_cleanup_auths(auths, auth_size); + SCOPE_EXIT_RTN_VALUE(AST_SIP_AUTHENTICATION_ERROR, + "%s:%s: Failed to retrieve some or all auth objects from endpoint\n", + endpoint_id, src_name); } } - /* Setup shallow copy of auths */ - if (ast_strlen_zero(default_realm)) { - auths_shallow = auths; - } else { + /* + * NOTE: The only reason to use multiple auth objects as a UAS might + * be to send challenges for multiple realms however we currently don't + * know of anyone actually doing this. + */ + for (idx = 0; idx < auth_size; ++idx) { + int i = 0; + struct ast_sip_auth *auth = auths[idx]; + const char *realm = S_OR(auth->realm, default_realm); + const char *auth_id = ast_sorcery_object_get_id(auth); + SCOPE_ENTER(4, "%s:%s:%s: Verifying\n", endpoint_id, auth_id, src_name); + /* - * Set default realm on a shallow copy of the authentication - * objects that don't have a realm set. + * Artificial auth objects are used for the purpose of + * sending challenges. We don't need to verify them. */ - auths_shallow = ast_alloca(auth_size * sizeof(*auths_shallow)); - for (idx = 0; idx < auth_size; ++idx) { - if (ast_strlen_zero(auths[idx]->realm)) { - /* - * Make a shallow copy and set the default realm on it. - * - * The stack allocation is OK here. Normally this will - * loop one time. If you have multiple auths then you - * shouldn't need more auths than the normal complement - * of fingers and toes. Otherwise, you should check - * your sanity for setting up your system up that way. - */ - auths_shallow[idx] = ast_alloca(sizeof(**auths_shallow)); - memcpy(auths_shallow[idx], auths[idx], sizeof(**auths_shallow)); - *((char **) (&auths_shallow[idx]->realm)) = default_realm; - ast_debug(3, "Using default realm '%s' on incoming auth '%s'.\n", - default_realm, ast_sorcery_object_get_id(auths_shallow[idx])); - } else { - auths_shallow[idx] = auths[idx]; + if (auth->type == AST_SIP_AUTH_TYPE_ARTIFICIAL) { + ast_trace(-1, "%s:%s:%s: Skipping verification on artificial endpoint\n", endpoint_id, auth_id, src_name ) + verify_res[idx] = AUTH_NOAUTH; + } else { + verify_res[idx] = SCOPE_CALL_WITH_RESULT(-1, int, verify, endpoint_id, auth, rdata, tdata->pool); + if (verify_res[idx] == AUTH_SUCCESS) { + res = AST_SIP_AUTHENTICATION_SUCCESS; + SCOPE_EXIT_EXPR(break, "%s:%s:%s: success\n", endpoint_id, auth_id, src_name); + } + if (verify_res[idx] == AUTH_FAIL) { + ast_trace(-1, "%s:%s:%s: fail\n", endpoint_id, auth_id, src_name); + failures++; } } - } - for (idx = 0; idx < auth_size; ++idx) { - verify_res[idx] = verify(auths_shallow[idx], rdata, tdata->pool); - if (verify_res[idx] == AUTH_SUCCESS) { - res = AST_SIP_AUTHENTICATION_SUCCESS; - goto cleanup; - } - if (verify_res[idx] == AUTH_FAIL) { - failures++; + for (i = 0; i < AST_VECTOR_SIZE(&auth->supported_algorithms_uas); i++) { + pjsip_auth_algorithm_type algorithm_type = AST_VECTOR_GET(&auth->supported_algorithms_uas, i); + const pjsip_auth_algorithm *algorithm = ast_sip_auth_get_algorithm_by_type(algorithm_type); + pjsip_www_authenticate_hdr *auth_hdr = NULL; + int already_sent_challenge = 0; + SCOPE_ENTER(5, "%s:%s:%s: Challenging with " PJSTR_PRINTF_SPEC "\n", + endpoint_id, auth_id, src_name, PJSTR_PRINTF_VAR(algorithm->iana_name)); + + /* + * Per RFC 7616, if we've already sent a challenge for this realm + * and algorithm, we must not send another. + */ + while ((auth_hdr = pjsip_msg_find_hdr(tdata->msg, + PJSIP_H_WWW_AUTHENTICATE, auth_hdr ? auth_hdr->next : NULL))) { + if (pj_strcmp2(&auth_hdr->challenge.common.realm, realm) == 0 && + !pj_stricmp(&auth_hdr->challenge.digest.algorithm, &algorithm->iana_name)) { + ast_trace(-1, "%s:%s:%s: Not sending duplicate challenge for realm: %s algorithm: " + PJSTR_PRINTF_SPEC "\n", + endpoint_id, auth_id, src_name, realm, PJSTR_PRINTF_VAR(algorithm->iana_name)); + already_sent_challenge = 1; + } + } + if (already_sent_challenge) { + SCOPE_EXIT_EXPR(continue); + } + + SCOPE_CALL(5, challenge, endpoint_id, auth, tdata, rdata, + verify_res[idx] == AUTH_STALE, algorithm); + + SCOPE_EXIT("%s:%s:%s: Challenged with " PJSTR_PRINTF_SPEC "\n", + endpoint_id, auth_id, src_name, PJSTR_PRINTF_VAR(algorithm->iana_name)); } + SCOPE_EXIT("%s:%s:%s: Done with auth challenge\n", endpoint_id, auth_id, src_name); } - for (idx = 0; idx < auth_size; ++idx) { - challenge(auths_shallow[idx]->realm, tdata, rdata, verify_res[idx] == AUTH_STALE); - } + /* + * If we've sent challenges for multiple auth objects, we currently + * return SUCCESS when the first one succeeds. We may want to change + * this in the future to require that all succeed but as stated above, + * currently we don't have a use case for even using more than one + * auth object as a UAS. + */ if (failures == auth_size) { res = AST_SIP_AUTHENTICATION_FAILED; - } else { + } else if (res != AST_SIP_AUTHENTICATION_SUCCESS){ res = AST_SIP_AUTHENTICATION_CHALLENGE; } -cleanup: ast_sip_cleanup_auths(auths, auth_size); - return res; + SCOPE_EXIT_RTN_VALUE(res, "%s:%s: Result: %s\n", + endpoint_id, src_name, + check_auth_result_str[res]); + } static struct ast_sip_authenticator digest_authenticator = { diff --git a/res/res_pjsip_outbound_authenticator_digest.c b/res/res_pjsip_outbound_authenticator_digest.c index aee4afc90ec..e94fde3bac3 100644 --- a/res/res_pjsip_outbound_authenticator_digest.c +++ b/res/res_pjsip_outbound_authenticator_digest.c @@ -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 pjproject res_pjsip @@ -32,10 +40,6 @@ #include "asterisk/strings.h" #include "asterisk/vector.h" -pj_str_t supported_digest_algorithms[] = { - { "MD5", 3} -}; - /*! * \internal * \brief Determine proper authenticate header @@ -59,27 +63,240 @@ static pjsip_hdr_e get_auth_search_type(pjsip_rx_data *challenge) /*! * \internal - * \brief Determine if digest algorithm in the header is one we support + * \brief Determine if digest algorithm in the header is one supported by + * pjproject and OpenSSL. + */ +static const pjsip_auth_algorithm *get_supported_algorithm(pjsip_www_authenticate_hdr *auth_hdr) +{ + const pjsip_auth_algorithm *algo = NULL; + + algo = ast_sip_auth_get_algorithm_by_iana_name(&auth_hdr->challenge.digest.algorithm); + if (!algo) { + return NULL; + } + + if (ast_sip_auth_is_algorithm_supported(algo->algorithm_type)) { + return algo; + } + return NULL; +} + +AST_VECTOR(cred_info_vector, pjsip_cred_info); + +/*! + * \brief Get credentials (if any) from auth objects for a WWW/Proxy-Authenticate header * - * \retval 1 If we support the algorithm - * \retval 0 If we do not + * \param id For logging + * \param src_name For logging + * \param auth_hdr The *-Authenticate header to check + * \param auth_object_count The number of auth objects available + * \param auth_objects_vector The vector of available auth objects + * \param auth_creds The vector to store the credentials in + * \param realms For logging * */ -static int is_digest_algorithm_supported(pjsip_www_authenticate_hdr *auth_hdr) +static void get_creds_for_header(const char *id, const char *src_name, + pjsip_www_authenticate_hdr *auth_hdr, size_t auth_object_count, + const struct ast_sip_auth_objects_vector *auth_objects_vector, + struct cred_info_vector *auth_creds, struct ast_str **realms) { - int digest; + int exact_match_index = -1; + int wildcard_match_index = -1; + struct ast_sip_auth *found_auth = NULL; + const pjsip_auth_algorithm *challenge_algorithm = + get_supported_algorithm(auth_hdr); + int i = 0; + pjsip_cred_info auth_cred; + const char *cred_data; + int res = 0; + SCOPE_ENTER(4, "%s:%s: Testing header realm: '" PJSTR_PRINTF_SPEC "' algorithm: '" + PJSTR_PRINTF_SPEC "'\n", id, src_name, + PJSTR_PRINTF_VAR(auth_hdr->challenge.digest.realm), + PJSTR_PRINTF_VAR(auth_hdr->challenge.digest.algorithm)); + + if (!challenge_algorithm) { + SCOPE_EXIT_RTN("%s:%s: Skipping header with realm '" PJSTR_PRINTF_SPEC "' " + "and unsupported " PJSTR_PRINTF_SPEC "' algorithm \n", id, src_name, + PJSTR_PRINTF_VAR(auth_hdr->challenge.digest.realm), + PJSTR_PRINTF_VAR(auth_hdr->challenge.digest.algorithm)); + } + + /* + * If we already have credentials for this realm, we don't need to + * process this header. We can just skip it. + */ + for (i = 0; i < AST_VECTOR_SIZE(auth_creds); i++) { + pjsip_cred_info auth_cred = AST_VECTOR_GET(auth_creds, i); + if (pj_stricmp(&auth_cred.realm, &auth_hdr->challenge.common.realm) == 0) { + SCOPE_EXIT_RTN("%s:%s: Skipping header with realm '" PJSTR_PRINTF_SPEC "' " + "because we already have credentials for it\n", id, src_name, + PJSTR_PRINTF_VAR(auth_hdr->challenge.digest.realm)); + } + } - /* An empty digest is assumed to be md5 */ - if (pj_strlen(&auth_hdr->challenge.digest.algorithm) == 0) { - return 1; + /* + * Appending "realm/agorithm" to realms is strictly so + * digest_create_request_with_auth() can display good error messages. + */ + if (*realms) { + ast_str_append(realms, 0, PJSTR_PRINTF_SPEC "/" PJSTR_PRINTF_SPEC ", ", + PJSTR_PRINTF_VAR(auth_hdr->challenge.digest.realm), + PJSTR_PRINTF_VAR(auth_hdr->challenge.digest.algorithm)); } - for (digest = 0; digest < ARRAY_LEN(supported_digest_algorithms); digest++) { - if (pj_stricmp(&auth_hdr->challenge.digest.algorithm, &supported_digest_algorithms[digest]) == 0) { - return 1; + /* + * Now that we have a valid header, we can loop over the auths available to + * find either an exact realm match or, failing that, a wildcard auth (an + * auth with an empty or "*" realm). + * + * NOTE: We never use the global default realm when we're the UAC responding + * to a 401 or 407. We only use that when we're the UAS (handled elsewhere) + * and the auth object didn't have a realm. + */ + ast_trace(-1, "%s:%s: Searching %zu auths to find matching ones for header with realm " + "'" PJSTR_PRINTF_SPEC "' and algorithm '" PJSTR_PRINTF_SPEC "'\n", + id, src_name, auth_object_count, + PJSTR_PRINTF_VAR(auth_hdr->challenge.digest.realm), + PJSTR_PRINTF_VAR(auth_hdr->challenge.digest.algorithm)); + + for (i = 0; i < auth_object_count; ++i) { + struct ast_sip_auth *auth = AST_VECTOR_GET(auth_objects_vector, i); + const char *auth_id = ast_sorcery_object_get_id(auth); + SCOPE_ENTER(5, "%s:%s: Checking auth '%s' with realm '%s'\n", + id, src_name, auth_id, auth->realm); + + /* + * Is the challenge algorithm in the auth's supported_algorithms_uac + * and is there either a plain text password or a password_digest + * for the algorithm? + */ + if (!ast_sip_auth_is_algorithm_available(auth, &auth->supported_algorithms_uac, + challenge_algorithm->algorithm_type)) { + SCOPE_EXIT_EXPR(continue, "%s:%s: Skipping auth '%s' with realm '%s' because it doesn't support " + " algorithm '" PJSTR_PRINTF_SPEC "'\n", id, src_name, + auth_id, auth->realm, + PJSTR_PRINTF_VAR(challenge_algorithm->iana_name)); } + + /* + * If this auth object's realm exactly matches the one + * from the header, we can just break out and use it. + * + * NOTE: If there's more than one auth object for an endpoint with + * a matching realm it's a misconfiguration. We'll only use the first. + */ + if (pj_stricmp2(&auth_hdr->challenge.digest.realm, auth->realm) == 0) { + exact_match_index = i; + /* + * If we found an exact realm match, there's no need to keep + * looking for a wildcard. + */ + SCOPE_EXIT_EXPR(break, "%s:%s: Found matching auth '%s' with realm '%s'\n", + id, src_name, auth_id, auth->realm); + } + + /* + * If this auth object's realm is empty or a "*", it's a wildcard + * auth object. We going to save its index but keep iterating over + * the vector in case we find an exact match later. + * + * NOTE: If there's more than one wildcard auth object for an endpoint + * it's a misconfiguration. We'll only use the first. + */ + if (wildcard_match_index < 0 + && (ast_strlen_zero(auth->realm) || ast_strings_equal(auth->realm, "*"))) { + ast_trace(-1, "%s:%s: Found wildcard auth '%s' for realm '" PJSTR_PRINTF_SPEC "'\n", + id, src_name, auth_id, + PJSTR_PRINTF_VAR(auth_hdr->challenge.digest.realm)); + wildcard_match_index = i; + } + SCOPE_EXIT("%s:%s: Done checking auth '%s' with realm '%s'. " + "Found exact? %s Found wildcard? %s\n", id, src_name, + auth_id, auth->realm, exact_match_index >= 0 ? "yes" : "no", + wildcard_match_index >= 0 ? "yes" : "no"); + } /* End auth object loop */ + + if (exact_match_index < 0 && wildcard_match_index < 0) { + /* + * Didn't find either a wildcard or an exact realm match. + * Move on to the next header. + */ + SCOPE_EXIT_RTN("%s:%s: No auth matching realm or no wildcard found for realm '" PJSTR_PRINTF_SPEC "'\n", + id, src_name, PJSTR_PRINTF_VAR(auth_hdr->challenge.digest.realm)); } - return 0; + + if (exact_match_index >= 0) { + /* + * If we found an exact match, we'll always prefer that. + */ + found_auth = AST_VECTOR_GET(auth_objects_vector, exact_match_index); + ast_trace(-1, "%s:%s: Using matched auth '%s' with realm '" PJSTR_PRINTF_SPEC "'\n", + id, src_name, ast_sorcery_object_get_id(found_auth), + PJSTR_PRINTF_VAR(auth_hdr->challenge.digest.realm)); + } else { + /* + * We'll only use the wildcard if we didn't find an exact match. + */ + found_auth = AST_VECTOR_GET(auth_objects_vector, wildcard_match_index); + ast_trace(-1, "%s:%s: Using wildcard auth '%s' for realm '" PJSTR_PRINTF_SPEC "'\n", + id, src_name, ast_sorcery_object_get_id(found_auth), + PJSTR_PRINTF_VAR(auth_hdr->challenge.digest.realm)); + } + + /* + * Now that we have an auth object to use, we need to create a + * pjsip_cred_info structure for each algorithm we support. + */ + + memset(&auth_cred, 0, sizeof(auth_cred)); + /* + * Copy the fields from the auth_object to the + * pjsip_cred_info structure. + */ + auth_cred.realm = auth_hdr->challenge.common.realm; + pj_cstr(&auth_cred.username, found_auth->auth_user); + pj_cstr(&auth_cred.scheme, "digest"); + + /* + * auth_cred.data_type tells us whether the credential is a plain text + * password or a pre-digested one. + */ + cred_data = SCOPE_CALL_WITH_RESULT(-1, const char *, ast_sip_auth_get_creds, + found_auth, challenge_algorithm->algorithm_type, &auth_cred.data_type); + /* + * This can't really fail because we already called + * ast_sip_auth_is_algorithm_available() for the auth + * but we check anyway. + */ + if (!cred_data) { + SCOPE_EXIT_RTN("%s:%s: Shouldn't have happened\n", id, src_name); + } + + pj_cstr(&auth_cred.data, cred_data); +#ifdef HAVE_PJSIP_AUTH_NEW_DIGESTS + if (auth_cred.data_type == PJSIP_CRED_DATA_DIGEST) { + auth_cred.algorithm_type = challenge_algorithm->algorithm_type; + } +#endif + /* + * Because the vector contains actual structures and not pointers + * to structures, the call to AST_VECTOR_APPEND results in a simple + * assign of one structure to another, effectively copying the auth_cred + * structure contents to the array element. + * + * Also note that the calls to pj_cstr above set their respective + * auth_cred fields to the _pointers_ of their corresponding auth + * object fields. This is safe because the call to + * pjsip_auth_clt_set_credentials() below strdups them before we + * return to the calling function which decrements the reference + * counts. + */ + res = AST_VECTOR_APPEND(auth_creds, auth_cred); + SCOPE_EXIT_RTN("%s:%s: %s credential for realm: '" PJSTR_PRINTF_SPEC "' algorithm: '" + PJSTR_PRINTF_SPEC "'\n", id, src_name, + res == 0 ? "Added" : "Failed to add", + PJSTR_PRINTF_VAR(auth_hdr->challenge.digest.realm), + PJSTR_PRINTF_VAR(auth_hdr->challenge.digest.algorithm)); } /*! @@ -89,7 +306,7 @@ static int is_digest_algorithm_supported(pjsip_www_authenticate_hdr *auth_hdr) * RFC7616 and RFC8760 allow more than one WWW-Authenticate or * Proxy-Authenticate header per realm, each with different digest * algorithms (including new ones like SHA-256 and SHA-512-256). However, - * thankfully, a UAS can NOT send back multiple Authenticate headers for + * a UAS can NOT send back multiple Authenticate headers for * the same realm with the same digest algorithm. The UAS is also * supposed to send the headers in order of preference with the first one * being the most preferred. @@ -99,14 +316,14 @@ static int is_digest_algorithm_supported(pjsip_www_authenticate_hdr *auth_hdr) * * The UAS can also send multiple realms, especially when it's a proxy * that has forked the request in which case the proxy will aggregate all - * of the Authenticate and then them all back to the UAC. + * of the Authenticate headers into one response back to the UAC. * * It doesn't stop there though... Each realm can require a different * username from the others. There's also nothing preventing each digest * algorithm from having a unique password although I'm not sure if * that adds any benefit. * - * So now... For each Authenticate header we encounter, we have to + * So now... For each WWW/Proxy-Authenticate header we encounter, we have to * determine if we support the digest algorithm and, if not, just skip the * header. We then have to find an auth object that matches the realm AND * the digest algorithm or find a wildcard object that matches the digest @@ -115,27 +332,22 @@ static int is_digest_algorithm_supported(pjsip_www_authenticate_hdr *auth_hdr) * we already added an auth object for that realm, we skip the header. * Otherwise we repeat the process for the next header. * - * In the end, we'll have accumulated a list of credentials we can pass to - * pjproject that it can use to add Authentication headers to a request. - * - * \note: Neither we nor pjproject can currently handle digest algorithms - * other than MD5. We don't even have a place for it in the ast_sip_auth - * object. For this reason, we just skip processing any Authenticate - * header that's not MD5. When we support the others, we'll move the - * check into the loop that searches the objects. + * In the end, we'll have accumulated a list of credentials, one per realm, + * we can pass to pjproject that it can use to add Authentication headers + * to a request. */ -static pj_status_t set_outbound_authentication_credentials(pjsip_auth_clt_sess *auth_sess, - const struct ast_sip_auth_objects_vector *auth_objects_vector, pjsip_rx_data *challenge, - struct ast_str **realms) +static pj_status_t set_auth_creds(const char *id, pjsip_auth_clt_sess *auth_sess, + const struct ast_sip_auth_objects_vector *auth_objects_vector, + pjsip_rx_data *challenge, struct ast_str **realms) { - int i; size_t auth_object_count; pjsip_www_authenticate_hdr *auth_hdr = NULL; pj_status_t res = PJ_SUCCESS; pjsip_hdr_e search_type; - size_t cred_count; + size_t cred_count = 0; pjsip_cred_info *creds_array; - + char *pj_err = NULL; + const char *src_name = challenge->pkt_info.src_name; /* * Normally vector elements are pointers to something else, usually * structures. In this case however, the elements are the @@ -147,7 +359,8 @@ static pj_status_t set_outbound_authentication_credentials(pjsip_auth_clt_sess * * which we'll pass to pjsip_auth_clt_set_credentials() at the * end. */ - AST_VECTOR(cred_info, pjsip_cred_info) auth_creds; + struct cred_info_vector auth_creds; + SCOPE_ENTER(3, "%s:%s\n", id, src_name); search_type = get_auth_search_type(challenge); if (search_type == PJSIP_H_OTHER) { @@ -156,13 +369,14 @@ static pj_status_t set_outbound_authentication_credentials(pjsip_auth_clt_sess * * so there are no WWW-Authenticate or Proxy-Authenticate * headers to process. */ - return PJ_ENOTSUP; + SCOPE_EXIT_RTN_VALUE(PJ_ENOTSUP, "%s:%s: Status code %d was received when it should have been 401 or 407.\n", + id, src_name, challenge->msg_info.msg->line.status.code); } auth_object_count = AST_VECTOR_SIZE(auth_objects_vector); if (auth_object_count == 0) { /* This shouldn't happen but we'll check anyway. */ - return PJ_EINVAL; + SCOPE_EXIT_RTN_VALUE(PJ_EINVAL, "%s:%s No auth objects available\n", id, src_name); } /* @@ -176,183 +390,29 @@ static pj_status_t set_outbound_authentication_credentials(pjsip_auth_clt_sess * * actual structures, not pointers to structures. */ if (AST_VECTOR_INIT(&auth_creds, 5) != 0) { - return PJ_ENOMEM; + SCOPE_EXIT_RTN_VALUE(PJ_ENOMEM); } /* - * It's going to be rare that we actually have more than one - * WWW-Authentication header or more than one auth object to - * match to it so the following nested loop should be fine. + * There may be multiple WWW/Proxy-Authenticate headers each one having + * a different realm/algorithm pair. Test each to see if we have credentials + * for it and accumulate them in the auth_creds vector. + * The code doesn't really care but just for reference, RFC-7616 says + * a UAS can't send multiple headers for the same realm with the same + * algorithm. It also says the UAS should send the headers in order + * of preference with the first one being the most preferred. */ while ((auth_hdr = pjsip_msg_find_hdr(challenge->msg_info.msg, search_type, auth_hdr ? auth_hdr->next : NULL))) { - int exact_match_index = -1; - int wildcard_match_index = -1; - int match_index = 0; - pjsip_cred_info auth_cred; - struct ast_sip_auth *auth = NULL; - memset(&auth_cred, 0, sizeof(auth_cred)); - /* - * Since we only support the MD5 algorithm at the current time, - * there's no sense searching for auth objects that match the algorithm. - * In fact, the auth_object structure doesn't even have a member - * for it. - * - * When we do support more algorithms, this check will need to be - * moved inside the auth object loop below. - * - * Note: The header may not have specified an algorithm at all in which - * case it's assumed to be MD5. is_digest_algorithm_supported() returns - * true for that case. - */ - if (!is_digest_algorithm_supported(auth_hdr)) { - ast_debug(3, "Skipping header with realm '%.*s' and unsupported '%.*s' algorithm \n", - (int)auth_hdr->challenge.digest.realm.slen, auth_hdr->challenge.digest.realm.ptr, - (int)auth_hdr->challenge.digest.algorithm.slen, auth_hdr->challenge.digest.algorithm.ptr); - continue; - } + get_creds_for_header(id, src_name, auth_hdr, auth_object_count, + auth_objects_vector, &auth_creds, realms); - /* - * Appending the realms is strictly so digest_create_request_with_auth() - * can display good error messages. Since we only support one algorithm, - * there can't be more than one header with the same realm. No need to worry - * about duplicate realms until then. - */ - if (*realms) { - ast_str_append(realms, 0, "%.*s, ", - (int)auth_hdr->challenge.digest.realm.slen, auth_hdr->challenge.digest.realm.ptr); - } - - ast_debug(3, "Searching auths to find matching ones for header with realm '%.*s' and algorithm '%.*s'\n", - (int)auth_hdr->challenge.digest.realm.slen, auth_hdr->challenge.digest.realm.ptr, - (int)auth_hdr->challenge.digest.algorithm.slen, auth_hdr->challenge.digest.algorithm.ptr); - - /* - * Now that we have a valid header, we can loop over the auths available to - * find either an exact realm match or, failing that, a wildcard auth (an - * auth with an empty or "*" realm). - * - * NOTE: We never use the global default realm when we're the UAC responding - * to a 401 or 407. We only use that when we're the UAS (handled elsewhere) - * and the auth object didn't have a realm. - */ - for (i = 0; i < auth_object_count; ++i) { - auth = AST_VECTOR_GET(auth_objects_vector, i); - - /* - * If this auth object's realm exactly matches the one - * from the header, we can just break out and use it. - * - * NOTE: If there's more than one auth object for an endpoint with - * a matching realm it's a misconfiguration. We'll only use the first. - */ - if (pj_stricmp2(&auth_hdr->challenge.digest.realm, auth->realm) == 0) { - ast_debug(3, "Found matching auth '%s' with realm '%s'\n", ast_sorcery_object_get_id(auth), - auth->realm); - exact_match_index = i; - /* - * If we found an exact realm match, there's no need to keep - * looking for a wildcard. - */ - break; - } - - /* - * If this auth object's realm is empty or a "*", it's a wildcard - * auth object. We going to save its index but keep iterating over - * the vector in case we find an exact match later. - * - * NOTE: If there's more than one wildcard auth object for an endpoint - * it's a misconfiguration. We'll only use the first. - */ - if (wildcard_match_index < 0 - && (ast_strlen_zero(auth->realm) || ast_strings_equal(auth->realm, "*"))) { - ast_debug(3, "Found wildcard auth '%s' for realm '%.*s'\n", ast_sorcery_object_get_id(auth), - (int)auth_hdr->challenge.digest.realm.slen, auth_hdr->challenge.digest.realm.ptr); - wildcard_match_index = i; - } - } - - if (exact_match_index < 0 && wildcard_match_index < 0) { - /* - * Didn't find either a wildcard or an exact realm match. - * Move on to the next header. - */ - ast_debug(3, "No auth matching realm or no wildcard found for realm '%.*s'\n", - (int)auth_hdr->challenge.digest.realm.slen, auth_hdr->challenge.digest.realm.ptr); - continue; - } - - if (exact_match_index >= 0) { - /* - * If we found an exact match, we'll always prefer that. - */ - match_index = exact_match_index; - auth = AST_VECTOR_GET(auth_objects_vector, match_index); - ast_debug(3, "Using matched auth '%s' with realm '%.*s'\n", ast_sorcery_object_get_id(auth), - (int)auth_hdr->challenge.digest.realm.slen, auth_hdr->challenge.digest.realm.ptr); - } else { - /* - * We'll only use the wildcard if we didn't find an exact match. - */ - match_index = wildcard_match_index; - auth = AST_VECTOR_GET(auth_objects_vector, match_index); - ast_debug(3, "Using wildcard auth '%s' for realm '%.*s'\n", ast_sorcery_object_get_id(auth), - (int)auth_hdr->challenge.digest.realm.slen, auth_hdr->challenge.digest.realm.ptr); - } - - /* - * Copy the fields from the auth_object to the - * pjsip_cred_info structure. - */ - auth_cred.realm = auth_hdr->challenge.common.realm; - pj_cstr(&auth_cred.username, auth->auth_user); - pj_cstr(&auth_cred.scheme, "digest"); - switch (auth->type) { - case AST_SIP_AUTH_TYPE_USER_PASS: - pj_cstr(&auth_cred.data, auth->auth_pass); - auth_cred.data_type = PJSIP_CRED_DATA_PLAIN_PASSWD; - break; - case AST_SIP_AUTH_TYPE_MD5: - pj_cstr(&auth_cred.data, auth->md5_creds); - auth_cred.data_type = PJSIP_CRED_DATA_DIGEST; - break; - case AST_SIP_AUTH_TYPE_GOOGLE_OAUTH: - /* nothing to do. handled seperately in res_pjsip_outbound_registration */ - break; - case AST_SIP_AUTH_TYPE_ARTIFICIAL: - ast_log(LOG_ERROR, - "Trying to set artificial outbound auth credentials shouldn't happen.\n"); - continue; - } /* End auth object loop */ - - /* - * Because the vector contains actual structures and not pointers - * to structures, the call to AST_VECTOR_APPEND results in a simple - * assign of one structure to another, effectively copying the auth_cred - * structure contents to the array element. - * - * Also note that the calls to pj_cstr above set their respective - * auth_cred fields to the _pointers_ of their corresponding auth - * object fields. This is safe because the call to - * pjsip_auth_clt_set_credentials() below strdups them before we - * return to the calling function which decrements the reference - * counts. - */ - res = AST_VECTOR_APPEND(&auth_creds, auth_cred); - if (res != PJ_SUCCESS) { - res = PJ_ENOMEM; - goto cleanup; - } } /* End header loop */ if (*realms && ast_str_strlen(*realms)) { /* - * Again, this is strictly so digest_create_request_with_auth() - * can display good error messages. - * - * Chop off the trailing ", " on the last realm. + * Chop off the trailing ", " on the last realm-algorithm. */ ast_str_truncate(*realms, ast_str_strlen(*realms) - 2); } @@ -383,15 +443,15 @@ static pj_status_t set_outbound_authentication_credentials(pjsip_auth_clt_sess * res = pjsip_auth_clt_set_credentials(auth_sess, cred_count, creds_array); ast_free(creds_array); - if (res == PJ_SUCCESS) { - ast_debug(3, "Set %zu credentials in auth session\n", cred_count); - } else { - ast_log(LOG_ERROR, "Failed to set %zu credentials in auth session\n", cred_count); - } cleanup: AST_VECTOR_FREE(&auth_creds); - return res; + if (res != PJ_SUCCESS) { + pj_err = ast_alloca(PJ_ERR_MSG_SIZE); + pj_strerror(res, pj_err, PJ_ERR_MSG_SIZE); + } + SCOPE_EXIT_RTN_VALUE(res, "%s:%s: Set %zu credentials in auth session: %s\n", + id, src_name, cred_count, S_OR(pj_err, "success")); } /*! @@ -415,12 +475,23 @@ static int digest_create_request_with_auth(const struct ast_sip_auth_vector *aut pj_status_t status; struct ast_sip_auth_objects_vector auth_objects_vector; size_t auth_object_count = 0; - struct ast_sip_endpoint *endpoint; - char *id = NULL; - const char *id_type; + pjsip_dialog *dlg = pjsip_rdata_get_dlg(challenge); + struct ast_sip_endpoint *endpoint = (dlg ? ast_sip_dialog_get_endpoint(dlg) : NULL); + /* + * We're ast_strdupa'ing the endpoint id because we're going to + * clean up the endpoint immediately after this. We only needed + * it to get the id for logging. + */ + char *endpoint_id = endpoint ? ast_strdupa(ast_sorcery_object_get_id(endpoint)) : NULL; + char *id = endpoint_id ?: "noendpoint"; + char *src_name = challenge->pkt_info.src_name; struct ast_str *realms = NULL; - pjsip_dialog *dlg; int res = -1; + char *pj_err = NULL; + SCOPE_ENTER(3, "%s:%s\n", id, src_name); + + /* We only needed endpoint to get the id */ + ao2_cleanup(endpoint); /* * Some older compilers have an issue with initializing structures with @@ -429,31 +500,18 @@ static int digest_create_request_with_auth(const struct ast_sip_auth_vector *aut */ memset(&auth_sess, 0, sizeof(auth_sess)); - dlg = pjsip_rdata_get_dlg(challenge); - if (dlg) { - /* The only thing we use endpoint for is to get an id for error/debug messages */ - endpoint = ast_sip_dialog_get_endpoint(dlg); - id = endpoint ? ast_strdupa(ast_sorcery_object_get_id(endpoint)) : NULL; - ao2_cleanup(endpoint); - id_type = "Endpoint"; - } - - /* If there was no dialog, then this is probably a REGISTER so no endpoint */ - if (!id) { - /* The only thing we use the address for is to get an id for error/debug messages */ - id = ast_alloca(AST_SOCKADDR_BUFLEN); - pj_sockaddr_print(&challenge->pkt_info.src_addr, id, AST_SOCKADDR_BUFLEN, 3); - id_type = "Host"; - } - if (!auth_ids_vector || AST_VECTOR_SIZE(auth_ids_vector) == 0) { - ast_log(LOG_ERROR, "%s: '%s': There were no auth ids available\n", id_type, id); + SCOPE_EXIT_LOG_RTN_VALUE(-1, LOG_ERROR, "%s:%s: There were no auth ids available\n", + id, src_name); return -1; } + /* + * auth_ids_vector contains only ids but we need the complete objects. + */ if (AST_VECTOR_INIT(&auth_objects_vector, AST_VECTOR_SIZE(auth_ids_vector)) != 0) { - ast_log(LOG_ERROR, "%s: '%s': Couldn't initialize auth object vector\n", id_type, id); - return -1; + SCOPE_EXIT_LOG_RTN_VALUE(-1, LOG_ERROR, "%s:%s: Couldn't initialize auth object vector\n", + id, src_name); } /* @@ -465,6 +523,8 @@ static int digest_create_request_with_auth(const struct ast_sip_auth_vector *aut * AST_VECTOR_FREE(&auth_objects_vector); * when you're done with the vector */ + ast_trace(-1, "%s:%s: Retrieving %d auth objects\n", id, src_name, + (int)AST_VECTOR_SIZE(auth_ids_vector)); ast_sip_retrieve_auths_vector(auth_ids_vector, &auth_objects_vector); auth_object_count = AST_VECTOR_SIZE(&auth_objects_vector); if (auth_object_count == 0) { @@ -475,13 +535,19 @@ static int digest_create_request_with_auth(const struct ast_sip_auth_vector *aut * id that wasn't found. */ res = -1; + ast_trace(-1, "%s:%s: No auth objects found\n", id, src_name); goto cleanup; } - - if (pjsip_auth_clt_init(&auth_sess, ast_sip_get_pjsip_endpoint(), - old_request->pool, 0) != PJ_SUCCESS) { - ast_log(LOG_ERROR, "%s: '%s': Failed to initialize client authentication session\n", - id_type, id); + ast_trace(-1, "%s:%s: Retrieved %d auth objects\n", id, src_name, + (int)auth_object_count); + + status = pjsip_auth_clt_init(&auth_sess, ast_sip_get_pjsip_endpoint(), + old_request->pool, 0); + if (status != PJ_SUCCESS) { + pj_err = ast_alloca(PJ_ERR_MSG_SIZE); + pj_strerror(status, pj_err, PJ_ERR_MSG_SIZE); + ast_log(LOG_ERROR, "%s:%s: Failed to initialize client authentication session: %s\n", + id, src_name, pj_err); res = -1; goto cleanup; } @@ -499,18 +565,24 @@ static int digest_create_request_with_auth(const struct ast_sip_auth_vector *aut * Load pjproject with the valid credentials for the Authentication headers * received on the 401 or 407 response. */ - status = set_outbound_authentication_credentials(&auth_sess, &auth_objects_vector, challenge, &realms); + status = SCOPE_CALL_WITH_RESULT(-1, pj_status_t, set_auth_creds, id, &auth_sess, &auth_objects_vector, challenge, &realms); + if (status != PJ_SUCCESS && status != PJSIP_ENOCREDENTIAL) { + pj_err = ast_alloca(PJ_ERR_MSG_SIZE); + } + switch (status) { case PJ_SUCCESS: break; case PJSIP_ENOCREDENTIAL: ast_log(LOG_WARNING, - "%s: '%s': No auth objects matching realm(s) '%s' from challenge found.\n", id_type, id, - realms ? ast_str_buffer(realms) : ""); + "%s:%s: No auth objects matching realm/algorithm(s) '%s' from challenge found.\n", + id, src_name, realms ? ast_str_buffer(realms) : ""); 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) : ""); + "%s:%s: No auth objects matching realm(s) '%s' from challenge found.\n", + id, src_name, realms ? ast_str_buffer(realms) : ""); 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 @@ static int digest_create_request_with_auth(const struct ast_sip_auth_vector *aut 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 = { diff --git a/third-party/pjproject/configure.m4 b/third-party/pjproject/configure.m4 index eb768731c05..94622f2b5e5 100644 --- a/third-party/pjproject/configure.m4 +++ b/third-party/pjproject/configure.m4 @@ -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]) diff --git a/third-party/pjproject/patches/0001-Add-full-support-for-SHA-256-and-SHA-512-256-digest-.patch b/third-party/pjproject/patches/0001-Add-full-support-for-SHA-256-and-SHA-512-256-digest-.patch new file mode 100644 index 00000000000..9b489b9d684 --- /dev/null +++ b/third-party/pjproject/patches/0001-Add-full-support-for-SHA-256-and-SHA-512-256-digest-.patch @@ -0,0 +1,2443 @@ +From 3200187a4dcc7336e2b99eb3109eac8b9d81c0a7 Mon Sep 17 00:00:00 2001 +From: George Joseph +Date: Wed, 23 Oct 2024 11:32:05 -0600 +Subject: [PATCH] Add full support for SHA-256 and SHA-512-256 digest + algorithms + +There are no breaking changes for this work however several structures +were extended with new fields. See below. + +In order to use the new algorithms, you MUST set the new +pjsip_cred_info.ext.algorithm_type field to the appropriate value +when the credential data type is PJSIP_CRED_DATA_DIGEST and when +acting as a server, you must also use pjsip_auth_srv_challenge2() +to send challenges so you can specify algorithms other than MD5. + +Summary of changes: + +* Added enum pjsip_auth_algorithm_type which list all digest algorithms +supported. + +* Added struct pjsip_auth_algorithm which defines parameters for each +algorithm including its IANA name, OpenSSL name, digest length and +digest string representation length. + +* Added pjsip_auth_algorithm_type to the pjsip_cred_info structure +so the digest algorithm can be specified when the cred data type +is PJSIP_CRED_DATA_DIGEST. + +* Added pjsip_auth_algorithm_type to the pjsip_cached_auth_hdr +structure so we can match on specific algorithm. + +* Added functions pjsip_auth_get_algorithm_by_type(), +pjsip_auth_get_algorithm_by_iana_name(), and +pjsip_auth_is_digest_algorithm_supported() to find and search +for supported algorithms. + +* Added pjsip_authorization_hdr to the pjsip_auth_lookup_cred_param +structure so we can look up credentiials by specific algorithm. + +* Added the pjsip_auth_srv_challenge2() function that takes +a pjsip_auth_algorithm_type so users can create challenges with +specific algorithms instead of defaulting to MD5. + +* pjsip_auth_create_digest() was heavily refactored to use the +new algorithm_type contained in pjsip_cred_info to determine the +algorithm to use when creating the digest. The function is now +generic and can use any supported algorithm. If OpenSSL isn't +available, it will fall back to the internal MD5 implementation. + +* pjsip_auth_create_digestSHA256() is now marked as deprecated and +simply calls the new function with PJSIP_AUTH_ALGORITHM_SHA256. + +* sip_auth_client.c and sip_auth_server.c were refactored to support +multiple digest algorithms. + +* sip_auth_client was updated to allow the AKEv2-MD5 algorithm +to pass through to the callback specified in pjsip_cred_info. + +* A bug was fixed with the PJSIP_AUTH_ALLOW_MULTIPLE_AUTH_HEADER +option where the default setting of 0 prevented sip_auth_client +from responding to WWW/Proxy-Authenticate headers from different +realms. The RFCs state that this behavior should be allowed. +The comment for this option in sip_config.h was also updated to +indicate that setting this option to 1 is probably not a good idea +for security reasons. + +Resolves: #4119 +--- + pjlib/src/pj/ssl_sock_ossl.c | 1 + + pjsip/include/pjsip/sip_auth.h | 224 ++++++++- + pjsip/include/pjsip/sip_auth_aka.h | 19 +- + pjsip/include/pjsip/sip_config.h | 23 +- + pjsip/src/pjsip/sip_auth_aka.c | 10 +- + pjsip/src/pjsip/sip_auth_client.c | 734 +++++++++++++++++------------ + pjsip/src/pjsip/sip_auth_server.c | 91 +++- + 7 files changed, 761 insertions(+), 341 deletions(-) + +diff --git a/pjlib/src/pj/ssl_sock_ossl.c b/pjlib/src/pj/ssl_sock_ossl.c +index 3716f4f61..67f386d00 100644 +--- a/pjlib/src/pj/ssl_sock_ossl.c ++++ b/pjlib/src/pj/ssl_sock_ossl.c +@@ -706,20 +706,21 @@ static pj_status_t init_openssl(void) + /* Init OpenSSL lib */ + #if USING_LIBRESSL || OPENSSL_VERSION_NUMBER < 0x10100000L + SSL_library_init(); + SSL_load_error_strings(); + #else + OPENSSL_init_ssl(0, NULL); + #endif + #if OPENSSL_VERSION_NUMBER < 0x009080ffL + /* This is now synonym of SSL_library_init() */ + OpenSSL_add_all_algorithms(); ++ OpenSSL_add_all_digests(); + #endif + + /* Init available ciphers */ + if (ssl_cipher_num == 0 || ssl_curves_num == 0) { + SSL_METHOD *meth = NULL; + SSL_CTX *ctx; + SSL *ssl; + STACK_OF(SSL_CIPHER) *sk_cipher; + SSL_SESSION *ssl_sess; + unsigned i, n; +diff --git a/pjsip/include/pjsip/sip_auth.h b/pjsip/include/pjsip/sip_auth.h +index fa55830fd..95c8d136c 100644 +--- a/pjsip/include/pjsip/sip_auth.h ++++ b/pjsip/include/pjsip/sip_auth.h +@@ -35,37 +35,98 @@ PJ_BEGIN_DECL + * @brief Client and server side authentication framework. + */ + + /** + * @defgroup PJSIP_AUTH_API Authentication API's + * @ingroup PJSIP_AUTH + * @brief Structures and functions to perform authentication. + * @{ + */ + +-/** Length of digest MD5 string. */ ++/** ++ * Length of digest MD5 string. ++ * \deprecated Use #pjsip_auth_algorithm::digest_str_length instead. ++ */ + #define PJSIP_MD5STRLEN 32 + +-/** Length of digest SHA256 string. */ ++/** ++ * Length of digest SHA256 string. ++ * \deprecated Use #pjsip_auth_algorithm::digest_str_length instead. ++ */ + #define PJSIP_SHA256STRLEN 64 + ++/** ++ * The length of the buffer needed to contain the largest ++ * supported algorithm's digest. ++ */ ++#define PJSIP_AUTH_MAX_DIGEST_BUFFER_LENGTH 64 ++ ++/** ++ * Digest Algorithm Types. ++ * \warning These entries must remain in order with ++ * no gaps and with _NOT_SET = 0 and _COUNT as the last entry. ++ * ++ * The MD5, SHA-256, and SHA-512/256 algorithms are described ++ * in RFC 7616 and RFC 8760. ++ * The AKA algorithms are described in RFC 3310 and RFC 4169 ++ * and 3GPP TS 33.203. ++ */ ++typedef enum pjsip_auth_algorithm_type ++{ ++ PJSIP_AUTH_ALGORITHM_NOT_SET = 0, /**< Algorithm not set. */ ++ PJSIP_AUTH_ALGORITHM_MD5, /**< MD5 algorithm. */ ++ PJSIP_AUTH_ALGORITHM_SHA256, /**< SHA-256 algorithm. */ ++ PJSIP_AUTH_ALGORITHM_SHA512_256, /**< SHA-512/256 algorithm */ ++ PJSIP_AUTH_ALGORITHM_AKAV1_MD5, /**< AKA v1 with MD5 algorithm. */ ++ PJSIP_AUTH_ALGORITHM_AKAV2_MD5, /**< AKA v2 with MD5 algorithm. */ ++ PJSIP_AUTH_ALGORITHM_COUNT, /**< Number of algorithms. */ ++} pjsip_auth_algorithm_type; ++ ++ ++/** ++ * Authentication Digest Algorithm ++ * ++ * This structure describes a digest algorithm used in ++ * SIP authentication. ++ * ++ */ ++typedef struct pjsip_auth_algorithm ++{ ++ pjsip_auth_algorithm_type algorithm_type; /**< Digest algorithm type */ ++ pj_str_t iana_name; /**< IANA/RFC name used in ++ SIP headers */ ++ const char *openssl_name; /**< The name used by OpenSSL's ++ EVP_get_digestbyname() */ ++ unsigned digest_length; /**< Length of the raw digest ++ in bytes */ ++ unsigned digest_str_length; /**< Length of the digest HEX ++ representation */ ++} pjsip_auth_algorithm; ++ + + /** Type of data in the credential information in #pjsip_cred_info. */ + typedef enum pjsip_cred_data_type + { + PJSIP_CRED_DATA_PLAIN_PASSWD=0, /**< Plain text password. */ + PJSIP_CRED_DATA_DIGEST =1, /**< Hashed digest. */ + + PJSIP_CRED_DATA_EXT_AKA =16 /**< Extended AKA info is available */ + + } pjsip_cred_data_type; + ++#define PJSIP_CRED_DATA_PASSWD_MASK 0x000F ++#define PJSIP_CRED_DATA_EXT_MASK 0x00F0 ++ ++#define PJSIP_CRED_DATA_IS_AKA(cred) (((cred)->data_type & PJSIP_CRED_DATA_EXT_MASK) == PJSIP_CRED_DATA_EXT_AKA) ++#define PJSIP_CRED_DATA_IS_PASSWD(cred) (((cred)->data_type & PJSIP_CRED_DATA_PASSWD_MASK) == PJSIP_CRED_DATA_PLAIN_PASSWD) ++#define PJSIP_CRED_DATA_IS_DIGEST(cred) (((cred)->data_type & PJSIP_CRED_DATA_PASSWD_MASK) == PJSIP_CRED_DATA_DIGEST) ++ + /** Authentication's quality of protection (qop) type. */ + typedef enum pjsip_auth_qop_type + { + PJSIP_AUTH_QOP_NONE, /**< No quality of protection. */ + PJSIP_AUTH_QOP_AUTH, /**< Authentication. */ + PJSIP_AUTH_QOP_AUTH_INT, /**< Authentication with integrity protection. */ + PJSIP_AUTH_QOP_UNKNOWN /**< Unknown protection. */ + } pjsip_auth_qop_type; + + +@@ -95,36 +156,45 @@ typedef enum pjsip_auth_qop_type + typedef pj_status_t (*pjsip_cred_cb)(pj_pool_t *pool, + const pjsip_digest_challenge *chal, + const pjsip_cred_info *cred, + const pj_str_t *method, + pjsip_digest_credential *auth); + + + /** + * This structure describes credential information. + * A credential information is a static, persistent information that identifies +- * username and password required to authorize to a specific realm. ++ * credentials required to authorize to a specific realm. + * + * Note that since PJSIP 0.7.0.1, it is possible to make a credential that is + * valid for any realms, by setting the realm to star/wildcard character, + * i.e. realm = pj_str("*");. ++ * ++ * You should always fill this structure with zeros using PJ_POOL_ZALLOC_T() ++ * or pj_bzero() before setting any fields. + */ + struct pjsip_cred_info + { + pj_str_t realm; /**< Realm. Use "*" to make a credential that + can be used to authenticate against any + challenges. */ + pj_str_t scheme; /**< Scheme (e.g. "digest"). */ + pj_str_t username; /**< User name. */ +- int data_type; /**< Type of data (0 for plaintext passwd). */ ++ int data_type; /**< Type of data \ref pjsip_cred_data_type */ + pj_str_t data; /**< The data, which can be a plaintext + password or a hashed digest. */ ++ /** ++ * If the data_type is #PJSIP_CRED_DATA_DIGEST and the digest algorithm ++ * used is not MD5 (the default), then this field MUST be set to the ++ * appropriate digest algorithm type. ++ */ ++ pjsip_auth_algorithm_type algorithm_type; /**< Digest algorithm type */ + + /** Extended data */ + union { + /** Digest AKA credential information. Note that when AKA credential + * is being used, the \a data field of this #pjsip_cred_info is + * not used, but it still must be initialized to an empty string. + * Please see \ref PJSIP_AUTH_AKA_API for more information. + */ + struct { + pj_str_t k; /**< Permanent subscriber key. */ +@@ -175,21 +245,22 @@ typedef struct pjsip_cached_auth + unsigned stale_cnt; /**< Number of stale retry. */ + #if PJSIP_AUTH_QOP_SUPPORT + pj_uint32_t nc; /**< Nonce count. */ + pj_str_t cnonce; /**< Cnonce value. */ + #endif + pjsip_www_authenticate_hdr *last_chal; /**< Last challenge seen. */ + #if PJSIP_AUTH_HEADER_CACHING + pjsip_cached_auth_hdr cached_hdr;/**< List of cached header for + each method. */ + #endif +- ++ pjsip_auth_algorithm_type challenge_algorithm_type; /**< Challenge ++ algorithm */ + } pjsip_cached_auth; + + + /** + * This structure describes client authentication session preference. + * The preference can be set by calling #pjsip_auth_clt_set_prefs(). + */ + typedef struct pjsip_auth_clt_pref + { + /** +@@ -201,20 +272,59 @@ typedef struct pjsip_auth_clt_pref + + /** + * Specify the algorithm to use when empty Authorization header + * is to be sent for each initial request (see above) + */ + pj_str_t algorithm; + + } pjsip_auth_clt_pref; + + ++/** ++ * Get a pjsip_auth_algorithm structure by type. ++ * ++ * @param algorithm_type The algorithm type ++ * ++ * @return A pointer to a pjsip_auth_algorithm structure ++ * or NULL if not found. ++ */ ++PJ_DECL(const pjsip_auth_algorithm *) pjsip_auth_get_algorithm_by_type( ++ pjsip_auth_algorithm_type algorithm_type); ++ ++ ++/** ++ * Get a pjsip_auth_algorithm by IANA name. ++ * ++ * @param iana_name The IANA name (MD5, SHA-256, SHA-512-256) ++ * ++ * @return A pointer to a pjsip_auth_algorithm structure ++ * or NULL if not found. ++ */ ++PJ_DECL(const pjsip_auth_algorithm *) pjsip_auth_get_algorithm_by_iana_name( ++ const pj_str_t *iana_name); ++ ++ ++/** ++ * Check if a digest algorithm is supported. ++ * Algorithms that require support from OpenSSL will be checked ++ * at runtime to determine if they are actually available in ++ * the current version of OpenSSL. ++ * ++ * @param algorithm_type The algorithm type ++ * ++ * @return PJ_TRUE if the algorithm is supported, ++ * PJ_FALSE otherwise. ++ */ ++PJ_DECL(pj_bool_t) pjsip_auth_is_algorithm_supported( ++ pjsip_auth_algorithm_type algorithm_type); ++ ++ + /** + * Duplicate a client authentication preference setting. + * + * @param pool The memory pool. + * @param dst Destination client authentication preference. + * @param src Source client authentication preference. + */ + PJ_DECL(void) pjsip_auth_clt_pref_dup(pj_pool_t *pool, + pjsip_auth_clt_pref *dst, + const pjsip_auth_clt_pref *src); +@@ -277,24 +387,26 @@ typedef pj_status_t pjsip_auth_lookup_cred( pj_pool_t *pool, + const pj_str_t *realm, + const pj_str_t *acc_name, + pjsip_cred_info *cred_info ); + + + /** + * This structure describes input param for credential lookup. + */ + typedef struct pjsip_auth_lookup_cred_param + { +- pj_str_t realm; /**< Realm to find the account. */ +- pj_str_t acc_name; /**< Account name to look for. */ +- pjsip_rx_data *rdata; /**< Incoming request to be authenticated. */ +- ++ pj_str_t realm; /**< Realm to find the account. */ ++ pj_str_t acc_name; /**< Account name to look for. */ ++ pjsip_rx_data *rdata; /**< Incoming request to be ++ authenticated. */ ++ pjsip_authorization_hdr *auth_hdr; /**< Authorization header to be ++ authenticated. */ + } pjsip_auth_lookup_cred_param; + + + /** + * Type of function to lookup credential for the specified name. + * + * @param pool Pool to initialize the credential info. + * @param param The input param for credential lookup. + * @param cred_info The structure to put the credential when it's found. + * +@@ -542,43 +654,78 @@ PJ_DECL(pj_status_t) pjsip_auth_srv_init2( + */ + PJ_DECL(pj_status_t) pjsip_auth_srv_verify( pjsip_auth_srv *auth_srv, + pjsip_rx_data *rdata, + int *status_code ); + + + /** + * Add authentication challenge headers to the outgoing response in tdata. + * Application may specify its customized nonce and opaque for the challenge, + * or can leave the value to NULL to make the function fills them in with +- * random characters. ++ * random characters. The digest algorithm defaults to MD5. If you need ++ * to specify a different algorithm, use #pjsip_auth_srv_challenge2. + * + * @param auth_srv The server authentication structure. + * @param qop Optional qop value. + * @param nonce Optional nonce value. + * @param opaque Optional opaque value. + * @param stale Stale indication. + * @param tdata The outgoing response message. The response must have + * 401 or 407 response code. + * + * @return PJ_SUCCESS on success. + */ + PJ_DECL(pj_status_t) pjsip_auth_srv_challenge( pjsip_auth_srv *auth_srv, + const pj_str_t *qop, + const pj_str_t *nonce, + const pj_str_t *opaque, + pj_bool_t stale, + pjsip_tx_data *tdata); + + /** +- * Helper function to create MD5 digest out of the specified ++ * Add authentication challenge headers to the outgoing response in tdata. ++ * Application may specify its customized nonce and opaque for the challenge, ++ * or can leave the value to NULL to make the function fills them in with ++ * random characters. ++ * Application must specify the algorithm to use. ++ * ++ * @param auth_srv The server authentication structure. ++ * @param qop Optional qop value. ++ * @param nonce Optional nonce value. ++ * @param opaque Optional opaque value. ++ * @param stale Stale indication. ++ * @param tdata The outgoing response message. The response must have ++ * 401 or 407 response code. ++ * @param algorithm_type One of the #pjsip_auth_algorithm_type values. ++ * ++ * @return PJ_SUCCESS on success. ++ */ ++PJ_DECL(pj_status_t) pjsip_auth_srv_challenge2(pjsip_auth_srv *auth_srv, ++ const pj_str_t *qop, ++ const pj_str_t *nonce, ++ const pj_str_t *opaque, ++ pj_bool_t stale, ++ pjsip_tx_data *tdata, ++ const pjsip_auth_algorithm_type algorithm_type); ++ ++/** ++ * Helper function to create a digest out of the specified + * parameters. + * ++ * \warning Because of ambiguities in the API, this function ++ * should only be used for backward compatibility with the ++ * MD5 digest algorithm. New code should use ++ * #pjsip_auth_create_digest2 ++ * ++ * pjsip_cred_info::data_type must be #PJSIP_CRED_DATA_PLAIN_PASSWD ++ * or #PJSIP_CRED_DATA_DIGEST. ++ * + * @param result String to store the response digest. This string + * must have been preallocated by caller with the + * buffer at least PJSIP_MD5STRLEN (32 bytes) in size. + * @param nonce Optional nonce. + * @param nc Nonce count. + * @param cnonce Optional cnonce. + * @param qop Optional qop. + * @param uri URI. + * @param realm Realm. + * @param cred_info Credential info. +@@ -592,45 +739,98 @@ PJ_DECL(pj_status_t) pjsip_auth_create_digest(pj_str_t *result, + const pj_str_t *cnonce, + const pj_str_t *qop, + const pj_str_t *uri, + const pj_str_t *realm, + const pjsip_cred_info *cred_info, + const pj_str_t *method); + + /** + * Helper function to create SHA-256 digest out of the specified + * parameters. ++ * \deprecated Use #pjsip_auth_create_digest2 with ++ * algorithm_type = #PJSIP_AUTH_ALGORITHM_SHA256. ++ * + * + * @param result String to store the response digest. This string + * must have been preallocated by caller with the + * buffer at least PJSIP_SHA256STRLEN (64 bytes) in size. + * @param nonce Optional nonce. + * @param nc Nonce count. + * @param cnonce Optional cnonce. + * @param qop Optional qop. + * @param uri URI. + * @param realm Realm. + * @param cred_info Credential info. + * @param method SIP method. + * + * @return PJ_SUCCESS on success. + */ +-PJ_DEF(pj_status_t) pjsip_auth_create_digestSHA256(pj_str_t* result, ++PJ_DECL(pj_status_t) pjsip_auth_create_digestSHA256(pj_str_t* result, + const pj_str_t* nonce, + const pj_str_t* nc, + const pj_str_t* cnonce, + const pj_str_t* qop, + const pj_str_t* uri, + const pj_str_t* realm, + const pjsip_cred_info* cred_info, + const pj_str_t* method); + ++/** ++ * Helper function to create a digest out of the specified ++ * parameters. ++ * ++ * pjsip_cred_info::data_type must be #PJSIP_CRED_DATA_PLAIN_PASSWD ++ * or #PJSIP_CRED_DATA_DIGEST. ++ * ++ * If pjsip_cred_info::data_type is #PJSIP_CRED_DATA_PLAIN_PASSWORD, ++ * pjsip_cred_info::username + ":" + realm + ":" + pjsip_cred_info::data ++ * will be hashed using the algorithm_type specified by the last ++ * parameter passed to this function to create the "ha1" hash. ++ * ++ * If pjsip_cred_info::data_type is #PJSIP_CRED_DATA_DIGEST, ++ * pjsip_cred_info::data must contain the value of ++ * username + ":" + realm + ":" + password ++ * pre-hashed with the algorithm specifed by pjsip_cred_info::algorithm_type ++ * and will be used as the "ha1" hash directly. In this case ++ * pjsip_cred_info::algorithm_type MUST match the algorithm_type ++ * passed as the last parameter to this function. ++ * ++ * \note If left unset (0), pjsip_cred_info::algorithm_type will ++ * default to #PJSIP_AUTH_ALGORITHM_MD5. ++ * ++ * @param result String to store the response digest. This string ++ * must have been preallocated by the caller with the ++ * buffer at least as large as the digest_str_length ++ * member of the appropriate pjsip_auth_algorithm. ++ * @param nonce Optional nonce. ++ * @param nc Nonce count. ++ * @param cnonce Optional cnonce. ++ * @param qop Optional qop. ++ * @param uri URI. ++ * @param realm Realm. ++ * @param cred_info Credential info. ++ * @param method SIP method. ++ * @param algorithm_type The hash algorithm to use. ++ * ++ * @return PJ_SUCCESS on success. ++ */ ++PJ_DECL(pj_status_t) pjsip_auth_create_digest2(pj_str_t *result, ++ const pj_str_t *nonce, ++ const pj_str_t *nc, ++ const pj_str_t *cnonce, ++ const pj_str_t *qop, ++ const pj_str_t *uri, ++ const pj_str_t *realm, ++ const pjsip_cred_info *cred_info, ++ const pj_str_t *method, ++ const pjsip_auth_algorithm_type algorithm_type); ++ + /** + * @} + */ + + + + PJ_END_DECL + + + #endif /* __PJSIP_AUTH_SIP_AUTH_H__ */ +diff --git a/pjsip/include/pjsip/sip_auth_aka.h b/pjsip/include/pjsip/sip_auth_aka.h +index 41b77e776..ba3f9ca4e 100644 +--- a/pjsip/include/pjsip/sip_auth_aka.h ++++ b/pjsip/include/pjsip/sip_auth_aka.h +@@ -49,57 +49,50 @@ PJ_BEGIN_DECL + #define PJSIP_HAS_DIGEST_AKA_AUTH 1 + @endcode + + * + * In addition, application would need to link with libmilenage + * library from \a third_party directory. + * + * Application then specifies digest AKA credential by initializing the + * authentication credential as follows: + * +- @code ++ \verbatim + + pjsip_cred_info cred; + + pj_bzero(&cred, sizeof(cred)); + + cred.scheme = pj_str("Digest"); + cred.realm = pj_str("ims-domain.test"); + cred.username = pj_str("user@ims-domain.test"); +- cred.data_type = PJSIP_CRED_DATA_PLAIN_PASSWD | PJSIP_CRED_DATA_EXT_AKA; +- cred.data = pj_str("password"); ++ cred.data_type = PJSIP_CRED_DATA_EXT_AKA; + + // AKA extended info + cred.ext.aka.k = pj_str("password"); + cred.ext.aka.cb = &pjsip_auth_create_aka_response + +- @endcode ++ \endverbatim + * + * Description: +- * - To support AKA, application adds \a PJSIP_CRED_DATA_EXT_AKA flag in the ++ * - To support AKA, application adds #PJSIP_CRED_DATA_EXT_AKA flag in the + * \a data_type field. This indicates that extended information specific to + * AKA authentication is available in the credential, and that response +- * digest computation will use the callback function instead of the usual MD5 ++ * digest computation will use the callback function instead of the usual + * digest computation. + * + * - The \a scheme for the credential is "Digest". + * + * - The \a realm is the expected realm in the challenge. Application may + * also specify wildcard realm ("*") if it wishes to respond to any realms + * in the challenge. + * +- * - The \a data field is optional. Application may fill this with the password +- * if it wants to support both MD5 and AKA MD5 in a single credential. The +- * pjsip_auth_create_aka_response() function will use this field if the +- * challenge indicates "MD5" as the algorithm instead of "AKAv1-MD5" or +- * "AKAv2-MD5". +- * + * - The \a ext.aka.k field specifies the permanent subscriber key to be used + * for AKA authentication. Application may specify binary password containing + * NULL character in this key, since the length of the key is indicated in + * the \a slen field of the string. + * + * - The \a ext.aka.cb field specifies the callback function to calculate the + * response digest. Application can specify pjsip_auth_create_aka_response() + * in this field to use PJSIP's implementation, but it's free to provide + * it's own function. + * +@@ -157,21 +150,21 @@ PJ_BEGIN_DECL + * Length of response digest in bytes. + */ + #define PJSIP_AKA_RESLEN 8 + + /** + * Length of sequence number (SQN) in bytes. + */ + #define PJSIP_AKA_SQNLEN 6 + + /** +- * This function creates MD5, AKAv1-MD5, or AKAv2-MD5 response for ++ * This function creates an AKAv1-MD5, or AKAv2-MD5 response for + * the specified challenge in \a chal, according to the algorithm + * specified in the challenge, and based on the information in the + * credential \a cred. + * + * Application may register this function as \a ext.aka.cb field of + * #pjsip_cred_info structure to make PJSIP automatically call this + * function to calculate the response digest. To do so, it needs to + * add \a PJSIP_CRED_DATA_EXT_AKA flag in the \a data_type field of + * the credential, and fills up other AKA specific information in + * the credential. +diff --git a/pjsip/include/pjsip/sip_config.h b/pjsip/include/pjsip/sip_config.h +index 504e7561d..57a1ebdb6 100644 +--- a/pjsip/include/pjsip/sip_config.h ++++ b/pjsip/include/pjsip/sip_config.h +@@ -1338,24 +1338,39 @@ PJ_INLINE(pjsip_cfg_t*) pjsip_cfg(void) + * ("-"). Some SIP servers do not like this GUID format, so this option will + * strip any hyphens from the GUID string. + * + * Default is 1 (cnonce will not contain any hyphen characters). + */ + #ifndef PJSIP_AUTH_CNONCE_USE_DIGITS_ONLY + # define PJSIP_AUTH_CNONCE_USE_DIGITS_ONLY 1 + #endif + + /** +- * Allow client to send multiple Authorization header when receiving multiple +- * WWW-Authenticate header fields. If this is disabled, the stack will send +- * Authorization header field containing credentials that match the +- * topmost header field. ++ * RFC-7616 and RFC-8760 state that for each realm a UAS requires authentication ++ * for it can send a WWW/Proxy-Authenticate header for each digest algorithm it ++ * supports and for each realm, they must be added in most-preferred to least- ++ * preferred order. The RFCs also state that the UAS MUST NOT send multiple ++ * WWW/Proxy-Authenticate headers with the same realm and algorithm. ++ * ++ * The RFCs also state that the UAC SHOULD respond to the topmost header ++ * for each realm containing a digest algorithm it supports. Neither RFC ++ * however, states whether the UAC should send multiple Authorization headers ++ * for the same realm if it can support multiple digest algorithms. Common ++ * sense dicates though that the UAC should NOT send additional Authorization ++ * headers for the same realm once it's already sent a more preferred one. ++ * The reasoning is simple... If a UAS sends two WWW-Authenticate headers, ++ * the first for SHA-256 and the second for MD5, a UAC responding to both ++ * completely defeats the purpose of the UAS sending the more secure SHA-256. ++ * ++ * Having said that, if there is some corner case where continuing to send ++ * additional Authorization headers for the same realm is necessary, then ++ * this define can be set to 1 to allow it. + * + * Default is 0 + */ + #ifndef PJSIP_AUTH_ALLOW_MULTIPLE_AUTH_HEADER + # define PJSIP_AUTH_ALLOW_MULTIPLE_AUTH_HEADER 0 + #endif + + /***************************************************************************** + * SIP Event framework and presence settings. + */ +diff --git a/pjsip/src/pjsip/sip_auth_aka.c b/pjsip/src/pjsip/sip_auth_aka.c +index 1b15e9cbd..c3d5d8b2f 100644 +--- a/pjsip/src/pjsip/sip_auth_aka.c ++++ b/pjsip/src/pjsip/sip_auth_aka.c +@@ -140,23 +140,24 @@ PJ_DEF(pj_status_t) pjsip_auth_create_aka_response( + aka_cred.data_type = PJSIP_CRED_DATA_PLAIN_PASSWD; + + /* Create a response */ + if (aka_version == 1) { + /* + * For AKAv1, the password is RES + */ + aka_cred.data.ptr = (char*)res; + aka_cred.data.slen = PJSIP_AKA_RESLEN; + +- status = pjsip_auth_create_digest(&auth->response, &chal->nonce, ++ status = pjsip_auth_create_digest2(&auth->response, &chal->nonce, + &auth->nc, &auth->cnonce, &auth->qop, +- &auth->uri, &chal->realm, &aka_cred, method); ++ &auth->uri, &chal->realm, &aka_cred, method, ++ PJSIP_AUTH_ALGORITHM_MD5); + + } else if (aka_version == 2) { + + /* + * For AKAv2, password is base64 encoded [1] parameters: + * PRF(RES||IK||CK,"http-digest-akav2-password") + * + * The pseudo-random function (PRF) is HMAC-MD5 in this case. + */ + +@@ -179,23 +180,24 @@ PJ_DEF(pj_status_t) pjsip_auth_create_aka_response( + hmac_digest); + + aka_cred.data.slen = hmac64_len = + PJ_BASE256_TO_BASE64_LEN(PJ_ARRAY_SIZE(hmac_digest)); + pj_assert(aka_cred.data.slen+1 <= PJ_ARRAY_SIZE(tmp_buf)); + aka_cred.data.ptr = tmp_buf; + pj_base64_encode(hmac_digest, PJ_ARRAY_SIZE(hmac_digest), + aka_cred.data.ptr, &len); + aka_cred.data.slen = hmac64_len; + +- status = pjsip_auth_create_digest(&auth->response, &chal->nonce, ++ status = pjsip_auth_create_digest2(&auth->response, &chal->nonce, + &auth->nc, &auth->cnonce, &auth->qop, +- &auth->uri, &chal->realm, &aka_cred, method); ++ &auth->uri, &chal->realm, &aka_cred, method, ++ PJSIP_AUTH_ALGORITHM_MD5); + + } else { + pj_assert(!"Bug!"); + return PJ_EBUG; + } + + /* Done */ + return status; + } + +diff --git a/pjsip/src/pjsip/sip_auth_client.c b/pjsip/src/pjsip/sip_auth_client.c +index c2c008a52..b26166ac3 100644 +--- a/pjsip/src/pjsip/sip_auth_client.c ++++ b/pjsip/src/pjsip/sip_auth_client.c +@@ -26,60 +26,91 @@ + #include + #include + #include + #include + #include + #include + #include + #include + + +-#if PJ_HAS_SSL_SOCK && PJ_SSL_SOCK_IMP==PJ_SSL_SOCK_IMP_OPENSSL +-# if !defined(PJSIP_AUTH_HAS_DIGEST_SHA256) +-# define PJSIP_AUTH_HAS_DIGEST_SHA256 1 +-# endif +-#else +-# undef PJSIP_AUTH_HAS_DIGEST_SHA256 +-# define PJSIP_AUTH_HAS_DIGEST_SHA256 0 +-#endif +- +-#if PJSIP_AUTH_HAS_DIGEST_SHA256 ++#if defined(PJ_HAS_SSL_SOCK) && PJ_SSL_SOCK_IMP==PJ_SSL_SOCK_IMP_OPENSSL ++# include + # include +-# if OPENSSL_VERSION_NUMBER >= 0x30000000L +-# include ++# include ++# include ++# include ++ ++# if OPENSSL_VERSION_NUMBER < 0x10100000L ++# define EVP_MD_CTX_new() EVP_MD_CTX_create() ++# define EVP_MD_CTX_free(ctx) EVP_MD_CTX_destroy(ctx) + # endif ++ + # ifdef _MSC_VER + # include + # if OPENSSL_VERSION_NUMBER >= 0x10100000L + # pragma comment(lib, "libcrypto") + # else + # pragma comment(lib, "libeay32") + # pragma comment(lib, "ssleay32") + # endif + # endif +-#endif + ++# define DEFINE_HASH_CONTEXT EVP_MD_CTX* mdctx ++ ++#else ++#define HAVE_NO_OPENSSL 1 ++#define MD5_DIGEST_LENGTH (PJSIP_MD5STRLEN / 2) ++#define SHA256_DIGEST_LENGTH (PJSIP_SHA256STRLEN / 2) + /* A macro just to get rid of type mismatch between char and unsigned char */ + #define MD5_APPEND(pms,buf,len) pj_md5_update(pms, (const pj_uint8_t*)buf, \ + (unsigned)len) ++#define EVP_MD char ++#define EVP_MD_CTX pj_md5_context; ++#define DEFINE_HASH_CONTEXT pj_md5_context pmc; pj_md5_context* mdctx = &pmc ++ ++#define EVP_get_digestbyname(digest_name) (digest_name) ++#define EVP_MD_CTX_new(mdctx) &pmc ++#define EVP_DigestInit_ex(mdctx, md, _unused) (void)md; pj_md5_init(mdctx) ++#define EVP_DigestUpdate(mdctx, data, len) MD5_APPEND(mdctx, data, len) ++#define EVP_DigestFinal_ex(mdctx, digest, _unused) pj_md5_final(mdctx, digest) ++#define EVP_MD_CTX_free(mdctx) ++#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_AKAV2_MD5, {"AKAv2-MD5", 9}, "", ++ MD5_DIGEST_LENGTH, MD5_DIGEST_LENGTH * 2}, ++ { PJSIP_AUTH_ALGORITHM_COUNT, {"", 0}, "", ++ 0, 0}, ++}; ++ + + /* Logging. */ + #define THIS_FILE "sip_auth_client.c" + #if 0 + # define AUTH_TRACE_(expr) PJ_LOG(3, expr) + #else + # define AUTH_TRACE_(expr) + #endif + +-#define PASSWD_MASK 0x000F +-#define EXT_MASK 0x00F0 +- + + static void dup_bin(pj_pool_t *pool, pj_str_t *dst, const pj_str_t *src) + { + dst->slen = src->slen; + + if (dst->slen) { + dst->ptr = (char*) pj_pool_alloc(pool, src->slen); + pj_memcpy(dst->ptr, src->ptr, src->slen); + } else { + dst->ptr = NULL; +@@ -89,22 +120,23 @@ static void dup_bin(pj_pool_t *pool, pj_str_t *dst, const pj_str_t *src) + PJ_DEF(void) pjsip_cred_info_dup(pj_pool_t *pool, + pjsip_cred_info *dst, + const pjsip_cred_info *src) + { + pj_memcpy(dst, src, sizeof(pjsip_cred_info)); + + pj_strdup_with_null(pool, &dst->realm, &src->realm); + pj_strdup_with_null(pool, &dst->scheme, &src->scheme); + pj_strdup_with_null(pool, &dst->username, &src->username); + pj_strdup_with_null(pool, &dst->data, &src->data); ++ dst->algorithm_type = src->algorithm_type; + +- if ((dst->data_type & EXT_MASK) == PJSIP_CRED_DATA_EXT_AKA) { ++ if (PJSIP_CRED_DATA_IS_AKA(dst)) { + dup_bin(pool, &dst->ext.aka.k, &src->ext.aka.k); + dup_bin(pool, &dst->ext.aka.op, &src->ext.aka.op); + dup_bin(pool, &dst->ext.aka.amf, &src->ext.aka.amf); + } + } + + + PJ_DEF(int) pjsip_cred_info_cmp(const pjsip_cred_info *cred1, + const pjsip_cred_info *cred2) + { +@@ -113,22 +145,24 @@ PJ_DEF(int) pjsip_cred_info_cmp(const pjsip_cred_info *cred1, + result = pj_strcmp(&cred1->realm, &cred2->realm); + if (result) goto on_return; + result = pj_strcmp(&cred1->scheme, &cred2->scheme); + if (result) goto on_return; + result = pj_strcmp(&cred1->username, &cred2->username); + if (result) goto on_return; + result = pj_strcmp(&cred1->data, &cred2->data); + if (result) goto on_return; + result = (cred1->data_type != cred2->data_type); + if (result) goto on_return; ++ result = cred1->algorithm_type != cred2->algorithm_type; ++ if (result) goto on_return; + +- if ((cred1->data_type & EXT_MASK) == PJSIP_CRED_DATA_EXT_AKA) { ++ if (PJSIP_CRED_DATA_IS_AKA(cred1)) { + result = pj_strcmp(&cred1->ext.aka.k, &cred2->ext.aka.k); + if (result) goto on_return; + result = pj_strcmp(&cred1->ext.aka.op, &cred2->ext.aka.op); + if (result) goto on_return; + result = pj_strcmp(&cred1->ext.aka.amf, &cred2->ext.aka.amf); + if (result) goto on_return; + } + + on_return: + return result; +@@ -155,288 +189,303 @@ static void digestNtoStr(const unsigned char digest[], int n, char *output) + pj_val_to_hex_digit(digest[i], output); + output += 2; + } + } + + + /* + * Create response digest based on the parameters and store the + * digest ASCII in 'result'. + */ +-PJ_DEF(pj_status_t) pjsip_auth_create_digest( pj_str_t *result, +- const pj_str_t *nonce, +- const pj_str_t *nc, +- const pj_str_t *cnonce, +- const pj_str_t *qop, +- const pj_str_t *uri, +- const pj_str_t *realm, +- const pjsip_cred_info *cred_info, +- const pj_str_t *method) ++PJ_DEF(pj_status_t) pjsip_auth_create_digest2( pj_str_t *result, ++ const pj_str_t *nonce, ++ const pj_str_t *nc, ++ const pj_str_t *cnonce, ++ const pj_str_t *qop, ++ const pj_str_t *uri, ++ const pj_str_t *realm, ++ const pjsip_cred_info *cred_info, ++ const pj_str_t *method, ++ const pjsip_auth_algorithm_type algorithm_type) + { +- char ha1[PJSIP_MD5STRLEN]; +- char ha2[PJSIP_MD5STRLEN]; +- unsigned char digest[16]; +- pj_md5_context pms; ++ const pjsip_auth_algorithm *algorithm = NULL; ++ unsigned digest_len = 0; ++ unsigned digest_strlen = 0; ++ char ha1[PJSIP_AUTH_MAX_DIGEST_BUFFER_LENGTH * 2]; ++ char ha2[PJSIP_AUTH_MAX_DIGEST_BUFFER_LENGTH * 2]; ++ unsigned char digest[PJSIP_AUTH_MAX_DIGEST_BUFFER_LENGTH]; ++ unsigned dig_len = 0; ++ const EVP_MD* md; ++ DEFINE_HASH_CONTEXT; + +- pj_assert(result->slen >= PJSIP_MD5STRLEN); ++ PJ_ASSERT_RETURN(result && nonce && uri && realm && cred_info && method, PJ_EINVAL); ++ pj_bzero(result->ptr, result->slen); + +- AUTH_TRACE_((THIS_FILE, "Begin creating digest")); ++ algorithm = pjsip_auth_get_algorithm_by_type(algorithm_type == PJSIP_AUTH_ALGORITHM_NOT_SET ++ ? PJSIP_AUTH_ALGORITHM_MD5 ++ : algorithm_type); + +- if ((cred_info->data_type & PASSWD_MASK) == PJSIP_CRED_DATA_PLAIN_PASSWD) { +- /*** +- *** ha1 = MD5(username ":" realm ":" password) +- ***/ +- pj_md5_init(&pms); +- MD5_APPEND( &pms, cred_info->username.ptr, cred_info->username.slen); +- MD5_APPEND( &pms, ":", 1); +- MD5_APPEND( &pms, realm->ptr, realm->slen); +- MD5_APPEND( &pms, ":", 1); +- MD5_APPEND( &pms, cred_info->data.ptr, cred_info->data.slen); +- pj_md5_final(&pms, digest); +- +- digestNtoStr(digest, 16, ha1); +- +- } else if ((cred_info->data_type & PASSWD_MASK) == PJSIP_CRED_DATA_DIGEST) { +- if (cred_info->data.slen != 32) { +- pj_assert(!"Invalid cred_info data length"); +- pj_bzero(result->ptr, result->slen); +- result->slen = 0; +- return PJ_EINVAL; +- } +- pj_memcpy( ha1, cred_info->data.ptr, cred_info->data.slen ); +- } else { +- pj_assert(!"Invalid data_type"); +- pj_bzero(result->ptr, result->slen); +- result->slen = 0; +- return PJ_EINVAL; ++ if (!algorithm) { ++ PJ_LOG(4, (THIS_FILE, "The algorithm_type is invalid")); ++ return PJ_ENOTSUP; + } + +- AUTH_TRACE_((THIS_FILE, " ha1=%.32s", ha1)); +- +- /*** +- *** ha2 = MD5(method ":" req_uri) +- ***/ +- pj_md5_init(&pms); +- MD5_APPEND( &pms, method->ptr, method->slen); +- MD5_APPEND( &pms, ":", 1); +- MD5_APPEND( &pms, uri->ptr, uri->slen); +- pj_md5_final(&pms, digest); +- digestNtoStr(digest, 16, ha2); +- +- AUTH_TRACE_((THIS_FILE, " ha2=%.32s", ha2)); +- +- /*** +- *** When qop is not used: +- *** response = MD5(ha1 ":" nonce ":" ha2) +- *** +- *** When qop=auth is used: +- *** response = MD5(ha1 ":" nonce ":" nc ":" cnonce ":" qop ":" ha2) +- ***/ +- pj_md5_init(&pms); +- MD5_APPEND( &pms, ha1, PJSIP_MD5STRLEN); +- MD5_APPEND( &pms, ":", 1); +- MD5_APPEND( &pms, nonce->ptr, nonce->slen); +- if (qop && qop->slen != 0) { +- MD5_APPEND( &pms, ":", 1); +- MD5_APPEND( &pms, nc->ptr, nc->slen); +- MD5_APPEND( &pms, ":", 1); +- MD5_APPEND( &pms, cnonce->ptr, cnonce->slen); +- MD5_APPEND( &pms, ":", 1); +- MD5_APPEND( &pms, qop->ptr, qop->slen); ++ if (!pjsip_auth_is_algorithm_supported(algorithm->algorithm_type)) { ++ PJ_LOG(4, (THIS_FILE, ++ "The algorithm (%.*s) referenced by algorithm_type is not supported", ++ (int)algorithm->iana_name.slen, algorithm->iana_name.ptr)); ++ return PJ_ENOTSUP; + } +- MD5_APPEND( &pms, ":", 1); +- MD5_APPEND( &pms, ha2, PJSIP_MD5STRLEN); +- +- /* This is the final response digest. */ +- pj_md5_final(&pms, digest); +- +- /* Convert digest to string and store in chal->response. */ +- result->slen = PJSIP_MD5STRLEN; +- digestNtoStr(digest, 16, result->ptr); +- +- AUTH_TRACE_((THIS_FILE, " digest=%.32s", result->ptr)); +- AUTH_TRACE_((THIS_FILE, "Digest created")); +- return PJ_SUCCESS; +-} + ++ if (qop && !(nc && cnonce)) { ++ PJ_LOG(4, (THIS_FILE, "nc and cnonce are required if qop is specified")); ++ return PJ_EINVAL; ++ } + +-/* +- * Create response SHA-256 digest based on the parameters and store the +- * digest ASCII in 'result'. +- */ +-PJ_DEF(pj_status_t) pjsip_auth_create_digestSHA256(pj_str_t *result, +- const pj_str_t *nonce, +- const pj_str_t *nc, +- const pj_str_t *cnonce, +- const pj_str_t *qop, +- const pj_str_t *uri, +- const pj_str_t *realm, +- const pjsip_cred_info *cred_info, +- const pj_str_t *method) +-{ +-#if PJSIP_AUTH_HAS_DIGEST_SHA256 ++ digest_len = algorithm->digest_length; ++ digest_strlen = algorithm->digest_str_length; ++ dig_len = digest_len; + +- char ha1[PJSIP_SHA256STRLEN]; +- char ha2[PJSIP_SHA256STRLEN]; +- unsigned char digest[32]; ++ if (result->slen < digest_strlen) { ++ PJ_LOG(4, (THIS_FILE, ++ "The length of the result buffer must be at least %d bytes " ++ "for algorithm %.*s", digest_strlen, ++ (int)algorithm->iana_name.slen, algorithm->iana_name.ptr)); ++ return PJ_EINVAL; ++ } ++ result->slen = 0; + +-#if OPENSSL_VERSION_NUMBER < 0x30000000L +- SHA256_CTX pms; +-#else +- EVP_MD_CTX* mdctx; +- const EVP_MD* md; +- unsigned dig_len; +-#endif ++ if (!PJSIP_CRED_DATA_IS_PASSWD(cred_info) && !PJSIP_CRED_DATA_IS_DIGEST(cred_info)) { ++ PJ_LOG(4, (THIS_FILE, ++ "cred_info->data_type must be PJSIP_CRED_DATA_PLAIN_PASSWD " ++ "or PJSIP_CRED_DATA_DIGEST")); ++ return PJ_EINVAL; ++ } + +- pj_assert(result->slen >= PJSIP_SHA256STRLEN); ++ if (PJSIP_CRED_DATA_IS_DIGEST(cred_info)) { ++ if (cred_info->algorithm_type != algorithm_type) { ++ PJ_LOG(4,(THIS_FILE, ++ "The algorithm specified in the cred_info (%.*s) " ++ "doesn't match the algorithm requested for hashing (%.*s)", ++ (int)pjsip_auth_algorithms[cred_info->algorithm_type].iana_name.slen, ++ pjsip_auth_algorithms[cred_info->algorithm_type].iana_name.ptr, ++ (int)pjsip_auth_algorithms[algorithm_type].iana_name.slen, ++ pjsip_auth_algorithms[algorithm_type].iana_name.ptr)); ++ return PJ_EINVAL; ++ } ++ PJ_ASSERT_RETURN(cred_info->data.slen >= digest_strlen, PJ_EINVAL); ++ } + +-#if OPENSSL_VERSION_NUMBER >= 0x30000000L +- md = EVP_get_digestbyname("SHA256"); ++ md = EVP_get_digestbyname(algorithm->openssl_name); + if (md == NULL) { ++ /* Shouldn't happen since it was checked above */ + return PJ_ENOTSUP; + } +-#endif + +- AUTH_TRACE_((THIS_FILE, "Begin creating digest")); ++ AUTH_TRACE_((THIS_FILE, "Begin creating %.*s digest", ++ (int)algorithm->iana_name.slen, algorithm->iana_name.ptr)); + +- if ((cred_info->data_type & PASSWD_MASK) == PJSIP_CRED_DATA_PLAIN_PASSWD) ++ if (PJSIP_CRED_DATA_IS_PASSWD(cred_info)) + { ++ AUTH_TRACE_((THIS_FILE, " Using plain text password for %.*s digest", ++ (int)algorithm->iana_name.slen, algorithm->iana_name.ptr)); + /*** +- *** ha1 = SHA256(username ":" realm ":" password) ++ *** ha1 = (digest)(username ":" realm ":" password) + ***/ +-#if OPENSSL_VERSION_NUMBER < 0x30000000L +- SHA256_Init(&pms); +- SHA256_Update( &pms, cred_info->username.ptr, +- cred_info->username.slen); +- SHA256_Update( &pms, ":", 1); +- SHA256_Update( &pms, realm->ptr, realm->slen); +- SHA256_Update( &pms, ":", 1); +- SHA256_Update( &pms, cred_info->data.ptr, cred_info->data.slen); +- SHA256_Final(digest, &pms); +-#else + mdctx = EVP_MD_CTX_new(); ++ + EVP_DigestInit_ex(mdctx, md, NULL); + EVP_DigestUpdate(mdctx, cred_info->username.ptr, cred_info->username.slen); + EVP_DigestUpdate(mdctx, ":", 1); + EVP_DigestUpdate(mdctx, realm->ptr, realm->slen); + EVP_DigestUpdate(mdctx, ":", 1); + EVP_DigestUpdate(mdctx, cred_info->data.ptr, cred_info->data.slen); + + EVP_DigestFinal_ex(mdctx, digest, &dig_len); + EVP_MD_CTX_free(mdctx); +-#endif +- digestNtoStr(digest, 32, ha1); ++ digestNtoStr(digest, dig_len, ha1); + +- } else if ((cred_info->data_type & PASSWD_MASK) == PJSIP_CRED_DATA_DIGEST) +- { +- if (cred_info->data.slen != 64) { +- pj_assert(!"Invalid cred_info data length"); +- pj_bzero(result->ptr, result->slen); +- result->slen = 0; +- return PJ_EINVAL; +- } +- pj_memcpy( ha1, cred_info->data.ptr, cred_info->data.slen ); + } else { +- pj_assert(!"Invalid data_type"); +- pj_bzero(result->ptr, result->slen); +- result->slen = 0; +- return PJ_EINVAL; ++ AUTH_TRACE_((THIS_FILE, " Using pre computed digest for %.*s digest", ++ (int)algorithm->iana_name.slen, algorithm->iana_name.ptr)); ++ pj_memcpy( ha1, cred_info->data.ptr, cred_info->data.slen ); + } + +- AUTH_TRACE_((THIS_FILE, " ha1=%.64s", ha1)); ++ AUTH_TRACE_((THIS_FILE, " ha1=%.*s", algorithm->digest_str_length, ha1)); + + /*** +- *** ha2 = SHA256(method ":" req_uri) ++ *** ha2 = (digest)(method ":" req_uri) + ***/ +-#if OPENSSL_VERSION_NUMBER < 0x30000000L +- SHA256_Init(&pms); +- SHA256_Update( &pms, method->ptr, method->slen); +- SHA256_Update( &pms, ":", 1); +- SHA256_Update( &pms, uri->ptr, uri->slen); +- SHA256_Final( digest, &pms); +-#else + mdctx = EVP_MD_CTX_new(); + EVP_DigestInit_ex(mdctx, md, NULL); + EVP_DigestUpdate(mdctx, method->ptr, method->slen); + EVP_DigestUpdate(mdctx, ":", 1); + EVP_DigestUpdate(mdctx, uri->ptr, uri->slen); + EVP_DigestFinal_ex(mdctx, digest, &dig_len); + EVP_MD_CTX_free(mdctx); +-#endif +- digestNtoStr(digest, 32, ha2); ++ digestNtoStr(digest, dig_len, ha2); + +- AUTH_TRACE_((THIS_FILE, " ha2=%.64s", ha2)); ++ AUTH_TRACE_((THIS_FILE, " ha2=%.*s", algorithm->digest_str_length, ha2)); + + /*** + *** When qop is not used: +- *** response = SHA256(ha1 ":" nonce ":" ha2) ++ *** response = (digest)(ha1 ":" nonce ":" ha2) + *** + *** When qop=auth is used: +- *** response = SHA256(ha1 ":" nonce ":" nc ":" cnonce ":" qop ":" ha2) ++ *** response = (digest)(ha1 ":" nonce ":" nc ":" cnonce ":" qop ":" ha2) + ***/ +-#if OPENSSL_VERSION_NUMBER < 0x30000000L +- SHA256_Init(&pms); +- SHA256_Update( &pms, ha1, PJSIP_SHA256STRLEN); +- SHA256_Update( &pms, ":", 1); +- SHA256_Update( &pms, nonce->ptr, nonce->slen); +- if (qop && qop->slen != 0) { +- SHA256_Update( &pms, ":", 1); +- SHA256_Update( &pms, nc->ptr, nc->slen); +- SHA256_Update( &pms, ":", 1); +- SHA256_Update( &pms, cnonce->ptr, cnonce->slen); +- SHA256_Update( &pms, ":", 1); +- SHA256_Update( &pms, qop->ptr, qop->slen); +- } +- SHA256_Update( &pms, ":", 1); +- SHA256_Update( &pms, ha2, PJSIP_SHA256STRLEN); +- +- /* This is the final response digest. */ +- SHA256_Final(digest, &pms); +-#else + mdctx = EVP_MD_CTX_new(); + EVP_DigestInit_ex(mdctx, md, NULL); +- EVP_DigestUpdate(mdctx, ha1, PJSIP_SHA256STRLEN); ++ EVP_DigestUpdate(mdctx, ha1, digest_strlen); + EVP_DigestUpdate(mdctx, ":", 1); + EVP_DigestUpdate(mdctx, nonce->ptr, nonce->slen); + if (qop && qop->slen != 0) { + EVP_DigestUpdate(mdctx, ":", 1); + EVP_DigestUpdate(mdctx, nc->ptr, nc->slen); + EVP_DigestUpdate(mdctx, ":", 1); + EVP_DigestUpdate(mdctx, cnonce->ptr, cnonce->slen); + EVP_DigestUpdate(mdctx, ":", 1); + EVP_DigestUpdate(mdctx, qop->ptr, qop->slen); + } + EVP_DigestUpdate(mdctx, ":", 1); +- EVP_DigestUpdate(mdctx, ha2, PJSIP_SHA256STRLEN); ++ EVP_DigestUpdate(mdctx, ha2, digest_strlen); + + EVP_DigestFinal_ex(mdctx, digest, &dig_len); + EVP_MD_CTX_free(mdctx); +-#endif ++ + /* Convert digest to string and store in chal->response. */ +- result->slen = PJSIP_SHA256STRLEN; +- digestNtoStr(digest, 32, result->ptr); ++ result->slen = digest_strlen; ++ digestNtoStr(digest, digest_len, result->ptr); + +- AUTH_TRACE_((THIS_FILE, " digest=%.64s", result->ptr)); +- AUTH_TRACE_((THIS_FILE, "Digest created")); ++ AUTH_TRACE_((THIS_FILE, "%.*s digest=%.*s", ++ (int)algorithm->iana_name.slen, algorithm->iana_name.ptr, ++ (int)result->slen, result->ptr)); + ++ return PJ_SUCCESS; ++} ++ ++ ++PJ_DEF(pj_status_t) pjsip_auth_create_digest( pj_str_t *result, ++ const pj_str_t *nonce, ++ const pj_str_t *nc, ++ const pj_str_t *cnonce, ++ const pj_str_t *qop, ++ const pj_str_t *uri, ++ const pj_str_t *realm, ++ const pjsip_cred_info *cred_info, ++ const pj_str_t *method) ++{ ++ PJ_ASSERT_RETURN(cred_info, PJ_EINVAL); ++ PJ_ASSERT_RETURN(!PJSIP_CRED_DATA_IS_AKA(cred_info), PJ_EINVAL); ++ ++ return pjsip_auth_create_digest2(result, nonce, nc, cnonce, ++ qop, uri, realm, cred_info, method, ++ PJSIP_AUTH_ALGORITHM_MD5); ++} ++ ++ ++/* ++ * Create response SHA-256 digest based on the parameters and store the ++ * digest ASCII in 'result'. ++ * \deprecated Use pjsip_auth_create_digest2 with ++ * algorithm_type = PJSIP_AUTH_ALGORITHM_SHA256. ++ */ ++PJ_DEF(pj_status_t) pjsip_auth_create_digestSHA256(pj_str_t *result, ++ const pj_str_t *nonce, ++ const pj_str_t *nc, ++ const pj_str_t *cnonce, ++ const pj_str_t *qop, ++ const pj_str_t *uri, ++ const pj_str_t *realm, ++ const pjsip_cred_info *cred_info, ++ const pj_str_t *method) ++{ ++ PJ_ASSERT_RETURN(cred_info, PJ_EINVAL); ++ PJ_ASSERT_RETURN(!PJSIP_CRED_DATA_IS_AKA(cred_info), PJ_EINVAL); ++ ++ return pjsip_auth_create_digest2(result, nonce, nc, cnonce, ++ qop, uri, realm, cred_info, method, ++ PJSIP_AUTH_ALGORITHM_SHA256); ++} ++ ++ ++PJ_DEF(const pjsip_auth_algorithm *) pjsip_auth_get_algorithm_by_type( ++ pjsip_auth_algorithm_type algorithm_type) ++{ ++ if (algorithm_type > PJSIP_AUTH_ALGORITHM_NOT_SET ++ && algorithm_type < PJSIP_AUTH_ALGORITHM_COUNT) { ++ return &pjsip_auth_algorithms[algorithm_type]; ++ } ++ return NULL; ++} ++ ++ ++PJ_DEF(const pjsip_auth_algorithm *) pjsip_auth_get_algorithm_by_iana_name( ++ const pj_str_t *iana_name) ++{ ++ int i; ++ ++ if (!iana_name) { ++ return NULL; ++ } ++ ++ if (iana_name->slen == 0) { ++ return &pjsip_auth_algorithms[PJSIP_AUTH_ALGORITHM_MD5]; ++ } ++ ++#ifdef HAVE_NO_OPENSSL ++ i = PJSIP_AUTH_ALGORITHM_MD5; ++ if (pj_stricmp(iana_name, &pjsip_auth_algorithms[i].iana_name) == 0) { ++ return &pjsip_auth_algorithms[i]; ++ } + #else +- PJ_UNUSED_ARG(result); +- PJ_UNUSED_ARG(nonce); +- PJ_UNUSED_ARG(nc); +- PJ_UNUSED_ARG(cnonce); +- PJ_UNUSED_ARG(qop); +- PJ_UNUSED_ARG(uri); +- PJ_UNUSED_ARG(realm); +- PJ_UNUSED_ARG(cred_info); +- PJ_UNUSED_ARG(method); ++ for (i = PJSIP_AUTH_ALGORITHM_NOT_SET + 1; i < PJSIP_AUTH_ALGORITHM_COUNT; i++) { ++ if (pj_stricmp(iana_name, &pjsip_auth_algorithms[i].iana_name) == 0) { ++ return &pjsip_auth_algorithms[i]; ++ } ++ } ++#endif ++ return NULL; ++} ++ ++ ++PJ_DEF(pj_bool_t) pjsip_auth_is_algorithm_supported( ++ pjsip_auth_algorithm_type algorithm_type) ++{ ++ const pjsip_auth_algorithm *algorithm = NULL; ++ ++ if (algorithm_type <= PJSIP_AUTH_ALGORITHM_NOT_SET ++ || algorithm_type >= PJSIP_AUTH_ALGORITHM_COUNT) { ++ return PJ_FALSE; ++ } ++ algorithm = &pjsip_auth_algorithms[algorithm_type]; ++ ++ /* ++ * If the openssl_name is empty there's no need to check ++ * if OpenSSL supports it. ++ */ ++ if (algorithm->openssl_name[0] == '\0') { ++ return PJ_TRUE; ++ } ++ ++#ifdef HAVE_NO_OPENSSL ++ return (algorithm_type == PJSIP_AUTH_ALGORITHM_MD5); ++#else ++ { ++ const EVP_MD* md; ++ md = EVP_get_digestbyname(algorithm->openssl_name); ++ if (md == NULL) { ++ return PJ_FALSE; ++ } ++ return PJ_TRUE; ++ } + #endif +- return PJ_SUCCESS; + } + + + /* + * Finds out if qop offer contains "auth" token. + */ + static pj_bool_t has_auth_qop( pj_pool_t *pool, const pj_str_t *qop_offer) + { + pj_str_t qop; + char *p; +@@ -464,125 +513,93 @@ static pj_bool_t has_auth_qop( pj_pool_t *pool, const pj_str_t *qop_offer) + return PJ_FALSE; + } + + /* + * Generate response digest. + * Most of the parameters to generate the digest (i.e. username, realm, uri, + * and nonce) are expected to be in the credential. Additional parameters (i.e. + * password and method param) should be supplied in the argument. + * + * The resulting digest will be stored in cred->response. +- * The pool is used to allocate 32 bytes to store the digest in cred->response. ++ * The pool is used to allocate enough bytes to store the digest in cred->response. + */ + static pj_status_t respond_digest( pj_pool_t *pool, + pjsip_digest_credential *cred, + const pjsip_digest_challenge *chal, + const pj_str_t *uri, + const pjsip_cred_info *cred_info, + const pj_str_t *cnonce, + pj_uint32_t nc, +- const pj_str_t *method) ++ const pj_str_t *method, ++ const pjsip_auth_algorithm_type challenge_algorithm_type) + { +- const pj_str_t pjsip_AKAv1_MD5_STR = { "AKAv1-MD5", 9 }; +- pj_bool_t algo_sha256 = PJ_FALSE; + pj_status_t status = PJ_SUCCESS; + +- /* Check if algo is sha256 */ +-#if PJSIP_AUTH_HAS_DIGEST_SHA256 +- algo_sha256 = (pj_stricmp(&chal->algorithm, &pjsip_SHA256_STR)==0); +-#endif +- +- /* Check algorithm is supported. We support MD5, AKAv1-MD5, and SHA256. */ +- if (chal->algorithm.slen==0 || +- (algo_sha256 || +- pj_stricmp(&chal->algorithm, &pjsip_MD5_STR)==0 || +- pj_stricmp(&chal->algorithm, &pjsip_AKAv1_MD5_STR)==0)) +- { +- PJ_LOG(4,(THIS_FILE, "Digest algorithm is \"%.*s\"", +- (int)chal->algorithm.slen, chal->algorithm.ptr)); +- } +- else { +- PJ_LOG(4,(THIS_FILE, "Unsupported digest algorithm \"%.*s\"", +- (int)chal->algorithm.slen, chal->algorithm.ptr)); +- return PJSIP_EINVALIDALGORITHM; +- } ++ AUTH_TRACE_((THIS_FILE, "Begin responding to %.*s challenge", ++ (int)chal->algorithm.slen, chal->algorithm.ptr)); + + /* Build digest credential from arguments. */ + pj_strdup(pool, &cred->username, &cred_info->username); + pj_strdup(pool, &cred->realm, &chal->realm); + pj_strdup(pool, &cred->nonce, &chal->nonce); + pj_strdup(pool, &cred->uri, uri); + pj_strdup(pool, &cred->algorithm, &chal->algorithm); + pj_strdup(pool, &cred->opaque, &chal->opaque); + + /* Allocate memory. */ +- cred->response.slen = algo_sha256? PJSIP_SHA256STRLEN : PJSIP_MD5STRLEN; ++ cred->response.slen = pjsip_auth_algorithms[challenge_algorithm_type].digest_str_length; + cred->response.ptr = (char*) pj_pool_alloc(pool, cred->response.slen); + + if (chal->qop.slen == 0) { + /* Server doesn't require quality of protection. */ + +- if ((cred_info->data_type & EXT_MASK) == PJSIP_CRED_DATA_EXT_AKA) { ++ if (PJSIP_CRED_DATA_IS_AKA(cred_info)) { + /* Call application callback to create the response digest */ + return (*cred_info->ext.aka.cb)(pool, chal, cred_info, + method, cred); + } + else { + /* Convert digest to string and store in chal->response. */ +- if (algo_sha256) { +- status = pjsip_auth_create_digestSHA256( +- &cred->response, &cred->nonce, NULL, +- NULL, NULL, uri, &chal->realm, +- cred_info, method); +- } else { +- status = pjsip_auth_create_digest( &cred->response, +- &cred->nonce, NULL, NULL, NULL, uri, +- &chal->realm, cred_info, method); +- } ++ status = pjsip_auth_create_digest2( ++ &cred->response, &cred->nonce, NULL, ++ NULL, NULL, uri, &chal->realm, ++ cred_info, method, challenge_algorithm_type); + } + + } else if (has_auth_qop(pool, &chal->qop)) { + /* Server requires quality of protection. + * We respond with selecting "qop=auth" protection. + */ + cred->qop = pjsip_AUTH_STR; + cred->nc.ptr = (char*) pj_pool_alloc(pool, 16); + cred->nc.slen = pj_ansi_snprintf(cred->nc.ptr, 16, "%08u", nc); + + if (cnonce && cnonce->slen) { + pj_strdup(pool, &cred->cnonce, cnonce); + } else { + pj_str_t dummy_cnonce = { "b39971", 6}; + pj_strdup(pool, &cred->cnonce, &dummy_cnonce); + } + +- if ((cred_info->data_type & EXT_MASK) == PJSIP_CRED_DATA_EXT_AKA) { ++ if (PJSIP_CRED_DATA_IS_AKA(cred_info)) { + /* Call application callback to create the response digest */ + return (*cred_info->ext.aka.cb)(pool, chal, cred_info, + method, cred); + } + else { + /* Convert digest to string and store in chal->response. */ +- if (algo_sha256) { +- status = pjsip_auth_create_digestSHA256( +- &cred->response, &cred->nonce, +- &cred->nc, &cred->cnonce, +- &pjsip_AUTH_STR, uri, +- &chal->realm, cred_info, +- method); +- } else { +- status = pjsip_auth_create_digest( &cred->response, +- &cred->nonce, &cred->nc, +- &cred->cnonce, &pjsip_AUTH_STR, +- uri, &chal->realm, +- cred_info, method); +- } ++ status = pjsip_auth_create_digest2( ++ &cred->response, &cred->nonce, ++ &cred->nc, &cred->cnonce, ++ &pjsip_AUTH_STR, uri, ++ &chal->realm, cred_info, ++ method, challenge_algorithm_type); + } + + } else { + /* Server requires quality protection that we don't support. */ + PJ_LOG(4,(THIS_FILE, "Unsupported qop offer %.*s", + (int)chal->qop.slen, chal->qop.ptr)); + return PJSIP_EINVALIDQOP; + } + + return status; +@@ -682,43 +699,72 @@ static void update_digest_session( pjsip_cached_auth *cached_auth, + } + cached_auth->nc = 1; + } + } + } + #endif /* PJSIP_AUTH_QOP_SUPPORT */ + + + /* Find cached authentication in the list for the specified realm. */ + static pjsip_cached_auth *find_cached_auth( pjsip_auth_clt_sess *sess, +- const pj_str_t *realm ) ++ const pj_str_t *realm, ++ pjsip_auth_algorithm_type algorithm_type) + { + pjsip_cached_auth *auth = sess->cached_auth.next; + while (auth != &sess->cached_auth) { +- if (pj_stricmp(&auth->realm, realm) == 0) ++ if (pj_stricmp(&auth->realm, realm) == 0 ++ && auth->challenge_algorithm_type == algorithm_type) + return auth; + auth = auth->next; + } + + return NULL; + } + + /* Find credential to use for the specified realm and auth scheme. */ + static const pjsip_cred_info* auth_find_cred( const pjsip_auth_clt_sess *sess, + const pj_str_t *realm, +- const pj_str_t *auth_scheme) ++ const pj_str_t *auth_scheme, ++ const pjsip_auth_algorithm_type algorithm_type) + { + unsigned i; + int wildcard = -1; + + PJ_UNUSED_ARG(auth_scheme); + + for (i=0; icred_cnt; ++i) { ++ switch(sess->cred_info[i].data_type) { ++ case PJSIP_CRED_DATA_PLAIN_PASSWD: ++ /* PLAIN_PASSWD creds can be used for any algorithm other than AKA */ ++ if (algorithm_type != PJSIP_AUTH_ALGORITHM_AKAV1_MD5 ++ || algorithm_type != PJSIP_AUTH_ALGORITHM_AKAV2_MD5) { ++ break; ++ } ++ continue; ++ case PJSIP_CRED_DATA_DIGEST: ++ /* Digest creds can only be used if the algorithms match */ ++ if (sess->cred_info[i].algorithm_type == algorithm_type) { ++ break; ++ } ++ continue; ++ case PJSIP_CRED_DATA_EXT_AKA: ++ /* AKA creds can only be used for AKA algorithm */ ++ if (algorithm_type == PJSIP_AUTH_ALGORITHM_AKAV1_MD5 ++ || algorithm_type == PJSIP_AUTH_ALGORITHM_AKAV2_MD5) { ++ break; ++ } ++ continue; ++ } ++ /* ++ * We've determined that the credential can be used for the ++ * specified algorithm, now check the realm. ++ */ + if (pj_stricmp(&sess->cred_info[i].realm, realm) == 0) + return &sess->cred_info[i]; + else if (sess->cred_info[i].realm.slen == 1 && + sess->cred_info[i].realm.ptr[0] == '*') + { + wildcard = i; + } + } + + /* No matching realm. See if we have credential with wildcard ('*') +@@ -782,20 +828,29 @@ PJ_DEF(pj_status_t) pjsip_auth_clt_clone( pj_pool_t *pool, + sess->cred_info = (pjsip_cred_info*) + pj_pool_alloc(pool, + sess->cred_cnt*sizeof(pjsip_cred_info)); + for (i=0; icred_cnt; ++i) { + pj_strdup(pool, &sess->cred_info[i].realm, &rhs->cred_info[i].realm); + pj_strdup(pool, &sess->cred_info[i].scheme, &rhs->cred_info[i].scheme); + pj_strdup(pool, &sess->cred_info[i].username, + &rhs->cred_info[i].username); + sess->cred_info[i].data_type = rhs->cred_info[i].data_type; + pj_strdup(pool, &sess->cred_info[i].data, &rhs->cred_info[i].data); ++ if (PJSIP_CRED_DATA_IS_DIGEST(&rhs->cred_info[i])) { ++ sess->cred_info[i].algorithm_type = rhs->cred_info[i].algorithm_type; ++ } ++ if (PJSIP_CRED_DATA_IS_AKA(&rhs->cred_info[i])) { ++ pj_strdup(pool, &sess->cred_info[i].ext.aka.k, &rhs->cred_info[i].ext.aka.k); ++ pj_strdup(pool, &sess->cred_info[i].ext.aka.op, &rhs->cred_info[i].ext.aka.op); ++ pj_strdup(pool, &sess->cred_info[i].ext.aka.amf, &rhs->cred_info[i].ext.aka.amf); ++ sess->cred_info[i].ext.aka.cb = rhs->cred_info[i].ext.aka.cb; ++ } + } + + /* TODO note: + * Cloning the full authentication client is quite a big task. + * We do only the necessary bits here, i.e. cloning the credentials. + * The drawback of this basic approach is, a forked dialog will have to + * re-authenticate itself on the next request because it has lost the + * cached authentication headers. + */ + PJ_TODO(FULL_CLONE_OF_AUTH_CLIENT_SESSION); +@@ -816,21 +871,21 @@ PJ_DEF(pj_status_t) pjsip_auth_clt_set_credentials( pjsip_auth_clt_sess *sess, + } else { + int i; + sess->cred_info = (pjsip_cred_info*) + pj_pool_alloc(sess->pool, cred_cnt * sizeof(*c)); + for (i=0; icred_info[i].data_type = c[i].data_type; + + /* When data_type is PJSIP_CRED_DATA_EXT_AKA, + * callback must be specified. + */ +- if ((c[i].data_type & EXT_MASK) == PJSIP_CRED_DATA_EXT_AKA) { ++ if (PJSIP_CRED_DATA_IS_AKA(&c[i])) { + + #if !PJSIP_HAS_DIGEST_AKA_AUTH + if (!PJSIP_HAS_DIGEST_AKA_AUTH) { + pj_assert(!"PJSIP_HAS_DIGEST_AKA_AUTH is not enabled"); + return PJSIP_EAUTHINAKACRED; + } + #endif + + /* Callback must be specified */ + PJ_ASSERT_RETURN(c[i].ext.aka.cb != NULL, PJ_EINVAL); +@@ -853,20 +908,21 @@ PJ_DEF(pj_status_t) pjsip_auth_clt_set_credentials( pjsip_auth_clt_sess *sess, + pj_strdup(sess->pool, &sess->cred_info[i].ext.aka.op, + &c[i].ext.aka.op); + pj_strdup(sess->pool, &sess->cred_info[i].ext.aka.amf, + &c[i].ext.aka.amf); + } + + pj_strdup(sess->pool, &sess->cred_info[i].scheme, &c[i].scheme); + pj_strdup(sess->pool, &sess->cred_info[i].realm, &c[i].realm); + pj_strdup(sess->pool, &sess->cred_info[i].username, &c[i].username); + pj_strdup(sess->pool, &sess->cred_info[i].data, &c[i].data); ++ sess->cred_info[i].algorithm_type = c[i].algorithm_type; + } + sess->cred_cnt = cred_cnt; + } + + return PJ_SUCCESS; + } + + + /* + * Set the preference for the client authentication session. +@@ -902,21 +958,22 @@ PJ_DEF(pj_status_t) pjsip_auth_clt_get_prefs(pjsip_auth_clt_sess *sess, + * Create Authorization/Proxy-Authorization response header based on the challege + * in WWW-Authenticate/Proxy-Authenticate header. + */ + static pj_status_t auth_respond( pj_pool_t *req_pool, + const pjsip_www_authenticate_hdr *hdr, + const pjsip_uri *uri, + const pjsip_cred_info *cred_info, + const pjsip_method *method, + pj_pool_t *sess_pool, + pjsip_cached_auth *cached_auth, +- pjsip_authorization_hdr **p_h_auth) ++ pjsip_authorization_hdr **p_h_auth, ++ const pjsip_auth_algorithm_type challenge_algorithm_type) + { + pjsip_authorization_hdr *hauth; + char tmp[PJSIP_MAX_URL_SIZE]; + pj_str_t uri_str; + pj_pool_t *pool; + pj_status_t status; + + /* Verify arguments. */ + PJ_ASSERT_RETURN(req_pool && hdr && uri && cred_info && method && + sess_pool && cached_auth && p_h_auth, PJ_EINVAL); +@@ -961,21 +1018,21 @@ static pj_status_t auth_respond( pj_pool_t *req_pool, + + cnonce = &cached_auth->cnonce; + nc = cached_auth->nc; + } + } + # endif /* PJSIP_AUTH_QOP_SUPPORT */ + + hauth->scheme = pjsip_DIGEST_STR; + status = respond_digest( pool, &hauth->credential.digest, + &hdr->challenge.digest, &uri_str, cred_info, +- cnonce, nc, &method->name); ++ cnonce, nc, &method->name, challenge_algorithm_type); + if (status != PJ_SUCCESS) + return status; + + /* Set qop type in auth session the first time only. */ + if (hdr->challenge.digest.qop.slen != 0 && cached_auth) { + if (cached_auth->qop_value == PJSIP_AUTH_QOP_NONE) { + pj_str_t *qop_val = &hauth->credential.digest.qop; + if (!pj_strcmp(qop_val, &pjsip_AUTH_STR)) { + cached_auth->qop_value = PJSIP_AUTH_QOP_AUTH; + } else { +@@ -1034,51 +1091,87 @@ static pj_status_t new_auth_for_req( pjsip_tx_data *tdata, + pjsip_cached_auth *auth, + pjsip_authorization_hdr **p_h_auth) + { + const pjsip_cred_info *cred; + pjsip_authorization_hdr *hauth; + pj_status_t status; + + PJ_ASSERT_RETURN(tdata && sess && auth, PJ_EINVAL); + PJ_ASSERT_RETURN(auth->last_chal != NULL, PJSIP_EAUTHNOPREVCHAL); + +- cred = auth_find_cred( sess, &auth->realm, &auth->last_chal->scheme ); ++ cred = auth_find_cred( sess, &auth->realm, &auth->last_chal->scheme, ++ auth->challenge_algorithm_type ); + if (!cred) + return PJSIP_ENOCREDENTIAL; + + status = auth_respond( tdata->pool, auth->last_chal, + tdata->msg->line.req.uri, + cred, &tdata->msg->line.req.method, +- sess->pool, auth, &hauth); ++ sess->pool, auth, &hauth, auth->challenge_algorithm_type); + if (status != PJ_SUCCESS) + return status; + + pjsip_msg_add_hdr( tdata->msg, (pjsip_hdr*)hauth); + + if (p_h_auth) + *p_h_auth = hauth; + + return PJ_SUCCESS; + } + #endif + + + /* Find credential in list of (Proxy-)Authorization headers */ +-static pjsip_authorization_hdr* get_header_for_realm(const pjsip_hdr *hdr_list, +- const pj_str_t *realm) ++static pjsip_authorization_hdr* get_header_for_cred_info( ++ const pjsip_hdr *hdr_list, ++ const pjsip_cred_info *cred_info) + { + pjsip_authorization_hdr *h; + + h = (pjsip_authorization_hdr*)hdr_list->next; + while (h != (pjsip_authorization_hdr*)hdr_list) { +- if (pj_stricmp(&h->credential.digest.realm, realm)==0) ++ /* If the realm doesn't match, just skip */ ++ if (pj_stricmp(&h->credential.digest.realm, &cred_info->realm) != 0) { ++ h = h->next; ++ continue; ++ } ++ ++ switch (cred_info->data_type) { ++ case PJSIP_CRED_DATA_PLAIN_PASSWD: ++ /* ++ * If cred_info->data_type is PLAIN_PASSWD, then we can use the header ++ * regardless of algorithm. ++ */ + return h; ++ case PJSIP_CRED_DATA_DIGEST: ++ /* ++ * If cred_info->data_type is DIGEST, then we need to check if the ++ * algorithms match. ++ */ ++ if (pj_stricmp(&h->credential.digest.algorithm, ++ &pjsip_auth_algorithms[cred_info->algorithm_type].iana_name) == 0) { ++ return h; ++ } ++ break; ++ case PJSIP_CRED_DATA_EXT_AKA: ++ /* ++ * If cred_info->data_type is EXT_AKA, then we need to check if the ++ * challenge algorithm is AKAv1-MD5 or AKAv2-MD5. ++ */ ++ if (pj_stricmp(&h->credential.digest.algorithm, ++ &pjsip_auth_algorithms[PJSIP_AUTH_ALGORITHM_AKAV1_MD5].iana_name) == 0 ++ || pj_stricmp(&h->credential.digest.algorithm, ++ &pjsip_auth_algorithms[PJSIP_AUTH_ALGORITHM_AKAV2_MD5].iana_name) == 0) { ++ return h; ++ } ++ break; ++ } + h = h->next; + } + + return NULL; + } + + + /* Initialize outgoing request. */ + PJ_DEF(pj_status_t) pjsip_auth_clt_init_req( pjsip_auth_clt_sess *sess, + pjsip_tx_data *tdata ) +@@ -1141,31 +1234,33 @@ PJ_DEF(pj_status_t) pjsip_auth_clt_init_req( pjsip_auth_clt_sess *sess, + defined(PJSIP_AUTH_AUTO_SEND_NEXT) && \ + (PJSIP_AUTH_QOP_SUPPORT && PJSIP_AUTH_AUTO_SEND_NEXT) + else if (auth->qop_value == PJSIP_AUTH_QOP_AUTH) { + /* For qop="auth", we have to re-create the authorization header. + */ + const pjsip_cred_info *cred; + pjsip_authorization_hdr *hauth; + pj_status_t status; + + cred = auth_find_cred(sess, &auth->realm, +- &auth->last_chal->scheme); ++ &auth->last_chal->scheme, ++ auth->challenge_algorithm_type); + if (!cred) { + auth = auth->next; + continue; + } + + status = auth_respond( tdata->pool, auth->last_chal, + tdata->msg->line.req.uri, + cred, + &tdata->msg->line.req.method, +- sess->pool, auth, &hauth); ++ sess->pool, auth, &hauth, ++ auth->challenge_algorithm_type); + if (status != PJ_SUCCESS) + return status; + + //pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)hauth); + pj_list_push_back(&added, hauth); + } + # endif /* PJSIP_AUTH_QOP_SUPPORT && PJSIP_AUTH_AUTO_SEND_NEXT */ + + auth = auth->next; + } +@@ -1194,21 +1289,21 @@ PJ_DEF(pj_status_t) pjsip_auth_clt_init_req( pjsip_auth_clt_sess *sess, + uri.slen = pjsip_uri_print(PJSIP_URI_IN_REQ_URI, + tdata->msg->line.req.uri, + uri.ptr, PJSIP_MAX_URL_SIZE); + if (uri.slen < 1 || uri.slen >= PJSIP_MAX_URL_SIZE) + return PJSIP_EURITOOLONG; + + for (i=0; icred_cnt; ++i) { + pjsip_cred_info *c = &sess->cred_info[i]; + pjsip_authorization_hdr *h; + +- h = get_header_for_realm(&added, &c->realm); ++ h = get_header_for_cred_info(&added, c); + if (h) { + pj_list_erase(h); + pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)h); + } else { + pjsip_authorization_hdr *hs; + + hs = pjsip_authorization_hdr_create(tdata->pool); + pj_strdup(tdata->pool, &hs->scheme, &c->scheme); + if (pj_stricmp(&c->scheme, &pjsip_BEARER_STR)==0) { + pj_strdup(tdata->pool, &hs->credential.oauth.username, +@@ -1216,22 +1311,28 @@ PJ_DEF(pj_status_t) pjsip_auth_clt_init_req( pjsip_auth_clt_sess *sess, + pj_strdup(tdata->pool, &hs->credential.oauth.realm, + &c->realm); + pj_strdup(tdata->pool, &hs->credential.oauth.token, + &c->data); + } else { //if (pj_stricmp(&c->scheme, &pjsip_DIGEST_STR)==0) + pj_strdup(tdata->pool, &hs->credential.digest.username, + &c->username); + pj_strdup(tdata->pool, &hs->credential.digest.realm, + &c->realm); + pj_strdup(tdata->pool,&hs->credential.digest.uri, &uri); +- pj_strdup(tdata->pool, &hs->credential.digest.algorithm, +- &sess->pref.algorithm); ++ ++ if (c->algorithm_type == PJSIP_AUTH_ALGORITHM_NOT_SET) { ++ pj_strdup(tdata->pool, &hs->credential.digest.algorithm, ++ &sess->pref.algorithm); ++ } else { ++ pj_strdup(tdata->pool, &hs->credential.digest.algorithm, ++ &pjsip_auth_algorithms[c->algorithm_type].iana_name); ++ } + } + + pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)hs); + } + } + } + + return PJ_SUCCESS; + } + +@@ -1263,21 +1364,22 @@ static void recreate_cached_auth_pool( pjsip_endpoint *endpt, + auth->pool = auth_pool; + } + + /* Process authorization challenge */ + static pj_status_t process_auth( pj_pool_t *req_pool, + const pjsip_www_authenticate_hdr *hchal, + const pjsip_uri *uri, + pjsip_tx_data *tdata, + pjsip_auth_clt_sess *sess, + pjsip_cached_auth *cached_auth, +- pjsip_authorization_hdr **h_auth) ++ pjsip_authorization_hdr **h_auth, ++ const pjsip_auth_algorithm_type challenge_algorithm_type) + { + const pjsip_cred_info *cred; + pjsip_authorization_hdr *sent_auth = NULL; + pjsip_hdr *hdr; + pj_status_t status; + + /* See if we have sent authorization header for this realm (and scheme) */ + hdr = tdata->msg->hdr.next; + while (hdr != &tdata->msg->hdr) { + if ((hchal->type == PJSIP_H_WWW_AUTHENTICATE && +@@ -1291,29 +1393,40 @@ static pj_status_t process_auth( pj_pool_t *req_pool, + pj_stricmp(&hchal->scheme, &sent_auth->scheme)==0) + { + /* If this authorization has empty response, remove it. */ + if (pj_stricmp(&sent_auth->scheme, &pjsip_DIGEST_STR)==0 && + sent_auth->credential.digest.response.slen == 0) + { + /* This is empty authorization, remove it. */ + hdr = hdr->next; + pj_list_erase(sent_auth); + continue; +- } else +- if (pj_stricmp(&sent_auth->scheme, &pjsip_DIGEST_STR)==0 && +- pj_stricmp(&sent_auth->credential.digest.algorithm, +- &hchal->challenge.digest.algorithm)!=0) +- { +- /* Same 'digest' scheme but different algo */ +- hdr = hdr->next; +- continue; + } else { ++#if defined(PJSIP_AUTH_ALLOW_MULTIPLE_AUTH_HEADER) && \ ++ PJSIP_AUTH_ALLOW_MULTIPLE_AUTH_HEADER!=0 ++ /* ++ * Keep sending additional headers if the the algorithm ++ * is different. ++ * WARNING: See the comment in sip_config.h regarding ++ * how using this option could be a security risk if ++ * a header with a more secure digest algorithm has already ++ * been sent. ++ */ ++ if (pj_stricmp(&sent_auth->scheme, &pjsip_DIGEST_STR)==0 && ++ pj_stricmp(&sent_auth->credential.digest.algorithm, ++ &hchal->challenge.digest.algorithm)!=0) ++ { ++ /* Same 'digest' scheme but different algo */ ++ hdr = hdr->next; ++ continue; ++ } else ++#endif + /* Found previous authorization attempt */ + break; + } + } + } + hdr = hdr->next; + } + + /* If we have sent, see if server rejected because of stale nonce or + * other causes. +@@ -1363,36 +1476,40 @@ static pj_status_t process_auth( pj_pool_t *req_pool, + } + + /* Otherwise remove old, stale authorization header from the mesasge. + * We will supply a new one. + */ + pj_list_erase(sent_auth); + } + + /* Find credential to be used for the challenge. */ + cred = auth_find_cred( sess, &hchal->challenge.common.realm, +- &hchal->scheme); ++ &hchal->scheme, challenge_algorithm_type); + if (!cred) { + const pj_str_t *realm = &hchal->challenge.common.realm; ++ AUTH_TRACE_((THIS_FILE, "No cred for for %.*s", ++ (int)hchal->challenge.digest.algorithm.slen, hchal->challenge.digest.algorithm.ptr)); ++ + PJ_LOG(4,(THIS_FILE, + "Unable to set auth for %s: can not find credential for " +- "%.*s/%.*s", ++ "%.*s/%.*s %.*s", + tdata->obj_name, + (int)realm->slen, realm->ptr, +- (int)hchal->scheme.slen, hchal->scheme.ptr)); ++ (int)hchal->scheme.slen, hchal->scheme.ptr, ++ (int)hchal->challenge.digest.algorithm.slen, hchal->challenge.digest.algorithm.ptr)); + return PJSIP_ENOCREDENTIAL; + } + + /* Respond to authorization challenge. */ + status = auth_respond( req_pool, hchal, uri, cred, + &tdata->msg->line.req.method, +- sess->pool, cached_auth, h_auth); ++ sess->pool, cached_auth, h_auth, challenge_algorithm_type); + return status; + } + + + /* Reinitialize outgoing request after 401/407 response is received. + * The purpose of this function is: + * - to add a Authorization/Proxy-Authorization header. + * - to put the newly created Authorization/Proxy-Authorization header + * in cached_list. + */ +@@ -1426,85 +1543,104 @@ PJ_DEF(pj_status_t) pjsip_auth_clt_reinit_req( pjsip_auth_clt_sess *sess, + * Respond to each authentication challenge. + */ + hdr = rdata->msg_info.msg->hdr.next; + chal_cnt = 0; + auth_cnt = 0; + last_auth_err = PJSIP_EAUTHNOAUTH; + while (hdr != &rdata->msg_info.msg->hdr) { + pjsip_cached_auth *cached_auth; + const pjsip_www_authenticate_hdr *hchal; + pjsip_authorization_hdr *hauth; ++ const pjsip_auth_algorithm *algorithm; + + /* Find WWW-Authenticate or Proxy-Authenticate header. */ + while (hdr != &rdata->msg_info.msg->hdr && + hdr->type != PJSIP_H_WWW_AUTHENTICATE && + hdr->type != PJSIP_H_PROXY_AUTHENTICATE) + { + hdr = hdr->next; + } + if (hdr == &rdata->msg_info.msg->hdr) + break; + + hchal = (const pjsip_www_authenticate_hdr*)hdr; + ++chal_cnt; + ++ /* At the current time, "digest" scheme is the only one supported. */ ++ if (pj_stricmp(&hchal->scheme, &pjsip_DIGEST_STR) != 0) { ++ AUTH_TRACE_((THIS_FILE, "Skipped header for scheme %.*s", ++ (int)hchal->scheme.slen, hchal->scheme.ptr)); ++ last_auth_err = PJSIP_EINVALIDAUTHSCHEME; ++ hdr = hdr->next; ++ continue; ++ } ++ ++ algorithm = pjsip_auth_get_algorithm_by_iana_name(&hchal->challenge.digest.algorithm); ++ if (!algorithm) { ++ AUTH_TRACE_((THIS_FILE, "Skipped header for algorithm %.*s", ++ (int)algorithm->iana_name.slen, algorithm->iana_name.ptr)); ++ last_auth_err = PJSIP_EINVALIDALGORITHM; ++ hdr = hdr->next; ++ continue; ++ } ++ + /* Find authentication session for this realm, create a new one + * if not present. + */ +- cached_auth = find_cached_auth(sess, &hchal->challenge.common.realm); ++ cached_auth = find_cached_auth(sess, &hchal->challenge.common.realm, ++ algorithm->algorithm_type); + if (!cached_auth) { + cached_auth = PJ_POOL_ZALLOC_T(sess->pool, pjsip_cached_auth); + cached_auth->pool = pjsip_endpt_create_pool(sess->endpt, + "auth_cli%p", + 1024, + 1024); + pj_strdup(cached_auth->pool, &cached_auth->realm, + &hchal->challenge.common.realm); + cached_auth->is_proxy = (hchal->type == PJSIP_H_PROXY_AUTHENTICATE); ++ cached_auth->challenge_algorithm_type = algorithm->algorithm_type; + # if (PJSIP_AUTH_HEADER_CACHING) + { + pj_list_init(&cached_auth->cached_hdr); + } + # endif + pj_list_insert_before(&sess->cached_auth, cached_auth); + } + + /* Create authorization header for this challenge, and update + * authorization session. + */ + status = process_auth(tdata->pool, hchal, tdata->msg->line.req.uri, +- tdata, sess, cached_auth, &hauth); ++ tdata, sess, cached_auth, &hauth, algorithm->algorithm_type); + if (status != PJ_SUCCESS) { + last_auth_err = status; ++ AUTH_TRACE_((THIS_FILE, "Skipped header for realm %.*s algorithm %.*s", ++ (int)hchal->challenge.common.realm.slen, hchal->challenge.common.realm.ptr, ++ (int)algorithm->iana_name.slen, algorithm->iana_name.ptr)); + + /* Process next header. */ + hdr = hdr->next; + continue; + } + + if (pj_pool_get_used_size(cached_auth->pool) > + PJSIP_AUTH_CACHED_POOL_MAX_SIZE) + { + recreate_cached_auth_pool(sess->endpt, cached_auth); + } + + /* Add to the message. */ + pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)hauth); + + /* Process next header. */ + hdr = hdr->next; + auth_cnt++; +- +-#if defined(PJSIP_AUTH_ALLOW_MULTIPLE_AUTH_HEADER) && \ +- PJSIP_AUTH_ALLOW_MULTIPLE_AUTH_HEADER==0 +- break; +-#endif + } + + /* Check if challenge is present */ + if (chal_cnt == 0) + return PJSIP_EAUTHNOCHAL; + + /* Check if any authorization header has been created */ + if (auth_cnt == 0) + return last_auth_err; + +diff --git a/pjsip/src/pjsip/sip_auth_server.c b/pjsip/src/pjsip/sip_auth_server.c +index 8eee2b51b..edc3aabb4 100644 +--- a/pjsip/src/pjsip/sip_auth_server.c ++++ b/pjsip/src/pjsip/sip_auth_server.c +@@ -15,23 +15,32 @@ + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + + #include + #include /* just to get pjsip_DIGEST_STR */ + #include + #include + #include ++#include + #include + #include + ++/* Logging. */ ++#define THIS_FILE "sip_auth_server.c" ++#if 0 ++# define AUTH_TRACE_(expr) PJ_LOG(3, expr) ++#else ++# define AUTH_TRACE_(expr) ++#endif ++ + + /* + * Initialize server authorization session data structure to serve the + * specified realm and to use lookup_func function to look for the credential + * info. + */ + PJ_DEF(pj_status_t) pjsip_auth_srv_init( pj_pool_t *pool, + pjsip_auth_srv *auth_srv, + const pj_str_t *realm, + pjsip_auth_lookup_cred *lookup, +@@ -68,55 +77,88 @@ PJ_DEF(pj_status_t) pjsip_auth_srv_init2( + } + + + /* Verify incoming Authorization/Proxy-Authorization header against the + * specified credential. + */ + static pj_status_t pjsip_auth_verify( const pjsip_authorization_hdr *hdr, + const pj_str_t *method, + const pjsip_cred_info *cred_info ) + { ++ + if (pj_stricmp(&hdr->scheme, &pjsip_DIGEST_STR) == 0) { +- char digest_buf[PJSIP_MD5STRLEN]; ++ const pjsip_digest_credential *dig = &hdr->credential.digest; ++ const pjsip_auth_algorithm *response_algorithm = ++ pjsip_auth_get_algorithm_by_iana_name(&dig->algorithm); ++ char digest_buf[PJSIP_AUTH_MAX_DIGEST_BUFFER_LENGTH * 2]; + pj_str_t digest; + pj_status_t status; +- const pjsip_digest_credential *dig = &hdr->credential.digest; ++ int result = 0; ++ ++ AUTH_TRACE_((THIS_FILE, "Authenticating with realm-digest %.*s-%.*s", ++ (int)dig->realm.slen, dig->realm.ptr, ++ (int)dig->algorithm.slen, dig->algorithm.ptr)); ++ /* ++ * Check that the response algorithm is supported. ++ * Since we sent the challenge it should be but ++ * we check anyway in case a client responds with a bad ++ * one. ++ */ ++ PJ_ASSERT_RETURN(response_algorithm ++ && pjsip_auth_is_algorithm_supported(response_algorithm->algorithm_type), ++ PJSIP_EINVALIDALGORITHM); ++ ++ /* ++ * Cred info type must be plain password or digest and if it's digest, ++ * the algorithm must match the algorithm in the header. ++ * We don't support the AKAv1-MD5 algorithm as a server. ++ */ ++ PJ_ASSERT_RETURN(PJSIP_CRED_DATA_IS_PASSWD(cred_info) ++ || (PJSIP_CRED_DATA_IS_DIGEST(cred_info) ++ && cred_info->algorithm_type == response_algorithm->algorithm_type), ++ PJSIP_EINVALIDALGORITHM); + + /* Check that username and realm match. + * These checks should have been performed before entering this + * function. + */ + PJ_ASSERT_RETURN(pj_strcmp(&dig->username, &cred_info->username) == 0, + PJ_EINVALIDOP); + PJ_ASSERT_RETURN(pj_strcmp(&dig->realm, &cred_info->realm) == 0, + PJ_EINVALIDOP); + + /* Prepare for our digest calculation. */ + digest.ptr = digest_buf; +- digest.slen = PJSIP_MD5STRLEN; ++ digest.slen = response_algorithm->digest_str_length; + + /* Create digest for comparison. */ +- status = pjsip_auth_create_digest(&digest, ++ status = pjsip_auth_create_digest2(&digest, + &hdr->credential.digest.nonce, + &hdr->credential.digest.nc, + &hdr->credential.digest.cnonce, + &hdr->credential.digest.qop, + &hdr->credential.digest.uri, + &cred_info->realm, + cred_info, +- method ); ++ method, response_algorithm->algorithm_type); + ++ AUTH_TRACE_((THIS_FILE, "Create digest response: %s", ++ (status == PJ_SUCCESS ? "success" : "failed"))); + if (status != PJ_SUCCESS) + return status; + + /* Compare digest. */ +- return (pj_stricmp(&digest, &hdr->credential.digest.response) == 0) ? ++ result = pj_stricmp(&digest, &hdr->credential.digest.response); ++ AUTH_TRACE_((THIS_FILE, "Digest comparison: %s", ++ result ? "failed" : "matched")); ++ ++ return (result == 0) ? + PJ_SUCCESS : PJSIP_EAUTHINVALIDDIGEST; + + } else { + pj_assert(!"Unsupported authentication scheme"); + return PJSIP_EINVALIDAUTHSCHEME; + } + } + + + /* +@@ -126,20 +168,22 @@ static pj_status_t pjsip_auth_verify( const pjsip_authorization_hdr *hdr, + PJ_DEF(pj_status_t) pjsip_auth_srv_verify( pjsip_auth_srv *auth_srv, + pjsip_rx_data *rdata, + int *status_code) + { + pjsip_authorization_hdr *h_auth; + pjsip_msg *msg = rdata->msg_info.msg; + pjsip_hdr_e htype; + pj_str_t acc_name; + pjsip_cred_info cred_info; + pj_status_t status; ++ const pjsip_auth_algorithm *algorithm; ++ + + PJ_ASSERT_RETURN(auth_srv && rdata, PJ_EINVAL); + PJ_ASSERT_RETURN(msg->type == PJSIP_REQUEST_MSG, PJSIP_ENOTREQUESTMSG); + + htype = auth_srv->is_proxy ? PJSIP_H_PROXY_AUTHORIZATION : + PJSIP_H_AUTHORIZATION; + + /* Initialize status with 200. */ + *status_code = 200; + +@@ -164,28 +208,39 @@ PJ_DEF(pj_status_t) pjsip_auth_srv_verify( pjsip_auth_srv *auth_srv, + } + + /* Check authorization scheme. */ + if (pj_stricmp(&h_auth->scheme, &pjsip_DIGEST_STR) == 0) + acc_name = h_auth->credential.digest.username; + else { + *status_code = auth_srv->is_proxy ? 407 : 401; + return PJSIP_EINVALIDAUTHSCHEME; + } + ++ algorithm = pjsip_auth_get_algorithm_by_iana_name( ++ &h_auth->credential.digest.algorithm); ++ ++ /* Check that algorithm is supported. */ ++ if (!algorithm || !pjsip_auth_is_algorithm_supported(algorithm->algorithm_type)) { ++ return PJSIP_EINVALIDALGORITHM; ++ } ++ + /* Find the credential information for the account. */ ++ pj_bzero(&cred_info, sizeof(cred_info)); ++ + if (auth_srv->lookup2) { + pjsip_auth_lookup_cred_param param; + + pj_bzero(¶m, sizeof(param)); + param.realm = auth_srv->realm; + param.acc_name = acc_name; + param.rdata = rdata; ++ param.auth_hdr = h_auth; + status = (*auth_srv->lookup2)(rdata->tp_info.pool, ¶m, &cred_info); + if (status != PJ_SUCCESS) { + *status_code = PJSIP_SC_FORBIDDEN; + return status; + } + } else { + status = (*auth_srv->lookup)(rdata->tp_info.pool, &auth_srv->realm, + &acc_name, &cred_info); + if (status != PJ_SUCCESS) { + *status_code = PJSIP_SC_FORBIDDEN; +@@ -202,47 +257,55 @@ PJ_DEF(pj_status_t) pjsip_auth_srv_verify( pjsip_auth_srv *auth_srv, + return status; + } + + + /* + * Add authentication challenge headers to the outgoing response in tdata. + * Application may specify its customized nonce and opaque for the challenge, + * or can leave the value to NULL to make the function fills them in with + * random characters. + */ +-PJ_DEF(pj_status_t) pjsip_auth_srv_challenge( pjsip_auth_srv *auth_srv, ++PJ_DEF(pj_status_t) pjsip_auth_srv_challenge2( pjsip_auth_srv *auth_srv, + const pj_str_t *qop, + const pj_str_t *nonce, + const pj_str_t *opaque, + pj_bool_t stale, +- pjsip_tx_data *tdata) ++ pjsip_tx_data *tdata, ++ const pjsip_auth_algorithm_type algorithm_type) + { + pjsip_www_authenticate_hdr *hdr; + char nonce_buf[16]; + pj_str_t random; ++ const pjsip_auth_algorithm *algorithm = ++ pjsip_auth_get_algorithm_by_type(algorithm_type); + + PJ_ASSERT_RETURN( auth_srv && tdata, PJ_EINVAL ); + ++ /* Check that algorithm is supported. */ ++ if (!algorithm || !pjsip_auth_is_algorithm_supported(algorithm_type)) { ++ return PJSIP_EINVALIDALGORITHM; ++ } ++ + random.ptr = nonce_buf; + random.slen = sizeof(nonce_buf); + + /* Create the header. */ + if (auth_srv->is_proxy) + hdr = pjsip_proxy_authenticate_hdr_create(tdata->pool); + else + hdr = pjsip_www_authenticate_hdr_create(tdata->pool); + + /* Initialize header. + * Note: only support digest authentication now. + */ + hdr->scheme = pjsip_DIGEST_STR; +- hdr->challenge.digest.algorithm = pjsip_MD5_STR; ++ pj_strdup(tdata->pool, &hdr->challenge.digest.algorithm, &algorithm->iana_name); + if (nonce) { + pj_strdup(tdata->pool, &hdr->challenge.digest.nonce, nonce); + } else { + pj_create_random_string(nonce_buf, sizeof(nonce_buf)); + pj_strdup(tdata->pool, &hdr->challenge.digest.nonce, &random); + } + if (opaque) { + pj_strdup(tdata->pool, &hdr->challenge.digest.opaque, opaque); + } else { + pj_create_random_string(nonce_buf, sizeof(nonce_buf)); +@@ -254,10 +317,20 @@ PJ_DEF(pj_status_t) pjsip_auth_srv_challenge( pjsip_auth_srv *auth_srv, + hdr->challenge.digest.qop.slen = 0; + } + pj_strdup(tdata->pool, &hdr->challenge.digest.realm, &auth_srv->realm); + hdr->challenge.digest.stale = stale; + + pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)hdr); + + return PJ_SUCCESS; + } + ++PJ_DEF(pj_status_t) pjsip_auth_srv_challenge( pjsip_auth_srv *auth_srv, ++ const pj_str_t *qop, ++ const pj_str_t *nonce, ++ const pj_str_t *opaque, ++ pj_bool_t stale, ++ pjsip_tx_data *tdata) ++{ ++ return pjsip_auth_srv_challenge2(auth_srv, qop, nonce, opaque, ++ stale, tdata, PJSIP_AUTH_ALGORITHM_MD5); ++} +-- +2.47.0 + diff --git a/third-party/pjproject/patches/config_site.h b/third-party/pjproject/patches/config_site.h index 0492b04812e..38afc021baf 100644 --- a/third-party/pjproject/patches/config_site.h +++ b/third-party/pjproject/patches/config_site.h @@ -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.