2009-05-17 19 views
103

Estoy usando Java 6 y estoy tratando de crear un HttpsURLConnection contra un servidor remoto, usando un certificado de cliente.
El servidor está utilizando 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 almacén de claves Java 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 entre ahí.Certificados de cliente Java a través de HTTPS/SSL

De todos modos, añadiendo el certificado raíz de esta tienda resuelto el infame javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed' problem.

Sin embargo, ahora estoy atascado en cómo utilizar el certificado de cliente. Intenté dos enfoques y ninguno me lleva a ninguna parte.
En primer lugar, y preferido, prueba:

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 

He intentado saltarse la clase HttpsURLConnection (no es ideal ya que quiero hablar con el servidor HTTP), y hacer esto en su lugar:

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í.

Respuesta

82

Finalmente solucioné;). Tengo una gran pista here (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 de servidor autofirmado debe ser importado en un almacén de confianza:

herramienta de claves -import -alias GridServer -file gridserver.crt -storepass $ PASS -keystore GridServer.almacén de claves

Estas propiedades tienen que ajustarse (ya sea en la línea de comandos, 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 

Trabajando código de ejemplo:

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); 
} 
+0

He usado una URL como: https: // localhost: 8443/Application_Name/getAttributes. Tengo un método con/getAttribute url mapping. Este método devuelve una lista de elementos. He usado HttpsUrlConnection, el código de respuesta de conexión es 200, pero no me da la lista de atributos cuando uso inputStream, me da el contenido html de mi página de inicio de sesión. He hecho Autenticación y configuré el tipo de contenido como JSON. Sugiera – Deepak

4

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 tome un vistazo aquí:

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

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

+1

Este parece un paquete bastante ordenado, pero la clase que debería hacer que todo funcione 'AuthSSLProtocolSocketFactory' aparentemente no forma parte de la distribución oficial, ni en 4.0beta (a pesar de que las notas de la versión indican que sí), ni en 3.1. Lo he hackeado un poco y ahora parece estar permanentemente atorado con un bloqueo de 5 minutos antes de que simplemente se desconecte. Es realmente extraño: si cargo la CA y el certificado del cliente en cualquier navegador, simplemente vuela. – Jan

+1

Apache HTTP Client 4 puede tomar 'SSLContext' directamente, por lo que puede configurar todo esto de esta manera, en lugar de usar' AuthSSLProtocolSocketFactory'. – Bruno

18

se ha marcado el almacén de claves propiedades y/o el sistema TrustStore?

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

o desde el código

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

mismo con javax.net.ssl.trustStore

3

yo creo que hay un problema con el certificado de servidor, no es un certificado válido (I cree que esto es lo que significa "handshake_failure" 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 <provide_an_alias> 
    -file <certificate_file> 
    -keystore <your_path_to_jre>/lib/security/cacerts 
+0

He intentado limpiar y comenzar de nuevo, y la falla del apretón de manos desapareció. Ahora solo tengo 5 minutos de silencio antes de que la conexión finalice: o – Jan

78

Aunque no es recomendable, también puede desactivar la validación SSL cert 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) {} 
    } 
} 
+0

Puedes hacer lo mismo de una manera aún más simple si estás usando el framework Axis. Ver mi respuesta a continuación. –

+62

Debe tenerse en cuenta que deshabilitar la validación de certificados como esta abre la conexión a posibles ataques MITM: ** no se usa en producción **. – Bruno

+1

El código no se compila, afortunadamente. Esta 'solución' es radicalmente insegura. – EJP

14

si se trata de un servicio web llamada utilizando el marco de Axis, hay una respuesta mucho más simple. Si todo lo que quiero es que el cliente sea capaz de llamar al servicio web SSL y pasar por alto los errores de certificado SSL, sólo hay que poner esta declaración antes de invocar cualquier servicio web:

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

Las renuncias habituales de que esta es una muy Se aplica algo malo para hacer en un entorno de producción.

Lo encontré en the Axis wiki.

+0

El OP está tratando con un HttpsURLConnection, no Axis – neu242

+2

Entiendo. No pretendo dar a entender que mi respuesta fue mejor en el caso general. Es solo que si * usa * el marco de Axis, puede tener la pregunta del OP en ese contexto. (Así es como encontré esta pregunta, en primer lugar.) En ese caso, la forma en que he proporcionado es más simple. –

+1

@MarkMeuer ¡Gracias! Funcionó perfectamente –

1

mediante el siguiente código no es en absoluto necesaria

-Djavax.net.ssl.keyStoreType=pkcs12 

o

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

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

También encontré el mismo problema, en mi caso hubo un problema con cadena de certificado completa que no se importó a las tiendas 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.

+0

La creación de su propia fábrica SSL personalizada es mucho más complicada y propensa a errores que la configuración de dos propiedades del sistema. – EJP

5

Para mí, esto es lo que ha funcionado 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 de cliente y la clave privada del cliente, creada 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(); 
} 
+0

El 'keyStore' es lo que contiene la clave privada y el certificado. – EpicPandaForce

+1

Necesita incluir estas 2 dependencias para que funcione el código convertPEMtoP12: org.BouncyCastle bcprov-jdk15on 1,53 org.bouncycastle bcpkix-jdk15on 1,53 BirdOfPrey

+0

@EpicPandaForce consigo un error: Atrapados: org.codehaus.groovy. runtime.typehandling.GroovyCastException: No se puede lanzar el objeto con la clase 'org.bouncycastle.jcajce.provider.asymmetric.x509.X509CertificateObject' a la clase 'en t 'in line ks.setKeyEntry - cualquier pista que podría estar equivocada –

Cuestiones relacionadas