Certificados de cliente Java a través de HTTPS / SSL

Estoy usando Java 6 y estoy tratando de crear un HttpsURLConnection contra un servidor remoto, usando un certificado de cliente.
El servidor utiliza un certificado raíz autofirmado y requiere que se presente un certificado de cliente protegido por contraseña. He agregado el certificado raíz del servidor y el certificado del cliente a un keystore predeterminado que encontré en /System/Library/Frameworks/JavaVM.framework/Versions/1.6.0/Home/lib/security/cacerts (OSX 10.5). El nombre del archivo del almacén de claves parece sugerir que no se supone que el certificado del cliente vaya allí?

De todos modos, agregar el certificado raíz a esta tienda resolvió la infame javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed' problem.

Sin embargo, ahora estoy atascado en cómo usar el certificado del cliente. Intenté dos enfoques y ninguno me lleva a ninguna parte.
Primero, y preferido, intente:

 SSLSocketFactory sslsocketfactory = (SSLSocketFactory) SSLSocketFactory.getDefault(); URL url = new URL("https://somehost.dk:3049"); HttpsURLConnection conn = (HttpsURLConnection)url.openConnection(); conn.setSSLSocketFactory(sslsocketfactory); InputStream inputstream = conn.getInputStream(); // The last line fails, and gives: // javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure 

Intenté omitir la clase HttpsURLConnection (no es ideal porque quiero hablar HTTP con el servidor), y hago esto:

 SSLSocketFactory sslsocketfactory = (SSLSocketFactory) SSLSocketFactory.getDefault(); SSLSocket sslsocket = (SSLSocket) sslsocketfactory.createSocket("somehost.dk", 3049); InputStream inputstream = sslsocket.getInputStream(); // do anything with the inputstream results in: // java.net.SocketTimeoutException: Read timed out 

Ni siquiera estoy seguro de que el certificado del cliente sea el problema aquí.

Finalmente lo resolvió;). Tengo una buena pista aquí (la respuesta de Gandalfs también tocó un poco). Los enlaces faltantes eran (principalmente) el primero de los siguientes parámetros, y hasta cierto punto, pasé por alto la diferencia entre los almacenes de claves y los almacenes de confianza.

El certificado del servidor autofirmado se debe importar a un almacén de confianza:

keytool -import -alias gridserver -file gridserver.crt -storepass $ PASS -keystore gridserver.keystore

Estas propiedades deben establecerse (ya sea en la línea de comando o en el código):

 -Djavax.net.ssl.keyStoreType=pkcs12 -Djavax.net.ssl.trustStoreType=jks -Djavax.net.ssl.keyStore=clientcertificate.p12 -Djavax.net.ssl.trustStore=gridserver.keystore -Djavax.net.debug=ssl # very verbose debug -Djavax.net.ssl.keyStorePassword=$PASS -Djavax.net.ssl.trustStorePassword=$PASS 

Código de ejemplo de trabajo:

 SSLSocketFactory sslsocketfactory = (SSLSocketFactory) SSLSocketFactory.getDefault(); URL url = new URL("https://gridserver:3049/cgi-bin/ls.py"); HttpsURLConnection conn = (HttpsURLConnection)url.openConnection(); conn.setSSLSocketFactory(sslsocketfactory); InputStream inputstream = conn.getInputStream(); InputStreamReader inputstreamreader = new InputStreamReader(inputstream); BufferedReader bufferedreader = new BufferedReader(inputstreamreader); String string = null; while ((string = bufferedreader.readLine()) != null) { System.out.println("Received " + string); } 

Si bien no se recomienda, también puede deshabilitar la validación de certificados SSL alltogether:

 import javax.net.ssl.*; import java.security.SecureRandom; import java.security.cert.X509Certificate; public class SSLTool { public static void disableCertificateValidation() { // Create a trust manager that does not validate certificate chains TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() { public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; } public void checkClientTrusted(X509Certificate[] certs, String authType) {} public void checkServerTrusted(X509Certificate[] certs, String authType) {} }}; // Ignore differences between given hostname and certificate hostname HostnameVerifier hv = new HostnameVerifier() { public boolean verify(String hostname, SSLSession session) { return true; } }; // Install the all-trusting trust manager try { SSLContext sc = SSLContext.getInstance("SSL"); sc.init(null, trustAllCerts, new SecureRandom()); HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory()); HttpsURLConnection.setDefaultHostnameVerifier(hv); } catch (Exception e) {} } } 

¿Ha establecido las propiedades KeyStore y / o TrustStore System?

 java -Djavax.net.ssl.keyStore=pathToKeystore -Djavax.net.ssl.keyStorePassword=123456 

o desde con el código

 System.setProperty("javax.net.ssl.keyStore", pathToKeyStore); 

Lo mismo con javax.net.ssl.trustStore

Si está tratando con una llamada de servicio web utilizando el marco de Axis, hay una respuesta mucho más simple. Si lo único que desea es que su cliente pueda llamar al servicio web SSL e ignorar los errores del certificado SSL, simplemente coloque esta statement antes de invocar cualquier servicio web:

System.setProperty("axis.socketSecureFactory", "org.apache.axis.components.net.SunFakeTrustSocketFactory");

Se aplican los descargos de responsabilidad habituales acerca de que esto es una muy mala cosa que hacer en un entorno de producción.

Encontré esto en la wiki de Axis .

Para mí, esto es lo que funcionó usando Apache HttpComponents ~ HttpClient 4.x:

  KeyStore keyStore = KeyStore.getInstance("PKCS12"); FileInputStream instream = new FileInputStream(new File("client-p12-keystore.p12")); try { keyStore.load(instream, "helloworld".toCharArray()); } finally { instream.close(); } // Trust own CA and all self-signed certs SSLContext sslcontext = SSLContexts.custom() .loadKeyMaterial(keyStore, "helloworld".toCharArray()) //.loadTrustMaterial(trustStore, new TrustSelfSignedStrategy()) //custom trust store .build(); // Allow TLSv1 protocol only SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory( sslcontext, new String[] { "TLSv1" }, null, SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); //TODO CloseableHttpClient httpclient = HttpClients.custom() .setHostnameVerifier(SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER) //TODO .setSSLSocketFactory(sslsf) .build(); try { HttpGet httpget = new HttpGet("https://localhost:8443/secure/index"); System.out.println("executing request" + httpget.getRequestLine()); CloseableHttpResponse response = httpclient.execute(httpget); try { HttpEntity entity = response.getEntity(); System.out.println("----------------------------------------"); System.out.println(response.getStatusLine()); if (entity != null) { System.out.println("Response content length: " + entity.getContentLength()); } EntityUtils.consume(entity); } finally { response.close(); } } finally { httpclient.close(); } 

El archivo P12 contiene el certificado del cliente y la clave privada del cliente, creados con BouncyCastle:

 public static byte[] convertPEMToPKCS12(final String keyFile, final String cerFile, final String password) throws IOException, CertificateException, KeyStoreException, NoSuchAlgorithmException, NoSuchProviderException { // Get the private key FileReader reader = new FileReader(keyFile); PEMParser pem = new PEMParser(reader); PEMKeyPair pemKeyPair = ((PEMKeyPair)pem.readObject()); JcaPEMKeyConverter jcaPEMKeyConverter = new JcaPEMKeyConverter().setProvider("BC"); KeyPair keyPair = jcaPEMKeyConverter.getKeyPair(pemKeyPair); PrivateKey key = keyPair.getPrivate(); pem.close(); reader.close(); // Get the certificate reader = new FileReader(cerFile); pem = new PEMParser(reader); X509CertificateHolder certHolder = (X509CertificateHolder) pem.readObject(); java.security.cert.Certificate x509Certificate = new JcaX509CertificateConverter().setProvider("BC") .getCertificate(certHolder); pem.close(); reader.close(); // Put them into a PKCS12 keystore and write it to a byte[] ByteArrayOutputStream bos = new ByteArrayOutputStream(); KeyStore ks = KeyStore.getInstance("PKCS12", "BC"); ks.load(null); ks.setKeyEntry("key-alias", (Key) key, password.toCharArray(), new java.security.cert.Certificate[]{x509Certificate}); ks.store(bos, password.toCharArray()); bos.close(); return bos.toByteArray(); } 

Utilizo el paquete Apache commons HTTP Client para hacer esto en mi proyecto actual y funciona bien con SSL y un certificado autofirmado (después de instalarlo en cacerts como usted mencionó). Por favor, échale un vistazo aquí:

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

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

Creo que tienes un problema con tu certificado de servidor, no es un certificado válido (creo que esto es lo que significa “falla de handshake” en este caso):

Importe su certificado de servidor en su almacén de claves trustcacerts en el JRE del cliente. Esto se hace fácilmente con keytool :

 keytool -import -alias  -file  -keystore /lib/security/cacerts 

Usando el código debajo

 -Djavax.net.ssl.keyStoreType=pkcs12 

o

 System.setProperty("javax.net.ssl.keyStore", pathToKeyStore); 

no es requerido en absoluto. Además, no es necesario crear su propia fábrica SSL personalizada.

También encontré el mismo problema, en mi caso había un problema de que la cadena completa de certificados no se importaba a los almacenes de confianza. Importe certificados utilizando la utilidad keytool a partir del certificado raíz, también puede abrir el archivo cacerts en el bloc de notas y ver si se importa o no la cadena de certificados completa. Verifique con el nombre de alias que ha proporcionado al importar certificados, abra los certificados y vea cuántos contiene, el mismo número de certificados debe estar presente en el archivo de cacerts.

También el archivo cacerts debe configurarse en el servidor donde está ejecutando su aplicación, los dos servidores se autenticarán entre sí con claves públicas / privadas.