Bläddra i källkod

Merge pull request #377 from paullouisageneau/ecdsa

ECDSA support
Paul-Louis Ageneau 4 år sedan
förälder
incheckning
9f17522230

+ 0 - 6
CMakeLists.txt

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

+ 2 - 1
README.md

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

+ 15 - 1
include/rtc/configuration.hpp

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

+ 7 - 0
include/rtc/rtc.h

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

+ 1 - 0
src/capi.cpp

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

+ 84 - 52
src/impl/certificate.cpp

@@ -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");
+
+	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";
 
-	if (!x509 || !pkey || !rsa || !exponent || !serial_number || !name)
-		throw std::runtime_error("Unable allocate structures for certificate generation");
+		const int bits = 2048;
+		const unsigned int e = 65537; // 2^16 + 1
 
-#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<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 (!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");
+		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

+ 2 - 3
src/impl/certificate.hpp

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

+ 17 - 2
src/impl/dtlstransport.cpp

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

+ 1 - 0
src/impl/dtlstransport.hpp

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

+ 1 - 1
src/impl/peerconnection.cpp

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

+ 1 - 0
src/impl/tls.hpp

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

+ 0 - 4
src/init.cpp

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