2009-10-02 8 views
26

J'essaie d'envoyer une requête à un serveur utilisant la classe HttpsUrlConnection. Le serveur a des problèmes de certificat, donc j'ai mis en place un TrustManager qui fait confiance à tout, ainsi qu'un vérificateur de nom d'hôte qui est également indulgent. Ce gestionnaire fonctionne très bien lorsque je fais ma demande directement, mais il ne semble pas être utilisé du tout lorsque j'envoie la requête via un proxy.Comment envoyer une requête HTTPS via un proxy en Java?

Je mis mes paramètres de proxy comme ceci:

Properties systemProperties = System.getProperties(); 
systemProperties.setProperty("http.proxyHost", "proxyserver"); 
systemProperties.setProperty("http.proxyPort", "8080"); 
systemProperties.setProperty("https.proxyHost", "proxyserver"); 
systemProperties.setProperty("https.proxyPort", "8080"); 

Le TrustManager pour le SSLSocketFactory par défaut est configuré comme ceci:

SSLContext sslContext = SSLContext.getInstance("SSL"); 

// set up a TrustManager that trusts everything 
sslContext.init(null, new TrustManager[] 
    { 
     new X509TrustManager() 
     { 
      public X509Certificate[] getAcceptedIssuers() 
      { 
       return null; 
      } 

      public void checkClientTrusted(X509Certificate[] certs, String authType) 
      { 
       // everything is trusted 
      } 

      public void checkServerTrusted(X509Certificate[] certs, String authType) 
      { 
       // everything is trusted 
      } 
     } 
    }, new SecureRandom()); 

// this doesn't seem to apply to connections through a proxy 
HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory()); 

// setup a hostname verifier that verifies everything 
HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() 
{ 
    public boolean verify(String arg0, SSLSession arg1) 
    { 
     return true; 
    } 
}); 

Si je lance le code suivant, je me retrouve avec un SSLHandshakException ("Connexion à distance de l'hôte distant pendant la négociation"):

URL url = new URL("https://someurl"); 

HttpsURLConnection connection = (HttpsURLConnection)url.openConnection(); 
connection.setDoOutput(true); 

connection.setRequestMethod("POST"); 
connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); 
connection.setRequestProperty("Content-Length", "0"); 

connection.connect(); 

Je suppose qu'il me manque un certain type de paramètre ayant à voir avec l'utilisation d'un proxy lors du traitement SSL. Si je n'utilise pas de proxy, ma méthode checkServerTrusted est appelée; C'est ce que je dois faire quand je passe par le proxy aussi.

Je n'ai pas l'habitude de traiter avec Java et je n'ai pas beaucoup d'expérience avec les trucs HTTP/web. Je crois avoir fourni tous les détails nécessaires pour comprendre ce que j'essaie de faire. Si ce n'est pas le cas, faites le moi savoir.

Mise à jour:

Après avoir lu l'article proposé par ZZ Coder, j'ai fait les modifications suivantes au code de connexion:

HttpsURLConnection connection = (HttpsURLConnection)url.openConnection(); 
connection.setSSLSocketFactory(new SSLTunnelSocketFactory(proxyHost, proxyPort)); 

connection.setDoOutput(true); 
connection.setRequestMethod("POST"); 
connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); 
connection.setRequestProperty("Content-Length", "0"); 

connection.connect(); 

Le résultat (SSLHandshakeException) est le même. Lorsque j'ai défini SLLSocketFactory ici à SSLTunnelSocketFactory (la classe expliquée dans l'article), les choses que j'ai faites avec le TrustManager et le SSLContext sont remplacées. N'ai-je pas encore besoin de ça?

Une autre mise à jour:

J'ai modifié la classe SSLTunnelSocketFactory d'utiliser le SSLSocketFactory qui utilise mon TrustManager qui fait confiance tout. Il ne semble pas que cela ait fait une différence. Ceci est la méthode createSocket de SSLTunnelSocketFactory:

public Socket createSocket(Socket s, String host, int port, boolean autoClose) 
    throws IOException, UnknownHostException 
{ 
    Socket tunnel = new Socket(tunnelHost, tunnelPort); 

    doTunnelHandshake(tunnel, host, port); 

    SSLSocket result = (SSLSocket)dfactory.createSocket(
     tunnel, host, port, autoClose); 

    result.addHandshakeCompletedListener(
     new HandshakeCompletedListener() 
     { 
      public void handshakeCompleted(HandshakeCompletedEvent event) 
      { 
       System.out.println("Handshake finished!"); 
       System.out.println(
        "\t CipherSuite:" + event.getCipherSuite()); 
       System.out.println(
        "\t SessionId " + event.getSession()); 
       System.out.println(
        "\t PeerHost " + event.getSession().getPeerHost()); 
      } 
     }); 

    result.startHandshake(); 

    return result; 
} 

Quand mon code appelle connection.connect, cette méthode est appelée, et l'appel à doTunnelHandshake est réussie. La ligne de code suivante utilise mon SSLSocketFactory pour créer un SSLSocket; la valeur toString du résultat après cet appel est:

"1d49247 [SSL_NULL_WITH_NULL_NULL: Socket [adr =/proxyHost, port = proxyPort, localport = 24372]]".

Cela n'a pas de sens pour moi, mais c'est peut-être la raison pour laquelle les choses se dégradent après cela. Lorsque result.startHandshake() est appelé, la même méthode createSocket est appelée à partir de, en fonction de la pile d'appels, HttpsClient.afterConnect, avec les mêmes arguments, sauf que Socket s est nul, et quand il s'agit de résultat .startHandshake() à nouveau, le résultat est le même SSLHandshakeException.Est-ce qu'il me manque encore une pièce importante pour ce puzzle de plus en plus compliqué?

C'est la trace de la pile:

 
javax.net.ssl.SSLHandshakeException: Remote host closed connection during handshake 
    at com.sun.net.ssl.internal.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:808) 
    at com.sun.net.ssl.internal.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1112) 
    at com.sun.net.ssl.internal.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1139) 
    at com.sun.net.ssl.internal.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1123) 
    at gsauthentication.SSLTunnelSocketFactory.createSocket(SSLTunnelSocketFactory.java:106) 
    at sun.net.www.protocol.https.HttpsClient.afterConnect(HttpsClient.java:391) 
    at sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:166) 
    at sun.net.www.protocol.https.HttpsURLConnectionImpl.connect(HttpsURLConnectionImpl.java:133) 
    at gsauthentication.GSAuthentication.main(GSAuthentication.java:52) 
Caused by: java.io.EOFException: SSL peer shut down incorrectly 
    at com.sun.net.ssl.internal.ssl.InputRecord.read(InputRecord.java:333) 
    at com.sun.net.ssl.internal.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:789) 
    ... 8 more 
+0

Avez-vous vérifié jamais si le proxy a fermer la connexion, à savoir le proxy a une validation de certificat intégrée? – sfussenegger

+0

Le message d'erreur indique «Connexion de l'hôte distant fermé pendant l'établissement de liaison». Est-ce le proxy ou le serveur auquel j'essaye d'envoyer la requête? Je n'ai aucune idée de la validation du certificat. –

+0

Du point de vue des clients, je considère que le proxy est également distant. Par conséquent, je m'assurerais de (au moins) exclure le proxy comme point de défaillance. J'ai une certaine expérience avec les magasins de confiance et à quel point SSL peut être douloureux parfois. Mais je n'ai jamais vu une telle exception. Je n'ai jamais utilisé de proxy avec SSL, mais d'abord, je m'assurerais que le proxy ne nuise pas à votre connexion. – sfussenegger

Répondre

28

proxy HTTPS n'a pas de sens parce que vous ne pouvez pas mettre fin à votre connexion HTTP au proxy pour des raisons de sécurité. Avec votre stratégie d'approbation, cela peut fonctionner si le serveur proxy possède un port HTTPS. Votre erreur est due à la connexion au port proxy HTTP avec HTTPS.

Vous pouvez vous connecter via un proxy en utilisant le tunneling SSL (beaucoup de gens appellent ce proxy) en utilisant la commande proxy CONNECT. Toutefois, Java ne prend pas en charge la version plus récente du tunneling de proxy. Dans ce cas, vous devez gérer vous-même le tunnel. Vous pouvez trouver le code exemple ici,

http://www.javaworld.com/javaworld/javatips/jw-javatip111.html

EDIT: Si vous voulez vaincre toutes les mesures de sécurité dans JSSE, vous avez encore besoin de votre propre TrustManager. Quelque chose comme ça,

public SSLTunnelSocketFactory(String proxyhost, String proxyport){ 
     tunnelHost = proxyhost; 
     tunnelPort = Integer.parseInt(proxyport); 
     dfactory = (SSLSocketFactory)sslContext.getSocketFactory(); 
} 

... 

connection.setSSLSocketFactory(new SSLTunnelSocketFactory(proxyHost, proxyPort)); 
connection.setDefaultHostnameVerifier(new HostnameVerifier() 
{ 
    public boolean verify(String arg0, SSLSession arg1) 
    { 
     return true; 
    } 
} ); 

EDIT 2: Je viens d'essayer mon programme je l'ai écrit il y a quelques années à l'aide SSLTunnelSocketFactory et il ne fonctionne pas non plus. Apparemment, Sun a présenté un nouveau bug quelque temps en Java 5. Voir ce rapport de bogue,

http://bugs.sun.com/view_bug.do?bug_id=6614957

Les bonnes nouvelles sont que le bogue de tunnel SSL est fixé de sorte que vous pouvez simplement utiliser l'usine par défaut. Je viens d'essayer avec un proxy et tout fonctionne comme prévu. Voir mon code,

public class SSLContextTest { 

    public static void main(String[] args) { 

     System.setProperty("https.proxyHost", "proxy.xxx.com"); 
     System.setProperty("https.proxyPort", "8888"); 

     try { 

      SSLContext sslContext = SSLContext.getInstance("SSL"); 

      // set up a TrustManager that trusts everything 
      sslContext.init(null, new TrustManager[] { new X509TrustManager() { 
       public X509Certificate[] getAcceptedIssuers() { 
        System.out.println("getAcceptedIssuers ============="); 
        return null; 
       } 

       public void checkClientTrusted(X509Certificate[] certs, 
         String authType) { 
        System.out.println("checkClientTrusted ============="); 
       } 

       public void checkServerTrusted(X509Certificate[] certs, 
         String authType) { 
        System.out.println("checkServerTrusted ============="); 
       } 
      } }, new SecureRandom()); 

      HttpsURLConnection.setDefaultSSLSocketFactory(
        sslContext.getSocketFactory()); 

      HttpsURLConnection 
        .setDefaultHostnameVerifier(new HostnameVerifier() { 
         public boolean verify(String arg0, SSLSession arg1) { 
          System.out.println("hostnameVerifier ============="); 
          return true; 
         } 
        }); 

      URL url = new URL("https://www.verisign.net"); 
      URLConnection conn = url.openConnection(); 
      BufferedReader reader = 
       new BufferedReader(new InputStreamReader(conn.getInputStream())); 
      String line; 
      while ((line = reader.readLine()) != null) { 
       System.out.println(line); 
      } 
     } catch (Exception e) { 
      e.printStackTrace(); 
     } 
    } 
} 

C'est ce que je reçois quand je lance le programme,

checkServerTrusted ============= 
hostnameVerifier ============= 
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> 
...... 

Comme vous pouvez le voir, les deux SSLContext et hostnameVerifier sont appelés se. HostnameVerifier n'est impliqué que lorsque le nom d'hôte ne correspond pas au certificat. J'ai utilisé "www.verisign.net" pour déclencher cela.

+0

Si j'utilise la classe SSLTunnelSocketFactory abordée dans cet article, elle remplace les éléments TrustManager par défaut que j'ai ajoutés auparavant. Ai-je encore besoin de ça? –

+0

L'approbation se situe entre le navigateur et le serveur final. Cela n'a rien à voir avec le tunnel. Vous en avez toujours besoin. –

+0

Je ne vois pas comment je peux toujours l'utiliser avec la classe SSLTunnelSocketFactory. Il semble que je ne peux utiliser que l'un ou l'autre. –

0

Essayez la bibliothèque Apache Commons HttpClient au lieu d'essayer de rouler votre propre: http://hc.apache.org/httpclient-3.x/index.html

De leur code exemple:

HttpClient httpclient = new HttpClient(); 
    httpclient.getHostConfiguration().setProxy("myproxyhost", 8080); 

    /* Optional if authentication is required. 
    httpclient.getState().setProxyCredentials("my-proxy-realm", " myproxyhost", 
    new UsernamePasswordCredentials("my-proxy-username", "my-proxy-password")); 
    */ 

    PostMethod post = new PostMethod("https://someurl"); 
    NameValuePair[] data = { 
    new NameValuePair("user", "joe"), 
    new NameValuePair("password", "bloggs") 
    }; 
    post.setRequestBody(data); 
    // execute method and handle any error responses. 
    // ... 
    InputStream in = post.getResponseBodyAsStream(); 
    // handle response. 


    /* Example for a GET reqeust 
    GetMethod httpget = new GetMethod("https://someurl"); 
    try { 
    httpclient.executeMethod(httpget); 
    System.out.println(httpget.getStatusLine()); 
    } finally { 
    httpget.releaseConnection(); 
    } 
    */ 
+0

Un de mes amis m'a recommandé cela la semaine dernière. Je l'ai téléchargé, mais je n'ai pas encore eu l'occasion de l'essayer. Je vais essayer la première chose demain matin. –

Questions connexes