Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ルート証明書を指定可能にする #124

Merged
merged 1 commit into from
Sep 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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