; Note: Using the same auth section for inbound and outbound
; authentication is not recommended. There is a difference in
; meaning for an empty realm setting between inbound and outbound
-; authentication uses. Look to the CLI config help
-; "config show help res_pjsip auth realm" or on https://docs.asterisk.org/
-; for the difference.
-;
-;auth_type=userpass ; Authentication type. May be
- ; "userpass" for plain text passwords or
- ; "md5" for pre-hashed credentials.
- ; (default: "userpass")
-;nonce_lifetime=32 ; Lifetime of a nonce associated with this
- ; authentication config (default: "32")
-;md5_cred= ; As an alternative to specifying a plain text password,
- ; you can hash the username, realm and password
- ; together one time and place the hash value here.
- ; The input to the hash function must be in the
- ; following format:
- ; <username>:<realm>:<password>
- ; For incoming authentication (asterisk is the UAS),
- ; the realm must match either the realm set in this object
- ; or the default set in in the "global" object.
- ;
- ; For outgoing authentication (asterisk is the UAC),
- ; the realm must match what the server will be sending
- ; in their WWW-Authenticate header. It can't be blank
- ; unless you expect the server to be sending a blank
- ; realm in the header.
- ; You can generate the hash with the following shell
- ; command:
- ; $ echo -n "myname:myrealm:mypassword" | md5sum
- ; Note the '-n'. You don't want a newline to be part
- ; of the hash. (default: "")
-;password= ; PlainText password used for authentication (default: "")
-;realm= ; For incoming authentication (asterisk is the UAS),
- ; this is the realm to be sent on WWW-Authenticate
- ; headers. If not specified, the global object's
- ; "default_realm" will be used.
- ;
- ; For outgoing authentication (asterisk is the UAC), this
- ; must either be the realm the server is expected to send,
- ; or left blank or contain a single '*' to automatically
- ; use the realm sent by the server. If you have multiple
- ; auth objects for an endpoint, the realm is also used to
- ; match the auth object to the realm the server sent.
- ;
- ; Using the same auth section for inbound and outbound
- ; authentication is not recommended. There is a difference in
- ; meaning for an empty realm setting between inbound and outbound
- ; authentication uses.
- ; (default: "")
-;type= ; Must be auth (default: "")
-;username= ; Username to use for account (default: "")
+; authentication uses.
+;
+; Note on Digest Algorithms: The currently supported digest algorithms are
+; "MD5", "SHA-256" and "SHA-512-256" but availability may be limited by
+; the versions of PJProject and OpenSSL installed. Run the CLI command
+; `pjproject show buildopts` to see the algorithms currently available and
+; see the documentation linked below for more info.
+;
+; Detailed discussion for this object, especially regarding hash algorithms
+; and realms can be found at
+; https://docs.asterisk.org/Configuration/Channel-Drivers/SIP/Configuring-res_pjsip/PJSIP-Authentication
+
+;type= ; Must be auth (default: "")
+
+;auth_type= ; Authentication mechanism.
+ ; Must be one of:
+ ; "digest" : The standard HTTP/SIP digest
+ ; authentication. "password" and/or one or more
+ ; "password_digest" parameters must also be specified.
+ ; "google_oauth": Google OAuth authentication used by
+ ; Google Voice.
+ ; "userpass" : (deprecated). Automatically converted
+ ; to "digest". Used to mean plain-text password but
+ ; that is now determined automatically.
+ ; "md5" : (deprecated) Automatically converted
+ ; to "digest". Used to mean pre-hashed password but
+ ; that is now determined automatically.
+ ; (default: "digest")
+
+;realm= ; For incoming authentication (asterisk is the UAS),
+ ; this is the realm to be sent on WWW-Authenticate
+ ; headers. If not specified, the global object's
+ ; "default_realm" will be used.
+ ;
+ ; For outgoing authentication (asterisk is the UAC), this
+ ; must either be the realm the server is expected to send,
+ ; or left blank or contain a single '*' to automatically
+ ; use the realm sent by the server. If you have multiple
+ ; auth objects for an endpoint, the realm is also used to
+ ; match the auth object to the realm the server sent.
+ ;
+ ; Using the same auth section for inbound and outbound
+ ; authentication is not recommended. There is a difference in
+ ; meaning for an empty realm setting between inbound and outbound
+ ; authentication uses.
+ ;
+ ; If more than one auth object with the same realm or
+ ; more than one wildcard auth object is associated to
+ ; an endpoint, only the first one of each defined on
+ ; the endpoint will be used.
+ ;
+ ; (default: "")
+
+;username= ; Username to use for account (Required)
+
+;password= ; PlainText password used for authentication (default: "")
+
+;password_digest= <digest-spec>
+ ; As an alternative to specifying a plain text password, you can
+ ; specify pre-computed digests.
+ ;
+ ; <digest-spec> = <IANA_digest_algorithm>:<hashed-credential>
+ ; <IANA_digest_algorithm>: One of the supported hash algorithms
+ ; which currently are "MD5", "SHA-256" and "SHA-512-256" but
+ ; see the note above.
+ ; <hashed-credential>: The result of passing the following
+ ; string through the selected hash algorithm:
+ ; <username>:<realm>:<password>
+ ; Example:
+ ; $ echo -n "fred:asterisk:mypass" | openssl dgst -md5
+ ; MD5(stdin)= 43a8d9be3da524f9a59ca0593d7b1b5d
+ ; would be specified as...
+;password_digest = MD5:43a8d9be3da524f9a59ca0593d7b1b5d
+ ; You can specify this parameter once for each algorithm.
+ ; See the documentation linked above for more info.
+
+;md5_cred= ; (deprecated) Will be automatically converted to a
+ ; "password_digest" parameter.
+
+;supported_algorithms_uas= <IANA_digest_algorithm>[,<IANA_digest_algorithm>]...
+ ; Specify the digest algorithms to offer when this auth object
+ ; is used by Asterisk acting as a UAS. Specify one or more of
+ ; the supported hash algorithms, which currently are "MD5",
+ ; "SHA-256" and "SHA-512-256", but see the note above.
+ ; The default is the value specified in the global object's
+ ; default_auth_algorithms_uas parameter.
+
+;supported_algorithms_uac= <IANA_digest_algorithm>[,<IANA_digest_algorithm>]...
+ ; Specify the digest algorithms to respond with when this auth
+ ; object is used by Asterisk acting as a UAC. Specify one or more of
+ ; the supported hash algorithms, which currently are "MD5",
+ ; "SHA-256" and "SHA-512-256", but see the note above.
+ ; The default is the value specified in the global object's
+ ; default_auth_algorithms_uac parameter.
+
+;nonce_lifetime=32 ; Lifetime of a nonce associated with this
+ ; authentication config (default: "32")
+
+; For the Google OAuth authentication mechanism, the following parameters are
+; required:
+;refresh_token= ; OAuth 2.0 refresh token
+;oauth_clientid= ; OAuth 2.0 application's client id
+;oauth_secret= ; OAuth 2.0 application's secret
;==========================DOMAIN_ALIAS SECTION OPTIONS=========================
; 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]
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
printf "%s\n" "#define HAVE_PJSIP_TLS_TRANSPORT_RESTART 1" >>confdefs.h
+printf "%s\n" "#define HAVE_PJSIP_AUTH_NEW_DIGESTS 1" >>confdefs.h
+
+
+
+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
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
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])
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
--- /dev/null
+"""Add fields to ps_auths to support new algorithms
+
+Revision ID: abdc9ede147d
+Revises: 44bd6dd914fa
+Create Date: 2024-10-27 15:26:25.165085
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = 'abdc9ede147d'
+down_revision = '44bd6dd914fa'
+
+from alembic import op
+import sqlalchemy as sa
+
+max_value_length = 1024
+
+def upgrade():
+ op.add_column('ps_auths', sa.Column('password_digest', sa.String(max_value_length)))
+ op.add_column('ps_auths', sa.Column('supported_algorithms_uas', sa.String(max_value_length)))
+ op.add_column('ps_auths', sa.Column('supported_algorithms_uac', sa.String(max_value_length)))
+ op.add_column('ps_globals', sa.Column('default_auth_algorithms_uas', sa.String(max_value_length)))
+ op.add_column('ps_globals', sa.Column('default_auth_algorithms_uac', sa.String(max_value_length)))
+
+
+def downgrade():
+ op.drop_column('ps_auths', 'password_digest')
+ op.drop_column('ps_auths', 'supported_algorithms_uas')
+ op.drop_column('ps_auths', 'supported_algorithms_uac')
+ op.drop_column('ps_globals', 'default_auth_algorithms_uas')
+ op.drop_column('ps_globals', 'default_auth_algorithms_uac')
/* 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
#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
};
/*!
- * \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);
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);
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 *);
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.
*
*/
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
*/
*/
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.
*
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;
}
#include "asterisk/logger.h"
#include "asterisk/sorcery.h"
#include "asterisk/cli.h"
+#include "asterisk/vector.h"
#include "include/res_pjsip_private.h"
#include "asterisk/res_pjsip_cli.h"
+#ifndef HAVE_PJSIP_AUTH_NEW_DIGESTS
+/*
+ * These are needed if the version of pjproject in use
+ * does not have the new digests.
+ * NOTE: We don't support AKA but we need to specify
+ * it to be compatible with the pjproject definition.
+ */
+#ifdef HAVE_OPENSSL
+#include "openssl/md5.h"
+#include "openssl/sha.h"
+#else
+#define MD5_DIGEST_LENGTH 16
+#define SHA256_DIGEST_LENGTH 32
+#endif
+
+const pjsip_auth_algorithm pjsip_auth_algorithms[] = {
+/* TYPE IANA name OpenSSL name */
+/* Raw digest byte length Hex representation length */
+ { PJSIP_AUTH_ALGORITHM_NOT_SET, {"", 0}, "",
+ 0, 0},
+ { PJSIP_AUTH_ALGORITHM_MD5, {"MD5", 3}, "MD5",
+ MD5_DIGEST_LENGTH, MD5_DIGEST_LENGTH * 2},
+ { PJSIP_AUTH_ALGORITHM_SHA256, {"SHA-256", 7}, "SHA256",
+ SHA256_DIGEST_LENGTH, SHA256_DIGEST_LENGTH * 2},
+ { PJSIP_AUTH_ALGORITHM_SHA512_256, {"SHA-512-256", 11}, "SHA512-256",
+ SHA256_DIGEST_LENGTH, SHA256_DIGEST_LENGTH * 2},
+ { PJSIP_AUTH_ALGORITHM_AKAV1_MD5, {"AKAv1-MD5", 9}, "",
+ MD5_DIGEST_LENGTH, MD5_DIGEST_LENGTH * 2},
+ { PJSIP_AUTH_ALGORITHM_AKAV1_MD5, {"AKAv2-MD5", 9}, "",
+ MD5_DIGEST_LENGTH, MD5_DIGEST_LENGTH * 2},
+ { PJSIP_AUTH_ALGORITHM_COUNT, {"", 0}, "",
+ 0, 0},
+};
+#endif
+
+const pjsip_auth_algorithm *ast_sip_auth_get_algorithm_by_type(
+ pjsip_auth_algorithm_type algorithm_type)
+{
+#ifdef HAVE_PJSIP_AUTH_NEW_DIGESTS
+ return pjsip_auth_get_algorithm_by_type(algorithm_type);
+#else
+ /*
+ * If we don't have a pjproject with the new algorithms, the
+ * only one we support is MD5.
+ */
+ if (algorithm_type == PJSIP_AUTH_ALGORITHM_MD5) {
+ return &pjsip_auth_algorithms[algorithm_type];
+ }
+ return NULL;
+#endif
+}
+
+const pjsip_auth_algorithm *ast_sip_auth_get_algorithm_by_iana_name(
+ const pj_str_t *iana_name)
+{
+#ifdef HAVE_PJSIP_AUTH_NEW_DIGESTS
+ return pjsip_auth_get_algorithm_by_iana_name(iana_name);
+#else
+ if (!iana_name) {
+ return NULL;
+ }
+ /*
+ * If we don't have a pjproject with the new algorithms, the
+ * only one we support is MD5. If iana_name is empty (but not NULL),
+ * the default is MD5.
+ */
+ if (iana_name->slen == 0 || pj_stricmp2(iana_name, "MD5") == 0) {
+ return &pjsip_auth_algorithms[PJSIP_AUTH_ALGORITHM_MD5];
+ }
+ return NULL;
+#endif
+}
+
+pj_bool_t ast_sip_auth_is_algorithm_supported(
+ pjsip_auth_algorithm_type algorithm_type)
+{
+#ifdef HAVE_PJSIP_AUTH_NEW_DIGESTS
+ return pjsip_auth_is_algorithm_supported(algorithm_type);
+#else
+ return algorithm_type == PJSIP_AUTH_ALGORITHM_MD5;
+#endif
+}
+
static void auth_destroy(void *obj)
{
struct ast_sip_auth *auth = obj;
+ int i = 0;
+
ast_string_field_free_memory(auth);
+
+ for (i = PJSIP_AUTH_ALGORITHM_NOT_SET + 1; i < PJSIP_AUTH_ALGORITHM_COUNT; i++) {
+ ast_free(auth->password_digests[i]);
+ }
+
+ AST_VECTOR_FREE(&auth->supported_algorithms_uac);
+ AST_VECTOR_FREE(&auth->supported_algorithms_uas);
}
static void *auth_alloc(const char *name)
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;
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"
};
return 0;
}
-static int auth_apply(const struct ast_sorcery *sorcery, void *obj)
+int ast_sip_auth_digest_algorithms_vector_init(const char *id,
+ struct pjsip_auth_algorithm_type_vector *algorithms, const char *agent_type,
+ const char *value)
{
- struct ast_sip_auth *auth = obj;
+ char *iana_names = ast_strdupa(value);
+ pj_str_t val;
int res = 0;
- if (ast_strlen_zero(auth->auth_user)) {
- ast_log(LOG_ERROR, "No authentication username for auth '%s'\n",
- ast_sorcery_object_get_id(auth));
+ ast_assert(algorithms != NULL);
+
+ if (AST_VECTOR_SIZE(algorithms)) {
+ AST_VECTOR_FREE(algorithms);
+ }
+ if (AST_VECTOR_INIT(algorithms, 4)) {
return -1;
}
- switch (auth->type) {
- case AST_SIP_AUTH_TYPE_MD5:
- if (ast_strlen_zero(auth->md5_creds)) {
- ast_log(LOG_ERROR, "'md5' authentication specified but no md5_cred "
- "specified for auth '%s'\n", ast_sorcery_object_get_id(auth));
+ while ((val.ptr = ast_strip(strsep(&iana_names, ",")))) {
+ const pjsip_auth_algorithm *algo;
+
+ if (ast_strlen_zero(val.ptr)) {
+ continue;
+ }
+ val.slen = strlen(val.ptr);
+
+ algo = ast_sip_auth_get_algorithm_by_iana_name(&val);
+ if (!algo) {
+ ast_log(LOG_WARNING, "%s: Unknown %s digest algorithm '%s' specified\n",
+ id, agent_type, val.ptr);
res = -1;
- } else if (strlen(auth->md5_creds) != PJSIP_MD5STRLEN) {
- ast_log(LOG_ERROR, "'md5' authentication requires digest of size '%d', but "
- "digest is '%d' in size for auth '%s'\n", PJSIP_MD5STRLEN, (int)strlen(auth->md5_creds),
- ast_sorcery_object_get_id(auth));
+ continue;
+ }
+ if (!ast_sip_auth_is_algorithm_supported(algo->algorithm_type)) {
+ ast_log(LOG_WARNING, "%s: %s digest algorithm '%s' is not supported by the version of OpenSSL in use\n",
+ id, agent_type, val.ptr);
res = -1;
+ continue;
+ }
+
+ if (AST_VECTOR_APPEND(algorithms, algo->algorithm_type)) {
+ AST_VECTOR_FREE(algorithms);
+ return -1;
+ }
+ }
+ return res;
+}
+
+static int uac_algorithms_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
+{
+ struct ast_sip_auth *auth = obj;
+
+ return ast_sip_auth_digest_algorithms_vector_init(ast_sorcery_object_get_id(auth),
+ &auth->supported_algorithms_uac, "UAC", var->value);
+}
+
+static int uas_algorithms_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
+{
+ struct ast_sip_auth *auth = obj;
+
+ return ast_sip_auth_digest_algorithms_vector_init(ast_sorcery_object_get_id(auth),
+ &auth->supported_algorithms_uas, "UAS", var->value);
+}
+
+int ast_sip_auth_digest_algorithms_vector_to_str(
+ const struct pjsip_auth_algorithm_type_vector *algorithms, char **buf)
+{
+ struct ast_str *str = NULL;
+ int i = 0;
+
+ if (!algorithms || !AST_VECTOR_SIZE(algorithms)) {
+ return 0;
+ }
+
+ str = ast_str_alloca(256);
+ if (!str) {
+ return -1;
+ }
+
+ for (i = 0; i < AST_VECTOR_SIZE(algorithms); ++i) {
+ const pjsip_auth_algorithm *algo = ast_sip_auth_get_algorithm_by_type(
+ AST_VECTOR_GET(algorithms, i));
+ ast_str_append(&str, 0, "%s" PJSTR_PRINTF_SPEC, i > 0 ? "," : "",
+ PJSTR_PRINTF_VAR(algo->iana_name));
+ }
+
+ *buf = ast_strdup(ast_str_buffer(str));
+
+ return *buf ? 0 : -1;
+}
+
+static int uac_algorithms_to_str(const void *obj, const intptr_t *args, char **buf)
+{
+ const struct ast_sip_auth *auth = obj;
+ return ast_sip_auth_digest_algorithms_vector_to_str(&auth->supported_algorithms_uac, buf);
+}
+
+static int uas_algorithms_to_str(const void *obj, const intptr_t *args, char **buf)
+{
+ const struct ast_sip_auth *auth = obj;
+ return ast_sip_auth_digest_algorithms_vector_to_str(&auth->supported_algorithms_uas, buf);
+}
+
+static int password_digest_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
+{
+ struct ast_sip_auth *auth = obj;
+ const char *auth_name = ast_sorcery_object_get_id(auth);
+ char *value = ast_strdupa(var->value);
+ char *unparsed_digest = NULL;
+
+ while ((unparsed_digest = ast_strsep(&value, ',', AST_STRSEP_TRIM))) {
+ const pjsip_auth_algorithm *algo;
+ char *iana_name;
+ char *digest;
+ struct ast_sip_auth_password_digest *pw;
+ pj_str_t pj_iana_name;
+
+ if (ast_strlen_zero(unparsed_digest)) {
+ continue;
+ }
+
+ if (strchr(unparsed_digest, ':') != NULL) {
+ iana_name = ast_strsep(&unparsed_digest, ':', AST_STRSEP_TRIM);
+ } else {
+ /*
+ * md5_cred doesn't have the algorithm name in front
+ * so we need to force it.
+ */
+ iana_name = "MD5";
}
- break;
- case AST_SIP_AUTH_TYPE_GOOGLE_OAUTH:
+ digest = unparsed_digest;
+
+ pj_iana_name = pj_str(iana_name);
+
+ algo = ast_sip_auth_get_algorithm_by_iana_name(&pj_iana_name);
+ if (!algo) {
+ ast_log(LOG_WARNING, "%s: Unknown password_digest algorithm '%s' specified\n",
+ auth_name, iana_name);
+ return -1;
+ }
+ if (!ast_sip_auth_is_algorithm_supported(algo->algorithm_type)) {
+ ast_log(LOG_WARNING, "%s: password_digest algorithm '%s' is not supported by the version of OpenSSL in use\n",
+ auth_name, iana_name);
+ return -1;
+ }
+ if (strlen(digest) != algo->digest_str_length) {
+ ast_log(LOG_WARNING, "%s: password_digest algorithm '%s' length (%d) must be %d\n",
+ auth_name, iana_name, (int)strlen(digest), (int)algo->digest_str_length);
+ return -1;
+ }
+
+ pw = ast_calloc(1, sizeof(*pw) + strlen(digest) + 1);
+ if (!pw) {
+ return -1;
+ }
+ pw->algorithm_type = algo->algorithm_type;
+ strcpy(pw->digest, digest); /* Safe */
+ auth->password_digests[pw->algorithm_type] = pw;
+ }
+
+ return 0;
+}
+
+static int password_digest_to_str(const void *obj, const intptr_t *args, char **buf)
+{
+ const struct ast_sip_auth *auth = obj;
+ struct ast_str *str = ast_str_alloca(256);
+ int i = 0;
+ int count = 0;
+
+ for (i = PJSIP_AUTH_ALGORITHM_NOT_SET + 1; i < PJSIP_AUTH_ALGORITHM_COUNT; i++) {
+ struct ast_sip_auth_password_digest *pw =
+ auth->password_digests[i];
+ const pjsip_auth_algorithm *algorithm;
+
+ if (!pw) {
+ continue;
+ }
+
+ algorithm = ast_sip_auth_get_algorithm_by_type(pw->algorithm_type);
+
+ ast_str_append(&str, 0, "%s" PJSTR_PRINTF_SPEC ":%s", count > 0 ? "," : "",
+ PJSTR_PRINTF_VAR(algorithm->iana_name), pw->digest);
+ count++;
+ }
+
+ *buf = ast_strdup(ast_str_buffer(str));
+
+ return 0;
+}
+
+static int md5cred_to_str(const void *obj, const intptr_t *args, char **buf)
+{
+ const struct ast_sip_auth *auth = obj;
+
+ if (auth->password_digests[PJSIP_AUTH_ALGORITHM_MD5]) {
+ *buf = ast_strdup(auth->password_digests[PJSIP_AUTH_ALGORITHM_MD5]->digest);
+ }
+
+ return 0;
+}
+
+int ast_sip_auth_is_algorithm_available(const struct ast_sip_auth *auth,
+ const struct pjsip_auth_algorithm_type_vector *algorithms,
+ pjsip_auth_algorithm_type algorithm_type)
+{
+ int i;
+
+ if (!algorithms) {
+ return 0;
+ }
+
+ for (i = 0; i < AST_VECTOR_SIZE(algorithms); ++i) {
+ if (AST_VECTOR_GET(algorithms, i) == algorithm_type) {
+ if (auth->password_digests[algorithm_type] || !ast_strlen_zero(auth->auth_pass)) {
+ return 1;
+ }
+ }
+ }
+
+ return 0;
+}
+
+const char *ast_sip_auth_get_creds(const struct ast_sip_auth *auth,
+ const pjsip_auth_algorithm_type algorithm_type, int *cred_type)
+{
+ struct ast_sip_auth_password_digest *pw_digest =
+ auth->password_digests[algorithm_type];
+
+ if (pw_digest) {
+ *cred_type = PJSIP_CRED_DATA_DIGEST;
+ return pw_digest->digest;
+ }
+
+ *cred_type = PJSIP_CRED_DATA_PLAIN_PASSWD;
+ return auth->auth_pass;
+}
+
+static int check_algorithm(const struct ast_sip_auth *auth,
+ const pjsip_auth_algorithm_type algorithm_type, const char *which_supported)
+{
+ const pjsip_auth_algorithm *algo = ast_sip_auth_get_algorithm_by_type(algorithm_type);
+ struct ast_sip_auth_password_digest *pw_digest =
+ auth->password_digests[algorithm_type];
+
+ if (!pw_digest && ast_strlen_zero(auth->auth_pass)) {
+ ast_log(LOG_ERROR, "%s: No plain text or digest password found for algorithm "
+ PJSTR_PRINTF_SPEC " in supported_algorithms_%s\n",
+ ast_sorcery_object_get_id(auth), PJSTR_PRINTF_VAR(algo->iana_name), which_supported);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int auth_apply(const struct ast_sorcery *sorcery, void *obj)
+{
+ struct ast_sip_auth *auth = obj;
+ const char *id = ast_sorcery_object_get_id(auth);
+ int i = 0;
+ int res = 0;
+
+ if (ast_strlen_zero(auth->auth_user)) {
+ ast_log(LOG_ERROR, "%s: No authentication username\n", id);
+ return -1;
+ }
+
+ if (auth->type == AST_SIP_AUTH_TYPE_GOOGLE_OAUTH) {
if (ast_strlen_zero(auth->refresh_token)
|| ast_strlen_zero(auth->oauth_clientid)
|| ast_strlen_zero(auth->oauth_secret)) {
- ast_log(LOG_ERROR, "'google_oauth' authentication specified but refresh_token,"
- " oauth_clientid, or oauth_secret not specified for auth '%s'\n",
- ast_sorcery_object_get_id(auth));
+ ast_log(LOG_ERROR, "%s: 'google_oauth' authentication specified but refresh_token,"
+ " oauth_clientid, or oauth_secret not specified\n", id);
res = -1;
}
- break;
- case AST_SIP_AUTH_TYPE_USER_PASS:
- case AST_SIP_AUTH_TYPE_ARTIFICIAL:
- break;
+ return res;
+ }
+
+ if (AST_VECTOR_SIZE(&auth->supported_algorithms_uas) == 0) {
+ char *default_algo_uas = ast_alloca(AST_SIP_AUTH_MAX_SUPPORTED_ALGORITHMS_LENGTH + 1);
+ ast_sip_get_default_auth_algorithms_uas(default_algo_uas, AST_SIP_AUTH_MAX_SUPPORTED_ALGORITHMS_LENGTH);
+ ast_sip_auth_digest_algorithms_vector_init(id, &auth->supported_algorithms_uas, "UAS", default_algo_uas);
+ }
+ if (AST_VECTOR_SIZE(&auth->supported_algorithms_uac) == 0) {
+ char *default_algo_uac = ast_alloca(AST_SIP_AUTH_MAX_SUPPORTED_ALGORITHMS_LENGTH + 1);
+ ast_sip_get_default_auth_algorithms_uac(default_algo_uac, AST_SIP_AUTH_MAX_SUPPORTED_ALGORITHMS_LENGTH);
+ ast_sip_auth_digest_algorithms_vector_init(id, &auth->supported_algorithms_uac, "UAC", default_algo_uac);
+ }
+
+ for (i = 0; i < AST_VECTOR_SIZE(&auth->supported_algorithms_uas); i++) {
+ res += check_algorithm(auth, AST_VECTOR_GET(&auth->supported_algorithms_uas, i), "uas");
+ }
+
+ for (i = 0; i < AST_VECTOR_SIZE(&auth->supported_algorithms_uac); i++) {
+ res += check_algorithm(auth, AST_VECTOR_GET(&auth->supported_algorithms_uac, i), "uac");
}
return res;
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)
{
"", 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);
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);
#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
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;
{
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,
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;
}
}
}
+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;
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;
}
</configOption>
</configObject>
<configObject name="auth">
+ <!--
+ Be sure to update the following documentation page when making changes to this object:
+ https://docs.asterisk.org/Configuration/Channel-Drivers/SIP/Configuring-res_pjsip/PJSIP-Authentication
+ -->
<synopsis>Authentication type</synopsis>
<description><para>
Authentication objects hold the authentication information for use
by other objects such as <literal>endpoints</literal> or <literal>registrations</literal>.
This also allows for multiple objects to use a single auth object. See
- the <literal>auth_type</literal> config option for password style choices.
- </para></description>
- <configOption name="auth_type" default="userpass">
+ the <literal>auth_type</literal> config option for security mechanism choices.
+ </para>
+ <note><para>
+ See the link below for detailed discussion of this object especially concerning
+ realms and digest hash algorithms.
+ </para>
+ <para>
+ https://docs.asterisk.org/Configuration/Channel-Drivers/SIP/Configuring-res_pjsip/PJSIP-Authentication
+ </para>
+ </note>
+ </description>
+ <see-also>
+ <ref type="link">https://docs.asterisk.org/Configuration/Channel-Drivers/SIP/Configuring-res_pjsip/PJSIP-Authentication</ref>
+ </see-also>
+ <configOption name="auth_type" default="digest">
<synopsis>Authentication type</synopsis>
<description><para>
- This option specifies which of the password style config options should be read
- when trying to authenticate an endpoint inbound request. If set to <literal>userpass</literal>
- then we'll read from the 'password' option. For <literal>md5</literal> we'll read
- from 'md5_cred'. If set to <literal>google_oauth</literal> then we'll read from the
- refresh_token/oauth_clientid/oauth_secret fields. The following values are valid:
+ If set to <literal>google_oauth</literal> then we'll read from the
+ refresh_token/oauth_clientid/oauth_secret parameters.
+ If set to <literal>digest</literal> then we'll read from the
+ <literal>password</literal> and/or <literal>password_digest</literal>
+ parameters. The older <literal>md5</literal> and <literal>userpass</literal>
+ values are deprecated and converted to <literal>digest</literal>.
</para>
<enumlist>
- <enum name="md5"/>
- <enum name="userpass"/>
- <enum name="google_oauth"/>
+ <enum name="userpass"><para>Deprecated. Use <literal>digest</literal>.</para></enum>
+ <enum name="md5"><para>Deprecated. Use <literal>digest</literal>.</para></enum>
+ <enum name="google_oauth"><para>If selected, the <literal>refresh_token</literal>,
+ <literal>oauth_clientid</literal> and <literal>oauth_secret</literal>
+ parameters must be provided.</para></enum>
+ <enum name="digest"><para>If selected, the <literal>password</literal>
+ and/or one or more <literal>password_digest</literal>
+ parameters must be provided.</para></enum>
</enumlist>
<para>
</para>
- <note>
- <para>
- This setting only describes whether the password is in
- plain text or has been pre-hashed with MD5. It doesn't describe
- the acceptable digest algorithms we'll accept in a received
- challenge.
- </para>
- </note>
</description>
</configOption>
- <configOption name="nonce_lifetime" default="32">
- <synopsis>Lifetime of a nonce associated with this authentication config.</synopsis>
+ <configOption name="username">
+ <synopsis>Username to use for account</synopsis>
</configOption>
- <configOption name="md5_cred" default="">
- <synopsis>MD5 Hash used for authentication.</synopsis>
- <description><para>
- Only used when auth_type is <literal>md5</literal>.
+ <configOption name="password">
+ <synopsis>Plain text password used for authentication.</synopsis>
+ <description><para>Only used when auth_type is <literal>digest</literal>.</para></description>
+ </configOption>
+ <configOption name="password_digest" default="">
+ <synopsis>One or more pre-computed hashes used for authentication.</synopsis>
+ <description><para>Only used when auth_type is <literal>digest</literal>.
As an alternative to specifying a plain text password,
- you can hash the username, realm and password
- together one time and place the hash value here.
- The input to the hash function must be in the
- following format:
- </para>
- <para>
- </para>
- <para>
- <username>:<realm>:<password>
- </para>
- <para>
- </para>
- <para>
- For incoming authentication (asterisk is the server),
- the realm must match either the realm set in this object
- or the <variable>default_realm</variable> set in in the
- <replaceable>global</replaceable> object.
- </para>
- <para>
- </para>
- <para>
- For outgoing authentication (asterisk is the UAC),
- the realm must match what the server will be sending
- in their WWW-Authenticate header. It can't be blank
- unless you expect the server to be sending a blank
- realm in the header. You can't use pre-hashed
- passwords with a wildcard auth object.
- You can generate the hash with the following shell
- command:
+ you can specify one or more pre-computed digests separated by
+ commas.
</para>
<para>
+ <literal>password_digest= <digest-spec>[,<digest_spec>]...</literal>
</para>
+ <enumlist>
+ <enum name="<digest-spec>"><para><hash-algorithm>:<hashed-credential></para></enum>
+ <enum name="<hash-algorithm>"><para>One of the supported hash algorithms
+ which currently are</para>
+ <enumlist>
+ <enum name="MD5"><para>Supported by all versions of OpenSSL and pjproject</para></enum>
+ <enum name="SHA-256"><para>Supported by OpenSSL versions >> 1.0.0 and pjproject versions >= 2.15.1</para></enum>
+ <enum name="SHA-512-256"><para>Supported by OpenSSL versions >= 1.1.1 and pjproject versions >= 2.15.1</para></enum>
+ </enumlist>
+ <para>You can see the current list by running the CLI command
+ <literal>pjproject show buildopts</literal>.
+ </para></enum>
+ <enum name="<hashed-credential>">
+ <para>The result of passing the following string through
+ the selected hash algorithm:
+ <literal><username>:<realm>:<password></literal>
+ </para>
+ </enum>
+ </enumlist>
+ <para>You can create the hash by piping the string into the appropriate
+ hash/checksum program. See the description for the <literal>realm</literal>
+ parameter for info on how to set it.</para>
+ <example>
+ $ echo -n "myname:myrealm:mypassword" | openssl dgst -md5
+ MD5(stdin)= dce9ccd0a69e3ef90d8b9bf725053e78
+ </example>
+ <para>You would then set:</para>
+ <example>
+ password_digest = md5:dce9ccd0a69e3ef90d8b9bf725053e78
+ </example>
+ </description>
+ </configOption>
+ <configOption name="md5_cred" default="">
+ <synopsis>MD5 Hash used for authentication. (deprecated)</synopsis>
+ <description><para>Use the <literal>password_digest</literal> parameter instead.
+ If supplied, a <literal>password_digest</literal> parameter will be created
+ for it.
+ </para></description>
+ </configOption>
+ <configOption name="supported_algorithms_uac">
+ <synopsis>Comma separated list of algorithms to support when this auth is used as a UAC</synopsis>
+ <description><para>Valid values:</para>
+ <enumlist>
+ <enum name="md5"><para>Supported by all versions of OpenSSL and pjproject</para></enum>
+ <enum name="sha-256"><para>Supported by all versions of OpenSSL but only pjproject versions > 2.14.1</para></enum>
+ <enum name="sha-512-256"><para>Supported by OpenSSL versions >= 1.1.1 and pjproject versions > 2.14.1</para></enum>
+ </enumlist>
<para>
- $ echo -n "myname:myrealm:mypassword" | md5sum
+ The default may be specified by the
+ <literal>default_auth_algorithms_uac</literal> parameter in
+ the global object. If that's not specified, the default is "MD5".
</para>
+ </description>
+ </configOption>
+ <configOption name="supported_algorithms_uas">
+ <synopsis>Comma separated list of algorithms to support when this auth is used as a UAS</synopsis>
+ <description><para>Valid values:</para>
+ <enumlist>
+ <enum name="md5"><para>Supported by all versions of OpenSSL and pjproject</para></enum>
+ <enum name="sha-256"><para>Supported by all versions of OpenSSL but only pjproject versions > 2.14.1</para></enum>
+ <enum name="sha-512-256"><para>Supported by OpenSSL versions >= 1.1.1 and pjproject versions > 2.14.1</para></enum>
+ </enumlist>
<para>
+ The default may be specified by the
+ <literal>default_auth_algorithms_uas</literal> parameter in
+ the global object. If that's not specified, the default is "MD5".
</para>
- <para>
- Note the '-n'. You don't want a newline to be part
- of the hash.
- </para></description>
- </configOption>
- <configOption name="password">
- <synopsis>Plain text password used for authentication.</synopsis>
- <description><para>Only used when auth_type is <literal>userpass</literal>.</para></description>
+ </description>
</configOption>
<configOption name="refresh_token">
<synopsis>OAuth 2.0 refresh token</synopsis>
<note>
<para>
If more than one auth object with the same realm or
- more than one wildcard auth object associated to
- an endpoint, we can only use the first one of
- each defined on the endpoint.
+ more than one wildcard auth object is associated to
+ an endpoint, only the first one of each defined on
+ the endpoint will be used.
</para>
</note>
</description>
</configOption>
+ <configOption name="nonce_lifetime" default="32">
+ <synopsis>Lifetime of a nonce associated with this authentication config.</synopsis>
+ </configOption>
<configOption name="type">
<synopsis>Must be 'auth'</synopsis>
</configOption>
- <configOption name="username">
- <synopsis>Username to use for account</synopsis>
- </configOption>
</configObject>
<configObject name="domain_alias">
<synopsis>Domain Alias</synopsis>
RFC 3261 specifies this as a SHOULD requirement.
</para></description>
</configOption>
+ <configOption name="default_auth_algorithms_uas" default="no">
+ <synopsis>List of default authentication algorithms to support when Asterisk is UAS</synopsis>
+ <description><para>Valid values:</para>
+ <enumlist>
+ <enum name="md5"><para>Supported by all versions of OpenSSL and pjproject</para></enum>
+ <enum name="sha-256"><para>Supported by all versions of OpenSSL but only pjproject versions > 2.14.1</para></enum>
+ <enum name="sha-512-256"><para>Supported by OpenSSL versions >= 1.1.1 and pjproject versions > 2.14.1</para></enum>
+ </enumlist>
+ <para>If not specified, the default is <literal>MD5</literal> only.</para>
+ </description>
+ </configOption>
+ <configOption name="default_auth_algorithms_uac" default="no">
+ <synopsis>List of default authentication algorithms to support when Asterisk is UAC</synopsis>
+ <description><para>Valid values:</para>
+ <enumlist>
+ <enum name="md5"><para>Supported by all versions of OpenSSL and pjproject</para></enum>
+ <enum name="sha-256"><para>Supported by all versions of OpenSSL but only pjproject versions > 2.14.1</para></enum>
+ <enum name="sha-512-256"><para>Supported by OpenSSL versions >= 1.1.1 and pjproject versions > 2.14.1</para></enum>
+ </enumlist>
+ <para>If not specified, the default is <literal>MD5</literal> only.</para>
+ </description>
+ </configOption>
</configObject>
</configFile>
</configInfo>
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;
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;
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;
}
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 */
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);
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;
}
#include "asterisk/strings.h"
#include "asterisk/test.h"
+/*!
+ * \file
+ * \brief PJSIP UAS Authentication
+ *
+ * This module handles authentication when Asterisk is the UAS.
+ *
+ */
+
/*** MODULEINFO
<depend>pjproject</depend>
<depend>res_pjsip</depend>
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));
}
/*!
* \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);
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;
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
*/
"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;
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);
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
*
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;
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);
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 = {
* at the top of the source tree.
*/
+/*!
+ * \file
+ * \brief PJSIP UAC Authentication
+ *
+ * This module handles authentication when Asterisk is the UAC.
+ *
+ */
+
/*** MODULEINFO
<depend>pjproject</depend>
<depend>res_pjsip</depend>
#include "asterisk/strings.h"
#include "asterisk/vector.h"
-pj_str_t supported_digest_algorithms[] = {
- { "MD5", 3}
-};
-
/*!
* \internal
* \brief Determine proper authenticate header
/*!
* \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));
}
/*!
* 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.
*
* 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
* 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
* 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) {
* 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);
}
/*
* 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);
}
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"));
}
/*!
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
*/
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);
}
/*
* 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) {
* 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;
}
* Load pjproject with the valid credentials for the Authentication headers
* received on the 401 or 407 response.
*/
- status = set_outbound_authentication_credentials(&auth_sess, &auth_objects_vector, challenge, &realms);
+ status = SCOPE_CALL_WITH_RESULT(-1, pj_status_t, set_auth_creds, id, &auth_sess, &auth_objects_vector, challenge, &realms);
+ if (status != PJ_SUCCESS && status != PJSIP_ENOCREDENTIAL) {
+ pj_err = ast_alloca(PJ_ERR_MSG_SIZE);
+ }
+
switch (status) {
case PJ_SUCCESS:
break;
case PJSIP_ENOCREDENTIAL:
ast_log(LOG_WARNING,
- "%s: '%s': No auth objects matching realm(s) '%s' from challenge found.\n", id_type, id,
- realms ? ast_str_buffer(realms) : "<none>");
+ "%s:%s: No auth objects matching realm/algorithm(s) '%s' from challenge found.\n",
+ id, src_name, realms ? ast_str_buffer(realms) : "<none>");
res = -1;
goto cleanup;
default:
- ast_log(LOG_WARNING, "%s: '%s': Failed to set authentication credentials\n", id_type, id);
+ pj_strerror(status, pj_err, PJ_ERR_MSG_SIZE);
+ ast_log(LOG_WARNING, "%s:%s: Failed to set authentication credentials: %s\n",
+ id, src_name, pj_err);
res = -1;
goto cleanup;
}
* 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:
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:
/*
* did the matching but you never know.
*/
ast_log(LOG_WARNING,
- "%s: '%s': No auth objects matching realm(s) '%s' from challenge found.\n", id_type, id,
- realms ? ast_str_buffer(realms) : "<none>");
+ "%s:%s: No auth objects matching realm(s) '%s' from challenge found.\n",
+ id, src_name, realms ? ast_str_buffer(realms) : "<none>");
break;
case PJSIP_EAUTHSTALECOUNT:
+ pj_strerror(status, pj_err, PJ_ERR_MSG_SIZE);
ast_log(LOG_WARNING,
- "%s: '%s': Unable to create request with auth. Number of stale retries exceeded.\n",
- id_type, id);
+ "%s:%s: Unable to create request with auth: %s\n",
+ id, src_name, pj_err);
break;
case PJSIP_EFAILEDCREDENTIAL:
- ast_log(LOG_WARNING, "%s: '%s': Authentication credentials not accepted by server.\n",
- id_type, id);
+ pj_strerror(status, pj_err, PJ_ERR_MSG_SIZE);
+ ast_log(LOG_WARNING, "%s:%s: Authentication credentials not accepted by server. %s\n",
+ id, src_name, pj_err);
break;
default:
- ast_log(LOG_WARNING, "%s: '%s': Unable to create request with auth. Unknown failure.\n",
- id_type, id);
+ pj_strerror(status, pj_err, PJ_ERR_MSG_SIZE);
+ ast_log(LOG_WARNING, "%s:%s: Unable to create request with auth: %s\n",
+ id, src_name, pj_err);
break;
}
res = -1;
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 = {
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])
#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.