summaryrefslogtreecommitdiff
path: root/pjnath/src/pjnath/stun_auth.c
diff options
context:
space:
mode:
authorBenny Prijono <bennylp@teluu.com>2008-03-19 23:00:30 +0000
committerBenny Prijono <bennylp@teluu.com>2008-03-19 23:00:30 +0000
commit97dfbff2ebfae83b7cb10517247215d38e4a74fd (patch)
tree0edea19f19049829e444733eced5d892b5a95a84 /pjnath/src/pjnath/stun_auth.c
parentb15dff0bed0f34a3371bce1b17e5a13ade0fb194 (diff)
Related to ticket #485: huge changeset to update STUN relating to managing authentication. See the ticket for the details
git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@1877 74dad513-b988-da41-8d7b-12977e46ad98
Diffstat (limited to 'pjnath/src/pjnath/stun_auth.c')
-rw-r--r--pjnath/src/pjnath/stun_auth.c291
1 files changed, 196 insertions, 95 deletions
diff --git a/pjnath/src/pjnath/stun_auth.c b/pjnath/src/pjnath/stun_auth.c
index 4a6024f0..4ef29599 100644
--- a/pjnath/src/pjnath/stun_auth.c
+++ b/pjnath/src/pjnath/stun_auth.c
@@ -19,9 +19,11 @@
#include <pjnath/stun_auth.h>
#include <pjnath/errno.h>
#include <pjlib-util/hmac_sha1.h>
+#include <pjlib-util/md5.h>
#include <pjlib-util/sha1.h>
#include <pj/assert.h>
#include <pj/log.h>
+#include <pj/pool.h>
#include <pj/string.h>
#define THIS_FILE "stun_auth.c"
@@ -53,6 +55,99 @@ PJ_DEF(void) pj_stun_auth_cred_dup( pj_pool_t *pool,
}
+/*
+ * Duplicate request credential.
+ */
+PJ_DEF(void) pj_stun_req_cred_info_dup( pj_pool_t *pool,
+ pj_stun_req_cred_info *dst,
+ const pj_stun_req_cred_info *src)
+{
+ pj_strdup(pool, &dst->realm, &src->realm);
+ pj_strdup(pool, &dst->username, &src->username);
+ pj_strdup(pool, &dst->nonce, &src->nonce);
+ pj_strdup(pool, &dst->auth_key, &src->auth_key);
+}
+
+
+/* Calculate HMAC-SHA1 key for long term credential, by getting
+ * MD5 digest of username, realm, and password.
+ */
+static void calc_md5_key(pj_uint8_t digest[16],
+ const pj_str_t *realm,
+ const pj_str_t *username,
+ const pj_str_t *passwd)
+{
+ /* The 16-byte key for MESSAGE-INTEGRITY HMAC is formed by taking
+ * the MD5 hash of the result of concatenating the following five
+ * fields: (1) The username, with any quotes and trailing nulls
+ * removed, (2) A single colon, (3) The realm, with any quotes and
+ * trailing nulls removed, (4) A single colon, and (5) The
+ * password, with any trailing nulls removed.
+ */
+ pj_md5_context ctx;
+ pj_str_t s;
+
+ pj_md5_init(&ctx);
+
+#define REMOVE_QUOTE(s) if (s.slen && *s.ptr=='"') \
+ s.ptr++, s.slen--; \
+ if (s.slen && s.ptr[s.slen-1]=='"') \
+ s.slen--;
+
+ /* Add username */
+ s = *username;
+ REMOVE_QUOTE(s);
+ pj_md5_update(&ctx, (pj_uint8_t*)s.ptr, s.slen);
+
+ /* Add single colon */
+ pj_md5_update(&ctx, (pj_uint8_t*)":", 1);
+
+ /* Add realm */
+ s = *realm;
+ REMOVE_QUOTE(s);
+ pj_md5_update(&ctx, (pj_uint8_t*)s.ptr, s.slen);
+
+#undef REMOVE_QUOTE
+
+ /* Another colon */
+ pj_md5_update(&ctx, (pj_uint8_t*)":", 1);
+
+ /* Add password */
+ pj_md5_update(&ctx, (pj_uint8_t*)passwd->ptr, passwd->slen);
+
+ /* Done */
+ pj_md5_final(&ctx, digest);
+}
+
+
+/*
+ * Create authentication key to be used for encoding the message with
+ * MESSAGE-INTEGRITY.
+ */
+PJ_DEF(void) pj_stun_create_key(pj_pool_t *pool,
+ pj_str_t *key,
+ const pj_str_t *realm,
+ const pj_str_t *username,
+ pj_stun_passwd_type data_type,
+ const pj_str_t *data)
+{
+ PJ_ASSERT_ON_FAIL(pool && key && username && data, return);
+
+ if (realm && realm->slen) {
+ if (data_type == PJ_STUN_PASSWD_PLAIN) {
+ key->ptr = (char*) pj_pool_alloc(pool, 16);
+ calc_md5_key((pj_uint8_t*)key->ptr, realm, username, data);
+ key->slen = 16;
+ } else {
+ pj_strdup(pool, key, data);
+ }
+ } else {
+ pj_assert(data_type == PJ_STUN_PASSWD_PLAIN);
+ pj_strdup(pool, key, data);
+ }
+}
+
+
PJ_INLINE(pj_uint16_t) GET_VAL16(const pj_uint8_t *pdu, unsigned pos)
{
return (pj_uint16_t) ((pdu[pos] << 8) + pdu[pos+1]);
@@ -86,8 +181,8 @@ static pj_status_t create_challenge(pj_pool_t *pool,
if (rc != PJ_SUCCESS)
return rc;
-
- if (realm && realm->slen) {
+ /* SHOULD NOT add REALM, NONCE, USERNAME, and M-I on 400 response */
+ if (err_code!=400 && realm && realm->slen) {
rc = pj_stun_msg_add_string_attr(pool, response,
PJ_STUN_ATTR_REALM,
realm);
@@ -101,7 +196,7 @@ static pj_status_t create_challenge(pj_pool_t *pool,
}
}
- if (nonce && nonce->slen) {
+ if (err_code!=400 && nonce && nonce->slen) {
rc = pj_stun_msg_add_string_attr(pool, response,
PJ_STUN_ATTR_NONCE,
nonce);
@@ -121,20 +216,20 @@ PJ_DEF(pj_status_t) pj_stun_authenticate_request(const pj_uint8_t *pkt,
const pj_stun_msg *msg,
pj_stun_auth_cred *cred,
pj_pool_t *pool,
- pj_str_t *auth_key,
+ pj_stun_req_cred_info *p_info,
pj_stun_msg **p_response)
{
- pj_str_t realm, nonce, password;
+ pj_stun_req_cred_info tmp_info;
const pj_stun_msgint_attr *amsgi;
unsigned i, amsgi_pos;
pj_bool_t has_attr_beyond_mi;
const pj_stun_username_attr *auser;
- pj_bool_t username_ok;
const pj_stun_realm_attr *arealm;
const pj_stun_realm_attr *anonce;
pj_hmac_sha1_context ctx;
pj_uint8_t digest[PJ_SHA1_DIGEST_SIZE];
- pj_str_t key;
+ pj_stun_status err_code;
+ const char *err_text = NULL;
pj_status_t status;
/* msg and credential MUST be specified */
@@ -149,18 +244,24 @@ PJ_DEF(pj_status_t) pj_stun_authenticate_request(const pj_uint8_t *pkt,
if (!PJ_STUN_IS_REQUEST(msg->hdr.type))
p_response = NULL;
+ if (p_info == NULL)
+ p_info = &tmp_info;
+
+ pj_bzero(p_info, sizeof(pj_stun_req_cred_info));
+
/* Get realm and nonce from credential */
- realm.slen = nonce.slen = 0;
+ p_info->realm.slen = p_info->nonce.slen = 0;
if (cred->type == PJ_STUN_AUTH_CRED_STATIC) {
- realm = cred->data.static_cred.realm;
- nonce = cred->data.static_cred.nonce;
+ p_info->realm = cred->data.static_cred.realm;
+ p_info->nonce = cred->data.static_cred.nonce;
} else if (cred->type == PJ_STUN_AUTH_CRED_DYNAMIC) {
status = cred->data.dyn_cred.get_auth(cred->data.dyn_cred.user_data,
- pool, &realm, &nonce);
+ pool, &p_info->realm,
+ &p_info->nonce);
if (status != PJ_SUCCESS)
return status;
} else {
- pj_assert(!"Unexpected");
+ pj_assert(!"Invalid credential type");
return PJ_EBUG;
}
@@ -184,14 +285,9 @@ PJ_DEF(pj_status_t) pj_stun_authenticate_request(const pj_uint8_t *pkt,
for short term, and 401 for long term.
The rule has been changed from rfc3489bis-06
*/
- int code;
-
- code = realm.slen ? PJ_STUN_SC_UNAUTHORIZED : PJ_STUN_SC_BAD_REQUEST;
- if (p_response) {
- create_challenge(pool, msg, code, NULL,
- &realm, &nonce, p_response);
- }
- return PJ_STATUS_FROM_STUN_CODE(code);
+ err_code = p_info->realm.slen ? PJ_STUN_SC_UNAUTHORIZED :
+ PJ_STUN_SC_BAD_REQUEST;
+ goto on_auth_failed;
}
/* Next check that USERNAME is present */
@@ -202,48 +298,67 @@ PJ_DEF(pj_status_t) pj_stun_authenticate_request(const pj_uint8_t *pkt,
for both short and long term, since M-I is present.
The rule has been changed from rfc3489bis-06
*/
- int code = PJ_STUN_SC_BAD_REQUEST;
- if (p_response) {
- create_challenge(pool, msg, code, "Missing USERNAME",
- &realm, &nonce, p_response);
- }
- return PJ_STATUS_FROM_STUN_CODE(code);
+ err_code = PJ_STUN_SC_BAD_REQUEST;
+ err_text = "Missing USERNAME";
+ goto on_auth_failed;
}
/* Get REALM, if any */
arealm = (const pj_stun_realm_attr*)
pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_REALM, 0);
+ /* Reject with 400 if we have long term credential and the request
+ * is missing REALM attribute.
+ */
+ if (p_info->realm.slen && arealm==NULL) {
+ err_code = PJ_STUN_SC_BAD_REQUEST;
+ err_text = "Missing REALM";
+ goto on_auth_failed;
+ }
+
/* Check if username match */
if (cred->type == PJ_STUN_AUTH_CRED_STATIC) {
+ pj_bool_t username_ok;
username_ok = !pj_strcmp(&auser->value,
&cred->data.static_cred.username);
- password = cred->data.static_cred.data;
+ if (username_ok) {
+ pj_strdup(pool, &p_info->username,
+ &cred->data.static_cred.username);
+ pj_stun_create_key(pool, &p_info->auth_key, &p_info->realm,
+ &auser->value, cred->data.static_cred.data_type,
+ &cred->data.static_cred.data);
+ } else {
+ /* Username mismatch */
+ /* According to rfc3489bis-10 Sec 10.1.2/10.2.2, we should
+ * return 401
+ */
+ err_code = PJ_STUN_SC_UNAUTHORIZED;
+ goto on_auth_failed;
+ }
} else if (cred->type == PJ_STUN_AUTH_CRED_DYNAMIC) {
- int data_type = 0;
+ pj_stun_passwd_type data_type = PJ_STUN_PASSWD_PLAIN;
+ pj_str_t password;
pj_status_t rc;
+
rc = cred->data.dyn_cred.get_password(msg,
cred->data.dyn_cred.user_data,
(arealm?&arealm->value:NULL),
&auser->value, pool,
&data_type, &password);
- username_ok = (rc == PJ_SUCCESS);
+ if (rc == PJ_SUCCESS) {
+ pj_strdup(pool, &p_info->username, &auser->value);
+ pj_stun_create_key(pool, &p_info->auth_key,
+ (arealm?&arealm->value:NULL), &auser->value,
+ data_type, &password);
+ } else {
+ err_code = PJ_STUN_SC_UNAUTHORIZED;
+ goto on_auth_failed;
+ }
} else {
- username_ok = PJ_TRUE;
- password.slen = 0;
+ pj_assert(!"Invalid credential type");
+ return PJ_EBUG;
}
- if (!username_ok) {
- /* Username mismatch */
- /* According to rfc3489bis-10 Sec 10.1.2/10.2.2, we should
- * return 401
- */
- if (p_response) {
- create_challenge(pool, msg, PJ_STUN_SC_UNAUTHORIZED, NULL,
- &realm, &nonce, p_response);
- }
- return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_UNAUTHORIZED);
- }
/* Get NONCE attribute */
@@ -251,40 +366,33 @@ PJ_DEF(pj_status_t) pj_stun_authenticate_request(const pj_uint8_t *pkt,
pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_NONCE, 0);
/* Check for long term/short term requirements. */
- if (realm.slen != 0 && arealm == NULL) {
+ if (p_info->realm.slen != 0 && arealm == NULL) {
/* Long term credential is required and REALM is not present */
- if (p_response) {
- create_challenge(pool, msg, PJ_STUN_SC_BAD_REQUEST,
- "Missing REALM",
- &realm, &nonce, p_response);
- }
- return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_BAD_REQUEST);
+ err_code = PJ_STUN_SC_BAD_REQUEST;
+ err_text = "Missing REALM";
+ goto on_auth_failed;
- } else if (realm.slen != 0 && arealm != NULL) {
+ } else if (p_info->realm.slen != 0 && arealm != NULL) {
/* We want long term, and REALM is present */
/* NONCE must be present. */
- if (anonce == NULL && nonce.slen) {
- if (p_response) {
- create_challenge(pool, msg, PJ_STUN_SC_BAD_REQUEST,
- "Missing NONCE", &realm, &nonce, p_response);
- }
- return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_BAD_REQUEST);
+ if (anonce == NULL && p_info->nonce.slen) {
+ err_code = PJ_STUN_SC_BAD_REQUEST;
+ err_text = "Missing NONCE";
+ goto on_auth_failed;
}
/* Verify REALM matches */
- if (pj_stricmp(&arealm->value, &realm)) {
+ if (pj_stricmp(&arealm->value, &p_info->realm)) {
/* REALM doesn't match */
- if (p_response) {
- create_challenge(pool, msg, PJ_STUN_SC_UNAUTHORIZED,
- "Invalid REALM", &realm, &nonce, p_response);
- }
- return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_UNAUTHORIZED);
+ err_code = PJ_STUN_SC_UNAUTHORIZED;
+ err_text = "Invalid REALM";
+ goto on_auth_failed;
}
/* Valid case, will validate the message integrity later */
- } else if (realm.slen == 0 && arealm != NULL) {
+ } else if (p_info->realm.slen == 0 && arealm != NULL) {
/* We want to use short term credential, but client uses long
* term credential. The draft doesn't mention anything about
* switching between long term and short term.
@@ -293,16 +401,14 @@ PJ_DEF(pj_status_t) pj_stun_authenticate_request(const pj_uint8_t *pkt,
/* For now just accept the credential, anyway it will probably
* cause wrong message integrity value later.
*/
- } else if (realm.slen==0 && arealm == NULL) {
+ } else if (p_info->realm.slen==0 && arealm == NULL) {
/* Short term authentication is wanted, and one is supplied */
/* Application MAY request NONCE to be supplied */
- if (nonce.slen != 0) {
- if (p_response) {
- create_challenge(pool, msg, PJ_STUN_SC_UNAUTHORIZED,
- "NONCE required", &realm, &nonce, p_response);
- }
- return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_UNAUTHORIZED);
+ if (p_info->nonce.slen != 0) {
+ err_code = PJ_STUN_SC_UNAUTHORIZED;
+ err_text = "NONCE required";
+ goto on_auth_failed;
}
}
@@ -321,31 +427,22 @@ PJ_DEF(pj_status_t) pj_stun_authenticate_request(const pj_uint8_t *pkt,
} else if (cred->type == PJ_STUN_AUTH_CRED_DYNAMIC) {
ok = PJ_TRUE;
} else {
- if (nonce.slen) {
- ok = !pj_strcmp(&anonce->value, &nonce);
+ if (p_info->nonce.slen) {
+ ok = !pj_strcmp(&anonce->value, &p_info->nonce);
} else {
ok = PJ_TRUE;
}
}
if (!ok) {
- if (p_response) {
- create_challenge(pool, msg, PJ_STUN_SC_STALE_NONCE,
- NULL, &realm, &nonce, p_response);
- }
- if (auth_key) {
- pj_stun_create_key(pool, auth_key, &realm,
- &auser->value, &password);
- }
- return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_STALE_NONCE);
+ err_code = PJ_STUN_SC_STALE_NONCE;
+ goto on_auth_failed;
}
}
- /* Calculate key */
- pj_stun_create_key(pool, &key, &realm, &auser->value, &password);
-
/* Now calculate HMAC of the message. */
- pj_hmac_sha1_init(&ctx, (pj_uint8_t*)key.ptr, key.slen);
+ pj_hmac_sha1_init(&ctx, (pj_uint8_t*)p_info->auth_key.ptr,
+ p_info->auth_key.slen);
#if PJ_STUN_OLD_STYLE_MI_FINGERPRINT
/* Pre rfc3489bis-06 style of calculation */
@@ -382,15 +479,20 @@ PJ_DEF(pj_status_t) pj_stun_authenticate_request(const pj_uint8_t *pkt,
if (pj_memcmp(amsgi->hmac, digest, 20)) {
/* HMAC value mismatch */
/* According to rfc3489bis-10 Sec 10.1.2 we should return 401 */
- if (p_response) {
- create_challenge(pool, msg, PJ_STUN_SC_UNAUTHORIZED,
- NULL, &realm, &nonce, p_response);
- }
- return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_UNAUTHORIZED);
+ err_code = PJ_STUN_SC_UNAUTHORIZED;
+ err_text = "MESSAGE-INTEGRITY mismatch";
+ goto on_auth_failed;
}
/* Everything looks okay! */
return PJ_SUCCESS;
+
+on_auth_failed:
+ if (p_response) {
+ create_challenge(pool, msg, err_code, err_text,
+ &p_info->realm, &p_info->nonce, p_response);
+ }
+ return PJ_STATUS_FROM_STUN_CODE(err_code);
}
@@ -425,11 +527,10 @@ PJ_DEF(pj_bool_t) pj_stun_auth_valid_for_msg(const pj_stun_msg *msg)
switch (err_attr->err_code) {
case PJ_STUN_SC_BAD_REQUEST: /* 400 (Bad Request) */
case PJ_STUN_SC_UNAUTHORIZED: /* 401 (Unauthorized) */
- //case PJ_STUN_SC_STALE_CREDENTIALS: /* 430 (Stale Credential) */
- //case PJ_STUN_SC_MISSING_USERNAME: /* 432 (Missing Username) */
- //case PJ_STUN_SC_MISSING_REALM: /* 434 (Missing Realm) */
- //case PJ_STUN_SC_UNKNOWN_USERNAME: /* 436 (Unknown Username) */
- //case PJ_STUN_SC_INTEGRITY_CHECK_FAILURE:/* 431 (Integrity Check Fail) */
+
+ /* Due to the way this response is generated here, we can't really
+ * authenticate 420 (Unknown Attribute) response */
+ case PJ_STUN_SC_UNKNOWN_ATTRIBUTE:
return PJ_FALSE;
default:
return PJ_TRUE;