summaryrefslogtreecommitdiff
path: root/pjmedia/src/pjmedia/transport_srtp.c
diff options
context:
space:
mode:
Diffstat (limited to 'pjmedia/src/pjmedia/transport_srtp.c')
-rw-r--r--pjmedia/src/pjmedia/transport_srtp.c1680
1 files changed, 1680 insertions, 0 deletions
diff --git a/pjmedia/src/pjmedia/transport_srtp.c b/pjmedia/src/pjmedia/transport_srtp.c
new file mode 100644
index 0000000..1321e75
--- /dev/null
+++ b/pjmedia/src/pjmedia/transport_srtp.c
@@ -0,0 +1,1680 @@
+/* $Id: transport_srtp.c 3999 2012-03-30 07:10:13Z bennylp $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <pjmedia/transport_srtp.h>
+#include <pjmedia/endpoint.h>
+#include <pjlib-util/base64.h>
+#include <pj/assert.h>
+#include <pj/ctype.h>
+#include <pj/lock.h>
+#include <pj/log.h>
+#include <pj/os.h>
+#include <pj/pool.h>
+
+#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
+
+#include <srtp.h>
+
+#define THIS_FILE "transport_srtp.c"
+
+/* Maximum size of packet */
+#define MAX_RTP_BUFFER_LEN 1500
+#define MAX_RTCP_BUFFER_LEN 1500
+#define MAX_KEY_LEN 32
+
+/* Initial value of probation counter. When probation counter > 0,
+ * it means SRTP is in probation state, and it may restart when
+ * srtp_unprotect() returns err_status_replay_*
+ */
+#define PROBATION_CNT_INIT 100
+
+#define DEACTIVATE_MEDIA(pool, m) pjmedia_sdp_media_deactivate(pool, m)
+
+static const pj_str_t ID_RTP_AVP = { "RTP/AVP", 7 };
+static const pj_str_t ID_RTP_SAVP = { "RTP/SAVP", 8 };
+static const pj_str_t ID_INACTIVE = { "inactive", 8 };
+static const pj_str_t ID_CRYPTO = { "crypto", 6 };
+
+typedef struct crypto_suite
+{
+ char *name;
+ cipher_type_id_t cipher_type;
+ unsigned cipher_key_len;
+ auth_type_id_t auth_type;
+ unsigned auth_key_len;
+ unsigned srtp_auth_tag_len;
+ unsigned srtcp_auth_tag_len;
+ sec_serv_t service;
+} crypto_suite;
+
+/* Crypto suites as defined on RFC 4568 */
+static crypto_suite crypto_suites[] = {
+ /* plain RTP/RTCP (no cipher & no auth) */
+ {"NULL", NULL_CIPHER, 0, NULL_AUTH, 0, 0, 0, sec_serv_none},
+
+ /* cipher AES_CM, auth HMAC_SHA1, auth tag len = 10 octets */
+ {"AES_CM_128_HMAC_SHA1_80", AES_128_ICM, 30, HMAC_SHA1, 20, 10, 10,
+ sec_serv_conf_and_auth},
+
+ /* cipher AES_CM, auth HMAC_SHA1, auth tag len = 4 octets */
+ {"AES_CM_128_HMAC_SHA1_32", AES_128_ICM, 30, HMAC_SHA1, 20, 4, 10,
+ sec_serv_conf_and_auth},
+
+ /*
+ * F8_128_HMAC_SHA1_8 not supported by libsrtp?
+ * {"F8_128_HMAC_SHA1_8", NULL_CIPHER, 0, NULL_AUTH, 0, 0, 0, sec_serv_none}
+ */
+};
+
+typedef struct transport_srtp
+{
+ pjmedia_transport base; /**< Base transport interface. */
+ pj_pool_t *pool; /**< Pool for transport SRTP. */
+ pj_lock_t *mutex; /**< Mutex for libsrtp contexts.*/
+ char rtp_tx_buffer[MAX_RTP_BUFFER_LEN];
+ char rtcp_tx_buffer[MAX_RTCP_BUFFER_LEN];
+ pjmedia_srtp_setting setting;
+ unsigned media_option;
+
+ /* SRTP policy */
+ pj_bool_t session_inited;
+ pj_bool_t offerer_side;
+ pj_bool_t bypass_srtp;
+ char tx_key[MAX_KEY_LEN];
+ char rx_key[MAX_KEY_LEN];
+ pjmedia_srtp_crypto tx_policy;
+ pjmedia_srtp_crypto rx_policy;
+
+ /* Temporary policy for negotiation */
+ pjmedia_srtp_crypto tx_policy_neg;
+ pjmedia_srtp_crypto rx_policy_neg;
+
+ /* libSRTP contexts */
+ srtp_t srtp_tx_ctx;
+ srtp_t srtp_rx_ctx;
+
+ /* Stream information */
+ void *user_data;
+ void (*rtp_cb)( void *user_data,
+ void *pkt,
+ pj_ssize_t size);
+ void (*rtcp_cb)(void *user_data,
+ void *pkt,
+ pj_ssize_t size);
+
+ /* Transport information */
+ pjmedia_transport *member_tp; /**< Underlying transport. */
+
+ /* SRTP usage policy of peer. This field is updated when media is starting.
+ * This is useful when SRTP is in optional mode and peer is using mandatory
+ * mode, so when local is about to reinvite/update, it should offer
+ * RTP/SAVP instead of offering RTP/AVP.
+ */
+ pjmedia_srtp_use peer_use;
+
+ /* When probation counter > 0, it means SRTP is in probation state,
+ * and it may restart when srtp_unprotect() returns err_status_replay_*
+ */
+ unsigned probation_cnt;
+} transport_srtp;
+
+
+/*
+ * This callback is called by transport when incoming rtp is received
+ */
+static void srtp_rtp_cb( void *user_data, void *pkt, pj_ssize_t size);
+
+/*
+ * This callback is called by transport when incoming rtcp is received
+ */
+static void srtp_rtcp_cb( void *user_data, void *pkt, pj_ssize_t size);
+
+
+/*
+ * These are media transport operations.
+ */
+static pj_status_t transport_get_info (pjmedia_transport *tp,
+ pjmedia_transport_info *info);
+static pj_status_t transport_attach (pjmedia_transport *tp,
+ void *user_data,
+ const pj_sockaddr_t *rem_addr,
+ const pj_sockaddr_t *rem_rtcp,
+ unsigned addr_len,
+ void (*rtp_cb)(void*,
+ void*,
+ pj_ssize_t),
+ void (*rtcp_cb)(void*,
+ void*,
+ pj_ssize_t));
+static void transport_detach (pjmedia_transport *tp,
+ void *strm);
+static pj_status_t transport_send_rtp( pjmedia_transport *tp,
+ const void *pkt,
+ pj_size_t size);
+static pj_status_t transport_send_rtcp(pjmedia_transport *tp,
+ const void *pkt,
+ pj_size_t size);
+static pj_status_t transport_send_rtcp2(pjmedia_transport *tp,
+ const pj_sockaddr_t *addr,
+ unsigned addr_len,
+ const void *pkt,
+ pj_size_t size);
+static pj_status_t transport_media_create(pjmedia_transport *tp,
+ pj_pool_t *sdp_pool,
+ unsigned options,
+ const pjmedia_sdp_session *sdp_remote,
+ unsigned media_index);
+static pj_status_t transport_encode_sdp(pjmedia_transport *tp,
+ pj_pool_t *sdp_pool,
+ pjmedia_sdp_session *sdp_local,
+ const pjmedia_sdp_session *sdp_remote,
+ unsigned media_index);
+static pj_status_t transport_media_start (pjmedia_transport *tp,
+ pj_pool_t *pool,
+ const pjmedia_sdp_session *sdp_local,
+ const pjmedia_sdp_session *sdp_remote,
+ unsigned media_index);
+static pj_status_t transport_media_stop(pjmedia_transport *tp);
+static pj_status_t transport_simulate_lost(pjmedia_transport *tp,
+ pjmedia_dir dir,
+ unsigned pct_lost);
+static pj_status_t transport_destroy (pjmedia_transport *tp);
+
+
+
+static pjmedia_transport_op transport_srtp_op =
+{
+ &transport_get_info,
+ &transport_attach,
+ &transport_detach,
+ &transport_send_rtp,
+ &transport_send_rtcp,
+ &transport_send_rtcp2,
+ &transport_media_create,
+ &transport_encode_sdp,
+ &transport_media_start,
+ &transport_media_stop,
+ &transport_simulate_lost,
+ &transport_destroy
+};
+
+/* This function may also be used by other module, e.g: pjmedia/errno.c,
+ * it should have C compatible declaration.
+ */
+PJ_BEGIN_DECL
+ const char* get_libsrtp_errstr(int err);
+PJ_END_DECL
+
+const char* get_libsrtp_errstr(int err)
+{
+#if defined(PJ_HAS_ERROR_STRING) && (PJ_HAS_ERROR_STRING != 0)
+ static char *liberr[] = {
+ "ok", /* err_status_ok = 0 */
+ "unspecified failure", /* err_status_fail = 1 */
+ "unsupported parameter", /* err_status_bad_param = 2 */
+ "couldn't allocate memory", /* err_status_alloc_fail = 3 */
+ "couldn't deallocate properly", /* err_status_dealloc_fail = 4 */
+ "couldn't initialize", /* err_status_init_fail = 5 */
+ "can't process as much data as requested",
+ /* err_status_terminus = 6 */
+ "authentication failure", /* err_status_auth_fail = 7 */
+ "cipher failure", /* err_status_cipher_fail = 8 */
+ "replay check failed (bad index)", /* err_status_replay_fail = 9 */
+ "replay check failed (index too old)",
+ /* err_status_replay_old = 10 */
+ "algorithm failed test routine", /* err_status_algo_fail = 11 */
+ "unsupported operation", /* err_status_no_such_op = 12 */
+ "no appropriate context found", /* err_status_no_ctx = 13 */
+ "unable to perform desired validation",
+ /* err_status_cant_check = 14 */
+ "can't use key any more", /* err_status_key_expired = 15 */
+ "error in use of socket", /* err_status_socket_err = 16 */
+ "error in use POSIX signals", /* err_status_signal_err = 17 */
+ "nonce check failed", /* err_status_nonce_bad = 18 */
+ "couldn't read data", /* err_status_read_fail = 19 */
+ "couldn't write data", /* err_status_write_fail = 20 */
+ "error pasring data", /* err_status_parse_err = 21 */
+ "error encoding data", /* err_status_encode_err = 22 */
+ "error while using semaphores", /* err_status_semaphore_err = 23 */
+ "error while using pfkey" /* err_status_pfkey_err = 24 */
+ };
+ if (err >= 0 && err < (int)PJ_ARRAY_SIZE(liberr)) {
+ return liberr[err];
+ } else {
+ static char msg[32];
+ pj_ansi_snprintf(msg, sizeof(msg), "Unknown libsrtp error %d", err);
+ return msg;
+ }
+#else
+ static char msg[32];
+ pj_ansi_snprintf(msg, sizeof(msg), "libsrtp error %d", err);
+ return msg;
+#endif
+}
+
+static pj_bool_t libsrtp_initialized;
+static void pjmedia_srtp_deinit_lib(pjmedia_endpt *endpt);
+
+PJ_DEF(pj_status_t) pjmedia_srtp_init_lib(pjmedia_endpt *endpt)
+{
+ if (libsrtp_initialized == PJ_FALSE) {
+ err_status_t err;
+
+ err = srtp_init();
+ if (err != err_status_ok) {
+ PJ_LOG(4, (THIS_FILE, "Failed to initialize libsrtp: %s",
+ get_libsrtp_errstr(err)));
+ return PJMEDIA_ERRNO_FROM_LIBSRTP(err);
+ }
+
+ if (pjmedia_endpt_atexit(endpt, pjmedia_srtp_deinit_lib) != PJ_SUCCESS)
+ {
+ /* There will be memory leak when it fails to schedule libsrtp
+ * deinitialization, however the memory leak could be harmless,
+ * since in modern OS's memory used by an application is released
+ * when the application terminates.
+ */
+ PJ_LOG(4, (THIS_FILE, "Failed to register libsrtp deinit."));
+ }
+
+ libsrtp_initialized = PJ_TRUE;
+ }
+
+ return PJ_SUCCESS;
+}
+
+static void pjmedia_srtp_deinit_lib(pjmedia_endpt *endpt)
+{
+ err_status_t err;
+
+ /* Note that currently this SRTP init/deinit is not equipped with
+ * reference counter, it should be safe as normally there is only
+ * one single instance of media endpoint and even if it isn't, the
+ * pjmedia_transport_srtp_create() will invoke SRTP init (the only
+ * drawback should be the delay described by #788).
+ */
+
+ PJ_UNUSED_ARG(endpt);
+
+ err = srtp_deinit();
+ if (err != err_status_ok) {
+ PJ_LOG(4, (THIS_FILE, "Failed to deinitialize libsrtp: %s",
+ get_libsrtp_errstr(err)));
+ }
+
+ libsrtp_initialized = PJ_FALSE;
+}
+
+
+static int get_crypto_idx(const pj_str_t* crypto_name)
+{
+ int i;
+ int cs_cnt = sizeof(crypto_suites)/sizeof(crypto_suites[0]);
+
+ /* treat unspecified crypto_name as crypto 'NULL' */
+ if (crypto_name->slen == 0)
+ return 0;
+
+ for (i=0; i<cs_cnt; ++i) {
+ if (!pj_stricmp2(crypto_name, crypto_suites[i].name))
+ return i;
+ }
+
+ return -1;
+}
+
+
+static int srtp_crypto_cmp(const pjmedia_srtp_crypto* c1,
+ const pjmedia_srtp_crypto* c2)
+{
+ int r;
+
+ r = pj_strcmp(&c1->key, &c2->key);
+ if (r != 0)
+ return r;
+
+ r = pj_stricmp(&c1->name, &c2->name);
+ if (r != 0)
+ return r;
+
+ return (c1->flags != c2->flags);
+}
+
+
+static pj_bool_t srtp_crypto_empty(const pjmedia_srtp_crypto* c)
+{
+ return (c->name.slen==0 || c->key.slen==0);
+}
+
+
+PJ_DEF(void) pjmedia_srtp_setting_default(pjmedia_srtp_setting *opt)
+{
+ unsigned i;
+
+ pj_assert(opt);
+
+ pj_bzero(opt, sizeof(pjmedia_srtp_setting));
+ opt->close_member_tp = PJ_TRUE;
+ opt->use = PJMEDIA_SRTP_OPTIONAL;
+
+ /* Copy default crypto-suites, but skip crypto 'NULL' */
+ opt->crypto_count = sizeof(crypto_suites)/sizeof(crypto_suites[0]) - 1;
+ for (i=0; i<opt->crypto_count; ++i)
+ opt->crypto[i].name = pj_str(crypto_suites[i+1].name);
+}
+
+
+/*
+ * Create an SRTP media transport.
+ */
+PJ_DEF(pj_status_t) pjmedia_transport_srtp_create(
+ pjmedia_endpt *endpt,
+ pjmedia_transport *tp,
+ const pjmedia_srtp_setting *opt,
+ pjmedia_transport **p_tp)
+{
+ pj_pool_t *pool;
+ transport_srtp *srtp;
+ pj_status_t status;
+ unsigned i;
+
+ PJ_ASSERT_RETURN(endpt && tp && p_tp, PJ_EINVAL);
+
+ /* Check crypto availability */
+ if (opt && opt->crypto_count == 0 &&
+ opt->use == PJMEDIA_SRTP_MANDATORY)
+ return PJMEDIA_SRTP_ESDPREQCRYPTO;
+
+ /* Check crypto */
+ if (opt && opt->use != PJMEDIA_SRTP_DISABLED) {
+ for (i=0; i < opt->crypto_count; ++i) {
+ int cs_idx = get_crypto_idx(&opt->crypto[i].name);
+
+ /* check crypto name */
+ if (cs_idx == -1)
+ return PJMEDIA_SRTP_ENOTSUPCRYPTO;
+
+ /* check key length */
+ if (opt->crypto[i].key.slen &&
+ opt->crypto[i].key.slen <
+ (pj_ssize_t)crypto_suites[cs_idx].cipher_key_len)
+ return PJMEDIA_SRTP_EINKEYLEN;
+ }
+ }
+
+ /* Init libsrtp. */
+ status = pjmedia_srtp_init_lib(endpt);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ pool = pjmedia_endpt_create_pool(endpt, "srtp%p", 1000, 1000);
+ srtp = PJ_POOL_ZALLOC_T(pool, transport_srtp);
+
+ srtp->pool = pool;
+ srtp->session_inited = PJ_FALSE;
+ srtp->bypass_srtp = PJ_FALSE;
+ srtp->probation_cnt = PROBATION_CNT_INIT;
+
+ if (opt) {
+ srtp->setting = *opt;
+ if (opt->use == PJMEDIA_SRTP_DISABLED)
+ srtp->setting.crypto_count = 0;
+
+ for (i=0; i < srtp->setting.crypto_count; ++i) {
+ int cs_idx = get_crypto_idx(&opt->crypto[i].name);
+ pj_str_t tmp_key = opt->crypto[i].key;
+
+ /* re-set crypto */
+ srtp->setting.crypto[i].name = pj_str(crypto_suites[cs_idx].name);
+ /* cut key length */
+ if (tmp_key.slen)
+ tmp_key.slen = crypto_suites[cs_idx].cipher_key_len;
+ pj_strdup(pool, &srtp->setting.crypto[i].key, &tmp_key);
+ }
+ } else {
+ pjmedia_srtp_setting_default(&srtp->setting);
+ }
+
+ status = pj_lock_create_recursive_mutex(pool, pool->obj_name, &srtp->mutex);
+ if (status != PJ_SUCCESS) {
+ pj_pool_release(pool);
+ return status;
+ }
+
+ /* Initialize base pjmedia_transport */
+ pj_memcpy(srtp->base.name, pool->obj_name, PJ_MAX_OBJ_NAME);
+ if (tp)
+ srtp->base.type = tp->type;
+ else
+ srtp->base.type = PJMEDIA_TRANSPORT_TYPE_UDP;
+ srtp->base.op = &transport_srtp_op;
+
+ /* Set underlying transport */
+ srtp->member_tp = tp;
+
+ /* Initialize peer's SRTP usage mode. */
+ srtp->peer_use = srtp->setting.use;
+
+ /* Done */
+ *p_tp = &srtp->base;
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Initialize and start SRTP session with the given parameters.
+ */
+PJ_DEF(pj_status_t) pjmedia_transport_srtp_start(
+ pjmedia_transport *tp,
+ const pjmedia_srtp_crypto *tx,
+ const pjmedia_srtp_crypto *rx)
+{
+ transport_srtp *srtp = (transport_srtp*) tp;
+ srtp_policy_t tx_;
+ srtp_policy_t rx_;
+ err_status_t err;
+ int cr_tx_idx = 0;
+ int au_tx_idx = 0;
+ int cr_rx_idx = 0;
+ int au_rx_idx = 0;
+ int crypto_suites_cnt;
+ pj_status_t status = PJ_SUCCESS;
+
+ PJ_ASSERT_RETURN(tp && tx && rx, PJ_EINVAL);
+
+ pj_lock_acquire(srtp->mutex);
+
+ if (srtp->session_inited) {
+ pjmedia_transport_srtp_stop(tp);
+ }
+
+ crypto_suites_cnt = sizeof(crypto_suites)/sizeof(crypto_suites[0]);
+
+ /* Get encryption and authentication method */
+ cr_tx_idx = au_tx_idx = get_crypto_idx(&tx->name);
+ if (tx->flags & PJMEDIA_SRTP_NO_ENCRYPTION)
+ cr_tx_idx = 0;
+ if (tx->flags & PJMEDIA_SRTP_NO_AUTHENTICATION)
+ au_tx_idx = 0;
+
+ cr_rx_idx = au_rx_idx = get_crypto_idx(&rx->name);
+ if (rx->flags & PJMEDIA_SRTP_NO_ENCRYPTION)
+ cr_rx_idx = 0;
+ if (rx->flags & PJMEDIA_SRTP_NO_AUTHENTICATION)
+ au_rx_idx = 0;
+
+ /* Check whether the crypto-suite requested is supported */
+ if (cr_tx_idx == -1 || cr_rx_idx == -1 || au_tx_idx == -1 ||
+ au_rx_idx == -1)
+ {
+ status = PJMEDIA_SRTP_ENOTSUPCRYPTO;
+ goto on_return;
+ }
+
+ /* If all options points to 'NULL' method, just bypass SRTP */
+ if (cr_tx_idx == 0 && cr_rx_idx == 0 && au_tx_idx == 0 && au_rx_idx == 0) {
+ srtp->bypass_srtp = PJ_TRUE;
+ goto on_return;
+ }
+
+ /* Check key length */
+ if (tx->key.slen != (pj_ssize_t)crypto_suites[cr_tx_idx].cipher_key_len ||
+ rx->key.slen != (pj_ssize_t)crypto_suites[cr_rx_idx].cipher_key_len)
+ {
+ status = PJMEDIA_SRTP_EINKEYLEN;
+ goto on_return;
+ }
+
+ /* Init transmit direction */
+ pj_bzero(&tx_, sizeof(srtp_policy_t));
+ pj_memmove(srtp->tx_key, tx->key.ptr, tx->key.slen);
+ if (cr_tx_idx && au_tx_idx)
+ tx_.rtp.sec_serv = sec_serv_conf_and_auth;
+ else if (cr_tx_idx)
+ tx_.rtp.sec_serv = sec_serv_conf;
+ else if (au_tx_idx)
+ tx_.rtp.sec_serv = sec_serv_auth;
+ else
+ tx_.rtp.sec_serv = sec_serv_none;
+ tx_.key = (uint8_t*)srtp->tx_key;
+ tx_.ssrc.type = ssrc_any_outbound;
+ tx_.ssrc.value = 0;
+ tx_.rtp.cipher_type = crypto_suites[cr_tx_idx].cipher_type;
+ tx_.rtp.cipher_key_len = crypto_suites[cr_tx_idx].cipher_key_len;
+ tx_.rtp.auth_type = crypto_suites[au_tx_idx].auth_type;
+ tx_.rtp.auth_key_len = crypto_suites[au_tx_idx].auth_key_len;
+ tx_.rtp.auth_tag_len = crypto_suites[au_tx_idx].srtp_auth_tag_len;
+ tx_.rtcp = tx_.rtp;
+ tx_.rtcp.auth_tag_len = crypto_suites[au_tx_idx].srtcp_auth_tag_len;
+ tx_.next = NULL;
+ err = srtp_create(&srtp->srtp_tx_ctx, &tx_);
+ if (err != err_status_ok) {
+ status = PJMEDIA_ERRNO_FROM_LIBSRTP(err);
+ goto on_return;
+ }
+ srtp->tx_policy = *tx;
+ pj_strset(&srtp->tx_policy.key, srtp->tx_key, tx->key.slen);
+ srtp->tx_policy.name=pj_str(crypto_suites[get_crypto_idx(&tx->name)].name);
+
+
+ /* Init receive direction */
+ pj_bzero(&rx_, sizeof(srtp_policy_t));
+ pj_memmove(srtp->rx_key, rx->key.ptr, rx->key.slen);
+ if (cr_rx_idx && au_rx_idx)
+ rx_.rtp.sec_serv = sec_serv_conf_and_auth;
+ else if (cr_rx_idx)
+ rx_.rtp.sec_serv = sec_serv_conf;
+ else if (au_rx_idx)
+ rx_.rtp.sec_serv = sec_serv_auth;
+ else
+ rx_.rtp.sec_serv = sec_serv_none;
+ rx_.key = (uint8_t*)srtp->rx_key;
+ rx_.ssrc.type = ssrc_any_inbound;
+ rx_.ssrc.value = 0;
+ rx_.rtp.sec_serv = crypto_suites[cr_rx_idx].service;
+ rx_.rtp.cipher_type = crypto_suites[cr_rx_idx].cipher_type;
+ rx_.rtp.cipher_key_len = crypto_suites[cr_rx_idx].cipher_key_len;
+ rx_.rtp.auth_type = crypto_suites[au_rx_idx].auth_type;
+ rx_.rtp.auth_key_len = crypto_suites[au_rx_idx].auth_key_len;
+ rx_.rtp.auth_tag_len = crypto_suites[au_rx_idx].srtp_auth_tag_len;
+ rx_.rtcp = rx_.rtp;
+ rx_.rtcp.auth_tag_len = crypto_suites[au_rx_idx].srtcp_auth_tag_len;
+ rx_.next = NULL;
+ err = srtp_create(&srtp->srtp_rx_ctx, &rx_);
+ if (err != err_status_ok) {
+ srtp_dealloc(srtp->srtp_tx_ctx);
+ status = PJMEDIA_ERRNO_FROM_LIBSRTP(err);
+ goto on_return;
+ }
+ srtp->rx_policy = *rx;
+ pj_strset(&srtp->rx_policy.key, srtp->rx_key, rx->key.slen);
+ srtp->rx_policy.name=pj_str(crypto_suites[get_crypto_idx(&rx->name)].name);
+
+ /* Declare SRTP session initialized */
+ srtp->session_inited = PJ_TRUE;
+
+ PJ_LOG(5, (srtp->pool->obj_name, "TX: %s key=%s", srtp->tx_policy.name.ptr,
+ octet_string_hex_string(tx->key.ptr, tx->key.slen)));
+ if (srtp->tx_policy.flags) {
+ PJ_LOG(5,(srtp->pool->obj_name,"TX: disable%s%s", (cr_tx_idx?"":" enc"),
+ (au_tx_idx?"":" auth")));
+ }
+
+ PJ_LOG(5, (srtp->pool->obj_name, "RX: %s key=%s", srtp->rx_policy.name.ptr,
+ octet_string_hex_string(rx->key.ptr, rx->key.slen)));
+ if (srtp->rx_policy.flags) {
+ PJ_LOG(5,(srtp->pool->obj_name,"RX: disable%s%s", (cr_rx_idx?"":" enc"),
+ (au_rx_idx?"":" auth")));
+ }
+
+on_return:
+ pj_lock_release(srtp->mutex);
+ return status;
+}
+
+/*
+ * Stop SRTP session.
+ */
+PJ_DEF(pj_status_t) pjmedia_transport_srtp_stop(pjmedia_transport *srtp)
+{
+ transport_srtp *p_srtp = (transport_srtp*) srtp;
+ err_status_t err;
+
+ PJ_ASSERT_RETURN(srtp, PJ_EINVAL);
+
+ pj_lock_acquire(p_srtp->mutex);
+
+ if (!p_srtp->session_inited) {
+ pj_lock_release(p_srtp->mutex);
+ return PJ_SUCCESS;
+ }
+
+ err = srtp_dealloc(p_srtp->srtp_rx_ctx);
+ if (err != err_status_ok) {
+ PJ_LOG(4, (p_srtp->pool->obj_name,
+ "Failed to dealloc RX SRTP context: %s",
+ get_libsrtp_errstr(err)));
+ }
+ err = srtp_dealloc(p_srtp->srtp_tx_ctx);
+ if (err != err_status_ok) {
+ PJ_LOG(4, (p_srtp->pool->obj_name,
+ "Failed to dealloc TX SRTP context: %s",
+ get_libsrtp_errstr(err)));
+ }
+
+ p_srtp->session_inited = PJ_FALSE;
+ pj_bzero(&p_srtp->rx_policy, sizeof(p_srtp->rx_policy));
+ pj_bzero(&p_srtp->tx_policy, sizeof(p_srtp->tx_policy));
+
+ pj_lock_release(p_srtp->mutex);
+
+ return PJ_SUCCESS;
+}
+
+PJ_DEF(pjmedia_transport *) pjmedia_transport_srtp_get_member(
+ pjmedia_transport *tp)
+{
+ transport_srtp *srtp = (transport_srtp*) tp;
+
+ PJ_ASSERT_RETURN(tp, NULL);
+
+ return srtp->member_tp;
+}
+
+
+static pj_status_t transport_get_info(pjmedia_transport *tp,
+ pjmedia_transport_info *info)
+{
+ transport_srtp *srtp = (transport_srtp*) tp;
+ pjmedia_srtp_info srtp_info;
+ int spc_info_idx;
+
+ PJ_ASSERT_RETURN(tp && info, PJ_EINVAL);
+ PJ_ASSERT_RETURN(info->specific_info_cnt <
+ PJMEDIA_TRANSPORT_SPECIFIC_INFO_MAXCNT, PJ_ETOOMANY);
+ PJ_ASSERT_RETURN(sizeof(pjmedia_srtp_info) <=
+ PJMEDIA_TRANSPORT_SPECIFIC_INFO_MAXSIZE, PJ_ENOMEM);
+
+ srtp_info.active = srtp->session_inited;
+ srtp_info.rx_policy = srtp->rx_policy;
+ srtp_info.tx_policy = srtp->tx_policy;
+ srtp_info.use = srtp->setting.use;
+ srtp_info.peer_use = srtp->peer_use;
+
+ spc_info_idx = info->specific_info_cnt++;
+ info->spc_info[spc_info_idx].type = PJMEDIA_TRANSPORT_TYPE_SRTP;
+ info->spc_info[spc_info_idx].cbsize = sizeof(srtp_info);
+ pj_memcpy(&info->spc_info[spc_info_idx].buffer, &srtp_info,
+ sizeof(srtp_info));
+
+ return pjmedia_transport_get_info(srtp->member_tp, info);
+}
+
+static pj_status_t transport_attach(pjmedia_transport *tp,
+ void *user_data,
+ const pj_sockaddr_t *rem_addr,
+ const pj_sockaddr_t *rem_rtcp,
+ unsigned addr_len,
+ void (*rtp_cb) (void*, void*,
+ pj_ssize_t),
+ void (*rtcp_cb)(void*, void*,
+ pj_ssize_t))
+{
+ transport_srtp *srtp = (transport_srtp*) tp;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(tp && rem_addr && addr_len, PJ_EINVAL);
+
+ /* Save the callbacks */
+ pj_lock_acquire(srtp->mutex);
+ srtp->rtp_cb = rtp_cb;
+ srtp->rtcp_cb = rtcp_cb;
+ srtp->user_data = user_data;
+ pj_lock_release(srtp->mutex);
+
+ /* Attach itself to transport */
+ status = pjmedia_transport_attach(srtp->member_tp, srtp, rem_addr,
+ rem_rtcp, addr_len, &srtp_rtp_cb,
+ &srtp_rtcp_cb);
+ if (status != PJ_SUCCESS) {
+ pj_lock_acquire(srtp->mutex);
+ srtp->rtp_cb = NULL;
+ srtp->rtcp_cb = NULL;
+ srtp->user_data = NULL;
+ pj_lock_release(srtp->mutex);
+ return status;
+ }
+
+ return PJ_SUCCESS;
+}
+
+static void transport_detach(pjmedia_transport *tp, void *strm)
+{
+ transport_srtp *srtp = (transport_srtp*) tp;
+
+ PJ_UNUSED_ARG(strm);
+ PJ_ASSERT_ON_FAIL(tp, return);
+
+ if (srtp->member_tp) {
+ pjmedia_transport_detach(srtp->member_tp, srtp);
+ }
+
+ /* Clear up application infos from transport */
+ pj_lock_acquire(srtp->mutex);
+ srtp->rtp_cb = NULL;
+ srtp->rtcp_cb = NULL;
+ srtp->user_data = NULL;
+ pj_lock_release(srtp->mutex);
+}
+
+static pj_status_t transport_send_rtp( pjmedia_transport *tp,
+ const void *pkt,
+ pj_size_t size)
+{
+ pj_status_t status;
+ transport_srtp *srtp = (transport_srtp*) tp;
+ int len = size;
+ err_status_t err;
+
+ if (srtp->bypass_srtp)
+ return pjmedia_transport_send_rtp(srtp->member_tp, pkt, size);
+
+ if (size > sizeof(srtp->rtp_tx_buffer))
+ return PJ_ETOOBIG;
+
+ pj_memcpy(srtp->rtp_tx_buffer, pkt, size);
+
+ pj_lock_acquire(srtp->mutex);
+ if (!srtp->session_inited) {
+ pj_lock_release(srtp->mutex);
+ return PJ_EINVALIDOP;
+ }
+ err = srtp_protect(srtp->srtp_tx_ctx, srtp->rtp_tx_buffer, &len);
+ pj_lock_release(srtp->mutex);
+
+ if (err == err_status_ok) {
+ status = pjmedia_transport_send_rtp(srtp->member_tp, srtp->rtp_tx_buffer, len);
+ } else {
+ status = PJMEDIA_ERRNO_FROM_LIBSRTP(err);
+ }
+
+ return status;
+}
+
+static pj_status_t transport_send_rtcp(pjmedia_transport *tp,
+ const void *pkt,
+ pj_size_t size)
+{
+ return transport_send_rtcp2(tp, NULL, 0, pkt, size);
+}
+
+static pj_status_t transport_send_rtcp2(pjmedia_transport *tp,
+ const pj_sockaddr_t *addr,
+ unsigned addr_len,
+ const void *pkt,
+ pj_size_t size)
+{
+ pj_status_t status;
+ transport_srtp *srtp = (transport_srtp*) tp;
+ int len = size;
+ err_status_t err;
+
+ if (srtp->bypass_srtp) {
+ return pjmedia_transport_send_rtcp2(srtp->member_tp, addr, addr_len,
+ pkt, size);
+ }
+
+ if (size > sizeof(srtp->rtcp_tx_buffer))
+ return PJ_ETOOBIG;
+
+ pj_memcpy(srtp->rtcp_tx_buffer, pkt, size);
+
+ pj_lock_acquire(srtp->mutex);
+ if (!srtp->session_inited) {
+ pj_lock_release(srtp->mutex);
+ return PJ_EINVALIDOP;
+ }
+ err = srtp_protect_rtcp(srtp->srtp_tx_ctx, srtp->rtcp_tx_buffer, &len);
+ pj_lock_release(srtp->mutex);
+
+ if (err == err_status_ok) {
+ status = pjmedia_transport_send_rtcp2(srtp->member_tp, addr, addr_len,
+ srtp->rtcp_tx_buffer, len);
+ } else {
+ status = PJMEDIA_ERRNO_FROM_LIBSRTP(err);
+ }
+
+ return status;
+}
+
+
+static pj_status_t transport_simulate_lost(pjmedia_transport *tp,
+ pjmedia_dir dir,
+ unsigned pct_lost)
+{
+ transport_srtp *srtp = (transport_srtp *) tp;
+
+ PJ_ASSERT_RETURN(tp, PJ_EINVAL);
+
+ return pjmedia_transport_simulate_lost(srtp->member_tp, dir, pct_lost);
+}
+
+static pj_status_t transport_destroy (pjmedia_transport *tp)
+{
+ transport_srtp *srtp = (transport_srtp *) tp;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(tp, PJ_EINVAL);
+
+ if (srtp->setting.close_member_tp && srtp->member_tp) {
+ pjmedia_transport_close(srtp->member_tp);
+ }
+
+ status = pjmedia_transport_srtp_stop(tp);
+
+ /* In case mutex is being acquired by other thread */
+ pj_lock_acquire(srtp->mutex);
+ pj_lock_release(srtp->mutex);
+
+ pj_lock_destroy(srtp->mutex);
+ pj_pool_release(srtp->pool);
+
+ return status;
+}
+
+/*
+ * This callback is called by transport when incoming rtp is received
+ */
+static void srtp_rtp_cb( void *user_data, void *pkt, pj_ssize_t size)
+{
+ transport_srtp *srtp = (transport_srtp *) user_data;
+ int len = size;
+ err_status_t err;
+ void (*cb)(void*, void*, pj_ssize_t) = NULL;
+ void *cb_data = NULL;
+
+ if (srtp->bypass_srtp) {
+ srtp->rtp_cb(srtp->user_data, pkt, size);
+ return;
+ }
+
+ if (size < 0) {
+ return;
+ }
+
+ /* Make sure buffer is 32bit aligned */
+ PJ_ASSERT_ON_FAIL( (((long)pkt) & 0x03)==0, return );
+
+ if (srtp->probation_cnt > 0)
+ --srtp->probation_cnt;
+
+ pj_lock_acquire(srtp->mutex);
+
+ if (!srtp->session_inited) {
+ pj_lock_release(srtp->mutex);
+ return;
+ }
+ err = srtp_unprotect(srtp->srtp_rx_ctx, (pj_uint8_t*)pkt, &len);
+ if (srtp->probation_cnt > 0 &&
+ (err == err_status_replay_old || err == err_status_replay_fail))
+ {
+ /* Handle such condition that stream is updated (RTP seq is reinited
+ * & SRTP is restarted), but some old packets are still coming
+ * so SRTP is learning wrong RTP seq. While the newly inited RTP seq
+ * comes, SRTP thinks the RTP seq is replayed, so srtp_unprotect()
+ * will return err_status_replay_*. Restarting SRTP can resolve this.
+ */
+ pjmedia_srtp_crypto tx, rx;
+ pj_status_t status;
+
+ tx = srtp->tx_policy;
+ rx = srtp->rx_policy;
+ status = pjmedia_transport_srtp_start((pjmedia_transport*)srtp,
+ &tx, &rx);
+ if (status != PJ_SUCCESS) {
+ PJ_LOG(5,(srtp->pool->obj_name, "Failed to restart SRTP, err=%s",
+ get_libsrtp_errstr(err)));
+ } else if (!srtp->bypass_srtp) {
+ err = srtp_unprotect(srtp->srtp_rx_ctx, (pj_uint8_t*)pkt, &len);
+ }
+ }
+
+ if (err != err_status_ok) {
+ PJ_LOG(5,(srtp->pool->obj_name,
+ "Failed to unprotect SRTP, pkt size=%d, err=%s",
+ size, get_libsrtp_errstr(err)));
+ } else {
+ cb = srtp->rtp_cb;
+ cb_data = srtp->user_data;
+ }
+
+ pj_lock_release(srtp->mutex);
+
+ if (cb) {
+ (*cb)(cb_data, pkt, len);
+ }
+}
+
+/*
+ * This callback is called by transport when incoming rtcp is received
+ */
+static void srtp_rtcp_cb( void *user_data, void *pkt, pj_ssize_t size)
+{
+ transport_srtp *srtp = (transport_srtp *) user_data;
+ int len = size;
+ err_status_t err;
+ void (*cb)(void*, void*, pj_ssize_t) = NULL;
+ void *cb_data = NULL;
+
+ if (srtp->bypass_srtp) {
+ srtp->rtcp_cb(srtp->user_data, pkt, size);
+ return;
+ }
+
+ if (size < 0) {
+ return;
+ }
+
+ /* Make sure buffer is 32bit aligned */
+ PJ_ASSERT_ON_FAIL( (((long)pkt) & 0x03)==0, return );
+
+ pj_lock_acquire(srtp->mutex);
+
+ if (!srtp->session_inited) {
+ pj_lock_release(srtp->mutex);
+ return;
+ }
+ err = srtp_unprotect_rtcp(srtp->srtp_rx_ctx, (pj_uint8_t*)pkt, &len);
+ if (err != err_status_ok) {
+ PJ_LOG(5,(srtp->pool->obj_name,
+ "Failed to unprotect SRTCP, pkt size=%d, err=%s",
+ size, get_libsrtp_errstr(err)));
+ } else {
+ cb = srtp->rtcp_cb;
+ cb_data = srtp->user_data;
+ }
+
+ pj_lock_release(srtp->mutex);
+
+ if (cb) {
+ (*cb)(cb_data, pkt, len);
+ }
+}
+
+/* Generate crypto attribute, including crypto key.
+ * If crypto-suite chosen is crypto NULL, just return PJ_SUCCESS,
+ * and set buffer_len = 0.
+ */
+static pj_status_t generate_crypto_attr_value(pj_pool_t *pool,
+ char *buffer, int *buffer_len,
+ pjmedia_srtp_crypto *crypto,
+ int tag)
+{
+ pj_status_t status;
+ int cs_idx = get_crypto_idx(&crypto->name);
+ char b64_key[PJ_BASE256_TO_BASE64_LEN(MAX_KEY_LEN)+1];
+ int b64_key_len = sizeof(b64_key);
+
+ if (cs_idx == -1)
+ return PJMEDIA_SRTP_ENOTSUPCRYPTO;
+
+ /* Crypto-suite NULL. */
+ if (cs_idx == 0) {
+ *buffer_len = 0;
+ return PJ_SUCCESS;
+ }
+
+ /* Generate key if not specified. */
+ if (crypto->key.slen == 0) {
+ pj_bool_t key_ok;
+ char key[MAX_KEY_LEN];
+ err_status_t err;
+ unsigned i;
+
+ PJ_ASSERT_RETURN(MAX_KEY_LEN >= crypto_suites[cs_idx].cipher_key_len,
+ PJ_ETOOSMALL);
+
+ do {
+ key_ok = PJ_TRUE;
+
+ err = crypto_get_random((unsigned char*)key,
+ crypto_suites[cs_idx].cipher_key_len);
+ if (err != err_status_ok) {
+ PJ_LOG(5,(THIS_FILE, "Failed generating random key: %s",
+ get_libsrtp_errstr(err)));
+ return PJMEDIA_ERRNO_FROM_LIBSRTP(err);
+ }
+ for (i=0; i<crypto_suites[cs_idx].cipher_key_len && key_ok; ++i)
+ if (key[i] == 0) key_ok = PJ_FALSE;
+
+ } while (!key_ok);
+ crypto->key.ptr = (char*)
+ pj_pool_zalloc(pool,
+ crypto_suites[cs_idx].cipher_key_len);
+ pj_memcpy(crypto->key.ptr, key, crypto_suites[cs_idx].cipher_key_len);
+ crypto->key.slen = crypto_suites[cs_idx].cipher_key_len;
+ }
+
+ if (crypto->key.slen != (pj_ssize_t)crypto_suites[cs_idx].cipher_key_len)
+ return PJMEDIA_SRTP_EINKEYLEN;
+
+ /* Key transmitted via SDP should be base64 encoded. */
+ status = pj_base64_encode((pj_uint8_t*)crypto->key.ptr, crypto->key.slen,
+ b64_key, &b64_key_len);
+ if (status != PJ_SUCCESS) {
+ PJ_LOG(5,(THIS_FILE, "Failed encoding plain key to base64"));
+ return status;
+ }
+
+ b64_key[b64_key_len] = '\0';
+
+ PJ_ASSERT_RETURN(*buffer_len >= (crypto->name.slen + \
+ b64_key_len + 16), PJ_ETOOSMALL);
+
+ /* Print the crypto attribute value. */
+ *buffer_len = pj_ansi_snprintf(buffer, *buffer_len, "%d %s inline:%s",
+ tag,
+ crypto_suites[cs_idx].name,
+ b64_key);
+
+ return PJ_SUCCESS;
+}
+
+/* Parse crypto attribute line */
+static pj_status_t parse_attr_crypto(pj_pool_t *pool,
+ const pjmedia_sdp_attr *attr,
+ pjmedia_srtp_crypto *crypto,
+ int *tag)
+{
+ pj_str_t input;
+ char *token;
+ int token_len;
+ pj_str_t tmp;
+ pj_status_t status;
+ int itmp;
+
+ pj_bzero(crypto, sizeof(*crypto));
+ pj_strdup_with_null(pool, &input, &attr->value);
+
+ /* Tag */
+ token = strtok(input.ptr, " ");
+ if (!token) {
+ PJ_LOG(4,(THIS_FILE, "Attribute crypto expecting tag"));
+ return PJMEDIA_SDP_EINATTR;
+ }
+ token_len = pj_ansi_strlen(token);
+
+ /* Tag must not use leading zeroes. */
+ if (token_len > 1 && *token == '0')
+ return PJMEDIA_SDP_EINATTR;
+
+ /* Tag must be decimal, i.e: contains only digit '0'-'9'. */
+ for (itmp = 0; itmp < token_len; ++itmp)
+ if (!pj_isdigit(token[itmp]))
+ return PJMEDIA_SDP_EINATTR;
+
+ /* Get tag value. */
+ *tag = atoi(token);
+
+ /* Crypto-suite */
+ token = strtok(NULL, " ");
+ if (!token) {
+ PJ_LOG(4,(THIS_FILE, "Attribute crypto expecting crypto suite"));
+ return PJMEDIA_SDP_EINATTR;
+ }
+ crypto->name = pj_str(token);
+
+ /* Key method */
+ token = strtok(NULL, ":");
+ if (!token) {
+ PJ_LOG(4,(THIS_FILE, "Attribute crypto expecting key method"));
+ return PJMEDIA_SDP_EINATTR;
+ }
+ if (pj_ansi_stricmp(token, "inline")) {
+ PJ_LOG(4,(THIS_FILE, "Attribute crypto key method '%s' not supported!",
+ token));
+ return PJMEDIA_SDP_EINATTR;
+ }
+
+ /* Key */
+ token = strtok(NULL, "| ");
+ if (!token) {
+ PJ_LOG(4,(THIS_FILE, "Attribute crypto expecting key"));
+ return PJMEDIA_SDP_EINATTR;
+ }
+ tmp = pj_str(token);
+ if (PJ_BASE64_TO_BASE256_LEN(tmp.slen) > MAX_KEY_LEN) {
+ PJ_LOG(4,(THIS_FILE, "Key too long"));
+ return PJMEDIA_SRTP_EINKEYLEN;
+ }
+
+ /* Decode key */
+ crypto->key.ptr = (char*) pj_pool_zalloc(pool, MAX_KEY_LEN);
+ itmp = MAX_KEY_LEN;
+ status = pj_base64_decode(&tmp, (pj_uint8_t*)crypto->key.ptr,
+ &itmp);
+ if (status != PJ_SUCCESS) {
+ PJ_LOG(4,(THIS_FILE, "Failed decoding crypto key from base64"));
+ return status;
+ }
+ crypto->key.slen = itmp;
+
+ return PJ_SUCCESS;
+}
+
+static pj_status_t transport_media_create(pjmedia_transport *tp,
+ pj_pool_t *sdp_pool,
+ unsigned options,
+ const pjmedia_sdp_session *sdp_remote,
+ unsigned media_index)
+{
+ struct transport_srtp *srtp = (struct transport_srtp*) tp;
+ unsigned member_tp_option;
+
+ PJ_ASSERT_RETURN(tp, PJ_EINVAL);
+
+ pj_bzero(&srtp->rx_policy_neg, sizeof(srtp->rx_policy_neg));
+ pj_bzero(&srtp->tx_policy_neg, sizeof(srtp->tx_policy_neg));
+
+ srtp->media_option = options;
+ member_tp_option = options | PJMEDIA_TPMED_NO_TRANSPORT_CHECKING;
+
+ srtp->offerer_side = sdp_remote == NULL;
+
+ /* Validations */
+ if (srtp->offerer_side) {
+
+ if (srtp->setting.use == PJMEDIA_SRTP_DISABLED)
+ goto BYPASS_SRTP;
+
+ } else {
+
+ pjmedia_sdp_media *m_rem;
+
+ m_rem = sdp_remote->media[media_index];
+
+ /* Nothing to do on inactive media stream */
+ if (pjmedia_sdp_media_find_attr(m_rem, &ID_INACTIVE, NULL))
+ goto BYPASS_SRTP;
+
+ /* Validate remote media transport based on SRTP usage option.
+ */
+ switch (srtp->setting.use) {
+ case PJMEDIA_SRTP_DISABLED:
+ if (pj_stricmp(&m_rem->desc.transport, &ID_RTP_SAVP) == 0)
+ return PJMEDIA_SRTP_ESDPINTRANSPORT;
+ goto BYPASS_SRTP;
+ case PJMEDIA_SRTP_OPTIONAL:
+ break;
+ case PJMEDIA_SRTP_MANDATORY:
+ if (pj_stricmp(&m_rem->desc.transport, &ID_RTP_SAVP) != 0)
+ return PJMEDIA_SRTP_ESDPINTRANSPORT;
+ break;
+ }
+
+ }
+ goto PROPAGATE_MEDIA_CREATE;
+
+BYPASS_SRTP:
+ srtp->bypass_srtp = PJ_TRUE;
+ member_tp_option &= ~PJMEDIA_TPMED_NO_TRANSPORT_CHECKING;
+
+PROPAGATE_MEDIA_CREATE:
+ return pjmedia_transport_media_create(srtp->member_tp, sdp_pool,
+ member_tp_option, sdp_remote,
+ media_index);
+}
+
+static pj_status_t transport_encode_sdp(pjmedia_transport *tp,
+ pj_pool_t *sdp_pool,
+ pjmedia_sdp_session *sdp_local,
+ const pjmedia_sdp_session *sdp_remote,
+ unsigned media_index)
+{
+ struct transport_srtp *srtp = (struct transport_srtp*) tp;
+ pjmedia_sdp_media *m_rem, *m_loc;
+ enum { MAXLEN = 512 };
+ char buffer[MAXLEN];
+ int buffer_len;
+ pj_status_t status;
+ pjmedia_sdp_attr *attr;
+ pj_str_t attr_value;
+ unsigned i, j;
+
+ PJ_ASSERT_RETURN(tp && sdp_pool && sdp_local, PJ_EINVAL);
+
+ pj_bzero(&srtp->rx_policy_neg, sizeof(srtp->rx_policy_neg));
+ pj_bzero(&srtp->tx_policy_neg, sizeof(srtp->tx_policy_neg));
+
+ srtp->offerer_side = sdp_remote == NULL;
+
+ m_rem = sdp_remote ? sdp_remote->media[media_index] : NULL;
+ m_loc = sdp_local->media[media_index];
+
+ /* Bypass if media transport is not RTP/AVP or RTP/SAVP */
+ if (pj_stricmp(&m_loc->desc.transport, &ID_RTP_AVP) != 0 &&
+ pj_stricmp(&m_loc->desc.transport, &ID_RTP_SAVP) != 0)
+ goto BYPASS_SRTP;
+
+ /* If the media is inactive, do nothing. */
+ /* No, we still need to process SRTP offer/answer even if the media is
+ * marked as inactive, because the transport is still alive in this
+ * case (e.g. for keep-alive). See:
+ * http://trac.pjsip.org/repos/ticket/1079
+ */
+ /*
+ if (pjmedia_sdp_media_find_attr(m_loc, &ID_INACTIVE, NULL) ||
+ (m_rem && pjmedia_sdp_media_find_attr(m_rem, &ID_INACTIVE, NULL)))
+ goto BYPASS_SRTP;
+ */
+
+ /* Check remote media transport & set local media transport
+ * based on SRTP usage option.
+ */
+ if (srtp->offerer_side) {
+
+ /* Generate transport */
+ switch (srtp->setting.use) {
+ case PJMEDIA_SRTP_DISABLED:
+ goto BYPASS_SRTP;
+ case PJMEDIA_SRTP_OPTIONAL:
+ m_loc->desc.transport =
+ (srtp->peer_use == PJMEDIA_SRTP_MANDATORY)?
+ ID_RTP_SAVP : ID_RTP_AVP;
+ break;
+ case PJMEDIA_SRTP_MANDATORY:
+ m_loc->desc.transport = ID_RTP_SAVP;
+ break;
+ }
+
+ /* Generate crypto attribute if not yet */
+ if (pjmedia_sdp_media_find_attr(m_loc, &ID_CRYPTO, NULL) == NULL) {
+ for (i=0; i<srtp->setting.crypto_count; ++i) {
+ /* Offer crypto-suites based on setting. */
+ buffer_len = MAXLEN;
+ status = generate_crypto_attr_value(srtp->pool, buffer, &buffer_len,
+ &srtp->setting.crypto[i],
+ i+1);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* If buffer_len==0, just skip the crypto attribute. */
+ if (buffer_len) {
+ pj_strset(&attr_value, buffer, buffer_len);
+ attr = pjmedia_sdp_attr_create(srtp->pool, ID_CRYPTO.ptr,
+ &attr_value);
+ m_loc->attr[m_loc->attr_count++] = attr;
+ }
+ }
+ }
+
+ } else {
+ /* Answerer side */
+
+ pj_assert(sdp_remote && m_rem);
+
+ /* Generate transport */
+ switch (srtp->setting.use) {
+ case PJMEDIA_SRTP_DISABLED:
+ if (pj_stricmp(&m_rem->desc.transport, &ID_RTP_SAVP) == 0)
+ return PJMEDIA_SRTP_ESDPINTRANSPORT;
+ goto BYPASS_SRTP;
+ case PJMEDIA_SRTP_OPTIONAL:
+ m_loc->desc.transport = m_rem->desc.transport;
+ break;
+ case PJMEDIA_SRTP_MANDATORY:
+ if (pj_stricmp(&m_rem->desc.transport, &ID_RTP_SAVP) != 0)
+ return PJMEDIA_SRTP_ESDPINTRANSPORT;
+ m_loc->desc.transport = ID_RTP_SAVP;
+ break;
+ }
+
+ /* Generate crypto attribute if not yet */
+ if (pjmedia_sdp_media_find_attr(m_loc, &ID_CRYPTO, NULL) == NULL) {
+
+ pjmedia_srtp_crypto tmp_rx_crypto;
+ pj_bool_t has_crypto_attr = PJ_FALSE;
+ int matched_idx = -1;
+ int chosen_tag = 0;
+ int tags[64]; /* assume no more than 64 crypto attrs in a media */
+ unsigned cr_attr_count = 0;
+
+ /* Find supported crypto-suite, get the tag, and assign policy_local */
+ for (i=0; i<m_rem->attr_count; ++i) {
+ if (pj_stricmp(&m_rem->attr[i]->name, &ID_CRYPTO) != 0)
+ continue;
+
+ has_crypto_attr = PJ_TRUE;
+
+ status = parse_attr_crypto(srtp->pool, m_rem->attr[i],
+ &tmp_rx_crypto, &tags[cr_attr_count]);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Check duplicated tag */
+ for (j=0; j<cr_attr_count; ++j) {
+ if (tags[j] == tags[cr_attr_count]) {
+ DEACTIVATE_MEDIA(sdp_pool, m_loc);
+ return PJMEDIA_SRTP_ESDPDUPCRYPTOTAG;
+ }
+ }
+
+ if (matched_idx == -1) {
+ /* lets see if the crypto-suite offered is supported */
+ for (j=0; j<srtp->setting.crypto_count; ++j)
+ if (pj_stricmp(&tmp_rx_crypto.name,
+ &srtp->setting.crypto[j].name) == 0)
+ {
+ int cs_idx = get_crypto_idx(&tmp_rx_crypto.name);
+
+ /* Force to use test key */
+ /* bad keys for snom: */
+ //char *hex_test_key = "58b29c5c8f42308120ce857e439f2d"
+ // "7810a8b10ad0b1446be5470faea496";
+ //char *hex_test_key = "20a26aac7ba062d356ff52b61e3993"
+ // "ccb78078f12c64db94b9c294927fd0";
+ //pj_str_t *test_key = &srtp->setting.crypto[j].key;
+ //char *raw_test_key = pj_pool_zalloc(srtp->pool, 64);
+ //hex_string_to_octet_string(
+ // raw_test_key,
+ // hex_test_key,
+ // strlen(hex_test_key));
+ //pj_strset(test_key, raw_test_key,
+ // crypto_suites[cs_idx].cipher_key_len);
+ /* EO Force to use test key */
+
+ if (tmp_rx_crypto.key.slen !=
+ (int)crypto_suites[cs_idx].cipher_key_len)
+ return PJMEDIA_SRTP_EINKEYLEN;
+
+ srtp->rx_policy_neg = tmp_rx_crypto;
+ chosen_tag = tags[cr_attr_count];
+ matched_idx = j;
+ break;
+ }
+ }
+ cr_attr_count++;
+ }
+
+ /* Check crypto negotiation result */
+ switch (srtp->setting.use) {
+ case PJMEDIA_SRTP_DISABLED:
+ pj_assert(!"Should never reach here");
+ break;
+
+ case PJMEDIA_SRTP_OPTIONAL:
+ /* bypass SRTP when no crypto-attr and remote uses RTP/AVP */
+ if (!has_crypto_attr &&
+ pj_stricmp(&m_rem->desc.transport, &ID_RTP_AVP) == 0)
+ goto BYPASS_SRTP;
+ /* bypass SRTP when nothing match and remote uses RTP/AVP */
+ else if (matched_idx == -1 &&
+ pj_stricmp(&m_rem->desc.transport, &ID_RTP_AVP) == 0)
+ goto BYPASS_SRTP;
+ break;
+
+ case PJMEDIA_SRTP_MANDATORY:
+ /* Do nothing, intentional */
+ break;
+ }
+
+ /* No crypto attr */
+ if (!has_crypto_attr) {
+ DEACTIVATE_MEDIA(sdp_pool, m_loc);
+ return PJMEDIA_SRTP_ESDPREQCRYPTO;
+ }
+
+ /* No crypto match */
+ if (matched_idx == -1) {
+ DEACTIVATE_MEDIA(sdp_pool, m_loc);
+ return PJMEDIA_SRTP_ENOTSUPCRYPTO;
+ }
+
+ /* we have to generate crypto answer,
+ * with srtp->tx_policy_neg matched the offer
+ * and rem_tag contains matched offer tag.
+ */
+ buffer_len = MAXLEN;
+ status = generate_crypto_attr_value(srtp->pool, buffer, &buffer_len,
+ &srtp->setting.crypto[matched_idx],
+ chosen_tag);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ srtp->tx_policy_neg = srtp->setting.crypto[matched_idx];
+
+ /* If buffer_len==0, just skip the crypto attribute. */
+ if (buffer_len) {
+ pj_strset(&attr_value, buffer, buffer_len);
+ attr = pjmedia_sdp_attr_create(sdp_pool, ID_CRYPTO.ptr,
+ &attr_value);
+ m_loc->attr[m_loc->attr_count++] = attr;
+ }
+
+ /* At this point, we get valid rx_policy_neg & tx_policy_neg. */
+ }
+
+ }
+ goto PROPAGATE_MEDIA_CREATE;
+
+BYPASS_SRTP:
+ /* Do not update this flag here as actually the media session hasn't been
+ * updated.
+ */
+ //srtp->bypass_srtp = PJ_TRUE;
+
+PROPAGATE_MEDIA_CREATE:
+ return pjmedia_transport_encode_sdp(srtp->member_tp, sdp_pool,
+ sdp_local, sdp_remote, media_index);
+}
+
+
+
+static pj_status_t transport_media_start(pjmedia_transport *tp,
+ pj_pool_t *pool,
+ const pjmedia_sdp_session *sdp_local,
+ const pjmedia_sdp_session *sdp_remote,
+ unsigned media_index)
+{
+ struct transport_srtp *srtp = (struct transport_srtp*) tp;
+ pjmedia_sdp_media *m_rem, *m_loc;
+ pj_status_t status;
+ unsigned i;
+
+ PJ_ASSERT_RETURN(tp && pool && sdp_local && sdp_remote, PJ_EINVAL);
+
+ m_rem = sdp_remote->media[media_index];
+ m_loc = sdp_local->media[media_index];
+
+ if (pj_stricmp(&m_rem->desc.transport, &ID_RTP_SAVP) == 0)
+ srtp->peer_use = PJMEDIA_SRTP_MANDATORY;
+ else
+ srtp->peer_use = PJMEDIA_SRTP_OPTIONAL;
+
+ /* For answerer side, this function will just have to start SRTP */
+
+ /* Check remote media transport & set local media transport
+ * based on SRTP usage option.
+ */
+ if (srtp->offerer_side) {
+ if (srtp->setting.use == PJMEDIA_SRTP_DISABLED) {
+ if (pjmedia_sdp_media_find_attr(m_rem, &ID_CRYPTO, NULL)) {
+ DEACTIVATE_MEDIA(pool, m_loc);
+ return PJMEDIA_SRTP_ESDPINCRYPTO;
+ }
+ goto BYPASS_SRTP;
+ } else if (srtp->setting.use == PJMEDIA_SRTP_OPTIONAL) {
+ // Regardless the answer's transport type (RTP/AVP or RTP/SAVP),
+ // the answer must be processed through in optional mode.
+ // Please note that at this point transport type is ensured to be
+ // RTP/AVP or RTP/SAVP, see transport_media_create()
+ //if (pj_stricmp(&m_rem->desc.transport, &m_loc->desc.transport)) {
+ //DEACTIVATE_MEDIA(pool, m_loc);
+ //return PJMEDIA_SDP_EINPROTO;
+ //}
+ } else if (srtp->setting.use == PJMEDIA_SRTP_MANDATORY) {
+ if (pj_stricmp(&m_rem->desc.transport, &ID_RTP_SAVP)) {
+ DEACTIVATE_MEDIA(pool, m_loc);
+ return PJMEDIA_SDP_EINPROTO;
+ }
+ }
+ }
+
+ if (srtp->offerer_side) {
+ /* find supported crypto-suite, get the tag, and assign policy_local */
+ pjmedia_srtp_crypto tmp_tx_crypto;
+ pj_bool_t has_crypto_attr = PJ_FALSE;
+ int rem_tag;
+
+ for (i=0; i<m_rem->attr_count; ++i) {
+ if (pj_stricmp(&m_rem->attr[i]->name, &ID_CRYPTO) != 0)
+ continue;
+
+ /* more than one crypto attribute in media answer */
+ if (has_crypto_attr) {
+ DEACTIVATE_MEDIA(pool, m_loc);
+ return PJMEDIA_SRTP_ESDPAMBIGUEANS;
+ }
+
+ has_crypto_attr = PJ_TRUE;
+
+ status = parse_attr_crypto(srtp->pool, m_rem->attr[i],
+ &tmp_tx_crypto, &rem_tag);
+ if (status != PJ_SUCCESS)
+ return status;
+
+
+ /* our offer tag is always ordered by setting */
+ if (rem_tag < 1 || rem_tag > (int)srtp->setting.crypto_count) {
+ DEACTIVATE_MEDIA(pool, m_loc);
+ return PJMEDIA_SRTP_ESDPINCRYPTOTAG;
+ }
+
+ /* match the crypto name */
+ if (pj_stricmp(&tmp_tx_crypto.name,
+ &srtp->setting.crypto[rem_tag-1].name) != 0)
+ {
+ DEACTIVATE_MEDIA(pool, m_loc);
+ return PJMEDIA_SRTP_ECRYPTONOTMATCH;
+ }
+
+ srtp->tx_policy_neg = srtp->setting.crypto[rem_tag-1];
+ srtp->rx_policy_neg = tmp_tx_crypto;
+ }
+
+ if (srtp->setting.use == PJMEDIA_SRTP_DISABLED) {
+ /* should never reach here */
+ goto BYPASS_SRTP;
+ } else if (srtp->setting.use == PJMEDIA_SRTP_OPTIONAL) {
+ if (!has_crypto_attr)
+ goto BYPASS_SRTP;
+ } else if (srtp->setting.use == PJMEDIA_SRTP_MANDATORY) {
+ if (!has_crypto_attr) {
+ DEACTIVATE_MEDIA(pool, m_loc);
+ return PJMEDIA_SRTP_ESDPREQCRYPTO;
+ }
+ }
+
+ /* At this point, we get valid rx_policy_neg & tx_policy_neg. */
+ }
+
+ /* Make sure we have the SRTP policies */
+ if (srtp_crypto_empty(&srtp->tx_policy_neg) ||
+ srtp_crypto_empty(&srtp->rx_policy_neg))
+ {
+ goto BYPASS_SRTP;
+ }
+
+ /* Reset probation counts */
+ srtp->probation_cnt = PROBATION_CNT_INIT;
+
+ /* Got policy_local & policy_remote, let's initalize the SRTP */
+
+ /* Ticket #1075: media_start() is called whenever media description
+ * gets updated, e.g: call hold, however we should restart SRTP only
+ * when the SRTP policy settings are updated.
+ */
+ if (srtp_crypto_cmp(&srtp->tx_policy_neg, &srtp->tx_policy) ||
+ srtp_crypto_cmp(&srtp->rx_policy_neg, &srtp->rx_policy))
+ {
+ status = pjmedia_transport_srtp_start(tp,
+ &srtp->tx_policy_neg,
+ &srtp->rx_policy_neg);
+ if (status != PJ_SUCCESS)
+ return status;
+ }
+
+ srtp->bypass_srtp = PJ_FALSE;
+
+ goto PROPAGATE_MEDIA_START;
+
+BYPASS_SRTP:
+ srtp->bypass_srtp = PJ_TRUE;
+ srtp->peer_use = PJMEDIA_SRTP_DISABLED;
+ if (srtp->session_inited) {
+ pjmedia_transport_srtp_stop(tp);
+ }
+
+PROPAGATE_MEDIA_START:
+ return pjmedia_transport_media_start(srtp->member_tp, pool,
+ sdp_local, sdp_remote,
+ media_index);
+}
+
+static pj_status_t transport_media_stop(pjmedia_transport *tp)
+{
+ struct transport_srtp *srtp = (struct transport_srtp*) tp;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(tp, PJ_EINVAL);
+
+ status = pjmedia_transport_media_stop(srtp->member_tp);
+ if (status != PJ_SUCCESS)
+ PJ_LOG(4, (srtp->pool->obj_name,
+ "SRTP failed stop underlying media transport."));
+
+ return pjmedia_transport_srtp_stop(tp);
+}
+
+/* Utility */
+PJ_DEF(pj_status_t) pjmedia_transport_srtp_decrypt_pkt(pjmedia_transport *tp,
+ pj_bool_t is_rtp,
+ void *pkt,
+ int *pkt_len)
+{
+ transport_srtp *srtp = (transport_srtp *)tp;
+ err_status_t err;
+
+ if (srtp->bypass_srtp)
+ return PJ_SUCCESS;
+
+ PJ_ASSERT_RETURN(tp && pkt && (*pkt_len>0), PJ_EINVAL);
+ PJ_ASSERT_RETURN(srtp->session_inited, PJ_EINVALIDOP);
+
+ /* Make sure buffer is 32bit aligned */
+ PJ_ASSERT_ON_FAIL( (((long)pkt) & 0x03)==0, return PJ_EINVAL);
+
+ pj_lock_acquire(srtp->mutex);
+
+ if (!srtp->session_inited) {
+ pj_lock_release(srtp->mutex);
+ return PJ_EINVALIDOP;
+ }
+
+ if (is_rtp)
+ err = srtp_unprotect(srtp->srtp_rx_ctx, pkt, pkt_len);
+ else
+ err = srtp_unprotect_rtcp(srtp->srtp_rx_ctx, pkt, pkt_len);
+
+ if (err != err_status_ok) {
+ PJ_LOG(5,(srtp->pool->obj_name,
+ "Failed to unprotect SRTP, pkt size=%d, err=%s",
+ *pkt_len, get_libsrtp_errstr(err)));
+ }
+
+ pj_lock_release(srtp->mutex);
+
+ return (err==err_status_ok) ? PJ_SUCCESS : PJMEDIA_ERRNO_FROM_LIBSRTP(err);
+}
+
+#endif
+
+