Class: TLSPretense::TestHarness::TestListener

Inherits:
PacketThief::Handlers::SSLSmartProxy show all
Defined in:
lib/tlspretense/test_harness/test_listener.rb

Overview

TestListener is the real workhorse used by SSLTestCases. It builds on the SSLSmartProxy from PacketThief in order to intercept and forward SSL connections. It uses SSLSmartProxy because SSLSmartProxy provides a default behavior where it grabs the remote certificate from the destination and re-signs it before presenting it to the client.

TestListener expands on this by presenting the configured test chain instead of the re-signed remote certificate when the destination corresponds to the hostname the test suite is testing off of.

Instance Attribute Summary

Attributes inherited from PacketThief::Handlers::SSLSmartProxy

#preflight_timeout

Attributes inherited from PacketThief::Handlers::SSLTransparentProxy

#buffer, #client, #client_host, #client_port, #closed, #dest, #dest_ctx, #dest_host, #dest_hostname, #dest_port

Attributes inherited from PacketThief::Handlers::SSLServer

#server_handler

Attributes inherited from PacketThief::Handlers::AbstractSSLHandler

#ctx, #sni_hostname, #sslsocket, #tcpsocket

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from PacketThief::Handlers::SSLSmartProxy

#doctor_cert, #lookup_cert, #preflight_for_cert

Methods inherited from PacketThief::Handlers::SSLTransparentProxy

#_send_buffer, #client_closed, #client_connected, #client_handshake_failed, #connect_to_dest, #dest_cert_chain, #dest_closed, #dest_connected, #dest_handshake_failed, #dest_recv, #receive_data, #send_to_client, #send_to_dest

Methods inherited from PacketThief::Handlers::SSLServer

start, #stop_server

Methods inherited from PacketThief::Handlers::AbstractSSLHandler

#close_connection, #close_connection_after_writing, #notify_readable, #notify_writable, #receive_data, #send_data, #tls_begin, #write_buffer, #write_buffer=

Methods included from PacketThief::Logging

log

Constructor Details

#initialize(tcpsocket, test_manager) ⇒ TestListener

For all hosts that do not match hosttotest, we currently use the cacert and re-sign the original cert provided by the actual host. This will cause issues with certificate revocation.

  • cacert [OpenSSL::X509::Certificate] A CA that the client should trust.

  • cakey [OpenSSL::PKey::PKey] The CA’s key, needed for resigning. It will also be the key used by the resigned certificates.

  • hosttotest [String] The hostname we want to apply the test chain to.

  • chaintotest [Array<OpenSSL::X509Certificate>] A chain of certs to present when the client attempts to connect to hostname.

  • keytotest [OpenSSL::PKey::PKey] The key corresponding to the leaf node in chaintotest.



28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# File 'lib/tlspretense/test_harness/test_listener.rb', line 28

def initialize(tcpsocket, test_manager)
  @test_manager = test_manager

  if @test_manager.paused?
    @paused = true
  else
    @paused = false
    @test = @test_manager.current_test
    @hosttotest = @test.hosttotest
    chain = @test.certchain.dup
    @hostcert = chain.shift
    @hostkey = @test.keychain[0]
    @extrachain = chain
  end
  # Use the goodca for hosts we don't care to test against.
  super(tcpsocket, @test_manager.goodcacert, @test_manager.goodcakey)

  @test_status = :running
  @testing_host = false
end

Class Method Details

.cert_matches_host(cert, hostname) ⇒ Object

Return true if cert’s CNAME or subjectAltName matches hostname, otherwise return false.



87
88
89
# File 'lib/tlspretense/test_harness/test_listener.rb', line 87

def self.cert_matches_host(cert, hostname)
  OpenSSL::SSL.verify_certificate_identity(cert, hostname)
end

Instance Method Details

#check_for_hosttotest(actx) ⇒ Object

Replaces the certificates used in the SSLContext with the test certificates if the destination matches the hostname we wish to test against. Otherwise, it leaves the context alone.

Additionally, if it matches, it sets @testing_host to true to check whether the test succeeds or not.



70
71
72
73
74
75
76
77
78
79
80
81
82
83
# File 'lib/tlspretense/test_harness/test_listener.rb', line 70

def check_for_hosttotest(actx)
  if @paused
    logdebug "Testing is paused, not checking whether this is the host to test", :certcubject => actx.cert.subject
  elsif TestListener.cert_matches_host(actx.cert, @hosttotest)
    logdebug "Destination matches host-to-test", :hosttotest => @hosttotest, :certsubject => actx.cert.subject, :testname => @test.id
    actx.cert = @hostcert
    actx.key = @hostkey
    actx.extra_chain_cert = @extrachain
    @testing_host = true
  else
    logdebug "Destination does not match host-to-test", :hosttotest => @hosttotest, :certsubject => actx.cert.subject
  end
  actx
end

#client_recv(data) ⇒ Object

client_recv means that the client sent data over the TLS connection, meaning they definately trusted our certificate chain.



129
130
131
132
133
134
135
136
137
138
# File 'lib/tlspretense/test_harness/test_listener.rb', line 129

def client_recv(data)
  if @testing_host
    @test_status = :sentdata
    if @test_manager.testing_method == 'senddata'
      @test_manager.test_completed(@test, @test_status)
      @testing_host = false
    end
  end
  super(data)
end

#post_initObject

Checks whether the initial original destination certificate (without SNI hostname) matches the test hostname. We do this with post_init to have the check happen after the parent class already added a re-signed certificate to @ctx.



53
54
55
# File 'lib/tlspretense/test_harness/test_listener.rb', line 53

def post_init
  check_for_hosttotest(@ctx)
end

#servername_cb(sslsock, hostname) ⇒ Object

Checks whether the original destination certificate after we handle the SNI hostname matches the test hostname. Super already replaced the context with a certificate based on the remote host’s certificate.



60
61
62
# File 'lib/tlspretense/test_harness/test_listener.rb', line 60

def servername_cb(sslsock, hostname)
  check_for_hosttotest(super(sslsock, hostname))
end

#tls_failed_handshake(e) ⇒ Object

If the handshake failed, then the client rejected our cert chain.



107
108
109
110
111
112
113
114
115
# File 'lib/tlspretense/test_harness/test_listener.rb', line 107

def tls_failed_handshake(e)
  super
  logdebug "failed handshake"
  if @testing_host
    @test_status = :rejected
    @test_manager.test_completed(@test, @test_status)
    @testing_host = false
  end
end

#tls_successful_handshakeObject

If the client completes connecting, we might consider that trusting our certificate chain. However, at least Java’s SSL client classes don’t reject until after completing the handshake.



94
95
96
97
98
99
100
101
102
103
104
# File 'lib/tlspretense/test_harness/test_listener.rb', line 94

def tls_successful_handshake
  super
  logdebug "successful handshake"
  if @testing_host
    @test_status = :connected
    if @test_manager.testing_method == 'tlshandshake'
      @test_manager.test_completed(@test, @test_status)
      @testing_host = false
    end
  end
end

#unbindObject

Report our result.



118
119
120
121
122
123
124
125
# File 'lib/tlspretense/test_harness/test_listener.rb', line 118

def unbind
  super
  logdebug "unbind"
  if @testing_host
    @test_manager.test_completed(@test, @test_status)
    @testing_host = false
  end
end