Skip to content

Commit

Permalink
Add full support for SHA-256 and SHA-512-256 digest algorithms
Browse files Browse the repository at this point in the history
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
  • Loading branch information
gtjoseph committed Oct 29, 2024
1 parent 9c134f4 commit c49669b
Show file tree
Hide file tree
Showing 6 changed files with 743 additions and 312 deletions.
1 change: 1 addition & 0 deletions pjlib/src/pj/ssl_sock_ossl.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down
197 changes: 189 additions & 8 deletions pjsip/include/pjsip/sip_auth.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
{
Expand Down Expand Up @@ -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
{
Expand All @@ -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 {
Expand Down Expand Up @@ -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;


Expand All @@ -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.
*
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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.
Expand All @@ -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.
Expand Down Expand Up @@ -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
Expand All @@ -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,
Expand All @@ -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);

/**
* @}
*/
Expand Down
23 changes: 19 additions & 4 deletions pjsip/include/pjsip/sip_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand Down
10 changes: 6 additions & 4 deletions pjsip/src/pjsip/sip_auth_aka.c
Original file line number Diff line number Diff line change
Expand Up @@ -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) {

Expand Down Expand Up @@ -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!");
Expand Down
Loading

0 comments on commit c49669b

Please sign in to comment.