gstreamer/ext/dtls/gstdtlsconnection.c
Olivier Crête 8a0d1d85cf dtlsconnection: Ignore OpenSSL system call errors
OpenSSL shouldn't be making real system calls, so we can safely
ignore syscall errors. System interactions should happen through
our BIO. So especially don't look at the system's errno, as it
should be meaningless.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-bad/-/merge_requests/1656>
2020-10-10 15:34:21 +00:00

1324 lines
37 KiB
C

/*
* Copyright (c) 2014, Ericsson AB. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this
* list of conditions and the following disclaimer in the documentation and/or other
* materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
* OF SUCH DAMAGE.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <gst/gst.h>
#include "gstdtlsconnection.h"
#include "gstdtlsagent.h"
#include "gstdtlscertificate.h"
#ifdef __APPLE__
# define __AVAILABILITYMACROS__
# define DEPRECATED_IN_MAC_OS_X_VERSION_10_7_AND_LATER
#endif
#include <openssl/err.h>
#include <openssl/ssl.h>
#ifdef G_OS_WIN32
#include <winsock2.h>
#else
#include <string.h>
#include <errno.h>
#endif
GST_DEBUG_CATEGORY_STATIC (gst_dtls_connection_debug);
#define GST_CAT_DEFAULT gst_dtls_connection_debug
#define SRTP_KEY_LEN 16
#define SRTP_SALT_LEN 14
enum
{
SIGNAL_ON_ENCODER_KEY,
SIGNAL_ON_DECODER_KEY,
SIGNAL_ON_PEER_CERTIFICATE,
NUM_SIGNALS
};
static guint signals[NUM_SIGNALS];
enum
{
PROP_0,
PROP_AGENT,
PROP_CONNECTION_STATE,
NUM_PROPERTIES
};
static GParamSpec *properties[NUM_PROPERTIES];
static int connection_ex_index;
static void handle_timeout (gpointer data, gpointer user_data);
struct _GstDtlsConnectionPrivate
{
SSL *ssl;
BIO *bio;
gboolean is_client;
gboolean is_alive;
gboolean keys_exported;
GstDtlsConnectionState connection_state;
gboolean sent_close_notify;
gboolean received_close_notify;
GMutex mutex;
GCond condition;
gpointer bio_buffer;
gint bio_buffer_len;
gint bio_buffer_offset;
GstDtlsConnectionSendCallback send_callback;
gpointer send_callback_user_data;
GDestroyNotify send_callback_destroy_notify;
gboolean timeout_pending;
GThreadPool *thread_pool;
};
G_DEFINE_TYPE_WITH_CODE (GstDtlsConnection, gst_dtls_connection, G_TYPE_OBJECT,
G_ADD_PRIVATE (GstDtlsConnection)
GST_DEBUG_CATEGORY_INIT (gst_dtls_connection_debug, "dtlsconnection", 0,
"DTLS Connection"));
static void gst_dtls_connection_finalize (GObject * gobject);
static void gst_dtls_connection_set_property (GObject *, guint prop_id,
const GValue *, GParamSpec *);
static void gst_dtls_connection_get_property (GObject *, guint prop_id,
GValue *, GParamSpec *);
static void log_state (GstDtlsConnection *, const gchar * str);
static gboolean export_srtp_keys (GstDtlsConnection *, GError ** err);
static GstFlowReturn openssl_poll (GstDtlsConnection *, gboolean * notify_state,
GError ** err);
static GstFlowReturn handle_error (GstDtlsConnection * self, int ret,
GstResourceError error_type, gboolean * notify_state, GError ** err);
static int openssl_verify_callback (int preverify_ok,
X509_STORE_CTX * x509_ctx);
static BIO_METHOD *BIO_s_gst_dtls_connection (void);
static int bio_method_write (BIO *, const char *data, int size);
static int bio_method_read (BIO *, char *out_buffer, int size);
static long bio_method_ctrl (BIO *, int cmd, long arg1, void *arg2);
static int bio_method_new (BIO *);
static int bio_method_free (BIO *);
static void
gst_dtls_connection_class_init (GstDtlsConnectionClass * klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
gobject_class->set_property = gst_dtls_connection_set_property;
gobject_class->get_property = gst_dtls_connection_get_property;
connection_ex_index =
SSL_get_ex_new_index (0, (gpointer) "gstdtlsagent connection index", NULL,
NULL, NULL);
signals[SIGNAL_ON_DECODER_KEY] =
g_signal_new ("on-decoder-key", G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL,
G_TYPE_NONE, 3, G_TYPE_POINTER, G_TYPE_UINT, G_TYPE_UINT);
signals[SIGNAL_ON_ENCODER_KEY] =
g_signal_new ("on-encoder-key", G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL,
G_TYPE_NONE, 3, G_TYPE_POINTER, G_TYPE_UINT, G_TYPE_UINT);
signals[SIGNAL_ON_PEER_CERTIFICATE] =
g_signal_new ("on-peer-certificate", G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_BOOLEAN, 1, G_TYPE_STRING);
properties[PROP_AGENT] =
g_param_spec_object ("agent",
"DTLS Agent",
"Agent to use in creation of the connection",
GST_TYPE_DTLS_AGENT,
G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
properties[PROP_CONNECTION_STATE] =
g_param_spec_enum ("connection-state",
"Connection State",
"Current connection state",
GST_DTLS_TYPE_CONNECTION_STATE,
GST_DTLS_CONNECTION_STATE_NEW, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
g_object_class_install_properties (gobject_class, NUM_PROPERTIES, properties);
_gst_dtls_init_openssl ();
gobject_class->finalize = gst_dtls_connection_finalize;
}
static void
gst_dtls_connection_init (GstDtlsConnection * self)
{
GstDtlsConnectionPrivate *priv;
self->priv = priv = gst_dtls_connection_get_instance_private (self);
priv->ssl = NULL;
priv->bio = NULL;
priv->is_client = FALSE;
priv->is_alive = TRUE;
priv->keys_exported = FALSE;
priv->bio_buffer = NULL;
priv->bio_buffer_len = 0;
priv->bio_buffer_offset = 0;
g_mutex_init (&priv->mutex);
g_cond_init (&priv->condition);
/* Thread pool for handling timeouts, we only need one thread for that
* really and share threads with all other thread pools around there as
* this is not going to happen very often */
priv->thread_pool = g_thread_pool_new (handle_timeout, self, 1, FALSE, NULL);
g_assert (priv->thread_pool);
priv->timeout_pending = FALSE;
}
static void
gst_dtls_connection_finalize (GObject * gobject)
{
GstDtlsConnection *self = GST_DTLS_CONNECTION (gobject);
GstDtlsConnectionPrivate *priv = self->priv;
g_thread_pool_free (priv->thread_pool, TRUE, TRUE);
priv->thread_pool = NULL;
SSL_free (priv->ssl);
priv->ssl = NULL;
if (priv->send_callback_destroy_notify)
priv->send_callback_destroy_notify (priv->send_callback_user_data);
g_mutex_clear (&priv->mutex);
g_cond_clear (&priv->condition);
GST_DEBUG_OBJECT (self, "finalized");
G_OBJECT_CLASS (gst_dtls_connection_parent_class)->finalize (gobject);
}
#if OPENSSL_VERSION_NUMBER < 0x10100001L
static void
BIO_set_data (BIO * bio, void *ptr)
{
bio->ptr = ptr;
}
static void *
BIO_get_data (BIO * bio)
{
return bio->ptr;
}
static void
BIO_set_shutdown (BIO * bio, int shutdown)
{
bio->shutdown = shutdown;
}
static void
BIO_set_init (BIO * bio, int init)
{
bio->init = init;
}
static X509 *
X509_STORE_CTX_get0_cert (X509_STORE_CTX * ctx)
{
return ctx->cert;
}
#endif
static void
gst_dtls_connection_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec)
{
GstDtlsConnection *self = GST_DTLS_CONNECTION (object);
GstDtlsAgent *agent;
GstDtlsConnectionPrivate *priv = self->priv;
SSL_CTX *ssl_context;
switch (prop_id) {
case PROP_AGENT:
g_return_if_fail (!priv->ssl);
agent = GST_DTLS_AGENT (g_value_get_object (value));
g_return_if_fail (GST_IS_DTLS_AGENT (agent));
ssl_context = _gst_dtls_agent_peek_context (agent);
priv->ssl = SSL_new (ssl_context);
g_return_if_fail (priv->ssl);
priv->bio = BIO_new (BIO_s_gst_dtls_connection ());
g_return_if_fail (priv->bio);
BIO_set_data (priv->bio, self);
SSL_set_bio (priv->ssl, priv->bio, priv->bio);
SSL_set_verify (priv->ssl,
SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT,
openssl_verify_callback);
SSL_set_ex_data (priv->ssl, connection_ex_index, self);
log_state (self, "connection created");
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (self, prop_id, pspec);
}
}
static void
gst_dtls_connection_get_property (GObject * object, guint prop_id,
GValue * value, GParamSpec * pspec)
{
GstDtlsConnection *self = GST_DTLS_CONNECTION (object);
GstDtlsConnectionPrivate *priv = self->priv;
switch (prop_id) {
case PROP_CONNECTION_STATE:
g_mutex_lock (&priv->mutex);
g_value_set_enum (value, priv->connection_state);
g_mutex_unlock (&priv->mutex);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (self, prop_id, pspec);
}
}
gboolean
gst_dtls_connection_start (GstDtlsConnection * self, gboolean is_client,
GError ** err)
{
GstDtlsConnectionPrivate *priv;
gboolean ret;
gboolean notify_state = FALSE;
priv = self->priv;
g_return_val_if_fail (priv->send_callback, FALSE);
g_return_val_if_fail (priv->ssl, FALSE);
g_return_val_if_fail (priv->bio, FALSE);
GST_TRACE_OBJECT (self, "locking @ start");
g_mutex_lock (&priv->mutex);
GST_TRACE_OBJECT (self, "locked @ start");
priv->is_alive = TRUE;
priv->bio_buffer = NULL;
priv->bio_buffer_len = 0;
priv->bio_buffer_offset = 0;
priv->keys_exported = FALSE;
priv->sent_close_notify = FALSE;
priv->received_close_notify = FALSE;
/* Client immediately starts connecting, the server waits for a client to
* start the handshake process */
priv->is_client = is_client;
if (priv->is_client) {
priv->connection_state = GST_DTLS_CONNECTION_STATE_CONNECTING;
notify_state = TRUE;
SSL_set_connect_state (priv->ssl);
} else {
if (priv->connection_state != GST_DTLS_CONNECTION_STATE_NEW) {
priv->connection_state = GST_DTLS_CONNECTION_STATE_NEW;
notify_state = TRUE;
}
SSL_set_accept_state (priv->ssl);
}
log_state (self, "initial state set");
ret = openssl_poll (self, &notify_state, err);
if (ret == GST_FLOW_EOS && err) {
*err =
g_error_new_literal (GST_RESOURCE_ERROR, GST_RESOURCE_ERROR_OPEN_WRITE,
"Connection closed");
}
log_state (self, "first poll done");
GST_TRACE_OBJECT (self, "unlocking @ start");
g_mutex_unlock (&priv->mutex);
if (notify_state) {
g_object_notify_by_pspec (G_OBJECT (self),
properties[PROP_CONNECTION_STATE]);
}
return ret == GST_FLOW_OK;
}
static void
handle_timeout (gpointer data, gpointer user_data)
{
GstDtlsConnection *self = user_data;
GstDtlsConnectionPrivate *priv;
gint ret;
gboolean notify_state = FALSE;
priv = self->priv;
g_mutex_lock (&priv->mutex);
priv->timeout_pending = FALSE;
if (priv->is_alive) {
ret = DTLSv1_handle_timeout (priv->ssl);
GST_DEBUG_OBJECT (self, "handle timeout returned %d, is_alive: %d", ret,
priv->is_alive);
if (ret < 0) {
GST_WARNING_OBJECT (self, "handling timeout failed");
} else if (ret > 0) {
log_state (self, "handling timeout before poll");
openssl_poll (self, &notify_state, NULL);
log_state (self, "handling timeout after poll");
}
}
g_mutex_unlock (&priv->mutex);
if (notify_state) {
g_object_notify_by_pspec (G_OBJECT (self),
properties[PROP_CONNECTION_STATE]);
}
}
static gboolean
schedule_timeout_handling (GstClock * clock, GstClockTime time, GstClockID id,
gpointer user_data)
{
GstDtlsConnection *self = user_data;
g_mutex_lock (&self->priv->mutex);
if (self->priv->is_alive && !self->priv->timeout_pending) {
self->priv->timeout_pending = TRUE;
GST_TRACE_OBJECT (self, "Schedule timeout now");
g_thread_pool_push (self->priv->thread_pool, GINT_TO_POINTER (0xc0ffee),
NULL);
}
g_mutex_unlock (&self->priv->mutex);
return TRUE;
}
static void
gst_dtls_connection_check_timeout_locked (GstDtlsConnection * self)
{
GstDtlsConnectionPrivate *priv;
struct timeval timeout;
gint64 end_time, wait_time;
g_return_if_fail (GST_IS_DTLS_CONNECTION (self));
priv = self->priv;
if (DTLSv1_get_timeout (priv->ssl, &timeout)) {
wait_time = timeout.tv_sec * G_USEC_PER_SEC + timeout.tv_usec;
GST_DEBUG_OBJECT (self, "waiting for %" G_GINT64_FORMAT " usec", wait_time);
if (wait_time) {
GstClock *system_clock = gst_system_clock_obtain ();
GstClockID clock_id;
#ifndef G_DISABLE_ASSERT
GstClockReturn clock_return;
#endif
end_time = gst_clock_get_time (system_clock) + wait_time * GST_USECOND;
clock_id = gst_clock_new_single_shot_id (system_clock, end_time);
#ifndef G_DISABLE_ASSERT
clock_return =
#else
(void)
#endif
gst_clock_id_wait_async (clock_id, schedule_timeout_handling,
g_object_ref (self), (GDestroyNotify) g_object_unref);
g_assert (clock_return == GST_CLOCK_OK);
gst_clock_id_unref (clock_id);
gst_object_unref (system_clock);
} else {
if (self->priv->is_alive && !self->priv->timeout_pending) {
self->priv->timeout_pending = TRUE;
GST_TRACE_OBJECT (self, "Schedule timeout now");
g_thread_pool_push (self->priv->thread_pool, GINT_TO_POINTER (0xc0ffee),
NULL);
}
}
} else {
GST_DEBUG_OBJECT (self, "no timeout set");
}
}
void
gst_dtls_connection_check_timeout (GstDtlsConnection * self)
{
GstDtlsConnectionPrivate *priv;
g_return_if_fail (GST_IS_DTLS_CONNECTION (self));
priv = self->priv;
GST_TRACE_OBJECT (self, "locking @ start_timeout");
g_mutex_lock (&priv->mutex);
GST_TRACE_OBJECT (self, "locked @ start_timeout");
gst_dtls_connection_check_timeout_locked (self);
g_mutex_unlock (&priv->mutex);
GST_TRACE_OBJECT (self, "unlocking @ start_timeout");
}
void
gst_dtls_connection_stop (GstDtlsConnection * self)
{
gboolean notify_state = FALSE;
g_return_if_fail (GST_IS_DTLS_CONNECTION (self));
g_return_if_fail (self->priv->ssl);
g_return_if_fail (self->priv->bio);
GST_DEBUG_OBJECT (self, "stopping connection");
GST_TRACE_OBJECT (self, "locking @ stop");
g_mutex_lock (&self->priv->mutex);
GST_TRACE_OBJECT (self, "locked @ stop");
self->priv->is_alive = FALSE;
if (self->priv->connection_state != GST_DTLS_CONNECTION_STATE_FAILED
&& self->priv->connection_state != GST_DTLS_CONNECTION_STATE_CLOSED) {
self->priv->connection_state = GST_DTLS_CONNECTION_STATE_CLOSED;
notify_state = TRUE;
}
GST_TRACE_OBJECT (self, "signaling @ stop");
g_cond_signal (&self->priv->condition);
GST_TRACE_OBJECT (self, "signaled @ stop");
GST_TRACE_OBJECT (self, "unlocking @ stop");
g_mutex_unlock (&self->priv->mutex);
GST_DEBUG_OBJECT (self, "stopped connection");
if (notify_state) {
g_object_notify_by_pspec (G_OBJECT (self),
properties[PROP_CONNECTION_STATE]);
}
}
void
gst_dtls_connection_close (GstDtlsConnection * self)
{
gboolean notify_state = FALSE;
g_return_if_fail (GST_IS_DTLS_CONNECTION (self));
g_return_if_fail (self->priv->ssl);
g_return_if_fail (self->priv->bio);
GST_DEBUG_OBJECT (self, "closing connection");
GST_TRACE_OBJECT (self, "locking @ close");
g_mutex_lock (&self->priv->mutex);
GST_TRACE_OBJECT (self, "locked @ close");
if (self->priv->is_alive) {
self->priv->is_alive = FALSE;
g_cond_signal (&self->priv->condition);
}
if (self->priv->connection_state != GST_DTLS_CONNECTION_STATE_FAILED
&& self->priv->connection_state != GST_DTLS_CONNECTION_STATE_CLOSED) {
self->priv->connection_state = GST_DTLS_CONNECTION_STATE_CLOSED;
notify_state = TRUE;
}
GST_TRACE_OBJECT (self, "unlocking @ close");
g_mutex_unlock (&self->priv->mutex);
GST_DEBUG_OBJECT (self, "closed connection");
if (notify_state) {
g_object_notify_by_pspec (G_OBJECT (self),
properties[PROP_CONNECTION_STATE]);
}
}
void
gst_dtls_connection_set_send_callback (GstDtlsConnection * self,
GstDtlsConnectionSendCallback callback, gpointer user_data,
GDestroyNotify destroy_notify)
{
GstDtlsConnectionPrivate *priv;
g_return_if_fail (GST_IS_DTLS_CONNECTION (self));
priv = self->priv;
GST_TRACE_OBJECT (self, "locking @ set_send_callback");
g_mutex_lock (&priv->mutex);
GST_TRACE_OBJECT (self, "locked @ set_send_callback");
if (priv->send_callback_destroy_notify)
priv->send_callback_destroy_notify (priv->send_callback_user_data);
priv->send_callback = callback;
priv->send_callback_user_data = user_data;
priv->send_callback_destroy_notify = destroy_notify;
GST_TRACE_OBJECT (self, "unlocking @ set_send_callback");
g_mutex_unlock (&priv->mutex);
}
GstFlowReturn
gst_dtls_connection_process (GstDtlsConnection * self, gpointer data, gsize len,
gsize * written, GError ** err)
{
GstFlowReturn flow_ret = GST_FLOW_OK;
GstDtlsConnectionPrivate *priv;
int ret;
gboolean notify_state = FALSE;
g_return_val_if_fail (GST_IS_DTLS_CONNECTION (self), 0);
g_return_val_if_fail (self->priv->ssl, 0);
g_return_val_if_fail (self->priv->bio, 0);
priv = self->priv;
GST_TRACE_OBJECT (self, "locking @ process");
g_mutex_lock (&priv->mutex);
GST_TRACE_OBJECT (self, "locked @ process");
g_warn_if_fail (!priv->bio_buffer);
if (self->priv->received_close_notify) {
GST_DEBUG_OBJECT (self, "Already received close_notify");
g_mutex_unlock (&priv->mutex);
return GST_FLOW_EOS;
}
if (self->priv->connection_state == GST_DTLS_CONNECTION_STATE_FAILED) {
GST_ERROR_OBJECT (self, "Had a fatal error before");
g_mutex_unlock (&priv->mutex);
if (err)
*err =
g_error_new_literal (GST_RESOURCE_ERROR, GST_RESOURCE_ERROR_READ,
"Had fatal error before");
return GST_FLOW_ERROR;
}
priv->bio_buffer = data;
priv->bio_buffer_len = len;
priv->bio_buffer_offset = 0;
log_state (self, "process start");
if (SSL_want_write (priv->ssl)) {
flow_ret = openssl_poll (self, &notify_state, err);
log_state (self, "process want write, after poll");
if (flow_ret != GST_FLOW_OK) {
g_mutex_unlock (&priv->mutex);
return flow_ret;
}
}
/* If we're a server and were in new state then by receiving the first data
* we would start the connection process */
if (!priv->is_client) {
if (self->priv->connection_state == GST_DTLS_CONNECTION_STATE_NEW) {
priv->connection_state = GST_DTLS_CONNECTION_STATE_CONNECTING;
notify_state = TRUE;
}
}
ret = SSL_read (priv->ssl, data, len);
*written = ret >= 0 ? ret : 0;
GST_DEBUG_OBJECT (self, "read result: %d", ret);
flow_ret =
handle_error (self, ret, GST_RESOURCE_ERROR_READ, &notify_state, err);
if (flow_ret == GST_FLOW_EOS) {
self->priv->received_close_notify = TRUE;
if (self->priv->connection_state != GST_DTLS_CONNECTION_STATE_FAILED
&& self->priv->connection_state != GST_DTLS_CONNECTION_STATE_CLOSED) {
self->priv->connection_state = GST_DTLS_CONNECTION_STATE_CLOSED;
notify_state = TRUE;
}
/* Notify about the connection being properly closed now if both
* sides did so */
if (self->priv->sent_close_notify && self->priv->send_callback)
self->priv->send_callback (self, NULL, 0, NULL);
g_mutex_unlock (&priv->mutex);
if (notify_state) {
g_object_notify_by_pspec (G_OBJECT (self),
properties[PROP_CONNECTION_STATE]);
}
return flow_ret;
} else if (flow_ret != GST_FLOW_OK) {
g_mutex_unlock (&priv->mutex);
if (notify_state) {
g_object_notify_by_pspec (G_OBJECT (self),
properties[PROP_CONNECTION_STATE]);
}
return flow_ret;
}
log_state (self, "process after read");
flow_ret = openssl_poll (self, &notify_state, err);
log_state (self, "process after poll");
GST_TRACE_OBJECT (self, "unlocking @ process");
g_mutex_unlock (&priv->mutex);
if (notify_state) {
g_object_notify_by_pspec (G_OBJECT (self),
properties[PROP_CONNECTION_STATE]);
}
return flow_ret;
}
GstFlowReturn
gst_dtls_connection_send (GstDtlsConnection * self, gconstpointer data,
gsize len, gsize * written, GError ** err)
{
GstFlowReturn flow_ret;
int ret = 0;
gboolean notify_state = FALSE;
g_return_val_if_fail (GST_IS_DTLS_CONNECTION (self), 0);
g_return_val_if_fail (self->priv->ssl, 0);
g_return_val_if_fail (self->priv->bio, 0);
GST_TRACE_OBJECT (self, "locking @ send");
g_mutex_lock (&self->priv->mutex);
GST_TRACE_OBJECT (self, "locked @ send");
if (self->priv->connection_state == GST_DTLS_CONNECTION_STATE_FAILED) {
GST_ERROR_OBJECT (self, "Had a fatal error before");
g_mutex_unlock (&self->priv->mutex);
if (err)
*err =
g_error_new_literal (GST_RESOURCE_ERROR, GST_RESOURCE_ERROR_WRITE,
"Had fatal error before");
return GST_FLOW_ERROR;
}
if (self->priv->sent_close_notify) {
len = 0;
GST_DEBUG_OBJECT (self, "Not sending new data after close_notify");
}
if (len == 0) {
if (written)
*written = 0;
GST_DEBUG_OBJECT (self, "Sending close_notify");
ret = SSL_shutdown (self->priv->ssl);
if (self->priv->connection_state != GST_DTLS_CONNECTION_STATE_CLOSED &&
self->priv->connection_state != GST_DTLS_CONNECTION_STATE_FAILED) {
self->priv->connection_state = GST_DTLS_CONNECTION_STATE_CLOSED;
notify_state = TRUE;
}
if (ret == 1) {
GST_LOG_OBJECT (self, "received peer close_notify already");
self->priv->received_close_notify = TRUE;
flow_ret = GST_FLOW_EOS;
} else if (ret == 0) {
GST_LOG_OBJECT (self, "did not receive peer close_notify yet");
flow_ret = GST_FLOW_OK;
} else {
flow_ret =
handle_error (self, ret, GST_RESOURCE_ERROR_WRITE, &notify_state,
err);
}
} else if (SSL_is_init_finished (self->priv->ssl)) {
GST_DEBUG_OBJECT (self, "sending data of %" G_GSIZE_FORMAT " B", len);
ret = SSL_write (self->priv->ssl, data, len);
if (ret <= 0) {
if (written)
*written = 0;
flow_ret =
handle_error (self, ret, GST_RESOURCE_ERROR_WRITE, &notify_state,
err);
} else {
if (written)
*written = ret;
flow_ret = GST_FLOW_OK;
}
} else {
if (written)
*written = ret;
GST_WARNING_OBJECT (self,
"tried to send data before handshake was complete");
if (err)
*err =
g_error_new_literal (GST_RESOURCE_ERROR, GST_RESOURCE_ERROR_WRITE,
"Tried to send data before handshake was complete");
flow_ret = GST_FLOW_ERROR;
}
GST_TRACE_OBJECT (self, "unlocking @ send");
g_mutex_unlock (&self->priv->mutex);
if (notify_state) {
g_object_notify_by_pspec (G_OBJECT (self),
properties[PROP_CONNECTION_STATE]);
}
return flow_ret;
}
/*
###### ####### ## ##
## ## ## ## ### ##
## ## ## #### ##
## ## ## ## ## ##
## ## ## ## ####
## ## ## ## ## ###
###### ####### ## ##
*/
static void
log_state (GstDtlsConnection * self, const gchar * str)
{
GstDtlsConnectionPrivate *priv = self->priv;
guint states = 0;
states |= (! !SSL_is_init_finished (priv->ssl) << 0);
states |= (! !SSL_in_init (priv->ssl) << 4);
states |= (! !SSL_in_before (priv->ssl) << 8);
states |= (! !SSL_in_connect_init (priv->ssl) << 12);
states |= (! !SSL_in_accept_init (priv->ssl) << 16);
states |= (! !SSL_want_write (priv->ssl) << 20);
states |= (! !SSL_want_read (priv->ssl) << 24);
#if OPENSSL_VERSION_NUMBER < 0x10100001L
GST_LOG_OBJECT (self, "%s: role=%s buf=(%d,%p:%d/%d) %x|%x %s",
str,
priv->is_client ? "client" : "server",
pqueue_size (priv->ssl->d1->sent_messages),
priv->bio_buffer,
priv->bio_buffer_offset,
priv->bio_buffer_len,
states, SSL_get_state (priv->ssl), SSL_state_string_long (priv->ssl));
#else
GST_LOG_OBJECT (self, "%s: role=%s buf=(%p:%d/%d) %x|%x %s",
str,
priv->is_client ? "client" : "server",
priv->bio_buffer,
priv->bio_buffer_offset,
priv->bio_buffer_len,
states, SSL_get_state (priv->ssl), SSL_state_string_long (priv->ssl));
#endif
}
static gboolean
export_srtp_keys (GstDtlsConnection * self, GError ** err)
{
typedef struct
{
guint8 v[SRTP_KEY_LEN];
} Key;
typedef struct
{
guint8 v[SRTP_SALT_LEN];
} Salt;
struct
{
Key client_key;
Key server_key;
Salt client_salt;
Salt server_salt;
} exported_keys;
struct
{
Key key;
Salt salt;
} client_key, server_key;
SRTP_PROTECTION_PROFILE *profile;
GstDtlsSrtpCipher cipher;
GstDtlsSrtpAuth auth;
gint success;
static gchar export_string[] = "EXTRACTOR-dtls_srtp";
success = SSL_export_keying_material (self->priv->ssl,
(gpointer) & exported_keys, 60, export_string, strlen (export_string),
NULL, 0, 0);
if (!success) {
GST_WARNING_OBJECT (self, "Failed to export SRTP keys");
if (err)
*err =
g_error_new_literal (GST_RESOURCE_ERROR, GST_RESOURCE_ERROR_READ,
"Failed to export SRTP keys");
return FALSE;
}
profile = SSL_get_selected_srtp_profile (self->priv->ssl);
if (!profile) {
GST_WARNING_OBJECT (self,
"No SRTP capabilities negotiated during handshake");
if (err)
*err =
g_error_new_literal (GST_RESOURCE_ERROR, GST_RESOURCE_ERROR_READ,
"No SRTP capabilities negotiated during handshake");
return FALSE;
}
GST_INFO_OBJECT (self, "keys received, profile is %s", profile->name);
switch (profile->id) {
case SRTP_AES128_CM_SHA1_80:
cipher = GST_DTLS_SRTP_CIPHER_AES_128_ICM;
auth = GST_DTLS_SRTP_AUTH_HMAC_SHA1_80;
break;
case SRTP_AES128_CM_SHA1_32:
cipher = GST_DTLS_SRTP_CIPHER_AES_128_ICM;
auth = GST_DTLS_SRTP_AUTH_HMAC_SHA1_32;
break;
default:
GST_WARNING_OBJECT (self,
"Invalid/unsupported crypto suite set by handshake");
if (err)
*err =
g_error_new_literal (GST_RESOURCE_ERROR, GST_RESOURCE_ERROR_READ,
"Invalid/unsupported crypto suite set by handshake");
return FALSE;
}
client_key.key = exported_keys.client_key;
server_key.key = exported_keys.server_key;
client_key.salt = exported_keys.client_salt;
server_key.salt = exported_keys.server_salt;
if (self->priv->is_client) {
g_signal_emit (self, signals[SIGNAL_ON_ENCODER_KEY], 0, &client_key, cipher,
auth);
g_signal_emit (self, signals[SIGNAL_ON_DECODER_KEY], 0, &server_key,
cipher, auth);
} else {
g_signal_emit (self, signals[SIGNAL_ON_ENCODER_KEY], 0, &server_key,
cipher, auth);
g_signal_emit (self, signals[SIGNAL_ON_DECODER_KEY], 0, &client_key, cipher,
auth);
}
self->priv->keys_exported = TRUE;
return TRUE;
}
static int
ssl_warn_cb (const char *str, size_t len, void *u)
{
GstDtlsConnection *self = u;
GST_WARNING_OBJECT (self, "ssl error: %s", str);
return 0;
}
static int
ssl_err_cb (const char *str, size_t len, void *u)
{
GstDtlsConnection *self = u;
GST_ERROR_OBJECT (self, "ssl error: %s", str);
return 0;
}
static GstFlowReturn
handle_error (GstDtlsConnection * self, int ret, GstResourceError error_type,
gboolean * notify_state, GError ** err)
{
int error;
error = SSL_get_error (self->priv->ssl, ret);
switch (error) {
case SSL_ERROR_NONE:
GST_TRACE_OBJECT (self, "No error");
return GST_FLOW_OK;
case SSL_ERROR_SSL:
GST_ERROR_OBJECT (self, "Fatal SSL error");
if (self->priv->connection_state != GST_DTLS_CONNECTION_STATE_FAILED) {
self->priv->connection_state = GST_DTLS_CONNECTION_STATE_FAILED;
*notify_state = TRUE;
}
ERR_print_errors_cb (ssl_err_cb, self);
if (err)
*err =
g_error_new_literal (GST_RESOURCE_ERROR, error_type,
"Fatal SSL error");
return GST_FLOW_ERROR;
case SSL_ERROR_ZERO_RETURN:
GST_LOG_OBJECT (self, "Connection was closed");
return GST_FLOW_EOS;
case SSL_ERROR_WANT_READ:
GST_LOG_OBJECT (self, "SSL wants read");
return GST_FLOW_OK;
case SSL_ERROR_WANT_WRITE:
GST_LOG_OBJECT (self, "SSL wants write");
return GST_FLOW_OK;
case SSL_ERROR_SYSCALL:
/* OpenSSL shouldn't be making real system calls, so we can safely
* ignore syscall errors. System interactions should happen through
* our BIO.
*/
GST_DEBUG_OBJECT (self, "OpenSSL reported a syscall error, ignoring.");
return GST_FLOW_OK;
default:
if (self->priv->connection_state != GST_DTLS_CONNECTION_STATE_FAILED) {
self->priv->connection_state = GST_DTLS_CONNECTION_STATE_FAILED;
*notify_state = TRUE;
}
GST_ERROR_OBJECT (self, "Unknown SSL error: %d, ret: %d", error, ret);
if (err)
*err =
g_error_new (GST_RESOURCE_ERROR, error_type,
"Unknown SSL error: %d, ret: %d", error, ret);
return GST_FLOW_ERROR;
}
}
static GstFlowReturn
openssl_poll (GstDtlsConnection * self, gboolean * notify_state, GError ** err)
{
int ret;
GstFlowReturn flow_ret;
log_state (self, "poll: before handshake");
ERR_clear_error ();
ret = SSL_do_handshake (self->priv->ssl);
log_state (self, "poll: after handshake");
switch (ret) {
case 1:
if (!self->priv->keys_exported) {
GST_INFO_OBJECT (self,
"handshake just completed successfully, exporting keys");
if (!export_srtp_keys (self, err))
return GST_FLOW_ERROR;
if (self->priv->connection_state != GST_DTLS_CONNECTION_STATE_FAILED
&& self->priv->connection_state != GST_DTLS_CONNECTION_STATE_CLOSED
&& self->priv->connection_state !=
GST_DTLS_CONNECTION_STATE_CONNECTED) {
self->priv->connection_state = GST_DTLS_CONNECTION_STATE_CONNECTED;
*notify_state = TRUE;
}
} else {
GST_INFO_OBJECT (self, "handshake is completed");
}
return GST_FLOW_OK;
case 0:
GST_DEBUG_OBJECT (self, "do_handshake encountered EOF");
break;
case -1:
GST_DEBUG_OBJECT (self, "do_handshake encountered potential BIO error");
break;
default:
GST_DEBUG_OBJECT (self, "do_handshake returned %d", ret);
break;
}
flow_ret =
handle_error (self, ret, GST_RESOURCE_ERROR_OPEN_WRITE, notify_state,
err);
ERR_print_errors_cb (ssl_warn_cb, self);
return flow_ret;
}
static int
openssl_verify_callback (int preverify_ok, X509_STORE_CTX * x509_ctx)
{
GstDtlsConnection *self;
SSL *ssl;
BIO *bio;
gchar *pem = NULL;
gboolean accepted = FALSE;
ssl =
X509_STORE_CTX_get_ex_data (x509_ctx,
SSL_get_ex_data_X509_STORE_CTX_idx ());
self = SSL_get_ex_data (ssl, connection_ex_index);
g_return_val_if_fail (GST_IS_DTLS_CONNECTION (self), FALSE);
pem = _gst_dtls_x509_to_pem (X509_STORE_CTX_get0_cert (x509_ctx));
if (!pem) {
GST_WARNING_OBJECT (self,
"failed to convert received certificate to pem format");
} else {
bio = BIO_new (BIO_s_mem ());
if (bio) {
gchar buffer[2048];
gint len;
len =
X509_NAME_print_ex (bio,
X509_get_subject_name (X509_STORE_CTX_get0_cert (x509_ctx)), 1,
XN_FLAG_MULTILINE);
BIO_read (bio, buffer, len);
buffer[len] = '\0';
GST_DEBUG_OBJECT (self, "Peer certificate received:\n%s", buffer);
BIO_free (bio);
} else {
GST_DEBUG_OBJECT (self, "failed to create certificate print membio");
}
g_signal_emit (self, signals[SIGNAL_ON_PEER_CERTIFICATE], 0, pem,
&accepted);
g_free (pem);
}
return accepted;
}
/*
######## #### #######
## ## ## ## ##
## ## ## ## ##
######## ## ## ##
## ## ## ## ##
## ## ## ## ##
######## #### #######
*/
#if OPENSSL_VERSION_NUMBER < 0x10100001L
static BIO_METHOD custom_bio_methods = {
BIO_TYPE_BIO,
"stream",
bio_method_write,
bio_method_read,
NULL,
NULL,
bio_method_ctrl,
bio_method_new,
bio_method_free,
NULL,
};
static BIO_METHOD *
BIO_s_gst_dtls_connection (void)
{
return &custom_bio_methods;
}
#else
static BIO_METHOD *custom_bio_methods;
static BIO_METHOD *
BIO_s_gst_dtls_connection (void)
{
if (custom_bio_methods != NULL)
return custom_bio_methods;
custom_bio_methods = BIO_meth_new (BIO_TYPE_BIO, "stream");
if (custom_bio_methods == NULL
|| !BIO_meth_set_write (custom_bio_methods, bio_method_write)
|| !BIO_meth_set_read (custom_bio_methods, bio_method_read)
|| !BIO_meth_set_ctrl (custom_bio_methods, bio_method_ctrl)
|| !BIO_meth_set_create (custom_bio_methods, bio_method_new)
|| !BIO_meth_set_destroy (custom_bio_methods, bio_method_free)) {
BIO_meth_free (custom_bio_methods);
return NULL;
}
return custom_bio_methods;
}
#endif
static int
bio_method_write (BIO * bio, const char *data, int size)
{
GstDtlsConnection *self = GST_DTLS_CONNECTION (BIO_get_data (bio));
gboolean ret = TRUE;
GST_LOG_OBJECT (self, "BIO: writing %d", size);
if (self->priv->send_callback)
ret = self->priv->send_callback (self, data, size,
self->priv->send_callback_user_data);
return ret ? size : -1;
}
static int
bio_method_read (BIO * bio, char *out_buffer, int size)
{
GstDtlsConnection *self = GST_DTLS_CONNECTION (BIO_get_data (bio));
GstDtlsConnectionPrivate *priv = self->priv;
guint internal_size;
gint copy_size;
internal_size = priv->bio_buffer_len - priv->bio_buffer_offset;
if (!priv->bio_buffer) {
GST_LOG_OBJECT (self, "BIO: EOF");
return 0;
}
if (!out_buffer || size <= 0) {
GST_WARNING_OBJECT (self, "BIO: read got invalid arguments");
if (internal_size) {
BIO_set_retry_read (bio);
}
return internal_size;
}
if (size > internal_size) {
copy_size = internal_size;
} else {
copy_size = size;
}
GST_DEBUG_OBJECT (self,
"reading %d/%d bytes %d at offset %d, output buff size is %d", copy_size,
priv->bio_buffer_len, internal_size, priv->bio_buffer_offset, size);
memcpy (out_buffer, (guint8 *) priv->bio_buffer + priv->bio_buffer_offset,
copy_size);
priv->bio_buffer_offset += copy_size;
if (priv->bio_buffer_len == priv->bio_buffer_offset) {
priv->bio_buffer = NULL;
}
return copy_size;
}
static long
bio_method_ctrl (BIO * bio, int cmd, long arg1, void *arg2)
{
GstDtlsConnection *self = GST_DTLS_CONNECTION (BIO_get_data (bio));
GstDtlsConnectionPrivate *priv = self->priv;
switch (cmd) {
case BIO_CTRL_DGRAM_SET_NEXT_TIMEOUT:
case BIO_CTRL_DGRAM_SET_RECV_TIMEOUT:
GST_LOG_OBJECT (self, "BIO: Timeout set");
gst_dtls_connection_check_timeout_locked (self);
return 1;
case BIO_CTRL_RESET:
priv->bio_buffer = NULL;
priv->bio_buffer_len = 0;
priv->bio_buffer_offset = 0;
GST_LOG_OBJECT (self, "BIO: EOF reset");
return 1;
case BIO_CTRL_EOF:{
gint eof = priv->is_alive == FALSE;
GST_LOG_OBJECT (self, "BIO: EOF query returned %d", eof);
return eof;
}
case BIO_CTRL_WPENDING:
GST_LOG_OBJECT (self, "BIO: pending write");
return 1;
case BIO_CTRL_PENDING:{
gint pending = priv->bio_buffer_len - priv->bio_buffer_offset;
GST_LOG_OBJECT (self, "BIO: %d bytes pending", pending);
return pending;
}
case BIO_CTRL_FLUSH:
GST_LOG_OBJECT (self, "BIO: flushing");
return 1;
case BIO_CTRL_DGRAM_QUERY_MTU:
GST_DEBUG_OBJECT (self, "BIO: MTU query, returning 0...");
return 0;
case BIO_CTRL_DGRAM_MTU_EXCEEDED:
GST_WARNING_OBJECT (self, "BIO: MTU exceeded");
return 0;
default:
GST_LOG_OBJECT (self, "BIO: unhandled ctrl, %d", cmd);
return 0;
}
}
static int
bio_method_new (BIO * bio)
{
GST_LOG_OBJECT (NULL, "BIO: new");
BIO_set_shutdown (bio, 0);
BIO_set_init (bio, 1);
return 1;
}
static int
bio_method_free (BIO * bio)
{
if (!bio) {
GST_LOG_OBJECT (NULL, "BIO free called with null bio");
return 0;
}
GST_LOG_OBJECT (GST_DTLS_CONNECTION (BIO_get_data (bio)), "BIO free");
return 0;
}
GType
gst_dtls_connection_state_get_type (void)
{
static GType type = 0;
static const GEnumValue values[] = {
{GST_DTLS_CONNECTION_STATE_NEW, "New connection", "new"},
{GST_DTLS_CONNECTION_STATE_CLOSED, "Closed connection on either side",
"closed"},
{GST_DTLS_CONNECTION_STATE_FAILED, "Failed connection", "failed"},
{GST_DTLS_CONNECTION_STATE_CONNECTING, "Connecting", "connecting"},
{GST_DTLS_CONNECTION_STATE_CONNECTED, "Successfully connected",
"connected"},
{0, NULL, NULL},
};
if (!type) {
type = g_enum_register_static ("GstDtlsConnectionState", values);
}
return type;
}