Skip to content

Commit 197ca22

Browse files
committed
Add full support for SHA-256 and SHA-512-256 digest algorithms
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
1 parent cc83544 commit 197ca22

6 files changed

Lines changed: 740 additions & 323 deletions

File tree

pjlib/src/pj/ssl_sock_ossl.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -713,6 +713,7 @@ static pj_status_t init_openssl(void)
713713
#if OPENSSL_VERSION_NUMBER < 0x009080ffL
714714
/* This is now synonym of SSL_library_init() */
715715
OpenSSL_add_all_algorithms();
716+
OpenSSL_add_all_digests();
716717
#endif
717718

718719
/* Init available ciphers */

pjsip/include/pjsip/sip_auth.h

Lines changed: 194 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,54 @@ PJ_BEGIN_DECL
4242
* @{
4343
*/
4444

45-
/** Length of digest MD5 string. */
45+
/**
46+
* Length of digest MD5 string.
47+
* \deprecated Use pjsip_auth_algorithm.digest_str_length instead.
48+
*/
4649
#define PJSIP_MD5STRLEN 32
4750

48-
/** Length of digest SHA256 string. */
51+
/**
52+
* Length of digest SHA256 string.
53+
* \deprecated Use pjsip_auth_algorithm.digest_str_length instead.
54+
*/
4955
#define PJSIP_SHA256STRLEN 64
5056

57+
/**
58+
* The largest supported algorithm's digest length
59+
*/
60+
#define PJSIP_AUTH_MAX_DIGEST_BUFFER_LENGTH 64
61+
62+
/**
63+
* Digest Algorithm Types.
64+
* \warning These entries must remain in order with
65+
* no gaps and with _NOT_SET = 0 and _COUNT as the last entry.
66+
*
67+
*/
68+
typedef enum pjsip_auth_algorithm_type
69+
{
70+
PJSIP_AUTH_ALGORITHM_NOT_SET = 0,
71+
PJSIP_AUTH_ALGORITHM_MD5,
72+
PJSIP_AUTH_ALGORITHM_SHA256,
73+
PJSIP_AUTH_ALGORITHM_SHA512_256,
74+
PJSIP_AUTH_ALGORITHM_AKAV1_MD5,
75+
PJSIP_AUTH_ALGORITHM_AKAV2_MD5,
76+
PJSIP_AUTH_ALGORITHM_COUNT,
77+
} pjsip_auth_algorithm_type;
78+
79+
80+
typedef struct pjsip_auth_algorithm
81+
{
82+
pjsip_auth_algorithm_type algorithm_type; /**< Digest algorithm type */
83+
pj_str_t iana_name; /**< IANA/RFC name used in
84+
SIP headers */
85+
const char *openssl_name; /**< The name used by
86+
EVP_get_digestbyname()*/
87+
unsigned digest_length; /**< Length of the raw digest
88+
in bytes */
89+
unsigned digest_str_length; /**< Length of the HEX
90+
representation */
91+
} pjsip_auth_algorithm;
92+
5193

5294
/** Type of data in the credential information in #pjsip_cred_info. */
5395
typedef enum pjsip_cred_data_type
@@ -59,6 +101,13 @@ typedef enum pjsip_cred_data_type
59101

60102
} pjsip_cred_data_type;
61103

104+
#define PJSIP_CRED_DATA_PASSWD_MASK 0x000F
105+
#define PJSIP_CRED_DATA_EXT_MASK 0x00F0
106+
107+
#define PJSIP_CRED_DATA_IS_AKA(cred) (((cred)->data_type & PJSIP_CRED_DATA_EXT_MASK) == PJSIP_CRED_DATA_EXT_AKA)
108+
#define PJSIP_CRED_DATA_IS_PASSWD(cred) (((cred)->data_type & PJSIP_CRED_DATA_PASSWD_MASK) == PJSIP_CRED_DATA_PLAIN_PASSWD)
109+
#define PJSIP_CRED_DATA_IS_DIGEST(cred) (((cred)->data_type & PJSIP_CRED_DATA_PASSWD_MASK) == PJSIP_CRED_DATA_DIGEST)
110+
62111
/** Authentication's quality of protection (qop) type. */
63112
typedef enum pjsip_auth_qop_type
64113
{
@@ -102,11 +151,14 @@ typedef pj_status_t (*pjsip_cred_cb)(pj_pool_t *pool,
102151
/**
103152
* This structure describes credential information.
104153
* A credential information is a static, persistent information that identifies
105-
* username and password required to authorize to a specific realm.
154+
* credentials required to authorize to a specific realm.
106155
*
107156
* Note that since PJSIP 0.7.0.1, it is possible to make a credential that is
108157
* valid for any realms, by setting the realm to star/wildcard character,
109158
* i.e. realm = pj_str("*");.
159+
*
160+
* You should always fill this structure with zeros using PJ_POOL_ZALLOC_T()
161+
* or pj_bzero() before setting any fields.
110162
*/
111163
struct pjsip_cred_info
112164
{
@@ -115,9 +167,15 @@ struct pjsip_cred_info
115167
challenges. */
116168
pj_str_t scheme; /**< Scheme (e.g. "digest"). */
117169
pj_str_t username; /**< User name. */
118-
int data_type; /**< Type of data (0 for plaintext passwd). */
170+
int data_type; /**< Type of data \ref pjsip_cred_data_type */
119171
pj_str_t data; /**< The data, which can be a plaintext
120172
password or a hashed digest. */
173+
/**
174+
* If the data_type is PJSIP_CRED_DATA_DIGEST and the digest algorithm
175+
* used is not MD5 (the default), then this field MUST be set to the
176+
* appropriate digest algorithm type.
177+
*/
178+
pjsip_auth_algorithm_type algorithm_type;/**< Digest algorithm type */
121179

122180
/** Extended data */
123181
union {
@@ -182,7 +240,8 @@ typedef struct pjsip_cached_auth
182240
pjsip_cached_auth_hdr cached_hdr;/**< List of cached header for
183241
each method. */
184242
#endif
185-
243+
pjsip_auth_algorithm_type challenge_algorithm_type; /**< Challenge
244+
algorithm */
186245
} pjsip_cached_auth;
187246

188247

@@ -208,6 +267,44 @@ typedef struct pjsip_auth_clt_pref
208267
} pjsip_auth_clt_pref;
209268

210269

270+
/**
271+
* Get a pjsip_auth_algorithm structure by type.
272+
*
273+
* @param algorithm_type The algorithm type
274+
*
275+
* @return A pointer to a pjsip_auth_algorithm structure
276+
* or NULL if not found.
277+
*/
278+
PJ_DECL(const pjsip_auth_algorithm *) pjsip_auth_get_algorithm_by_type(
279+
pjsip_auth_algorithm_type algorithm_type);
280+
281+
282+
/**
283+
* Get a pjsip_auth_algorithm by IANA name.
284+
*
285+
* @param iana_name The IANA name (MD5, SHA-256, SHA-512-256)
286+
*
287+
* @return A pointer to a pjsip_auth_algorithm structure
288+
* or NULL if not found.
289+
*/
290+
PJ_DECL(const pjsip_auth_algorithm *) pjsip_auth_get_algorithm_by_iana_name(
291+
const pj_str_t *iana_name);
292+
293+
294+
/**
295+
* Check if a digest algorithm is supported.
296+
* Algorithms that require support from OpenSSL will be checked
297+
* to determine if they can actually be used.
298+
*
299+
* @param algorithm_type The algorithm type
300+
*
301+
* @return PJ_TRUE if the algorithm is supported,
302+
* PJ_FALSE otherwise.
303+
*/
304+
PJ_DECL(pj_bool_t) pjsip_auth_is_algorithm_supported(
305+
pjsip_auth_algorithm_type algorithm_type);
306+
307+
211308
/**
212309
* Duplicate a client authentication preference setting.
213310
*
@@ -287,6 +384,7 @@ typedef struct pjsip_auth_lookup_cred_param
287384
pj_str_t realm; /**< Realm to find the account. */
288385
pj_str_t acc_name; /**< Account name to look for. */
289386
pjsip_rx_data *rdata; /**< Incoming request to be authenticated. */
387+
pjsip_authorization_hdr *auth_hdr; /**< Authorization header to be authenticated. */
290388

291389
} pjsip_auth_lookup_cred_param;
292390

@@ -549,7 +647,8 @@ PJ_DECL(pj_status_t) pjsip_auth_srv_verify( pjsip_auth_srv *auth_srv,
549647
* Add authentication challenge headers to the outgoing response in tdata.
550648
* Application may specify its customized nonce and opaque for the challenge,
551649
* or can leave the value to NULL to make the function fills them in with
552-
* random characters.
650+
* random characters. The digest algorithm defaults to MD5. If you need
651+
* to specify a different algorithm, use \ref pjsip_auth_srv_challenge2.
553652
*
554653
* @param auth_srv The server authentication structure.
555654
* @param qop Optional qop value.
@@ -569,9 +668,43 @@ PJ_DECL(pj_status_t) pjsip_auth_srv_challenge( pjsip_auth_srv *auth_srv,
569668
pjsip_tx_data *tdata);
570669

571670
/**
572-
* Helper function to create MD5 digest out of the specified
671+
* Add authentication challenge headers to the outgoing response in tdata.
672+
* Application may specify its customized nonce and opaque for the challenge,
673+
* or can leave the value to NULL to make the function fills them in with
674+
* random characters.
675+
* Application must specify the algorithm to use.
676+
*
677+
* @param auth_srv The server authentication structure.
678+
* @param qop Optional qop value.
679+
* @param nonce Optional nonce value.
680+
* @param opaque Optional opaque value.
681+
* @param stale Stale indication.
682+
* @param tdata The outgoing response message. The response must have
683+
* 401 or 407 response code.
684+
* @param algorithm_type One of the \ref pjsip_auth_algorithm_type values.
685+
*
686+
* @return PJ_SUCCESS on success.
687+
*/
688+
PJ_DECL(pj_status_t) pjsip_auth_srv_challenge2(pjsip_auth_srv *auth_srv,
689+
const pj_str_t *qop,
690+
const pj_str_t *nonce,
691+
const pj_str_t *opaque,
692+
pj_bool_t stale,
693+
pjsip_tx_data *tdata,
694+
const pjsip_auth_algorithm_type algorithm_type);
695+
696+
/**
697+
* Helper function to create a digest out of the specified
573698
* parameters.
574699
*
700+
* \warning Because of ambiguities in the API, this function
701+
* should only be used for backward compatibility with the
702+
* MD5 digest algorithm. New code should use
703+
* \ref pjsip_auth_create_digest2
704+
*
705+
* cred_info->data_type must be PJSIP_CRED_DATA_PLAIN_PASSWORD
706+
* or PJSIP_CRED_DATA_DIGEST.
707+
*
575708
* @param result String to store the response digest. This string
576709
* must have been preallocated by caller with the
577710
* buffer at least PJSIP_MD5STRLEN (32 bytes) in size.
@@ -599,6 +732,9 @@ PJ_DECL(pj_status_t) pjsip_auth_create_digest(pj_str_t *result,
599732
/**
600733
* Helper function to create SHA-256 digest out of the specified
601734
* parameters.
735+
* \deprecated Use \ref pjsip_auth_create_digest2 with
736+
* algorithm_type = PJSIP_AUTH_ALGORITHM_SHA256.
737+
*
602738
*
603739
* @param result String to store the response digest. This string
604740
* must have been preallocated by caller with the
@@ -614,7 +750,7 @@ PJ_DECL(pj_status_t) pjsip_auth_create_digest(pj_str_t *result,
614750
*
615751
* @return PJ_SUCCESS on success.
616752
*/
617-
PJ_DEF(pj_status_t) pjsip_auth_create_digestSHA256(pj_str_t* result,
753+
PJ_DECL(pj_status_t) pjsip_auth_create_digestSHA256(pj_str_t* result,
618754
const pj_str_t* nonce,
619755
const pj_str_t* nc,
620756
const pj_str_t* cnonce,
@@ -624,6 +760,56 @@ PJ_DEF(pj_status_t) pjsip_auth_create_digestSHA256(pj_str_t* result,
624760
const pjsip_cred_info* cred_info,
625761
const pj_str_t* method);
626762

763+
/**
764+
* Helper function to create a digest out of the specified
765+
* parameters.
766+
*
767+
* cred_info->data_type must be PJSIP_CRED_DATA_PLAIN_PASSWORD
768+
* or PJSIP_CRED_DATA_DIGEST.
769+
*
770+
* If cred_info->data_type is PJSIP_CRED_DATA_PLAIN_PASSWORD,
771+
* cred_info->username + ":" + realm + ":" + cred_info->data
772+
* will be hashed using the algorithm_type specified by the last
773+
* parameter passed to this function to create the "ha1" hash.
774+
*
775+
* If cred_info->data_type is PJSIP_CRED_DATA_DIGEST,
776+
* cred_info->data must contain the value of
777+
* username + ":" + realm + ":" + password pre-hashed
778+
* with the algorithm specifed by cred_info->algorithm_type
779+
* and will be used as the "ha1" hash directly. In this case
780+
* cred_info->algorithm_type MUST match the algorithm_type
781+
* passed as the last parameter to this function.
782+
*
783+
* \note If left unset (0), cred_info->algorithm_type will
784+
* default to PJSIP_AUTH_ALGORITHM_MD5.
785+
*
786+
* @param result String to store the response digest. This string
787+
* must have been preallocated by the caller with the
788+
* buffer at least as large as the digest_str_length
789+
* member of the appropriate pjsip_auth_algorithm.
790+
* @param nonce Optional nonce.
791+
* @param nc Nonce count.
792+
* @param cnonce Optional cnonce.
793+
* @param qop Optional qop.
794+
* @param uri URI.
795+
* @param realm Realm.
796+
* @param cred_info Credential info.
797+
* @param method SIP method.
798+
* @param algorithm_type The hash algorithm to use.
799+
*
800+
* @return PJ_SUCCESS on success.
801+
*/
802+
PJ_DECL(pj_status_t) pjsip_auth_create_digest2(pj_str_t *result,
803+
const pj_str_t *nonce,
804+
const pj_str_t *nc,
805+
const pj_str_t *cnonce,
806+
const pj_str_t *qop,
807+
const pj_str_t *uri,
808+
const pj_str_t *realm,
809+
const pjsip_cred_info *cred_info,
810+
const pj_str_t *method,
811+
const pjsip_auth_algorithm_type algorithm_type);
812+
627813
/**
628814
* @}
629815
*/

pjsip/include/pjsip/sip_config.h

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1345,10 +1345,25 @@ PJ_INLINE(pjsip_cfg_t*) pjsip_cfg(void)
13451345
#endif
13461346

13471347
/**
1348-
* Allow client to send multiple Authorization header when receiving multiple
1349-
* WWW-Authenticate header fields. If this is disabled, the stack will send
1350-
* Authorization header field containing credentials that match the
1351-
* topmost header field.
1348+
* RFC-7616 and RFC-8760 state that for each realm a UAS requires authentication
1349+
* for it can send a WWW/Proxy-Authenticate header for each digest algorithm it
1350+
* supports and for each realm, they must be added in most-preferred to least-
1351+
* preferred order. The RFCs also state that the UAS MUST NOT send multiple
1352+
* WWW/Proxy-Authenticate headers with the same realm and algorithm.
1353+
*
1354+
* The RFCs also state that the UAC SHOULD respond to the topmost header
1355+
* for each realm containing a digest algorithm it supports. Neither RFC
1356+
* however, states whether the UAC should send multiple Authorization headers
1357+
* for the same realm if it can support multiple digest algorithms. Common
1358+
* sense dicates though that the UAC should NOT send additional Authorization
1359+
* headers for the same realm once it's already sent a more preferred one.
1360+
* The reasoning is simple... If a UAS sends two WWW-Authenticate headers,
1361+
* the first for SHA-256 and the second for MD5, a UAC responding to both
1362+
* completely defeats the purpose of the UAS sending the more secure SHA-256.
1363+
*
1364+
* Having said that, if there is some corner case where continuing to send
1365+
* additional Authorization headers for the same realm is necessary, then
1366+
* this define can be set to 1 to allow it.
13521367
*
13531368
* Default is 0
13541369
*/

pjsip/src/pjsip/sip_auth_aka.c

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -147,9 +147,10 @@ PJ_DEF(pj_status_t) pjsip_auth_create_aka_response(
147147
aka_cred.data.ptr = (char*)res;
148148
aka_cred.data.slen = PJSIP_AKA_RESLEN;
149149

150-
status = pjsip_auth_create_digest(&auth->response, &chal->nonce,
150+
status = pjsip_auth_create_digest2(&auth->response, &chal->nonce,
151151
&auth->nc, &auth->cnonce, &auth->qop,
152-
&auth->uri, &chal->realm, &aka_cred, method);
152+
&auth->uri, &chal->realm, &aka_cred, method,
153+
PJSIP_AUTH_ALGORITHM_MD5);
153154

154155
} else if (aka_version == 2) {
155156

@@ -186,9 +187,10 @@ PJ_DEF(pj_status_t) pjsip_auth_create_aka_response(
186187
aka_cred.data.ptr, &len);
187188
aka_cred.data.slen = hmac64_len;
188189

189-
status = pjsip_auth_create_digest(&auth->response, &chal->nonce,
190+
status = pjsip_auth_create_digest2(&auth->response, &chal->nonce,
190191
&auth->nc, &auth->cnonce, &auth->qop,
191-
&auth->uri, &chal->realm, &aka_cred, method);
192+
&auth->uri, &chal->realm, &aka_cred, method,
193+
PJSIP_AUTH_ALGORITHM_MD5);
192194

193195
} else {
194196
pj_assert(!"Bug!");

0 commit comments

Comments
 (0)