长答案:可以,但您必须更新 EventMachine 的 C++ 扩展以构建 ssl 连接,并通过 EventMachine 和 Thin 更新调用堆栈以传递证书授权文件。
-
thin 的命令行选项在 thin:lib/thin/runner.rb 中解析
opts.separator "SSL options:"
opts.on( "--ssl", "Enables SSL") { @options[:ssl] = true }
opts.on( "--ssl-key-file PATH", "Path to private key") { |path| @options[:ssl_key_file] = path }
opts.on( "--ssl-cert-file PATH", "Path to certificate") { |path| @options[:ssl_cert_file] = path }
opts.on( "--ssl-verify", "Enables SSL certificate verification") { @options[:ssl_verify] = true }
-
然后用来创建控制器
controller = case
when cluster? then Controllers::Cluster.new(@options)
when service? then Controllers::Service.new(@options)
else Controllers::Controller.new(@options)
end
-
在thin:lib/controllers/controller.rb 中,ssl 选项被拉回以与服务器对象一起存储
# ssl support
if @options[:ssl]
server.ssl = true
server.ssl_options = { :private_key_file => @options[:ssl_key_file], :cert_chain_file => @options[:ssl_cert_file], :verify_peer => @options[:ssl_verify] }
end
-
并最终用于初始化与客户端的连接
def initialize_connection(connection)
connection.backend = self
connection.app = @server.app
connection.comm_inactivity_timeout = @timeout
connection.threaded = @threaded
if @ssl
connection.start_tls(@ssl_options)
end
-
此连接是EventMachine::Connection,在eventmachine:lib/em/connection.rb 中定义。 EventMachine::Connection#start_tls 将参数传递给EventMachine::set_tls_parms。
def start_tls args={}
priv_key, cert_chain, verify_peer = args.values_at(:private_key_file, :cert_chain_file, :verify_peer)
[priv_key, cert_chain].each do |file|
next if file.nil? or file.empty?
raise FileNotFoundException,
"Could not find #{file} for start_tls" unless File.exists? file
end
EventMachine::set_tls_parms(@signature, priv_key || '', cert_chain || '', verify_peer)
EventMachine::start_tls @signature
end
-
EventMachine::set_tls_parms 是 C++ 扩展的一部分,在 eventmachine:ext/rubymain.cpp 中定义为五参数 C 函数 t_set_tls_parms
rb_define_module_function (EmModule, "set_tls_parms", (VALUE(*)(...))t_set_tls_parms, 4);
-
而t_set_tls_parms 在同一文件中的其他位置定义只是将ssl 选项传递给evma_set_tls_parms。
static VALUE t_set_tls_parms (VALUE self, VALUE signature, VALUE privkeyfile, VALUE certchainfile, VALUE verify_peer)
{
/* set_tls_parms takes a series of positional arguments for specifying such things
* as private keys and certificate chains.
* It's expected that the parameter list will grow as we add more supported features.
* ALL of these parameters are optional, and can be specified as empty or NULL strings.
*/
evma_set_tls_parms (NUM2ULONG (signature), StringValuePtr (privkeyfile), StringValuePtr (certchainfile), (verify_peer == Qtrue ? 1 : 0));
return Qnil;
}
-
原版 C 函数 evma_set_tls_parms 在 eventmachine:ext/cmain.cpp 中定义。它将 ssl 选项传递给 EventableDescriptor 的 SetTlsParms 方法:
extern "C" void evma_set_tls_parms (const unsigned long binding, const char *privatekey_filename, const char *certchain_filename, int verify_peer)
{
ensure_eventmachine("evma_set_tls_parms");
EventableDescriptor *ed = dynamic_cast <EventableDescriptor*> (Bindable_t::GetObject (binding));
if (ed)
ed->SetTlsParms (privatekey_filename, certchain_filename, (verify_peer == 1 ? true : false));
}
-
SetTlsParms 实例方法是在eventmachine:ed.cpp 中定义的,它真正所做的只是在某些实例变量中缓存 ssl 选项。
void ConnectionDescriptor::SetTlsParms (const char *privkey_filename, const char *certchain_filename, bool verify_peer)
{
#ifdef WITH_SSL
if (SslBox)
throw std::runtime_error ("call SetTlsParms before calling StartTls");
if (privkey_filename && *privkey_filename)
PrivateKeyFilename = privkey_filename;
if (certchain_filename && *certchain_filename)
CertChainFilename = certchain_filename;
bSslVerifyPeer = verify_peer;
#endif
#ifdef WITHOUT_SSL
throw std::runtime_error ("Encryption not available on this event-machine");
#endif
}
-
这些实例变量稍后在StartTls 实例方法中使用(在同一文件中定义),并传递给初始化一个新的SslBox_t
void ConnectionDescriptor::StartTls()
{
#ifdef WITH_SSL
if (SslBox)
throw std::runtime_error ("SSL/TLS already running on connection");
SslBox = new SslBox_t (bIsServer, PrivateKeyFilename, CertChainFilename, bSslVerifyPeer, GetBinding());
_DispatchCiphertext();
#endif
-
SslBox_t 构造函数在 eventmachine:ext/ssl.cpp 中定义,它使用 ssl 选项初始化一个新的SslContext_t。
SslBox_t::SslBox_t (bool is_server, const string &privkeyfile, const string &certchainfile, bool verify_peer, const unsigned long binding):
bIsServer (is_server),
bHandshakeCompleted (false),
bVerifyPeer (verify_peer),
pSSL (NULL),
pbioRead (NULL),
pbioWrite (NULL)
{
/* TODO someday: make it possible to re-use SSL contexts so we don't have to create
* a new one every time we come here.
*/
Context = new SslContext_t (bIsServer, privkeyfile, certchainfile);
assert (Context);
-
SslContext_t 构造函数在同一个文件中定义,它使用标准 OpenSSL C 绑定的这些选项:
// The SSL_CTX calls here do NOT allocate memory.
int e;
if (privkeyfile.length() > 0)
e = SSL_CTX_use_PrivateKey_file (pCtx, privkeyfile.c_str(), SSL_FILETYPE_PEM);
else
e = SSL_CTX_use_PrivateKey (pCtx, DefaultPrivateKey);
if (e <= 0) ERR_print_errors_fp(stderr);
assert (e > 0);
if (certchainfile.length() > 0)
e = SSL_CTX_use_certificate_chain_file (pCtx, certchainfile.c_str());
else
e = SSL_CTX_use_certificate (pCtx, DefaultCertificate);
if (e <= 0) ERR_print_errors_fp(stderr);
assert (e > 0);