/* Verify incoming Authorization/Proxy-Authorization header against the * specified credential. */ static pj_status_t pjsip_auth_verify( const pjsip_authorization_hdr *hdr, const pj_str_t *method, const pjsip_cred_info *cred_info ) { if (pj_stricmp(&hdr->scheme, &pjsip_DIGEST_STR) == 0) { char digest_buf[PJSIP_MD5STRLEN]; pj_str_t digest; const pjsip_digest_credential *dig = &hdr->credential.digest; /* Check that username and realm match. * These checks should have been performed before entering this * function. */ PJ_ASSERT_RETURN(pj_strcmp(&dig->username, &cred_info->username) == 0, PJ_EINVALIDOP); PJ_ASSERT_RETURN(pj_strcmp(&dig->realm, &cred_info->realm) == 0, PJ_EINVALIDOP); /* Prepare for our digest calculation. */ digest.ptr = digest_buf; digest.slen = PJSIP_MD5STRLEN; /* Create digest for comparison. */ pjsip_auth_create_digest(&digest, &hdr->credential.digest.nonce, &hdr->credential.digest.nc, &hdr->credential.digest.cnonce, &hdr->credential.digest.qop, &hdr->credential.digest.uri, &cred_info->realm, cred_info, method ); /* Compare digest. */ return (pj_stricmp(&digest, &hdr->credential.digest.response) == 0) ? PJ_SUCCESS : PJSIP_EAUTHINVALIDDIGEST; } else { pj_assert(!"Unsupported authentication scheme"); return PJSIP_EINVALIDAUTHSCHEME; } }
/* * Create MD5-AKA1 digest response. */ PJ_DEF(pj_status_t) pjsip_auth_create_aka_response( pj_pool_t *pool, const pjsip_digest_challenge*chal, const pjsip_cred_info *cred, const pj_str_t *method, pjsip_digest_credential *auth) { pj_str_t nonce_bin; int aka_version; const pj_str_t pjsip_AKAv1_MD5 = { "AKAv1-MD5", 9 }; const pj_str_t pjsip_AKAv2_MD5 = { "AKAv2-MD5", 9 }; pj_uint8_t *chal_rand, *chal_sqnxoraka, *chal_mac; pj_uint8_t k[PJSIP_AKA_KLEN]; pj_uint8_t op[PJSIP_AKA_OPLEN]; pj_uint8_t amf[PJSIP_AKA_AMFLEN]; pj_uint8_t res[PJSIP_AKA_RESLEN]; pj_uint8_t ck[PJSIP_AKA_CKLEN]; pj_uint8_t ik[PJSIP_AKA_IKLEN]; pj_uint8_t ak[PJSIP_AKA_AKLEN]; pj_uint8_t sqn[PJSIP_AKA_SQNLEN]; pj_uint8_t xmac[PJSIP_AKA_MACLEN]; pjsip_cred_info aka_cred; int i, len; pj_status_t status; /* Check the algorithm is supported. */ if (chal->algorithm.slen==0 || pj_stricmp2(&chal->algorithm, "md5") == 0) { /* * A normal MD5 authentication is requested. Fallbackt to the usual * MD5 digest creation. */ pjsip_auth_create_digest(&auth->response, &auth->nonce, &auth->nc, &auth->cnonce, &auth->qop, &auth->uri, &auth->realm, cred, method); return PJ_SUCCESS; } else if (pj_stricmp(&chal->algorithm, &pjsip_AKAv1_MD5) == 0) { /* * AKA version 1 is requested. */ aka_version = 1; } else if (pj_stricmp(&chal->algorithm, &pjsip_AKAv2_MD5) == 0) { /* * AKA version 2 is requested. */ aka_version = 2; } else { /* Unsupported algorithm */ return PJSIP_EINVALIDALGORITHM; } /* Decode nonce */ nonce_bin.slen = len = PJ_BASE64_TO_BASE256_LEN(chal->nonce.slen); nonce_bin.ptr = pj_pool_alloc(pool, nonce_bin.slen + 1); status = pj_base64_decode(&chal->nonce, (pj_uint8_t*)nonce_bin.ptr, &len); nonce_bin.slen = len; if (status != PJ_SUCCESS) return PJSIP_EAUTHINNONCE; if (nonce_bin.slen < PJSIP_AKA_RANDLEN + PJSIP_AKA_AUTNLEN) return PJSIP_EAUTHINNONCE; /* Get RAND, AUTN, and MAC */ chal_rand = (pj_uint8_t*)(nonce_bin.ptr + 0); chal_sqnxoraka = (pj_uint8_t*) (nonce_bin.ptr + PJSIP_AKA_RANDLEN); chal_mac = (pj_uint8_t*) (nonce_bin.ptr + PJSIP_AKA_RANDLEN + PJSIP_AKA_SQNLEN + PJSIP_AKA_AMFLEN); /* Copy k. op, and amf */ pj_bzero(k, sizeof(k)); pj_bzero(op, sizeof(op)); pj_bzero(amf, sizeof(amf)); if (cred->ext.aka.k.slen) pj_memcpy(k, cred->ext.aka.k.ptr, cred->ext.aka.k.slen); if (cred->ext.aka.op.slen) pj_memcpy(op, cred->ext.aka.op.ptr, cred->ext.aka.op.slen); if (cred->ext.aka.amf.slen) pj_memcpy(amf, cred->ext.aka.amf.ptr, cred->ext.aka.amf.slen); /* Given key K and random challenge RAND, compute response RES, * confidentiality key CK, integrity key IK and anonymity key AK. */ f2345(k, chal_rand, res, ck, ik, ak, op); /* Compute sequence number SQN */ for (i=0; i<PJSIP_AKA_SQNLEN; ++i) sqn[i] = (pj_uint8_t) (chal_sqnxoraka[i] ^ ak[i]); /* Verify MAC in the challenge */ /* Compute XMAC */ f1(k, chal_rand, sqn, amf, xmac, op); if (pj_memcmp(chal_mac, xmac, PJSIP_AKA_MACLEN) != 0) { return PJSIP_EAUTHINNONCE; } /* Build a temporary credential info to create MD5 digest, using * "res" as the password. */ pj_memcpy(&aka_cred, cred, sizeof(aka_cred)); aka_cred.data_type = PJSIP_CRED_DATA_PLAIN_PASSWD; /* Create a response */ if (aka_version == 1) { /* * For AKAv1, the password is RES */ aka_cred.data.ptr = (char*)res; aka_cred.data.slen = PJSIP_AKA_RESLEN; pjsip_auth_create_digest(&auth->response, &chal->nonce, &auth->nc, &auth->cnonce, &auth->qop, &auth->uri, &chal->realm, &aka_cred, method); } else if (aka_version == 2) { /* * For AKAv2, password is base64 encoded [1] parameters: * PRF(RES||IK||CK,"http-digest-akav2-password") * * The pseudo-random function (PRF) is HMAC-MD5 in this case. */ pj_str_t resikck; const pj_str_t AKAv2_Passwd = { "http-digest-akav2-password", 26 }; pj_uint8_t hmac_digest[16]; char tmp_buf[48]; int hmac64_len; resikck.slen = PJSIP_AKA_RESLEN + PJSIP_AKA_IKLEN + PJSIP_AKA_CKLEN; pj_assert(resikck.slen <= PJ_ARRAY_SIZE(tmp_buf)); resikck.ptr = tmp_buf; pj_memcpy(resikck.ptr + 0, res, PJSIP_AKA_RESLEN); pj_memcpy(resikck.ptr + PJSIP_AKA_RESLEN, ik, PJSIP_AKA_IKLEN); pj_memcpy(resikck.ptr + PJSIP_AKA_RESLEN + PJSIP_AKA_IKLEN, ck, PJSIP_AKA_CKLEN); pj_hmac_md5((const pj_uint8_t*)AKAv2_Passwd.ptr, AKAv2_Passwd.slen, (const pj_uint8_t*)resikck.ptr, resikck.slen, hmac_digest); aka_cred.data.slen = hmac64_len = PJ_BASE256_TO_BASE64_LEN(PJ_ARRAY_SIZE(hmac_digest)); pj_assert(aka_cred.data.slen+1 <= PJ_ARRAY_SIZE(tmp_buf)); aka_cred.data.ptr = tmp_buf; pj_base64_encode(hmac_digest, PJ_ARRAY_SIZE(hmac_digest), aka_cred.data.ptr, &len); aka_cred.data.slen = hmac64_len; pjsip_auth_create_digest(&auth->response, &chal->nonce, &auth->nc, &auth->cnonce, &auth->qop, &auth->uri, &chal->realm, &aka_cred, method); } else { pj_assert(!"Bug!"); return PJ_EBUG; } /* Done */ return PJ_SUCCESS; }
/* * Generate response digest. * Most of the parameters to generate the digest (i.e. username, realm, uri, * and nonce) are expected to be in the credential. Additional parameters (i.e. * password and method param) should be supplied in the argument. * * The resulting digest will be stored in cred->response. * The pool is used to allocate 32 bytes to store the digest in cred->response. */ static pj_status_t respond_digest( pj_pool_t *pool, pjsip_digest_credential *cred, const pjsip_digest_challenge *chal, const pj_str_t *uri, const pjsip_cred_info *cred_info, const pj_str_t *cnonce, pj_uint32_t nc, const pj_str_t *method) { const pj_str_t pjsip_AKAv1_MD5_STR = { "AKAv1-MD5", 9 }; /* Check algorithm is supported. We support MD5 and AKAv1-MD5. */ if (chal->algorithm.slen==0 || (pj_stricmp(&chal->algorithm, &pjsip_MD5_STR)==0 || pj_stricmp(&chal->algorithm, &pjsip_AKAv1_MD5_STR)==0)) { ; } else { PJ_LOG(4,(THIS_FILE, "Unsupported digest algorithm \"%.*s\"", chal->algorithm.slen, chal->algorithm.ptr)); return PJSIP_EINVALIDALGORITHM; } /* Build digest credential from arguments. */ pj_strdup(pool, &cred->username, &cred_info->username); pj_strdup(pool, &cred->realm, &chal->realm); pj_strdup(pool, &cred->nonce, &chal->nonce); pj_strdup(pool, &cred->uri, uri); pj_strdup(pool, &cred->algorithm, &chal->algorithm); pj_strdup(pool, &cred->opaque, &chal->opaque); /* Allocate memory. */ cred->response.ptr = (char*) pj_pool_alloc(pool, PJSIP_MD5STRLEN); cred->response.slen = PJSIP_MD5STRLEN; if (chal->qop.slen == 0) { /* Server doesn't require quality of protection. */ if ((cred_info->data_type & EXT_MASK) == PJSIP_CRED_DATA_EXT_AKA) { /* Call application callback to create the response digest */ return (*cred_info->ext.aka.cb)(pool, chal, cred_info, method, cred); } else { /* Convert digest to string and store in chal->response. */ pjsip_auth_create_digest( &cred->response, &cred->nonce, NULL, NULL, NULL, uri, &chal->realm, cred_info, method); } } else if (has_auth_qop(pool, &chal->qop)) { /* Server requires quality of protection. * We respond with selecting "qop=auth" protection. */ cred->qop = pjsip_AUTH_STR; cred->nc.ptr = (char*) pj_pool_alloc(pool, 16); cred->nc.slen = pj_ansi_snprintf(cred->nc.ptr, 16, "%08u", nc); if (cnonce && cnonce->slen) { pj_strdup(pool, &cred->cnonce, cnonce); } else { pj_str_t dummy_cnonce = { "b39971", 6}; pj_strdup(pool, &cred->cnonce, &dummy_cnonce); } if ((cred_info->data_type & EXT_MASK) == PJSIP_CRED_DATA_EXT_AKA) { /* Call application callback to create the response digest */ return (*cred_info->ext.aka.cb)(pool, chal, cred_info, method, cred); } else { pjsip_auth_create_digest( &cred->response, &cred->nonce, &cred->nc, cnonce, &pjsip_AUTH_STR, uri, &chal->realm, cred_info, method ); } } else { /* Server requires quality protection that we don't support. */ PJ_LOG(4,(THIS_FILE, "Unsupported qop offer %.*s", chal->qop.slen, chal->qop.ptr)); return PJSIP_EINVALIDQOP; } return PJ_SUCCESS; }