This SSL proxy needs a CA certificate (or chain) and private key. It then uses that information to automatically generate host certificates. It creates a host certificate by performing a pre-flight request to the original destination to get its certificate. It then doctors that certificate so that it’s keypair is the supplied keypair, and so that it appears to be issued by the CA or last CA in the chain. Note that the supplied key must correspond to the last signing certificate, and that the key will be used as the doctored certificate’s new key.
How long to wait before giving up on a preflight request. Defaults to 5 seconds.
# File lib/packetthief/handlers/ssl_smart_proxy.rb, line 18 def initialize(tcpsocket, ca_chain, key, logger=nil) super(tcpsocket, logger) @preflight_timeout = 5 if ca_chain.kind_of? Array @ca_chain = ca_chain else @ca_chain = [ca_chain] end @key = key begin # preflight the original destination. @ctx.cert = lookup_cert @ctx.extra_chain_cert = @ca_chain @ctx.key = @key rescue OpenSSL::SSL::SSLError, Errno::ECONNREFUSED, Errno::ETIMEDOUT => e logerror "initialize: Failed to look up cert", :error => e close_connection end end
Replace the issuer, the public key, and the signature.
# File lib/packetthief/handlers/ssl_smart_proxy.rb, line 88 def doctor_cert(oldcert, cacert, key) newcert = oldcert.dup newcert.issuer = cacert.subject newcert.public_key = key.public_key exts = newcert.extensions.dup exts.each_index do |i| if exts[i].oid == "authorityKeyIdentifier" ef = OpenSSL::X509::ExtensionFactory.new ef.subject_certificate = newcert ef.issuer_certificate = cacert newe = ef.create_ext_from_string("authorityKeyIdentifier=keyid:always") exts[i] = newe end end newcert.extensions = exts sigalg = case newcert.signature_algorithm when %rMD5/ OpenSSL::Digest::MD5.new when %rSHA1/ OpenSSL::Digest::SHA1.new when %rSHA256/ OpenSSL::Digest::SHA256.new else raise "Unsupported signing algorithm: #{@ctx.cert.signing_algorithm}" end newcert.sign(key, sigalg) return newcert end
Check a class-level cache for an existing doctored certificate that corresponds to the requested hostname (IP address for non-SNI or pre-SNI lookup, and hostnames from SNI lookup).
# File lib/packetthief/handlers/ssl_smart_proxy.rb, line 122 def lookup_cert(hostname=nil) @@certcache ||= {} if hostname cachekey = "#{hostname}:#{dest_port}" else cachekey = "#{dest_host}:#{dest_port}" end unless @@certcache.has_key? cachekey logdebug "lookup_cert: cache miss, looking up and doctoring actual cert", :dest => cachekey @@certcache[cachekey] = doctor_cert(preflight_for_cert(hostname), @ca_chain[0], @key) else logdebug "lookup_cert: cache hit", :dest => cachekey end logdebug "lookup_cert: returning", :subject => @@certcache[cachekey].subject @@certcache[cachekey] end
Requests a certificate from the original destination.
# File lib/packetthief/handlers/ssl_smart_proxy.rb, line 55 def preflight_for_cert(hostname=nil) logdebug "prefilight for: #{hostname}" begin pfctx = OpenSSL::SSL::SSLContext.new pfctx.verify_mode = OpenSSL::SSL::VERIFY_NONE # Try to use a non-blocking Socket to create a short timeout. pfs = Socket.new(:AF_INET, :SOCK_STREAM) begin pfs.connect_nonblock(Socket.sockaddr_in(dest_port, dest_host)) rescue Errno::EINPROGRESS IO.select(nil, [pfs], nil, preflight_timeout) or raise Errno::ETIMEDOUT, "Connection to #{dest_host}:#{dest_port} timed out after #{preflight_timeout} seconds" end logdebug "preflight tcp socket connected" pfssl = OpenSSL::SSL::SSLSocket.new(pfs, pfctx) pfssl.hostname = hostname if hostname and pfssl.respond_to? :hostname begin pfssl.connect_nonblock rescue IO::WaitReadable, IO::WaitWritable IO.select([pfssl],[pfssl],nil,preflight_timeout) or raise Errno::ETIMEDOUT, "SSL handshake to #{dest_host}:#{dest_port} timed out #{preflight_timeout} seconds" retry end logdebug "preflight complete" return pfssl.peer_cert rescue OpenSSL::SSL::SSLError, Errno::ECONNREFUSED, Errno::ETIMEDOUT => e logerror "Error during preflight SSL connection: #{e} (#{e.class})" raise ensure pfssl.close if pfssl end end
def client_connected
# don't try to connect to dest here.
end
# File lib/packetthief/handlers/ssl_smart_proxy.rb, line 45 def servername_cb(sslsock, hostname) super newctx = sslsock.context.dup newctx.cert = lookup_cert(hostname) newctx.extra_chain_cert = @ca_chain newctx.key = @key newctx end