2010-09-14 27 views
24

Nuestro sistema se comunica con varios proveedores de servicios web. Todos se invocan desde una única aplicación de cliente Java. Todos los servicios web hasta ahora han estado en SSL, pero ninguno usa certificados de cliente. Bueno, un nuevo compañero está cambiando eso.Elegir certificado de cliente SSL en Java

Hacer que la aplicación use un certificado para la invocación es fácil; configuración javax.net.ssl.keyStore y javax.net.ssl.keyStorePassword lo hará. Sin embargo, ahora el problema es cómo hacerlo para que solo use el certificado al invocar ese servicio web en particular. Supongo que, en términos más generales, nos gustaría poder elegir el certificado del cliente que se utilizará, si corresponde.

Una solución rápida podría ser configurar las propiedades del sistema, invocando los métodos, y luego desarmarlos. El único problema con eso es que estamos tratando con una aplicación de subprocesos múltiples, por lo que ahora tendríamos que ocuparnos de la sincronización o bloqueos, o lo que sea.

Se supone que cada cliente de servicio es completamente independiente el uno del otro, y están empaquetados individualmente en JAR separados. Por lo tanto, una opción que se me ha ocurrido (aunque no la hemos analizado adecuadamente) es aislar de alguna manera cada JAR, tal vez cargar cada uno en una máquina virtual diferente con diferentes parámetros. Eso es simplemente una idea que no sé cómo implementar (o si es posible, para el caso)

This post sugiere que es posible seleccionar un certificado individual de un almacén de claves, pero cómo adjuntarlo a la solicitud parece ser un problema diferente por completo.

Estamos utilizando clases Java 1.5, Axis2 y cliente generadas con wsimport o wsdl2java.

Respuesta

7

Los clientes SSL de Java solo enviarán un certificado si así lo solicita el servidor. Un servidor puede enviar una sugerencia opcional sobre qué certificados aceptará; esto ayudará a un cliente a elegir un solo certificado si tiene múltiples.

Normalmente, se crea un nuevo SSLContext con un certificado de cliente específico, y las instancias Socket se crean a partir de una fábrica obtenida de ese contexto. Desafortunadamente, Axis2 no parece admitir el uso de un SSLContext o un SocketFactory personalizado. Sus configuraciones de certificado de cliente son globales.

+0

Lamentablemente no tenemos control sobre los servidores. También esperaba que esos consejos pudieran ser útiles, pero una prueba mostró que al menos con un servidor no lo eran. WRT Axis2, esa también ha sido mi experiencia. Si usted sabe de otra herramienta Java que permite este tipo de comportamiento, le agradecería enormemente que comparta; con gusto lo exploraremos. – Carlos

+0

@Carlos - Tal vez no estaba claro. No necesita ningún control sobre el servidor; si su socio ahora necesita un certificado de cliente, configurará su servidor para solicitarlo. Su cliente * * no enviará ese certificado a ningún otro servidor, ya que (presumiblemente) no lo están solicitando. El único problema potencial sería si los servicios adicionales comienzan a requerir un certificado de cliente, pero no aceptan las mismas CA. – erickson

+0

Entendido, gracias. Supongo que la otra pregunta permanece, sin embargo.Si mañana agregamos otro servicio web que también requiere un certificado de cliente, ¿hay alguna manera de elegir qué certificado en el almacén de claves presentar a cada servicio? – Carlos

14

La configuración se realiza a través de SSLContext, que es efectivamente una fábrica para el SSLSocketFactory (o SSLEngine). Por defecto, esto se configurará desde las propiedades javax.net.ssl.*. Además, cuando un servidor solicita un certificado, envía un mensaje TLS/SSL CertificateRequest que contiene una lista de nombres completos de CA que está dispuesto a aceptar. Aunque esta lista es, estrictamente hablando, solo indicativa (es decir, los servidores pueden aceptar certificados de emisores que no figuran en la lista o pueden rechazar certificaciones válidas de CA en la lista), generalmente funciona de esta manera.

De forma predeterminada, el selector de certificados en X509KeyManager configurado en SSLContext (de nuevo, normalmente no tiene que preocuparse por ello), elegirá uno de los certificados emitidos por uno en la lista (o puede ser encadenado a un emisor allí). Esa lista es el parámetro issuers en X509KeyManager.chooseClientAlias (el alias es el nombre de alias para el certificado que desea seleccionar, como se menciona en el almacén de claves). Si tiene varios candidatos, también puede usar el parámetro socket, que le proporcionará la dirección IP del par si eso ayuda a tomar una decisión.

Si esto ayuda, puede encontrar utilizando jSSLutils (and its wrapper) para la configuración de su SSLContext (estas son principalmente clases de ayuda para compilar SSLContext s). (Tenga en cuenta que este ejemplo es para elegir el alias del lado del servidor, pero puede ser adaptado, el source code is available.)

Una vez hecho esto, se debe buscar la documentación sobre la propiedad axis.socketSecureFactory sistema en el eje (y SecureSocketFactory). Si observa el código fuente de Axis, no debería ser demasiado difícil construir un org.apache.axis.components.net.SunJSSESocketFactory que se inicializó desde el SSLContext de su elección (consulte this question).

Acaba de darse cuenta de que estaba hablando de Axis2, donde el SecureSocketFactory parece haber desaparecido. Es posible que pueda encontrar una solución mediante el predeterminado SSLContext, pero esto afectará a toda la aplicación (lo cual no es genial). Si usa un X509KeyManagerWrapper de jSSLutils, es posible que pueda usar el predeterminado X509KeyManager y trate solo ciertos hosts como una excepción. (Esto no es una situación ideal, no estoy seguro de cómo utilizar una costumbre SSLContext/SSLSocketFactory en el eje 2.)

Alternativamente, de acuerdo con this Axis 2 document, parece que el eje 2 utiliza Apache HTTP cliente 3.x:

Si desea realizar la autenticación de cliente SSL (2 vías SSL), puede utilizar la función de Protocol.registerProtocol de HttpClient. Puede sobrescribir el protocolo "https", o utilizar un protocolo diferente para su SSL comunicaciones de autenticación de cliente si no desea meterse con https normal. Encontrar más información en http://jakarta.apache.org/commons/httpclient/sslguide.html

En este caso, el SslContextedSecureProtocolSocketFactory debería ayudarle a configurar un SSLContext.

+0

Hum ... y acaba de darse cuenta de que está utilizando Java 1.5, por lo que no 'SSLContext.setDefault (...)' de todos modos. ¿Por qué no usar Java 6? Java 5 ya no es compatible, por lo que sé (no haría daño tener varios parches de seguridad). – Bruno

+0

(Configuración del 'SSLContext' a través de Apache HTTP Client 3.x debería funcionar con Java 1.5.) – Bruno

+0

¡Gracias por su ayuda! – Carlos

0

Me inicializar EasySSLProtocolSocketFactory y el Protocolo de casos de diferentes criterios de valoración y registrar el protocolo con clave única de esta manera:

/** 
* This method does the following: 
* 1. Creates a new and unique protocol for each SSL URL that is secured by client certificate 
* 2. Bind keyStore related information to this protocol 
* 3. Registers it with HTTP Protocol object 
* 4. Stores the local reference for this custom protocol for use during furture collect calls 
* 
* @throws Exception 
*/ 
public void registerProtocolCertificate() throws Exception { 
    EasySSLProtocolSocketFactory easySSLPSFactory = new EasySSLProtocolSocketFactory(); 
    easySSLPSFactory.setKeyMaterial(createKeyMaterial()); 
    myProtocolPrefix = (HTTPS_PROTOCOL + uniqueCounter.incrementAndGet()); 
    Protocol httpsProtocol = new Protocol(myProtocolPrefix,(ProtocolSocketFactory) easySSLPSFactory, port); 
    Protocol.registerProtocol(myProtocolPrefix, httpsProtocol); 
    log.trace("Protocol [ "+myProtocolPrefix+" ] registered for the first time"); 
} 

/** 
* Load keystore for CLIENT-CERT protected endpoints 
*/ 
private KeyMaterial createKeyMaterial() throws GeneralSecurityException, Exception { 
    KeyMaterial km = null; 
    char[] password = keyStorePassphrase.toCharArray(); 
    File f = new File(keyStoreLocation); 
    if (f.exists()) { 
     try { 
      km = new KeyMaterial(keyStoreLocation, password); 
      log.trace("Keystore location is: " + keyStoreLocation + ""); 
     } catch (GeneralSecurityException gse) { 
      if (logErrors){ 
       log.error("Exception occured while loading keystore from the following location: "+keyStoreLocation, gse); 
       throw gse; 
      } 
     } 
    } else { 
     log.error("Unable to load Keystore from the following location: " + keyStoreLocation); 
     throw new CollectorInitException("Unable to load Keystore from the following location: " + keyStoreLocation); 
    } 
    return km; 
} 

Cuando tengo que invocar el servicio web, hacer esto (que básicamente reemplazar "https" en la dirección URL con https1 o https2 o algo más dependiendo del Protocolo se inicializa para que determinado punto final):

httpClient.getHostConfiguration().setHost(host, port,Protocol.getProtocol(myProtocolPrefix)); 
initializeHttpMethod(this.url.toString().replace(HTTPS_PROTOCOL, myProtocolPrefix)); 

funciona como un encanto!

+1

¿Puedes señalar las importaciones? EasySSLProtocolSocketFactory? KeyMaterial? Un ejemplo completo con dependencias sería muy agradable. Gracias. – marcolopes

+0

Sería bueno si pudieras elaborar un poco más tu código. –

+0

Eche un vistazo a este archivo en github para responder la mayoría de sus preguntas: https://github.com/nickman/helios/blob/gh-pages/helios-collectors/collectors-http/src/main/java/org/ helios/colectores/url/URLCollector.java – helios

Cuestiones relacionadas