Merge pull request #377 from paullouisageneau/ecdsa

ECDSA support
This commit is contained in:
Paul-Louis Ageneau
2021-03-22 21:05:29 +01:00
committed by GitHub
12 changed files with 131 additions and 70 deletions

View File

@ -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)

View File

@ -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))

View File

@ -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;
};

View File

@ -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;

View File

@ -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;

View File

@ -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
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");
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;
gnutls::check(gnutls_x509_privkey_generate(*privkey, GNUTLS_PK_RSA, bits, 0),
"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<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 (!x509 || !pkey || !serial_number || !name)
throw std::runtime_error("Unable to allocate structures for certificate generation");
if (!x509 || !pkey || !rsa || !exponent || !serial_number || !name)
throw std::runtime_error("Unable 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";
#ifdef RSA_KEY_BITS_2048
const int bits = 2048;
#else
const int bits = 3072;
#endif
const unsigned int e = 65537; // 2^16 + 1
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");
if (!pkey || !rsa || !exponent || !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");
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);
if (!rsa || !exponent)
throw std::runtime_error("Unable to allocate structures for RSA key pair");
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 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

View File

@ -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

View File

@ -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);

View File

@ -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);

View File

@ -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";

View File

@ -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>

View File

@ -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() {