summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoshua Colp <jcolp@digium.com>2012-09-20 18:27:28 +0000
committerJoshua Colp <jcolp@digium.com>2012-09-20 18:27:28 +0000
commite8380afc8a147ee299c3881423b2e0b27c4cfc0d (patch)
tree9930ca060cafb0821bd7f2d977f1aede33a67877
parentf1fb120f5d62933cac50dc47810418ebf535af7c (diff)
Add support for DTLS-SRTP to res_rtp_asterisk and chan_sip.
As mentioned on the review for this, WebRTC has moved towards choosing DTLS-SRTP as the mechanism for key exchange for SRTP. This commit adds support for this but makes it available for normal SIP clients as well. Testing has been done to ensure that this introduces no regressions with existing behavior and also that it functions as expected. Review: https://reviewboard.asterisk.org/r/2113/ ........ Merged revisions 373229 from http://svn.asterisk.org/svn/asterisk/branches/11 git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@373234 65c4cc65-6c06-0410-ace0-fbb531ad65f3
-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]);