diff options
author | twilson <twilson@f38db490-d61c-443f-a65b-d21fe96a405b> | 2010-06-08 05:29:08 +0000 |
---|---|---|
committer | twilson <twilson@f38db490-d61c-443f-a65b-d21fe96a405b> | 2010-06-08 05:29:08 +0000 |
commit | 9b1a36a294342fc418d9a359a4cf06bd90c4acb9 (patch) | |
tree | ecc27fc0db142ea1cd335a74cd1265f993fecd11 /channels | |
parent | 5f87b66641d86dbe7afec3b083016b2b1aceafc7 (diff) |
Add SRTP support for Asterisk
After 5 years in mantis and over a year on reviewboard, SRTP support is finally
being comitted. This includes generic CHANNEL dialplan functions that work for
getting the status of whether a call has secure media or signaling as defined
by the underlying channel technology and for setting whether or not a new
channel being bridged to a calling channel should have secure signaling or
media. See doc/tex/secure-calls.tex for examples.
Original patch by mikma, updated for trunk and revised by me.
(closes issue #5413)
Reported by: mikma
Tested by: twilson, notthematrix, hemanshurpatel
Review: https://reviewboard.asterisk.org/r/191/
git-svn-id: http://svn.digium.com/svn/asterisk/trunk@268894 f38db490-d61c-443f-a65b-d21fe96a405b
Diffstat (limited to 'channels')
-rw-r--r-- | channels/chan_iax2.c | 39 | ||||
-rw-r--r-- | channels/chan_sip.c | 247 | ||||
-rw-r--r-- | channels/sip/dialplan_functions.c | 4 | ||||
-rw-r--r-- | channels/sip/include/sdp_crypto.h | 82 | ||||
-rw-r--r-- | channels/sip/include/sip.h | 9 | ||||
-rw-r--r-- | channels/sip/include/srtp.h | 57 | ||||
-rw-r--r-- | channels/sip/sdp_crypto.c | 310 | ||||
-rw-r--r-- | channels/sip/srtp.c | 51 |
8 files changed, 781 insertions, 18 deletions
diff --git a/channels/chan_iax2.c b/channels/chan_iax2.c index 04ce7ee7b..591e2d68a 100644 --- a/channels/chan_iax2.c +++ b/channels/chan_iax2.c @@ -1171,6 +1171,7 @@ static int iax2_sendhtml(struct ast_channel *c, int subclass, const char *data, static int iax2_sendimage(struct ast_channel *c, struct ast_frame *img); static int iax2_sendtext(struct ast_channel *c, const char *text); static int iax2_setoption(struct ast_channel *c, int option, void *data, int datalen); +static int iax2_queryoption(struct ast_channel *c, int option, void *data, int *datalen); static int iax2_transfer(struct ast_channel *c, const char *dest); static int iax2_write(struct ast_channel *c, struct ast_frame *f); static int send_trunk(struct iax2_trunk_peer *tpeer, struct timeval *now); @@ -1218,6 +1219,7 @@ static const struct ast_channel_tech iax2_tech = { .write_video = iax2_write, .indicate = iax2_indicate, .setoption = iax2_setoption, + .queryoption = iax2_queryoption, .bridge = iax2_bridge, .transfer = iax2_transfer, .fixup = iax2_fixup, @@ -4903,6 +4905,11 @@ static int iax2_call(struct ast_channel *c, char *dest, int timeout) ast_log(LOG_WARNING, "No address associated with '%s'\n", pds.peer); return -1; } + if (ast_test_flag64(iaxs[callno], IAX_FORCE_ENCRYPT) && !cai.encmethods) { + ast_log(LOG_WARNING, "Encryption forced for call, but not enabled\n"); + c->hangupcause = AST_CAUSE_BEARERCAPABILITY_NOTAVAIL; + return -1; + } if (ast_strlen_zero(cai.secret) && ast_test_flag64(iaxs[callno], IAX_FORCE_ENCRYPT)) { ast_log(LOG_WARNING, "Call terminated. No secret given and force encrypt enabled\n"); return -1; @@ -5144,6 +5151,19 @@ static int iax2_setoption(struct ast_channel *c, int option, void *data, int dat case AST_OPTION_OPRMODE: errno = EINVAL; return -1; + case AST_OPTION_SECURE_SIGNALING: + case AST_OPTION_SECURE_MEDIA: + { + unsigned short callno = PTR_TO_CALLNO(c->tech_pvt); + ast_mutex_lock(&iaxsl[callno]); + if ((*(int *) data)) { + ast_set_flag64(iaxs[callno], IAX_FORCE_ENCRYPT); + } else { + ast_clear_flag64(iaxs[callno], IAX_FORCE_ENCRYPT); + } + ast_mutex_unlock(&iaxsl[callno]); + return 0; + } default: { unsigned short callno = PTR_TO_CALLNO(c->tech_pvt); @@ -5175,6 +5195,23 @@ static int iax2_setoption(struct ast_channel *c, int option, void *data, int dat } } +static int iax2_queryoption(struct ast_channel *c, int option, void *data, int *datalen) +{ + switch (option) { + case AST_OPTION_SECURE_SIGNALING: + case AST_OPTION_SECURE_MEDIA: + { + unsigned short callno = PTR_TO_CALLNO(c->tech_pvt); + ast_mutex_lock(&iaxsl[callno]); + *((int *) data) = ast_test_flag64(iaxs[callno], IAX_FORCE_ENCRYPT) ? 1 : 0; + ast_mutex_unlock(&iaxsl[callno]); + return 0; + } + default: + return -1; + } +} + static struct ast_frame *iax2_read(struct ast_channel *c) { ast_log(LOG_NOTICE, "I should never be called!\n"); @@ -13609,6 +13646,8 @@ static int acf_channel_read(struct ast_channel *chan, const char *funcname, char ast_copy_string(buf, pvt->addr.sin_addr.s_addr ? ast_inet_ntoa(pvt->addr.sin_addr) : "", buflen); } else if (!strcasecmp(args, "peername")) { ast_copy_string(buf, pvt->username, buflen); + } else if (!strcasecmp(args, "secure_signaling") || !strcasecmp(args, "secure_media")) { + snprintf(buf, buflen, "%s", IAX_CALLENCRYPTED(pvt) ? "1" : ""); } else { res = -1; } diff --git a/channels/chan_sip.c b/channels/chan_sip.c index a1bfcfe7e..b79f35a1a 100644 --- a/channels/chan_sip.c +++ b/channels/chan_sip.c @@ -267,11 +267,14 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "sip/include/config_parser.h" #include "sip/include/reqresp_parser.h" #include "sip/include/sip_utils.h" +#include "sip/include/srtp.h" +#include "sip/include/sdp_crypto.h" #include "asterisk/ccss.h" #include "asterisk/xml.h" #include "sip/include/dialog.h" #include "sip/include/dialplan_functions.h" + /*** DOCUMENTATION <application name="SIPDtmfMode" language="en_US"> <synopsis> @@ -1566,6 +1569,10 @@ static void handle_response_subscribe(struct sip_pvt *p, int resp, const char *r static int handle_response_register(struct sip_pvt *p, int resp, const char *rest, struct sip_request *req, int seqno); static void handle_response(struct sip_pvt *p, int resp, const char *rest, struct sip_request *req, int seqno); +/*------ SRTP Support -------- */ +static int setup_srtp(struct sip_srtp **srtp); +static int process_crypto(struct sip_pvt *p, struct ast_rtp_instance *rtp, struct sip_srtp **srtp, const char *a); + /*------ T38 Support --------- */ static int transmit_response_with_t38_sdp(struct sip_pvt *p, char *msg, struct sip_request *req, int retrans); static struct ast_udptl *sip_get_udptl_peer(struct ast_channel *chan); @@ -3910,7 +3917,16 @@ static int sip_setoption(struct ast_channel *chan, int option, void *data, int d res = 0; } break; + case AST_OPTION_SECURE_SIGNALING: + p->req_secure_signaling = *(unsigned int *) data; + res = 0; + break; + case AST_OPTION_SECURE_MEDIA: + ast_set2_flag(&p->flags[1], *(unsigned int *) data, SIP_PAGE2_USE_SRTP); + res = 0; + break; default: + ast_log(LOG_NOTICE, "Unknown option: %d\n", option); break; } @@ -3961,6 +3977,14 @@ static int sip_queryoption(struct ast_channel *chan, int option, void *data, int *cp = p->dsp ? 1 : 0; ast_debug(1, "Reporting digit detection %sabled on %s\n", *cp ? "en" : "dis", chan->name); break; + case AST_OPTION_SECURE_SIGNALING: + *((unsigned int *) data) = p->req_secure_signaling; + res = 0; + break; + case AST_OPTION_SECURE_MEDIA: + *((unsigned int *) data) = ast_test_flag(&p->flags[1], SIP_PAGE2_USE_SRTP) ? 1 : 0; + res = 0; + break; case AST_OPTION_DEVICE_NAME: if (p && p->outgoing_call) { cp = (char *) data; @@ -5004,6 +5028,35 @@ static int sip_call(struct ast_channel *ast, char *dest, int timeout) } } + /* Check to see if we should try to force encryption */ + if (p->req_secure_signaling && p->socket.type != SIP_TRANSPORT_TLS) { + ast_log(LOG_WARNING, "Encrypted signaling is required\n"); + ast->hangupcause = AST_CAUSE_BEARERCAPABILITY_NOTAVAIL; + return -1; + } + + if (ast_test_flag(&p->flags[1], SIP_PAGE2_USE_SRTP)) { + if (ast_test_flag(&p->flags[0], SIP_REINVITE)) { + ast_debug(1, "Direct media not possible when using SRTP, ignoring canreinvite setting\n"); + ast_clear_flag(&p->flags[0], SIP_REINVITE); + } + + if (p->rtp && !p->srtp && setup_srtp(&p->srtp) < 0) { + ast_log(LOG_WARNING, "SRTP audio setup failed\n"); + return -1; + } + + if (p->vrtp && !p->vsrtp && setup_srtp(&p->vsrtp) < 0) { + ast_log(LOG_WARNING, "SRTP video setup failed\n"); + return -1; + } + + if (p->trtp && !p->vsrtp && setup_srtp(&p->tsrtp) < 0) { + ast_log(LOG_WARNING, "SRTP text setup failed\n"); + return -1; + } + } + res = 0; ast_set_flag(&p->flags[0], SIP_OUTGOING); @@ -5211,6 +5264,21 @@ void __sip_destroy(struct sip_pvt *p, int lockowner, int lockdialoglist) p->chanvars = NULL; } + if (p->srtp) { + sip_srtp_destroy(p->srtp); + p->srtp = NULL; + } + + if (p->vsrtp) { + sip_srtp_destroy(p->vsrtp); + p->vsrtp = NULL; + } + + if (p->tsrtp) { + sip_srtp_destroy(p->tsrtp); + p->tsrtp = NULL; + } + if (p->directmediaha) { ast_free_ha(p->directmediaha); p->directmediaha = NULL; @@ -7671,6 +7739,10 @@ static int process_sdp(struct sip_pvt *p, struct sip_request *req, int t38action const char *codecs; int codec; + /* SRTP */ + int secure_audio = FALSE; + int secure_video = FALSE; + /* Others */ int sendonly = -1; int vsendonly = -1; @@ -7770,6 +7842,7 @@ static int process_sdp(struct sip_pvt *p, struct sip_request *req, int t38action int video = FALSE; int image = FALSE; int text = FALSE; + char protocol[5] = {0,}; int x; numberofports = 1; @@ -7780,8 +7853,14 @@ static int process_sdp(struct sip_pvt *p, struct sip_request *req, int t38action nextm = get_sdp_iterate(&next, req, "m"); /* Search for audio media definition */ - if ((sscanf(m, "audio %30u/%30u RTP/AVP %n", &x, &numberofports, &len) == 2 && len > 0) || - (sscanf(m, "audio %30u RTP/AVP %n", &x, &len) == 1 && len > 0)) { + if ((sscanf(m, "audio %30u/%30u RTP/%4s %n", &x, &numberofports, protocol, &len) == 3 && len > 0) || + (sscanf(m, "audio %30u RTP/%4s %n", &x, protocol, &len) == 2 && len > 0)) { + if (!strcmp(protocol, "SAVP")) { + secure_audio = 1; + } else if (strcmp(protocol, "AVP")) { + ast_log(LOG_WARNING, "unknown SDP media protocol in offer: %s\n", protocol); + continue; + } audio = TRUE; p->offered_media[SDP_AUDIO].offered = TRUE; numberofmediastreams++; @@ -7801,8 +7880,14 @@ static int process_sdp(struct sip_pvt *p, struct sip_request *req, int t38action ast_rtp_codecs_payloads_set_m_type(&newaudiortp, NULL, codec); } /* Search for video media definition */ - } else if ((sscanf(m, "video %30u/%30u RTP/AVP %n", &x, &numberofports, &len) == 2 && len > 0) || - (sscanf(m, "video %30u RTP/AVP %n", &x, &len) == 1 && len >= 0)) { + } else if ((sscanf(m, "video %30u/%30u RTP/%4s %n", &x, &numberofports, protocol, &len) == 3 && len > 0) || + (sscanf(m, "video %30u RTP/%4s %n", &x, protocol, &len) == 2 && len >= 0)) { + if (!strcmp(protocol, "SAVP")) { + secure_video = 1; + } else if (strcmp(protocol, "AVP")) { + ast_log(LOG_WARNING, "unknown SDP media protocol in offer: %s\n", protocol); + continue; + } video = TRUE; p->novideo = FALSE; p->offered_media[SDP_VIDEO].offered = TRUE; @@ -7864,8 +7949,6 @@ static int process_sdp(struct sip_pvt *p, struct sip_request *req, int t38action if (numberofports > 1) ast_log(LOG_WARNING, "SDP offered %d ports for media, not supported by Asterisk. Will try anyway...\n", numberofports); - - /* Media stream specific parameters */ while ((type = get_sdp_line(&iterator, next - 1, req, &value)) != '\0') { int processed = FALSE; @@ -7899,6 +7982,8 @@ static int process_sdp(struct sip_pvt *p, struct sip_request *req, int t38action if (audio) { if (process_sdp_a_sendonly(value, &sendonly)) processed = TRUE; + else if (process_crypto(p, p->rtp, &p->srtp, value)) + processed = TRUE; else if (process_sdp_a_audio(value, p, &newaudiortp, &last_rtpmap_codec)) processed = TRUE; } @@ -7906,6 +7991,8 @@ static int process_sdp(struct sip_pvt *p, struct sip_request *req, int t38action else if (video) { if (process_sdp_a_sendonly(value, &vsendonly)) processed = TRUE; + else if (process_crypto(p, p->vrtp, &p->vsrtp, value)) + processed = TRUE; else if (process_sdp_a_video(value, p, &newvideortp, &last_rtpmap_codec)) processed = TRUE; } @@ -7913,6 +8000,8 @@ static int process_sdp(struct sip_pvt *p, struct sip_request *req, int t38action else if (text) { if (process_sdp_a_text(value, p, &newtextrtp, red_fmtp, &red_num_gen, red_data_pt, &last_rtpmap_codec)) processed = TRUE; + else if (process_crypto(p, p->trtp, &p->tsrtp, value)) + processed = TRUE; } /* Image (T.38 FAX) specific scanning */ else if (image) { @@ -7937,20 +8026,48 @@ static int process_sdp(struct sip_pvt *p, struct sip_request *req, int t38action return -1; } - if (portno == -1 && vportno == -1 && udptlportno == -1 && tportno == -1) + if (portno == -1 && vportno == -1 && udptlportno == -1 && tportno == -1) { /* No acceptable offer found in SDP - we have no ports */ /* Do not change RTP or VRTP if this is a re-invite */ + ast_log(LOG_WARNING, "Failing due to no acceptable offer found\n"); return -2; + } - if (numberofmediastreams > 3) + if (numberofmediastreams > 3) { /* We have too many fax, audio and/or video and/or text media streams, fail this offer */ + ast_log(LOG_WARNING, "Faling due to too many media streams\n"); return -3; + } + + if (secure_audio && !(p->srtp && (ast_test_flag(p->srtp, SRTP_CRYPTO_OFFER_OK)))) { + ast_log(LOG_WARNING, "Can't provide secure audio requested in SDP offer\n"); + return -4; + } + + if (!secure_audio && p->srtp) { + ast_log(LOG_WARNING, "We are requesting SRTP, but they responded without it!\n"); + return -4; + } + + if (secure_video && !(p->vsrtp && (ast_test_flag(p->vsrtp, SRTP_CRYPTO_OFFER_OK)))) { + ast_log(LOG_WARNING, "Can't provide secure video requested in SDP offer\n"); + return -4; + } + + if (!p->novideo && !secure_video && p->vsrtp) { + ast_log(LOG_WARNING, "We are requesting SRTP, but they responded without it!\n"); + return -4; + } + + if (!(secure_audio || secure_video) && ast_test_flag(&p->flags[1], SIP_PAGE2_USE_SRTP)) { + ast_log(LOG_WARNING, "Matched device setup to use SRTP, but request was not!\n"); + return -4; + } if (udptlportno == -1) { change_t38_state(p, T38_DISABLED); } - /* Now gather all of the codecs that we are asked for: */ ast_rtp_codecs_payload_formats(&newaudiortp, &peercapability, &peernoncodeccapability); ast_rtp_codecs_payload_formats(&newvideortp, &vpeercapability, &vpeernoncodeccapability); @@ -9843,6 +9960,23 @@ static void get_our_media_address(struct sip_pvt *p, int needvideo, int needtext } } +static void get_crypto_attrib(struct sip_srtp *srtp, const char **a_crypto) +{ + /* Set encryption properties */ + if (srtp) { + if (!srtp->crypto) { + srtp->crypto = sdp_crypto_setup(); + } + if (srtp->crypto && (sdp_crypto_offer(srtp->crypto) >= 0)) { + *a_crypto = sdp_crypto_attrib(srtp->crypto); + } + + if (!*a_crypto) { + ast_log(LOG_WARNING, "No SRTP key management enabled\n"); + } + } +} + /*! \brief Add Session Description Protocol message If oldsdp is TRUE, then the SDP version number is not incremented. This mechanism @@ -9879,6 +10013,9 @@ static enum sip_result add_sdp(struct sip_request *resp, struct sip_pvt *p, int struct ast_str *a_video = ast_str_alloca(1024); /* Attributes for video */ struct ast_str *a_text = ast_str_alloca(1024); /* Attributes for text */ struct ast_str *a_modem = ast_str_alloca(1024); /* Attributes for modem */ + const char *a_crypto = NULL; + const char *v_a_crypto = NULL; + const char *t_a_crypto = NULL; format_t x; format_t capability = 0; @@ -9962,7 +10099,9 @@ static enum sip_result add_sdp(struct sip_request *resp, struct sip_pvt *p, int /* Ok, we need video. Let's add what we need for video and set codecs. Video is handled differently than audio since we can not transcode. */ if (needvideo) { - ast_str_append(&m_video, 0, "m=video %d RTP/AVP", ntohs(vdest.sin_port)); + get_crypto_attrib(p->vsrtp, &v_a_crypto); + ast_str_append(&m_video, 0, "m=video %d RTP/%s", ntohs(vdest.sin_port), + v_a_crypto ? "SAVP" : "AVP"); /* Build max bitrate string */ if (p->maxcallbitrate) @@ -9976,7 +10115,9 @@ static enum sip_result add_sdp(struct sip_request *resp, struct sip_pvt *p, int if (needtext) { if (sipdebug_text) ast_verbose("Lets set up the text sdp\n"); - ast_str_append(&m_text, 0, "m=text %d RTP/AVP", ntohs(tdest.sin_port)); + get_crypto_attrib(p->tsrtp, &t_a_crypto); + ast_str_append(&m_text, 0, "m=text %d RTP/%s", ntohs(tdest.sin_port), + t_a_crypto ? "SAVP" : "AVP"); if (debug) /* XXX should I use tdest below ? */ ast_verbose("Text is at %s port %d\n", ast_inet_ntoa(p->ourip.sin_addr), ntohs(tsin.sin_port)); @@ -9987,7 +10128,9 @@ static enum sip_result add_sdp(struct sip_request *resp, struct sip_pvt *p, int /* We break with the "recommendation" and send our IP, in order that our peer doesn't have to ast_gethostbyname() us */ - ast_str_append(&m_audio, 0, "m=audio %d RTP/AVP", ntohs(dest.sin_port)); + get_crypto_attrib(p->srtp, &a_crypto); + ast_str_append(&m_audio, 0, "m=audio %d RTP/%s", ntohs(dest.sin_port), + a_crypto ? "SAVP" : "AVP"); if (ast_test_flag(&p->flags[1], SIP_PAGE2_CALL_ONHOLD) == SIP_PAGE2_CALL_ONHOLD_ONEDIR) hold = "a=recvonly\r\n"; @@ -10149,7 +10292,15 @@ static enum sip_result add_sdp(struct sip_request *resp, struct sip_pvt *p, int len += m_text->used + a_text->used + strlen(hold); if (add_t38) len += m_modem->used + a_modem->used; - + if (a_crypto) { + len += strlen(a_crypto); + } + if (v_a_crypto) { + len += strlen(v_a_crypto); + } + if (t_a_crypto) { + len += strlen(t_a_crypto); + } add_header(resp, "Content-Type", "application/sdp"); add_header_contentLength(resp, len); add_line(resp, version); @@ -10163,6 +10314,9 @@ static enum sip_result add_sdp(struct sip_request *resp, struct sip_pvt *p, int add_line(resp, m_audio->str); add_line(resp, a_audio->str); add_line(resp, hold); + if (a_crypto) { + add_line(resp, a_crypto); + } } else if (p->offered_media[SDP_AUDIO].offered) { snprintf(dummy_answer, sizeof(dummy_answer), "m=audio 0 RTP/AVP %s\r\n", p->offered_media[SDP_AUDIO].codecs); add_line(resp, dummy_answer); @@ -10171,6 +10325,9 @@ static enum sip_result add_sdp(struct sip_request *resp, struct sip_pvt *p, int add_line(resp, m_video->str); add_line(resp, a_video->str); add_line(resp, hold); /* Repeat hold for the video stream */ + if (v_a_crypto) { + add_line(resp, v_a_crypto); + } } else if (p->offered_media[SDP_VIDEO].offered) { snprintf(dummy_answer, sizeof(dummy_answer), "m=video 0 RTP/AVP %s\r\n", p->offered_media[SDP_VIDEO].codecs); add_line(resp, dummy_answer); @@ -10179,6 +10336,9 @@ static enum sip_result add_sdp(struct sip_request *resp, struct sip_pvt *p, int add_line(resp, m_text->str); add_line(resp, a_text->str); add_line(resp, hold); /* Repeat hold for the text stream */ + if (t_a_crypto) { + add_line(resp, t_a_crypto); + } } else if (p->offered_media[SDP_TEXT].offered) { snprintf(dummy_answer, sizeof(dummy_answer), "m=text 0 RTP/AVP %s\r\n", p->offered_media[SDP_TEXT].codecs); add_line(resp, dummy_answer); @@ -17482,6 +17642,8 @@ static int function_sippeer(struct ast_channel *chan, const char *cmd, char *dat ast_copy_string(buf, peer->cid_num, len); } else if (!strcasecmp(colname, "codecs")) { ast_getformatname_multiple(buf, len -1, peer->capability); + } else if (!strcasecmp(colname, "encryption")) { + snprintf(buf, len, "%d", ast_test_flag(&peer->flags[1], SIP_PAGE2_USE_SRTP)); } else if (!strncasecmp(colname, "chanvar[", 8)) { char *chanvar=colname + 8; struct ast_variable *v; @@ -21015,8 +21177,13 @@ static int handle_request_invite(struct sip_pvt *p, struct sip_request *req, int transmit_response_with_t38_sdp(p, "200 OK", req, (reinvite ? XMIT_RELIABLE : (req->ignore ? XMIT_UNRELIABLE : XMIT_CRITICAL))); } else if (p->t38.state == T38_DISABLED) { /* If this is not a re-invite or something to ignore - it's critical */ - ast_set_flag(&p->flags[1], SIP_PAGE2_DIALOG_ESTABLISHED); - transmit_response_with_sdp(p, "200 OK", req, (reinvite ? XMIT_RELIABLE : (req->ignore ? XMIT_UNRELIABLE : XMIT_CRITICAL)), p->session_modify == TRUE ? FALSE : TRUE, FALSE); + if (p->srtp && !ast_test_flag(p->srtp, SRTP_CRYPTO_OFFER_OK)) { + ast_log(LOG_WARNING, "Target does not support required crypto\n"); + transmit_response_reliable(p, "488 Not Acceptable Here (crypto)", req); + } else { + ast_set_flag(&p->flags[1], SIP_PAGE2_DIALOG_ESTABLISHED); + transmit_response_with_sdp(p, "200 OK", req, (reinvite ? XMIT_RELIABLE : (req->ignore ? XMIT_UNRELIABLE : XMIT_CRITICAL)), p->session_modify == TRUE ? FALSE : TRUE, FALSE); + } } p->invitestate = INV_TERMINATED; @@ -25374,6 +25541,8 @@ static struct sip_peer *build_peer(const char *name, struct ast_variable *v, str ast_string_field_set(peer, unsolicited_mailbox, v->value); } else if (!strcasecmp(v->name, "use_q850_reason")) { ast_set2_flag(&peer->flags[1], ast_true(v->value), SIP_PAGE2_Q850_REASON); + } else if (!strcasecmp(v->name, "encryption")) { + ast_set2_flag(&peer->flags[1], ast_true(v->value), SIP_PAGE2_USE_SRTP); } else if (!strcasecmp(v->name, "snom_aoc_enabled")) { ast_set2_flag(&peer->flags[2], ast_true(v->value), SIP_PAGE3_SNOM_AOC); } @@ -26666,6 +26835,10 @@ static enum ast_rtp_glue_result sip_get_rtp_peer(struct ast_channel *chan, struc res = AST_RTP_GLUE_RESULT_FORBID; } + if (p->srtp) { + res = AST_RTP_GLUE_RESULT_FORBID; + } + sip_pvt_unlock(p); return res; @@ -27084,6 +27257,50 @@ static void sip_send_all_mwi_subscriptions(void) } while (0)); } +/* SRTP */ +static int setup_srtp(struct sip_srtp **srtp) +{ + if (!ast_rtp_engine_srtp_is_registered()) { + ast_log(LOG_ERROR, "No SRTP module loaded, can't setup SRTP session.\n"); + return -1; + } + + if (!(*srtp = sip_srtp_alloc())) { /* Allocate SRTP data structure */ + return -1; + } + + return 0; +} + +static int process_crypto(struct sip_pvt *p, struct ast_rtp_instance *rtp, struct sip_srtp **srtp, const char *a) +{ + if (strncasecmp(a, "crypto:", 7)) { + return FALSE; + } + if (!*srtp) { + if (ast_test_flag(&p->flags[0], SIP_OUTGOING)) { + ast_log(LOG_WARNING, "Ignoring unexpected crypto attribute in SDP answer\n"); + return FALSE; + } + + if (setup_srtp(srtp) < 0) { + return FALSE; + } + } + + if (!(*srtp)->crypto && !((*srtp)->crypto = sdp_crypto_setup())) { + return FALSE; + } + + if (sdp_crypto_process((*srtp)->crypto, a, rtp) < 0) { + return FALSE; + } + + ast_set_flag(*srtp, SRTP_CRYPTO_OFFER_OK); + + return TRUE; +} + /*! \brief Reload module */ static int sip_do_reload(enum channelreloadreason reason) { diff --git a/channels/sip/dialplan_functions.c b/channels/sip/dialplan_functions.c index d39a2779e..d09627ed8 100644 --- a/channels/sip/dialplan_functions.c +++ b/channels/sip/dialplan_functions.c @@ -214,6 +214,10 @@ int sip_acf_channel_read(struct ast_channel *chan, const char *funcname, char *p ast_log(LOG_WARNING, "Unrecognized argument '%s' to %s\n", preparse, funcname); return -1; } + } else if (!strcasecmp(args.param, "secure_signaling")) { + snprintf(buf, buflen, "%s", p->socket.type == SIP_TRANSPORT_TLS ? "1" : ""); + } else if (!strcasecmp(args.param, "secure_media")) { + snprintf(buf, buflen, "%s", p->srtp ? "1" : ""); } else { res = -1; } diff --git a/channels/sip/include/sdp_crypto.h b/channels/sip/include/sdp_crypto.h new file mode 100644 index 000000000..b1c153438 --- /dev/null +++ b/channels/sip/include/sdp_crypto.h @@ -0,0 +1,82 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2006 - 2007, Mikael Magnusson + * + * Mikael Magnusson <mikma@users.sourceforge.net> + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! \file sdp_crypto.h + * + * \brief SDP Security descriptions + * + * Specified in RFC 4568 + * + * \author Mikael Magnusson <mikma@users.sourceforge.net> + */ + +#ifndef _SDP_CRYPTO_H +#define _SDP_CRYPTO_H + +#include <asterisk/rtp_engine.h> + +struct sdp_crypto; + +/*! \brief Initialize an return an sdp_crypto struct + * + * \details + * This function allocates a new sdp_crypto struct and initializes its values + * + * \retval NULL on failure + * \retval a pointer to a new sdp_crypto structure + */ +struct sdp_crypto *sdp_crypto_setup(void); + +/*! \brief Destroy a previously allocated sdp_crypto struct */ +void sdp_crypto_destroy(struct sdp_crypto *crypto); + +/*! \brief Parse the a=crypto line from SDP and set appropriate values on the + * sdp_crypto struct. + * + * \param p A valid sdp_crypto struct + * \param attr the a:crypto line from SDP + * \param rtp The rtp instance associated with the SDP being parsed + * + * \retval 0 success + * \retval nonzero failure + */ +int sdp_crypto_process(struct sdp_crypto *p, const char *attr, struct ast_rtp_instance *rtp); + + +/*! \brief Generate an SRTP a=crypto offer + * + * \details + * The offer is stored on the sdp_crypto struct in a_crypto + * + * \param A valid sdp_crypto struct + * + * \retval 0 success + * \retval nonzero failure + */ +int sdp_crypto_offer(struct sdp_crypto *p); + + +/*! \brief Return the a_crypto value of the sdp_crypto struct + * + * \param p An sdp_crypto struct that has had sdp_crypto_offer called + * + * \retval The value of the a_crypto for p + */ +const char *sdp_crypto_attrib(struct sdp_crypto *p); + +#endif /* _SDP_CRYPTO_H */ diff --git a/channels/sip/include/sip.h b/channels/sip/include/sip.h index 9017d7e6b..8d6d0abcb 100644 --- a/channels/sip/include/sip.h +++ b/channels/sip/include/sip.h @@ -307,10 +307,8 @@ #define SIP_PAGE2_RTAUTOCLEAR (1 << 1) /*!< GP: Should we clean memory from peers after expiry? */ #define SIP_PAGE2_RPID_UPDATE (1 << 2) #define SIP_PAGE2_Q850_REASON (1 << 3) /*!< DP: Get/send cause code via Reason header */ - #define SIP_PAGE2_SYMMETRICRTP (1 << 4) /*!< GDP: Whether symmetric RTP is enabled or not */ #define SIP_PAGE2_STATECHANGEQUEUE (1 << 5) /*!< D: Unsent state pending change exists */ - #define SIP_PAGE2_CONNECTLINEUPDATE_PEND (1 << 6) #define SIP_PAGE2_RPID_IMMEDIATE (1 << 7) #define SIP_PAGE2_RPORT_PRESENT (1 << 8) /*!< Was rport received in the Via header? */ @@ -345,6 +343,7 @@ #define SIP_PAGE2_UDPTL_DESTINATION (1 << 25) /*!< DP: Use source IP of RTP as destination if NAT is enabled */ #define SIP_PAGE2_VIDEOSUPPORT_ALWAYS (1 << 26) /*!< DP: Always set up video, even if endpoints don't support it */ #define SIP_PAGE2_HAVEPEERCONTEXT (1 << 27) /*< Are we associated with a configured peer context? */ +#define SIP_PAGE2_USE_SRTP (1 << 28) /*!< DP: Whether we should offer (only) SRTP */ #define SIP_PAGE2_FLAGS_TO_COPY \ (SIP_PAGE2_ALLOWSUBSCRIBE | SIP_PAGE2_ALLOWOVERLAP | SIP_PAGE2_IGNORESDPVERSION | \ @@ -352,7 +351,7 @@ SIP_PAGE2_BUGGY_MWI | SIP_PAGE2_TEXTSUPPORT | SIP_PAGE2_FAX_DETECT | \ SIP_PAGE2_UDPTL_DESTINATION | SIP_PAGE2_VIDEOSUPPORT_ALWAYS | SIP_PAGE2_PREFERRED_CODEC | \ SIP_PAGE2_RPID_IMMEDIATE | SIP_PAGE2_RPID_UPDATE | SIP_PAGE2_SYMMETRICRTP |\ - SIP_PAGE2_Q850_REASON | SIP_PAGE2_HAVEPEERCONTEXT) + SIP_PAGE2_Q850_REASON | SIP_PAGE2_HAVEPEERCONTEXT | SIP_PAGE2_USE_SRTP) #define SIP_PAGE3_SNOM_AOC (1 << 0) /*!< DPG: Allow snom aoc messages */ @@ -965,6 +964,7 @@ struct sip_pvt { * or respect the other endpoint's request for frame sizes (on) * for incoming calls */ + unsigned short req_secure_signaling:1;/*!< Whether we are required to have secure signaling or not */ char tag[11]; /*!< Our tag for this session */ int timer_t1; /*!< SIP timer T1, ms rtt */ int timer_b; /*!< SIP timer B, ms */ @@ -1048,6 +1048,9 @@ struct sip_pvt { AST_LIST_HEAD_NOLOCK(request_queue, sip_request) request_queue; /*!< Requests that arrived but could not be processed immediately */ struct sip_invite_param *options; /*!< Options for INVITE */ struct sip_st_dlg *stimer; /*!< SIP Session-Timers */ + struct sip_srtp *srtp; /*!< Structure to hold Secure RTP session data for audio */ + struct sip_srtp *vsrtp; /*!< Structure to hold Secure RTP session data for video */ + struct sip_srtp *tsrtp; /*!< Structure to hold Secure RTP session data for text */ int red; /*!< T.140 RTP Redundancy */ int hangupcause; /*!< Storage of hangupcause copied from our owner before we disconnect from the AST channel (only used at hangup) */ diff --git a/channels/sip/include/srtp.h b/channels/sip/include/srtp.h new file mode 100644 index 000000000..b7a3fc30b --- /dev/null +++ b/channels/sip/include/srtp.h @@ -0,0 +1,57 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2006 - 2007, Mikael Magnusson + * + * Mikael Magnusson <mikma@users.sourceforge.net> + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! \file sip_srtp.h + * + * \brief SIP Secure RTP (SRTP) + * + * Specified in RFC 3711 + * + * \author Mikael Magnusson <mikma@users.sourceforge.net> + */ + +#ifndef _SIP_SRTP_H +#define _SIP_SRTP_H + +#include "sdp_crypto.h" + +/* SRTP flags */ +#define SRTP_ENCR_OPTIONAL (1 << 1) /* SRTP encryption optional */ +#define SRTP_CRYPTO_ENABLE (1 << 2) +#define SRTP_CRYPTO_OFFER_OK (1 << 3) + +/*! \brief structure for secure RTP audio */ +struct sip_srtp { + unsigned int flags; + struct sdp_crypto *crypto; +}; + +/*! + * \brief allocate a sip_srtp structure + * \retval a new malloc'd sip_srtp structure on success + * \retval NULL on failure +*/ +struct sip_srtp *sip_srtp_alloc(void); + +/*! + * \brief free a sip_srtp structure + * \param srtp a sip_srtp structure +*/ +void sip_srtp_destroy(struct sip_srtp *srtp); + +#endif /* _SIP_SRTP_H */ diff --git a/channels/sip/sdp_crypto.c b/channels/sip/sdp_crypto.c new file mode 100644 index 000000000..94bf85619 --- /dev/null +++ b/channels/sip/sdp_crypto.c @@ -0,0 +1,310 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2006 - 2007, Mikael Magnusson + * + * Mikael Magnusson <mikma@users.sourceforge.net> + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! \file sdp_crypto.c + * + * \brief SDP Security descriptions + * + * Specified in RFC 4568 + * + * \author Mikael Magnusson <mikma@users.sourceforge.net> + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include "asterisk/options.h" +#include "asterisk/utils.h" +#include "include/sdp_crypto.h" + +#define SRTP_MASTER_LEN 30 +#define SRTP_MASTERKEY_LEN 16 +#define SRTP_MASTERSALT_LEN ((SRTP_MASTER_LEN) - (SRTP_MASTERKEY_LEN)) +#define SRTP_MASTER_LEN64 (((SRTP_MASTER_LEN) * 8 + 5) / 6 + 1) + +extern struct ast_srtp_res *res_srtp; +extern struct ast_srtp_policy_res *res_srtp_policy; + +struct sdp_crypto { + char *a_crypto; + unsigned char local_key[SRTP_MASTER_LEN]; + char local_key64[SRTP_MASTER_LEN64]; +}; + +static int set_crypto_policy(struct ast_srtp_policy *policy, int suite_val, const unsigned char *master_key, unsigned long ssrc, int inbound); + +static struct sdp_crypto *sdp_crypto_alloc(void) +{ + struct sdp_crypto *crypto; + + return crypto = ast_calloc(1, sizeof(*crypto)); +} + +void sdp_crypto_destroy(struct sdp_crypto *crypto) +{ + ast_free(crypto->a_crypto); + crypto->a_crypto = NULL; + ast_free(crypto); +} + +struct sdp_crypto *sdp_crypto_setup(void) +{ + struct sdp_crypto *p; + int key_len; + unsigned char remote_key[SRTP_MASTER_LEN]; + + if (!ast_rtp_engine_srtp_is_registered()) { + return NULL; + } + + if (!(p = sdp_crypto_alloc())) { + return NULL; + } + + if (res_srtp->get_random(p->local_key, sizeof(p->local_key)) < 0) { + sdp_crypto_destroy(p); + return NULL; + } + + ast_base64encode(p->local_key64, p->local_key, SRTP_MASTER_LEN, sizeof(p->local_key64)); + + key_len = ast_base64decode(remote_key, p->local_key64, sizeof(remote_key)); + + if (key_len != SRTP_MASTER_LEN) { + ast_log(LOG_ERROR, "base64 encode/decode bad len %d != %d\n", key_len, SRTP_MASTER_LEN); + ast_free(p); + return NULL; + } + + if (memcmp(remote_key, p->local_key, SRTP_MASTER_LEN)) { + ast_log(LOG_ERROR, "base64 encode/decode bad key\n"); + ast_free(p); + return NULL; + } + + ast_debug(1 , "local_key64 %s len %zu\n", p->local_key64, strlen(p->local_key64)); + + return p; +} + +static int set_crypto_policy(struct ast_srtp_policy *policy, int suite_val, const unsigned char *master_key, unsigned long ssrc, int inbound) +{ + const unsigned char *master_salt = NULL; + + if (!ast_rtp_engine_srtp_is_registered()) { + return -1; + } + + master_salt = master_key + SRTP_MASTERKEY_LEN; + if (res_srtp_policy->set_master_key(policy, master_key, SRTP_MASTERKEY_LEN, master_salt, SRTP_MASTERSALT_LEN) < 0) { + return -1; + } + + if (res_srtp_policy->set_suite(policy, suite_val)) { + ast_log(LOG_WARNING, "Could not set remote SRTP suite\n"); + return -1; + } + + res_srtp_policy->set_ssrc(policy, ssrc, inbound); + + return 0; +} + +static int sdp_crypto_activate(struct sdp_crypto *p, int suite_val, unsigned char *remote_key, struct ast_rtp_instance *rtp) +{ + struct ast_srtp_policy *local_policy = NULL; + struct ast_srtp_policy *remote_policy = NULL; + struct ast_rtp_instance_stats stats = {0,}; + int res = -1; + + if (!ast_rtp_engine_srtp_is_registered()) { + return -1; + } + + if (!p) { + return -1; + } + + if (!(local_policy = res_srtp_policy->alloc())) { + return -1; + } + + if (!(remote_policy = res_srtp_policy->alloc())) { + goto err; + } + + if (ast_rtp_instance_get_stats(rtp, &stats, AST_RTP_INSTANCE_STAT_LOCAL_SSRC)) { + goto err; + } + + if (set_crypto_policy(local_policy, suite_val, p->local_key, stats.local_ssrc, 0) < 0) { + goto err; + } + + if (set_crypto_policy(remote_policy, suite_val, remote_key, 0, 1) < 0) { + goto err; + } + + /* FIXME MIKMA */ + /* ^^^ I wish I knew what needed fixing... */ + if (ast_rtp_instance_add_srtp_policy(rtp, local_policy)) { + ast_log(LOG_WARNING, "Could not set local SRTP policy\n"); + goto err; + } + + if (ast_rtp_instance_add_srtp_policy(rtp, remote_policy)) { + ast_log(LOG_WARNING, "Could not set remote SRTP policy\n"); + goto err; + } + + ast_debug(1 , "SRTP policy activated\n"); + res = 0; + +err: + if (local_policy) { + res_srtp_policy->destroy(local_policy); + } + + if (remote_policy) { + res_srtp_policy->destroy(remote_policy); + } + + return res; +} + +int sdp_crypto_process(struct sdp_crypto *p, const char *attr, struct ast_rtp_instance *rtp) +{ + char *str = NULL; + char *name = NULL; + char *tag = NULL; + char *suite = NULL; + char *key_params = NULL; + char *key_param = NULL; + char *session_params = NULL; + char *key_salt = NULL; + char *lifetime = NULL; + int found = 0; + int attr_len = strlen(attr); + int key_len = 0; + int suite_val = 0; + unsigned char remote_key[SRTP_MASTER_LEN]; + + if (!ast_rtp_engine_srtp_is_registered()) { + return -1; + } + + str = ast_strdupa(attr); + + name = strsep(&str, ":"); + tag = strsep(&str, " "); + suite = strsep(&str, " "); + key_params = strsep(&str, " "); + session_params = strsep(&str, " "); + + if (!tag || !suite) { + ast_log(LOG_WARNING, "Unrecognized a=%s", attr); + return -1; + } + + if (session_params) { + ast_log(LOG_WARNING, "Unsupported crypto parameters: %s", session_params); + return -1; + } + + if (!strcmp(suite, "AES_CM_128_HMAC_SHA1_80")) { + suite_val = AST_AES_CM_128_HMAC_SHA1_80; + } else if (!strcmp(suite, "AES_CM_128_HMAC_SHA1_32")) { + suite_val = AST_AES_CM_128_HMAC_SHA1_32; + } else { + ast_log(LOG_WARNING, "Unsupported crypto suite: %s\n", suite); + return -1; + } + + while ((key_param = strsep(&key_params, ";"))) { + char *method = NULL; + char *info = NULL; + + method = strsep(&key_param, ":"); + info = strsep(&key_param, ";"); + + if (!strcmp(method, "inline")) { + key_salt = strsep(&info, "|"); + lifetime = strsep(&info, "|"); + + if (lifetime) { + ast_log(LOG_NOTICE, "Crypto life time unsupported: %s\n", attr); + continue; + } + + found = 1; + break; + } + } + + if (!found) { + ast_log(LOG_NOTICE, "SRTP crypto offer not acceptable\n"); + return -1; + } + + + if ((key_len = ast_base64decode(remote_key, key_salt, sizeof(remote_key))) != SRTP_MASTER_LEN) { + ast_log(LOG_WARNING, "SRTP sdescriptions key %d != %d\n", key_len, SRTP_MASTER_LEN); + return -1; + } + + if (sdp_crypto_activate(p, suite_val, remote_key, rtp) < 0) { + return -1; + } + + if (!p->a_crypto) { + if (!(p->a_crypto = ast_calloc(1, attr_len + 11))) { + ast_log(LOG_ERROR, "Could not allocate memory for a_crypto\n"); + return -1; + } + + snprintf(p->a_crypto, attr_len + 10, "a=crypto:%s %s inline:%s\r\n", tag, suite, p->local_key64); + } + + return 0; +} + +int sdp_crypto_offer(struct sdp_crypto *p) +{ + char crypto_buf[128]; + const char *crypto_suite = "AES_CM_128_HMAC_SHA1_80"; /* Crypto offer */ + + if (p->a_crypto) { + ast_free(p->a_crypto); + } + + if (snprintf(crypto_buf, sizeof(crypto_buf), "a=crypto:1 %s inline:%s\r\n", crypto_suite, p->local_key64) < 1) { + return -1; + } + + if (!(p->a_crypto = ast_strdup(crypto_buf))) { + return -1; + } + + return 0; +} + +const char *sdp_crypto_attrib(struct sdp_crypto *p) +{ + return p->a_crypto; +} diff --git a/channels/sip/srtp.c b/channels/sip/srtp.c new file mode 100644 index 000000000..3b55106ab --- /dev/null +++ b/channels/sip/srtp.c @@ -0,0 +1,51 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2006 - 2007, Mikael Magnusson + * + * Mikael Magnusson <mikma@users.sourceforge.net> + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! \file sip_srtp.c + * + * \brief SIP Secure RTP (SRTP) + * + * Specified in RFC 3711 + * + * \author Mikael Magnusson <mikma@users.sourceforge.net> + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include "asterisk/utils.h" +#include "include/srtp.h" + +struct sip_srtp *sip_srtp_alloc(void) +{ + struct sip_srtp *srtp; + + srtp = ast_calloc(1, sizeof(*srtp)); + + return srtp; +} + +void sip_srtp_destroy(struct sip_srtp *srtp) +{ + if (srtp->crypto) { + sdp_crypto_destroy(srtp->crypto); + } + srtp->crypto = NULL; + ast_free(srtp); +} |