decrypting SOCKS proxy

sockhole: Rewrite to use EventMachine

This fixes a weird buffering issue with SSL sockets in the previous
version, though there is now an issue with SSL peer verification.

OpenSSL::SSL.verify_certificate_identity seems to return false for
everything, even when the verification succeeds (because no error is
printed to STDERR from the SSL library).

+168 -174
+2
.gitignore
··· 1 + .bundle 2 + vendor/
+5
Gemfile
··· 1 + source "https://rubygems.org" 2 + 3 + gem "bundler" 4 + 5 + gem "eventmachine"
+14
Gemfile.lock
··· 1 + GEM 2 + remote: https://rubygems.org/ 3 + specs: 4 + eventmachine (1.2.7) 5 + 6 + PLATFORMS 7 + ruby 8 + 9 + DEPENDENCIES 10 + bundler 11 + eventmachine 12 + 13 + BUNDLED WITH 14 + 1.17.2
+147 -174
sockhole.rb
··· 16 16 # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 17 # 18 18 19 + require "eventmachine" 19 20 require "socket" 20 21 require "logger" 21 22 require "ipaddr" 22 23 require "resolv" 23 24 require "openssl" 24 25 26 + # a connection to these ports will make a TLS connection and decrypt data 27 + # before handing it back to the client 28 + TLS_PORTS = [ 29 + 443, # https 30 + 993, # imaps 31 + 995, # pop3s 32 + ] 33 + 34 + LISTEN_PORT = 1080 35 + LISTEN_IP = "0.0.0.0" 36 + 25 37 LOGGER = Logger.new(STDOUT) 26 38 if ARGV[0] == "-d" 27 39 LOGGER.level = Logger::DEBUG ··· 33 45 "[#{datetime}] [#{severity[0]}] #{msg}\n" 34 46 end 35 47 36 - # a connection to these ports will make a TLS connection and decrypt data 37 - # before handing it back to the client 38 - TLS_PORTS = [ 39 - 443, # https 40 - 993, # imaps 41 - 995, # pop3s 42 - ] 43 - 44 - BUF_SIZE = 512 45 - 46 48 VERSION_SOCKS5 = 0x05 47 49 48 - METHOD_MIN_REQUEST_LENGTH = 3 50 + METHOD_MIN_LENGTH = 3 49 51 METHOD_AUTH_NONE = 0x0 50 52 53 + REQUEST_MIN_LENGTH = 9 51 54 REQUEST_COMMAND_CONNECT = 0x1 52 55 REQUEST_ATYP_IP = 0x1 53 56 REQUEST_ATYP_HOSTNAME = 0x3 ··· 69 72 end 70 73 end 71 74 72 - class Server 73 - def self.log(prio, str) 74 - LOGGER.send(prio, "[server] #{str}") 75 + class ClientDead < StandardError; end 76 + 77 + module EMProxyConnection 78 + attr_reader :client, :hostname, :connected, :tls, :did_tls_verification 79 + 80 + def initialize(client, hostname, tls) 81 + @client = client 82 + @hostname = hostname 83 + @connected = false 84 + @tls = tls 85 + @did_tls_verification = false 86 + end 87 + 88 + def post_init 89 + if tls 90 + start_tls(:verify_peer => true, :cert_chain_file => "/etc/ssl/cert.pem") 91 + end 75 92 end 76 93 77 - def self.run! 78 - server = TCPServer.new(1080) 94 + def log(prio, str) 95 + client.log(prio, str) 96 + end 79 97 80 - log :info, "listening on :1080" 98 + def connection_completed 99 + @connected = true 81 100 82 - while tcpsocket = server.accept do 83 - Thread.new { 84 - begin 85 - Client.new(tcpsocket) 86 - rescue => e 87 - log :error, "unhandled #{e.class} exception in client: " << 88 - "#{e.message}" 89 - e.backtrace[0, 4].each{|l| log :error, " #{l}" } 90 - end 91 - }.join 101 + # tls connections will call back once verification completes 102 + if !tls 103 + client.send_reply REPLY_SUCCESS 92 104 end 93 105 end 94 - end 95 106 96 - class ClientDead < StandardError; end 107 + def ssl_verify_peer(pem) 108 + if hostname.empty? 109 + return true 110 + end 97 111 98 - class Client 99 - attr_reader :socket, :ip, :remote_socket, :tls_decrypt 100 - attr_accessor :remote_hostname, :remote_ip, :remote_port 112 + # we'll get called again for other certs in the chain 113 + if did_tls_verification 114 + return true 115 + end 116 + 117 + log :debug, "verifying TLS hostname #{hostname.inspect}" 101 118 102 - def initialize(tcpsocket) 103 - @socket = tcpsocket 104 - @ip = @socket.peeraddr[2] 119 + cert = OpenSSL::X509::Certificate.new(pem) 120 + ret = OpenSSL::SSL.verify_certificate_identity(cert, hostname) 105 121 106 - log :info, "new connection" 122 + @did_tls_verification = true 107 123 108 - if !verify_method 109 - close 110 - return 124 + # XXX: this always seems to fail, even when no OpenSSL error is reported 125 + if !ret 126 + log :warn, "TLS verification failed for #{hostname.inspect}, aborting" 127 + #close_connection 128 + #return false 111 129 end 112 130 113 - handle_request 114 - close 115 - return 131 + return ret 116 132 117 - rescue ClientDead 118 - close 119 - return 133 + rescue => e 134 + log :warn, "error in ssl_verify_peer: #{e.inspect}" 135 + return false 120 136 end 121 137 122 - def log(prio, str) 123 - LOGGER.send(prio, "[#{ip}] #{str}") 138 + def ssl_handshake_completed 139 + log :debug, "TLS handshake completed, sending reply" 140 + client.send_reply REPLY_SUCCESS 124 141 end 125 142 126 - def close 127 - if socket 128 - log :info, "closing connection" 129 - socket.close 130 - end 143 + def receive_data(_data) 144 + client.send_data _data 145 + end 131 146 132 - if remote_socket 133 - log :info, "closing remote connection" 134 - remote_socket.close 147 + def unbind 148 + if connected 149 + log :info, "closed remote connection" 150 + client.close_connection_after_writing 151 + else 152 + log :info, "failed connecting to remote" 153 + client.send_reply REPLY_FAIL 135 154 end 155 + end 156 + end 136 157 137 - @socket = nil 138 - @remote_socket = nil 158 + module EMSOCKS5Connection 159 + attr_reader :state, :ip, :data, :remote_connection, :tls_decrypt 160 + attr_accessor :remote_hostname, :remote_ip, :remote_port 161 + 162 + def initialize 163 + @state = :INIT 164 + port, @ip = Socket.unpack_sockaddr_in(get_peername) 165 + end 166 + 167 + def log(prio, str) 168 + LOGGER.send(prio, "[#{ip}] #{str}") 139 169 end 140 170 141 171 def fail_close(code) 142 - write [ 172 + send_data [ 143 173 VERSION_SOCKS5, 144 174 code, 145 175 0, ··· 148 178 0, 0, 149 179 ].pack("C*") 150 180 151 - close 152 - 153 - return false 181 + close_connection_after_writing 182 + @state = :DEAD 154 183 end 155 184 156 185 def hex(data) 157 186 data.unpack("C*").map{|c| sprintf("%02x", c) }.join(" ") 158 187 end 159 188 160 - def read(len) 161 - data = socket.sysread(BUF_SIZE) 162 - log :debug, "<-C #{hex(data)}" 163 - return data 164 - rescue SystemCallError, EOFError => e 165 - log :error, "read: #{e.message}" 166 - raise ClientDead 167 - end 189 + def send_reply(code) 190 + resp = [ VERSION_SOCKS5, code, 0, REQUEST_ATYP_IP ] 191 + resp += IPAddr.new(remote_ip).hton.unpack("C*") 192 + resp += remote_port.to_s.unpack("n2").map(&:to_i) 193 + send_data resp.pack("C*") 168 194 169 - def write(data) 170 - log :debug, "->C #{hex(data)}" 171 - wrote = 0 172 - while data.bytesize > 0 173 - log :debug, "#{data.bytesize} byte(s) left to write to client" 174 - len = socket.syswrite(data[0, BUF_SIZE]) 175 - data = data.byteslice(len .. -1) 176 - wrote += len 195 + if code == REPLY_SUCCESS 196 + @state = :PROXY 197 + @data = "" 198 + else 199 + close_connection_after_writing 200 + @state = :DEAD 177 201 end 178 - rescue SystemCallError => e 179 - log :error, "write: #{e.message}" 180 - raise ClientDead 181 202 end 182 203 183 - def remote_read(len) 184 - raise ClientDead if !remote_socket 185 - data = remote_socket.sysread(BUF_SIZE) 186 - log :debug, "<-R #{data.inspect}" 187 - return data 188 - rescue SystemCallError => e 189 - log :error, "remote read: #{e.message}" 190 - raise ClientDead 191 - rescue EOFError => e 192 - log :error, "remote EOF" 193 - raise ClientDead 194 - end 204 + def receive_data(_data) 205 + log :debug, "<-C #{_data.inspect} #{hex(_data)}" 195 206 196 - def remote_write(data) 197 - raise ClientDead if !remote_socket 198 - log :debug, "->R #{hex(data)}" 199 - wrote = 0 200 - while data.bytesize > 0 201 - log :debug, "#{data.bytesize} byte(s) left to write to remote" 202 - len = remote_socket.syswrite(data[0, BUF_SIZE]) 203 - data = data.byteslice(len .. -1) 204 - wrote += len 207 + (@data ||= "") << _data 208 + 209 + case state 210 + when :INIT 211 + if data.bytesize < METHOD_MIN_LENGTH 212 + return 213 + end 214 + 215 + @state = :METHOD 216 + verify_method 217 + 218 + when :REQUEST 219 + if data.bytesize < REQUEST_MIN_LENGTH 220 + return 221 + end 222 + 223 + handle_request 224 + 225 + when :PROXY 226 + remote_connection.send_data data 227 + @data = "" 205 228 end 206 - rescue SystemCallError => e 207 - log :error, "remote write: #{e.message}" 208 - raise ClientDead 209 229 end 210 230 211 - def verify_method 212 - data = "" 213 - while data.bytesize < METHOD_MIN_REQUEST_LENGTH 214 - data << read(METHOD_MIN_REQUEST_LENGTH) 215 - end 231 + def send_data(_data) 232 + log :debug, "->C #{_data.inspect} #{hex(_data)}" 233 + super 234 + end 216 235 236 + def verify_method 217 237 if data[0].ord != VERSION_SOCKS5 218 238 log :error, "unsupported version: #{data[0].inspect}" 219 239 return fail_close(REPLY_FAIL) ··· 222 242 data[1].ord.times do |i| 223 243 case data[2 + i].ord 224 244 when METHOD_AUTH_NONE 225 - write [ VERSION_SOCKS5, METHOD_AUTH_NONE ].pack("C*") 226 - return true 245 + send_data [ VERSION_SOCKS5, METHOD_AUTH_NONE ].pack("C*") 246 + @state = :REQUEST 247 + @data = "" 248 + return 227 249 end 228 250 end 229 251 230 252 log :error, "no supported auth methods" 231 - return fail_close(REPLY_FAIL) 253 + fail_close(REPLY_FAIL) 232 254 end 233 255 234 256 def handle_request 235 - data = read(BUF_SIZE) 236 - 237 257 if data[0].ord != VERSION_SOCKS5 238 258 log :error, "unsupported request version: #{data[0].inspect}" 239 259 return fail_close(REPLY_FAIL) ··· 315 335 end 316 336 log :info, l 317 337 318 - begin 319 - Timeout.timeout(5) do 320 - @remote_socket = TCPSocket.new(remote_ip, remote_port) 338 + # this will call back with send_reply(REPLY_SUCCESS) once connected 339 + @remote_connection = EventMachine.connect(remote_ip, remote_port, 340 + EMProxyConnection, self, remote_hostname, tls_decrypt) 341 + end 321 342 322 - if tls_decrypt 323 - ctx = OpenSSL::SSL::SSLContext.new 324 - # verification doesn't make sense without a hostname 325 - if remote_hostname 326 - ctx.set_params(:verify_mode => OpenSSL::SSL::VERIFY_PEER) 327 - end 328 - 329 - ssl_socket = OpenSSL::SSL::SSLSocket.new(remote_socket, ctx) 330 - ssl_socket.hostname = remote_hostname ? remote_hostname : remote_ip 331 - ssl_socket.sync_close = true 332 - ssl_socket.connect 333 - @remote_socket = ssl_socket 334 - end 335 - end 336 - rescue Timeout::Error => e 337 - log :error, "connection to #{remote_ip}:#{remote_port} failed: " << 338 - e.message 339 - return fail_close(REPLY_TTL_EXPIRED) 340 - rescue Errno::ECONNREFUSED => e 341 - log :error, "connection to #{remote_ip}:#{remote_port} failed: " << 342 - e.message 343 - return fail_close(REPLY_CONN_REFUSED) 344 - rescue OpenSSL::SSL::SSLError => e 345 - log :error, "TLS failure: #{e.message}" 346 - return fail_close(REPLY_CONN_REFUSED) 343 + def unbind 344 + if remote_connection 345 + remote_connection.close_connection 347 346 end 348 347 349 - resp = [ VERSION_SOCKS5, REPLY_SUCCESS, 0, REQUEST_ATYP_IP ] 350 - resp += IPAddr.new(remote_ip).hton.unpack("C*") 351 - resp += remote_port.to_s.unpack("n2").map(&:to_i) 352 - write resp.pack("C*") 353 - 354 - loop do 355 - log :debug, "selecting" 356 - 357 - r, w, e = IO.select([ socket, remote_socket ]) 358 - 359 - r.each do |io| 360 - case io 361 - when socket 362 - log :debug, "need to read from client socket" 363 - data = read(BUF_SIZE) 364 - log :debug, "read #{data.bytesize} from client" 365 - remote_write(data) 366 - 367 - when remote_socket 368 - log :debug, "need to read from remote socket" 369 - data = remote_read(BUF_SIZE) 370 - log :debug, "read #{data.bytesize} from remote: #{data.inspect}" 371 - write(data) 372 - end 373 - end 374 - 375 - if e.any? 376 - log :error, "select failed, closing" 377 - return close 378 - end 379 - end 348 + log :info, "closed connection" 380 349 end 381 350 end 382 351 383 - Server.run! 352 + EM.kqueue = true 353 + EM.run do 354 + EM.start_server(LISTEN_IP, LISTEN_PORT, EMSOCKS5Connection) 355 + LOGGER.info "[server] listening on #{LISTEN_IP}:#{LISTEN_PORT}" 356 + end