~ubuntu-branches/ubuntu/wily/sflphone/wily

« back to all changes in this revision

Viewing changes to sflphone-common/libs/pjproject/pjnath/src/pjnath/stun_auth.c

  • Committer: Package Import Robot
  • Author(s): Francois Marier
  • Date: 2011-11-25 13:24:12 UTC
  • mfrom: (4.1.10 sid)
  • Revision ID: package-import@ubuntu.com-20111125132412-dc4qvhyosk74cd42
Tags: 1.0.1-4
Don't assume that arch:all packages will get built (closes: #649726)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
/* $Id: stun_auth.c 2724 2009-05-29 13:04:03Z bennylp $ */
2
 
/* 
3
 
 * Copyright (C) 2008-2009 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
 
 *  Additional permission under GNU GPL version 3 section 7:
21
 
 *
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.
30
 
 */
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>
37
 
#include <pj/log.h>
38
 
#include <pj/pool.h>
39
 
#include <pj/string.h>
40
 
 
41
 
#define THIS_FILE   "stun_auth.c"
42
 
 
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)
47
 
{
48
 
    dst->type = src->type;
49
 
 
50
 
    switch (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);
61
 
        break;
62
 
    case PJ_STUN_AUTH_CRED_DYNAMIC:
63
 
        pj_memcpy(&dst->data.dyn_cred, &src->data.dyn_cred, 
64
 
                  sizeof(src->data.dyn_cred));
65
 
        break;
66
 
    }
67
 
}
68
 
 
69
 
 
70
 
/*
71
 
 * Duplicate request credential.
72
 
 */
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)
76
 
{
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);
81
 
}
82
 
 
83
 
 
84
 
/* Calculate HMAC-SHA1 key for long term credential, by getting
85
 
 * MD5 digest of username, realm, and password. 
86
 
 */
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)
91
 
{
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.
98
 
     */
99
 
    pj_md5_context ctx;
100
 
    pj_str_t s;
101
 
 
102
 
    pj_md5_init(&ctx);
103
 
 
104
 
#define REMOVE_QUOTE(s) if (s.slen && *s.ptr=='"') \
105
 
                            s.ptr++, s.slen--; \
106
 
                        if (s.slen && s.ptr[s.slen-1]=='"') \
107
 
                            s.slen--;
108
 
 
109
 
    /* Add username */
110
 
    s = *username;
111
 
    REMOVE_QUOTE(s);
112
 
    pj_md5_update(&ctx, (pj_uint8_t*)s.ptr, s.slen);
113
 
 
114
 
    /* Add single colon */
115
 
    pj_md5_update(&ctx, (pj_uint8_t*)":", 1);
116
 
 
117
 
    /* Add realm */
118
 
    s = *realm;
119
 
    REMOVE_QUOTE(s);
120
 
    pj_md5_update(&ctx, (pj_uint8_t*)s.ptr, s.slen);
121
 
 
122
 
#undef REMOVE_QUOTE
123
 
 
124
 
    /* Another colon */
125
 
    pj_md5_update(&ctx, (pj_uint8_t*)":", 1);
126
 
 
127
 
    /* Add password */
128
 
    pj_md5_update(&ctx, (pj_uint8_t*)passwd->ptr, passwd->slen);
129
 
 
130
 
    /* Done */
131
 
    pj_md5_final(&ctx, digest);
132
 
}
133
 
 
134
 
 
135
 
/*
136
 
 * Create authentication key to be used for encoding the message with
137
 
 * MESSAGE-INTEGRITY. 
138
 
 */
139
 
PJ_DEF(void) pj_stun_create_key(pj_pool_t *pool,
140
 
                                pj_str_t *key,
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)
145
 
{
146
 
    PJ_ASSERT_ON_FAIL(pool && key && username && data, return);
147
 
 
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);
152
 
            key->slen = 16;
153
 
        } else {
154
 
            pj_strdup(pool, key, data);
155
 
        }
156
 
    } else {
157
 
        pj_assert(data_type == PJ_STUN_PASSWD_PLAIN);
158
 
        pj_strdup(pool, key, data);
159
 
    }
160
 
}
161
 
 
162
 
 
163
 
PJ_INLINE(pj_uint16_t) GET_VAL16(const pj_uint8_t *pdu, unsigned pos)
164
 
{
165
 
    return (pj_uint16_t) ((pdu[pos] << 8) + pdu[pos+1]);
166
 
}
167
 
 
168
 
 
169
 
PJ_INLINE(void) PUT_VAL16(pj_uint8_t *buf, unsigned pos, pj_uint16_t hval)
170
 
{
171
 
    buf[pos+0] = (pj_uint8_t) ((hval & 0xFF00) >> 8);
172
 
    buf[pos+1] = (pj_uint8_t) ((hval & 0x00FF) >> 0);
173
 
}
174
 
 
175
 
 
176
 
/* Send 401 response */
177
 
static pj_status_t create_challenge(pj_pool_t *pool,
178
 
                                    const pj_stun_msg *msg,
179
 
                                    int err_code,
180
 
                                    const char *errstr,
181
 
                                    const pj_str_t *realm,
182
 
                                    const pj_str_t *nonce,
183
 
                                    pj_stun_msg **p_response)
184
 
{
185
 
    pj_stun_msg *response;
186
 
    pj_str_t tmp_nonce;
187
 
    pj_str_t err_msg;
188
 
    pj_status_t rc;
189
 
 
190
 
    rc = pj_stun_msg_create_response(pool, msg, err_code, 
191
 
                                     (errstr?pj_cstr(&err_msg, errstr):NULL), 
192
 
                                     &response);
193
 
    if (rc != PJ_SUCCESS)
194
 
        return rc;
195
 
 
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,
199
 
                                         PJ_STUN_ATTR_REALM, 
200
 
                                         realm);
201
 
        if (rc != PJ_SUCCESS)
202
 
            return rc;
203
 
 
204
 
        /* long term must include nonce */
205
 
        if (!nonce || nonce->slen == 0) {
206
 
            tmp_nonce = pj_str("pjstun");
207
 
            nonce = &tmp_nonce;
208
 
        }
209
 
    }
210
 
 
211
 
    if (err_code!=400 && nonce && nonce->slen) {
212
 
        rc = pj_stun_msg_add_string_attr(pool, response,
213
 
                                         PJ_STUN_ATTR_NONCE, 
214
 
                                         nonce);
215
 
        if (rc != PJ_SUCCESS)
216
 
            return rc;
217
 
    }
218
 
 
219
 
    *p_response = response;
220
 
 
221
 
    return PJ_SUCCESS;
222
 
}
223
 
 
224
 
 
225
 
/* Verify credential in the request */
226
 
PJ_DEF(pj_status_t) pj_stun_authenticate_request(const pj_uint8_t *pkt,
227
 
                                                 unsigned pkt_len,
228
 
                                                 const pj_stun_msg *msg,
229
 
                                                 pj_stun_auth_cred *cred,
230
 
                                                 pj_pool_t *pool,
231
 
                                                 pj_stun_req_cred_info *p_info,
232
 
                                                 pj_stun_msg **p_response)
233
 
{
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;
245
 
    pj_status_t status;
246
 
 
247
 
    /* msg and credential MUST be specified */
248
 
    PJ_ASSERT_RETURN(pkt && pkt_len && msg && cred, PJ_EINVAL);
249
 
 
250
 
    /* If p_response is specified, pool MUST be specified. */
251
 
    PJ_ASSERT_RETURN(!p_response || pool, PJ_EINVAL);
252
 
 
253
 
    if (p_response)
254
 
        *p_response = NULL;
255
 
 
256
 
    if (!PJ_STUN_IS_REQUEST(msg->hdr.type))
257
 
        p_response = NULL;
258
 
 
259
 
    if (p_info == NULL)
260
 
        p_info = &tmp_info;
261
 
 
262
 
    pj_bzero(p_info, sizeof(pj_stun_req_cred_info));
263
 
 
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, 
272
 
                                              &p_info->nonce);
273
 
        if (status != PJ_SUCCESS)
274
 
            return status;
275
 
    } else {
276
 
        pj_assert(!"Invalid credential type");
277
 
        return PJ_EBUG;
278
 
    }
279
 
 
280
 
    /* Look for MESSAGE-INTEGRITY while counting the position */
281
 
    amsgi_pos = 0;
282
 
    has_attr_beyond_mi = PJ_FALSE;
283
 
    amsgi = NULL;
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];
287
 
        } else if (amsgi) {
288
 
            has_attr_beyond_mi = PJ_TRUE;
289
 
            break;
290
 
        } else {
291
 
            amsgi_pos += ((msg->attr[i]->length+3) & ~0x03) + 4;
292
 
        }
293
 
    }
294
 
 
295
 
    if (amsgi == NULL) {
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
299
 
        */
300
 
        err_code = p_info->realm.slen ? PJ_STUN_SC_UNAUTHORIZED : 
301
 
                    PJ_STUN_SC_BAD_REQUEST;
302
 
        goto on_auth_failed;
303
 
    }
304
 
 
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);
308
 
    if (auser == NULL) {
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
312
 
        */
313
 
        err_code = PJ_STUN_SC_BAD_REQUEST;
314
 
        err_text = "Missing USERNAME";
315
 
        goto on_auth_failed;
316
 
    }
317
 
 
318
 
    /* Get REALM, if any */
319
 
    arealm = (const pj_stun_realm_attr*)
320
 
             pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_REALM, 0);
321
 
 
322
 
    /* Reject with 400 if we have long term credential and the request
323
 
     * is missing REALM attribute.
324
 
     */
325
 
    if (p_info->realm.slen && arealm==NULL) {
326
 
        err_code = PJ_STUN_SC_BAD_REQUEST;
327
 
        err_text = "Missing REALM";
328
 
        goto on_auth_failed;
329
 
    }
330
 
 
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);
336
 
        if (username_ok) {
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);
342
 
        } else {
343
 
            /* Username mismatch */
344
 
            /* According to rfc3489bis-10 Sec 10.1.2/10.2.2, we should 
345
 
             * return 401 
346
 
             */
347
 
            err_code = PJ_STUN_SC_UNAUTHORIZED;
348
 
            goto on_auth_failed;
349
 
        }
350
 
    } else if (cred->type == PJ_STUN_AUTH_CRED_DYNAMIC) {
351
 
        pj_stun_passwd_type data_type = PJ_STUN_PASSWD_PLAIN;
352
 
        pj_str_t password;
353
 
        pj_status_t rc;
354
 
 
355
 
        rc = cred->data.dyn_cred.get_password(msg, 
356
 
                                              cred->data.dyn_cred.user_data,
357
 
                                              (arealm?&arealm->value:NULL),
358
 
                                              &auser->value, pool,
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);
365
 
        } else {
366
 
            err_code = PJ_STUN_SC_UNAUTHORIZED;
367
 
            goto on_auth_failed;
368
 
        }
369
 
    } else {
370
 
        pj_assert(!"Invalid credential type");
371
 
        return PJ_EBUG;
372
 
    }
373
 
 
374
 
 
375
 
 
376
 
    /* Get NONCE attribute */
377
 
    anonce = (pj_stun_nonce_attr*)
378
 
             pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_NONCE, 0);
379
 
 
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";
385
 
        goto on_auth_failed;
386
 
 
387
 
    } else if (p_info->realm.slen != 0 && arealm != NULL) {
388
 
        /* We want long term, and REALM is present */
389
 
 
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";
394
 
            goto on_auth_failed;
395
 
        }
396
 
 
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";
402
 
            goto on_auth_failed;
403
 
        }
404
 
 
405
 
        /* Valid case, will validate the message integrity later */
406
 
 
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.
411
 
         */
412
 
        
413
 
        /* For now just accept the credential, anyway it will probably
414
 
         * cause wrong message integrity value later.
415
 
         */
416
 
    } else if (p_info->realm.slen==0 && arealm == NULL) {
417
 
        /* Short term authentication is wanted, and one is supplied */
418
 
 
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";
423
 
            goto on_auth_failed;
424
 
        }
425
 
    }
426
 
 
427
 
    /* If NONCE is present, validate it */
428
 
    if (anonce) {
429
 
        pj_bool_t ok;
430
 
 
431
 
        if (cred->type == PJ_STUN_AUTH_CRED_DYNAMIC &&
432
 
            cred->data.dyn_cred.verify_nonce != NULL) 
433
 
        {
434
 
            ok=cred->data.dyn_cred.verify_nonce(msg, 
435
 
                                                cred->data.dyn_cred.user_data,
436
 
                                                (arealm?&arealm->value:NULL),
437
 
                                                &auser->value,
438
 
                                                &anonce->value);
439
 
        } else if (cred->type == PJ_STUN_AUTH_CRED_DYNAMIC) {
440
 
            ok = PJ_TRUE;
441
 
        } else {
442
 
            if (p_info->nonce.slen) {
443
 
                ok = !pj_strcmp(&anonce->value, &p_info->nonce);
444
 
            } else {
445
 
                ok = PJ_TRUE;
446
 
            }
447
 
        }
448
 
 
449
 
        if (!ok) {
450
 
            err_code = PJ_STUN_SC_STALE_NONCE;
451
 
            goto on_auth_failed;
452
 
        }
453
 
    }
454
 
 
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);
458
 
 
459
 
#if PJ_STUN_OLD_STYLE_MI_FINGERPRINT
460
 
    /* Pre rfc3489bis-06 style of calculation */
461
 
    pj_hmac_sha1_update(&ctx, pkt, 20);
462
 
#else
463
 
    /* First calculate HMAC for the header.
464
 
     * The calculation is different depending on whether FINGERPRINT attribute
465
 
     * is present in the message.
466
 
     */
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);
472
 
    } else {
473
 
        pj_hmac_sha1_update(&ctx, pkt, 20);
474
 
    }
475
 
#endif  /* PJ_STUN_OLD_STYLE_MI_FINGERPRINT */
476
 
 
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));
485
 
    }
486
 
#endif
487
 
    pj_hmac_sha1_final(&ctx, digest);
488
 
 
489
 
 
490
 
    /* Compare HMACs */
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";
496
 
        goto on_auth_failed;
497
 
    }
498
 
 
499
 
    /* Everything looks okay! */
500
 
    return PJ_SUCCESS;
501
 
 
502
 
on_auth_failed:
503
 
    if (p_response) {
504
 
        create_challenge(pool, msg, err_code, err_text,
505
 
                         &p_info->realm, &p_info->nonce, p_response);
506
 
    }
507
 
    return PJ_STATUS_FROM_STUN_CODE(err_code);
508
 
}
509
 
 
510
 
 
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)
513
 
{
514
 
    unsigned msg_type = msg->hdr.type;
515
 
    const pj_stun_errcode_attr *err_attr;
516
 
 
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))
520
 
    {
521
 
        return PJ_TRUE;
522
 
    }
523
 
 
524
 
    /* STUN Indication cannot be authenticated */
525
 
    if (PJ_STUN_IS_INDICATION(msg_type))
526
 
        return PJ_FALSE;
527
 
 
528
 
    /* Authentication for STUN error responses depend on the error
529
 
     * code.
530
 
     */
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 "
535
 
                             "error response"));
536
 
        return PJ_TRUE;
537
 
    }
538
 
 
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)            */
543
 
 
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:
547
 
        return PJ_FALSE;
548
 
    default:
549
 
        return PJ_TRUE;
550
 
    }
551
 
}
552
 
 
553
 
 
554
 
/* Authenticate MESSAGE-INTEGRITY in the response */
555
 
PJ_DEF(pj_status_t) pj_stun_authenticate_response(const pj_uint8_t *pkt,
556
 
                                                  unsigned pkt_len,
557
 
                                                  const pj_stun_msg *msg,
558
 
                                                  const pj_str_t *key)
559
 
{
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];
565
 
 
566
 
    PJ_ASSERT_RETURN(pkt && pkt_len && msg && key, PJ_EINVAL);
567
 
 
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);
571
 
    if (amsgi == NULL) {
572
 
        return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_UNAUTHORIZED);
573
 
    }
574
 
 
575
 
 
576
 
    /* Check that message length is valid */
577
 
    if (msg->hdr.length < 24) {
578
 
        return PJNATH_EINSTUNMSGLEN;
579
 
    }
580
 
 
581
 
    /* Look for MESSAGE-INTEGRITY while counting the position */
582
 
    amsgi_pos = 0;
583
 
    has_attr_beyond_mi = PJ_FALSE;
584
 
    amsgi = NULL;
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];
588
 
        } else if (amsgi) {
589
 
            has_attr_beyond_mi = PJ_TRUE;
590
 
            break;
591
 
        } else {
592
 
            amsgi_pos += ((msg->attr[i]->length+3) & ~0x03) + 4;
593
 
        }
594
 
    }
595
 
 
596
 
    if (amsgi == NULL) {
597
 
        return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_BAD_REQUEST);
598
 
    }
599
 
 
600
 
    /* Now calculate HMAC of the message. */
601
 
    pj_hmac_sha1_init(&ctx, (pj_uint8_t*)key->ptr, key->slen);
602
 
 
603
 
#if PJ_STUN_OLD_STYLE_MI_FINGERPRINT
604
 
    /* Pre rfc3489bis-06 style of calculation */
605
 
    pj_hmac_sha1_update(&ctx, pkt, 20);
606
 
#else
607
 
    /* First calculate HMAC for the header.
608
 
     * The calculation is different depending on whether FINGERPRINT attribute
609
 
     * is present in the message.
610
 
     */
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);
616
 
    } else {
617
 
        pj_hmac_sha1_update(&ctx, pkt, 20);
618
 
    }
619
 
#endif  /* PJ_STUN_OLD_STYLE_MI_FINGERPRINT */
620
 
 
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));
629
 
    }
630
 
#endif
631
 
    pj_hmac_sha1_final(&ctx, digest);
632
 
 
633
 
    /* Compare HMACs */
634
 
    if (pj_memcmp(amsgi->hmac, digest, 20)) {
635
 
        /* HMAC value mismatch */
636
 
        return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_UNAUTHORIZED);
637
 
    }
638
 
 
639
 
    /* Everything looks okay! */
640
 
    return PJ_SUCCESS;
641
 
}
642