Skip to content

Commit

Permalink
Merge pull request #124 from shiguredo/feature/add-ca-cert-option
Browse files Browse the repository at this point in the history
ルート証明書を指定可能にする
  • Loading branch information
melpon authored Sep 17, 2024
2 parents 63472ff + 17146ca commit 3619b2d
Show file tree
Hide file tree
Showing 10 changed files with 116 additions and 48 deletions.
2 changes: 2 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
- @tnoho
- [ADD] Intel VPL で AV1 エンコーダを動くようにする
- @tnoho
- [ADD] ルート証明書を指定可能にする
- @melpon

### misc

Expand Down
29 changes: 29 additions & 0 deletions examples/sumomo/src/sumomo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -162,6 +167,24 @@ class Sumomo : public std::enable_shared_from_this<Sumomo>,
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<char>(ifs)),
std::istreambuf_iterator<char>());
};
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<boost::asio::io_context::executor_type>
Expand Down Expand Up @@ -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<bool> use_hardware_encoder;
add_optional_bool(app, "--use-hardware-encoder", use_hardware_encoder,
Expand Down
6 changes: 5 additions & 1 deletion include/sora/rtc_ssl_verifier.h
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
#ifndef RTC_SSL_VERIFIER
#define RTC_SSL_VERIFIER

#include <optional>
#include <string>

// WebRTC
#include <rtc_base/ssl_certificate.h>

namespace sora {

class RTCSSLVerifier : public rtc::SSLCertificateVerifier {
public:
RTCSSLVerifier(bool insecure);
RTCSSLVerifier(bool insecure, std::optional<std::string> ca_cert);
bool Verify(const rtc::SSLCertificate& certificate) override;

private:
bool insecure_;
std::optional<std::string> ca_cert_;
};

} // namespace sora
Expand Down
1 change: 1 addition & 0 deletions include/sora/sora_signaling.h
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ struct SoraSignalingConfig {

std::string client_cert;
std::string client_key;
std::optional<std::string> ca_cert;

int websocket_close_timeout = 3;
int websocket_connection_timeout = 30;
Expand Down
7 changes: 5 additions & 2 deletions include/sora/ssl_verifier.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#ifndef SORA_SSL_VERIFIER_H_
#define SORA_SSL_VERIFIER_H_

#include <optional>
#include <string>

// openssl
Expand All @@ -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<std::string>& ca_cert);

private:
// PEM 形式のルート証明書を追加する
Expand All @@ -20,6 +23,6 @@ class SSLVerifier {
static bool LoadBuiltinSSLRootCertificates(X509_STORE* store);
};

}
} // namespace sora

#endif
11 changes: 9 additions & 2 deletions include/sora/websocket.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

#include <functional>
#include <memory>
#include <optional>

// Boost
#include <boost/asio/io_context.hpp>
Expand Down Expand Up @@ -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<std::string>& 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<std::string>& ca_cert,
std::string proxy_url,
std::string proxy_username,
std::string proxy_password);
Expand Down Expand Up @@ -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<std::string>& ca_cert);

void OnResolve(std::string host,
std::string port,
Expand Down Expand Up @@ -161,6 +166,8 @@ class Websocket {
boost::beast::http::response_parser<boost::beast::http::empty_body>>
proxy_resp_parser_;
boost::beast::flat_buffer proxy_buffer_;

std::optional<std::string> ca_cert_;
};

} // namespace sora
Expand Down
8 changes: 5 additions & 3 deletions src/rtc_ssl_verifier.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@

namespace sora {

RTCSSLVerifier::RTCSSLVerifier(bool insecure) : insecure_(insecure) {}
RTCSSLVerifier::RTCSSLVerifier(bool insecure,
std::optional<std::string> ca_cert)
: insecure_(insecure), ca_cert_(ca_cert) {}

bool RTCSSLVerifier::Verify(const rtc::SSLCertificate& certificate) {
// insecure の場合は証明書をチェックしない
Expand All @@ -17,7 +19,7 @@ bool RTCSSLVerifier::Verify(const rtc::SSLCertificate& certificate) {
SSL* ssl = static_cast<const rtc::BoringSSLCertificate&>(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_);
}

}
} // namespace sora
15 changes: 8 additions & 7 deletions src/sora_signaling.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down Expand Up @@ -509,7 +510,7 @@ SoraSignaling::CreatePeerConnection(boost::json::value jconfig) {
//
// それを解消するために tls_cert_verifier を設定して自前で検証を行う。
dependencies.tls_cert_verifier = std::unique_ptr<rtc::SSLCertificateVerifier>(
new RTCSSLVerifier(config_.insecure));
new RTCSSLVerifier(config_.insecure, config_.ca_cert));

// Proxy を設定する
if (!config_.proxy_url.empty() && config_.network_manager != nullptr &&
Expand Down Expand Up @@ -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));
Expand Down
64 changes: 38 additions & 26 deletions src/ssl_verifier.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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<std::string>& ca_cert) {
{
char data[256];
RTC_LOG(LS_INFO) << "cert:";
Expand Down Expand Up @@ -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";
Expand Down
21 changes: 14 additions & 7 deletions src/websocket.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<std::string>& 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))),
Expand All @@ -78,13 +80,15 @@ Websocket::Websocket(https_proxy_tag,
bool insecure,
const std::string& client_cert,
const std::string& client_key,
const std::optional<std::string>& ca_cert,
std::string proxy_url,
std::string proxy_username,
std::string proxy_password)
: resolver_(new boost::asio::ip::tcp::resolver(ioc)),
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)),
Expand All @@ -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<std::string>& 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;
}
Expand All @@ -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);
});
}

Expand Down Expand Up @@ -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(),
Expand Down

0 comments on commit 3619b2d

Please sign in to comment.