summaryrefslogtreecommitdiff
path: root/pjnath
diff options
context:
space:
mode:
authorBenny Prijono <bennylp@teluu.com>2007-09-18 19:33:33 +0000
committerBenny Prijono <bennylp@teluu.com>2007-09-18 19:33:33 +0000
commit41234fb3ea6190fdf7bebf0d11bc41d578b66934 (patch)
tree845b6ba62a1477e77501189b0221b638cae67af9 /pjnath
parent0b5b5d6d0b9e0c0362c15126bd74176aa1ce5161 (diff)
Ticket #374: Update STUN specification from rfc3489bis-06 to rfc3489bis-10
git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@1439 74dad513-b988-da41-8d7b-12977e46ad98
Diffstat (limited to 'pjnath')
-rw-r--r--pjnath/include/pjnath/config.h15
-rw-r--r--pjnath/include/pjnath/stun_msg.h30
-rw-r--r--pjnath/include/pjnath/types.h4
-rw-r--r--pjnath/src/pjnath-test/stun.c698
-rw-r--r--pjnath/src/pjnath-test/test.c4
-rw-r--r--pjnath/src/pjnath-test/test.h2
-rw-r--r--pjnath/src/pjnath/ice_session.c6
-rw-r--r--pjnath/src/pjnath/stun_auth.c228
-rw-r--r--pjnath/src/pjnath/stun_msg.c83
-rw-r--r--pjnath/src/pjnath/stun_msg_dump.c16
10 files changed, 924 insertions, 162 deletions
diff --git a/pjnath/include/pjnath/config.h b/pjnath/include/pjnath/config.h
index 3b24b944..25eb5b73 100644
--- a/pjnath/include/pjnath/config.h
+++ b/pjnath/include/pjnath/config.h
@@ -49,7 +49,7 @@
/* **************************************************************************
- * STUN CLIENT CONFIGURATION
+ * STUN CONFIGURATION
*/
/**
@@ -80,10 +80,10 @@
* After the last retransmission is sent and if no response is received
* after this time, the STUN transaction will be considered to have failed.
*
- * The default value is 1600 miliseconds (as per RFC 3489-bis).
+ * The default value is 16x RTO (as per RFC 3489-bis).
*/
#ifndef PJ_STUN_TIMEOUT_VALUE
-# define PJ_STUN_TIMEOUT_VALUE 1600
+# define PJ_STUN_TIMEOUT_VALUE (16 * PJ_STUN_RTO_VALUE)
#endif
@@ -121,6 +121,15 @@
#define PJ_STUN_PORT 3478
+/**
+ * Padding character for string attributes.
+ *
+ * Default: ASCII 0
+ */
+#ifndef PJ_STUN_STRING_ATTR_PAD_CHR
+# define PJ_STUN_STRING_ATTR_PAD_CHR 0
+#endif
+
/* **************************************************************************
* ICE CONFIGURATION
diff --git a/pjnath/include/pjnath/stun_msg.h b/pjnath/include/pjnath/stun_msg.h
index f977e8eb..9fc16f1c 100644
--- a/pjnath/include/pjnath/stun_msg.h
+++ b/pjnath/include/pjnath/stun_msg.h
@@ -280,7 +280,7 @@ typedef enum pj_stun_attr_type
PJ_STUN_ATTR_SOURCE_ADDR = 0x0004,/**< SOURCE-ADDRESS (deprecated)*/
PJ_STUN_ATTR_CHANGED_ADDR = 0x0005,/**< CHANGED-ADDRESS (deprecatd)*/
PJ_STUN_ATTR_USERNAME = 0x0006,/**< USERNAME attribute. */
- PJ_STUN_ATTR_PASSWORD = 0x0007,/**< PASSWORD attribute. */
+ PJ_STUN_ATTR_PASSWORD = 0x0007,/**< was PASSWORD attribute. */
PJ_STUN_ATTR_MESSAGE_INTEGRITY = 0x0008,/**< MESSAGE-INTEGRITY. */
PJ_STUN_ATTR_ERROR_CODE = 0x0009,/**< ERROR-CODE. */
PJ_STUN_ATTR_UNKNOWN_ATTRIBUTES = 0x000A,/**< UNKNOWN-ATTRIBUTES. */
@@ -329,14 +329,17 @@ typedef enum pj_stun_status
PJ_STUN_SC_BAD_REQUEST = 400, /**< Bad Request */
PJ_STUN_SC_UNAUTHORIZED = 401, /**< Unauthorized */
PJ_STUN_SC_UNKNOWN_ATTRIBUTE = 420, /**< Unknown Attribute */
- PJ_STUN_SC_STALE_CREDENTIALS = 430, /**< Stale Credentials */
- PJ_STUN_SC_INTEGRITY_CHECK_FAILURE = 431, /**< Integrity Chk Fail */
- PJ_STUN_SC_MISSING_USERNAME = 432, /**< Missing Username */
- PJ_STUN_SC_USE_TLS = 433, /**< Use TLS */
- PJ_STUN_SC_MISSING_REALM = 434, /**< Missing Realm */
- PJ_STUN_SC_MISSING_NONCE = 435, /**< Missing Nonce */
- PJ_STUN_SC_UNKNOWN_USERNAME = 436, /**< Unknown Username */
- PJ_STUN_SC_NO_BINDING = 437, /**< No Binding. */
+#if 0
+ /* These were obsolete in recent rfc3489bis */
+ //PJ_STUN_SC_STALE_CREDENTIALS = 430, /**< Stale Credentials */
+ //PJ_STUN_SC_INTEGRITY_CHECK_FAILURE= 431, /**< Integrity Chk Fail */
+ //PJ_STUN_SC_MISSING_USERNAME = 432, /**< Missing Username */
+ //PJ_STUN_SC_USE_TLS = 433, /**< Use TLS */
+ //PJ_STUN_SC_MISSING_REALM = 434, /**< Missing Realm */
+ //PJ_STUN_SC_MISSING_NONCE = 435, /**< Missing Nonce */
+ //PJ_STUN_SC_UNKNOWN_USERNAME = 436, /**< Unknown Username */
+ //PJ_STUN_SC_NO_BINDING = 437, /**< No Binding. */
+#endif
PJ_STUN_SC_STALE_NONCE = 438, /**< Stale Nonce */
PJ_STUN_SC_TRANSITIONING = 439, /**< Transitioning. */
PJ_STUN_SC_UNSUPP_TRANSPORT_PROTO = 442, /**< Unsupported Transport or
@@ -1091,6 +1094,15 @@ PJ_DECL(pj_str_t) pj_stun_get_err_reason(int err_code);
/**
+ * Internal: set the padding character for string attribute.
+ * The default padding character is PJ_STUN_STRING_ATTR_PAD_CHR.
+ *
+ * @return The previous padding character.
+ */
+PJ_DECL(int) pj_stun_set_padding_char(int chr);
+
+
+/**
* Create a generic STUN message.
*
* @param pool Pool to create the STUN message.
diff --git a/pjnath/include/pjnath/types.h b/pjnath/include/pjnath/types.h
index 10544d22..0714c4c3 100644
--- a/pjnath/include/pjnath/types.h
+++ b/pjnath/include/pjnath/types.h
@@ -142,8 +142,8 @@ PJ_END_DECL
*
* References for STUN:
*
- * - <A HREF="http://www.ietf.org/internet-drafts/draft-ietf-behave-rfc3489bis-06.txt">
- * <B>draft-ietf-behave-rfc3489bis-06</b></A>: Session Traversal
+ * - <A HREF="http://www.ietf.org/internet-drafts/draft-ietf-behave-rfc3489bis-10.txt">
+ * <B>draft-ietf-behave-rfc3489bis-10</b></A>: Session Traversal
* Utilities for (NAT) (STUN),
* - <A HREF="http://www.ietf.org/internet-drafts/draft-ietf-behave-turn-03.txt">
* <B>draft-ietf-behave-turn-03</B></A>: Obtaining Relay Addresses
diff --git a/pjnath/src/pjnath-test/stun.c b/pjnath/src/pjnath-test/stun.c
index 230e51e7..29a8eb1a 100644
--- a/pjnath/src/pjnath-test/stun.c
+++ b/pjnath/src/pjnath-test/stun.c
@@ -16,42 +16,392 @@
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
+#include "test.h"
-static int decode_test(void)
-{
- /* Invalid message type */
-
- /* Short message */
-
- /* Long, random message */
+#define THIS_FILE "stun.c"
- /* Message length in header is shorter */
+static pj_stun_msg* create1(pj_pool_t*);
+static int verify1(pj_stun_msg*);
+static int verify2(pj_stun_msg*);
+static int verify5(pj_stun_msg*);
- /* Message length in header is longer */
-
- /* Invalid magic */
-
- /* Attribute length is not valid */
+static struct test
+{
+ const char *title;
+ char *pdu;
+ unsigned pdu_len;
+ pj_stun_msg* (*create)(pj_pool_t*);
+ pj_status_t expected_status;
+ int (*verify)(pj_stun_msg*);
+} tests[] =
+{
+ {
+ "Invalid message type",
+ "\x11\x01\x00\x00\x21\x12\xa4\x42"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
+ 20,
+ NULL,
+ PJNATH_EINSTUNMSGTYPE,
+ NULL
+ },
+ {
+ "Short message (1) (partial header)",
+ "\x00\x01",
+ 2,
+ NULL,
+ PJNATH_EINSTUNMSGLEN,
+ NULL
+ },
+ {
+ "Short message (2) (partial header)",
+ "\x00\x01\x00\x00\x21\x12\xa4\x42"
+ "\x00\x00\x00\x00\x00\x00\x00\x00",
+ 16,
+ NULL,
+ PJNATH_EINSTUNMSGLEN,
+ NULL
+ },
+ {
+ "Short message (3), (missing attribute)",
+ "\x00\x01\x00\x08\x21\x12\xa4\x42"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
+ 20,
+ NULL,
+ PJNATH_EINSTUNMSGLEN,
+ NULL
+ },
+ {
+ "Short message (4), (partial attribute header)",
+ "\x00\x01\x00\x08\x21\x12\xa4\x42"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x80\x28",
+ 22,
+ NULL,
+ PJNATH_EINSTUNMSGLEN,
+ NULL
+ },
+ {
+ "Short message (5), (partial attribute header)",
+ "\x00\x01\x00\x08\x21\x12\xa4\x42"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x80\x28\x00",
+ 23,
+ NULL,
+ PJNATH_EINSTUNMSGLEN,
+ NULL
+ },
+ {
+ "Short message (6), (partial attribute header)",
+ "\x00\x01\x00\x08\x21\x12\xa4\x42"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x80\x28\x00\x04",
+ 24,
+ NULL,
+ PJNATH_EINSTUNMSGLEN,
+ NULL
+ },
+ {
+ "Short message (7), (partial attribute body)",
+ "\x00\x01\x00\x08\x21\x12\xa4\x42"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x80\x28\x00\x04\x00\x00\x00",
+ 27,
+ NULL,
+ PJNATH_EINSTUNMSGLEN,
+ NULL
+ },
+ {
+ "Message length in header is too long",
+ "\x00\x01\xff\xff\x21\x12\xa4\x42"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x80\x28\x00\x04\x00\x00\x00",
+ 27,
+ NULL,
+ PJNATH_EINSTUNMSGLEN,
+ NULL
+ },
+ {
+ "Message length in header is shorter",
+ "\x00\x01\x00\x04\x21\x12\xa4\x42"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x80\x28\x00\x04\x00\x00\x00\x00",
+ 28,
+ NULL,
+ PJNATH_EINSTUNMSGLEN,
+ NULL
+ },
+ {
+ "Invalid magic",
+ "\x00\x01\x00\x08\x00\x12\xa4\x42"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x80\x28\x00\x04\x00\x00\x00\x00",
+ 28,
+ NULL,
+ PJ_SUCCESS,
+ NULL
+ },
+ {
+ "Character beyond message",
+ "\x00\x01\x00\x08\x21\x12\xa4\x42"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x80\x28\x00\x04\x00\x00\x00\x00\x0a",
+ 29,
+ NULL,
+ PJNATH_EINSTUNMSGLEN,
+ NULL
+ },
+ {
+ "Respond unknown mandatory attribute with 420 and "
+ "UNKNOWN-ATTRIBUTES attribute",
+ NULL,
+ 0,
+ &create1,
+ 0,
+ &verify1
+ },
+ {
+ "Unknown but non-mandatory should be okay",
+ "\x00\x01\x00\x08\x21\x12\xa4\x42"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x80\xff\x00\x04\x00\x00\x00\x00",
+ 28,
+ NULL,
+ PJ_SUCCESS,
+ &verify2
+ },
+ {
+ "String attr length larger than message",
+ "\x00\x01\x00\x08\x00\x12\xa4\x42"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x00\x06\x00\xff\x00\x00\x00\x00",
+ 28,
+ NULL,
+ PJNATH_ESTUNINATTRLEN,
+ NULL
+ },
+ {
+ "Attribute other than FINGERPRINT after MESSAGE-INTEGRITY is allowed",
+ "\x00\x01\x00\x20\x21\x12\xa4\x42"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x00\x08\x00\x14\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" // M-I
+ "\x80\x24\x00\x04\x00\x00\x00\x00", // REFRESH-INTERVAL
+ 52,
+ NULL,
+ PJ_SUCCESS,
+ NULL
+ },
+ {
+ "Attribute between MESSAGE-INTEGRITY and FINGERPRINT is allowed",
+ "\x00\x01\x00\x28\x21\x12\xa4\x42"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x00\x08\x00\x14\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" // M-I
+ "\x80\x24\x00\x04\x00\x00\x00\x00" // REFRESH-INTERVAL
+ "\x80\x28\x00\x04\xc7\xde\xdd\x65", // FINGERPRINT
+ 60,
+ NULL,
+ PJ_SUCCESS,
+ &verify5
+ },
+ {
+ "Attribute past FINGERPRINT is not allowed",
+ "\x00\x01\x00\x10\x21\x12\xa4\x42"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x80\x28\x00\x04\x00\x00\x00\x00"
+ "\x80\x24\x00\x04\x00\x00\x00\x00",
+ 36,
+ NULL,
+ PJNATH_ESTUNFINGERPOS,
+ NULL
+ }
+};
+
+static const char *err(pj_status_t status)
+{
+ static char errmsg[PJ_ERR_MSG_SIZE];
+ pj_strerror(status, errmsg, sizeof(errmsg));
+ return errmsg;
+}
- /* Unknown mandatory attribute type should generate error */
+static const pj_str_t USERNAME = {"user", 4};
+static const pj_str_t PASSWORD = {"password", 8};
- /* Unknown but non-mandatory should be okay */
+static int decode_test(void)
+{
+ unsigned i;
+ pj_pool_t *pool;
+ int rc = 0;
+
+ pool = pj_pool_create(mem, "decode_test", 1024, 1024, NULL);
+
+ PJ_LOG(3,(THIS_FILE, " STUN decode test"));
+
+ for (i=0; i<PJ_ARRAY_SIZE(tests); ++i) {
+ struct test *t = &tests[i];
+ pj_stun_msg *msg, *msg2;
+ pj_uint8_t buf[1500];
+ pj_str_t key;
+ unsigned len;
+ pj_status_t status;
+
+ PJ_LOG(3,(THIS_FILE, " %s", t->title));
+
+ if (t->pdu) {
+ status = pj_stun_msg_decode(pool, (pj_uint8_t*)t->pdu, t->pdu_len,
+ PJ_STUN_IS_DATAGRAM | PJ_STUN_CHECK_PACKET,
+ &msg, NULL, NULL);
+
+ /* Check expected decode result */
+ if (t->expected_status != status) {
+ PJ_LOG(1,(THIS_FILE, " expecting status %d, got %d",
+ t->expected_status, status));
+ rc = -10;
+ goto on_return;
+ }
+
+ } else {
+ msg = t->create(pool);
+ status = PJ_SUCCESS;
+ }
+
+ if (status != PJ_SUCCESS)
+ continue;
+
+ /* Try to encode message */
+ pj_stun_create_key(pool, &key, NULL, &USERNAME, &PASSWORD);
+ status = pj_stun_msg_encode(msg, buf, sizeof(buf), 0, &key, &len);
+ if (status != PJ_SUCCESS) {
+ PJ_LOG(1,(THIS_FILE, " encode error: %s", err(status)));
+ rc = -40;
+ goto on_return;
+ }
+
+ /* Try to decode it once more */
+ status = pj_stun_msg_decode(pool, buf, len,
+ PJ_STUN_IS_DATAGRAM | PJ_STUN_CHECK_PACKET,
+ &msg2, NULL, NULL);
+ if (status != PJ_SUCCESS) {
+ PJ_LOG(1,(THIS_FILE, " subsequent decoding failed: %s", err(status)));
+ rc = -50;
+ goto on_return;
+ }
+
+ /* Verify */
+ if (t->verify) {
+ rc = t->verify(msg);
+ if (rc != 0) {
+ goto on_return;
+ }
+ }
+ }
+
+on_return:
+ pj_pool_release(pool);
+ if (rc == 0)
+ PJ_LOG(3,(THIS_FILE, "...success!"));
+ return rc;
+}
- /* String/binary attribute length is larger than the message */
+/* Create 420 response */
+static pj_stun_msg* create1(pj_pool_t *pool)
+{
+ char *pdu = "\x00\x01\x00\x08\x21\x12\xa4\x42"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x00\xff\x00\x04\x00\x00\x00\x00";
+ unsigned pdu_len = 28;
+ pj_stun_msg *msg, *res;
+ pj_status_t status;
+
+ status = pj_stun_msg_decode(pool, (pj_uint8_t*)pdu, pdu_len,
+ PJ_STUN_IS_DATAGRAM | PJ_STUN_CHECK_PACKET,
+ &msg, NULL, &res);
+ pj_assert(status != PJ_SUCCESS);
+ pj_assert(res != NULL);
+
+ return res;
+}
- /* Valid message with MESSAGE-INTEGRITY */
+/* Error response MUST have ERROR-CODE attribute */
+/* 420 response MUST contain UNKNOWN-ATTRIBUTES */
+static int verify1(pj_stun_msg *msg)
+{
+ pj_stun_errcode_attr *aerr;
+ pj_stun_unknown_attr *aunk;
+
+ if (!PJ_STUN_IS_ERROR_RESPONSE(msg->hdr.type)) {
+ PJ_LOG(1,(THIS_FILE, " expecting error message"));
+ return -100;
+ }
+
+ aerr = (pj_stun_errcode_attr*)
+ pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_ERROR_CODE, 0);
+ if (aerr == NULL) {
+ PJ_LOG(1,(THIS_FILE, " missing ERROR-CODE attribute"));
+ return -110;
+ }
+
+ if (aerr->err_code != 420) {
+ PJ_LOG(1,(THIS_FILE, " expecting 420 error"));
+ return -120;
+ }
+
+ aunk = (pj_stun_unknown_attr*)
+ pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_UNKNOWN_ATTRIBUTES, 0);
+ if (aunk == NULL) {
+ PJ_LOG(1,(THIS_FILE, " missing UNKNOWN-ATTRIBUTE attribute"));
+ return -130;
+ }
+
+ if (aunk->attr_count != 1) {
+ PJ_LOG(1,(THIS_FILE, " expecting one unknown attribute"));
+ return -140;
+ }
+
+ if (aunk->attrs[0] != 0xff) {
+ PJ_LOG(1,(THIS_FILE, " expecting 0xff as unknown attribute"));
+ return -150;
+ }
- /* Valid message with FINGERPRINT */
+ return 0;
+}
- /* Valid message with MESSAGE-INTEGRITY and FINGERPRINT */
+/* Attribute count should be zero since unknown attribute is not parsed */
+static int verify2(pj_stun_msg *msg)
+{
+ if (msg->attr_count != 0) {
+ PJ_LOG(1,(THIS_FILE, " expecting zero attribute count"));
+ return -200;
+ }
+ return 0;
+}
- /* Another attribute not FINGERPRINT exists after MESSAGE-INTEGRITY */
- /* Another attribute exists after FINGERPRINT */
+/* Attribute between MESSAGE-INTEGRITY and FINGERPRINT is allowed */
+static int verify5(pj_stun_msg *msg)
+{
+ if (msg->attr_count != 3) {
+ PJ_LOG(1,(THIS_FILE, " expecting 3 attribute count"));
+ return -500;
+ }
+
+ if (msg->attr[0]->type != PJ_STUN_ATTR_MESSAGE_INTEGRITY) {
+ PJ_LOG(1,(THIS_FILE, " expecting MESSAGE-INTEGRITY"));
+ return -510;
+ }
+ if (msg->attr[1]->type != PJ_STUN_ATTR_REFRESH_INTERVAL) {
+ PJ_LOG(1,(THIS_FILE, " expecting REFRESH-INTERVAL"));
+ return -520;
+ }
+ if (msg->attr[2]->type != PJ_STUN_ATTR_FINGERPRINT) {
+ PJ_LOG(1,(THIS_FILE, " expecting FINGERPRINT"));
+ return -530;
+ }
return 0;
}
+
static int decode_verify(void)
{
/* Decode all attribute types */
@@ -107,12 +457,310 @@ static int auth_test(void)
return 0;
}
+typedef struct test_vector test_vector;
+
+static pj_stun_msg* create_msgint1(pj_pool_t *pool, test_vector *v);
+static pj_stun_msg* create_msgint2(pj_pool_t *pool, test_vector *v);
+
+enum
+{
+ USE_MESSAGE_INTEGRITY = 1,
+ USE_FINGERPRINT = 2
+};
+
+struct test_vector
+{
+ unsigned msg_type;
+ char *tsx_id;
+ char *pdu;
+ unsigned pdu_len;
+ unsigned options;
+ char *username;
+ char *password;
+ pj_stun_msg* (*create)(pj_pool_t*, test_vector*);
+} test_vectors[] =
+{
+ {
+ PJ_STUN_BINDING_REQUEST,
+ "\xb7\xe7\xa7\x01\xbc\x34\xd6\x86\xfa\x87\xdf\xae",
+ "\x00\x01\x00\x44\x21\x12\xa4\x42\xb7\xe7"
+ "\xa7\x01\xbc\x34\xd6\x86\xfa\x87\xdf\xae"
+ "\x00\x24\x00\x04\x6e\x00\x01\xff\x80\x29"
+ "\x00\x08\x93\x2f\xf9\xb1\x51\x26\x3b\x36"
+ "\x00\x06\x00\x09\x65\x76\x74\x6a\x3a\x68"
+ "\x36\x76\x59\x20\x20\x20\x00\x08\x00\x14"
+ "\x62\x4e\xeb\xdc\x3c\xc9\x2d\xd8\x4b\x74"
+ "\xbf\x85\xd1\xc0\xf5\xde\x36\x87\xbd\x33"
+ "\x80\x28\x00\x04\xad\x8a\x85\xff",
+ 88,
+ USE_MESSAGE_INTEGRITY | USE_FINGERPRINT,
+ "evtj:h6vY",
+ "VOkJxbRl1RmTxUk/WvJxBt",
+ &create_msgint1
+ },
+ {
+ PJ_STUN_BINDING_RESPONSE,
+ "\xb7\xe7\xa7\x01\xbc\x34\xd6\x86\xfa\x87\xdf\xae",
+ "\x01\x01\x00\x3c\x21\x12\xa4\x42\xb7\xe7"
+ "\xa7\x01\xbc\x34\xd6\x86\xfa\x87\xdf\xae"
+ "\x80\x22\x00\x0b\x74\x65\x73\x74\x20\x76"
+ "\x65\x63\x74\x6f\x72\x20\x00\x20\x00\x08"
+ "\x00\x01\xa1\x47\x5e\x12\xa4\x43\x00\x08"
+ "\x00\x14\xab\x4e\x53\x29\x61\x00\x08\x4c"
+ "\x89\xf2\x7c\x69\x30\x33\x5c\xa3\x58\x14"
+ "\xea\x90\x80\x28\x00\x04\xae\x25\x8d\xf2",
+ 80,
+ USE_MESSAGE_INTEGRITY | USE_FINGERPRINT,
+ "evtj:h6vY",
+ "VOkJxbRl1RmTxUk/WvJxBt",
+ &create_msgint2
+ }
+};
+
+
+static char* print_binary(const pj_uint8_t *data, unsigned data_len)
+{
+ static char buf[1500];
+ unsigned length = sizeof(buf);
+ char *p = buf;
+ unsigned i;
+
+ for (i=0; i<data_len;) {
+ unsigned j;
+
+ pj_ansi_snprintf(p, 1500-(p-buf),
+ "%04d-%04d ",
+ i, (i+20 < data_len) ? i+20 : data_len);
+ p += 12;
+
+ for (j=0; j<20 && i<data_len && p<(buf+length-10); ++j, ++i) {
+ pj_ansi_sprintf(p, "%02x ", (*data) & 0xFF);
+ p += 3;
+ data++;
+ }
+
+ pj_ansi_sprintf(p, "\n");
+ p++;
+ }
+
+ return buf;
+}
+
+static int cmp_buf(const pj_uint8_t *s1, const pj_uint8_t *s2, unsigned len)
+{
+ unsigned i;
+ for (i=0; i<len; ++i) {
+ if (s1[i] != s2[i])
+ return i;
+ }
+
+ return -1;
+}
+
+static int fingerprint_test_vector()
+{
+ pj_pool_t *pool;
+ pj_status_t status;
+ unsigned i;
+ int rc = 0;
+
+ PJ_LOG(3,(THIS_FILE, " STUN message test vectors"));
+
+ pool = pj_pool_create(mem, "fingerprint", 1024, 1024, NULL);
+
+ for (i=0; i<PJ_ARRAY_SIZE(test_vectors); ++i) {
+ struct test_vector *v;
+ pj_stun_msg *ref_msg, *msg;
+ unsigned parsed_len;
+ unsigned len, pos;
+ pj_uint8_t buf[1500];
+ char print[1500];
+ pj_str_t key;
+
+ PJ_LOG(3,(THIS_FILE, " Running test %d/%d", i,
+ PJ_ARRAY_SIZE(test_vectors)));
+
+ v = &test_vectors[i];
+
+ /* Print reference message */
+ PJ_LOG(4,(THIS_FILE, "Reference message PDU:\n%s",
+ print_binary((pj_uint8_t*)v->pdu, v->pdu_len)));
+
+ /* Try to parse the reference message first */
+ status = pj_stun_msg_decode(pool, (pj_uint8_t*)v->pdu, v->pdu_len,
+ PJ_STUN_IS_DATAGRAM | PJ_STUN_CHECK_PACKET,
+ &ref_msg, &parsed_len, NULL);
+ if (status != PJ_SUCCESS) {
+ PJ_LOG(1,(THIS_FILE, " Error decoding reference message"));
+ rc = -1010;
+ goto on_return;
+ }
+
+ if (parsed_len != v->pdu_len) {
+ PJ_LOG(1,(THIS_FILE, " Parsed len error"));
+ rc = -1020;
+ goto on_return;
+ }
+
+ /* Print the reference message */
+ pj_stun_msg_dump(ref_msg, print, sizeof(print), NULL);
+ PJ_LOG(4,(THIS_FILE, "Reference message:\n%s", print));
+
+ /* Create our message */
+ msg = v->create(pool, v);
+
+ /* Encode message */
+ if (v->options & USE_MESSAGE_INTEGRITY) {
+ pj_str_t s1, s2;
+
+ pj_stun_create_key(pool, &key, NULL, pj_cstr(&s1, v->username),
+ pj_cstr(&s2, v->password));
+ pj_stun_msg_encode(msg, buf, sizeof(buf), 0, &key, &len);
+
+ } else {
+ pj_stun_msg_encode(msg, buf, sizeof(buf), 0, NULL, &len);
+ }
+
+ /* Print our raw message */
+ PJ_LOG(4,(THIS_FILE, "Message PDU:\n%s",
+ print_binary((pj_uint8_t*)buf, len)));
+
+ /* Print our message */
+ pj_stun_msg_dump(msg, print, sizeof(print), NULL);
+ PJ_LOG(4,(THIS_FILE, "Message is:\n%s", print));
+
+ /* Compare message length */
+ if (len != v->pdu_len) {
+ PJ_LOG(1,(THIS_FILE, " Message length mismatch"));
+ rc = -1050;
+ goto on_return;
+ }
+
+ pos = cmp_buf(buf, (const pj_uint8_t*)v->pdu, len);
+ if (pos != -1) {
+ PJ_LOG(1,(THIS_FILE, " Message mismatch at byte %d", pos));
+ rc = -1060;
+ goto on_return;
+ }
+
+ /* Authenticate the request/response */
+ if (v->options & USE_MESSAGE_INTEGRITY) {
+ if (PJ_STUN_IS_REQUEST(msg->hdr.type)) {
+ pj_stun_auth_cred cred;
+ pj_status_t status;
+
+ pj_bzero(&cred, sizeof(cred));
+ cred.type = PJ_STUN_AUTH_CRED_STATIC;
+ cred.data.static_cred.username = pj_str(v->username);
+ cred.data.static_cred.data = pj_str(v->password);
+
+ status = pj_stun_authenticate_request(buf, len, msg,
+ &cred, pool, NULL);
+ if (status != PJ_SUCCESS) {
+ char errmsg[PJ_ERR_MSG_SIZE];
+ pj_strerror(status, errmsg, sizeof(errmsg));
+ PJ_LOG(1,(THIS_FILE,
+ " Request authentication failed: %s",
+ errmsg));
+ rc = -1070;
+ goto on_return;
+ }
+
+ } else if (PJ_STUN_IS_RESPONSE(msg->hdr.type)) {
+ pj_status_t status;
+ status = pj_stun_authenticate_response(buf, len, msg, &key);
+ if (status != PJ_SUCCESS) {
+ char errmsg[PJ_ERR_MSG_SIZE];
+ pj_strerror(status, errmsg, sizeof(errmsg));
+ PJ_LOG(1,(THIS_FILE,
+ " Response authentication failed: %s",
+ errmsg));
+ rc = -1080;
+ goto on_return;
+ }
+ }
+ }
+ }
+
+
+on_return:
+ pj_pool_release(pool);
+ return rc;
+}
+
+static pj_stun_msg* create_msgint1(pj_pool_t *pool, test_vector *v)
+{
+ pj_stun_msg *msg;
+ pj_timestamp u64;
+ pj_str_t s1;
+
+ pj_stun_msg_create(pool, v->msg_type, PJ_STUN_MAGIC,
+ (pj_uint8_t*)v->tsx_id, &msg);
+
+ pj_stun_msg_add_uint_attr(pool, msg, PJ_STUN_ATTR_PRIORITY, 0x6e0001ff);
+ u64.u32.hi = 0x932ff9b1;
+ u64.u32.lo = 0x51263b36;
+ pj_stun_msg_add_uint64_attr(pool, msg, PJ_STUN_ATTR_ICE_CONTROLLED,
+ &u64);
+
+ pj_stun_msg_add_string_attr(pool, msg, PJ_STUN_ATTR_USERNAME,
+ pj_cstr(&s1, v->username));
+
+ pj_stun_msg_add_msgint_attr(pool, msg);
+
+ pj_stun_msg_add_uint_attr(pool, msg, PJ_STUN_ATTR_FINGERPRINT, 0);
+
+ return msg;
+}
+
+static pj_stun_msg* create_msgint2(pj_pool_t *pool, test_vector *v)
+{
+ pj_stun_msg *msg;
+ pj_sockaddr_in mapped_addr;
+ pj_str_t s1;
+
+ pj_stun_msg_create(pool, v->msg_type, PJ_STUN_MAGIC,
+ (pj_uint8_t*)v->tsx_id, &msg);
+
+ pj_stun_msg_add_string_attr(pool, msg, PJ_STUN_ATTR_SERVER,
+ pj_cstr(&s1, "test vector"));
+
+ pj_sockaddr_in_init(&mapped_addr, pj_cstr(&s1, "127.0.0.1"), 32853);
+ pj_stun_msg_add_sockaddr_attr(pool, msg, PJ_STUN_ATTR_XOR_MAPPED_ADDR,
+ PJ_TRUE, &mapped_addr,
+ sizeof(pj_sockaddr_in));
+
+ pj_stun_msg_add_msgint_attr(pool, msg);
+ pj_stun_msg_add_uint_attr(pool, msg, PJ_STUN_ATTR_FINGERPRINT, 0);
+
+ return msg;
+}
+
int stun_test(void)
{
- decode_verify();
- decode_test();
- auth_test();
- return 0;
+ int pad, rc;
+
+ pad = pj_stun_set_padding_char(32);
+
+ rc = decode_test();
+ if (rc != 0)
+ goto on_return;
+
+ rc = decode_verify();
+ if (rc != 0)
+ goto on_return;
+
+ rc = auth_test();
+ if (rc != 0)
+ goto on_return;
+
+ rc = fingerprint_test_vector();
+ if (rc != 0)
+ goto on_return;
+
+on_return:
+ pj_stun_set_padding_char(pad);
+ return rc;
}
diff --git a/pjnath/src/pjnath-test/test.c b/pjnath/src/pjnath-test/test.c
index b9776539..fc1c124c 100644
--- a/pjnath/src/pjnath-test/test.c
+++ b/pjnath/src/pjnath-test/test.c
@@ -66,6 +66,10 @@ static int test_inner(void)
pjnath_init();
+#if INCLUDE_STUN_TEST
+ DO_TEST(stun_test());
+#endif
+
#if INCLUDE_ICE_TEST
DO_TEST(ice_test());
#endif
diff --git a/pjnath/src/pjnath-test/test.h b/pjnath/src/pjnath-test/test.h
index 5663a84e..39ad4112 100644
--- a/pjnath/src/pjnath-test/test.h
+++ b/pjnath/src/pjnath-test/test.h
@@ -20,8 +20,10 @@
#include <pjlib-util.h>
#include <pjnath.h>
+#define INCLUDE_STUN_TEST 1
#define INCLUDE_ICE_TEST 1
+extern int stun_test(void);
extern int ice_test(void);
extern int test_main(void);
diff --git a/pjnath/src/pjnath/ice_session.c b/pjnath/src/pjnath/ice_session.c
index 27634442..94e5989d 100644
--- a/pjnath/src/pjnath/ice_session.c
+++ b/pjnath/src/pjnath/ice_session.c
@@ -489,7 +489,7 @@ static pj_status_t stun_auth_get_password(const pj_stun_msg *msg,
/* Incoming response is authenticated with TX credential */
/* Verify username */
if (pj_strcmp(username, &ice->tx_uname) != 0)
- return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_UNKNOWN_USERNAME);
+ return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_UNAUTHORIZED);
*data_type = 0;
*data = ice->tx_pass;
@@ -507,13 +507,13 @@ static pj_status_t stun_auth_get_password(const pj_stun_msg *msg,
pos = (const char*)pj_memchr(username->ptr, ':', username->slen);
if (pos == NULL)
- return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_UNKNOWN_USERNAME);
+ return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_UNAUTHORIZED);
ufrag.ptr = (char*)username->ptr;
ufrag.slen = (pos - username->ptr);
if (pj_strcmp(&ufrag, &ice->rx_ufrag) != 0)
- return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_UNKNOWN_USERNAME);
+ return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_UNAUTHORIZED);
*data_type = 0;
*data = ice->rx_pass;
diff --git a/pjnath/src/pjnath/stun_auth.c b/pjnath/src/pjnath/stun_auth.c
index 6509cb38..bc1ef421 100644
--- a/pjnath/src/pjnath/stun_auth.c
+++ b/pjnath/src/pjnath/stun_auth.c
@@ -59,21 +59,30 @@ PJ_INLINE(pj_uint16_t) GET_VAL16(const pj_uint8_t *pdu, unsigned pos)
}
+PJ_INLINE(void) PUT_VAL16(pj_uint8_t *buf, unsigned pos, pj_uint16_t hval)
+{
+ buf[pos+0] = (pj_uint8_t) ((hval & 0xFF00) >> 8);
+ buf[pos+1] = (pj_uint8_t) ((hval & 0x00FF) >> 0);
+}
+
+
/* 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 char *errstr,
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_str_t err_msg;
pj_status_t rc;
- rc = pj_stun_msg_create_response(pool, msg,
- err_code, err_msg, &response);
+ rc = pj_stun_msg_create_response(pool, msg, err_code,
+ (errstr?pj_cstr(&err_msg, errstr):NULL),
+ &response);
if (rc != PJ_SUCCESS)
return rc;
@@ -116,7 +125,8 @@ PJ_DEF(pj_status_t) pj_stun_authenticate_request(const pj_uint8_t *pkt,
{
pj_str_t realm, nonce, password;
const pj_stun_msgint_attr *amsgi;
- unsigned amsgi_pos;
+ 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;
@@ -138,7 +148,7 @@ 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;
- /* Get realm and nonce */
+ /* Get realm and nonce from credential */
realm.slen = nonce.slen = 0;
if (cred->type == PJ_STUN_AUTH_CRED_STATIC) {
realm = cred->data.static_cred.realm;
@@ -153,26 +163,50 @@ PJ_DEF(pj_status_t) pj_stun_authenticate_request(const pj_uint8_t *pkt,
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);
+ /* Look for MESSAGE-INTEGRITY while counting the position */
+ amsgi_pos = 0;
+ has_attr_beyond_mi = PJ_FALSE;
+ amsgi = NULL;
+ for (i=0; i<msg->attr_count; ++i) {
+ if (msg->attr[i]->type == PJ_STUN_ATTR_MESSAGE_INTEGRITY) {
+ amsgi = (const pj_stun_msgint_attr*) msg->attr[i];
+ } else if (amsgi) {
+ has_attr_beyond_mi = PJ_TRUE;
+ break;
+ } else {
+ amsgi_pos += ((msg->attr[i]->length+3) & ~0x03) + 4;
+ }
+ }
+
if (amsgi == NULL) {
+ /* According to rfc3489bis-10 Sec 10.1.2/10.2.2, we should return 400
+ 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, PJ_STUN_SC_UNAUTHORIZED, NULL,
+ create_challenge(pool, msg, code, NULL,
&realm, &nonce, p_response);
}
- return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_INTEGRITY_CHECK_FAILURE);
+ return PJ_STATUS_FROM_STUN_CODE(code);
}
/* 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) {
+ /* According to rfc3489bis-10 Sec 10.1.2/10.2.2, we should return 400
+ 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, PJ_STUN_SC_MISSING_USERNAME, NULL,
+ create_challenge(pool, msg, code, "Missing USERNAME",
&realm, &nonce, p_response);
}
- return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_MISSING_USERNAME);
+ return PJ_STATUS_FROM_STUN_CODE(code);
}
/* Get REALM, if any */
@@ -200,11 +234,14 @@ PJ_DEF(pj_status_t) pj_stun_authenticate_request(const pj_uint8_t *pkt,
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_UNKNOWN_USERNAME, NULL,
+ create_challenge(pool, msg, PJ_STUN_SC_UNAUTHORIZED, NULL,
&realm, &nonce, p_response);
}
- return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_UNKNOWN_USERNAME);
+ return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_UNAUTHORIZED);
}
@@ -216,10 +253,11 @@ PJ_DEF(pj_status_t) pj_stun_authenticate_request(const pj_uint8_t *pkt,
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,
+ create_challenge(pool, msg, PJ_STUN_SC_BAD_REQUEST,
+ "Missing REALM",
&realm, &nonce, p_response);
}
- return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_MISSING_REALM);
+ return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_BAD_REQUEST);
} else if (realm.slen != 0 && arealm != NULL) {
/* We want long term, and REALM is present */
@@ -227,20 +265,20 @@ PJ_DEF(pj_status_t) pj_stun_authenticate_request(const pj_uint8_t *pkt,
/* NONCE must be present. */
if (anonce == NULL && nonce.slen) {
if (p_response) {
- create_challenge(pool, msg, PJ_STUN_SC_MISSING_NONCE,
- NULL, &realm, &nonce, 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_MISSING_NONCE);
+ return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_BAD_REQUEST);
}
/* 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);
+ create_challenge(pool, msg, PJ_STUN_SC_UNAUTHORIZED,
+ "Invalid REALM", &realm, &nonce, p_response);
}
- return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_MISSING_REALM);
+ return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_UNAUTHORIZED);
}
/* Valid case, will validate the message integrity later */
@@ -260,10 +298,10 @@ PJ_DEF(pj_status_t) pj_stun_authenticate_request(const pj_uint8_t *pkt,
/* 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);
+ create_challenge(pool, msg, PJ_STUN_SC_UNAUTHORIZED,
+ "NONCE required", &realm, &nonce, p_response);
}
- return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_MISSING_NONCE);
+ return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_UNAUTHORIZED);
}
}
@@ -294,55 +332,49 @@ PJ_DEF(pj_status_t) pj_stun_authenticate_request(const pj_uint8_t *pkt,
create_challenge(pool, msg, PJ_STUN_SC_STALE_NONCE,
NULL, &realm, &nonce, p_response);
}
- return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_MISSING_NONCE);
+ return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_STALE_NONCE);
}
}
- /* 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;
- }
-
/* Calculate key */
pj_stun_create_key(pool, &key, &realm, &auser->value, &password);
- /* Now calculate HMAC of the message, adding zero padding if necessary
- * to make the input 64 bytes aligned.
- */
+ /* Now calculate HMAC of the message. */
pj_hmac_sha1_init(&ctx, (pj_uint8_t*)key.ptr, key.slen);
- pj_hmac_sha1_update(&ctx, pkt, amsgi_pos);
- if (amsgi_pos & 63) {
- pj_uint8_t zeroes[64];
- pj_bzero(zeroes, sizeof(zeroes));
- pj_hmac_sha1_update(&ctx, zeroes, 64-(amsgi_pos & 63));
+
+ /* First calculate HMAC for the header.
+ * The calculation is different depending on whether FINGERPRINT attribute
+ * is present in the message.
+ */
+ if (has_attr_beyond_mi) {
+ pj_uint8_t hdr_copy[20];
+ pj_memcpy(hdr_copy, pkt, 20);
+ PUT_VAL16(hdr_copy, 2, (pj_uint16_t)(amsgi_pos + 24));
+ pj_hmac_sha1_update(&ctx, hdr_copy, 20);
+ } else {
+ pj_hmac_sha1_update(&ctx, pkt, 20);
}
+
+ /* Now update with the message body */
+ pj_hmac_sha1_update(&ctx, pkt+20, amsgi_pos);
+ // This is no longer necessary as per rfc3489bis-08
+ //if (amsgi_pos & 0x3F) {
+ // pj_uint8_t zeroes[64];
+ // pj_bzero(zeroes, sizeof(zeroes));
+ // pj_hmac_sha1_update(&ctx, zeroes, 64-(amsgi_pos & 0x3F));
+ //}
pj_hmac_sha1_final(&ctx, digest);
+
/* Compare HMACs */
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_INTEGRITY_CHECK_FAILURE,
+ create_challenge(pool, msg, PJ_STUN_SC_UNAUTHORIZED,
NULL, &realm, &nonce, p_response);
}
- return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_INTEGRITY_CHECK_FAILURE);
+ return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_UNAUTHORIZED);
}
/* Everything looks okay! */
@@ -381,11 +413,11 @@ 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) */
+ //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) */
return PJ_FALSE;
default:
return PJ_TRUE;
@@ -400,7 +432,8 @@ PJ_DEF(pj_status_t) pj_stun_authenticate_response(const pj_uint8_t *pkt,
const pj_str_t *key)
{
const pj_stun_msgint_attr *amsgi;
- unsigned amsgi_pos;
+ unsigned i, amsgi_pos;
+ pj_bool_t has_attr_beyond_mi;
pj_hmac_sha1_context ctx;
pj_uint8_t digest[PJ_SHA1_DIGEST_SIZE];
@@ -410,7 +443,7 @@ PJ_DEF(pj_status_t) pj_stun_authenticate_response(const pj_uint8_t *pkt,
amsgi = (const pj_stun_msgint_attr*)
pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_MESSAGE_INTEGRITY, 0);
if (amsgi == NULL) {
- return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_INTEGRITY_CHECK_FAILURE);
+ return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_UNAUTHORIZED);
}
@@ -419,48 +452,55 @@ PJ_DEF(pj_status_t) pj_stun_authenticate_response(const pj_uint8_t *pkt,
return PJNATH_EINSTUNMSGLEN;
}
- /* 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) {
- /* Check that message length is valid */
- if (msg->hdr.length < 32) {
- return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_INTEGRITY_CHECK_FAILURE);
- }
-
- amsgi_pos = 20+msg->hdr.length-8-24;
- if (GET_VAL16(pkt, amsgi_pos) == PJ_STUN_ATTR_MESSAGE_INTEGRITY) {
- /* Found MESSAGE-INTEGRITY before FINGERPRINT */
+ /* Look for MESSAGE-INTEGRITY while counting the position */
+ amsgi_pos = 0;
+ has_attr_beyond_mi = PJ_FALSE;
+ amsgi = NULL;
+ for (i=0; i<msg->attr_count; ++i) {
+ if (msg->attr[i]->type == PJ_STUN_ATTR_MESSAGE_INTEGRITY) {
+ amsgi = (const pj_stun_msgint_attr*) msg->attr[i];
+ } else if (amsgi) {
+ has_attr_beyond_mi = PJ_TRUE;
+ break;
} else {
- amsgi_pos = 0;
+ amsgi_pos += ((msg->attr[i]->length+3) & ~0x03) + 4;
}
}
- if (amsgi_pos==0) {
- return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_INTEGRITY_CHECK_FAILURE);
+ if (amsgi == NULL) {
+ return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_BAD_REQUEST);
}
- /* Now calculate HMAC of the message, adding zero padding if necessary
- * to make the input 64 bytes aligned.
- */
+ /* Now calculate HMAC of the message. */
pj_hmac_sha1_init(&ctx, (pj_uint8_t*)key->ptr, key->slen);
- pj_hmac_sha1_update(&ctx, pkt, amsgi_pos);
- if (amsgi_pos & 0x3F) {
- pj_uint8_t zeroes[64];
- pj_bzero(zeroes, sizeof(zeroes));
- pj_hmac_sha1_update(&ctx, zeroes, 64-(amsgi_pos & 0x3F));
+
+ /* First calculate HMAC for the header.
+ * The calculation is different depending on whether FINGERPRINT attribute
+ * is present in the message.
+ */
+ if (has_attr_beyond_mi) {
+ pj_uint8_t hdr_copy[20];
+ pj_memcpy(hdr_copy, pkt, 20);
+ PUT_VAL16(hdr_copy, 2, (pj_uint16_t)(amsgi_pos+24));
+ pj_hmac_sha1_update(&ctx, hdr_copy, 20);
+ } else {
+ pj_hmac_sha1_update(&ctx, pkt, 20);
}
+
+ /* Now update with the message body */
+ pj_hmac_sha1_update(&ctx, pkt+20, amsgi_pos);
+ // This is no longer necessary as per rfc3489bis-08
+ //if (amsgi_pos & 0x3F) {
+ // pj_uint8_t zeroes[64];
+ // pj_bzero(zeroes, sizeof(zeroes));
+ // pj_hmac_sha1_update(&ctx, zeroes, 64-(amsgi_pos & 0x3F));
+ //}
pj_hmac_sha1_final(&ctx, digest);
/* Compare HMACs */
if (pj_memcmp(amsgi->hmac, digest, 20)) {
/* HMAC value mismatch */
- return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_INTEGRITY_CHECK_FAILURE);
+ return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_UNAUTHORIZED);
}
/* Everything looks okay! */
diff --git a/pjnath/src/pjnath/stun_msg.c b/pjnath/src/pjnath/stun_msg.c
index 2dadca40..582af0ce 100644
--- a/pjnath/src/pjnath/stun_msg.c
+++ b/pjnath/src/pjnath/stun_msg.c
@@ -31,6 +31,8 @@
#define THIS_FILE "stun_msg.c"
#define STUN_XOR_FINGERPRINT 0x5354554eL
+static int padding_char;
+
static const char *stun_method_names[] =
{
"Unknown", /* 0 */
@@ -54,14 +56,14 @@ static struct
{ PJ_STUN_SC_BAD_REQUEST, "Bad Request"},
{ PJ_STUN_SC_UNAUTHORIZED, "Unauthorized"},
{ PJ_STUN_SC_UNKNOWN_ATTRIBUTE, "Unknown Attribute"},
- { PJ_STUN_SC_STALE_CREDENTIALS, "Stale Credentials"},
- { PJ_STUN_SC_INTEGRITY_CHECK_FAILURE, "Integrity Check Failure"},
- { PJ_STUN_SC_MISSING_USERNAME, "Missing Username"},
- { PJ_STUN_SC_USE_TLS, "Use TLS"},
- { PJ_STUN_SC_MISSING_REALM, "Missing Realm"},
- { PJ_STUN_SC_MISSING_NONCE, "Missing Nonce"},
- { PJ_STUN_SC_UNKNOWN_USERNAME, "Unknown Username"},
- { PJ_STUN_SC_NO_BINDING, "No Binding"},
+ //{ PJ_STUN_SC_STALE_CREDENTIALS, "Stale Credentials"},
+ //{ PJ_STUN_SC_INTEGRITY_CHECK_FAILURE, "Integrity Check Failure"},
+ //{ PJ_STUN_SC_MISSING_USERNAME, "Missing Username"},
+ //{ PJ_STUN_SC_USE_TLS, "Use TLS"},
+ //{ PJ_STUN_SC_MISSING_REALM, "Missing Realm"},
+ //{ PJ_STUN_SC_MISSING_NONCE, "Missing Nonce"},
+ //{ PJ_STUN_SC_UNKNOWN_USERNAME, "Unknown Username"},
+ //{ PJ_STUN_SC_NO_BINDING, "No Binding"},
{ PJ_STUN_SC_STALE_NONCE, "Stale Nonce"},
{ PJ_STUN_SC_TRANSITIONING, "Active Destination Already Set"},
{ PJ_STUN_SC_UNSUPP_TRANSPORT_PROTO, "Unsupported Transport Protocol"},
@@ -568,6 +570,17 @@ PJ_DEF(pj_str_t) pj_stun_get_err_reason(int err_code)
}
+/*
+ * Set padding character.
+ */
+PJ_DEF(int) pj_stun_set_padding_char(int chr)
+{
+ int old_pad = padding_char;
+ padding_char = chr;
+ return old_pad;
+}
+
+
//////////////////////////////////////////////////////////////////////////////
@@ -900,6 +913,14 @@ static pj_status_t encode_string_attr(const void *a, pj_uint8_t *buf,
/* Copy the string */
pj_memcpy(buf+ATTR_HDR_LEN, ca->value.ptr, ca->value.slen);
+ /* Add padding character, if string is not 4-bytes aligned. */
+ if (ca->value.slen & 0x03) {
+ pj_uint8_t pad[3];
+ pj_memset(pad, padding_char, sizeof(pad));
+ pj_memcpy(buf+ATTR_HDR_LEN+ca->value.slen, pad,
+ 4-(ca->value.slen & 0x03));
+ }
+
/* Done */
return PJ_SUCCESS;
}
@@ -1378,11 +1399,13 @@ PJ_DEF(pj_status_t) pj_stun_unknown_attr_create(pj_pool_t *pool,
/* If the number of unknown attributes is an odd number, one of the
* attributes MUST be repeated in the list.
*/
+ /* No longer necessary
if ((attr_cnt & 0x01)) {
attr->attrs[attr_cnt] = attr_array[attr_cnt-1];
}
+ */
- *p_attr = NULL;
+ *p_attr = attr;
return PJ_SUCCESS;
}
@@ -1636,6 +1659,13 @@ PJ_DEF(pj_status_t) pj_stun_msg_check(const pj_uint8_t *pdu, unsigned pdu_len,
return PJNATH_EINSTUNMSGLEN;
}
+ /* STUN message is always padded to the nearest 4 bytes, thus
+ * the last two bits of the length field are always zero.
+ */
+ if ((msg_len & 0x03) != 0) {
+ return PJNATH_EINSTUNMSGLEN;
+ }
+
/* If magic is set, then there is great possibility that this is
* a STUN message.
*/
@@ -1878,16 +1908,17 @@ PJ_DEF(pj_status_t) pj_stun_msg_decode(pj_pool_t *pool,
}
has_fingerprint = PJ_TRUE;
} else {
- if (has_msg_int || has_fingerprint) {
+ if (has_fingerprint) {
/* Another attribute is found which is not FINGERPRINT
- * after FINGERPRINT or MESSAGE-INTEGRITY */
+ * after FINGERPRINT. Note that non-FINGERPRINT is
+ * allowed to appear after M-I
+ */
if (p_response) {
pj_stun_msg_create_response(pool, msg,
PJ_STUN_SC_BAD_REQUEST,
NULL, p_response);
}
- return has_fingerprint ? PJNATH_ESTUNFINGERPOS :
- PJNATH_ESTUNMSGINTPOS;
+ return PJNATH_ESTUNFINGERPOS;
}
}
@@ -2114,16 +2145,11 @@ PJ_DEF(pj_status_t) pj_stun_msg_encode(pj_stun_msg *msg,
}
}
- /* 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 MESSAGE-INTEGRITY is present, include the M-I attribute
+ * in message length before calculating M-I
*/
- if (amsgint && afingerprint) {
- body_len = (pj_uint16_t)((buf - start) - 20 + 24 + 8);
- } else if (amsgint) {
+ if (amsgint) {
body_len = (pj_uint16_t)((buf - start) - 20 + 24);
- } else if (afingerprint) {
- body_len = (pj_uint16_t)((buf - start) - 20 + 8);
} else {
body_len = (pj_uint16_t)((buf - start) - 20);
}
@@ -2161,11 +2187,12 @@ PJ_DEF(pj_status_t) pj_stun_msg_encode(pj_stun_msg *msg,
*/
pj_hmac_sha1_init(&ctx, (pj_uint8_t*)key->ptr, key->slen);
pj_hmac_sha1_update(&ctx, (pj_uint8_t*)start, buf-start);
- if ((buf-start) & 0x3F) {
- pj_uint8_t zeroes[64];
- pj_bzero(zeroes, sizeof(zeroes));
- pj_hmac_sha1_update(&ctx, zeroes, 64-((buf-start) & 0x3F));
- }
+ // These are obsoleted in rfc3489bis-08
+ //if ((buf-start) & 0x3F) {
+ // pj_uint8_t zeroes[64];
+ // pj_bzero(zeroes, sizeof(zeroes));
+ // pj_hmac_sha1_update(&ctx, zeroes, 64-((buf-start) & 0x3F));
+ //}
pj_hmac_sha1_final(&ctx, amsgint->hmac);
/* Put this attribute in the message */
@@ -2180,6 +2207,10 @@ PJ_DEF(pj_status_t) pj_stun_msg_encode(pj_stun_msg *msg,
/* Calculate FINGERPRINT if present */
if (afingerprint != NULL) {
+ /* Update message length */
+ PUTVAL16H(start, 2,
+ (pj_uint16_t)(GETVAL16H(start, 2)+8));
+
afingerprint->value = pj_crc32_calc(start, buf-start);
afingerprint->value ^= STUN_XOR_FINGERPRINT;
diff --git a/pjnath/src/pjnath/stun_msg_dump.c b/pjnath/src/pjnath/stun_msg_dump.c
index c208879f..3745ebe3 100644
--- a/pjnath/src/pjnath/stun_msg_dump.c
+++ b/pjnath/src/pjnath/stun_msg_dump.c
@@ -192,6 +192,22 @@ static int print_attr(char *buffer, unsigned length,
APPLY();
}
break;
+ case PJ_STUN_ATTR_ICE_CONTROLLED:
+ case PJ_STUN_ATTR_ICE_CONTROLLING:
+ {
+ const pj_stun_uint64_attr *attr;
+ pj_uint8_t data[8];
+ int i;
+
+ attr = (const pj_stun_uint64_attr*) ahdr;
+
+ for (i=0; i<8; ++i)
+ data[i] = ((const pj_uint8_t*)&attr->value)[7-i];
+
+ len = print_binary(p, end-p, data, 8);
+ APPLY();
+ }
+ break;
case PJ_STUN_ATTR_USE_CANDIDATE:
default:
len = pj_ansi_snprintf(p, end-p, "\n");