blob: 06cb4992e96805440c47490156dd7be03a859636 [file] [log] [blame]
Alexandre Lision67916dd2014-01-24 13:33:04 -05001/* $Id$ */
2/*
3 * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
4 * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
5 *
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.
10 *
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.
15 *
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
19 */
20#include <pjnath/stun_auth.h>
21#include <pjnath/errno.h>
22#include <pjlib-util/hmac_sha1.h>
23#include <pjlib-util/md5.h>
24#include <pjlib-util/sha1.h>
25#include <pj/assert.h>
26#include <pj/log.h>
27#include <pj/pool.h>
28#include <pj/string.h>
29
30#define THIS_FILE "stun_auth.c"
31
32/* Duplicate credential */
33PJ_DEF(void) pj_stun_auth_cred_dup( pj_pool_t *pool,
34 pj_stun_auth_cred *dst,
35 const pj_stun_auth_cred *src)
36{
37 dst->type = src->type;
38
39 switch (src->type) {
40 case PJ_STUN_AUTH_CRED_STATIC:
41 pj_strdup(pool, &dst->data.static_cred.realm,
42 &src->data.static_cred.realm);
43 pj_strdup(pool, &dst->data.static_cred.username,
44 &src->data.static_cred.username);
45 dst->data.static_cred.data_type = src->data.static_cred.data_type;
46 pj_strdup(pool, &dst->data.static_cred.data,
47 &src->data.static_cred.data);
48 pj_strdup(pool, &dst->data.static_cred.nonce,
49 &src->data.static_cred.nonce);
50 break;
51 case PJ_STUN_AUTH_CRED_DYNAMIC:
52 pj_memcpy(&dst->data.dyn_cred, &src->data.dyn_cred,
53 sizeof(src->data.dyn_cred));
54 break;
55 }
56}
57
58
59/*
60 * Duplicate request credential.
61 */
62PJ_DEF(void) pj_stun_req_cred_info_dup( pj_pool_t *pool,
63 pj_stun_req_cred_info *dst,
64 const pj_stun_req_cred_info *src)
65{
66 pj_strdup(pool, &dst->realm, &src->realm);
67 pj_strdup(pool, &dst->username, &src->username);
68 pj_strdup(pool, &dst->nonce, &src->nonce);
69 pj_strdup(pool, &dst->auth_key, &src->auth_key);
70}
71
72
73/* Calculate HMAC-SHA1 key for long term credential, by getting
74 * MD5 digest of username, realm, and password.
75 */
76static void calc_md5_key(pj_uint8_t digest[16],
77 const pj_str_t *realm,
78 const pj_str_t *username,
79 const pj_str_t *passwd)
80{
81 /* The 16-byte key for MESSAGE-INTEGRITY HMAC is formed by taking
82 * the MD5 hash of the result of concatenating the following five
83 * fields: (1) The username, with any quotes and trailing nulls
84 * removed, (2) A single colon, (3) The realm, with any quotes and
85 * trailing nulls removed, (4) A single colon, and (5) The
86 * password, with any trailing nulls removed.
87 */
88 pj_md5_context ctx;
89 pj_str_t s;
90
91 pj_md5_init(&ctx);
92
93#define REMOVE_QUOTE(s) if (s.slen && *s.ptr=='"') \
94 s.ptr++, s.slen--; \
95 if (s.slen && s.ptr[s.slen-1]=='"') \
96 s.slen--;
97
98 /* Add username */
99 s = *username;
100 REMOVE_QUOTE(s);
101 pj_md5_update(&ctx, (pj_uint8_t*)s.ptr, (unsigned)s.slen);
102
103 /* Add single colon */
104 pj_md5_update(&ctx, (pj_uint8_t*)":", 1);
105
106 /* Add realm */
107 s = *realm;
108 REMOVE_QUOTE(s);
109 pj_md5_update(&ctx, (pj_uint8_t*)s.ptr, (unsigned)s.slen);
110
111#undef REMOVE_QUOTE
112
113 /* Another colon */
114 pj_md5_update(&ctx, (pj_uint8_t*)":", 1);
115
116 /* Add password */
117 pj_md5_update(&ctx, (pj_uint8_t*)passwd->ptr, (unsigned)passwd->slen);
118
119 /* Done */
120 pj_md5_final(&ctx, digest);
121}
122
123
124/*
125 * Create authentication key to be used for encoding the message with
126 * MESSAGE-INTEGRITY.
127 */
128PJ_DEF(void) pj_stun_create_key(pj_pool_t *pool,
129 pj_str_t *key,
130 const pj_str_t *realm,
131 const pj_str_t *username,
132 pj_stun_passwd_type data_type,
133 const pj_str_t *data)
134{
135 PJ_ASSERT_ON_FAIL(pool && key && username && data, return);
136
137 if (realm && realm->slen) {
138 if (data_type == PJ_STUN_PASSWD_PLAIN) {
139 key->ptr = (char*) pj_pool_alloc(pool, 16);
140 calc_md5_key((pj_uint8_t*)key->ptr, realm, username, data);
141 key->slen = 16;
142 } else {
143 pj_strdup(pool, key, data);
144 }
145 } else {
146 pj_assert(data_type == PJ_STUN_PASSWD_PLAIN);
147 pj_strdup(pool, key, data);
148 }
149}
150
151
152PJ_INLINE(pj_uint16_t) GET_VAL16(const pj_uint8_t *pdu, unsigned pos)
153{
154 return (pj_uint16_t) ((pdu[pos] << 8) + pdu[pos+1]);
155}
156
157
158PJ_INLINE(void) PUT_VAL16(pj_uint8_t *buf, unsigned pos, pj_uint16_t hval)
159{
160 buf[pos+0] = (pj_uint8_t) ((hval & 0xFF00) >> 8);
161 buf[pos+1] = (pj_uint8_t) ((hval & 0x00FF) >> 0);
162}
163
164
165/* Send 401 response */
166static pj_status_t create_challenge(pj_pool_t *pool,
167 const pj_stun_msg *msg,
168 int err_code,
169 const char *errstr,
170 const pj_str_t *realm,
171 const pj_str_t *nonce,
172 pj_stun_msg **p_response)
173{
174 pj_stun_msg *response;
175 pj_str_t tmp_nonce;
176 pj_str_t err_msg;
177 pj_status_t rc;
178
179 rc = pj_stun_msg_create_response(pool, msg, err_code,
180 (errstr?pj_cstr(&err_msg, errstr):NULL),
181 &response);
182 if (rc != PJ_SUCCESS)
183 return rc;
184
185 /* SHOULD NOT add REALM, NONCE, USERNAME, and M-I on 400 response */
186 if (err_code!=400 && realm && realm->slen) {
187 rc = pj_stun_msg_add_string_attr(pool, response,
188 PJ_STUN_ATTR_REALM,
189 realm);
190 if (rc != PJ_SUCCESS)
191 return rc;
192
193 /* long term must include nonce */
194 if (!nonce || nonce->slen == 0) {
195 tmp_nonce = pj_str("pjstun");
196 nonce = &tmp_nonce;
197 }
198 }
199
200 if (err_code!=400 && nonce && nonce->slen) {
201 rc = pj_stun_msg_add_string_attr(pool, response,
202 PJ_STUN_ATTR_NONCE,
203 nonce);
204 if (rc != PJ_SUCCESS)
205 return rc;
206 }
207
208 *p_response = response;
209
210 return PJ_SUCCESS;
211}
212
213
214/* Verify credential in the request */
215PJ_DEF(pj_status_t) pj_stun_authenticate_request(const pj_uint8_t *pkt,
216 unsigned pkt_len,
217 const pj_stun_msg *msg,
218 pj_stun_auth_cred *cred,
219 pj_pool_t *pool,
220 pj_stun_req_cred_info *p_info,
221 pj_stun_msg **p_response)
222{
223 pj_stun_req_cred_info tmp_info;
224 const pj_stun_msgint_attr *amsgi;
225 unsigned i, amsgi_pos;
226 pj_bool_t has_attr_beyond_mi;
227 const pj_stun_username_attr *auser;
228 const pj_stun_realm_attr *arealm;
229 const pj_stun_realm_attr *anonce;
230 pj_hmac_sha1_context ctx;
231 pj_uint8_t digest[PJ_SHA1_DIGEST_SIZE];
232 pj_stun_status err_code;
233 const char *err_text = NULL;
234 pj_status_t status;
235
236 /* msg and credential MUST be specified */
237 PJ_ASSERT_RETURN(pkt && pkt_len && msg && cred, PJ_EINVAL);
238
239 /* If p_response is specified, pool MUST be specified. */
240 PJ_ASSERT_RETURN(!p_response || pool, PJ_EINVAL);
241
242 if (p_response)
243 *p_response = NULL;
244
245 if (!PJ_STUN_IS_REQUEST(msg->hdr.type))
246 p_response = NULL;
247
248 if (p_info == NULL)
249 p_info = &tmp_info;
250
251 pj_bzero(p_info, sizeof(pj_stun_req_cred_info));
252
253 /* Get realm and nonce from credential */
254 p_info->realm.slen = p_info->nonce.slen = 0;
255 if (cred->type == PJ_STUN_AUTH_CRED_STATIC) {
256 p_info->realm = cred->data.static_cred.realm;
257 p_info->nonce = cred->data.static_cred.nonce;
258 } else if (cred->type == PJ_STUN_AUTH_CRED_DYNAMIC) {
259 status = cred->data.dyn_cred.get_auth(cred->data.dyn_cred.user_data,
260 pool, &p_info->realm,
261 &p_info->nonce);
262 if (status != PJ_SUCCESS)
263 return status;
264 } else {
265 pj_assert(!"Invalid credential type");
266 return PJ_EBUG;
267 }
268
269 /* Look for MESSAGE-INTEGRITY while counting the position */
270 amsgi_pos = 0;
271 has_attr_beyond_mi = PJ_FALSE;
272 amsgi = NULL;
273 for (i=0; i<msg->attr_count; ++i) {
274 if (msg->attr[i]->type == PJ_STUN_ATTR_MESSAGE_INTEGRITY) {
275 amsgi = (const pj_stun_msgint_attr*) msg->attr[i];
276 } else if (amsgi) {
277 has_attr_beyond_mi = PJ_TRUE;
278 break;
279 } else {
280 amsgi_pos += ((msg->attr[i]->length+3) & ~0x03) + 4;
281 }
282 }
283
284 if (amsgi == NULL) {
285 /* According to rfc3489bis-10 Sec 10.1.2/10.2.2, we should return 400
286 for short term, and 401 for long term.
287 The rule has been changed from rfc3489bis-06
288 */
289 err_code = p_info->realm.slen ? PJ_STUN_SC_UNAUTHORIZED :
290 PJ_STUN_SC_BAD_REQUEST;
291 goto on_auth_failed;
292 }
293
294 /* Next check that USERNAME is present */
295 auser = (const pj_stun_username_attr*)
296 pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_USERNAME, 0);
297 if (auser == NULL) {
298 /* According to rfc3489bis-10 Sec 10.1.2/10.2.2, we should return 400
299 for both short and long term, since M-I is present.
300 The rule has been changed from rfc3489bis-06
301 */
302 err_code = PJ_STUN_SC_BAD_REQUEST;
303 err_text = "Missing USERNAME";
304 goto on_auth_failed;
305 }
306
307 /* Get REALM, if any */
308 arealm = (const pj_stun_realm_attr*)
309 pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_REALM, 0);
310
311 /* Reject with 400 if we have long term credential and the request
312 * is missing REALM attribute.
313 */
314 if (p_info->realm.slen && arealm==NULL) {
315 err_code = PJ_STUN_SC_BAD_REQUEST;
316 err_text = "Missing REALM";
317 goto on_auth_failed;
318 }
319
320 /* Check if username match */
321 if (cred->type == PJ_STUN_AUTH_CRED_STATIC) {
322 pj_bool_t username_ok;
323 username_ok = !pj_strcmp(&auser->value,
324 &cred->data.static_cred.username);
325 if (username_ok) {
326 pj_strdup(pool, &p_info->username,
327 &cred->data.static_cred.username);
328 pj_stun_create_key(pool, &p_info->auth_key, &p_info->realm,
329 &auser->value, cred->data.static_cred.data_type,
330 &cred->data.static_cred.data);
331 } else {
332 /* Username mismatch */
333 /* According to rfc3489bis-10 Sec 10.1.2/10.2.2, we should
334 * return 401
335 */
336 err_code = PJ_STUN_SC_UNAUTHORIZED;
337 goto on_auth_failed;
338 }
339 } else if (cred->type == PJ_STUN_AUTH_CRED_DYNAMIC) {
340 pj_stun_passwd_type data_type = PJ_STUN_PASSWD_PLAIN;
341 pj_str_t password;
342 pj_status_t rc;
343
344 rc = cred->data.dyn_cred.get_password(msg,
345 cred->data.dyn_cred.user_data,
346 (arealm?&arealm->value:NULL),
347 &auser->value, pool,
348 &data_type, &password);
349 if (rc == PJ_SUCCESS) {
350 pj_strdup(pool, &p_info->username, &auser->value);
351 pj_stun_create_key(pool, &p_info->auth_key,
352 (arealm?&arealm->value:NULL), &auser->value,
353 data_type, &password);
354 } else {
355 err_code = PJ_STUN_SC_UNAUTHORIZED;
356 goto on_auth_failed;
357 }
358 } else {
359 pj_assert(!"Invalid credential type");
360 return PJ_EBUG;
361 }
362
363
364
365 /* Get NONCE attribute */
366 anonce = (pj_stun_nonce_attr*)
367 pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_NONCE, 0);
368
369 /* Check for long term/short term requirements. */
370 if (p_info->realm.slen != 0 && arealm == NULL) {
371 /* Long term credential is required and REALM is not present */
372 err_code = PJ_STUN_SC_BAD_REQUEST;
373 err_text = "Missing REALM";
374 goto on_auth_failed;
375
376 } else if (p_info->realm.slen != 0 && arealm != NULL) {
377 /* We want long term, and REALM is present */
378
379 /* NONCE must be present. */
380 if (anonce == NULL && p_info->nonce.slen) {
381 err_code = PJ_STUN_SC_BAD_REQUEST;
382 err_text = "Missing NONCE";
383 goto on_auth_failed;
384 }
385
386 /* Verify REALM matches */
387 if (pj_stricmp(&arealm->value, &p_info->realm)) {
388 /* REALM doesn't match */
389 err_code = PJ_STUN_SC_UNAUTHORIZED;
390 err_text = "Invalid REALM";
391 goto on_auth_failed;
392 }
393
394 /* Valid case, will validate the message integrity later */
395
396 } else if (p_info->realm.slen == 0 && arealm != NULL) {
397 /* We want to use short term credential, but client uses long
398 * term credential. The draft doesn't mention anything about
399 * switching between long term and short term.
400 */
401
402 /* For now just accept the credential, anyway it will probably
403 * cause wrong message integrity value later.
404 */
405 } else if (p_info->realm.slen==0 && arealm == NULL) {
406 /* Short term authentication is wanted, and one is supplied */
407
408 /* Application MAY request NONCE to be supplied */
409 if (p_info->nonce.slen != 0) {
410 err_code = PJ_STUN_SC_UNAUTHORIZED;
411 err_text = "NONCE required";
412 goto on_auth_failed;
413 }
414 }
415
416 /* If NONCE is present, validate it */
417 if (anonce) {
418 pj_bool_t ok;
419
420 if (cred->type == PJ_STUN_AUTH_CRED_DYNAMIC &&
421 cred->data.dyn_cred.verify_nonce != NULL)
422 {
423 ok=cred->data.dyn_cred.verify_nonce(msg,
424 cred->data.dyn_cred.user_data,
425 (arealm?&arealm->value:NULL),
426 &auser->value,
427 &anonce->value);
428 } else if (cred->type == PJ_STUN_AUTH_CRED_DYNAMIC) {
429 ok = PJ_TRUE;
430 } else {
431 if (p_info->nonce.slen) {
432 ok = !pj_strcmp(&anonce->value, &p_info->nonce);
433 } else {
434 ok = PJ_TRUE;
435 }
436 }
437
438 if (!ok) {
439 err_code = PJ_STUN_SC_STALE_NONCE;
440 goto on_auth_failed;
441 }
442 }
443
444 /* Now calculate HMAC of the message. */
445 pj_hmac_sha1_init(&ctx, (pj_uint8_t*)p_info->auth_key.ptr,
446 (unsigned)p_info->auth_key.slen);
447
448#if PJ_STUN_OLD_STYLE_MI_FINGERPRINT
449 /* Pre rfc3489bis-06 style of calculation */
450 pj_hmac_sha1_update(&ctx, pkt, 20);
451#else
452 /* First calculate HMAC for the header.
453 * The calculation is different depending on whether FINGERPRINT attribute
454 * is present in the message.
455 */
456 if (has_attr_beyond_mi) {
457 pj_uint8_t hdr_copy[20];
458 pj_memcpy(hdr_copy, pkt, 20);
459 PUT_VAL16(hdr_copy, 2, (pj_uint16_t)(amsgi_pos + 24));
460 pj_hmac_sha1_update(&ctx, hdr_copy, 20);
461 } else {
462 pj_hmac_sha1_update(&ctx, pkt, 20);
463 }
464#endif /* PJ_STUN_OLD_STYLE_MI_FINGERPRINT */
465
466 /* Now update with the message body */
467 pj_hmac_sha1_update(&ctx, pkt+20, amsgi_pos);
468#if PJ_STUN_OLD_STYLE_MI_FINGERPRINT
469 // This is no longer necessary as per rfc3489bis-08
470 if ((amsgi_pos+20) & 0x3F) {
471 pj_uint8_t zeroes[64];
472 pj_bzero(zeroes, sizeof(zeroes));
473 pj_hmac_sha1_update(&ctx, zeroes, 64-((amsgi_pos+20) & 0x3F));
474 }
475#endif
476 pj_hmac_sha1_final(&ctx, digest);
477
478
479 /* Compare HMACs */
480 if (pj_memcmp(amsgi->hmac, digest, 20)) {
481 /* HMAC value mismatch */
482 /* According to rfc3489bis-10 Sec 10.1.2 we should return 401 */
483 err_code = PJ_STUN_SC_UNAUTHORIZED;
484 err_text = "MESSAGE-INTEGRITY mismatch";
485 goto on_auth_failed;
486 }
487
488 /* Everything looks okay! */
489 return PJ_SUCCESS;
490
491on_auth_failed:
492 if (p_response) {
493 create_challenge(pool, msg, err_code, err_text,
494 &p_info->realm, &p_info->nonce, p_response);
495 }
496 return PJ_STATUS_FROM_STUN_CODE(err_code);
497}
498
499
500/* Determine if STUN message can be authenticated */
501PJ_DEF(pj_bool_t) pj_stun_auth_valid_for_msg(const pj_stun_msg *msg)
502{
503 unsigned msg_type = msg->hdr.type;
504 const pj_stun_errcode_attr *err_attr;
505
506 /* STUN requests and success response can be authenticated */
507 if (!PJ_STUN_IS_ERROR_RESPONSE(msg_type) &&
508 !PJ_STUN_IS_INDICATION(msg_type))
509 {
510 return PJ_TRUE;
511 }
512
513 /* STUN Indication cannot be authenticated */
514 if (PJ_STUN_IS_INDICATION(msg_type))
515 return PJ_FALSE;
516
517 /* Authentication for STUN error responses depend on the error
518 * code.
519 */
520 err_attr = (const pj_stun_errcode_attr*)
521 pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_ERROR_CODE, 0);
522 if (err_attr == NULL) {
523 PJ_LOG(4,(THIS_FILE, "STUN error code attribute not present in "
524 "error response"));
525 return PJ_TRUE;
526 }
527
528 switch (err_attr->err_code) {
529 case PJ_STUN_SC_BAD_REQUEST: /* 400 (Bad Request) */
530 case PJ_STUN_SC_UNAUTHORIZED: /* 401 (Unauthorized) */
531 case PJ_STUN_SC_STALE_NONCE: /* 438 (Stale Nonce) */
532
533 /* Due to the way this response is generated here, we can't really
534 * authenticate 420 (Unknown Attribute) response */
535 case PJ_STUN_SC_UNKNOWN_ATTRIBUTE:
536 return PJ_FALSE;
537 default:
538 return PJ_TRUE;
539 }
540}
541
542
543/* Authenticate MESSAGE-INTEGRITY in the response */
544PJ_DEF(pj_status_t) pj_stun_authenticate_response(const pj_uint8_t *pkt,
545 unsigned pkt_len,
546 const pj_stun_msg *msg,
547 const pj_str_t *key)
548{
549 const pj_stun_msgint_attr *amsgi;
550 unsigned i, amsgi_pos;
551 pj_bool_t has_attr_beyond_mi;
552 pj_hmac_sha1_context ctx;
553 pj_uint8_t digest[PJ_SHA1_DIGEST_SIZE];
554
555 PJ_ASSERT_RETURN(pkt && pkt_len && msg && key, PJ_EINVAL);
556
557 /* First check that MESSAGE-INTEGRITY is present */
558 amsgi = (const pj_stun_msgint_attr*)
559 pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_MESSAGE_INTEGRITY, 0);
560 if (amsgi == NULL) {
561 return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_UNAUTHORIZED);
562 }
563
564
565 /* Check that message length is valid */
566 if (msg->hdr.length < 24) {
567 return PJNATH_EINSTUNMSGLEN;
568 }
569
570 /* Look for MESSAGE-INTEGRITY while counting the position */
571 amsgi_pos = 0;
572 has_attr_beyond_mi = PJ_FALSE;
573 amsgi = NULL;
574 for (i=0; i<msg->attr_count; ++i) {
575 if (msg->attr[i]->type == PJ_STUN_ATTR_MESSAGE_INTEGRITY) {
576 amsgi = (const pj_stun_msgint_attr*) msg->attr[i];
577 } else if (amsgi) {
578 has_attr_beyond_mi = PJ_TRUE;
579 break;
580 } else {
581 amsgi_pos += ((msg->attr[i]->length+3) & ~0x03) + 4;
582 }
583 }
584
585 if (amsgi == NULL) {
586 return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_BAD_REQUEST);
587 }
588
589 /* Now calculate HMAC of the message. */
590 pj_hmac_sha1_init(&ctx, (pj_uint8_t*)key->ptr, (unsigned)key->slen);
591
592#if PJ_STUN_OLD_STYLE_MI_FINGERPRINT
593 /* Pre rfc3489bis-06 style of calculation */
594 pj_hmac_sha1_update(&ctx, pkt, 20);
595#else
596 /* First calculate HMAC for the header.
597 * The calculation is different depending on whether FINGERPRINT attribute
598 * is present in the message.
599 */
600 if (has_attr_beyond_mi) {
601 pj_uint8_t hdr_copy[20];
602 pj_memcpy(hdr_copy, pkt, 20);
603 PUT_VAL16(hdr_copy, 2, (pj_uint16_t)(amsgi_pos+24));
604 pj_hmac_sha1_update(&ctx, hdr_copy, 20);
605 } else {
606 pj_hmac_sha1_update(&ctx, pkt, 20);
607 }
608#endif /* PJ_STUN_OLD_STYLE_MI_FINGERPRINT */
609
610 /* Now update with the message body */
611 pj_hmac_sha1_update(&ctx, pkt+20, amsgi_pos);
612#if PJ_STUN_OLD_STYLE_MI_FINGERPRINT
613 // This is no longer necessary as per rfc3489bis-08
614 if ((amsgi_pos+20) & 0x3F) {
615 pj_uint8_t zeroes[64];
616 pj_bzero(zeroes, sizeof(zeroes));
617 pj_hmac_sha1_update(&ctx, zeroes, 64-((amsgi_pos+20) & 0x3F));
618 }
619#endif
620 pj_hmac_sha1_final(&ctx, digest);
621
622 /* Compare HMACs */
623 if (pj_memcmp(amsgi->hmac, digest, 20)) {
624 /* HMAC value mismatch */
625 return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_UNAUTHORIZED);
626 }
627
628 /* Everything looks okay! */
629 return PJ_SUCCESS;
630}
631