¿Cómo manejar certificados SSL inválidos con Apache HttpClient?

Lo sé, hay muchas preguntas diferentes y tantas respuestas sobre este problema … Pero no puedo entender …

Tengo: ubuntu-9.10-desktop-amd64 + NetBeans6.7.1 instalado “como está” desde apagado. reps. Necesito conectarme a algún sitio a través de HTTPS. Para esto uso el HttpClient de Apache.

Del tutorial leo:

“Una vez que tenga JSSE instalado correctamente, la comunicación HTTP segura a través de SSL debe ser como
simple como comunicación HTTP simple. “Y algunos ejemplos:

HttpClient httpclient = new HttpClient(); GetMethod httpget = new GetMethod("https://www.verisign.com/"); try { httpclient.executeMethod(httpget); System.out.println(httpget.getStatusLine()); } finally { httpget.releaseConnection(); } 

Por ahora, escribo esto:

 HttpClient client = new HttpClient(); HttpMethod get = new GetMethod("https://mms.nw.ru"); //get.setDoAuthentication(true); try { int status = client.executeMethod(get); System.out.println(status); BufferedInputStream is = new BufferedInputStream(get.getResponseBodyAsStream()); int r=0;byte[] buf = new byte[10]; while((r = is.read(buf)) > 0) { System.out.write(buf,0,r); } } catch(Exception ex) { ex.printStackTrace(); } 

Como resultado, tengo un conjunto de errores:

 javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target at sun.security.ssl.Alerts.getSSLException(Alerts.java:192) at sun.security.ssl.SSLSocketImpl.fatal(SSLSocketImpl.java:1627) at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:204) at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:198) at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:994) at sun.security.ssl.ClientHandshaker.processMessage(ClientHandshaker.java:142) at sun.security.ssl.Handshaker.processLoop(Handshaker.java:533) at sun.security.ssl.Handshaker.process_record(Handshaker.java:471) at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:904) at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1132) at sun.security.ssl.SSLSocketImpl.writeRecord(SSLSocketImpl.java:643) at sun.security.ssl.AppOutputStream.write(AppOutputStream.java:78) at java.io.BufferedOutputStream.flushBuffer(BufferedOutputStream.java:82) at java.io.BufferedOutputStream.flush(BufferedOutputStream.java:140) at org.apache.commons.httpclient.HttpConnection.flushRequestOutputStream(HttpConnection.java:828) at org.apache.commons.httpclient.HttpMethodBase.writeRequest(HttpMethodBase.java:2116) at org.apache.commons.httpclient.HttpMethodBase.execute(HttpMethodBase.java:1096) at org.apache.commons.httpclient.HttpMethodDirector.executeWithRetry(HttpMethodDirector.java:398) at org.apache.commons.httpclient.HttpMethodDirector.executeMethod(HttpMethodDirector.java:171) at org.apache.commons.httpclient.HttpClient.executeMethod(HttpClient.java:397) at org.apache.commons.httpclient.HttpClient.executeMethod(HttpClient.java:323) at simpleapachehttp.Main.main(Main.java:41) Caused by: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:302) at sun.security.validator.PKIXValidator.engineValidate(PKIXValidator.java:205) at sun.security.validator.Validator.validate(Validator.java:235) at sun.security.ssl.X509TrustManagerImpl.validate(X509TrustManagerImpl.java:147) at sun.security.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:230) at sun.security.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:270) at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:973) ... 17 more Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target at sun.security.provider.certpath.SunCertPathBuilder.engineBuild(SunCertPathBuilder.java:191) at java.security.cert.CertPathBuilder.build(CertPathBuilder.java:255) at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:297) ... 23 more 

¿Qué tengo que hacer para crear la conexión SSL más simple? (Probablemente sin KeyManager y Trust manager, etc., mientras que.)

https://mms.nw.ru usa un certificado autofirmado que obviamente no está incluido en el conjunto predeterminado de administradores de confianza.

Necesitarás uno de los siguientes:

  • Configure el SSLContext con un TrustManager que acepte cualquier certificado (consulte a continuación)

  • Configure el SSLContext con un almacén de confianza apropiado que incluya su certificado

  • Agregue el certificado para ese sitio al almacén de confianza de Java predeterminado.

Aquí hay un progtwig de ejemplo que crea un Contexto SSL (casi sin valor) que acepta cualquier cert:

 import java.net.URL; import java.security.SecureRandom; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.KeyManager; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSession; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; public class SSLTest { public static void main(String [] args) throws Exception { // configure the SSLContext with a TrustManager SSLContext ctx = SSLContext.getInstance("TLS"); ctx.init(new KeyManager[0], new TrustManager[] {new DefaultTrustManager()}, new SecureRandom()); SSLContext.setDefault(ctx); URL url = new URL("https://mms.nw.ru"); HttpsURLConnection conn = (HttpsURLConnection) url.openConnection(); conn.setHostnameVerifier(new HostnameVerifier() { @Override public boolean verify(String arg0, SSLSession arg1) { return true; } }); System.out.println(conn.getResponseCode()); conn.disconnect(); } private static class DefaultTrustManager implements X509TrustManager { @Override public void checkClientTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {} @Override public void checkServerTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {} @Override public X509Certificate[] getAcceptedIssuers() { return null; } } } 

https://mms.nw.ru probablemente use un certificado no emitido por una autoridad certificadora. En consecuencia, debe agregar el certificado a su almacén de claves de Java de confianza tal como se explica en la sección no se puede encontrar una ruta de certificación válida para el destino solicitado :

Al trabajar en un cliente que trabaja con un servidor SSL habilitado en protocolo https, puede obtener el error “no se puede encontrar una ruta de certificación válida para el objective solicitado” si el certificado del servidor no es emitido por la autoridad de certificación, sino auto firmado o emitido por un CMS privado

No entres en pánico Todo lo que necesita hacer es agregar el certificado del servidor a su almacén clave clave de confianza si su cliente está escrito en Java. Es posible que se pregunte cómo es que no puede acceder a la máquina donde está instalado el servidor. Hay un progtwig simple que puede ayudarte. Descargue el progtwig Java y ejecute

 % java InstallCert _web_site_hostname_ 

Este progtwig abrió una conexión con el host especificado e inició un protocolo de enlace SSL. Imprimió el rastro de la stack de excepción del error que ocurrió y le muestra los certificados utilizados por el servidor. Ahora le pide que agregue el certificado a su KeyStore de confianza.

Si ha cambiado de opinión, ingrese ‘q’. Si realmente desea agregar el certificado, ingrese ‘1’ u otros números para agregar otros certificados, incluso un certificado de CA, pero generalmente no desea hacer eso. Una vez que haya hecho su elección, el progtwig mostrará el certificado completo y luego lo agregará a una Java KeyStore llamada ‘jssecacerts’ en el directorio actual.

Para utilizarlo en su progtwig, configure JSSE para utilizarlo como su almacén de confianza o cópielo en su directorio $ JAVA_HOME / jre / lib / security. Si desea que todas las aplicaciones Java reconozcan el certificado como confiable y no solo como JSSE, también podría sobrescribir el archivo cacerts en ese directorio.

Después de todo eso, JSSE podrá completar un apretón de manos con el host, que puede verificar ejecutando el progtwig nuevamente.

Para obtener más detalles, puede consultar el blog de Leeland No más ‘no se puede encontrar una ruta de certificación válida para el objective solicitado’

Además de la respuesta correcta de Pascal Thivent, otra forma es guardar el certificado de Firefox (Ver certificado -> Detalles -> exportar) o openssl s_client e importarlo al almacén de confianza.

Solo debe hacer esto si tiene una forma de verificar ese certificado. De lo contrario, hágalo la primera vez que se conecte, al menos le dará un error si el certificado cambia inesperadamente en conexiones posteriores.

Para importarlo en una tienda de confianza, use:

 keytool -importcert -keystore truststore.jks -file servercert.pem 

De forma predeterminada, el almacén de confianza predeterminado debe ser lib/security/cacerts y su contraseña debe changeit ; consulte la Guía de referencia de JSSE para obtener más información.

Si no desea permitir ese certificado de forma global, pero solo para estas conexiones, es posible crear un SSLContext para él:

 TrustManagerFactory tmf = TrustManagerFactory .getInstance(TrustManagerFactory.getDefaultAlgorithm()); KeyStore ks = KeyStore.getInstance("JKS"); FileInputStream fis = new FileInputStream("/.../truststore.jks"); ks.load(fis, null); // or ks.load(fis, "thepassword".toCharArray()); fis.close(); tmf.init(ks); SSLContext sslContext = SSLContext.getInstance("TLS"); sslContext.init(null, tmf.getTrustManagers(), null); 

Luego, debe configurarlo para Apache HTTP Client 3.x implementando uno si es SecureProtocolSocketFactory para usar este SSLContext . (Hay ejemplos aquí ).

Apache HTTP Client 4.x (aparte de la versión más antigua) tiene soporte directo para pasar un SSLContext .

Desde http://hc.apache.org/httpclient-3.x/sslguide.html :

 Protocol.registerProtocol("https", new Protocol("https", new MySSLSocketFactory(), 443)); HttpClient httpclient = new HttpClient(); GetMethod httpget = new GetMethod("https://www.whatever.com/"); try { httpclient.executeMethod(httpget); System.out.println(httpget.getStatusLine()); } finally { httpget.releaseConnection(); } 

Donde se puede encontrar el ejemplo MySSLSocketFactory aquí . Hace referencia a TrustManager , que puede modificar para confiar en todo (¡aunque debe tener esto en cuenta!)

El modo Apache HttpClient 4.5:

 org.apache.http.ssl.SSLContextBuilder sslContextBuilder = SSLContextBuilder.create(); sslContextBuilder.loadTrustMaterial(new org.apache.http.conn.ssl.TrustSelfSignedStrategy()); SSLContext sslContext = sslContextBuilder.build(); org.apache.http.conn.ssl.SSLConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory(sslContext, new org.apache.http.conn.ssl.DefaultHostnameVerifier()); HttpClientBuilder httpClientBuilder = HttpClients.custom().setSSLSocketFactory(sslSocketFactory); httpClient = httpClientBuilder.build(); 

NOTA: org.apache.http.conn.ssl.SSLContextBuilder está en desuso y org.apache.http.ssl.SSLContextBuilder es el nuevo (falta notificación de conn en el nombre del paquete de este último).

Una vez que tenga una Java Cert Store (utilizando la gran clase InstallCert creada anteriormente), puede hacer que java la use al pasar el parámetro “javax.net.ssl.trustStore” al inicio de java.

Ex:

 java -Djavax.net.ssl.trustStore=/path/to/jssecacerts MyClassName 

Para Apache HttpClient 4.5+ y Java8:

 SSLContext sslContext = SSLContexts.custom() .loadTrustMaterial((chain, authType) -> true).build(); SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslContext, new String[] {"SSLv2Hello", "SSLv3", "TLSv1","TLSv1.1", "TLSv1.2" }, null, NoopHostnameVerifier.INSTANCE); CloseableHttpClient client = HttpClients.custom() .setSSLSocketFactory(sslConnectionSocketFactory) .build(); 

Pero si su HttpClient utiliza un ConnectionManager para buscar conexión, por ejemplo, de esta manera:

  PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(); CloseableHttpClient client = HttpClients.custom() .setConnectionManager(connectionManager) .build(); 

El HttpClients.custom().setSSLSocketFactory(sslConnectionSocketFactory) no tiene ningún efecto , el problema no se resuelve.

Debido a que el HttpClient usa el connectionManager especificado para buscar la conexión y el connectionManager especificado no ha registrado nuestro SSLConnectionSocketFactory personalizado. Para resolver esto, debe registrar el SSLConnectionSocketFactory personalizado en el connectionManager. El código correcto debería tener este aspecto:

 PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(RegistryBuilder. create() .register("http",PlainConnectionSocketFactory.getSocketFactory()) .register("https", sslConnectionSocketFactory).build()); CloseableHttpClient client = HttpClients.custom() .setConnectionManager(connectionManager) .build(); 

Otro problema que puede encontrarse con los certificados de prueba autofirmados es este:

java.io.IOException: nombre de host HTTPS incorrecto: debería ser …

Este error se produce cuando intenta acceder a una url HTTPS. Es posible que ya hayas instalado el certificado del servidor en el almacén de claves de tu JRE. Pero este error significa que el nombre del certificado del servidor no coincide con el nombre real del servidor que se menciona en la URL. Esto normalmente sucede cuando está utilizando un certificado que no es emitido por CA.

Este ejemplo muestra cómo escribir un HttpsURLConnection DefaultHostnameVerifier que ignora el nombre del servidor de certificados:

http://www.java-samples.com/showtutorial.php?tutorialid=211

Para una forma sencilla de agregar hosts en los que confíe en tiempo de ejecución sin descartar todos los controles, intente con el código aquí: http://code.google.com/p/self-signed-cert-trust-manager/ .

EasySSLProtocolSocketFactory me estaba dando problemas, así que terminé implementando mi propio ProtocolSocketFactory.

Primero debes registrarlo:

 Protocol.registerProtocol("https", new Protocol("https", new TrustAllSSLSocketFactory(), 443)); HttpClient client = new HttpClient(); ... 

Luego implementa ProtocolSocketFactory:

 class TrustAllSSLSocketFactory implements ProtocolSocketFactory { public static final TrustManager[] TRUST_ALL_CERTS = new TrustManager[]{ new X509TrustManager() { public void checkClientTrusted(final X509Certificate[] certs, final String authType) { } public void checkServerTrusted(final X509Certificate[] certs, final String authType) { } public X509Certificate[] getAcceptedIssuers() { return null; } } }; private TrustManager[] getTrustManager() { return TRUST_ALL_CERTS; } public Socket createSocket(final String host, final int port, final InetAddress clientHost, final int clientPort) throws IOException { return getSocketFactory().createSocket(host, port, clientHost, clientPort); } @Override public Socket createSocket(final String host, final int port, final InetAddress localAddress, final int localPort, final HttpConnectionParams params) throws IOException { return createSocket(host, port); } public Socket createSocket(final String host, final int port) throws IOException { return getSocketFactory().createSocket(host, port); } private SocketFactory getSocketFactory() throws UnknownHostException { TrustManager[] trustAllCerts = getTrustManager(); try { SSLContext context = SSLContext.getInstance("SSL"); context.init(null, trustAllCerts, new SecureRandom()); final SSLSocketFactory socketFactory = context.getSocketFactory(); HttpsURLConnection.setDefaultSSLSocketFactory(socketFactory); return socketFactory; } catch (NoSuchAlgorithmException | KeyManagementException exception) { throw new UnknownHostException(exception.getMessage()); } } } 

Nota: Esto es con HttpClient 3.1 y Java 8

Este enlace explica el requisito que tiene paso a paso. Si no está realmente interesado en qué certificado puede continuar con el proceso en el siguiente enlace.

Nota: es posible que desee verificar dos veces lo que está haciendo, ya que esta es una operación insegura.

Usar InstallCert para generar el archivo jssecacerts y do -Djavax.net.ssl.trustStore=/path/to/jssecacerts funcionó muy bien.

Estoy usando httpclient 3.1.X, y esto funciona para mí

  try { SSLContext sslContext = SSLContext.getInstance("TLS"); TrustManager trustManager = new X509TrustManager() { @Override public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException { } @Override public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException { } @Override public X509Certificate[] getAcceptedIssuers() { return null; } }; sslContext.init(null, new TrustManager[]{trustManager}, null); SslContextSecureProtocolSocketFactory socketFactory = new SslContextSecureProtocolSocketFactory(sslContext,false); Protocol.registerProtocol("https", new Protocol("https", (ProtocolSocketFactory) socketFactory, 443));//同样会影响到HttpUtils } catch (Throwable e) { e.printStackTrace(); 

}

 public class SslContextSecureProtocolSocketFactory implements SecureProtocolSocketFactory { private SSLContext sslContext; private boolean verifyHostname; public SslContextSecureProtocolSocketFactory(SSLContext sslContext, boolean verifyHostname) { this.verifyHostname = true; this.sslContext = sslContext; this.verifyHostname = verifyHostname; } public SslContextSecureProtocolSocketFactory(SSLContext sslContext) { this(sslContext, true); } public SslContextSecureProtocolSocketFactory(boolean verifyHostname) { this((SSLContext)null, verifyHostname); } public SslContextSecureProtocolSocketFactory() { this((SSLContext)null, true); } public synchronized void setHostnameVerification(boolean verifyHostname) { this.verifyHostname = verifyHostname; } public synchronized boolean getHostnameVerification() { return this.verifyHostname; } public Socket createSocket(String host, int port, InetAddress clientHost, int clientPort) throws IOException, UnknownHostException { SSLSocketFactory sf = this.getSslSocketFactory(); SSLSocket sslSocket = (SSLSocket)sf.createSocket(host, port, clientHost, clientPort); this.verifyHostname(sslSocket); return sslSocket; } public Socket createSocket(String host, int port, InetAddress localAddress, int localPort, HttpConnectionParams params) throws IOException, UnknownHostException, ConnectTimeoutException { if(params == null) { throw new IllegalArgumentException("Parameters may not be null"); } else { int timeout = params.getConnectionTimeout(); Socket socket = null; SSLSocketFactory socketfactory = this.getSslSocketFactory(); if(timeout == 0) { socket = socketfactory.createSocket(host, port, localAddress, localPort); } else { socket = socketfactory.createSocket(); InetSocketAddress localaddr = new InetSocketAddress(localAddress, localPort); InetSocketAddress remoteaddr = new InetSocketAddress(host, port); socket.bind(localaddr); socket.connect(remoteaddr, timeout); } this.verifyHostname((SSLSocket)socket); return socket; } } public Socket createSocket(String host, int port) throws IOException, UnknownHostException { SSLSocketFactory sf = this.getSslSocketFactory(); SSLSocket sslSocket = (SSLSocket)sf.createSocket(host, port); this.verifyHostname(sslSocket); return sslSocket; } public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException, UnknownHostException { SSLSocketFactory sf = this.getSslSocketFactory(); SSLSocket sslSocket = (SSLSocket)sf.createSocket(socket, host, port, autoClose); this.verifyHostname(sslSocket); return sslSocket; } private void verifyHostname(SSLSocket socket) throws SSLPeerUnverifiedException, UnknownHostException { synchronized(this) { if(!this.verifyHostname) { return; } } SSLSession session = socket.getSession(); String hostname = session.getPeerHost(); try { InetAddress.getByName(hostname); } catch (UnknownHostException var10) { throw new UnknownHostException("Could not resolve SSL sessions server hostname: " + hostname); } X509Certificate[] certs = (X509Certificate[])((X509Certificate[])session.getPeerCertificates()); if(certs != null && certs.length != 0) { X500Principal subjectDN = certs[0].getSubjectX500Principal(); List cns = this.getCNs(subjectDN); boolean foundHostName = false; Iterator i$ = cns.iterator(); AntPathMatcher matcher = new AntPathMatcher(); while(i$.hasNext()) { String cn = (String)i$.next(); if(matcher.match(cn.toLowerCase(),hostname.toLowerCase())) { foundHostName = true; break; } } if(!foundHostName) { throw new SSLPeerUnverifiedException("HTTPS hostname invalid: expected \'" + hostname + "\', received \'" + cns + "\'"); } } else { throw new SSLPeerUnverifiedException("No server certificates found!"); } } private List getCNs(X500Principal subjectDN) { ArrayList cns = new ArrayList(); StringTokenizer st = new StringTokenizer(subjectDN.getName(), ","); while(st.hasMoreTokens()) { String cnField = st.nextToken(); if(cnField.startsWith("CN=")) { cns.add(cnField.substring(3)); } } return cns; } protected SSLSocketFactory getSslSocketFactory() { SSLSocketFactory sslSocketFactory = null; synchronized(this) { if(this.sslContext != null) { sslSocketFactory = this.sslContext.getSocketFactory(); } } if(sslSocketFactory == null) { sslSocketFactory = (SSLSocketFactory)SSLSocketFactory.getDefault(); } return sslSocketFactory; } public synchronized void setSSLContext(SSLContext sslContext) { this.sslContext = sslContext; } 

}

Para HttpClient, podemos hacer esto:

 SSLContext ctx = SSLContext.getInstance("TLS"); ctx.init(new KeyManager[0], new TrustManager[] {new DefaultTrustManager()}, new SecureRandom()); SSLContext.setDefault(ctx); String uri = new StringBuilder("url").toString(); HostnameVerifier hostnameVerifier = new HostnameVerifier() { @Override public boolean verify(String arg0, SSLSession arg1) { return true; } }; HttpClient client = HttpClientBuilder.create().setSSLContext(ctx) .setSSLHostnameVerifier(hostnameVerifier).build()