diff options
-rw-r--r-- | pjnath/include/pjnath/config.h | 15 | ||||
-rw-r--r-- | pjnath/include/pjnath/stun_msg.h | 30 | ||||
-rw-r--r-- | pjnath/include/pjnath/types.h | 4 | ||||
-rw-r--r-- | pjnath/src/pjnath-test/stun.c | 698 | ||||
-rw-r--r-- | pjnath/src/pjnath-test/test.c | 4 | ||||
-rw-r--r-- | pjnath/src/pjnath-test/test.h | 2 | ||||
-rw-r--r-- | pjnath/src/pjnath/ice_session.c | 6 | ||||
-rw-r--r-- | pjnath/src/pjnath/stun_auth.c | 228 | ||||
-rw-r--r-- | pjnath/src/pjnath/stun_msg.c | 83 | ||||
-rw-r--r-- | pjnath/src/pjnath/stun_msg_dump.c | 16 |
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"); |