| /* $Id$ */ |
| /* |
| * 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 outgoing packet */ |
| #define MAX_RTP_BUFFER_LEN PJMEDIA_MAX_MTU |
| #define MAX_RTCP_BUFFER_LEN PJMEDIA_MAX_MTU |
| |
| /* Maximum SRTP crypto key length */ |
| #define MAX_KEY_LEN 128 |
| |
| /* 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; |
| |
| /* Logging stuffs */ |
| #if PJ_LOG_MAX_LEVEL >= 5 |
| { |
| char b64[PJ_BASE256_TO_BASE64_LEN(MAX_KEY_LEN)]; |
| int b64_len; |
| |
| /* TX crypto and key */ |
| b64_len = sizeof(b64); |
| status = pj_base64_encode((pj_uint8_t*)tx->key.ptr, tx->key.slen, |
| b64, &b64_len); |
| if (status != PJ_SUCCESS) |
| b64_len = pj_ansi_sprintf(b64, "--key too long--"); |
| else |
| b64[b64_len] = '\0'; |
| |
| PJ_LOG(5, (srtp->pool->obj_name, "TX: %s key=%s", |
| srtp->tx_policy.name.ptr, b64)); |
| if (srtp->tx_policy.flags) { |
| PJ_LOG(5,(srtp->pool->obj_name, "TX: disable%s%s", |
| (cr_tx_idx?"":" enc"), |
| (au_tx_idx?"":" auth"))); |
| } |
| |
| /* RX crypto and key */ |
| b64_len = sizeof(b64); |
| status = pj_base64_encode((pj_uint8_t*)rx->key.ptr, rx->key.slen, |
| b64, &b64_len); |
| if (status != PJ_SUCCESS) |
| b64_len = pj_ansi_sprintf(b64, "--key too long--"); |
| else |
| b64[b64_len] = '\0'; |
| |
| PJ_LOG(5, (srtp->pool->obj_name, "RX: %s key=%s", |
| srtp->rx_policy.name.ptr, b64)); |
| if (srtp->rx_policy.flags) { |
| PJ_LOG(5,(srtp->pool->obj_name,"RX: disable%s%s", |
| (cr_rx_idx?"":" enc"), |
| (au_rx_idx?"":" auth"))); |
| } |
| } |
| #endif |
| |
| 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 = (int)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) - 10) |
| 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 = (int)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) - 10) |
| 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( (((pj_ssize_t)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( (((pj_ssize_t)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; |
| pj_size_t 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( (((pj_ssize_t)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 |
| |
| |