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); +}