1
/* $Id: stun_auth.c 2724 2009-05-29 13:04:03Z bennylp $ */
3
* Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com)
4
* Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
6
* This program is free software; you can redistribute it and/or modify
7
* it under the terms of the GNU General Public License as published by
8
* the Free Software Foundation; either version 2 of the License, or
9
* (at your option) any later version.
11
* This program is distributed in the hope that it will be useful,
12
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
* GNU General Public License for more details.
16
* You should have received a copy of the GNU General Public License
17
* along with this program; if not, write to the Free Software
18
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20
* Additional permission under GNU GPL version 3 section 7:
22
* If you modify this program, or any covered work, by linking or
23
* combining it with the OpenSSL project's OpenSSL library (or a
24
* modified version of that library), containing parts covered by the
25
* terms of the OpenSSL or SSLeay licenses, Teluu Inc. (http://www.teluu.com)
26
* grants you additional permission to convey the resulting work.
27
* Corresponding Source for a non-source form of such a combination
28
* shall include the source code for the parts of OpenSSL used as well
29
* as that of the covered work.
31
#include <pjnath/stun_auth.h>
32
#include <pjnath/errno.h>
33
#include <pjlib-util/hmac_sha1.h>
34
#include <pjlib-util/md5.h>
35
#include <pjlib-util/sha1.h>
36
#include <pj/assert.h>
39
#include <pj/string.h>
41
#define THIS_FILE "stun_auth.c"
43
/* Duplicate credential */
44
PJ_DEF(void) pj_stun_auth_cred_dup( pj_pool_t *pool,
45
pj_stun_auth_cred *dst,
46
const pj_stun_auth_cred *src)
48
dst->type = src->type;
51
case PJ_STUN_AUTH_CRED_STATIC:
52
pj_strdup(pool, &dst->data.static_cred.realm,
53
&src->data.static_cred.realm);
54
pj_strdup(pool, &dst->data.static_cred.username,
55
&src->data.static_cred.username);
56
dst->data.static_cred.data_type = src->data.static_cred.data_type;
57
pj_strdup(pool, &dst->data.static_cred.data,
58
&src->data.static_cred.data);
59
pj_strdup(pool, &dst->data.static_cred.nonce,
60
&src->data.static_cred.nonce);
62
case PJ_STUN_AUTH_CRED_DYNAMIC:
63
pj_memcpy(&dst->data.dyn_cred, &src->data.dyn_cred,
64
sizeof(src->data.dyn_cred));
71
* Duplicate request credential.
73
PJ_DEF(void) pj_stun_req_cred_info_dup( pj_pool_t *pool,
74
pj_stun_req_cred_info *dst,
75
const pj_stun_req_cred_info *src)
77
pj_strdup(pool, &dst->realm, &src->realm);
78
pj_strdup(pool, &dst->username, &src->username);
79
pj_strdup(pool, &dst->nonce, &src->nonce);
80
pj_strdup(pool, &dst->auth_key, &src->auth_key);
84
/* Calculate HMAC-SHA1 key for long term credential, by getting
85
* MD5 digest of username, realm, and password.
87
static void calc_md5_key(pj_uint8_t digest[16],
88
const pj_str_t *realm,
89
const pj_str_t *username,
90
const pj_str_t *passwd)
92
/* The 16-byte key for MESSAGE-INTEGRITY HMAC is formed by taking
93
* the MD5 hash of the result of concatenating the following five
94
* fields: (1) The username, with any quotes and trailing nulls
95
* removed, (2) A single colon, (3) The realm, with any quotes and
96
* trailing nulls removed, (4) A single colon, and (5) The
97
* password, with any trailing nulls removed.
104
#define REMOVE_QUOTE(s) if (s.slen && *s.ptr=='"') \
106
if (s.slen && s.ptr[s.slen-1]=='"') \
112
pj_md5_update(&ctx, (pj_uint8_t*)s.ptr, s.slen);
114
/* Add single colon */
115
pj_md5_update(&ctx, (pj_uint8_t*)":", 1);
120
pj_md5_update(&ctx, (pj_uint8_t*)s.ptr, s.slen);
125
pj_md5_update(&ctx, (pj_uint8_t*)":", 1);
128
pj_md5_update(&ctx, (pj_uint8_t*)passwd->ptr, passwd->slen);
131
pj_md5_final(&ctx, digest);
136
* Create authentication key to be used for encoding the message with
139
PJ_DEF(void) pj_stun_create_key(pj_pool_t *pool,
141
const pj_str_t *realm,
142
const pj_str_t *username,
143
pj_stun_passwd_type data_type,
144
const pj_str_t *data)
146
PJ_ASSERT_ON_FAIL(pool && key && username && data, return);
148
if (realm && realm->slen) {
149
if (data_type == PJ_STUN_PASSWD_PLAIN) {
150
key->ptr = (char*) pj_pool_alloc(pool, 16);
151
calc_md5_key((pj_uint8_t*)key->ptr, realm, username, data);
154
pj_strdup(pool, key, data);
157
pj_assert(data_type == PJ_STUN_PASSWD_PLAIN);
158
pj_strdup(pool, key, data);
163
PJ_INLINE(pj_uint16_t) GET_VAL16(const pj_uint8_t *pdu, unsigned pos)
165
return (pj_uint16_t) ((pdu[pos] << 8) + pdu[pos+1]);
169
PJ_INLINE(void) PUT_VAL16(pj_uint8_t *buf, unsigned pos, pj_uint16_t hval)
171
buf[pos+0] = (pj_uint8_t) ((hval & 0xFF00) >> 8);
172
buf[pos+1] = (pj_uint8_t) ((hval & 0x00FF) >> 0);
176
/* Send 401 response */
177
static pj_status_t create_challenge(pj_pool_t *pool,
178
const pj_stun_msg *msg,
181
const pj_str_t *realm,
182
const pj_str_t *nonce,
183
pj_stun_msg **p_response)
185
pj_stun_msg *response;
190
rc = pj_stun_msg_create_response(pool, msg, err_code,
191
(errstr?pj_cstr(&err_msg, errstr):NULL),
193
if (rc != PJ_SUCCESS)
196
/* SHOULD NOT add REALM, NONCE, USERNAME, and M-I on 400 response */
197
if (err_code!=400 && realm && realm->slen) {
198
rc = pj_stun_msg_add_string_attr(pool, response,
201
if (rc != PJ_SUCCESS)
204
/* long term must include nonce */
205
if (!nonce || nonce->slen == 0) {
206
tmp_nonce = pj_str("pjstun");
211
if (err_code!=400 && nonce && nonce->slen) {
212
rc = pj_stun_msg_add_string_attr(pool, response,
215
if (rc != PJ_SUCCESS)
219
*p_response = response;
225
/* Verify credential in the request */
226
PJ_DEF(pj_status_t) pj_stun_authenticate_request(const pj_uint8_t *pkt,
228
const pj_stun_msg *msg,
229
pj_stun_auth_cred *cred,
231
pj_stun_req_cred_info *p_info,
232
pj_stun_msg **p_response)
234
pj_stun_req_cred_info tmp_info;
235
const pj_stun_msgint_attr *amsgi;
236
unsigned i, amsgi_pos;
237
pj_bool_t has_attr_beyond_mi;
238
const pj_stun_username_attr *auser;
239
const pj_stun_realm_attr *arealm;
240
const pj_stun_realm_attr *anonce;
241
pj_hmac_sha1_context ctx;
242
pj_uint8_t digest[PJ_SHA1_DIGEST_SIZE];
243
pj_stun_status err_code;
244
const char *err_text = NULL;
247
/* msg and credential MUST be specified */
248
PJ_ASSERT_RETURN(pkt && pkt_len && msg && cred, PJ_EINVAL);
250
/* If p_response is specified, pool MUST be specified. */
251
PJ_ASSERT_RETURN(!p_response || pool, PJ_EINVAL);
256
if (!PJ_STUN_IS_REQUEST(msg->hdr.type))
262
pj_bzero(p_info, sizeof(pj_stun_req_cred_info));
264
/* Get realm and nonce from credential */
265
p_info->realm.slen = p_info->nonce.slen = 0;
266
if (cred->type == PJ_STUN_AUTH_CRED_STATIC) {
267
p_info->realm = cred->data.static_cred.realm;
268
p_info->nonce = cred->data.static_cred.nonce;
269
} else if (cred->type == PJ_STUN_AUTH_CRED_DYNAMIC) {
270
status = cred->data.dyn_cred.get_auth(cred->data.dyn_cred.user_data,
271
pool, &p_info->realm,
273
if (status != PJ_SUCCESS)
276
pj_assert(!"Invalid credential type");
280
/* Look for MESSAGE-INTEGRITY while counting the position */
282
has_attr_beyond_mi = PJ_FALSE;
284
for (i=0; i<msg->attr_count; ++i) {
285
if (msg->attr[i]->type == PJ_STUN_ATTR_MESSAGE_INTEGRITY) {
286
amsgi = (const pj_stun_msgint_attr*) msg->attr[i];
288
has_attr_beyond_mi = PJ_TRUE;
291
amsgi_pos += ((msg->attr[i]->length+3) & ~0x03) + 4;
296
/* According to rfc3489bis-10 Sec 10.1.2/10.2.2, we should return 400
297
for short term, and 401 for long term.
298
The rule has been changed from rfc3489bis-06
300
err_code = p_info->realm.slen ? PJ_STUN_SC_UNAUTHORIZED :
301
PJ_STUN_SC_BAD_REQUEST;
305
/* Next check that USERNAME is present */
306
auser = (const pj_stun_username_attr*)
307
pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_USERNAME, 0);
309
/* According to rfc3489bis-10 Sec 10.1.2/10.2.2, we should return 400
310
for both short and long term, since M-I is present.
311
The rule has been changed from rfc3489bis-06
313
err_code = PJ_STUN_SC_BAD_REQUEST;
314
err_text = "Missing USERNAME";
318
/* Get REALM, if any */
319
arealm = (const pj_stun_realm_attr*)
320
pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_REALM, 0);
322
/* Reject with 400 if we have long term credential and the request
323
* is missing REALM attribute.
325
if (p_info->realm.slen && arealm==NULL) {
326
err_code = PJ_STUN_SC_BAD_REQUEST;
327
err_text = "Missing REALM";
331
/* Check if username match */
332
if (cred->type == PJ_STUN_AUTH_CRED_STATIC) {
333
pj_bool_t username_ok;
334
username_ok = !pj_strcmp(&auser->value,
335
&cred->data.static_cred.username);
337
pj_strdup(pool, &p_info->username,
338
&cred->data.static_cred.username);
339
pj_stun_create_key(pool, &p_info->auth_key, &p_info->realm,
340
&auser->value, cred->data.static_cred.data_type,
341
&cred->data.static_cred.data);
343
/* Username mismatch */
344
/* According to rfc3489bis-10 Sec 10.1.2/10.2.2, we should
347
err_code = PJ_STUN_SC_UNAUTHORIZED;
350
} else if (cred->type == PJ_STUN_AUTH_CRED_DYNAMIC) {
351
pj_stun_passwd_type data_type = PJ_STUN_PASSWD_PLAIN;
355
rc = cred->data.dyn_cred.get_password(msg,
356
cred->data.dyn_cred.user_data,
357
(arealm?&arealm->value:NULL),
359
&data_type, &password);
360
if (rc == PJ_SUCCESS) {
361
pj_strdup(pool, &p_info->username, &auser->value);
362
pj_stun_create_key(pool, &p_info->auth_key,
363
(arealm?&arealm->value:NULL), &auser->value,
364
data_type, &password);
366
err_code = PJ_STUN_SC_UNAUTHORIZED;
370
pj_assert(!"Invalid credential type");
376
/* Get NONCE attribute */
377
anonce = (pj_stun_nonce_attr*)
378
pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_NONCE, 0);
380
/* Check for long term/short term requirements. */
381
if (p_info->realm.slen != 0 && arealm == NULL) {
382
/* Long term credential is required and REALM is not present */
383
err_code = PJ_STUN_SC_BAD_REQUEST;
384
err_text = "Missing REALM";
387
} else if (p_info->realm.slen != 0 && arealm != NULL) {
388
/* We want long term, and REALM is present */
390
/* NONCE must be present. */
391
if (anonce == NULL && p_info->nonce.slen) {
392
err_code = PJ_STUN_SC_BAD_REQUEST;
393
err_text = "Missing NONCE";
397
/* Verify REALM matches */
398
if (pj_stricmp(&arealm->value, &p_info->realm)) {
399
/* REALM doesn't match */
400
err_code = PJ_STUN_SC_UNAUTHORIZED;
401
err_text = "Invalid REALM";
405
/* Valid case, will validate the message integrity later */
407
} else if (p_info->realm.slen == 0 && arealm != NULL) {
408
/* We want to use short term credential, but client uses long
409
* term credential. The draft doesn't mention anything about
410
* switching between long term and short term.
413
/* For now just accept the credential, anyway it will probably
414
* cause wrong message integrity value later.
416
} else if (p_info->realm.slen==0 && arealm == NULL) {
417
/* Short term authentication is wanted, and one is supplied */
419
/* Application MAY request NONCE to be supplied */
420
if (p_info->nonce.slen != 0) {
421
err_code = PJ_STUN_SC_UNAUTHORIZED;
422
err_text = "NONCE required";
427
/* If NONCE is present, validate it */
431
if (cred->type == PJ_STUN_AUTH_CRED_DYNAMIC &&
432
cred->data.dyn_cred.verify_nonce != NULL)
434
ok=cred->data.dyn_cred.verify_nonce(msg,
435
cred->data.dyn_cred.user_data,
436
(arealm?&arealm->value:NULL),
439
} else if (cred->type == PJ_STUN_AUTH_CRED_DYNAMIC) {
442
if (p_info->nonce.slen) {
443
ok = !pj_strcmp(&anonce->value, &p_info->nonce);
450
err_code = PJ_STUN_SC_STALE_NONCE;
455
/* Now calculate HMAC of the message. */
456
pj_hmac_sha1_init(&ctx, (pj_uint8_t*)p_info->auth_key.ptr,
457
p_info->auth_key.slen);
459
#if PJ_STUN_OLD_STYLE_MI_FINGERPRINT
460
/* Pre rfc3489bis-06 style of calculation */
461
pj_hmac_sha1_update(&ctx, pkt, 20);
463
/* First calculate HMAC for the header.
464
* The calculation is different depending on whether FINGERPRINT attribute
465
* is present in the message.
467
if (has_attr_beyond_mi) {
468
pj_uint8_t hdr_copy[20];
469
pj_memcpy(hdr_copy, pkt, 20);
470
PUT_VAL16(hdr_copy, 2, (pj_uint16_t)(amsgi_pos + 24));
471
pj_hmac_sha1_update(&ctx, hdr_copy, 20);
473
pj_hmac_sha1_update(&ctx, pkt, 20);
475
#endif /* PJ_STUN_OLD_STYLE_MI_FINGERPRINT */
477
/* Now update with the message body */
478
pj_hmac_sha1_update(&ctx, pkt+20, amsgi_pos);
479
#if PJ_STUN_OLD_STYLE_MI_FINGERPRINT
480
// This is no longer necessary as per rfc3489bis-08
481
if ((amsgi_pos+20) & 0x3F) {
482
pj_uint8_t zeroes[64];
483
pj_bzero(zeroes, sizeof(zeroes));
484
pj_hmac_sha1_update(&ctx, zeroes, 64-((amsgi_pos+20) & 0x3F));
487
pj_hmac_sha1_final(&ctx, digest);
491
if (pj_memcmp(amsgi->hmac, digest, 20)) {
492
/* HMAC value mismatch */
493
/* According to rfc3489bis-10 Sec 10.1.2 we should return 401 */
494
err_code = PJ_STUN_SC_UNAUTHORIZED;
495
err_text = "MESSAGE-INTEGRITY mismatch";
499
/* Everything looks okay! */
504
create_challenge(pool, msg, err_code, err_text,
505
&p_info->realm, &p_info->nonce, p_response);
507
return PJ_STATUS_FROM_STUN_CODE(err_code);
511
/* Determine if STUN message can be authenticated */
512
PJ_DEF(pj_bool_t) pj_stun_auth_valid_for_msg(const pj_stun_msg *msg)
514
unsigned msg_type = msg->hdr.type;
515
const pj_stun_errcode_attr *err_attr;
517
/* STUN requests and success response can be authenticated */
518
if (!PJ_STUN_IS_ERROR_RESPONSE(msg_type) &&
519
!PJ_STUN_IS_INDICATION(msg_type))
524
/* STUN Indication cannot be authenticated */
525
if (PJ_STUN_IS_INDICATION(msg_type))
528
/* Authentication for STUN error responses depend on the error
531
err_attr = (const pj_stun_errcode_attr*)
532
pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_ERROR_CODE, 0);
533
if (err_attr == NULL) {
534
PJ_LOG(4,(THIS_FILE, "STUN error code attribute not present in "
539
switch (err_attr->err_code) {
540
case PJ_STUN_SC_BAD_REQUEST: /* 400 (Bad Request) */
541
case PJ_STUN_SC_UNAUTHORIZED: /* 401 (Unauthorized) */
542
case PJ_STUN_SC_STALE_NONCE: /* 438 (Stale Nonce) */
544
/* Due to the way this response is generated here, we can't really
545
* authenticate 420 (Unknown Attribute) response */
546
case PJ_STUN_SC_UNKNOWN_ATTRIBUTE:
554
/* Authenticate MESSAGE-INTEGRITY in the response */
555
PJ_DEF(pj_status_t) pj_stun_authenticate_response(const pj_uint8_t *pkt,
557
const pj_stun_msg *msg,
560
const pj_stun_msgint_attr *amsgi;
561
unsigned i, amsgi_pos;
562
pj_bool_t has_attr_beyond_mi;
563
pj_hmac_sha1_context ctx;
564
pj_uint8_t digest[PJ_SHA1_DIGEST_SIZE];
566
PJ_ASSERT_RETURN(pkt && pkt_len && msg && key, PJ_EINVAL);
568
/* First check that MESSAGE-INTEGRITY is present */
569
amsgi = (const pj_stun_msgint_attr*)
570
pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_MESSAGE_INTEGRITY, 0);
572
return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_UNAUTHORIZED);
576
/* Check that message length is valid */
577
if (msg->hdr.length < 24) {
578
return PJNATH_EINSTUNMSGLEN;
581
/* Look for MESSAGE-INTEGRITY while counting the position */
583
has_attr_beyond_mi = PJ_FALSE;
585
for (i=0; i<msg->attr_count; ++i) {
586
if (msg->attr[i]->type == PJ_STUN_ATTR_MESSAGE_INTEGRITY) {
587
amsgi = (const pj_stun_msgint_attr*) msg->attr[i];
589
has_attr_beyond_mi = PJ_TRUE;
592
amsgi_pos += ((msg->attr[i]->length+3) & ~0x03) + 4;
597
return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_BAD_REQUEST);
600
/* Now calculate HMAC of the message. */
601
pj_hmac_sha1_init(&ctx, (pj_uint8_t*)key->ptr, key->slen);
603
#if PJ_STUN_OLD_STYLE_MI_FINGERPRINT
604
/* Pre rfc3489bis-06 style of calculation */
605
pj_hmac_sha1_update(&ctx, pkt, 20);
607
/* First calculate HMAC for the header.
608
* The calculation is different depending on whether FINGERPRINT attribute
609
* is present in the message.
611
if (has_attr_beyond_mi) {
612
pj_uint8_t hdr_copy[20];
613
pj_memcpy(hdr_copy, pkt, 20);
614
PUT_VAL16(hdr_copy, 2, (pj_uint16_t)(amsgi_pos+24));
615
pj_hmac_sha1_update(&ctx, hdr_copy, 20);
617
pj_hmac_sha1_update(&ctx, pkt, 20);
619
#endif /* PJ_STUN_OLD_STYLE_MI_FINGERPRINT */
621
/* Now update with the message body */
622
pj_hmac_sha1_update(&ctx, pkt+20, amsgi_pos);
623
#if PJ_STUN_OLD_STYLE_MI_FINGERPRINT
624
// This is no longer necessary as per rfc3489bis-08
625
if ((amsgi_pos+20) & 0x3F) {
626
pj_uint8_t zeroes[64];
627
pj_bzero(zeroes, sizeof(zeroes));
628
pj_hmac_sha1_update(&ctx, zeroes, 64-((amsgi_pos+20) & 0x3F));
631
pj_hmac_sha1_final(&ctx, digest);
634
if (pj_memcmp(amsgi->hmac, digest, 20)) {
635
/* HMAC value mismatch */
636
return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_UNAUTHORIZED);
639
/* Everything looks okay! */