From b986af37339679319d4c912b91d8dfaf28051fe1 Mon Sep 17 00:00:00 2001 From: Johan Pascal Date: Wed, 10 Dec 2014 15:11:36 +0100 Subject: [PATCH] Add dtls srtp --- configure.ac | 12 ++++ coreapi/bellesip_sal/sal_impl.c | 36 ++++++++++ coreapi/bellesip_sal/sal_sdp.c | 98 +++++++++++++++++++++++++ coreapi/call_params.c | 2 + coreapi/linphonecall.c | 124 ++++++++++++++++++++++++++++---- coreapi/linphonecore.c | 33 +++++++++ coreapi/linphonecore.h | 19 ++++- coreapi/offeranswer.c | 46 ++++++++++++ coreapi/private.h | 2 + coreapi/sal.c | 18 ++++- gtk/incall_view.c | 6 ++ gtk/main.c | 5 ++ gtk/propertybox.c | 32 +++++++-- include/sal/sal.h | 27 +++++++ mediastreamer2 | 2 +- oRTP | 2 +- 16 files changed, 440 insertions(+), 24 deletions(-) diff --git a/configure.ac b/configure.ac index 067a13b62..02b034f8d 100644 --- a/configure.ac +++ b/configure.ac @@ -585,6 +585,16 @@ AC_ARG_ENABLE(zrtp, [zrtp=false] ) +AC_ARG_ENABLE(dtls, + [AS_HELP_STRING([--enable-dtls], [Turn on dtls support - requires polarssl > 1.4])], + [case "${enableval}" in + yes) dtls=true ;; + no) dtls=false ;; + *) AC_MSG_ERROR(bad value ${enableval} for --enable-dtls) ;; + esac], + [dtls=false] +) + dnl build console if required AM_CONDITIONAL(BUILD_CONSOLE, test x$console_ui = xtrue) @@ -595,6 +605,7 @@ dnl compilation of gtk user interface AM_CONDITIONAL(BUILD_GTK_UI, [test x$gtk_ui = xtrue ] ) AM_CONDITIONAL(BUILD_WIN32, test x$mingw_found = xyes ) AM_CONDITIONAL(BUILD_ZRTP, test x$zrtp = xtrue) +AM_CONDITIONAL(BUILD_DTLS, test x$dtls = xtrue) dnl check getenv AH_TEMPLATE([HAVE_GETENV]) @@ -943,6 +954,7 @@ printf "* %-30s %s\n" "Console interface" $console_ui printf "* %-30s %s\n" "Tools" $build_tools printf "* %-30s %s\n" "Message storage" $enable_msg_storage printf "* %-30s %s\n" "zRTP encryption" $zrtp +printf "* %-30s %s\n" "DTLS encryption" $dtls printf "* %-30s %s\n" "uPnP support" $build_upnp printf "* %-30s %s\n" "LDAP support" $enable_ldap diff --git a/coreapi/bellesip_sal/sal_impl.c b/coreapi/bellesip_sal/sal_impl.c index 052c83971..0dfc10aa7 100644 --- a/coreapi/bellesip_sal/sal_impl.c +++ b/coreapi/bellesip_sal/sal_impl.c @@ -1060,6 +1060,42 @@ void sal_signing_key_parse_file(SalAuthInfo* auth_info, const char* path, const if (auth_info->key) belle_sip_object_ref((belle_sip_object_t *) auth_info->key); } +/** + * Parse a directory to get a certificate with the given subject common name + * + */ +void sal_certificates_chain_parse_directory(unsigned char **certificate_pem, unsigned char **key_pem, unsigned char **fingerprint, const char* path, const char *subject, SalCertificateRawFormat format, bool_t generate_certificate, bool_t generate_dtls_fingerprint) { + belle_sip_certificates_chain_t *certificate = NULL; + belle_sip_signing_key_t *key = NULL; + *certificate_pem = NULL; + *key_pem = NULL; + if (belle_sip_get_certificate_and_pkey_in_dir(path, subject, &certificate, &key, (belle_sip_certificate_raw_format_t)format) == 0) { + *certificate_pem = belle_sip_get_certificates_pem(certificate); + *key_pem = belle_sip_get_key_pem(key); + ms_message("Retrieve certificate with CN=%s successful\n", subject); + } else { + if (generate_certificate == TRUE) { + if ( belle_sip_generate_self_signed_certificate(path, subject, &certificate, &key) == 0) { + *certificate_pem = belle_sip_get_certificates_pem(certificate); + *key_pem = belle_sip_get_key_pem(key); + ms_message("Generate self-signed certificate with CN=%s successful\n", subject); + } + } + } + /* generate the fingerprint as described in RFC4572 if needed */ + if ((generate_dtls_fingerprint == TRUE) && (fingerprint != NULL)) { + *fingerprint = belle_sip_generate_certificate_fingerprint(certificate); + } + + /* free key and certificate */ + if ( certificate != NULL ) { + belle_sip_object_unref(certificate); + } + if ( key != NULL ) { + belle_sip_object_unref(key); + } +} + unsigned char * sal_get_random_bytes(unsigned char *ret, size_t size){ return belle_sip_random_bytes(ret,size); } diff --git a/coreapi/bellesip_sal/sal_sdp.c b/coreapi/bellesip_sal/sal_sdp.c index fe952bb56..cc6db442f 100644 --- a/coreapi/bellesip_sal/sal_sdp.c +++ b/coreapi/bellesip_sal/sal_sdp.c @@ -233,6 +233,26 @@ static void stream_description_to_sdp ( belle_sdp_session_description_t *session }else break; } } + + /* insert DTLS session attribute if needed */ + if ((stream->proto == SalProtoUdpTlsRtpSavpf) || (stream->proto == SalProtoUdpTlsRtpSavp)) { + if ((stream->dtls_role != SalDtlsRoleInvalid) && (strlen(stream->dtls_fingerprint)>0)) { + switch(stream->dtls_role) { + case SalDtlsRoleIsClient: + belle_sdp_media_description_add_attribute(media_desc, belle_sdp_attribute_create("setup","active")); + break; + case SalDtlsRoleIsServer: + belle_sdp_media_description_add_attribute(media_desc, belle_sdp_attribute_create("setup","passive")); + break; + case SalDtlsRoleUnset: + default: + belle_sdp_media_description_add_attribute(media_desc, belle_sdp_attribute_create("setup","actpass")); + break; + } + belle_sdp_media_description_add_attribute(media_desc, belle_sdp_attribute_create("fingerprint",stream->dtls_fingerprint)); + } + } + switch ( stream->dir ) { case SalStreamSendRecv: /*dir="sendrecv";*/ @@ -351,6 +371,23 @@ belle_sdp_session_description_t * media_description_to_sdp ( const SalMediaDescr if (desc->ice_pwd[0] != '\0') belle_sdp_session_description_add_attribute(session_desc, belle_sdp_attribute_create("ice-pwd",desc->ice_pwd)); if (desc->ice_ufrag[0] != '\0') belle_sdp_session_description_add_attribute(session_desc, belle_sdp_attribute_create("ice-ufrag",desc->ice_ufrag)); + /* insert DTLS session attribute if needed */ + if ((desc->dtls_role != SalDtlsRoleInvalid) && (strlen(desc->dtls_fingerprint)>0)) { + switch(desc->dtls_role) { + case SalDtlsRoleIsClient: + belle_sdp_session_description_add_attribute(session_desc, belle_sdp_attribute_create("setup","active")); + break; + case SalDtlsRoleIsServer: + belle_sdp_session_description_add_attribute(session_desc, belle_sdp_attribute_create("setup","passive")); + break; + case SalDtlsRoleUnset: + default: + belle_sdp_session_description_add_attribute(session_desc, belle_sdp_attribute_create("setup","actpass")); + break; + } + belle_sdp_session_description_add_attribute(session_desc, belle_sdp_attribute_create("fingerprint",desc->dtls_fingerprint)); + } + if (desc->rtcp_xr.enabled == TRUE) { belle_sdp_session_description_add_attribute(session_desc, create_rtcp_xr_attribute(&desc->rtcp_xr)); } @@ -646,6 +683,10 @@ static SalStreamDescription * sdp_to_stream_description(SalMediaDescription *md, stream->proto = SalProtoRtpAvpf; } else if (strcasecmp(proto, "RTP/SAVPF") == 0) { stream->proto = SalProtoRtpSavpf; + } else if (strcasecmp(proto, "UDP/TLS/RTP/SAVP") == 0) { + stream->proto = SalProtoUdpTlsRtpSavp; + } else if (strcasecmp(proto, "UDP/TLS/RTP/SAVPF") == 0) { + stream->proto = SalProtoUdpTlsRtpSavpf; } else { strncpy(stream->proto_other,proto,sizeof(stream->proto_other)-1); } @@ -701,6 +742,36 @@ static SalStreamDescription * sdp_to_stream_description(SalMediaDescription *md, } } + /* Read DTLS specific attributes : check is some are found in the stream description otherwise copy the session description one(which are at least set to Invalid) */ + stream->dtls_role = SalDtlsRoleInvalid; + stream->dtls_fingerprint[0] = '\0'; + if (((stream->proto == SalProtoUdpTlsRtpSavpf) || (stream->proto == SalProtoUdpTlsRtpSavp))) { + attribute=belle_sdp_media_description_get_attribute(media_desc,"setup"); + if (attribute && (value=belle_sdp_attribute_get_value(attribute))!=NULL){ + if (strncmp(value, "actpass", 7) == 0) { + stream->dtls_role = SalDtlsRoleUnset; + } else if (strncmp(value, "active", 6) == 0) { + stream->dtls_role = SalDtlsRoleIsClient; + } else if (strncmp(value, "passive", 7) == 0) { + stream->dtls_role = SalDtlsRoleIsServer; + } + + if (stream->dtls_role != SalDtlsRoleInvalid) { + attribute=belle_sdp_media_description_get_attribute(media_desc,"fingerprint"); + if (attribute && (value=belle_sdp_attribute_get_value(attribute))!=NULL){ + strncpy(stream->dtls_fingerprint, value, strlen(value)+1); + } else { + /* no valid stream attributes, get them from session */ + stream->dtls_role = md->dtls_role; + strncpy(stream->dtls_fingerprint, md->dtls_fingerprint, strlen(md->dtls_fingerprint)+1); + } + } + } else { /* no setup attribute found in the stream, get the one from the session */ + stream->dtls_role = md->dtls_role; + strncpy(stream->dtls_fingerprint, md->dtls_fingerprint, strlen(md->dtls_fingerprint)+1); + } + } + /* Read crypto lines if any */ if ((stream->proto == SalProtoRtpSavpf) || (stream->proto == SalProtoRtpSavp)) { sdp_parse_media_crypto_parameters(media_desc, stream); @@ -756,6 +827,33 @@ int sdp_to_media_description ( belle_sdp_session_description_t *session_desc, S desc->dir=SalStreamInactive; } + /* Read dtls specific session attributes if any (setup and fingerprint - rfc5763) */ + /* Presence of a valid dtls offer(setup and fingerprint attribute) is set in media Description by a dtls_fingerprint string longer than 0 + * and a dtls_role != SalDtlsRoleInvalid */ + desc->dtls_role = SalDtlsRoleInvalid; + desc->dtls_fingerprint[0] = '\0'; + value=belle_sdp_session_description_get_attribute_value(session_desc,"setup"); + if (value){ + if (strncmp(value, "actpass", 7) == 0) { + desc->dtls_role = SalDtlsRoleUnset; + } else if (strncmp(value, "active", 6) == 0) { + desc->dtls_role = SalDtlsRoleIsClient; + } else if (strncmp(value, "passive", 7) == 0) { + desc->dtls_role = SalDtlsRoleIsServer; + } + } + + if (desc->dtls_role != SalDtlsRoleInvalid) { + value=belle_sdp_session_description_get_attribute_value(session_desc,"fingerprint"); + if (value){ + strncpy(desc->dtls_fingerprint, value, strlen(value)+1); + } else { + desc->dtls_role = SalDtlsRoleInvalid; + } + } + + + /* Get ICE remote ufrag and remote pwd, and ice_lite flag */ value=belle_sdp_session_description_get_attribute_value(session_desc,"ice-ufrag"); if (value) strncpy(desc->ice_ufrag, value, sizeof(desc->ice_ufrag) - 1); diff --git a/coreapi/call_params.c b/coreapi/call_params.c index a48066169..255103fb8 100644 --- a/coreapi/call_params.c +++ b/coreapi/call_params.c @@ -27,6 +27,8 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. SalMediaProto get_proto_from_call_params(const LinphoneCallParams *params) { if ((params->media_encryption == LinphoneMediaEncryptionSRTP) && params->avpf_enabled) return SalProtoRtpSavpf; if (params->media_encryption == LinphoneMediaEncryptionSRTP) return SalProtoRtpSavp; + if ((params->media_encryption == LinphoneMediaEncryptionDTLS) && params->avpf_enabled) return SalProtoUdpTlsRtpSavpf; + if (params->media_encryption == LinphoneMediaEncryptionDTLS) return SalProtoUdpTlsRtpSavp; if (params->avpf_enabled) return SalProtoRtpAvpf; return SalProtoRtpAvp; } diff --git a/coreapi/linphonecall.c b/coreapi/linphonecall.c index dc991c616..2990406aa 100644 --- a/coreapi/linphonecall.c +++ b/coreapi/linphonecall.c @@ -158,8 +158,12 @@ static void propagate_encryption_changed(LinphoneCall *call){ call->current_params->media_encryption=LinphoneMediaEncryptionNone; linphone_core_notify_call_encryption_changed(call->core, call, FALSE, call->auth_token); } else { - ms_message("All streams are encrypted"); - call->current_params->media_encryption=LinphoneMediaEncryptionZRTP; + ms_message("All streams are encrypted key exchanged using %s", call->current_params->media_encryption==LinphoneMediaEncryptionZRTP?"ZRTP":call->current_params->media_encryption==LinphoneMediaEncryptionDTLS?"DTLS":"Unknown mechanism"); + if (call->auth_token) {/* ZRTP only is using auth_token */ + call->current_params->media_encryption=LinphoneMediaEncryptionZRTP; + } else { /* otherwise it must be DTLS as SDES doesn't go through this function */ + call->current_params->media_encryption=LinphoneMediaEncryptionDTLS; + } linphone_core_notify_call_encryption_changed(call->core, call, TRUE, call->auth_token); } } @@ -171,8 +175,10 @@ static void linphone_call_audiostream_encryption_changed(void *data, bool_t encr call = (LinphoneCall *)data; if (encrypted) { - snprintf(status,sizeof(status)-1,_("Authentication token is %s"),call->auth_token); - linphone_core_notify_display_status(call->core, status); + if (call->params->media_encryption==LinphoneMediaEncryptionZRTP) { /* if encryption is DTLS, no status to be displayed */ + snprintf(status,sizeof(status)-1,_("Authentication token is %s"),call->auth_token); + linphone_core_notify_display_status(call->core, status); + } } propagate_encryption_changed(call); @@ -472,6 +478,14 @@ void linphone_call_make_local_media_description(LinphoneCore *lc, LinphoneCall * } setup_encryption_keys(call,md); + /* if media encryption is set to DTLS check presence of fingerprint in the call which shall have been set at stream init but it may have failed when retrieving certificate resulting in no fingerprint present and then DTLS not usable */ + if ((call->params->media_encryption==LinphoneMediaEncryptionDTLS) && (call->dtls_certificate_fingerprint!= NULL)) { + memcpy(md->dtls_fingerprint, call->dtls_certificate_fingerprint, strlen((const char *)(call->dtls_certificate_fingerprint))); /* get the self fingerprint from call(it's computed at stream init) */ + md->dtls_role = SalDtlsRoleUnset; /* if we are offering, SDP will have actpass setup attribute when role is unset, if we are responding the result mediadescription will be set to SalDtlsRoleIsClient */ + } else { + md->dtls_fingerprint[0] = '\0'; + md->dtls_role = SalDtlsRoleInvalid; + } setup_rtcp_fb(call, md); setup_rtcp_xr(call, md); @@ -572,6 +586,7 @@ static void linphone_call_init_common(LinphoneCall *call, LinphoneAddress *from, call->camera_enabled=TRUE; call->current_params = linphone_call_params_new(); call->current_params->media_encryption=LinphoneMediaEncryptionNone; + call->dtls_certificate_fingerprint = NULL; linphone_core_get_audio_port_range(call->core, &min_port, &max_port); port_config_set(call,0,min_port,max_port); @@ -735,7 +750,7 @@ static void linphone_call_incoming_select_ip_version(LinphoneCall *call){ void linphone_call_set_compatible_incoming_call_parameters(LinphoneCall *call, const SalMediaDescription *md) { call->params->has_video &= linphone_core_media_description_contains_video_stream(md); - /* Handle AVPF and SRTP. */ + /* Handle AVPF, SRTP and DTLS. */ call->params->avpf_enabled = sal_media_description_has_avpf(md); if (call->params->avpf_enabled == TRUE) { if (call->dest_proxy != NULL) { @@ -744,6 +759,9 @@ void linphone_call_set_compatible_incoming_call_parameters(LinphoneCall *call, c call->params->avpf_rr_interval = linphone_core_get_avpf_rr_interval(call->core)*1000; } } + if ((sal_media_description_has_dtls(md) == TRUE) && (media_stream_dtls_supported() == TRUE)) { + call->params->media_encryption = LinphoneMediaEncryptionDTLS; + } if ((sal_media_description_has_srtp(md) == TRUE) && (media_stream_srtp_supported() == TRUE)) { call->params->media_encryption = LinphoneMediaEncryptionSRTP; } @@ -917,8 +935,19 @@ static void linphone_call_set_terminated(LinphoneCall *call){ void linphone_call_fix_call_parameters(LinphoneCall *call){ call->params->has_video=call->current_params->has_video; - if (call->params->media_encryption != LinphoneMediaEncryptionZRTP) /*in case of ZRTP call parameter are handle after zrtp negociation*/ - call->params->media_encryption=call->current_params->media_encryption; + switch(call->params->media_encryption) { + case LinphoneMediaEncryptionZRTP: + case LinphoneMediaEncryptionDTLS: + case LinphoneMediaEncryptionNone: + /* do nothing */ + break; + case LinphoneMediaEncryptionSRTP: + call->params->media_encryption=call->current_params->media_encryption; + break; + default: + ms_fatal("Unknown media encryption type on call [%p]", call); + break; + } } const char *linphone_call_state_to_string(LinphoneCallState cs){ @@ -1066,6 +1095,10 @@ static void linphone_call_destroy(LinphoneCall *obj){ ms_free(obj->auth_token); obj->auth_token=NULL; } + if (obj->dtls_certificate_fingerprint) { + ms_free(obj->dtls_certificate_fingerprint); + obj->dtls_certificate_fingerprint=NULL; + } if (obj->dtmfs_timer) { linphone_call_cancel_dtmfs(obj); } @@ -1118,11 +1151,16 @@ const LinphoneCallParams * linphone_call_get_current_params(LinphoneCall *call){ #endif if (linphone_call_all_streams_encrypted(call)) { - if (linphone_call_get_authentication_token(call)) { - call->current_params->media_encryption=LinphoneMediaEncryptionZRTP; - } else { - call->current_params->media_encryption=LinphoneMediaEncryptionSRTP; - } + if (linphone_call_get_authentication_token(call)) { + call->current_params->media_encryption=LinphoneMediaEncryptionZRTP; + } else { + /* TODO : check this or presence of dtls_fingerprint in the call? */ + if (call->params->media_encryption == LinphoneMediaEncryptionDTLS) { + call->current_params->media_encryption=LinphoneMediaEncryptionDTLS; + } else { + call->current_params->media_encryption=LinphoneMediaEncryptionSRTP; + } + } } else { call->current_params->media_encryption=LinphoneMediaEncryptionNone; } @@ -1553,6 +1591,34 @@ void linphone_call_init_audio_stream(LinphoneCall *call){ if (call->sessions[0].rtp_session==NULL){ call->audiostream=audiostream=audio_stream_new(call->media_ports[0].rtp_port,call->media_ports[0].rtcp_port,call->af==AF_INET6); rtp_session_set_symmetric_rtp(audiostream->ms.sessions.rtp_session,linphone_core_symmetric_rtp_enabled(lc)); + if (call->params->media_encryption==LinphoneMediaEncryptionDTLS) { + MSDtlsSrtpParams params; + unsigned char *certificate, *key; + memset(¶ms,0,sizeof(MSDtlsSrtpParams)); + /* TODO : search for a certificate with CNAME=sip uri(retrieved from variable me) or defautl celui par default linphone-dtls-default-identity */ + /* This will parse the directory to find a matching fingerprint or generate it if not found */ + /* returned string must be freed */ + sal_certificates_chain_parse_directory(&certificate, &key, &call->dtls_certificate_fingerprint, lc->user_certificates_path, "linphone-dtls-default-identity", SAL_CERTIFICATE_RAW_FORMAT_PEM, TRUE, TRUE); + + if (key!= NULL && certificate!=NULL) { + params.pem_certificate = (char *)certificate; + params.pem_pkey = (char *)key; + params.role = MSDtlsSrtpRoleUnset; /* default is unset, then check if we have a result SalMediaDescription */ + audio_stream_enable_dtls(call->audiostream,¶ms); + ms_free(certificate); + ms_free(key); + } else { + ms_error("Unable to retrieve or generate DTLS certificate and key - DTLS disabled"); + /* TODO : check if encryption forced, if yes, stop call */ + } + #if TODO_DTLS_VIDEO_ENCRYPTION //VIDEO_ENABLED + if (media_stream_secured((MediaStream *)call->audiostream) && media_stream_get_state((MediaStream *)call->videostream) == MSStreamStarted) { + /*audio stream is already encrypted and video stream is active*/ + memset(¶ms,0,sizeof(MSDtlsSrtpParams)); + video_stream_enable_dtls(call->videostream,call->audiostream,¶ms); + } + #endif + } }else{ call->audiostream=audio_stream_new_with_sessions(&call->sessions[0]); } @@ -2241,9 +2307,32 @@ void linphone_call_start_media_streams(LinphoneCall *call, bool_t all_inputs_mut video_stream_enable_zrtp(call->videostream,call->audiostream,¶ms); } #endif - }else{ + } else if (call->params->media_encryption==LinphoneMediaEncryptionDTLS) { + /* DTLS engine was already initialised during stream init. Before starting it we must be sure that the role(client or server) is set. + * Role may have already been set to server if we initiate the call and already received a packet from peer, in that case do nothing */ + SalDtlsRole salRole = call->resultdesc->streams[0].dtls_role; /* TODO: is streams[0] necessary the audiostream in the media description ? */ + if (salRole==SalDtlsRoleInvalid) { /* it's invalid in streams[0] but check also at session level */ + salRole = call->resultdesc->dtls_role; + } + + if (salRole!=SalDtlsRoleInvalid) { /* if DTLS is available at both end points */ + /* give the peer certificate fingerprint to dtls context */ + SalMediaDescription *remote_desc = sal_call_get_remote_media_description(call->op); + ms_dtls_srtp_set_peer_fingerprint(call->audiostream->ms.sessions.dtls_context, remote_desc->streams[0].dtls_fingerprint); + } else { + ms_warning("unable to start DTLS engine, Dtls role in resulting media description is invalid\n"); + } + if (salRole == SalDtlsRoleIsClient) { /* local endpoint is client */ + ms_dtls_srtp_set_role(call->audiostream->ms.sessions.dtls_context, MSDtlsSrtpRoleIsClient); /* set the role to client */ + ms_dtls_srtp_start(call->audiostream->ms.sessions.dtls_context); /* then start the engine, it will send the DTLS client Hello */ + } else if (salRole == SalDtlsRoleIsServer) { /* local endpoint is server */ + ms_dtls_srtp_set_role(call->audiostream->ms.sessions.dtls_context, MSDtlsSrtpRoleIsServer); /* this may complete the server setup */ + /* no need to start engine, we are waiting for DTLS Client Hello */ + } + + } else { call->current_params->media_encryption=linphone_call_all_streams_encrypted(call) ? - LinphoneMediaEncryptionSRTP : LinphoneMediaEncryptionNone; + LinphoneMediaEncryptionSRTP : LinphoneMediaEncryptionNone; } if ((call->ice_session != NULL) && (ice_session_state(call->ice_session) != IS_Completed)) { @@ -2968,7 +3057,12 @@ void linphone_call_handle_stream_events(LinphoneCall *call, int stream_index){ } else if (evt == ORTP_EVENT_ZRTP_SAS_READY) { if (ms->type==AudioStreamType) linphone_call_audiostream_auth_token_ready(call, evd->info.zrtp_sas.sas, evd->info.zrtp_sas.verified); - } else if ((evt == ORTP_EVENT_ICE_SESSION_PROCESSING_FINISHED) || (evt == ORTP_EVENT_ICE_GATHERING_FINISHED) + } else if (evt == ORTP_EVENT_DTLS_ENCRYPTION_CHANGED) { + if (ms->type==AudioStreamType) + linphone_call_audiostream_encryption_changed(call, evd->info.dtls_stream_encrypted); + else if (ms->type==VideoStreamType) + propagate_encryption_changed(call); + }else if ((evt == ORTP_EVENT_ICE_SESSION_PROCESSING_FINISHED) || (evt == ORTP_EVENT_ICE_GATHERING_FINISHED) || (evt == ORTP_EVENT_ICE_LOSING_PAIRS_COMPLETED) || (evt == ORTP_EVENT_ICE_RESTART_NEEDED)) { handle_ice_events(call, ev); } else if (evt==ORTP_EVENT_TELEPHONE_EVENT){ diff --git a/coreapi/linphonecore.c b/coreapi/linphonecore.c index 3ead49966..a3fbb21c7 100644 --- a/coreapi/linphonecore.c +++ b/coreapi/linphonecore.c @@ -28,6 +28,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include #include #include +#include #include "mediastreamer2/mediastream.h" #include "mediastreamer2/mseventqueue.h" #include "mediastreamer2/msvolume.h" @@ -1487,6 +1488,11 @@ static void misc_config_read(LinphoneCore *lc) { lp_config_set_string(config,"misc","uuid",tmp); }else if (strcmp(uuid,"0")!=0) /*to allow to disable sip.instance*/ sal_set_uuid(lc->sal, uuid); + + /* DTLS: if media_encryption is DTLS, get or create the certificate directory */ + if (linphone_core_get_media_encryption(lc) == LinphoneMediaEncryptionDTLS) { + /* TODO*/ + } } static void linphone_core_start(LinphoneCore * lc) { @@ -6253,6 +6259,9 @@ static void linphone_core_uninit(LinphoneCore *lc) if(lc->zrtp_secrets_cache != NULL) { ms_free(lc->zrtp_secrets_cache); } + if(lc->user_certificates_path != NULL) { + ms_free(lc->user_certificates_path); + } if(lc->play_file!=NULL){ ms_free(lc->play_file); } @@ -6685,6 +6694,17 @@ const char *linphone_core_get_zrtp_secrets_file(LinphoneCore *lc){ return lc->zrtp_secrets_cache; } +void linphone_core_set_user_certificates_path(LinphoneCore *lc, const char* path){ + if (lc->user_certificates_path != NULL) { + ms_free(lc->user_certificates_path); + } + lc->user_certificates_path = path ? ms_strdup(path) : NULL; +} + +const char *linphone_core_get_user_certificates_path(LinphoneCore *lc){ + return lc->user_certificates_path; +} + LinphoneCall* linphone_core_find_call_from_uri(const LinphoneCore *lc, const char *uri) { MSList *calls; LinphoneCall *c; @@ -6745,6 +6765,8 @@ const char *linphone_media_encryption_to_string(LinphoneMediaEncryption menc){ switch(menc){ case LinphoneMediaEncryptionSRTP: return "LinphoneMediaEncryptionSRTP"; + case LinphoneMediaEncryptionDTLS: + return "LinphoneMediaEncryptionDTLS"; case LinphoneMediaEncryptionZRTP: return "LinphoneMediaEncryptionZRTP"; case LinphoneMediaEncryptionNone: @@ -6761,6 +6783,8 @@ bool_t linphone_core_media_encryption_supported(const LinphoneCore *lc, Linphone switch(menc){ case LinphoneMediaEncryptionSRTP: return media_stream_srtp_supported(); + case LinphoneMediaEncryptionDTLS: + return ms_dtls_available(); case LinphoneMediaEncryptionZRTP: return ms_zrtp_available(); case LinphoneMediaEncryptionNone: @@ -6784,7 +6808,14 @@ int linphone_core_set_media_encryption(LinphoneCore *lc, LinphoneMediaEncryption type="none"; ret=-1; }else type="zrtp"; + }else if (menc == LinphoneMediaEncryptionDTLS){ + if (!ms_dtls_available()){ + ms_warning("DTLS not supported by library."); + type="none"; + ret=-1; + }else type="dtls"; } + lp_config_set_string(lc->config,"sip","media_encryption",type); return ret; } @@ -6796,6 +6827,8 @@ LinphoneMediaEncryption linphone_core_get_media_encryption(LinphoneCore *lc) { return LinphoneMediaEncryptionNone; else if (strcmp(menc, "srtp")==0) return LinphoneMediaEncryptionSRTP; + else if (strcmp(menc, "dtls")==0) + return LinphoneMediaEncryptionDTLS; else if (strcmp(menc, "zrtp")==0) return LinphoneMediaEncryptionZRTP; else diff --git a/coreapi/linphonecore.h b/coreapi/linphonecore.h index add43e4a0..37c09e7b2 100644 --- a/coreapi/linphonecore.h +++ b/coreapi/linphonecore.h @@ -287,7 +287,8 @@ typedef enum _LinphoneAVPFMode LinphoneAVPFMode; enum _LinphoneMediaEncryption { LinphoneMediaEncryptionNone, /**< No media encryption is used */ LinphoneMediaEncryptionSRTP, /**< Use SRTP media encryption */ - LinphoneMediaEncryptionZRTP /**< Use ZRTP media encryption */ + LinphoneMediaEncryptionZRTP, /**< Use ZRTP media encryption */ + LinphoneMediaEncryptionDTLS /**< Use DTLS media encryption */ }; /** @@ -2908,6 +2909,22 @@ LINPHONE_PUBLIC void linphone_core_set_zrtp_secrets_file(LinphoneCore *lc, const */ LINPHONE_PUBLIC const char *linphone_core_get_zrtp_secrets_file(LinphoneCore *lc); +/** + * Set the path to the directory storing the user's x509 certificates (used by dtls) + * @param[in] lc #LinphoneCore object + * @param[in] path The path to the directory to use to store the user's certificates. + * @ingroup initializing + */ +LINPHONE_PUBLIC void linphone_core_set_user_certificates_path(LinphoneCore *lc, const char* path); + +/** + * Get the path to the directory storing the user's certificates. + * @param[in] lc #LinphoneCore object. + * @returns The path to the directory storing the user's certificates. + * @ingroup initializing + */ +LINPHONE_PUBLIC const char *linphone_core_get_user_certificates_path(LinphoneCore *lc); + /** * Search from the list of current calls if a remote address match uri * @ingroup call_control diff --git a/coreapi/offeranswer.c b/coreapi/offeranswer.c index d7d2e6e84..9f2ce5f6b 100644 --- a/coreapi/offeranswer.c +++ b/coreapi/offeranswer.c @@ -315,6 +315,21 @@ int offer_answer_initiate_outgoing(const SalMediaDescription *local_offer, result->rtcp_xr.enabled = FALSE; } + // Handle dtls session attribute: if both local and remote have a dtls fingerprint and a dtls setup, get the remote fingerprint into the result + if ((local_offer->dtls_role!=SalDtlsRoleInvalid) && (remote_answer->dtls_role!=SalDtlsRoleInvalid) + &&(strlen(local_offer->dtls_fingerprint)>0) && (strlen(remote_answer->dtls_fingerprint)>0)) { + strcpy(result->dtls_fingerprint, remote_answer->dtls_fingerprint); + if (remote_answer->dtls_role==SalDtlsRoleIsClient) { + result->dtls_role = SalDtlsRoleIsServer; + } else { + result->dtls_role = SalDtlsRoleIsClient; + } + } else { + result->dtls_fingerprint[0] = '\0'; + result->dtls_role = SalDtlsRoleInvalid; + } + + return 0; } @@ -334,7 +349,9 @@ static bool_t local_stream_not_already_used(const SalMediaDescription *result, c static bool_t proto_compatible(SalMediaProto local, SalMediaProto remote) { if (local == remote) return TRUE; if ((remote == SalProtoRtpAvp) && ((local == SalProtoRtpSavp) || (local == SalProtoRtpSavpf))) return TRUE; + if ((remote == SalProtoRtpAvp) && ((local == SalProtoUdpTlsRtpSavp) || (local == SalProtoUdpTlsRtpSavpf))) return TRUE; if ((remote == SalProtoRtpAvpf) && (local == SalProtoRtpSavpf)) return TRUE; + if ((remote == SalProtoRtpAvpf) && (local == SalProtoUdpTlsRtpSavpf)) return TRUE; return FALSE; } @@ -368,6 +385,23 @@ int offer_answer_initiate_incoming(const SalMediaDescription *local_capabilities if (ls){ initiate_incoming(ls,rs,&result->streams[i],one_matching_codec); + // Handle dtls stream attribute: if both local and remote have a dtls fingerprint and a dtls setup, add the local fingerprint to the answer + // Note: local description usually stores dtls config at session level which means it apply to all streams, check this too + if (((ls->dtls_role!=SalDtlsRoleInvalid) || (local_capabilities->dtls_role!=SalDtlsRoleInvalid)) && (rs->dtls_role!=SalDtlsRoleInvalid) + && ((strlen(ls->dtls_fingerprint)>0) || (strlen(local_capabilities->dtls_fingerprint)>0)) && (strlen(rs->dtls_fingerprint)>0)) { + if (strlen(ls->dtls_fingerprint)>0) { /* get the fingerprint in stream description */ + strcpy(result->streams[i].dtls_fingerprint, ls->dtls_fingerprint); + } else { /* get the fingerprint in session description */ + strcpy(result->streams[i].dtls_fingerprint, local_capabilities->dtls_fingerprint); + } + if (rs->dtls_role==SalDtlsRoleUnset) { + result->streams[i].dtls_role = SalDtlsRoleIsClient; + } + } else { + result->streams[i].dtls_fingerprint[0] = '\0'; + result->streams[i].dtls_role = SalDtlsRoleInvalid; + } + // Handle media RTCP XR attribute memset(&result->streams[i].rtcp_xr, 0, sizeof(result->streams[i].rtcp_xr)); if (rs->rtcp_xr.enabled == TRUE) { @@ -408,6 +442,18 @@ int offer_answer_initiate_incoming(const SalMediaDescription *local_capabilities strcpy(result->name,local_capabilities->name); + // Handle dtls session attribute: if both local and remote have a dtls fingerprint and a dtls setup, add the local fingerprint to the answer + if ((local_capabilities->dtls_role!=SalDtlsRoleInvalid) && (remote_offer->dtls_role!=SalDtlsRoleInvalid) + &&(strlen(local_capabilities->dtls_fingerprint)>0) && (strlen(remote_offer->dtls_fingerprint)>0)) { + strcpy(result->dtls_fingerprint, local_capabilities->dtls_fingerprint); + if (remote_offer->dtls_role==SalDtlsRoleUnset) { + result->dtls_role = SalDtlsRoleIsClient; + } + } else { + result->dtls_fingerprint[0] = '\0'; + result->dtls_role = SalDtlsRoleInvalid; + } + // Handle session RTCP XR attribute memset(&result->rtcp_xr, 0, sizeof(result->rtcp_xr)); if (remote_offer->rtcp_xr.enabled == TRUE) { diff --git a/coreapi/private.h b/coreapi/private.h index 8e1bfaf8a..8198e08af 100644 --- a/coreapi/private.h +++ b/coreapi/private.h @@ -241,6 +241,7 @@ struct _LinphoneCall{ char *dtmf_sequence; /*DTMF sequence needed to be sent using #dtmfs_timer*/ belle_sip_source_t *dtmfs_timer; /*DTMF timer needed to send a DTMF sequence*/ + unsigned char *dtls_certificate_fingerprint; /**> This fingerprint is computed during stream init and is stored in call to be used when making local media description */ bool_t refer_pending; bool_t expect_media_in_ack; bool_t audio_muted; @@ -734,6 +735,7 @@ struct _LinphoneCore MSList *hooks; LinphoneConference conf_ctx; char* zrtp_secrets_cache; + char* user_certificates_path; LinphoneVideoPolicy video_policy; bool_t use_files; bool_t apply_nat_settings; diff --git a/coreapi/sal.c b/coreapi/sal.c index b8395c897..5d43546be 100644 --- a/coreapi/sal.c +++ b/coreapi/sal.c @@ -194,13 +194,17 @@ bool_t sal_stream_description_active(const SalStreamDescription *sd) { } bool_t sal_stream_description_has_avpf(const SalStreamDescription *sd) { - return ((sd->proto == SalProtoRtpAvpf) || (sd->proto == SalProtoRtpSavpf)); + return ((sd->proto == SalProtoRtpAvpf) || (sd->proto == SalProtoRtpSavpf) || (sd->proto == SalProtoUdpTlsRtpSavpf)); } bool_t sal_stream_description_has_srtp(const SalStreamDescription *sd) { return ((sd->proto == SalProtoRtpSavp) || (sd->proto == SalProtoRtpSavpf)); } +bool_t sal_stream_description_has_dtls(const SalStreamDescription *sd) { + return ((sd->proto == SalProtoUdpTlsRtpSavp) || (sd->proto == SalProtoUdpTlsRtpSavpf)); +} + bool_t sal_media_description_has_avpf(const SalMediaDescription *md) { int i; if (md->nb_streams == 0) return FALSE; @@ -221,6 +225,16 @@ bool_t sal_media_description_has_srtp(const SalMediaDescription *md) { return TRUE; } +bool_t sal_media_description_has_dtls(const SalMediaDescription *md) { + int i; + if (md->nb_streams == 0) return FALSE; + for (i = 0; i < md->nb_streams; i++) { + if (!sal_stream_description_active(&md->streams[i])) continue; + if (sal_stream_description_has_dtls(&md->streams[i]) != TRUE) return FALSE; + } + return TRUE; +} + /* static bool_t fmtp_equals(const char *p1, const char *p2){ if (p1 && p2 && strcmp(p1,p2)==0) return TRUE; @@ -607,8 +621,10 @@ const char* sal_media_proto_to_string(SalMediaProto type) { switch (type) { case SalProtoRtpAvp:return "RTP/AVP"; case SalProtoRtpSavp:return "RTP/SAVP"; + case SalProtoUdpTlsRtpSavp:return "UDP/TLS/RTP/SAVP"; case SalProtoRtpAvpf:return "RTP/AVPF"; case SalProtoRtpSavpf:return "RTP/SAVPF"; + case SalProtoUdpTlsRtpSavpf:return "UDP/TLS/RTP/SAVPF"; default: return "unknown"; } } diff --git a/gtk/incall_view.c b/gtk/incall_view.c index 4fa0bd66b..0d6606aee 100644 --- a/gtk/incall_view.c +++ b/gtk/incall_view.c @@ -680,6 +680,12 @@ void linphone_gtk_in_call_view_show_encryption(LinphoneCall *call){ gtk_widget_hide(status_icon); gtk_widget_hide(verify_button); break; + case LinphoneMediaEncryptionDTLS: + gtk_widget_show_all(encryption_box); + gtk_label_set_markup(GTK_LABEL(label),_("Secured by DTLS")); + gtk_widget_hide(status_icon); + gtk_widget_hide(verify_button); + break; case LinphoneMediaEncryptionZRTP: { gchar *text=g_strdup_printf(_("Secured by ZRTP - [auth token: %s]"),linphone_call_get_authentication_token(call)); diff --git a/gtk/main.c b/gtk/main.c index ea3078c9a..13e622969 100644 --- a/gtk/main.c +++ b/gtk/main.c @@ -172,9 +172,11 @@ static GOptionEntry linphone_options[]={ #ifndef WIN32 #define CONFIG_FILE ".linphonerc" #define SECRETS_FILE ".linphone-zidcache" +#define CERTIFICATES_PATH ".linphone-usr-crt" #else #define CONFIG_FILE "linphonerc" #define SECRETS_FILE "linphone-zidcache" +#define CERTIFICATES_PATH "linphone-usr-crt" #endif char *linphone_gtk_get_config_file(const char *filename){ @@ -279,6 +281,7 @@ static void linphone_gtk_init_liblinphone(const char *config_file, const char *factory_config_file, const char *db_file) { LinphoneCoreVTable vtable={0}; gchar *secrets_file=linphone_gtk_get_config_file(SECRETS_FILE); + gchar *user_certificates_dir=linphone_gtk_get_config_file(CERTIFICATES_PATH); vtable.global_state_changed=linphone_gtk_global_state_changed; vtable.call_state_changed=linphone_gtk_call_state_changed; @@ -316,6 +319,8 @@ static void linphone_gtk_init_liblinphone(const char *config_file, linphone_core_set_waiting_callback(the_core,linphone_gtk_wait,NULL); linphone_core_set_zrtp_secrets_file(the_core,secrets_file); g_free(secrets_file); + linphone_core_set_user_certificates_path(the_core,user_certificates_dir); + g_free(user_certificates_dir); linphone_core_enable_video_capture(the_core, TRUE); linphone_core_enable_video_display(the_core, TRUE); linphone_core_set_native_video_window_id(the_core,-1);/*don't create the window*/ diff --git a/gtk/propertybox.c b/gtk/propertybox.c index 677fe5f06..c075fe91c 100644 --- a/gtk/propertybox.c +++ b/gtk/propertybox.c @@ -1199,11 +1199,13 @@ static void linphone_gtk_media_encryption_changed(GtkWidget *combo){ if (strcasecmp(selected,"SRTP")==0){ linphone_core_set_media_encryption(lc,LinphoneMediaEncryptionSRTP); linphone_gtk_set_media_encryption_mandatory_sensitive(toplevel,TRUE); + }else if (strcasecmp(selected,"DTLS")==0){ + linphone_core_set_media_encryption(lc,LinphoneMediaEncryptionDTLS); + linphone_gtk_set_media_encryption_mandatory_sensitive(toplevel,FALSE); }else if (strcasecmp(selected,"ZRTP")==0){ linphone_core_set_media_encryption(lc,LinphoneMediaEncryptionZRTP); linphone_gtk_set_media_encryption_mandatory_sensitive(toplevel,FALSE); - } - else { + } else { linphone_core_set_media_encryption(lc,LinphoneMediaEncryptionNone); linphone_gtk_set_media_encryption_mandatory_sensitive(toplevel,FALSE); } @@ -1219,7 +1221,7 @@ static void linphone_gtk_show_media_encryption(GtkWidget *pb){ LinphoneCore *lc=linphone_gtk_get_core(); GtkWidget *combo=linphone_gtk_get_widget(pb,"media_encryption_combo"); bool_t no_enc=TRUE; - int srtp_id=-1,zrtp_id=-1; + int srtp_id=-1,zrtp_id=-1,dtls_id=-1; GtkTreeModel *model; GtkListStore *store; GtkTreeIter iter; @@ -1239,12 +1241,26 @@ static void linphone_gtk_show_media_encryption(GtkWidget *pb){ srtp_id=1; no_enc=FALSE; } + if (linphone_core_media_encryption_supported(lc,LinphoneMediaEncryptionDTLS)){ + gtk_list_store_append(store,&iter); + gtk_list_store_set(store,&iter,0,_("DTLS"),-1); + if (srtp_id!=-1) dtls_id=2; + else dtls_id=1; + no_enc=FALSE; + } if (linphone_core_media_encryption_supported(lc,LinphoneMediaEncryptionZRTP)){ gtk_list_store_append(store,&iter); gtk_list_store_set(store,&iter,0,_("ZRTP"),-1); no_enc=FALSE; - if (srtp_id!=-1) zrtp_id=2; - else zrtp_id=1; + if (srtp_id!=-1) { + if (dtls_id!=-1) + zrtp_id=3; + else zrtp_id=2; + } else { + if (dtls_id!=-1) + zrtp_id=2; + else zrtp_id=1; + } } if (no_enc){ /*hide this setting*/ @@ -1264,6 +1280,12 @@ static void linphone_gtk_show_media_encryption(GtkWidget *pb){ linphone_gtk_set_media_encryption_mandatory_sensitive(pb,TRUE); } break; + case LinphoneMediaEncryptionDTLS: + if (dtls_id!=-1) { + gtk_combo_box_set_active(GTK_COMBO_BOX(combo),dtls_id); + linphone_gtk_set_media_encryption_mandatory_sensitive(pb,TRUE); + } + break; case LinphoneMediaEncryptionZRTP: if (zrtp_id!=-1) { gtk_combo_box_set_active(GTK_COMBO_BOX(combo),zrtp_id); diff --git a/include/sal/sal.h b/include/sal/sal.h index 5fb0fbd92..a4c155b99 100644 --- a/include/sal/sal.h +++ b/include/sal/sal.h @@ -129,6 +129,8 @@ typedef enum{ SalProtoRtpSavp, SalProtoRtpAvpf, SalProtoRtpSavpf, + SalProtoUdpTlsRtpSavp, + SalProtoUdpTlsRtpSavpf, SalProtoOther }SalMediaProto; const char* sal_media_proto_to_string(SalMediaProto type); @@ -182,6 +184,13 @@ typedef struct SalSrtpCryptoAlgo { #define SAL_CRYPTO_ALGO_MAX 4 +typedef enum { + SalDtlsRoleInvalid, + SalDtlsRoleIsServer, + SalDtlsRoleIsClient, + SalDtlsRoleUnset +} SalDtlsRole; + typedef struct SalStreamDescription{ char name[16]; /*unique name of stream, in order to ease offer/answer model algorithm*/ SalMediaProto proto; @@ -207,6 +216,8 @@ typedef struct SalStreamDescription{ bool_t ice_mismatch; bool_t ice_completed; bool_t pad[2]; + char dtls_fingerprint[256]; + SalDtlsRole dtls_role; } SalStreamDescription; const char *sal_stream_description_get_type_as_string(const SalStreamDescription *desc); @@ -231,6 +242,8 @@ typedef struct SalMediaDescription{ bool_t ice_lite; bool_t ice_completed; bool_t pad[2]; + char dtls_fingerprint[256]; + SalDtlsRole dtls_role; } SalMediaDescription; typedef struct SalMessage{ @@ -265,8 +278,10 @@ void sal_media_description_set_dir(SalMediaDescription *md, SalStreamDir stream_ bool_t sal_stream_description_active(const SalStreamDescription *sd); bool_t sal_stream_description_has_avpf(const SalStreamDescription *sd); bool_t sal_stream_description_has_srtp(const SalStreamDescription *sd); +bool_t sal_stream_description_has_dtls(const SalStreamDescription *sd); bool_t sal_media_description_has_avpf(const SalMediaDescription *md); bool_t sal_media_description_has_srtp(const SalMediaDescription *md); +bool_t sal_media_description_has_dtls(const SalMediaDescription *md); int sal_media_description_get_nb_active_streams(const SalMediaDescription *md); @@ -510,6 +525,18 @@ void sal_certificates_chain_parse_file(SalAuthInfo* auth_info, const char* path, */ void sal_signing_key_parse_file(SalAuthInfo* auth_info, const char* path, const char *passwd); +/** + * Parse a directory for files containing certificate with the given subject CNAME + * @param[out] certificate_pem the address of a string to store the certificate in PEM format. To be freed by caller + * @param[out] key_pem the address of a string to store the key in PEM format. To be freed by caller + * @param[in] path directory to parse + * @param[in] subject subject CNAME + * @param[in] format either PEM or DER + * @param[in] generate_certificate if true, if matching certificate and key can't be found, generate it and store it into the given dir, filename will be subject.pem + * @param[in] generate_dtls_fingerprint if true and we have a certificate, generate the dtls fingerprint as described in rfc4572 + */ +void sal_certificates_chain_parse_directory(unsigned char **certificate_pem, unsigned char **key_pem, unsigned char **fingerprint, const char* path, const char *subject, SalCertificateRawFormat format, bool_t generate_certificate, bool_t generate_dtls_fingerprint); + void sal_certificates_chain_delete(SalCertificatesChain *chain); void sal_signing_key_delete(SalSigningKey *key); diff --git a/mediastreamer2 b/mediastreamer2 index 90bad6f9e..dd1d93944 160000 --- a/mediastreamer2 +++ b/mediastreamer2 @@ -1 +1 @@ -Subproject commit 90bad6f9e48f7d18cba2d1a50c4e55fbc6b3c686 +Subproject commit dd1d93944369d5e19808109ea94b51f28b0e5a92 diff --git a/oRTP b/oRTP index 5333d998c..29802b08d 160000 --- a/oRTP +++ b/oRTP @@ -1 +1 @@ -Subproject commit 5333d998c8eaf3c43e66c4a5335c0606c8dea3df +Subproject commit 29802b08dac2de2f320db28249d5d459a47507e8