From c49669b0058b77502cde94575109471f54a64168 Mon Sep 17 00:00:00 2001 From: George Joseph Date: Wed, 23 Oct 2024 11:32:05 -0600 Subject: [PATCH] Add full support for SHA-256 and SHA-512-256 digest algorithms There are no breaking changes for this work however several structures were extended with new fields. See below. In order to use the new algorithms, you MUST set the new pjsip_cred_info.ext.algorithm_type field to the appropriate value when the credential data type is PJSIP_CRED_DATA_DIGEST and when acting as a server, you must also use pjsip_auth_srv_challenge2() to send challenges so you can specify algorithms other than MD5. Summary of changes: * Added enum pjsip_auth_algorithm_type which list all digest algorithms supported. * Added struct pjsip_auth_algorithm which defines parameters for each algorithm including its IANA name, OpenSSL name, digest length and digest string representation length. * Added pjsip_auth_algorithm_type to the pjsip_cred_info structure so the digest algorithm can be specified when the cred data type is PJSIP_CRED_DATA_DIGEST. * Added pjsip_auth_algorithm_type to the pjsip_cached_auth_hdr structure so we can match on specific algorithm. * Added functions pjsip_auth_get_algorithm_by_type(), pjsip_auth_get_algorithm_by_iana_name(), and pjsip_auth_is_digest_algorithm_supported() to find and search for supported algorithms. * Added pjsip_authorization_hdr to the pjsip_auth_lookup_cred_param structure so we can look up credentiials by specific algorithm. * Added the pjsip_auth_srv_challenge2() function that takes a pjsip_auth_algorithm_type so users can create challenges with specific algorithms instead of defaulting to MD5. * pjsip_auth_create_digest() was heavily refactored to use the new algorithm_type contained in pjsip_cred_info to determine the algorithm to use when creating the digest. The function is now generic and can use any supported algorithm. If OpenSSL isn't available, it will fall back to the internal MD5 implementation. * pjsip_auth_create_digestSHA256() is now marked as deprecated and simply calls the new function with PJSIP_AUTH_ALGORITHM_SHA256. * sip_auth_client.c and sip_auth_server.c were refactored to support multiple digest algorithms. * sip_auth_client was updated to allow the AKEv2-MD5 algorithm to pass through to the callback specified in pjsip_cred_info. * A bug was fixed with the PJSIP_AUTH_ALLOW_MULTIPLE_AUTH_HEADER option where the default setting of 0 prevented sip_auth_client from responding to WWW/Proxy-Authenticate headers from different realms. The RFCs state that this behavior should be allowed. The comment for this option in sip_config.h was also updated to indicate that setting this option to 1 is probably not a good idea for security reasons. Resolves: #4119 --- pjlib/src/pj/ssl_sock_ossl.c | 1 + pjsip/include/pjsip/sip_auth.h | 197 +++++++- pjsip/include/pjsip/sip_config.h | 23 +- pjsip/src/pjsip/sip_auth_aka.c | 10 +- pjsip/src/pjsip/sip_auth_client.c | 732 ++++++++++++++++++------------ pjsip/src/pjsip/sip_auth_server.c | 92 +++- 6 files changed, 743 insertions(+), 312 deletions(-) diff --git a/pjlib/src/pj/ssl_sock_ossl.c b/pjlib/src/pj/ssl_sock_ossl.c index 3716f4f616..67f386d00d 100644 --- a/pjlib/src/pj/ssl_sock_ossl.c +++ b/pjlib/src/pj/ssl_sock_ossl.c @@ -713,6 +713,7 @@ static pj_status_t init_openssl(void) #if OPENSSL_VERSION_NUMBER < 0x009080ffL /* This is now synonym of SSL_library_init() */ OpenSSL_add_all_algorithms(); + OpenSSL_add_all_digests(); #endif /* Init available ciphers */ diff --git a/pjsip/include/pjsip/sip_auth.h b/pjsip/include/pjsip/sip_auth.h index fa55830fd7..d29a46c8ae 100644 --- a/pjsip/include/pjsip/sip_auth.h +++ b/pjsip/include/pjsip/sip_auth.h @@ -42,12 +42,49 @@ PJ_BEGIN_DECL * @{ */ -/** Length of digest MD5 string. */ +/** + * Length of digest MD5 string. + * \deprecated Use pjsip_auth_algorithm.digest_str_length instead. + */ #define PJSIP_MD5STRLEN 32 -/** Length of digest SHA256 string. */ +/** + * Length of digest SHA256 string. + * \deprecated Use pjsip_auth_algorithm.digest_str_length instead. + */ #define PJSIP_SHA256STRLEN 64 +/** + * Digest Algorithm Types. + * \warning These entries must remain in order with + * no gaps and with _NOT_SET = 0 and _COUNT as the last entry. + * + */ +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_AKAV2_MD5, + PJSIP_AUTH_ALGORITHM_COUNT, +} pjsip_auth_algorithm_type; + + +typedef struct pjsip_auth_algorithm +{ + pjsip_auth_algorithm_type algorithm_type; /**< Digest algorithm type */ + pj_str_t iana_name; /**< IANA/RFC name used in + SIP headers */ + const char *openssl_name; /**< The name used by + EVP_get_digestbyname()*/ + unsigned digest_length; /**< Length of the raw digest + in bytes */ + unsigned digest_str_length; /**< Length of the HEX + representation */ +} pjsip_auth_algorithm; + /** Type of data in the credential information in #pjsip_cred_info. */ typedef enum pjsip_cred_data_type @@ -59,6 +96,13 @@ typedef enum pjsip_cred_data_type } pjsip_cred_data_type; +#define PJSIP_CRED_DATA_PASSWD_MASK 0x000F +#define PJSIP_CRED_DATA_EXT_MASK 0x00F0 + +#define PJSIP_CRED_DATA_IS_AKA(cred) (((cred)->data_type & PJSIP_CRED_DATA_EXT_MASK) == PJSIP_CRED_DATA_EXT_AKA) +#define PJSIP_CRED_DATA_IS_PASSWD(cred) (((cred)->data_type & PJSIP_CRED_DATA_PASSWD_MASK) == PJSIP_CRED_DATA_PLAIN_PASSWD) +#define PJSIP_CRED_DATA_IS_DIGEST(cred) (((cred)->data_type & PJSIP_CRED_DATA_PASSWD_MASK) == PJSIP_CRED_DATA_DIGEST) + /** Authentication's quality of protection (qop) type. */ typedef enum pjsip_auth_qop_type { @@ -102,11 +146,14 @@ typedef pj_status_t (*pjsip_cred_cb)(pj_pool_t *pool, /** * This structure describes credential information. * A credential information is a static, persistent information that identifies - * username and password required to authorize to a specific realm. + * credentials required to authorize to a specific realm. * * Note that since PJSIP 0.7.0.1, it is possible to make a credential that is * valid for any realms, by setting the realm to star/wildcard character, * i.e. realm = pj_str("*");. + * + * You should always fill this structure with zeros using PJ_POOL_ZALLOC_T() + * or pj_bzero() before setting any fields. */ struct pjsip_cred_info { @@ -115,9 +162,15 @@ struct pjsip_cred_info challenges. */ pj_str_t scheme; /**< Scheme (e.g. "digest"). */ pj_str_t username; /**< User name. */ - int data_type; /**< Type of data (0 for plaintext passwd). */ + int data_type; /**< Type of data \ref pjsip_cred_data_type */ pj_str_t data; /**< The data, which can be a plaintext password or a hashed digest. */ + /** + * If the data_type is PJSIP_CRED_DATA_DIGEST and the digest algorithm + * used is not MD5 (the default), then this field MUST be set to the + * appropriate digest algorithm type. + */ + pjsip_auth_algorithm_type algorithm_type;/**< Digest algorithm type */ /** Extended data */ union { @@ -182,7 +235,8 @@ typedef struct pjsip_cached_auth pjsip_cached_auth_hdr cached_hdr;/**< List of cached header for each method. */ #endif - + pjsip_auth_algorithm_type challenge_algorithm_type; /**< Challenge + algorithm */ } pjsip_cached_auth; @@ -208,6 +262,44 @@ typedef struct pjsip_auth_clt_pref } pjsip_auth_clt_pref; +/** + * Get a pjsip_auth_algorithm structure by type. + * + * @param algorithm_type The algorithm type + * + * @return A pointer to a pjsip_auth_algorithm structure + * or NULL if not found. + */ +PJ_DECL(const pjsip_auth_algorithm *) pjsip_auth_get_algorithm_by_type( + pjsip_auth_algorithm_type algorithm_type); + + +/** + * Get a pjsip_auth_algorithm by IANA name. + * + * @param iana_name The IANA name (MD5, SHA-256, SHA-512-256) + * + * @return A pointer to a pjsip_auth_algorithm structure + * or NULL if not found. + */ +PJ_DECL(const pjsip_auth_algorithm *) pjsip_auth_get_algorithm_by_iana_name( + const pj_str_t *iana_name); + + +/** + * Check if a digest algorithm is supported. + * Algorithms that require support from OpenSSL will be checked + * to determine if they can actually be used. + * + * @param algorithm_type The algorithm type + * + * @return PJ_TRUE if the algorithm is supported, + * PJ_FALSE otherwise. + */ +PJ_DECL(pj_bool_t) pjsip_auth_is_algorithm_supported( + pjsip_auth_algorithm_type algorithm_type); + + /** * Duplicate a client authentication preference setting. * @@ -287,6 +379,7 @@ typedef struct pjsip_auth_lookup_cred_param pj_str_t realm; /**< Realm to find the account. */ pj_str_t acc_name; /**< Account name to look for. */ pjsip_rx_data *rdata; /**< Incoming request to be authenticated. */ + pjsip_authorization_hdr *auth_hdr; /**< Authorization header to be authenticated. */ } pjsip_auth_lookup_cred_param; @@ -549,7 +642,8 @@ PJ_DECL(pj_status_t) pjsip_auth_srv_verify( pjsip_auth_srv *auth_srv, * Add authentication challenge headers to the outgoing response in tdata. * Application may specify its customized nonce and opaque for the challenge, * or can leave the value to NULL to make the function fills them in with - * random characters. + * random characters. The digest algorithm defaults to MD5. If you need + * to specify a different algorithm, use \ref pjsip_auth_srv_challenge2. * * @param auth_srv The server authentication structure. * @param qop Optional qop value. @@ -569,9 +663,43 @@ PJ_DECL(pj_status_t) pjsip_auth_srv_challenge( pjsip_auth_srv *auth_srv, pjsip_tx_data *tdata); /** - * Helper function to create MD5 digest out of the specified + * Add authentication challenge headers to the outgoing response in tdata. + * Application may specify its customized nonce and opaque for the challenge, + * or can leave the value to NULL to make the function fills them in with + * random characters. + * Application must specify the algorithm to use. + * + * @param auth_srv The server authentication structure. + * @param qop Optional qop value. + * @param nonce Optional nonce value. + * @param opaque Optional opaque value. + * @param stale Stale indication. + * @param tdata The outgoing response message. The response must have + * 401 or 407 response code. + * @param algorithm_type One of the \ref pjsip_auth_algorithm_type values. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) pjsip_auth_srv_challenge2(pjsip_auth_srv *auth_srv, + const pj_str_t *qop, + const pj_str_t *nonce, + const pj_str_t *opaque, + pj_bool_t stale, + pjsip_tx_data *tdata, + const pjsip_auth_algorithm_type algorithm_type); + +/** + * Helper function to create a digest out of the specified * parameters. * + * \warning Because of ambiguities in the API, this function + * should only be used for backward compatibility with the + * MD5 digest algorithm. New code should use + * \ref pjsip_auth_create_digest2 + * + * cred_info->data_type must be PJSIP_CRED_DATA_PLAIN_PASSWORD + * or PJSIP_CRED_DATA_DIGEST. + * * @param result String to store the response digest. This string * must have been preallocated by caller with the * buffer at least PJSIP_MD5STRLEN (32 bytes) in size. @@ -599,6 +727,9 @@ PJ_DECL(pj_status_t) pjsip_auth_create_digest(pj_str_t *result, /** * Helper function to create SHA-256 digest out of the specified * parameters. + * \deprecated Use \ref pjsip_auth_create_digest2 with + * algorithm_type = PJSIP_AUTH_ALGORITHM_SHA256. + * * * @param result String to store the response digest. This string * must have been preallocated by caller with the @@ -614,7 +745,7 @@ PJ_DECL(pj_status_t) pjsip_auth_create_digest(pj_str_t *result, * * @return PJ_SUCCESS on success. */ -PJ_DEF(pj_status_t) pjsip_auth_create_digestSHA256(pj_str_t* result, +PJ_DECL(pj_status_t) pjsip_auth_create_digestSHA256(pj_str_t* result, const pj_str_t* nonce, const pj_str_t* nc, const pj_str_t* cnonce, @@ -624,6 +755,56 @@ PJ_DEF(pj_status_t) pjsip_auth_create_digestSHA256(pj_str_t* result, const pjsip_cred_info* cred_info, const pj_str_t* method); +/** + * Helper function to create a digest out of the specified + * parameters. + * + * cred_info->data_type must be PJSIP_CRED_DATA_PLAIN_PASSWORD + * or PJSIP_CRED_DATA_DIGEST. + * + * If cred_info->data_type is PJSIP_CRED_DATA_PLAIN_PASSWORD, + * cred_info->username + ":" + realm + ":" + cred_info->data + * will be hashed using the algorithm_type specified by the last + * parameter passed to this function to create the "ha1" hash. + * + * If cred_info->data_type is PJSIP_CRED_DATA_DIGEST, + * cred_info->data must contain the value of + * username + ":" + realm + ":" + password pre-hashed + * with the algorithm specifed by cred_info->algorithm_type + * and will be used as the "ha1" hash directly. In this case + * cred_info->algorithm_type MUST match the algorithm_type + * passed as the last parameter to this function. + * + * \note If left unset (0), cred_info->algorithm_type will + * default to PJSIP_AUTH_ALGORITHM_MD5. + * + * @param result String to store the response digest. This string + * must have been preallocated by the caller with the + * buffer at least as large as the digest_str_length + * member of the appropriate pjsip_auth_algorithm. + * @param nonce Optional nonce. + * @param nc Nonce count. + * @param cnonce Optional cnonce. + * @param qop Optional qop. + * @param uri URI. + * @param realm Realm. + * @param cred_info Credential info. + * @param method SIP method. + * @param algorithm_type The hash algorithm to use. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) pjsip_auth_create_digest2(pj_str_t *result, + const pj_str_t *nonce, + const pj_str_t *nc, + const pj_str_t *cnonce, + const pj_str_t *qop, + const pj_str_t *uri, + const pj_str_t *realm, + const pjsip_cred_info *cred_info, + const pj_str_t *method, + const pjsip_auth_algorithm_type algorithm_type); + /** * @} */ diff --git a/pjsip/include/pjsip/sip_config.h b/pjsip/include/pjsip/sip_config.h index ca7999bf21..f292e6c3d2 100644 --- a/pjsip/include/pjsip/sip_config.h +++ b/pjsip/include/pjsip/sip_config.h @@ -1331,10 +1331,25 @@ PJ_INLINE(pjsip_cfg_t*) pjsip_cfg(void) #endif /** - * Allow client to send multiple Authorization header when receiving multiple - * WWW-Authenticate header fields. If this is disabled, the stack will send - * Authorization header field containing credentials that match the - * topmost header field. + * RFC-7616 and RFC-8760 state that for each realm a UAS requires authentication + * for it can send a WWW/Proxy-Authenticate header for each digest algorithm it + * supports and for each realm, they must be added in most-preferred to least- + * preferred order. The RFCs also state that the UAS MUST NOT send multiple + * WWW/Proxy-Authenticate headers with the same realm and algorithm. + * + * The RFCs also state that the UAC SHOULD respond to the topmost header + * for each realm containing a digest algorithm it supports. Neither RFC + * however, states whether the UAC should send multiple Authorization headers + * for the same realm if it can support multiple digest algorithms. Common + * sense dicates though that the UAC should NOT send additional Authorization + * headers for the same realm once it's already sent a more preferred one. + * The reasoning is simple... If a UAS sends two WWW-Authenticate headers, + * the first for SHA-256 and the second for MD5, a UAC responding to both + * completely defeats the purpose of the UAS sending the more secure SHA-256. + * + * Having said that, if there is some corner case where continuing to send + * additional Authorization headers for the same realm is necessary, then + * this define can be set to 1 to allow it. * * Default is 0 */ diff --git a/pjsip/src/pjsip/sip_auth_aka.c b/pjsip/src/pjsip/sip_auth_aka.c index 1b15e9cbd1..c3d5d8b2fa 100644 --- a/pjsip/src/pjsip/sip_auth_aka.c +++ b/pjsip/src/pjsip/sip_auth_aka.c @@ -147,9 +147,10 @@ PJ_DEF(pj_status_t) pjsip_auth_create_aka_response( aka_cred.data.ptr = (char*)res; aka_cred.data.slen = PJSIP_AKA_RESLEN; - status = pjsip_auth_create_digest(&auth->response, &chal->nonce, + status = pjsip_auth_create_digest2(&auth->response, &chal->nonce, &auth->nc, &auth->cnonce, &auth->qop, - &auth->uri, &chal->realm, &aka_cred, method); + &auth->uri, &chal->realm, &aka_cred, method, + PJSIP_AUTH_ALGORITHM_MD5); } else if (aka_version == 2) { @@ -186,9 +187,10 @@ PJ_DEF(pj_status_t) pjsip_auth_create_aka_response( aka_cred.data.ptr, &len); aka_cred.data.slen = hmac64_len; - status = pjsip_auth_create_digest(&auth->response, &chal->nonce, + status = pjsip_auth_create_digest2(&auth->response, &chal->nonce, &auth->nc, &auth->cnonce, &auth->qop, - &auth->uri, &chal->realm, &aka_cred, method); + &auth->uri, &chal->realm, &aka_cred, method, + PJSIP_AUTH_ALGORITHM_MD5); } else { pj_assert(!"Bug!"); diff --git a/pjsip/src/pjsip/sip_auth_client.c b/pjsip/src/pjsip/sip_auth_client.c index c2c008a522..3a33d5bbb1 100644 --- a/pjsip/src/pjsip/sip_auth_client.c +++ b/pjsip/src/pjsip/sip_auth_client.c @@ -32,21 +32,21 @@ #include #include +#define WITHOUT_OPENSSL 1 #if PJ_HAS_SSL_SOCK && PJ_SSL_SOCK_IMP==PJ_SSL_SOCK_IMP_OPENSSL -# if !defined(PJSIP_AUTH_HAS_DIGEST_SHA256) -# define PJSIP_AUTH_HAS_DIGEST_SHA256 1 -# endif -#else -# undef PJSIP_AUTH_HAS_DIGEST_SHA256 -# define PJSIP_AUTH_HAS_DIGEST_SHA256 0 -#endif - -#if PJSIP_AUTH_HAS_DIGEST_SHA256 +#undef WITHOUT_OPENSSL +# include # include -# if OPENSSL_VERSION_NUMBER >= 0x30000000L -# include +# include +# include +# include + +# if OPENSSL_VERSION_NUMBER < 0x10100000L +# define EVP_MD_CTX_new() EVP_MD_CTX_create() +# define EVP_MD_CTX_free(ctx) EVP_MD_CTX_destroy(ctx) # endif + # ifdef _MSC_VER # include # if OPENSSL_VERSION_NUMBER >= 0x10100000L @@ -56,12 +56,41 @@ # pragma comment(lib, "ssleay32") # endif # endif -#endif +#else +#define MD5_DIGEST_LENGTH (PJSIP_MD5STRLEN / 2) +#define SHA256_DIGEST_LENGTH (PJSIP_SHA256STRLEN / 2) /* A macro just to get rid of type mismatch between char and unsigned char */ #define MD5_APPEND(pms,buf,len) pj_md5_update(pms, (const pj_uint8_t*)buf, \ (unsigned)len) +#define EVP_MD_CTX_new(mdctx) &pms +#define EVP_DigestInit_ex(mdctx, ...) pj_md5_init(mdctx) +#define EVP_DigestUpdate(mdctx, data, len) MD5_APPEND(mdctx, data, len) +#define EVP_DigestFinal_ex(mdctx, digest, ...) pj_md5_final(mdctx, digest) +#define EVP_MD_CTX_free(mdctx) +#endif + +const pjsip_auth_algorithm pjsip_auth_algorithms[] = { +/* TYPE IANA name OpenSSL name */ +/* Raw digest byte length Hex representation length */ + { PJSIP_AUTH_ALGORITHM_NOT_SET, {"", 0}, "", + 1, 1}, + { PJSIP_AUTH_ALGORITHM_MD5, {"MD5", 3}, "MD5", + MD5_DIGEST_LENGTH, MD5_DIGEST_LENGTH * 2}, + { PJSIP_AUTH_ALGORITHM_SHA256, {"SHA-256", 7}, "SHA256", + SHA256_DIGEST_LENGTH, SHA256_DIGEST_LENGTH * 2}, + { PJSIP_AUTH_ALGORITHM_SHA512_256, {"SHA-512-256", 11}, "SHA512-256", + SHA256_DIGEST_LENGTH, SHA256_DIGEST_LENGTH * 2}, + { PJSIP_AUTH_ALGORITHM_AKAV1_MD5, {"AKAv1-MD5", 9}, "", + MD5_DIGEST_LENGTH, MD5_DIGEST_LENGTH * 2}, + { PJSIP_AUTH_ALGORITHM_AKAV2_MD5, {"AKAv2-MD5", 9}, "", + MD5_DIGEST_LENGTH, MD5_DIGEST_LENGTH * 2}, + { PJSIP_AUTH_ALGORITHM_COUNT, {"", 0}, "", + 1, 1}, +}; + + /* Logging. */ #define THIS_FILE "sip_auth_client.c" #if 0 @@ -70,9 +99,6 @@ # define AUTH_TRACE_(expr) #endif -#define PASSWD_MASK 0x000F -#define EXT_MASK 0x00F0 - static void dup_bin(pj_pool_t *pool, pj_str_t *dst, const pj_str_t *src) { @@ -96,8 +122,9 @@ PJ_DEF(void) pjsip_cred_info_dup(pj_pool_t *pool, pj_strdup_with_null(pool, &dst->scheme, &src->scheme); pj_strdup_with_null(pool, &dst->username, &src->username); pj_strdup_with_null(pool, &dst->data, &src->data); + dst->algorithm_type = src->algorithm_type; - if ((dst->data_type & EXT_MASK) == PJSIP_CRED_DATA_EXT_AKA) { + if (PJSIP_CRED_DATA_IS_AKA(dst)) { dup_bin(pool, &dst->ext.aka.k, &src->ext.aka.k); dup_bin(pool, &dst->ext.aka.op, &src->ext.aka.op); dup_bin(pool, &dst->ext.aka.amf, &src->ext.aka.amf); @@ -120,8 +147,10 @@ PJ_DEF(int) pjsip_cred_info_cmp(const pjsip_cred_info *cred1, if (result) goto on_return; result = (cred1->data_type != cred2->data_type); if (result) goto on_return; + result = cred1->algorithm_type != cred2->algorithm_type; + if (result) goto on_return; - if ((cred1->data_type & EXT_MASK) == PJSIP_CRED_DATA_EXT_AKA) { + if (PJSIP_CRED_DATA_IS_AKA(cred1)) { result = pj_strcmp(&cred1->ext.aka.k, &cred2->ext.aka.k); if (result) goto on_return; result = pj_strcmp(&cred1->ext.aka.op, &cred2->ext.aka.op); @@ -162,158 +191,118 @@ static void digestNtoStr(const unsigned char digest[], int n, char *output) * Create response digest based on the parameters and store the * digest ASCII in 'result'. */ -PJ_DEF(pj_status_t) pjsip_auth_create_digest( pj_str_t *result, - const pj_str_t *nonce, - const pj_str_t *nc, - const pj_str_t *cnonce, - const pj_str_t *qop, - const pj_str_t *uri, - const pj_str_t *realm, - const pjsip_cred_info *cred_info, - const pj_str_t *method) +PJ_DEF(pj_status_t) pjsip_auth_create_digest2(pj_str_t *result, + const pj_str_t *nonce, + const pj_str_t *nc, + const pj_str_t *cnonce, + const pj_str_t *qop, + const pj_str_t *uri, + const pj_str_t *realm, + const pjsip_cred_info *cred_info, + const pj_str_t *method, + const pjsip_auth_algorithm_type algorithm_type) { - char ha1[PJSIP_MD5STRLEN]; - char ha2[PJSIP_MD5STRLEN]; - unsigned char digest[16]; + const pjsip_auth_algorithm *algorithm = NULL; + unsigned digest_len = 0; + unsigned digest_strlen = 0; + char *ha1 = NULL; + char *ha2 = NULL; + unsigned char *digest; + unsigned dig_len = digest_len; +#ifdef WITHOUT_OPENSSL pj_md5_context pms; + pj_md5_context *mdctx = &pms; + const void* md; +#else + EVP_MD_CTX* mdctx; + const EVP_MD* md; +#endif - pj_assert(result->slen >= PJSIP_MD5STRLEN); - - AUTH_TRACE_((THIS_FILE, "Begin creating digest")); - - if ((cred_info->data_type & PASSWD_MASK) == PJSIP_CRED_DATA_PLAIN_PASSWD) { - /*** - *** ha1 = MD5(username ":" realm ":" password) - ***/ - pj_md5_init(&pms); - MD5_APPEND( &pms, cred_info->username.ptr, cred_info->username.slen); - MD5_APPEND( &pms, ":", 1); - MD5_APPEND( &pms, realm->ptr, realm->slen); - MD5_APPEND( &pms, ":", 1); - MD5_APPEND( &pms, cred_info->data.ptr, cred_info->data.slen); - pj_md5_final(&pms, digest); - - digestNtoStr(digest, 16, ha1); - - } else if ((cred_info->data_type & PASSWD_MASK) == PJSIP_CRED_DATA_DIGEST) { - if (cred_info->data.slen != 32) { - pj_assert(!"Invalid cred_info data length"); - pj_bzero(result->ptr, result->slen); - result->slen = 0; - return PJ_EINVAL; - } - pj_memcpy( ha1, cred_info->data.ptr, cred_info->data.slen ); - } else { - pj_assert(!"Invalid data_type"); - pj_bzero(result->ptr, result->slen); - result->slen = 0; + if (!(result && nonce && uri && realm && cred_info && method)) { + PJ_LOG(4, (THIS_FILE, + "result, nonce, uri, realm, cred_info and method are all required")); return PJ_EINVAL; } + pj_bzero(result->ptr, result->slen); - AUTH_TRACE_((THIS_FILE, " ha1=%.32s", ha1)); + algorithm = pjsip_auth_get_algorithm_by_type(algorithm_type == PJSIP_AUTH_ALGORITHM_NOT_SET + ? PJSIP_AUTH_ALGORITHM_MD5 + : algorithm_type); - /*** - *** ha2 = MD5(method ":" req_uri) - ***/ - pj_md5_init(&pms); - MD5_APPEND( &pms, method->ptr, method->slen); - MD5_APPEND( &pms, ":", 1); - MD5_APPEND( &pms, uri->ptr, uri->slen); - pj_md5_final(&pms, digest); - digestNtoStr(digest, 16, ha2); - - AUTH_TRACE_((THIS_FILE, " ha2=%.32s", ha2)); - - /*** - *** When qop is not used: - *** response = MD5(ha1 ":" nonce ":" ha2) - *** - *** When qop=auth is used: - *** response = MD5(ha1 ":" nonce ":" nc ":" cnonce ":" qop ":" ha2) - ***/ - pj_md5_init(&pms); - MD5_APPEND( &pms, ha1, PJSIP_MD5STRLEN); - MD5_APPEND( &pms, ":", 1); - MD5_APPEND( &pms, nonce->ptr, nonce->slen); - if (qop && qop->slen != 0) { - MD5_APPEND( &pms, ":", 1); - MD5_APPEND( &pms, nc->ptr, nc->slen); - MD5_APPEND( &pms, ":", 1); - MD5_APPEND( &pms, cnonce->ptr, cnonce->slen); - MD5_APPEND( &pms, ":", 1); - MD5_APPEND( &pms, qop->ptr, qop->slen); + if (!algorithm) { + PJ_LOG(4, (THIS_FILE, "The algorithm_type is invalid")); + return PJ_ENOTSUP; } - MD5_APPEND( &pms, ":", 1); - MD5_APPEND( &pms, ha2, PJSIP_MD5STRLEN); - - /* This is the final response digest. */ - pj_md5_final(&pms, digest); - /* Convert digest to string and store in chal->response. */ - result->slen = PJSIP_MD5STRLEN; - digestNtoStr(digest, 16, result->ptr); - - AUTH_TRACE_((THIS_FILE, " digest=%.32s", result->ptr)); - AUTH_TRACE_((THIS_FILE, "Digest created")); - return PJ_SUCCESS; -} + if (!pjsip_auth_is_algorithm_supported(algorithm->algorithm_type)) { + PJ_LOG(4, (THIS_FILE, + "The algorithm (%.*s) referenced by algorithm_type is not supported", + (int)algorithm->iana_name.slen, algorithm->iana_name.ptr)); + return PJ_ENOTSUP; + } + if (qop && !(nc && cnonce)) { + PJ_LOG(4, (THIS_FILE, "nc and cnonce are required if qop is specified")); + return PJ_EINVAL; + } -/* - * Create response SHA-256 digest based on the parameters and store the - * digest ASCII in 'result'. - */ -PJ_DEF(pj_status_t) pjsip_auth_create_digestSHA256(pj_str_t *result, - const pj_str_t *nonce, - const pj_str_t *nc, - const pj_str_t *cnonce, - const pj_str_t *qop, - const pj_str_t *uri, - const pj_str_t *realm, - const pjsip_cred_info *cred_info, - const pj_str_t *method) -{ -#if PJSIP_AUTH_HAS_DIGEST_SHA256 + digest_len = algorithm->digest_length; + digest_strlen = algorithm->digest_str_length; - char ha1[PJSIP_SHA256STRLEN]; - char ha2[PJSIP_SHA256STRLEN]; - unsigned char digest[32]; + if (result->slen < digest_strlen) { + PJ_LOG(4, (THIS_FILE, + "The length of the result buffer must be at least %d bytes " + "for algorithm %.*s", digest_strlen, + (int)algorithm->iana_name.slen, algorithm->iana_name.ptr)); + return PJ_EINVAL; + } + result->slen = 0; -#if OPENSSL_VERSION_NUMBER < 0x30000000L - SHA256_CTX pms; -#else - EVP_MD_CTX* mdctx; - const EVP_MD* md; - unsigned dig_len; -#endif + if (!PJSIP_CRED_DATA_IS_PASSWD(cred_info) && !PJSIP_CRED_DATA_IS_DIGEST(cred_info)) { + PJ_LOG(4, (THIS_FILE, + "cred_info->data_type must be PJSIP_CRED_DATA_PLAIN_PASSWD " + "or PJSIP_CRED_DATA_DIGEST")); + return PJ_EINVAL; + } - pj_assert(result->slen >= PJSIP_SHA256STRLEN); + if (PJSIP_CRED_DATA_IS_DIGEST(cred_info)) { + if (cred_info->algorithm_type != algorithm_type) { + PJ_LOG(4,(THIS_FILE, + "The algorithm specified in the cred_info (%.*s) " + "doesn't match the algorithm requested for hashing (%.*s)", + (int)pjsip_auth_algorithms[cred_info->algorithm_type].iana_name.slen, + pjsip_auth_algorithms[cred_info->algorithm_type].iana_name.ptr, + (int)pjsip_auth_algorithms[algorithm_type].iana_name.slen, + pjsip_auth_algorithms[algorithm_type].iana_name.ptr)); + return PJ_EINVAL; + } + PJ_ASSERT_RETURN(cred_info->data.slen >= digest_strlen, PJ_EINVAL); + } -#if OPENSSL_VERSION_NUMBER >= 0x30000000L - md = EVP_get_digestbyname("SHA256"); +#ifndef WITHOUT_OPENSSL + md = EVP_get_digestbyname(algorithm->openssl_name); if (md == NULL) { + /* Shouldn't happen since it was checked above */ return PJ_ENOTSUP; } #endif - AUTH_TRACE_((THIS_FILE, "Begin creating digest")); + AUTH_TRACE_((THIS_FILE, "Begin creating %.*s digest", + (int)algorithm->iana_name.slen, algorithm->iana_name.ptr)); + + ha1 = alloca(digest_strlen); + ha2 = alloca(digest_strlen); + digest = alloca(digest_len); - if ((cred_info->data_type & PASSWD_MASK) == PJSIP_CRED_DATA_PLAIN_PASSWD) + if (PJSIP_CRED_DATA_IS_PASSWD(cred_info)) { + AUTH_TRACE_((THIS_FILE, " Using plain text password for %.*s digest", + (int)algorithm->iana_name.slen, algorithm->iana_name.ptr)); /*** - *** ha1 = SHA256(username ":" realm ":" password) + *** ha1 = (digest)(username ":" realm ":" password) ***/ -#if OPENSSL_VERSION_NUMBER < 0x30000000L - SHA256_Init(&pms); - SHA256_Update( &pms, cred_info->username.ptr, - cred_info->username.slen); - SHA256_Update( &pms, ":", 1); - SHA256_Update( &pms, realm->ptr, realm->slen); - SHA256_Update( &pms, ":", 1); - SHA256_Update( &pms, cred_info->data.ptr, cred_info->data.slen); - SHA256_Final(digest, &pms); -#else mdctx = EVP_MD_CTX_new(); + EVP_DigestInit_ex(mdctx, md, NULL); EVP_DigestUpdate(mdctx, cred_info->username.ptr, cred_info->username.slen); EVP_DigestUpdate(mdctx, ":", 1); @@ -323,37 +312,19 @@ PJ_DEF(pj_status_t) pjsip_auth_create_digestSHA256(pj_str_t *result, EVP_DigestFinal_ex(mdctx, digest, &dig_len); EVP_MD_CTX_free(mdctx); -#endif - digestNtoStr(digest, 32, ha1); + digestNtoStr(digest, dig_len, ha1); - } else if ((cred_info->data_type & PASSWD_MASK) == PJSIP_CRED_DATA_DIGEST) - { - if (cred_info->data.slen != 64) { - pj_assert(!"Invalid cred_info data length"); - pj_bzero(result->ptr, result->slen); - result->slen = 0; - return PJ_EINVAL; - } - pj_memcpy( ha1, cred_info->data.ptr, cred_info->data.slen ); } else { - pj_assert(!"Invalid data_type"); - pj_bzero(result->ptr, result->slen); - result->slen = 0; - return PJ_EINVAL; + AUTH_TRACE_((THIS_FILE, " Using pre computed digest for %.*s digest", + (int)algorithm->iana_name.slen, algorithm->iana_name.ptr)); + pj_memcpy( ha1, cred_info->data.ptr, cred_info->data.slen ); } - AUTH_TRACE_((THIS_FILE, " ha1=%.64s", ha1)); + AUTH_TRACE_((THIS_FILE, " ha1=%.*s", algorithm->digest_str_length, ha1)); /*** - *** ha2 = SHA256(method ":" req_uri) + *** ha2 = (digest)(method ":" req_uri) ***/ -#if OPENSSL_VERSION_NUMBER < 0x30000000L - SHA256_Init(&pms); - SHA256_Update( &pms, method->ptr, method->slen); - SHA256_Update( &pms, ":", 1); - SHA256_Update( &pms, uri->ptr, uri->slen); - SHA256_Final( digest, &pms); -#else mdctx = EVP_MD_CTX_new(); EVP_DigestInit_ex(mdctx, md, NULL); EVP_DigestUpdate(mdctx, method->ptr, method->slen); @@ -361,40 +332,20 @@ PJ_DEF(pj_status_t) pjsip_auth_create_digestSHA256(pj_str_t *result, EVP_DigestUpdate(mdctx, uri->ptr, uri->slen); EVP_DigestFinal_ex(mdctx, digest, &dig_len); EVP_MD_CTX_free(mdctx); -#endif - digestNtoStr(digest, 32, ha2); + digestNtoStr(digest, dig_len, ha2); - AUTH_TRACE_((THIS_FILE, " ha2=%.64s", ha2)); + AUTH_TRACE_((THIS_FILE, " ha2=%.*s", algorithm->digest_str_length, ha2)); /*** *** When qop is not used: - *** response = SHA256(ha1 ":" nonce ":" ha2) + *** response = (digest)(ha1 ":" nonce ":" ha2) *** *** When qop=auth is used: - *** response = SHA256(ha1 ":" nonce ":" nc ":" cnonce ":" qop ":" ha2) + *** response = (digest)(ha1 ":" nonce ":" nc ":" cnonce ":" qop ":" ha2) ***/ -#if OPENSSL_VERSION_NUMBER < 0x30000000L - SHA256_Init(&pms); - SHA256_Update( &pms, ha1, PJSIP_SHA256STRLEN); - SHA256_Update( &pms, ":", 1); - SHA256_Update( &pms, nonce->ptr, nonce->slen); - if (qop && qop->slen != 0) { - SHA256_Update( &pms, ":", 1); - SHA256_Update( &pms, nc->ptr, nc->slen); - SHA256_Update( &pms, ":", 1); - SHA256_Update( &pms, cnonce->ptr, cnonce->slen); - SHA256_Update( &pms, ":", 1); - SHA256_Update( &pms, qop->ptr, qop->slen); - } - SHA256_Update( &pms, ":", 1); - SHA256_Update( &pms, ha2, PJSIP_SHA256STRLEN); - - /* This is the final response digest. */ - SHA256_Final(digest, &pms); -#else mdctx = EVP_MD_CTX_new(); EVP_DigestInit_ex(mdctx, md, NULL); - EVP_DigestUpdate(mdctx, ha1, PJSIP_SHA256STRLEN); + EVP_DigestUpdate(mdctx, ha1, digest_strlen); EVP_DigestUpdate(mdctx, ":", 1); EVP_DigestUpdate(mdctx, nonce->ptr, nonce->slen); if (qop && qop->slen != 0) { @@ -406,18 +357,65 @@ PJ_DEF(pj_status_t) pjsip_auth_create_digestSHA256(pj_str_t *result, EVP_DigestUpdate(mdctx, qop->ptr, qop->slen); } EVP_DigestUpdate(mdctx, ":", 1); - EVP_DigestUpdate(mdctx, ha2, PJSIP_SHA256STRLEN); + EVP_DigestUpdate(mdctx, ha2, digest_strlen); EVP_DigestFinal_ex(mdctx, digest, &dig_len); EVP_MD_CTX_free(mdctx); -#endif + /* Convert digest to string and store in chal->response. */ - result->slen = PJSIP_SHA256STRLEN; - digestNtoStr(digest, 32, result->ptr); + result->slen = digest_strlen; + digestNtoStr(digest, digest_len, result->ptr); - AUTH_TRACE_((THIS_FILE, " digest=%.64s", result->ptr)); - AUTH_TRACE_((THIS_FILE, "Digest created")); + AUTH_TRACE_((THIS_FILE, "%.*s digest=%.*s", + (int)algorithm->iana_name.slen, algorithm->iana_name.ptr, + (int)result->slen, result->ptr)); + + return PJ_SUCCESS; +} + + +PJ_DEF(pj_status_t) pjsip_auth_create_digest(pj_str_t *result, + const pj_str_t *nonce, + const pj_str_t *nc, + const pj_str_t *cnonce, + const pj_str_t *qop, + const pj_str_t *uri, + const pj_str_t *realm, + const pjsip_cred_info *cred_info, + const pj_str_t *method) +{ + PJ_ASSERT_RETURN(cred_info, PJ_EINVAL); + PJ_ASSERT_RETURN(!PJSIP_CRED_DATA_IS_AKA(cred_info), PJ_EINVAL); + + return pjsip_auth_create_digest2(result, nonce, nc, cnonce, + qop, uri, realm, cred_info, method, + PJSIP_AUTH_ALGORITHM_MD5); +} + +/* + * Create response SHA-256 digest based on the parameters and store the + * digest ASCII in 'result'. + * \deprecated Use pjsip_auth_create_digest with + * cred_info->algorithm_type = PJSIP_AUTH_ALGORITHM_SHA256. + */ +PJ_DEF(pj_status_t) pjsip_auth_create_digestSHA256(pj_str_t *result, + const pj_str_t *nonce, + const pj_str_t *nc, + const pj_str_t *cnonce, + const pj_str_t *qop, + const pj_str_t *uri, + const pj_str_t *realm, + const pjsip_cred_info *cred_info, + const pj_str_t *method) +{ +#ifndef WITHOUT_OPENSSL + PJ_ASSERT_RETURN(cred_info, PJ_EINVAL); + PJ_ASSERT_RETURN(!PJSIP_CRED_DATA_IS_AKA(cred_info), PJ_EINVAL); + + return pjsip_auth_create_digest2(result, nonce, nc, cnonce, + qop, uri, realm, cred_info, method, + PJSIP_AUTH_ALGORITHM_SHA256); #else PJ_UNUSED_ARG(result); PJ_UNUSED_ARG(nonce); @@ -428,8 +426,83 @@ PJ_DEF(pj_status_t) pjsip_auth_create_digestSHA256(pj_str_t *result, PJ_UNUSED_ARG(realm); PJ_UNUSED_ARG(cred_info); PJ_UNUSED_ARG(method); -#endif + return PJ_SUCCESS; +#endif +} + + +PJ_DEF(const pjsip_auth_algorithm *) pjsip_auth_get_algorithm_by_type( + pjsip_auth_algorithm_type algorithm_type) +{ + if (algorithm_type > PJSIP_AUTH_ALGORITHM_NOT_SET + && algorithm_type < PJSIP_AUTH_ALGORITHM_COUNT) { + return &pjsip_auth_algorithms[algorithm_type]; + } + return NULL; +} + + +PJ_DEF(const pjsip_auth_algorithm *) pjsip_auth_get_algorithm_by_iana_name( + const pj_str_t *iana_name) +{ + int i; + + if (!iana_name) { + return NULL; + } + + if (iana_name->slen == 0) { + return &pjsip_auth_algorithms[PJSIP_AUTH_ALGORITHM_MD5]; + } + +#ifdef WITHOUT_OPENSSL + i = PJSIP_AUTH_ALGORITHM_MD5; + if (pj_stricmp(iana_name, &pjsip_auth_algorithms[i].iana_name) == 0) { + return &pjsip_auth_algorithms[i]; + } +#else + for (i = PJSIP_AUTH_ALGORITHM_NOT_SET + 1; i < PJSIP_AUTH_ALGORITHM_COUNT; i++) { + if (pj_stricmp(iana_name, &pjsip_auth_algorithms[i].iana_name) == 0) { + return &pjsip_auth_algorithms[i]; + } + } +#endif + return NULL; +} + + +PJ_DEF(pj_bool_t) pjsip_auth_is_algorithm_supported( + pjsip_auth_algorithm_type algorithm_type) +{ + const pjsip_auth_algorithm *algorithm = NULL; + + if (algorithm_type <= PJSIP_AUTH_ALGORITHM_NOT_SET + || algorithm_type >= PJSIP_AUTH_ALGORITHM_COUNT) { + return PJ_FALSE; + } + algorithm = &pjsip_auth_algorithms[algorithm_type]; + + /* + * If the openssl_name is empty there's no need to check + * if OpenSSL supports it. + */ + if (algorithm->openssl_name[0] == '\0') { + return PJ_TRUE; + } + +#ifdef WITHOUT_OPENSSL + return (algorithm_type == PJSIP_AUTH_ALGORITHM_MD5); +#else + { + const EVP_MD* md; + md = EVP_get_digestbyname(algorithm->openssl_name); + if (md == NULL) { + return PJ_FALSE; + } + return PJ_TRUE; + } +#endif } @@ -471,7 +544,7 @@ static pj_bool_t has_auth_qop( pj_pool_t *pool, const pj_str_t *qop_offer) * password and method param) should be supplied in the argument. * * The resulting digest will be stored in cred->response. - * The pool is used to allocate 32 bytes to store the digest in cred->response. + * The pool is used to allocate enough bytes to store the digest in cred->response. */ static pj_status_t respond_digest( pj_pool_t *pool, pjsip_digest_credential *cred, @@ -480,31 +553,13 @@ static pj_status_t respond_digest( pj_pool_t *pool, const pjsip_cred_info *cred_info, const pj_str_t *cnonce, pj_uint32_t nc, - const pj_str_t *method) + const pj_str_t *method, + const pjsip_auth_algorithm_type challenge_algorithm_type) { - const pj_str_t pjsip_AKAv1_MD5_STR = { "AKAv1-MD5", 9 }; - pj_bool_t algo_sha256 = PJ_FALSE; pj_status_t status = PJ_SUCCESS; - /* Check if algo is sha256 */ -#if PJSIP_AUTH_HAS_DIGEST_SHA256 - algo_sha256 = (pj_stricmp(&chal->algorithm, &pjsip_SHA256_STR)==0); -#endif - - /* Check algorithm is supported. We support MD5, AKAv1-MD5, and SHA256. */ - if (chal->algorithm.slen==0 || - (algo_sha256 || - pj_stricmp(&chal->algorithm, &pjsip_MD5_STR)==0 || - pj_stricmp(&chal->algorithm, &pjsip_AKAv1_MD5_STR)==0)) - { - PJ_LOG(4,(THIS_FILE, "Digest algorithm is \"%.*s\"", - (int)chal->algorithm.slen, chal->algorithm.ptr)); - } - else { - PJ_LOG(4,(THIS_FILE, "Unsupported digest algorithm \"%.*s\"", - (int)chal->algorithm.slen, chal->algorithm.ptr)); - return PJSIP_EINVALIDALGORITHM; - } + AUTH_TRACE_((THIS_FILE, "Begin responding to %.*s challenge", + (int)chal->algorithm.slen, chal->algorithm.ptr)); /* Build digest credential from arguments. */ pj_strdup(pool, &cred->username, &cred_info->username); @@ -515,29 +570,23 @@ static pj_status_t respond_digest( pj_pool_t *pool, pj_strdup(pool, &cred->opaque, &chal->opaque); /* Allocate memory. */ - cred->response.slen = algo_sha256? PJSIP_SHA256STRLEN : PJSIP_MD5STRLEN; + cred->response.slen = pjsip_auth_algorithms[challenge_algorithm_type].digest_str_length; cred->response.ptr = (char*) pj_pool_alloc(pool, cred->response.slen); if (chal->qop.slen == 0) { /* Server doesn't require quality of protection. */ - if ((cred_info->data_type & EXT_MASK) == PJSIP_CRED_DATA_EXT_AKA) { + if (PJSIP_CRED_DATA_IS_AKA(cred_info)) { /* Call application callback to create the response digest */ return (*cred_info->ext.aka.cb)(pool, chal, cred_info, method, cred); } else { /* Convert digest to string and store in chal->response. */ - if (algo_sha256) { - status = pjsip_auth_create_digestSHA256( - &cred->response, &cred->nonce, NULL, - NULL, NULL, uri, &chal->realm, - cred_info, method); - } else { - status = pjsip_auth_create_digest( &cred->response, - &cred->nonce, NULL, NULL, NULL, uri, - &chal->realm, cred_info, method); - } + status = pjsip_auth_create_digest2( + &cred->response, &cred->nonce, NULL, + NULL, NULL, uri, &chal->realm, + cred_info, method, challenge_algorithm_type); } } else if (has_auth_qop(pool, &chal->qop)) { @@ -555,27 +604,19 @@ static pj_status_t respond_digest( pj_pool_t *pool, pj_strdup(pool, &cred->cnonce, &dummy_cnonce); } - if ((cred_info->data_type & EXT_MASK) == PJSIP_CRED_DATA_EXT_AKA) { + if (PJSIP_CRED_DATA_IS_AKA(cred_info)) { /* Call application callback to create the response digest */ return (*cred_info->ext.aka.cb)(pool, chal, cred_info, method, cred); } else { /* Convert digest to string and store in chal->response. */ - if (algo_sha256) { - status = pjsip_auth_create_digestSHA256( - &cred->response, &cred->nonce, - &cred->nc, &cred->cnonce, - &pjsip_AUTH_STR, uri, - &chal->realm, cred_info, - method); - } else { - status = pjsip_auth_create_digest( &cred->response, - &cred->nonce, &cred->nc, - &cred->cnonce, &pjsip_AUTH_STR, - uri, &chal->realm, - cred_info, method); - } + status = pjsip_auth_create_digest2( + &cred->response, &cred->nonce, + &cred->nc, &cred->cnonce, + &pjsip_AUTH_STR, uri, + &chal->realm, cred_info, + method, challenge_algorithm_type); } } else { @@ -689,11 +730,13 @@ static void update_digest_session( pjsip_cached_auth *cached_auth, /* Find cached authentication in the list for the specified realm. */ static pjsip_cached_auth *find_cached_auth( pjsip_auth_clt_sess *sess, - const pj_str_t *realm ) + const pj_str_t *realm, + pjsip_auth_algorithm_type algorithm_type) { pjsip_cached_auth *auth = sess->cached_auth.next; while (auth != &sess->cached_auth) { - if (pj_stricmp(&auth->realm, realm) == 0) + if (pj_stricmp(&auth->realm, realm) == 0 + && auth->challenge_algorithm_type == algorithm_type) return auth; auth = auth->next; } @@ -704,7 +747,8 @@ static pjsip_cached_auth *find_cached_auth( pjsip_auth_clt_sess *sess, /* Find credential to use for the specified realm and auth scheme. */ static const pjsip_cred_info* auth_find_cred( const pjsip_auth_clt_sess *sess, const pj_str_t *realm, - const pj_str_t *auth_scheme) + const pj_str_t *auth_scheme, + const pjsip_auth_algorithm_type algorithm_type) { unsigned i; int wildcard = -1; @@ -712,6 +756,32 @@ static const pjsip_cred_info* auth_find_cred( const pjsip_auth_clt_sess *sess, PJ_UNUSED_ARG(auth_scheme); for (i=0; icred_cnt; ++i) { + switch(sess->cred_info[i].data_type) { + case PJSIP_CRED_DATA_PLAIN_PASSWD: + /* PLAIN_PASSWD creds can be used for any algorithm other than AKA */ + if (algorithm_type != PJSIP_AUTH_ALGORITHM_AKAV1_MD5 + || algorithm_type != PJSIP_AUTH_ALGORITHM_AKAV2_MD5) { + break; + } + continue; + case PJSIP_CRED_DATA_DIGEST: + /* Digest creds can only be used if the algorithms match */ + if (sess->cred_info[i].algorithm_type == algorithm_type) { + break; + } + continue; + case PJSIP_CRED_DATA_EXT_AKA: + /* AKA creds can only be used for AKA algorithm */ + if (algorithm_type == PJSIP_AUTH_ALGORITHM_AKAV1_MD5 + || algorithm_type == PJSIP_AUTH_ALGORITHM_AKAV2_MD5) { + break; + } + continue; + } + /* + * We've determined that the credential can be used for the + * specified algorithm, now check the realm. + */ if (pj_stricmp(&sess->cred_info[i].realm, realm) == 0) return &sess->cred_info[i]; else if (sess->cred_info[i].realm.slen == 1 && @@ -789,6 +859,15 @@ PJ_DEF(pj_status_t) pjsip_auth_clt_clone( pj_pool_t *pool, &rhs->cred_info[i].username); sess->cred_info[i].data_type = rhs->cred_info[i].data_type; pj_strdup(pool, &sess->cred_info[i].data, &rhs->cred_info[i].data); + if (PJSIP_CRED_DATA_IS_DIGEST(&rhs->cred_info[i])) { + sess->cred_info[i].algorithm_type = rhs->cred_info[i].algorithm_type; + } + if (PJSIP_CRED_DATA_IS_AKA(&rhs->cred_info[i])) { + pj_strdup(pool, &sess->cred_info[i].ext.aka.k, &rhs->cred_info[i].ext.aka.k); + pj_strdup(pool, &sess->cred_info[i].ext.aka.op, &rhs->cred_info[i].ext.aka.op); + pj_strdup(pool, &sess->cred_info[i].ext.aka.amf, &rhs->cred_info[i].ext.aka.amf); + sess->cred_info[i].ext.aka.cb = rhs->cred_info[i].ext.aka.cb; + } } /* TODO note: @@ -823,7 +902,7 @@ PJ_DEF(pj_status_t) pjsip_auth_clt_set_credentials( pjsip_auth_clt_sess *sess, /* When data_type is PJSIP_CRED_DATA_EXT_AKA, * callback must be specified. */ - if ((c[i].data_type & EXT_MASK) == PJSIP_CRED_DATA_EXT_AKA) { + if (PJSIP_CRED_DATA_IS_AKA(&c[i])) { #if !PJSIP_HAS_DIGEST_AKA_AUTH if (!PJSIP_HAS_DIGEST_AKA_AUTH) { @@ -860,6 +939,7 @@ PJ_DEF(pj_status_t) pjsip_auth_clt_set_credentials( pjsip_auth_clt_sess *sess, pj_strdup(sess->pool, &sess->cred_info[i].realm, &c[i].realm); pj_strdup(sess->pool, &sess->cred_info[i].username, &c[i].username); pj_strdup(sess->pool, &sess->cred_info[i].data, &c[i].data); + sess->cred_info[i].algorithm_type = c[i].algorithm_type; } sess->cred_cnt = cred_cnt; } @@ -909,7 +989,8 @@ static pj_status_t auth_respond( pj_pool_t *req_pool, const pjsip_method *method, pj_pool_t *sess_pool, pjsip_cached_auth *cached_auth, - pjsip_authorization_hdr **p_h_auth) + pjsip_authorization_hdr **p_h_auth, + const pjsip_auth_algorithm_type challenge_algorithm_type) { pjsip_authorization_hdr *hauth; char tmp[PJSIP_MAX_URL_SIZE]; @@ -968,7 +1049,7 @@ static pj_status_t auth_respond( pj_pool_t *req_pool, hauth->scheme = pjsip_DIGEST_STR; status = respond_digest( pool, &hauth->credential.digest, &hdr->challenge.digest, &uri_str, cred_info, - cnonce, nc, &method->name); + cnonce, nc, &method->name, challenge_algorithm_type); if (status != PJ_SUCCESS) return status; @@ -1041,14 +1122,15 @@ static pj_status_t new_auth_for_req( pjsip_tx_data *tdata, PJ_ASSERT_RETURN(tdata && sess && auth, PJ_EINVAL); PJ_ASSERT_RETURN(auth->last_chal != NULL, PJSIP_EAUTHNOPREVCHAL); - cred = auth_find_cred( sess, &auth->realm, &auth->last_chal->scheme ); + cred = auth_find_cred( sess, &auth->realm, &auth->last_chal->scheme, + auth->challenge_algorithm_type ); if (!cred) return PJSIP_ENOCREDENTIAL; status = auth_respond( tdata->pool, auth->last_chal, tdata->msg->line.req.uri, cred, &tdata->msg->line.req.method, - sess->pool, auth, &hauth); + sess->pool, auth, &hauth, auth->challenge_algorithm_type); if (status != PJ_SUCCESS) return status; @@ -1063,15 +1145,50 @@ static pj_status_t new_auth_for_req( pjsip_tx_data *tdata, /* Find credential in list of (Proxy-)Authorization headers */ -static pjsip_authorization_hdr* get_header_for_realm(const pjsip_hdr *hdr_list, - const pj_str_t *realm) +static pjsip_authorization_hdr* get_header_for_cred_info( + const pjsip_hdr *hdr_list, + const pjsip_cred_info *cred_info) { pjsip_authorization_hdr *h; h = (pjsip_authorization_hdr*)hdr_list->next; while (h != (pjsip_authorization_hdr*)hdr_list) { - if (pj_stricmp(&h->credential.digest.realm, realm)==0) + /* If the realm doesn't match, just skip */ + if (pj_stricmp(&h->credential.digest.realm, &cred_info->realm) != 0) { + h = h->next; + continue; + } + + switch (cred_info->data_type) { + case PJSIP_CRED_DATA_PLAIN_PASSWD: + /* + * If cred_info->data_type is PLAIN_PASSWD, then we can use the header + * regardless of algorithm. + */ return h; + case PJSIP_CRED_DATA_DIGEST: + /* + * If cred_info->data_type is DIGEST, then we need to check if the + * algorithms match. + */ + if (pj_stricmp(&h->credential.digest.algorithm, + &pjsip_auth_algorithms[cred_info->algorithm_type].iana_name) == 0) { + return h; + } + break; + case PJSIP_CRED_DATA_EXT_AKA: + /* + * If cred_info->data_type is EXT_AKA, then we need to check if the + * challenge algorithm is AKAv1-MD5 or AKAv2-MD5. + */ + if (pj_stricmp(&h->credential.digest.algorithm, + &pjsip_auth_algorithms[PJSIP_AUTH_ALGORITHM_AKAV1_MD5].iana_name) == 0 + || pj_stricmp(&h->credential.digest.algorithm, + &pjsip_auth_algorithms[PJSIP_AUTH_ALGORITHM_AKAV2_MD5].iana_name) == 0) { + return h; + } + break; + } h = h->next; } @@ -1148,7 +1265,8 @@ PJ_DEF(pj_status_t) pjsip_auth_clt_init_req( pjsip_auth_clt_sess *sess, pj_status_t status; cred = auth_find_cred(sess, &auth->realm, - &auth->last_chal->scheme); + &auth->last_chal->scheme, + auth->challenge_algorithm_type); if (!cred) { auth = auth->next; continue; @@ -1158,7 +1276,8 @@ PJ_DEF(pj_status_t) pjsip_auth_clt_init_req( pjsip_auth_clt_sess *sess, tdata->msg->line.req.uri, cred, &tdata->msg->line.req.method, - sess->pool, auth, &hauth); + sess->pool, auth, &hauth, + auth->challenge_algorithm_type); if (status != PJ_SUCCESS) return status; @@ -1201,7 +1320,7 @@ PJ_DEF(pj_status_t) pjsip_auth_clt_init_req( pjsip_auth_clt_sess *sess, pjsip_cred_info *c = &sess->cred_info[i]; pjsip_authorization_hdr *h; - h = get_header_for_realm(&added, &c->realm); + h = get_header_for_cred_info(&added, c); if (h) { pj_list_erase(h); pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)h); @@ -1223,8 +1342,14 @@ PJ_DEF(pj_status_t) pjsip_auth_clt_init_req( pjsip_auth_clt_sess *sess, pj_strdup(tdata->pool, &hs->credential.digest.realm, &c->realm); pj_strdup(tdata->pool,&hs->credential.digest.uri, &uri); - pj_strdup(tdata->pool, &hs->credential.digest.algorithm, - &sess->pref.algorithm); + + if (c->algorithm_type == PJSIP_AUTH_ALGORITHM_NOT_SET) { + pj_strdup(tdata->pool, &hs->credential.digest.algorithm, + &sess->pref.algorithm); + } else { + pj_strdup(tdata->pool, &hs->credential.digest.algorithm, + &pjsip_auth_algorithms[c->algorithm_type].iana_name); + } } pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)hs); @@ -1270,7 +1395,8 @@ static pj_status_t process_auth( pj_pool_t *req_pool, pjsip_tx_data *tdata, pjsip_auth_clt_sess *sess, pjsip_cached_auth *cached_auth, - pjsip_authorization_hdr **h_auth) + pjsip_authorization_hdr **h_auth, + const pjsip_auth_algorithm_type challenge_algorithm_type) { const pjsip_cred_info *cred; pjsip_authorization_hdr *sent_auth = NULL; @@ -1298,15 +1424,26 @@ static pj_status_t process_auth( pj_pool_t *req_pool, hdr = hdr->next; pj_list_erase(sent_auth); continue; - } else - if (pj_stricmp(&sent_auth->scheme, &pjsip_DIGEST_STR)==0 && - pj_stricmp(&sent_auth->credential.digest.algorithm, - &hchal->challenge.digest.algorithm)!=0) - { - /* Same 'digest' scheme but different algo */ - hdr = hdr->next; - continue; } else { +#if defined(PJSIP_AUTH_ALLOW_MULTIPLE_AUTH_HEADER) && \ + PJSIP_AUTH_ALLOW_MULTIPLE_AUTH_HEADER!=0 + /* + * Keep sending additional headers if the the algorithm + * is different. + * WARNING: See the comment in sip_config.h regarding + * how using this option could be a security risk if + * a header with a more secure digest algorithm has already + * been sent. + */ + if (pj_stricmp(&sent_auth->scheme, &pjsip_DIGEST_STR)==0 && + pj_stricmp(&sent_auth->credential.digest.algorithm, + &hchal->challenge.digest.algorithm)!=0) + { + /* Same 'digest' scheme but different algo */ + hdr = hdr->next; + continue; + } else +#endif /* Found previous authorization attempt */ break; } @@ -1370,22 +1507,26 @@ static pj_status_t process_auth( pj_pool_t *req_pool, /* Find credential to be used for the challenge. */ cred = auth_find_cred( sess, &hchal->challenge.common.realm, - &hchal->scheme); + &hchal->scheme, challenge_algorithm_type); if (!cred) { const pj_str_t *realm = &hchal->challenge.common.realm; + AUTH_TRACE_((THIS_FILE, "No cred for for %.*s", + (int)hchal->challenge.digest.algorithm.slen, hchal->challenge.digest.algorithm.ptr)); + PJ_LOG(4,(THIS_FILE, "Unable to set auth for %s: can not find credential for " - "%.*s/%.*s", + "%.*s/%.*s %.*s", tdata->obj_name, (int)realm->slen, realm->ptr, - (int)hchal->scheme.slen, hchal->scheme.ptr)); + (int)hchal->scheme.slen, hchal->scheme.ptr, + (int)hchal->challenge.digest.algorithm.slen, hchal->challenge.digest.algorithm.ptr)); return PJSIP_ENOCREDENTIAL; } /* Respond to authorization challenge. */ status = auth_respond( req_pool, hchal, uri, cred, &tdata->msg->line.req.method, - sess->pool, cached_auth, h_auth); + sess->pool, cached_auth, h_auth, challenge_algorithm_type); return status; } @@ -1433,6 +1574,7 @@ PJ_DEF(pj_status_t) pjsip_auth_clt_reinit_req( pjsip_auth_clt_sess *sess, pjsip_cached_auth *cached_auth; const pjsip_www_authenticate_hdr *hchal; pjsip_authorization_hdr *hauth; + const pjsip_auth_algorithm *algorithm; /* Find WWW-Authenticate or Proxy-Authenticate header. */ while (hdr != &rdata->msg_info.msg->hdr && @@ -1447,10 +1589,29 @@ PJ_DEF(pj_status_t) pjsip_auth_clt_reinit_req( pjsip_auth_clt_sess *sess, hchal = (const pjsip_www_authenticate_hdr*)hdr; ++chal_cnt; + /* At the current time, "digest" scheme is the only one supported. */ + if (pj_stricmp(&hchal->scheme, &pjsip_DIGEST_STR) != 0) { + AUTH_TRACE_((THIS_FILE, "Skipped header for scheme %.*s", + (int)hchal->scheme.slen, hchal->scheme.ptr)); + last_auth_err = PJSIP_EINVALIDAUTHSCHEME; + hdr = hdr->next; + continue; + } + + algorithm = pjsip_auth_get_algorithm_by_iana_name(&hchal->challenge.digest.algorithm); + if (!algorithm) { + AUTH_TRACE_((THIS_FILE, "Skipped header for algorithm %.*s", + (int)algorithm->iana_name.slen, algorithm->iana_name.ptr)); + last_auth_err = PJSIP_EINVALIDALGORITHM; + hdr = hdr->next; + continue; + } + /* Find authentication session for this realm, create a new one * if not present. */ - cached_auth = find_cached_auth(sess, &hchal->challenge.common.realm); + cached_auth = find_cached_auth(sess, &hchal->challenge.common.realm, + algorithm->algorithm_type); if (!cached_auth) { cached_auth = PJ_POOL_ZALLOC_T(sess->pool, pjsip_cached_auth); cached_auth->pool = pjsip_endpt_create_pool(sess->endpt, @@ -1460,6 +1621,7 @@ PJ_DEF(pj_status_t) pjsip_auth_clt_reinit_req( pjsip_auth_clt_sess *sess, pj_strdup(cached_auth->pool, &cached_auth->realm, &hchal->challenge.common.realm); cached_auth->is_proxy = (hchal->type == PJSIP_H_PROXY_AUTHENTICATE); + cached_auth->challenge_algorithm_type = algorithm->algorithm_type; # if (PJSIP_AUTH_HEADER_CACHING) { pj_list_init(&cached_auth->cached_hdr); @@ -1472,9 +1634,12 @@ PJ_DEF(pj_status_t) pjsip_auth_clt_reinit_req( pjsip_auth_clt_sess *sess, * authorization session. */ status = process_auth(tdata->pool, hchal, tdata->msg->line.req.uri, - tdata, sess, cached_auth, &hauth); + tdata, sess, cached_auth, &hauth, algorithm->algorithm_type); if (status != PJ_SUCCESS) { last_auth_err = status; + AUTH_TRACE_((THIS_FILE, "Skipped header for realm %.*s algorithm %.*s", + (int)hchal->challenge.common.realm.slen, hchal->challenge.common.realm.ptr, + (int)algorithm->iana_name.slen, algorithm->iana_name.ptr)); /* Process next header. */ hdr = hdr->next; @@ -1493,11 +1658,6 @@ PJ_DEF(pj_status_t) pjsip_auth_clt_reinit_req( pjsip_auth_clt_sess *sess, /* Process next header. */ hdr = hdr->next; auth_cnt++; - -#if defined(PJSIP_AUTH_ALLOW_MULTIPLE_AUTH_HEADER) && \ - PJSIP_AUTH_ALLOW_MULTIPLE_AUTH_HEADER==0 - break; -#endif } /* Check if challenge is present */ diff --git a/pjsip/src/pjsip/sip_auth_server.c b/pjsip/src/pjsip/sip_auth_server.c index 8eee2b51b6..3798fbb78c 100644 --- a/pjsip/src/pjsip/sip_auth_server.c +++ b/pjsip/src/pjsip/sip_auth_server.c @@ -22,9 +22,18 @@ #include #include #include +#include #include #include +/* Logging. */ +#define THIS_FILE "sip_auth_server.c" +#if 0 +# define AUTH_TRACE_(expr) PJ_LOG(3, expr) +#else +# define AUTH_TRACE_(expr) +#endif + /* * Initialize server authorization session data structure to serve the @@ -75,11 +84,37 @@ static pj_status_t pjsip_auth_verify( const pjsip_authorization_hdr *hdr, const pj_str_t *method, const pjsip_cred_info *cred_info ) { + if (pj_stricmp(&hdr->scheme, &pjsip_DIGEST_STR) == 0) { - char digest_buf[PJSIP_MD5STRLEN]; + const pjsip_digest_credential *dig = &hdr->credential.digest; + const pjsip_auth_algorithm *response_algorithm = + pjsip_auth_get_algorithm_by_iana_name(&dig->algorithm); pj_str_t digest; pj_status_t status; - const pjsip_digest_credential *dig = &hdr->credential.digest; + int result = 0; + + AUTH_TRACE_((THIS_FILE, "Authenticating with realm-digest %.*s-%.*s", + (int)dig->realm.slen, dig->realm.ptr, + (int)dig->algorithm.slen, dig->algorithm.ptr)); + /* + * Check that the response algorithm is supported. + * Since we sent the challenge it should be but + * we check anyway in case a client responds with a bad + * one. + */ + PJ_ASSERT_RETURN(response_algorithm + && pjsip_auth_is_algorithm_supported(response_algorithm->algorithm_type), + PJSIP_EINVALIDALGORITHM); + + /* + * Cred info type must be plain password or digest and if it's digest, + * the algorithm must match the algorithm in the header. + * We don't support the AKAv1-MD5 algorithm as a server. + */ + PJ_ASSERT_RETURN(PJSIP_CRED_DATA_IS_PASSWD(cred_info) + || (PJSIP_CRED_DATA_IS_DIGEST(cred_info) + && cred_info->algorithm_type == response_algorithm->algorithm_type), + PJSIP_EINVALIDALGORITHM); /* Check that username and realm match. * These checks should have been performed before entering this @@ -91,11 +126,11 @@ static pj_status_t pjsip_auth_verify( const pjsip_authorization_hdr *hdr, PJ_EINVALIDOP); /* Prepare for our digest calculation. */ - digest.ptr = digest_buf; - digest.slen = PJSIP_MD5STRLEN; + digest.ptr = alloca(response_algorithm->digest_str_length); + digest.slen = response_algorithm->digest_str_length; /* Create digest for comparison. */ - status = pjsip_auth_create_digest(&digest, + status = pjsip_auth_create_digest2(&digest, &hdr->credential.digest.nonce, &hdr->credential.digest.nc, &hdr->credential.digest.cnonce, @@ -103,13 +138,19 @@ static pj_status_t pjsip_auth_verify( const pjsip_authorization_hdr *hdr, &hdr->credential.digest.uri, &cred_info->realm, cred_info, - method ); + method, response_algorithm->algorithm_type); + AUTH_TRACE_((THIS_FILE, "Create digest response: %s", + (status == PJ_SUCCESS ? "success" : "failed"))); if (status != PJ_SUCCESS) return status; /* Compare digest. */ - return (pj_stricmp(&digest, &hdr->credential.digest.response) == 0) ? + result = pj_stricmp(&digest, &hdr->credential.digest.response); + AUTH_TRACE_((THIS_FILE, "Digest comparison: %s", + result ? "failed" : "matched")); + + return (result == 0) ? PJ_SUCCESS : PJSIP_EAUTHINVALIDDIGEST; } else { @@ -133,6 +174,8 @@ PJ_DEF(pj_status_t) pjsip_auth_srv_verify( pjsip_auth_srv *auth_srv, pj_str_t acc_name; pjsip_cred_info cred_info; pj_status_t status; + const pjsip_auth_algorithm *algorithm; + PJ_ASSERT_RETURN(auth_srv && rdata, PJ_EINVAL); PJ_ASSERT_RETURN(msg->type == PJSIP_REQUEST_MSG, PJSIP_ENOTREQUESTMSG); @@ -171,7 +214,17 @@ PJ_DEF(pj_status_t) pjsip_auth_srv_verify( pjsip_auth_srv *auth_srv, return PJSIP_EINVALIDAUTHSCHEME; } + algorithm = pjsip_auth_get_algorithm_by_iana_name( + &h_auth->credential.digest.algorithm); + + /* Check that algorithm is supported. */ + if (!algorithm || !pjsip_auth_is_algorithm_supported(algorithm->algorithm_type)) { + return PJSIP_EINVALIDALGORITHM; + } + /* Find the credential information for the account. */ + pj_bzero(&cred_info, sizeof(cred_info)); + if (auth_srv->lookup2) { pjsip_auth_lookup_cred_param param; @@ -179,6 +232,7 @@ PJ_DEF(pj_status_t) pjsip_auth_srv_verify( pjsip_auth_srv *auth_srv, param.realm = auth_srv->realm; param.acc_name = acc_name; param.rdata = rdata; + param.auth_hdr = h_auth; status = (*auth_srv->lookup2)(rdata->tp_info.pool, ¶m, &cred_info); if (status != PJ_SUCCESS) { *status_code = PJSIP_SC_FORBIDDEN; @@ -209,19 +263,27 @@ PJ_DEF(pj_status_t) pjsip_auth_srv_verify( pjsip_auth_srv *auth_srv, * or can leave the value to NULL to make the function fills them in with * random characters. */ -PJ_DEF(pj_status_t) pjsip_auth_srv_challenge( pjsip_auth_srv *auth_srv, +PJ_DEF(pj_status_t) pjsip_auth_srv_challenge2( pjsip_auth_srv *auth_srv, const pj_str_t *qop, const pj_str_t *nonce, const pj_str_t *opaque, pj_bool_t stale, - pjsip_tx_data *tdata) + pjsip_tx_data *tdata, + const pjsip_auth_algorithm_type algorithm_type) { pjsip_www_authenticate_hdr *hdr; char nonce_buf[16]; pj_str_t random; + const pjsip_auth_algorithm *algorithm = + pjsip_auth_get_algorithm_by_type(algorithm_type); PJ_ASSERT_RETURN( auth_srv && tdata, PJ_EINVAL ); + /* Check that algorithm is supported. */ + if (!algorithm || !pjsip_auth_is_algorithm_supported(algorithm_type)) { + return PJSIP_EINVALIDALGORITHM; + } + random.ptr = nonce_buf; random.slen = sizeof(nonce_buf); @@ -235,7 +297,7 @@ PJ_DEF(pj_status_t) pjsip_auth_srv_challenge( pjsip_auth_srv *auth_srv, * Note: only support digest authentication now. */ hdr->scheme = pjsip_DIGEST_STR; - hdr->challenge.digest.algorithm = pjsip_MD5_STR; + pj_strdup(tdata->pool, &hdr->challenge.digest.algorithm, &algorithm->iana_name); if (nonce) { pj_strdup(tdata->pool, &hdr->challenge.digest.nonce, nonce); } else { @@ -261,3 +323,13 @@ PJ_DEF(pj_status_t) pjsip_auth_srv_challenge( pjsip_auth_srv *auth_srv, return PJ_SUCCESS; } +PJ_DEF(pj_status_t) pjsip_auth_srv_challenge( pjsip_auth_srv *auth_srv, + const pj_str_t *qop, + const pj_str_t *nonce, + const pj_str_t *opaque, + pj_bool_t stale, + pjsip_tx_data *tdata) +{ + return pjsip_auth_srv_challenge2(auth_srv, qop, nonce, opaque, + stale, tdata, PJSIP_AUTH_ALGORITHM_MD5); +}