class Ritm::Proxy::CertSigningHTTPSServer

Patches WEBrick::HTTPServer SSL context creation to get a callback on the 'Client Helo' step of the SSL-Handshake if SNI is specified So RitM can create self-signed certificates on the fly

Public Instance Methods

setup_ssl_context(config) click to toggle source

Override

Calls superclass method
# File lib/ritm/proxy/cert_signing_https_server.rb, line 15
def setup_ssl_context(config)
  ctx = super(config)
  prepare_sni_callback(ctx, config[:ca])
  ctx
end

Private Instance Methods

context_with_cert(original_ctx, cert) click to toggle source
# File lib/ritm/proxy/cert_signing_https_server.rb, line 46
def context_with_cert(original_ctx, cert)
  ctx = duplicate_context(original_ctx)
  ctx.key = cert.private_key
  ctx.cert = cert.x509
  ctx
end
duplicate_context(original_ctx) click to toggle source
# File lib/ritm/proxy/cert_signing_https_server.rb, line 53
def duplicate_context(original_ctx)
  return original_ctx.dup unless IS_RUBY_2_4_OR_OLDER

  ctx = OpenSSL::SSL::SSLContext.new

  original_ctx.instance_variables.each do |variable_name|
    prop_name = variable_name.to_s.sub(/^@/, '')
    set_prop_method = "#{prop_name}="
    ctx.send(set_prop_method, original_ctx.send(prop_name)) if ctx.respond_to? set_prop_method
  end
  ctx
end
prepare_sni_callback(ctx, ca) click to toggle source

Keeps track of the created self-signed certificates TODO: this can grow a lot and take up memory, fix by either:

  1. implementing wildcard certificates generation (so there's one certificate per top level domain)

  2. Use the same key material (private/public keys) for all the server names and just do the signing on-the-fly

  3. both of the above

# File lib/ritm/proxy/cert_signing_https_server.rb, line 28
def prepare_sni_callback(ctx, ca)
  contexts = {}
  mutex = Mutex.new

  # Sets the SNI callback on the SSLTCPSocket
  ctx.servername_cb = proc do |sock, servername|
    mutex.synchronize do
      unless contexts.include? servername
        cert = Ritm::Certificate.create(servername)
        cert.cert.extensions['subjectAltName'].dns_names = [servername]
        ca.sign(cert)
        contexts[servername] = context_with_cert(sock.context, cert)
      end
    end
    contexts[servername]
  end
end