summaryrefslogtreecommitdiff
path: root/pjlib-util/src
diff options
context:
space:
mode:
authorBenny Prijono <bennylp@teluu.com>2007-02-26 22:31:06 +0000
committerBenny Prijono <bennylp@teluu.com>2007-02-26 22:31:06 +0000
commit79768e50b4c2e0d1c4fb871dc99a696fc5d8d2dd (patch)
tree290fc8ef0b0c5c0870278f9aad2cac38db649299 /pjlib-util/src
parentbf839cd77520ad28437e55a73fd8167838023f42 (diff)
Finishing up STUN server side authentication
git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@1003 74dad513-b988-da41-8d7b-12977e46ad98
Diffstat (limited to 'pjlib-util/src')
-rw-r--r--pjlib-util/src/pjlib-util-test/encryption.c135
-rw-r--r--pjlib-util/src/pjlib-util-test/stun.c118
-rw-r--r--pjlib-util/src/pjlib-util-test/test.c4
-rw-r--r--pjlib-util/src/pjlib-util-test/test.h3
-rw-r--r--pjlib-util/src/pjlib-util/crc32.c54
-rw-r--r--pjlib-util/src/pjlib-util/stun_msg.c471
6 files changed, 667 insertions, 118 deletions
diff --git a/pjlib-util/src/pjlib-util-test/encryption.c b/pjlib-util/src/pjlib-util-test/encryption.c
index 34e5ae59..47dc17cd 100644
--- a/pjlib-util/src/pjlib-util-test/encryption.c
+++ b/pjlib-util/src/pjlib-util-test/encryption.c
@@ -422,6 +422,7 @@ static int crc32_test(void)
PJ_LOG(3, (THIS_FILE, " crc32 test.."));
+ /* testing pj_crc32_calc */
for (i=0; i<PJ_ARRAY_SIZE(crc32_test_data); ++i) {
pj_uint32_t crc;
@@ -432,6 +433,34 @@ static int crc32_test(void)
return -80;
}
}
+
+ /* testing incremental CRC32 calculation */
+ for (i=0; i<PJ_ARRAY_SIZE(crc32_test_data); ++i) {
+ pj_crc32_context ctx;
+ pj_uint32_t crc0, crc1;
+ unsigned len;
+
+ len = pj_ansi_strlen(crc32_test_data[i].input);
+ crc0 = pj_crc32_calc((pj_uint8_t*)crc32_test_data[i].input, len);
+
+ pj_crc32_init(&ctx);
+ pj_crc32_update(&ctx, (pj_uint8_t*)crc32_test_data[i].input,
+ len / 2);
+
+ if (len/2 > 0) {
+ pj_crc32_update(&ctx, (pj_uint8_t*)crc32_test_data[i].input + len/2,
+ len - len/2);
+ }
+
+ crc1 = pj_crc32_final(&ctx);
+
+ if (crc0 != crc1) {
+ PJ_LOG(3,(THIS_FILE,
+ " error: crc algorithm error on test %d", i));
+ return -85;
+ }
+
+ }
return 0;
}
@@ -459,6 +488,112 @@ int encryption_test()
return 0;
}
+static void crc32_update(pj_crc32_context *c, const pj_uint8_t *data,
+ pj_size_t nbytes)
+{
+ pj_crc32_update(c, data, nbytes);
+}
+
+static void crc32_final(pj_crc32_context *ctx, pj_uint32_t *digest)
+{
+ *digest = pj_crc32_final(ctx);
+}
+
+int encryption_benchmark()
+{
+ pj_pool_t *pool;
+ pj_uint8_t *input;
+ union {
+ pj_md5_context md5_context;
+ pj_sha1_context sha1_context;
+ } context;
+ pj_uint8_t digest[32];
+ pj_size_t input_len;
+ struct algorithm
+ {
+ const char *name;
+ void (*init_context)(void*);
+ void (*update)(void*, const pj_uint8_t*, unsigned);
+ void (*final)(void*, void*);
+ pj_uint32_t t;
+ } algorithms[] =
+ {
+ {
+ "MD5 ",
+ &pj_md5_init,
+ &pj_md5_update,
+ &pj_md5_final
+ },
+ {
+ "SHA1 ",
+ &pj_sha1_init,
+ &pj_sha1_update,
+ &pj_sha1_final
+ },
+ {
+ "CRC32",
+ &pj_crc32_init,
+ &crc32_update,
+ &crc32_final
+ }
+ };
+#if defined(PJ_DEBUG) && PJ_DEBUG!=0
+ enum { LOOP = 1000 };
+#else
+ enum { LOOP = 10000 };
+#endif
+ unsigned i;
+ double total_len;
+
+ input_len = 2048;
+ total_len = input_len * LOOP;
+ pool = pj_pool_create(mem, "enc", input_len+256, 0, NULL);
+ if (!pool)
+ return PJ_ENOMEM;
+
+ input = pj_pool_alloc(pool, input_len);
+ pj_memset(input, '\xaa', input_len);
+
+ PJ_LOG(3, (THIS_FILE, " feeding %d Mbytes of data",
+ (unsigned)(total_len/1024/1024)));
+
+ /* Dry run */
+ for (i=0; i<PJ_ARRAY_SIZE(algorithms); ++i) {
+ algorithms[i].init_context(&context);
+ algorithms[i].update(&context, input, input_len);
+ algorithms[i].final(&context, digest);
+ }
+
+ /* Run */
+ for (i=0; i<PJ_ARRAY_SIZE(algorithms); ++i) {
+ int j;
+ pj_timestamp t1, t2;
+
+ pj_get_timestamp(&t1);
+ algorithms[i].init_context(&context);
+ for (j=0; j<LOOP; ++j) {
+ algorithms[i].update(&context, input, input_len);
+ }
+ algorithms[i].final(&context, digest);
+ pj_get_timestamp(&t2);
+
+ algorithms[i].t = pj_elapsed_usec(&t1, &t2);
+ }
+
+ /* Results */
+ for (i=0; i<PJ_ARRAY_SIZE(algorithms); ++i) {
+ double bytes;
+
+ bytes = (total_len * 1000000 / algorithms[i].t);
+ PJ_LOG(3, (THIS_FILE, " %s:%8d usec (%3d.%03d Mbytes/sec)",
+ algorithms[i].name, algorithms[i].t,
+ (unsigned)(bytes / 1024 / 1024),
+ ((unsigned)(bytes) % (1024 * 1024)) / 1024));
+ }
+
+ return 0;
+}
+
#endif /* INCLUDE_ENCRYPTION_TEST */
diff --git a/pjlib-util/src/pjlib-util-test/stun.c b/pjlib-util/src/pjlib-util-test/stun.c
new file mode 100644
index 00000000..230e51e7
--- /dev/null
+++ b/pjlib-util/src/pjlib-util-test/stun.c
@@ -0,0 +1,118 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2003-2007 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
+ */
+
+static int decode_test(void)
+{
+ /* Invalid message type */
+
+ /* Short message */
+
+ /* Long, random message */
+
+ /* Message length in header is shorter */
+
+ /* Message length in header is longer */
+
+ /* Invalid magic */
+
+ /* Attribute length is not valid */
+
+ /* Unknown mandatory attribute type should generate error */
+
+ /* Unknown but non-mandatory should be okay */
+
+ /* String/binary attribute length is larger than the message */
+
+ /* Valid message with MESSAGE-INTEGRITY */
+
+ /* Valid message with FINGERPRINT */
+
+ /* Valid message with MESSAGE-INTEGRITY and FINGERPRINT */
+
+ /* Another attribute not FINGERPRINT exists after MESSAGE-INTEGRITY */
+
+ /* Another attribute exists after FINGERPRINT */
+
+ return 0;
+}
+
+static int decode_verify(void)
+{
+ /* Decode all attribute types */
+ return 0;
+}
+
+static int auth_test(void)
+{
+ /* REALM and USERNAME is present, but MESSAGE-INTEGRITY is not present.
+ * For short term, must with reply 401 without REALM.
+ * For long term, must reply with 401 with REALM.
+ */
+
+ /* USERNAME is not present, server must respond with 432 (Missing
+ * Username).
+ */
+
+ /* If long term credential is wanted and REALM is not present, server
+ * must respond with 434 (Missing Realm)
+ */
+
+ /* If REALM doesn't match, server must respond with 434 (Missing Realm)
+ * too, containing REALM and NONCE attribute.
+ */
+
+ /* When long term authentication is wanted and NONCE is NOT present,
+ * server must respond with 435 (Missing Nonce), containing REALM and
+ * NONCE attribute.
+ */
+
+ /* Simulate 438 (Stale Nonce) */
+
+ /* Simulate 436 (Unknown Username) */
+
+ /* When server wants to use short term credential, but request has
+ * REALM, reject with .... ???
+ */
+
+ /* Invalid HMAC */
+
+ /* Valid static short term, without NONCE */
+
+ /* Valid static short term, WITH NONCE */
+
+ /* Valid static long term (with NONCE */
+
+ /* Valid dynamic short term (without NONCE) */
+
+ /* Valid dynamic short term (with NONCE) */
+
+ /* Valid dynamic long term (with NONCE) */
+
+ return 0;
+}
+
+
+int stun_test(void)
+{
+ decode_verify();
+ decode_test();
+ auth_test();
+ return 0;
+}
+
diff --git a/pjlib-util/src/pjlib-util-test/test.c b/pjlib-util/src/pjlib-util-test/test.c
index b6cc7290..599b72e3 100644
--- a/pjlib-util/src/pjlib-util-test/test.c
+++ b/pjlib-util/src/pjlib-util-test/test.c
@@ -68,8 +68,12 @@ static int test_inner(void)
#if INCLUDE_ENCRYPTION_TEST
DO_TEST(encryption_test());
+ DO_TEST(encryption_benchmark());
#endif
+#if INCLUDE_STUN_TEST
+ DO_TEST(stun_test());
+#endif
on_return:
return rc;
diff --git a/pjlib-util/src/pjlib-util-test/test.h b/pjlib-util/src/pjlib-util-test/test.h
index 252b3399..e5e1e24f 100644
--- a/pjlib-util/src/pjlib-util-test/test.h
+++ b/pjlib-util/src/pjlib-util-test/test.h
@@ -20,9 +20,12 @@
#define INCLUDE_XML_TEST 1
#define INCLUDE_ENCRYPTION_TEST 1
+#define INCLUDE_STUN_TEST 1
extern int xml_test(void);
extern int encryption_test();
+extern int encryption_benchmark();
+extern int stun_test();
extern int test_main(void);
extern void app_perror(const char *title, pj_status_t rc);
diff --git a/pjlib-util/src/pjlib-util/crc32.c b/pjlib-util/src/pjlib-util/crc32.c
index fa225ac9..e42ef699 100644
--- a/pjlib-util/src/pjlib-util/crc32.c
+++ b/pjlib-util/src/pjlib-util/crc32.c
@@ -3,13 +3,18 @@
* This is an implementation of CRC32. See ISO 3309 and ITU-T V.42
* for a formal specification
*
- * This file is partly taken from Crypto++ library (http://www.cryptopp.com).
+ * This file is partly taken from Crypto++ library (http://www.cryptopp.com)
+ * and http://www.di-mgt.com.au/crypto.html#CRC.
*
* Since the original version of the code is put in public domain,
* this file is put on public domain as well.
*/
#include <pjlib-util/crc32.h>
+
+#define CRC32_NEGL 0xffffffffL
+
+#if defined(PJ_CRC32_HAS_TABLES) && PJ_CRC32_HAS_TABLES!=0
// crc.cpp - written and placed in the public domain by Wei Dai
/* Table of CRC-32's of all single byte values (made by makecrc.c) */
@@ -138,9 +143,6 @@ static const pj_uint32_t crc_tab[] = {
#endif
-#define CRC32_NEGL 0xffffffffL
-
-
PJ_DEF(void) pj_crc32_init(pj_crc32_context *ctx)
{
ctx->crc_state = 0;
@@ -180,6 +182,50 @@ PJ_DEF(pj_uint32_t) pj_crc32_final(pj_crc32_context *ctx)
return ctx->crc_state;
}
+
+#else
+
+PJ_DEF(void) pj_crc32_init(pj_crc32_context *ctx)
+{
+ ctx->crc_state = CRC32_NEGL;
+}
+
+
+PJ_DEF(pj_uint32_t) pj_crc32_update(pj_crc32_context *ctx,
+ const pj_uint8_t *octets,
+ pj_size_t len)
+
+{
+ pj_uint32_t crc = ctx->crc_state;
+
+ while (len--) {
+ pj_uint32_t temp;
+ int j;
+
+ temp = (pj_uint32_t)((crc & 0xFF) ^ *octets++);
+ for (j = 0; j < 8; j++)
+ {
+ if (temp & 0x1)
+ temp = (temp >> 1) ^ 0xEDB88320;
+ else
+ temp >>= 1;
+ }
+ crc = (crc >> 8) ^ temp;
+ }
+ ctx->crc_state = crc;
+
+ return crc ^ CRC32_NEGL;
+}
+
+PJ_DEF(pj_uint32_t) pj_crc32_final(pj_crc32_context *ctx)
+{
+ ctx->crc_state ^= CRC32_NEGL;
+ return ctx->crc_state;
+}
+
+#endif
+
+
PJ_DEF(pj_uint32_t) pj_crc32_calc( const pj_uint8_t *data,
pj_size_t nbytes)
{
diff --git a/pjlib-util/src/pjlib-util/stun_msg.c b/pjlib-util/src/pjlib-util/stun_msg.c
index 44c390d8..47d70dc0 100644
--- a/pjlib-util/src/pjlib-util/stun_msg.c
+++ b/pjlib-util/src/pjlib-util/stun_msg.c
@@ -21,6 +21,7 @@
#include <pjlib-util/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/os.h>
@@ -1559,6 +1560,8 @@ PJ_DEF(pj_status_t) pj_stun_msg_decode(pj_pool_t *pool,
pj_stun_msg *msg;
unsigned uattr_cnt;
const pj_uint8_t *start_pdu = pdu;
+ pj_bool_t has_msg_int = PJ_FALSE;
+ pj_bool_t has_fingerprint = PJ_FALSE;
pj_status_t status;
PJ_UNUSED_ARG(options);
@@ -1586,7 +1589,8 @@ PJ_DEF(pj_status_t) pj_stun_msg_decode(pj_pool_t *pool,
msg->hdr.magic = pj_ntohl(msg->hdr.magic);
pdu += sizeof(pj_stun_msg_hdr);
- pdu_len -= sizeof(pj_stun_msg_hdr);
+ /* pdu_len -= sizeof(pj_stun_msg_hdr); */
+ pdu_len = msg->hdr.length;
/* No need to create response if this is not a request */
if (!PJ_STUN_IS_REQUEST(msg->hdr.type))
@@ -1687,7 +1691,51 @@ PJ_DEF(pj_status_t) pj_stun_msg_decode(pj_pool_t *pool,
return status;
}
-
+
+ if (attr_type == PJ_STUN_ATTR_MESSAGE_INTEGRITY &&
+ !has_fingerprint)
+ {
+ if (has_msg_int) {
+ /* Already has MESSAGE-INTEGRITY */
+ if (p_response) {
+ pj_str_t e;
+ e = pj_str("MESSAGE-INTEGRITY already present");
+ pj_stun_msg_create_response(pool, msg,
+ PJ_STUN_STATUS_BAD_REQUEST,
+ NULL, p_response);
+ }
+ return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_STATUS_BAD_REQUEST);
+ }
+ has_msg_int = PJ_TRUE;
+
+ } else if (attr_type == PJ_STUN_ATTR_FINGERPRINT) {
+ if (has_fingerprint) {
+ /* Already has FINGERPRINT */
+ if (p_response) {
+ pj_str_t e;
+ e = pj_str("FINGERPRINT already present");
+ pj_stun_msg_create_response(pool, msg,
+ PJ_STUN_STATUS_BAD_REQUEST,
+ NULL, p_response);
+ }
+ return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_STATUS_BAD_REQUEST);
+ }
+ has_fingerprint = PJ_TRUE;
+ } else {
+ if (has_msg_int || has_fingerprint) {
+ /* Another attribute is found which is not FINGERPRINT
+ * after FINGERPRINT or MESSAGE-INTEGRITY */
+ if (p_response) {
+ pj_str_t e;
+ e = pj_str("Invalid attribute order");
+ pj_stun_msg_create_response(pool, msg,
+ PJ_STUN_STATUS_BAD_REQUEST,
+ NULL, p_response);
+ }
+ return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_STATUS_BAD_REQUEST);
+ }
+ }
+
/* Make sure we have rooms for the new attribute */
if (msg->attr_count >= PJ_STUN_MAX_ATTR) {
if (p_response) {
@@ -1717,6 +1765,56 @@ PJ_DEF(pj_status_t) pj_stun_msg_decode(pj_pool_t *pool,
return PJ_SUCCESS;
}
+/* 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);
+}
+
/*
* Print the message structure to a buffer.
@@ -1795,6 +1893,24 @@ PJ_DEF(pj_status_t) pj_stun_msg_encode(const pj_stun_msg *msg,
buf_size -= printed;
}
+ /* We MUST update the message length in the header NOW before
+ * calculating MESSAGE-INTEGRITY and FINGERPRINT.
+ * Note that length is not including the 20 bytes header.
+ */
+ if (amsg_integrity && afingerprint) {
+ length = (pj_uint16_t)((buf - start) - 20 + 24 + 8);
+ } else if (amsg_integrity) {
+ length = (pj_uint16_t)((buf - start) - 20 + 24);
+ } else if (afingerprint) {
+ length = (pj_uint16_t)((buf - start) - 20 + 8);
+ } else {
+ length = (pj_uint16_t)((buf - start) - 20);
+ }
+
+ /* hdr->length = pj_htons(length); */
+ *(buf+2) = (pj_uint8_t)((length >> 8) & 0x00FF);
+ *(buf+3) = (pj_uint8_t)(length & 0x00FF);
+
/* Calculate message integrity, if present */
if (amsg_integrity != NULL) {
@@ -1835,46 +1951,8 @@ PJ_DEF(pj_status_t) pj_stun_msg_encode(const pj_stun_msg *msg,
key = *password;
} else {
- /* 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 = auname->value;
- 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 = arealm->value;
- 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*)password->ptr, password->slen);
-
- /* Done */
- pj_md5_final(&ctx, md5_key_buf);
+ calc_md5_key(md5_key_buf, &arealm->value, &auname->value,
+ password);
key.ptr = (char*) md5_key_buf;
key.slen = 16;
}
@@ -1909,15 +1987,6 @@ PJ_DEF(pj_status_t) pj_stun_msg_encode(const pj_stun_msg *msg,
buf_size -= printed;
}
- /* Update the message length in the header.
- * Note that length is not including the 20 bytes header.
- */
- length = (pj_uint16_t)((buf - start) - 20);
- /* hdr->length = pj_htons(length); */
- *(buf+2) = (pj_uint8_t)((length >> 8) & 0x00FF);
- *(buf+3) = (pj_uint8_t)(length & 0x00FF);
-
-
/* Done */
if (p_msg_len)
*p_msg_len = (buf - start);
@@ -1945,41 +2014,108 @@ PJ_DEF(pj_stun_attr_hdr*) pj_stun_msg_find_attr( const pj_stun_msg *msg,
}
+/**************************************************************************/
+/*
+ * Authentication
+ */
+
+
+/* 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_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_generic_string_attr(pool, response,
+ PJ_STUN_ATTR_REALM,
+ realm);
+ if (rc != PJ_SUCCESS)
+ return rc;
+ }
+
+ if (nonce && nonce->slen) {
+ rc = pj_stun_msg_add_generic_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_stun_msg *msg,
- const pj_str_t *realm,
- const pj_str_t *username,
- const pj_str_t *password,
- unsigned options,
+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_policy *pol,
pj_pool_t *pool,
pj_stun_msg **p_response)
{
+ pj_str_t realm, nonce, password;
const pj_stun_msg_integrity_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;
- PJ_ASSERT_RETURN(msg && password, PJ_EINVAL);
- PJ_ASSERT_RETURN(options==0, PJ_EINVAL);
- PJ_UNUSED_ARG(options);
+ /* msg and policy MUST be specified */
+ PJ_ASSERT_RETURN(pkt && pkt_len && msg && pol, 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 (pol->type == PJ_STUN_POLICY_STATIC_SHORT_TERM) {
+ realm.slen = 0;
+ nonce = pol->data.static_short_term.nonce;
+ } else if (pol->type == PJ_STUN_POLICY_STATIC_LONG_TERM) {
+ realm = pol->data.static_long_term.realm;
+ nonce = pol->data.static_long_term.nonce;
+ } else if (pol->type == PJ_STUN_POLICY_DYNAMIC) {
+ status = pol->data.dynamic.get_auth(pol->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_msg_integrity_attr*)
pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_MESSAGE_INTEGRITY, 0);
if (amsgi == NULL) {
- if (pool && p_response) {
- pj_status_t rc;
-
- rc = pj_stun_msg_create_response(pool, msg,
- PJ_STUN_STATUS_UNAUTHORIZED,
- NULL, p_response);
- if (rc==PJ_SUCCESS && realm) {
- pj_stun_msg_add_generic_string_attr(pool, *p_response,
- PJ_STUN_ATTR_REALM,
- realm);
- }
+ if (p_response) {
+ create_challenge(pool, msg, PJ_STUN_STATUS_UNAUTHORIZED, NULL,
+ &realm, &nonce, p_response);
}
return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_STATUS_UNAUTHORIZED);
}
@@ -1988,71 +2124,178 @@ PJ_DEF(pj_status_t) pj_stun_verify_credential( const pj_stun_msg *msg,
auser = (const pj_stun_username_attr*)
pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_USERNAME, 0);
if (auser == NULL) {
- if (pool && p_response) {
- pj_status_t rc;
-
- rc = pj_stun_msg_create_response(pool, msg,
- PJ_STUN_STATUS_MISSING_USERNAME,
- NULL, p_response);
- if (rc==PJ_SUCCESS && realm) {
- pj_stun_msg_add_generic_string_attr(pool, *p_response,
- PJ_STUN_ATTR_REALM,
- realm);
- }
+ if (p_response) {
+ create_challenge(pool, msg, PJ_STUN_STATUS_MISSING_USERNAME, NULL,
+ &realm, &nonce, p_response);
}
return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_STATUS_MISSING_USERNAME);
}
+ /* 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 (username && pj_stricmp(&auser->value, username) != 0) {
+ if (pol->type == PJ_STUN_POLICY_STATIC_SHORT_TERM) {
+ username_ok = !pj_strcmp(&auser->value,
+ &pol->data.static_short_term.username);
+ password = pol->data.static_short_term.password;
+ } else if (pol->type == PJ_STUN_POLICY_STATIC_LONG_TERM) {
+ username_ok = !pj_strcmp(&auser->value,
+ &pol->data.static_long_term.username);
+ password = pol->data.static_long_term.password;
+ } else if (pol->type == PJ_STUN_POLICY_DYNAMIC) {
+ pj_status_t rc;
+ rc = pol->data.dynamic.get_password(pol->user_data,
+ (arealm?&arealm->value:NULL),
+ &auser->value, pool,
+ &password);
+ username_ok = (rc == PJ_SUCCESS);
+ } else {
+ username_ok = PJ_TRUE;
+ password.slen = 0;
+ }
+
+ if (!username_ok) {
/* Username mismatch */
- if (pool && p_response) {
- pj_status_t rc;
-
- rc = pj_stun_msg_create_response(pool, msg,
- PJ_STUN_STATUS_WRONG_USERNAME,
- NULL, p_response);
- if (rc==PJ_SUCCESS && realm) {
- pj_stun_msg_add_generic_string_attr(pool, *p_response,
- PJ_STUN_ATTR_REALM,
- realm);
- }
+ if (p_response) {
+ create_challenge(pool, msg, PJ_STUN_STATUS_UNKNOWN_USERNAME, NULL,
+ &realm, &nonce, p_response);
}
- return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_STATUS_WRONG_USERNAME);
+ return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_STATUS_UNKNOWN_USERNAME);
}
- /* Next check that REALM is present */
- arealm = (const pj_stun_realm_attr*)
- pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_REALM, 0);
- if (realm != NULL && arealm == NULL) {
- /* Long term credential is required */
- if (pool && p_response) {
- pj_status_t rc;
-
- rc = pj_stun_msg_create_response(pool, msg,
- PJ_STUN_STATUS_MISSING_REALM,
- NULL, p_response);
- if (rc==PJ_SUCCESS) {
- pj_stun_msg_add_generic_string_attr(pool, *p_response,
- PJ_STUN_ATTR_REALM,
- realm);
- }
+
+ /* 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_STATUS_MISSING_REALM, NULL,
+ &realm, &nonce, p_response);
}
return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_STATUS_MISSING_REALM);
- } else if (realm != NULL && arealm != NULL) {
+ } 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_STATUS_MISSING_NONCE,
+ NULL, &realm, &nonce, p_response);
+ }
+ return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_STATUS_MISSING_NONCE);
+ }
- } else if (realm == NULL && arealm != NULL) {
+ /* Verify REALM matches */
+ if (pj_stricmp(&arealm->value, &realm)) {
+ /* REALM doesn't match */
+ if (p_response) {
+ create_challenge(pool, msg, PJ_STUN_STATUS_MISSING_REALM,
+ NULL, &realm, &nonce, p_response);
+ }
+ return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_STATUS_MISSING_REALM);
+ }
+
+ /* 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.
*/
- PJ_TODO(SWITCHING_BETWEEN_SHORT_TERM_AND_LONG_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_STATUS_MISSING_NONCE,
+ NULL, &realm, &nonce, p_response);
+ }
+ return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_STATUS_MISSING_NONCE);
+ }
}
- PJ_TODO(CONTINUE_IMPLEMENTATION);
+ /* If NONCE is present, validate it */
+ if (anonce) {
+ pj_bool_t ok;
+
+ if (pol->type == PJ_STUN_POLICY_DYNAMIC) {
+ ok = pol->data.dynamic.verify_nonce(pol->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_STATUS_STALE_NONCE,
+ NULL, &realm, &nonce, p_response);
+ }
+ return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_STATUS_STALE_NONCE);
+ }
+ }
+
+ /* Get the position of MESSAGE-INTEGRITY in the packet */
+ amsgi_pos = 20+msg->hdr.length-22;
+ 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-22;
+ 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) {
+ 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_STATUS_INTEGRITY_CHECK_FAILURE,
+ NULL, &realm, &nonce, p_response);
+ }
+ return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_STATUS_INTEGRITY_CHECK_FAILURE);
+ }
+ /* Everything looks okay! */
return PJ_SUCCESS;
}