SOCKS エンドポイントはサイトまで運んでくれます。その接続の内側の TLS ピアはサイト自身なので、宛先のホスト名を重ねたソケットファクトリに渡します:

String destination = "example.com";
int destinationPort = 443;
Proxy proxy = new Proxy(
    Proxy.Type.SOCKS,
    new InetSocketAddress("127.0.0.1", 1080)
);

try (Socket transport = new Socket(proxy)) {
    transport.connect(
        InetSocketAddress.createUnresolved(destination, destinationPort),
        5000
    );
    SSLSocketFactory factory =
        (SSLSocketFactory) SSLSocketFactory.getDefault();
    try (SSLSocket tls = (SSLSocket) factory.createSocket(
            transport, destination, destinationPort, true)) {
        SSLParameters parameters = tls.getSSLParameters();
        parameters.setEndpointIdentificationAlgorithm("HTTPS");
        tls.setSSLParameters(parameters);
        tls.setSoTimeout(15000);
        tls.startHandshake();
        // Send the application's HTTPS request through tls.
    }
}

この断片は標準的な Java ネットワークと TLS のインポート、既存のデフォルト信頼設定、プロキシ資格情報を必要としない SOCKS 接続を前提としています。ハンドシェイクを実行しますが、アプリケーションはサーバーが期待する形式で HTTP リクエストを送る必要があります。許可された公開経路で公開 HTTPS を対象とする場合、SOCKS プロキシ一覧が候補のエンドポイントを提供します。サーバーのバージョンとホスト名の対応をこの Java ソケット経路に合わせてください。

例に現れる 2 つの識別情報を読む

127.0.0.1:1080 はプロキシのエンドポイントで、この Java プロセスから到達できます。destination と destinationPort はサイトを識別します。createUnresolved はホスト名をクライアント側で先に解決せず、SOCKS 経路のために保持します。

重ねた createSocket は宛先の識別情報を受け取ります。setEndpointIdentificationAlgorithm("HTTPS") はホスト名検証を明示的に要求します。生の SSLSocket の証明書チェーン信頼だけでは、ホスト名検証が有効であることを単独では証明しません。不一致を抑え込むためにプロキシ名やループバックへ置き換えると、誤ったエンドポイントを認証してしまう可能性があります。

信頼と後始末をそれぞれの段階に保つ

デフォルトのファクトリは JVM に設定された信頼構成を使います。独自要件のあるアプリケーションは、代わりに適切に初期化した SSLContext ファクトリを使えます。その選択は SOCKS 経路とは別であり、すべての証明書を受け入れるトラストマネージャは誤ったピア名を修復しません。

SOCKS の拒否は TLS より前に起きます。信頼されない証明書や誤ったホスト名は TLS の間に失敗します。その後で HTTP エラーが続くことがあります。プロキシへのログインはトンネル要求を認可するもので、その内側のサイトを認証するものではありません。

自動クローズを有効にして重ねたソケットを閉じると、そのトランスポートも閉じます。外側のリソーススコープも、通常の完了前にラップやハンドシェイクが失敗した場合にそのトランスポートを閉じます。

関連記事