diff --git a/CHANGES b/CHANGES index 0552d861b94a2bf5fe6ffb5f023466df59a3b637..0b6a15f7799ee4247145b35e21eaaeaf879444c4 100644 --- a/CHANGES +++ b/CHANGES @@ -137,6 +137,10 @@ SIP Changes IAX Changes ----------- * Existing DNS manager lookups extended to check for SRV records. + * IAX2 encryption support has been improved to support periodic key rotation + within a call for enhanced security. The option "keyrotate" has been + provided to disable this functionality to preserve backwards compatibility + with older versions of IAX2 that do not support key rotation. CLI Changes ----------- diff --git a/channels/chan_iax2.c b/channels/chan_iax2.c index e7f59c51feb5da2d3547fe64add034b4097018da..6722818ce7ca9428ebf807cf312d4035725f5a06 100644 --- a/channels/chan_iax2.c +++ b/channels/chan_iax2.c @@ -203,6 +203,23 @@ int (*iax2_regfunk)(const char *username, int onoff) = NULL; #define DEFAULT_FREQ_OK 60 * 1000 /* How often to check for the host to be up */ #define DEFAULT_FREQ_NOTOK 10 * 1000 /* How often to check, if the host is down... */ +/* if a pvt has encryption setup done and is running on the call */ +#define IAX_CALLENCRYPTED(pvt) \ + (ast_test_flag(pvt, IAX_ENCRYPTED) && ast_test_flag(pvt, IAX_KEYPOPULATED)) + +#define IAX_DEBUGDIGEST(msg, key) do { \ + int idx; \ + char digest[33] = ""; \ + \ + if (!iaxdebug) \ + break; \ + \ + for (idx = 0; idx < 16; idx++) \ + sprintf(digest + (idx << 1), "%2.2x", (unsigned char) key[idx]); \ + \ + ast_log(LOG_NOTICE, msg " IAX_COMMAND_RTKEY to rotate key to '%s'\n", digest); \ + } while(0) + static struct io_context *io; static struct sched_context *sched; @@ -277,6 +294,7 @@ enum iax2_flags { response, so that we've achieved a three-way handshake with them before sending voice or anything else*/ IAX_ALLOWFWDOWNLOAD = (1 << 26), /*!< Allow the FWDOWNL command? */ + IAX_NOKEYROTATE = (1 << 27), /*!< Disable key rotation with encryption */ }; static int global_rtautoclear = 120; @@ -588,6 +606,9 @@ struct chan_iax2_pvt { ast_aes_encrypt_key ecx; /*! Decryption AES-128 Key */ ast_aes_decrypt_key dcx; + /*! scheduler id associated with iax_key_rotate + * for encrypted calls*/ + int keyrotateid; /*! 32 bytes of semi-random data */ unsigned char semirand[32]; /*! Associated registry */ @@ -1411,6 +1432,7 @@ static void iax2_destroy_helper(struct chan_iax2_pvt *pvt) AST_SCHED_DEL(sched, pvt->authid); AST_SCHED_DEL(sched, pvt->initid); AST_SCHED_DEL(sched, pvt->jbid); + AST_SCHED_DEL(sched, pvt->keyrotateid); } static void iax2_frame_free(struct iax_frame *fr) @@ -1479,6 +1501,7 @@ static struct chan_iax2_pvt *new_iax(struct sockaddr_in *sin, const char *host) tmp->autoid = -1; tmp->authid = -1; tmp->initid = -1; + tmp->keyrotateid = -1; ast_string_field_set(tmp,exten, "s"); ast_string_field_set(tmp,host, host); @@ -1768,7 +1791,7 @@ static int __find_callno(unsigned short callno, unsigned short dcallno, struct s iaxs[x]->pingid = iax2_sched_add(sched, ping_time * 1000, send_ping, (void *)(long)x); iaxs[x]->lagid = iax2_sched_add(sched, lagrq_time * 1000, send_lagrq, (void *)(long)x); iaxs[x]->amaflags = amaflags; - ast_copy_flags(iaxs[x], (&globalflags), IAX_NOTRANSFER | IAX_TRANSFERMEDIA | IAX_USEJITTERBUF | IAX_FORCEJITTERBUF); + ast_copy_flags(iaxs[x], &globalflags, IAX_NOTRANSFER | IAX_TRANSFERMEDIA | IAX_USEJITTERBUF | IAX_FORCEJITTERBUF | IAX_NOKEYROTATE); ast_string_field_set(iaxs[x], accountcode, accountcode); ast_string_field_set(iaxs[x], mohinterpret, mohinterpret); @@ -3384,7 +3407,7 @@ static int create_addr(const char *peername, struct ast_channel *c, struct socka if (peer->maxms && ((peer->lastms > peer->maxms) || (peer->lastms < 0))) goto return_unref; - ast_copy_flags(cai, peer, IAX_SENDANI | IAX_TRUNK | IAX_NOTRANSFER | IAX_TRANSFERMEDIA | IAX_USEJITTERBUF | IAX_FORCEJITTERBUF); + ast_copy_flags(cai, peer, IAX_SENDANI | IAX_TRUNK | IAX_NOTRANSFER | IAX_TRANSFERMEDIA | IAX_USEJITTERBUF | IAX_FORCEJITTERBUF | IAX_NOKEYROTATE); cai->maxtime = peer->maxms; cai->capability = peer->capability; cai->encmethods = peer->encmethods; @@ -3808,12 +3831,54 @@ static struct ast_frame *iax2_read(struct ast_channel *c) return &ast_null_frame; } +static int iax2_key_rotate(const void *vpvt) +{ + int res = 0; + struct chan_iax2_pvt *pvt = (void *) vpvt; + struct MD5Context md5; + char key[17] = ""; + struct iax_ie_data ied = { + .pos = 0, + }; + + ast_mutex_lock(&iaxsl[pvt->callno]); + + pvt->keyrotateid = + ast_sched_add(sched, 120000 + (ast_random() % 180001), iax2_key_rotate, vpvt); + + snprintf(key, sizeof(key), "%lX", ast_random()); + + MD5Init(&md5); + MD5Update(&md5, (unsigned char *) key, strlen(key)); + MD5Final((unsigned char *) key, &md5); + + IAX_DEBUGDIGEST("Sending", key); + + iax_ie_append_raw(&ied, IAX_IE_CHALLENGE, key, 16); + + res = send_command(pvt, AST_FRAME_IAX, IAX_COMMAND_RTKEY, 0, ied.buf, ied.pos, -1); + + ast_aes_encrypt_key((unsigned char *) key, &pvt->ecx); + + ast_mutex_unlock(&iaxsl[pvt->callno]); + + return res; +} + static int iax2_start_transfer(unsigned short callno0, unsigned short callno1, int mediaonly) { int res; struct iax_ie_data ied0; struct iax_ie_data ied1; unsigned int transferid = (unsigned int)ast_random(); + + if (IAX_CALLENCRYPTED(iaxs[callno0]) || IAX_CALLENCRYPTED(iaxs[callno1])) { + ast_debug(1, "transfers are not supported for encrypted calls at this time"); + ast_set_flag(iaxs[callno0], IAX_NOTRANSFER); + ast_set_flag(iaxs[callno1], IAX_NOTRANSFER); + return 0; + } + memset(&ied0, 0, sizeof(ied0)); iaxs[callno0]->transferid = transferid; iax_ie_append_addr(&ied0, IAX_IE_APPARENT_ADDR, &iaxs[callno1]->addr); @@ -4720,8 +4785,23 @@ static int iax2_send(struct chan_iax2_pvt *pvt, struct ast_frame *f, unsigned in * (the endpoint should detect the lost packet itself). But, we want to do this here, so that we * increment the "predicted timestamps" for voice, if we're predicting */ if(f->frametype == AST_FRAME_VOICE && f->datalen == 0) - return 0; + return 0; +#if 0 + ast_log(LOG_NOTICE, + "f->frametype %c= AST_FRAME_VOICE, %sencrypted, %srotation scheduled...\n", + *("=!" + (f->frametype == AST_FRAME_VOICE)), + IAX_CALLENCRYPTED(pvt) ? "" : "not ", + pvt->keyrotateid != -1 ? "" : "no " + ); +#endif + if (pvt->keyrotateid == -1 && f->frametype == AST_FRAME_VOICE && IAX_CALLENCRYPTED(pvt)) { + if (ast_test_flag(pvt, IAX_NOKEYROTATE)) { + pvt->keyrotateid = -2; + } else { + iax2_key_rotate(pvt); + } + } if ((ast_test_flag(pvt, IAX_TRUNK) || (((fts & 0xFFFF0000L) == (lastsent & 0xFFFF0000L)) || @@ -5896,6 +5976,7 @@ static int check_access(int callno, struct sockaddr_in *sin, struct iax_ies *ies ast_copy_flags(iaxs[callno], user, IAX_CODEC_USER_FIRST); ast_copy_flags(iaxs[callno], user, IAX_CODEC_NOPREFS); ast_copy_flags(iaxs[callno], user, IAX_CODEC_NOCAP); + ast_copy_flags(iaxs[callno], user, IAX_NOKEYROTATE); iaxs[callno]->encmethods = user->encmethods; /* Store the requested username if not specified */ if (ast_strlen_zero(iaxs[callno]->username)) @@ -9394,7 +9475,20 @@ retryowner2: iaxs[fr->callno]->transferring = TRANSFER_NONE; iaxs[fr->callno]->mediareleased = 1; } - break; + break; + case IAX_COMMAND_RTKEY: + if (!IAX_CALLENCRYPTED(iaxs[fr->callno])) { + ast_log(LOG_WARNING, + "we've been told to rotate our encryption key, " + "but this isn't an encrypted call. bad things will happen.\n" + ); + break; + } + + IAX_DEBUGDIGEST("Receiving", ies.challenge); + + ast_aes_decrypt_key((unsigned char *) ies.challenge, &iaxs[fr->callno]->dcx); + break; case IAX_COMMAND_DPREP: complete_dpreply(iaxs[fr->callno], &ies); break; @@ -9993,7 +10087,7 @@ static struct ast_channel *iax2_request(const char *type, int format, void *data memset(&cai, 0, sizeof(cai)); cai.capability = iax2_capability; - ast_copy_flags(&cai, &globalflags, IAX_NOTRANSFER | IAX_TRANSFERMEDIA | IAX_USEJITTERBUF | IAX_FORCEJITTERBUF); + ast_copy_flags(&cai, &globalflags, IAX_NOTRANSFER | IAX_TRANSFERMEDIA | IAX_USEJITTERBUF | IAX_FORCEJITTERBUF | IAX_NOKEYROTATE); /* Populate our address from the given */ if (create_addr(pds.peer, NULL, &sin, &cai)) { @@ -10012,7 +10106,7 @@ static struct ast_channel *iax2_request(const char *type, int format, void *data } /* If this is a trunk, update it now */ - ast_copy_flags(iaxs[callno], &cai, IAX_TRUNK | IAX_SENDANI | IAX_NOTRANSFER | IAX_TRANSFERMEDIA | IAX_USEJITTERBUF | IAX_FORCEJITTERBUF); + ast_copy_flags(iaxs[callno], &cai, IAX_TRUNK | IAX_SENDANI | IAX_NOTRANSFER | IAX_TRANSFERMEDIA | IAX_USEJITTERBUF | IAX_FORCEJITTERBUF | IAX_NOKEYROTATE); if (ast_test_flag(&cai, IAX_TRUNK)) { int new_callno; if ((new_callno = make_trunk(callno, 1)) != -1) @@ -10353,6 +10447,9 @@ static struct iax2_peer *build_peer(const char *name, struct ast_variable *v, st if (peer) { if (firstpass) { + if (ast_test_flag(&globalflags, IAX_NOKEYROTATE)) { + ast_copy_flags(peer, &globalflags, IAX_NOKEYROTATE); + } ast_copy_flags(peer, &globalflags, IAX_USEJITTERBUF | IAX_FORCEJITTERBUF); peer->encmethods = iax2_encryption; peer->adsi = adsi; @@ -10403,6 +10500,11 @@ static struct iax2_peer *build_peer(const char *name, struct ast_variable *v, st peer->authmethods = get_auth_methods(v->value); } else if (!strcasecmp(v->name, "encryption")) { peer->encmethods = get_encrypt_methods(v->value); + } else if (!strcasecmp(v->name, "keyrotate")) { + if (ast_false(v->value)) + ast_set_flag(peer, IAX_NOKEYROTATE); + else + ast_clear_flag(peer, IAX_NOKEYROTATE); } else if (!strcasecmp(v->name, "transfer")) { if (!strcasecmp(v->value, "mediaonly")) { ast_set_flags_to(peer, IAX_NOTRANSFER|IAX_TRANSFERMEDIA, IAX_TRANSFERMEDIA); @@ -10625,7 +10727,7 @@ static struct iax2_user *build_user(const char *name, struct ast_variable *v, st user->adsi = adsi; ast_string_field_set(user, name, name); ast_string_field_set(user, language, language); - ast_copy_flags(user, &globalflags, IAX_USEJITTERBUF | IAX_FORCEJITTERBUF | IAX_CODEC_USER_FIRST | IAX_CODEC_NOPREFS | IAX_CODEC_NOCAP); + ast_copy_flags(user, &globalflags, IAX_USEJITTERBUF | IAX_FORCEJITTERBUF | IAX_CODEC_USER_FIRST | IAX_CODEC_NOPREFS | IAX_CODEC_NOCAP | IAX_NOKEYROTATE); ast_clear_flag(user, IAX_HASCALLERID); ast_string_field_set(user, cid_name, ""); ast_string_field_set(user, cid_num, ""); @@ -10671,6 +10773,11 @@ static struct iax2_user *build_user(const char *name, struct ast_variable *v, st user->authmethods = get_auth_methods(v->value); } else if (!strcasecmp(v->name, "encryption")) { user->encmethods = get_encrypt_methods(v->value); + } else if (!strcasecmp(v->name, "keyrotate")) { + if (ast_false(v->value)) + ast_set_flag(user, IAX_NOKEYROTATE); + else + ast_clear_flag(user, IAX_NOKEYROTATE); } else if (!strcasecmp(v->name, "transfer")) { if (!strcasecmp(v->value, "mediaonly")) { ast_set_flags_to(user, IAX_NOTRANSFER|IAX_TRANSFERMEDIA, IAX_TRANSFERMEDIA); @@ -11032,7 +11139,12 @@ static int set_config(char *config_file, int reload) authdebug = ast_true(v->value); else if (!strcasecmp(v->name, "encryption")) iax2_encryption = get_encrypt_methods(v->value); - else if (!strcasecmp(v->name, "transfer")) { + else if (!strcasecmp(v->name, "keyrotate")) { + if (ast_false(v->value)) + ast_set_flag((&globalflags), IAX_NOKEYROTATE); + else + ast_clear_flag((&globalflags), IAX_NOKEYROTATE); + } else if (!strcasecmp(v->name, "transfer")) { if (!strcasecmp(v->value, "mediaonly")) { ast_set_flags_to((&globalflags), IAX_NOTRANSFER|IAX_TRANSFERMEDIA, IAX_TRANSFERMEDIA); } else if (ast_true(v->value)) { diff --git a/channels/iax2-parser.c b/channels/iax2-parser.c index 010c85056313226a909f5c0f6cbc9302eaba0fa4..09ec3b5e8efd9dd0fd971cb9b3e1e4935787baf5 100644 --- a/channels/iax2-parser.c +++ b/channels/iax2-parser.c @@ -88,6 +88,16 @@ static void dump_addr(char *output, int maxlen, void *value, int len) } } +static void dump_string_hex(char *output, int maxlen, void *value, int len) +{ + int i = 0; + + while (len-- && (i + 1) * 4 < maxlen) { + sprintf(output + (4 * i), "\\x%2.2x", *((unsigned char *)value + i)); + i++; + } +} + static void dump_string(char *output, int maxlen, void *value, int len) { maxlen--; @@ -229,7 +239,7 @@ static struct iax2_ie { { IAX_IE_ADSICPE, "ADSICPE", dump_short }, { IAX_IE_DNID, "DNID", dump_string }, { IAX_IE_AUTHMETHODS, "AUTHMETHODS", dump_short }, - { IAX_IE_CHALLENGE, "CHALLENGE", dump_string }, + { IAX_IE_CHALLENGE, "CHALLENGE", dump_string_hex }, { IAX_IE_MD5_RESULT, "MD5 RESULT", dump_string }, { IAX_IE_RSA_RESULT, "RSA RESULT", dump_string }, { IAX_IE_APPARENT_ADDR, "APPARENT ADDRESS", dump_addr }, diff --git a/channels/iax2.h b/channels/iax2.h index a945afee4cd1157dc2256689416f2afb405e38e6..98e7480db6be093468ef86c3386db77d1a1bc655 100644 --- a/channels/iax2.h +++ b/channels/iax2.h @@ -109,6 +109,8 @@ enum { IAX_COMMAND_FWDATA = 37, /*! Transfer media only */ IAX_COMMAND_TXMEDIA = 38, + /*! Command to rotate key */ + IAX_COMMAND_RTKEY = 39, }; /*! By default require re-registration once per minute */ diff --git a/configs/iax.conf.sample b/configs/iax.conf.sample index bbdfdca89f48ab00b534b123339fe7a615e1bf67..dc3c46568a5e59d5de53fefef14194d93a016b35 100644 --- a/configs/iax.conf.sample +++ b/configs/iax.conf.sample @@ -172,6 +172,15 @@ forcejitterbuffer=no ; ; trunkmtu = 0 ; +; Enable IAX2 encryption. The default is no. +; +; encryption = yes +; +; This is a compatibility option for older versions of IAX2 that do not support +; key rotation with encryption. This option will disable the IAX_COMMAND_RTENC message. +; default is on +; +; keyrotate=off ; This option defines the maximum size an IAX2 trunk can grow to. The default value is 128000 bytes which ; represents 40ms uncompressed linear with 200 channels. Depending on different things though @@ -385,6 +394,12 @@ inkeys=freeworlddialup ;accountcode=markster0101 ;permit=209.16.236.73/255.255.255.0 ;language=en ; Use english as default language +;encryption=yes ; Enable IAX2 encryption. The default is no. +;keyrotate=off ; This is a compatibility option for older versions of +; ; IAX2 that do not support key rotation with encryption. +; ; This option will disable the IAX_COMMAND_RTENC message. +; ; default is on. +; ; ; ; Peers may also be specified, with a secret and ; a remote hostname. @@ -407,8 +422,13 @@ host=216.207.245.47 ;qualifyfreqnotok = 10000 ; how frequently to ping the peer when it's ; either LAGGED or UNAVAILABLE, in milliseconds ;jitterbuffer=no ; Turn off jitter buffer for this peer - ; +;encryption=yes ; Enable IAX2 encryption. The default is no. +;keyrotate=off ; This is a compatibility option for older versions of +; ; IAX2 that do not support key rotation with encryption. +; ; This option will disable the IAX_COMMAND_RTENC message. +; ; default is on. +; ; ; Peers can remotely register as well, so that they can be mobile. Default ; IP's can also optionally be given but are not required. Caller*ID can be ; suggested to the other side as well if it is for example a phone instead of