diff --git a/src/crypto/dtls_srtp.cpp b/src/crypto/dtls_srtp.cpp index f12a810e86ebf5821f4e671c3ecce4051b213598..55a9e6c3fa068603f89df99804ea5926dcfa1104 100644 --- a/src/crypto/dtls_srtp.cpp +++ b/src/crypto/dtls_srtp.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2022 Belledonne Communications SARL. + * Copyright (c) 2010-2025 Belledonne Communications SARL. * * This file is part of mediastreamer2 * (see https://gitlab.linphone.org/BC/public/mediastreamer2). @@ -18,8 +18,10 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ +#include <array> #include <bctoolbox/defs.h> #include <mutex> +#include <queue> #include "mediastreamer2/dtls_srtp.h" #include "mediastreamer2/mediastream.h" @@ -30,89 +32,123 @@ #include "bctoolbox/crypto.h" -typedef struct _DtlsBcToolBoxContexts { +namespace { +/** A class to manage all crypto contexts needed by Dtls-Srtp */ +class DtlsCrypto { +public: bctbx_x509_certificate_t *crt; bctbx_ssl_config_t *ssl_config; bctbx_ssl_context_t *ssl; bctbx_rng_context_t *rng; bctbx_signing_key_t *pkey; -} DtlsBcToolBoxContext; -/** - * incoming DTLS message are stored in a chain buffer to feed handshake when needed - */ -typedef struct _DtlsRawPacket { - unsigned char *data; - size_t length; - void *next; -} DtlsRawPacket; + DtlsCrypto() + : crt(bctbx_x509_certificate_new()), ssl_config(bctbx_ssl_config_new()), ssl(nullptr), + rng(bctbx_rng_context_new()), pkey(bctbx_signing_key_new()) { + } + ~DtlsCrypto() { + bctbx_rng_context_free(rng); + bctbx_signing_key_free(pkey); + bctbx_x509_certificate_free(crt); + bctbx_ssl_context_free(ssl); + bctbx_ssl_config_free(ssl_config); + } +}; /* DTLS only allow use of AES128 so we have 16 bytes key and 14 byte salt in any case */ -#define DTLS_SRTP_KEY_LEN 16 -#define DTLS_SRTP_SALT_LEN 14 -#define DTLS_SRTP_KEY_MATERIAL_LEN 128 +constexpr size_t DtlsSrtpKeyLen = 16; +constexpr size_t DtlsSrtpSaltLen = 14; +// key material generated during the DTLS handshake: a pair of key+salt : one for reception, one for sending +constexpr size_t DtlsSrtpKeyMaterial = 2 * (DtlsSrtpKeyLen + DtlsSrtpSaltLen); +// period in ms to trigger the repetition timer +constexpr uint64_t DtlsRepetitionTimerPoll = 100; /* Do not modify this values: fingerprint_verified MUST be > handshake_over*/ -#define DTLS_STATUS_CONTEXT_NOT_READY 0 -#define DTLS_STATUS_CONTEXT_READY 1 -#define DTLS_STATUS_HANDSHAKE_ONGOING 2 -#define DTLS_STATUS_HANDSHAKE_OVER 3 -#define DTLS_STATUS_FINGERPRINT_VERIFIED 4 - -#define DTLS_REPETITION_TIMER_POLL 100 +enum class DtlsStatus : uint8_t { + ContextNotReady = 0, + ContextReady = 1, + HandshakeOngoing = 2, + HandshakeOver = 3, + FingerprintVerified = 4, +}; +} // anonymous namespace struct _MSDtlsSrtpContext { - MSMediaStreamSessions *stream_sessions; - MSDtlsSrtpRole role; /**< can be unset(at init on caller side), client or server */ - char peer_fingerprint[256]; /**< used to store peer fingerprint passed through SDP */ + MSMediaStreamSessions *mStreamSessions; + MSDtlsSrtpRole mRole; /**< can be unset(at init on caller side), client or server */ + std::string mPeerFingerprint; /**< used to store peer fingerprint passed through SDP */ int mtu; RtpTransportModifier *rtp_modifier; - DtlsBcToolBoxContext - *rtp_dtls_context; /**< a structure containing all contexts needed by DTLS handshake for RTP channel */ - uint8_t rtp_channel_status; /**< channel status : DTLS_STATUS_CONTEXT_NOT_READY, DTLS_STATUS_CONTEXT_READY, - DTLS_STATUS_HANDSHAKE_ONGOING, DTLS_STATUS_HANDSHAKE_OVER, - DTLS_STATUS_FINGERPRINT_VERIFIED */ - uint8_t rtp_channel_key_material[DTLS_SRTP_KEY_MATERIAL_LEN]; /**< Store the key material generated by the handshake + DtlsCrypto mDtlsCryptoContext; /**< a structure containing all contexts needed by DTLS handshake for RTP channel */ + DtlsStatus mChannelStatus; /**< channel status :not ready, ready, hanshake on going, handshake over, fingerprint + verified */ + std::array<uint8_t, DtlsSrtpKeyMaterial> mSrtpKeyMaterial; /**< Store the key material generated by the handshake on rtp channel */ - MSCryptoSuite rtp_agreed_srtp_protection_profile; /**< agreed protection profile on rtp channel */ - DtlsRawPacket *rtp_incoming_buffer; /**< buffer of incoming DTLS packet to be read by mbedtls callback */ - uint64_t rtp_time_reference; /**< an epoch in ms, used to manage retransmission when we are client */ - bool retry_sending; /**< a flag to set a retry after failed packet sending */ - std::mutex mtx; /**< lock any operation on this context */ + MSCryptoSuite mSrtpProtectionProfile; /**< agreed protection profile on rtp channel */ + std::queue<std::vector<uint8_t>> + mRtpIncomingBuffer; /**< buffer of incoming DTLS packet to be read by mbedtls callback */ + uint64_t rtp_time_reference; /**< an epoch in ms, used to manage retransmission when we are client */ + bool retry_sending; /**< a flag to set a retry after failed packet sending */ + std::mutex mtx; /**< lock any operation on this context */ + + _MSDtlsSrtpContext() = delete; + _MSDtlsSrtpContext(MSMediaStreamSessions *sessions, MSDtlsSrtpParams *params) { + mRole = params->role; + mtu = params->mtu; + rtp_time_reference = 0; + retry_sending = false; + + mStreamSessions = sessions; + mChannelStatus = DtlsStatus::ContextNotReady; + mSrtpProtectionProfile = MS_CRYPTO_SUITE_INVALID; + }; + ~_MSDtlsSrtpContext() = default; + + int initialiseDtlsCryptoContext(MSDtlsSrtpParams *params); + void start(); + void createSslContext(); + void setRole(MSDtlsSrtpRole role); + void checkChannelStatus(); + void setKeyMaterial(); + int processDtlsPacket(mblk_t *msg); }; +namespace { /***********************************************/ /***** LOCAL FUNCTIONS *****/ /***********************************************/ -namespace { -/*** DtlsBcToolBox context create/dstroy ***/ - -DtlsBcToolBoxContext *ms_dtls_srtp_bctbx_context_new(void) { - // allocate the memory - DtlsBcToolBoxContext *ctx = ms_new0(DtlsBcToolBoxContext, 1); - - // create and initialise the requested fields - ctx->rng = bctbx_rng_context_new(); - ctx->pkey = bctbx_signing_key_new(); - ctx->crt = bctbx_x509_certificate_new(); - ctx->ssl_config = bctbx_ssl_config_new(); - ctx->ssl = NULL; - return ctx; -} - -void ms_dtls_srtp_bctbx_context_free(DtlsBcToolBoxContext *ctx) { - if (ctx != NULL) { - bctbx_rng_context_free(ctx->rng); - bctbx_signing_key_free(ctx->pkey); - bctbx_x509_certificate_free(ctx->crt); - bctbx_ssl_context_free(ctx->ssl); - bctbx_ssl_config_free(ctx->ssl_config); - ms_free(ctx); - } -} +// Fingerprint size should never be more than 255 (actually less than that with supported types: +// SHA-512 is 64 bytes long -> 3*64 for its hexa and : representation +8 for the prefix = 200 +constexpr size_t maxFingerPrintSize = 255; /**************************/ /**** Helper functions ****/ +// case-insensitive prefix comparison of fingerprints +bool startsWithCaseInsensitive(const std::string &fingerprint, const char *prefix, size_t prefixSize) { + if (fingerprint.size() < prefixSize) { + return false; + } + for (size_t i = 0; i < prefixSize; ++i) { + if (std::tolower(static_cast<unsigned char>(fingerprint[i])) != prefix[i]) { + return false; + } + } + return true; +} +// case-insensitive compare for whole fingerprints +bool caseInsensitiveCompare(const std::string &fingerprint, const char *cStr) { + size_t cStrLen = strlen(cStr); + if (fingerprint.size() != cStrLen) { + return false; + } + for (size_t i = 0; i < cStrLen; ++i) { + if (std::tolower(static_cast<unsigned char>(fingerprint[i])) != + std::tolower(static_cast<unsigned char>(cStr[i]))) { + return false; + } + } + return true; +} /** * @Brief Compute the certificate fingerprint(hash of DER formated certificate) * hash function to use shall be the same used by certificate signature(this is a way to ensure that the hash function @@ -127,7 +163,7 @@ void ms_dtls_srtp_bctbx_context_free(DtlsBcToolBoxContext *ctx) { * @return 0 if the fingerprint doesn't match, 1 is they do. */ uint8_t ms_dtls_srtp_check_certificate_fingerprint(const bctbx_x509_certificate_t *certificate, - const char *peer_fingerprint) { + const std::string &peer_fingerprint) { char fingerprint[256]; /* maximum length of the fingerprint for sha-512: 8+3*64+1 so we're good with 256 bytes buffer */ bctbx_md_type_t hash_function = BCTBX_MD_UNDEFINED; @@ -135,18 +171,18 @@ uint8_t ms_dtls_srtp_check_certificate_fingerprint(const bctbx_x509_certificate_ int32_t ret = 0; /* get Hash algorithm used from peer fingerprint */ - if (strncasecmp(peer_fingerprint, "sha-1 ", 6) == 0) { + if (startsWithCaseInsensitive(peer_fingerprint, "sha-1 ", 6) == 0) { hash_function = BCTBX_MD_SHA1; - } else if (strncasecmp(peer_fingerprint, "sha-224 ", 8) == 0) { + } else if (startsWithCaseInsensitive(peer_fingerprint, "sha-224 ", 8) == 0) { hash_function = BCTBX_MD_SHA224; - } else if (strncasecmp(peer_fingerprint, "sha-256 ", 8) == 0) { + } else if (startsWithCaseInsensitive(peer_fingerprint, "sha-256 ", 8) == 0) { hash_function = BCTBX_MD_SHA256; - } else if (strncasecmp(peer_fingerprint, "sha-384 ", 8) == 0) { + } else if (startsWithCaseInsensitive(peer_fingerprint, "sha-384 ", 8) == 0) { hash_function = BCTBX_MD_SHA384; - } else if (strncasecmp(peer_fingerprint, "sha-512 ", 8) == 0) { + } else if (startsWithCaseInsensitive(peer_fingerprint, "sha-512 ", 8) == 0) { hash_function = BCTBX_MD_SHA512; } else { /* we have an unknown hash function: return null */ - ms_error("DTLS-SRTP received invalid peer fingerprint %s, hash function unknown", peer_fingerprint); + ms_error("DTLS-SRTP received invalid peer fingerprint %s, hash function unknown", peer_fingerprint.c_str()); return 0; } @@ -168,11 +204,11 @@ uint8_t ms_dtls_srtp_check_certificate_fingerprint(const bctbx_x509_certificate_ } /* compare fingerprints */ - if (strncasecmp((const char *)fingerprint, peer_fingerprint, strlen((const char *)fingerprint)) == 0) { + if (caseInsensitiveCompare(peer_fingerprint, (char *)fingerprint) == 0) { return 1; } else { - ms_error("DTLS Handshake successful but fingerprints differ received : %s computed %s", peer_fingerprint, - fingerprint); + ms_error("DTLS Handshake successful but fingerprints differ received : %s computed %s", + peer_fingerprint.c_str(), fingerprint); return 0; } } @@ -205,275 +241,24 @@ void schedule_rtp(struct _RtpTransportModifier *t) { if (ctx->retry_sending) { std::lock_guard<std::mutex> lock(ctx->mtx); ctx->retry_sending = false; - bctbx_ssl_handshake(ctx->rtp_dtls_context->ssl); + bctbx_ssl_handshake(ctx->mDtlsCryptoContext.ssl); return; } /* the retransmission timer increasing value is managed by the crypto lib * just poke it each 100ms */ if (ctx->rtp_time_reference > 0) { /* only when retransmission timer is armed */ auto current_time = bctbx_get_cur_time_ms(); - if (current_time - ctx->rtp_time_reference > DTLS_REPETITION_TIMER_POLL) { + if (current_time - ctx->rtp_time_reference > DtlsRepetitionTimerPoll) { std::lock_guard<std::mutex> lock(ctx->mtx); if (ctx->rtp_time_reference > 0) { /* recheck the timer is still armed once we're into the guarded section */ - bctbx_ssl_handshake(ctx->rtp_dtls_context->ssl); + bctbx_ssl_handshake(ctx->mDtlsCryptoContext.ssl); ctx->rtp_time_reference = bctbx_get_cur_time_ms(); } } } } -void ms_dtls_srtp_set_role_nolock(MSDtlsSrtpContext *context, MSDtlsSrtpRole role); -void ms_dtls_srtp_start_nolock(MSDtlsSrtpContext *context); - -/** - * Check if the incoming message is a DTLS packet. - * If it is, store it in the context incoming buffer and call the bctoolbox function wich will process it. - * This function also manages the client retransmission timer - * - * @param[in] msg the incoming message - * @param[in/out] ctx the context containing the incoming buffer to store the DTLS packet - * @return the value returned by the bctoolbox function processing the packet(ssl_handshake) - */ -int ms_dtls_srtp_process_dtls_packet(mblk_t *msg, MSDtlsSrtpContext *ctx) { - size_t msgLength = msgdsize(msg); - bctbx_ssl_context_t *ssl = ctx->rtp_dtls_context->ssl; - uint8_t channel_status = ctx->rtp_channel_status; - int ret = 0; - - /* UGLY PATCH CLIENT HELLO PACKET PARSING */ - const int Content_Type_Index = 0; - const int Content_Length_Index = 11; - const int Handshake_Type_Index = 13; - const int Handshake_Message_Length_Index = 14; - const int Handshake_Message_Seq_Index = 17; - const int Handshake_Frag_Offset_Index = 19; - const int Handshake_Frag_Length_Index = 22; - const size_t Handshake_Header_Length = 25; - unsigned char *frag = msg->b_rptr; - size_t base_index = 0; - int message_length = 0; - int message_seq = 0; - int current_message_seq = -1; - int frag_offset = 0; - int frag_length = 0; - unsigned char *reassembled_packet = NULL; - /* end of UGLY PATCH CLIENT HELLO PACKET PARSING */ - - DtlsRawPacket *incoming_dtls_packet; - incoming_dtls_packet = (DtlsRawPacket *)ms_malloc0(sizeof(DtlsRawPacket)); - incoming_dtls_packet->next = NULL; - incoming_dtls_packet->data = (unsigned char *)ms_malloc(msgLength); - incoming_dtls_packet->length = msgLength; - memcpy(incoming_dtls_packet->data, msg->b_rptr, msgLength); - - /*required by webrtc in server case when ice is not completed yet*/ - /* no more required because change is performed by ice.c once a check list is ready - * rtp_session_update_remote_sock_addr(rtp_session, msg,is_rtp,FALSE);*/ - - ms_message("DTLS Receive RTP packet len %d sessions: %p rtp session %p", (int)msgLength, ctx->stream_sessions, - ctx->stream_sessions->rtp_session); - - /* UGLY PATCH CLIENT HELLO PACKET PARSING */ - /* Parse the DTLS packet to check if we have a Client Hello packet fragmented at DTLS level but all set in one - * datagram (some kind of bug in certain versions openssl produce that and mbedtls does not support Client Hello - * fragmentation ) This patch is not very resistant to any change and target that very particular situation, a - * better solution should be to implement support of Client Hello Fragmentation in mbedtls */ - if (msgLength > Handshake_Header_Length && frag[Content_Type_Index] == 0x16 && - frag[Handshake_Type_Index] == - 0x01) { // If the first fragment(there may be only one) is of a DTLS Handshake Client Hello message - while (base_index + Handshake_Header_Length < - msgLength) { // loop on the message, parsing all fragments it may contain (loop until we have at - // least enough unparsed byte to read a handshake header) - if (frag[Content_Type_Index] == 0x16) { // Type index 0x16 is DLTS Handshake message - if (frag[Handshake_Type_Index] == 0x01) { // Handshake type 0x01 is Client Hello - // Get message length - message_length = frag[Handshake_Message_Length_Index] << 16 | - frag[Handshake_Message_Length_Index + 1] << 8 | - frag[Handshake_Message_Length_Index + 2]; - - // message sequence number - message_seq = frag[Handshake_Message_Seq_Index] << 8 | frag[Handshake_Message_Seq_Index + 1]; - if (current_message_seq == -1) { - current_message_seq = message_seq; - } - - // fragment offset - frag_offset = frag[Handshake_Frag_Offset_Index] << 16 | frag[Handshake_Frag_Offset_Index + 1] << 8 | - frag[Handshake_Frag_Offset_Index + 2]; - - // and fragment length - frag_length = frag[Handshake_Frag_Length_Index] << 16 | frag[Handshake_Frag_Length_Index + 1] << 8 | - frag[Handshake_Frag_Length_Index + 2]; - - // check the message is not malformed and would lead us to read after the message buffer - // or write after our reassembled packet buffer - if (base_index + Handshake_Header_Length + frag_length <= - msgLength // we will read frag_length starting at base_index (frag in the code)+ header - && frag_length + frag_offset <= - message_length) { // we will write in the reassembled buffer frag_length byte, - // starting at frag_offset, the buffer is message_length long - - // If message length and fragment length differs, we have a fragmented Client Hello - // Check they are part of the same message (message_seq) - // We will just collect all fragments (in our very particuliar case, they are all in the - // same datagram so we do not need long term storage, juste parsing this packet) - if (message_length != frag_length && message_seq == current_message_seq) { - if (reassembled_packet == NULL) { // this is first fragment we get - reassembled_packet = - (unsigned char *)ms_malloc(Handshake_Header_Length + message_length); - // copy the header - memcpy(reassembled_packet, msg->b_rptr, Handshake_Header_Length); - // set the message length to be in line with reassembled fragments - reassembled_packet[Content_Length_Index] = ((message_length + 12) >> 8) & 0xFF; - reassembled_packet[Content_Length_Index + 1] = (message_length + 12) & 0xFF; - - // set the frag length to be the same than message length - reassembled_packet[Handshake_Frag_Length_Index] = - reassembled_packet[Handshake_Message_Length_Index]; - reassembled_packet[Handshake_Frag_Length_Index + 1] = - reassembled_packet[Handshake_Message_Length_Index + 1]; - reassembled_packet[Handshake_Frag_Length_Index + 2] = - reassembled_packet[Handshake_Message_Length_Index + 2]; - } - // copy the received fragment - memcpy(reassembled_packet + Handshake_Header_Length + frag_offset, - frag + Handshake_Header_Length, frag_length); - } - - // read what is next in the datagram - base_index += Handshake_Header_Length + frag_length; // bytes parsed so far - frag += Handshake_Header_Length + frag_length; // point to the begining of the next fragment - } else { // message is malformed in a nasty way - ms_warning("DTLS Received RTP packet len %d sessions: %p rtp session %p is malformed in an " - "agressive way", - (int)msgLength, ctx->stream_sessions, ctx->stream_sessions->rtp_session); - base_index = msgLength; // get out of the while - ms_free(reassembled_packet); - reassembled_packet = NULL; - } - } else { - base_index = msgLength; // get out of the while - ms_free(reassembled_packet); - reassembled_packet = NULL; - } - } - } - } - // if we made a reassembled client hello packet, use this one as incoming dlts packet and discard the original - // one - if (reassembled_packet != NULL) { - ms_message("DTLS re-assembled a fragmented Client Hello packet"); - ms_free(incoming_dtls_packet->data); - incoming_dtls_packet->data = (unsigned char *)ms_malloc(Handshake_Header_Length + message_length); - incoming_dtls_packet->length = Handshake_Header_Length + message_length; - memcpy(incoming_dtls_packet->data, reassembled_packet, Handshake_Header_Length + message_length); - ms_free(reassembled_packet); - } - /* end of UGLY PATCH CLIENT HELLO PACKET PARSING */ - - /* store the packet in the incoming buffer */ - if (ctx->rtp_incoming_buffer == NULL) { /* buffer is empty */ - ctx->rtp_incoming_buffer = incoming_dtls_packet; - } else { /* queue it at the end of current buffer */ - DtlsRawPacket *last_packet = ctx->rtp_incoming_buffer; - while (last_packet->next != NULL) - last_packet = (DtlsRawPacket *)(last_packet->next); - last_packet->next = incoming_dtls_packet; - } - - /* while DTLS handshake is on going route DTLS packets to bctoolbox engine through ssl_handshake() */ - if (channel_status < DTLS_STATUS_HANDSHAKE_OVER) { - /* role is unset but we receive a packet: we are caller and shall initialise as server and then process the - * incoming packet */ - if (ctx->role == MSDtlsSrtpRoleUnset) { - ms_dtls_srtp_set_role_nolock( - ctx, MSDtlsSrtpRoleIsServer); /* this call will update role and complete server setup */ - ms_dtls_srtp_start_nolock( - ctx); /* complete the ssl setup and change channel_status to DTLS_STATUS_HANDSHAKE_ONGOING */ - ssl = ctx->rtp_dtls_context->ssl; - } - /* process the packet and store result */ - ret = bctbx_ssl_handshake(ssl); - ms_message("DTLS Handshake process RTP packet len %d sessions: %p rtp session %p return %s0x%0x", - (int)msgLength, ctx->stream_sessions, ctx->stream_sessions->rtp_session, ret > 0 ? "+" : "-", - ret > 0 ? ret : -ret); - - /* if we are client, manage the retransmission timer */ - if (ctx->role == MSDtlsSrtpRoleIsClient) { - ctx->rtp_time_reference = bctbx_get_cur_time_ms(); - } - } else { /* when DTLS handshake is over, route DTLS packets to bctoolbox engine through ssl_read() */ - /* we need a buffer to store the message read even if we don't use it */ - unsigned char *buf = (unsigned char *)ms_malloc(msgLength + 1); - ret = bctbx_ssl_read(ssl, buf, msgLength); - ms_message("DTLS Handshake read RTP packet len %d sessions: %p rtp session %p return %s0x%0x", (int)msgLength, - ctx->stream_sessions, ctx->stream_sessions->rtp_session, ret > 0 ? "+" : "-", ret > 0 ? ret : -ret); - ms_free(buf); - } - - /* report the error in logs only when different than requested read(waiting for data) */ - if (ret < 0 && ret != BCTBX_ERROR_NET_WANT_READ) { - char err_str[512]; - err_str[0] = '\0'; - bctbx_strerror(ret, err_str, 512); - ms_warning("DTLS Handshake returns -0x%x : %s [on sessions: %p rtp session %p]", -ret, err_str, - ctx->stream_sessions, ctx->stream_sessions->rtp_session); - } - return ret; -} - -void ms_dtls_srtp_check_channels_status(MSDtlsSrtpContext *ctx) { - /* Check if we're are ready: rtp_channel done and rtcp mux on */ - if ((ctx->rtp_channel_status == DTLS_STATUS_FINGERPRINT_VERIFIED) && - (rtp_session_rtcp_mux_enabled(ctx->stream_sessions->rtp_session))) { - OrtpEventData *eventData; - OrtpEvent *ev; - ev = ortp_event_new(ORTP_EVENT_DTLS_ENCRYPTION_CHANGED); - eventData = ortp_event_get_data(ev); - eventData->info.dtls_stream_encrypted = 1; - rtp_session_dispatch_event(ctx->stream_sessions->rtp_session, ev); - ms_message("DTLS Event dispatched to all: secrets are on for this stream"); - } -} - -/* Get keys from context and set then in the SRTP context according to the given stream type - * - * @param ctx Pointer to the MSDtlsSrtpContext structure - */ -void ms_dtls_srtp_set_srtp_key_material(MSDtlsSrtpContext *ctx) { - - uint8_t key[256]; - MSCryptoSuite srtp_protection_profile = MS_CRYPTO_SUITE_INVALID; - uint8_t *key_material; - - srtp_protection_profile = ctx->rtp_agreed_srtp_protection_profile; - key_material = ctx->rtp_channel_key_material; - - if (ctx->role == MSDtlsSrtpRoleIsServer) { - /* reception(client write) key and salt +16bits padding */ - memcpy(key, key_material, DTLS_SRTP_KEY_LEN); - memcpy(key + DTLS_SRTP_KEY_LEN, key_material + 2 * DTLS_SRTP_KEY_LEN, DTLS_SRTP_SALT_LEN); - ms_media_stream_sessions_set_srtp_recv_key(ctx->stream_sessions, srtp_protection_profile, key, - DTLS_SRTP_KEY_LEN + DTLS_SRTP_SALT_LEN, MSSrtpKeySourceDTLS); - /* emission(server write) key and salt +16bits padding */ - memcpy(key, key_material + DTLS_SRTP_KEY_LEN, DTLS_SRTP_KEY_LEN); - memcpy(key + DTLS_SRTP_KEY_LEN, key_material + 2 * DTLS_SRTP_KEY_LEN + DTLS_SRTP_SALT_LEN, DTLS_SRTP_SALT_LEN); - ms_media_stream_sessions_set_srtp_send_key(ctx->stream_sessions, srtp_protection_profile, key, - DTLS_SRTP_KEY_LEN + DTLS_SRTP_SALT_LEN, MSSrtpKeySourceDTLS); - } else if (ctx->role == MSDtlsSrtpRoleIsClient) { /* this enpoint act as DTLS client */ - /* emission(client write) key and salt +16bits padding */ - memcpy(key, key_material, DTLS_SRTP_KEY_LEN); - memcpy(key + DTLS_SRTP_KEY_LEN, key_material + 2 * DTLS_SRTP_KEY_LEN, DTLS_SRTP_SALT_LEN); - ms_media_stream_sessions_set_srtp_send_key(ctx->stream_sessions, srtp_protection_profile, key, - DTLS_SRTP_KEY_LEN + DTLS_SRTP_SALT_LEN, MSSrtpKeySourceDTLS); - /* reception(server write) key and salt +16bits padding */ - memcpy(key, key_material + DTLS_SRTP_KEY_LEN, DTLS_SRTP_KEY_LEN); - memcpy(key + DTLS_SRTP_KEY_LEN, key_material + 2 * DTLS_SRTP_KEY_LEN + DTLS_SRTP_SALT_LEN, DTLS_SRTP_SALT_LEN); - ms_media_stream_sessions_set_srtp_recv_key(ctx->stream_sessions, srtp_protection_profile, key, - DTLS_SRTP_KEY_LEN + DTLS_SRTP_SALT_LEN, MSSrtpKeySourceDTLS); - } -} /********************************************/ /**** bctoolbox DTLS packet I/O functions ****/ @@ -493,13 +278,13 @@ void ms_dtls_srtp_set_srtp_key_material(MSDtlsSrtpContext *ctx) { */ int ms_dtls_srtp_rtp_sendData(void *ctx, const unsigned char *data, size_t length) { MSDtlsSrtpContext *context = (MSDtlsSrtpContext *)ctx; - RtpSession *session = context->stream_sessions->rtp_session; + RtpSession *session = context->mStreamSessions->rtp_session; RtpTransport *rtpt = NULL; mblk_t *msg; int ret; - ms_message("DTLS Send RTP packet len %d sessions: %p rtp session %p", (int)length, context->stream_sessions, - context->stream_sessions->rtp_session); + ms_message("DTLS Send RTP packet len %d sessions: %p rtp session %p", (int)length, context->mStreamSessions, + context->mStreamSessions->rtp_session); /* get RTP transport from session */ rtp_session_get_transports(session, &rtpt, NULL); @@ -513,28 +298,30 @@ int ms_dtls_srtp_rtp_sendData(void *ctx, const unsigned char *data, size_t lengt /* sending failed - allow to retry at the next schedule tick */ if (ret < 0) { ms_warning("DTLS Send RTP packet len %d sessions: %p rtp session %p failed returns %d", (int)length, - context->stream_sessions, context->stream_sessions->rtp_session, ret); + context->mStreamSessions, context->mStreamSessions->rtp_session, ret); context->retry_sending = true; return BCTBX_ERROR_NET_WANT_WRITE; } return ret; } -int ms_dtls_srtp_rtp_DTLSread(void *ctx, unsigned char *buf, BCTBX_UNUSED(size_t len)) { +int ms_dtls_srtp_rtp_DTLSread(void *ctx, unsigned char *buf, size_t len) { MSDtlsSrtpContext *context = (MSDtlsSrtpContext *)ctx; /* do we have something in the incoming buffer */ - if (context->rtp_incoming_buffer == NULL) { + if (context->mRtpIncomingBuffer.empty()) { return BCTBX_ERROR_NET_WANT_READ; - } else { /* read the first packet in the buffer and delete it */ - DtlsRawPacket *next_packet = (DtlsRawPacket *)(context->rtp_incoming_buffer->next); - size_t dataLength = context->rtp_incoming_buffer->length; - memcpy(buf, context->rtp_incoming_buffer->data, dataLength); - ms_free(context->rtp_incoming_buffer->data); - ms_free(context->rtp_incoming_buffer); - context->rtp_incoming_buffer = next_packet; - - return (int)dataLength; + } else { /* read the first packet in the buffer */ + auto packet = context->mRtpIncomingBuffer.front(); + auto packetSize = packet.size(); + if (packet.size() > len) { + ms_error("DTLS wants to read incoming packet of size %d but provides a buffer size %d to read it", + (int)(packetSize), (int)(len)); + return BCTBX_ERROR_NET_WANT_READ; + } + memcpy(buf, packet.data(), packetSize); + context->mRtpIncomingBuffer.pop(); // remove the packet from incoming buffer + return (int)packetSize; } } @@ -547,7 +334,7 @@ int ms_dtls_srtp_rtp_process_on_receive(struct _RtpTransportModifier *t, mblk_t size_t msgLength = msgdsize(msg); /* check if we have an on-going handshake */ - if (ctx->rtp_channel_status == DTLS_STATUS_CONTEXT_NOT_READY) { + if (ctx->mChannelStatus == DtlsStatus::ContextNotReady) { return (int)msgLength; } @@ -563,27 +350,27 @@ int ms_dtls_srtp_rtp_process_on_receive(struct _RtpTransportModifier *t, mblk_t std::lock_guard<std::mutex> lock(ctx->mtx); /* process it */ - int ret = ms_dtls_srtp_process_dtls_packet(msg, ctx); + int ret = ctx->processDtlsPacket(msg); if ((ret == 0) && - (ctx->rtp_channel_status == - DTLS_STATUS_HANDSHAKE_ONGOING)) { /* handshake is over, give the keys to srtp : 128 bits client write - 128 + (ctx->mChannelStatus == + DtlsStatus::HandshakeOngoing)) { /* handshake is over, give the keys to srtp : 128 bits client write - 128 bits server write - 112 bits client salt - 112 server salt */ - ctx->rtp_channel_status = DTLS_STATUS_HANDSHAKE_OVER; + ctx->mChannelStatus = DtlsStatus::HandshakeOver; /* check the srtp profile get selected during handshake */ - ctx->rtp_agreed_srtp_protection_profile = ms_dtls_srtp_bctbx_protection_profile_to_ms_crypto_suite( - bctbx_ssl_get_dtls_srtp_protection_profile(ctx->rtp_dtls_context->ssl)); - if (ctx->rtp_agreed_srtp_protection_profile == MS_CRYPTO_SUITE_INVALID) { + ctx->mSrtpProtectionProfile = ms_dtls_srtp_bctbx_protection_profile_to_ms_crypto_suite( + bctbx_ssl_get_dtls_srtp_protection_profile(ctx->mDtlsCryptoContext.ssl)); + if (ctx->mSrtpProtectionProfile == MS_CRYPTO_SUITE_INVALID) { ms_message("DTLS RTP handshake successful but unable to agree on srtp_profile to use"); return 0; } else { /* Get key material generated by DTLS handshake */ - size_t dtls_srtp_key_material_length = DTLS_SRTP_KEY_MATERIAL_LEN; + size_t dtls_srtp_key_material_length = ctx->mSrtpKeyMaterial.size(); ms_message("DTLS Handshake on RTP channel successful, srtp protection profile %d", - ctx->rtp_agreed_srtp_protection_profile); + ctx->mSrtpProtectionProfile); ctx->rtp_time_reference = 0; /* unarm the timer */ - ret = bctbx_ssl_get_dtls_srtp_key_material(ctx->rtp_dtls_context->ssl, ctx->rtp_channel_key_material, + ret = bctbx_ssl_get_dtls_srtp_key_material(ctx->mDtlsCryptoContext.ssl, ctx->mSrtpKeyMaterial.data(), &dtls_srtp_key_material_length); if (ret < 0) { ms_error("DTLS RTP Handshake : Unable to retrieve DTLS SRTP key material [-0x%x]", -ret); @@ -591,23 +378,23 @@ int ms_dtls_srtp_rtp_process_on_receive(struct _RtpTransportModifier *t, mblk_t } /* Check certificate fingerprint */ - if (ctx->peer_fingerprint[0] == '\0') { /* fingerprint not set yet - peer's 200Ok didn't arrived yet */ - ms_warning("DTLS-SRTP: RTP empty peer_fingerprint - waiting for it"); + if (ctx->mPeerFingerprint.empty()) { /* fingerprint not set yet - peer's 200Ok didn't arrived yet */ + ms_warning("DTLS-SRTP: RTP empty peer fingerprint - waiting for it"); return 0; } - if (ms_dtls_srtp_check_certificate_fingerprint(bctbx_ssl_get_peer_certificate(ctx->rtp_dtls_context->ssl), - (const char *)(ctx->peer_fingerprint)) == 1) { - ms_dtls_srtp_set_srtp_key_material(ctx); - ctx->rtp_channel_status = DTLS_STATUS_FINGERPRINT_VERIFIED; - ms_dtls_srtp_check_channels_status(ctx); + if (ms_dtls_srtp_check_certificate_fingerprint(bctbx_ssl_get_peer_certificate(ctx->mDtlsCryptoContext.ssl), + ctx->mPeerFingerprint) == 1) { + ctx->setKeyMaterial(); + ctx->mChannelStatus = DtlsStatus::FingerprintVerified; + ctx->checkChannelStatus(); } } - if (ctx->role != MSDtlsSrtpRoleIsServer) { /* close the connection only if we are client, if we are server, + if (ctx->mRole != MSDtlsSrtpRoleIsServer) { /* close the connection only if we are client, if we are server, the client may ask again for last packets */ /*FireFox version 43 requires DTLS channel to be kept openned, probably a bug in FireFox ret = - * ssl_close_notify( &(ctx->rtp_dtls_context->ssl) );*/ + * ssl_close_notify( &(ctx->mDtlsCryptoContext.ssl) );*/ } } return 0; @@ -626,7 +413,7 @@ void ms_dtls_srtp_transport_modifier_destroy(RtpTransportModifier *tp) { int ms_dtls_srtp_transport_modifier_new(MSDtlsSrtpContext *ctx, RtpTransportModifier **rtpt) { if (rtpt) { *rtpt = ms_new0(RtpTransportModifier, 1); - (*rtpt)->data = ctx; /* back link to get access to the other fields of the OrtoZrtpContext from the + (*rtpt)->data = ctx; /* back link to get access to the other fields of the MSDtlsSrtpContext from the RtpTransportModifier structure */ (*rtpt)->t_process_on_send = ms_dtls_srtp_rtp_process_on_send; (*rtpt)->t_process_on_receive = ms_dtls_srtp_rtp_process_on_receive; @@ -636,138 +423,353 @@ int ms_dtls_srtp_transport_modifier_new(MSDtlsSrtpContext *ctx, RtpTransportModi return 0; } -void ms_dtls_srtp_set_transport(MSDtlsSrtpContext *userData, RtpSession *s) { +void ms_dtls_srtp_set_transport(MSDtlsSrtpContext *ctx, RtpSession *s) { RtpTransport *rtpt = NULL; RtpTransportModifier *rtp_modifier; rtp_session_get_transports(s, &rtpt, NULL); - - ms_dtls_srtp_transport_modifier_new(userData, &rtp_modifier); - + ms_dtls_srtp_transport_modifier_new(ctx, &rtp_modifier); meta_rtp_transport_append_modifier(rtpt, rtp_modifier); - /* save transport modifier into context, needed to inject packets generated by DTLS */ - userData->rtp_modifier = rtp_modifier; + ctx->rtp_modifier = rtp_modifier; } +} // anonymous namespace -int ms_dtls_srtp_initialise_bctbx_dtls_context(DtlsBcToolBoxContext *dtlsContext, MSDtlsSrtpParams *params) { +/***********************************************/ +/***** MSDtlsSrtpContext methods *****/ +/***********************************************/ +int MSDtlsSrtpContext::initialiseDtlsCryptoContext(MSDtlsSrtpParams *params) { int ret; bctbx_dtls_srtp_profile_t dtls_srtp_protection_profiles[2] = {BCTBX_SRTP_AES128_CM_HMAC_SHA1_80, BCTBX_SRTP_AES128_CM_HMAC_SHA1_32}; /* initialise certificate */ - ret = bctbx_x509_certificate_parse(dtlsContext->crt, (const char *)params->pem_certificate, + ret = bctbx_x509_certificate_parse(mDtlsCryptoContext.crt, (const char *)params->pem_certificate, strlen(params->pem_certificate) + 1); if (ret < 0) { return ret; } - ret = bctbx_signing_key_parse(dtlsContext->pkey, (const char *)params->pem_pkey, strlen(params->pem_pkey) + 1, NULL, - 0); + ret = bctbx_signing_key_parse(mDtlsCryptoContext.pkey, (const char *)params->pem_pkey, strlen(params->pem_pkey) + 1, + NULL, 0); if (ret != 0) { return ret; } /* configure ssl */ if (params->role == MSDtlsSrtpRoleIsClient) { - bctbx_ssl_config_defaults(dtlsContext->ssl_config, BCTBX_SSL_IS_CLIENT, BCTBX_SSL_TRANSPORT_DATAGRAM); + bctbx_ssl_config_defaults(mDtlsCryptoContext.ssl_config, BCTBX_SSL_IS_CLIENT, BCTBX_SSL_TRANSPORT_DATAGRAM); } else { /* configure it by default as server, nothing is actually performed until we start the channel but this helps to get correct defaults settings */ - bctbx_ssl_config_defaults(dtlsContext->ssl_config, BCTBX_SSL_IS_SERVER, BCTBX_SSL_TRANSPORT_DATAGRAM); + bctbx_ssl_config_defaults(mDtlsCryptoContext.ssl_config, BCTBX_SSL_IS_SERVER, BCTBX_SSL_TRANSPORT_DATAGRAM); } bctbx_ssl_config_set_dtls_srtp_protection_profiles( - dtlsContext->ssl_config, dtls_srtp_protection_profiles, + mDtlsCryptoContext.ssl_config, dtls_srtp_protection_profiles, 2); /* TODO: get param from caller to select available profiles */ - bctbx_ssl_config_set_rng(dtlsContext->ssl_config, (int (*)(void *, unsigned char *, size_t))bctbx_rng_get, - dtlsContext->rng); + bctbx_ssl_config_set_rng(mDtlsCryptoContext.ssl_config, (int (*)(void *, unsigned char *, size_t))bctbx_rng_get, + mDtlsCryptoContext.rng); /* set certificates */ /* this will force server to send his certificate to client as we need it to compute the fingerprint even if we * won't verify it */ - bctbx_ssl_config_set_authmode(dtlsContext->ssl_config, BCTBX_SSL_VERIFY_OPTIONAL); - bctbx_ssl_config_set_own_cert(dtlsContext->ssl_config, dtlsContext->crt, dtlsContext->pkey); + bctbx_ssl_config_set_authmode(mDtlsCryptoContext.ssl_config, BCTBX_SSL_VERIFY_OPTIONAL); + bctbx_ssl_config_set_own_cert(mDtlsCryptoContext.ssl_config, mDtlsCryptoContext.crt, mDtlsCryptoContext.pkey); /* This is useless as peer would certainly be a self signed certificate and we won't verify it but avoid runtime * warnings */ - bctbx_ssl_config_set_ca_chain(dtlsContext->ssl_config, dtlsContext->crt); + bctbx_ssl_config_set_ca_chain(mDtlsCryptoContext.ssl_config, mDtlsCryptoContext.crt); /* we are not ready yet to actually start the ssl context, this will be done by calling bctbx_ssl_context_setup when * stream starts */ return 0; } -void ms_dtls_srtp_create_ssl_context(MSDtlsSrtpContext *context) { - ms_message("DTLS create a new ssl context on stream session %p", context->stream_sessions); - /* create the ssl context for this connection */ - if (context->rtp_dtls_context->ssl != NULL) { - bctbx_ssl_context_free(context->rtp_dtls_context->ssl); - } - context->rtp_dtls_context->ssl = bctbx_ssl_context_new(); -} -void ms_dtls_srtp_set_role_nolock(MSDtlsSrtpContext *context, MSDtlsSrtpRole role) { +void MSDtlsSrtpContext::setRole(MSDtlsSrtpRole role) { /* if role has changed and handshake already setup and going, reset the session */ - if (context->role != role) { - if ((context->rtp_channel_status == DTLS_STATUS_HANDSHAKE_ONGOING) || - (context->rtp_channel_status == DTLS_STATUS_HANDSHAKE_OVER)) { - bctbx_ssl_session_reset(context->rtp_dtls_context->ssl); + if (mRole != role) { + if ((mChannelStatus == DtlsStatus::HandshakeOngoing) || (mChannelStatus == DtlsStatus::HandshakeOver)) { + bctbx_ssl_session_reset(mDtlsCryptoContext.ssl); } } else { return; } - /* if role is isServer and was Unset, we must complete the server setup */ - if (((context->role == MSDtlsSrtpRoleIsClient) || (context->role == MSDtlsSrtpRoleUnset)) && - (role == MSDtlsSrtpRoleIsServer)) { - bctbx_ssl_config_set_endpoint(context->rtp_dtls_context->ssl_config, BCTBX_SSL_IS_SERVER); + /* if role is server and was Unset or Client, we must complete the server setup */ + if (((mRole == MSDtlsSrtpRoleIsClient) || (mRole == MSDtlsSrtpRoleUnset)) && (role == MSDtlsSrtpRoleIsServer)) { + bctbx_ssl_config_set_endpoint(mDtlsCryptoContext.ssl_config, BCTBX_SSL_IS_SERVER); } ms_message("DTLS set role from [%s] to [%s] for context [%p]", - context->role == MSDtlsSrtpRoleIsServer - ? "server" - : (context->role == MSDtlsSrtpRoleIsClient ? "client" : "unset role"), + mRole == MSDtlsSrtpRoleIsServer ? "server" : (mRole == MSDtlsSrtpRoleIsClient ? "client" : "unset role"), role == MSDtlsSrtpRoleIsServer ? "server" : (role == MSDtlsSrtpRoleIsClient ? "client" : "unset role"), - context); - context->role = role; + this); + mRole = role; } -void ms_dtls_srtp_start_nolock(MSDtlsSrtpContext *context) { +void MSDtlsSrtpContext::createSslContext() { + ms_message("DTLS create a new ssl context on stream session %p", mStreamSessions); + /* create the ssl context for this connection */ + if (mDtlsCryptoContext.ssl != NULL) { + bctbx_ssl_context_free(mDtlsCryptoContext.ssl); + } + mDtlsCryptoContext.ssl = bctbx_ssl_context_new(); +} - ms_message( - "DTLS start stream on stream sessions [%p], RTCP mux is %s, MTU is %d, role is %s", context->stream_sessions, - rtp_session_rtcp_mux_enabled(context->stream_sessions->rtp_session) ? "enabled" : "disabled", context->mtu, - context->role == MSDtlsSrtpRoleIsServer ? "server" - : (context->role == MSDtlsSrtpRoleIsClient ? "client" : "unset role")); +void MSDtlsSrtpContext::start() { + ms_message("DTLS start stream on stream sessions [%p], RTCP mux is %s, MTU is %d, role is %s", mStreamSessions, + rtp_session_rtcp_mux_enabled(mStreamSessions->rtp_session) ? "enabled" : "disabled", mtu, + mRole == MSDtlsSrtpRoleIsServer ? "server" + : (mRole == MSDtlsSrtpRoleIsClient ? "client" : "unset role")); /* if we are client, start the handshake(send a clientHello) */ - if (context->role == MSDtlsSrtpRoleIsClient) { - ms_dtls_srtp_create_ssl_context(context); - bctbx_ssl_config_set_endpoint(context->rtp_dtls_context->ssl_config, BCTBX_SSL_IS_CLIENT); + if (mRole == MSDtlsSrtpRoleIsClient) { + createSslContext(); + bctbx_ssl_config_set_endpoint(mDtlsCryptoContext.ssl_config, BCTBX_SSL_IS_CLIENT); /* complete ssl setup*/ - bctbx_ssl_context_setup(context->rtp_dtls_context->ssl, context->rtp_dtls_context->ssl_config); - bctbx_ssl_set_io_callbacks(context->rtp_dtls_context->ssl, context, ms_dtls_srtp_rtp_sendData, - ms_dtls_srtp_rtp_DTLSread); - bctbx_ssl_set_mtu(context->rtp_dtls_context->ssl, context->mtu); + bctbx_ssl_context_setup(mDtlsCryptoContext.ssl, mDtlsCryptoContext.ssl_config); + bctbx_ssl_set_io_callbacks(mDtlsCryptoContext.ssl, this, ms_dtls_srtp_rtp_sendData, ms_dtls_srtp_rtp_DTLSread); + bctbx_ssl_set_mtu(mDtlsCryptoContext.ssl, mtu); /* and start the handshake */ - bctbx_ssl_handshake(context->rtp_dtls_context->ssl); - context->rtp_time_reference = bctbx_get_cur_time_ms(); /* arm the timer for retransmission */ - context->rtp_channel_status = DTLS_STATUS_HANDSHAKE_ONGOING; + bctbx_ssl_handshake(mDtlsCryptoContext.ssl); + rtp_time_reference = bctbx_get_cur_time_ms(); /* arm the timer for retransmission */ + mChannelStatus = DtlsStatus::HandshakeOngoing; } /* if we are server and we didn't started yet the DTLS engine, do it now */ - if (context->role == MSDtlsSrtpRoleIsServer) { - if (context->rtp_channel_status == DTLS_STATUS_CONTEXT_READY) { - ms_dtls_srtp_create_ssl_context(context); - bctbx_ssl_config_set_endpoint(context->rtp_dtls_context->ssl_config, BCTBX_SSL_IS_SERVER); + if (mRole == MSDtlsSrtpRoleIsServer) { + if (mChannelStatus == DtlsStatus::ContextReady) { + createSslContext(); + bctbx_ssl_config_set_endpoint(mDtlsCryptoContext.ssl_config, BCTBX_SSL_IS_SERVER); /* complete ssl setup*/ - bctbx_ssl_context_setup(context->rtp_dtls_context->ssl, context->rtp_dtls_context->ssl_config); - bctbx_ssl_set_io_callbacks(context->rtp_dtls_context->ssl, context, ms_dtls_srtp_rtp_sendData, + bctbx_ssl_context_setup(mDtlsCryptoContext.ssl, mDtlsCryptoContext.ssl_config); + bctbx_ssl_set_io_callbacks(mDtlsCryptoContext.ssl, this, ms_dtls_srtp_rtp_sendData, ms_dtls_srtp_rtp_DTLSread); - bctbx_ssl_set_mtu(context->rtp_dtls_context->ssl, context->mtu); - context->rtp_channel_status = DTLS_STATUS_HANDSHAKE_ONGOING; + bctbx_ssl_set_mtu(mDtlsCryptoContext.ssl, mtu); + mChannelStatus = DtlsStatus::HandshakeOngoing; + } + } +} + +void MSDtlsSrtpContext::checkChannelStatus() { + /* Check if we're are ready: rtp_channel done and rtcp mux on */ + if ((mChannelStatus == DtlsStatus::FingerprintVerified) && + (rtp_session_rtcp_mux_enabled(mStreamSessions->rtp_session))) { + OrtpEventData *eventData; + OrtpEvent *ev; + ev = ortp_event_new(ORTP_EVENT_DTLS_ENCRYPTION_CHANGED); + eventData = ortp_event_get_data(ev); + eventData->info.dtls_stream_encrypted = 1; + rtp_session_dispatch_event(mStreamSessions->rtp_session, ev); + ms_message("DTLS Event dispatched to all: secrets are on for this stream"); + } +} + +/* Get keys from context and set then in the SRTP context according to the given stream type + */ +void MSDtlsSrtpContext::setKeyMaterial() { + + uint8_t key[256]; + uint8_t *key_material = mSrtpKeyMaterial.data(); + + if (mRole == MSDtlsSrtpRoleIsServer) { + /* reception(client write) key and salt +16bits padding */ + memcpy(key, key_material, DtlsSrtpKeyLen); + memcpy(key + DtlsSrtpKeyLen, key_material + 2 * DtlsSrtpKeyLen, DtlsSrtpSaltLen); + ms_media_stream_sessions_set_srtp_recv_key(mStreamSessions, mSrtpProtectionProfile, key, + DtlsSrtpKeyLen + DtlsSrtpSaltLen, MSSrtpKeySourceDTLS); + /* emission(server write) key and salt +16bits padding */ + memcpy(key, key_material + DtlsSrtpKeyLen, DtlsSrtpKeyLen); + memcpy(key + DtlsSrtpKeyLen, key_material + 2 * DtlsSrtpKeyLen + DtlsSrtpSaltLen, DtlsSrtpSaltLen); + ms_media_stream_sessions_set_srtp_send_key(mStreamSessions, mSrtpProtectionProfile, key, + DtlsSrtpKeyLen + DtlsSrtpSaltLen, MSSrtpKeySourceDTLS); + } else if (mRole == MSDtlsSrtpRoleIsClient) { /* this enpoint act as DTLS client */ + /* emission(client write) key and salt +16bits padding */ + memcpy(key, key_material, DtlsSrtpKeyLen); + memcpy(key + DtlsSrtpKeyLen, key_material + 2 * DtlsSrtpKeyLen, DtlsSrtpSaltLen); + ms_media_stream_sessions_set_srtp_send_key(mStreamSessions, mSrtpProtectionProfile, key, + DtlsSrtpKeyLen + DtlsSrtpSaltLen, MSSrtpKeySourceDTLS); + /* reception(server write) key and salt +16bits padding */ + memcpy(key, key_material + DtlsSrtpKeyLen, DtlsSrtpKeyLen); + memcpy(key + DtlsSrtpKeyLen, key_material + 2 * DtlsSrtpKeyLen + DtlsSrtpSaltLen, DtlsSrtpSaltLen); + ms_media_stream_sessions_set_srtp_recv_key(mStreamSessions, mSrtpProtectionProfile, key, + DtlsSrtpKeyLen + DtlsSrtpSaltLen, MSSrtpKeySourceDTLS); + } +} + +/** + * Check if the incoming message is a DTLS packet. + * If it is, store it in the context incoming buffer and call the bctoolbox function wich will process it. + * This function also manages the client retransmission timer + * + * @param[in] msg the incoming message + * @return the value returned by the bctoolbox function processing the packet(ssl_handshake) + */ +int MSDtlsSrtpContext::processDtlsPacket(mblk_t *msg) { + size_t msgLength = msgdsize(msg); + bctbx_ssl_context_t *ssl = mDtlsCryptoContext.ssl; + int ret = 0; + + /* UGLY PATCH CLIENT HELLO PACKET PARSING */ + const int Content_Type_Index = 0; + const int Content_Length_Index = 11; + const int Handshake_Type_Index = 13; + const int Handshake_Message_Length_Index = 14; + const int Handshake_Message_Seq_Index = 17; + const int Handshake_Frag_Offset_Index = 19; + const int Handshake_Frag_Length_Index = 22; + const size_t Handshake_Header_Length = 25; + unsigned char *frag = msg->b_rptr; + size_t base_index = 0; + int message_length = 0; + int message_seq = 0; + int current_message_seq = -1; + int frag_offset = 0; + int frag_length = 0; + unsigned char *reassembled_packet = NULL; + /* end of UGLY PATCH CLIENT HELLO PACKET PARSING */ + + /*required by webrtc in server case when ice is not completed yet*/ + /* no more required because change is performed by ice.c once a check list is ready + * rtp_session_update_remote_sock_addr(rtp_session, msg,is_rtp,FALSE);*/ + + ms_message("DTLS Receive RTP packet len %d sessions: %p rtp session %p", (int)msgLength, mStreamSessions, + mStreamSessions->rtp_session); + + /* UGLY PATCH CLIENT HELLO PACKET PARSING */ + /* Parse the DTLS packet to check if we have a Client Hello packet fragmented at DTLS level but all set in one + * datagram (some kind of bug in certain versions openssl produce that and mbedtls does not support Client Hello + * fragmentation ) This patch is not very resistant to any change and target that very particular situation, a + * better solution should be to implement support of Client Hello Fragmentation in mbedtls */ + if (msgLength > Handshake_Header_Length && frag[Content_Type_Index] == 0x16 && + frag[Handshake_Type_Index] == + 0x01) { // If the first fragment(there may be only one) is of a DTLS Handshake Client Hello message + while (base_index + Handshake_Header_Length < + msgLength) { // loop on the message, parsing all fragments it may contain (loop until we have at + // least enough unparsed byte to read a handshake header) + if (frag[Content_Type_Index] == 0x16) { // Type index 0x16 is DLTS Handshake message + if (frag[Handshake_Type_Index] == 0x01) { // Handshake type 0x01 is Client Hello + // Get message length + message_length = frag[Handshake_Message_Length_Index] << 16 | + frag[Handshake_Message_Length_Index + 1] << 8 | + frag[Handshake_Message_Length_Index + 2]; + + // message sequence number + message_seq = frag[Handshake_Message_Seq_Index] << 8 | frag[Handshake_Message_Seq_Index + 1]; + if (current_message_seq == -1) { + current_message_seq = message_seq; + } + + // fragment offset + frag_offset = frag[Handshake_Frag_Offset_Index] << 16 | frag[Handshake_Frag_Offset_Index + 1] << 8 | + frag[Handshake_Frag_Offset_Index + 2]; + + // and fragment length + frag_length = frag[Handshake_Frag_Length_Index] << 16 | frag[Handshake_Frag_Length_Index + 1] << 8 | + frag[Handshake_Frag_Length_Index + 2]; + + // check the message is not malformed and would lead us to read after the message buffer + // or write after our reassembled packet buffer + if (base_index + Handshake_Header_Length + frag_length <= + msgLength // we will read frag_length starting at base_index (frag in the code)+ header + && frag_length + frag_offset <= + message_length) { // we will write in the reassembled buffer frag_length byte, + // starting at frag_offset, the buffer is message_length long + + // If message length and fragment length differs, we have a fragmented Client Hello + // Check they are part of the same message (message_seq) + // We will just collect all fragments (in our very particuliar case, they are all in the + // same datagram so we do not need long term storage, juste parsing this packet) + if (message_length != frag_length && message_seq == current_message_seq) { + if (reassembled_packet == NULL) { // this is first fragment we get + reassembled_packet = + (unsigned char *)ms_malloc(Handshake_Header_Length + message_length); + // copy the header + memcpy(reassembled_packet, msg->b_rptr, Handshake_Header_Length); + // set the message length to be in line with reassembled fragments + reassembled_packet[Content_Length_Index] = ((message_length + 12) >> 8) & 0xFF; + reassembled_packet[Content_Length_Index + 1] = (message_length + 12) & 0xFF; + + // set the frag length to be the same than message length + reassembled_packet[Handshake_Frag_Length_Index] = + reassembled_packet[Handshake_Message_Length_Index]; + reassembled_packet[Handshake_Frag_Length_Index + 1] = + reassembled_packet[Handshake_Message_Length_Index + 1]; + reassembled_packet[Handshake_Frag_Length_Index + 2] = + reassembled_packet[Handshake_Message_Length_Index + 2]; + } + // copy the received fragment + memcpy(reassembled_packet + Handshake_Header_Length + frag_offset, + frag + Handshake_Header_Length, frag_length); + } + + // read what is next in the datagram + base_index += Handshake_Header_Length + frag_length; // bytes parsed so far + frag += Handshake_Header_Length + frag_length; // point to the begining of the next fragment + } else { // message is malformed in a nasty way + ms_warning("DTLS Received RTP packet len %d sessions: %p rtp session %p is malformed in an " + "agressive way", + (int)msgLength, mStreamSessions, mStreamSessions->rtp_session); + base_index = msgLength; // get out of the while + ms_free(reassembled_packet); + reassembled_packet = NULL; + } + } else { + base_index = msgLength; // get out of the while + ms_free(reassembled_packet); + reassembled_packet = NULL; + } + } + } + } + /* end of UGLY PATCH CLIENT HELLO PACKET PARSING */ + // if we made a reassembled client hello packet, use this one as incoming dlts packet + if (reassembled_packet != NULL) { + ms_message("DTLS re-assembled a fragmented Client Hello packet"); + mRtpIncomingBuffer.emplace(reassembled_packet, reassembled_packet + Handshake_Header_Length + message_length); + ms_free(reassembled_packet); + } else { // otherwise enqueue the one we received + mRtpIncomingBuffer.emplace(msg->b_rptr, msg->b_rptr + msgLength); + } + + /* while DTLS handshake is on going route DTLS packets to bctoolbox engine through ssl_handshake() */ + if (mChannelStatus < DtlsStatus::HandshakeOver) { + /* role is unset but we receive a packet: we are caller and shall initialise as server and then process the + * incoming packet */ + if (mRole == MSDtlsSrtpRoleUnset) { + setRole(MSDtlsSrtpRoleIsServer); /* this call will update role and complete server setup */ + start(); /* complete the ssl setup and change channel_status to handshake ongoing */ + ssl = mDtlsCryptoContext.ssl; + } + /* process the packet and store result */ + ret = bctbx_ssl_handshake(ssl); + ms_message("DTLS Handshake process RTP packet len %d sessions: %p rtp session %p return %s0x%0x", + (int)msgLength, mStreamSessions, mStreamSessions->rtp_session, ret > 0 ? "+" : "-", + ret > 0 ? ret : -ret); + + /* if we are client, manage the retransmission timer */ + if (mRole == MSDtlsSrtpRoleIsClient) { + rtp_time_reference = bctbx_get_cur_time_ms(); } + } else { /* when DTLS handshake is over, route DTLS packets to bctoolbox engine through ssl_read() */ + /* we need a buffer to store the message read even if we don't use it */ + unsigned char *buf = (unsigned char *)ms_malloc(msgLength + 1); + ret = bctbx_ssl_read(ssl, buf, msgLength); + ms_message("DTLS Handshake read RTP packet len %d sessions: %p rtp session %p return %s0x%0x", (int)msgLength, + mStreamSessions, mStreamSessions->rtp_session, ret > 0 ? "+" : "-", ret > 0 ? ret : -ret); + ms_free(buf); } + + /* report the error in logs only when different than requested read(waiting for data) */ + if (ret < 0 && ret != BCTBX_ERROR_NET_WANT_READ) { + char err_str[512]; + err_str[0] = '\0'; + bctbx_strerror(ret, err_str, 512); + ms_warning("DTLS Handshake returns -0x%x : %s [on sessions: %p rtp session %p]", -ret, err_str, mStreamSessions, + mStreamSessions->rtp_session); + } + return ret; } -} // namespace + /***********************************************/ /***** EXPORTED FUNCTIONS *****/ /***********************************************/ @@ -776,7 +778,7 @@ void ms_dtls_srtp_start_nolock(MSDtlsSrtpContext *context) { extern "C" void ms_dtls_srtp_set_stream_sessions(MSDtlsSrtpContext *dtls_context, MSMediaStreamSessions *stream_sessions) { if (dtls_context != NULL) { - dtls_context->stream_sessions = stream_sessions; + dtls_context->mStreamSessions = stream_sessions; } } @@ -790,25 +792,23 @@ extern "C" void ms_dtls_srtp_set_peer_fingerprint(MSDtlsSrtpContext *ctx, const if (ctx) { std::lock_guard<std::mutex> lock(ctx->mtx); - size_t peer_fingerprint_length = strlen(peer_fingerprint) + 1; // include the null termination - if (peer_fingerprint_length > sizeof(ctx->peer_fingerprint)) { - memcpy(ctx->peer_fingerprint, peer_fingerprint, sizeof(ctx->peer_fingerprint)); + if (strlen(peer_fingerprint) > maxFingerPrintSize) { ms_error("DTLS-SRTP received from SDP INVITE a peer fingerprint %d bytes length wich is longer than " - "maximum storage %d bytes", - (int)peer_fingerprint_length, (int)sizeof(ctx->peer_fingerprint)); + "maximum allowed size: %d bytes, ignore it", + (int)(strlen(peer_fingerprint)), (int)maxFingerPrintSize); } else { - memcpy(ctx->peer_fingerprint, peer_fingerprint, peer_fingerprint_length); + ctx->mPeerFingerprint = peer_fingerprint; } - ms_message("DTLS-SRTP peer fingerprint is %s", ctx->peer_fingerprint); + ms_message("DTLS-SRTP peer fingerprint is %s", ctx->mPeerFingerprint.c_str()); /* Check if any of the context has finished its handshake and was waiting for the fingerprint */ - if (ctx->rtp_channel_status == DTLS_STATUS_HANDSHAKE_OVER) { + if (ctx->mChannelStatus == DtlsStatus::HandshakeOver) { ms_message("DTLS SRTP : late fingerprint arrival, check it after RTP Handshake is over"); - if (ms_dtls_srtp_check_certificate_fingerprint(bctbx_ssl_get_peer_certificate(ctx->rtp_dtls_context->ssl), - (const char *)(ctx->peer_fingerprint)) == 1) { - ms_dtls_srtp_set_srtp_key_material(ctx); - ctx->rtp_channel_status = DTLS_STATUS_FINGERPRINT_VERIFIED; - ms_dtls_srtp_check_channels_status(ctx); + if (ms_dtls_srtp_check_certificate_fingerprint(bctbx_ssl_get_peer_certificate(ctx->mDtlsCryptoContext.ssl), + ctx->mPeerFingerprint) == 1) { + ctx->setKeyMaterial(); + ctx->mChannelStatus = DtlsStatus::FingerprintVerified; + ctx->checkChannelStatus(); } } } @@ -820,66 +820,50 @@ extern "C" void ms_dtls_srtp_reset_context(MSDtlsSrtpContext *context) { ms_message("Reseting DTLS context [%p] and SSL connections", context); - if ((context->rtp_channel_status == DTLS_STATUS_HANDSHAKE_ONGOING) || - (context->rtp_channel_status == DTLS_STATUS_HANDSHAKE_OVER)) { - bctbx_ssl_session_reset(context->rtp_dtls_context->ssl); + if ((context->mChannelStatus == DtlsStatus::HandshakeOngoing) || + (context->mChannelStatus == DtlsStatus::HandshakeOver)) { + bctbx_ssl_session_reset(context->mDtlsCryptoContext.ssl); } - context->rtp_channel_status = DTLS_STATUS_CONTEXT_READY; - context->rtp_agreed_srtp_protection_profile = MS_CRYPTO_SUITE_INVALID; + context->mChannelStatus = DtlsStatus::ContextReady; + context->mSrtpProtectionProfile = MS_CRYPTO_SUITE_INVALID; - context->role = MSDtlsSrtpRoleUnset; + context->mRole = MSDtlsSrtpRoleUnset; } } extern "C" MSDtlsSrtpRole ms_dtls_srtp_get_role(const MSDtlsSrtpContext *context) { - return context->role; + return context->mRole; } extern "C" void ms_dtls_srtp_set_role(MSDtlsSrtpContext *context, MSDtlsSrtpRole role) { if (context) { std::lock_guard<std::mutex> lock(context->mtx); - ms_dtls_srtp_set_role_nolock(context, role); + context->setRole(role); } } extern "C" MSDtlsSrtpContext *ms_dtls_srtp_context_new(MSMediaStreamSessions *sessions, MSDtlsSrtpParams *params) { - MSDtlsSrtpContext *userData; - RtpSession *s = sessions->rtp_session; - int ret; - - /* Create and init the mbedtls DTLS contexts */ - DtlsBcToolBoxContext *rtp_dtls_context = ms_dtls_srtp_bctbx_context_new(); ms_message("Creating DTLS-SRTP engine on stream sessions [%p] as %s, RTCP mux is %s", sessions, params->role == MSDtlsSrtpRoleIsServer ? "server" : (params->role == MSDtlsSrtpRoleIsClient ? "client" : "unset role"), - rtp_session_rtcp_mux_enabled(s) ? "enabled" : "disabled"); - - /* create and link user data */ - userData = ms_new0(MSDtlsSrtpContext, 1); - userData->rtp_dtls_context = rtp_dtls_context; - userData->role = params->role; - userData->mtu = params->mtu; - userData->rtp_time_reference = 0; - userData->retry_sending = false; - - userData->stream_sessions = sessions; - userData->rtp_incoming_buffer = NULL; - userData->rtp_channel_status = DTLS_STATUS_CONTEXT_NOT_READY; - userData->rtp_agreed_srtp_protection_profile = MS_CRYPTO_SUITE_INVALID; - ms_dtls_srtp_set_transport(userData, s); - - ret = ms_dtls_srtp_initialise_bctbx_dtls_context(rtp_dtls_context, params); + rtp_session_rtcp_mux_enabled(sessions->rtp_session) ? "enabled" : "disabled"); + + /* create context and link it to the rtp session */ + auto context = new MSDtlsSrtpContext(sessions, params); + ms_dtls_srtp_set_transport(context, sessions->rtp_session); + + int ret = context->initialiseDtlsCryptoContext(params); if (ret != 0) { ms_error("DTLS init error : rtp bctoolbox context init returned -0x%0x on stream session [%p]", -ret, sessions); + delete context; return NULL; } - userData->rtp_channel_status = DTLS_STATUS_CONTEXT_READY; - - return userData; + context->mChannelStatus = DtlsStatus::ContextReady; + return context; } extern "C" void ms_dtls_srtp_start(MSDtlsSrtpContext *context) { @@ -889,21 +873,10 @@ extern "C" void ms_dtls_srtp_start(MSDtlsSrtpContext *context) { return; } std::lock_guard<std::mutex> lock(context->mtx); - ms_dtls_srtp_start_nolock(context); + context->start(); } extern "C" void ms_dtls_srtp_context_destroy(MSDtlsSrtpContext *ctx) { - /* clean bctoolbox contexts */ - ms_dtls_srtp_bctbx_context_free(ctx->rtp_dtls_context); - - /* clean incoming buffers */ - while (ctx->rtp_incoming_buffer != NULL) { - DtlsRawPacket *next_packet = (DtlsRawPacket *)(ctx->rtp_incoming_buffer->next); - ms_free(ctx->rtp_incoming_buffer->data); - ms_free(ctx->rtp_incoming_buffer); - ctx->rtp_incoming_buffer = next_packet; - } - - ms_free(ctx); + delete ctx; ms_message("DTLS-SRTP context destroyed"); }