Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add full support for SHA-256 and SHA-512-256 digest algorithms #4118

Merged
merged 1 commit into from
Nov 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here and below I would use pj_size_t.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Digest lengths will never need a size_t so why allocate an extra 4 bytes for each of them?

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 */
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not using pjsip_cred_data_type instead of int?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because that's the way it was historically and I didn't want to change the signature with this patch.

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
Loading