1 ZRTP源码下载
这里采用的是libzrtp来自于freeswitch:libs/libzrtp。
2 ZRTP交叉编译
zrtp编译比较简单,采用configure进行编译在根目录心中zrtp编译脚本,只需要指定交叉编译工具链和安装地址即可。脚本如下所示:
unset CC CFLAGS LDFLAGS CPPFLAGS CPP LD STRIP
./configure --host=arm-linux-androideabi --prefix=`pwd`/../objects/
成果物如下所示include和lib库:
3 ZRTP移植
zrtp移植主要对zrtp库进行封装,对外提供初始化和加密解密能力。接口设计如下:
3.1 API设计
typedef void ZrtpEventObserver(int id, BOOL encrypt);typedef struct zrtp_handle_t zrtp_handle_t;typedef struct zrtp_handle_t{//加密rtpvoid (*encrypt_rtp)(zrtp_handle_t *pthis, int channel, unsigned char* in_data,unsigned char* out_data,int bytes_in, int* bytes_out);//解密rtpvoid (*decrypt_rtp)(zrtp_handle_t *pthis, int channel, unsigned char* in_data, unsigned char* out_data, int bytes_in, int* bytes_out);//加密rtcpvoid (*encrypt_rtcp)(zrtp_handle_t *pthis, int channel, unsigned char* in_data,unsigned char* out_data, int bytes_in,int* bytes_out);//解密rtcpvoid (*decrypt_rtcp)(zrtp_handle_t *pthis, int channel, unsigned char* in_data, unsigned char* out_data, int bytes_in, int* bytes_out);BOOL (*is_enabled)(zrtp_handle_t *pthis);void *priv;
}zrtp_handle_t;//构建zrtp会话
int zrtp_handle_alloc(zrtp_handle_t **ppthis, const WebRtc_Word32 id, ZrtpEventObserver *observer);
//释放zrtp会话
void zrtp_handle_free(zrtp_handle_t *pthis);
//初始化
int zrtp_handle_init();
//反初始化
void zrtp_handle_denit();
3.2 初始化
初始化只需要初始化一次,初始化需要注册发送回调函数,这里协商发送的数据包构造好后最终是有这个接口on_send_packet返回到应用发送。
int zrtp_handle_init()
{zrtp_config_defaults(&g_zrtp_config);zrtp_randstr2((unsigned char *)g_zrtp_config.client_id, sizeof(zrtp_client_id_t));
// zrtp_randstr2((unsigned char *)g_zrtp_config.zid, sizeof(zrtp_zid_t));g_zrtp_config.lic_mode = ZRTP_LICENSE_MODE_ACTIVE;g_zrtp_config.cb.misc_cb.on_send_packet = on_send_packet;g_zrtp_config.cb.event_cb.on_zrtp_protocol_event = on_zrtp_protocol_event;g_zrtp_config.cb.event_cb.on_zrtp_security_event = on_zrtp_security_event;g_zrtp_config.cb.event_cb.on_zrtp_secure = on_zrtp_secure;g_zrtp_config.cb.event_cb.on_zrtp_not_secure = on_zrtp_not_secure;zrtp_status_t s = zrtp_init(&g_zrtp_config, &g_pzrtp_global);if (zrtp_status_ok != s) {printf("ZRTP init failed, status = %d \n", s);return -1;}return 0;
}void zrtp_handle_denit()
{if (NULL != g_pzrtp_global) {zrtp_down(g_pzrtp_global);g_pzrtp_global = NULL;}
}
3.3 会话实例
每一路会话需要实例化一个zrtphandle对象,需要传入一个随机的zrtpid,和观察者。
int zrtp_handle_alloc(zrtp_handle_t **ppthis, const WebRtc_Word32 id, ZrtpEventObserver *observer)
{if(!ppthis)return -1; zrtp_handle_t *phl = (zrtp_handle_t *)malloc(sizeof(zrtp_handle_t)); if(!phl){return -1;}priv_t *ppriv = (priv_t *)malloc(sizeof(priv_t)); if(!ppriv){free(phl);return -1;}memset(phl, 0, sizeof(zrtp_handle_t));memset(ppriv, 0, sizeof(priv_t));phl->priv = ppriv; ppriv->__zrtp_session = NULL;ppriv->__zrtp_audio = NULL;ppriv->__lSSRC = 0;ppriv->__rSSRC = 0;ppriv->__stream_seq = 0;ppriv->__stream_timestamp = 0;ppriv->__is_first_start_stream = 0;ppriv->__is_need_send_hello = 0;ppriv->__is_zrtp_enable = 0;ppriv->__obverserptr = observer;ppriv->id = id;ppriv->__buffer_out_data = (char *)malloc(IP_PACKET_SIZE);phl->audio_encrypt_rtp = encrypt;phl->audio_decrypt_rtp = decrypt;phl->audio_encrypt_rtcp = encrypt_rtcp;phl->audio_decrypt_rtcp = decrypt_rtcp;*ppthis = phl;return 0;
}void zrtp_handle_free(zrtp_handle_t *pthis)
{if(!pthis)return;DisableZsrtp(pthis);priv_t *ppriv = pthis->priv;if (NULL != ppriv->__buffer_out_data) {free(ppriv->__buffer_out_data);ppriv->__buffer_out_data = NULL;}if (pthis->priv){free(pthis->priv);pthis->priv = NULL;} free(pthis);pthis = NULL;
}
3.4 加解密实现
加解密会用首先会进入到协商流程zrtp_stream_start完成协商,之后再进入加解密流程zrtp_process_rtp。
int encrypt(zrtp_handle_t *pthis,int channel,unsigned char* in_data,unsigned char* out_data,int bytes_in,int* bytes_out) {priv_t *ppriv = pthis->priv;if (!ppriv->__is_zrtp_enable || NULL == in_data || NULL == out_data|| 0 > bytes_in || NULL == bytes_out) {// invalidreturn 0;}if(ppriv->__is_need_send_hello){zrtp_stream_start(ppriv->__zrtp_audio, ppriv->__lSSRC);ppriv->__is_need_send_hello = RL_FALSE;}if(!ppriv->__buffer_out_data_consumed){++ppriv->__buffer_out_data_retry;memcpy(out_data, ppriv->__buffer_out_data, ppriv->__buffer_out_data_bytes);*bytes_out = ppriv->__buffer_out_data_bytes;ppriv->__buffer_out_data_consumed = RL_TRUE;return 0;}if (-1 == ppriv->__voice_encrypt_status && ppriv->__buffer_out_data_retry == ZRTP_COUNT_THRESHOLD_DEFAULT){// LinKy: Rollback to unecrypt, this operation will close zrtp session!UpdateEncryptStatus(pthis, RL_FALSE);//return 0;}if (ppriv->__zrtp_audio->state != ZRTP_STATE_SECURE) {// Not ready, use original data memcpy(out_data, in_data, bytes_in);*bytes_out = bytes_in;return 1;}char packet[IP_PACKET_SIZE];ZRTP_UNALIGNED(zrtp_rtp_hdr_t) *rtp_hdr = (zrtp_rtp_hdr_t*)packet;/* Fill RTP Header according to the specification */zrtp_memset(rtp_hdr, 0, sizeof(zrtp_rtp_hdr_t));rtp_hdr->version = 2; /* Current RTP version 2 */rtp_hdr->pt = 0; /* PCMU padding type */rtp_hdr->ssrc = zrtp_hton32(ppriv->__lSSRC); /* Use stream Identifier as it's SSRC */if (ppriv->__stream_seq >= 0xFFFF) {ppriv->__stream_seq = 0;}rtp_hdr->seq = libzrtp_swap16(ppriv->__stream_seq++);rtp_hdr->ts = zrtp_hton32(ppriv->__stream_timestamp);ppriv->__stream_timestamp += 20; // LinKy: Assume the interval is 20mszrtp_memcpy(packet + sizeof(zrtp_rtp_hdr_t), in_data, bytes_in);unsigned int size = sizeof(zrtp_rtp_hdr_t) + bytes_in;zrtp_status_t s = zrtp_process_rtp(ppriv->__zrtp_audio, packet, &size);switch (s) {case zrtp_status_ok: {//// Packet was successfully decrypted. Dont forget that packet// size was changed during decryption. New size now in size //memcpy(out_data, packet, size);*bytes_out = size;return 2;}break;case zrtp_status_drop: {//// This is a protocol ZRTP packet or masked RTP media.// In either case the packet must be dropped to protect your // private data and media codec// LinKy: Shall we rollback to unencrypt here?return 3;}break;case zrtp_status_fail: {//// This is some kind of error - see logs for more information.// Don't put such packet to the network. It is not secure.//memcpy(out_data, in_data, bytes_in);*bytes_out = bytes_in;return 4;}break;}return 0;
}int decrypt(zrtp_handle_t *pthis,int channel,unsigned char* in_data,unsigned char* out_data,int bytes_in,int* bytes_out) {unsigned int size = bytes_in;priv_t *ppriv = pthis->priv;if (!ppriv->__is_zrtp_enable || NULL == in_data || NULL == out_data|| 0 > bytes_in || NULL == bytes_out) {// invalidreturn 0;}if(ppriv->__is_need_send_hello){zrtp_stream_start(ppriv->__zrtp_audio, ppriv->__lSSRC);ppriv->__is_need_send_hello = RL_FALSE;}//if zrtp success,ppriv->__buffer_in_data_retry Approximately equals 18;if (-1 == ppriv->__voice_encrypt_status && ppriv->__buffer_in_data_retry >= ZRTP_COUNT_THRESHOLD_DEFAULT){//if (ppriv->__zrtp_audio->state != ZRTP_STATE_SECURE){// Not ready, use original datamemcpy(out_data, in_data, bytes_in);*bytes_out = bytes_in;return 1;}else if(ppriv->__voice_encrypt_status == 0){memcpy(out_data, in_data, bytes_in);*bytes_out = bytes_in;return 1;}ppriv->__buffer_in_data_retry++;char packet[IP_PACKET_SIZE];memcpy(packet, in_data, bytes_in);zrtp_status_t s = zrtp_process_srtp(ppriv->__zrtp_audio, packet, &size);switch (s) {case zrtp_status_ok: {//// Packet was successfully decrypted. Dont forget that packet// size was changed during decryption. New size now in size //char *body = packet + sizeof(zrtp_rtp_hdr_t);memcpy(out_data, body, size - sizeof(zrtp_rtp_hdr_t));*bytes_out = size - sizeof(zrtp_rtp_hdr_t);return 2;}break;case zrtp_status_drop: {//// This is a protocol ZRTP packet or masked RTP media.// In either case the packet must be dropped to protect your // private data and media codec// LinKy: Yep, we drop it, use original data to play!memcpy(out_data, in_data, bytes_in);*bytes_out = bytes_in;return 3;}break;case zrtp_status_fail: {//// This is some kind of error - see logs for more information.// Don't put such packet to the network. It is not secure.//// LinKy: Be careful! This may cause noise if data is encrypted actually!memcpy(out_data, in_data, bytes_in);*bytes_out = bytes_in;return 4;}break;}return 0;
}void encrypt_rtcp(zrtp_handle_t *pthis,int channel,unsigned char* in_data,unsigned char* out_data,int bytes_in,int* bytes_out) {priv_t *ppriv = pthis->priv;unsigned int size = bytes_in;if (!ppriv->__is_zrtp_enable || NULL == in_data || NULL == out_data|| 0 > bytes_in || NULL == bytes_out) {// invalidreturn;}if(ppriv->__is_need_send_hello){zrtp_stream_start(ppriv->__zrtp_audio, ppriv->__lSSRC);ppriv->__is_need_send_hello = RL_FALSE;}if (ppriv->__zrtp_audio->state != ZRTP_STATE_SECURE) {// Not ready, use original datamemcpy(out_data, in_data, bytes_in);*bytes_out = bytes_in;return;}char packet[IP_PACKET_SIZE];ZRTP_UNALIGNED(zrtp_rtcp_hdr_t) *rtcp_hdr = (zrtp_rtcp_hdr_t*)packet;/* Fill RTCP Header according to the specification */rtcp_hdr->rc = 0;rtcp_hdr->version = 2;rtcp_hdr->ssrc = zrtp_hton32(ppriv->__lSSRC);/* Get RTP body from PGP words lists. Put RTCP marker at the beginning */zrtp_memcpy(packet + sizeof(zrtp_rtcp_hdr_t), "RTCP", 4); zrtp_memcpy(packet + sizeof(zrtp_rtcp_hdr_t) + 4, in_data, bytes_in);size = sizeof(zrtp_rtcp_hdr_t) + bytes_in + 4;/* RTCP packets sould be 32 byes aligned */size += (size % 4) ? (4 - size % 4) : 0;zrtp_status_t s = zrtp_process_rtcp(ppriv->__zrtp_audio, packet, &size);switch (s) {case zrtp_status_ok: {//// Packet was successfully decrypted. Dont forget that packet// size was changed during decryption. New size now in size //memcpy(out_data, packet, size);*bytes_out = size;}break;case zrtp_status_drop: {//// This is a protocol ZRTP packet or masked RTP media.// In either case the packet must be dropped to protect your // private data and media codec// LinKy: Shall we rollback to unencrypt here?}break;case zrtp_status_fail: {//// This is some kind of error - see logs for more information.// Don't put such packet to the network. It is not secure.//memcpy(out_data, in_data, bytes_in);*bytes_out = bytes_in;}break;}
}void decrypt_rtcp(zrtp_handle_t *pthis,int channel,unsigned char* in_data,unsigned char* out_data,int bytes_in,int* bytes_out) {priv_t *ppriv = pthis->priv;unsigned int size = bytes_in;if (!ppriv->__is_zrtp_enable || NULL == in_data || NULL == out_data|| 0 > bytes_in || NULL == bytes_out) {// invalidreturn;}if(ppriv->__is_first_start_stream){zrtp_stream_start(ppriv->__zrtp_audio, channel);ppriv->__is_first_start_stream = RL_FALSE;}if (ppriv->__zrtp_audio->state != ZRTP_STATE_SECURE) {// Not ready, use original datamemcpy(out_data, in_data, bytes_in);*bytes_out = bytes_in;return;}char packet[IP_PACKET_SIZE];memcpy(packet,in_data,bytes_in);zrtp_status_t s = zrtp_process_srtcp(ppriv->__zrtp_audio, packet, &size);switch (s) {case zrtp_status_ok: {//// Packet was successfully decrypted. Dont forget that packet// size was changed during decryption. New size now in size //char *body = packet + sizeof(zrtp_rtp_hdr_t);memcpy(out_data, body, size - sizeof(zrtp_rtp_hdr_t));*bytes_out = size - sizeof(zrtp_rtp_hdr_t);}break;case zrtp_status_drop: {//// This is a protocol ZRTP packet or masked RTP media.// In either case the packet must be dropped to protect your // private data and media codec// LinKy: Yep, we drop it, use original data to play!memcpy(out_data, in_data, bytes_in);*bytes_out = bytes_in;}break;case zrtp_status_fail: {//// This is some kind of error - see logs for more information.// Don't put such packet to the network. It is not secure.//// LinKy: Be careful! This may cause noise if data is encrypted actually!memcpy(out_data, in_data, bytes_in);*bytes_out = bytes_in;}break;}
}
4 ZRTP抓包分析
一个完整的ZRTP协商流程,首先是hello进双方加密方式的交换,之后会进行一个收到的应答,然后是commit消息确认HMAC秘钥,DHPart消息交换公钥,Confirm消息确认签名,最后是ConfACK应答。下面是wareshark抓包显示的交互流程。