]> git.ipfire.org Git - thirdparty/asterisk.git/commitdiff
Add SHA-256 and SHA-512-256 as authentication digest algorithms
authorGeorge Joseph <gjoseph@sangoma.com>
Thu, 17 Oct 2024 14:02:08 +0000 (08:02 -0600)
committerAsterisk Development Team <asteriskteam@digium.com>
Thu, 23 Jan 2025 18:39:41 +0000 (18:39 +0000)
* Refactored pjproject code to support the new algorithms and
added a patch file to third-party/pjproject/patches

* Added new parameters to the pjsip auth object:
  * password_digest = <algorithm>:<digest>
  * supported_algorithms_uac = List of algorithms to support
    when acting as a UAC.
  * supported_algorithms_uas = List of algorithms to support
    when acting as a UAS.
  See the auth object in pjsip.conf.sample for detailed info.

* Updated both res_pjsip_authenticator_digest.c (for UAS) and
res_pjsip_outbound_authentocator_digest.c (UAC) to suport the
new algorithms.

The new algorithms are only available with the bundled version
of pjproject, or an external version > 2.14.1.  OpenSSL version
1.1.1 or greater is required to support SHA-512-256.

Resolves: #948

UserNote: The SHA-256 and SHA-512-256 algorithms are now available
for authentication as both a UAS and a UAC.

(cherry picked from commit 1933548d41b3fba4bc90b3d3c0cc20f142707966)

15 files changed:
configs/samples/pjsip.conf.sample
configure
configure.ac
contrib/ast-db-manage/config/versions/abdc9ede147d_add_fields_to_ps_auths_to_support_new_.py [new file with mode: 0644]
include/asterisk/autoconfig.h.in
include/asterisk/res_pjsip.h
res/res_pjproject.c
res/res_pjsip/config_auth.c
res/res_pjsip/config_global.c
res/res_pjsip/pjsip_config.xml
res/res_pjsip/pjsip_distributor.c
res/res_pjsip_authenticator_digest.c
res/res_pjsip_outbound_authenticator_digest.c
third-party/pjproject/configure.m4
third-party/pjproject/patches/config_site.h

index c48f38240c505d8551e456148ed137f7b12e1ffd..c399ba8a24489a89649bbad5832817f176ef0dde 100644 (file)
 ;  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]
index f1da11a7341548681b39b80de668694d29b616ae..5ec61226d4fe9c120f9c551362391901d0717c7a 100755 (executable)
--- a/configure
+++ b/configure
@@ -935,6 +935,10 @@ PBX_POPT
 POPT_DIR
 POPT_INCLUDE
 POPT_LIB
+PBX_PJSIP_AUTH_NEW_DIGESTS
+PJSIP_AUTH_NEW_DIGESTS_DIR
+PJSIP_AUTH_NEW_DIGESTS_INCLUDE
+PJSIP_AUTH_NEW_DIGESTS_LIB
 PBX_PJSIP_TLS_TRANSPORT_RESTART
 PJSIP_TLS_TRANSPORT_RESTART_DIR
 PJSIP_TLS_TRANSPORT_RESTART_INCLUDE
@@ -22026,6 +22030,9 @@ printf "%s\n" "#define HAVE_PJPROJECT_ON_VALID_ICE_PAIR_CALLBACK 1" >>confdefs.h
 printf "%s\n" "#define HAVE_PJSIP_TLS_TRANSPORT_RESTART 1" >>confdefs.h
 
 
+printf "%s\n" "#define HAVE_PJSIP_AUTH_NEW_DIGESTS 1" >>confdefs.h
+
+
 
 
 
@@ -24218,6 +24225,18 @@ PBX_PJSIP_TLS_TRANSPORT_RESTART=0
 
 
 
+
+PJSIP_AUTH_NEW_DIGESTS_DESCRIP="PJSIP Auth new digests like SHA-256 and SHA-512-256"
+PJSIP_AUTH_NEW_DIGESTS_OPTION=pjsip
+PJSIP_AUTH_NEW_DIGESTS_DIR=${PJPROJECT_DIR}
+
+PBX_PJSIP_AUTH_NEW_DIGESTS=0
+
+
+
+
+
+
 fi
 
 
@@ -39683,6 +39702,102 @@ _ACEOF
 fi
 
 
+
+if test "x${PBX_PJSIP_AUTH_NEW_DIGESTS}" != "x1" -a "${USE_PJSIP_AUTH_NEW_DIGESTS}" != "no"; then
+   pbxlibdir=""
+   # if --with-PJSIP_AUTH_NEW_DIGESTS=DIR has been specified, use it.
+   if test "x${PJSIP_AUTH_NEW_DIGESTS_DIR}" != "x"; then
+      if test -d ${PJSIP_AUTH_NEW_DIGESTS_DIR}/lib; then
+         pbxlibdir="-L${PJSIP_AUTH_NEW_DIGESTS_DIR}/lib"
+      else
+         pbxlibdir="-L${PJSIP_AUTH_NEW_DIGESTS_DIR}"
+      fi
+   fi
+
+      ast_ext_lib_check_save_CFLAGS="${CFLAGS}"
+      CFLAGS="${CFLAGS} $PJPROJECT_CFLAGS"
+      { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for pjsip_auth_get_algorithm_by_type in -lpjsip" >&5
+printf %s "checking for pjsip_auth_get_algorithm_by_type in -lpjsip... " >&6; }
+if test ${ac_cv_lib_pjsip_pjsip_auth_get_algorithm_by_type+y}
+then :
+  printf %s "(cached) " >&6
+else $as_nop
+  ac_check_lib_save_LIBS=$LIBS
+LIBS="-lpjsip ${pbxlibdir} $PJPROJECT_LIB $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+/* Override any GCC internal prototype to avoid an error.
+   Use char because int might match the return type of a GCC
+   builtin and then its argument prototype would still apply.  */
+char pjsip_auth_get_algorithm_by_type ();
+int
+main (void)
+{
+return pjsip_auth_get_algorithm_by_type ();
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"
+then :
+  ac_cv_lib_pjsip_pjsip_auth_get_algorithm_by_type=yes
+else $as_nop
+  ac_cv_lib_pjsip_pjsip_auth_get_algorithm_by_type=no
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.beam \
+    conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_pjsip_pjsip_auth_get_algorithm_by_type" >&5
+printf "%s\n" "$ac_cv_lib_pjsip_pjsip_auth_get_algorithm_by_type" >&6; }
+if test "x$ac_cv_lib_pjsip_pjsip_auth_get_algorithm_by_type" = xyes
+then :
+  AST_PJSIP_AUTH_NEW_DIGESTS_FOUND=yes
+else $as_nop
+  AST_PJSIP_AUTH_NEW_DIGESTS_FOUND=no
+fi
+
+      CFLAGS="${ast_ext_lib_check_save_CFLAGS}"
+
+
+   # now check for the header.
+   if test "${AST_PJSIP_AUTH_NEW_DIGESTS_FOUND}" = "yes"; then
+      PJSIP_AUTH_NEW_DIGESTS_LIB="${pbxlibdir} -lpjsip $PJPROJECT_LIB"
+      # if --with-PJSIP_AUTH_NEW_DIGESTS=DIR has been specified, use it.
+      if test "x${PJSIP_AUTH_NEW_DIGESTS_DIR}" != "x"; then
+         PJSIP_AUTH_NEW_DIGESTS_INCLUDE="-I${PJSIP_AUTH_NEW_DIGESTS_DIR}/include"
+      fi
+      PJSIP_AUTH_NEW_DIGESTS_INCLUDE="${PJSIP_AUTH_NEW_DIGESTS_INCLUDE} $PJPROJECT_CFLAGS"
+
+         # check for the header
+         ast_ext_lib_check_saved_CPPFLAGS="${CPPFLAGS}"
+         CPPFLAGS="${CPPFLAGS} ${PJSIP_AUTH_NEW_DIGESTS_INCLUDE}"
+         ac_fn_c_check_header_compile "$LINENO" "pjsip.h" "ac_cv_header_pjsip_h" "$ac_includes_default"
+if test "x$ac_cv_header_pjsip_h" = xyes
+then :
+  PJSIP_AUTH_NEW_DIGESTS_HEADER_FOUND=1
+else $as_nop
+  PJSIP_AUTH_NEW_DIGESTS_HEADER_FOUND=0
+fi
+
+         CPPFLAGS="${ast_ext_lib_check_saved_CPPFLAGS}"
+
+      if test "x${PJSIP_AUTH_NEW_DIGESTS_HEADER_FOUND}" = "x0" ; then
+         PJSIP_AUTH_NEW_DIGESTS_LIB=""
+         PJSIP_AUTH_NEW_DIGESTS_INCLUDE=""
+      else
+
+         PBX_PJSIP_AUTH_NEW_DIGESTS=1
+         cat >>confdefs.h <<_ACEOF
+#define HAVE_PJSIP_AUTH_NEW_DIGESTS 1
+_ACEOF
+
+      fi
+   fi
+fi
+
+
       fi
    fi
 
index ea570dbca58ceeaa3b0d8535fc9573388711c419..6acca786d01adcebad314200a88942a0f8ecb936 100644 (file)
@@ -620,6 +620,7 @@ AST_EXT_LIB_SETUP_OPTIONAL([PJSIP_ENDPOINT_COMPACT_FORM], [PJSIP Compact Form Su
 AST_EXT_LIB_SETUP_OPTIONAL([PJSIP_TRANSPORT_DISABLE_CONNECTION_REUSE], [PJSIP Transport Connection Reuse Disabling], [PJPROJECT], [pjsip])
 AST_EXT_LIB_SETUP_OPTIONAL([PJSIP_OAUTH_AUTHENTICATION], [PJSIP OAuth Authentication Support], [PJPROJECT], [pjsip])
 AST_EXT_LIB_SETUP_OPTIONAL([PJSIP_TLS_TRANSPORT_RESTART], [PJSIP TLS Transport Restart Support], [PJPROJECT], [pjsip])
+AST_EXT_LIB_SETUP_OPTIONAL([PJSIP_AUTH_NEW_DIGESTS], [PJSIP Auth new digests like SHA-256 and SHA-512-256], [PJPROJECT], [pjsip])
 fi
 
 AST_EXT_LIB_SETUP([POPT], [popt], [popt])
@@ -2546,6 +2547,7 @@ if test "$USE_PJPROJECT" != "no" ; then
          AST_EXT_LIB_CHECK([PJSIP_AUTH_CLT_DEINIT], [pjsip], [pjsip_auth_clt_deinit], [pjsip.h], [$PJPROJECT_LIB], [$PJPROJECT_CFLAGS])
          AST_EXT_LIB_CHECK([PJSIP_TSX_LAYER_FIND_TSX2], [pjsip], [pjsip_tsx_layer_find_tsx2], [pjsip.h], [$PJPROJECT_LIB], [$PJPROJECT_CFLAGS])
          AST_EXT_LIB_CHECK([PJSIP_TLS_TRANSPORT_RESTART], [pjsip], [pjsip_tls_transport_restart], [pjsip.h], [$PJPROJECT_LIB], [$PJPROJECT_CFLAGS])
+         AST_EXT_LIB_CHECK([PJSIP_AUTH_NEW_DIGESTS], [pjsip], [pjsip_auth_get_algorithm_by_type], [pjsip.h], [$PJPROJECT_LIB], [$PJPROJECT_CFLAGS])
       fi
    fi
 
diff --git a/contrib/ast-db-manage/config/versions/abdc9ede147d_add_fields_to_ps_auths_to_support_new_.py b/contrib/ast-db-manage/config/versions/abdc9ede147d_add_fields_to_ps_auths_to_support_new_.py
new file mode 100644 (file)
index 0000000..8452a5e
--- /dev/null
@@ -0,0 +1,31 @@
+"""Add fields to ps_auths to support new algorithms
+
+Revision ID: abdc9ede147d
+Revises: 44bd6dd914fa
+Create Date: 2024-10-27 15:26:25.165085
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = 'abdc9ede147d'
+down_revision = '44bd6dd914fa'
+
+from alembic import op
+import sqlalchemy as sa
+
+max_value_length = 1024
+
+def upgrade():
+    op.add_column('ps_auths', sa.Column('password_digest', sa.String(max_value_length)))
+    op.add_column('ps_auths', sa.Column('supported_algorithms_uas', sa.String(max_value_length)))
+    op.add_column('ps_auths', sa.Column('supported_algorithms_uac', sa.String(max_value_length)))
+    op.add_column('ps_globals', sa.Column('default_auth_algorithms_uas', sa.String(max_value_length)))
+    op.add_column('ps_globals', sa.Column('default_auth_algorithms_uac', sa.String(max_value_length)))
+
+
+def downgrade():
+    op.drop_column('ps_auths', 'password_digest')
+    op.drop_column('ps_auths', 'supported_algorithms_uas')
+    op.drop_column('ps_auths', 'supported_algorithms_uac')
+    op.drop_column('ps_globals', 'default_auth_algorithms_uas')
+    op.drop_column('ps_globals', 'default_auth_algorithms_uac')
index 13491daf3751f83e598b4649d65adf8cf29be83e..e52cd6eecb8f83eac86602ec8df66a0a8d2e7d08 100644 (file)
 /* 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
index d1061eeb079eeda69f6da94205bb3ee023a3f48f..0a0724db21a46e5ceaf80633e0ab1c7e9aeb531e 100644 (file)
@@ -72,6 +72,7 @@
 #define PJSTR_PRINTF_VAR(_v) ((int)(_v).slen), ((_v).ptr)
 
 #define AST_SIP_AUTH_MAX_REALM_LENGTH 255      /* From the auth/realm realtime column size */
+#define AST_SIP_AUTH_MAX_SUPPORTED_ALGORITHMS_LENGTH (255) /* From the supported algorithms realtime column size */
 
 /* ":12345" */
 #define COLON_PORT_STRLEN 6
@@ -558,25 +559,104 @@ enum ast_sip_dtmf_mode {
 };
 
 /*!
- * \brief Methods of storing SIP digest authentication credentials.
+ * \brief Authentication methods.
  *
- * Note that both methods result in MD5 digest authentication being
- * used. The two methods simply alter how Asterisk determines the
- * credentials for a SIP authentication
+ * The meaning of this type has changed.  It used to indicate how
+ * the credentials were stored, but now it indicates which authentication
+ * method will be used...  Google Oauth, Artificial (fake auth) or Digest.
+ * The USER_PASS and MD5 types are still used for backwards compatibility
+ * but will map to DIGEST.
  */
 enum ast_sip_auth_type {
-       /*! Credentials stored as a username and password combination */
-       AST_SIP_AUTH_TYPE_USER_PASS,
-       /*! Credentials stored as an MD5 sum */
+       AST_SIP_AUTH_TYPE_NONE = -1,
+       /*!
+        * Credentials stored as a username and password combination
+        * \deprecated Now automatically determined
+        */
+       AST_SIP_AUTH_TYPE_USER_PASS = 0,
+       /*!
+        * Credentials stored as an MD5 sum
+        * \deprecated Use AST_SIP_AUTH_TYPE_DIGEST instead
+        */
        AST_SIP_AUTH_TYPE_MD5,
        /*! Google Oauth */
        AST_SIP_AUTH_TYPE_GOOGLE_OAUTH,
        /*! Credentials not stored this is a fake auth */
-       AST_SIP_AUTH_TYPE_ARTIFICIAL
+       AST_SIP_AUTH_TYPE_ARTIFICIAL,
+       /*! Digest method will be used */
+       AST_SIP_AUTH_TYPE_DIGEST,
+};
+
+enum ast_sip_auth_cred_usage {
+       /*! The credentials used as a UAC */
+       AST_SIP_AUTH_CRED_USAGE_UAC,
+       /*! The credentials used as a UAS */
+       AST_SIP_AUTH_CRED_USAGE_UAS,
 };
 
 #define SIP_SORCERY_AUTH_TYPE "auth"
 
+#ifndef HAVE_PJSIP_AUTH_NEW_DIGESTS
+/*
+ * These are needed if the version of pjproject in use
+ * does not have the new digests.
+ * NOTE: We don't support AKAV1_MD5 but we need to specify
+ * it to be compatible with the pjproject definition.
+ */
+typedef enum pjsip_auth_algorithm_type
+{
+    PJSIP_AUTH_ALGORITHM_NOT_SET = 0,
+    PJSIP_AUTH_ALGORITHM_MD5,
+    PJSIP_AUTH_ALGORITHM_SHA256,
+    PJSIP_AUTH_ALGORITHM_SHA512_256,
+    PJSIP_AUTH_ALGORITHM_AKAV1_MD5,
+    PJSIP_AUTH_ALGORITHM_COUNT,
+} pjsip_auth_algorithm_type;
+
+typedef struct pjsip_auth_algorithm
+{
+       pjsip_auth_algorithm_type algorithm_type;
+       pj_str_t iana_name;
+       const char *openssl_name;
+       unsigned digest_length;
+       unsigned digest_str_length;
+} pjsip_auth_algorithm;
+#endif
+
+/*!
+ * \brief Get algorithm by algorithm type
+ *
+ * \param algorithm_type The algorithm type
+ * \retval The algorithm or NULL if not found
+ */
+const pjsip_auth_algorithm *ast_sip_auth_get_algorithm_by_type(
+       pjsip_auth_algorithm_type algorithm_type);
+
+/*!
+ * \brief Get algorithm by IANA name
+ *
+ * \param iana_name The algorithm IANA name
+ * \retval The algorithm or NULL if not found
+ */
+const pjsip_auth_algorithm *ast_sip_auth_get_algorithm_by_iana_name(
+       const pj_str_t *iana_name);
+
+/*!
+ * \brief Is algorithm supported by OpenSSL and pjproject?
+ *
+ * \param algorithm_type The algorithm IANA name
+ * \retval The algorithm or NULL if not found
+ */
+pj_bool_t ast_sip_auth_is_algorithm_supported(
+       pjsip_auth_algorithm_type algorithm_type);
+
+AST_VECTOR(pjsip_auth_algorithm_type_vector, pjsip_auth_algorithm_type);
+
+struct ast_sip_auth_password_digest {
+       pjsip_auth_algorithm_type algorithm_type;
+       char digest[0];
+};
+
 struct ast_sip_auth {
        /*! Sorcery ID of the auth is its name */
        SORCERY_OBJECT(details);
@@ -587,7 +667,10 @@ struct ast_sip_auth {
                AST_STRING_FIELD(auth_user);
                /*! Authentication password */
                AST_STRING_FIELD(auth_pass);
-               /*! Authentication credentials in MD5 format (hash of user:realm:pass) */
+               /*!
+                * Authentication credentials in MD5 format (hash of user:realm:pass)
+                * \deprecated Use password_digests[PJSIP_AUTH_ALGORITHM_MD5] instead.
+                */
                AST_STRING_FIELD(md5_creds);
                /*! Refresh token to use for OAuth authentication */
                AST_STRING_FIELD(refresh_token);
@@ -600,6 +683,12 @@ struct ast_sip_auth {
        unsigned int nonce_lifetime;
        /*! Used to determine what to use when authenticating */
        enum ast_sip_auth_type type;
+       /*! Digest algorithms to support when UAC */
+       struct pjsip_auth_algorithm_type_vector supported_algorithms_uac;
+       /*! Digest algorithms to send challenges for when UAS */
+       struct pjsip_auth_algorithm_type_vector supported_algorithms_uas;
+       /*! Array of pre-digested passwords indexed by pjsip_auth_algorithm_type */
+       struct ast_sip_auth_password_digest *password_digests[PJSIP_AUTH_ALGORITHM_COUNT];
 };
 
 AST_VECTOR(ast_sip_auth_vector, const char *);
@@ -1240,6 +1329,33 @@ enum ast_sip_check_auth_result {
     AST_SIP_AUTHENTICATION_ERROR,
 };
 
+/*!
+ * \brief Populate a vector of algorithm types from a string.
+ *
+ * \param id           The object id to use in error messages
+ * \param algorithms   The vector to populate
+ * \param agent_type   The type of agent to use in error messages ("UAC" or "UAS")
+ * \param value        The comma-separated string to parse for algorithms
+ *
+ * \retval 0 Success
+ * \retval non-zero Failure
+ */
+int ast_sip_auth_digest_algorithms_vector_init(const char *id,
+       struct pjsip_auth_algorithm_type_vector *algorithms, const char *agent_type, const char *value);
+
+/*!
+ * \brief Dump a vector of algorithm types to a string.
+ *
+ * \param algorithms The vector to dump
+ * \param[out] buf   Pointer to the buffer to dump the algorithms to
+ *                   Must be freed by the caller.
+ *
+ * \retval 0 Success
+ * \retval non-zero Failure
+ */
+int ast_sip_auth_digest_algorithms_vector_to_str(
+       const struct pjsip_auth_algorithm_type_vector *algorithms, char **buf);
+
 /*!
  * \brief An interchangeable way of handling digest authentication for SIP.
  *
@@ -3044,6 +3160,40 @@ const char *ast_sip_auth_type_to_str(enum ast_sip_auth_type type);
  */
 int ast_sip_auths_to_str(const struct ast_sip_auth_vector *auths, char **buf);
 
+/*!
+ * \brief Checks an pjsip_auth_algorithm_type_vector to see if it contains an algorithm
+ *
+ * \param auth            The auth object
+ * \param algorithms      The auth object's supported_algorithms_uac or supported_algorithms_uas
+ * \param algorithm_type  The algorithm_type to check
+ *
+ * \retval 1 The algorithm-type is in the vector
+ * \retval 0 The algorithm-type is not in the vector
+ */
+int ast_sip_auth_is_algorithm_available(const struct ast_sip_auth *auth,
+       const struct pjsip_auth_algorithm_type_vector *algorithms,
+       pjsip_auth_algorithm_type algorithm_type);
+
+/*!
+ * \brief Get the plain text or digest password from an auth object
+ *
+ * \param auth            The auth object
+ * \param algorithm_type  The algorithm type to retrieve the password for
+ * \param cred_type       [out]Pointer to an int to receive the credential type
+ *
+ * \note cred_type will contain one of the following values:
+ *      - PJSIP_CRED_DATA_DIGEST
+ *      - PJSIP_CRED_DATA_PLAIN_PASSWD
+
+ * If a password digest is available for the algorithm type it will
+ * be returned, otherwise if a plain text password is available
+ * that will be returned instead.
+ *
+ * \retval The plain text or digest password or NULL if not found for the algorithm type
+ */
+const char *ast_sip_auth_get_creds(const struct ast_sip_auth *auth,
+       const pjsip_auth_algorithm_type algorithm_type, int *cred_type);
+
 /*!
  * \brief AMI variable container
  */
@@ -3409,6 +3559,22 @@ char *ast_sip_get_default_voicemail_extension(void);
  */
 void ast_sip_get_default_realm(char *realm, size_t size);
 
+/*!
+ * \brief Retrieve the global auth algorithms for UAS.
+ *
+ * \param[out] default_auth_algorithms_uas The default algorithms
+ * \param size The buffer size of default_auth_algorithms_uas
+ */
+void ast_sip_get_default_auth_algorithms_uas(char *default_auth_algorithms_uas, size_t size);
+
+/*!
+ * \brief Retrieve the global auth algorithms for UAC.
+ *
+ * \param[out] default_auth_algorithms_uac The default algorithms
+ * \param size The buffer size of default_auth_algorithms_uac
+ */
+void ast_sip_get_default_auth_algorithms_uac(char *default_auth_algorithms_uac, size_t size);
+
 /*!
  * \brief Retrieve the global default from user.
  *
index 865f06278fcedb8281ad7def484dd057688f687a..eeaf954039c7cafe210e03ba08b3ba59659d148c 100644 (file)
@@ -315,6 +315,26 @@ static char *handle_pjproject_show_buildopts(struct ast_cli_entry *e, int cmd, s
                ast_cli(a->fd, "%s\n", AST_VECTOR_GET(&buildopts, i));
        }
 
+#ifdef HAVE_PJSIP_AUTH_NEW_DIGESTS
+       {
+               struct ast_str *buf = ast_str_alloca(256);
+               for (i = PJSIP_AUTH_ALGORITHM_NOT_SET + 1; i < PJSIP_AUTH_ALGORITHM_COUNT; i++) {
+                       const pjsip_auth_algorithm *algorithm = pjsip_auth_get_algorithm_by_type(i);
+                       if (!ast_strlen_zero(algorithm->openssl_name)) {
+                               if (pjsip_auth_is_algorithm_supported(i)) {
+                                       ast_str_append(&buf, 0, "%.*s/%s, ", (int)algorithm->iana_name.slen,
+                                               algorithm->iana_name.ptr, algorithm->openssl_name);
+                               }
+                       }
+               }
+               /* Trim off the trailing ", " */
+               ast_str_truncate(buf, -2);
+               ast_cli(a->fd, "Supported Digest Algorithms (IANA name/OpenmSSL name): %s\n", ast_str_buffer(buf));
+       }
+#else
+       ast_cli(a->fd, "Supported Digest Algorithms (IANA name/OpenmSSL name): MD5/MD5\n");
+#endif
+
        return CLI_SUCCESS;
 }
 
index 2350140f5339f26298f7d6c314a259d6c55ee8f9..c55d3b2e8ee374c093bf6c8848a7c41bcaa192f6 100644 (file)
 #include "asterisk/logger.h"
 #include "asterisk/sorcery.h"
 #include "asterisk/cli.h"
+#include "asterisk/vector.h"
 #include "include/res_pjsip_private.h"
 #include "asterisk/res_pjsip_cli.h"
 
+#ifndef HAVE_PJSIP_AUTH_NEW_DIGESTS
+/*
+ * These are needed if the version of pjproject in use
+ * does not have the new digests.
+ * NOTE: We don't support AKA but we need to specify
+ * it to be compatible with the pjproject definition.
+ */
+#ifdef HAVE_OPENSSL
+#include "openssl/md5.h"
+#include "openssl/sha.h"
+#else
+#define MD5_DIGEST_LENGTH     16
+#define SHA256_DIGEST_LENGTH  32
+#endif
+
+const pjsip_auth_algorithm pjsip_auth_algorithms[] = {
+/*    TYPE                             IANA name            OpenSSL name */
+/*      Raw digest byte length  Hex representation length                */
+    { PJSIP_AUTH_ALGORITHM_NOT_SET,    {"", 0},             "",
+        0,                      0},
+    { PJSIP_AUTH_ALGORITHM_MD5,        {"MD5", 3},          "MD5",
+        MD5_DIGEST_LENGTH,      MD5_DIGEST_LENGTH * 2},
+    { PJSIP_AUTH_ALGORITHM_SHA256,     {"SHA-256", 7},      "SHA256",
+        SHA256_DIGEST_LENGTH,   SHA256_DIGEST_LENGTH * 2},
+    { PJSIP_AUTH_ALGORITHM_SHA512_256, {"SHA-512-256", 11}, "SHA512-256",
+        SHA256_DIGEST_LENGTH,   SHA256_DIGEST_LENGTH * 2},
+    { PJSIP_AUTH_ALGORITHM_AKAV1_MD5,  {"AKAv1-MD5", 9},    "",
+        MD5_DIGEST_LENGTH,      MD5_DIGEST_LENGTH * 2},
+    { PJSIP_AUTH_ALGORITHM_AKAV1_MD5,  {"AKAv2-MD5", 9},    "",
+        MD5_DIGEST_LENGTH,      MD5_DIGEST_LENGTH * 2},
+    { PJSIP_AUTH_ALGORITHM_COUNT,      {"", 0},             "",
+        0,                      0},
+};
+#endif
+
+const pjsip_auth_algorithm *ast_sip_auth_get_algorithm_by_type(
+       pjsip_auth_algorithm_type algorithm_type)
+{
+#ifdef HAVE_PJSIP_AUTH_NEW_DIGESTS
+       return pjsip_auth_get_algorithm_by_type(algorithm_type);
+#else
+       /*
+        * If we don't have a pjproject with the new algorithms, the
+        * only one we support is MD5.
+        */
+       if (algorithm_type == PJSIP_AUTH_ALGORITHM_MD5) {
+               return &pjsip_auth_algorithms[algorithm_type];
+       }
+       return NULL;
+#endif
+}
+
+const pjsip_auth_algorithm *ast_sip_auth_get_algorithm_by_iana_name(
+       const pj_str_t *iana_name)
+{
+#ifdef HAVE_PJSIP_AUTH_NEW_DIGESTS
+       return pjsip_auth_get_algorithm_by_iana_name(iana_name);
+#else
+       if (!iana_name) {
+               return NULL;
+       }
+       /*
+        * If we don't have a pjproject with the new algorithms, the
+        * only one we support is MD5.  If iana_name is empty (but not NULL),
+        * the default is MD5.
+        */
+       if (iana_name->slen == 0 || pj_stricmp2(iana_name, "MD5") == 0) {
+               return &pjsip_auth_algorithms[PJSIP_AUTH_ALGORITHM_MD5];
+       }
+       return NULL;
+#endif
+}
+
+pj_bool_t ast_sip_auth_is_algorithm_supported(
+       pjsip_auth_algorithm_type algorithm_type)
+{
+#ifdef HAVE_PJSIP_AUTH_NEW_DIGESTS
+       return pjsip_auth_is_algorithm_supported(algorithm_type);
+#else
+       return algorithm_type == PJSIP_AUTH_ALGORITHM_MD5;
+#endif
+}
+
 static void auth_destroy(void *obj)
 {
        struct ast_sip_auth *auth = obj;
+       int i = 0;
+
        ast_string_field_free_memory(auth);
+
+       for (i = PJSIP_AUTH_ALGORITHM_NOT_SET + 1; i < PJSIP_AUTH_ALGORITHM_COUNT; i++) {
+               ast_free(auth->password_digests[i]);
+       }
+
+       AST_VECTOR_FREE(&auth->supported_algorithms_uac);
+       AST_VECTOR_FREE(&auth->supported_algorithms_uas);
 }
 
 static void *auth_alloc(const char *name)
@@ -56,6 +149,8 @@ static int auth_type_handler(const struct aco_option *opt, struct ast_variable *
                auth->type = AST_SIP_AUTH_TYPE_USER_PASS;
        } else if (!strcasecmp(var->value, "md5")) {
                auth->type = AST_SIP_AUTH_TYPE_MD5;
+       } else if (!strcasecmp(var->value, "digest")) {
+               auth->type = AST_SIP_AUTH_TYPE_DIGEST;
        } else if (!strcasecmp(var->value, "google_oauth")) {
 #ifdef HAVE_PJSIP_OAUTH_AUTHENTICATION
                auth->type = AST_SIP_AUTH_TYPE_GOOGLE_OAUTH;
@@ -74,6 +169,7 @@ static int auth_type_handler(const struct aco_option *opt, struct ast_variable *
 static const char *auth_types_map[] = {
        [AST_SIP_AUTH_TYPE_USER_PASS] = "userpass",
        [AST_SIP_AUTH_TYPE_MD5] = "md5",
+       [AST_SIP_AUTH_TYPE_DIGEST] = "digest",
        [AST_SIP_AUTH_TYPE_GOOGLE_OAUTH] = "google_oauth"
 };
 
@@ -90,43 +186,300 @@ static int auth_type_to_str(const void *obj, const intptr_t *args, char **buf)
        return 0;
 }
 
-static int auth_apply(const struct ast_sorcery *sorcery, void *obj)
+int ast_sip_auth_digest_algorithms_vector_init(const char *id,
+       struct pjsip_auth_algorithm_type_vector *algorithms, const char *agent_type,
+       const char *value)
 {
-       struct ast_sip_auth *auth = obj;
+       char *iana_names = ast_strdupa(value);
+       pj_str_t val;
        int res = 0;
 
-       if (ast_strlen_zero(auth->auth_user)) {
-               ast_log(LOG_ERROR, "No authentication username for auth '%s'\n",
-                               ast_sorcery_object_get_id(auth));
+       ast_assert(algorithms != NULL);
+
+       if (AST_VECTOR_SIZE(algorithms)) {
+               AST_VECTOR_FREE(algorithms);
+       }
+       if (AST_VECTOR_INIT(algorithms, 4)) {
                return -1;
        }
 
-       switch (auth->type) {
-       case AST_SIP_AUTH_TYPE_MD5:
-               if (ast_strlen_zero(auth->md5_creds)) {
-                       ast_log(LOG_ERROR, "'md5' authentication specified but no md5_cred "
-                                       "specified for auth '%s'\n", ast_sorcery_object_get_id(auth));
+       while ((val.ptr = ast_strip(strsep(&iana_names, ",")))) {
+               const pjsip_auth_algorithm *algo;
+
+               if (ast_strlen_zero(val.ptr)) {
+                       continue;
+               }
+               val.slen = strlen(val.ptr);
+
+               algo = ast_sip_auth_get_algorithm_by_iana_name(&val);
+               if (!algo) {
+                       ast_log(LOG_WARNING, "%s: Unknown %s digest algorithm '%s' specified\n",
+                               id, agent_type, val.ptr);
                        res = -1;
-               } else if (strlen(auth->md5_creds) != PJSIP_MD5STRLEN) {
-                       ast_log(LOG_ERROR, "'md5' authentication requires digest of size '%d', but "
-                               "digest is '%d' in size for auth '%s'\n", PJSIP_MD5STRLEN, (int)strlen(auth->md5_creds),
-                               ast_sorcery_object_get_id(auth));
+                       continue;
+               }
+               if (!ast_sip_auth_is_algorithm_supported(algo->algorithm_type)) {
+                       ast_log(LOG_WARNING, "%s: %s digest algorithm '%s' is not supported by the version of OpenSSL in use\n",
+                               id, agent_type, val.ptr);
                        res = -1;
+                       continue;
+               }
+
+               if (AST_VECTOR_APPEND(algorithms, algo->algorithm_type)) {
+                       AST_VECTOR_FREE(algorithms);
+                       return -1;
+               }
+       }
+       return res;
+}
+
+static int uac_algorithms_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
+{
+       struct ast_sip_auth *auth = obj;
+
+       return ast_sip_auth_digest_algorithms_vector_init(ast_sorcery_object_get_id(auth),
+               &auth->supported_algorithms_uac, "UAC", var->value);
+}
+
+static int uas_algorithms_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
+{
+       struct ast_sip_auth *auth = obj;
+
+       return ast_sip_auth_digest_algorithms_vector_init(ast_sorcery_object_get_id(auth),
+               &auth->supported_algorithms_uas, "UAS", var->value);
+}
+
+int ast_sip_auth_digest_algorithms_vector_to_str(
+       const struct pjsip_auth_algorithm_type_vector *algorithms, char **buf)
+{
+       struct ast_str *str = NULL;
+       int i = 0;
+
+       if (!algorithms || !AST_VECTOR_SIZE(algorithms)) {
+               return 0;
+       }
+
+       str = ast_str_alloca(256);
+       if (!str) {
+               return -1;
+       }
+
+       for (i = 0; i < AST_VECTOR_SIZE(algorithms); ++i) {
+               const pjsip_auth_algorithm *algo = ast_sip_auth_get_algorithm_by_type(
+                       AST_VECTOR_GET(algorithms, i));
+               ast_str_append(&str, 0, "%s" PJSTR_PRINTF_SPEC, i > 0 ? "," : "",
+                       PJSTR_PRINTF_VAR(algo->iana_name));
+       }
+
+       *buf = ast_strdup(ast_str_buffer(str));
+
+       return *buf ? 0 : -1;
+}
+
+static int uac_algorithms_to_str(const void *obj, const intptr_t *args, char **buf)
+{
+       const struct ast_sip_auth *auth = obj;
+       return ast_sip_auth_digest_algorithms_vector_to_str(&auth->supported_algorithms_uac, buf);
+}
+
+static int uas_algorithms_to_str(const void *obj, const intptr_t *args, char **buf)
+{
+       const struct ast_sip_auth *auth = obj;
+       return ast_sip_auth_digest_algorithms_vector_to_str(&auth->supported_algorithms_uas, buf);
+}
+
+static int password_digest_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
+{
+       struct ast_sip_auth *auth = obj;
+       const char *auth_name = ast_sorcery_object_get_id(auth);
+       char *value = ast_strdupa(var->value);
+       char *unparsed_digest = NULL;
+
+       while ((unparsed_digest = ast_strsep(&value, ',', AST_STRSEP_TRIM))) {
+               const pjsip_auth_algorithm *algo;
+               char *iana_name;
+               char *digest;
+               struct ast_sip_auth_password_digest *pw;
+               pj_str_t pj_iana_name;
+
+               if (ast_strlen_zero(unparsed_digest)) {
+                       continue;
+               }
+
+               if (strchr(unparsed_digest, ':') != NULL) {
+                       iana_name = ast_strsep(&unparsed_digest, ':', AST_STRSEP_TRIM);
+               } else {
+                       /*
+                        * md5_cred doesn't have the algorithm name in front
+                        * so we need to force it.
+                        */
+                       iana_name = "MD5";
                }
-               break;
-       case AST_SIP_AUTH_TYPE_GOOGLE_OAUTH:
+               digest = unparsed_digest;
+
+               pj_iana_name = pj_str(iana_name);
+
+               algo = ast_sip_auth_get_algorithm_by_iana_name(&pj_iana_name);
+               if (!algo) {
+                       ast_log(LOG_WARNING, "%s: Unknown password_digest algorithm '%s' specified\n",
+                               auth_name, iana_name);
+                       return -1;
+               }
+               if (!ast_sip_auth_is_algorithm_supported(algo->algorithm_type)) {
+                       ast_log(LOG_WARNING, "%s: password_digest algorithm '%s' is not supported by the version of OpenSSL in use\n",
+                               auth_name, iana_name);
+                       return -1;
+               }
+               if (strlen(digest) != algo->digest_str_length) {
+                       ast_log(LOG_WARNING, "%s: password_digest algorithm '%s' length (%d) must be %d\n",
+                               auth_name, iana_name, (int)strlen(digest), (int)algo->digest_str_length);
+                       return -1;
+               }
+
+               pw = ast_calloc(1, sizeof(*pw) + strlen(digest) + 1);
+               if (!pw) {
+                       return -1;
+               }
+               pw->algorithm_type = algo->algorithm_type;
+               strcpy(pw->digest, digest); /* Safe */
+               auth->password_digests[pw->algorithm_type] = pw;
+       }
+
+       return 0;
+}
+
+static int password_digest_to_str(const void *obj, const intptr_t *args, char **buf)
+{
+       const struct ast_sip_auth *auth = obj;
+       struct ast_str *str = ast_str_alloca(256);
+       int i = 0;
+       int count = 0;
+
+       for (i = PJSIP_AUTH_ALGORITHM_NOT_SET + 1; i < PJSIP_AUTH_ALGORITHM_COUNT; i++) {
+               struct ast_sip_auth_password_digest *pw =
+                       auth->password_digests[i];
+               const pjsip_auth_algorithm *algorithm;
+
+               if (!pw) {
+                       continue;
+               }
+
+               algorithm = ast_sip_auth_get_algorithm_by_type(pw->algorithm_type);
+
+               ast_str_append(&str, 0, "%s" PJSTR_PRINTF_SPEC ":%s", count > 0 ? "," : "",
+                       PJSTR_PRINTF_VAR(algorithm->iana_name), pw->digest);
+               count++;
+       }
+
+       *buf = ast_strdup(ast_str_buffer(str));
+
+       return 0;
+}
+
+static int md5cred_to_str(const void *obj, const intptr_t *args, char **buf)
+{
+       const struct ast_sip_auth *auth = obj;
+
+       if (auth->password_digests[PJSIP_AUTH_ALGORITHM_MD5]) {
+               *buf = ast_strdup(auth->password_digests[PJSIP_AUTH_ALGORITHM_MD5]->digest);
+       }
+
+       return 0;
+}
+
+int ast_sip_auth_is_algorithm_available(const struct ast_sip_auth *auth,
+       const struct pjsip_auth_algorithm_type_vector *algorithms,
+       pjsip_auth_algorithm_type algorithm_type)
+{
+       int i;
+
+       if (!algorithms) {
+               return 0;
+       }
+
+       for (i = 0; i < AST_VECTOR_SIZE(algorithms); ++i) {
+               if (AST_VECTOR_GET(algorithms, i) == algorithm_type) {
+                       if (auth->password_digests[algorithm_type] || !ast_strlen_zero(auth->auth_pass)) {
+                               return 1;
+                       }
+               }
+       }
+
+       return 0;
+}
+
+const char *ast_sip_auth_get_creds(const struct ast_sip_auth *auth,
+       const pjsip_auth_algorithm_type algorithm_type, int *cred_type)
+{
+       struct ast_sip_auth_password_digest *pw_digest =
+               auth->password_digests[algorithm_type];
+
+       if (pw_digest) {
+               *cred_type = PJSIP_CRED_DATA_DIGEST;
+               return pw_digest->digest;
+       }
+
+       *cred_type = PJSIP_CRED_DATA_PLAIN_PASSWD;
+       return auth->auth_pass;
+}
+
+static int check_algorithm(const struct ast_sip_auth *auth,
+       const pjsip_auth_algorithm_type algorithm_type, const char *which_supported)
+{
+       const pjsip_auth_algorithm *algo = ast_sip_auth_get_algorithm_by_type(algorithm_type);
+       struct ast_sip_auth_password_digest *pw_digest =
+               auth->password_digests[algorithm_type];
+
+       if (!pw_digest && ast_strlen_zero(auth->auth_pass)) {
+               ast_log(LOG_ERROR, "%s: No plain text or digest password found for algorithm "
+                       PJSTR_PRINTF_SPEC " in supported_algorithms_%s\n",
+                       ast_sorcery_object_get_id(auth), PJSTR_PRINTF_VAR(algo->iana_name), which_supported);
+               return -1;
+       }
+
+       return 0;
+}
+
+static int auth_apply(const struct ast_sorcery *sorcery, void *obj)
+{
+       struct ast_sip_auth *auth = obj;
+       const char *id = ast_sorcery_object_get_id(auth);
+       int i = 0;
+       int res = 0;
+
+       if (ast_strlen_zero(auth->auth_user)) {
+               ast_log(LOG_ERROR, "%s: No authentication username\n", id);
+               return -1;
+       }
+
+       if (auth->type == AST_SIP_AUTH_TYPE_GOOGLE_OAUTH) {
                if (ast_strlen_zero(auth->refresh_token)
                        || ast_strlen_zero(auth->oauth_clientid)
                        || ast_strlen_zero(auth->oauth_secret)) {
-                       ast_log(LOG_ERROR, "'google_oauth' authentication specified but refresh_token,"
-                               " oauth_clientid, or oauth_secret not specified for auth '%s'\n",
-                               ast_sorcery_object_get_id(auth));
+                       ast_log(LOG_ERROR, "%s: 'google_oauth' authentication specified but refresh_token,"
+                               " oauth_clientid, or oauth_secret not specified\n", id);
                        res = -1;
                }
-               break;
-       case AST_SIP_AUTH_TYPE_USER_PASS:
-       case AST_SIP_AUTH_TYPE_ARTIFICIAL:
-               break;
+               return res;
+       }
+
+       if (AST_VECTOR_SIZE(&auth->supported_algorithms_uas) == 0) {
+               char *default_algo_uas = ast_alloca(AST_SIP_AUTH_MAX_SUPPORTED_ALGORITHMS_LENGTH + 1);
+               ast_sip_get_default_auth_algorithms_uas(default_algo_uas, AST_SIP_AUTH_MAX_SUPPORTED_ALGORITHMS_LENGTH);
+               ast_sip_auth_digest_algorithms_vector_init(id, &auth->supported_algorithms_uas, "UAS", default_algo_uas);
+       }
+       if (AST_VECTOR_SIZE(&auth->supported_algorithms_uac) == 0) {
+               char *default_algo_uac = ast_alloca(AST_SIP_AUTH_MAX_SUPPORTED_ALGORITHMS_LENGTH + 1);
+               ast_sip_get_default_auth_algorithms_uac(default_algo_uac, AST_SIP_AUTH_MAX_SUPPORTED_ALGORITHMS_LENGTH);
+               ast_sip_auth_digest_algorithms_vector_init(id, &auth->supported_algorithms_uac, "UAC", default_algo_uac);
+       }
+
+       for (i = 0; i < AST_VECTOR_SIZE(&auth->supported_algorithms_uas); i++) {
+               res += check_algorithm(auth, AST_VECTOR_GET(&auth->supported_algorithms_uas, i), "uas");
+       }
+
+       for (i = 0; i < AST_VECTOR_SIZE(&auth->supported_algorithms_uac); i++) {
+               res += check_algorithm(auth, AST_VECTOR_GET(&auth->supported_algorithms_uac, i), "uac");
        }
 
        return res;
@@ -366,6 +719,18 @@ static struct ast_cli_entry cli_commands[] = {
 
 static struct ast_sip_cli_formatter_entry *cli_formatter;
 
+#if 1
+static void global_loaded(const char *object_type)
+{
+       ast_sorcery_force_reload_object(ast_sip_get_sorcery(), "auth");
+}
+
+/*! \brief Observer which is used to update our interval and default_realm when the global setting changes */
+static struct ast_sorcery_observer global_observer = {
+       .loaded = global_loaded,
+};
+#endif
+
 /*! \brief Initialize sorcery with auth support */
 int ast_sip_initialize_sorcery_auth(void)
 {
@@ -389,14 +754,20 @@ int ast_sip_initialize_sorcery_auth(void)
                        "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_auth, oauth_clientid));
        ast_sorcery_object_field_register(sorcery, SIP_SORCERY_AUTH_TYPE, "oauth_secret",
                        "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_auth, oauth_secret));
-       ast_sorcery_object_field_register(sorcery, SIP_SORCERY_AUTH_TYPE, "md5_cred",
-                       "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_auth, md5_creds));
+       ast_sorcery_object_field_register_custom(sorcery, SIP_SORCERY_AUTH_TYPE, "md5_cred",
+                       NULL, password_digest_handler, md5cred_to_str, NULL, 0, 0);
        ast_sorcery_object_field_register(sorcery, SIP_SORCERY_AUTH_TYPE, "realm",
                        "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_auth, realm));
        ast_sorcery_object_field_register(sorcery, SIP_SORCERY_AUTH_TYPE, "nonce_lifetime",
                        "32", OPT_UINT_T, 0, FLDSET(struct ast_sip_auth, nonce_lifetime));
        ast_sorcery_object_field_register_custom(sorcery, SIP_SORCERY_AUTH_TYPE, "auth_type",
                        "userpass", auth_type_handler, auth_type_to_str, NULL, 0, 0);
+       ast_sorcery_object_field_register_custom(sorcery, SIP_SORCERY_AUTH_TYPE, "password_digest",
+               NULL, password_digest_handler, password_digest_to_str, NULL, 0, 0);
+       ast_sorcery_object_field_register_custom(sorcery, SIP_SORCERY_AUTH_TYPE, "supported_algorithms_uac",
+               "", uac_algorithms_handler, uac_algorithms_to_str, NULL, 0, 0);
+       ast_sorcery_object_field_register_custom(sorcery, SIP_SORCERY_AUTH_TYPE, "supported_algorithms_uas",
+               "", uas_algorithms_handler, uas_algorithms_to_str, NULL, 0, 0);
 
        ast_sip_register_endpoint_formatter(&endpoint_auth_formatter);
 
@@ -420,11 +791,14 @@ int ast_sip_initialize_sorcery_auth(void)
                return -1;
        }
 
+       ast_sorcery_observer_add(sorcery, "global", &global_observer);
        return 0;
 }
 
 int ast_sip_destroy_sorcery_auth(void)
 {
+       ast_sorcery_observer_remove(ast_sip_get_sorcery(), "global", &global_observer);
+
        ast_cli_unregister_multiple(cli_commands, ARRAY_LEN(cli_commands));
        ast_sip_unregister_cli_formatter(cli_formatter);
        ast_sip_unregister_endpoint_formatter(&endpoint_auth_formatter);
index 5a71b485b4161f1873ab970c8650d31e3d6d9fb9..17b3f0f8e06cc47f2b10142c59c86c55104da10c 100644 (file)
@@ -55,6 +55,8 @@
 #define DEFAULT_TASKPROCESSOR_OVERLOAD_TRIGGER TASKPROCESSOR_OVERLOAD_TRIGGER_GLOBAL
 #define DEFAULT_NOREFERSUB 1
 #define DEFAULT_ALL_CODECS_ON_EMPTY_REINVITE 0
+#define DEFAULT_AUTH_ALGORITHMS_UAS "MD5"
+#define DEFAULT_AUTH_ALGORITHMS_UAC "MD5"
 
 /*!
  * \brief Cached global config object
@@ -83,6 +85,10 @@ struct global_config {
                AST_STRING_FIELD(default_voicemail_extension);
                /*! Realm to use in challenges before an endpoint is identified */
                AST_STRING_FIELD(default_realm);
+               /*! Default authentication algorithms for UAS */
+               AST_STRING_FIELD(default_auth_algorithms_uas);
+               /*! Default authentication algorithms for UAC */
+               AST_STRING_FIELD(default_auth_algorithms_uac);
        );
        /*! Value to put in Max-Forwards header */
        unsigned int max_forwards;
@@ -188,6 +194,8 @@ static int global_apply(const struct ast_sorcery *sorcery, void *obj)
 {
        struct global_config *cfg = obj;
        char max_forwards[10];
+       struct pjsip_auth_algorithm_type_vector algorithms;
+       int res = 0;
 
        if (ast_strlen_zero(cfg->debug)) {
                ast_log(LOG_ERROR,
@@ -211,6 +219,25 @@ static int global_apply(const struct ast_sorcery *sorcery, void *obj)
                return -1;
        }
 
+       AST_VECTOR_INIT(&algorithms, 4);
+       res = ast_sip_auth_digest_algorithms_vector_init("global",
+               &algorithms, "UAS", cfg->default_auth_algorithms_uas);
+       AST_VECTOR_FREE(&algorithms);
+       if (res) {
+               ast_log(LOG_WARNING, "global: Invalid values in default_auth_algorithms_uas. "
+                       "Defaulting to %s\n", DEFAULT_AUTH_ALGORITHMS_UAS);
+               ast_string_field_set(cfg, default_auth_algorithms_uas, DEFAULT_AUTH_ALGORITHMS_UAS);
+       }
+       AST_VECTOR_INIT(&algorithms, 4);
+       res = ast_sip_auth_digest_algorithms_vector_init("global",
+               &algorithms, "UAC", cfg->default_auth_algorithms_uac);
+       AST_VECTOR_FREE(&algorithms);
+       if (res) {
+               ast_log(LOG_WARNING, "global: Invalid values in default_auth_algorithms_uac. "
+                       "Defaulting to %s\n", DEFAULT_AUTH_ALGORITHMS_UAC);
+               ast_string_field_set(cfg, default_auth_algorithms_uac, DEFAULT_AUTH_ALGORITHMS_UAC);
+       }
+
        ao2_t_global_obj_replace_unref(global_cfg, cfg, "Applying global settings");
        return 0;
 }
@@ -391,6 +418,32 @@ void ast_sip_get_default_realm(char *realm, size_t size)
        }
 }
 
+void ast_sip_get_default_auth_algorithms_uas(char *default_auth_algorithms_uas, size_t size)
+{
+       struct global_config *cfg;
+
+       cfg = get_global_cfg();
+       if (!cfg) {
+               ast_copy_string(default_auth_algorithms_uas, DEFAULT_AUTH_ALGORITHMS_UAS, size);
+       } else {
+               ast_copy_string(default_auth_algorithms_uas, cfg->default_auth_algorithms_uas, size);
+               ao2_ref(cfg, -1);
+       }
+}
+
+void ast_sip_get_default_auth_algorithms_uac(char *default_auth_algorithms_uac, size_t size)
+{
+       struct global_config *cfg;
+
+       cfg = get_global_cfg();
+       if (!cfg) {
+               ast_copy_string(default_auth_algorithms_uac, DEFAULT_AUTH_ALGORITHMS_UAC, size);
+       } else {
+               ast_copy_string(default_auth_algorithms_uac, cfg->default_auth_algorithms_uac, size);
+               ao2_ref(cfg, -1);
+       }
+}
+
 void ast_sip_get_default_from_user(char *from_user, size_t size)
 {
        struct global_config *cfg;
@@ -765,10 +818,17 @@ int ast_sip_initialize_sorcery_global(void)
        ast_sorcery_object_field_register(sorcery, "global", "all_codecs_on_empty_reinvite",
                DEFAULT_ALL_CODECS_ON_EMPTY_REINVITE ? "yes" : "no",
                OPT_BOOL_T, 1, FLDSET(struct global_config, all_codecs_on_empty_reinvite));
+       ast_sorcery_object_field_register(sorcery, "global", "default_auth_algorithms_uas",
+               DEFAULT_AUTH_ALGORITHMS_UAS, OPT_STRINGFIELD_T, 0,
+               STRFLDSET(struct global_config, default_auth_algorithms_uas));
+       ast_sorcery_object_field_register(sorcery, "global", "default_auth_algorithms_uac",
+               DEFAULT_AUTH_ALGORITHMS_UAC, OPT_STRINGFIELD_T, 0,
+               STRFLDSET(struct global_config, default_auth_algorithms_uac));
 
        if (ast_sorcery_instance_observer_add(sorcery, &observer_callbacks_global)) {
                return -1;
        }
+       ast_sorcery_load_object(ast_sip_get_sorcery(), "global");
 
        return 0;
 }
index 127499641c5768013f171234127ef7a74cf4f6f7..91d4994053f2c7236ce3d6c1b643725f1833ce04 100644 (file)
                                </configOption>
                        </configObject>
                        <configObject name="auth">
+                               <!--
+                               Be sure to update the following documentation page when making changes to this object:
+                               https://docs.asterisk.org/Configuration/Channel-Drivers/SIP/Configuring-res_pjsip/PJSIP-Authentication
+                                -->
                                <synopsis>Authentication type</synopsis>
                                <description><para>
                                        Authentication objects hold the authentication information for use
                                        by other objects such as <literal>endpoints</literal> or <literal>registrations</literal>.
                                        This also allows for multiple objects to use a single auth object. See
-                                       the <literal>auth_type</literal> config option for password style choices.
-                               </para></description>
-                               <configOption name="auth_type" default="userpass">
+                                       the <literal>auth_type</literal> config option for security mechanism choices.
+                               </para>
+                               <note><para>
+                               See the link below for detailed discussion of this object especially concerning
+                               realms and digest hash algorithms.
+                               </para>
+                               <para>
+                               https://docs.asterisk.org/Configuration/Channel-Drivers/SIP/Configuring-res_pjsip/PJSIP-Authentication
+                               </para>
+                               </note>
+                               </description>
+                               <see-also>
+                                       <ref type="link">https://docs.asterisk.org/Configuration/Channel-Drivers/SIP/Configuring-res_pjsip/PJSIP-Authentication</ref>
+                               </see-also>
+                               <configOption name="auth_type" default="digest">
                                        <synopsis>Authentication type</synopsis>
                                        <description><para>
-                                               This option specifies which of the password style config options should be read
-                                               when trying to authenticate an endpoint inbound request. If set to <literal>userpass</literal>
-                                               then we'll read from the 'password' option. For <literal>md5</literal> we'll read
-                                               from 'md5_cred'. If set to <literal>google_oauth</literal> then we'll read from the
-                                               refresh_token/oauth_clientid/oauth_secret fields. The following values are valid:
+                                               If set to <literal>google_oauth</literal> then we'll read from the
+                                               refresh_token/oauth_clientid/oauth_secret parameters.
+                                               If set to <literal>digest</literal> then we'll read from the
+                                               <literal>password</literal> and/or <literal>password_digest</literal>
+                                               parameters.  The older <literal>md5</literal> and <literal>userpass</literal>
+                                               values are deprecated and converted to <literal>digest</literal>.
                                                </para>
                                                <enumlist>
-                                                       <enum name="md5"/>
-                                                       <enum name="userpass"/>
-                                                       <enum name="google_oauth"/>
+                                                       <enum name="userpass"><para>Deprecated.  Use <literal>digest</literal>.</para></enum>
+                                                       <enum name="md5"><para>Deprecated.  Use <literal>digest</literal>.</para></enum>
+                                                       <enum name="google_oauth"><para>If selected, the <literal>refresh_token</literal>,
+                                                       <literal>oauth_clientid</literal> and <literal>oauth_secret</literal>
+                                                       parameters must be provided.</para></enum>
+                                                       <enum name="digest"><para>If selected, the <literal>password</literal>
+                                                       and/or one or more <literal>password_digest</literal>
+                                                       parameters must be provided.</para></enum>
                                                </enumlist>
                                                <para>
                                                </para>
-                                               <note>
-                                                       <para>
-                                                               This setting only describes whether the password is in
-                                                               plain text or has been pre-hashed with MD5.  It doesn't describe
-                                                               the acceptable digest algorithms we'll accept in a received
-                                                               challenge.
-                                                       </para>
-                                               </note>
                                        </description>
                                </configOption>
-                               <configOption name="nonce_lifetime" default="32">
-                                       <synopsis>Lifetime of a nonce associated with this authentication config.</synopsis>
+                               <configOption name="username">
+                                       <synopsis>Username to use for account</synopsis>
                                </configOption>
-                               <configOption name="md5_cred" default="">
-                                       <synopsis>MD5 Hash used for authentication.</synopsis>
-                                       <description><para>
-                                               Only used when auth_type is <literal>md5</literal>.
+                               <configOption name="password">
+                                       <synopsis>Plain text password used for authentication.</synopsis>
+                                       <description><para>Only used when auth_type is <literal>digest</literal>.</para></description>
+                               </configOption>
+                               <configOption name="password_digest" default="">
+                                       <synopsis>One or more pre-computed hashes used for authentication.</synopsis>
+                                       <description><para>Only used when auth_type is <literal>digest</literal>.
                                                As an alternative to specifying a plain text password,
-                                               you can hash the username, realm and password
-                                               together one time and place the hash value here.
-                                               The input to the hash function must be in the
-                                               following format:
-                                               </para>
-                                               <para>
-                                               </para>
-                                               <para>
-                                               &lt;username&gt;:&lt;realm&gt;:&lt;password&gt;
-                                               </para>
-                                               <para>
-                                               </para>
-                                               <para>
-                                               For incoming authentication (asterisk is the server),
-                                               the realm must match either the realm set in this object
-                                               or the <variable>default_realm</variable> set in in the
-                                               <replaceable>global</replaceable> object.
-                                               </para>
-                                               <para>
-                                               </para>
-                                               <para>
-                                               For outgoing authentication (asterisk is the UAC),
-                                               the realm must match what the server will be sending
-                                               in their WWW-Authenticate header.  It can't be blank
-                                               unless you expect the server to be sending a blank
-                                               realm in the header.  You can't use pre-hashed
-                                               passwords with a wildcard auth object.
-                                               You can generate the hash with the following shell
-                                               command:
+                                               you can specify one or more pre-computed digests separated by
+                                               commas.
                                                </para>
                                                <para>
+                                               <literal>password_digest= &lt;digest-spec&gt;[,&lt;digest_spec&gt;]...</literal>
                                                </para>
+                                               <enumlist>
+                                                       <enum name="&lt;digest-spec&gt;"><para>&lt;hash-algorithm&gt;:&lt;hashed-credential&gt;</para></enum>
+                                                       <enum name="&lt;hash-algorithm&gt;"><para>One of the supported hash algorithms
+                                                               which currently are</para>
+                                                               <enumlist>
+                                                                       <enum name="MD5"><para>Supported by all versions of OpenSSL and pjproject</para></enum>
+                                                                       <enum name="SHA-256"><para>Supported by OpenSSL versions &gt;> 1.0.0 and pjproject versions &gt;= 2.15.1</para></enum>
+                                                                       <enum name="SHA-512-256"><para>Supported by OpenSSL versions &gt;= 1.1.1 and pjproject versions &gt;= 2.15.1</para></enum>
+                                                               </enumlist>
+                                                               <para>You can see the current list by running the CLI command
+                                                               <literal>pjproject show buildopts</literal>.
+                                                       </para></enum>
+                                                       <enum name="&lt;hashed-credential&gt;">
+                                                               <para>The result of passing the following string through
+                                                               the selected hash algorithm:
+                                                               <literal>&lt;username&gt;:&lt;realm&gt;:&lt;password&gt;</literal>
+                                                               </para>
+                                                       </enum>
+                                               </enumlist>
+                                               <para>You can create the hash by piping the string into the appropriate
+                                               hash/checksum program.  See the description for the <literal>realm</literal>
+                                               parameter for info on how to set it.</para>
+                                               <example>
+                                               $ echo -n "myname:myrealm:mypassword" | openssl dgst -md5
+                                               MD5(stdin)= dce9ccd0a69e3ef90d8b9bf725053e78
+                                               </example>
+                                               <para>You would then set:</para>
+                                               <example>
+                                               password_digest = md5:dce9ccd0a69e3ef90d8b9bf725053e78
+                                               </example>
+                                       </description>
+                               </configOption>
+                               <configOption name="md5_cred" default="">
+                                       <synopsis>MD5 Hash used for authentication. (deprecated)</synopsis>
+                                       <description><para>Use the <literal>password_digest</literal> parameter instead.
+                                       If supplied, a <literal>password_digest</literal> parameter will be created
+                                       for it.
+                                       </para></description>
+                               </configOption>
+                               <configOption name="supported_algorithms_uac">
+                                       <synopsis>Comma separated list of algorithms to support when this auth is used as a UAC</synopsis>
+                                       <description><para>Valid values:</para>
+                                               <enumlist>
+                                                       <enum name="md5"><para>Supported by all versions of OpenSSL and pjproject</para></enum>
+                                                       <enum name="sha-256"><para>Supported by all versions of OpenSSL but only pjproject versions &gt; 2.14.1</para></enum>
+                                                       <enum name="sha-512-256"><para>Supported by OpenSSL versions &gt;= 1.1.1 and pjproject versions &gt; 2.14.1</para></enum>
+                                               </enumlist>
                                                <para>
-                                               $ echo -n "myname:myrealm:mypassword" | md5sum
+                                                       The default may be specified by the
+                                                       <literal>default_auth_algorithms_uac</literal> parameter in
+                                                       the global object. If that's not specified, the default is "MD5".
                                                </para>
+                                       </description>
+                               </configOption>
+                               <configOption name="supported_algorithms_uas">
+                                       <synopsis>Comma separated list of algorithms to support when this auth is used as a UAS</synopsis>
+                                       <description><para>Valid values:</para>
+                                               <enumlist>
+                                                       <enum name="md5"><para>Supported by all versions of OpenSSL and pjproject</para></enum>
+                                                       <enum name="sha-256"><para>Supported by all versions of OpenSSL but only pjproject versions &gt; 2.14.1</para></enum>
+                                                       <enum name="sha-512-256"><para>Supported by OpenSSL versions &gt;= 1.1.1 and pjproject versions &gt; 2.14.1</para></enum>
+                                               </enumlist>
                                                <para>
+                                                       The default may be specified by the
+                                                       <literal>default_auth_algorithms_uas</literal> parameter in
+                                                       the global object. If that's not specified, the default is "MD5".
                                                </para>
-                                               <para>
-                                               Note the '-n'.  You don't want a newline to be part
-                                               of the hash.
-                                       </para></description>
-                               </configOption>
-                               <configOption name="password">
-                                       <synopsis>Plain text password used for authentication.</synopsis>
-                                       <description><para>Only used when auth_type is <literal>userpass</literal>.</para></description>
+                                       </description>
                                </configOption>
                                <configOption name="refresh_token">
                                        <synopsis>OAuth 2.0 refresh token</synopsis>
                                                <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>
index 909f99a8f98cc624e1e9b754b6653977be0328f7..50608679cbe3f491c621713fd31caab86dd557ec 100644 (file)
@@ -588,7 +588,8 @@ static pj_bool_t distributor(pjsip_rx_data *rdata)
        return PJ_TRUE;
 }
 
-static struct ast_sip_auth *alloc_artificial_auth(char *default_realm)
+static struct ast_sip_auth *alloc_artificial_auth(char *default_realm,
+       char *default_algos_uac, char *default_algos_uas)
 {
        struct ast_sip_auth *fake_auth;
 
@@ -601,6 +602,13 @@ static struct ast_sip_auth *alloc_artificial_auth(char *default_realm)
        ast_string_field_set(fake_auth, realm, default_realm);
        ast_string_field_set(fake_auth, auth_user, "");
        ast_string_field_set(fake_auth, auth_pass, "");
+
+       ast_sip_auth_digest_algorithms_vector_init("artificial",
+               &fake_auth->supported_algorithms_uac, "UAC", default_algos_uac);
+
+       ast_sip_auth_digest_algorithms_vector_init("artificial",
+               &fake_auth->supported_algorithms_uas, "UAS", default_algos_uas);
+
        fake_auth->type = AST_SIP_AUTH_TYPE_ARTIFICIAL;
 
        return fake_auth;
@@ -608,20 +616,48 @@ static struct ast_sip_auth *alloc_artificial_auth(char *default_realm)
 
 static AO2_GLOBAL_OBJ_STATIC(artificial_auth);
 
-static int create_artificial_auth(void)
+static int create_artificial_auth(int reload)
 {
        char default_realm[AST_SIP_AUTH_MAX_REALM_LENGTH + 1];
        struct ast_sip_auth *fake_auth;
+       char default_algos_uac[AST_SIP_AUTH_MAX_SUPPORTED_ALGORITHMS_LENGTH + 1];
+       char default_algos_uas[AST_SIP_AUTH_MAX_SUPPORTED_ALGORITHMS_LENGTH + 1];
+       int need_update = 1;
 
        ast_sip_get_default_realm(default_realm, sizeof(default_realm));
-       fake_auth = alloc_artificial_auth(default_realm);
-       if (!fake_auth) {
-               ast_log(LOG_ERROR, "Unable to create artificial auth\n");
-               return -1;
+       ast_sip_get_default_auth_algorithms_uac(default_algos_uac,
+               sizeof(default_algos_uac));
+       ast_sip_get_default_auth_algorithms_uas(default_algos_uas,
+               sizeof(default_algos_uas));
+
+       fake_auth = ast_sip_get_artificial_auth();
+       if (fake_auth && reload) {
+               char *fake_algorithms_uac = NULL;
+               char *fake_algorithms_uas = NULL;
+
+               ast_sip_auth_digest_algorithms_vector_to_str(
+                       &fake_auth->supported_algorithms_uac, &fake_algorithms_uac);
+               ast_sip_auth_digest_algorithms_vector_to_str(
+                       &fake_auth->supported_algorithms_uas, &fake_algorithms_uas);
+               if (strcmp(fake_auth->realm, default_realm) == 0
+                       && strcmp(fake_algorithms_uac, default_algos_uac) == 0
+                       && strcmp(fake_algorithms_uas, default_algos_uas) == 0) {
+                       need_update = 0;
+               }
+               ast_free(fake_algorithms_uac);
+               ast_free(fake_algorithms_uas);
        }
 
-       ao2_global_obj_replace_unref(artificial_auth, fake_auth);
-       ao2_ref(fake_auth, -1);
+       ao2_cleanup(fake_auth);
+       if (!need_update) {
+               return 0;
+       }
+
+       fake_auth = alloc_artificial_auth(default_realm, default_algos_uac,
+                       default_algos_uas);
+       if (fake_auth) {
+               ao2_global_obj_replace_unref(artificial_auth, fake_auth);
+       }
        return 0;
 }
 
@@ -1161,8 +1197,6 @@ static int clean_task(const void *data)
 
 static void global_loaded(const char *object_type)
 {
-       char default_realm[AST_SIP_AUTH_MAX_REALM_LENGTH + 1];
-       struct ast_sip_auth *fake_auth;
        char *identifier_order;
 
        /* Update using_auth_username */
@@ -1182,18 +1216,7 @@ static void global_loaded(const char *object_type)
                using_auth_username = new_using;
        }
 
-       /* Update default_realm of artificial_auth */
-       ast_sip_get_default_realm(default_realm, sizeof(default_realm));
-       fake_auth = ast_sip_get_artificial_auth();
-       if (!fake_auth || strcmp(fake_auth->realm, default_realm)) {
-               ao2_cleanup(fake_auth);
-
-               fake_auth = alloc_artificial_auth(default_realm);
-               if (fake_auth) {
-                       ao2_global_obj_replace_unref(artificial_auth, fake_auth);
-               }
-       }
-       ao2_cleanup(fake_auth);
+       create_artificial_auth(1);
 
        ast_sip_get_unidentified_request_thresholds(&unidentified_count, &unidentified_period, &unidentified_prune_interval);
 
@@ -1287,7 +1310,7 @@ int ast_sip_initialize_distributor(void)
        ast_sorcery_observer_add(ast_sip_get_sorcery(), "global", &global_observer);
        ast_sorcery_reload_object(ast_sip_get_sorcery(), "global");
 
-       if (create_artificial_endpoint() || create_artificial_auth()) {
+       if (create_artificial_endpoint() || create_artificial_auth(0)) {
                ast_sip_destroy_distributor();
                return -1;
        }
index 0b9424e0b3479b83a32a457dd1e3bf881a025914..a97052562a40e2fc83b2147e8eee90a1ba89806d 100644 (file)
 #include "asterisk/strings.h"
 #include "asterisk/test.h"
 
+/*!
+ * \file
+ * \brief PJSIP UAS Authentication
+ *
+ * This module handles authentication when Asterisk is the UAS.
+ *
+ */
+
 /*** MODULEINFO
        <depend>pjproject</depend>
        <depend>res_pjsip</depend>
@@ -131,58 +139,132 @@ static const struct ast_sip_auth *get_auth(void)
        return NULL;
 }
 
+static struct pjsip_authorization_hdr *get_authorization_hdr(
+       const char *auth_id, const char *realm, const pjsip_rx_data *rdata)
+{
+       const char *src_name = rdata->pkt_info.src_name;
+       struct pjsip_authorization_hdr *auth_hdr =
+               (pjsip_authorization_hdr *) &rdata->msg_info.msg->hdr;
+       SCOPE_ENTER(3, "%s:%s: realm: %s\n", auth_id, src_name, realm);
+
+       while ((auth_hdr = pjsip_msg_find_hdr(rdata->msg_info.msg,
+               PJSIP_H_AUTHORIZATION, auth_hdr ? auth_hdr->next : NULL))) {
+               if (pj_strcmp2(&auth_hdr->credential.common.realm, realm) == 0) {
+                       SCOPE_EXIT_RTN_VALUE(auth_hdr, "%s:%s: realm: %s Found header\n",
+                               auth_id, src_name, realm);
+               }
+       }
+       SCOPE_EXIT_RTN_VALUE(NULL, "%s:%s: realm: %s No auth header found\n",
+               auth_id, src_name, realm);
+}
+
 /*!
  * \brief Lookup callback for authentication verification
  *
  * This function is called when we call pjsip_auth_srv_verify(). It
  * expects us to verify that the realm and account name from the
- * Authorization header is correct. We are then supposed to supply
- * a password or MD5 sum of credentials.
+ * Authorization header are correct and that we can support the digest
+ * algorithm specified. We are then supposed to supply a password or
+ * password_digest for the algorithm.
+ *
+ * The auth object must have previously been saved to thread-local storage.
  *
  * \param pool A memory pool we can use for allocations
- * \param realm The realm from the Authorization header
- * \param acc_name the user from the Authorization header
- * \param[out] info The credentials we need to fill in
+ * \param param Contains the realm, username, rdata and auth header
+ * \param cred_info The credentials we need to fill in
  * \retval PJ_SUCCESS Successful authentication
  * \retval other Unsuccessful
  */
-static pj_status_t digest_lookup(pj_pool_t *pool, const pj_str_t *realm,
-               const pj_str_t *acc_name, pjsip_cred_info *info)
+static pj_status_t digest_lookup(pj_pool_t *pool,
+       const pjsip_auth_lookup_cred_param *param,
+       pjsip_cred_info *cred_info)
 {
-       const struct ast_sip_auth *auth;
+       const struct ast_sip_auth *auth = get_auth();
+       const char *realm = S_OR(auth->realm, default_realm);
+       const char *creds;
+       const char *auth_name = (auth ? ast_sorcery_object_get_id(auth) : "none");
+       struct pjsip_authorization_hdr *auth_hdr = get_authorization_hdr(auth_name, realm, param->rdata);
+       const pjsip_auth_algorithm *algorithm =
+               ast_sip_auth_get_algorithm_by_iana_name(&auth_hdr->credential.digest.algorithm);
+       const char *src_name = param->rdata->pkt_info.src_name;
+       SCOPE_ENTER(4, "%s:%s:"
+               " srv realm: " PJSTR_PRINTF_SPEC
+               " auth realm: %s"
+               " hdr realm: " PJSTR_PRINTF_SPEC
+               " auth user: %s"
+               " hdr user: " PJSTR_PRINTF_SPEC
+               " algorithm: " PJSTR_PRINTF_SPEC
+               "\n",
+               auth_name, src_name,
+               PJSTR_PRINTF_VAR(param->realm),
+               realm,
+               PJSTR_PRINTF_VAR(auth_hdr->credential.common.realm),
+               auth->auth_user,
+               PJSTR_PRINTF_VAR(param->acc_name),
+               PJSTR_PRINTF_VAR(algorithm->iana_name));
 
-       auth = get_auth();
        if (!auth) {
-               return PJSIP_SC_FORBIDDEN;
+               /* This can only happen if the auth object was not saved to thread-local storage */
+               SCOPE_EXIT_RTN_VALUE(PJSIP_SC_FORBIDDEN, "%s:%s: No auth object found\n",
+                       auth_name, src_name);
        }
 
        if (auth->type == AST_SIP_AUTH_TYPE_ARTIFICIAL) {
-               return PJSIP_SC_FORBIDDEN;
+               /*
+                * This shouldn't happen because this function can only be invoked
+                * if there was an Authorization header in the incoming request.
+                */
+               SCOPE_EXIT_RTN_VALUE(PJSIP_SC_FORBIDDEN, "%s:%s: Artificial auth object\n",
+                       auth_name, src_name);
        }
 
-       if (pj_strcmp2(realm, auth->realm)) {
-               return PJSIP_SC_FORBIDDEN;
+       if (pj_strcmp2(&param->realm, realm) != 0) {
+               /*
+                * This shouldn't happen because param->realm was passed in from the auth
+                * when we called pjsip_auth_srv_init2.
+                */
+               SCOPE_EXIT_RTN_VALUE(PJSIP_SC_FORBIDDEN, "%s:%s: Realm '%s' mismatch\n",
+                       auth_name, src_name, realm);
        }
-       if (pj_strcmp2(acc_name, auth->auth_user)) {
-               return PJSIP_SC_FORBIDDEN;
+
+       if (pj_strcmp2(&param->acc_name, auth->auth_user) != 0) {
+               SCOPE_EXIT_RTN_VALUE(PJSIP_SC_FORBIDDEN, "%s:%s: Username '%s' mismatch\n",
+                       auth_name, src_name, auth->auth_user);
        }
 
-       pj_strdup2(pool, &info->realm, auth->realm);
-       pj_strdup2(pool, &info->username, auth->auth_user);
-
-       switch (auth->type) {
-       case AST_SIP_AUTH_TYPE_USER_PASS:
-               pj_strdup2(pool, &info->data, auth->auth_pass);
-               info->data_type = PJSIP_CRED_DATA_PLAIN_PASSWD;
-               break;
-       case AST_SIP_AUTH_TYPE_MD5:
-               pj_strdup2(pool, &info->data, auth->md5_creds);
-               info->data_type = PJSIP_CRED_DATA_DIGEST;
-               break;
-       default:
-               return PJSIP_SC_FORBIDDEN;
+       if (!ast_sip_auth_is_algorithm_available(auth, &auth->supported_algorithms_uas,
+               algorithm->algorithm_type)) {
+               /*
+                * This shouldn't happen because we shouldn't have sent a challenge for
+                * an unsupported algorithm.
+                */
+               SCOPE_EXIT_RTN_VALUE(PJSIP_SC_FORBIDDEN, "%s:%s: Algorithm '" PJSTR_PRINTF_SPEC
+                       "' not supported or auth doesn't contain appropriate credentials\n",
+                       auth_name, src_name, PJSTR_PRINTF_VAR(algorithm->iana_name));
        }
-       return PJ_SUCCESS;
+
+       pj_strdup2(pool, &cred_info->realm, realm);
+       pj_strdup2(pool, &cred_info->username, auth->auth_user);
+
+       creds = ast_sip_auth_get_creds(auth, algorithm->algorithm_type, &cred_info->data_type);
+       if (!creds) {
+               /*
+                * This shouldn't happen because we checked the auth object when we
+                * loaded it to make sure it had the appropriate credentials for each
+                * algorithm in supported_algorithms_uas.
+                */
+               SCOPE_EXIT_RTN_VALUE(PJSIP_SC_FORBIDDEN, "%s:%s: No plain text or digest password found for algorithm '" PJSTR_PRINTF_SPEC "'\n",
+                       auth_name, src_name, PJSTR_PRINTF_VAR(algorithm->iana_name));
+       }
+       pj_strdup2(pool, &cred_info->data, creds);
+#ifdef HAVE_PJSIP_AUTH_NEW_DIGESTS
+       if (cred_info->data_type == PJSIP_CRED_DATA_DIGEST) {
+               cred_info->algorithm_type = algorithm->algorithm_type;
+       }
+#endif
+
+       SCOPE_EXIT_RTN_VALUE(PJ_SUCCESS, "%s:%s: Success.  Data type: %s  Algorithm '" PJSTR_PRINTF_SPEC "'\n",
+               auth_name, src_name, cred_info->data_type ? "digest" : "plain text", PJSTR_PRINTF_VAR(algorithm->iana_name));
 }
 
 /*!
@@ -202,7 +284,8 @@ static pj_status_t digest_lookup(pj_pool_t *pool, const pj_str_t *realm,
  * \param rdata The incoming request
  * \param realm The realm for which authentication should occur
  */
-static int build_nonce(struct ast_str **nonce, const char *timestamp, const pjsip_rx_data *rdata, const char *realm)
+static int build_nonce(struct ast_str **nonce, const char *timestamp,
+       const pjsip_rx_data *rdata, const char *realm)
 {
        struct ast_str *str = ast_str_alloca(256);
        RAII_VAR(char *, eid, ao2_global_obj_ref(entity_id), ao2_cleanup);
@@ -255,7 +338,7 @@ static int check_nonce(const char *candidate, const pjsip_rx_data *rdata, const
                return 0;
        }
 
-       build_nonce(&calculated, timestamp, rdata, auth->realm);
+       build_nonce(&calculated, timestamp, rdata, S_OR(auth->realm, default_realm));
        ast_debug(3, "Calculated nonce %s. Actual nonce is %s\n", ast_str_buffer(calculated), candidate);
        if (strcmp(ast_str_buffer(calculated), candidate)) {
                return 0;
@@ -263,34 +346,6 @@ static int check_nonce(const char *candidate, const pjsip_rx_data *rdata, const
        return 1;
 }
 
-static int find_challenge(const pjsip_rx_data *rdata, const struct ast_sip_auth *auth)
-{
-       struct pjsip_authorization_hdr *auth_hdr = (pjsip_authorization_hdr *) &rdata->msg_info.msg->hdr;
-       int challenge_found = 0;
-       char nonce[64];
-
-       while ((auth_hdr = (pjsip_authorization_hdr *) pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_AUTHORIZATION, auth_hdr->next))) {
-               ast_copy_pj_str(nonce, &auth_hdr->credential.digest.nonce, sizeof(nonce));
-               if (check_nonce(nonce, rdata, auth) && !pj_strcmp2(&auth_hdr->credential.digest.realm, auth->realm)) {
-                       challenge_found = 1;
-                       break;
-               }
-       }
-
-       return challenge_found;
-}
-
-/*!
- * \brief Common code for initializing a pjsip_auth_srv
- */
-static void setup_auth_srv(pj_pool_t *pool, pjsip_auth_srv *auth_server, const char *realm)
-{
-       pj_str_t realm_str;
-       pj_cstr(&realm_str, realm);
-
-       pjsip_auth_srv_init(pool, auth_server, &realm_str, digest_lookup, 0);
-}
-
 /*!
  * \brief Result of digest verification
  */
@@ -311,69 +366,147 @@ static char *verify_result_str[] = {
        "STALE",
        "NOAUTH"
 };
+
+static enum digest_verify_result find_authorization(const char *endpoint_id,
+       const struct ast_sip_auth *auth, const pjsip_rx_data *rdata)
+{
+       const char *auth_id = ast_sorcery_object_get_id(auth);
+       const char *src_name = rdata->pkt_info.src_name;
+       const char *realm = S_OR(auth->realm, default_realm);
+       struct pjsip_authorization_hdr *auth_hdr =
+               (pjsip_authorization_hdr *) &rdata->msg_info.msg->hdr;
+       enum digest_verify_result res = AUTH_NOAUTH;
+       int authorization_found = 0;
+       char nonce[64];
+       SCOPE_ENTER(3, "%s:%s:%s: realm: %s\n",
+               endpoint_id, auth_id, src_name, realm);
+
+       while ((auth_hdr = pjsip_msg_find_hdr(rdata->msg_info.msg,
+               PJSIP_H_AUTHORIZATION, auth_hdr ? auth_hdr->next : NULL))) {
+               ast_copy_pj_str(nonce, &auth_hdr->credential.digest.nonce, sizeof(nonce));
+               ast_trace(-1, "%s:%s:%s: Checking nonce %s  hdr-realm: " PJSTR_PRINTF_SPEC "  hdr-algo: " PJSTR_PRINTF_SPEC " \n",
+                       endpoint_id, auth_id, src_name, nonce,
+                       PJSTR_PRINTF_VAR(auth_hdr->credential.digest.realm),
+                       PJSTR_PRINTF_VAR(auth_hdr->credential.digest.algorithm));
+               authorization_found++;
+               if (check_nonce(nonce, rdata, auth)
+                       && pj_strcmp2(&auth_hdr->credential.digest.realm, realm) == 0) {
+                       res = AUTH_SUCCESS;
+                       break;
+               } else {
+                       res = AUTH_STALE;
+               }
+       }
+       if (!authorization_found) {
+               ast_trace(-1, "%s:%s:%s: No Authorization header found\n",
+                       endpoint_id, auth_id, src_name);
+               res = AUTH_NOAUTH;
+       }
+
+       SCOPE_EXIT_RTN_VALUE(res, "%s:%s:%s: realm: %s Result %s\n",
+               endpoint_id, auth_id, src_name, realm, verify_result_str[res]);
+}
+
+/*!
+ * \brief Common code for initializing a pjsip_auth_srv
+ */
+static void setup_auth_srv(pj_pool_t *pool, pjsip_auth_srv *auth_server, const char *realm)
+{
+       pjsip_auth_srv_init_param *param = pj_pool_alloc(pool, sizeof(*param));
+       pj_str_t *pj_realm = pj_pool_alloc(pool, sizeof(*pj_realm));
+
+       pj_cstr(pj_realm, realm);
+       param->realm = pj_realm;
+       param->lookup2 = digest_lookup;
+       param->options = 0;
+
+       pjsip_auth_srv_init2(pool, auth_server, param);
+}
+
 /*!
- * \brief astobj2 callback for verifying incoming credentials
+ * \brief Verify incoming credentials
  *
- * \param auth The ast_sip_auth to check against
- * \param rdata The incoming request
- * \param pool A pool to use for the auth server
- * \return CMP_MATCH on successful authentication
- * \return 0 on failed authentication
+ * \param endpoint_id  For logging
+ * \param auth         The ast_sip_auth to check against
+ * \param rdata        The incoming request
+ * \param pool         A pool to use for the auth server
+ * \return One of digest_verify_result
  */
-static int verify(const struct ast_sip_auth *auth, pjsip_rx_data *rdata, pj_pool_t *pool)
+static int verify(const char *endpoint_id, const struct ast_sip_auth *auth,
+       pjsip_rx_data *rdata, pj_pool_t *pool)
 {
+       const char *auth_id = ast_sorcery_object_get_id(auth);
+       const char *realm = S_OR(auth->realm, default_realm);
+       const char *src_name = rdata->pkt_info.src_name;
        pj_status_t authed;
        int response_code;
        pjsip_auth_srv auth_server;
        int stale = 0;
-       int res = AUTH_FAIL;
+       enum digest_verify_result res = AUTH_FAIL;
+       SCOPE_ENTER(3, "%s:%s:%s: realm: %s\n",
+               endpoint_id, auth_id, src_name, realm);
+
+       res = find_authorization(endpoint_id, auth, rdata);
+       if (res == AUTH_NOAUTH)
+       {
+               ast_test_suite_event_notify("INCOMING_AUTH_VERIFY_RESULT",
+                       "Realm: %s\r\n"
+                       "Username: %s\r\n"
+                       "Status: %s",
+                       realm, auth->auth_user, verify_result_str[res]);
+               SCOPE_EXIT_RTN_VALUE(res, "%s:%s:%s: No Authorization header found\n",
+                       endpoint_id, auth_id, src_name);
+       }
 
-       if (!find_challenge(rdata, auth)) {
-               /* Couldn't find a challenge with a sane nonce.
+       if (res == AUTH_STALE) {
+               /* Couldn't find an authorization with a sane nonce.
                 * Nonce mismatch may just be due to staleness.
                 */
                stale = 1;
        }
 
-       setup_auth_srv(pool, &auth_server, auth->realm);
-
+       setup_auth_srv(pool, &auth_server, realm);
        store_auth(auth);
-       authed = pjsip_auth_srv_verify(&auth_server, rdata, &response_code);
+       /* pjsip_auth_srv_verify will invoke digest_lookup */
+       authed = SCOPE_CALL_WITH_RESULT(-1, pj_status_t, pjsip_auth_srv_verify, &auth_server, rdata, &response_code);
        remove_auth();
-
        if (authed == PJ_SUCCESS) {
                if (stale) {
                        res = AUTH_STALE;
                } else {
                        res = AUTH_SUCCESS;
                }
+       } else {
+               char err[256];
+               res = AUTH_FAIL;
+               pj_strerror(authed, err, sizeof(err));
+               ast_trace(-1, "%s:%s:%s: authed: %s\n", endpoint_id, auth_id, src_name, err);
        }
 
-       if (authed == PJSIP_EAUTHNOAUTH) {
-               res = AUTH_NOAUTH;
-       }
-
-       ast_debug(3, "Realm: %s  Username: %s  Result: %s\n",
-               auth->realm, auth->auth_user, verify_result_str[res]);
-
        ast_test_suite_event_notify("INCOMING_AUTH_VERIFY_RESULT",
                "Realm: %s\r\n"
                "Username: %s\r\n"
                "Status: %s",
-               auth->realm, auth->auth_user, verify_result_str[res]);
+               realm, auth->auth_user, verify_result_str[res]);
 
-       return res;
+       SCOPE_EXIT_RTN_VALUE(res, "%s:%s:%s: Realm: %s  Username: %s  Result: %s\n",
+               endpoint_id, auth_id, src_name, realm,
+               auth->auth_user, verify_result_str[res]);
 }
 
 /*!
- * \brief astobj2 callback for adding digest challenges to responses
+ * \brief Send a WWW-Authenticate challenge
  *
- * \param realm An auth's realm to build a challenge from
+ * \param endpoint_id  For logging
+ * \param auth The auth object to use for the challenge
  * \param tdata The response to add the challenge to
  * \param rdata The request the challenge is in response to
  * \param is_stale Indicates whether nonce on incoming request was stale
+ * \param algorithm_type The algorithm to use for the challenge
  */
-static void challenge(const char *realm, pjsip_tx_data *tdata, const pjsip_rx_data *rdata, int is_stale)
+static void challenge(const char *endpoint_id, struct ast_sip_auth *auth,
+       pjsip_tx_data *tdata, const pjsip_rx_data *rdata, int is_stale,
+       const pjsip_auth_algorithm *algorithm)
 {
        pj_str_t qop;
        pj_str_t pj_nonce;
@@ -381,6 +514,14 @@ static void challenge(const char *realm, pjsip_tx_data *tdata, const pjsip_rx_da
        struct ast_str *nonce = ast_str_alloca(256);
        char time_buf[32];
        time_t timestamp = time(NULL);
+       pj_status_t res;
+       const char *realm = S_OR(auth->realm, default_realm);
+       const char *auth_id = ast_sorcery_object_get_id(auth);
+       const char *src_name = rdata->pkt_info.src_name;
+       SCOPE_ENTER(5, "%s:%s:%s: realm: %s time: %d algorithm: " PJSTR_PRINTF_SPEC " stale? %s\n",
+               endpoint_id, auth_id, src_name, realm, (int)timestamp,
+               PJSTR_PRINTF_VAR(algorithm->iana_name), is_stale ? "yes" : "no");
+
        snprintf(time_buf, sizeof(time_buf), "%d", (int) timestamp);
 
        build_nonce(&nonce, time_buf, rdata, realm);
@@ -389,9 +530,27 @@ static void challenge(const char *realm, pjsip_tx_data *tdata, const pjsip_rx_da
 
        pj_cstr(&pj_nonce, ast_str_buffer(nonce));
        pj_cstr(&qop, "auth");
-       pjsip_auth_srv_challenge(&auth_server, &qop, &pj_nonce, NULL, is_stale ? PJ_TRUE : PJ_FALSE, tdata);
+#ifdef HAVE_PJSIP_AUTH_NEW_DIGESTS
+       res = pjsip_auth_srv_challenge2(&auth_server, &qop, &pj_nonce,
+               NULL, is_stale ? PJ_TRUE : PJ_FALSE, tdata, algorithm->algorithm_type);
+#else
+       res = pjsip_auth_srv_challenge(&auth_server, &qop, &pj_nonce,
+               NULL, is_stale ? PJ_TRUE : PJ_FALSE, tdata);
+#endif
+       SCOPE_EXIT_RTN("%s:%s:%s: Sending challenge for realm: %s algorithm: " PJSTR_PRINTF_SPEC
+               " %s\n",
+               endpoint_id, auth_id, src_name, realm, PJSTR_PRINTF_VAR(algorithm->iana_name),
+               res == PJ_SUCCESS ? "succeeded" : "failed");
 }
 
+static char *check_auth_result_str[] = {
+    "CHALLENGE",
+    "SUCCESS",
+    "FAILED",
+    "ERROR",
+};
+
+
 /*!
  * \brief Check authentication using Digest scheme
  *
@@ -405,7 +564,6 @@ static enum ast_sip_check_auth_result digest_check_auth(struct ast_sip_endpoint
                pjsip_rx_data *rdata, pjsip_tx_data *tdata)
 {
        struct ast_sip_auth **auths;
-       struct ast_sip_auth **auths_shallow;
        enum digest_verify_result *verify_res;
        struct ast_sip_endpoint *artificial_endpoint;
        enum ast_sip_check_auth_result res;
@@ -413,6 +571,9 @@ static enum ast_sip_check_auth_result digest_check_auth(struct ast_sip_endpoint
        int is_artificial;
        int failures = 0;
        size_t auth_size;
+       const char *endpoint_id = ast_sorcery_object_get_id(endpoint);
+       char *src_name = rdata->pkt_info.src_name;
+       SCOPE_ENTER(3, "%s:%s\n", endpoint_id, src_name);
 
        auth_size = AST_VECTOR_SIZE(&endpoint->inbound_auths);
        ast_assert(0 < auth_size);
@@ -423,81 +584,122 @@ static enum ast_sip_check_auth_result digest_check_auth(struct ast_sip_endpoint
        artificial_endpoint = ast_sip_get_artificial_endpoint();
        if (!artificial_endpoint) {
                /* Should not happen except possibly if we are shutting down. */
-               return AST_SIP_AUTHENTICATION_ERROR;
+               SCOPE_EXIT_RTN_VALUE(AST_SIP_AUTHENTICATION_ERROR);
        }
 
        is_artificial = endpoint == artificial_endpoint;
        ao2_ref(artificial_endpoint, -1);
        if (is_artificial) {
+               ast_trace(3, "%s:%s: Using artificial endpoint for authentication\n",
+                       endpoint_id, src_name);
                ast_assert(auth_size == 1);
                auths[0] = ast_sip_get_artificial_auth();
                if (!auths[0]) {
                        /* Should not happen except possibly if we are shutting down. */
-                       return AST_SIP_AUTHENTICATION_ERROR;
+                       SCOPE_EXIT_RTN_VALUE(AST_SIP_AUTHENTICATION_ERROR);
                }
        } else {
+               ast_trace(3, "%s:%s: Using endpoint for authentication\n",
+                       endpoint_id, src_name);
                memset(auths, 0, auth_size * sizeof(*auths));
+               /*
+                * If ast_sip_retrieve_auths returns a failure we still need
+                * to cleanup the auths array because it may have been partially
+                * filled in.
+                */
                if (ast_sip_retrieve_auths(&endpoint->inbound_auths, auths)) {
-                       res = AST_SIP_AUTHENTICATION_ERROR;
-                       goto cleanup;
+                       ast_sip_cleanup_auths(auths, auth_size);
+                       SCOPE_EXIT_RTN_VALUE(AST_SIP_AUTHENTICATION_ERROR,
+                               "%s:%s: Failed to retrieve some or all auth objects from endpoint\n",
+                               endpoint_id, src_name);
                }
        }
 
-       /* Setup shallow copy of auths */
-       if (ast_strlen_zero(default_realm)) {
-               auths_shallow = auths;
-       } else {
+       /*
+        * NOTE:  The only reason to use multiple auth objects as a UAS might
+        * be to send challenges for multiple realms however we currently don't
+        * know of anyone actually doing this.
+        */
+       for (idx = 0; idx < auth_size; ++idx) {
+               int i = 0;
+               struct ast_sip_auth *auth = auths[idx];
+               const char *realm = S_OR(auth->realm, default_realm);
+               const char *auth_id = ast_sorcery_object_get_id(auth);
+               SCOPE_ENTER(4, "%s:%s:%s: Verifying\n", endpoint_id, auth_id, src_name);
+
                /*
-                * Set default realm on a shallow copy of the authentication
-                * objects that don't have a realm set.
+                * Artificial auth objects are used for the purpose of
+                * sending challenges. We don't need to verify them.
                 */
-               auths_shallow = ast_alloca(auth_size * sizeof(*auths_shallow));
-               for (idx = 0; idx < auth_size; ++idx) {
-                       if (ast_strlen_zero(auths[idx]->realm)) {
-                               /*
-                                * Make a shallow copy and set the default realm on it.
-                                *
-                                * The stack allocation is OK here.  Normally this will
-                                * loop one time.  If you have multiple auths then you
-                                * shouldn't need more auths than the normal complement
-                                * of fingers and toes.  Otherwise, you should check
-                                * your sanity for setting up your system up that way.
-                                */
-                               auths_shallow[idx] = ast_alloca(sizeof(**auths_shallow));
-                               memcpy(auths_shallow[idx], auths[idx], sizeof(**auths_shallow));
-                               *((char **) (&auths_shallow[idx]->realm)) = default_realm;
-                               ast_debug(3, "Using default realm '%s' on incoming auth '%s'.\n",
-                                       default_realm, ast_sorcery_object_get_id(auths_shallow[idx]));
-                       } else {
-                               auths_shallow[idx] = auths[idx];
+               if (auth->type == AST_SIP_AUTH_TYPE_ARTIFICIAL) {
+                       ast_trace(-1, "%s:%s:%s: Skipping verification on artificial endpoint\n", endpoint_id, auth_id, src_name )
+                       verify_res[idx] = AUTH_NOAUTH;
+               } else {
+                       verify_res[idx] = SCOPE_CALL_WITH_RESULT(-1, int, verify, endpoint_id, auth, rdata, tdata->pool);
+                       if (verify_res[idx] == AUTH_SUCCESS) {
+                               res = AST_SIP_AUTHENTICATION_SUCCESS;
+                               SCOPE_EXIT_EXPR(break, "%s:%s:%s: success\n", endpoint_id, auth_id, src_name);
+                       }
+                       if (verify_res[idx] == AUTH_FAIL) {
+                               ast_trace(-1, "%s:%s:%s: fail\n", endpoint_id, auth_id, src_name);
+                               failures++;
                        }
                }
-       }
 
-       for (idx = 0; idx < auth_size; ++idx) {
-               verify_res[idx] = verify(auths_shallow[idx], rdata, tdata->pool);
-               if (verify_res[idx] == AUTH_SUCCESS) {
-                       res = AST_SIP_AUTHENTICATION_SUCCESS;
-                       goto cleanup;
-               }
-               if (verify_res[idx] == AUTH_FAIL) {
-                       failures++;
+               for (i = 0; i < AST_VECTOR_SIZE(&auth->supported_algorithms_uas); i++) {
+                       pjsip_auth_algorithm_type algorithm_type = AST_VECTOR_GET(&auth->supported_algorithms_uas, i);
+                       const pjsip_auth_algorithm *algorithm = ast_sip_auth_get_algorithm_by_type(algorithm_type);
+                       pjsip_www_authenticate_hdr *auth_hdr = NULL;
+                       int already_sent_challenge = 0;
+                       SCOPE_ENTER(5, "%s:%s:%s: Challenging with " PJSTR_PRINTF_SPEC "\n",
+                               endpoint_id, auth_id, src_name, PJSTR_PRINTF_VAR(algorithm->iana_name));
+
+                       /*
+                        * Per RFC 7616, if we've already sent a challenge for this realm
+                        * and algorithm, we must not send another.
+                        */
+                       while ((auth_hdr = pjsip_msg_find_hdr(tdata->msg,
+                               PJSIP_H_WWW_AUTHENTICATE, auth_hdr ? auth_hdr->next : NULL))) {
+                               if (pj_strcmp2(&auth_hdr->challenge.common.realm, realm) == 0 &&
+                                       !pj_stricmp(&auth_hdr->challenge.digest.algorithm, &algorithm->iana_name)) {
+                                       ast_trace(-1, "%s:%s:%s: Not sending duplicate challenge for realm: %s algorithm: "
+                                               PJSTR_PRINTF_SPEC "\n",
+                                               endpoint_id, auth_id, src_name, realm, PJSTR_PRINTF_VAR(algorithm->iana_name));
+                                       already_sent_challenge = 1;
+                               }
+                       }
+                       if (already_sent_challenge) {
+                               SCOPE_EXIT_EXPR(continue);
+                       }
+
+                       SCOPE_CALL(5, challenge, endpoint_id, auth, tdata, rdata,
+                               verify_res[idx] == AUTH_STALE, algorithm);
+
+                       SCOPE_EXIT("%s:%s:%s: Challenged with " PJSTR_PRINTF_SPEC "\n",
+                               endpoint_id, auth_id, src_name, PJSTR_PRINTF_VAR(algorithm->iana_name));
                }
+               SCOPE_EXIT("%s:%s:%s: Done with auth challenge\n", endpoint_id, auth_id, src_name);
        }
 
-       for (idx = 0; idx < auth_size; ++idx) {
-               challenge(auths_shallow[idx]->realm, tdata, rdata, verify_res[idx] == AUTH_STALE);
-       }
+       /*
+        * If we've sent challenges for multiple auth objects, we currently
+        * return SUCCESS when the first one succeeds. We may want to change
+        * this in the future to require that all succeed but as stated above,
+        * currently we don't have a use case for even using more than one
+        * auth object as a UAS.
+        */
 
        if (failures == auth_size) {
                res = AST_SIP_AUTHENTICATION_FAILED;
-       } else {
+       } else if (res != AST_SIP_AUTHENTICATION_SUCCESS){
                res = AST_SIP_AUTHENTICATION_CHALLENGE;
        }
 
-cleanup:
        ast_sip_cleanup_auths(auths, auth_size);
-       return res;
+       SCOPE_EXIT_RTN_VALUE(res, "%s:%s: Result: %s\n",
+               endpoint_id, src_name,
+               check_auth_result_str[res]);
+
 }
 
 static struct ast_sip_authenticator digest_authenticator = {
index aee4afc90ec5a23b9b4090ce1c278eb9c5f24380..e94fde3bac3a92786601fa87bd777b8e8648c57a 100644 (file)
  * 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
@@ -59,27 +63,240 @@ static pjsip_hdr_e get_auth_search_type(pjsip_rx_data *challenge)
 
 /*!
  * \internal
- * \brief Determine if digest algorithm in the header is one we support
+ * \brief Determine if digest algorithm in the header is one supported by
+ * pjproject and OpenSSL.
+ */
+static const pjsip_auth_algorithm *get_supported_algorithm(pjsip_www_authenticate_hdr *auth_hdr)
+{
+       const pjsip_auth_algorithm *algo = NULL;
+
+       algo = ast_sip_auth_get_algorithm_by_iana_name(&auth_hdr->challenge.digest.algorithm);
+       if (!algo) {
+               return NULL;
+       }
+
+       if (ast_sip_auth_is_algorithm_supported(algo->algorithm_type)) {
+               return algo;
+       }
+       return NULL;
+}
+
+AST_VECTOR(cred_info_vector, pjsip_cred_info);
+
+/*!
+ * \brief Get credentials (if any) from auth objects for a WWW/Proxy-Authenticate header
  *
- * \retval 1 If we support the algorithm
- * \retval 0 If we do not
+ * \param id                   For logging
+ * \param src_name             For logging
+ * \param auth_hdr             The *-Authenticate header to check
+ * \param auth_object_count    The number of auth objects available
+ * \param auth_objects_vector  The vector of available auth objects
+ * \param auth_creds           The vector to store the credentials in
+ * \param realms               For logging
  *
  */
-static int is_digest_algorithm_supported(pjsip_www_authenticate_hdr *auth_hdr)
+static void get_creds_for_header(const char *id, const char *src_name,
+       pjsip_www_authenticate_hdr *auth_hdr, size_t auth_object_count,
+       const struct ast_sip_auth_objects_vector *auth_objects_vector,
+       struct cred_info_vector *auth_creds, struct ast_str **realms)
 {
-       int digest;
+       int exact_match_index = -1;
+       int wildcard_match_index = -1;
+       struct ast_sip_auth *found_auth = NULL;
+       const pjsip_auth_algorithm *challenge_algorithm =
+               get_supported_algorithm(auth_hdr);
+       int i = 0;
+       pjsip_cred_info auth_cred;
+       const char *cred_data;
+       int res = 0;
+       SCOPE_ENTER(4, "%s:%s: Testing header realm: '" PJSTR_PRINTF_SPEC "' algorithm: '"
+               PJSTR_PRINTF_SPEC "'\n", id, src_name,
+               PJSTR_PRINTF_VAR(auth_hdr->challenge.digest.realm),
+               PJSTR_PRINTF_VAR(auth_hdr->challenge.digest.algorithm));
+
+       if (!challenge_algorithm) {
+               SCOPE_EXIT_RTN("%s:%s: Skipping header with realm '" PJSTR_PRINTF_SPEC "' "
+                       "and unsupported " PJSTR_PRINTF_SPEC "' algorithm \n", id, src_name,
+                       PJSTR_PRINTF_VAR(auth_hdr->challenge.digest.realm),
+                       PJSTR_PRINTF_VAR(auth_hdr->challenge.digest.algorithm));
+       }
+
+       /*
+        * If we already have credentials for this realm, we don't need to
+        * process this header.  We can just skip it.
+        */
+       for (i = 0; i < AST_VECTOR_SIZE(auth_creds); i++) {
+               pjsip_cred_info auth_cred = AST_VECTOR_GET(auth_creds, i);
+               if (pj_stricmp(&auth_cred.realm, &auth_hdr->challenge.common.realm) == 0) {
+                       SCOPE_EXIT_RTN("%s:%s: Skipping header with realm '" PJSTR_PRINTF_SPEC "' "
+                               "because we already have credentials for it\n", id, src_name,
+                               PJSTR_PRINTF_VAR(auth_hdr->challenge.digest.realm));
+               }
+       }
 
-       /* An empty digest is assumed to be md5 */
-       if (pj_strlen(&auth_hdr->challenge.digest.algorithm) == 0) {
-               return 1;
+       /*
+        * Appending "realm/agorithm" to realms is strictly so
+        * digest_create_request_with_auth() can display good error messages.
+        */
+       if (*realms) {
+               ast_str_append(realms, 0, PJSTR_PRINTF_SPEC "/" PJSTR_PRINTF_SPEC ", ",
+                       PJSTR_PRINTF_VAR(auth_hdr->challenge.digest.realm),
+                       PJSTR_PRINTF_VAR(auth_hdr->challenge.digest.algorithm));
        }
 
-       for (digest = 0; digest < ARRAY_LEN(supported_digest_algorithms); digest++) {
-               if (pj_stricmp(&auth_hdr->challenge.digest.algorithm, &supported_digest_algorithms[digest]) == 0) {
-                       return 1;
+       /*
+        * Now that we have a valid header, we can loop over the auths available to
+        * find either an exact realm match or, failing that, a wildcard auth (an
+        * auth with an empty or "*" realm).
+        *
+        * NOTE: We never use the global default realm when we're the UAC responding
+        * to a 401 or 407.  We only use that when we're the UAS (handled elsewhere)
+        * and the auth object didn't have a realm.
+        */
+       ast_trace(-1, "%s:%s: Searching %zu auths to find matching ones for header with realm "
+               "'" PJSTR_PRINTF_SPEC "' and algorithm '" PJSTR_PRINTF_SPEC "'\n",
+               id, src_name, auth_object_count,
+               PJSTR_PRINTF_VAR(auth_hdr->challenge.digest.realm),
+               PJSTR_PRINTF_VAR(auth_hdr->challenge.digest.algorithm));
+
+       for (i = 0; i < auth_object_count; ++i) {
+               struct ast_sip_auth *auth = AST_VECTOR_GET(auth_objects_vector, i);
+               const char *auth_id = ast_sorcery_object_get_id(auth);
+               SCOPE_ENTER(5, "%s:%s: Checking auth '%s' with realm '%s'\n",
+                       id, src_name, auth_id, auth->realm);
+
+               /*
+                * Is the challenge algorithm in the auth's supported_algorithms_uac
+                * and is there either a plain text password or a password_digest
+                * for the algorithm?
+                */
+               if (!ast_sip_auth_is_algorithm_available(auth, &auth->supported_algorithms_uac,
+                       challenge_algorithm->algorithm_type)) {
+                       SCOPE_EXIT_EXPR(continue, "%s:%s: Skipping auth '%s' with realm '%s' because it doesn't support "
+                               " algorithm '" PJSTR_PRINTF_SPEC "'\n", id, src_name,
+                               auth_id, auth->realm,
+                               PJSTR_PRINTF_VAR(challenge_algorithm->iana_name));
                }
+
+               /*
+                * If this auth object's realm exactly matches the one
+                * from the header, we can just break out and use it.
+                *
+                * NOTE: If there's more than one auth object for an endpoint with
+                * a matching realm it's a misconfiguration.  We'll only use the first.
+                */
+               if (pj_stricmp2(&auth_hdr->challenge.digest.realm, auth->realm) == 0) {
+                       exact_match_index = i;
+                       /*
+                        * If we found an exact realm match, there's no need to keep
+                        * looking for a wildcard.
+                        */
+                       SCOPE_EXIT_EXPR(break, "%s:%s: Found matching auth '%s' with realm '%s'\n",
+                               id, src_name, auth_id, auth->realm);
+               }
+
+               /*
+                * If this auth object's realm is empty or a "*", it's a wildcard
+                * auth object.  We going to save its index but keep iterating over
+                * the vector in case we find an exact match later.
+                *
+                * NOTE: If there's more than one wildcard auth object for an endpoint
+                * it's a misconfiguration.  We'll only use the first.
+                */
+               if (wildcard_match_index < 0
+                       && (ast_strlen_zero(auth->realm) || ast_strings_equal(auth->realm, "*"))) {
+                       ast_trace(-1, "%s:%s: Found wildcard auth '%s' for realm '" PJSTR_PRINTF_SPEC "'\n",
+                               id, src_name, auth_id,
+                               PJSTR_PRINTF_VAR(auth_hdr->challenge.digest.realm));
+                       wildcard_match_index = i;
+               }
+               SCOPE_EXIT("%s:%s: Done checking auth '%s' with realm '%s'. "
+                       "Found exact? %s  Found wildcard? %s\n", id, src_name,
+                       auth_id, auth->realm, exact_match_index >= 0 ? "yes" : "no",
+                               wildcard_match_index >= 0 ? "yes" : "no");
+       } /* End auth object loop */
+
+       if (exact_match_index < 0 && wildcard_match_index < 0) {
+               /*
+                * Didn't find either a wildcard or an exact realm match.
+                * Move on to the next header.
+                */
+               SCOPE_EXIT_RTN("%s:%s: No auth matching realm or no wildcard found for realm '" PJSTR_PRINTF_SPEC "'\n",
+                       id, src_name, PJSTR_PRINTF_VAR(auth_hdr->challenge.digest.realm));
        }
-       return 0;
+
+       if (exact_match_index >= 0) {
+               /*
+                * If we found an exact match, we'll always prefer that.
+                */
+               found_auth = AST_VECTOR_GET(auth_objects_vector, exact_match_index);
+               ast_trace(-1, "%s:%s: Using matched auth '%s' with realm '" PJSTR_PRINTF_SPEC "'\n",
+                       id, src_name, ast_sorcery_object_get_id(found_auth),
+                       PJSTR_PRINTF_VAR(auth_hdr->challenge.digest.realm));
+       } else {
+               /*
+                * We'll only use the wildcard if we didn't find an exact match.
+                */
+               found_auth = AST_VECTOR_GET(auth_objects_vector, wildcard_match_index);
+               ast_trace(-1, "%s:%s: Using wildcard auth '%s' for realm '" PJSTR_PRINTF_SPEC "'\n",
+                       id, src_name, ast_sorcery_object_get_id(found_auth),
+                       PJSTR_PRINTF_VAR(auth_hdr->challenge.digest.realm));
+       }
+
+       /*
+        * Now that we have an auth object to use, we need to create a
+        * pjsip_cred_info structure for each algorithm we support.
+        */
+
+       memset(&auth_cred, 0, sizeof(auth_cred));
+       /*
+        * Copy the fields from the auth_object to the
+        * pjsip_cred_info structure.
+        */
+       auth_cred.realm = auth_hdr->challenge.common.realm;
+       pj_cstr(&auth_cred.username, found_auth->auth_user);
+       pj_cstr(&auth_cred.scheme, "digest");
+
+       /*
+        * auth_cred.data_type tells us whether the credential is a plain text
+        * password or a pre-digested one.
+        */
+       cred_data = SCOPE_CALL_WITH_RESULT(-1, const char *, ast_sip_auth_get_creds,
+               found_auth, challenge_algorithm->algorithm_type, &auth_cred.data_type);
+       /*
+        * This can't really fail because we already called
+        * ast_sip_auth_is_algorithm_available() for the auth
+        * but we check anyway.
+        */
+       if (!cred_data) {
+               SCOPE_EXIT_RTN("%s:%s: Shouldn't have happened\n", id, src_name);
+       }
+
+       pj_cstr(&auth_cred.data, cred_data);
+#ifdef HAVE_PJSIP_AUTH_NEW_DIGESTS
+       if (auth_cred.data_type == PJSIP_CRED_DATA_DIGEST) {
+               auth_cred.algorithm_type = challenge_algorithm->algorithm_type;
+       }
+#endif
+       /*
+        * Because the vector contains actual structures and not pointers
+        * to structures, the call to AST_VECTOR_APPEND results in a simple
+        * assign of one structure to another, effectively copying the auth_cred
+        * structure contents to the array element.
+        *
+        * Also note that the calls to pj_cstr above set their respective
+        * auth_cred fields to the _pointers_ of their corresponding auth
+        * object fields.  This is safe because the call to
+        * pjsip_auth_clt_set_credentials() below strdups them before we
+        * return to the calling function which decrements the reference
+        * counts.
+        */
+       res = AST_VECTOR_APPEND(auth_creds, auth_cred);
+       SCOPE_EXIT_RTN("%s:%s: %s credential for realm: '" PJSTR_PRINTF_SPEC "' algorithm: '"
+               PJSTR_PRINTF_SPEC "'\n", id, src_name,
+               res == 0 ? "Added" : "Failed to add",
+               PJSTR_PRINTF_VAR(auth_hdr->challenge.digest.realm),
+               PJSTR_PRINTF_VAR(auth_hdr->challenge.digest.algorithm));
 }
 
 /*!
@@ -89,7 +306,7 @@ static int is_digest_algorithm_supported(pjsip_www_authenticate_hdr *auth_hdr)
  * RFC7616 and RFC8760 allow more than one WWW-Authenticate or
  * Proxy-Authenticate header per realm, each with different digest
  * algorithms (including new ones like SHA-256 and SHA-512-256). However,
- * thankfully, a UAS can NOT send back multiple Authenticate headers for
+ * a UAS can NOT send back multiple Authenticate headers for
  * the same realm with the same digest algorithm.  The UAS is also
  * supposed to send the headers in order of preference with the first one
  * being the most preferred.
@@ -99,14 +316,14 @@ static int is_digest_algorithm_supported(pjsip_www_authenticate_hdr *auth_hdr)
  *
  * The UAS can also send multiple realms, especially when it's a proxy
  * that has forked the request in which case the proxy will aggregate all
- * of the Authenticate and then them all back to the UAC.
+ * of the Authenticate headers into one response back to the UAC.
  *
  * It doesn't stop there though... Each realm can require a different
  * username from the others. There's also nothing preventing each digest
  * algorithm from having a unique password although I'm not sure if
  * that adds any benefit.
  *
- * So now... For each Authenticate header we encounter, we have to
+ * So now... For each WWW/Proxy-Authenticate header we encounter, we have to
  * determine if we support the digest algorithm and, if not, just skip the
  * header.  We then have to find an auth object that matches the realm AND
  * the digest algorithm or find a wildcard object that matches the digest
@@ -115,27 +332,22 @@ static int is_digest_algorithm_supported(pjsip_www_authenticate_hdr *auth_hdr)
  * we already added an auth object for that realm, we skip the header.
  * Otherwise we repeat the process for the next header.
  *
- * In the end, we'll have accumulated a list of credentials we can pass to
- * pjproject that it can use to add Authentication headers to a request.
- *
- * \note: Neither we nor pjproject can currently handle digest algorithms
- * other than MD5.  We don't even have a place for it in the ast_sip_auth
- * object. For this reason, we just skip processing any Authenticate
- * header that's not MD5.  When we support the others, we'll move the
- * check into the loop that searches the objects.
+ * In the end, we'll have accumulated a list of credentials, one per realm,
+ * we can pass to pjproject that it can use to add Authentication headers
+ * to a request.
  */
-static pj_status_t set_outbound_authentication_credentials(pjsip_auth_clt_sess *auth_sess,
-               const struct ast_sip_auth_objects_vector *auth_objects_vector, pjsip_rx_data *challenge,
-               struct ast_str **realms)
+static pj_status_t set_auth_creds(const char *id, pjsip_auth_clt_sess *auth_sess,
+       const struct ast_sip_auth_objects_vector *auth_objects_vector,
+       pjsip_rx_data *challenge, struct ast_str **realms)
 {
-       int i;
        size_t auth_object_count;
        pjsip_www_authenticate_hdr *auth_hdr = NULL;
        pj_status_t res = PJ_SUCCESS;
        pjsip_hdr_e search_type;
-       size_t cred_count;
+       size_t cred_count = 0;
        pjsip_cred_info *creds_array;
-
+       char *pj_err = NULL;
+       const char *src_name = challenge->pkt_info.src_name;
        /*
         * Normally vector elements are pointers to something else, usually
         * structures. In this case however, the elements are the
@@ -147,7 +359,8 @@ static pj_status_t set_outbound_authentication_credentials(pjsip_auth_clt_sess *
         * which we'll pass to pjsip_auth_clt_set_credentials() at the
         * end.
         */
-       AST_VECTOR(cred_info, pjsip_cred_info) auth_creds;
+       struct cred_info_vector auth_creds;
+       SCOPE_ENTER(3, "%s:%s\n", id, src_name);
 
        search_type = get_auth_search_type(challenge);
        if (search_type == PJSIP_H_OTHER) {
@@ -156,13 +369,14 @@ static pj_status_t set_outbound_authentication_credentials(pjsip_auth_clt_sess *
                 * so there are no WWW-Authenticate or Proxy-Authenticate
                 * headers to process.
                 */
-               return PJ_ENOTSUP;
+               SCOPE_EXIT_RTN_VALUE(PJ_ENOTSUP, "%s:%s: Status code %d was received when it should have been 401 or 407.\n",
+               id, src_name, challenge->msg_info.msg->line.status.code);
        }
 
        auth_object_count = AST_VECTOR_SIZE(auth_objects_vector);
        if (auth_object_count == 0) {
                /* This shouldn't happen but we'll check anyway. */
-               return PJ_EINVAL;
+               SCOPE_EXIT_RTN_VALUE(PJ_EINVAL, "%s:%s No auth objects available\n", id, src_name);
        }
 
        /*
@@ -176,183 +390,29 @@ static pj_status_t set_outbound_authentication_credentials(pjsip_auth_clt_sess *
         * actual structures, not pointers to structures.
         */
        if (AST_VECTOR_INIT(&auth_creds, 5) != 0) {
-               return PJ_ENOMEM;
+               SCOPE_EXIT_RTN_VALUE(PJ_ENOMEM);
        }
 
        /*
-        * It's going to be rare that we actually have more than one
-        * WWW-Authentication header or more than one auth object to
-        * match to it so the following nested loop should be fine.
+        * There may be multiple WWW/Proxy-Authenticate headers each one having
+        * a different realm/algorithm pair. Test each to see if we have credentials
+        * for it and accumulate them in the auth_creds vector.
+        * The code doesn't really care but just for reference, RFC-7616 says
+        * a UAS can't send multiple headers for the same realm with the same
+        * algorithm.  It also says the UAS should send the headers in order
+        * of preference with the first one being the most preferred.
         */
        while ((auth_hdr = pjsip_msg_find_hdr(challenge->msg_info.msg,
                search_type, auth_hdr ? auth_hdr->next : NULL))) {
-               int exact_match_index = -1;
-               int wildcard_match_index = -1;
-               int match_index = 0;
-               pjsip_cred_info auth_cred;
-               struct ast_sip_auth *auth = NULL;
 
-               memset(&auth_cred, 0, sizeof(auth_cred));
-               /*
-                * Since we only support the MD5 algorithm at the current time,
-                * there's no sense searching for auth objects that match the algorithm.
-                * In fact, the auth_object structure doesn't even have a member
-                * for it.
-                *
-                * When we do support more algorithms, this check will need to be
-                * moved inside the auth object loop below.
-                *
-                * Note: The header may not have specified an algorithm at all in which
-                * case it's assumed to be MD5. is_digest_algorithm_supported() returns
-                * true for that case.
-                */
-               if (!is_digest_algorithm_supported(auth_hdr)) {
-                       ast_debug(3, "Skipping header with realm '%.*s' and unsupported '%.*s' algorithm \n",
-                               (int)auth_hdr->challenge.digest.realm.slen, auth_hdr->challenge.digest.realm.ptr,
-                               (int)auth_hdr->challenge.digest.algorithm.slen, auth_hdr->challenge.digest.algorithm.ptr);
-                       continue;
-               }
+               get_creds_for_header(id, src_name, auth_hdr, auth_object_count,
+                       auth_objects_vector, &auth_creds, realms);
 
-               /*
-                * Appending the realms is strictly so digest_create_request_with_auth()
-                * can display good error messages.  Since we only support one algorithm,
-                * there can't be more than one header with the same realm.  No need to worry
-                * about duplicate realms until then.
-                */
-               if (*realms) {
-                       ast_str_append(realms, 0, "%.*s, ",
-                               (int)auth_hdr->challenge.digest.realm.slen, auth_hdr->challenge.digest.realm.ptr);
-               }
-
-               ast_debug(3, "Searching auths to find matching ones for header with realm '%.*s' and algorithm '%.*s'\n",
-                       (int)auth_hdr->challenge.digest.realm.slen, auth_hdr->challenge.digest.realm.ptr,
-                       (int)auth_hdr->challenge.digest.algorithm.slen, auth_hdr->challenge.digest.algorithm.ptr);
-
-               /*
-                * Now that we have a valid header, we can loop over the auths available to
-                * find either an exact realm match or, failing that, a wildcard auth (an
-                * auth with an empty or "*" realm).
-                *
-                * NOTE: We never use the global default realm when we're the UAC responding
-                * to a 401 or 407.  We only use that when we're the UAS (handled elsewhere)
-                * and the auth object didn't have a realm.
-                */
-               for (i = 0; i < auth_object_count; ++i) {
-                       auth = AST_VECTOR_GET(auth_objects_vector, i);
-
-                       /*
-                        * If this auth object's realm exactly matches the one
-                        * from the header, we can just break out and use it.
-                        *
-                        * NOTE: If there's more than one auth object for an endpoint with
-                        * a matching realm it's a misconfiguration.  We'll only use the first.
-                        */
-                       if (pj_stricmp2(&auth_hdr->challenge.digest.realm, auth->realm) == 0) {
-                               ast_debug(3, "Found matching auth '%s' with realm '%s'\n", ast_sorcery_object_get_id(auth),
-                                       auth->realm);
-                               exact_match_index = i;
-                               /*
-                                * If we found an exact realm match, there's no need to keep
-                                * looking for a wildcard.
-                                */
-                               break;
-                       }
-
-                       /*
-                        * If this auth object's realm is empty or a "*", it's a wildcard
-                        * auth object.  We going to save its index but keep iterating over
-                        * the vector in case we find an exact match later.
-                        *
-                        * NOTE: If there's more than one wildcard auth object for an endpoint
-                        * it's a misconfiguration.  We'll only use the first.
-                        */
-                       if (wildcard_match_index < 0
-                               && (ast_strlen_zero(auth->realm) || ast_strings_equal(auth->realm, "*"))) {
-                               ast_debug(3, "Found wildcard auth '%s' for realm '%.*s'\n", ast_sorcery_object_get_id(auth),
-                                       (int)auth_hdr->challenge.digest.realm.slen, auth_hdr->challenge.digest.realm.ptr);
-                               wildcard_match_index = i;
-                       }
-               }
-
-               if (exact_match_index < 0 && wildcard_match_index < 0) {
-                       /*
-                        * Didn't find either a wildcard or an exact realm match.
-                        * Move on to the next header.
-                        */
-                       ast_debug(3, "No auth matching realm or no wildcard found for realm '%.*s'\n",
-                               (int)auth_hdr->challenge.digest.realm.slen, auth_hdr->challenge.digest.realm.ptr);
-                       continue;
-               }
-
-               if (exact_match_index >= 0) {
-                       /*
-                        * If we found an exact match, we'll always prefer that.
-                        */
-                       match_index = exact_match_index;
-                       auth = AST_VECTOR_GET(auth_objects_vector, match_index);
-                       ast_debug(3, "Using matched auth '%s' with realm '%.*s'\n", ast_sorcery_object_get_id(auth),
-                               (int)auth_hdr->challenge.digest.realm.slen, auth_hdr->challenge.digest.realm.ptr);
-               } else {
-                       /*
-                        * We'll only use the wildcard if we didn't find an exact match.
-                        */
-                       match_index = wildcard_match_index;
-                       auth = AST_VECTOR_GET(auth_objects_vector, match_index);
-                       ast_debug(3, "Using wildcard auth '%s' for realm '%.*s'\n", ast_sorcery_object_get_id(auth),
-                               (int)auth_hdr->challenge.digest.realm.slen, auth_hdr->challenge.digest.realm.ptr);
-               }
-
-               /*
-                * Copy the fields from the auth_object to the
-                * pjsip_cred_info structure.
-                */
-               auth_cred.realm = auth_hdr->challenge.common.realm;
-               pj_cstr(&auth_cred.username, auth->auth_user);
-               pj_cstr(&auth_cred.scheme, "digest");
-               switch (auth->type) {
-               case AST_SIP_AUTH_TYPE_USER_PASS:
-                       pj_cstr(&auth_cred.data, auth->auth_pass);
-                       auth_cred.data_type = PJSIP_CRED_DATA_PLAIN_PASSWD;
-                       break;
-               case AST_SIP_AUTH_TYPE_MD5:
-                       pj_cstr(&auth_cred.data, auth->md5_creds);
-                       auth_cred.data_type = PJSIP_CRED_DATA_DIGEST;
-                       break;
-               case AST_SIP_AUTH_TYPE_GOOGLE_OAUTH:
-                       /* nothing to do. handled seperately in res_pjsip_outbound_registration */
-                       break;
-               case AST_SIP_AUTH_TYPE_ARTIFICIAL:
-                       ast_log(LOG_ERROR,
-                               "Trying to set artificial outbound auth credentials shouldn't happen.\n");
-                       continue;
-               } /* End auth object loop */
-
-               /*
-                * Because the vector contains actual structures and not pointers
-                * to structures, the call to AST_VECTOR_APPEND results in a simple
-                * assign of one structure to another, effectively copying the auth_cred
-                * structure contents to the array element.
-                *
-                * Also note that the calls to pj_cstr above set their respective
-                * auth_cred fields to the _pointers_ of their corresponding auth
-                * object fields.  This is safe because the call to
-                * pjsip_auth_clt_set_credentials() below strdups them before we
-                * return to the calling function which decrements the reference
-                * counts.
-                */
-               res = AST_VECTOR_APPEND(&auth_creds, auth_cred);
-               if (res != PJ_SUCCESS) {
-                       res = PJ_ENOMEM;
-                       goto cleanup;
-               }
        } /* End header loop */
 
        if (*realms && ast_str_strlen(*realms)) {
                /*
-                * Again, this is strictly so digest_create_request_with_auth()
-                * can display good error messages.
-                *
-                * Chop off the trailing ", " on the last realm.
+                * Chop off the trailing ", " on the last realm-algorithm.
                 */
                ast_str_truncate(*realms, ast_str_strlen(*realms) - 2);
        }
@@ -383,15 +443,15 @@ static pj_status_t set_outbound_authentication_credentials(pjsip_auth_clt_sess *
 
        res = pjsip_auth_clt_set_credentials(auth_sess, cred_count, creds_array);
        ast_free(creds_array);
-       if (res == PJ_SUCCESS) {
-               ast_debug(3, "Set %zu credentials in auth session\n", cred_count);
-       } else {
-               ast_log(LOG_ERROR, "Failed to set %zu credentials in auth session\n", cred_count);
-       }
 
 cleanup:
        AST_VECTOR_FREE(&auth_creds);
-       return res;
+       if (res != PJ_SUCCESS) {
+               pj_err = ast_alloca(PJ_ERR_MSG_SIZE);
+               pj_strerror(res, pj_err, PJ_ERR_MSG_SIZE);
+       }
+       SCOPE_EXIT_RTN_VALUE(res, "%s:%s: Set %zu credentials in auth session: %s\n",
+               id, src_name, cred_count, S_OR(pj_err, "success"));
 }
 
 /*!
@@ -415,12 +475,23 @@ static int digest_create_request_with_auth(const struct ast_sip_auth_vector *aut
        pj_status_t status;
        struct ast_sip_auth_objects_vector auth_objects_vector;
        size_t auth_object_count = 0;
-       struct ast_sip_endpoint *endpoint;
-       char *id = NULL;
-       const char *id_type;
+       pjsip_dialog *dlg = pjsip_rdata_get_dlg(challenge);
+       struct ast_sip_endpoint *endpoint = (dlg ? ast_sip_dialog_get_endpoint(dlg) : NULL);
+       /*
+        * We're ast_strdupa'ing the endpoint id because we're going to
+        * clean up the endpoint immediately after this. We only needed
+        * it to get the id for logging.
+        */
+       char *endpoint_id = endpoint ? ast_strdupa(ast_sorcery_object_get_id(endpoint)) : NULL;
+       char *id = endpoint_id ?: "noendpoint";
+       char *src_name = challenge->pkt_info.src_name;
        struct ast_str *realms = NULL;
-       pjsip_dialog *dlg;
        int res = -1;
+       char *pj_err = NULL;
+       SCOPE_ENTER(3, "%s:%s\n", id, src_name);
+
+       /* We only needed endpoint to get the id */
+       ao2_cleanup(endpoint);
 
        /*
         * Some older compilers have an issue with initializing structures with
@@ -429,31 +500,18 @@ static int digest_create_request_with_auth(const struct ast_sip_auth_vector *aut
         */
        memset(&auth_sess, 0, sizeof(auth_sess));
 
-       dlg = pjsip_rdata_get_dlg(challenge);
-       if (dlg) {
-               /* The only thing we use endpoint for is to get an id for error/debug messages */
-               endpoint = ast_sip_dialog_get_endpoint(dlg);
-               id = endpoint ? ast_strdupa(ast_sorcery_object_get_id(endpoint)) : NULL;
-               ao2_cleanup(endpoint);
-               id_type = "Endpoint";
-       }
-
-       /* If there was no dialog, then this is probably a REGISTER so no endpoint */
-       if (!id) {
-               /* The only thing we use the address for is to get an id for error/debug messages */
-               id = ast_alloca(AST_SOCKADDR_BUFLEN);
-               pj_sockaddr_print(&challenge->pkt_info.src_addr, id, AST_SOCKADDR_BUFLEN, 3);
-               id_type = "Host";
-       }
-
        if (!auth_ids_vector || AST_VECTOR_SIZE(auth_ids_vector) == 0) {
-               ast_log(LOG_ERROR, "%s: '%s': There were no auth ids available\n", id_type, id);
+               SCOPE_EXIT_LOG_RTN_VALUE(-1, LOG_ERROR, "%s:%s: There were no auth ids available\n",
+                       id, src_name);
                return -1;
        }
 
+       /*
+        * auth_ids_vector contains only ids but we need the complete objects.
+        */
        if (AST_VECTOR_INIT(&auth_objects_vector, AST_VECTOR_SIZE(auth_ids_vector)) != 0) {
-               ast_log(LOG_ERROR, "%s: '%s': Couldn't initialize auth object vector\n", id_type, id);
-               return -1;
+               SCOPE_EXIT_LOG_RTN_VALUE(-1, LOG_ERROR, "%s:%s: Couldn't initialize auth object vector\n",
+                       id, src_name);
        }
 
        /*
@@ -465,6 +523,8 @@ static int digest_create_request_with_auth(const struct ast_sip_auth_vector *aut
         *  AST_VECTOR_FREE(&auth_objects_vector);
         * when you're done with the vector
         */
+       ast_trace(-1, "%s:%s: Retrieving %d auth objects\n", id, src_name,
+               (int)AST_VECTOR_SIZE(auth_ids_vector));
        ast_sip_retrieve_auths_vector(auth_ids_vector, &auth_objects_vector);
        auth_object_count = AST_VECTOR_SIZE(&auth_objects_vector);
        if (auth_object_count == 0) {
@@ -475,13 +535,19 @@ static int digest_create_request_with_auth(const struct ast_sip_auth_vector *aut
                 * id that wasn't found.
                 */
                res = -1;
+               ast_trace(-1, "%s:%s: No auth objects found\n", id, src_name);
                goto cleanup;
        }
-
-       if (pjsip_auth_clt_init(&auth_sess, ast_sip_get_pjsip_endpoint(),
-                               old_request->pool, 0) != PJ_SUCCESS) {
-               ast_log(LOG_ERROR, "%s: '%s': Failed to initialize client authentication session\n",
-                       id_type, id);
+       ast_trace(-1, "%s:%s: Retrieved %d auth objects\n", id, src_name,
+               (int)auth_object_count);
+
+       status = pjsip_auth_clt_init(&auth_sess, ast_sip_get_pjsip_endpoint(),
+               old_request->pool, 0);
+       if (status != PJ_SUCCESS) {
+               pj_err = ast_alloca(PJ_ERR_MSG_SIZE);
+               pj_strerror(status, pj_err, PJ_ERR_MSG_SIZE);
+               ast_log(LOG_ERROR, "%s:%s: Failed to initialize client authentication session: %s\n",
+                       id, src_name, pj_err);
                res = -1;
                goto cleanup;
        }
@@ -499,18 +565,24 @@ static int digest_create_request_with_auth(const struct ast_sip_auth_vector *aut
         * Load pjproject with the valid credentials for the Authentication headers
         * received on the 401 or 407 response.
         */
-       status = set_outbound_authentication_credentials(&auth_sess, &auth_objects_vector, challenge, &realms);
+       status = SCOPE_CALL_WITH_RESULT(-1, pj_status_t, set_auth_creds, id, &auth_sess, &auth_objects_vector, challenge, &realms);
+       if (status != PJ_SUCCESS && status != PJSIP_ENOCREDENTIAL) {
+               pj_err = ast_alloca(PJ_ERR_MSG_SIZE);
+       }
+
        switch (status) {
        case PJ_SUCCESS:
                break;
        case PJSIP_ENOCREDENTIAL:
                ast_log(LOG_WARNING,
-                       "%s: '%s': No auth objects matching realm(s) '%s' from challenge found.\n", id_type, id,
-                       realms ? ast_str_buffer(realms) : "<none>");
+                       "%s:%s: No auth objects matching realm/algorithm(s) '%s' from challenge found.\n",
+                       id, src_name, realms ? ast_str_buffer(realms) : "<none>");
                res = -1;
                goto cleanup;
        default:
-               ast_log(LOG_WARNING, "%s: '%s': Failed to set authentication credentials\n", id_type, id);
+               pj_strerror(status, pj_err, PJ_ERR_MSG_SIZE);
+               ast_log(LOG_WARNING, "%s:%s: Failed to set authentication credentials: %s\n",
+                       id, src_name, pj_err);
                res = -1;
                goto cleanup;
        }
@@ -521,7 +593,11 @@ static int digest_create_request_with_auth(const struct ast_sip_auth_vector *aut
         * from an earlier successful authorization, it'll use it. Otherwise
         * it'll create a new authorization and cache it.
         */
-       status = pjsip_auth_clt_reinit_req(&auth_sess, challenge, old_request, new_request);
+       status = SCOPE_CALL_WITH_RESULT(-1, pj_status_t, pjsip_auth_clt_reinit_req,
+               &auth_sess, challenge, old_request, new_request);
+       if (status != PJ_SUCCESS) {
+               pj_err = ast_alloca(PJ_ERR_MSG_SIZE);
+       }
 
        switch (status) {
        case PJ_SUCCESS:
@@ -535,6 +611,7 @@ static int digest_create_request_with_auth(const struct ast_sip_auth_vector *aut
                ast_assert(cseq != NULL);
                ++cseq->cseq;
                res = 0;
+               ast_trace(-1, "%s:%s: Created new request with auth\n", id, src_name);
                goto cleanup;
        case PJSIP_ENOCREDENTIAL:
                /*
@@ -542,21 +619,24 @@ static int digest_create_request_with_auth(const struct ast_sip_auth_vector *aut
                 * did the matching but you never know.
                 */
                ast_log(LOG_WARNING,
-                       "%s: '%s': No auth objects matching realm(s) '%s' from challenge found.\n", id_type, id,
-                       realms ? ast_str_buffer(realms) : "<none>");
+                       "%s:%s: No auth objects matching realm(s) '%s' from challenge found.\n",
+                       id, src_name, realms ? ast_str_buffer(realms) : "<none>");
                break;
        case PJSIP_EAUTHSTALECOUNT:
+               pj_strerror(status, pj_err, PJ_ERR_MSG_SIZE);
                ast_log(LOG_WARNING,
-                       "%s: '%s': Unable to create request with auth.  Number of stale retries exceeded.\n",
-                       id_type, id);
+                       "%s:%s: Unable to create request with auth: %s\n",
+                       id, src_name, pj_err);
                break;
        case PJSIP_EFAILEDCREDENTIAL:
-               ast_log(LOG_WARNING, "%s: '%s': Authentication credentials not accepted by server.\n",
-                       id_type, id);
+               pj_strerror(status, pj_err, PJ_ERR_MSG_SIZE);
+               ast_log(LOG_WARNING, "%s:%s: Authentication credentials not accepted by server. %s\n",
+                       id, src_name, pj_err);
                break;
        default:
-               ast_log(LOG_WARNING, "%s: '%s': Unable to create request with auth. Unknown failure.\n",
-                       id_type, id);
+               pj_strerror(status, pj_err, PJ_ERR_MSG_SIZE);
+               ast_log(LOG_WARNING, "%s:%s: Unable to create request with auth: %s\n",
+                       id, src_name, pj_err);
                break;
        }
        res = -1;
@@ -573,7 +653,8 @@ cleanup:
        AST_VECTOR_FREE(&auth_objects_vector);
        ast_free(realms);
 
-       return res;
+       SCOPE_EXIT_RTN_VALUE(res, "%s:%s: result: %s\n", id, src_name,
+               res == 0 ? "success" : "failure");
 }
 
 static struct ast_sip_outbound_authenticator digest_authenticator = {
index eb768731c05d1c3f9d97bbf73b3b104ac215bc79..94622f2b5e58f11b8e86c1e4c31ff97fe31a6a6c 100644 (file)
@@ -139,6 +139,7 @@ AC_DEFUN([_PJPROJECT_CONFIGURE],
                AC_DEFINE([HAVE_PJSIP_OAUTH_AUTHENTICATION], 1, [Define if your system has HAVE_PJSIP_OAUTH_AUTHENTICATION declared])
                AC_DEFINE([HAVE_PJPROJECT_ON_VALID_ICE_PAIR_CALLBACK], 1, [Define if your system has the on_valid_pair pjnath callback.])
                AC_DEFINE([HAVE_PJSIP_TLS_TRANSPORT_RESTART], 1, [Define if your system has pjsip_tls_transport_restart support.])
+               AC_DEFINE([HAVE_PJSIP_AUTH_NEW_DIGESTS], 1, [Define if your system has pjsip new auth algorithm support.])
 
                AC_SUBST([PJPROJECT_BUNDLED])
                AC_SUBST([PJPROJECT_BUNDLED_OOT])
index 0492b04812e8485572f0a17f923067f98e8f2396..38afc021baf7d25c6d112660da494457b81bb1db 100644 (file)
@@ -99,7 +99,7 @@
 
 #define PJSIP_TSX_UAS_CONTINUE_ON_TP_ERROR 0
 #define PJ_SSL_SOCK_OSSL_USE_THREAD_CB 0
-#define PJSIP_AUTH_ALLOW_MULTIPLE_AUTH_HEADER 1
+#define PJSIP_AUTH_ALLOW_MULTIPLE_AUTH_HEADER 0
 
 /*
  * The default is 32 with 8 being used by pjproject itself.