From 79768e50b4c2e0d1c4fb871dc99a696fc5d8d2dd Mon Sep 17 00:00:00 2001 From: Benny Prijono Date: Mon, 26 Feb 2007 22:31:06 +0000 Subject: Finishing up STUN server side authentication git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@1003 74dad513-b988-da41-8d7b-12977e46ad98 --- pjlib-util/build/pjlib_util_test.dsp | 8 +- pjlib-util/build/pjlib_util_test.vcproj | 4 + pjlib-util/include/pjlib-util/config.h | 18 ++ pjlib-util/include/pjlib-util/crc32.h | 2 +- pjlib-util/include/pjlib-util/stun_msg.h | 125 +++++++- pjlib-util/src/pjlib-util-test/encryption.c | 135 ++++++++ pjlib-util/src/pjlib-util-test/stun.c | 118 +++++++ pjlib-util/src/pjlib-util-test/test.c | 4 + pjlib-util/src/pjlib-util-test/test.h | 3 + pjlib-util/src/pjlib-util/crc32.c | 54 +++- pjlib-util/src/pjlib-util/stun_msg.c | 471 +++++++++++++++++++++------- 11 files changed, 806 insertions(+), 136 deletions(-) create mode 100644 pjlib-util/src/pjlib-util-test/stun.c diff --git a/pjlib-util/build/pjlib_util_test.dsp b/pjlib-util/build/pjlib_util_test.dsp index 647e33cb..e3830125 100644 --- a/pjlib-util/build/pjlib_util_test.dsp +++ b/pjlib-util/build/pjlib_util_test.dsp @@ -42,7 +42,7 @@ RSC=rc.exe # PROP Ignore_Export_Lib 0 # PROP Target_Dir "" # ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c -# ADD CPP /nologo /MD /W3 /GX /O2 /I "../include" /I "../../pjlib/include" /D "NDEBUG" /D "WIN32" /D "_CONSOLE" /D "_MBCS" /D PJ_WIN32=1 /D PJ_M_I386=1 /FR /YX /FD /c +# ADD CPP /nologo /MD /W4 /GX /O2 /I "../include" /I "../../pjlib/include" /D "NDEBUG" /D "WIN32" /D "_CONSOLE" /D "_MBCS" /D PJ_WIN32=1 /D PJ_M_I386=1 /FR /YX /FD /c # ADD BASE RSC /l 0x409 /d "NDEBUG" # ADD RSC /l 0x409 /d "NDEBUG" BSC32=bscmake.exe @@ -66,7 +66,7 @@ LINK32=link.exe # PROP Ignore_Export_Lib 0 # PROP Target_Dir "" # ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c -# ADD CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /I "../include" /I "../../pjlib/include" /D "_DEBUG" /D "WIN32" /D "_CONSOLE" /D "_MBCS" /D PJ_WIN32=1 /D PJ_M_I386=1 /FR /YX /FD /GZ /c +# ADD CPP /nologo /MTd /W4 /Gm /GX /ZI /Od /I "../include" /I "../../pjlib/include" /D "_DEBUG" /D "WIN32" /D "_CONSOLE" /D "_MBCS" /D PJ_WIN32=1 /D PJ_M_I386=1 /FR /YX /FD /GZ /c # ADD BASE RSC /l 0x409 /d "_DEBUG" # ADD RSC /l 0x409 /d "_DEBUG" BSC32=bscmake.exe @@ -95,6 +95,10 @@ SOURCE="..\src\pjlib-util-test\main.c" # End Source File # Begin Source File +SOURCE="..\src\pjlib-util-test\stun.c" +# End Source File +# Begin Source File + SOURCE="..\src\pjlib-util-test\test.c" # End Source File # Begin Source File diff --git a/pjlib-util/build/pjlib_util_test.vcproj b/pjlib-util/build/pjlib_util_test.vcproj index 0b7219d8..63a93244 100644 --- a/pjlib-util/build/pjlib_util_test.vcproj +++ b/pjlib-util/build/pjlib_util_test.vcproj @@ -232,6 +232,10 @@ /> + + diff --git a/pjlib-util/include/pjlib-util/config.h b/pjlib-util/include/pjlib-util/config.h index 3127a415..612f4bc0 100644 --- a/pjlib-util/include/pjlib-util/config.h +++ b/pjlib-util/include/pjlib-util/config.h @@ -211,6 +211,24 @@ # define PJ_STUN_MAX_ATTR 16 #endif + +/* ************************************************************************** + * ENCRYPTION + */ + +/** + * Specifies whether CRC32 algorithm should use the table based lookup table + * for faster calculation, at the expense of about 1KB table size on the + * executable. If zero, the CRC32 will use non-table based which is more than + * an order of magnitude slower. + * + * Default: 1 + */ +#ifndef PJ_CRC32_HAS_TABLES +# define PJ_CRC32_HAS_TABLES 1 +#endif + + /** * @} */ diff --git a/pjlib-util/include/pjlib-util/crc32.h b/pjlib-util/include/pjlib-util/crc32.h index a85a51a6..ebc6fb89 100644 --- a/pjlib-util/include/pjlib-util/crc32.h +++ b/pjlib-util/include/pjlib-util/crc32.h @@ -24,7 +24,7 @@ * @brief CRC32 implementation */ -#include +#include PJ_BEGIN_DECL diff --git a/pjlib-util/include/pjlib-util/stun_msg.h b/pjlib-util/include/pjlib-util/stun_msg.h index 652472b5..05aa7d67 100644 --- a/pjlib-util/include/pjlib-util/stun_msg.h +++ b/pjlib-util/include/pjlib-util/stun_msg.h @@ -1245,6 +1245,106 @@ PJ_DECL(pj_status_t) pj_stun_msg_decode(pj_pool_t *pool, unsigned *p_parsed_len, pj_stun_msg **p_response); +typedef enum pj_stun_auth_policy_type +{ + PJ_STUN_POLICY_NONE, + PJ_STUN_POLICY_STATIC_SHORT_TERM, + PJ_STUN_POLICY_STATIC_LONG_TERM, + PJ_STUN_POLICY_DYNAMIC +} pj_stun_auth_policy_type; + +typedef struct pj_stun_auth_policy +{ + pj_stun_auth_policy_type type; + void *user_data; + + union + { + struct + { + pj_str_t username; + pj_str_t password; + pj_str_t nonce; + } static_short_term; + + struct + { + pj_str_t realm; + pj_str_t username; + pj_str_t password; + pj_str_t nonce; + } static_long_term; + + struct + { + /** + * This callback is called by pj_stun_verify_credential() when + * server needs to challenge the request with 401 response. + * + * @param user_data The user data as specified in the policy. + * @param pool Pool to allocate memory. + * @param realm On return, the function should fill in with + * realm if application wants to use long term + * credential. Otherwise application should set + * empty string for the realm. + * @param nonce On return, if application wants to use long + * term credential, it MUST fill in the nonce + * with some value. Otherwise if short term + * credential is wanted, it MAY set this value. + * If short term credential is wanted and the + * application doesn't want to include NONCE, + * then it must set this to empty string. + * + * @return The callback should return PJ_SUCCESS, or + * otherwise response message will not be + * created. + */ + pj_status_t (*get_auth)(void *user_data, + pj_pool_t *pool, + pj_str_t *realm, + pj_str_t *nonce); + + /** + * Get the password for the specified username. This function + * is also used to check whether the username is valid. + * + * @param user_data The user data as specified in the policy. + * @param realm The realm as specified in the message. + * @param username The username as specified in the message. + * @param pool Pool to allocate memory when necessary. + * @param password On return, application should fill up this + * argument with the password. + * + * @return The callback should return PJ_SUCCESS if + * username has been successfully verified + * and password was obtained. If non-PJ_SUCCESS + * is returned, it is assumed that the + * username is not valid. + */ + pj_status_t (*get_password)(void *user_data, + const pj_str_t *realm, + const pj_str_t *username, + pj_pool_t *pool, + pj_str_t *password); + pj_bool_t (*require_nonce)(void *user_data, + const pj_str_t *realm, + const pj_str_t *username); + pj_bool_t (*verify_nonce)(void *data, + const pj_str_t *realm, + const pj_str_t *username, + const pj_str_t *nonce); + pj_status_t (*make_nonce)(void *user_data, + const pj_str_t *realm, + const pj_str_t *username, + pj_pool_t *pool, + pj_str_t *nonce); + } dynamic; + + } data; + +} pj_stun_auth_policy; + + /** * Verify credential in the STUN message. Note that before calling this * function, application must have checked that the message contains @@ -1252,16 +1352,12 @@ PJ_DECL(pj_status_t) pj_stun_msg_decode(pj_pool_t *pool, * function, because this function will reject the message with 401 error * if it doesn't contain PJ_STUN_ATTR_MESSAGE_INTEGRITY attribute. * - * @param msg The message to be verified. - * @param realm Realm, if long term credential is required. If - * short term credential is required, this argument - * must be set to NULL. - * @param username If this attribute is specified, then the USERNAME - * attribute in the message will be compared against - * this value. If NULL is specified, then this function - * will accept any usernames. - * @param password The password. - * @param options Options, must be zero for now. + * @param pkt The original packet which has been parsed into + * the message. This packet MUST NOT have been modified + * after the parsing. + * @param pkt_len The length of the packet. + * @param msg The parsed message to be verified. + * @param policy Pointer to authentication policy. * @param pool If response is to be created, then memory will * be allocated from this pool. * @param p_response Optional pointer to receive the response message @@ -1273,11 +1369,10 @@ PJ_DECL(pj_status_t) pj_stun_msg_decode(pj_pool_t *pool, * NULL, an appropriate response will be returned in * \a p_response. */ -PJ_DECL(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_DECL(pj_status_t) pj_stun_verify_credential(const pj_uint8_t *pkt, + unsigned pkt_len, + const pj_stun_msg *msg, + pj_stun_auth_policy *policy, pj_pool_t *pool, pj_stun_msg **p_response); 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 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 + * + * 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 + +#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 #include #include +#include #include #include #include @@ -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; } -- cgit v1.2.3