summaryrefslogtreecommitdiff
path: root/pjnath/src/pjnath/stun_auth.c
diff options
context:
space:
mode:
Diffstat (limited to 'pjnath/src/pjnath/stun_auth.c')
-rw-r--r--pjnath/src/pjnath/stun_auth.c341
1 files changed, 341 insertions, 0 deletions
diff --git a/pjnath/src/pjnath/stun_auth.c b/pjnath/src/pjnath/stun_auth.c
new file mode 100644
index 00000000..9a94fe0d
--- /dev/null
+++ b/pjnath/src/pjnath/stun_auth.c
@@ -0,0 +1,341 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2003-2005 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include <pjlib-util/stun_auth.h>
+#include <pjlib-util/errno.h>
+#include <pjlib-util/hmac_sha1.h>
+#include <pjlib-util/sha1.h>
+#include <pj/assert.h>
+#include <pj/string.h>
+
+
+/* Duplicate credential */
+PJ_DEF(void) pj_stun_auth_cred_dup( pj_pool_t *pool,
+ pj_stun_auth_cred *dst,
+ const pj_stun_auth_cred *src)
+{
+ dst->type = src->type;
+
+ switch (src->type) {
+ case PJ_STUN_AUTH_CRED_STATIC:
+ pj_strdup(pool, &dst->data.static_cred.realm,
+ &src->data.static_cred.realm);
+ pj_strdup(pool, &dst->data.static_cred.username,
+ &src->data.static_cred.username);
+ dst->data.static_cred.data_type = src->data.static_cred.data_type;
+ pj_strdup(pool, &dst->data.static_cred.data,
+ &src->data.static_cred.data);
+ pj_strdup(pool, &dst->data.static_cred.nonce,
+ &src->data.static_cred.nonce);
+ break;
+ case PJ_STUN_AUTH_CRED_DYNAMIC:
+ pj_memcpy(&dst->data.dyn_cred, &src->data.dyn_cred,
+ sizeof(src->data.dyn_cred));
+ break;
+ }
+}
+
+
+PJ_INLINE(pj_uint16_t) GET_VAL16(const pj_uint8_t *pdu, unsigned pos)
+{
+ return (pj_uint16_t) ((pdu[pos] << 8) + pdu[pos+1]);
+}
+
+
+/* Send 401 response */
+static pj_status_t create_challenge(pj_pool_t *pool,
+ const pj_stun_msg *msg,
+ int err_code,
+ const pj_str_t *err_msg,
+ const pj_str_t *realm,
+ const pj_str_t *nonce,
+ pj_stun_msg **p_response)
+{
+ pj_stun_msg *response;
+ pj_str_t tmp_nonce;
+ pj_status_t rc;
+
+ rc = pj_stun_msg_create_response(pool, msg,
+ err_code, err_msg, &response);
+ if (rc != PJ_SUCCESS)
+ return rc;
+
+
+ if (realm && realm->slen) {
+ rc = pj_stun_msg_add_string_attr(pool, response,
+ PJ_STUN_ATTR_REALM,
+ realm);
+ if (rc != PJ_SUCCESS)
+ return rc;
+
+ /* long term must include nonce */
+ if (!nonce || nonce->slen == 0) {
+ tmp_nonce = pj_str("pjstun");
+ nonce = &tmp_nonce;
+ }
+ }
+
+ if (nonce && nonce->slen) {
+ rc = pj_stun_msg_add_string_attr(pool, response,
+ PJ_STUN_ATTR_NONCE,
+ nonce);
+ if (rc != PJ_SUCCESS)
+ return rc;
+ }
+
+ *p_response = response;
+
+ return PJ_SUCCESS;
+}
+
+
+/* Verify credential */
+PJ_DEF(pj_status_t) pj_stun_verify_credential( const pj_uint8_t *pkt,
+ unsigned pkt_len,
+ const pj_stun_msg *msg,
+ pj_stun_auth_cred *cred,
+ pj_pool_t *pool,
+ pj_stun_msg **p_response)
+{
+ pj_str_t realm, nonce, password;
+ const pj_stun_msgint_attr *amsgi;
+ unsigned amsgi_pos;
+ const pj_stun_username_attr *auser;
+ pj_bool_t username_ok;
+ const pj_stun_realm_attr *arealm;
+ const pj_stun_realm_attr *anonce;
+ pj_uint8_t digest[PJ_SHA1_DIGEST_SIZE];
+ pj_uint8_t md5_digest[16];
+ pj_str_t key;
+ pj_status_t status;
+
+ /* msg and credential MUST be specified */
+ PJ_ASSERT_RETURN(pkt && pkt_len && msg && cred, PJ_EINVAL);
+
+ /* If p_response is specified, pool MUST be specified. */
+ PJ_ASSERT_RETURN(!p_response || pool, PJ_EINVAL);
+
+ if (p_response)
+ *p_response = NULL;
+
+ if (!PJ_STUN_IS_REQUEST(msg->hdr.type))
+ p_response = NULL;
+
+ /* Get realm and nonce */
+ realm.slen = nonce.slen = 0;
+ if (cred->type == PJ_STUN_AUTH_CRED_STATIC) {
+ realm = cred->data.static_cred.realm;
+ 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);
+ if (status != PJ_SUCCESS)
+ return status;
+ } else {
+ pj_assert(!"Unexpected");
+ return PJ_EBUG;
+ }
+
+ /* First check that MESSAGE-INTEGRITY is present */
+ amsgi = (const pj_stun_msgint_attr*)
+ pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_MESSAGE_INTEGRITY, 0);
+ if (amsgi == NULL) {
+ if (p_response) {
+ create_challenge(pool, msg, PJ_STUN_SC_UNAUTHORIZED, NULL,
+ &realm, &nonce, p_response);
+ }
+ return PJLIB_UTIL_ESTUNMSGINT;
+ }
+
+ /* Next check that USERNAME is present */
+ auser = (const pj_stun_username_attr*)
+ pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_USERNAME, 0);
+ if (auser == NULL) {
+ if (p_response) {
+ create_challenge(pool, msg, PJ_STUN_SC_MISSING_USERNAME, NULL,
+ &realm, &nonce, p_response);
+ }
+ return PJLIB_UTIL_ESTUNNOUSERNAME;
+ }
+
+ /* Get REALM, if any */
+ arealm = (const pj_stun_realm_attr*)
+ pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_REALM, 0);
+
+ /* Check if username match */
+ if (cred->type == PJ_STUN_AUTH_CRED_STATIC) {
+ username_ok = !pj_strcmp(&auser->value,
+ &cred->data.static_cred.username);
+ password = cred->data.static_cred.data;
+ } else if (cred->type == PJ_STUN_AUTH_CRED_DYNAMIC) {
+ int data_type = 0;
+ pj_status_t rc;
+ rc = cred->data.dyn_cred.get_password(cred->data.dyn_cred.user_data,
+ (arealm?&arealm->value:NULL),
+ &auser->value, pool,
+ &data_type, &password);
+ username_ok = (rc == PJ_SUCCESS);
+ } else {
+ username_ok = PJ_TRUE;
+ password.slen = 0;
+ }
+
+ if (!username_ok) {
+ /* Username mismatch */
+ if (p_response) {
+ create_challenge(pool, msg, PJ_STUN_SC_UNKNOWN_USERNAME, NULL,
+ &realm, &nonce, p_response);
+ }
+ return PJLIB_UTIL_ESTUNUSERNAME;
+ }
+
+
+ /* Get NONCE attribute */
+ anonce = (pj_stun_nonce_attr*)
+ pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_NONCE, 0);
+
+ /* Check for long term/short term requirements. */
+ if (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_MISSING_REALM, NULL,
+ &realm, &nonce, p_response);
+ }
+ return PJLIB_UTIL_ESTUNNOREALM;
+
+ } else if (realm.slen != 0 && arealm != NULL) {
+ /* We want long term, and REALM is present */
+
+ /* NONCE must be present. */
+ if (anonce == NULL) {
+ if (p_response) {
+ create_challenge(pool, msg, PJ_STUN_SC_MISSING_NONCE,
+ NULL, &realm, &nonce, p_response);
+ }
+ return PJLIB_UTIL_ESTUNNONCE;
+ }
+
+ /* Verify REALM matches */
+ if (pj_stricmp(&arealm->value, &realm)) {
+ /* REALM doesn't match */
+ if (p_response) {
+ create_challenge(pool, msg, PJ_STUN_SC_MISSING_REALM,
+ NULL, &realm, &nonce, p_response);
+ }
+ return PJLIB_UTIL_ESTUNNOREALM;
+ }
+
+ /* Valid case, will validate the message integrity later */
+
+ } else if (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.
+ */
+
+ /* For now just accept the credential, anyway it will probably
+ * cause wrong message integrity value later.
+ */
+ } else if (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_MISSING_NONCE,
+ NULL, &realm, &nonce, p_response);
+ }
+ return PJLIB_UTIL_ESTUNNONCE;
+ }
+ }
+
+ /* If NONCE is present, validate it */
+ if (anonce) {
+ pj_bool_t ok;
+
+ if (cred->type == PJ_STUN_AUTH_CRED_DYNAMIC) {
+ ok=cred->data.dyn_cred.verify_nonce(cred->data.dyn_cred.user_data,
+ (arealm?&arealm->value:NULL),
+ &auser->value,
+ &anonce->value);
+ } else {
+ if (nonce.slen) {
+ ok = !pj_strcmp(&anonce->value, &nonce);
+ } else {
+ ok = PJ_TRUE;
+ }
+ }
+
+ if (!ok) {
+ if (p_response) {
+ create_challenge(pool, msg, PJ_STUN_SC_STALE_NONCE,
+ NULL, &realm, &nonce, p_response);
+ }
+ return PJLIB_UTIL_ESTUNNONCE;
+ }
+ }
+
+ /* Get the position of MESSAGE-INTEGRITY in the packet */
+ amsgi_pos = 20+msg->hdr.length-24;
+ if (GET_VAL16(pkt, amsgi_pos) == PJ_STUN_ATTR_MESSAGE_INTEGRITY) {
+ /* Found MESSAGE-INTEGRITY as the last attribute */
+ } else {
+ amsgi_pos = 0;
+ }
+
+ if (amsgi_pos==0) {
+ amsgi_pos = 20+msg->hdr.length-8-24;
+ if (GET_VAL16(pkt, amsgi_pos) == PJ_STUN_ATTR_MESSAGE_INTEGRITY) {
+ /* Found MESSAGE-INTEGRITY before FINGERPRINT */
+ } else {
+ amsgi_pos = 0;
+ }
+ }
+
+ if (amsgi_pos==0) {
+ pj_assert(!"Unable to find MESSAGE-INTEGRITY in the message!");
+ return PJ_EBUG;
+ }
+
+ /* Determine which key to use */
+ if (realm.slen) {
+ pj_stun_calc_md5_key(md5_digest, &realm, &auser->value, &password);
+ key.ptr = (char*)md5_digest;
+ key.slen = 16;
+ } else {
+ key = password;
+ }
+
+ /* Now calculate HMAC of the message */
+ pj_hmac_sha1(pkt, amsgi_pos, (pj_uint8_t*)key.ptr, key.slen, digest);
+
+ /* Compare HMACs */
+ if (pj_memcmp(amsgi->hmac, digest, 20)) {
+ /* HMAC value mismatch */
+ if (p_response) {
+ create_challenge(pool, msg, PJ_STUN_SC_INTEGRITY_CHECK_FAILURE,
+ NULL, &realm, &nonce, p_response);
+ }
+ return PJLIB_UTIL_ESTUNMSGINT;
+ }
+
+ /* Everything looks okay! */
+ return PJ_SUCCESS;
+}
+
+