Merge changes from team/bbryant/keyrotation
authorRussell Bryant <russell@russellbryant.com>
Fri, 1 Aug 2008 18:16:24 +0000 (18:16 +0000)
committerRussell Bryant <russell@russellbryant.com>
Fri, 1 Aug 2008 18:16:24 +0000 (18:16 +0000)
This set of changes enhances IAX2 encryption support by adding key rotation
to provide enhanced security.  The key used for encryption is rotated right
after the call gets set up, and then again every few minutes.  This was
discussed at the last AstriDevCon.  For interoperability with older versions
of Asterisk, there is an option that disables key rotation.

(closes issue #13018)
Reported by: bbryant
Patches:
      07072008__iax2_key_rotation.diff uploaded by bbryant (license 36)
Tested by: russell, bbryant

git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@135158 65c4cc65-6c06-0410-ace0-fbb531ad65f3

CHANGES
channels/chan_iax2.c
channels/iax2-parser.c
channels/iax2.h
configs/iax.conf.sample

diff --git a/CHANGES b/CHANGES
index 0552d86..0b6a15f 100644 (file)
--- 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
 -----------
index e7f59c5..6722818 100644 (file)
@@ -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)) {
index 010c850..09ec3b5 100644 (file)
@@ -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 },
index a945afe..98e7480 100644 (file)
@@ -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 */
index bbdfdca..dc3c465 100644 (file)
@@ -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