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 Nov 4, 2024
1 parent cc83544 commit 3200187
Show file tree
Hide file tree
Showing 7 changed files with 761 additions and 341 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
224 changes: 212 additions & 12 deletions pjsip/include/pjsip/sip_auth.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,66 @@ 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

/**
* The length of the buffer needed to contain the largest
* supported algorithm's digest.
*/
#define PJSIP_AUTH_MAX_DIGEST_BUFFER_LENGTH 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.
*
* The MD5, SHA-256, and SHA-512/256 algorithms are described
* in RFC 7616 and RFC 8760.
* The AKA algorithms are described in RFC 3310 and RFC 4169
* and 3GPP TS 33.203.
*/
typedef enum pjsip_auth_algorithm_type
{
PJSIP_AUTH_ALGORITHM_NOT_SET = 0, /**< Algorithm not set. */
PJSIP_AUTH_ALGORITHM_MD5, /**< MD5 algorithm. */
PJSIP_AUTH_ALGORITHM_SHA256, /**< SHA-256 algorithm. */
PJSIP_AUTH_ALGORITHM_SHA512_256, /**< SHA-512/256 algorithm */
PJSIP_AUTH_ALGORITHM_AKAV1_MD5, /**< AKA v1 with MD5 algorithm. */
PJSIP_AUTH_ALGORITHM_AKAV2_MD5, /**< AKA v2 with MD5 algorithm. */
PJSIP_AUTH_ALGORITHM_COUNT, /**< Number of algorithms. */
} pjsip_auth_algorithm_type;


/**
* Authentication Digest Algorithm
*
* This structure describes a digest algorithm used in
* SIP authentication.
*
*/
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 OpenSSL's
EVP_get_digestbyname() */
unsigned digest_length; /**< Length of the raw digest
in bytes */
unsigned digest_str_length; /**< Length of the digest 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 +113,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 +163,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 +179,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 +252,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 +279,45 @@ 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
* at runtime to determine if they are actually available in
* the current version of OpenSSL.
*
* @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 @@ -284,10 +394,12 @@ typedef pj_status_t pjsip_auth_lookup_cred( pj_pool_t *pool,
*/
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. */

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 +661,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 #pjsip_auth_srv_challenge2.
*
* @param auth_srv The server authentication structure.
* @param qop Optional qop value.
Expand All @@ -569,9 +682,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 #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
* #pjsip_auth_create_digest2
*
* pjsip_cred_info::data_type must be #PJSIP_CRED_DATA_PLAIN_PASSWD
* 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 +746,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 #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 +764,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 +774,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.
*
* pjsip_cred_info::data_type must be #PJSIP_CRED_DATA_PLAIN_PASSWD
* or #PJSIP_CRED_DATA_DIGEST.
*
* If pjsip_cred_info::data_type is #PJSIP_CRED_DATA_PLAIN_PASSWORD,
* pjsip_cred_info::username + ":" + realm + ":" + pjsip_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 pjsip_cred_info::data_type is #PJSIP_CRED_DATA_DIGEST,
* pjsip_cred_info::data must contain the value of
* username + ":" + realm + ":" + password
* pre-hashed with the algorithm specifed by pjsip_cred_info::algorithm_type
* and will be used as the "ha1" hash directly. In this case
* pjsip_cred_info::algorithm_type MUST match the algorithm_type
* passed as the last parameter to this function.
*
* \note If left unset (0), pjsip_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
19 changes: 6 additions & 13 deletions pjsip/include/pjsip/sip_auth_aka.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ PJ_BEGIN_DECL
* Application then specifies digest AKA credential by initializing the
* authentication credential as follows:
*
@code
\verbatim
pjsip_cred_info cred;
Expand All @@ -65,20 +65,19 @@ PJ_BEGIN_DECL
cred.scheme = pj_str("Digest");
cred.realm = pj_str("ims-domain.test");
cred.username = pj_str("user@ims-domain.test");
cred.data_type = PJSIP_CRED_DATA_PLAIN_PASSWD | PJSIP_CRED_DATA_EXT_AKA;
cred.data = pj_str("password");
cred.data_type = PJSIP_CRED_DATA_EXT_AKA;
// AKA extended info
cred.ext.aka.k = pj_str("password");
cred.ext.aka.cb = &pjsip_auth_create_aka_response
@endcode
\endverbatim
*
* Description:
* - To support AKA, application adds \a PJSIP_CRED_DATA_EXT_AKA flag in the
* - To support AKA, application adds #PJSIP_CRED_DATA_EXT_AKA flag in the
* \a data_type field. This indicates that extended information specific to
* AKA authentication is available in the credential, and that response
* digest computation will use the callback function instead of the usual MD5
* digest computation will use the callback function instead of the usual
* digest computation.
*
* - The \a scheme for the credential is "Digest".
Expand All @@ -87,12 +86,6 @@ PJ_BEGIN_DECL
* also specify wildcard realm ("*") if it wishes to respond to any realms
* in the challenge.
*
* - The \a data field is optional. Application may fill this with the password
* if it wants to support both MD5 and AKA MD5 in a single credential. The
* pjsip_auth_create_aka_response() function will use this field if the
* challenge indicates "MD5" as the algorithm instead of "AKAv1-MD5" or
* "AKAv2-MD5".
*
* - The \a ext.aka.k field specifies the permanent subscriber key to be used
* for AKA authentication. Application may specify binary password containing
* NULL character in this key, since the length of the key is indicated in
Expand Down Expand Up @@ -164,7 +157,7 @@ PJ_BEGIN_DECL
#define PJSIP_AKA_SQNLEN 6

/**
* This function creates MD5, AKAv1-MD5, or AKAv2-MD5 response for
* This function creates an AKAv1-MD5, or AKAv2-MD5 response for
* the specified challenge in \a chal, according to the algorithm
* specified in the challenge, and based on the information in the
* credential \a cred.
Expand Down
Loading

0 comments on commit 3200187

Please sign in to comment.