Skip to content

Commit

Permalink
Merge pull request #55 from evgeni/ssl-proxies
Browse files Browse the repository at this point in the history
implement talking SSL to the proxy too
  • Loading branch information
hsbt authored Jul 11, 2024
2 parents bd8f570 + ae2d83f commit 227ed00
Show file tree
Hide file tree
Showing 2 changed files with 68 additions and 5 deletions.
25 changes: 20 additions & 5 deletions lib/net/http.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1103,7 +1103,7 @@ class << HTTP
# For proxy-defining arguments +p_addr+ through +p_no_proxy+,
# see {Proxy Server}[rdoc-ref:Net::HTTP@Proxy+Server].
#
def HTTP.new(address, port = nil, p_addr = :ENV, p_port = nil, p_user = nil, p_pass = nil, p_no_proxy = nil)
def HTTP.new(address, port = nil, p_addr = :ENV, p_port = nil, p_user = nil, p_pass = nil, p_no_proxy = nil, p_use_ssl = nil)
http = super address, port

if proxy_class? then # from Net::HTTP::Proxy()
Expand All @@ -1112,6 +1112,7 @@ def HTTP.new(address, port = nil, p_addr = :ENV, p_port = nil, p_user = nil, p_p
http.proxy_port = @proxy_port
http.proxy_user = @proxy_user
http.proxy_pass = @proxy_pass
http.proxy_use_ssl = @proxy_use_ssl
elsif p_addr == :ENV then
http.proxy_from_env = true
else
Expand All @@ -1123,6 +1124,7 @@ def HTTP.new(address, port = nil, p_addr = :ENV, p_port = nil, p_user = nil, p_p
http.proxy_port = p_port || default_port
http.proxy_user = p_user
http.proxy_pass = p_pass
http.proxy_use_ssl = p_use_ssl
end

http
Expand Down Expand Up @@ -1190,6 +1192,7 @@ def initialize(address, port = nil) # :nodoc:
@proxy_port = nil
@proxy_user = nil
@proxy_pass = nil
@proxy_use_ssl = nil

@use_ssl = false
@ssl_context = nil
Expand Down Expand Up @@ -1324,6 +1327,7 @@ def response_body_encoding=(value)
# Sets the proxy password;
# see {Proxy Server}[rdoc-ref:Net::HTTP@Proxy+Server].
attr_writer :proxy_pass
attr_writer :proxy_use_ssl

# Returns the IP address for the connection.
#
Expand Down Expand Up @@ -1668,7 +1672,13 @@ def connect
debug "opened"
if use_ssl?
if proxy?
plain_sock = BufferedIO.new(s, read_timeout: @read_timeout,
if @proxy_use_ssl
proxy_sock = OpenSSL::SSL::SSLSocket.new(s)
ssl_socket_connect(proxy_sock, @open_timeout)
else
proxy_sock = s
end
proxy_sock = BufferedIO.new(proxy_sock, read_timeout: @read_timeout,
write_timeout: @write_timeout,
continue_timeout: @continue_timeout,
debug_output: @debug_output)
Expand All @@ -1679,8 +1689,8 @@ def connect
buf << "Proxy-Authorization: Basic #{credential}\r\n"
end
buf << "\r\n"
plain_sock.write(buf)
HTTPResponse.read_new(plain_sock).value
proxy_sock.write(buf)
HTTPResponse.read_new(proxy_sock).value
# assuming nothing left in buffers after successful CONNECT response
end

Expand Down Expand Up @@ -1788,13 +1798,14 @@ def do_finish
@proxy_port = nil
@proxy_user = nil
@proxy_pass = nil
@proxy_use_ssl = nil

# Creates an \HTTP proxy class which behaves like \Net::HTTP, but
# performs all access via the specified proxy.
#
# This class is obsolete. You may pass these same parameters directly to
# \Net::HTTP.new. See Net::HTTP.new for details of the arguments.
def HTTP.Proxy(p_addr = :ENV, p_port = nil, p_user = nil, p_pass = nil) #:nodoc:
def HTTP.Proxy(p_addr = :ENV, p_port = nil, p_user = nil, p_pass = nil, p_use_ssl = nil) #:nodoc:
return self unless p_addr

Class.new(self) {
Expand All @@ -1812,6 +1823,7 @@ def HTTP.Proxy(p_addr = :ENV, p_port = nil, p_user = nil, p_pass = nil) #:nodoc:

@proxy_user = p_user
@proxy_pass = p_pass
@proxy_use_ssl = p_use_ssl
}
end

Expand All @@ -1836,6 +1848,9 @@ def proxy_class?
# Returns the password for accessing the proxy, or +nil+ if none;
# see Net::HTTP@Proxy+Server.
attr_reader :proxy_pass

# Use SSL when talking to the proxy. If Net::HTTP does not use a proxy, nil.
attr_reader :proxy_use_ssl
end

# Returns +true+ if a proxy server is defined, +false+ otherwise;
Expand Down
48 changes: 48 additions & 0 deletions test/net/http/test_https_proxy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -43,5 +43,53 @@ def test_https_proxy_authentication
assert_join_threads([client_thread, server_thread])
}
end

def test_https_proxy_ssl_connection
begin
OpenSSL
rescue LoadError
omit 'autoload problem. see [ruby-dev:45021][Bug #5786]'
end

tcpserver = TCPServer.new("127.0.0.1", 0)
ctx = OpenSSL::SSL::SSLContext.new
ctx.key = OpenSSL::PKey::RSA.new 2048
ctx.cert = OpenSSL::X509::Certificate.new
ctx.cert.subject = OpenSSL::X509::Name.new [['CN', 'localhost']]
ctx.cert.issuer = ctx.cert.subject
ctx.cert.public_key = ctx.key
ctx.cert.not_before = Time.now
ctx.cert.not_after = Time.now + 60 * 60 * 24
ctx.cert.sign ctx.key, OpenSSL::Digest::SHA1.new
serv = OpenSSL::SSL::SSLServer.new(tcpserver, ctx)

_, port, _, _ = serv.addr
client_thread = Thread.new {
proxy = Net::HTTP.Proxy("127.0.0.1", port, 'user', 'password', true)
http = proxy.new("foo.example.org", 8000)
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
begin
http.start
rescue EOFError
end
}
server_thread = Thread.new {
sock = serv.accept
begin
proxy_request = sock.gets("\r\n\r\n")
assert_equal(
"CONNECT foo.example.org:8000 HTTP/1.1\r\n" +
"Host: foo.example.org:8000\r\n" +
"Proxy-Authorization: Basic dXNlcjpwYXNzd29yZA==\r\n" +
"\r\n",
proxy_request,
"[ruby-core:96672]")
ensure
sock.close
end
}
assert_join_threads([client_thread, server_thread])
end
end if defined?(OpenSSL)

0 comments on commit 227ed00

Please sign in to comment.