From 7fc04933961ea3ea5a2aa595172ca7cd29a718f5 Mon Sep 17 00:00:00 2001 From: Hiroshi Nakamura Date: Wed, 26 Oct 2011 12:57:09 +0900 Subject: [PATCH] Initial try for SSL session reuse Should have tests. --- lib/httpclient/session.rb | 27 ++++++++++++++---- lib/httpclient/ssl_config.rb | 53 ++++++++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+), 6 deletions(-) diff --git a/lib/httpclient/session.rb b/lib/httpclient/session.rb index 80253c1f..64a43d79 100644 --- a/lib/httpclient/session.rb +++ b/lib/httpclient/session.rb @@ -300,6 +300,18 @@ def ssl_connect(hostname = nil) @ssl_socket.connect end + def session_reused? + @ssl_socket.session_reused? if @ssl_socket.respond_to?(:session_reused?) + end + + def session=(session) + @ssl_socket.session = session if @ssl_socket.respond_to?(:session=) + end + + def session + @ssl_socket.session if @ssl_socket.respond_to?(:session) + end + def post_connection_check(host) verify_mode = @context.verify_mode || OpenSSL::SSL::VERIFY_NONE if verify_mode == OpenSSL::SSL::VERIFY_NONE @@ -376,16 +388,13 @@ def check_mask(value, mask) end def create_openssl_socket(socket) - ssl_socket = nil - if OpenSSL::SSL.const_defined?("SSLContext") - ctx = OpenSSL::SSL::SSLContext.new - @context.set_context(ctx) - ssl_socket = OpenSSL::SSL::SSLSocket.new(socket, ctx) + if ctx = @context.ssl_context + OpenSSL::SSL::SSLSocket.new(socket, ctx) else ssl_socket = OpenSSL::SSL::SSLSocket.new(socket) @context.set_context(ssl_socket) + ssl_socket end - ssl_socket end def debug(str) @@ -736,9 +745,15 @@ def connect else @socket = create_ssl_socket(@socket) connect_ssl_proxy(@socket, URI.parse(@dest.to_s)) if @proxy + if cached_session = @ssl_config.get_ssl_session(@dest) + @socket.session = cached_session + end @socket.ssl_connect(@dest.host) @socket.post_connection_check(@dest) @ssl_peer_cert = @socket.peer_cert + if !cached_session or @socket.session != cached_session + @ssl_config.add_ssl_session(@dest, @socket.session) + end end end # Use Ruby internal buffering instead of passing data immediately diff --git a/lib/httpclient/ssl_config.rb b/lib/httpclient/ssl_config.rb index 38c67af3..29f6ae1a 100644 --- a/lib/httpclient/ssl_config.rb +++ b/lib/httpclient/ssl_config.rb @@ -69,6 +69,33 @@ class SSLConfig # For server side configuration. Ignore this. attr_reader :client_ca # :nodoc: + # external ssl session cache + class SSLSessionCache + def initialize + @pool = {} + end + + def get(site) + if sessions = @pool[site] + sess = sessions.first + p [:get, sess] + sess + end + end + + def add(site, session) + p [:add, session] + puts session.to_text + (@pool[site] ||= []) << session + end + + def remove(session) + @pool.each do |site, sessions| + p [:remove, sessions.delete(session)] + end + end + end + # Creates a SSLConfig. def initialize(client) return unless SSLEnabled @@ -83,6 +110,7 @@ def initialize(client) @options = defined?(SSL::OP_ALL) ? SSL::OP_ALL | SSL::OP_NO_SSLv2 : nil @ciphers = "ALL:!ADH:!LOW:!EXP:!MD5:+SSLv2:@STRENGTH" @cacerts_loaded = false + @ssl_session_cache = SSLSessionCache.new end # Sets certificate (OpenSSL::X509::Certificate) for SSL client @@ -238,6 +266,18 @@ def client_ca=(client_ca) # :nodoc: end # interfaces for SSLSocketWrap. + def ssl_context + @ssl_context ||= create_context + end + + def add_ssl_session(site, session) + @ssl_session_cache.add(site, session) + end + + def get_ssl_session(site) + @ssl_session_cache.get(site) + end + def set_context(ctx) # :nodoc: load_trust_ca unless @cacerts_loaded @cacerts_loaded = true @@ -253,6 +293,12 @@ def set_context(ctx) # :nodoc: ctx.timeout = @timeout ctx.options = @options ctx.ciphers = @ciphers + ctx.session_cache_mode = OpenSSL::SSL::SSLContext::SESSION_CACHE_CLIENT + ctx.session_cache_size = 2 + ctx.session_id_context = "TODO" + ctx.session_remove_cb = proc do |ctx, sess| + @ssl_session_cache.remove(sess) + end end # post connection check proc for ruby < 1.8.5. @@ -354,6 +400,13 @@ def sample_verify_callback(is_ok, ctx) def change_notify @client.reset_all + @ssl_context = nil + end + + def create_context + ctx = OpenSSL::SSL::SSLContext.new + set_context(ctx) + ctx end def load_cacerts(cert_store)