<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
</head>
<body>
<p>Hello Alessandro,</p>
<p>I've moved this discussion to net-dev mailing list which is more
relevant for this discussion (and Bcced core-libs-dev). If you
haven't already subscribed to net-dev, then you can do it here
<a class="moz-txt-link-freetext" href="https://mail.openjdk.org/mailman/listinfo/net-dev">https://mail.openjdk.org/mailman/listinfo/net-dev</a>.</p>
<p>-Jaikiran<br>
</p>
<div class="moz-cite-prefix">On 13/05/24 3:28 am, Alessandro Autiero
wrote:<br>
</div>
<blockquote type="cite"
cite="mid:CAM0RScncCo+J2PMpEA71Ug8RXXL9QMP+KN86Y+h1x9kn_qm2AA@mail.gmail.com">
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<div dir="ltr">
<div>Hello, my name is Alessandro Autiero and I'd like to
propose three enhancements for the java core libraries to
better support proxies in network components of the JDK.<br>
</div>
<div><br>
</div>
<div>There are three classes in the <a href="http://java.net"
target="_blank" moz-do-not-send="true">java.net</a> package
that have proxy support:<br>
<ul>
<li>java.net.Socket <br>
Introduced in Java 1.0, supports HTTP(S)/SOCKS proxies
modelled by java.net.Proxy through the
java.net.Socket(java.net.Proxy) constructor<br>
</li>
<li>java.net.HttpURLConnection <br>
Introduced in Java 1.1, supports HTTP(S)/SOCKS proxies
modelled by java.net.Proxy through the
java.net.URL#openConnection(java.net.Proxy) public method<br>
</li>
<li>java.net.HttpClient (Introduced in Java 11)<br>
Introduced in Java 11, supports HTTP(S) proxies modelled
by java.net.ProxySelector through the public
proxy(java.net.ProxySelector) method in its builder or the
default java.net.ProxySelector, which can be set by
calling
java.net.ProxySelector#setDefault(java.net.ProxySelector)</li>
</ul>
<div>While most proxies provide support for both the HTTP and
SOCKS scheme, considering that the older HTTP client API had
support for both, developers might choose to use the older
api, or to use an external one, if they need or want to
provide support for this feature. A quick Google search for
a recommendation on which Http Client to use on a modern
Java version yields many results that list SOCKS support as
a feature to keep in mind when making a choice. While this
is not necessarily indicative of the average Java developer
sentiment about the feature, I think that it should be
considered, alongside a couple of issues that were opened on
<a
href="https://stackoverflow.com/questions/70011046/how-to-use-a-socks-proxy-with-java-jdk-httpclient"
moz-do-not-send="true">StackOverFlow</a> asking about
support for this feature. Accordingly, I propose adding
support for SOCKS proxies in java.net.HttpClient. If the
change is allowed, consider that the default
java.net.ProxySelector is an instance of
sun.net.spi.DefaultProxySelector, which supports SOCKS
proxies, but this implementation cannot be initialized by
the user as it's not exposed by the module system. Starting
from Java 9, ProxySelector#of(InetSocketAddress) was
introduced, which returns an instance of <a
href="https://github.com/openjdk/jdk/blob/5053b70a7fc67ce9b73dbeecbdd88fbc34d45e04/src/java.base/share/classes/java/net/ProxySelector.java#L194"
target="_blank" moz-do-not-send="true">java.net.ProxySelector$StaticProxySelector</a>,
a static inner class of ProxySelector introduced in Java 9
which only implements support for HTTP(S) proxies.
StaticProxySelector's constructor could be modified from
StaticProxySelector(java.net.InetSocketAddress) to
StaticProxySelector(java.net.Proxy$Type,
java.net.InetSocketAddress) to initialize the java.net.Proxy
instance with a specified proxy type instead of hard coding
HTTP. Then we could overload the method
ProxySelector#of(InetSocketAddress) with
ProxySelector#of(java.net.Proxy$Type, InetSocketAddress)
method which would invoke the constructor we defined
earlier. This change would not be breaking as
StaticProxySelector is package private, no public methods
would be deleted and the default scheme would still be HTTP.
jdk.internal.net.http.HttpRequestImpl uses the ProxySelector
in its retrieveProxy method, but checks if the proxy is an
HTTP proxy: this would need to be changed as well. Finally,
considering that unlike HttpURLConnection, HttpClient
doesn't delegate the underlying connection to
java.net.Socket, the java.net.http module would need to be
enhanced to support SOCKS authentication, which could take
more effort.<br>
</div>
<div><br>
</div>
<div>Another observation that I've made is about
authentication. If a proxy requires basic authentication,
that is authentication through a username and optionally a
password, a developer can implement the
java.net.Authenticator class and override the
getPasswordAuthentication method. While basic authentication
is still the norm for most proxies, it's disabled by default
in the JDK since Java 8. Though, it's possible to enable it
by overriding the net properties
jdk.http.auth.proxying.disabledSchemes and
jdk.http.auth.tunneling.disabledSchemes using
System.setProperty. I couldn't find an explanation about why
this change was implemented, so I can only speculate that it
was done to incentivize Java developers to use an IP
whitelist instead of basic auth to improve security,
assuming that the connection isn't secure(HTTP). The problem
though is that the net properties that need to be changed to
allow basic proxy authentication are only read only one time
in the static initializer of
sun.net.www.protocol.http.HttpURLConnection class, the
underlying implementation of java.net.HttpURLConnection. So,
if for example a library loads this class before the
developer calls System.setProperty, the change will have no
effect and the authentication will subsequently fail. This
may seem like an edge case, but for example in a Spring Boot
environment, this exact issue will arise if the property
isn't set before calling SpringApplication.run. I think that
the best solution would be to remove the
disabledTunnelingSchemes and disabledProxyingSchemes static
fields from sun.net.www.protocol.http.HttpURLConnection and
read the net properties when they are used instead of
caching them. This solution is not a breaking change and
should be very easy to implement as both fields are only
referenced when initializing a AuthenticationHeader in the
same class. Additionally, if we can agree on the fact that
basic authentication is still the predominant way to provide
authentication capabilities for a proxy and/or that
disabling it doesn't provide a direct security benefit, I
propose to also set jdk.http.auth.proxying.disabledSchemes
and jdk.http.auth.tunneling.disabledSchemes to an empty
String so no schemes are disabled by default. This change
could be breaking for Applications developed starting from
Java 8 that expect basic authentication to be disabled, but
I think that the scope of the impact would be much smaller,
if there would be any at all, than when these flags were
introduced breaking basic authentication for existing
applications upgrading to Java 8.<br>
</div>
<div>
<div><br>
</div>
<div>The last issue I've noticed is also about
authentication. If a developer wants to set an instance of
Authenticator instead of relying on the default one, which
can be set using Authenticator.setAuthenticator, this may
not be possible depending on the implementation:<br>
<ul>
<li>java.net.Socket <br>
Not supported<br>
</li>
<li>java.net.HttpURLConnection <br>
Supported through the setAuthenticator(Authenticator)
method introduced in Java 9</li>
<li>java.net.HttpClient<br>
Supported through the authenticator(Authenticator)
method in its builder</li>
</ul>
</div>
</div>
<div>The reason why a developer might want to provide an
instance of Authenticator instead of relying on the default
one is that, for example, in a concurrent environment where
multiple sockets exist, each using a different proxy, if the
proxy host and port are the same, but each Socket instance
is expected to use a different pair of username and
passwords, the default Authenticator cannot determine which
pair of credentials should be assigned to a given
authentication request as it lacks the scope to make this
decision. This change is not breaking as the default
authenticator of a Socket would still be the default
authenticator, which is the current behaviour. If the change
is allowed, a possible solution would be to add a private
field named authenticator and a public method
setAuthenticator(Authenticator) to java.net.Socket. Then
HttpConnectSocketImpl, the socket implementation for a
socket using an http(s) proxy, would need to use the
authenticator of its delegating socket instead of the
default one in the doTunnel method: this is a one line of
code change as HttpURLConnection already has support for
setAuthenticator from Java 9. Finally, SocksSocketImpl, the
socket implementation for a socket using a socks4/5 proxy,
would need to use the authenticator of the delegating socket
in the authenticate method. instead of calling
Authenticator.requestPasswordAuthentication, which uses the
default authenticator. <br>
<br>
</div>
<div>I am happy to work on all, if any, of the enhancements
that are considered feasible.<br>
</div>
<div>I'm also looking forward to any possible criticism and
feedback.<br>
</div>
<div>Thanks in advance.<br>
</div>
<div><br>
</div>
</div>
<div>I<br>
</div>
</div>
</blockquote>
</body>
</html>