Ver Fonte

Implemented ECDSA certificates support and removed certificate cache

Paul-Louis Ageneau há 4 anos atrás
pai
commit
9b36ac3dd3

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

+ 11 - 1
include/rtc/configuration.hpp

@@ -64,13 +64,23 @@ struct RTC_CPP_EXPORT ProxyServer {
 	string password;
 };
 
+enum class CertificateType { Ecdsa = 0, 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;
 };
 

+ 82 - 53
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,33 @@ string make_fingerprint(gnutls_x509_crt_t crt) {
 
 namespace {
 
-certificate_ptr make_certificate_impl(string commonName) {
-	using namespace gnutls;
+certificate_ptr make_certificate_impl(CertificateType type) {
+	PLOG_DEBUG << "Generating certificate (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::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 = gnutls_sec_param_to_pk_bits(GNUTLS_PK_RSA, GNUTLS_SEC_PARAM_HIGH);
+		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 +125,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 +191,70 @@ 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::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 = 3072;
+		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 +275,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::Ecdsa);
 
 } // namespace rtc::impl
 

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