/* * 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; }
int main(int argc, char *argv[]) { pj_str_t input, output, srtp_crypto, srtp_key, codec; pjmedia_aud_dev_index dev_id = PJMEDIA_AUD_DEFAULT_PLAYBACK_DEV; pj_pcap_filter filter; pj_status_t status; enum { OPT_SRC_IP = 1, OPT_DST_IP, OPT_SRC_PORT, OPT_DST_PORT, OPT_CODEC, OPT_PLAY_DEV_ID }; struct pj_getopt_option long_options[] = { { "srtp-crypto", 1, 0, 'c' }, { "srtp-key", 1, 0, 'k' }, { "src-ip", 1, 0, OPT_SRC_IP }, { "dst-ip", 1, 0, OPT_DST_IP }, { "src-port", 1, 0, OPT_SRC_PORT }, { "dst-port", 1, 0, OPT_DST_PORT }, { "codec", 1, 0, OPT_CODEC }, { "play-dev-id", 1, 0, OPT_PLAY_DEV_ID }, { NULL, 0, 0, 0} }; int c; int option_index; char key_bin[32]; srtp_crypto.slen = srtp_key.slen = 0; codec.slen = 0; pj_pcap_filter_default(&filter); filter.link = PJ_PCAP_LINK_TYPE_ETH; filter.proto = PJ_PCAP_PROTO_TYPE_UDP; /* Parse arguments */ pj_optind = 0; while((c=pj_getopt_long(argc,argv, "c:k:", long_options, &option_index))!=-1) { switch (c) { case 'c': srtp_crypto = pj_str(pj_optarg); break; case 'k': { int key_len = sizeof(key_bin); srtp_key = pj_str(pj_optarg); if (pj_base64_decode(&srtp_key, (pj_uint8_t*)key_bin, &key_len)) { puts("Error: invalid key"); return 1; } srtp_key.ptr = key_bin; srtp_key.slen = key_len; } break; case OPT_SRC_IP: { pj_str_t t = pj_str(pj_optarg); pj_in_addr a = pj_inet_addr(&t); filter.ip_src = a.s_addr; } break; case OPT_DST_IP: { pj_str_t t = pj_str(pj_optarg); pj_in_addr a = pj_inet_addr(&t); filter.ip_dst = a.s_addr; } break; case OPT_SRC_PORT: filter.src_port = pj_htons((pj_uint16_t)atoi(pj_optarg)); break; case OPT_DST_PORT: filter.dst_port = pj_htons((pj_uint16_t)atoi(pj_optarg)); break; case OPT_CODEC: codec = pj_str(pj_optarg); break; case OPT_PLAY_DEV_ID: dev_id = atoi(pj_optarg); break; default: puts("Error: invalid option"); return 1; } } if (pj_optind != argc - 2) { puts(USAGE); return 1; } if (!(srtp_crypto.slen) != !(srtp_key.slen)) { puts("Error: both SRTP crypto and key must be specified"); puts(USAGE); return 1; } input = pj_str(argv[pj_optind]); output = pj_str(argv[pj_optind+1]); T( pj_init() ); pj_caching_pool_init(&app.cp, NULL, 0); app.pool = pj_pool_create(&app.cp.factory, "pcaputil", 1000, 1000, NULL); T( pjlib_util_init() ); T( pjmedia_endpt_create(&app.cp.factory, NULL, 0, &app.mept) ); T( pj_pcap_open(app.pool, input.ptr, &app.pcap) ); T( pj_pcap_set_filter(app.pcap, &filter) ); pcap2wav(&codec, &output, dev_id, &srtp_crypto, &srtp_key); cleanup(); return 0; }
/* Parse crypto attribute line */ static pj_status_t parse_attr_crypto(pj_pool_t *pool, const pjmedia_sdp_attr *attr, pjmedia_srtp_crypto *crypto, int *tag) { pj_str_t input; char *token; int token_len; pj_str_t tmp; pj_status_t status; int itmp; pj_bzero(crypto, sizeof(*crypto)); pj_strdup_with_null(pool, &input, &attr->value); /* Tag */ token = strtok(input.ptr, " "); if (!token) { PJ_LOG(4,(THIS_FILE, "Attribute crypto expecting tag")); return PJMEDIA_SDP_EINATTR; } token_len = pj_ansi_strlen(token); /* Tag must not use leading zeroes. */ if (token_len > 1 && *token == '0') return PJMEDIA_SDP_EINATTR; /* Tag must be decimal, i.e: contains only digit '0'-'9'. */ for (itmp = 0; itmp < token_len; ++itmp) if (!pj_isdigit(token[itmp])) return PJMEDIA_SDP_EINATTR; /* Get tag value. */ *tag = atoi(token); /* Crypto-suite */ token = strtok(NULL, " "); if (!token) { PJ_LOG(4,(THIS_FILE, "Attribute crypto expecting crypto suite")); return PJMEDIA_SDP_EINATTR; } crypto->name = pj_str(token); /* Key method */ token = strtok(NULL, ":"); if (!token) { PJ_LOG(4,(THIS_FILE, "Attribute crypto expecting key method")); return PJMEDIA_SDP_EINATTR; } if (pj_ansi_stricmp(token, "inline")) { PJ_LOG(4,(THIS_FILE, "Attribute crypto key method '%s' not supported!", token)); return PJMEDIA_SDP_EINATTR; } /* Key */ token = strtok(NULL, "| "); if (!token) { PJ_LOG(4,(THIS_FILE, "Attribute crypto expecting key")); return PJMEDIA_SDP_EINATTR; } tmp = pj_str(token); crypto->key.ptr = (char*) pj_pool_zalloc(pool, MAX_KEY_LEN); /* Decode key */ itmp = MAX_KEY_LEN; status = pj_base64_decode(&tmp, (pj_uint8_t*)crypto->key.ptr, &itmp); if (status != PJ_SUCCESS) { PJ_LOG(4,(THIS_FILE, "Failed decoding crypto key from base64")); return status; } crypto->key.slen = itmp; return PJ_SUCCESS; }
/* Parse crypto attribute line */ static pj_status_t parse_attr_crypto(pj_pool_t *pool, const pjmedia_sdp_attr *attr, pjmedia_srtp_crypto *crypto, int *tag) { pj_str_t input; char *token; pj_str_t tmp; pj_status_t status; int itmp; pj_bzero(crypto, sizeof(*crypto)); pj_strdup_with_null(pool, &input, &attr->value); /* Tag */ token = strtok(input.ptr, " "); if (!token) { PJ_LOG(4,(THIS_FILE, "Attribute crypto expecting tag")); return PJMEDIA_SDP_EINATTR; } *tag = atoi(token); if (*tag == 0) return PJMEDIA_SDP_EINATTR; /* Crypto-suite */ token = strtok(NULL, " "); if (!token) { PJ_LOG(4,(THIS_FILE, "Attribute crypto expecting crypto suite")); return PJMEDIA_SDP_EINATTR; } crypto->name = pj_str(token); /* Key method */ token = strtok(NULL, ":"); if (!token) { PJ_LOG(4,(THIS_FILE, "Attribute crypto expecting key method")); return PJMEDIA_SDP_EINATTR; } if (pj_ansi_stricmp(token, "inline")) { PJ_LOG(4,(THIS_FILE, "Attribute crypto key method '%s' not supported!", token)); return PJMEDIA_SDP_EINATTR; } /* Key */ token = strtok(NULL, "| "); if (!token) { PJ_LOG(4,(THIS_FILE, "Attribute crypto expecting key")); return PJMEDIA_SDP_EINATTR; } tmp = pj_str(token); crypto->key.ptr = (char*) pj_pool_zalloc(pool, MAX_KEY_LEN); /* Decode key */ itmp = MAX_KEY_LEN; status = pj_base64_decode(&tmp, (pj_uint8_t*)crypto->key.ptr, &itmp); if (status != PJ_SUCCESS) { PJ_LOG(4,(THIS_FILE, "Failed decoding crypto key from base64")); return status; } crypto->key.slen = itmp; return PJ_SUCCESS; }