diff options
-rw-r--r-- | channels/chan_sip.c | 322 | ||||
-rw-r--r-- | channels/sip/include/sip.h | 5 | ||||
-rw-r--r-- | configs/sip.conf.sample | 32 | ||||
-rwxr-xr-x | configure | 122 | ||||
-rw-r--r-- | configure.ac | 4 | ||||
-rw-r--r-- | include/asterisk/autoconfig.h.in | 3 | ||||
-rw-r--r-- | include/asterisk/rtp_engine.h | 94 | ||||
-rw-r--r-- | main/rtp_engine.c | 68 | ||||
-rw-r--r-- | res/res_rtp_asterisk.c | 593 |
9 files changed, 1179 insertions, 64 deletions
diff --git a/channels/chan_sip.c b/channels/chan_sip.c index dd64867e8..a4a6da9d5 100644 --- a/channels/chan_sip.c +++ b/channels/chan_sip.c @@ -1387,11 +1387,13 @@ static int process_sdp_o(const char *o, struct sip_pvt *p); static int process_sdp_c(const char *c, struct ast_sockaddr *addr); static int process_sdp_a_sendonly(const char *a, int *sendonly); static int process_sdp_a_ice(const char *a, struct sip_pvt *p, struct ast_rtp_instance *instance); +static int process_sdp_a_dtls(const char *a, struct sip_pvt *p, struct ast_rtp_instance *instance); static int process_sdp_a_audio(const char *a, struct sip_pvt *p, struct ast_rtp_codecs *newaudiortp, int *last_rtpmap_codec); static int process_sdp_a_video(const char *a, struct sip_pvt *p, struct ast_rtp_codecs *newvideortp, int *last_rtpmap_codec); static int process_sdp_a_text(const char *a, struct sip_pvt *p, struct ast_rtp_codecs *newtextrtp, char *red_fmtp, int *red_num_gen, int *red_data_pt, int *last_rtpmap_codec); static int process_sdp_a_image(const char *a, struct sip_pvt *p); static void add_ice_to_sdp(struct ast_rtp_instance *instance, struct ast_str **a_buf); +static void add_dtls_to_sdp(struct ast_rtp_instance *instance, struct ast_str **a_buf); static void start_ice(struct ast_rtp_instance *instance); static void add_codec_to_sdp(const struct sip_pvt *p, struct ast_format *codec, struct ast_str **m_buf, struct ast_str **a_buf, @@ -4928,6 +4930,8 @@ static void sip_destroy_peer(struct sip_peer *peer) ast_string_field_free_memory(peer); peer->caps = ast_format_cap_destroy(peer->caps); + + ast_rtp_dtls_cfg_free(&peer->dtls_cfg); } /*! \brief Update peer data in database (if used) */ @@ -5515,6 +5519,29 @@ static void copy_socket_data(struct sip_socket *to_sock, const struct sip_socket *to_sock = *from_sock; } +/*! \brief Initialize DTLS-SRTP support on an RTP instance */ +static int dialog_initialize_dtls_srtp(const struct sip_pvt *dialog, struct ast_rtp_instance *rtp, struct sip_srtp **srtp) +{ + struct ast_rtp_engine_dtls *dtls; + + if (!dialog->dtls_cfg.enabled) { + return 0; + } + + if (!ast_rtp_engine_srtp_is_registered()) { + ast_log(LOG_ERROR, "No SRTP module loaded, can't setup SRTP session.\n"); + return -1; + } + + if (!(dtls = ast_rtp_instance_get_dtls(rtp)) || + dtls->set_configuration(rtp, &dialog->dtls_cfg) || + !(*srtp = sip_srtp_alloc())) { + return -1; + } + + return 0; +} + /*! \brief Initialize RTP portion of a dialog * \return -1 on failure, 0 on success */ @@ -5536,6 +5563,10 @@ static int dialog_initialize_rtp(struct sip_pvt *dialog) ice->stop(dialog->rtp); } + if (dialog_initialize_dtls_srtp(dialog, dialog->rtp, &dialog->srtp)) { + return -1; + } + if (ast_test_flag(&dialog->flags[1], SIP_PAGE2_VIDEOSUPPORT_ALWAYS) || (ast_test_flag(&dialog->flags[1], SIP_PAGE2_VIDEOSUPPORT) && (ast_format_cap_has_type(dialog->caps, AST_FORMAT_TYPE_VIDEO)))) { if (!(dialog->vrtp = ast_rtp_instance_new(dialog->engine, sched, &bindaddr_tmp, NULL))) { @@ -5546,6 +5577,10 @@ static int dialog_initialize_rtp(struct sip_pvt *dialog) ice->stop(dialog->vrtp); } + if (dialog_initialize_dtls_srtp(dialog, dialog->vrtp, &dialog->vsrtp)) { + return -1; + } + ast_rtp_instance_set_timeout(dialog->vrtp, dialog->rtptimeout); ast_rtp_instance_set_hold_timeout(dialog->vrtp, dialog->rtpholdtimeout); ast_rtp_instance_set_keepalive(dialog->vrtp, dialog->rtpkeepalive); @@ -5562,6 +5597,10 @@ static int dialog_initialize_rtp(struct sip_pvt *dialog) ice->stop(dialog->trtp); } + if (dialog_initialize_dtls_srtp(dialog, dialog->trtp, &dialog->tsrtp)) { + return -1; + } + /* Do not timeout text as its not constant*/ ast_rtp_instance_set_keepalive(dialog->trtp, dialog->rtpkeepalive); @@ -5618,6 +5657,8 @@ static int create_addr_from_peer(struct sip_pvt *dialog, struct sip_peer *peer) ast_string_field_set(dialog, engine, peer->engine); + ast_rtp_dtls_cfg_copy(&peer->dtls_cfg, &dialog->dtls_cfg); + dialog->rtptimeout = peer->rtptimeout; dialog->rtpholdtimeout = peer->rtpholdtimeout; dialog->rtpkeepalive = peer->rtpkeepalive; @@ -6263,6 +6304,8 @@ void __sip_destroy(struct sip_pvt *p, int lockowner, int lockdialoglist) p->redircaps = ast_format_cap_destroy(p->redircaps); p->prefcaps = ast_format_cap_destroy(p->prefcaps); + ast_rtp_dtls_cfg_free(&p->dtls_cfg); + if (p->last_device_state_info) { ao2_ref(p->last_device_state_info, -1); p->last_device_state_info = NULL; @@ -9577,6 +9620,16 @@ static int process_sdp(struct sip_pvt *p, struct sip_request *req, int t38action processed = TRUE; } + if (process_sdp_a_dtls(value, p, p->rtp)) { + processed = TRUE; + } + if (process_sdp_a_dtls(value, p, p->vrtp)) { + processed = TRUE; + } + if (process_sdp_a_dtls(value, p, p->trtp)) { + processed = TRUE; + } + break; } @@ -9594,8 +9647,9 @@ static int process_sdp(struct sip_pvt *p, struct sip_request *req, int t38action int image = FALSE; int text = FALSE; int processed_crypto = FALSE; - char protocol[6] = {0,}; + char protocol[18] = {0,}; int x; + struct ast_rtp_engine_dtls *dtls; numberofports = 0; len = -1; @@ -9614,50 +9668,60 @@ static int process_sdp(struct sip_pvt *p, struct sip_request *req, int t38action /* Check for 'audio' media offer */ if (strncmp(m, "audio ", 6) == 0) { - if ((sscanf(m, "audio %30u/%30u RTP/%5s %n", &x, &numberofports, protocol, &len) == 3 && len > 0) || - (sscanf(m, "audio %30u RTP/%5s %n", &x, protocol, &len) == 2 && len > 0)) { + if ((sscanf(m, "audio %30u/%30u %17s %n", &x, &numberofports, protocol, &len) == 3 && len > 0) || + (sscanf(m, "audio %30u %17s %n", &x, protocol, &len) == 2 && len > 0)) { codecs = m + len; /* produce zero-port m-line since it may be needed later - * length is "m=audio 0 RTP/" + protocol + " " + codecs + "\0" */ - if (!(offer->decline_m_line = ast_malloc(14 + strlen(protocol) + 1 + strlen(codecs) + 1))) { + * length is "m=audio 0 " + protocol + " " + codecs + "\0" */ + if (!(offer->decline_m_line = ast_malloc(10 + strlen(protocol) + 1 + strlen(codecs) + 1))) { ast_log(LOG_WARNING, "Failed to allocate memory for SDP offer declination\n"); res = -1; goto process_sdp_cleanup; } /* guaranteed to be exactly the right length */ - sprintf(offer->decline_m_line, "m=audio 0 RTP/%s %s", protocol, codecs); + sprintf(offer->decline_m_line, "m=audio 0 %s %s", protocol, codecs); if (x == 0) { ast_log(LOG_WARNING, "Ignoring audio media offer because port number is zero\n"); continue; } + if (has_media_stream(p, SDP_AUDIO)) { + ast_log(LOG_WARNING, "Declining non-primary audio stream: %s\n", m); + continue; + } + /* Check number of ports offered for stream */ if (numberofports > 1) { ast_log(LOG_WARNING, "%d ports offered for audio media, not supported by Asterisk. Will try anyway...\n", numberofports); } - if (!strcmp(protocol, "SAVPF") && !ast_test_flag(&p->flags[2], SIP_PAGE3_USE_AVPF)) { + if ((!strcmp(protocol, "RTP/SAVPF") || !strcmp(protocol, "UDP/TLS/RTP/SAVPF")) && !ast_test_flag(&p->flags[2], SIP_PAGE3_USE_AVPF)) { ast_log(LOG_WARNING, "Received SAVPF profle in audio offer but AVPF is not enabled: %s\n", m); continue; - } else if (!strcmp(protocol, "SAVP") && ast_test_flag(&p->flags[2], SIP_PAGE3_USE_AVPF)) { + } else if ((!strcmp(protocol, "RTP/SAVP") || !strcmp(protocol, "UDP/TLS/RTP/SAVP")) && ast_test_flag(&p->flags[2], SIP_PAGE3_USE_AVPF)) { ast_log(LOG_WARNING, "Received SAVP profile in audio offer but AVPF is enabled: %s\n", m); continue; - } else if (!strcmp(protocol, "SAVP") || !strcmp(protocol, "SAVPF")) { + } else if (!strcmp(protocol, "UDP/TLS/RTP/SAVP") || !strcmp(protocol, "UDP/TLS/RTP/SAVPF")) { secure_audio = 1; - } else if (!strcmp(protocol, "AVPF") && !ast_test_flag(&p->flags[2], SIP_PAGE3_USE_AVPF)) { + + if (p->srtp) { + ast_set_flag(p->srtp, SRTP_CRYPTO_OFFER_OK); + } + } else if (!strcmp(protocol, "RTP/SAVP") || !strcmp(protocol, "RTP/SAVPF")) { + secure_audio = 1; + } else if (!strcmp(protocol, "RTP/AVPF") && !ast_test_flag(&p->flags[2], SIP_PAGE3_USE_AVPF)) { ast_log(LOG_WARNING, "Received AVPF profile in audio offer but AVPF is not enabled: %s\n", m); continue; - } else if (!strcmp(protocol, "AVP") && ast_test_flag(&p->flags[2], SIP_PAGE3_USE_AVPF)) { + } else if (!strcmp(protocol, "RTP/AVP") && ast_test_flag(&p->flags[2], SIP_PAGE3_USE_AVPF)) { ast_log(LOG_WARNING, "Received AVP profile in audio offer but AVPF is enabled: %s\n", m); continue; - } else if (strcmp(protocol, "AVP") && strcmp(protocol, "AVPF")) { - ast_log(LOG_WARNING, "Unknown RTP profile in audio offer: %s\n", m); + } else if ((!strcmp(protocol, "UDP/TLS/RTP/SAVP") || !strcmp(protocol, "UDP/TLS/RTP/SAVPF")) && + (!(dtls = ast_rtp_instance_get_dtls(p->rtp)) || !dtls->active(p->rtp))) { + ast_log(LOG_WARNING, "Received UDP/TLS in audio offer but DTLS is not enabled: %s\n", m); continue; - } - - if (has_media_stream(p, SDP_AUDIO)) { - ast_log(LOG_WARNING, "Declining non-primary audio stream: %s\n", m); + } else if (strcmp(protocol, "RTP/AVP") && strcmp(protocol, "RTP/AVPF")) { + ast_log(LOG_WARNING, "Unknown RTP profile in audio offer: %s\n", m); continue; } @@ -9686,18 +9750,18 @@ static int process_sdp(struct sip_pvt *p, struct sip_request *req, int t38action } /* Check for 'video' media offer */ else if (strncmp(m, "video ", 6) == 0) { - if ((sscanf(m, "video %30u/%30u RTP/%5s %n", &x, &numberofports, protocol, &len) == 3 && len > 0) || - (sscanf(m, "video %30u RTP/%5s %n", &x, protocol, &len) == 2 && len > 0)) { + if ((sscanf(m, "video %30u/%30u %17s %n", &x, &numberofports, protocol, &len) == 3 && len > 0) || + (sscanf(m, "video %30u %17s %n", &x, protocol, &len) == 2 && len > 0)) { codecs = m + len; /* produce zero-port m-line since it may be needed later - * length is "m=video 0 RTP/" + protocol + " " + codecs + "\0" */ - if (!(offer->decline_m_line = ast_malloc(14 + strlen(protocol) + 1 + strlen(codecs) + 1))) { + * length is "m=video 0 " + protocol + " " + codecs + "\0" */ + if (!(offer->decline_m_line = ast_malloc(10 + strlen(protocol) + 1 + strlen(codecs) + 1))) { ast_log(LOG_WARNING, "Failed to allocate memory for SDP offer declination\n"); res = -1; goto process_sdp_cleanup; } /* guaranteed to be exactly the right length */ - sprintf(offer->decline_m_line, "m=video 0 RTP/%s %s", protocol, codecs); + sprintf(offer->decline_m_line, "m=video 0 %s %s", protocol, codecs); if (x == 0) { ast_log(LOG_WARNING, "Ignoring video stream offer because port number is zero\n"); @@ -9709,30 +9773,36 @@ static int process_sdp(struct sip_pvt *p, struct sip_request *req, int t38action ast_log(LOG_WARNING, "%d ports offered for video stream, not supported by Asterisk. Will try anyway...\n", numberofports); } - if (!strcmp(protocol, "SAVPF") && !ast_test_flag(&p->flags[2], SIP_PAGE3_USE_AVPF)) { + if (has_media_stream(p, SDP_VIDEO)) { + ast_log(LOG_WARNING, "Declining non-primary video stream: %s\n", m); + continue; + } + + if ((!strcmp(protocol, "RTP/SAVPF") || !strcmp(protocol, "UDP/TLS/RTP/SAVPF")) && !ast_test_flag(&p->flags[2], SIP_PAGE3_USE_AVPF)) { ast_log(LOG_WARNING, "Received SAVPF profle in video offer but AVPF is not enabled: %s\n", m); continue; - } else if (!strcmp(protocol, "SAVP") && ast_test_flag(&p->flags[2], SIP_PAGE3_USE_AVPF)) { + } else if ((!strcmp(protocol, "RTP/SAVP") || !strcmp(protocol, "UDP/TLS/RTP/SAVP")) && ast_test_flag(&p->flags[2], SIP_PAGE3_USE_AVPF)) { ast_log(LOG_WARNING, "Received SAVP profile in video offer but AVPF is enabled: %s\n", m); continue; - } else if (!strcmp(protocol, "SAVP") || !strcmp(protocol, "SAVPF")) { + } else if (!strcmp(protocol, "UDP/TLS/RTP/SAVP") || !strcmp(protocol, "UDP/TLS/RTP/SAVPF")) { + secure_video = 1; + + if (p->vsrtp || (p->vsrtp = sip_srtp_alloc())) { + ast_set_flag(p->vsrtp, SRTP_CRYPTO_OFFER_OK); + } + } else if (!strcmp(protocol, "RTP/SAVP") || !strcmp(protocol, "RTP/SAVPF")) { secure_video = 1; - } else if (!strcmp(protocol, "AVPF") && !ast_test_flag(&p->flags[2], SIP_PAGE3_USE_AVPF)) { + } else if (!strcmp(protocol, "RTP/AVPF") && !ast_test_flag(&p->flags[2], SIP_PAGE3_USE_AVPF)) { ast_log(LOG_WARNING, "Received AVPF profile in video offer but AVPF is not enabled: %s\n", m); continue; - } else if (!strcmp(protocol, "AVP") && ast_test_flag(&p->flags[2], SIP_PAGE3_USE_AVPF)) { + } else if (!strcmp(protocol, "RTP/AVP") && ast_test_flag(&p->flags[2], SIP_PAGE3_USE_AVPF)) { ast_log(LOG_WARNING, "Received AVP profile in video offer but AVPF is enabled: %s\n", m); continue; - } else if (strcmp(protocol, "AVP") && strcmp(protocol, "AVPF")) { + } else if (strcmp(protocol, "RTP/AVP") && strcmp(protocol, "RTP/AVPF")) { ast_log(LOG_WARNING, "Unknown RTP profile in video offer: %s\n", m); continue; } - if (has_media_stream(p, SDP_VIDEO)) { - ast_log(LOG_WARNING, "Declining non-primary video stream: %s\n", m); - continue; - } - video = TRUE; p->novideo = FALSE; offer->type = SDP_VIDEO; @@ -9758,18 +9828,18 @@ static int process_sdp(struct sip_pvt *p, struct sip_request *req, int t38action } /* Check for 'text' media offer */ else if (strncmp(m, "text ", 5) == 0) { - if ((sscanf(m, "text %30u/%30u RTP/%5s %n", &x, &numberofports, protocol, &len) == 3 && len > 0) || - (sscanf(m, "text %30u RTP/%5s %n", &x, protocol, &len) == 2 && len > 0)) { + if ((sscanf(m, "text %30u/%30u %17s %n", &x, &numberofports, protocol, &len) == 3 && len > 0) || + (sscanf(m, "text %30u %17s %n", &x, protocol, &len) == 2 && len > 0)) { codecs = m + len; /* produce zero-port m-line since it may be needed later - * length is "m=text 0 RTP/" + protocol + " " + codecs + "\0" */ - if (!(offer->decline_m_line = ast_malloc(13 + strlen(protocol) + 1 + strlen(codecs) + 1))) { + * length is "m=text 0 " + protocol + " " + codecs + "\0" */ + if (!(offer->decline_m_line = ast_malloc(9 + strlen(protocol) + 1 + strlen(codecs) + 1))) { ast_log(LOG_WARNING, "Failed to allocate memory for SDP offer declination\n"); res = -1; goto process_sdp_cleanup; } /* guaranteed to be exactly the right length */ - sprintf(offer->decline_m_line, "m=text 0 RTP/%s %s", protocol, codecs); + sprintf(offer->decline_m_line, "m=text 0 %s %s", protocol, codecs); if (x == 0) { ast_log(LOG_WARNING, "Ignoring text stream offer because port number is zero\n"); @@ -9781,13 +9851,13 @@ static int process_sdp(struct sip_pvt *p, struct sip_request *req, int t38action ast_log(LOG_WARNING, "%d ports offered for text stream, not supported by Asterisk. Will try anyway...\n", numberofports); } - if (!strcmp(protocol, "AVPF") && !ast_test_flag(&p->flags[2], SIP_PAGE3_USE_AVPF)) { + if (!strcmp(protocol, "RTP/AVPF") && !ast_test_flag(&p->flags[2], SIP_PAGE3_USE_AVPF)) { ast_log(LOG_WARNING, "Received AVPF profile in text offer but AVPF is not enabled: %s\n", m); continue; - } else if (!strcmp(protocol, "AVP") && ast_test_flag(&p->flags[2], SIP_PAGE3_USE_AVPF)) { + } else if (!strcmp(protocol, "RTP/AVP") && ast_test_flag(&p->flags[2], SIP_PAGE3_USE_AVPF)) { ast_log(LOG_WARNING, "Received AVP profile in text offer but AVPF is enabled: %s\n", m); continue; - } else if (strcmp(protocol, "AVP") && strcmp(protocol, "AVPF")) { + } else if (strcmp(protocol, "RTP/AVP") && strcmp(protocol, "RTP/AVPF")) { ast_log(LOG_WARNING, "Unknown RTP profile in text offer: %s\n", m); continue; } @@ -9923,6 +9993,8 @@ static int process_sdp(struct sip_pvt *p, struct sip_request *req, int t38action if (audio) { if (process_sdp_a_ice(value, p, p->rtp)) { processed = TRUE; + } else if (process_sdp_a_dtls(value, p, p->rtp)) { + processed = TRUE; } else if (process_sdp_a_sendonly(value, &sendonly)) { processed = TRUE; } else if (!processed_crypto && process_crypto(p, p->rtp, &p->srtp, value)) { @@ -9936,6 +10008,8 @@ static int process_sdp(struct sip_pvt *p, struct sip_request *req, int t38action else if (video) { if (process_sdp_a_ice(value, p, p->vrtp)) { processed = TRUE; + } else if (process_sdp_a_dtls(value, p, p->vrtp)) { + processed = TRUE; } else if (!processed_crypto && process_crypto(p, p->vrtp, &p->vsrtp, value)) { processed_crypto = TRUE; processed = TRUE; @@ -9947,7 +10021,7 @@ static int process_sdp(struct sip_pvt *p, struct sip_request *req, int t38action else if (text) { if (process_sdp_a_ice(value, p, p->trtp)) { processed = TRUE; - } if (process_sdp_a_text(value, p, &newtextrtp, red_fmtp, &red_num_gen, red_data_pt, &last_rtpmap_codec)) { + } else if (process_sdp_a_text(value, p, &newtextrtp, red_fmtp, &red_num_gen, red_data_pt, &last_rtpmap_codec)) { processed = TRUE; } else if (!processed_crypto && process_crypto(p, p->trtp, &p->tsrtp, value)) { processed_crypto = TRUE; @@ -10466,6 +10540,56 @@ static int process_sdp_a_ice(const char *a, struct sip_pvt *p, struct ast_rtp_in return found; } +static int process_sdp_a_dtls(const char *a, struct sip_pvt *p, struct ast_rtp_instance *instance) +{ + struct ast_rtp_engine_dtls *dtls; + int found = FALSE; + char value[256], hash[6]; + + if (!instance || !p->dtls_cfg.enabled || !(dtls = ast_rtp_instance_get_dtls(instance))) { + return found; + } + + if (sscanf(a, "setup: %255s", value) == 1) { + found = TRUE; + + if (!strcasecmp(value, "active")) { + dtls->set_setup(instance, AST_RTP_DTLS_SETUP_ACTIVE); + } else if (!strcasecmp(value, "passive")) { + dtls->set_setup(instance, AST_RTP_DTLS_SETUP_PASSIVE); + } else if (!strcasecmp(value, "actpass")) { + dtls->set_setup(instance, AST_RTP_DTLS_SETUP_ACTPASS); + } else if (!strcasecmp(value, "holdconn")) { + dtls->set_setup(instance, AST_RTP_DTLS_SETUP_HOLDCONN); + } else { + ast_log(LOG_WARNING, "Unsupported setup attribute value '%s' received on dialog '%s'\n", + value, p->callid); + } + } else if (sscanf(a, "connection: %255s", value) == 1) { + found = TRUE; + + if (!strcasecmp(value, "new")) { + dtls->reset(instance); + } else if (!strcasecmp(value, "existing")) { + /* Since they want to just use what already exists we go on as if nothing happened */ + } else { + ast_log(LOG_WARNING, "Unsupported connection attribute value '%s' received on dialog '%s'\n", + value, p->callid); + } + } else if (sscanf(a, "fingerprint: %5s %255s", hash, value) == 2) { + found = TRUE; + + if (!strcasecmp(hash, "sha-1")) { + dtls->set_fingerprint(instance, AST_RTP_DTLS_HASH_SHA1, value); + } else { + ast_log(LOG_WARNING, "Unsupported fingerprint hash type '%s' received on dialog '%s'\n", + hash, p->callid); + } + } + + return found; +} + static int process_sdp_a_audio(const char *a, struct sip_pvt *p, struct ast_rtp_codecs *newaudiortp, int *last_rtpmap_codec) { int found = FALSE; @@ -12053,6 +12177,49 @@ static void start_ice(struct ast_rtp_instance *instance) ice->start(instance); } +/*! \brief Add DTLS attributes to SDP */ +static void add_dtls_to_sdp(struct ast_rtp_instance *instance, struct ast_str **a_buf) +{ + struct ast_rtp_engine_dtls *dtls; + const char *fingerprint; + + if (!instance || !(dtls = ast_rtp_instance_get_dtls(instance)) || !dtls->active(instance)) { + return; + } + + switch (dtls->get_connection(instance)) { + case AST_RTP_DTLS_CONNECTION_NEW: + ast_str_append(a_buf, 0, "a=connection:new\r\n"); + break; + case AST_RTP_DTLS_CONNECTION_EXISTING: + ast_str_append(a_buf, 0, "a=connection:existing\r\n"); + break; + default: + break; + } + + switch (dtls->get_setup(instance)) { + case AST_RTP_DTLS_SETUP_ACTIVE: + ast_str_append(a_buf, 0, "a=setup:active\r\n"); + break; + case AST_RTP_DTLS_SETUP_PASSIVE: + ast_str_append(a_buf, 0, "a=setup:passive\r\n"); + break; + case AST_RTP_DTLS_SETUP_ACTPASS: + ast_str_append(a_buf, 0, "a=setup:actpass\r\n"); + break; + case AST_RTP_DTLS_SETUP_HOLDCONN: + ast_str_append(a_buf, 0, "a=setup:holdconn\r\n"); + break; + default: + break; + } + + if ((fingerprint = dtls->get_fingerprint(instance, AST_RTP_DTLS_HASH_SHA1))) { + ast_str_append(a_buf, 0, "a=fingerprint:SHA-1 %s\r\n", fingerprint); + } +} + /*! \brief Add codec offer to SDP offer/answer body in INVITE or 200 OK */ static void add_codec_to_sdp(const struct sip_pvt *p, struct ast_format *format, @@ -12323,6 +12490,11 @@ static void get_crypto_attrib(struct sip_pvt *p, struct sip_srtp *srtp, const ch srtp->crypto = sdp_crypto_setup(); } + if (p->dtls_cfg.enabled) { + /* If DTLS-SRTP is enabled the key details will be pulled from TLS */ + return; + } + /* set the key length based on INVITE or settings */ if (ast_test_flag(srtp, SRTP_CRYPTO_TAG_80)) { taglen = 80; @@ -12341,12 +12513,18 @@ static void get_crypto_attrib(struct sip_pvt *p, struct sip_srtp *srtp, const ch } } -static char *get_sdp_rtp_profile(const struct sip_pvt *p, unsigned int secure) +static char *get_sdp_rtp_profile(const struct sip_pvt *p, unsigned int secure, struct ast_rtp_instance *instance) { - if (ast_test_flag(&p->flags[2], SIP_PAGE3_USE_AVPF)) { - return secure ? "SAVPF" : "AVPF"; + struct ast_rtp_engine_dtls *dtls; + + if ((dtls = ast_rtp_instance_get_dtls(instance)) && dtls->active(instance)) { + return ast_test_flag(&p->flags[2], SIP_PAGE3_USE_AVPF) ? "UDP/TLS/RTP/SAVPF" : "UDP/TLS/RTP/SAVP"; } else { - return secure ? "SAVP" : "AVP"; + if (ast_test_flag(&p->flags[2], SIP_PAGE3_USE_AVPF)) { + return secure ? "RTP/SAVPF" : "RTP/AVPF"; + } else { + return secure ? "RTP/SAVP" : "RTP/AVP"; + } } } @@ -12509,8 +12687,8 @@ static enum sip_result add_sdp(struct sip_request *resp, struct sip_pvt *p, int Video is handled differently than audio since we can not transcode. */ if (needvideo) { get_crypto_attrib(p, p->vsrtp, &v_a_crypto); - ast_str_append(&m_video, 0, "m=video %d RTP/%s", ast_sockaddr_port(&vdest), - get_sdp_rtp_profile(p, a_crypto ? 1 : 0)); + ast_str_append(&m_video, 0, "m=video %d %s", ast_sockaddr_port(&vdest), + get_sdp_rtp_profile(p, a_crypto ? 1 : 0, p->vrtp)); /* Build max bitrate string */ if (p->maxcallbitrate) @@ -12519,8 +12697,12 @@ static enum sip_result add_sdp(struct sip_request *resp, struct sip_pvt *p, int ast_verbose("Video is at %s\n", ast_sockaddr_stringify(&vdest)); } - if (!doing_directmedia && ast_test_flag(&p->flags[2], SIP_PAGE3_ICE_SUPPORT)) { - add_ice_to_sdp(p->vrtp, &a_video); + if (!doing_directmedia) { + if (ast_test_flag(&p->flags[2], SIP_PAGE3_ICE_SUPPORT)) { + add_ice_to_sdp(p->vrtp, &a_video); + } + + add_dtls_to_sdp(p->vrtp, &a_video); } } @@ -12530,14 +12712,18 @@ static enum sip_result add_sdp(struct sip_request *resp, struct sip_pvt *p, int if (sipdebug_text) ast_verbose("Lets set up the text sdp\n"); get_crypto_attrib(p, p->tsrtp, &t_a_crypto); - ast_str_append(&m_text, 0, "m=text %d RTP/%s", ast_sockaddr_port(&tdest), - get_sdp_rtp_profile(p, a_crypto ? 1 : 0)); + ast_str_append(&m_text, 0, "m=text %d %s", ast_sockaddr_port(&tdest), + get_sdp_rtp_profile(p, a_crypto ? 1 : 0, p->trtp)); if (debug) { /* XXX should I use tdest below ? */ ast_verbose("Text is at %s\n", ast_sockaddr_stringify(&taddr)); } - if (!doing_directmedia && ast_test_flag(&p->flags[2], SIP_PAGE3_ICE_SUPPORT)) { - add_ice_to_sdp(p->trtp, &a_text); + if (!doing_directmedia) { + if (ast_test_flag(&p->flags[2], SIP_PAGE3_ICE_SUPPORT)) { + add_ice_to_sdp(p->trtp, &a_text); + } + + add_dtls_to_sdp(p->trtp, &a_text); } } @@ -12547,8 +12733,8 @@ static enum sip_result add_sdp(struct sip_request *resp, struct sip_pvt *p, int peer doesn't have to ast_gethostbyname() us */ get_crypto_attrib(p, p->srtp, &a_crypto); - ast_str_append(&m_audio, 0, "m=audio %d RTP/%s", ast_sockaddr_port(&dest), - get_sdp_rtp_profile(p, a_crypto ? 1 : 0)); + ast_str_append(&m_audio, 0, "m=audio %d %s", ast_sockaddr_port(&dest), + get_sdp_rtp_profile(p, a_crypto ? 1 : 0, p->rtp)); /* Now, start adding audio codecs. These are added in this order: - First what was requested by the calling channel @@ -12635,8 +12821,12 @@ static enum sip_result add_sdp(struct sip_request *resp, struct sip_pvt *p, int if (min_text_packet_size) ast_str_append(&a_text, 0, "a=ptime:%d\r\n", min_text_packet_size); - if (!doing_directmedia && ast_test_flag(&p->flags[2], SIP_PAGE3_ICE_SUPPORT)) { - add_ice_to_sdp(p->rtp, &a_audio); + if (!doing_directmedia) { + if (ast_test_flag(&p->flags[2], SIP_PAGE3_ICE_SUPPORT)) { + add_ice_to_sdp(p->rtp, &a_audio); + } + + add_dtls_to_sdp(p->rtp, &a_audio); } if (m_audio->len - m_audio->used < 2 || m_video->len - m_video->used < 2 || @@ -17354,6 +17544,8 @@ static enum check_auth_result check_peer_ok(struct sip_pvt *p, char *of, set_t38_capabilities(p); } + ast_rtp_dtls_cfg_copy(&peer->dtls_cfg, &p->dtls_cfg); + /* Copy SIP extensions profile to peer */ /* XXX is this correct before a successful auth ? */ if (p->sipoptions) @@ -17400,6 +17592,8 @@ static enum check_auth_result check_peer_ok(struct sip_pvt *p, char *of, ast_string_field_set(p, peername, peer->name); ast_string_field_set(p, authname, peer->name); + ast_rtp_dtls_cfg_copy(&peer->dtls_cfg, &p->dtls_cfg); + if (sipmethod == SIP_INVITE) { /* destroy old channel vars and copy in new ones. */ ast_variables_destroy(p->chanvars); @@ -30009,9 +30203,14 @@ static struct sip_peer *build_peer(const char *name, struct ast_variable *v, str ast_set2_flag(&peer->flags[2], ast_true(v->value), SIP_PAGE3_USE_AVPF); } else if (!strcasecmp(v->name, "icesupport")) { ast_set2_flag(&peer->flags[2], ast_true(v->value), SIP_PAGE3_ICE_SUPPORT); + } else { + ast_rtp_dtls_cfg_parse(&peer->dtls_cfg, v->name, v->value); } } + /* Apply the encryption tag length to the DTLS configuration, in case DTLS is in use */ + peer->dtls_cfg.suite = (ast_test_flag(&peer->flags[2], SIP_PAGE3_SRTP_TAG_32) ? AST_AES_CM_128_HMAC_SHA1_32 : AST_AES_CM_128_HMAC_SHA1_80); + /* These apply to devstate lookups */ if (realtime && !strcasecmp(v->name, "lastms")) { sscanf(v->value, "%30d", &peer->lastms); @@ -32201,6 +32400,8 @@ 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) { + struct ast_rtp_engine_dtls *dtls; + /* If no RTP instance exists for this media stream don't bother processing the crypto line */ if (!rtp) { ast_debug(3, "Received offer with crypto line for media stream that is not enabled\n"); @@ -32231,6 +32432,11 @@ static int process_crypto(struct sip_pvt *p, struct ast_rtp_instance *rtp, struc ast_set_flag(*srtp, SRTP_CRYPTO_OFFER_OK); + if ((dtls = ast_rtp_instance_get_dtls(rtp))) { + dtls->stop(rtp); + p->dtls_cfg.enabled = 0; + } + return TRUE; } diff --git a/channels/sip/include/sip.h b/channels/sip/include/sip.h index 047310fc4..48a5a3bb5 100644 --- a/channels/sip/include/sip.h +++ b/channels/sip/include/sip.h @@ -36,6 +36,7 @@ #include "asterisk/security_events.h" #include "asterisk/features.h" #include "asterisk/http_websocket.h" +#include "asterisk/rtp_engine.h" #ifndef FALSE #define FALSE 0 @@ -1212,6 +1213,8 @@ struct sip_pvt { struct ast_cc_config_params *cc_params; struct sip_epa_entry *epa_entry; int fromdomainport; /*!< Domain port to show in from field */ + + struct ast_rtp_dtls_cfg dtls_cfg; }; /*! \brief sip packet - raw format for outbound packets that are sent or scheduled for transmission @@ -1361,6 +1364,8 @@ struct sip_peer { enum sip_peer_type type; /*!< Distinguish between "user" and "peer" types. This is used solely for CLI and manager commands */ unsigned int disallowed_methods; struct ast_cc_config_params *cc_params; + + struct ast_rtp_dtls_cfg dtls_cfg; }; /*! diff --git a/configs/sip.conf.sample b/configs/sip.conf.sample index 81ca998d5..6c3df58b3 100644 --- a/configs/sip.conf.sample +++ b/configs/sip.conf.sample @@ -1240,6 +1240,38 @@ srvlookup=yes ; Enable DNS SRV lookups on outbound calls ; maxforwards ; encryption ; description ; Used to provide a description of the peer in console output +; dtlsenable +; dtlsverify +; dtlsrekey +; dtlscertfile +; dtlsprivatekey +; dtlscipher +; dtlscafile +; dtlscapath +; dtlssetup +; + +;------------------------------------------------------------------------------ +; DTLS-SRTP CONFIGURATION +; +; DTLS-SRTP support is available if the underlying RTP engine in use supports it. +; +; dtlsenable = yes ; Enable or disable DTLS-SRTP support +; dtlsverify = yes ; Verify that the provided peer certificate is valid +; dtlsrekey = 60 ; Interval at which to renegotiate the TLS session and rekey the SRTP session +; ; If this is not set or the value provided is 0 rekeying will be disabled +; dtlscertfile = file ; Path to certificate file to present +; dtlsprivatekey = file ; Path to private key for certificate file +; dtlscipher = <SSL cipher string> ; Cipher to use for TLS negotiation +; ; A list of valid SSL cipher strings can be found at: +; ; http://www.openssl.org/docs/apps/ciphers.html#CIPHER_STRINGS +; dtlscafile = file ; Path to certificate authority certificate +; dtlscapath = path ; Path to a directory containing certificate authority certificates +; dtlssetup = actpass ; Whether we are willing to accept connections, connect to the other party, or both. +; ; Valid options are active (we want to connect to the other party), passive (we want to +; ; accept connections only), and actpass (we will do both). This value will be used in +; ; the outgoing SDP when offering and for incoming SDP offers when the remote party sends +; ; actpass ;[sip_proxy] ; For incoming calls only. Example: FWD (Free World Dialup) @@ -1,5 +1,5 @@ #! /bin/sh -# From configure.ac Revision: 371030 . +# From configure.ac Revision: 373120 . # Guess values for system-dependent variables and create Makefiles. # Generated by GNU Autoconf 2.68 for asterisk trunk. # @@ -1018,6 +1018,10 @@ PBX_DAHDI DAHDI_DIR DAHDI_INCLUDE DAHDI_LIB +PBX_OPENSSL_SRTP +OPENSSL_SRTP_DIR +OPENSSL_SRTP_INCLUDE +OPENSSL_SRTP_LIB PBX_CRYPTO CRYPTO_DIR CRYPTO_INCLUDE @@ -8277,6 +8281,18 @@ fi +OPENSSL_SRTP_DESCRIP="OpenSSL SRTP Extension Support" +OPENSSL_SRTP_OPTION=crypto +OPENSSL_SRTP_DIR=${CRYPTO_DIR} + +PBX_OPENSSL_SRTP=0 + + + + + + + DAHDI_DESCRIP="DAHDI" DAHDI_OPTION="dahdi" PBX_DAHDI=0 @@ -27448,6 +27464,110 @@ $as_echo "no" >&6; } fi fi + +if test "x${PBX_OPENSSL_SRTP}" != "x1" -a "${USE_OPENSSL_SRTP}" != "no"; then + pbxlibdir="" + # if --with-OPENSSL_SRTP=DIR has been specified, use it. + if test "x${OPENSSL_SRTP_DIR}" != "x"; then + if test -d ${OPENSSL_SRTP_DIR}/lib; then + pbxlibdir="-L${OPENSSL_SRTP_DIR}/lib" + else + pbxlibdir="-L${OPENSSL_SRTP_DIR}" + fi + fi + pbxfuncname="SSL_CTX_set_tlsext_use_srtp" + if test "x${pbxfuncname}" = "x" ; then # empty lib, assume only headers + AST_OPENSSL_SRTP_FOUND=yes + else + ast_ext_lib_check_save_CFLAGS="${CFLAGS}" + CFLAGS="${CFLAGS} " + as_ac_Lib=`$as_echo "ac_cv_lib_ssl_${pbxfuncname}" | $as_tr_sh` +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for ${pbxfuncname} in -lssl" >&5 +$as_echo_n "checking for ${pbxfuncname} in -lssl... " >&6; } +if eval \${$as_ac_Lib+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-lssl ${pbxlibdir} -lcrypto $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char ${pbxfuncname} (); +int +main () +{ +return ${pbxfuncname} (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + eval "$as_ac_Lib=yes" +else + eval "$as_ac_Lib=no" +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +eval ac_res=\$$as_ac_Lib + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 +$as_echo "$ac_res" >&6; } +if eval test \"x\$"$as_ac_Lib"\" = x"yes"; then : + AST_OPENSSL_SRTP_FOUND=yes +else + AST_OPENSSL_SRTP_FOUND=no +fi + + CFLAGS="${ast_ext_lib_check_save_CFLAGS}" + fi + + # now check for the header. + if test "${AST_OPENSSL_SRTP_FOUND}" = "yes"; then + OPENSSL_SRTP_LIB="${pbxlibdir} -lssl -lcrypto" + # if --with-OPENSSL_SRTP=DIR has been specified, use it. + if test "x${OPENSSL_SRTP_DIR}" != "x"; then + OPENSSL_SRTP_INCLUDE="-I${OPENSSL_SRTP_DIR}/include" + fi + OPENSSL_SRTP_INCLUDE="${OPENSSL_SRTP_INCLUDE} " + if test "xopenssl/ssl.h" = "x" ; then # no header, assume found + OPENSSL_SRTP_HEADER_FOUND="1" + else # check for the header + ast_ext_lib_check_saved_CPPFLAGS="${CPPFLAGS}" + CPPFLAGS="${CPPFLAGS} ${OPENSSL_SRTP_INCLUDE}" + ac_fn_c_check_header_mongrel "$LINENO" "openssl/ssl.h" "ac_cv_header_openssl_ssl_h" "$ac_includes_default" +if test "x$ac_cv_header_openssl_ssl_h" = xyes; then : + OPENSSL_SRTP_HEADER_FOUND=1 +else + OPENSSL_SRTP_HEADER_FOUND=0 +fi + + + CPPFLAGS="${ast_ext_lib_check_saved_CPPFLAGS}" + fi + if test "x${OPENSSL_SRTP_HEADER_FOUND}" = "x0" ; then + OPENSSL_SRTP_LIB="" + OPENSSL_SRTP_INCLUDE="" + else + if test "x${pbxfuncname}" = "x" ; then # only checking headers -> no library + OPENSSL_SRTP_LIB="" + fi + PBX_OPENSSL_SRTP=1 + cat >>confdefs.h <<_ACEOF +#define HAVE_OPENSSL_SRTP 1 +_ACEOF + + fi + fi +fi + + fi diff --git a/configure.ac b/configure.ac index 5d56dfa76..b6fe5ee1f 100644 --- a/configure.ac +++ b/configure.ac @@ -387,6 +387,7 @@ AST_EXT_LIB_SETUP([COROSYNC], [Corosync], [cpg]) AST_EXT_LIB_SETUP_OPTIONAL([COROSYNC_CFG_STATE_TRACK], [A callback only in corosync 1.x], [COROSYNC], [cfg]) AST_EXT_LIB_SETUP([CURSES], [curses], [curses]) AST_EXT_LIB_SETUP([CRYPTO], [OpenSSL Cryptography], [crypto]) +AST_EXT_LIB_SETUP_OPTIONAL([OPENSSL_SRTP], [OpenSSL SRTP Extension Support], [CRYPTO], [crypto]) AST_EXT_LIB_SETUP([DAHDI], [DAHDI], [dahdi]) AST_EXT_LIB_SETUP([FFMPEG], [Ffmpeg and avcodec], [avcodec]) AST_EXT_LIB_SETUP([GSM], [External GSM], [gsm], [, use 'internal' GSM otherwise]) @@ -2103,7 +2104,8 @@ fi if test "$PBX_OPENSSL" = "1"; then - AST_CHECK_OSPTK([4], [0], [0]) + AST_CHECK_OSPTK([4], [0], [0]) + AST_EXT_LIB_CHECK([OPENSSL_SRTP], [ssl], [SSL_CTX_set_tlsext_use_srtp], [openssl/ssl.h], [-lcrypto]) fi AST_EXT_LIB_CHECK([SRTP], [srtp], [srtp_init], [srtp/srtp.h]) diff --git a/include/asterisk/autoconfig.h.in b/include/asterisk/autoconfig.h.in index 9288cce54..e6835a030 100644 --- a/include/asterisk/autoconfig.h.in +++ b/include/asterisk/autoconfig.h.in @@ -527,6 +527,9 @@ /* Define to 1 if you have the OpenSSL Secure Sockets Layer library. */ #undef HAVE_OPENSSL +/* Define to 1 if CRYPTO has the OpenSSL SRTP Extension Support feature. */ +#undef HAVE_OPENSSL_SRTP + /* Define this to indicate the ${OSPTK_DESCRIP} library */ #undef HAVE_OSPTK diff --git a/include/asterisk/rtp_engine.h b/include/asterisk/rtp_engine.h index 9820e51bc..293a7a792 100644 --- a/include/asterisk/rtp_engine.h +++ b/include/asterisk/rtp_engine.h @@ -353,6 +353,61 @@ struct ast_rtp_engine_ice { void (*ice_lite)(struct ast_rtp_instance *instance); }; +/*! \brief DTLS setup types */ +enum ast_rtp_dtls_setup { + AST_RTP_DTLS_SETUP_ACTIVE, /*!< Endpoint is willing to inititate connections */ + AST_RTP_DTLS_SETUP_PASSIVE, /*!< Endpoint is willing to accept connections */ + AST_RTP_DTLS_SETUP_ACTPASS, /*!< Endpoint is willing to both accept and initiate connections */ + AST_RTP_DTLS_SETUP_HOLDCONN, /*!< Endpoint does not want the connection to be established right now */ +}; + +/*! \brief DTLS connection states */ +enum ast_rtp_dtls_connection { + AST_RTP_DTLS_CONNECTION_NEW, /*!< Endpoint wants to use a new connection */ + AST_RTP_DTLS_CONNECTION_EXISTING, /*!< Endpoint wishes to use existing connection */ +}; + +/*! \brief DTLS fingerprint hashes */ +enum ast_rtp_dtls_hash { + AST_RTP_DTLS_HASH_SHA1, /*!< SHA-1 fingerprint hash */ +}; + +/*! \brief DTLS configuration structure */ +struct ast_rtp_dtls_cfg { + unsigned int enabled:1; /*!< Whether DTLS support is enabled or not */ + unsigned int verify:1; /*!< Whether to request and verify a client certificate when acting as server */ + unsigned int rekey; /*!< Interval at which to renegotiate and rekey - defaults to 0 (off) */ + enum ast_rtp_dtls_setup default_setup; /*!< Default setup type to use for outgoing */ + enum ast_srtp_suite suite; /*!< Crypto suite in use */ + char *certfile; /*!< Certificate file */ + char *pvtfile; /*!< Private key file */ + char *cipher; /*!< Cipher to use */ + char *cafile; /*!< Certificate authority file */ + char *capath; /*!< Path to certificate authority */ +}; + +/*! \brief Structure that represents the optional DTLS SRTP support within an RTP engine */ +struct ast_rtp_engine_dtls { + /*! Set the configuration of the DTLS support on the instance */ + int (*set_configuration)(struct ast_rtp_instance *instance, const struct ast_rtp_dtls_cfg *dtls_cfg); + /*! Get if the DTLS SRTP support is active or not */ + int (*active)(struct ast_rtp_instance *instance); + /*! Stop and terminate DTLS SRTP support */ + void (*stop)(struct ast_rtp_instance *instance); + /*! Reset the connection and start fresh */ + void (*reset)(struct ast_rtp_instance *instance); + /*! Get the current connection state */ + enum ast_rtp_dtls_connection (*get_connection)(struct ast_rtp_instance *instance); + /*! Get the current setup state */ + enum ast_rtp_dtls_setup (*get_setup)(struct ast_rtp_instance *instance); + /*! Set the remote setup state */ + void (*set_setup)(struct ast_rtp_instance *instance, enum ast_rtp_dtls_setup setup); + /*! Set the remote fingerprint */ + void (*set_fingerprint)(struct ast_rtp_instance *instance, enum ast_rtp_dtls_hash hash, const char *fingerprint); + /*! Get the local fingerprint */ + const char *(*get_fingerprint)(struct ast_rtp_instance *instance, enum ast_rtp_dtls_hash hash); +}; + /*! Structure that represents an RTP stack (engine) */ struct ast_rtp_engine { /*! Name of the RTP engine, used when explicitly requested */ @@ -426,6 +481,8 @@ struct ast_rtp_engine { int (*sendcng)(struct ast_rtp_instance *instance, int level); /*! Callback to pointer for optional ICE support */ struct ast_rtp_engine_ice *ice; + /*! Callback to pointer for optional DTLS SRTP support */ + struct ast_rtp_engine_dtls *dtls; /*! Linked list information */ AST_RWLIST_ENTRY(ast_rtp_engine) entry; }; @@ -2014,6 +2071,43 @@ int ast_rtp_engine_unload_format(const struct ast_format *format); */ struct ast_rtp_engine_ice *ast_rtp_instance_get_ice(struct ast_rtp_instance *instance); +/*! + * \brief Obtain a pointer to the DTLS support present on an RTP instance + * + * \param instance the RTP instance + * + * \retval DTLS support if present + * \retval NULL if no DTLS support available + */ +struct ast_rtp_engine_dtls *ast_rtp_instance_get_dtls(struct ast_rtp_instance *instance); + +/*! + * \brief Parse DTLS related configuration options + * + * \param dtls_cfg a DTLS configuration structure + * \param name name of the configuration option + * \param value value of the configuration option + * + * \retval 0 if handled + * \retval -1 if not handled + */ +int ast_rtp_dtls_cfg_parse(struct ast_rtp_dtls_cfg *dtls_cfg, const char *name, const char *value); + +/*! + * \brief Copy contents of a DTLS configuration structure + * + * \param src_cfg source DTLS configuration structure + * \param dst_cfg destination DTLS configuration structure + */ +void ast_rtp_dtls_cfg_copy(const struct ast_rtp_dtls_cfg *src_cfg, struct ast_rtp_dtls_cfg *dst_cfg); + +/*! + * \brief Free contents of a DTLS configuration structure + * + * \param dtls_cfg a DTLS configuration structure + */ +void ast_rtp_dtls_cfg_free(struct ast_rtp_dtls_cfg *dtls_cfg); + #if defined(__cplusplus) || defined(c_plusplus) } #endif diff --git a/main/rtp_engine.c b/main/rtp_engine.c index 90e1abb45..4bcdb138a 100644 --- a/main/rtp_engine.c +++ b/main/rtp_engine.c @@ -2058,6 +2058,74 @@ struct ast_rtp_engine_ice *ast_rtp_instance_get_ice(struct ast_rtp_instance *ins return instance->engine->ice; } +struct ast_rtp_engine_dtls *ast_rtp_instance_get_dtls(struct ast_rtp_instance *instance) +{ + return instance->engine->dtls; +} + +int ast_rtp_dtls_cfg_parse(struct ast_rtp_dtls_cfg *dtls_cfg, const char *name, const char *value) +{ + if (!strcasecmp(name, "dtlsenable")) { + dtls_cfg->enabled = ast_true(value) ? 1 : 0; + } else if (!strcasecmp(name, "dtlsverify")) { + dtls_cfg->verify = ast_true(value) ? 1 : 0; + } else if (!strcasecmp(name, "dtlsrekey")) { + if (sscanf(value, "%30u", &dtls_cfg->rekey) != 1) { + return -1; + } + } else if (!strcasecmp(name, "dtlscertfile")) { + ast_free(dtls_cfg->certfile); + dtls_cfg->certfile = ast_strdup(value); + } else if (!strcasecmp(name, "dtlsprivatekey")) { + ast_free(dtls_cfg->pvtfile); + dtls_cfg->pvtfile = ast_strdup(value); + } else if (!strcasecmp(name, "dtlscipher")) { + ast_free(dtls_cfg->cipher); + dtls_cfg->cipher = ast_strdup(value); + } else if (!strcasecmp(name, "dtlscafile")) { + ast_free(dtls_cfg->cafile); + dtls_cfg->cafile = ast_strdup(value); + } else if (!strcasecmp(name, "dtlscapath") || !strcasecmp(name, "dtlscadir")) { + ast_free(dtls_cfg->capath); + dtls_cfg->capath = ast_strdup(value); + } else if (!strcasecmp(name, "dtlssetup")) { + if (!strcasecmp(value, "active")) { + dtls_cfg->default_setup = AST_RTP_DTLS_SETUP_ACTIVE; + } else if (!strcasecmp(value, "passive")) { + dtls_cfg->default_setup = AST_RTP_DTLS_SETUP_PASSIVE; + } else if (!strcasecmp(value, "actpass")) { + dtls_cfg->default_setup = AST_RTP_DTLS_SETUP_ACTPASS; + } + } else { + return -1; + } + + return 0; +} + +void ast_rtp_dtls_cfg_copy(const struct ast_rtp_dtls_cfg *src_cfg, struct ast_rtp_dtls_cfg *dst_cfg) +{ + dst_cfg->enabled = src_cfg->enabled; + dst_cfg->verify = src_cfg->verify; + dst_cfg->rekey = src_cfg->rekey; + dst_cfg->suite = src_cfg->suite; + dst_cfg->certfile = ast_strdup(src_cfg->certfile); + dst_cfg->pvtfile = ast_strdup(src_cfg->pvtfile); + dst_cfg->cipher = ast_strdup(src_cfg->cipher); + dst_cfg->cafile = ast_strdup(src_cfg->cafile); + dst_cfg->capath = ast_strdup(src_cfg->capath); + dst_cfg->default_setup = src_cfg->default_setup; +} + +void ast_rtp_dtls_cfg_free(struct ast_rtp_dtls_cfg *dtls_cfg) +{ + ast_free(dtls_cfg->certfile); + ast_free(dtls_cfg->pvtfile); + ast_free(dtls_cfg->cipher); + ast_free(dtls_cfg->cafile); + ast_free(dtls_cfg->capath); +} + static void set_next_mime_type(const struct ast_format *format, int rtp_code, char *type, char *subtype, unsigned int sample_rate) { int x = mime_types_len; diff --git a/res/res_rtp_asterisk.c b/res/res_rtp_asterisk.c index 9283f8083..958d5831c 100644 --- a/res/res_rtp_asterisk.c +++ b/res/res_rtp_asterisk.c @@ -40,6 +40,12 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include <signal.h> #include <fcntl.h> +#ifdef HAVE_OPENSSL_SRTP +#include <openssl/ssl.h> +#include <openssl/err.h> +#include <openssl/bio.h> +#endif + /* Asterisk discourages the use of bzero in favor of memset, in fact if you try to use bzero it will tell you to use memset. As a result bzero has to be undefined * here since it is used internally by pjlib. The only other option would be to modify pjlib... which won't happen. */ #undef bzero @@ -94,6 +100,10 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #define DEFAULT_LEARNING_MIN_SEQUENTIAL 4 +#define SRTP_MASTER_KEY_LEN 16 +#define SRTP_MASTER_SALT_LEN 14 +#define SRTP_MASTER_LEN (SRTP_MASTER_KEY_LEN + SRTP_MASTER_SALT_LEN) + enum strict_rtp_state { STRICT_RTP_OPEN = 0, /*! No RTP packets should be dropped, all sources accepted */ STRICT_RTP_LEARN, /*! Accept next packet as source */ @@ -104,6 +114,8 @@ enum strict_rtp_state { #define DEFAULT_ICESUPPORT 1 extern struct ast_srtp_res *res_srtp; +extern struct ast_srtp_policy_res *res_srtp_policy; + static int dtmftimeout = DEFAULT_DTMF_TIMEOUT; static int rtpstart = DEFAULT_RTP_START; /*!< First port for RTP sessions (set in rtp.conf) */ @@ -253,6 +265,21 @@ struct ast_rtp { struct ao2_container *local_candidates; /*!< The local ICE candidates */ struct ao2_container *remote_candidates; /*!< The remote ICE candidates */ + +#ifdef HAVE_OPENSSL_SRTP + SSL_CTX *ssl_ctx; /*!< SSL context */ + SSL *ssl; /*!< SSL session */ + BIO *read_bio; /*!< Memory buffer for reading */ + BIO *write_bio; /*!< Memory buffer for writing */ + enum ast_rtp_dtls_setup dtls_setup; /*!< Current setup state */ + enum ast_srtp_suite suite; /*!< SRTP crypto suite */ + char local_fingerprint[160]; /*!< Fingerprint of our certificate */ + unsigned char remote_fingerprint[EVP_MAX_MD_SIZE]; /*!< Fingerprint of the peer certificate */ + enum ast_rtp_dtls_connection connection; /*!< Whether this is a new or existing connection */ + unsigned int dtls_failure:1; /*!< Failure occurred during DTLS negotiation */ + unsigned int rekey; /*!< Interval at which to renegotiate and rekey */ + int rekeyid; /*!< Scheduled item id for rekeying */ +#endif }; /*! @@ -359,6 +386,12 @@ static void ast_rtp_stop(struct ast_rtp_instance *instance); static int ast_rtp_qos_set(struct ast_rtp_instance *instance, int tos, int cos, const char* desc); static int ast_rtp_sendcng(struct ast_rtp_instance *instance, int level); +#ifdef HAVE_OPENSSL_SRTP +static int ast_rtp_activate(struct ast_rtp_instance *instance); +#endif + +static int __rtp_sendto(struct ast_rtp_instance *instance, void *buf, size_t size, int flags, struct ast_sockaddr *sa, int rtcp, int *ice, int use_srtp); + /*! \brief Destructor for locally created ICE candidates */ static void ast_rtp_ice_candidate_destroy(void *obj) { @@ -657,6 +690,305 @@ static struct ast_rtp_engine_ice ast_rtp_ice = { .ice_lite = ast_rtp_ice_lite, }; +#ifdef HAVE_OPENSSL_SRTP +static void dtls_info_callback(const SSL *ssl, int where, int ret) +{ + struct ast_rtp *rtp = SSL_get_ex_data(ssl, 0); + + /* We only care about alerts */ + if (!(where & SSL_CB_ALERT)) { + return; + } + + rtp->dtls_failure = 1; +} + +static int ast_rtp_dtls_set_configuration(struct ast_rtp_instance *instance, const struct ast_rtp_dtls_cfg *dtls_cfg) +{ + struct ast_rtp *rtp = ast_rtp_instance_get_data(instance); + + if (!dtls_cfg->enabled) { + return 0; + } + + if (!ast_rtp_engine_srtp_is_registered()) { + return -1; + } + + if (!(rtp->ssl_ctx = SSL_CTX_new(DTLSv1_method()))) { + return -1; + } + + SSL_CTX_set_verify(rtp->ssl_ctx, dtls_cfg->verify ? SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT : SSL_VERIFY_NONE, NULL); + + if (dtls_cfg->suite == AST_AES_CM_128_HMAC_SHA1_80) { + SSL_CTX_set_tlsext_use_srtp(rtp->ssl_ctx, "SRTP_AES128_CM_SHA1_80"); + } else if (dtls_cfg->suite == AST_AES_CM_128_HMAC_SHA1_32) { + SSL_CTX_set_tlsext_use_srtp(rtp->ssl_ctx, "SRTP_AES128_CM_SHA1_32"); + } else { + ast_log(LOG_ERROR, "Unsupported suite specified for DTLS-SRTP on RTP instance '%p'\n", instance); + goto error; + } + + if (!ast_strlen_zero(dtls_cfg->certfile)) { + char *private = ast_strlen_zero(dtls_cfg->pvtfile) ? dtls_cfg->certfile : dtls_cfg->pvtfile; + BIO *certbio; + X509 *cert; + unsigned int size, i; + unsigned char fingerprint[EVP_MAX_MD_SIZE]; + char *local_fingerprint = rtp->local_fingerprint; + + if (!SSL_CTX_use_certificate_file(rtp->ssl_ctx, dtls_cfg->certfile, SSL_FILETYPE_PEM)) { + ast_log(LOG_ERROR, "Specified certificate file '%s' for RTP instance '%p' could not be used\n", + dtls_cfg->certfile, instance); + goto error; + } + + if (!SSL_CTX_use_PrivateKey_file(rtp->ssl_ctx, private, SSL_FILETYPE_PEM) || + !SSL_CTX_check_private_key(rtp->ssl_ctx)) { + ast_log(LOG_ERROR, "Specified private key file '%s' for RTP instance '%p' could not be used\n", + private, instance); + goto error; + } + + if (!(certbio = BIO_new(BIO_s_file()))) { + ast_log(LOG_ERROR, "Failed to allocate memory for certificate fingerprinting on RTP instance '%p'\n", + instance); + goto error; + } + + if (!BIO_read_filename(certbio, dtls_cfg->certfile) || + !(cert = PEM_read_bio_X509(certbio, NULL, 0, NULL)) || + !X509_digest(cert, EVP_sha1(), fingerprint, &size) || + !size) { + ast_log(LOG_ERROR, "Could not produce fingerprint from certificate '%s' for RTP instance '%p'\n", + dtls_cfg->certfile, instance); + BIO_free_all(certbio); + goto error; + } + + for (i = 0; i < size; i++) { + sprintf(local_fingerprint, "%.2X:", fingerprint[i]); + local_fingerprint += 3; + } + + *(local_fingerprint-1) = 0; + + BIO_free_all(certbio); + } + + if (!ast_strlen_zero(dtls_cfg->cipher)) { + if (!SSL_CTX_set_cipher_list(rtp->ssl_ctx, dtls_cfg->cipher)) { + ast_log(LOG_ERROR, "Invalid cipher specified in cipher list '%s' for RTP instance '%p'\n", + dtls_cfg->cipher, instance); + goto error; + } + } + + if (!ast_strlen_zero(dtls_cfg->cafile) || !ast_strlen_zero(dtls_cfg->capath)) { + if (!SSL_CTX_load_verify_locations(rtp->ssl_ctx, S_OR(dtls_cfg->cafile, NULL), S_OR(dtls_cfg->capath, NULL))) { + ast_log(LOG_ERROR, "Invalid certificate authority file '%s' or path '%s' specified for RTP instance '%p'\n", + S_OR(dtls_cfg->cafile, ""), S_OR(dtls_cfg->capath, ""), instance); + goto error; + } + } + + rtp->rekey = dtls_cfg->rekey; + rtp->dtls_setup = dtls_cfg->default_setup; + rtp->suite = dtls_cfg->suite; + + if (!(rtp->ssl = SSL_new(rtp->ssl_ctx))) { + ast_log(LOG_ERROR, "Failed to allocate memory for SSL context on RTP instance '%p'\n", + instance); + goto error; + } + + SSL_set_ex_data(rtp->ssl, 0, rtp); + SSL_set_info_callback(rtp->ssl, dtls_info_callback); + + if (!(rtp->read_bio = BIO_new(BIO_s_mem()))) { + ast_log(LOG_ERROR, "Failed to allocate memory for inbound SSL traffic on RTP instance '%p'\n", + instance); + goto error; + } + BIO_set_mem_eof_return(rtp->read_bio, -1); + + if (!(rtp->write_bio = BIO_new(BIO_s_mem()))) { + ast_log(LOG_ERROR, "Failed to allocate memory for outbound SSL traffic on RTP instance '%p'\n", + instance); + goto error; + } + BIO_set_mem_eof_return(rtp->write_bio, -1); + + SSL_set_bio(rtp->ssl, rtp->read_bio, rtp->write_bio); + + if (rtp->dtls_setup == AST_RTP_DTLS_SETUP_PASSIVE) { + SSL_set_accept_state(rtp->ssl); + } else { + SSL_set_connect_state(rtp->ssl); + } + + rtp->connection = AST_RTP_DTLS_CONNECTION_NEW; + + return 0; + +error: + if (rtp->read_bio) { + BIO_free(rtp->read_bio); + rtp->read_bio = NULL; + } + + if (rtp->write_bio) { + BIO_free(rtp->write_bio); + rtp->write_bio = NULL; + } + + if (rtp->ssl) { + SSL_free(rtp->ssl); + rtp->ssl = NULL; + } + + SSL_CTX_free(rtp->ssl_ctx); + rtp->ssl_ctx = NULL; + + return -1; +} + +static int ast_rtp_dtls_active(struct ast_rtp_instance *instance) +{ + struct ast_rtp *rtp = ast_rtp_instance_get_data(instance); + + return !rtp->ssl_ctx ? 0 : 1; +} + +static void ast_rtp_dtls_stop(struct ast_rtp_instance *instance) +{ + struct ast_rtp *rtp = ast_rtp_instance_get_data(instance); + + if (rtp->ssl_ctx) { + SSL_CTX_free(rtp->ssl_ctx); + rtp->ssl_ctx = NULL; + } + + if (rtp->ssl) { + SSL_free(rtp->ssl); + rtp->ssl = NULL; + } +} + +static void ast_rtp_dtls_reset(struct ast_rtp_instance *instance) +{ + struct ast_rtp *rtp = ast_rtp_instance_get_data(instance); + + /* If the SSL session is not yet finalized don't bother resetting */ + if (!SSL_is_init_finished(rtp->ssl)) { + return; + } + + SSL_shutdown(rtp->ssl); + rtp->connection = AST_RTP_DTLS_CONNECTION_NEW; +} + +static enum ast_rtp_dtls_connection ast_rtp_dtls_get_connection(struct ast_rtp_instance *instance) +{ + struct ast_rtp *rtp = ast_rtp_instance_get_data(instance); + + return rtp->connection; +} + +static enum ast_rtp_dtls_setup ast_rtp_dtls_get_setup(struct ast_rtp_instance *instance) +{ + struct ast_rtp *rtp = ast_rtp_instance_get_data(instance); + + return rtp->dtls_setup; +} + +static void ast_rtp_dtls_set_setup(struct ast_rtp_instance *instance, enum ast_rtp_dtls_setup setup) +{ + struct ast_rtp *rtp = ast_rtp_instance_get_data(instance); + enum ast_rtp_dtls_setup old = rtp->dtls_setup; + + switch (setup) { + case AST_RTP_DTLS_SETUP_ACTIVE: + rtp->dtls_setup = AST_RTP_DTLS_SETUP_PASSIVE; + break; + case AST_RTP_DTLS_SETUP_PASSIVE: + rtp->dtls_setup = AST_RTP_DTLS_SETUP_ACTIVE; + break; + case AST_RTP_DTLS_SETUP_ACTPASS: + /* We can't respond to an actpass setup with actpass ourselves... so respond with active, as we can initiate connections */ + if (rtp->dtls_setup == AST_RTP_DTLS_SETUP_ACTPASS) { + rtp->dtls_setup = AST_RTP_DTLS_SETUP_ACTIVE; + } + break; + case AST_RTP_DTLS_SETUP_HOLDCONN: + rtp->dtls_setup = AST_RTP_DTLS_SETUP_HOLDCONN; + break; + default: + /* This should never occur... if it does exit early as we don't know what state things are in */ + return; + } + + /* If the setup state did not change we go on as if nothing happened */ + if (old == rtp->dtls_setup) { + return; + } + + /* If they don't want us to establish a connection wait until later */ + if (rtp->dtls_setup == AST_RTP_DTLS_SETUP_HOLDCONN) { + return; + } + + if (rtp->dtls_setup == AST_RTP_DTLS_SETUP_ACTIVE) { + SSL_set_connect_state(rtp->ssl); + } else if (rtp->dtls_setup == AST_RTP_DTLS_SETUP_PASSIVE) { + SSL_set_accept_state(rtp->ssl); + } else { + return; + } +} + +static void ast_rtp_dtls_set_fingerprint(struct ast_rtp_instance *instance, enum ast_rtp_dtls_hash hash, const char *fingerprint) +{ + char *tmp = ast_strdupa(fingerprint), *value; + int pos = 0; + struct ast_rtp *rtp = ast_rtp_instance_get_data(instance); + + if (hash != AST_RTP_DTLS_HASH_SHA1) { + return; + } + + while ((value = strsep(&tmp, ":")) && (pos != (EVP_MAX_MD_SIZE - 1))) { + sscanf(value, "%02x", (unsigned int*)&rtp->remote_fingerprint[pos++]); + } +} + +static const char *ast_rtp_dtls_get_fingerprint(struct ast_rtp_instance *instance, enum ast_rtp_dtls_hash hash) +{ + struct ast_rtp *rtp = ast_rtp_instance_get_data(instance); + + if (hash != AST_RTP_DTLS_HASH_SHA1) { + return NULL; + } + + return rtp->local_fingerprint; +} + +/* DTLS RTP Engine interface declaration */ +static struct ast_rtp_engine_dtls ast_rtp_dtls = { + .set_configuration = ast_rtp_dtls_set_configuration, + .active = ast_rtp_dtls_active, + .stop = ast_rtp_dtls_stop, + .reset = ast_rtp_dtls_reset, + .get_connection = ast_rtp_dtls_get_connection, + .get_setup = ast_rtp_dtls_get_setup, + .set_setup = ast_rtp_dtls_set_setup, + .set_fingerprint = ast_rtp_dtls_set_fingerprint, + .get_fingerprint = ast_rtp_dtls_get_fingerprint, +}; + +#endif + /* RTP Engine Declaration */ static struct ast_rtp_engine asterisk_rtp_engine = { .name = "asterisk", @@ -685,6 +1017,10 @@ static struct ast_rtp_engine asterisk_rtp_engine = { .qos = ast_rtp_qos_set, .sendcng = ast_rtp_sendcng, .ice = &ast_rtp_ice, +#ifdef HAVE_OPENSSL_SRTP + .dtls = &ast_rtp_dtls, + .activate = ast_rtp_activate, +#endif }; static void ast_rtp_on_ice_rx_data(pj_ice_sess *ice, unsigned comp_id, unsigned transport_id, void *pkt, pj_size_t size, const pj_sockaddr_t *src_addr, unsigned src_addr_len) @@ -874,6 +1210,168 @@ static inline int rtcp_debug_test_addr(struct ast_sockaddr *addr) return 1; } +#ifdef HAVE_OPENSSL_SRTP +static void dtls_srtp_check_pending(struct ast_rtp_instance *instance, struct ast_rtp *rtp) +{ + size_t pending = BIO_ctrl_pending(rtp->write_bio); + + if (pending > 0) { + char outgoing[pending]; + size_t out; + struct ast_sockaddr remote_address = { {0, } }; + int ice; + + ast_rtp_instance_get_remote_address(instance, &remote_address); + + /* If we do not yet know an address to send this to defer it until we do */ + if (ast_sockaddr_isnull(&remote_address)) { + return; + } + + out = BIO_read(rtp->write_bio, outgoing, sizeof(outgoing)); + + __rtp_sendto(instance, outgoing, out, 0, &remote_address, 0, &ice, 0); + } +} + +static int dtls_srtp_renegotiate(const void *data) +{ + struct ast_rtp_instance *instance = (struct ast_rtp_instance *)data; + struct ast_rtp *rtp = ast_rtp_instance_get_data(instance); + + SSL_renegotiate(rtp->ssl); + SSL_do_handshake(rtp->ssl); + dtls_srtp_check_pending(instance, rtp); + + rtp->rekeyid = -1; + ao2_ref(instance, -1); + + return 0; +} + +static int dtls_srtp_setup(struct ast_rtp *rtp, struct ast_srtp *srtp, struct ast_rtp_instance *instance) +{ + unsigned char material[SRTP_MASTER_LEN * 2]; + unsigned char *local_key, *local_salt, *remote_key, *remote_salt; + struct ast_srtp_policy *local_policy, *remote_policy = NULL; + struct ast_rtp_instance_stats stats = { 0, }; + + /* If a fingerprint is present in the SDP make sure that the peer certificate matches it */ + if (SSL_CTX_get_verify_mode(rtp->ssl_ctx) != SSL_VERIFY_NONE) { + X509 *certificate; + + if (!(certificate = SSL_get_peer_certificate(rtp->ssl))) { + ast_log(LOG_WARNING, "No certificate was provided by the peer on RTP instance '%p'\n", instance); + return -1; + } + + /* If a fingerprint is present in the SDP make sure that the peer certificate matches it */ + if (rtp->remote_fingerprint[0]) { + unsigned char fingerprint[EVP_MAX_MD_SIZE]; + unsigned int size; + + if (!X509_digest(certificate, EVP_sha1(), fingerprint, &size) || + !size || + memcmp(fingerprint, rtp->remote_fingerprint, size)) { + X509_free(certificate); + ast_log(LOG_WARNING, "Fingerprint provided by remote party does not match that of peer certificate on RTP instance '%p'\n", + instance); + return -1; + } + } + + X509_free(certificate); + } + + /* Ensure that certificate verification was successful */ + if (SSL_get_verify_result(rtp->ssl) != X509_V_OK) { + ast_log(LOG_WARNING, "Peer certificate on RTP instance '%p' failed verification test\n", + instance); + return -1; + } + + /* Produce key information and set up SRTP */ + if (!SSL_export_keying_material(rtp->ssl, material, SRTP_MASTER_LEN * 2, "EXTRACTOR-dtls_srtp", 19, NULL, 0, 0)) { + ast_log(LOG_WARNING, "Unable to extract SRTP keying material from DTLS-SRTP negotiation on RTP instance '%p'\n", + instance); + return -1; + } + + /* Whether we are acting as a server or client determines where the keys/salts are */ + if (rtp->dtls_setup == AST_RTP_DTLS_SETUP_ACTIVE) { + local_key = material; + remote_key = local_key + SRTP_MASTER_KEY_LEN; + local_salt = remote_key + SRTP_MASTER_KEY_LEN; + remote_salt = local_salt + SRTP_MASTER_SALT_LEN; + } else { + remote_key = material; + local_key = remote_key + SRTP_MASTER_KEY_LEN; + remote_salt = local_key + SRTP_MASTER_KEY_LEN; + local_salt = remote_salt + SRTP_MASTER_SALT_LEN; + } + + if (!(local_policy = res_srtp_policy->alloc())) { + return -1; + } + + if (res_srtp_policy->set_master_key(local_policy, local_key, SRTP_MASTER_KEY_LEN, local_salt, SRTP_MASTER_SALT_LEN) < 0) { + ast_log(LOG_WARNING, "Could not set key/salt information on local policy of '%p' when setting up DTLS-SRTP\n", rtp); + goto error; + } + + if (res_srtp_policy->set_suite(local_policy, rtp->suite)) { + ast_log(LOG_WARNING, "Could not set suite to '%d' on local policy of '%p' when setting up DTLS-SRTP\n", rtp->suite, rtp); + goto error; + } + + if (ast_rtp_instance_get_stats(instance, &stats, AST_RTP_INSTANCE_STAT_LOCAL_SSRC)) { + goto error; + } + + res_srtp_policy->set_ssrc(local_policy, stats.local_ssrc, 0); + + if (!(remote_policy = res_srtp_policy->alloc())) { + goto error; + } + + if (res_srtp_policy->set_master_key(remote_policy, remote_key, SRTP_MASTER_KEY_LEN, remote_salt, SRTP_MASTER_SALT_LEN) < 0) { + ast_log(LOG_WARNING, "Could not set key/salt information on remote policy of '%p' when setting up DTLS-SRTP\n", rtp); + goto error; + } + + if (res_srtp_policy->set_suite(remote_policy, rtp->suite)) { + ast_log(LOG_WARNING, "Could not set suite to '%d' on remote policy of '%p' when setting up DTLS-SRTP\n", rtp->suite, rtp); + goto error; + } + + res_srtp_policy->set_ssrc(remote_policy, 0, 1); + + if (ast_rtp_instance_add_srtp_policy(instance, remote_policy, local_policy)) { + ast_log(LOG_WARNING, "Could not set policies when setting up DTLS-SRTP on '%p'\n", rtp); + goto error; + } + + if (rtp->rekey) { + ao2_ref(instance, +1); + if ((rtp->rekeyid = ast_sched_add(rtp->sched, rtp->rekey * 1000, dtls_srtp_renegotiate, instance)) < 0) { + ao2_ref(instance, -1); + goto error; + } + } + + return 0; + +error: + res_srtp_policy->destroy(local_policy); + + if (remote_policy) { + res_srtp_policy->destroy(remote_policy); + } + + return -1; +} +#endif + static int __rtp_recvfrom(struct ast_rtp_instance *instance, void *buf, size_t size, int flags, struct ast_sockaddr *sa, int rtcp) { int len; @@ -884,6 +1382,56 @@ static int __rtp_recvfrom(struct ast_rtp_instance *instance, void *buf, size_t s return len; } +#ifdef HAVE_OPENSSL_SRTP + if (!rtcp) { + char *in = buf; + + dtls_srtp_check_pending(instance, rtp); + + /* If this is an SSL packet pass it to OpenSSL for processing */ + if ((*in >= 20) && (*in <= 64)) { + int res = 0; + + /* If no SSL session actually exists terminate things */ + if (!rtp->ssl) { + ast_log(LOG_ERROR, "Received SSL traffic on RTP instance '%p' without an SSL session\n", + instance); + return -1; + } + + /* If we don't yet know if we are active or passive and we receive a packet... we are obviously passive */ + if (rtp->dtls_setup == AST_RTP_DTLS_SETUP_ACTPASS) { + rtp->dtls_setup = AST_RTP_DTLS_SETUP_PASSIVE; + SSL_set_accept_state(rtp->ssl); + } + + dtls_srtp_check_pending(instance, rtp); + + BIO_write(rtp->read_bio, buf, len); + + len = SSL_read(rtp->ssl, buf, len); + + dtls_srtp_check_pending(instance, rtp); + + if (rtp->dtls_failure) { + ast_log(LOG_ERROR, "DTLS failure occurred on RTP instance '%p', terminating\n", + instance); + return -1; + } + + if (SSL_is_init_finished(rtp->ssl)) { + /* Any further connections will be existing since this is now established */ + rtp->connection = AST_RTP_DTLS_CONNECTION_EXISTING; + + /* Use the keying material to set up key/salt information */ + res = dtls_srtp_setup(rtp, srtp, instance); + } + + return res; + } + } +#endif + if (rtp->ice) { pj_str_t combined = pj_str(ast_sockaddr_stringify(sa)); pj_sockaddr address; @@ -927,7 +1475,7 @@ static int rtp_recvfrom(struct ast_rtp_instance *instance, void *buf, size_t siz return __rtp_recvfrom(instance, buf, size, flags, sa, 0); } -static int __rtp_sendto(struct ast_rtp_instance *instance, void *buf, size_t size, int flags, struct ast_sockaddr *sa, int rtcp, int *ice) +static int __rtp_sendto(struct ast_rtp_instance *instance, void *buf, size_t size, int flags, struct ast_sockaddr *sa, int rtcp, int *ice, int use_srtp) { int len = size; void *temp = buf; @@ -936,7 +1484,7 @@ static int __rtp_sendto(struct ast_rtp_instance *instance, void *buf, size_t siz *ice = 0; - if (res_srtp && srtp && res_srtp->protect(srtp, &temp, &len, rtcp) < 0) { + if (use_srtp && res_srtp && srtp && res_srtp->protect(srtp, &temp, &len, rtcp) < 0) { return -1; } @@ -954,12 +1502,12 @@ static int __rtp_sendto(struct ast_rtp_instance *instance, void *buf, size_t siz static int rtcp_sendto(struct ast_rtp_instance *instance, void *buf, size_t size, int flags, struct ast_sockaddr *sa, int *ice) { - return __rtp_sendto(instance, buf, size, flags, sa, 1, ice); + return __rtp_sendto(instance, buf, size, flags, sa, 1, ice, 1); } static int rtp_sendto(struct ast_rtp_instance *instance, void *buf, size_t size, int flags, struct ast_sockaddr *sa, int *ice) { - return __rtp_sendto(instance, buf, size, flags, sa, 0, ice); + return __rtp_sendto(instance, buf, size, flags, sa, 0, ice, 1); } static int rtp_get_rate(struct ast_format *format) @@ -1223,6 +1771,10 @@ static int ast_rtp_new(struct ast_rtp_instance *instance, /* Record any information we may need */ rtp->sched = sched; +#ifdef HAVE_OPENSSL_SRTP + rtp->rekeyid = -1; +#endif + return 0; } @@ -1285,6 +1837,18 @@ static int ast_rtp_destroy(struct ast_rtp_instance *instance) ao2_ref(rtp->remote_candidates, -1); } +#ifdef HAVE_OPENSSL_SRTP + /* Destroy the SSL context if present */ + if (rtp->ssl_ctx) { + SSL_CTX_free(rtp->ssl_ctx); + } + + /* Destroy the SSL session if present */ + if (rtp->ssl) { + SSL_free(rtp->ssl); + } +#endif + /* Destroy synchronization items */ ast_mutex_destroy(&rtp->lock); ast_cond_destroy(&rtp->cond); @@ -3507,6 +4071,10 @@ static void ast_rtp_stop(struct ast_rtp_instance *instance) struct ast_rtp *rtp = ast_rtp_instance_get_data(instance); struct ast_sockaddr addr = { {0,} }; +#ifdef HAVE_OPENSSL_SRTP + AST_SCHED_DEL_UNREF(rtp->sched, rtp->rekeyid, ao2_ref(instance, -1)); +#endif + if (rtp->rtcp && rtp->rtcp->schedid > 0) { if (!ast_sched_del(rtp->sched, rtp->rtcp->schedid)) { /* successfully cancelled scheduler entry. */ @@ -3586,6 +4154,23 @@ static int ast_rtp_sendcng(struct ast_rtp_instance *instance, int level) return res; } +#ifdef HAVE_OPENSSL_SRTP +static int ast_rtp_activate(struct ast_rtp_instance *instance) +{ + struct ast_rtp *rtp = ast_rtp_instance_get_data(instance); + + if (!rtp->ssl) { + return 0; + } + + SSL_do_handshake(rtp->ssl); + + dtls_srtp_check_pending(instance, rtp); + + return 0; +} +#endif + static char *rtp_do_debug_ip(struct ast_cli_args *a) { char *arg = ast_strdupa(a->argv[4]); |