Web Services en Java y Axis: solucionar javax.net.ssl.SSLHandshakeException

Como apunte extra de la entrada de cómo hacer un cliente de Web Service en Java con Axis, voy a explicaros cómo solucionar la siguiente excepción:

 

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

 

Esta excepción es lanzada cuando nuestro cliente realiza la petición al web service y en el endpoint se encuentra un certificado desconocido. Es decir, desconfía de que ese certificado sea seguro.

 

Lo primero que vamos a hacer es generar una keystore (almacén de claves) que contendrá el certificado del sitio donde se encuentra el web service. Para ello, hay que seguir los siguientes pasos:
  1. Acceder con el navegador a la URL del endpoint. En Chrome, para bajar el certificado, hay que hacer clic en el candadito al lado de la URL y seleccionar “Datos del certificado”.

  1. Después, en la pestaña “Detalles”, hacer clic en “Copiar en archivo”. Se abrirá un asistente para la exportación de certificados. Le damos todo a siguiente, le asignamos un nombre (por ejemplo, certificado.cer) y lo guardamos en el escritorio. Al finalizar el asistente, se habrá creado un archivo en el escritorio.
  2. Para generar la keystore, lo primero que hay que hacer es localizar la herramienta keytool. Para ello, abrimos una consola en Windows (Inicio > Ejecutar > cmd) y escribimos la orden cd %JAVA_HOME%/bin. El prompt debería apuntar al directorio que contiene el ejecutable keytool.exe. Una vez en él, ejecutamos la siguiente orden:
    keytool.exe -genKey -keyalg RSA -alias mykeystore -keystore c:\Users\tuusuario\Desktop\keystore.jks -storepass elblogdeselo -validity 1000 -keysize 2048 (donde mykeystore será el alias que le queramos dar al almacén de claves, elblogdeselo la contraseña con la que queremos protegerlo y 1000 será el número de días de validez del almacén. Además, habrá que poner la ruta correcta de vuestro escritorio o de la ubicación donde queráis guardar la keystore).
  3. Después de responder a las preguntas que nos plantea para crear el almacén de claves, se habrá generado el archivo keystore.jks en la ruta especificada. Para añadir el certificado a la keystore ejecutamos el siguiente comando:
    keytool.exe -import -alias mycert -file c:\Users\tuusuario\Desktop\certificado.cer -keystore c:\Users\tuusuario\Desktop\keystore.jks. Nos pedirá la contraseña y preguntará si queremos confiar en el certificado, a lo que respondemos “si”. Una vez hecho esto ya se habrá incluído el certificado dentro de la keystore.
Volviendo al proyecto de Eclipse donde tenemos nuestro cliente de Web Service, creamos un paquete llamado utilities. Dentro de este paquete meteremos el archivo keystore.jks generado en el paso 4. Además crearemos una clase nueva, que herede de la clase org.apache.axis.components.net.JSSESocketFactory y que implemente la interfaz org.apache.axis.components.net.SecureSocketFactory. Esta clase lo que hará básicamente es apuntar a la keystore correcta para que cuando el cliente realice la petición al Web Service confíe en el certificado que éste le ofrece. Así no saltará la excepción SSLHandshakeException. Dicha clase contendrá el siguiente código:
[JAVA] package utilities;
import java.io.IOException;
import java.io.InputStream;
import java.security.KeyStore;
import java.security.SecureRandom;
import java.util.Hashtable;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManagerFactory;
import org.apache.axis.components.net.JSSESocketFactory;
import org.apache.axis.components.net.SecureSocketFactory;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;

/**
* @author elblogdeselo
*
*/

public class AxisSSLSocketFactory extends JSSESocketFactory implements
SecureSocketFactory {
private static Logger logger = Logger.getLogger(AxisSSLSocketFactory.class);
private static String MY_KEYSTORE_PASSWORD = null;  // “elblogdeselo”;

// local keystore file (contains the self-signed certificate from the server)
private static String RESOURCE_PATH_TO_KEYSTORE = null;
public static String getKeystorePassword() {
return MY_KEYSTORE_PASSWORD;
}

public static void setKeystorePassword(String keystorePassword) {
MY_KEYSTORE_PASSWORD = keystorePassword;
}

public static String getResourcePathToKeystore() {
return RESOURCE_PATH_TO_KEYSTORE;
}

public static void setResourcePathToKeystore(String resourcePathToKeystore) {
RESOURCE_PATH_TO_KEYSTORE = resourcePathToKeystore;
}

public AxisSSLSocketFactory(Hashtable attributes) {
super(attributes);
}

/**
* Read the keystore, init the SSL socket factory
*
* This overrides the parent class to provide our SocketFactory
* implementation.
*
* @throws IOException
*/
protected void initFactory() throws IOException {
try {
SSLContext context = getContext();
sslFactory = context.getSocketFactory();
} catch (Exception e) {
if (e instanceof IOException) {
throw (IOException) e;
}
throw new IOException(e.getMessage());
}
}

/**
* Gets a custom SSL Context. This is the main working of this class. The
* following are the steps that make up our custom configuration:
*
* 1. Open our keystore file using the password provided
* 2. Create a KeyManagerFactory and TrustManagerFactory using this file

*3. Initialise a SSLContext using these factories

*
*/
protected SSLContext getContext() {
char[] keystorepass = MY_KEYSTORE_PASSWORD.toCharArray();
if (StringUtils.isBlank(new String(keystorepass)))
logger.error(“Could not read password for configured keystore!”);
InputStream keystoreFile = this.getClass().getClassLoader()
.getResourceAsStream(RESOURCE_PATH_TO_KEYSTORE);
if (keystoreFile == null)
logger.error(“Could not read the configured keystore file at ”
+ RESOURCE_PATH_TO_KEYSTORE);
try {
// create required keystores and their corresponding manager objects
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(keystoreFile, keystorepass);
KeyManagerFactory kmf = KeyManagerFactory
.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(keyStore, keystorepass);
TrustManagerFactory tmf = TrustManagerFactory
.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(keyStore);
// congifure a local SSLContext to use created keystores
SSLContext sslContext = SSLContext.getInstance(“SSL”);
sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(),
new SecureRandom());
return sslContext;
} catch (Exception e) {
logger.error(“Error creating context for SSLSocket!”);
}
return null;
}
}
[/JAVA]

Cuando ya tengamos la clase lista, sólo nos queda especificar en las propiedades de Axis que debe usar nuestra clase para realizar la conexión. Para ello, debemos introducir el siguiente código justo antes de la llamada al Web Service:
[JAVA]AxisSSLSocketFactory.setKeystorePassword(“elblogdeselo”);
AxisSSLSocketFactory.setResourcePathToKeystore(“utilities/keystore.jks”);
AxisProperties.setProperty(“axis.socketSecureFactory”, “utilities.AxisSSLSocketFactory”);
[/JAVA]
Y voilà! Cuando consumamos el Web Service ya no debería aparecer la dichosa excepción.

 

Publicado por

21 comentarios sobre “Web Services en Java y Axis: solucionar javax.net.ssl.SSLHandshakeException”

  1. hermano en mi computadora no logro accesar al cd %JAVA_HOME%/bin que tu dices, me envia un mensaje que dice: “system can not find the path specified” y no se que sera… te agradeceria si me ayudas.

    Gracias!!

    1. añade la variable de entorno JAVA_HOME a tu windows y que apunte al directorio del JDK que estás usando, como por ejemplo: C:\Program Files\Java\jdk1.6.0_27.
      O si no, directamente haz un cd C:/Program Files/Java/jdk1.6.0_27/bin.

  2. Muchas gracias neets, me ha sido de gran ayuda tu guía. He estado leyendo mucho en la web, y esto es lo que se necesita para poder invocar un servicio https.
    Saludos

  3. Buen dia amigo, aqui probando tu explicacion, pero no me funciona la verdad no se si sea general estoy utilizando la conexion pero con AXIS2 y creando desde un bat la conexion con webservices y eh realizado todo este proceso pero me sigue enviando el error.

    1. Buenas!! La verdad es que el cliente se hizo usando Axis y no Axis2, así que no te puedo ayudar, posiblemente la autenticación se haga de manera diferente al cambiar la versión del framework…

  4. Exelente hermano!! me funciono perfecto.. ahora me brinda otra excepcion… copio a continuacion.

    java.lang.NullPointerException
    AxisFault
    faultCode: {http://schemas.xmlsoap.org/soap/envelope/}Server.userException
    faultSubcode:
    faultString: java.net.SocketException: java.security.NoSuchAlgorithmException: Error constructing implementation (algorithm: Default, provider: SunJSSE, class: com.sun.net.ssl.internal.ssl.DefaultSSLContextImpl)
    faultActor:
    faultNode:
    faultDetail:
    {http://xml.apache.org/axis/}stackTrace:java.net.SocketException: java.security.NoSuchAlgorithmException: Error constructing implementation (algorithm: Default, provider: SunJSSE, class: com.sun.net.ssl.internal.ssl.DefaultSSLContextImpl)
    at javax.net.ssl.DefaultSSLSocketFactory.throwException(Unknown Source)
    at javax.net.ssl.DefaultSSLSocketFactory.createSocket(Unknown Source)
    at org.apache.axis.components.net.JSSESocketFactory.create(JSSESocketFactory.java:92)
    at org.apache.axis.transport.http.HTTPSender.getSocket(HTTPSender.java:191)
    at org.apache.axis.transport.http.HTTPSender.writeToSocket(HTTPSender.java:404)
    at org.apache.axis.transport.http.HTTPSender.invoke(HTTPSender.java:138)
    at org.apache.axis.strategies.InvocationStrategy.visit(InvocationStrategy.java:32)
    at org.apache.axis.SimpleChain.doVisiting(SimpleChain.java:118)
    at org.apache.axis.SimpleChain.invoke(SimpleChain.java:83)
    at org.apache.axis.client.AxisClient.invoke(AxisClient.java:165)
    at org.apache.axis.client.Call.invokeEngine(Call.java:2784)
    at org.apache.axis.client.Call.invoke(Call.java:2767)
    at org.apache.axis.client.Call.invoke(Call.java:2443)
    at org.apache.axis.client.Call.invoke(Call.java:2366)
    at org.apache.axis.client.Call.invoke(Call.java:1812)
    at afip_wsaa_client.invoke_wsaa(afip_wsaa_client.java:86)
    at wsaa_test.main(wsaa_test.java:90)

  5. Bacán Bro, pero ahora tengo este error:
    AxisFault
    faultCode: {http://schemas.xmlsoap.org/soap/envelope/}Server.userException
    faultSubcode:
    faultString:javax.net.ssl.SSLException: java.lang.RuntimeException
    : Unexpected error: java.security.InvalidAlgorithmParameterException: the trustAnchors parameter must be non-empty

    Tienes idea de cómo solucionarlo?… Muchísimas gracias

  6. Muchas gracias, es lo que estaba buscando…
    Para la excepción de Diego y Alejando, es porque no encuentra el archivo keystore.jks, hay que buscar la línea InputStream keystoreFile = this.getClass().getClassLoader().getResourceAsStream(RESOURCE_PATH_TO_KEYSTORE); y reemplazarla por:

    File file = new File(RESOURCE_PATH_TO_KEYSTORE);
    InputStream keystoreFile = null;
    try
    {
    keystoreFile = new FileInputStream(file);
    } catch (FileNotFoundException e1)
    {
    e1.printStackTrace();
    }

  7. Para los que les de un IOException lo más probable es que sea porque no encuentra el keystore.jks . Para poder evitar este error, agregad a vuestro package el fichero keystore.jks y apuntad a el de la siguiente manera (sin comillas)
    “tupackage/keystore.jks”. En caso de tener varios package dentro de vuestro package principal escribid “packagepadre/packagehija/packagenieta/keystore.jks”.
    Espero que haya quedado claro :)

  8. Nueva version. Ahora te da la clave publica y se puede leer el keystore desde cualquier punto del sistema. Solo tienes que pasarle la ruta absoluta.

    import java.io.File;
    import java.io.FileInputStream;
    import java.io.FileNotFoundException;
    import java.io.IOException;
    import java.io.InputStream;
    import java.security.KeyStore;
    import java.security.PublicKey;
    import java.security.SecureRandom;
    import java.util.Hashtable;

    import javax.net.ssl.KeyManagerFactory;
    import javax.net.ssl.SSLContext;
    import javax.net.ssl.TrustManagerFactory;

    import org.apache.axis.components.net.JSSESocketFactory;
    import org.apache.axis.components.net.SecureSocketFactory;
    import org.apache.commons.lang3.StringUtils;
    import org.apache.log4j.Logger;

    public class AxisSSLSocketFactory extends JSSESocketFactory implements SecureSocketFactory {
    private static Logger logger = Logger.getLogger(AxisSSLSocketFactory.class);
    private static String MY_KEYSTORE_PASSWORD;
    private static String RESOURCE_PATH_TO_KEYSTORE;
    private static String ALIAS;

    public static String getAlias() {
    return ALIAS;
    }

    public static void setAlias(String alias) {
    ALIAS = alias;
    }

    public static String getKeystorePassword() {
    return MY_KEYSTORE_PASSWORD;
    }

    public static void setKeystorePassword(String keystorePassword) {
    MY_KEYSTORE_PASSWORD = keystorePassword;
    }

    public static String getResourcePathToKeystore() {
    return RESOURCE_PATH_TO_KEYSTORE;
    }

    public static void setResourcePathToKeystore(String resourcePathToKeystore) {
    RESOURCE_PATH_TO_KEYSTORE = resourcePathToKeystore;
    }

    @SuppressWarnings(“rawtypes”)
    public AxisSSLSocketFactory(Hashtable attributes) {
    super(attributes);
    }
    /**
    * Leer el almacén de claves e inicializar el SSL socket factory
    *
    * Esto sobreescribe la clase padre para proveer la implementación de nuestra SocketFactory
    * implementation.
    *
    * @throws IOException
    */
    protected void initFactory() throws IOException {
    try {
    SSLContext context = getContext();
    sslFactory = context.getSocketFactory();
    } catch (Exception e) {
    if (e instanceof IOException) {
    throw (IOException) e;
    }
    throw new IOException(e.getMessage());
    }
    }

    /**
    * Obtiene un contexto SSL customizado. Esta es la función principal de esta clase
    * que permite la conexión por protocolo SSL utilizando el certificado dado. Los
    * siguientes pasos permiten la configuración customizada de nuestra conexión:
    *
    * 1. Abrir el fichero del almacén de claves usando la contraseña que pusimos para crearlo.
    * 2. Crear un KeyManagerFactory y un TrustManagerFactory usando este fichero.
    * 3. Inicializar un contexto SSL (SSLContext) usando estos factories.
    * @throws Exception
    *
    */
    protected SSLContext getContext() throws Exception {
    char[] keystorepass = MY_KEYSTORE_PASSWORD.toCharArray();
    if (StringUtils.isBlank(new String(keystorepass)))
    logger.error(“No pudo leerse la contraseña para el keystore configurado!”);
    File f=new File(RESOURCE_PATH_TO_KEYSTORE);
    InputStream keystoreFile2 = new FileInputStream(f);
    //InputStream keystoreFile = this.getClass().getClassLoader().getResourceAsStream(RESOURCE_PATH_TO_KEYSTORE);
    try {
    KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
    keyStore.load(keystoreFile2, keystorepass);
    KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
    kmf.init(keyStore, keystorepass);
    TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
    tmf.init(keyStore);
    SSLContext sslContext = SSLContext.getInstance(“SSL”);
    sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), new SecureRandom());
    return sslContext;
    } catch (Exception e) {
    throw new Exception(“Error creando el contexto para SSLSocket!”, e);
    }
    }

    /**
    * Esta función recupera la clave pública que usaremos para encriptar el mensaje
    * @return PublicKey
    * @throws FileNotFoundException
    */
    public static PublicKey getPublicKey() throws FileNotFoundException{
    char[] keystorepass = MY_KEYSTORE_PASSWORD.toCharArray();
    if (StringUtils.isBlank(new String(keystorepass)))
    logger.error(“No pudo leerse la contraseña para el keystore configurado!”);
    //InputStream keystoreFile = AxisSSLSocketFactory.class.getClassLoader().getResourceAsStream(RESOURCE_PATH_TO_KEYSTORE);
    File f=new File(RESOURCE_PATH_TO_KEYSTORE);
    InputStream keystoreFile2 = new FileInputStream(f);
    PublicKey pk = null;
    try {
    KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
    keyStore.load(keystoreFile2, keystorepass);
    pk= keyStore.getCertificate(ALIAS).getPublicKey();
    byte[] b=keyStore.getCertificate(ALIAS).getPublicKey().getEncoded();
    return pk;
    } catch (Exception e) {

    }
    return pk;
    }
    }

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos necesarios están marcados *