这里采用的是libzrtp来自于freeswitch:libs/libzrtp。
zrtp编译比较简单,采用configure进行编译在根目录心中zrtp编译脚本,只需要指定交叉编译工具链和安装地址即可。脚本如下所示:
unset CC CFLAGS LDFLAGS CPPFLAGS CPP LD STRIP
./configure --host=arm-linux-androideabi --prefix=`pwd`/../objects/
zrtp移植主要对zrtp库进行封装,对外提供初始化和加密解密能力。接口设计如下:
typedef void ZrtpEventObserver(int id, BOOL encrypt);
typedef struct zrtp_handle_t zrtp_handle_t;
typedef struct zrtp_handle_t{
//加密rtp
void (*encrypt_rtp)(zrtp_handle_t *pthis, int channel, unsigned char* in_data,unsigned char* out_data,int bytes_in, int* bytes_out);
//解密rtp
void (*decrypt_rtp)(zrtp_handle_t *pthis, int channel, unsigned char* in_data, unsigned char* out_data, int bytes_in, int* bytes_out);
//加密rtcp
void (*encrypt_rtcp)(zrtp_handle_t *pthis, int channel, unsigned char* in_data,unsigned char* out_data, int bytes_in,int* bytes_out);
//解密rtcp
void (*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();
初始化只需要初始化一次,初始化需要注册发送回调函数,这里协商发送的数据包构造好后最终是有这个接口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;
}
}
每一路会话需要实例化一个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;
}
加解密会用首先会进入到协商流程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) {
// invalid
return 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 20ms
zrtp_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) {
// invalid
return 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 data
memcpy(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) {
// invalid
return;
}
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 data
memcpy(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) {
// invalid
return;
}
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 data
memcpy(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;
}
}
一个完整的ZRTP协商流程,首先是hello进双方加密方式的交换,之后会进行一个收到的应答,然后是commit消息确认HMAC秘钥,DHPart消息交换公钥,Confirm消息确认签名,最后是ConfACK应答。下面是wareshark抓包显示的交互流程。