2009-10-02 19 views
26

Estoy tratando de enviar una solicitud a un servidor utilizando la clase HttpsUrlConnection. El servidor tiene problemas de certificado, por lo que configuro un TrustManager que confía en todo, así como un verificador de nombre de host que es igualmente indulgente. Este administrador funciona bien cuando realizo mi solicitud directamente, pero no parece usarse en absoluto cuando envío la solicitud a través de un proxy.¿Cómo se envía una solicitud HTTPS a través de un proxy en Java?

puse mi configuración del proxy de esta manera:

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

El TrustManager para la SSLSocketFactory por defecto está configurado de esta manera:

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 funciono el siguiente código, que terminan con una SSLHandshakException ("Conexión remota del host remoto durante el protocolo de enlace"):

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(); 

Supongo que me falta algún tipo de ajuste que tenga que ver con el uso de un proxy cuando se trata de SSL. Si no uso un proxy, se llama a mi método checkServerTrusted; esto es lo que necesito que pase cuando estoy pasando por el proxy también.

Normalmente no trato con Java y no tengo mucha experiencia con cosas HTTP/web. Creo que he proporcionado todos los detalles necesarios para entender lo que estoy tratando de hacer. Si este no es el caso, házmelo saber.

Actualización:

Después de leer el artículo propuesto por ZZ Coder, he hecho los siguientes cambios en el código de conexión:

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(); 

El resultado (SSLHandshakeException) es el mismo. Cuando configuro SLLSocketFactory aquí en SSLTunnelSocketFactory (la clase explicada en el artículo), las cosas que hice con TrustManager y SSLContext quedan anuladas. ¿Todavía no necesito eso?

Otro Actualización:

he modificado la clase SSLTunnelSocketFactory utilizar el SSLSocketFactory que utiliza mi TrustManager que confía todo. No parece que esto haya hecho ninguna diferencia. Este es el método de createSocket 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; 
} 

Cuando mi código llama connection.connect, se llama este método, y el llamado a doTunnelHandshake tiene éxito. La siguiente línea de código usa mi SSLSocketFactory para crear un SSLSocket; el valor toString de resultado después de esta llamada es:

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

Esto no tiene sentido para mí, pero podría ser la razón por la cual las cosas se descomponen después de esto.

Cuando se llama a result.startHandshake(), se llama al mismo método createSocket de nuevo, de acuerdo con la pila de llamadas, HttpsClient.afterConnect, con los mismos argumentos, excepto que Socket s es nulo, y cuando se produce el resultado .startHandshake() de nuevo, el resultado es la misma SSLHandshakeException.

¿Todavía me falta una pieza importante en este rompecabezas cada vez más complicado?

Este es el seguimiento de la pila:

 
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

¿Alguna vez comprobar si el proxy se cerró la conexión, es decir, el proxy tiene validación de certificado incorporada? – sfussenegger

+0

El mensaje de error dice "Conexión remota del host remoto durante el protocolo de enlace". ¿Es ese el proxy o el servidor al que intento enviar la solicitud? No tengo idea de la validación del certificado. –

+0

Desde el punto de vista del cliente, consideraría que el proxy también es remoto. Por lo tanto, me aseguraré de (al menos) excluir el proxy como un punto de falla. Tengo cierta experiencia con tiendas de confianza y cuán doloroso puede ser SSL a veces. Pero nunca he visto una excepción. Nunca utilicé un proxy con SSL, pero al principio me aseguré de que el proxy no perjudicara tu conexión. – sfussenegger

Respuesta

28

proxy HTTPS no tiene sentido porque no se puede interrumpir la conexión HTTP en el servidor proxy por razones de seguridad. Con su política de confianza, podría funcionar si el servidor proxy tiene un puerto HTTPS. Su error es causado por la conexión al puerto proxy HTTP con HTTPS.

Puede conectarse a través de un proxy utilizando tunneling SSL (muchas personas llaman a ese proxy) mediante el comando proxy CONNECT. Sin embargo, Java no es compatible con la versión más nueva de túnel proxy. En ese caso, debe manejar el túnel usted mismo. Puede encontrar código de ejemplo aquí,

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

EDIT: Si quieres derrotar a todas las medidas de seguridad en JSSE, todavía se necesita su propio TrustManager. Algo como esto,

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: Me acaba de intentar mi programa que escribí hace unos años usando SSLTunnelSocketFactory y no funciona bien. Al parecer, Sun presentó un nuevo error en algún momento en Java 5. Ver este informe de errores,

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

La buena noticia es que el error túnel SSL se fija por lo que sólo puede utilizar la fábrica por defecto. Lo intenté con un proxy y todo funciona como esperaba. Ver mi código,

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(); 
     } 
    } 
} 

Esto es lo que me pasa cuando ejecuto el programa,

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

Como se puede ver, tanto SSLContext y hostnameVerifier están recibiendo llamadas. HostnameVerifier solo está involucrado cuando el nombre de host no coincide con el certificado. Usé "www.verisign.net" para activar esto.

+0

Si uso la clase SSLTunnelSocketFactory discutida en este artículo, reemplaza las cosas predeterminadas de TrustManager que puse antes. ¿Aún necesito eso? –

+0

La confianza es entre el navegador y el servidor final. No tiene nada que ver con el túnel. Aún lo necesitas. –

+0

No veo una forma en que todavía pueda usarlo con la clase SSLTunnelSocketFactory. Parece que solo puedo usar uno o el otro. –

0

Pruebe los Comunes Apache HttpClient biblioteca en lugar de tratar de rodar su propia: http://hc.apache.org/httpclient-3.x/index.html

de su código de ejemplo:

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 amigo mío me recomendó esto la semana pasada. Lo descargué, pero no he tenido la oportunidad de probarlo todavía. Lo intentaré mañana a primera hora. –

Cuestiones relacionadas