Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

implement talking SSL to the proxy too #55

Merged
merged 1 commit into from
Jul 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -1158,6 +1160,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 @@ -1292,6 +1295,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 @@ -1651,7 +1655,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)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this probably needs some more work, especially when it comes to verification etc, but it's a start :)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@evgeni, I try to avoid code changes.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure what you mean by that?

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 @@ -1662,8 +1672,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 @@ -1771,13 +1781,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 @@ -1795,6 +1806,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 @@ -1819,6 +1831,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)