From 97dfbff2ebfae83b7cb10517247215d38e4a74fd Mon Sep 17 00:00:00 2001 From: Benny Prijono Date: Wed, 19 Mar 2008 23:00:30 +0000 Subject: Related to ticket #485: huge changeset to update STUN relating to managing authentication. See the ticket for the details git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@1877 74dad513-b988-da41-8d7b-12977e46ad98 --- pjnath/src/pjnath/stun_auth.c | 291 ++++++++++++++++++++++++++++-------------- 1 file changed, 196 insertions(+), 95 deletions(-) (limited to 'pjnath/src/pjnath/stun_auth.c') diff --git a/pjnath/src/pjnath/stun_auth.c b/pjnath/src/pjnath/stun_auth.c index 4a6024f0..4ef29599 100644 --- a/pjnath/src/pjnath/stun_auth.c +++ b/pjnath/src/pjnath/stun_auth.c @@ -19,9 +19,11 @@ #include #include #include +#include #include #include #include +#include #include #define THIS_FILE "stun_auth.c" @@ -53,6 +55,99 @@ PJ_DEF(void) pj_stun_auth_cred_dup( pj_pool_t *pool, } +/* + * Duplicate request credential. + */ +PJ_DEF(void) pj_stun_req_cred_info_dup( pj_pool_t *pool, + pj_stun_req_cred_info *dst, + const pj_stun_req_cred_info *src) +{ + pj_strdup(pool, &dst->realm, &src->realm); + pj_strdup(pool, &dst->username, &src->username); + pj_strdup(pool, &dst->nonce, &src->nonce); + pj_strdup(pool, &dst->auth_key, &src->auth_key); +} + + +/* 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); +} + + +/* + * Create authentication key to be used for encoding the message with + * MESSAGE-INTEGRITY. + */ +PJ_DEF(void) pj_stun_create_key(pj_pool_t *pool, + pj_str_t *key, + const pj_str_t *realm, + const pj_str_t *username, + pj_stun_passwd_type data_type, + const pj_str_t *data) +{ + PJ_ASSERT_ON_FAIL(pool && key && username && data, return); + + if (realm && realm->slen) { + if (data_type == PJ_STUN_PASSWD_PLAIN) { + key->ptr = (char*) pj_pool_alloc(pool, 16); + calc_md5_key((pj_uint8_t*)key->ptr, realm, username, data); + key->slen = 16; + } else { + pj_strdup(pool, key, data); + } + } else { + pj_assert(data_type == PJ_STUN_PASSWD_PLAIN); + pj_strdup(pool, key, data); + } +} + + PJ_INLINE(pj_uint16_t) GET_VAL16(const pj_uint8_t *pdu, unsigned pos) { return (pj_uint16_t) ((pdu[pos] << 8) + pdu[pos+1]); @@ -86,8 +181,8 @@ static pj_status_t create_challenge(pj_pool_t *pool, if (rc != PJ_SUCCESS) return rc; - - if (realm && realm->slen) { + /* SHOULD NOT add REALM, NONCE, USERNAME, and M-I on 400 response */ + if (err_code!=400 && realm && realm->slen) { rc = pj_stun_msg_add_string_attr(pool, response, PJ_STUN_ATTR_REALM, realm); @@ -101,7 +196,7 @@ static pj_status_t create_challenge(pj_pool_t *pool, } } - if (nonce && nonce->slen) { + if (err_code!=400 && nonce && nonce->slen) { rc = pj_stun_msg_add_string_attr(pool, response, PJ_STUN_ATTR_NONCE, nonce); @@ -121,20 +216,20 @@ PJ_DEF(pj_status_t) pj_stun_authenticate_request(const pj_uint8_t *pkt, const pj_stun_msg *msg, pj_stun_auth_cred *cred, pj_pool_t *pool, - pj_str_t *auth_key, + pj_stun_req_cred_info *p_info, pj_stun_msg **p_response) { - pj_str_t realm, nonce, password; + pj_stun_req_cred_info tmp_info; const pj_stun_msgint_attr *amsgi; 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; const pj_stun_realm_attr *anonce; pj_hmac_sha1_context ctx; pj_uint8_t digest[PJ_SHA1_DIGEST_SIZE]; - pj_str_t key; + pj_stun_status err_code; + const char *err_text = NULL; pj_status_t status; /* msg and credential MUST be specified */ @@ -149,18 +244,24 @@ 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; + if (p_info == NULL) + p_info = &tmp_info; + + pj_bzero(p_info, sizeof(pj_stun_req_cred_info)); + /* Get realm and nonce from credential */ - realm.slen = nonce.slen = 0; + p_info->realm.slen = p_info->nonce.slen = 0; if (cred->type == PJ_STUN_AUTH_CRED_STATIC) { - realm = cred->data.static_cred.realm; - nonce = cred->data.static_cred.nonce; + p_info->realm = cred->data.static_cred.realm; + p_info->nonce = cred->data.static_cred.nonce; } else if (cred->type == PJ_STUN_AUTH_CRED_DYNAMIC) { status = cred->data.dyn_cred.get_auth(cred->data.dyn_cred.user_data, - pool, &realm, &nonce); + pool, &p_info->realm, + &p_info->nonce); if (status != PJ_SUCCESS) return status; } else { - pj_assert(!"Unexpected"); + pj_assert(!"Invalid credential type"); return PJ_EBUG; } @@ -184,14 +285,9 @@ PJ_DEF(pj_status_t) pj_stun_authenticate_request(const pj_uint8_t *pkt, 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, code, NULL, - &realm, &nonce, p_response); - } - return PJ_STATUS_FROM_STUN_CODE(code); + err_code = p_info->realm.slen ? PJ_STUN_SC_UNAUTHORIZED : + PJ_STUN_SC_BAD_REQUEST; + goto on_auth_failed; } /* Next check that USERNAME is present */ @@ -202,48 +298,67 @@ PJ_DEF(pj_status_t) pj_stun_authenticate_request(const pj_uint8_t *pkt, 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, code, "Missing USERNAME", - &realm, &nonce, p_response); - } - return PJ_STATUS_FROM_STUN_CODE(code); + err_code = PJ_STUN_SC_BAD_REQUEST; + err_text = "Missing USERNAME"; + goto on_auth_failed; } /* Get REALM, if any */ arealm = (const pj_stun_realm_attr*) pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_REALM, 0); + /* Reject with 400 if we have long term credential and the request + * is missing REALM attribute. + */ + if (p_info->realm.slen && arealm==NULL) { + err_code = PJ_STUN_SC_BAD_REQUEST; + err_text = "Missing REALM"; + goto on_auth_failed; + } + /* Check if username match */ if (cred->type == PJ_STUN_AUTH_CRED_STATIC) { + pj_bool_t username_ok; username_ok = !pj_strcmp(&auser->value, &cred->data.static_cred.username); - password = cred->data.static_cred.data; + if (username_ok) { + pj_strdup(pool, &p_info->username, + &cred->data.static_cred.username); + pj_stun_create_key(pool, &p_info->auth_key, &p_info->realm, + &auser->value, cred->data.static_cred.data_type, + &cred->data.static_cred.data); + } else { + /* Username mismatch */ + /* According to rfc3489bis-10 Sec 10.1.2/10.2.2, we should + * return 401 + */ + err_code = PJ_STUN_SC_UNAUTHORIZED; + goto on_auth_failed; + } } else if (cred->type == PJ_STUN_AUTH_CRED_DYNAMIC) { - int data_type = 0; + pj_stun_passwd_type data_type = PJ_STUN_PASSWD_PLAIN; + pj_str_t password; pj_status_t rc; + rc = cred->data.dyn_cred.get_password(msg, cred->data.dyn_cred.user_data, (arealm?&arealm->value:NULL), &auser->value, pool, &data_type, &password); - username_ok = (rc == PJ_SUCCESS); + if (rc == PJ_SUCCESS) { + pj_strdup(pool, &p_info->username, &auser->value); + pj_stun_create_key(pool, &p_info->auth_key, + (arealm?&arealm->value:NULL), &auser->value, + data_type, &password); + } else { + err_code = PJ_STUN_SC_UNAUTHORIZED; + goto on_auth_failed; + } } else { - username_ok = PJ_TRUE; - password.slen = 0; + pj_assert(!"Invalid credential type"); + return PJ_EBUG; } - 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_UNAUTHORIZED, NULL, - &realm, &nonce, p_response); - } - return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_UNAUTHORIZED); - } /* Get NONCE attribute */ @@ -251,40 +366,33 @@ PJ_DEF(pj_status_t) pj_stun_authenticate_request(const pj_uint8_t *pkt, pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_NONCE, 0); /* Check for long term/short term requirements. */ - if (realm.slen != 0 && arealm == NULL) { + if (p_info->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_BAD_REQUEST, - "Missing REALM", - &realm, &nonce, p_response); - } - return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_BAD_REQUEST); + err_code = PJ_STUN_SC_BAD_REQUEST; + err_text = "Missing REALM"; + goto on_auth_failed; - } else if (realm.slen != 0 && arealm != NULL) { + } else if (p_info->realm.slen != 0 && arealm != NULL) { /* We want long term, and REALM is present */ /* NONCE must be present. */ - if (anonce == NULL && nonce.slen) { - if (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_BAD_REQUEST); + if (anonce == NULL && p_info->nonce.slen) { + err_code = PJ_STUN_SC_BAD_REQUEST; + err_text = "Missing NONCE"; + goto on_auth_failed; } /* Verify REALM matches */ - if (pj_stricmp(&arealm->value, &realm)) { + if (pj_stricmp(&arealm->value, &p_info->realm)) { /* REALM doesn't match */ - if (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_UNAUTHORIZED); + err_code = PJ_STUN_SC_UNAUTHORIZED; + err_text = "Invalid REALM"; + goto on_auth_failed; } /* Valid case, will validate the message integrity later */ - } else if (realm.slen == 0 && arealm != NULL) { + } else if (p_info->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. @@ -293,16 +401,14 @@ PJ_DEF(pj_status_t) pj_stun_authenticate_request(const pj_uint8_t *pkt, /* For now just accept the credential, anyway it will probably * cause wrong message integrity value later. */ - } else if (realm.slen==0 && arealm == NULL) { + } else if (p_info->realm.slen==0 && arealm == NULL) { /* Short term authentication is wanted, and one is supplied */ /* Application MAY request NONCE to be supplied */ - if (nonce.slen != 0) { - if (p_response) { - create_challenge(pool, msg, PJ_STUN_SC_UNAUTHORIZED, - "NONCE required", &realm, &nonce, p_response); - } - return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_UNAUTHORIZED); + if (p_info->nonce.slen != 0) { + err_code = PJ_STUN_SC_UNAUTHORIZED; + err_text = "NONCE required"; + goto on_auth_failed; } } @@ -321,31 +427,22 @@ PJ_DEF(pj_status_t) pj_stun_authenticate_request(const pj_uint8_t *pkt, } else if (cred->type == PJ_STUN_AUTH_CRED_DYNAMIC) { ok = PJ_TRUE; } else { - if (nonce.slen) { - ok = !pj_strcmp(&anonce->value, &nonce); + if (p_info->nonce.slen) { + ok = !pj_strcmp(&anonce->value, &p_info->nonce); } else { ok = PJ_TRUE; } } if (!ok) { - if (p_response) { - create_challenge(pool, msg, PJ_STUN_SC_STALE_NONCE, - NULL, &realm, &nonce, p_response); - } - if (auth_key) { - pj_stun_create_key(pool, auth_key, &realm, - &auser->value, &password); - } - return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_STALE_NONCE); + err_code = PJ_STUN_SC_STALE_NONCE; + goto on_auth_failed; } } - /* Calculate key */ - pj_stun_create_key(pool, &key, &realm, &auser->value, &password); - /* Now calculate HMAC of the message. */ - pj_hmac_sha1_init(&ctx, (pj_uint8_t*)key.ptr, key.slen); + pj_hmac_sha1_init(&ctx, (pj_uint8_t*)p_info->auth_key.ptr, + p_info->auth_key.slen); #if PJ_STUN_OLD_STYLE_MI_FINGERPRINT /* Pre rfc3489bis-06 style of calculation */ @@ -382,15 +479,20 @@ PJ_DEF(pj_status_t) pj_stun_authenticate_request(const pj_uint8_t *pkt, 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_UNAUTHORIZED, - NULL, &realm, &nonce, p_response); - } - return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_UNAUTHORIZED); + err_code = PJ_STUN_SC_UNAUTHORIZED; + err_text = "MESSAGE-INTEGRITY mismatch"; + goto on_auth_failed; } /* Everything looks okay! */ return PJ_SUCCESS; + +on_auth_failed: + if (p_response) { + create_challenge(pool, msg, err_code, err_text, + &p_info->realm, &p_info->nonce, p_response); + } + return PJ_STATUS_FROM_STUN_CODE(err_code); } @@ -425,11 +527,10 @@ 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) */ + + /* Due to the way this response is generated here, we can't really + * authenticate 420 (Unknown Attribute) response */ + case PJ_STUN_SC_UNKNOWN_ATTRIBUTE: return PJ_FALSE; default: return PJ_TRUE; -- cgit v1.2.3