freeswitch/libs/libzrtp/src/zrtp_initiator.c
Travis Cross d2edcad66e Merge Phil Zimmermann's libzrtp as a FreeSWITCH library
Thanks to Phil Zimmermann for the code and for the license exception
we needed to include it.

There remains some build system integration work to be done before
this code will build properly in the FreeSWITCH tree.
2012-03-31 23:42:27 +00:00

558 lines
19 KiB
C

/*
* libZRTP SDK library, implements the ZRTP secure VoIP protocol.
* Copyright (c) 2006-2009 Philip R. Zimmermann. All rights reserved.
* Contact: http://philzimmermann.com
* For licensing and other legal details, see the file zrtp_legal.c.
*
* Viktor Krykun <v.krikun at zfoneproject.com>
*/
#include "zrtp.h"
#define _ZTU_ "zrtp initiator"
extern zrtp_status_t _zrtp_machine_start_initiating_secure(zrtp_stream_t *stream);
/*! These functions set constructs and start ZRTP messages replays */
static zrtp_status_t _zrtp_machine_start_send_and_resend_commit(zrtp_stream_t *stream);
static zrtp_status_t _zrtp_machine_start_send_and_resend_dhpart2(zrtp_stream_t *stream);
static zrtp_status_t _zrtp_machine_start_send_and_resend_confirm2(zrtp_stream_t *stream);
/*!
* We need to know the contents of the DH2 packet before we send the Commit to
* compute the hash value. So, we construct DH packet but don't send it till
* WAITING_FOR_CONFIRM1 state.
*/
static void _prepare_dhpart2(zrtp_stream_t *stream);
/*
* Parses DH packet: check for MitM1 attack and makes a copy of the packet for
* later. \exception: Handles all exceptions -- informs user and switches to
* CLEAR.(MITM attacks)
*/
static zrtp_status_t _zrtp_machine_process_incoming_dhpart1( zrtp_stream_t *stream,
zrtp_rtp_info_t *packet);
/*
* Just a wrapper over the protocol::_zrtp_machine_process_confirm().
* \exception: Handles all exceptions -- informs user and switches to
* CLEAR. (SOFTWARE)
*/
static zrtp_status_t _zrtp_machine_process_incoming_confirm1( zrtp_stream_t *stream,
zrtp_rtp_info_t *packet);
/*===========================================================================*/
/* State handlers */
/*===========================================================================*/
/*---------------------------------------------------------------------------*/
zrtp_status_t _zrtp_machine_process_while_in_initiatingsecure( zrtp_stream_t* stream,
zrtp_rtp_info_t* packet)
{
zrtp_status_t s = zrtp_status_ok;
switch (packet->type)
{
case ZRTP_COMMIT:
if (ZRTP_STATEMACHINE_RESPONDER == _zrtp_machine_preparse_commit(stream, packet)) {
_zrtp_cancel_send_packet_later(stream, ZRTP_COMMIT);
s = _zrtp_machine_enter_pendingsecure(stream, packet);
}
break;
case ZRTP_DHPART1:
if (ZRTP_IS_STREAM_DH(stream)) {
_zrtp_cancel_send_packet_later(stream, ZRTP_COMMIT);
s = _zrtp_machine_process_incoming_dhpart1(stream, packet);
if (zrtp_status_ok != s) {
ZRTP_LOG(1,(_ZTU_,"\tERROR! _zrtp_machine_process_incoming_dhpart1() failed with status=%d ID=%u\n.", s, stream->id));
break;
}
_zrtp_machine_start_send_and_resend_dhpart2(stream);
/* Perform Key generation according to draft 5.6 */
s = _zrtp_set_public_value(stream, 1);
if (zrtp_status_ok != s) {
ZRTP_LOG(1,(_ZTU_,"\tERROR! set_public_value1() failed with status=%d ID=%u.\n", s, stream->id));
_zrtp_machine_enter_initiatingerror(stream, zrtp_error_software, 1);
break;
}
_zrtp_change_state(stream, ZRTP_STATE_WAIT_CONFIRM1);
}
break;
case ZRTP_CONFIRM1:
if (ZRTP_IS_STREAM_FAST(stream)) {
s = _zrtp_set_public_value(stream, 1);
if (zrtp_status_ok != s) {
break;
}
s = _zrtp_machine_process_incoming_confirm1(stream, packet);
if (zrtp_status_ok != s) {
ZRTP_LOG(1,(_ZTU_,"\tERROR! process_incoming_confirm1() failed with status=%d ID=%u.\n", s, stream->id));
break;
}
_zrtp_cancel_send_packet_later(stream, ZRTP_COMMIT);
_zrtp_change_state(stream, ZRTP_STATE_WAIT_CONFIRMACK);
s = _zrtp_machine_start_send_and_resend_confirm2(stream);
}
break;
case ZRTP_NONE:
s = zrtp_status_drop;
break;
default:
break;
}
return s;
}
/*---------------------------------------------------------------------------*/
zrtp_status_t _zrtp_machine_process_while_in_waitconfirm1( zrtp_stream_t* stream,
zrtp_rtp_info_t* packet)
{
zrtp_status_t s = zrtp_status_ok;
switch (packet->type)
{
case ZRTP_CONFIRM1:
s = _zrtp_machine_process_incoming_confirm1(stream, packet);
if (zrtp_status_ok != s) {
ZRTP_LOG(1,(_ZTU_,"\tERROR! process_incoming_confirm1() failed with status=%d ID=%u.\n", s, stream->id));
break;
}
_zrtp_change_state(stream, ZRTP_STATE_WAIT_CONFIRMACK);
_zrtp_cancel_send_packet_later(stream, ZRTP_DHPART2);
s = _zrtp_machine_start_send_and_resend_confirm2(stream);
break;
case ZRTP_NONE:
s = zrtp_status_drop;
break;
default:
break;
}
return s;
}
/*---------------------------------------------------------------------------*/
zrtp_status_t _zrtp_machine_process_while_in_waitconfirmack( zrtp_stream_t* stream,
zrtp_rtp_info_t* packet)
{
zrtp_status_t s = zrtp_status_ok;
switch (packet->type)
{
case ZRTP_NONE:
s = _zrtp_protocol_decrypt(stream->protocol, packet, 1);
if (s == zrtp_status_ok) {
/*
* High level functions triggers mutexes for protocol messages only.
* We have manually protect this transaction triggered by media packet, not protocol packet.
*/
zrtp_mutex_lock(stream->stream_protector);
ZRTP_LOG(3,(_ZTU_, "Received FIRST VALID SRTP packet - switching to SECURE state. ID=%u\n", stream->id));
_zrtp_cancel_send_packet_later(stream, ZRTP_CONFIRM2);
_zrtp_machine_enter_secure(stream);
zrtp_mutex_unlock(stream->stream_protector);
}
break;
case ZRTP_CONFIRM2ACK:
_zrtp_cancel_send_packet_later(stream, ZRTP_CONFIRM2);
s = _zrtp_machine_enter_secure(stream);
break;
default:
break;
}
return s;
}
/*===========================================================================*/
/* State switchers */
/*===========================================================================*/
/*---------------------------------------------------------------------------*/
zrtp_status_t _zrtp_machine_enter_initiatingsecure(zrtp_stream_t* stream)
{
zrtp_status_t s = zrtp_status_ok;
ZRTP_LOG(3,(_ZTU_,"\tENTER STATE INITIATING SECURE for ID=%u mode=%s state=%s.\n",
stream->id, zrtp_log_mode2str(stream->mode), zrtp_log_state2str(stream->state)));
if (!ZRTP_IS_STREAM_MULT(stream)) {
uint8_t id = ZRTP_COMP_UNKN;
zrtp_session_t *session = stream->session;
zrtp_packet_Hello_t *peer_hello = &stream->messages.peer_hello;
/*
* ZRTP specification provides that default crypto components may be
* omitted from the Hello message, so we initialize components with
* default values.
*/
session->hash = zrtp_comp_find(ZRTP_CC_HASH, ZRTP_HASH_SHA256, session->zrtp);
session->blockcipher = zrtp_comp_find(ZRTP_CC_CIPHER, ZRTP_CIPHER_AES128, session->zrtp);
session->authtaglength = zrtp_comp_find(ZRTP_CC_ATL, ZRTP_ATL_HS32, session->zrtp);
session->sasscheme = zrtp_comp_find(ZRTP_CC_SAS, ZRTP_SAS_BASE32, session->zrtp);
id = _zrtp_choose_best_comp(&session->profile, peer_hello, ZRTP_CC_HASH);
if (id != ZRTP_COMP_UNKN) {
session->hash = zrtp_comp_find(ZRTP_CC_HASH, id, session->zrtp);
}
id = _zrtp_choose_best_comp(&session->profile, peer_hello, ZRTP_CC_CIPHER);
if (id != ZRTP_COMP_UNKN) {
session->blockcipher = zrtp_comp_find(ZRTP_CC_CIPHER, id, session->zrtp);
}
id = _zrtp_choose_best_comp(&session->profile, peer_hello, ZRTP_CC_ATL);
if (id != ZRTP_COMP_UNKN) {
session->authtaglength = zrtp_comp_find(ZRTP_CC_ATL, id, session->zrtp);
}
id = _zrtp_choose_best_comp(&session->profile, peer_hello, ZRTP_CC_SAS);
if (id != ZRTP_COMP_UNKN) {
session->sasscheme = zrtp_comp_find(ZRTP_CC_SAS, id, session->zrtp);
}
ZRTP_LOG(3,(_ZTU_,"\tInitiator selected following options:\n"));
ZRTP_LOG(3,(_ZTU_,"\t Hash: %.4s\n", session->hash->base.type));
ZRTP_LOG(3,(_ZTU_,"\t Cipher: %.4s\n", session->blockcipher->base.type));
ZRTP_LOG(3,(_ZTU_,"\t ATL: %.4s\n", session->authtaglength->base.type));
ZRTP_LOG(3,(_ZTU_,"\tVAD scheme: %.4s\n", session->sasscheme->base.type));
}
do{
/* Allocate resources for Initiator's state-machine */
s = _zrtp_protocol_init(stream, 1, &stream->protocol);
if (zrtp_status_ok != s) {
break; /* Software error */
}
_zrtp_change_state(stream, ZRTP_STATE_INITIATINGSECURE);
/* Prepare DHPart2 message to compute hvi. For DH and Preshared streams only*/
if (ZRTP_IS_STREAM_DH(stream)) {
_prepare_dhpart2(stream);
}
s = _zrtp_machine_start_send_and_resend_commit(stream);
if (zrtp_status_ok != s) {
break; /* EH: Software error */
}
if (stream->zrtp->cb.event_cb.on_zrtp_protocol_event) {
stream->zrtp->cb.event_cb.on_zrtp_protocol_event(stream, ZRTP_EVENT_IS_INITIATINGSECURE);
}
} while (0);
if (zrtp_status_ok != s) {
if (stream->protocol) {
_zrtp_protocol_destroy(stream->protocol);
stream->protocol = NULL;
}
_zrtp_machine_enter_initiatingerror(stream, zrtp_error_software, 1);
}
if (ZRTP_IS_STREAM_DH(stream)) {
/*
* If stream->concurrent is set this means that we stopped a concurrent
* DH stream to break a tie. This can happen when Commit messages are
* sent by both ZRTP endpoints at the same time, but are received in
* different media streams. Now current stream has finished DH setup and
* we can resume the other one.
*/
if (stream->concurrent) {
zrtp_stream_t* tctx = stream->concurrent;
stream->concurrent = NULL;
ZRTP_LOG(3,(_ZTU_,"\tRelease Concurrent Stream ID=%u. ID=%u\n", tctx->id, stream->id));
_zrtp_machine_start_initiating_secure(tctx);
}
}
return s;
}
/*===========================================================================*/
/* Packet handlers */
/*===========================================================================*/
/*---------------------------------------------------------------------------*/
static zrtp_status_t _zrtp_machine_process_incoming_dhpart1( zrtp_stream_t *stream,
zrtp_rtp_info_t *packet)
{
zrtp_status_t s = zrtp_status_ok;
zrtp_packet_DHPart_t *dhpart1 = (zrtp_packet_DHPart_t*) packet->message;
/* Validating DH (pvr is 1 or p-1) */
bnInsertBigBytes(&stream->dh_cc.peer_pv, dhpart1->pv, 0, stream->pubkeyscheme->pv_length);
s = stream->pubkeyscheme->validate(stream->pubkeyscheme, &stream->dh_cc.peer_pv);
if (zrtp_status_ok != s) {
ZRTP_LOG(2,(_ZTU_,"\tERROR! " ZRTP_MITM1_WARNING_STR " ID=%u\n", stream->id));
_zrtp_machine_enter_initiatingerror(stream, zrtp_error_possible_mitm1, 1);
return s;
}
/* Copy DH Part1 packet for further hashing */
zrtp_memcpy(&stream->messages.peer_dhpart, dhpart1, zrtp_ntoh16(dhpart1->hdr.length)*4);
return s;
}
/*----------------------------------------------------------------------------*/
static zrtp_status_t _zrtp_machine_process_incoming_confirm1( zrtp_stream_t *stream,
zrtp_rtp_info_t *packet)
{
return _zrtp_machine_process_confirm(stream, (zrtp_packet_Confirm_t*) packet->message);
}
/*===========================================================================*/
/* Packet senders */
/*===========================================================================*/
static void _send_and_resend_commit(zrtp_stream_t *stream, zrtp_retry_task_t* task)
{
if (task->_retrys >= ZRTP_T2_MAX_COUNT) {
ZRTP_LOG(2,(_ZTU_,"WARNING! COMMIT Max retransmissions count reached. ID=%u\n", stream->id));
_zrtp_machine_enter_initiatingerror(stream, zrtp_error_timeout, 0);
} else if (task->_is_enabled) {
zrtp_status_t s = zrtp_status_fail;
zrtp_packet_Commit_t* commit = (zrtp_packet_Commit_t*) &stream->messages.commit;
s = _zrtp_packet_send_message(stream, ZRTP_COMMIT, commit);
task->timeout = _zrtp_get_timeout((uint32_t)task->timeout, ZRTP_COMMIT);
if (s == zrtp_status_ok) {
task->_retrys++;
}
if (stream->zrtp->cb.sched_cb.on_call_later) {
stream->zrtp->cb.sched_cb.on_call_later(stream, task);
}
}
}
/*---------------------------------------------------------------------------*/
static zrtp_status_t _zrtp_machine_start_send_and_resend_commit(zrtp_stream_t *stream)
{
zrtp_proto_crypto_t* cc = stream->protocol->cc;
zrtp_packet_Commit_t* commit = &stream->messages.commit;
zrtp_retry_task_t* task = &stream->messages.commit_task;
uint8_t hmac_offset = ZRTP_COMMIT_STATIC_SIZE;
zrtp_session_t *session = stream->session;
zrtp_memcpy(commit->zid, stream->messages.hello.zid, sizeof(zrtp_zid_t));
zrtp_memcpy(commit->hash_type, session->hash->base.type, ZRTP_COMP_TYPE_SIZE);
zrtp_memcpy(commit->cipher_type, session->blockcipher->base.type, ZRTP_COMP_TYPE_SIZE);
zrtp_memcpy(commit->auth_tag_length, session->authtaglength->base.type, ZRTP_COMP_TYPE_SIZE );
zrtp_memcpy(commit->public_key_type, stream->pubkeyscheme->base.type, ZRTP_COMP_TYPE_SIZE);
zrtp_memcpy(commit->sas_type, session->sasscheme->base.type, ZRTP_COMP_TYPE_SIZE);
/*
* According to the last version of the internet draft 08b., hvi should be
* computed as:
* a) hvi=hash(initiator's DHPart2 message | responder's Hello message) for DH stream.
* b) For Multistream it just a 128 bit random nonce.
* c) For Preshared streams it keyID = HMAC(preshared_key, "Prsh") truncated to 64 bits
*/
switch (stream->mode)
{
case ZRTP_STREAM_MODE_DH:
{
void *hash_ctx = session->hash->hash_begin(session->hash);
if (!hash_ctx) {
return zrtp_status_alloc_fail;
}
session->hash->hash_update( session->hash,
hash_ctx,
(const int8_t*)&stream->messages.dhpart,
zrtp_ntoh16(stream->messages.dhpart.hdr.length)*4);
session->hash->hash_update( session->hash,
hash_ctx,
(const int8_t*)&stream->messages.peer_hello,
zrtp_ntoh16(stream->messages.peer_hello.hdr.length)*4);
session->hash->hash_end(session->hash, hash_ctx, ZSTR_GV(cc->hv));
zrtp_memcpy(commit->hv, cc->hv.buffer, ZRTP_HV_SIZE);
hmac_offset += ZRTP_HV_SIZE;
} break;
case ZRTP_STREAM_MODE_PRESHARED:
{
zrtp_string8_t key_id = ZSTR_INIT_EMPTY(key_id);
zrtp_status_t s = zrtp_status_ok;
/* Generate random 4 word nonce */
if (ZRTP_HV_NONCE_SIZE != zrtp_randstr(session->zrtp, (unsigned char*)cc->hv.buffer, ZRTP_HV_NONCE_SIZE)) {
return zrtp_status_rng_fail;
}
cc->hv.length = ZRTP_HV_NONCE_SIZE;
/*
* Generate Preshared_key:
* hash(len(rs1) | rs1 | len(auxsecret) | auxsecret | len(pbxsecret) | pbxsecret)
*/
s = _zrtp_compute_preshared_key( session,
ZSTR_GV(session->secrets.rs1->value),
(session->secrets.auxs->_cachedflag) ? ZSTR_GV(session->secrets.auxs->value) : NULL,
(session->secrets.pbxs->_cachedflag) ? ZSTR_GV(session->secrets.pbxs->value) : NULL,
NULL,
ZSTR_GV(key_id));
if (zrtp_status_ok != s) {
return s;
}
/* Copy 4 word nonce and add 2 word keyID */
zrtp_memcpy(commit->hv, cc->hv.buffer, ZRTP_HV_NONCE_SIZE);
hmac_offset += ZRTP_HV_NONCE_SIZE;
zrtp_memcpy(commit->hv+ZRTP_HV_NONCE_SIZE, key_id.buffer, ZRTP_HV_KEY_SIZE);
hmac_offset += ZRTP_HV_KEY_SIZE;
} break;
case ZRTP_STREAM_MODE_MULT:
{
if(ZRTP_HV_NONCE_SIZE != zrtp_randstr(session->zrtp, (unsigned char*)cc->hv.buffer, ZRTP_HV_NONCE_SIZE)) {
return zrtp_status_rng_fail;
}
cc->hv.length = ZRTP_HV_NONCE_SIZE;
zrtp_memcpy(commit->hv, cc->hv.buffer, ZRTP_HV_NONCE_SIZE);
hmac_offset += ZRTP_HV_NONCE_SIZE;
}break;
default: break;
}
_zrtp_packet_fill_msg_hdr(stream, ZRTP_COMMIT, hmac_offset + ZRTP_HMAC_SIZE, &commit->hdr);
{
char buff[256];
ZRTP_LOG(3,(_ZTU_,"\tStart Sending COMMIT ID=%u mode=%s state=%s:\n",
stream->id, zrtp_log_mode2str(stream->mode), zrtp_log_state2str(stream->state)));
ZRTP_LOG(3,(_ZTU_,"\t Hash: %.4s\n", commit->hash_type));
ZRTP_LOG(3,(_ZTU_,"\t Cipher: %.4s\n", commit->cipher_type));
ZRTP_LOG(3,(_ZTU_,"\t ATL: %.4s\n", commit->auth_tag_length));
ZRTP_LOG(3,(_ZTU_,"\t PK scheme: %.4s\n", commit->public_key_type));
ZRTP_LOG(3,(_ZTU_,"\tVAD scheme: %.4s\n", commit->sas_type));
ZRTP_LOG(3,(_ZTU_,"\t hv: %s\n", hex2str((const char*)commit->hv, ZRTP_HV_SIZE, (char*)buff, sizeof(buff))));
}
task->_is_enabled = 1;
task->callback = _send_and_resend_commit;
task->_retrys = 0;
_send_and_resend_commit(stream, task);
return zrtp_status_ok;
}
/*----------------------------------------------------------------------------*/
static void _send_and_resend_dhpart2(zrtp_stream_t *stream, zrtp_retry_task_t* task)
{
if (task->_retrys >= ZRTP_T2_MAX_COUNT)
{
ZRTP_LOG(1,(_ZTU_,"WARNING! DH2 Max retransmissions count reached. ID=%u\n", stream->id));
_zrtp_machine_enter_initiatingerror(stream, zrtp_error_timeout, 0);
} else if (task->_is_enabled) {
zrtp_status_t s = _zrtp_packet_send_message(stream, ZRTP_DHPART2, &stream->messages.dhpart);
task->timeout = _zrtp_get_timeout((uint32_t)task->timeout, ZRTP_DHPART2);
if (zrtp_status_ok == s) {
task->_retrys++;
}
if (stream->zrtp->cb.sched_cb.on_call_later) {
stream->zrtp->cb.sched_cb.on_call_later(stream, task);
}
}
}
static void _prepare_dhpart2(zrtp_stream_t *stream)
{
zrtp_proto_crypto_t* cc = stream->protocol->cc;
zrtp_packet_DHPart_t *dh2 = &stream->messages.dhpart;
uint16_t dh_length = (uint16_t)stream->pubkeyscheme->pv_length;
zrtp_memcpy(dh2->rs1ID, cc->rs1.id.buffer, ZRTP_RSID_SIZE);
zrtp_memcpy(dh2->rs2ID, cc->rs2.id.buffer, ZRTP_RSID_SIZE);
zrtp_memcpy(dh2->auxsID, cc->auxs.id.buffer, ZRTP_RSID_SIZE);
zrtp_memcpy(dh2->pbxsID, cc->pbxs.id.buffer, ZRTP_RSID_SIZE);
bnExtractBigBytes(&stream->dh_cc.pv, dh2->pv, 0, dh_length);
_zrtp_packet_fill_msg_hdr( stream,
ZRTP_DHPART2,
dh_length + ZRTP_DH_STATIC_SIZE + ZRTP_HMAC_SIZE,
&dh2->hdr );
}
static zrtp_status_t _zrtp_machine_start_send_and_resend_dhpart2(zrtp_stream_t *stream)
{
zrtp_retry_task_t* task = &stream->messages.dhpart_task;
task->_is_enabled = 1;
task->callback = _send_and_resend_dhpart2;
task->_retrys = 0;
_send_and_resend_dhpart2(stream, task);
return zrtp_status_ok;
}
/*---------------------------------------------------------------------------*/
static void _send_and_resend_confirm2(zrtp_stream_t *stream, zrtp_retry_task_t* task)
{
if (task->_retrys >= ZRTP_T2_MAX_COUNT) {
ZRTP_LOG(1,(_ZTU_,"WARNING! CONFIRM2 Max retransmissions count reached. ID=%u\n", stream->id));
_zrtp_machine_enter_initiatingerror(stream, zrtp_error_timeout, 0);
} else if (task->_is_enabled) {
zrtp_status_t s = zrtp_status_ok;
s = _zrtp_packet_send_message(stream, ZRTP_CONFIRM2, &stream->messages.confirm);
task->timeout = _zrtp_get_timeout((uint32_t)task->timeout, ZRTP_CONFIRM2);
if (zrtp_status_ok == s) {
task->_retrys++;
}
if (stream->zrtp->cb.sched_cb.on_call_later) {
stream->zrtp->cb.sched_cb.on_call_later(stream, task);
}
}
}
static zrtp_status_t _zrtp_machine_start_send_and_resend_confirm2(zrtp_stream_t *stream)
{
zrtp_retry_task_t* task = &stream->messages.confirm_task;
zrtp_status_t s = _zrtp_machine_create_confirm(stream, &stream->messages.confirm);
if (zrtp_status_ok != s) {
return s;
}
s = _zrtp_packet_fill_msg_hdr( stream,
ZRTP_CONFIRM2,
sizeof(zrtp_packet_Confirm_t) - sizeof(zrtp_msg_hdr_t),
&stream->messages.confirm.hdr);
if (zrtp_status_ok == s) {
task->_is_enabled = 1;
task->callback = _send_and_resend_confirm2;
task->_retrys = 0;
_send_and_resend_confirm2(stream, task);
}
return s;
}