From 17146ca0aced90f00707e99419701cd9d3c9801b Mon Sep 17 00:00:00 2001 From: melpon Date: Tue, 17 Sep 2024 18:05:39 +0900 Subject: [PATCH] =?UTF-8?q?=E3=83=AB=E3=83=BC=E3=83=88=E8=A8=BC=E6=98=8E?= =?UTF-8?q?=E6=9B=B8=E3=82=92=E6=8C=87=E5=AE=9A=E5=8F=AF=E8=83=BD=E3=81=AB?= =?UTF-8?q?=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGES.md | 2 ++ examples/sumomo/src/sumomo.cpp | 29 +++++++++++++++ include/sora/rtc_ssl_verifier.h | 6 +++- include/sora/sora_signaling.h | 1 + include/sora/ssl_verifier.h | 7 ++-- include/sora/websocket.h | 11 ++++-- src/rtc_ssl_verifier.cpp | 8 +++-- src/sora_signaling.cpp | 15 ++++---- src/ssl_verifier.cpp | 64 +++++++++++++++++++-------------- src/websocket.cpp | 21 +++++++---- 10 files changed, 116 insertions(+), 48 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 7ea09953..053528a3 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -27,6 +27,8 @@ - @tnoho - [ADD] Intel VPL で AV1 エンコーダを動くようにする - @tnoho +- [ADD] ルート証明書を指定可能にする + - @melpon ### misc diff --git a/examples/sumomo/src/sumomo.cpp b/examples/sumomo/src/sumomo.cpp index 85feba06..e0c2421f 100644 --- a/examples/sumomo/src/sumomo.cpp +++ b/examples/sumomo/src/sumomo.cpp @@ -57,6 +57,11 @@ struct SumomoConfig { std::string srtp_key_log_file; + bool insecure = false; + std::string client_cert; + std::string client_key; + std::string ca_cert; + struct Size { int width; int height; @@ -162,6 +167,24 @@ class Sumomo : public std::enable_shared_from_this, context_->signaling_thread()->BlockingCall([this]() { return context_->connection_context()->default_socket_factory(); }); + config_.insecure = config.insecure; + auto load_file = [](const std::string& path) { + std::ifstream ifs(path, std::ios::binary); + if (!ifs) { + return std::string(); + } + return std::string((std::istreambuf_iterator(ifs)), + std::istreambuf_iterator()); + }; + if (!config_.client_cert.empty()) { + config.client_cert = load_file(config_.client_cert); + } + if (!config_.client_key.empty()) { + config.client_key = load_file(config_.client_key); + } + if (!config_.ca_cert.empty()) { + config.ca_cert = load_file(config_.ca_cert); + } conn_ = sora::SoraSignaling::Create(config); boost::asio::executor_work_guard @@ -413,6 +436,12 @@ int main(int argc, char* argv[]) { app.add_option("--srtp-key-log-file", config.srtp_key_log_file, "SRTP keying material output file"); + // 証明書に関するオプション + app.add_flag("--insecure", config.insecure, "Allow insecure connection"); + app.add_option("--client-cert", config.client_cert, "Client certificate file")->check(CLI::ExistingFile); + app.add_option("--client-key", config.client_key, "Client key file")->check(CLI::ExistingFile); + app.add_option("--ca-cert", config.ca_cert, "CA certificate file")->check(CLI::ExistingFile); + // SoraClientContextConfig に関するオプション boost::optional use_hardware_encoder; add_optional_bool(app, "--use-hardware-encoder", use_hardware_encoder, diff --git a/include/sora/rtc_ssl_verifier.h b/include/sora/rtc_ssl_verifier.h index ef2910ba..79e5d37a 100644 --- a/include/sora/rtc_ssl_verifier.h +++ b/include/sora/rtc_ssl_verifier.h @@ -1,6 +1,9 @@ #ifndef RTC_SSL_VERIFIER #define RTC_SSL_VERIFIER +#include +#include + // WebRTC #include @@ -8,11 +11,12 @@ namespace sora { class RTCSSLVerifier : public rtc::SSLCertificateVerifier { public: - RTCSSLVerifier(bool insecure); + RTCSSLVerifier(bool insecure, std::optional ca_cert); bool Verify(const rtc::SSLCertificate& certificate) override; private: bool insecure_; + std::optional ca_cert_; }; } // namespace sora diff --git a/include/sora/sora_signaling.h b/include/sora/sora_signaling.h index 9fb90244..514a6996 100644 --- a/include/sora/sora_signaling.h +++ b/include/sora/sora_signaling.h @@ -135,6 +135,7 @@ struct SoraSignalingConfig { std::string client_cert; std::string client_key; + std::optional ca_cert; int websocket_close_timeout = 3; int websocket_connection_timeout = 30; diff --git a/include/sora/ssl_verifier.h b/include/sora/ssl_verifier.h index 6b2d18c4..220f8055 100644 --- a/include/sora/ssl_verifier.h +++ b/include/sora/ssl_verifier.h @@ -1,6 +1,7 @@ #ifndef SORA_SSL_VERIFIER_H_ #define SORA_SSL_VERIFIER_H_ +#include #include // openssl @@ -11,7 +12,9 @@ namespace sora { // 自前で SSL の証明書検証を行うためのクラス class SSLVerifier { public: - static bool VerifyX509(X509* x509, STACK_OF(X509) * chain); + static bool VerifyX509(X509* x509, + STACK_OF(X509) * chain, + const std::optional& ca_cert); private: // PEM 形式のルート証明書を追加する @@ -20,6 +23,6 @@ class SSLVerifier { static bool LoadBuiltinSSLRootCertificates(X509_STORE* store); }; -} +} // namespace sora #endif diff --git a/include/sora/websocket.h b/include/sora/websocket.h index 4208de62..8667c6bb 100644 --- a/include/sora/websocket.h +++ b/include/sora/websocket.h @@ -3,6 +3,7 @@ #include #include +#include // Boost #include @@ -52,13 +53,15 @@ class Websocket { boost::asio::io_context& ioc, bool insecure, const std::string& client_cert, - const std::string& client_key); + const std::string& client_key, + const std::optional& ca_cert); // HTTP Proxy + SSL Websocket(https_proxy_tag, boost::asio::io_context& ioc, bool insecure, const std::string& client_cert, const std::string& client_key, + const std::optional& ca_cert, std::string proxy_url, std::string proxy_username, std::string proxy_password); @@ -89,7 +92,9 @@ class Websocket { private: bool IsSSL() const; - void InitWss(ssl_websocket_t* wss, bool insecure); + void InitWss(ssl_websocket_t* wss, + bool insecure, + const std::optional& ca_cert); void OnResolve(std::string host, std::string port, @@ -161,6 +166,8 @@ class Websocket { boost::beast::http::response_parser> proxy_resp_parser_; boost::beast::flat_buffer proxy_buffer_; + + std::optional ca_cert_; }; } // namespace sora diff --git a/src/rtc_ssl_verifier.cpp b/src/rtc_ssl_verifier.cpp index 40fceb80..9f27ef2a 100644 --- a/src/rtc_ssl_verifier.cpp +++ b/src/rtc_ssl_verifier.cpp @@ -7,7 +7,9 @@ namespace sora { -RTCSSLVerifier::RTCSSLVerifier(bool insecure) : insecure_(insecure) {} +RTCSSLVerifier::RTCSSLVerifier(bool insecure, + std::optional ca_cert) + : insecure_(insecure), ca_cert_(ca_cert) {} bool RTCSSLVerifier::Verify(const rtc::SSLCertificate& certificate) { // insecure の場合は証明書をチェックしない @@ -17,7 +19,7 @@ bool RTCSSLVerifier::Verify(const rtc::SSLCertificate& certificate) { SSL* ssl = static_cast(certificate).ssl(); X509* x509 = SSL_get_peer_certificate(ssl); STACK_OF(X509)* chain = SSL_get_peer_cert_chain(ssl); - return SSLVerifier::VerifyX509(x509, chain); + return SSLVerifier::VerifyX509(x509, chain, ca_cert_); } -} \ No newline at end of file +} // namespace sora \ No newline at end of file diff --git a/src/sora_signaling.cpp b/src/sora_signaling.cpp index b955ae6b..2a4335ef 100644 --- a/src/sora_signaling.cpp +++ b/src/sora_signaling.cpp @@ -171,13 +171,14 @@ void SoraSignaling::Redirect(std::string url) { ws.reset( new Websocket(Websocket::ssl_tag(), *self->config_.io_context, self->config_.insecure, self->config_.client_cert, - self->config_.client_key)); + self->config_.client_key, self->config_.ca_cert)); } else { ws.reset(new Websocket( Websocket::https_proxy_tag(), *self->config_.io_context, self->config_.insecure, self->config_.client_cert, - self->config_.client_key, self->config_.proxy_url, - self->config_.proxy_username, self->config_.proxy_password)); + self->config_.client_key, self->config_.ca_cert, + self->config_.proxy_url, self->config_.proxy_username, + self->config_.proxy_password)); } } else { ws.reset(new Websocket(*self->config_.io_context)); @@ -509,7 +510,7 @@ SoraSignaling::CreatePeerConnection(boost::json::value jconfig) { // // それを解消するために tls_cert_verifier を設定して自前で検証を行う。 dependencies.tls_cert_verifier = std::unique_ptr( - new RTCSSLVerifier(config_.insecure)); + new RTCSSLVerifier(config_.insecure, config_.ca_cert)); // Proxy を設定する if (!config_.proxy_url.empty() && config_.network_manager != nullptr && @@ -1192,12 +1193,12 @@ void SoraSignaling::DoConnect() { if (config_.proxy_url.empty()) { ws.reset(new Websocket(Websocket::ssl_tag(), *config_.io_context, config_.insecure, config_.client_cert, - config_.client_key)); + config_.client_key, config_.ca_cert)); } else { ws.reset(new Websocket( Websocket::https_proxy_tag(), *config_.io_context, config_.insecure, - config_.client_cert, config_.client_key, config_.proxy_url, - config_.proxy_username, config_.proxy_password)); + config_.client_cert, config_.client_key, config_.ca_cert, + config_.proxy_url, config_.proxy_username, config_.proxy_password)); } } else { ws.reset(new Websocket(*config_.io_context)); diff --git a/src/ssl_verifier.cpp b/src/ssl_verifier.cpp index 458d2f69..a198a825 100644 --- a/src/ssl_verifier.cpp +++ b/src/ssl_verifier.cpp @@ -97,20 +97,22 @@ bool SSLVerifier::AddCert(const std::string& pem, X509_STORE* store) { RTC_LOG(LS_ERROR) << "BIO_new_mem_buf failed"; return false; } - X509* cert = PEM_read_bio_X509(bio, nullptr, nullptr, nullptr); - if (cert == nullptr) { - BIO_free(bio); - RTC_LOG(LS_ERROR) << "PEM_read_bio_X509 failed"; - return false; - } - int r = X509_STORE_add_cert(store, cert); - if (r == 0) { + while (true) { + X509* cert = PEM_read_bio_X509(bio, nullptr, nullptr, nullptr); + if (cert == nullptr) { + break; + } + + int r = X509_STORE_add_cert(store, cert); + if (r == 0) { + X509_free(cert); + BIO_free(bio); + RTC_LOG(LS_ERROR) << "X509_STORE_add_cert failed"; + return false; + } X509_free(cert); - BIO_free(bio); - RTC_LOG(LS_ERROR) << "X509_STORE_add_cert failed"; - return false; } - X509_free(cert); + BIO_free(bio); return true; } @@ -138,7 +140,9 @@ bool SSLVerifier::LoadBuiltinSSLRootCertificates(X509_STORE* store) { return count_of_added_certs > 0; } -bool SSLVerifier::VerifyX509(X509* x509, STACK_OF(X509) * chain) { +bool SSLVerifier::VerifyX509(X509* x509, + STACK_OF(X509) * chain, + const std::optional& ca_cert) { { char data[256]; RTC_LOG(LS_INFO) << "cert:"; @@ -186,20 +190,28 @@ bool SSLVerifier::VerifyX509(X509* x509, STACK_OF(X509) * chain) { return false; } - // Let's Encrypt の証明書を追加 - if (!AddCert(isrg_root, store)) { - return false; - } - if (!AddCert(lets_encrypt_r3, store)) { - return false; - } - - // WebRTC が用意しているルート証明書の設定 - LoadBuiltinSSLRootCertificates(store); - // デフォルト証明書のパスの設定 - X509_STORE_set_default_paths(store); - RTC_LOG(LS_INFO) << "default cert file: " << X509_get_default_cert_file(); + if (!ca_cert) { + // Let's Encrypt の証明書を追加 + if (!AddCert(isrg_root, store)) { + return false; + } + if (!AddCert(lets_encrypt_r3, store)) { + return false; + } + // WebRTC が用意しているルート証明書の設定 + LoadBuiltinSSLRootCertificates(store); + // デフォルト証明書のパスの設定 + X509_STORE_set_default_paths(store); + RTC_LOG(LS_INFO) << "default cert file: " << X509_get_default_cert_file(); + } else { + // ルート証明書が指定されている場合、その証明書以外は読み込まない + if (!AddCert(*ca_cert, store)) { + RTC_LOG(LS_ERROR) << "Failed to add ca_cert: ca_cert_length=" + << ca_cert->size(); + return false; + } + } ctx = X509_STORE_CTX_new(); if (ctx == nullptr) { RTC_LOG(LS_ERROR) << "X509_STORE_CTX_new failed"; diff --git a/src/websocket.cpp b/src/websocket.cpp index b2101045..a44aa72b 100644 --- a/src/websocket.cpp +++ b/src/websocket.cpp @@ -56,15 +56,17 @@ Websocket::Websocket(Websocket::ssl_tag, boost::asio::io_context& ioc, bool insecure, const std::string& client_cert, - const std::string& client_key) + const std::string& client_key, + const std::optional& ca_cert) : resolver_(new boost::asio::ip::tcp::resolver(ioc)), strand_(ioc.get_executor()), close_timeout_timer_(ioc), insecure_(insecure), - user_agent_(Version::GetDefaultUserAgent()) { + user_agent_(Version::GetDefaultUserAgent()), + ca_cert_(ca_cert) { ssl_ctx_ = CreateSSLContext(client_cert, client_key); wss_.reset(new ssl_websocket_t(ioc, *ssl_ctx_)); - InitWss(wss_.get(), insecure); + InitWss(wss_.get(), insecure, ca_cert); } Websocket::Websocket(boost::asio::ip::tcp::socket socket) : ws_(new websocket_t(std::move(socket))), @@ -78,6 +80,7 @@ Websocket::Websocket(https_proxy_tag, bool insecure, const std::string& client_cert, const std::string& client_key, + const std::optional& ca_cert, std::string proxy_url, std::string proxy_username, std::string proxy_password) @@ -85,6 +88,7 @@ Websocket::Websocket(https_proxy_tag, strand_(ioc.get_executor()), close_timeout_timer_(ioc), insecure_(insecure), + ca_cert_(ca_cert), https_proxy_(true), proxy_socket_(new boost::asio::ip::tcp::socket(ioc)), proxy_url_(std::move(proxy_url)), @@ -106,12 +110,15 @@ bool Websocket::IsSSL() const { return https_proxy_ || wss_ != nullptr; } -void Websocket::InitWss(ssl_websocket_t* wss, bool insecure) { +void Websocket::InitWss(ssl_websocket_t* wss, + bool insecure, + const std::optional& ca_cert) { wss->write_buffer_bytes(8192); wss->next_layer().set_verify_mode(boost::asio::ssl::verify_peer); wss->next_layer().set_verify_callback( - [insecure](bool preverified, boost::asio::ssl::verify_context& ctx) { + [insecure, ca_cert](bool preverified, + boost::asio::ssl::verify_context& ctx) { if (preverified) { return true; } @@ -121,7 +128,7 @@ void Websocket::InitWss(ssl_websocket_t* wss, bool insecure) { } X509* cert = X509_STORE_CTX_get0_cert(ctx.native_handle()); STACK_OF(X509)* chain = X509_STORE_CTX_get0_chain(ctx.native_handle()); - return SSLVerifier::VerifyX509(cert, chain); + return SSLVerifier::VerifyX509(cert, chain, ca_cert); }); } @@ -412,7 +419,7 @@ void Websocket::OnReadProxy(boost::system::error_code ec, // wss を作って、あとは普通の SSL ハンドシェイクを行う wss_.reset(new ssl_websocket_t(std::move(*proxy_socket_), *ssl_ctx_)); - InitWss(wss_.get(), insecure_); + InitWss(wss_.get(), insecure_, ca_cert_); // SNI の設定を行う if (!SSL_set_tlsext_host_name(wss_->next_layer().native_handle(),