From 460c7f90ac53ad1fbd7b80faa0c715352a42e073 Mon Sep 17 00:00:00 2001
From: Paul-Louis Ageneau <paul-louis@ageneau.org>
Date: Mon, 27 May 2024 13:09:49 +0200
Subject: [PATCH 1/3] Add chain to Certificate with OpenSSL

---
 src/impl/certificate.cpp | 40 ++++++++++++++++++++++++++++++++--------
 src/impl/certificate.hpp |  4 +++-
 2 files changed, 35 insertions(+), 9 deletions(-)

diff --git a/src/impl/certificate.cpp b/src/impl/certificate.cpp
index fed8cef1e..1194bed74 100644
--- a/src/impl/certificate.cpp
+++ b/src/impl/certificate.cpp
@@ -9,6 +9,7 @@
 #include "certificate.hpp"
 #include "threadpool.hpp"
 
+#include <algorithm>
 #include <cassert>
 #include <chrono>
 #include <iomanip>
@@ -384,9 +385,16 @@ Certificate Certificate::FromString(string crt_pem, string key_pem) {
 	BIO *bio = BIO_new(BIO_s_mem());
 	BIO_write(bio, crt_pem.data(), int(crt_pem.size()));
 	auto x509 = shared_ptr<X509>(PEM_read_bio_X509(bio, nullptr, nullptr, nullptr), X509_free);
-	BIO_free(bio);
-	if (!x509)
+	if (!x509) {
+		BIO_free(bio);
 		throw std::invalid_argument("Unable to import PEM certificate");
+	}
+	std::vector<shared_ptr<X509>> chain;
+	while (auto extra =
+	           shared_ptr<X509>(PEM_read_bio_X509(bio, nullptr, nullptr, nullptr), X509_free)) {
+		chain.push_back(std::move(extra));
+	}
+	BIO_free(bio);
 
 	bio = BIO_new(BIO_s_mem());
 	BIO_write(bio, key_pem.data(), int(key_pem.size()));
@@ -396,7 +404,7 @@ Certificate Certificate::FromString(string crt_pem, string key_pem) {
 	if (!pkey)
 		throw std::invalid_argument("Unable to import PEM key");
 
-	return Certificate(x509, pkey);
+	return Certificate(x509, pkey, std::move(chain));
 }
 
 Certificate Certificate::FromFile(const string &crt_pem_file, const string &key_pem_file,
@@ -408,9 +416,16 @@ Certificate Certificate::FromFile(const string &crt_pem_file, const string &key_
 		throw std::invalid_argument("Unable to open PEM certificate file");
 
 	auto x509 = shared_ptr<X509>(PEM_read_bio_X509(bio, nullptr, nullptr, nullptr), X509_free);
-	BIO_free(bio);
-	if (!x509)
+	if (!x509) {
+		BIO_free(bio);
 		throw std::invalid_argument("Unable to import PEM certificate from file");
+	}
+	std::vector<shared_ptr<X509>> chain;
+	while (auto extra =
+	           shared_ptr<X509>(PEM_read_bio_X509(bio, nullptr, nullptr, nullptr), X509_free)) {
+		chain.push_back(std::move(extra));
+	}
+	BIO_free(bio);
 
 	bio = openssl::BIO_new_from_file(key_pem_file);
 	if (!bio)
@@ -423,7 +438,7 @@ Certificate Certificate::FromFile(const string &crt_pem_file, const string &key_
 	if (!pkey)
 		throw std::invalid_argument("Unable to import PEM key from file");
 
-	return Certificate(x509, pkey);
+	return Certificate(x509, pkey, std::move(chain));
 }
 
 Certificate Certificate::Generate(CertificateType type, const string &commonName) {
@@ -514,14 +529,23 @@ Certificate Certificate::Generate(CertificateType type, const string &commonName
 	return Certificate(x509, pkey);
 }
 
-Certificate::Certificate(shared_ptr<X509> x509, shared_ptr<EVP_PKEY> pkey)
-    : mX509(std::move(x509)), mPKey(std::move(pkey)),
+Certificate::Certificate(shared_ptr<X509> x509, shared_ptr<EVP_PKEY> pkey,
+                         std::vector<shared_ptr<X509>> chain)
+    : mX509(std::move(x509)), mPKey(std::move(pkey)), mChain(std::move(chain)),
       mFingerprint(make_fingerprint(mX509.get(), CertificateFingerprint::Algorithm::Sha256)) {}
 
 std::tuple<X509 *, EVP_PKEY *> Certificate::credentials() const {
 	return {mX509.get(), mPKey.get()};
 }
 
+std::vector<X509 *> Certificate::chain() const {
+	std::vector<X509 *> v;
+	v.reserve(mChain.size());
+	std::transform(mChain.begin(), mChain.end(), std::back_inserter(v),
+	               [](const auto &c) { return c.get(); });
+	return v;
+}
+
 string make_fingerprint(X509 *x509, CertificateFingerprint::Algorithm fingerprintAlgorithm) {
 	size_t size = CertificateFingerprint::AlgorithmSize(fingerprintAlgorithm);
 	std::vector<unsigned char> buffer(size);
diff --git a/src/impl/certificate.hpp b/src/impl/certificate.hpp
index 66111ccb4..0617ba173 100644
--- a/src/impl/certificate.hpp
+++ b/src/impl/certificate.hpp
@@ -34,8 +34,9 @@ class Certificate {
 	Certificate(shared_ptr<mbedtls_x509_crt> crt, shared_ptr<mbedtls_pk_context> pk);
 	std::tuple<shared_ptr<mbedtls_x509_crt>, shared_ptr<mbedtls_pk_context>> credentials() const;
 #else // OPENSSL
-	Certificate(shared_ptr<X509> x509, shared_ptr<EVP_PKEY> pkey);
+	Certificate(shared_ptr<X509> x509, shared_ptr<EVP_PKEY> pkey, std::vector<shared_ptr<X509>> chain = {});
 	std::tuple<X509 *, EVP_PKEY *> credentials() const;
+	std::vector<X509 *> chain() const;
 #endif
 
 	CertificateFingerprint fingerprint() const;
@@ -52,6 +53,7 @@ class Certificate {
 #else
 	const shared_ptr<X509> mX509;
 	const shared_ptr<EVP_PKEY> mPKey;
+	const std::vector<shared_ptr<X509>> mChain;
 #endif
 
 	const string mFingerprint;

From f9ee7aff144a3ccf97401a31c402a0fd99be5058 Mon Sep 17 00:00:00 2001
From: Paul-Louis Ageneau <paul-louis@ageneau.org>
Date: Mon, 27 May 2024 13:10:56 +0200
Subject: [PATCH 2/3] Use certificate chain in TLS transport with OpenSSL

---
 src/impl/tlstransport.cpp | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/src/impl/tlstransport.cpp b/src/impl/tlstransport.cpp
index 8f66d200f..0f5e9e967 100644
--- a/src/impl/tlstransport.cpp
+++ b/src/impl/tlstransport.cpp
@@ -603,6 +603,9 @@ TlsTransport::TlsTransport(variant<shared_ptr<TcpTransport>, shared_ptr<HttpProx
 			auto [x509, pkey] = certificate->credentials();
 			SSL_CTX_use_certificate(mCtx, x509);
 			SSL_CTX_use_PrivateKey(mCtx, pkey);
+
+			for (auto c : certificate->chain())
+				SSL_CTX_add1_chain_cert(mCtx, c); // add1 increments reference count
 		}
 
 		SSL_CTX_set_options(mCtx, SSL_OP_NO_SSLv3 | SSL_OP_NO_RENEGOTIATION);

From 5d2303ede30834619c2a42f746bc1e4a1336054e Mon Sep 17 00:00:00 2001
From: Paul-Louis Ageneau <paul-louis@ageneau.org>
Date: Thu, 30 May 2024 13:29:33 +0200
Subject: [PATCH 3/3] Enhance application-only SDP generation

---
 src/description.cpp | 18 +++++++++---------
 1 file changed, 9 insertions(+), 9 deletions(-)

diff --git a/src/description.cpp b/src/description.cpp
index df3e91f61..77d0d8033 100644
--- a/src/description.cpp
+++ b/src/description.cpp
@@ -310,7 +310,6 @@ string Description::generateSdp(string_view eol) const {
 
 	// Session-level attributes
 	sdp << "a=msid-semantic:WMS *" << eol;
-
 	if (!mIceOptions.empty())
 		sdp << "a=ice-options:" << utils::implode(mIceOptions, ',') << eol;
 	if (mFingerprint)
@@ -372,28 +371,29 @@ string Description::generateApplicationSdp(string_view eol) const {
 	const uint16_t port =
 	    cand && cand->isResolved() ? *cand->port() : 9; // Port 9 is the discard protocol
 
+	// Session-level attributes
+	sdp << "a=msid-semantic:WMS *" << eol;
+	if (!mIceOptions.empty())
+		sdp << "a=ice-options:" << utils::implode(mIceOptions, ',') << eol;
+
+	for (const auto &attr : mAttributes)
+		sdp << "a=" << attr << eol;
+
 	// Application
 	auto app = mApplication ? mApplication : std::make_shared<Application>();
 	sdp << app->generateSdp(eol, addr, port);
 
-	// Session-level attributes
-	sdp << "a=msid-semantic:WMS *" << eol;
+	// Media-level attributes
 	sdp << "a=setup:" << mRole << eol;
-
 	if (mIceUfrag)
 		sdp << "a=ice-ufrag:" << *mIceUfrag << eol;
 	if (mIcePwd)
 		sdp << "a=ice-pwd:" << *mIcePwd << eol;
-	if (!mIceOptions.empty())
-		sdp << "a=ice-options:" << utils::implode(mIceOptions, ',') << eol;
 	if (mFingerprint)
 		sdp << "a=fingerprint:"
 		    << CertificateFingerprint::AlgorithmIdentifier(mFingerprint->algorithm) << " "
 		    << mFingerprint->value << eol;
 
-	for (const auto &attr : mAttributes)
-		sdp << "a=" << attr << eol;
-
 	// Candidates
 	for (const auto &candidate : mCandidates)
 		sdp << string(candidate) << eol;