mirror of
https://github.com/mii443/libdatachannel.git
synced 2025-08-22 15:15:28 +00:00
@ -13,7 +13,6 @@ option(NO_MEDIA "Disable media transport support" OFF)
|
||||
option(NO_EXAMPLES "Disable examples" OFF)
|
||||
option(NO_TESTS "Disable tests build" OFF)
|
||||
option(WARNINGS_AS_ERRORS "Treat warnings as errors" OFF)
|
||||
option(RSA_KEY_BITS_2048 "Use 2048-bit RSA key instead of 3072-bit" OFF)
|
||||
option(CAPI_STDCALL "Set calling convention of C API callbacks stdcall" OFF)
|
||||
|
||||
if(USE_NICE)
|
||||
@ -308,11 +307,6 @@ else()
|
||||
target_link_libraries(datachannel-static PRIVATE LibJuice::LibJuiceStatic)
|
||||
endif()
|
||||
|
||||
if(RSA_KEY_BITS_2048)
|
||||
target_compile_definitions(datachannel PUBLIC RSA_KEY_BITS_2048)
|
||||
target_compile_definitions(datachannel-static PUBLIC RSA_KEY_BITS_2048)
|
||||
endif()
|
||||
|
||||
if(CAPI_STDCALL)
|
||||
target_compile_definitions(datachannel PUBLIC CAPI_STDCALL)
|
||||
target_compile_definitions(datachannel-static PUBLIC CAPI_STDCALL)
|
||||
|
@ -1,6 +1,6 @@
|
||||
# libdatachannel - C/C++ WebRTC Data Channels
|
||||
|
||||
libdatachannel is a standalone implementation of WebRTC Data Channels, WebRTC Media Transport, and WebSockets in C++17 with C bindings for POSIX platforms (including GNU/Linux, Android, and Apple macOS) and Microsoft Windows. It enables direct connectivity between native applications and web browsers without the pain of importing the entire WebRTC stack. The interface consists of simplified versions of the JavaScript WebRTC and WebSocket APIs present in browsers, in order to ease the design of cross-environment applications.
|
||||
libdatachannel is a standalone implementation of WebRTC Data Channels, WebRTC Media Transport, and WebSockets in C++17 with C bindings for POSIX platforms (including GNU/Linux, Android, and Apple macOS) and Microsoft Windows. It aims at being both straightforward and lightweight with a minimum of external dependencies, to enable direct connectivity between native applications and web browsers without the pain of importing the bloated [Google reference library](https://webrtc.googlesource.com/src/). The interface consists of somewhat simplified versions of the JavaScript WebRTC and WebSocket APIs present in browsers, in order to ease the design of cross-environment applications.
|
||||
It can be compiled with multiple backends:
|
||||
- The security layer can be provided through [OpenSSL](https://www.openssl.org/) or [GnuTLS](https://www.gnutls.org/).
|
||||
- The connectivity for WebRTC can be provided through my ad-hoc ICE library [libjuice](https://github.com/paullouisageneau/libjuice) as submodule or through [libnice](https://github.com/libnice/libnice).
|
||||
@ -26,6 +26,7 @@ Features:
|
||||
- Trickle ICE ([draft-ietf-ice-trickle-21](https://tools.ietf.org/html/draft-ietf-ice-trickle-21))
|
||||
- JSEP compatible ([draft-ietf-rtcweb-jsep-26](https://tools.ietf.org/html/draft-ietf-rtcweb-jsep-26))
|
||||
- Multicast DNS candidates ([draft-ietf-rtcweb-mdns-ice-candidates-04](https://tools.ietf.org/html/draft-ietf-rtcweb-mdns-ice-candidates-04))
|
||||
- DTLS with ECDSA or RSA keys ([RFC8824](https://tools.ietf.org/html/rfc8827))
|
||||
- SRTP and SRTCP key derivation from DTLS ([RFC5764](https://tools.ietf.org/html/rfc5764))
|
||||
- Differentiated Services QoS ([draft-ietf-tsvwg-rtcweb-qos-18](https://tools.ietf.org/html/draft-ietf-tsvwg-rtcweb-qos-18))
|
||||
|
||||
|
@ -64,13 +64,27 @@ struct RTC_CPP_EXPORT ProxyServer {
|
||||
string password;
|
||||
};
|
||||
|
||||
enum class CertificateType {
|
||||
Default = RTC_CERTIFICATE_DEFAULT, // ECDSA
|
||||
Ecdsa = RTC_CERTIFICATE_ECDSA,
|
||||
Rsa = RTC_CERTIFICATE_RSA
|
||||
};
|
||||
|
||||
struct RTC_CPP_EXPORT Configuration {
|
||||
// ICE settings
|
||||
std::vector<IceServer> iceServers;
|
||||
optional<ProxyServer> proxyServer;
|
||||
optional<ProxyServer> proxyServer; // libnice only
|
||||
|
||||
// Options
|
||||
CertificateType certificateType = CertificateType::Ecdsa;
|
||||
bool enableIceTcp = false;
|
||||
bool disableAutoNegotiation = false;
|
||||
|
||||
// Port range
|
||||
uint16_t portRangeBegin = 1024;
|
||||
uint16_t portRangeEnd = 65535;
|
||||
|
||||
// MTU
|
||||
optional<size_t> mtu;
|
||||
};
|
||||
|
||||
|
@ -88,6 +88,12 @@ typedef enum { // Don't change, it must match plog severity
|
||||
RTC_LOG_VERBOSE = 6
|
||||
} rtcLogLevel;
|
||||
|
||||
typedef enum {
|
||||
RTC_CERTIFICATE_DEFAULT = 0,
|
||||
RTC_CERTIFICATE_ECDSA = 1,
|
||||
RTC_CERTIFICATE_RSA = 2,
|
||||
} rtcCertificateType;
|
||||
|
||||
#if RTC_ENABLE_MEDIA
|
||||
|
||||
typedef enum {
|
||||
@ -119,6 +125,7 @@ typedef enum {
|
||||
typedef struct {
|
||||
const char **iceServers;
|
||||
int iceServersCount;
|
||||
rtcCertificateType certificateType;
|
||||
bool enableIceTcp;
|
||||
bool disableAutoNegotiation;
|
||||
uint16_t portRangeBegin;
|
||||
|
@ -351,6 +351,7 @@ int rtcCreatePeerConnection(const rtcConfiguration *config) {
|
||||
for (int i = 0; i < config->iceServersCount; ++i)
|
||||
c.iceServers.emplace_back(string(config->iceServers[i]));
|
||||
|
||||
c.certificateType = static_cast<CertificateType>(config->certificateType);
|
||||
c.enableIceTcp = config->enableIceTcp;
|
||||
c.disableAutoNegotiation = config->disableAutoNegotiation;
|
||||
|
||||
|
@ -28,6 +28,8 @@
|
||||
|
||||
namespace rtc::impl {
|
||||
|
||||
const string COMMON_NAME = "libdatachannel";
|
||||
|
||||
#if USE_GNUTLS
|
||||
|
||||
Certificate::Certificate(string crt_pem, string key_pem)
|
||||
@ -53,8 +55,7 @@ Certificate::Certificate(string crt_pem, string key_pem)
|
||||
gnutls_free(crt_list);
|
||||
};
|
||||
|
||||
unique_ptr<gnutls_x509_crt_t, decltype(free_crt_list)> crt_list(new_crt_list(),
|
||||
free_crt_list);
|
||||
unique_ptr<gnutls_x509_crt_t, decltype(free_crt_list)> crt_list(new_crt_list(), free_crt_list);
|
||||
|
||||
mFingerprint = make_fingerprint(*crt_list);
|
||||
}
|
||||
@ -90,18 +91,35 @@ string make_fingerprint(gnutls_x509_crt_t crt) {
|
||||
|
||||
namespace {
|
||||
|
||||
certificate_ptr make_certificate_impl(string commonName) {
|
||||
certificate_ptr make_certificate_impl(CertificateType type) {
|
||||
PLOG_DEBUG << "Generating certificate (GnuTLS)";
|
||||
|
||||
using namespace gnutls;
|
||||
unique_ptr<gnutls_x509_crt_t, decltype(&free_crt)> crt(new_crt(), free_crt);
|
||||
unique_ptr<gnutls_x509_privkey_t, decltype(&free_privkey)> privkey(new_privkey(), free_privkey);
|
||||
|
||||
#ifdef RSA_KEY_BITS_2048
|
||||
switch (type) {
|
||||
// RFC 8827 WebRTC Security Architecture 6.5. Communications Security
|
||||
// All implementations MUST support DTLS 1.2 with the TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
|
||||
// cipher suite and the P-256 curve
|
||||
// See https://tools.ietf.org/html/rfc8827#section-6.5
|
||||
case CertificateType::Default:
|
||||
case CertificateType::Ecdsa: {
|
||||
gnutls::check(gnutls_x509_privkey_generate(*privkey, GNUTLS_PK_ECDSA,
|
||||
GNUTLS_CURVE_TO_BITS(GNUTLS_ECC_CURVE_SECP256R1),
|
||||
0),
|
||||
"Unable to generate ECDSA P-256 key pair");
|
||||
break;
|
||||
}
|
||||
case CertificateType::Rsa: {
|
||||
const unsigned int bits = 2048;
|
||||
#else
|
||||
const unsigned int bits = gnutls_sec_param_to_pk_bits(GNUTLS_PK_RSA, GNUTLS_SEC_PARAM_HIGH);
|
||||
#endif
|
||||
gnutls::check(gnutls_x509_privkey_generate(*privkey, GNUTLS_PK_RSA, bits, 0),
|
||||
"Unable to generate key pair");
|
||||
"Unable to generate RSA key pair");
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw std::invalid_argument("Unknown certificate type");
|
||||
}
|
||||
|
||||
using namespace std::chrono;
|
||||
auto now = time_point_cast<seconds>(system_clock::now());
|
||||
@ -109,8 +127,8 @@ certificate_ptr make_certificate_impl(string commonName) {
|
||||
gnutls_x509_crt_set_expiration_time(*crt, (now + hours(24 * 365)).time_since_epoch().count());
|
||||
gnutls_x509_crt_set_version(*crt, 1);
|
||||
gnutls_x509_crt_set_key(*crt, *privkey);
|
||||
gnutls_x509_crt_set_dn_by_oid(*crt, GNUTLS_OID_X520_COMMON_NAME, 0, commonName.data(),
|
||||
commonName.size());
|
||||
gnutls_x509_crt_set_dn_by_oid(*crt, GNUTLS_OID_X520_COMMON_NAME, 0, COMMON_NAME.data(),
|
||||
COMMON_NAME.size());
|
||||
|
||||
const size_t serialSize = 16;
|
||||
char serial[serialSize];
|
||||
@ -175,37 +193,71 @@ string make_fingerprint(X509 *x509) {
|
||||
|
||||
namespace {
|
||||
|
||||
certificate_ptr make_certificate_impl(string commonName) {
|
||||
certificate_ptr make_certificate_impl(CertificateType type) {
|
||||
PLOG_DEBUG << "Generating certificate (OpenSSL)";
|
||||
|
||||
shared_ptr<X509> x509(X509_new(), X509_free);
|
||||
shared_ptr<EVP_PKEY> pkey(EVP_PKEY_new(), EVP_PKEY_free);
|
||||
unique_ptr<BIGNUM, decltype(&BN_free)> serial_number(BN_new(), BN_free);
|
||||
unique_ptr<X509_NAME, decltype(&X509_NAME_free)> name(X509_NAME_new(), X509_NAME_free);
|
||||
if (!x509 || !pkey || !serial_number || !name)
|
||||
throw std::runtime_error("Unable to allocate structures for certificate generation");
|
||||
|
||||
switch (type) {
|
||||
// RFC 8827 WebRTC Security Architecture 6.5. Communications Security
|
||||
// All implementations MUST support DTLS 1.2 with the TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
|
||||
// cipher suite and the P-256 curve
|
||||
// See https://tools.ietf.org/html/rfc8827#section-6.5
|
||||
case CertificateType::Default:
|
||||
case CertificateType::Ecdsa: {
|
||||
PLOG_VERBOSE << "Generating ECDSA P-256 key pair";
|
||||
|
||||
unique_ptr<EC_KEY, decltype(&EC_KEY_free)> ecc(EC_KEY_new_by_curve_name(NID_X9_62_prime256v1),
|
||||
EC_KEY_free);
|
||||
if (!ecc)
|
||||
throw std::runtime_error("Unable to allocate structure for ECDSA P-256 key pair");
|
||||
|
||||
EC_KEY_set_asn1_flag(ecc.get(), OPENSSL_EC_NAMED_CURVE); // Set ASN1 OID
|
||||
if (!EC_KEY_generate_key(ecc.get()) ||
|
||||
!EVP_PKEY_assign_EC_KEY(pkey.get(),
|
||||
ecc.release())) // the key will be freed when pkey is freed
|
||||
throw std::runtime_error("Unable to generate ECDSA P-256 key pair");
|
||||
|
||||
break;
|
||||
}
|
||||
case CertificateType::Rsa: {
|
||||
PLOG_VERBOSE << "Generating RSA key pair";
|
||||
|
||||
const int bits = 2048;
|
||||
const unsigned int e = 65537; // 2^16 + 1
|
||||
|
||||
unique_ptr<RSA, decltype(&RSA_free)> rsa(RSA_new(), RSA_free);
|
||||
unique_ptr<BIGNUM, decltype(&BN_free)> exponent(BN_new(), BN_free);
|
||||
unique_ptr<BIGNUM, decltype(&BN_free)> serial_number(BN_new(), BN_free);
|
||||
unique_ptr<X509_NAME, decltype(&X509_NAME_free)> name(X509_NAME_new(), X509_NAME_free);
|
||||
if (!rsa || !exponent)
|
||||
throw std::runtime_error("Unable to allocate structures for RSA key pair");
|
||||
|
||||
if (!x509 || !pkey || !rsa || !exponent || !serial_number || !name)
|
||||
throw std::runtime_error("Unable allocate structures for certificate generation");
|
||||
|
||||
#ifdef RSA_KEY_BITS_2048
|
||||
const int bits = 2048;
|
||||
#else
|
||||
const int bits = 3072;
|
||||
#endif
|
||||
const unsigned int e = 65537; // 2^16 + 1
|
||||
|
||||
if (!pkey || !rsa || !exponent || !BN_set_word(exponent.get(), e) ||
|
||||
if (!BN_set_word(exponent.get(), e) ||
|
||||
!RSA_generate_key_ex(rsa.get(), bits, exponent.get(), NULL) ||
|
||||
!EVP_PKEY_assign_RSA(pkey.get(), rsa.release())) // the key will be freed when pkey is freed
|
||||
throw std::runtime_error("Unable to generate key pair");
|
||||
!EVP_PKEY_assign_RSA(pkey.get(),
|
||||
rsa.release())) // the key will be freed when pkey is freed
|
||||
throw std::runtime_error("Unable to generate RSA key pair");
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw std::invalid_argument("Unknown certificate type");
|
||||
}
|
||||
|
||||
const size_t serialSize = 16;
|
||||
auto *commonNameBytes =
|
||||
reinterpret_cast<unsigned char *>(const_cast<char *>(commonName.c_str()));
|
||||
reinterpret_cast<unsigned char *>(const_cast<char *>(COMMON_NAME.c_str()));
|
||||
|
||||
if (!X509_set_pubkey(x509.get(), pkey.get()))
|
||||
throw std::runtime_error("Unable to set certificate public key");
|
||||
|
||||
if (!X509_gmtime_adj(X509_getm_notBefore(x509.get()), 3600 * -1) ||
|
||||
!X509_gmtime_adj(X509_getm_notAfter(x509.get()), 3600 * 24 * 365) ||
|
||||
!X509_set_version(x509.get(), 1) || !X509_set_pubkey(x509.get(), pkey.get()) ||
|
||||
!X509_set_version(x509.get(), 1) ||
|
||||
!BN_pseudo_rand(serial_number.get(), serialSize, 0, 0) ||
|
||||
!BN_to_ASN1_INTEGER(serial_number.get(), X509_get_serialNumber(x509.get())) ||
|
||||
!X509_NAME_add_entry_by_NID(name.get(), NID_commonName, MBSTRING_UTF8, commonNameBytes, -1,
|
||||
@ -226,28 +278,8 @@ certificate_ptr make_certificate_impl(string commonName) {
|
||||
|
||||
// Common for GnuTLS and OpenSSL
|
||||
|
||||
namespace {
|
||||
|
||||
static std::unordered_map<string, future_certificate_ptr> CertificateCache;
|
||||
static std::mutex CertificateCacheMutex;
|
||||
|
||||
} // namespace
|
||||
|
||||
future_certificate_ptr make_certificate(string commonName) {
|
||||
std::lock_guard lock(CertificateCacheMutex);
|
||||
|
||||
if (auto it = CertificateCache.find(commonName); it != CertificateCache.end())
|
||||
return it->second;
|
||||
|
||||
auto future = ThreadPool::Instance().enqueue(make_certificate_impl, commonName);
|
||||
auto shared = future.share();
|
||||
CertificateCache.emplace(std::move(commonName), shared);
|
||||
return shared;
|
||||
}
|
||||
|
||||
void CleanupCertificateCache() {
|
||||
std::lock_guard lock(CertificateCacheMutex);
|
||||
CertificateCache.clear();
|
||||
future_certificate_ptr make_certificate(CertificateType type) {
|
||||
return ThreadPool::Instance().enqueue(make_certificate_impl, type);
|
||||
}
|
||||
|
||||
} // namespace rtc::impl
|
||||
|
@ -21,6 +21,7 @@
|
||||
|
||||
#include "common.hpp"
|
||||
#include "tls.hpp"
|
||||
#include "configuration.hpp" // for CertificateType
|
||||
|
||||
#include <future>
|
||||
#include <tuple>
|
||||
@ -61,9 +62,7 @@ string make_fingerprint(X509 *x509);
|
||||
using certificate_ptr = shared_ptr<Certificate>;
|
||||
using future_certificate_ptr = std::shared_future<certificate_ptr>;
|
||||
|
||||
future_certificate_ptr make_certificate(string commonName = "libdatachannel"); // cached
|
||||
|
||||
void CleanupCertificateCache();
|
||||
future_certificate_ptr make_certificate(CertificateType type = CertificateType::Default);
|
||||
|
||||
} // namespace rtc::impl
|
||||
|
||||
|
@ -119,9 +119,10 @@ bool DtlsTransport::send(message_ptr message) {
|
||||
|
||||
PLOG_VERBOSE << "Send size=" << message->size();
|
||||
|
||||
mCurrentDscp = message->dscp;
|
||||
ssize_t ret;
|
||||
do {
|
||||
std::lock_guard lock(mSendMutex);
|
||||
mCurrentDscp = message->dscp;
|
||||
ret = gnutls_record_send(mSession, message->data(), message->size());
|
||||
} while (ret == GNUTLS_E_INTERRUPTED || ret == GNUTLS_E_AGAIN);
|
||||
|
||||
@ -197,6 +198,17 @@ void DtlsTransport::runRecvLoop() {
|
||||
ret = gnutls_record_recv(mSession, buffer, bufferSize);
|
||||
} while (ret == GNUTLS_E_INTERRUPTED || ret == GNUTLS_E_AGAIN);
|
||||
|
||||
// RFC 8827: Implementations MUST NOT implement DTLS renegotiation and MUST reject it
|
||||
// with a "no_renegotiation" alert if offered.
|
||||
// See https://tools.ietf.org/html/rfc8827#section-6.5
|
||||
if (ret == GNUTLS_E_REHANDSHAKE) {
|
||||
do {
|
||||
std::lock_guard lock(mSendMutex);
|
||||
ret = gnutls_alert_send(mSession, GNUTLS_AL_WARNING, GNUTLS_A_NO_RENEGOTIATION);
|
||||
} while (ret == GNUTLS_E_INTERRUPTED || ret == GNUTLS_E_AGAIN);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Consider premature termination as remote closing
|
||||
if (ret == GNUTLS_E_PREMATURE_TERMINATION) {
|
||||
PLOG_DEBUG << "DTLS connection terminated";
|
||||
@ -332,7 +344,10 @@ DtlsTransport::DtlsTransport(shared_ptr<IceTransport> lower, shared_ptr<Certific
|
||||
// RFC 8261: SCTP performs segmentation and reassembly based on the path MTU.
|
||||
// Therefore, the DTLS layer MUST NOT use any compression algorithm.
|
||||
// See https://tools.ietf.org/html/rfc8261#section-5
|
||||
SSL_CTX_set_options(mCtx, SSL_OP_NO_SSLv3 | SSL_OP_NO_COMPRESSION | SSL_OP_NO_QUERY_MTU);
|
||||
// RFC 8827: Implementations MUST NOT implement DTLS renegotiation
|
||||
// See https://tools.ietf.org/html/rfc8827#section-6.5
|
||||
SSL_CTX_set_options(mCtx, SSL_OP_NO_SSLv3 | SSL_OP_NO_COMPRESSION | SSL_OP_NO_QUERY_MTU |
|
||||
SSL_OP_NO_RENEGOTIATION);
|
||||
SSL_CTX_set_min_proto_version(mCtx, DTLS1_VERSION);
|
||||
SSL_CTX_set_read_ahead(mCtx, 1);
|
||||
SSL_CTX_set_quiet_shutdown(mCtx, 1);
|
||||
|
@ -68,6 +68,7 @@ protected:
|
||||
|
||||
#if USE_GNUTLS
|
||||
gnutls_session_t mSession;
|
||||
std::mutex mSendMutex;
|
||||
|
||||
static int CertificateCallback(gnutls_session_t session);
|
||||
static ssize_t WriteCallback(gnutls_transport_ptr_t ptr, const void *data, size_t len);
|
||||
|
@ -53,7 +53,7 @@ static LogCounter
|
||||
"Number of unknown RTCP packet types over past second");
|
||||
|
||||
PeerConnection::PeerConnection(Configuration config_)
|
||||
: config(std::move(config_)), mCertificate(make_certificate()),
|
||||
: config(std::move(config_)), mCertificate(make_certificate(config.certificateType)),
|
||||
mProcessor(std::make_unique<Processor>()) {
|
||||
PLOG_VERBOSE << "Creating PeerConnection";
|
||||
|
||||
|
@ -58,6 +58,7 @@ gnutls_datum_t make_datum(char *data, size_t size);
|
||||
#include <openssl/bio.h>
|
||||
#include <openssl/bn.h>
|
||||
#include <openssl/ec.h>
|
||||
#include <openssl/ec.h>
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/pem.h>
|
||||
#include <openssl/rsa.h>
|
||||
|
@ -72,7 +72,6 @@ void doCleanup() {
|
||||
PLOG_DEBUG << "Global cleanup";
|
||||
|
||||
impl::ThreadPool::Instance().join();
|
||||
impl::CleanupCertificateCache();
|
||||
|
||||
impl::SctpTransport::Cleanup();
|
||||
impl::DtlsTransport::Cleanup();
|
||||
@ -111,9 +110,6 @@ void Init::Preload() {
|
||||
auto token = Token();
|
||||
if (!Global)
|
||||
Global = new shared_ptr<void>(token);
|
||||
|
||||
PLOG_DEBUG << "Preloading certificate";
|
||||
impl::make_certificate().wait();
|
||||
}
|
||||
|
||||
void Init::Cleanup() {
|
||||
|
Reference in New Issue
Block a user