2050 lines
64 KiB
C
2050 lines
64 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
|
|
*/
|
|
|
|
#include <pjmedia/clock.h>
|
|
#include <pjmedia/sdp.h>
|
|
#include <pjmedia/transport_ice.h>
|
|
#include <pj/errno.h>
|
|
#include <pj/rand.h>
|
|
#include <pj/ssl_sock.h>
|
|
|
|
/*
|
|
* Include OpenSSL headers
|
|
*/
|
|
#include <openssl/bn.h>
|
|
#include <openssl/err.h>
|
|
#include <openssl/rsa.h>
|
|
#include <openssl/ssl.h>
|
|
|
|
#if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
|
|
defined(OPENSSL_API_COMPAT) && OPENSSL_API_COMPAT >= 0x10100000L
|
|
# define X509_get_notBefore(x) X509_getm_notBefore(x)
|
|
# define X509_get_notAfter(x) X509_getm_notAfter(x)
|
|
#endif
|
|
|
|
/* Set to 1 to enable DTLS-SRTP debugging */
|
|
#define DTLS_DEBUG 0
|
|
|
|
#define NUM_CHANNEL 2
|
|
|
|
enum {
|
|
RTP_CHANNEL = 0,
|
|
RTCP_CHANNEL = 1
|
|
};
|
|
|
|
#define CHANNEL_TO_STRING(idx) (idx == RTP_CHANNEL? "RTP channel": \
|
|
"RTCP channel")
|
|
|
|
/* DTLS-SRTP transport op */
|
|
static pj_status_t dtls_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 dtls_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 dtls_media_start (pjmedia_transport *tp,
|
|
pj_pool_t *tmp_pool,
|
|
const pjmedia_sdp_session *sdp_local,
|
|
const pjmedia_sdp_session *sdp_remote,
|
|
unsigned media_index);
|
|
static pj_status_t dtls_media_stop (pjmedia_transport *tp);
|
|
static pj_status_t dtls_destroy (pjmedia_transport *tp);
|
|
static pj_status_t dtls_on_recv_rtp (pjmedia_transport *tp,
|
|
const void *pkt,
|
|
pj_size_t size);
|
|
static pj_status_t dtls_on_recv_rtcp (pjmedia_transport *tp,
|
|
const void *pkt,
|
|
pj_size_t size);
|
|
|
|
static void on_ice_complete2(pjmedia_transport *tp,
|
|
pj_ice_strans_op op,
|
|
pj_status_t status,
|
|
void *user_data);
|
|
|
|
static void dtls_on_destroy(void *arg);
|
|
|
|
|
|
static pjmedia_transport_op dtls_op =
|
|
{
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
&dtls_on_recv_rtp, // originally send_rtp()
|
|
&dtls_on_recv_rtcp, // originally send_rtcp()
|
|
NULL,
|
|
&dtls_media_create,
|
|
&dtls_encode_sdp,
|
|
&dtls_media_start,
|
|
&dtls_media_stop,
|
|
NULL,
|
|
&dtls_destroy,
|
|
NULL,
|
|
};
|
|
|
|
|
|
typedef enum dtls_setup
|
|
{
|
|
DTLS_SETUP_UNKNOWN,
|
|
DTLS_SETUP_ACTPASS,
|
|
DTLS_SETUP_ACTIVE,
|
|
DTLS_SETUP_PASSIVE
|
|
} dtls_setup;
|
|
|
|
typedef struct dtls_srtp dtls_srtp;
|
|
|
|
typedef struct dtls_srtp_channel
|
|
{
|
|
dtls_srtp *dtls_srtp;
|
|
unsigned channel;
|
|
} dtls_srtp_channel;
|
|
|
|
typedef struct dtls_srtp
|
|
{
|
|
pjmedia_transport base;
|
|
pj_pool_t *pool;
|
|
transport_srtp *srtp;
|
|
|
|
dtls_setup setup;
|
|
unsigned long last_err;
|
|
pj_bool_t use_ice;
|
|
dtls_srtp_channel channel[NUM_CHANNEL];
|
|
pj_bool_t nego_started[NUM_CHANNEL];
|
|
pj_bool_t nego_completed[NUM_CHANNEL];
|
|
pj_str_t rem_fingerprint; /* Remote fingerprint in SDP */
|
|
pj_status_t rem_fprint_status; /* Fingerprint verif. status */
|
|
pj_sockaddr rem_addr; /* Remote address (from SDP/RTP)*/
|
|
pj_sockaddr rem_rtcp; /* Remote RTCP address (SDP) */
|
|
pj_bool_t pending_start; /* media_start() invoked but DTLS
|
|
nego not done yet, so start
|
|
the SRTP once the nego done */
|
|
pj_bool_t is_destroying; /* DTLS being destroyed? */
|
|
pj_bool_t got_keys; /* DTLS nego done & keys ready */
|
|
pjmedia_srtp_crypto tx_crypto[NUM_CHANNEL];
|
|
pjmedia_srtp_crypto rx_crypto[NUM_CHANNEL];
|
|
|
|
char buf[NUM_CHANNEL][PJMEDIA_MAX_MTU];
|
|
pjmedia_clock *clock[NUM_CHANNEL];/* Timer workaround for retrans */
|
|
|
|
SSL_CTX *ossl_ctx[NUM_CHANNEL];
|
|
SSL *ossl_ssl[NUM_CHANNEL];
|
|
BIO *ossl_rbio[NUM_CHANNEL];
|
|
BIO *ossl_wbio[NUM_CHANNEL];
|
|
pj_lock_t *ossl_lock;
|
|
} dtls_srtp;
|
|
|
|
|
|
static const pj_str_t ID_TP_DTLS_SRTP = { "UDP/TLS/RTP/SAVP", 16 };
|
|
static const pj_str_t ID_SETUP = { "setup", 5 };
|
|
static const pj_str_t ID_ACTPASS = { "actpass", 7 };
|
|
static const pj_str_t ID_ACTIVE = { "active", 6 };
|
|
static const pj_str_t ID_PASSIVE = { "passive", 7 };
|
|
static const pj_str_t ID_FINGERPRINT = { "fingerprint", 11 };
|
|
|
|
/* Map of OpenSSL-pjmedia SRTP cryptos. Currently OpenSSL seems to
|
|
* support few cryptos only (based on ssl/d1_srtp.c of OpenSSL 1.1.0c).
|
|
*/
|
|
#define OPENSSL_PROFILE_NUM 4
|
|
|
|
static char* ossl_profiles[OPENSSL_PROFILE_NUM] =
|
|
{
|
|
"SRTP_AES128_CM_SHA1_80",
|
|
"SRTP_AES128_CM_SHA1_32",
|
|
"SRTP_AEAD_AES_256_GCM",
|
|
"SRTP_AEAD_AES_128_GCM"
|
|
};
|
|
static char* pj_profiles[OPENSSL_PROFILE_NUM] =
|
|
{
|
|
"AES_CM_128_HMAC_SHA1_80",
|
|
"AES_CM_128_HMAC_SHA1_32",
|
|
"AEAD_AES_256_GCM",
|
|
"AEAD_AES_128_GCM"
|
|
};
|
|
|
|
/* This will store the valid OpenSSL profiles which is mapped from
|
|
* OpenSSL-pjmedia SRTP cryptos.
|
|
*/
|
|
static char *valid_pj_profiles_list[OPENSSL_PROFILE_NUM];
|
|
static char *valid_ossl_profiles_list[OPENSSL_PROFILE_NUM];
|
|
static unsigned valid_profiles_cnt;
|
|
|
|
|
|
/* Certificate & private key */
|
|
static X509 *dtls_cert;
|
|
static EVP_PKEY *dtls_priv_key;
|
|
static pj_status_t ssl_generate_cert(X509 **p_cert, EVP_PKEY **p_priv_key);
|
|
|
|
static pj_status_t dtls_init()
|
|
{
|
|
/* Make sure OpenSSL library has been initialized */
|
|
{
|
|
pj_ssl_cipher ciphers[1];
|
|
unsigned cipher_num = 1;
|
|
pj_ssl_cipher_get_availables(ciphers, &cipher_num);
|
|
}
|
|
|
|
/* Generate cert if not yet */
|
|
if (!dtls_cert) {
|
|
pj_status_t status;
|
|
status = ssl_generate_cert(&dtls_cert, &dtls_priv_key);
|
|
if (status != PJ_SUCCESS) {
|
|
pj_perror(4, "DTLS-SRTP", status,
|
|
"Failed generating DTLS certificate");
|
|
return status;
|
|
}
|
|
}
|
|
|
|
if (valid_profiles_cnt == 0) {
|
|
unsigned n, j;
|
|
int rc;
|
|
char *p, *end, buf[OPENSSL_PROFILE_NUM*25];
|
|
|
|
/* Create DTLS context */
|
|
SSL_CTX *ctx = SSL_CTX_new(DTLS_method());
|
|
if (ctx == NULL) {
|
|
return PJ_ENOMEM;
|
|
}
|
|
|
|
p = buf;
|
|
end = buf + sizeof(buf);
|
|
for (j=0; j<PJ_ARRAY_SIZE(ossl_profiles); ++j) {
|
|
rc = SSL_CTX_set_tlsext_use_srtp(ctx, ossl_profiles[j]);
|
|
if (rc == 0) {
|
|
valid_pj_profiles_list[valid_profiles_cnt] =
|
|
pj_profiles[j];
|
|
valid_ossl_profiles_list[valid_profiles_cnt++] =
|
|
ossl_profiles[j];
|
|
|
|
n = pj_ansi_snprintf(p, end - p, ":%s", pj_profiles[j]);
|
|
p += n;
|
|
}
|
|
}
|
|
SSL_CTX_free(ctx);
|
|
|
|
if (valid_profiles_cnt > 0) {
|
|
PJ_LOG(4,("DTLS-SRTP", "%s profile is supported", buf));
|
|
} else {
|
|
PJ_PERROR(4, ("DTLS-SRTP", PJMEDIA_SRTP_DTLS_ENOPROFILE,
|
|
"Error getting SRTP profile"));
|
|
|
|
return PJMEDIA_SRTP_DTLS_ENOPROFILE;
|
|
}
|
|
}
|
|
|
|
return PJ_SUCCESS;
|
|
}
|
|
|
|
static void dtls_deinit()
|
|
{
|
|
if (dtls_cert) {
|
|
X509_free(dtls_cert);
|
|
dtls_cert = NULL;
|
|
|
|
EVP_PKEY_free(dtls_priv_key);
|
|
dtls_priv_key = NULL;
|
|
}
|
|
|
|
valid_profiles_cnt = 0;
|
|
}
|
|
|
|
|
|
/* Create DTLS-SRTP keying instance */
|
|
static pj_status_t dtls_create(transport_srtp *srtp,
|
|
pjmedia_transport **p_keying)
|
|
{
|
|
dtls_srtp *ds;
|
|
pj_pool_t *pool;
|
|
pj_status_t status;
|
|
|
|
pool = pj_pool_create(srtp->pool->factory, "dtls%p",
|
|
2000, 256, NULL);
|
|
ds = PJ_POOL_ZALLOC_T(pool, dtls_srtp);
|
|
ds->pool = pool;
|
|
|
|
pj_ansi_strxcpy(ds->base.name, pool->obj_name, PJ_MAX_OBJ_NAME);
|
|
ds->base.type = (pjmedia_transport_type)PJMEDIA_SRTP_KEYING_DTLS_SRTP;
|
|
ds->base.op = &dtls_op;
|
|
ds->base.user_data = srtp;
|
|
ds->srtp = srtp;
|
|
|
|
/* Setup group lock handler for destroy and callback synchronization */
|
|
if (srtp->base.grp_lock) {
|
|
pj_grp_lock_t *grp_lock = srtp->base.grp_lock;
|
|
|
|
ds->base.grp_lock = grp_lock;
|
|
pj_grp_lock_add_ref(grp_lock);
|
|
pj_grp_lock_add_handler(grp_lock, pool, ds, &dtls_on_destroy);
|
|
} else {
|
|
status = pj_lock_create_recursive_mutex(ds->pool, "dtls_ssl_lock%p",
|
|
&ds->ossl_lock);
|
|
if (status != PJ_SUCCESS)
|
|
return status;
|
|
}
|
|
|
|
*p_keying = &ds->base;
|
|
PJ_LOG(5,(srtp->pool->obj_name, "SRTP keying DTLS-SRTP created"));
|
|
return PJ_SUCCESS;
|
|
}
|
|
|
|
|
|
/* Lock/unlock for DTLS states access protection */
|
|
|
|
static void DTLS_LOCK(dtls_srtp *ds) {
|
|
if (ds->base.grp_lock)
|
|
pj_grp_lock_acquire(ds->base.grp_lock);
|
|
else
|
|
pj_lock_acquire(ds->ossl_lock);
|
|
}
|
|
|
|
static pj_status_t DTLS_TRY_LOCK(dtls_srtp *ds) {
|
|
if (ds->base.grp_lock)
|
|
return pj_grp_lock_tryacquire(ds->base.grp_lock);
|
|
else
|
|
return pj_lock_tryacquire(ds->ossl_lock);
|
|
}
|
|
|
|
static void DTLS_UNLOCK(dtls_srtp *ds) {
|
|
if (ds->base.grp_lock)
|
|
pj_grp_lock_release(ds->base.grp_lock);
|
|
else
|
|
pj_lock_release(ds->ossl_lock);
|
|
}
|
|
|
|
|
|
/**
|
|
* Mapping from OpenSSL error codes to pjlib error space.
|
|
*/
|
|
#define PJ_SSL_ERRNO_START (PJ_ERRNO_START_USER + \
|
|
PJ_ERRNO_SPACE_SIZE*6)
|
|
|
|
#define PJ_SSL_ERRNO_SPACE_SIZE PJ_ERRNO_SPACE_SIZE
|
|
|
|
/* Expected maximum value of reason component in OpenSSL error code */
|
|
#define MAX_OSSL_ERR_REASON 1200
|
|
|
|
static pj_status_t STATUS_FROM_SSL_ERR(dtls_srtp *ds,
|
|
unsigned long err)
|
|
{
|
|
pj_status_t status;
|
|
|
|
/* General SSL error, dig more from OpenSSL error queue */
|
|
if (err == SSL_ERROR_SSL)
|
|
err = ERR_get_error();
|
|
|
|
/* OpenSSL error range is much wider than PJLIB errno space, so
|
|
* if it exceeds the space, only the error reason will be kept.
|
|
* Note that the last native error will be kept as is and can be
|
|
* retrieved via SSL socket info.
|
|
*/
|
|
status = ERR_GET_LIB(err)*MAX_OSSL_ERR_REASON + ERR_GET_REASON(err);
|
|
if (status > PJ_SSL_ERRNO_SPACE_SIZE)
|
|
status = ERR_GET_REASON(err);
|
|
|
|
if (status != PJ_SUCCESS)
|
|
status += PJ_SSL_ERRNO_START;
|
|
|
|
ds->last_err = err;
|
|
return status;
|
|
}
|
|
|
|
|
|
static pj_status_t GET_SSL_STATUS(dtls_srtp *ds)
|
|
{
|
|
return STATUS_FROM_SSL_ERR(ds, ERR_get_error());
|
|
}
|
|
|
|
|
|
/* SSL cert verification callback. */
|
|
static int verify_cb(int preverify_ok, X509_STORE_CTX *x509_ctx)
|
|
{
|
|
PJ_UNUSED_ARG(preverify_ok);
|
|
PJ_UNUSED_ARG(x509_ctx);
|
|
/* Just skip it for now (as usually it's a self-signed cert) */
|
|
return 1;
|
|
}
|
|
|
|
/* Get fingerprint from TLS cert, output is formatted for SDP a=fingerprint,
|
|
* e.g: "SHA-256 XX:XX:XX...". If is_sha256 is true, SHA-256 hash algo will
|
|
* be used, otherwise it is SHA-1.
|
|
*/
|
|
static pj_status_t ssl_get_fingerprint(X509 *cert, pj_bool_t is_sha256,
|
|
char *buf, pj_size_t *buf_len)
|
|
{
|
|
unsigned int len, st_out_len, i;
|
|
unsigned char tmp[EVP_MAX_MD_SIZE];
|
|
char *p, *end=buf+*buf_len;
|
|
|
|
if (!X509_digest(cert, (is_sha256?EVP_sha256():EVP_sha1()), tmp, &len))
|
|
return PJ_EUNKNOWN;
|
|
|
|
st_out_len = len*3 + (is_sha256? 7 : 5);
|
|
if (*buf_len < st_out_len + 1)
|
|
return PJ_ETOOSMALL;
|
|
|
|
/* Format fingerprint to "SHA-256 XX:XX:XX..." */
|
|
p = buf;
|
|
p += pj_ansi_snprintf(p, end-p, "SHA-%s %.2X",
|
|
(is_sha256?"256":"1"), tmp[0]);
|
|
for (i=1; i<len; ++i)
|
|
p += pj_ansi_snprintf(p, end-p, ":%.2X", tmp[i]);
|
|
|
|
*buf_len = st_out_len;
|
|
|
|
return PJ_SUCCESS;
|
|
}
|
|
|
|
/* Generate self-signed cert */
|
|
static pj_status_t ssl_generate_cert(X509 **p_cert, EVP_PKEY **p_priv_key)
|
|
{
|
|
BIGNUM *bne = NULL;
|
|
RSA *rsa_key = NULL;
|
|
X509_NAME *cert_name = NULL;
|
|
X509 *cert = NULL;
|
|
EVP_PKEY *priv_key = NULL;
|
|
|
|
/* Create big number */
|
|
bne = BN_new();
|
|
if (!bne) goto on_error;
|
|
if (!BN_set_word(bne, RSA_F4)) goto on_error;
|
|
|
|
/* Generate RSA key */
|
|
rsa_key = RSA_new();
|
|
if (!rsa_key) goto on_error;
|
|
if (!RSA_generate_key_ex(rsa_key, 2048, bne, NULL)) goto on_error;
|
|
|
|
/* Create private key */
|
|
priv_key = EVP_PKEY_new();
|
|
if (!priv_key) goto on_error;
|
|
if (!EVP_PKEY_assign_RSA(priv_key, rsa_key)) goto on_error;
|
|
rsa_key = NULL;
|
|
|
|
/* Create certificate */
|
|
cert = X509_new();
|
|
if (!cert) goto on_error;
|
|
|
|
/* Set version to 3 (2 = x509v3) */
|
|
X509_set_version(cert, 2);
|
|
|
|
/* Set serial number */
|
|
ASN1_INTEGER_set(X509_get_serialNumber(cert), pj_rand());
|
|
|
|
/* Set valid period */
|
|
X509_gmtime_adj(X509_get_notBefore(cert), -60*60*24);
|
|
X509_gmtime_adj(X509_get_notAfter(cert), 60*60*24*365);
|
|
|
|
/* Set subject name */
|
|
cert_name = X509_get_subject_name(cert);
|
|
if (!cert_name) goto on_error;
|
|
if (!X509_NAME_add_entry_by_txt(cert_name, "CN", MBSTRING_ASC,
|
|
(const unsigned char*)"pjmedia.pjsip.org",
|
|
-1, -1, 0)) goto on_error;
|
|
|
|
/* Set the issuer name (to subject name as this is self-signed cert) */
|
|
if (!X509_set_issuer_name(cert, cert_name)) goto on_error;
|
|
|
|
/* Set the public key */
|
|
if (!X509_set_pubkey(cert, priv_key)) goto on_error;
|
|
|
|
/* Sign with the private key */
|
|
if (!X509_sign(cert, priv_key, EVP_sha1())) goto on_error;
|
|
|
|
/* Free big number */
|
|
BN_free(bne);
|
|
|
|
*p_cert = cert;
|
|
*p_priv_key = priv_key;
|
|
return PJ_SUCCESS;
|
|
|
|
on_error:
|
|
if (bne) BN_free(bne);
|
|
if (rsa_key && !priv_key) RSA_free(rsa_key);
|
|
if (priv_key) EVP_PKEY_free(priv_key);
|
|
if (cert) X509_free(cert);
|
|
return PJ_EUNKNOWN;
|
|
}
|
|
|
|
/* Create and initialize new SSL context and instance */
|
|
static pj_status_t ssl_create(dtls_srtp *ds, unsigned idx)
|
|
{
|
|
SSL_CTX *ctx;
|
|
unsigned i;
|
|
int mode, rc;
|
|
|
|
/* Check if it is already instantiated */
|
|
if (ds->ossl_ssl[idx])
|
|
return PJ_SUCCESS;
|
|
|
|
/* Create DTLS context */
|
|
ctx = SSL_CTX_new(DTLS_method());
|
|
if (ctx == NULL) {
|
|
return GET_SSL_STATUS(ds);
|
|
}
|
|
|
|
if (valid_profiles_cnt == 0) {
|
|
SSL_CTX_free(ctx);
|
|
return PJMEDIA_SRTP_DTLS_ENOPROFILE;
|
|
}
|
|
|
|
/* Set crypto */
|
|
if (1) {
|
|
char *p, *end, buf[PJ_ARRAY_SIZE(ossl_profiles)*25];
|
|
unsigned n;
|
|
|
|
p = buf;
|
|
end = buf + sizeof(buf);
|
|
for (i=0; i<ds->srtp->setting.crypto_count && p < end; ++i) {
|
|
pjmedia_srtp_crypto *crypto = &ds->srtp->setting.crypto[i];
|
|
unsigned j;
|
|
for (j=0; j < valid_profiles_cnt; ++j) {
|
|
if (!pj_ansi_strcmp(crypto->name.ptr,
|
|
valid_pj_profiles_list[j]))
|
|
{
|
|
n = pj_ansi_snprintf(p, end-p, ":%s",
|
|
valid_ossl_profiles_list[j]);
|
|
p += n;
|
|
break;
|
|
}
|
|
}
|
|
|
|
}
|
|
rc = SSL_CTX_set_tlsext_use_srtp(ctx, buf+1);
|
|
PJ_LOG(4,(ds->base.name, "Setting crypto [%s], errcode=%d", buf, rc));
|
|
if (rc != 0) {
|
|
SSL_CTX_free(ctx);
|
|
return GET_SSL_STATUS(ds);
|
|
}
|
|
}
|
|
|
|
/* Set ciphers */
|
|
SSL_CTX_set_cipher_list(ctx, PJMEDIA_SRTP_DTLS_OSSL_CIPHERS);
|
|
|
|
/* Set cert & private key */
|
|
rc = SSL_CTX_use_certificate(ctx, dtls_cert);
|
|
pj_assert(rc);
|
|
rc = SSL_CTX_use_PrivateKey(ctx, dtls_priv_key);
|
|
pj_assert(rc);
|
|
rc = SSL_CTX_check_private_key(ctx);
|
|
pj_assert(rc);
|
|
|
|
/* Create SSL instance */
|
|
ds->ossl_ctx[idx] = ctx;
|
|
ds->ossl_ssl[idx] = SSL_new(ds->ossl_ctx[idx]);
|
|
if (ds->ossl_ssl[idx] == NULL) {
|
|
SSL_CTX_free(ctx);
|
|
return GET_SSL_STATUS(ds);
|
|
}
|
|
|
|
/* Set MTU */
|
|
#ifdef DTLS_CTRL_SET_LINK_MTU
|
|
if (!SSL_ctrl(ds->ossl_ssl[idx], DTLS_CTRL_SET_LINK_MTU, PJMEDIA_MAX_MTU,
|
|
NULL))
|
|
{
|
|
PJ_LOG(4, (ds->base.name,
|
|
"Ignored failure in setting MTU to %d (too small?)",
|
|
PJMEDIA_MAX_MTU));
|
|
}
|
|
#endif
|
|
|
|
/* SSL verification options, must be mutual auth */
|
|
mode = SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
|
|
SSL_set_verify(ds->ossl_ssl[idx], mode, &verify_cb);
|
|
|
|
/* Setup SSL BIOs */
|
|
ds->ossl_rbio[idx] = BIO_new(BIO_s_mem());
|
|
ds->ossl_wbio[idx] = BIO_new(BIO_s_mem());
|
|
(void)BIO_set_close(ds->ossl_rbio[idx], BIO_CLOSE);
|
|
(void)BIO_set_close(ds->ossl_wbio[idx], BIO_CLOSE);
|
|
SSL_set_bio(ds->ossl_ssl[idx], ds->ossl_rbio[idx], ds->ossl_wbio[idx]);
|
|
|
|
return PJ_SUCCESS;
|
|
}
|
|
|
|
|
|
/* Destroy SSL context and instance */
|
|
static void ssl_destroy(dtls_srtp *ds, unsigned idx)
|
|
{
|
|
DTLS_LOCK(ds);
|
|
|
|
/* Destroy SSL instance */
|
|
if (ds->ossl_ssl[idx]) {
|
|
/**
|
|
* Avoid calling SSL_shutdown() if handshake wasn't completed.
|
|
* OpenSSL 1.0.2f complains if SSL_shutdown() is called during an
|
|
* SSL handshake, while previous versions always return 0.
|
|
*/
|
|
if (SSL_in_init(ds->ossl_ssl[idx]) == 0) {
|
|
SSL_shutdown(ds->ossl_ssl[idx]);
|
|
}
|
|
SSL_free(ds->ossl_ssl[idx]); /* this will also close BIOs */
|
|
ds->ossl_ssl[idx] = NULL;
|
|
/* thus reset the BIOs as well */
|
|
ds->ossl_rbio[idx] = NULL;
|
|
ds->ossl_wbio[idx] = NULL;
|
|
}
|
|
|
|
/* Destroy SSL context */
|
|
if (ds->ossl_ctx[idx]) {
|
|
SSL_CTX_free(ds->ossl_ctx[idx]);
|
|
ds->ossl_ctx[idx] = NULL;
|
|
}
|
|
|
|
DTLS_UNLOCK(ds);
|
|
}
|
|
|
|
static pj_status_t ssl_get_srtp_material(dtls_srtp *ds, unsigned idx)
|
|
{
|
|
unsigned char material[SRTP_MAX_KEY_LEN * 2];
|
|
SRTP_PROTECTION_PROFILE *profile;
|
|
int rc, i, crypto_idx = -1;
|
|
pjmedia_srtp_crypto *tx, *rx;
|
|
pj_status_t status = PJ_SUCCESS;
|
|
|
|
DTLS_LOCK(ds);
|
|
|
|
if (!ds->ossl_ssl[idx]) {
|
|
status = PJ_EGONE;
|
|
goto on_return;
|
|
}
|
|
|
|
/* Get selected crypto-suite */
|
|
profile = SSL_get_selected_srtp_profile(ds->ossl_ssl[idx]);
|
|
if (!profile) {
|
|
status = PJMEDIA_SRTP_DTLS_ENOCRYPTO;
|
|
goto on_return;
|
|
}
|
|
|
|
tx = &ds->tx_crypto[idx];
|
|
rx = &ds->rx_crypto[idx];
|
|
pj_bzero(tx, sizeof(*tx));
|
|
pj_bzero(rx, sizeof(*rx));
|
|
for (i=0; i<(int)PJ_ARRAY_SIZE(ossl_profiles); ++i) {
|
|
if (pj_ansi_stricmp(profile->name, ossl_profiles[i])==0) {
|
|
pj_strset2(&tx->name, pj_profiles[i]);
|
|
pj_strset2(&rx->name, pj_profiles[i]);
|
|
crypto_idx = get_crypto_idx(&tx->name);
|
|
break;
|
|
}
|
|
}
|
|
if (crypto_idx == -1) {
|
|
status = PJMEDIA_SRTP_ENOTSUPCRYPTO;
|
|
goto on_return;
|
|
}
|
|
|
|
/* Get keying material from DTLS nego. There seems to be no info about
|
|
* material length returned by SSL_export_keying_material()?
|
|
*/
|
|
rc = SSL_export_keying_material(ds->ossl_ssl[idx], material,
|
|
sizeof(material), "EXTRACTOR-dtls_srtp",
|
|
19, NULL, 0, 0);
|
|
if (rc == 0) {
|
|
status = PJMEDIA_SRTP_EINKEYLEN;
|
|
goto on_return;
|
|
}
|
|
|
|
/* Parse SRTP master key & salt from keying material */
|
|
{
|
|
char *p = (char*)material;
|
|
char *k1, *k2;
|
|
crypto_suite *cs = &crypto_suites[crypto_idx];
|
|
unsigned key_len, salt_len;
|
|
|
|
key_len = cs->cipher_key_len - cs->cipher_salt_len;
|
|
salt_len = cs->cipher_salt_len;
|
|
|
|
tx->key.ptr = (char*)pj_pool_alloc(ds->pool, key_len+salt_len);
|
|
tx->key.slen = key_len+salt_len;
|
|
rx->key.ptr = (char*)pj_pool_alloc(ds->pool, key_len+salt_len);
|
|
rx->key.slen = key_len+salt_len;
|
|
if (ds->setup == DTLS_SETUP_ACTIVE) {
|
|
k1 = tx->key.ptr;
|
|
k2 = rx->key.ptr;
|
|
} else {
|
|
k1 = rx->key.ptr;
|
|
k2 = tx->key.ptr;
|
|
}
|
|
pj_memcpy(k1, p, key_len); p += key_len;
|
|
pj_memcpy(k2, p, key_len); p += key_len;
|
|
pj_memcpy(k1+key_len, p, salt_len); p += salt_len;
|
|
pj_memcpy(k2+key_len, p, salt_len);
|
|
ds->got_keys = PJ_TRUE;
|
|
}
|
|
|
|
on_return:
|
|
DTLS_UNLOCK(ds);
|
|
return status;
|
|
}
|
|
|
|
/* Match remote fingerprint: SDP vs actual */
|
|
static pj_status_t ssl_match_fingerprint(dtls_srtp *ds, unsigned idx)
|
|
{
|
|
X509 *rem_cert;
|
|
pj_bool_t is_sha256;
|
|
char buf[128];
|
|
pj_size_t buf_len = sizeof(buf);
|
|
pj_status_t status;
|
|
|
|
/* Check hash algo, currently we only support SHA-256 & SHA-1 */
|
|
if (!pj_strnicmp2(&ds->rem_fingerprint, "SHA-256 ", 8))
|
|
is_sha256 = PJ_TRUE;
|
|
else if (!pj_strnicmp2(&ds->rem_fingerprint, "SHA-1 ", 6))
|
|
is_sha256 = PJ_FALSE;
|
|
else {
|
|
PJ_LOG(4,(ds->base.name, "Hash algo specified in remote SDP for "
|
|
"its DTLS certificate fingerprint is not supported"));
|
|
return PJ_ENOTSUP;
|
|
}
|
|
|
|
DTLS_LOCK(ds);
|
|
if (!ds->ossl_ssl[idx]) {
|
|
DTLS_UNLOCK(ds);
|
|
return PJ_EGONE;
|
|
}
|
|
|
|
/* Get remote cert & calculate the hash */
|
|
rem_cert = SSL_get_peer_certificate(ds->ossl_ssl[idx]);
|
|
|
|
DTLS_UNLOCK(ds);
|
|
|
|
if (!rem_cert)
|
|
return PJMEDIA_SRTP_DTLS_EPEERNOCERT;
|
|
|
|
status = ssl_get_fingerprint(rem_cert, is_sha256, buf, &buf_len);
|
|
X509_free(rem_cert);
|
|
if (status != PJ_SUCCESS)
|
|
return status;
|
|
|
|
/* Do they match? */
|
|
if (pj_stricmp2(&ds->rem_fingerprint, buf))
|
|
return PJMEDIA_SRTP_DTLS_EFPNOTMATCH;
|
|
|
|
return PJ_SUCCESS;
|
|
}
|
|
|
|
|
|
/* Send data to network */
|
|
static pj_status_t send_raw(dtls_srtp *ds, unsigned idx, const void *buf,
|
|
pj_size_t len)
|
|
{
|
|
#if DTLS_DEBUG
|
|
PJ_LOG(2,(ds->base.name, "DTLS-SRTP %s sending %lu bytes",
|
|
CHANNEL_TO_STRING(idx), (unsigned long)len));
|
|
#endif
|
|
|
|
return (idx == RTP_CHANNEL?
|
|
pjmedia_transport_send_rtp(ds->srtp->member_tp, buf, len):
|
|
pjmedia_transport_send_rtcp(ds->srtp->member_tp, buf, len));
|
|
}
|
|
|
|
|
|
/* Start socket if member transport is UDP */
|
|
static pj_status_t udp_member_transport_media_start(dtls_srtp *ds)
|
|
{
|
|
pjmedia_transport_info info;
|
|
pj_status_t status;
|
|
|
|
if (!ds->srtp->member_tp)
|
|
return PJ_SUCCESS;
|
|
|
|
pjmedia_transport_info_init(&info);
|
|
status = pjmedia_transport_get_info(ds->srtp->member_tp, &info);
|
|
if (status != PJ_SUCCESS)
|
|
return status;
|
|
|
|
if (info.specific_info_cnt == 1 &&
|
|
info.spc_info[0].type == PJMEDIA_TRANSPORT_TYPE_UDP)
|
|
{
|
|
return pjmedia_transport_media_start(ds->srtp->member_tp, 0, 0, 0, 0);
|
|
}
|
|
|
|
return PJ_SUCCESS;
|
|
}
|
|
|
|
|
|
/* Flush write BIO */
|
|
static pj_status_t ssl_flush_wbio(dtls_srtp *ds, unsigned idx)
|
|
{
|
|
pj_size_t len;
|
|
pj_status_t status = PJ_SUCCESS;
|
|
|
|
DTLS_LOCK(ds);
|
|
|
|
if (!ds->ossl_wbio[idx]) {
|
|
DTLS_UNLOCK(ds);
|
|
return PJ_EGONE;
|
|
}
|
|
|
|
/* Check whether there is data to send */
|
|
if (BIO_ctrl_pending(ds->ossl_wbio[idx]) > 0) {
|
|
/* Yes, get and send it */
|
|
len = BIO_read(ds->ossl_wbio[idx], ds->buf[idx], sizeof(ds->buf));
|
|
if (len > 0) {
|
|
DTLS_UNLOCK(ds);
|
|
|
|
status = send_raw(ds, idx, ds->buf[idx], len);
|
|
if (status != PJ_SUCCESS) {
|
|
#if DTLS_DEBUG
|
|
pj_perror(2, ds->base.name, status, "Send error");
|
|
#endif
|
|
/* This error should be recoverable, remote will retransmit
|
|
* its packet when not receiving from us.
|
|
*/
|
|
}
|
|
DTLS_LOCK(ds);
|
|
}
|
|
}
|
|
|
|
if (!ds->ossl_ssl[idx]) {
|
|
DTLS_UNLOCK(ds);
|
|
return PJ_EGONE;
|
|
}
|
|
|
|
/* Just return if handshake completion procedure (key parsing, fingerprint
|
|
* verification, etc) has been done or handshake is still in progress.
|
|
*/
|
|
if (ds->nego_completed[idx] || !SSL_is_init_finished(ds->ossl_ssl[idx])) {
|
|
DTLS_UNLOCK(ds);
|
|
return PJ_SUCCESS;
|
|
}
|
|
|
|
/* Yes, SSL handshake is done! */
|
|
ds->nego_completed[idx] = PJ_TRUE;
|
|
PJ_LOG(2,(ds->base.name, "DTLS-SRTP negotiation for %s completed!",
|
|
CHANNEL_TO_STRING(idx)));
|
|
|
|
DTLS_UNLOCK(ds);
|
|
|
|
/* Stop the retransmission clock. Note that the clock may not be stopped
|
|
* if this function is called from clock thread context. We'll try again
|
|
* later in socket context.
|
|
*/
|
|
if (ds->clock[idx])
|
|
pjmedia_clock_stop(ds->clock[idx]);
|
|
|
|
/* Get SRTP key material */
|
|
status = ssl_get_srtp_material(ds, idx);
|
|
if (status != PJ_SUCCESS) {
|
|
pj_perror(4, ds->base.name, status,
|
|
"Failed to get SRTP material");
|
|
goto on_return;
|
|
}
|
|
|
|
/* Verify remote fingerprint if we've already got one from SDP */
|
|
if (ds->rem_fingerprint.slen && ds->rem_fprint_status == PJ_EPENDING) {
|
|
ds->rem_fprint_status = status = ssl_match_fingerprint(ds, idx);
|
|
if (status != PJ_SUCCESS) {
|
|
pj_perror(4, ds->base.name, status,
|
|
"Fingerprint specified in remote SDP doesn't match "
|
|
"to actual remote certificate fingerprint!");
|
|
goto on_return;
|
|
}
|
|
}
|
|
|
|
/* If media_start() has been called, start SRTP now */
|
|
if (ds->pending_start && idx == RTP_CHANNEL) {
|
|
ds->pending_start = PJ_FALSE;
|
|
ds->srtp->keying_pending_cnt--;
|
|
|
|
/* Copy negotiated policy to SRTP */
|
|
ds->srtp->srtp_ctx.tx_policy_neg = ds->tx_crypto[idx];
|
|
ds->srtp->srtp_ctx.rx_policy_neg = ds->rx_crypto[idx];
|
|
|
|
status = start_srtp(ds->srtp);
|
|
if (status != PJ_SUCCESS)
|
|
pj_perror(4, ds->base.name, status, "Failed starting SRTP");
|
|
} else if (idx == RTCP_CHANNEL) {
|
|
pjmedia_srtp_setting setting;
|
|
|
|
pjmedia_srtp_setting_default (&setting);
|
|
|
|
/* Copy negotiated policy to SRTP */
|
|
ds->srtp->srtp_rtcp.tx_policy_neg = ds->tx_crypto[idx];
|
|
ds->srtp->srtp_rtcp.rx_policy_neg = ds->rx_crypto[idx];
|
|
|
|
status = create_srtp_ctx(ds->srtp, &ds->srtp->srtp_rtcp,
|
|
&setting, &ds->srtp->srtp_rtcp.tx_policy_neg,
|
|
&ds->srtp->srtp_rtcp.rx_policy_neg);
|
|
if (status != PJ_SUCCESS)
|
|
pj_perror(4, ds->base.name, status, "Failed creating SRTP RTCP");
|
|
}
|
|
|
|
on_return:
|
|
if (idx == RTP_CHANNEL && ds->srtp->setting.cb.on_srtp_nego_complete) {
|
|
(*ds->srtp->setting.cb.on_srtp_nego_complete)
|
|
(&ds->srtp->base, status);
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
static void clock_cb(const pj_timestamp *ts, void *user_data)
|
|
{
|
|
dtls_srtp_channel *ds_ch = (dtls_srtp_channel*)user_data;
|
|
dtls_srtp *ds = ds_ch->dtls_srtp;
|
|
unsigned idx = ds_ch->channel;
|
|
pj_status_t status;
|
|
|
|
PJ_UNUSED_ARG(ts);
|
|
|
|
while (1) {
|
|
/* Check if we should quit before trying to acquire the lock. */
|
|
if (ds->nego_completed[idx])
|
|
return;
|
|
|
|
/* To avoid deadlock, we must use TRY_LOCK here. */
|
|
status = DTLS_TRY_LOCK(ds);
|
|
if (status == PJ_SUCCESS)
|
|
break;
|
|
|
|
/* Acquiring lock failed, check if we have been signaled to quit. */
|
|
if (ds->nego_completed[idx])
|
|
return;
|
|
|
|
pj_thread_sleep(20);
|
|
}
|
|
|
|
|
|
if (!ds->ossl_ssl[idx]) {
|
|
DTLS_UNLOCK(ds);
|
|
return;
|
|
}
|
|
|
|
if (DTLSv1_handle_timeout(ds->ossl_ssl[idx]) > 0) {
|
|
DTLS_UNLOCK(ds);
|
|
ssl_flush_wbio(ds, idx);
|
|
} else {
|
|
DTLS_UNLOCK(ds);
|
|
}
|
|
}
|
|
|
|
|
|
/* Asynchronous handshake */
|
|
static pj_status_t ssl_handshake_channel(dtls_srtp *ds, unsigned idx)
|
|
{
|
|
pj_status_t status;
|
|
int err;
|
|
|
|
DTLS_LOCK(ds);
|
|
|
|
/* Init DTLS (if not yet) */
|
|
status = ssl_create(ds, idx);
|
|
if (status != PJ_SUCCESS) {
|
|
DTLS_UNLOCK(ds);
|
|
return status;
|
|
}
|
|
|
|
/* Check if handshake has been initiated or even completed */
|
|
if (ds->nego_started[idx] || SSL_is_init_finished(ds->ossl_ssl[idx])) {
|
|
DTLS_UNLOCK(ds);
|
|
return PJ_SUCCESS;
|
|
}
|
|
|
|
/* Perform SSL handshake */
|
|
if (ds->setup == DTLS_SETUP_ACTIVE) {
|
|
SSL_set_connect_state(ds->ossl_ssl[idx]);
|
|
} else {
|
|
SSL_set_accept_state(ds->ossl_ssl[idx]);
|
|
}
|
|
err = SSL_do_handshake(ds->ossl_ssl[idx]);
|
|
if (err < 0) {
|
|
err = SSL_get_error(ds->ossl_ssl[idx], err);
|
|
|
|
DTLS_UNLOCK(ds);
|
|
|
|
if (err == SSL_ERROR_WANT_READ) {
|
|
status = ssl_flush_wbio(ds, idx);
|
|
if (status != PJ_SUCCESS)
|
|
goto on_return;
|
|
} else if (err != SSL_ERROR_NONE) {
|
|
/* Handshake fails */
|
|
status = STATUS_FROM_SSL_ERR(ds, err);
|
|
pj_perror(2, ds->base.name, status, "SSL_do_handshake() error");
|
|
goto on_return;
|
|
}
|
|
} else {
|
|
DTLS_UNLOCK(ds);
|
|
}
|
|
|
|
/* Create and start clock @4Hz for retransmission */
|
|
if (!ds->clock[idx]) {
|
|
ds->channel[idx].dtls_srtp = ds;
|
|
ds->channel[idx].channel = idx;
|
|
status = pjmedia_clock_create(ds->pool, 4, 1, 1,
|
|
PJMEDIA_CLOCK_NO_HIGHEST_PRIO, clock_cb,
|
|
&ds->channel[idx], &ds->clock[idx]);
|
|
if (status != PJ_SUCCESS)
|
|
goto on_return;
|
|
}
|
|
status = pjmedia_clock_start(ds->clock[idx]);
|
|
if (status != PJ_SUCCESS)
|
|
goto on_return;
|
|
|
|
/* Finally, DTLS nego started! */
|
|
ds->nego_started[idx] = PJ_TRUE;
|
|
PJ_LOG(4,(ds->base.name, "DTLS-SRTP %s negotiation initiated as %s",
|
|
CHANNEL_TO_STRING(idx),
|
|
(ds->setup==DTLS_SETUP_ACTIVE? "client":"server")));
|
|
|
|
on_return:
|
|
if (status != PJ_SUCCESS) {
|
|
ds->nego_completed[idx] = PJ_TRUE;
|
|
if (ds->clock[idx])
|
|
pjmedia_clock_stop(ds->clock[idx]);
|
|
}
|
|
return status;
|
|
}
|
|
|
|
static pj_status_t ssl_handshake(dtls_srtp *ds)
|
|
{
|
|
pj_status_t status;
|
|
|
|
status = ssl_handshake_channel(ds, RTP_CHANNEL);
|
|
if (status != PJ_SUCCESS)
|
|
return status;
|
|
|
|
if (!ds->srtp->use_rtcp_mux)
|
|
status = ssl_handshake_channel(ds, RTCP_CHANNEL);
|
|
|
|
return status;
|
|
}
|
|
|
|
/* Parse a=setup & a=fingerprint in remote SDP to update DTLS-SRTP states
|
|
* 'setup' and 'rem_fingerprint'.
|
|
* TODO: check those attributes in a=acap too?
|
|
*/
|
|
static pj_status_t parse_setup_finger_attr(dtls_srtp *ds,
|
|
pj_bool_t rem_as_offerer,
|
|
const pjmedia_sdp_session *sdp,
|
|
unsigned media_index)
|
|
{
|
|
pjmedia_sdp_media *m;
|
|
pjmedia_sdp_attr *a;
|
|
|
|
m = sdp->media[media_index];
|
|
|
|
/* Parse a=setup */
|
|
a = pjmedia_sdp_media_find_attr(m, &ID_SETUP, NULL);
|
|
if (!a)
|
|
a = pjmedia_sdp_attr_find(sdp->attr_count,
|
|
sdp->attr, &ID_SETUP, NULL);
|
|
if (!a)
|
|
return PJMEDIA_SRTP_ESDPAMBIGUEANS;
|
|
|
|
if (pj_stristr(&a->value, &ID_PASSIVE) ||
|
|
(rem_as_offerer && pj_stristr(&a->value, &ID_ACTPASS)))
|
|
{
|
|
/* Remote offers/answers 'passive' (or offers 'actpass'), so we are
|
|
* the client.
|
|
*/
|
|
ds->setup = DTLS_SETUP_ACTIVE;
|
|
} else if (pj_stristr(&a->value, &ID_ACTIVE)) {
|
|
/* Remote offers/answers 'active' so we are the server. */
|
|
ds->setup = DTLS_SETUP_PASSIVE;
|
|
} else {
|
|
/* Unknown value set in remote a=setup */
|
|
return PJMEDIA_SRTP_ESDPAMBIGUEANS;
|
|
}
|
|
|
|
/* Parse a=fingerprint */
|
|
a = pjmedia_sdp_media_find_attr(m, &ID_FINGERPRINT, NULL);
|
|
if (!a)
|
|
a = pjmedia_sdp_attr_find(sdp->attr_count,
|
|
sdp->attr, &ID_FINGERPRINT,
|
|
NULL);
|
|
if (!a) {
|
|
/* No fingerprint attribute in remote SDP */
|
|
return PJMEDIA_SRTP_DTLS_ENOFPRINT;
|
|
} else {
|
|
pj_str_t rem_fp = a->value;
|
|
pj_strtrim(&rem_fp);
|
|
if (pj_stricmp(&ds->rem_fingerprint, &rem_fp))
|
|
pj_strdup(ds->pool, &ds->rem_fingerprint, &rem_fp);
|
|
}
|
|
|
|
return PJ_SUCCESS;
|
|
}
|
|
|
|
static pj_status_t get_rem_addrs(dtls_srtp *ds,
|
|
const pjmedia_sdp_session *sdp_remote,
|
|
unsigned media_index,
|
|
pj_sockaddr *rem_rtp,
|
|
pj_sockaddr *rem_rtcp,
|
|
pj_bool_t *rtcp_mux)
|
|
{
|
|
pjmedia_sdp_media *m_rem = sdp_remote->media[media_index];
|
|
pjmedia_sdp_conn *conn;
|
|
pjmedia_sdp_attr *a;
|
|
int af = pj_AF_UNSPEC();
|
|
pj_bool_t use_ice_info = PJ_FALSE;
|
|
|
|
/* Init RTP & RTCP address */
|
|
pj_bzero(rem_rtp, sizeof(*rem_rtp));
|
|
pj_bzero(rem_rtcp, sizeof(*rem_rtcp));
|
|
|
|
/* If underlying transport is ICE, get remote addresses from ICE */
|
|
if (ds->use_ice) {
|
|
pjmedia_transport_info info;
|
|
pjmedia_ice_transport_info *ice_info;
|
|
|
|
pjmedia_transport_info_init(&info);
|
|
pjmedia_transport_get_info(ds->srtp->member_tp, &info);
|
|
ice_info = (pjmedia_ice_transport_info*)
|
|
pjmedia_transport_info_get_spc_info(
|
|
&info, PJMEDIA_TRANSPORT_TYPE_ICE);
|
|
if (ice_info) {
|
|
*rem_rtp = ice_info->comp[0].rcand_addr;
|
|
if (ice_info->comp_cnt > 1)
|
|
*rem_rtcp = ice_info->comp[1].rcand_addr;
|
|
|
|
use_ice_info = PJ_TRUE;
|
|
}
|
|
}
|
|
|
|
/* Get remote addresses from SDP */
|
|
if (!use_ice_info) {
|
|
|
|
/* Get RTP address */
|
|
conn = m_rem->conn ? m_rem->conn : sdp_remote->conn;
|
|
if (pj_stricmp2(&conn->net_type, "IN")==0) {
|
|
if (pj_stricmp2(&conn->addr_type, "IP4")==0) {
|
|
af = pj_AF_INET();
|
|
} else if (pj_stricmp2(&conn->addr_type, "IP6")==0) {
|
|
af = pj_AF_INET6();
|
|
}
|
|
}
|
|
if (af != pj_AF_UNSPEC()) {
|
|
pj_sockaddr_init(af, rem_rtp, &conn->addr,
|
|
m_rem->desc.port);
|
|
} else {
|
|
return PJ_EAFNOTSUP;
|
|
}
|
|
|
|
/* Get RTCP address. If "rtcp" attribute is present in the SDP,
|
|
* set the RTCP address from that attribute. Otherwise, calculate
|
|
* from RTP address.
|
|
*/
|
|
a = pjmedia_sdp_attr_find2(m_rem->attr_count, m_rem->attr,
|
|
"rtcp", NULL);
|
|
if (a) {
|
|
pjmedia_sdp_rtcp_attr rtcp;
|
|
pj_status_t status;
|
|
status = pjmedia_sdp_attr_get_rtcp(a, &rtcp);
|
|
if (status == PJ_SUCCESS) {
|
|
if (rtcp.addr.slen) {
|
|
pj_sockaddr_init(af, rem_rtcp, &rtcp.addr,
|
|
(pj_uint16_t)rtcp.port);
|
|
} else {
|
|
pj_sockaddr_init(af, rem_rtcp, NULL,
|
|
(pj_uint16_t)rtcp.port);
|
|
pj_memcpy(pj_sockaddr_get_addr(rem_rtcp),
|
|
pj_sockaddr_get_addr(rem_rtp),
|
|
pj_sockaddr_get_addr_len(rem_rtp));
|
|
}
|
|
}
|
|
}
|
|
if (!pj_sockaddr_has_addr(rem_rtcp)) {
|
|
int rtcp_port;
|
|
pj_memcpy(rem_rtcp, rem_rtp, sizeof(pj_sockaddr));
|
|
rtcp_port = pj_sockaddr_get_port(rem_rtp) + 1;
|
|
pj_sockaddr_set_port(rem_rtcp, (pj_uint16_t)rtcp_port);
|
|
}
|
|
}
|
|
|
|
/* Check if remote indicates the desire to use rtcp-mux in its SDP. */
|
|
if (rtcp_mux) {
|
|
a = pjmedia_sdp_attr_find2(m_rem->attr_count, m_rem->attr,
|
|
"rtcp-mux", NULL);
|
|
*rtcp_mux = (a? PJ_TRUE: PJ_FALSE);
|
|
}
|
|
|
|
return PJ_SUCCESS;
|
|
}
|
|
|
|
/* Check if an incoming packet is a DTLS packet (rfc5764 section 5.1.2) */
|
|
#define IS_DTLS_PKT(pkt, pkt_len) (*(char*)pkt > 19 && *(char*)pkt < 64)
|
|
|
|
|
|
/* Received packet (SSL handshake) from socket */
|
|
static pj_status_t ssl_on_recv_packet(dtls_srtp *ds, unsigned idx,
|
|
const void *data, pj_size_t len)
|
|
{
|
|
char tmp[128];
|
|
pj_size_t nwritten;
|
|
|
|
DTLS_LOCK(ds);
|
|
|
|
if (!ds->ossl_rbio[idx]) {
|
|
DTLS_UNLOCK(ds);
|
|
return PJ_EGONE;
|
|
}
|
|
|
|
nwritten = BIO_write(ds->ossl_rbio[idx], data, (int)len);
|
|
if (nwritten < len) {
|
|
/* Error? */
|
|
pj_status_t status;
|
|
status = GET_SSL_STATUS(ds);
|
|
#if DTLS_DEBUG
|
|
pj_perror(2, ds->base.name, status, "BIO_write() error");
|
|
#endif
|
|
DTLS_UNLOCK(ds);
|
|
return status;
|
|
}
|
|
|
|
if (!ds->ossl_ssl[idx]) {
|
|
DTLS_UNLOCK(ds);
|
|
return PJ_EGONE;
|
|
}
|
|
|
|
/* Consume (and ignore) the packet */
|
|
while (1) {
|
|
int rc = SSL_read(ds->ossl_ssl[idx], tmp, sizeof(tmp));
|
|
if (rc <= 0) {
|
|
#if DTLS_DEBUG
|
|
pj_status_t status = GET_SSL_STATUS(ds);
|
|
if (status != PJ_SUCCESS)
|
|
pj_perror(2, ds->base.name, status, "SSL_read() error");
|
|
#endif
|
|
break;
|
|
}
|
|
}
|
|
|
|
DTLS_UNLOCK(ds);
|
|
|
|
/* Flush anything pending in the write BIO */
|
|
return ssl_flush_wbio(ds, idx);
|
|
}
|
|
|
|
|
|
static void on_ice_complete2(pjmedia_transport *tp,
|
|
pj_ice_strans_op op,
|
|
pj_status_t status,
|
|
void *user_data)
|
|
{
|
|
dtls_srtp *ds = (dtls_srtp*)user_data;
|
|
pj_assert(ds);
|
|
|
|
PJ_UNUSED_ARG(tp);
|
|
|
|
if (op == PJ_ICE_STRANS_OP_NEGOTIATION && status == PJ_SUCCESS &&
|
|
ds->setup == DTLS_SETUP_ACTIVE)
|
|
{
|
|
pj_status_t tmp_st;
|
|
tmp_st = ssl_handshake(ds);
|
|
if (tmp_st != PJ_SUCCESS)
|
|
pj_perror(4, ds->base.name, tmp_st, "Failed starting DTLS nego");
|
|
}
|
|
}
|
|
|
|
|
|
/* *************************************
|
|
*
|
|
* DTLS-SRTP transport keying operations
|
|
*
|
|
* *************************************/
|
|
|
|
static pj_status_t dtls_on_recv(pjmedia_transport *tp, unsigned idx,
|
|
const void *pkt, pj_size_t size)
|
|
{
|
|
dtls_srtp *ds = (dtls_srtp*)tp;
|
|
|
|
DTLS_LOCK(ds);
|
|
|
|
/* Destroy the retransmission clock if handshake has been completed. */
|
|
if (ds->clock[idx] && ds->nego_completed[idx]) {
|
|
pjmedia_clock_destroy(ds->clock[idx]);
|
|
ds->clock[idx] = NULL;
|
|
}
|
|
|
|
if (size < 1 || !IS_DTLS_PKT(pkt, size) || ds->is_destroying) {
|
|
DTLS_UNLOCK(ds);
|
|
return PJ_EIGNORED;
|
|
}
|
|
|
|
#if DTLS_DEBUG
|
|
PJ_LOG(2,(ds->base.name, "DTLS-SRTP %s receiving %lu bytes",
|
|
CHANNEL_TO_STRING(idx), (unsigned long)size));
|
|
#endif
|
|
|
|
/* This is DTLS packet, let's process it. Note that if DTLS nego has
|
|
* been completed, this may be a retransmission (e.g: remote didn't
|
|
* receive our last handshake packet) or just a stray.
|
|
*/
|
|
|
|
/* Check remote address info, reattach member tp if changed */
|
|
if (!ds->use_ice && !ds->nego_completed[idx]) {
|
|
pjmedia_transport_info info;
|
|
pj_bool_t reattach_tp = PJ_FALSE;
|
|
|
|
pjmedia_transport_get_info(ds->srtp->member_tp, &info);
|
|
|
|
if (idx == RTP_CHANNEL &&
|
|
pj_sockaddr_cmp(&ds->rem_addr, &info.src_rtp_name))
|
|
{
|
|
pj_sockaddr_cp(&ds->rem_addr, &info.src_rtp_name);
|
|
reattach_tp = PJ_TRUE;
|
|
} else if (idx == RTCP_CHANNEL && !ds->srtp->use_rtcp_mux &&
|
|
pj_sockaddr_has_addr(&info.src_rtcp_name) &&
|
|
pj_sockaddr_cmp(&ds->rem_rtcp, &info.src_rtcp_name))
|
|
{
|
|
pj_sockaddr_cp(&ds->rem_rtcp, &info.src_rtcp_name);
|
|
reattach_tp = PJ_TRUE;
|
|
}
|
|
|
|
if (reattach_tp) {
|
|
pjmedia_transport_attach_param ap;
|
|
pj_status_t status;
|
|
|
|
/* Attach member transport */
|
|
pj_bzero(&ap, sizeof(ap));
|
|
ap.user_data = ds->srtp;
|
|
if (pj_sockaddr_has_addr(&ds->rem_addr)) {
|
|
pj_sockaddr_cp(&ap.rem_addr, &ds->rem_addr);
|
|
} else {
|
|
pj_sockaddr_init(pj_AF_INET(), &ap.rem_addr, 0, 0);
|
|
}
|
|
if (ds->srtp->use_rtcp_mux) {
|
|
/* Using RTP & RTCP multiplexing */
|
|
pj_sockaddr_cp(&ap.rem_rtcp, &ap.rem_addr);
|
|
} else if (pj_sockaddr_has_addr(&ds->rem_rtcp)) {
|
|
pj_sockaddr_cp(&ap.rem_rtcp, &ds->rem_rtcp);
|
|
} else if (pj_sockaddr_has_addr(&ds->rem_addr)) {
|
|
pj_sockaddr_cp(&ap.rem_rtcp, &ds->rem_addr);
|
|
pj_sockaddr_set_port(&ap.rem_rtcp,
|
|
pj_sockaddr_get_port(&ap.rem_rtcp) + 1);
|
|
} else {
|
|
pj_sockaddr_init(pj_AF_INET(), &ap.rem_rtcp, 0, 0);
|
|
}
|
|
ap.addr_len = pj_sockaddr_get_len(&ap.rem_addr);
|
|
status = pjmedia_transport_attach2(&ds->srtp->base, &ap);
|
|
if (status != PJ_SUCCESS) {
|
|
DTLS_UNLOCK(ds);
|
|
return status;
|
|
}
|
|
|
|
#if DTLS_DEBUG
|
|
{
|
|
char addr[PJ_INET6_ADDRSTRLEN];
|
|
char addr2[PJ_INET6_ADDRSTRLEN];
|
|
PJ_LOG(2,(ds->base.name, "Re-attached transport to update "
|
|
"remote addr=%s remote rtcp=%s",
|
|
pj_sockaddr_print(&ap.rem_addr, addr,
|
|
sizeof(addr), 3),
|
|
pj_sockaddr_print(&ap.rem_rtcp, addr2,
|
|
sizeof(addr2), 3)));
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
/* If our setup is ACTPASS, incoming packet may be a client hello,
|
|
* so let's update setup to PASSIVE and initiate DTLS handshake.
|
|
*/
|
|
if (!ds->nego_started[idx] &&
|
|
(ds->setup == DTLS_SETUP_ACTPASS || ds->setup == DTLS_SETUP_PASSIVE))
|
|
{
|
|
pj_status_t status;
|
|
ds->setup = DTLS_SETUP_PASSIVE;
|
|
status = ssl_handshake_channel(ds, idx);
|
|
if (status != PJ_SUCCESS) {
|
|
DTLS_UNLOCK(ds);
|
|
return status;
|
|
}
|
|
}
|
|
|
|
DTLS_UNLOCK(ds);
|
|
|
|
/* Send it to OpenSSL */
|
|
ssl_on_recv_packet(ds, idx, pkt, size);
|
|
|
|
return PJ_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
* This callback is called by SRTP transport when incoming rtp is received.
|
|
* Originally this is send_rtp() op.
|
|
*/
|
|
static pj_status_t dtls_on_recv_rtp( pjmedia_transport *tp,
|
|
const void *pkt,
|
|
pj_size_t size)
|
|
{
|
|
return dtls_on_recv(tp, RTP_CHANNEL, pkt, size);
|
|
}
|
|
|
|
/*
|
|
* This callback is called by SRTP transport when incoming rtcp is received.
|
|
* Originally this is send_rtcp() op.
|
|
*/
|
|
static pj_status_t dtls_on_recv_rtcp(pjmedia_transport *tp,
|
|
const void *pkt,
|
|
pj_size_t size)
|
|
{
|
|
return dtls_on_recv(tp, RTCP_CHANNEL, pkt, size);
|
|
}
|
|
|
|
static pj_status_t dtls_media_create( pjmedia_transport *tp,
|
|
pj_pool_t *sdp_pool,
|
|
unsigned options,
|
|
const pjmedia_sdp_session *sdp_remote,
|
|
unsigned media_index)
|
|
{
|
|
dtls_srtp *ds = (dtls_srtp*) tp;
|
|
pj_status_t status = PJ_SUCCESS;
|
|
|
|
#if DTLS_DEBUG
|
|
PJ_LOG(2,(ds->base.name, "dtls_media_create()"));
|
|
#endif
|
|
|
|
PJ_UNUSED_ARG(sdp_pool);
|
|
PJ_UNUSED_ARG(options);
|
|
|
|
if (ds->srtp->offerer_side) {
|
|
/* As offerer: do nothing. */
|
|
} else {
|
|
/* As answerer:
|
|
* Check for DTLS-SRTP support in remote SDP. Detect remote
|
|
* support of DTLS-SRTP by inspecting remote SDP offer for
|
|
* SDP a=fingerprint attribute. And currently we only support
|
|
* RTP/AVP transports.
|
|
*/
|
|
pjmedia_sdp_media *m_rem = sdp_remote->media[media_index];
|
|
pjmedia_sdp_attr *attr_fp;
|
|
pj_uint32_t rem_proto = 0;
|
|
|
|
/* Find SDP a=fingerprint line. */
|
|
attr_fp = pjmedia_sdp_media_find_attr(m_rem, &ID_FINGERPRINT, NULL);
|
|
if (!attr_fp)
|
|
attr_fp = pjmedia_sdp_attr_find(sdp_remote->attr_count,
|
|
sdp_remote->attr, &ID_FINGERPRINT,
|
|
NULL);
|
|
|
|
/* Get media transport proto */
|
|
rem_proto = pjmedia_sdp_transport_get_proto(&m_rem->desc.transport);
|
|
if (!PJMEDIA_TP_PROTO_HAS_FLAG(rem_proto, PJMEDIA_TP_PROTO_RTP_AVP) ||
|
|
!attr_fp)
|
|
{
|
|
/* Remote doesn't signal DTLS-SRTP */
|
|
status = PJMEDIA_SRTP_ESDPINTRANSPORT;
|
|
goto on_return;
|
|
}
|
|
|
|
/* Check for a=fingerprint in remote SDP. */
|
|
switch (ds->srtp->setting.use) {
|
|
case PJMEDIA_SRTP_DISABLED:
|
|
status = PJMEDIA_SRTP_ESDPINTRANSPORT;
|
|
goto on_return;
|
|
break;
|
|
case PJMEDIA_SRTP_OPTIONAL:
|
|
break;
|
|
case PJMEDIA_SRTP_MANDATORY:
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Set remote cert fingerprint verification status to PJ_EPENDING */
|
|
ds->rem_fprint_status = PJ_EPENDING;
|
|
|
|
on_return:
|
|
#if DTLS_DEBUG
|
|
if (status != PJ_SUCCESS) {
|
|
pj_perror(4, ds->base.name, status, "dtls_media_create() failed");
|
|
}
|
|
#endif
|
|
return status;
|
|
}
|
|
|
|
static void dtls_media_stop_channel(dtls_srtp *ds, unsigned idx)
|
|
{
|
|
ds->nego_completed[idx] = PJ_TRUE;
|
|
if (ds->clock[idx])
|
|
pjmedia_clock_stop(ds->clock[idx]);
|
|
|
|
/* Reset DTLS state */
|
|
ssl_destroy(ds, idx);
|
|
ds->nego_started[idx] = PJ_FALSE;
|
|
ds->nego_completed[idx] = PJ_FALSE;
|
|
}
|
|
|
|
static pj_status_t dtls_encode_sdp( pjmedia_transport *tp,
|
|
pj_pool_t *sdp_pool,
|
|
pjmedia_sdp_session *sdp_local,
|
|
const pjmedia_sdp_session *sdp_remote,
|
|
unsigned media_index)
|
|
{
|
|
dtls_srtp *ds = (dtls_srtp *)tp;
|
|
pjmedia_sdp_media *m_loc;
|
|
pjmedia_sdp_attr *a;
|
|
pj_bool_t use_ice = PJ_FALSE;
|
|
pj_status_t status = PJ_SUCCESS;
|
|
|
|
#if DTLS_DEBUG
|
|
PJ_LOG(2,(ds->base.name, "dtls_encode_sdp()"));
|
|
#endif
|
|
|
|
PJ_UNUSED_ARG(sdp_pool);
|
|
|
|
m_loc = sdp_local->media[media_index];
|
|
if (ds->srtp->offerer_side) {
|
|
/* As offerer */
|
|
|
|
/* Add attribute a=setup if none (rfc5763 section 5) */
|
|
a = pjmedia_sdp_media_find_attr(m_loc, &ID_SETUP, NULL);
|
|
if (!a)
|
|
a = pjmedia_sdp_attr_find(sdp_local->attr_count,
|
|
sdp_local->attr, &ID_SETUP, NULL);
|
|
if (!a) {
|
|
pj_str_t val;
|
|
|
|
if (ds->setup == DTLS_SETUP_UNKNOWN)
|
|
ds->setup = DTLS_SETUP_ACTPASS;
|
|
|
|
if (ds->setup == DTLS_SETUP_ACTIVE)
|
|
val = ID_ACTIVE;
|
|
else if (ds->setup == DTLS_SETUP_PASSIVE)
|
|
val = ID_PASSIVE;
|
|
else
|
|
val = ID_ACTPASS;
|
|
a = pjmedia_sdp_attr_create(ds->pool, ID_SETUP.ptr, &val);
|
|
pjmedia_sdp_media_add_attr(m_loc, a);
|
|
}
|
|
} else {
|
|
/* As answerer */
|
|
dtls_setup last_setup = ds->setup;
|
|
pj_str_t last_rem_fp = ds->rem_fingerprint;
|
|
pj_bool_t rem_addr_changed = PJ_FALSE;
|
|
|
|
/* Parse a=setup and a=fingerprint */
|
|
status = parse_setup_finger_attr(ds, PJ_TRUE, sdp_remote,
|
|
media_index);
|
|
if (status != PJ_SUCCESS)
|
|
goto on_return;
|
|
|
|
/* Add attribute a=setup:active/passive if we are client/server. */
|
|
a = pjmedia_sdp_attr_create(ds->pool, ID_SETUP.ptr,
|
|
(ds->setup==DTLS_SETUP_ACTIVE? &ID_ACTIVE:&ID_PASSIVE));
|
|
pjmedia_sdp_media_add_attr(m_loc, a);
|
|
|
|
if (last_setup != DTLS_SETUP_UNKNOWN) {
|
|
pj_sockaddr rem_rtp;
|
|
pj_sockaddr rem_rtcp;
|
|
pj_bool_t use_rtcp_mux;
|
|
|
|
status = get_rem_addrs(ds, sdp_remote, media_index, &rem_rtp,
|
|
&rem_rtcp, &use_rtcp_mux);
|
|
if (status == PJ_SUCCESS) {
|
|
if (use_rtcp_mux) {
|
|
/* Remote indicates it wants to use rtcp-mux */
|
|
pjmedia_transport_info info;
|
|
|
|
pjmedia_transport_info_init(&info);
|
|
pjmedia_transport_get_info(ds->srtp->member_tp, &info);
|
|
if (pj_sockaddr_cmp(&info.sock_info.rtp_addr_name,
|
|
&info.sock_info.rtcp_addr_name))
|
|
{
|
|
/* But we do not wish to use rtcp mux */
|
|
use_rtcp_mux = PJ_FALSE;
|
|
}
|
|
}
|
|
if (pj_sockaddr_has_addr(&ds->rem_addr) &&
|
|
pj_sockaddr_has_addr(&rem_rtp) &&
|
|
(pj_sockaddr_cmp(&ds->rem_addr, &rem_rtp) ||
|
|
(!use_rtcp_mux &&
|
|
pj_sockaddr_has_addr(&ds->rem_rtcp) &&
|
|
pj_sockaddr_has_addr(&rem_rtcp) &&
|
|
pj_sockaddr_cmp(&ds->rem_rtcp, &rem_rtcp))))
|
|
{
|
|
rem_addr_changed = PJ_TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Check if remote signals DTLS re-nego by changing its
|
|
* setup/fingerprint in SDP or media transport address in SDP.
|
|
*/
|
|
if ((last_setup != DTLS_SETUP_UNKNOWN && last_setup != ds->setup) ||
|
|
(last_rem_fp.slen &&
|
|
pj_memcmp(&last_rem_fp, &ds->rem_fingerprint, sizeof(pj_str_t)))||
|
|
(rem_addr_changed))
|
|
{
|
|
dtls_media_stop_channel(ds, RTP_CHANNEL);
|
|
dtls_media_stop_channel(ds, RTCP_CHANNEL);
|
|
ds->got_keys = PJ_FALSE;
|
|
ds->rem_fprint_status = PJ_EPENDING;
|
|
}
|
|
}
|
|
|
|
/* Set media transport to UDP/TLS/RTP/SAVP if we are the offerer,
|
|
* otherwise just match it to the offer (currently we only accept
|
|
* UDP/TLS/RTP/SAVP in remote offer though).
|
|
*/
|
|
if (ds->srtp->offerer_side) {
|
|
m_loc->desc.transport = ID_TP_DTLS_SRTP;
|
|
} else {
|
|
m_loc->desc.transport =
|
|
sdp_remote->media[media_index]->desc.transport;
|
|
}
|
|
|
|
/* Add a=fingerprint attribute, fingerprint of our TLS certificate */
|
|
{
|
|
char buf[128];
|
|
pj_size_t buf_len = sizeof(buf);
|
|
pj_str_t fp;
|
|
|
|
status = ssl_get_fingerprint(dtls_cert, PJ_TRUE, buf, &buf_len);
|
|
if (status != PJ_SUCCESS)
|
|
goto on_return;
|
|
|
|
pj_strset(&fp, buf, buf_len);
|
|
a = pjmedia_sdp_attr_create(ds->pool, ID_FINGERPRINT.ptr, &fp);
|
|
pjmedia_sdp_media_add_attr(m_loc, a);
|
|
}
|
|
|
|
if (ds->nego_completed[RTP_CHANNEL]) {
|
|
/* This is subsequent SDP offer/answer and no DTLS re-nego has been
|
|
* signalled.
|
|
*/
|
|
goto on_return;
|
|
}
|
|
|
|
/* Attach member transport, so we can receive DTLS init (if our setup
|
|
* is PASSIVE/ACTPASS) or send DTLS init (if our setup is ACTIVE).
|
|
*/
|
|
{
|
|
pjmedia_transport_attach_param ap;
|
|
pjmedia_transport_info info;
|
|
|
|
pj_bzero(&ap, sizeof(ap));
|
|
ap.user_data = ds->srtp;
|
|
pjmedia_transport_get_info(ds->srtp->member_tp, &info);
|
|
|
|
if (sdp_remote) {
|
|
get_rem_addrs(ds, sdp_remote, media_index, &ds->rem_addr,
|
|
&ds->rem_rtcp, NULL);
|
|
}
|
|
|
|
if (pj_sockaddr_has_addr(&ds->rem_addr)) {
|
|
pj_sockaddr_cp(&ap.rem_addr, &ds->rem_addr);
|
|
} else if (pj_sockaddr_has_addr(&info.sock_info.rtp_addr_name)) {
|
|
pj_sockaddr_cp(&ap.rem_addr, &info.sock_info.rtp_addr_name);
|
|
} else {
|
|
pj_sockaddr_init(pj_AF_INET(), &ap.rem_addr, 0, 0);
|
|
}
|
|
|
|
if (pj_sockaddr_cmp(&info.sock_info.rtp_addr_name,
|
|
&info.sock_info.rtcp_addr_name) == 0)
|
|
{
|
|
/* Using RTP & RTCP multiplexing */
|
|
pj_sockaddr_cp(&ap.rem_rtcp, &ap.rem_addr);
|
|
} else if (pj_sockaddr_has_addr(&ds->rem_rtcp)) {
|
|
pj_sockaddr_cp(&ap.rem_rtcp, &ds->rem_rtcp);
|
|
} else if (pj_sockaddr_has_addr(&info.sock_info.rtcp_addr_name)) {
|
|
pj_sockaddr_cp(&ap.rem_rtcp, &info.sock_info.rtcp_addr_name);
|
|
} else {
|
|
pj_sockaddr_init(pj_AF_INET(), &ap.rem_rtcp, 0, 0);
|
|
}
|
|
|
|
ap.addr_len = pj_sockaddr_get_len(&ap.rem_addr);
|
|
status = pjmedia_transport_attach2(&ds->srtp->base, &ap);
|
|
if (status != PJ_SUCCESS)
|
|
goto on_return;
|
|
|
|
/* Start member transport if it is UDP, so we can receive packet
|
|
* (see also #2097).
|
|
*/
|
|
udp_member_transport_media_start(ds);
|
|
|
|
#if DTLS_DEBUG
|
|
{
|
|
char addr[PJ_INET6_ADDRSTRLEN];
|
|
char addr2[PJ_INET6_ADDRSTRLEN];
|
|
PJ_LOG(2,(ds->base.name, "Attached transport, remote addr=%s "
|
|
"remote rtcp=%s",
|
|
pj_sockaddr_print(&ap.rem_addr, addr2, sizeof(addr2), 3),
|
|
pj_sockaddr_print(&ap.rem_rtcp, addr, sizeof(addr), 3)));
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/* If our setup is ACTIVE and member transport is not ICE,
|
|
* start DTLS nego.
|
|
*/
|
|
if (ds->setup == DTLS_SETUP_ACTIVE) {
|
|
pjmedia_transport_info info;
|
|
pjmedia_ice_transport_info *ice_info;
|
|
|
|
pjmedia_transport_info_init(&info);
|
|
pjmedia_transport_get_info(ds->srtp->member_tp, &info);
|
|
ice_info = (pjmedia_ice_transport_info*)
|
|
pjmedia_transport_info_get_spc_info(
|
|
&info, PJMEDIA_TRANSPORT_TYPE_ICE);
|
|
use_ice = ice_info && ice_info->comp_cnt;
|
|
if (!use_ice) {
|
|
/* Start SSL nego */
|
|
status = ssl_handshake(ds);
|
|
if (status != PJ_SUCCESS)
|
|
goto on_return;
|
|
}
|
|
}
|
|
|
|
on_return:
|
|
#if DTLS_DEBUG
|
|
if (status != PJ_SUCCESS) {
|
|
pj_perror(4, ds->base.name, status, "dtls_encode_sdp() failed");
|
|
}
|
|
#endif
|
|
return status;
|
|
}
|
|
|
|
|
|
static pj_status_t dtls_media_start( pjmedia_transport *tp,
|
|
pj_pool_t *tmp_pool,
|
|
const pjmedia_sdp_session *sdp_local,
|
|
const pjmedia_sdp_session *sdp_remote,
|
|
unsigned media_index)
|
|
{
|
|
dtls_srtp *ds = (dtls_srtp *)tp;
|
|
pj_ice_strans_state ice_state;
|
|
pj_bool_t use_rtcp_mux = PJ_FALSE;
|
|
pj_status_t status = PJ_SUCCESS;
|
|
struct transport_srtp *srtp = (struct transport_srtp*)tp->user_data;
|
|
|
|
#if DTLS_DEBUG
|
|
PJ_LOG(2,(ds->base.name, "dtls_media_start()"));
|
|
#endif
|
|
|
|
PJ_UNUSED_ARG(tmp_pool);
|
|
PJ_UNUSED_ARG(sdp_local);
|
|
|
|
if (ds->srtp->offerer_side) {
|
|
/* As offerer */
|
|
dtls_setup last_setup = ds->setup;
|
|
pj_str_t last_rem_fp = ds->rem_fingerprint;
|
|
|
|
/* Parse a=setup and a=fingerprint */
|
|
status = parse_setup_finger_attr(ds, PJ_FALSE, sdp_remote,
|
|
media_index);
|
|
if (status != PJ_SUCCESS)
|
|
goto on_return;
|
|
|
|
/* Check if remote signals DTLS re-nego by changing its
|
|
* setup/fingerprint in SDP.
|
|
*/
|
|
if ((last_setup != DTLS_SETUP_ACTPASS && last_setup != ds->setup) ||
|
|
(last_rem_fp.slen &&
|
|
pj_memcmp(&last_rem_fp, &ds->rem_fingerprint, sizeof(pj_str_t))))
|
|
{
|
|
dtls_media_stop_channel(ds, RTP_CHANNEL);
|
|
dtls_media_stop_channel(ds, RTCP_CHANNEL);
|
|
ds->got_keys = PJ_FALSE;
|
|
ds->rem_fprint_status = PJ_EPENDING;
|
|
}
|
|
} else {
|
|
/* As answerer */
|
|
|
|
/* Nothing to do? */
|
|
}
|
|
|
|
/* Check and update ICE and rtcp-mux status */
|
|
{
|
|
pjmedia_transport_info info;
|
|
pjmedia_ice_transport_info *ice_info;
|
|
|
|
pjmedia_transport_info_init(&info);
|
|
pjmedia_transport_get_info(ds->srtp->member_tp, &info);
|
|
if (pj_sockaddr_cmp(&info.sock_info.rtp_addr_name,
|
|
&info.sock_info.rtcp_addr_name) == 0)
|
|
{
|
|
ds->srtp->use_rtcp_mux = use_rtcp_mux = PJ_TRUE;
|
|
}
|
|
ice_info = (pjmedia_ice_transport_info*)
|
|
pjmedia_transport_info_get_spc_info(
|
|
&info, PJMEDIA_TRANSPORT_TYPE_ICE);
|
|
ds->use_ice = ice_info && ice_info->active;
|
|
ice_state = ds->use_ice? ice_info->sess_state : 0;
|
|
|
|
/* Update remote RTP & RTCP addresses */
|
|
get_rem_addrs(ds, sdp_remote, media_index, &ds->rem_addr,
|
|
&ds->rem_rtcp, NULL);
|
|
}
|
|
|
|
/* Check if the background DTLS nego has completed */
|
|
if (ds->got_keys) {
|
|
unsigned idx = RTP_CHANNEL;
|
|
|
|
ds->srtp->srtp_ctx.tx_policy_neg = ds->tx_crypto[idx];
|
|
ds->srtp->srtp_ctx.rx_policy_neg = ds->rx_crypto[idx];
|
|
|
|
/* Verify remote fingerprint (if available) */
|
|
if (ds->rem_fingerprint.slen && ds->rem_fprint_status == PJ_EPENDING)
|
|
{
|
|
ds->rem_fprint_status = ssl_match_fingerprint(ds, idx);
|
|
if (ds->rem_fprint_status != PJ_SUCCESS) {
|
|
pj_perror(4, ds->base.name, ds->rem_fprint_status,
|
|
"Fingerprint specified in remote SDP doesn't match "
|
|
"to actual remote certificate fingerprint!");
|
|
return ds->rem_fprint_status;
|
|
}
|
|
}
|
|
|
|
return PJ_SUCCESS;
|
|
}
|
|
|
|
/* SRTP key is not ready, SRTP start is pending */
|
|
ds->srtp->keying_pending_cnt++;
|
|
ds->pending_start = PJ_TRUE;
|
|
|
|
srtp->peer_use = PJMEDIA_SRTP_MANDATORY;
|
|
|
|
/* If our DTLS setup is ACTIVE:
|
|
* - start DTLS nego after ICE nego, or
|
|
* - start it now if there is no ICE.
|
|
*/
|
|
if (ds->setup == DTLS_SETUP_ACTIVE) {
|
|
if (ds->use_ice && ice_state < PJ_ICE_STRANS_STATE_RUNNING) {
|
|
/* Register ourselves to listen to ICE notifications */
|
|
pjmedia_ice_cb ice_cb;
|
|
pj_bzero(&ice_cb, sizeof(ice_cb));
|
|
ice_cb.on_ice_complete2 = &on_ice_complete2;
|
|
pjmedia_ice_add_ice_cb(ds->srtp->member_tp, &ice_cb, ds);
|
|
} else {
|
|
/* This can happen when we are SDP offerer and remote wants
|
|
* PASSIVE DTLS role.
|
|
*/
|
|
pjmedia_transport_attach_param ap;
|
|
pj_bzero(&ap, sizeof(ap));
|
|
ap.user_data = ds->srtp;
|
|
|
|
/* Attach ourselves to member transport for DTLS nego. */
|
|
if (pj_sockaddr_has_addr(&ds->rem_addr))
|
|
pj_sockaddr_cp(&ap.rem_addr, &ds->rem_addr);
|
|
else
|
|
pj_sockaddr_init(pj_AF_INET(), &ap.rem_addr, 0, 0);
|
|
|
|
if (use_rtcp_mux) {
|
|
/* Using RTP & RTCP multiplexing */
|
|
pj_sockaddr_cp(&ap.rem_rtcp, &ds->rem_addr);
|
|
} else if (pj_sockaddr_has_addr(&ds->rem_rtcp)) {
|
|
pj_sockaddr_cp(&ap.rem_rtcp, &ds->rem_rtcp);
|
|
} else if (pj_sockaddr_has_addr(&ds->rem_addr)) {
|
|
pj_sockaddr_cp(&ap.rem_rtcp, &ds->rem_addr);
|
|
pj_sockaddr_set_port(&ap.rem_rtcp,
|
|
pj_sockaddr_get_port(&ap.rem_rtcp) + 1);
|
|
} else {
|
|
pj_sockaddr_init(pj_AF_INET(), &ap.rem_rtcp, 0, 0);
|
|
}
|
|
|
|
ap.addr_len = pj_sockaddr_get_len(&ap.rem_addr);
|
|
status = pjmedia_transport_attach2(&ds->srtp->base, &ap);
|
|
if (status != PJ_SUCCESS)
|
|
goto on_return;
|
|
#if DTLS_DEBUG
|
|
{
|
|
char addr[PJ_INET6_ADDRSTRLEN];
|
|
char addr2[PJ_INET6_ADDRSTRLEN];
|
|
PJ_LOG(2,(ds->base.name, "Attached transport, "
|
|
"remote addr=%s remote rtcp=%s",
|
|
pj_sockaddr_print(&ap.rem_addr, addr,
|
|
sizeof(addr), 3),
|
|
pj_sockaddr_print(&ap.rem_rtcp, addr2,
|
|
sizeof(addr2), 3)));
|
|
}
|
|
#endif
|
|
|
|
status = ssl_handshake(ds);
|
|
if (status != PJ_SUCCESS)
|
|
goto on_return;
|
|
}
|
|
}
|
|
|
|
on_return:
|
|
#if DTLS_DEBUG
|
|
if (status != PJ_SUCCESS) {
|
|
pj_perror(4, ds->base.name, status, "dtls_media_start() failed");
|
|
}
|
|
#endif
|
|
return status;
|
|
}
|
|
|
|
static pj_status_t dtls_media_stop(pjmedia_transport *tp)
|
|
{
|
|
dtls_srtp *ds = (dtls_srtp *)tp;
|
|
|
|
#if DTLS_DEBUG
|
|
PJ_LOG(2,(ds->base.name, "dtls_media_stop()"));
|
|
#endif
|
|
|
|
dtls_media_stop_channel(ds, RTP_CHANNEL);
|
|
dtls_media_stop_channel(ds, RTCP_CHANNEL);
|
|
|
|
ds->setup = DTLS_SETUP_UNKNOWN;
|
|
ds->use_ice = PJ_FALSE;
|
|
ds->got_keys = PJ_FALSE;
|
|
ds->rem_fingerprint.slen = 0;
|
|
ds->rem_fprint_status = PJ_EPENDING;
|
|
|
|
return PJ_SUCCESS;
|
|
}
|
|
|
|
static void dtls_destroy_channel(dtls_srtp *ds, unsigned idx)
|
|
{
|
|
if (ds->clock[idx]) {
|
|
ds->nego_completed[idx] = PJ_TRUE;
|
|
pjmedia_clock_destroy(ds->clock[idx]);
|
|
ds->clock[idx] = NULL;
|
|
}
|
|
ssl_destroy(ds, idx);
|
|
}
|
|
|
|
static void dtls_on_destroy(void *arg) {
|
|
dtls_srtp *ds = (dtls_srtp *)arg;
|
|
|
|
if (ds->ossl_lock)
|
|
pj_lock_destroy(ds->ossl_lock);
|
|
|
|
pj_pool_safe_release(&ds->pool);
|
|
}
|
|
|
|
static pj_status_t dtls_destroy(pjmedia_transport *tp)
|
|
{
|
|
dtls_srtp *ds = (dtls_srtp *)tp;
|
|
|
|
#if DTLS_DEBUG
|
|
PJ_LOG(2,(ds->base.name, "dtls_destroy()"));
|
|
#endif
|
|
|
|
ds->is_destroying = PJ_TRUE;
|
|
|
|
DTLS_LOCK(ds);
|
|
|
|
dtls_destroy_channel(ds, RTP_CHANNEL);
|
|
dtls_destroy_channel(ds, RTCP_CHANNEL);
|
|
|
|
DTLS_UNLOCK(ds);
|
|
|
|
if (ds->base.grp_lock) {
|
|
pj_grp_lock_dec_ref(ds->base.grp_lock);
|
|
} else {
|
|
dtls_on_destroy(tp);
|
|
}
|
|
|
|
return PJ_SUCCESS;
|
|
}
|
|
|
|
|
|
/* Get fingerprint of local DTLS-SRTP certificate. */
|
|
PJ_DEF(pj_status_t) pjmedia_transport_srtp_dtls_get_fingerprint(
|
|
pjmedia_transport *tp,
|
|
const char *hash,
|
|
char *buf, pj_size_t *len)
|
|
{
|
|
PJ_ASSERT_RETURN(dtls_cert, PJ_EINVALIDOP);
|
|
PJ_ASSERT_RETURN(tp && hash && buf && len, PJ_EINVAL);
|
|
PJ_ASSERT_RETURN(pj_ansi_strcmp(hash, "SHA-256")==0 ||
|
|
pj_ansi_strcmp(hash, "SHA-1")==0, PJ_EINVAL);
|
|
PJ_UNUSED_ARG(tp);
|
|
|
|
return ssl_get_fingerprint(dtls_cert,
|
|
pj_ansi_strcmp(hash, "SHA-256")==0,
|
|
buf, len);
|
|
}
|
|
|
|
|
|
/* Manually start DTLS-SRTP negotiation (without SDP offer/answer) */
|
|
PJ_DEF(pj_status_t) pjmedia_transport_srtp_dtls_start_nego(
|
|
pjmedia_transport *tp,
|
|
const pjmedia_srtp_dtls_nego_param *param)
|
|
{
|
|
transport_srtp *srtp = (transport_srtp*)tp;
|
|
dtls_srtp *ds = NULL;
|
|
unsigned j;
|
|
pjmedia_transport_attach_param ap;
|
|
pj_status_t status;
|
|
|
|
PJ_ASSERT_RETURN(tp && param, PJ_EINVAL);
|
|
PJ_ASSERT_RETURN(pj_sockaddr_has_addr(¶m->rem_addr), PJ_EINVAL);
|
|
|
|
/* Find DTLS keying and destroy any other keying. */
|
|
for (j = 0; j < srtp->all_keying_cnt; ++j) {
|
|
if (srtp->all_keying[j]->op == &dtls_op)
|
|
ds = (dtls_srtp*)srtp->all_keying[j];
|
|
else
|
|
pjmedia_transport_close(srtp->all_keying[j]);
|
|
}
|
|
|
|
/* DTLS-SRTP is not enabled */
|
|
if (!ds)
|
|
return PJ_ENOTSUP;
|
|
|
|
/* Set SRTP keying to DTLS-SRTP only */
|
|
srtp->keying_cnt = 1;
|
|
srtp->keying[0] = &ds->base;
|
|
srtp->keying_pending_cnt = 0;
|
|
|
|
/* Apply param to DTLS-SRTP internal states */
|
|
pj_strdup(ds->pool, &ds->rem_fingerprint, ¶m->rem_fingerprint);
|
|
ds->rem_fprint_status = PJ_EPENDING;
|
|
ds->rem_addr = param->rem_addr;
|
|
ds->rem_rtcp = param->rem_rtcp;
|
|
ds->setup = param->is_role_active? DTLS_SETUP_ACTIVE:DTLS_SETUP_PASSIVE;
|
|
|
|
/* Pending start SRTP */
|
|
ds->pending_start = PJ_TRUE;
|
|
srtp->keying_pending_cnt++;
|
|
|
|
/* Attach member transport, so we can send/receive DTLS init packets */
|
|
pj_bzero(&ap, sizeof(ap));
|
|
ap.user_data = ds->srtp;
|
|
pj_sockaddr_cp(&ap.rem_addr, &ds->rem_addr);
|
|
pj_sockaddr_cp(&ap.rem_rtcp, &ds->rem_rtcp);
|
|
if (pj_sockaddr_cmp(&ds->rem_addr, &ds->rem_rtcp) == 0)
|
|
ds->srtp->use_rtcp_mux = PJ_TRUE;
|
|
ap.addr_len = pj_sockaddr_get_len(&ap.rem_addr);
|
|
status = pjmedia_transport_attach2(&ds->srtp->base, &ap);
|
|
if (status != PJ_SUCCESS)
|
|
goto on_return;
|
|
|
|
#if DTLS_DEBUG
|
|
{
|
|
char addr[PJ_INET6_ADDRSTRLEN];
|
|
char addr2[PJ_INET6_ADDRSTRLEN];
|
|
PJ_LOG(2,(ds->base.name, "Attached transport, remote addr=%s "
|
|
"remote rtcp=%s",
|
|
pj_sockaddr_print(&ap.rem_addr, addr, sizeof(addr), 3),
|
|
pj_sockaddr_print(&ap.rem_addr, addr2, sizeof(addr2), 3)));
|
|
}
|
|
#endif
|
|
|
|
/* Start DTLS handshake */
|
|
pj_bzero(&srtp->srtp_ctx.rx_policy_neg,
|
|
sizeof(srtp->srtp_ctx.rx_policy_neg));
|
|
pj_bzero(&srtp->srtp_ctx.tx_policy_neg,
|
|
sizeof(srtp->srtp_ctx.tx_policy_neg));
|
|
status = ssl_handshake(ds);
|
|
if (status != PJ_SUCCESS)
|
|
goto on_return;
|
|
|
|
on_return:
|
|
if (status != PJ_SUCCESS) {
|
|
ssl_destroy(ds, RTP_CHANNEL);
|
|
ssl_destroy(ds, RTCP_CHANNEL);
|
|
}
|
|
return status;
|
|
}
|