799 lines
24 KiB
C
799 lines
24 KiB
C
/*
|
|
* Copyright (C) 2013-2015 Mamadou DIOP
|
|
* Copyright (C) 2013-2015 Doubango Telecom <http://www.doubango.org>
|
|
*
|
|
* This file is part of Open Source Doubango Framework.
|
|
*
|
|
* DOUBANGO 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 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* DOUBANGO 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 DOUBANGO.
|
|
*
|
|
*/
|
|
|
|
/**@file tnet_dtls.c
|
|
* @brief DTLS utilitity functions, based on openssl.
|
|
*/
|
|
#include "tnet_dtls.h"
|
|
#include "tnet_tls.h"
|
|
#include "tnet_utils.h"
|
|
|
|
#include "tsk_object.h"
|
|
#include "tsk_string.h"
|
|
#include "tsk_memory.h"
|
|
#include "tsk_time.h"
|
|
#include "tsk_safeobj.h"
|
|
#include "tsk_debug.h"
|
|
|
|
typedef struct tnet_dtls_socket_s
|
|
{
|
|
TSK_DECLARE_OBJECT;
|
|
|
|
struct tnet_socket_s* wrapped_sock; /* not owner: do not try to close */
|
|
tsk_bool_t verify_peer;
|
|
tsk_bool_t use_srtp;
|
|
tsk_bool_t handshake_completed;
|
|
tsk_bool_t handshake_started;
|
|
tsk_bool_t handshake_storedata; // whether to store handshaking data or to send it to the remote party
|
|
tnet_dtls_setup_t setup;
|
|
|
|
struct {
|
|
void* ptr;
|
|
tsk_size_t size;
|
|
tsk_size_t count;
|
|
} handshake_data;
|
|
|
|
struct{
|
|
const void* usrdata;
|
|
tnet_dtls_socket_cb_f func;
|
|
} cb;
|
|
|
|
struct{
|
|
tnet_fingerprint_t fp;
|
|
tnet_dtls_hash_type_t hash;
|
|
struct sockaddr_storage addr;
|
|
} remote;
|
|
struct{
|
|
tnet_fingerprint_t fp;
|
|
tnet_dtls_hash_type_t hash;
|
|
} local;
|
|
|
|
#if HAVE_OPENSSL
|
|
SSL *ssl;
|
|
BIO* rbio;
|
|
BIO* wbio;
|
|
#endif
|
|
|
|
TSK_DECLARE_SAFEOBJ;
|
|
}
|
|
tnet_dtls_socket_t;
|
|
|
|
#define _tnet_dtls_socket_do_handshake(self) tnet_dtls_socket_do_handshake(self, tsk_null)
|
|
#define _tnet_dtls_socket_raise_event(self, type, data, size) ((self) && (self)->cb.func ? (self)->cb.func((self)->cb.usrdata, (type), (self), (data), (size)) : 0)
|
|
#define _tnet_dtls_socket_raise_event_dataless(self, type) _tnet_dtls_socket_raise_event((self), (type), tsk_null, 0)
|
|
|
|
tsk_bool_t tnet_dtls_is_srtp_supported()
|
|
{
|
|
#if HAVE_OPENSSL_DTLS_SRTP
|
|
return tsk_true;
|
|
#else
|
|
return tsk_false;
|
|
#endif
|
|
}
|
|
|
|
tsk_bool_t tnet_dtls_is_supported()
|
|
{
|
|
#if HAVE_OPENSSL_DTLS
|
|
return tsk_true;
|
|
#else
|
|
return tsk_false;
|
|
#endif
|
|
}
|
|
|
|
|
|
#if HAVE_OPENSSL
|
|
|
|
static tsk_bool_t _tnet_dtls_is_fingerprint_matching(X509* cert, tnet_fingerprint_t* fingerprint, tnet_dtls_hash_type_t hash);
|
|
|
|
static int _tnet_dtls_verify_cert(int preverify_ok, X509_STORE_CTX *ctx)
|
|
{
|
|
SSL *ssl;
|
|
tnet_dtls_socket_t* socket;
|
|
|
|
TSK_DEBUG_INFO("_tnet_dtls_verify_cert");
|
|
|
|
ssl = X509_STORE_CTX_get_app_data(ctx);
|
|
socket = (tnet_dtls_socket_t*)SSL_get_app_data(ssl);
|
|
if (!ssl || !socket){
|
|
TSK_DEBUG_ERROR("Not expected");
|
|
return 0;
|
|
}
|
|
tsk_safeobj_lock(socket);
|
|
if (_tnet_dtls_is_fingerprint_matching(ctx->cert, &socket->remote.fp, socket->remote.hash) == tsk_false) {
|
|
TSK_DEBUG_ERROR("Failed to match fingerprint");
|
|
tsk_safeobj_unlock(socket);
|
|
return 0;
|
|
}
|
|
tsk_safeobj_unlock(socket);
|
|
return 1;
|
|
}
|
|
|
|
static const EVP_MD *_tnet_dtls_get_hash_evp(tnet_dtls_hash_type_t hash)
|
|
{
|
|
switch (hash){
|
|
case tnet_dtls_hash_type_md5: return EVP_md5();
|
|
case tnet_dtls_hash_type_sha1: return EVP_sha1();
|
|
case tnet_dtls_hash_type_sha256: return EVP_sha256();
|
|
case tnet_dtls_hash_type_sha512: return EVP_sha512();
|
|
default: TSK_DEBUG_ERROR("Invalid parameter: %d not valid as hash type", hash); return tsk_null;
|
|
}
|
|
}
|
|
|
|
static int _tnet_dtls_get_fingerprint(X509* cert, const EVP_MD *evp, tnet_fingerprint_t* fingerprint)
|
|
{
|
|
if (!cert || !evp || !fingerprint){
|
|
TSK_DEBUG_ERROR("Invalid parameter");
|
|
return -1;
|
|
}
|
|
else{
|
|
unsigned len = 0, i, j;
|
|
tnet_fingerprint_t fp;
|
|
|
|
if (X509_digest(cert, evp, fp, &len) != 1 || len <= 0){
|
|
TSK_DEBUG_ERROR("X509_digest() failed [%s]", ERR_error_string(ERR_get_error(), tsk_null));
|
|
return -2;
|
|
}
|
|
for (i = 0, j = 0; i < len; ++i, j += 3){
|
|
sprintf((char*)&(*fingerprint)[j], (i == (len - 1)) ? "%.2X" : "%.2X:", fp[i]);
|
|
}
|
|
(*fingerprint)[len * 3] = '\0';
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static tsk_bool_t _tnet_dtls_is_fingerprint_matching(X509* cert, tnet_fingerprint_t* fingerprint, tnet_dtls_hash_type_t hash)
|
|
{
|
|
const EVP_MD* evp;
|
|
tnet_fingerprint_t fp;
|
|
int ret;
|
|
|
|
if (!cert || !fingerprint){
|
|
TSK_DEBUG_ERROR("Invalid parameter");
|
|
return tsk_false;
|
|
}
|
|
|
|
if (!(evp = _tnet_dtls_get_hash_evp(hash))){
|
|
return tsk_false;
|
|
}
|
|
if ((ret = _tnet_dtls_get_fingerprint(cert, evp, &fp))){
|
|
return tsk_false;
|
|
}
|
|
if (!tsk_striequals(fp, fingerprint)){
|
|
TSK_DEBUG_ERROR("DTLS certificate fingerprints mismatch: [%s]#[%s]", fp, *fingerprint);
|
|
return tsk_false;
|
|
}
|
|
return tsk_true;
|
|
}
|
|
|
|
static tsk_bool_t _tnet_dtls_socket_is_remote_cert_fp_match(tnet_dtls_socket_t* socket)
|
|
{
|
|
if (!socket){
|
|
TSK_DEBUG_ERROR("Invalid parameter");
|
|
return tsk_false;
|
|
}
|
|
else if (socket->verify_peer){
|
|
X509* cert;
|
|
|
|
if (!(cert = SSL_get_peer_certificate(socket->ssl))){
|
|
if (socket->verify_peer){ // print error only if verify certs is enabled
|
|
TSK_DEBUG_ERROR("Failed to get peer certificate [%s]", ERR_error_string(ERR_get_error(), tsk_null));
|
|
}
|
|
return tsk_false;
|
|
}
|
|
if (!_tnet_dtls_is_fingerprint_matching(cert, &socket->remote.fp, socket->remote.hash)){
|
|
X509_free(cert);
|
|
return tsk_false;
|
|
}
|
|
X509_free(cert);
|
|
|
|
if (SSL_get_verify_result(socket->ssl) != X509_V_OK){
|
|
TSK_DEBUG_ERROR("SSL_get_verify_result()#X509_V_OK [%s]", ERR_error_string(ERR_get_error(), tsk_null));
|
|
return tsk_false;
|
|
}
|
|
}
|
|
|
|
return tsk_true;
|
|
}
|
|
|
|
#endif /* HAVE_OPENSSL */
|
|
|
|
tnet_dtls_hash_type_t tnet_dtls_get_hash_from_string(const char* hash)
|
|
{
|
|
if (hash){
|
|
int32_t i;
|
|
for (i = 0; i < TNET_DTLS_HASH_TYPE_MAX; ++i){
|
|
if (tsk_striequals(TNET_DTLS_HASH_NAMES[i], hash)){
|
|
return (tnet_dtls_hash_type_t)i;
|
|
}
|
|
}
|
|
}
|
|
return tnet_dtls_hash_type_none;
|
|
}
|
|
|
|
tnet_dtls_setup_t tnet_dtls_get_setup_from_string(const char* setup)
|
|
{
|
|
if (setup){
|
|
int32_t i;
|
|
for (i = 0; i < TNET_DTLS_SETUP_MAX; ++i){
|
|
if (tsk_striequals(TNET_DTLS_SETUP_NAMES[i], setup)){
|
|
return (tnet_dtls_setup_t)i;
|
|
}
|
|
}
|
|
}
|
|
return tnet_dtls_setup_none;
|
|
}
|
|
|
|
int tnet_dtls_get_fingerprint(const char* certfile, tnet_fingerprint_t* fingerprint, tnet_dtls_hash_type_t hash)
|
|
{
|
|
#if !HAVE_OPENSSL || !HAVE_OPENSSL_DTLS
|
|
TSK_DEBUG_ERROR("OpenSSL or DTLS not enabled");
|
|
return -200;
|
|
#else
|
|
{
|
|
X509* x509;
|
|
BIO* bio;
|
|
int ret = 0;
|
|
const EVP_MD *evp;
|
|
|
|
if (tsk_strnullORempty(certfile) || !fingerprint){
|
|
TSK_DEBUG_ERROR("Invalid parameter");
|
|
return -1;
|
|
}
|
|
|
|
if (!(evp = _tnet_dtls_get_hash_evp(hash))){
|
|
return -1;
|
|
}
|
|
|
|
x509 = tsk_null;
|
|
bio = tsk_null;
|
|
|
|
if (!(bio = BIO_new(BIO_s_file()))){
|
|
TSK_DEBUG_ERROR("BIO_new(BIO_s_file()) failed [%s]", ERR_error_string(ERR_get_error(), tsk_null));
|
|
ret = -3;
|
|
goto bail;
|
|
}
|
|
if (BIO_read_filename(bio, certfile) != 1){
|
|
TSK_DEBUG_ERROR("BIO_read_filename(%s) failed [%s]", certfile, ERR_error_string(ERR_get_error(), tsk_null));
|
|
ret = -4;
|
|
goto bail;
|
|
}
|
|
if (!(x509 = PEM_read_bio_X509(bio, tsk_null, 0, tsk_null))){
|
|
TSK_DEBUG_ERROR("PEM_read_bio() failed [%s]", ERR_error_string(ERR_get_error(), tsk_null));
|
|
ret = -5;
|
|
goto bail;
|
|
}
|
|
if ((ret = _tnet_dtls_get_fingerprint(x509, evp, fingerprint))){
|
|
goto bail;
|
|
}
|
|
|
|
bail:
|
|
if (bio){
|
|
BIO_free_all(bio);
|
|
}
|
|
return ret;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
tnet_dtls_socket_handle_t* tnet_dtls_socket_create(struct tnet_socket_s* wrapped_sock, struct ssl_ctx_st* ssl_ctx)
|
|
{
|
|
#if !HAVE_OPENSSL || !HAVE_OPENSSL_DTLS
|
|
TSK_DEBUG_ERROR("OpenSSL or DTLS not enabled");
|
|
return tsk_null;
|
|
#else
|
|
tnet_dtls_socket_t* socket;
|
|
|
|
if (!wrapped_sock || !ssl_ctx){
|
|
TSK_DEBUG_ERROR("Invalid parameter");
|
|
return tsk_null;
|
|
}
|
|
if ((socket = tsk_object_new(tnet_dtls_socket_def_t))) {
|
|
const tsk_bool_t set_mtu = TNET_SOCKET_TYPE_IS_DGRAM(wrapped_sock->type) || 1; //!\ This is required even if the local transport is TCP/TLS because the relayed (TURN) transport could be UDP
|
|
socket->wrapped_sock = tsk_object_ref(wrapped_sock);
|
|
if (!(socket->ssl = SSL_new(ssl_ctx))) {
|
|
TSK_DEBUG_ERROR("SSL_new(CTX) failed [%s]", ERR_error_string(ERR_get_error(), tsk_null));
|
|
TSK_OBJECT_SAFE_FREE(socket);
|
|
return tsk_null;
|
|
}
|
|
if (set_mtu) {
|
|
SSL_set_options(socket->ssl, SSL_OP_NO_QUERY_MTU);
|
|
SSL_set_mtu(socket->ssl, TNET_DTLS_MTU - 28);
|
|
socket->ssl->d1->mtu = TNET_DTLS_MTU - 28;
|
|
}
|
|
if (!(socket->rbio = BIO_new(BIO_s_mem())) || !(socket->wbio = BIO_new(BIO_s_mem()))){
|
|
TSK_DEBUG_ERROR("BIO_new_socket(%d) failed [%s]", socket->wrapped_sock->fd, ERR_error_string(ERR_get_error(), tsk_null));
|
|
if (socket->rbio){
|
|
BIO_free(socket->rbio);
|
|
}
|
|
if (socket->wbio){
|
|
BIO_free(socket->wbio);
|
|
}
|
|
TSK_OBJECT_SAFE_FREE(socket);
|
|
return tsk_null;
|
|
}
|
|
BIO_set_mem_eof_return(socket->rbio, -1);
|
|
BIO_set_mem_eof_return(socket->wbio, -1);
|
|
SSL_set_bio(socket->ssl, socket->rbio, socket->wbio);
|
|
SSL_set_mode(socket->ssl, SSL_MODE_AUTO_RETRY);
|
|
SSL_set_read_ahead(socket->ssl, 1);
|
|
if (set_mtu) {
|
|
BIO_ctrl(SSL_get_wbio(socket->ssl), BIO_CTRL_DGRAM_SET_MTU, TNET_DTLS_MTU - 28, NULL);
|
|
}
|
|
|
|
if ((socket->verify_peer = (SSL_CTX_get_verify_mode(ssl_ctx) != SSL_VERIFY_NONE))){
|
|
TSK_DEBUG_INFO("SSL cert verify: ON");
|
|
socket->verify_peer = tsk_true;
|
|
SSL_set_verify(socket->ssl, (SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT), _tnet_dtls_verify_cert);
|
|
}
|
|
else {
|
|
TSK_DEBUG_ERROR("Verity not enabled");
|
|
}
|
|
|
|
SSL_set_app_data(socket->ssl, socket);
|
|
}
|
|
return socket;
|
|
#endif
|
|
}
|
|
|
|
tnet_fd_t tnet_dtls_socket_get_fd(const tnet_dtls_socket_handle_t* handle)
|
|
{
|
|
return handle ? ((const tnet_dtls_socket_t*)handle)->wrapped_sock->fd : TNET_INVALID_FD;
|
|
}
|
|
|
|
const struct sockaddr_storage* tnet_dtls_socket_get_remote_addr(const tnet_dtls_socket_handle_t* handle)
|
|
{
|
|
return handle ? &((const tnet_dtls_socket_t*)handle)->remote.addr : tsk_null;
|
|
}
|
|
|
|
int tnet_dtls_socket_set_callback(tnet_dtls_socket_handle_t* handle, const void* usrdata, tnet_dtls_socket_cb_f func)
|
|
{
|
|
tnet_dtls_socket_t* socket = handle;
|
|
|
|
if (!socket){
|
|
TSK_DEBUG_ERROR("Invalid parameter");
|
|
return -1;
|
|
}
|
|
|
|
socket->cb.usrdata = usrdata;
|
|
socket->cb.func = func;
|
|
return 0;
|
|
}
|
|
|
|
int tnet_dtls_socket_set_remote_fingerprint(tnet_dtls_socket_handle_t* handle, const tnet_fingerprint_t* fingerprint, tnet_dtls_hash_type_t hash)
|
|
{
|
|
tnet_dtls_socket_t* socket = handle;
|
|
|
|
if (!socket || !fingerprint){
|
|
TSK_DEBUG_ERROR("Invalid parameter");
|
|
return -1;
|
|
}
|
|
|
|
memcpy(socket->remote.fp, &(*fingerprint)[0], sizeof(tnet_fingerprint_t));
|
|
socket->remote.hash = hash;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
rfc5764: 4.1. The use_srtp Extension
|
|
*/
|
|
int tnet_dtls_socket_use_srtp(tnet_dtls_socket_handle_t*handle)
|
|
{
|
|
#if !HAVE_OPENSSL || !HAVE_OPENSSL_DTLS || !HAVE_OPENSSL_DTLS_SRTP
|
|
TSK_DEBUG_ERROR("OpenSSL or DTLS not enabled");
|
|
return -200;
|
|
#else
|
|
tnet_dtls_socket_t* socket = handle;
|
|
if (!socket){
|
|
TSK_DEBUG_ERROR("Invalid parameter");
|
|
return -1;
|
|
}
|
|
|
|
if ((socket->use_srtp = tsk_true)){
|
|
if (!socket->verify_peer){
|
|
socket->verify_peer = tsk_true; // DTLS-SRTP requires certtificates
|
|
SSL_set_verify(socket->ssl, (SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT), _tnet_dtls_verify_cert);
|
|
}
|
|
}
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
int tnet_dtls_socket_set_setup(tnet_dtls_socket_handle_t* handle, tnet_dtls_setup_t setup)
|
|
{
|
|
#if !HAVE_OPENSSL || !HAVE_OPENSSL_DTLS
|
|
TSK_DEBUG_ERROR("OpenSSL or DTLS not enabled");
|
|
return -200;
|
|
#else
|
|
tnet_dtls_socket_t* socket = handle;
|
|
if (!socket){
|
|
TSK_DEBUG_ERROR("Invalid parameter");
|
|
return -1;
|
|
}
|
|
|
|
switch ((socket->setup = setup)){
|
|
case tnet_dtls_setup_passive:
|
|
SSL_set_accept_state(socket->ssl);
|
|
break;
|
|
case tnet_dtls_setup_active:
|
|
case tnet_dtls_setup_actpass:
|
|
case tnet_dtls_setup_none:
|
|
if (setup != tnet_dtls_setup_active){
|
|
TSK_DEBUG_WARN("using setup=%s is not a good idea", TNET_DTLS_SETUP_NAMES[setup]);
|
|
}
|
|
SSL_set_connect_state(socket->ssl);
|
|
break;
|
|
default:
|
|
TSK_DEBUG_ERROR("%d not valid value for DTLS setup", (int32_t)setup);
|
|
break;
|
|
}
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
int tnet_dtls_socket_set_store_handshakingdata(tnet_dtls_socket_handle_t* handle, tsk_bool_t handshake_storedata)
|
|
{
|
|
if (!handle) {
|
|
TSK_DEBUG_ERROR("Invalid parameter");
|
|
return -1;
|
|
}
|
|
((tnet_dtls_socket_t*)handle)->handshake_storedata = handshake_storedata;
|
|
return 0;
|
|
}
|
|
|
|
int tnet_dtls_socket_get_handshakingdata(tnet_dtls_socket_handle_t* handle, const void** data, tsk_size_t *size)
|
|
{
|
|
if (!handle || !data || !size) {
|
|
TSK_DEBUG_ERROR("Invalid parameter");
|
|
return -1;
|
|
}
|
|
*data = ((tnet_dtls_socket_t*)handle)->handshake_data.ptr;
|
|
*size = ((tnet_dtls_socket_t*)handle)->handshake_data.count;
|
|
return 0;
|
|
}
|
|
|
|
// This function returns first DTLS record. It's useful to send handshaking data by records to avoid IP fragmentation
|
|
int tnet_dtls_socket_get_record_first(const void* records, tsk_size_t records_size, const void** record, tsk_size_t* size)
|
|
{
|
|
/* https://tools.ietf.org/html/rfc6347#section-3.2.3
|
|
TLS and DTLS handshake messages can be quite large(in theory up to
|
|
2 ^ 24 - 1 bytes, in practice many kilobytes).By contrast, UDP
|
|
datagrams are often limited to <1500 bytes if IP fragmentation is not
|
|
desired.In order to compensate for this limitation, each DTLS
|
|
handshake message may be fragmented over several DTLS records, each
|
|
of which is intended to fit in a single IP datagram.Each DTLS
|
|
handshake message contains both a fragment offset and a fragment
|
|
length.Thus, a recipient in possession of all bytes of a handshake
|
|
message can reassemble the original unfragmented message. */
|
|
// 4.1. Record Layer - https://tools.ietf.org/html/rfc6347#section-4.1
|
|
#define kDTLSv1RecordHdrStartIndex 11
|
|
#define kDTLSv1RecordHdrLengthFieldLen 2 // uint16
|
|
#define kDTLSv1RecordHdrLen (kDTLSv1RecordHdrStartIndex + kDTLSv1RecordHdrLengthFieldLen)
|
|
|
|
const uint8_t* pc_records;
|
|
tsk_size_t record_length;
|
|
if (!records || records_size < kDTLSv1RecordHdrLen || !record || !size) {
|
|
TSK_DEBUG_ERROR("Invalid parameter");
|
|
return -1;
|
|
}
|
|
pc_records = (const uint8_t*)records;
|
|
record_length = ((pc_records[kDTLSv1RecordHdrStartIndex] << 8) & 0xFF00) | (pc_records[kDTLSv1RecordHdrStartIndex + 1] & 0xFF);
|
|
*record = records;
|
|
*size = kDTLSv1RecordHdrLen + record_length;
|
|
if ((*size) > TNET_DTLS_MTU) {
|
|
TSK_DEBUG_WARN("DTLS record length(%u) > MTU(%u)", (unsigned)(*size), TNET_DTLS_MTU);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
tsk_bool_t tnet_dtls_socket_is_remote_cert_fp_match(tnet_dtls_socket_handle_t* handle)
|
|
{
|
|
#if !HAVE_OPENSSL || !HAVE_OPENSSL_DTLS
|
|
TSK_DEBUG_ERROR("OpenSSL or DTLS not enabled");
|
|
return -1;
|
|
#else
|
|
return _tnet_dtls_socket_is_remote_cert_fp_match((tnet_dtls_socket_t*)handle);
|
|
#endif
|
|
}
|
|
|
|
int tnet_dtls_socket_do_handshake(tnet_dtls_socket_handle_t* handle, const struct sockaddr_storage* remote_addr)
|
|
{
|
|
#if !HAVE_OPENSSL || !HAVE_OPENSSL_DTLS
|
|
TSK_DEBUG_ERROR("OpenSSL or DTLS not enabled");
|
|
return -1;
|
|
|
|
#else
|
|
tnet_dtls_socket_t *socket = handle;
|
|
int ret = 0, len;
|
|
void* out_data;
|
|
|
|
if (!socket) {
|
|
TSK_DEBUG_ERROR("Invalid parameter");
|
|
return -1;
|
|
}
|
|
|
|
tsk_safeobj_lock(socket);
|
|
|
|
// update remote address even if handshaking is completed
|
|
if (remote_addr) {
|
|
socket->remote.addr = *remote_addr;
|
|
}
|
|
|
|
if (socket->handshake_completed) {
|
|
TSK_DEBUG_INFO("Handshake completed");
|
|
ret = 0;
|
|
goto bail;
|
|
}
|
|
|
|
if (!socket->handshake_started) {
|
|
if ((ret = SSL_do_handshake(socket->ssl)) != 1) {
|
|
switch ((ret = SSL_get_error(socket->ssl, ret))) {
|
|
case SSL_ERROR_WANT_READ:
|
|
case SSL_ERROR_WANT_WRITE:
|
|
case SSL_ERROR_NONE:
|
|
break;
|
|
default:
|
|
TSK_DEBUG_ERROR("DTLS handshake failed [%s]", ERR_error_string(ERR_get_error(), tsk_null));
|
|
_tnet_dtls_socket_raise_event_dataless(socket, tnet_dtls_socket_event_type_handshake_failed);
|
|
ret = -2;
|
|
goto bail;
|
|
}
|
|
}
|
|
socket->handshake_started = (ret == SSL_ERROR_NONE); // TODO: reset for renegotiation
|
|
}
|
|
|
|
if ((len = (int)BIO_get_mem_data(socket->wbio, &out_data)) > 0 && out_data) {
|
|
if (socket->handshake_storedata) { // e.g. when TURN is enabled we have to query handshaking data and sent it via the negotiated channel
|
|
if ((int)socket->handshake_data.size < len) {
|
|
if (!(socket->handshake_data.ptr = tsk_realloc(socket->handshake_data.ptr, len))) {
|
|
socket->handshake_data.size = 0;
|
|
socket->handshake_data.count = 0;
|
|
ret = -5;
|
|
goto bail;
|
|
}
|
|
socket->handshake_data.size = len;
|
|
}
|
|
socket->handshake_data.count = len;
|
|
memcpy(socket->handshake_data.ptr, out_data, len);
|
|
}
|
|
else {
|
|
int sentlen = 0;
|
|
tnet_port_t port;
|
|
tnet_ip_t ip;
|
|
tsk_bool_t is_dgram = TNET_SOCKET_TYPE_IS_DGRAM(socket->wrapped_sock->type);
|
|
const uint8_t *record_ptr, *records_ptr = out_data;
|
|
tsk_size_t record_size;
|
|
int records_len = len;
|
|
|
|
tnet_get_sockip_n_port((const struct sockaddr *)&socket->remote.addr, &ip, &port);
|
|
TSK_DEBUG_INFO("DTLS data handshake to send with len = %d, from(%.*s/%d) to(%.*s/%d)", len, (int)sizeof(socket->wrapped_sock->ip), socket->wrapped_sock->ip, socket->wrapped_sock->port, (int)sizeof(ip), ip, port);
|
|
|
|
//!\ IP fragmentation issues must be avoided even if the local transport is TCP/TLS because the relayed (TURN) transport could be UDP
|
|
while (records_len > 0 && (ret = tnet_dtls_socket_get_record_first(records_ptr, (tsk_size_t)records_len, &record_ptr, &record_size)) == 0) {
|
|
if (is_dgram) {
|
|
sentlen += tnet_sockfd_sendto(socket->wrapped_sock->fd, (const struct sockaddr *)&socket->remote.addr, record_ptr, record_size);
|
|
}
|
|
else {
|
|
sentlen += tnet_socket_send_stream(socket->wrapped_sock, record_ptr, record_size);
|
|
}
|
|
records_len -= (int)record_size;
|
|
records_ptr += record_size;
|
|
}
|
|
TSK_DEBUG_INFO("DTLS data handshake sent len = %d", sentlen);
|
|
}
|
|
}
|
|
|
|
BIO_reset(socket->rbio);
|
|
BIO_reset(socket->wbio);
|
|
|
|
if ((socket->handshake_completed = SSL_is_init_finished(socket->ssl))) {
|
|
TSK_DEBUG_INFO("DTLS handshake completed");
|
|
|
|
#if HAVE_OPENSSL_DTLS_SRTP
|
|
if (socket->use_srtp){
|
|
#if !defined(SRTP_MAX_KEY_LEN)
|
|
# define cipher_key_length (128 >> 3) // rfc5764 4.1.2. SRTP Protection Profiles
|
|
# define cipher_salt_length (112 >> 3) // rfc5764 4.1.2. SRTP Protection Profiles
|
|
// "cipher_key_length" is also equal to srtp_profile_get_master_key_length(srtp_profile_aes128_cm_sha1_80)
|
|
// "cipher_salt_length" is also srtp_profile_get_master_salt_length(srtp_profile_aes128_cm_sha1_80)
|
|
# define SRTP_MAX_KEY_LEN (cipher_key_length + cipher_salt_length)
|
|
#endif /* SRTP_MAX_KEY_LEN */
|
|
#define EXTRACTOR_dtls_srtp_text "EXTRACTOR-dtls_srtp"
|
|
#define EXTRACTOR_dtls_srtp_text_len 19
|
|
uint8_t keying_material[SRTP_MAX_KEY_LEN << 1];
|
|
static const tsk_size_t keying_material_size = sizeof(keying_material);
|
|
/*if(socket->use_srtp)*/{
|
|
SRTP_PROTECTION_PROFILE *p = SSL_get_selected_srtp_profile(socket->ssl);
|
|
if (!p) {
|
|
TSK_DEBUG_ERROR("SSL_get_selected_srtp_profile() returned null [%s]", ERR_error_string(ERR_get_error(), tsk_null));
|
|
ret = -2;
|
|
goto bail;
|
|
}
|
|
// alert user
|
|
_tnet_dtls_socket_raise_event(socket, tnet_dtls_socket_event_type_dtls_srtp_profile_selected, p->name, tsk_strlen(p->name));
|
|
|
|
memset(keying_material, 0, sizeof(keying_material));
|
|
|
|
// rfc5764 - 4.2. Key Derivation
|
|
ret = SSL_export_keying_material(socket->ssl, keying_material, sizeof(keying_material), EXTRACTOR_dtls_srtp_text, EXTRACTOR_dtls_srtp_text_len, tsk_null, 0, 0);
|
|
if (ret != 1) {
|
|
// alert listener
|
|
_tnet_dtls_socket_raise_event_dataless(socket, tnet_dtls_socket_event_type_error);
|
|
TSK_DEBUG_ERROR("SSL_export_keying_material() failed [%s]", ERR_error_string(ERR_get_error(), tsk_null));
|
|
ret = -2;
|
|
goto bail;
|
|
}
|
|
}
|
|
// alert listener
|
|
_tnet_dtls_socket_raise_event(socket, tnet_dtls_socket_event_type_dtls_srtp_data, keying_material, keying_material_size);
|
|
}
|
|
#endif /* HAVE_OPENSSL_DTLS_SRTP */
|
|
_tnet_dtls_socket_raise_event_dataless(socket, tnet_dtls_socket_event_type_handshake_succeed);
|
|
}
|
|
ret = 0; // clear "ret", error will directly jump to "bail:"
|
|
bail:
|
|
tsk_safeobj_unlock(socket);
|
|
return ret;
|
|
#endif
|
|
}
|
|
|
|
tsk_bool_t tnet_dtls_socket_is_handshake_completed(const tnet_dtls_socket_handle_t* handle)
|
|
{
|
|
return (handle && ((const tnet_dtls_socket_t *)handle)->handshake_completed);
|
|
}
|
|
|
|
/*
|
|
Handles DTLS data received over the network using standard functions (e.g. recvfrom())
|
|
@param handle
|
|
@param data When "use_srtp" is enabled this must point to DTLS handshake data.
|
|
@param size DTLS data size
|
|
@returns 0 if succeed, non-zero error code otherwise
|
|
*/
|
|
int tnet_dtls_socket_handle_incoming_data(tnet_dtls_socket_handle_t* handle, const void* data, tsk_size_t size)
|
|
{
|
|
#if !HAVE_OPENSSL || !HAVE_OPENSSL_DTLS
|
|
TSK_DEBUG_ERROR("OpenSSL or DTLS not enabled");
|
|
return -200;
|
|
#else
|
|
tnet_dtls_socket_t *socket = handle;
|
|
int ret = 0;
|
|
|
|
if (!socket || !data || !size) {
|
|
TSK_DEBUG_ERROR("Invalid parameter");
|
|
return -1;
|
|
}
|
|
|
|
tsk_safeobj_lock(socket);
|
|
|
|
TSK_DEBUG_INFO("Receive DTLS data: %lu", (unsigned long)size);
|
|
|
|
// BIO_reset(socket->rbio);
|
|
// BIO_reset(socket->wbio);
|
|
|
|
if (!socket->rbio || !socket->wbio) {
|
|
TSK_DEBUG_ERROR("BIO not initialized yet");
|
|
ret = -2;
|
|
goto bail;
|
|
}
|
|
|
|
if ((ret = _tnet_dtls_socket_do_handshake(socket))) {
|
|
goto bail;
|
|
}
|
|
|
|
if ((ret = BIO_write(socket->rbio, data, (int)size)) != size) {
|
|
ret = SSL_get_error(socket->ssl, ret);
|
|
TSK_DEBUG_ERROR("BIO_write(rbio, %lu) failed [%s]", (unsigned long)size, ERR_error_string(ERR_get_error(), tsk_null));
|
|
ret = -1;
|
|
goto bail;
|
|
}
|
|
|
|
/*if((ret = SSL_read(socket->ssl, (void*)data, size)) <= 0){
|
|
switch((ret = SSL_get_error(socket->ssl, ret))){
|
|
case SSL_ERROR_WANT_READ:
|
|
case SSL_ERROR_WANT_WRITE:
|
|
case SSL_ERROR_NONE:
|
|
break;
|
|
default:
|
|
{
|
|
unsigned long sslErr = ERR_get_error();
|
|
const uint8_t* pData = (const uint8_t*)data;
|
|
TSK_DEBUG_ERROR("%lu = SSL_read(rbio, %u) failed [%s]", sslErr, size, ERR_error_string(ret, tsk_null));
|
|
// try to understand what's going on
|
|
// rfc6347 - 4.1. Record Layer
|
|
// rfc6347 - 4.2.2. Handshake Message Format
|
|
// rfc6347 - 4.3.2. Handshake Protocol
|
|
if(size > 14 && pData[0] == 0x16){ // content-type=='Handshake'
|
|
if(pData[13] == 0x01 && (socket->setup == tnet_dtls_setup_active || socket->setup == tnet_dtls_setup_actpass)){ // Handshake Type=='client Hello'
|
|
TSK_DEBUG_INFO("DTLS engine was in client mode but we are receiving 'Client Hello' messages. This is a bug in the remote peer: Re-negotiating!");
|
|
tnet_dtls_socket_set_setup(socket, tnet_dtls_setup_passive);
|
|
break;
|
|
}
|
|
else if(pData[13] == 0x02 && (socket->setup == tnet_dtls_setup_passive || socket->setup == tnet_dtls_setup_actpass)){ // Handshake Type=='server Hello'
|
|
TSK_DEBUG_INFO("DTLS engine was in server mode but we are receiving 'Server Hello' messages. This is a bug in the remote peer: Re-negotiating!");
|
|
tnet_dtls_socket_set_setup(socket, tnet_dtls_setup_active);
|
|
break;
|
|
}
|
|
}
|
|
//return -1;
|
|
break;
|
|
}
|
|
}
|
|
}*/
|
|
|
|
ret = _tnet_dtls_socket_do_handshake(socket);
|
|
|
|
bail:
|
|
tsk_safeobj_unlock(socket);
|
|
return ret;
|
|
#endif
|
|
}
|
|
|
|
|
|
//=================================================================================================
|
|
// DTLS socket object definition
|
|
//
|
|
static tsk_object_t* tnet_dtls_socket_ctor(tsk_object_t * self, va_list * app)
|
|
{
|
|
tnet_dtls_socket_t *socket = self;
|
|
if (socket){
|
|
tsk_safeobj_init(socket);
|
|
}
|
|
return self;
|
|
}
|
|
|
|
static tsk_object_t* tnet_dtls_socket_dtor(tsk_object_t * self)
|
|
{
|
|
tnet_dtls_socket_t *socket = self;
|
|
if (socket){
|
|
#if HAVE_OPENSSL
|
|
if (socket->rbio) {
|
|
//BIO_free(socket->rbio);
|
|
socket->rbio = tsk_null;
|
|
}
|
|
if (socket->wbio) {
|
|
//BIO_free(socket->wbio);
|
|
socket->wbio = tsk_null;
|
|
}
|
|
if (socket->ssl) {
|
|
SSL_shutdown(socket->ssl);
|
|
// https://www.openssl.org/docs/crypto/BIO_s_bio.html
|
|
// implicitly frees internal_bio
|
|
SSL_free(socket->ssl);
|
|
}
|
|
#endif
|
|
TSK_FREE(socket->handshake_data.ptr);
|
|
TSK_OBJECT_SAFE_FREE(socket->wrapped_sock);
|
|
tsk_safeobj_deinit(socket);
|
|
|
|
TSK_DEBUG_INFO("*** tnet_dtls_socket_t destroyed ***");
|
|
}
|
|
return self;
|
|
}
|
|
|
|
static const tsk_object_def_t tnet_dtls_socket_def_s =
|
|
{
|
|
sizeof(tnet_dtls_socket_t),
|
|
tnet_dtls_socket_ctor,
|
|
tnet_dtls_socket_dtor,
|
|
tsk_null,
|
|
};
|
|
const tsk_object_def_t *tnet_dtls_socket_def_t = &tnet_dtls_socket_def_s;
|