summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--channels/chan_sip.c322
-rw-r--r--channels/sip/include/sip.h5
-rw-r--r--configs/sip.conf.sample32
-rwxr-xr-xconfigure122
-rw-r--r--configure.ac4
-rw-r--r--include/asterisk/autoconfig.h.in3
-rw-r--r--include/asterisk/rtp_engine.h94
-rw-r--r--main/rtp_engine.c68
-rw-r--r--res/res_rtp_asterisk.c593
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)
diff --git a/configure b/configure
index d6d26367b..db14c6d5f 100755
--- a/configure
+++ b/configure
@@ -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]);