799 lines
28 KiB
C
799 lines
28 KiB
C
/*
|
|
* Copyright (C) 2017 Teluu Inc. (http://www.teluu.com)
|
|
*
|
|
* 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
|
|
*/
|
|
|
|
#if defined(PJ_HAS_SSL_SOCK) && (PJ_HAS_SSL_SOCK != 0)
|
|
|
|
/* Include OpenSSL libraries for MSVC */
|
|
# ifdef _MSC_VER
|
|
# if (PJ_SSL_SOCK_IMP == PJ_SSL_SOCK_IMP_OPENSSL)
|
|
# include <openssl/opensslv.h>
|
|
# if OPENSSL_VERSION_NUMBER >= 0x10100000L
|
|
# pragma comment(lib, "libcrypto")
|
|
# pragma comment(lib, "libssl")
|
|
# pragma comment(lib, "crypt32")
|
|
# else
|
|
# pragma comment(lib, "libeay32")
|
|
# pragma comment(lib, "ssleay32")
|
|
# endif
|
|
# endif
|
|
# endif
|
|
#endif
|
|
|
|
#if (PJ_SSL_SOCK_IMP == PJ_SSL_SOCK_IMP_APPLE)
|
|
#include <Security/SecRandom.h>
|
|
#endif
|
|
|
|
#include <pj/rand.h>
|
|
|
|
|
|
static pj_status_t sdes_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 sdes_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 sdes_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 sdes_media_stop (pjmedia_transport *tp);
|
|
|
|
|
|
static pjmedia_transport_op sdes_op =
|
|
{
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
&sdes_media_create,
|
|
&sdes_encode_sdp,
|
|
&sdes_media_start,
|
|
&sdes_media_stop,
|
|
NULL,
|
|
NULL
|
|
};
|
|
|
|
|
|
static pj_status_t sdes_create(transport_srtp *srtp,
|
|
pjmedia_transport **p_keying)
|
|
{
|
|
pjmedia_transport *sdes;
|
|
|
|
sdes = PJ_POOL_ZALLOC_T(srtp->pool, pjmedia_transport);
|
|
pj_ansi_strxcpy(sdes->name, srtp->pool->obj_name, PJ_MAX_OBJ_NAME);
|
|
pj_memcpy(sdes->name, "sdes", 4);
|
|
sdes->type = (pjmedia_transport_type)PJMEDIA_SRTP_KEYING_SDES;
|
|
sdes->op = &sdes_op;
|
|
sdes->user_data = srtp;
|
|
|
|
*p_keying = sdes;
|
|
PJ_LOG(5,(srtp->pool->obj_name, "SRTP keying SDES created"));
|
|
return PJ_SUCCESS;
|
|
}
|
|
|
|
|
|
/* 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);
|
|
int print_len;
|
|
|
|
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];
|
|
unsigned i;
|
|
|
|
PJ_ASSERT_RETURN(MAX_KEY_LEN >= crypto_suites[cs_idx].cipher_key_len,
|
|
PJ_ETOOSMALL);
|
|
|
|
do {
|
|
#if defined(PJ_HAS_SSL_SOCK) && (PJ_HAS_SSL_SOCK != 0) && \
|
|
(PJ_SSL_SOCK_IMP == PJ_SSL_SOCK_IMP_OPENSSL)
|
|
int err = RAND_bytes((unsigned char*)key,
|
|
crypto_suites[cs_idx].cipher_key_len);
|
|
if (err != 1) {
|
|
PJ_LOG(4,(THIS_FILE, "Failed generating random key "
|
|
"(native err=%d)", err));
|
|
return PJMEDIA_ERRNO_FROM_LIBSRTP(1);
|
|
}
|
|
#elif defined(PJ_HAS_SSL_SOCK) && (PJ_HAS_SSL_SOCK != 0) && \
|
|
(PJ_SSL_SOCK_IMP == PJ_SSL_SOCK_IMP_APPLE)
|
|
int err = SecRandomCopyBytes(kSecRandomDefault,
|
|
crypto_suites[cs_idx].cipher_key_len,
|
|
&key);
|
|
if (err != errSecSuccess) {
|
|
PJ_LOG(4,(THIS_FILE, "Failed generating random key "
|
|
"(native err=%d)", err));
|
|
return PJMEDIA_ERRNO_FROM_LIBSRTP(1);
|
|
}
|
|
#else
|
|
PJ_LOG(3,(THIS_FILE, "Warning: simple random generator is used "
|
|
"for generating SRTP key"));
|
|
for (i=0; i<crypto_suites[cs_idx].cipher_key_len; ++i) {
|
|
pj_timestamp ts;
|
|
if (pj_rand() % 7 < 2)
|
|
pj_thread_sleep(pj_rand() % 11);
|
|
pj_get_timestamp(&ts);
|
|
key[i] = (char)((pj_rand() + ts.u32.lo) & 0xFF);
|
|
}
|
|
#endif
|
|
|
|
key_ok = PJ_TRUE;
|
|
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,
|
|
(int)crypto->key.slen,
|
|
b64_key, &b64_key_len);
|
|
if (status != PJ_SUCCESS) {
|
|
PJ_PERROR(4,(THIS_FILE, status,
|
|
"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. */
|
|
print_len = pj_ansi_snprintf(buffer, *buffer_len, "%d %s inline:%s",
|
|
tag,
|
|
crypto_suites[cs_idx].name,
|
|
b64_key);
|
|
if (print_len < 1 || print_len >= *buffer_len)
|
|
return PJ_ETOOSMALL;
|
|
|
|
*buffer_len = print_len;
|
|
|
|
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 token, delim;
|
|
pj_status_t status;
|
|
int itmp;
|
|
pj_ssize_t found_idx;
|
|
|
|
pj_bzero(crypto, sizeof(*crypto));
|
|
|
|
/* Tag */
|
|
delim = pj_str(" ");
|
|
found_idx = pj_strtok(&attr->value, &delim, &token, 0);
|
|
if (found_idx == attr->value.slen) {
|
|
PJ_LOG(4,(THIS_FILE, "Attribute crypto expecting tag"));
|
|
return PJMEDIA_SDP_EINATTR;
|
|
}
|
|
|
|
/* Tag must not use leading zeroes. */
|
|
if (token.slen > 1 && *token.ptr == '0')
|
|
return PJMEDIA_SDP_EINATTR;
|
|
|
|
/* Tag must be decimal, i.e: contains only digit '0'-'9'. */
|
|
for (itmp = 0; itmp < token.slen; ++itmp)
|
|
if (!pj_isdigit(token.ptr[itmp]))
|
|
return PJMEDIA_SDP_EINATTR;
|
|
|
|
/* Get tag value. */
|
|
*tag = pj_strtoul(&token);
|
|
|
|
/* Crypto-suite */
|
|
found_idx = pj_strtok(&attr->value, &delim, &token, found_idx+token.slen);
|
|
if (found_idx == attr->value.slen) {
|
|
PJ_LOG(4,(THIS_FILE, "Attribute crypto expecting crypto suite"));
|
|
return PJMEDIA_SDP_EINATTR;
|
|
}
|
|
pj_strdup(pool, &crypto->name, &token);
|
|
|
|
/* Key method */
|
|
delim = pj_str(": ");
|
|
found_idx = pj_strtok(&attr->value, &delim, &token, found_idx+token.slen);
|
|
if (found_idx == attr->value.slen) {
|
|
PJ_LOG(4,(THIS_FILE, "Attribute crypto expecting key method"));
|
|
return PJMEDIA_SDP_EINATTR;
|
|
}
|
|
if (pj_stricmp2(&token, "inline")) {
|
|
PJ_LOG(4,(THIS_FILE, "Attribute crypto key method '%.*s' "
|
|
"not supported!", (int)token.slen, token.ptr));
|
|
return PJMEDIA_SDP_EINATTR;
|
|
}
|
|
|
|
/* Key */
|
|
delim = pj_str("| ");
|
|
found_idx = pj_strtok(&attr->value, &delim, &token, found_idx+token.slen);
|
|
if (found_idx == attr->value.slen) {
|
|
PJ_LOG(4,(THIS_FILE, "Attribute crypto expecting key"));
|
|
return PJMEDIA_SDP_EINATTR;
|
|
}
|
|
|
|
if (PJ_BASE64_TO_BASE256_LEN(token.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(&token, (pj_uint8_t*)crypto->key.ptr,
|
|
&itmp);
|
|
if (status != PJ_SUCCESS) {
|
|
PJ_PERROR(4,(THIS_FILE, status,
|
|
"Failed decoding crypto key from base64"));
|
|
return status;
|
|
}
|
|
crypto->key.slen = itmp;
|
|
|
|
return PJ_SUCCESS;
|
|
}
|
|
|
|
|
|
static pj_status_t sdes_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->user_data;
|
|
pj_uint32_t rem_proto = 0;
|
|
|
|
PJ_UNUSED_ARG(options);
|
|
PJ_UNUSED_ARG(sdp_pool);
|
|
|
|
/* Verify remote media transport, it has to be RTP/AVP or RTP/SAVP */
|
|
if (!srtp->offerer_side) {
|
|
pjmedia_sdp_media *m = sdp_remote->media[media_index];
|
|
|
|
/* Get transport protocol and drop any RTCP-FB flag */
|
|
rem_proto = pjmedia_sdp_transport_get_proto(&m->desc.transport);
|
|
PJMEDIA_TP_PROTO_TRIM_FLAG(rem_proto, PJMEDIA_TP_PROFILE_RTCP_FB);
|
|
if (rem_proto != PJMEDIA_TP_PROTO_RTP_AVP &&
|
|
rem_proto != PJMEDIA_TP_PROTO_RTP_SAVP)
|
|
{
|
|
return PJMEDIA_SRTP_ESDPINTRANSPORT;
|
|
}
|
|
}
|
|
|
|
/* Validations */
|
|
if (srtp->offerer_side) {
|
|
/* As offerer: do nothing. */
|
|
} else {
|
|
/* Validate remote media transport based on SRTP usage option. */
|
|
switch (srtp->setting.use) {
|
|
case PJMEDIA_SRTP_DISABLED:
|
|
if (rem_proto == PJMEDIA_TP_PROTO_RTP_SAVP)
|
|
return PJMEDIA_SRTP_ESDPINTRANSPORT;
|
|
break;
|
|
case PJMEDIA_SRTP_OPTIONAL:
|
|
break;
|
|
case PJMEDIA_SRTP_MANDATORY:
|
|
if (rem_proto != PJMEDIA_TP_PROTO_RTP_SAVP)
|
|
return PJMEDIA_SRTP_ESDPINTRANSPORT;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return PJ_SUCCESS;
|
|
}
|
|
|
|
static pj_status_t sdes_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->user_data;
|
|
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;
|
|
|
|
m_rem = sdp_remote ? sdp_remote->media[media_index] : NULL;
|
|
m_loc = sdp_local->media[media_index];
|
|
|
|
/* Verify media transport, it has to be RTP/AVP or RTP/SAVP */
|
|
{
|
|
pjmedia_sdp_media *m = sdp_remote? m_rem : m_loc;
|
|
pj_uint32_t proto = 0;
|
|
|
|
/* Get transport protocol and drop any RTCP-FB flag */
|
|
proto = pjmedia_sdp_transport_get_proto(&m->desc.transport);
|
|
PJMEDIA_TP_PROTO_TRIM_FLAG(proto, PJMEDIA_TP_PROFILE_RTCP_FB);
|
|
if (proto != PJMEDIA_TP_PROTO_RTP_AVP &&
|
|
proto != PJMEDIA_TP_PROTO_RTP_SAVP)
|
|
{
|
|
return PJMEDIA_SRTP_ESDPINTRANSPORT;
|
|
}
|
|
}
|
|
|
|
/* 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:
|
|
* https://github.com/pjsip/pjproject/issues/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:
|
|
/* Should never reach here */
|
|
return PJ_SUCCESS;
|
|
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) {
|
|
int tag = 1;
|
|
|
|
/* Offer only current active crypto if any, otherwise offer all
|
|
* crypto-suites in the setting.
|
|
*/
|
|
for (i=0; i<srtp->setting.crypto_count; ++i) {
|
|
if (srtp->srtp_ctx.tx_policy.name.slen &&
|
|
pj_stricmp(&srtp->srtp_ctx.tx_policy.name,
|
|
&srtp->setting.crypto[i].name) != 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
buffer_len = MAXLEN;
|
|
status = generate_crypto_attr_value(srtp->pool, buffer,
|
|
&buffer_len,
|
|
&srtp->setting.crypto[i],
|
|
tag);
|
|
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;
|
|
++tag;
|
|
}
|
|
}
|
|
}
|
|
|
|
} else {
|
|
/* Answerer side */
|
|
pj_uint32_t rem_proto = 0;
|
|
|
|
pj_assert(sdp_remote && m_rem);
|
|
|
|
/* Get transport protocol and drop any RTCP-FB flag */
|
|
rem_proto = pjmedia_sdp_transport_get_proto(&m_rem->desc.transport);
|
|
PJMEDIA_TP_PROTO_TRIM_FLAG(rem_proto, PJMEDIA_TP_PROFILE_RTCP_FB);
|
|
|
|
/* Generate transport */
|
|
switch (srtp->setting.use) {
|
|
case PJMEDIA_SRTP_DISABLED:
|
|
/* Should never reach here */
|
|
if (rem_proto == PJMEDIA_TP_PROTO_RTP_SAVP)
|
|
return PJMEDIA_SRTP_ESDPINTRANSPORT;
|
|
return PJ_SUCCESS;
|
|
case PJMEDIA_SRTP_OPTIONAL:
|
|
break;
|
|
case PJMEDIA_SRTP_MANDATORY:
|
|
if (rem_proto != PJMEDIA_TP_PROTO_RTP_SAVP)
|
|
return PJMEDIA_SRTP_ESDPINTRANSPORT;
|
|
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);
|
|
|
|
if (cs_idx == -1)
|
|
return PJMEDIA_SRTP_ENOTSUPCRYPTO;
|
|
|
|
if (tmp_rx_crypto.key.slen !=
|
|
(int)crypto_suites[cs_idx].cipher_key_len)
|
|
return PJMEDIA_SRTP_EINKEYLEN;
|
|
|
|
srtp->srtp_ctx.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:
|
|
/* Should never reach here */
|
|
break;
|
|
|
|
case PJMEDIA_SRTP_OPTIONAL:
|
|
/* Bypass SDES if remote uses RTP/AVP and:
|
|
* - has no crypto-attr, or
|
|
* - has no matching crypto
|
|
*/
|
|
if ((!has_crypto_attr || matched_idx == -1) &&
|
|
!PJMEDIA_TP_PROTO_HAS_FLAG(rem_proto,
|
|
PJMEDIA_TP_PROFILE_SRTP))
|
|
{
|
|
return PJ_SUCCESS;
|
|
}
|
|
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->srtp_ctx.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->srtp_ctx.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. */
|
|
}
|
|
|
|
/* Update transport description in local media SDP */
|
|
m_loc->desc.transport = m_rem->desc.transport;
|
|
}
|
|
|
|
return PJ_SUCCESS;
|
|
}
|
|
|
|
|
|
static pj_status_t fill_local_crypto(pj_pool_t *pool,
|
|
const pjmedia_sdp_media *m_loc,
|
|
pjmedia_srtp_crypto loc_crypto[],
|
|
int *count)
|
|
{
|
|
int i;
|
|
int crypto_count = 0;
|
|
pj_status_t status = PJ_SUCCESS;
|
|
|
|
for (i = 0; i < *count; ++i) {
|
|
pj_bzero(&loc_crypto[i], sizeof(loc_crypto[i]));
|
|
}
|
|
|
|
for (i = 0; i < (int)m_loc->attr_count; ++i) {
|
|
pjmedia_srtp_crypto tmp_crypto;
|
|
int loc_tag;
|
|
|
|
if (pj_stricmp(&m_loc->attr[i]->name, &ID_CRYPTO) != 0)
|
|
continue;
|
|
|
|
status = parse_attr_crypto(pool, m_loc->attr[i],
|
|
&tmp_crypto, &loc_tag);
|
|
if (status != PJ_SUCCESS)
|
|
return status;
|
|
|
|
if (loc_tag <= 0 || loc_tag > *count)
|
|
return PJMEDIA_SRTP_ESDPINCRYPTOTAG;
|
|
|
|
loc_crypto[loc_tag-1] = tmp_crypto;
|
|
++crypto_count;
|
|
}
|
|
*count = crypto_count;
|
|
return status;
|
|
}
|
|
|
|
|
|
static pj_status_t sdes_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->user_data;
|
|
pjmedia_sdp_media *m_rem, *m_loc;
|
|
pj_status_t status;
|
|
unsigned i;
|
|
pjmedia_srtp_crypto loc_crypto[PJMEDIA_SRTP_MAX_CRYPTOS];
|
|
int loc_cryto_cnt = PJMEDIA_SRTP_MAX_CRYPTOS;
|
|
pjmedia_srtp_crypto tmp_tx_crypto;
|
|
pj_bool_t has_crypto_attr = PJ_FALSE;
|
|
int rem_tag;
|
|
int j;
|
|
|
|
|
|
m_rem = sdp_remote->media[media_index];
|
|
m_loc = sdp_local->media[media_index];
|
|
|
|
/* Verify media transport, it has to be RTP/AVP or RTP/SAVP */
|
|
{
|
|
pj_uint32_t rem_proto;
|
|
|
|
/* Get transport protocol and drop any RTCP-FB flag */
|
|
rem_proto = pjmedia_sdp_transport_get_proto(&m_rem->desc.transport);
|
|
PJMEDIA_TP_PROTO_TRIM_FLAG(rem_proto, PJMEDIA_TP_PROFILE_RTCP_FB);
|
|
if (rem_proto != PJMEDIA_TP_PROTO_RTP_AVP &&
|
|
rem_proto != PJMEDIA_TP_PROTO_RTP_SAVP)
|
|
{
|
|
return PJMEDIA_SRTP_ESDPINTRANSPORT;
|
|
}
|
|
|
|
/* Also check if peer signal SRTP as mandatory */
|
|
if (rem_proto == PJMEDIA_TP_PROTO_RTP_SAVP)
|
|
srtp->peer_use = PJMEDIA_SRTP_MANDATORY;
|
|
else
|
|
srtp->peer_use = PJMEDIA_SRTP_OPTIONAL;
|
|
}
|
|
|
|
/* For answerer side, SRTP crypto policies have been populated in
|
|
* media_encode_sdp(). Check if the key changes on the local SDP.
|
|
*/
|
|
if (!srtp->offerer_side) {
|
|
if (srtp->srtp_ctx.tx_policy_neg.name.slen == 0)
|
|
return PJ_SUCCESS;
|
|
|
|
/* Get the local crypto. */
|
|
fill_local_crypto(srtp->pool, m_loc, loc_crypto, &loc_cryto_cnt);
|
|
|
|
if (loc_cryto_cnt == 0)
|
|
return PJ_SUCCESS;
|
|
|
|
if ((pj_stricmp(&srtp->srtp_ctx.tx_policy_neg.name,
|
|
&loc_crypto[0].name) == 0) &&
|
|
(pj_stricmp(&srtp->srtp_ctx.tx_policy_neg.key,
|
|
&loc_crypto[0].key) != 0))
|
|
{
|
|
srtp->srtp_ctx.tx_policy_neg = loc_crypto[0];
|
|
for (i = 0; i<srtp->setting.crypto_count ;++i) {
|
|
if ((pj_stricmp(&srtp->setting.crypto[i].name,
|
|
&loc_crypto[0].name) == 0) &&
|
|
(pj_stricmp(&srtp->setting.crypto[i].key,
|
|
&loc_crypto[0].key) != 0))
|
|
{
|
|
pj_strdup(pool, &srtp->setting.crypto[i].key,
|
|
&loc_crypto[0].key);
|
|
}
|
|
}
|
|
}
|
|
|
|
return PJ_SUCCESS;
|
|
}
|
|
|
|
/* Check remote media transport & set local media transport
|
|
* based on SRTP usage option.
|
|
*/
|
|
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;
|
|
}
|
|
return PJ_SUCCESS;
|
|
} 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 sdes_media_create()
|
|
//if (pj_stricmp(&m_rem->desc.transport, &m_loc->desc.transport)) {
|
|
//DEACTIVATE_MEDIA(pool, m_loc);
|
|
//return PJMEDIA_SDP_EINPROTO;
|
|
//}
|
|
fill_local_crypto(srtp->pool, m_loc, loc_crypto, &loc_cryto_cnt);
|
|
} else if (srtp->setting.use == PJMEDIA_SRTP_MANDATORY) {
|
|
if (srtp->peer_use != PJMEDIA_SRTP_MANDATORY) {
|
|
DEACTIVATE_MEDIA(pool, m_loc);
|
|
return PJMEDIA_SDP_EINPROTO;
|
|
}
|
|
fill_local_crypto(srtp->pool, m_loc, loc_crypto, &loc_cryto_cnt);
|
|
}
|
|
|
|
/* 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;
|
|
|
|
/* 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;
|
|
|
|
|
|
/* Tag range check, our tags in the offer must be in the SRTP
|
|
* setting range, so does the remote answer's. The remote answer's
|
|
* tag must not exceed the tag range of the local offer.
|
|
*/
|
|
if (rem_tag < 1 || rem_tag > (int)srtp->setting.crypto_count ||
|
|
rem_tag > loc_cryto_cnt)
|
|
{
|
|
DEACTIVATE_MEDIA(pool, m_loc);
|
|
return PJMEDIA_SRTP_ESDPINCRYPTOTAG;
|
|
}
|
|
|
|
/* match the crypto name */
|
|
if (pj_stricmp(&tmp_tx_crypto.name, &loc_crypto[rem_tag-1].name))
|
|
{
|
|
DEACTIVATE_MEDIA(pool, m_loc);
|
|
return PJMEDIA_SRTP_ECRYPTONOTMATCH;
|
|
}
|
|
|
|
/* Find the crypto from the local crypto. */
|
|
for (j = 0; j < (int)loc_cryto_cnt; ++j) {
|
|
if (pj_stricmp(&tmp_tx_crypto.name,
|
|
&loc_crypto[j].name) == 0)
|
|
{
|
|
srtp->srtp_ctx.tx_policy_neg = loc_crypto[j];
|
|
break;
|
|
}
|
|
}
|
|
|
|
srtp->srtp_ctx.rx_policy_neg = tmp_tx_crypto;
|
|
}
|
|
|
|
if (srtp->setting.use == PJMEDIA_SRTP_DISABLED) {
|
|
/* should never reach here */
|
|
return PJ_SUCCESS;
|
|
} else if (srtp->setting.use == PJMEDIA_SRTP_OPTIONAL) {
|
|
if (!has_crypto_attr)
|
|
return PJ_SUCCESS;
|
|
} 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. */
|
|
|
|
return PJ_SUCCESS;
|
|
}
|
|
|
|
static pj_status_t sdes_media_stop(pjmedia_transport *tp)
|
|
{
|
|
PJ_UNUSED_ARG(tp);
|
|
return PJ_SUCCESS;
|
|
}
|