The OpenJDK net-dev mailing list is the best place to bring this. There was discussion about SOCKS when the HTTP client was developed, I thought JEP 321 had a summary on this but it seems not. I'm sure others on net-dev can say more on this.

-Alan


On 12/05/2024 22:58, Alessandro Autiero wrote:
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.

There are three classes in the java.net <http://java.net> package that have proxy support:

  * java.net.Socket
    Introduced in Java 1.0, supports HTTP(S)/SOCKS proxies modelled by
    java.net.Proxy through the java.net.Socket(java.net.Proxy) constructor
  * java.net.HttpURLConnection
    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
  * java.net.HttpClient (Introduced in Java 11)
    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)

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 StackOverFlow <https://stackoverflow.com/questions/70011046/how-to-use-a-socks-proxy-with-java-jdk-httpclient> 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 java.net.ProxySelector$StaticProxySelector <https://github.com/openjdk/jdk/blob/5053b70a7fc67ce9b73dbeecbdd88fbc34d45e04/src/java.base/share/classes/java/net/ProxySelector.java#L194>, 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.

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.

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:

  * java.net.Socket
    Not supported
  * java.net.HttpURLConnection
    Supported through the setAuthenticator(Authenticator) method
    introduced in Java 9
  * java.net.HttpClient
    Supported through the authenticator(Authenticator) method in its
    builder

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.

I am happy to work on all, if any, of the enhancements that are considered feasible.
I'm also looking forward to any possible criticism and feedback.
Thanks in advance.

I

Reply via email to