Skip to content

Commit

Permalink
Add full support for SHA-256 and SHA-512-256 digest algorithms (#4118)
Browse files Browse the repository at this point in the history
  • Loading branch information
gtjoseph authored Nov 7, 2024
1 parent b209c98 commit 2e00417
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 2e00417

Please sign in to comment.