Ignore:
Timestamp:
Mar 19, 2008 11:00:30 PM (17 years ago)
Author:
bennylp
Message:

Related to ticket #485: huge changeset to update STUN relating to managing authentication. See the ticket for the details

File:
1 edited

Legend:

Unmodified
Added
Removed
  • pjproject/trunk/pjnath/src/pjnath/stun_auth.c

    r1869 r1877  
    2020#include <pjnath/errno.h> 
    2121#include <pjlib-util/hmac_sha1.h> 
     22#include <pjlib-util/md5.h> 
    2223#include <pjlib-util/sha1.h> 
    2324#include <pj/assert.h> 
    2425#include <pj/log.h> 
     26#include <pj/pool.h> 
    2527#include <pj/string.h> 
    2628 
     
    5456 
    5557 
     58/* 
     59 * Duplicate request credential. 
     60 */ 
     61PJ_DEF(void) pj_stun_req_cred_info_dup( pj_pool_t *pool, 
     62                                        pj_stun_req_cred_info *dst, 
     63                                        const pj_stun_req_cred_info *src) 
     64{ 
     65    pj_strdup(pool, &dst->realm, &src->realm); 
     66    pj_strdup(pool, &dst->username, &src->username); 
     67    pj_strdup(pool, &dst->nonce, &src->nonce); 
     68    pj_strdup(pool, &dst->auth_key, &src->auth_key); 
     69} 
     70 
     71 
     72/* Calculate HMAC-SHA1 key for long term credential, by getting 
     73 * MD5 digest of username, realm, and password.  
     74 */ 
     75static void calc_md5_key(pj_uint8_t digest[16], 
     76                         const pj_str_t *realm, 
     77                         const pj_str_t *username, 
     78                         const pj_str_t *passwd) 
     79{ 
     80    /* The 16-byte key for MESSAGE-INTEGRITY HMAC is formed by taking 
     81     * the MD5 hash of the result of concatenating the following five 
     82     * fields: (1) The username, with any quotes and trailing nulls 
     83     * removed, (2) A single colon, (3) The realm, with any quotes and 
     84     * trailing nulls removed, (4) A single colon, and (5) The  
     85     * password, with any trailing nulls removed. 
     86     */ 
     87    pj_md5_context ctx; 
     88    pj_str_t s; 
     89 
     90    pj_md5_init(&ctx); 
     91 
     92#define REMOVE_QUOTE(s) if (s.slen && *s.ptr=='"') \ 
     93                            s.ptr++, s.slen--; \ 
     94                        if (s.slen && s.ptr[s.slen-1]=='"') \ 
     95                            s.slen--; 
     96 
     97    /* Add username */ 
     98    s = *username; 
     99    REMOVE_QUOTE(s); 
     100    pj_md5_update(&ctx, (pj_uint8_t*)s.ptr, s.slen); 
     101 
     102    /* Add single colon */ 
     103    pj_md5_update(&ctx, (pj_uint8_t*)":", 1); 
     104 
     105    /* Add realm */ 
     106    s = *realm; 
     107    REMOVE_QUOTE(s); 
     108    pj_md5_update(&ctx, (pj_uint8_t*)s.ptr, s.slen); 
     109 
     110#undef REMOVE_QUOTE 
     111 
     112    /* Another colon */ 
     113    pj_md5_update(&ctx, (pj_uint8_t*)":", 1); 
     114 
     115    /* Add password */ 
     116    pj_md5_update(&ctx, (pj_uint8_t*)passwd->ptr, passwd->slen); 
     117 
     118    /* Done */ 
     119    pj_md5_final(&ctx, digest); 
     120} 
     121 
     122 
     123/* 
     124 * Create authentication key to be used for encoding the message with 
     125 * MESSAGE-INTEGRITY.  
     126 */ 
     127PJ_DEF(void) pj_stun_create_key(pj_pool_t *pool, 
     128                                pj_str_t *key, 
     129                                const pj_str_t *realm, 
     130                                const pj_str_t *username, 
     131                                pj_stun_passwd_type data_type, 
     132                                const pj_str_t *data) 
     133{ 
     134    PJ_ASSERT_ON_FAIL(pool && key && username && data, return); 
     135 
     136    if (realm && realm->slen) { 
     137        if (data_type == PJ_STUN_PASSWD_PLAIN) { 
     138            key->ptr = (char*) pj_pool_alloc(pool, 16); 
     139            calc_md5_key((pj_uint8_t*)key->ptr, realm, username, data); 
     140            key->slen = 16; 
     141        } else { 
     142            pj_strdup(pool, key, data); 
     143        } 
     144    } else { 
     145        pj_assert(data_type == PJ_STUN_PASSWD_PLAIN); 
     146        pj_strdup(pool, key, data); 
     147    } 
     148} 
     149 
     150 
    56151PJ_INLINE(pj_uint16_t) GET_VAL16(const pj_uint8_t *pdu, unsigned pos) 
    57152{ 
     
    87182        return rc; 
    88183 
    89  
    90     if (realm && realm->slen) { 
     184    /* SHOULD NOT add REALM, NONCE, USERNAME, and M-I on 400 response */ 
     185    if (err_code!=400 && realm && realm->slen) { 
    91186        rc = pj_stun_msg_add_string_attr(pool, response, 
    92187                                         PJ_STUN_ATTR_REALM,  
     
    102197    } 
    103198 
    104     if (nonce && nonce->slen) { 
     199    if (err_code!=400 && nonce && nonce->slen) { 
    105200        rc = pj_stun_msg_add_string_attr(pool, response, 
    106201                                         PJ_STUN_ATTR_NONCE,  
     
    122217                                                 pj_stun_auth_cred *cred, 
    123218                                                 pj_pool_t *pool, 
    124                                                  pj_str_t *auth_key, 
     219                                                 pj_stun_req_cred_info *p_info, 
    125220                                                 pj_stun_msg **p_response) 
    126221{ 
    127     pj_str_t realm, nonce, password; 
     222    pj_stun_req_cred_info tmp_info; 
    128223    const pj_stun_msgint_attr *amsgi; 
    129224    unsigned i, amsgi_pos; 
    130225    pj_bool_t has_attr_beyond_mi; 
    131226    const pj_stun_username_attr *auser; 
    132     pj_bool_t username_ok; 
    133227    const pj_stun_realm_attr *arealm; 
    134228    const pj_stun_realm_attr *anonce; 
    135229    pj_hmac_sha1_context ctx; 
    136230    pj_uint8_t digest[PJ_SHA1_DIGEST_SIZE]; 
    137     pj_str_t key; 
     231    pj_stun_status err_code; 
     232    const char *err_text = NULL; 
    138233    pj_status_t status; 
    139234 
     
    150245        p_response = NULL; 
    151246 
     247    if (p_info == NULL) 
     248        p_info = &tmp_info; 
     249 
     250    pj_bzero(p_info, sizeof(pj_stun_req_cred_info)); 
     251 
    152252    /* Get realm and nonce from credential */ 
    153     realm.slen = nonce.slen = 0; 
     253    p_info->realm.slen = p_info->nonce.slen = 0; 
    154254    if (cred->type == PJ_STUN_AUTH_CRED_STATIC) { 
    155         realm = cred->data.static_cred.realm; 
    156         nonce = cred->data.static_cred.nonce; 
     255        p_info->realm = cred->data.static_cred.realm; 
     256        p_info->nonce = cred->data.static_cred.nonce; 
    157257    } else if (cred->type == PJ_STUN_AUTH_CRED_DYNAMIC) { 
    158258        status = cred->data.dyn_cred.get_auth(cred->data.dyn_cred.user_data, 
    159                                               pool, &realm, &nonce); 
     259                                              pool, &p_info->realm,  
     260                                              &p_info->nonce); 
    160261        if (status != PJ_SUCCESS) 
    161262            return status; 
    162263    } else { 
    163         pj_assert(!"Unexpected"); 
     264        pj_assert(!"Invalid credential type"); 
    164265        return PJ_EBUG; 
    165266    } 
     
    185286           The rule has been changed from rfc3489bis-06 
    186287        */ 
    187         int code; 
    188  
    189         code = realm.slen ? PJ_STUN_SC_UNAUTHORIZED : PJ_STUN_SC_BAD_REQUEST; 
    190         if (p_response) { 
    191             create_challenge(pool, msg, code, NULL, 
    192                              &realm, &nonce, p_response); 
    193         } 
    194         return PJ_STATUS_FROM_STUN_CODE(code); 
     288        err_code = p_info->realm.slen ? PJ_STUN_SC_UNAUTHORIZED :  
     289                    PJ_STUN_SC_BAD_REQUEST; 
     290        goto on_auth_failed; 
    195291    } 
    196292 
     
    203299           The rule has been changed from rfc3489bis-06 
    204300        */ 
    205         int code = PJ_STUN_SC_BAD_REQUEST; 
    206         if (p_response) { 
    207             create_challenge(pool, msg, code, "Missing USERNAME", 
    208                              &realm, &nonce, p_response); 
    209         } 
    210         return PJ_STATUS_FROM_STUN_CODE(code); 
     301        err_code = PJ_STUN_SC_BAD_REQUEST; 
     302        err_text = "Missing USERNAME"; 
     303        goto on_auth_failed; 
    211304    } 
    212305 
     
    215308             pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_REALM, 0); 
    216309 
     310    /* Reject with 400 if we have long term credential and the request 
     311     * is missing REALM attribute. 
     312     */ 
     313    if (p_info->realm.slen && arealm==NULL) { 
     314        err_code = PJ_STUN_SC_BAD_REQUEST; 
     315        err_text = "Missing REALM"; 
     316        goto on_auth_failed; 
     317    } 
     318 
    217319    /* Check if username match */ 
    218320    if (cred->type == PJ_STUN_AUTH_CRED_STATIC) { 
     321        pj_bool_t username_ok; 
    219322        username_ok = !pj_strcmp(&auser->value,  
    220323                                 &cred->data.static_cred.username); 
    221         password = cred->data.static_cred.data; 
     324        if (username_ok) { 
     325            pj_strdup(pool, &p_info->username,  
     326                      &cred->data.static_cred.username); 
     327            pj_stun_create_key(pool, &p_info->auth_key, &p_info->realm, 
     328                               &auser->value, cred->data.static_cred.data_type, 
     329                               &cred->data.static_cred.data); 
     330        } else { 
     331            /* Username mismatch */ 
     332            /* According to rfc3489bis-10 Sec 10.1.2/10.2.2, we should  
     333             * return 401  
     334             */ 
     335            err_code = PJ_STUN_SC_UNAUTHORIZED; 
     336            goto on_auth_failed; 
     337        } 
    222338    } else if (cred->type == PJ_STUN_AUTH_CRED_DYNAMIC) { 
    223         int data_type = 0; 
     339        pj_stun_passwd_type data_type = PJ_STUN_PASSWD_PLAIN; 
     340        pj_str_t password; 
    224341        pj_status_t rc; 
     342 
    225343        rc = cred->data.dyn_cred.get_password(msg,  
    226344                                              cred->data.dyn_cred.user_data, 
     
    228346                                              &auser->value, pool, 
    229347                                              &data_type, &password); 
    230         username_ok = (rc == PJ_SUCCESS); 
     348        if (rc == PJ_SUCCESS) { 
     349            pj_strdup(pool, &p_info->username, &auser->value); 
     350            pj_stun_create_key(pool, &p_info->auth_key,  
     351                               (arealm?&arealm->value:NULL), &auser->value,  
     352                               data_type, &password); 
     353        } else { 
     354            err_code = PJ_STUN_SC_UNAUTHORIZED; 
     355            goto on_auth_failed; 
     356        } 
    231357    } else { 
    232         username_ok = PJ_TRUE; 
    233         password.slen = 0; 
    234     } 
    235  
    236     if (!username_ok) { 
    237         /* Username mismatch */ 
    238         /* According to rfc3489bis-10 Sec 10.1.2/10.2.2, we should  
    239          * return 401  
    240          */ 
    241         if (p_response) { 
    242             create_challenge(pool, msg, PJ_STUN_SC_UNAUTHORIZED, NULL, 
    243                              &realm, &nonce, p_response); 
    244         } 
    245         return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_UNAUTHORIZED); 
    246     } 
     358        pj_assert(!"Invalid credential type"); 
     359        return PJ_EBUG; 
     360    } 
     361 
    247362 
    248363 
     
    252367 
    253368    /* Check for long term/short term requirements. */ 
    254     if (realm.slen != 0 && arealm == NULL) { 
     369    if (p_info->realm.slen != 0 && arealm == NULL) { 
    255370        /* Long term credential is required and REALM is not present */ 
    256         if (p_response) { 
    257             create_challenge(pool, msg, PJ_STUN_SC_BAD_REQUEST,  
    258                              "Missing REALM", 
    259                              &realm, &nonce, p_response); 
    260         } 
    261         return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_BAD_REQUEST); 
    262  
    263     } else if (realm.slen != 0 && arealm != NULL) { 
     371        err_code = PJ_STUN_SC_BAD_REQUEST; 
     372        err_text = "Missing REALM"; 
     373        goto on_auth_failed; 
     374 
     375    } else if (p_info->realm.slen != 0 && arealm != NULL) { 
    264376        /* We want long term, and REALM is present */ 
    265377 
    266378        /* NONCE must be present. */ 
    267         if (anonce == NULL && nonce.slen) { 
    268             if (p_response) { 
    269                 create_challenge(pool, msg, PJ_STUN_SC_BAD_REQUEST,  
    270                                  "Missing NONCE", &realm, &nonce, p_response); 
    271             } 
    272             return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_BAD_REQUEST); 
     379        if (anonce == NULL && p_info->nonce.slen) { 
     380            err_code = PJ_STUN_SC_BAD_REQUEST; 
     381            err_text = "Missing NONCE"; 
     382            goto on_auth_failed; 
    273383        } 
    274384 
    275385        /* Verify REALM matches */ 
    276         if (pj_stricmp(&arealm->value, &realm)) { 
     386        if (pj_stricmp(&arealm->value, &p_info->realm)) { 
    277387            /* REALM doesn't match */ 
    278             if (p_response) { 
    279                 create_challenge(pool, msg, PJ_STUN_SC_UNAUTHORIZED,  
    280                                  "Invalid REALM", &realm, &nonce, p_response); 
    281             } 
    282             return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_UNAUTHORIZED); 
     388            err_code = PJ_STUN_SC_UNAUTHORIZED; 
     389            err_text = "Invalid REALM"; 
     390            goto on_auth_failed; 
    283391        } 
    284392 
    285393        /* Valid case, will validate the message integrity later */ 
    286394 
    287     } else if (realm.slen == 0 && arealm != NULL) { 
     395    } else if (p_info->realm.slen == 0 && arealm != NULL) { 
    288396        /* We want to use short term credential, but client uses long 
    289397         * term credential. The draft doesn't mention anything about 
     
    294402         * cause wrong message integrity value later. 
    295403         */ 
    296     } else if (realm.slen==0 && arealm == NULL) { 
     404    } else if (p_info->realm.slen==0 && arealm == NULL) { 
    297405        /* Short term authentication is wanted, and one is supplied */ 
    298406 
    299407        /* Application MAY request NONCE to be supplied */ 
    300         if (nonce.slen != 0) { 
    301             if (p_response) { 
    302                 create_challenge(pool, msg, PJ_STUN_SC_UNAUTHORIZED,  
    303                                  "NONCE required", &realm, &nonce, p_response); 
    304             } 
    305             return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_UNAUTHORIZED); 
     408        if (p_info->nonce.slen != 0) { 
     409            err_code = PJ_STUN_SC_UNAUTHORIZED; 
     410            err_text = "NONCE required"; 
     411            goto on_auth_failed; 
    306412        } 
    307413    } 
     
    322428            ok = PJ_TRUE; 
    323429        } else { 
    324             if (nonce.slen) { 
    325                 ok = !pj_strcmp(&anonce->value, &nonce); 
     430            if (p_info->nonce.slen) { 
     431                ok = !pj_strcmp(&anonce->value, &p_info->nonce); 
    326432            } else { 
    327433                ok = PJ_TRUE; 
     
    330436 
    331437        if (!ok) { 
    332             if (p_response) { 
    333                 create_challenge(pool, msg, PJ_STUN_SC_STALE_NONCE,  
    334                                  NULL, &realm, &nonce, p_response); 
    335             } 
    336             if (auth_key) { 
    337                 pj_stun_create_key(pool, auth_key, &realm,  
    338                                    &auser->value, &password); 
    339             } 
    340             return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_STALE_NONCE); 
    341         } 
    342     } 
    343  
    344     /* Calculate key */ 
    345     pj_stun_create_key(pool, &key, &realm, &auser->value, &password); 
     438            err_code = PJ_STUN_SC_STALE_NONCE; 
     439            goto on_auth_failed; 
     440        } 
     441    } 
    346442 
    347443    /* Now calculate HMAC of the message. */ 
    348     pj_hmac_sha1_init(&ctx, (pj_uint8_t*)key.ptr, key.slen); 
     444    pj_hmac_sha1_init(&ctx, (pj_uint8_t*)p_info->auth_key.ptr,  
     445                      p_info->auth_key.slen); 
    349446 
    350447#if PJ_STUN_OLD_STYLE_MI_FINGERPRINT 
     
    383480        /* HMAC value mismatch */ 
    384481        /* According to rfc3489bis-10 Sec 10.1.2 we should return 401 */ 
    385         if (p_response) { 
    386             create_challenge(pool, msg, PJ_STUN_SC_UNAUTHORIZED, 
    387                              NULL, &realm, &nonce, p_response); 
    388         } 
    389         return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_UNAUTHORIZED); 
     482        err_code = PJ_STUN_SC_UNAUTHORIZED; 
     483        err_text = "MESSAGE-INTEGRITY mismatch"; 
     484        goto on_auth_failed; 
    390485    } 
    391486 
    392487    /* Everything looks okay! */ 
    393488    return PJ_SUCCESS; 
     489 
     490on_auth_failed: 
     491    if (p_response) { 
     492        create_challenge(pool, msg, err_code, err_text, 
     493                         &p_info->realm, &p_info->nonce, p_response); 
     494    } 
     495    return PJ_STATUS_FROM_STUN_CODE(err_code); 
    394496} 
    395497 
     
    426528    case PJ_STUN_SC_BAD_REQUEST:            /* 400 (Bad Request)            */ 
    427529    case PJ_STUN_SC_UNAUTHORIZED:           /* 401 (Unauthorized)           */ 
    428     //case PJ_STUN_SC_STALE_CREDENTIALS:    /* 430 (Stale Credential)       */ 
    429     //case PJ_STUN_SC_MISSING_USERNAME:     /* 432 (Missing Username)       */ 
    430     //case PJ_STUN_SC_MISSING_REALM:        /* 434 (Missing Realm)          */ 
    431     //case PJ_STUN_SC_UNKNOWN_USERNAME:     /* 436 (Unknown Username)       */ 
    432     //case PJ_STUN_SC_INTEGRITY_CHECK_FAILURE:/* 431 (Integrity Check Fail) */ 
     530 
     531    /* Due to the way this response is generated here, we can't really 
     532     * authenticate 420 (Unknown Attribute) response                        */ 
     533    case PJ_STUN_SC_UNKNOWN_ATTRIBUTE: 
    433534        return PJ_FALSE; 
    434535    default: 
Note: See TracChangeset for help on using the changeset viewer.