diff --git a/CMakeLists.txt b/CMakeLists.txt index 0c2617d6..e7b4779a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -277,6 +277,7 @@ target_sources(momo src/momo_version.cpp src/util.cpp src/watchdog.cpp + src/ssl_verifier.cpp src/ayame/ayame_server.cpp src/ayame/ayame_websocket_client.cpp src/p2p/p2p_connection.cpp diff --git a/src/ayame/ayame_websocket_client.cpp b/src/ayame/ayame_websocket_client.cpp index 137acc2c..46dfbfec 100644 --- a/src/ayame/ayame_websocket_client.cpp +++ b/src/ayame/ayame_websocket_client.cpp @@ -1,9 +1,13 @@ #include "ayame_websocket_client.h" +// boost #include + +// json #include -#include "../momo_version.h" +#include "momo_version.h" +#include "ssl_verifier.h" #include "url_parts.h" #include "util.h" @@ -28,7 +32,7 @@ bool AyameWebsocketClient::parseURL(URLParts& parts) const { boost::asio::ssl::context AyameWebsocketClient::createSSLContext() const { boost::asio::ssl::context ctx(boost::asio::ssl::context::tlsv12); - ctx.set_default_verify_paths(); + //ctx.set_default_verify_paths(); ctx.set_options(boost::asio::ssl::context::default_workarounds | boost::asio::ssl::context::no_sslv2 | boost::asio::ssl::context::no_sslv3 | @@ -72,10 +76,18 @@ void AyameWebsocketClient::reset() { if (parseURL(parts_)) { auto ssl_ctx = createSSLContext(); - boost::beast::websocket::stream< - boost::asio::ssl::stream> - wss(ioc_, ssl_ctx); ws_.reset(new Websocket(ioc_, std::move(ssl_ctx))); + ws_->nativeSecureSocket().next_layer().set_verify_mode( + boost::asio::ssl::verify_peer); + ws_->nativeSecureSocket().next_layer().set_verify_callback( + [](bool preverified, boost::asio::ssl::verify_context& ctx) { + if (preverified) { + return true; + } + X509* cert = X509_STORE_CTX_get_current_cert(ctx.native_handle()); + return SSLVerifier::VerifyX509(cert); + }); + // SNI の設定を行う if (!SSL_set_tlsext_host_name( ws_->nativeSecureSocket().next_layer().native_handle(), diff --git a/src/rtc/manager.cpp b/src/rtc/manager.cpp index dcda1e48..bccb224b 100644 --- a/src/rtc/manager.cpp +++ b/src/rtc/manager.cpp @@ -17,6 +17,7 @@ #include "modules/video_capture/video_capture_factory.h" #include "observer.h" #include "rtc_base/logging.h" +#include "rtc_base/openssl_certificate.h" #include "rtc_base/ssl_adapter.h" #include "scalable_track_source.h" #include "util.h" @@ -41,6 +42,8 @@ #include "hw_video_decoder_factory.h" #endif +#include "ssl_verifier.h" + RTCManager::RTCManager( ConnectionSettings conn_settings, rtc::scoped_refptr video_track_source, @@ -191,6 +194,15 @@ void RTCManager::SetDataManager(RTCDataManager* data_manager) { _data_manager = data_manager; } +class RTCSSLVerifier : public rtc::SSLCertificateVerifier { + public: + RTCSSLVerifier() {} + bool Verify(const rtc::SSLCertificate& certificate) override { + return SSLVerifier::VerifyX509( + static_cast(certificate).x509()); + } +}; + std::shared_ptr RTCManager::createConnection( webrtc::PeerConnectionInterface::RTCConfiguration rtc_config, RTCMessageSender* sender) { @@ -198,9 +210,17 @@ std::shared_ptr RTCManager::createConnection( rtc_config.sdp_semantics = webrtc::SdpSemantics::kUnifiedPlan; std::unique_ptr observer( new PeerConnectionObserver(sender, _receiver, _data_manager)); + webrtc::PeerConnectionDependencies dependencies(observer.get()); + + // WebRTC の SSL 接続の検証は自前のルート証明書(rtc_base/ssl_roots.h)でやっていて、 + // その中に Let's Encrypt の証明書が無いため、接続先によっては接続できないことがある。 + // + // それを解消するために tls_cert_verifier を設定して自前で検証を行う。 + dependencies.tls_cert_verifier = + std::unique_ptr(new RTCSSLVerifier()); + rtc::scoped_refptr connection = - _factory->CreatePeerConnection(rtc_config, nullptr, nullptr, - observer.get()); + _factory->CreatePeerConnection(rtc_config, std::move(dependencies)); if (!connection) { RTC_LOG(LS_ERROR) << __FUNCTION__ << ": CreatePeerConnection failed"; return nullptr; diff --git a/src/sora/sora_websocket_client.cpp b/src/sora/sora_websocket_client.cpp index 4a92cc4e..2aa9c05f 100644 --- a/src/sora/sora_websocket_client.cpp +++ b/src/sora/sora_websocket_client.cpp @@ -10,6 +10,7 @@ #include #include "momo_version.h" +#include "ssl_verifier.h" #include "url_parts.h" #include "util.h" @@ -34,7 +35,7 @@ bool SoraWebsocketClient::parseURL(URLParts& parts) const { boost::asio::ssl::context SoraWebsocketClient::createSSLContext() const { boost::asio::ssl::context ctx(boost::asio::ssl::context::tlsv12); - ctx.set_default_verify_paths(); + //ctx.set_default_verify_paths(); ctx.set_options(boost::asio::ssl::context::default_workarounds | boost::asio::ssl::context::no_sslv2 | boost::asio::ssl::context::no_sslv3 | @@ -74,10 +75,18 @@ void SoraWebsocketClient::reset() { if (parseURL(parts_)) { auto ssl_ctx = createSSLContext(); - boost::beast::websocket::stream< - boost::asio::ssl::stream> - wss(ioc_, ssl_ctx); ws_.reset(new Websocket(ioc_, std::move(ssl_ctx))); + ws_->nativeSecureSocket().next_layer().set_verify_mode( + boost::asio::ssl::verify_peer); + ws_->nativeSecureSocket().next_layer().set_verify_callback( + [](bool preverified, boost::asio::ssl::verify_context& ctx) { + if (preverified) { + return true; + } + X509* cert = X509_STORE_CTX_get_current_cert(ctx.native_handle()); + return SSLVerifier::VerifyX509(cert); + }); + // SNI の設定を行う if (!SSL_set_tlsext_host_name( ws_->nativeSecureSocket().next_layer().native_handle(), diff --git a/src/ssl_verifier.cpp b/src/ssl_verifier.cpp new file mode 100644 index 00000000..cb0c0bb9 --- /dev/null +++ b/src/ssl_verifier.cpp @@ -0,0 +1,115 @@ +#include "ssl_verifier.h" + +// webrtc +#include +#include +#include + +// openssl +#include + +// boost +#include +#include +#include + +bool SSLVerifier::AddCert(const std::string& pem, X509_STORE* store) { + BIO* bio = BIO_new_mem_buf(pem.c_str(), pem.size()); + if (bio == nullptr) { + 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) { + 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; +} + +// rtc_base/openssl_utility.cc を今回の用途に合わせたもの +bool SSLVerifier::LoadBuiltinSSLRootCertificates(X509_STORE* store) { + int count_of_added_certs = 0; + for (size_t i = 0; + i < sizeof(kSSLCertCertificateList) / sizeof(kSSLCertCertificateList[0]); + i++) { + const unsigned char* cert_buffer = kSSLCertCertificateList[i]; + size_t cert_buffer_len = kSSLCertCertificateSizeList[i]; + X509* cert = d2i_X509(nullptr, &cert_buffer, + cert_buffer_len); // NOLINT + if (cert) { + int return_value = X509_STORE_add_cert(store, cert); + if (return_value == 0) { + RTC_LOG(LS_WARNING) << "Unable to add certificate."; + } else { + count_of_added_certs++; + } + X509_free(cert); + } + } + return count_of_added_certs > 0; +} + +bool SSLVerifier::VerifyX509(X509* x509) { + { + char subject_name[256]; + X509_NAME_oneline(X509_get_subject_name(x509), subject_name, 256); + RTC_LOG(LS_INFO) << "Verifying " << subject_name; + } + + X509_STORE* store = nullptr; + X509_STORE_CTX* ctx = nullptr; + + struct Guard { + std::function f; + Guard(std::function f) : f(std::move(f)) {} + ~Guard() { f(); } + }; + Guard guard([&]() { + // nullptr を渡しても何もしない + X509_STORE_CTX_free(ctx); + X509_STORE_free(store); + }); + + store = X509_STORE_new(); + if (store == nullptr) { + RTC_LOG(LS_ERROR) << "X509_STORE_new failed"; + return false; + } + + // WebRTC が用意しているルート証明書の設定 + LoadBuiltinSSLRootCertificates(store); + // デフォルト証明書のパスの設定 + X509_STORE_set_default_paths(store); + RTC_LOG(LS_INFO) << "default cert file: " << X509_get_default_cert_file(); + + ctx = X509_STORE_CTX_new(); + if (ctx == nullptr) { + RTC_LOG(LS_ERROR) << "X509_STORE_CTX_new failed"; + return false; + } + int r; + r = X509_STORE_CTX_init(ctx, store, x509, nullptr); + if (r == 0) { + RTC_LOG(LS_ERROR) << "X509_STORE_CTX_init failed"; + return false; + } + r = X509_verify_cert(ctx); + if (r <= 0) { + RTC_LOG(LS_INFO) << "X509_verify_cert failed: r=" << r << " message=" + << X509_verify_cert_error_string( + X509_STORE_CTX_get_error(ctx)); + return false; + } + return true; +} diff --git a/src/ssl_verifier.h b/src/ssl_verifier.h new file mode 100644 index 00000000..c1b08387 --- /dev/null +++ b/src/ssl_verifier.h @@ -0,0 +1,21 @@ +#ifndef SSL_VERIFIER_H_INCLUDED +#define SSL_VERIFIER_H_INCLUDED + +#include + +// openssl +#include + +// 自前で SSL の証明書検証を行うためのクラス +class SSLVerifier { + public: + static bool VerifyX509(X509* x509); + + private: + // PEM 形式のルート証明書を追加する + static bool AddCert(const std::string& pem, X509_STORE* store); + // WebRTC の組み込みルート証明書を追加する + static bool LoadBuiltinSSLRootCertificates(X509_STORE* store); +}; + +#endif // SSL_VERIFIER_H_INCLUDED