~ubuntu-branches/ubuntu/trusty/sflphone/trusty

« back to all changes in this revision

Viewing changes to daemon/libs/pjproject-2.1.0/pjnath/src/pjnath/stun_auth.c

  • Committer: Package Import Robot
  • Author(s): Mark Purcell
  • Date: 2014-01-28 18:23:36 UTC
  • mfrom: (4.3.4 sid)
  • Revision ID: package-import@ubuntu.com-20140128182336-jrsv0k9u6cawc068
Tags: 1.3.0-1
* New upstream release 
  - Fixes "New Upstream Release" (Closes: #735846)
  - Fixes "Ringtone does not stop" (Closes: #727164)
  - Fixes "[sflphone-kde] crash on startup" (Closes: #718178)
  - Fixes "sflphone GUI crashes when call is hung up" (Closes: #736583)
* Build-Depends: ensure GnuTLS 2.6
  - libucommon-dev (>= 6.0.7-1.1), libccrtp-dev (>= 2.0.6-3)
  - Fixes "FTBFS Build-Depends libgnutls{26,28}-dev" (Closes: #722040)
* Fix "boost 1.49 is going away" unversioned Build-Depends: (Closes: #736746)
* Add Build-Depends: libsndfile-dev, nepomuk-core-dev

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/* $Id: stun_auth.c 3553 2011-05-05 06:14:19Z nanang $ */
 
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 */
 
33
PJ_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
 */
 
62
PJ_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
 */
 
76
static 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, 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, 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, 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
 */
 
128
PJ_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
 
 
152
PJ_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
 
 
158
PJ_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 */
 
166
static 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 */
 
215
PJ_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
                      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
 
 
491
on_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 */
 
501
PJ_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 */
 
544
PJ_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, 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