example cliente certificado autenticación java ssl https client-certificates

cliente - java https client certificate authentication example



Autenticación de certificado de cliente HTTPS de Java (6)

Soy bastante nuevo en HTTPS / SSL / TLS y estoy un poco confundido sobre lo que se supone que deben presentar los clientes cuando se autentican con certificados.

Estoy escribiendo un cliente Java que necesita hacer un POST simple de datos a una URL particular. Esa parte funciona bien, el único problema es que se supone que debe hacerse a través de HTTPS. La parte HTTPS es bastante fácil de manejar (ya sea con HTTPclient o utilizando el soporte HTTPS incorporado de Java), pero no puedo realizar la autenticación con certificados de cliente. He notado que ya hay una pregunta muy similar aquí, que aún no he probado con mi código (lo haré muy pronto). Mi problema actual es que, haga lo que haga, el cliente Java nunca envía el certificado (puedo verificar esto con volcados de PCAP).

Me gustaría saber qué se supone que debe presentar exactamente el cliente al servidor cuando se autentica con certificados (específicamente para Java, si es que eso importa). ¿Es este un archivo JKS, o PKCS # 12? Lo que se supone que está en ellos; sólo el certificado de cliente, o una clave? Si es así, ¿qué clave? Existe bastante confusión acerca de los diferentes tipos de archivos, tipos de certificados y demás.

Como he dicho antes, soy nuevo en HTTPS / SSL / TLS, por lo que agradecería también algunos antecedentes (no tiene que ser un ensayo; me conformaré con los enlaces a buenos artículos).


Creo que la solución aquí fue el tipo de almacén de claves, pkcs12 (pfx) siempre tiene clave privada y el tipo JKS puede existir sin clave privada. A menos que especifique en su código o seleccione un certificado a través del navegador, el servidor no tiene forma de saber que está representando a un cliente en el otro extremo.


El archivo JKS es solo un contenedor de certificados y pares de claves. En un escenario de autenticación del lado del cliente, las distintas partes de las claves se ubicarán aquí:

  • La tienda del cliente contendrá el par de claves privada y pública del cliente. Se llama un almacén de claves .
  • El almacén del servidor contendrá la clave pública del cliente. Se llama un almacén de confianza .

La separación del almacén de confianza y el almacén de claves no es obligatoria pero se recomienda. Pueden ser el mismo archivo físico.

Para establecer las ubicaciones del sistema de archivos de las dos tiendas, use las siguientes propiedades del sistema:

-Djavax.net.ssl.keyStore=clientsidestore.jks

y en el servidor:

-Djavax.net.ssl.trustStore=serversidestore.jks

Para exportar el certificado del cliente (clave pública) a un archivo, para que pueda copiarlo en el servidor, use

keytool -export -alias MYKEY -file publicclientkey.cer -store clientsidestore.jks

Para importar la clave pública del cliente en el almacén de claves del servidor, use (como se menciona en el póster, esto ya lo hicieron los administradores del servidor)

keytool -import -file publicclientkey.cer -store serversidestore.jks


Finalmente logré resolver todos los problemas, así que responderé a mi propia pregunta. Estas son las configuraciones / archivos que solía administrar para resolver mis problemas particulares;

El almacén de claves del cliente es un archivo de formato PKCS # 12 que contiene

  1. El certificado público del cliente (en este caso firmado por una CA autofirmada)
  2. La clave privada del cliente.

Para generarlo pkcs12 comando pkcs12 de OpenSSL, por ejemplo;

openssl pkcs12 -export -in client.crt -inkey client.key -out client.p12 -name "Whatever"

Consejo: asegúrate de obtener la última versión de OpenSSL, no la versión 0.9.8h, ya que parece tener un error que no te permite generar correctamente los archivos PKCS # 12.

El cliente Java utilizará este archivo PKCS # 12 para presentar el certificado del cliente al servidor cuando el servidor haya solicitado explícitamente al cliente que se autentique. Consulte el artículo de Wikipedia en TLS para obtener una descripción general de cómo funciona realmente el protocolo para la autenticación del certificado del cliente (también explica por qué necesitamos la clave privada del cliente aquí).

El almacén de confianza del cliente es un archivo de formato JKS sencillo que contiene los certificados de CA raíz o intermedios . Estos certificados de CA determinarán con qué puntos finales se le permitirá comunicarse, en este caso le permitirá a su cliente conectarse a cualquier servidor que presente un certificado firmado por una de las CA del almacén de confianza.

Para generarlo puedes usar la herramienta de Java estándar, por ejemplo;

keytool -genkey -dname "cn=CLIENT" -alias truststorekey -keyalg RSA -keystore ./client-truststore.jks -keypass whatever -storepass whatever keytool -import -keystore ./client-truststore.jks -file myca.crt -alias myca

Usando este almacén de confianza, su cliente intentará hacer un protocolo de enlace SSL completo con todos los servidores que presenten un certificado firmado por la CA identificado por myca.crt .

Los archivos de arriba son estrictamente para el cliente solamente. Cuando también desea configurar un servidor, el servidor necesita sus propios archivos de almacén de confianza y claves. En este sitio web se puede encontrar un gran tutorial para configurar un ejemplo completamente funcional tanto para un cliente como para un servidor Java (utilizando Tomcat).

Problemas / observaciones / consejos

  1. La autenticación del certificado de cliente solo puede ser aplicada por el servidor.
  2. ( ¡Importante! ) Cuando el servidor solicita un certificado de cliente (como parte del protocolo de enlace TLS), también proporcionará una lista de CA confiables como parte de la solicitud de certificado. Cuando el certificado de cliente que desea presentar para la autenticación no está firmado por una de estas CA, no se presentará en absoluto (en mi opinión, este es un comportamiento extraño, pero estoy seguro de que hay una razón para ello). Esta fue la causa principal de mis problemas, ya que la otra parte no había configurado su servidor correctamente para aceptar mi certificado de cliente autofirmado y asumimos que el problema estaba en mi extremo por no proporcionar correctamente el certificado de cliente en la solicitud.
  3. Obtener Wireshark. Tiene un excelente análisis de paquetes SSL / HTTPS y será de gran ayuda en la depuración y la solución del problema. Es similar a -Djavax.net.debug=ssl pero es más estructurado y (posiblemente) más fácil de interpretar si no se siente cómodo con la salida de depuración de Java SSL.
  4. Es perfectamente posible utilizar la biblioteca httpclient de Apache. Si desea usar httpclient, simplemente reemplace la URL de destino con el equivalente de HTTPS y agregue los siguientes argumentos de JVM (que son los mismos para cualquier otro cliente, independientemente de la biblioteca que desee usar para enviar / recibir datos a través de HTTP / HTTPS) :

    -Djavax.net.debug=ssl -Djavax.net.ssl.keyStoreType=pkcs12 -Djavax.net.ssl.keyStore=client.p12 -Djavax.net.ssl.keyStorePassword=whatever -Djavax.net.ssl.trustStoreType=jks -Djavax.net.ssl.trustStore=client-truststore.jks -Djavax.net.ssl.trustStorePassword=whatever


Maven pom.xml:

<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>some.examples</groupId> <artifactId>sslcliauth</artifactId> <version>1.0-SNAPSHOT</version> <packaging>jar</packaging> <name>sslcliauth</name> <dependencies> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.4</version> </dependency> </dependencies> </project>

Código de Java:

package some.examples; import java.io.FileInputStream; import java.io.IOException; import java.security.KeyManagementException; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.UnrecoverableKeyException; import java.security.cert.CertificateException; import java.util.logging.Level; import java.util.logging.Logger; import javax.net.ssl.SSLContext; import org.apache.http.HttpEntity; import org.apache.http.HttpHost; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpPost; import org.apache.http.conn.ssl.SSLConnectionSocketFactory; import org.apache.http.ssl.SSLContexts; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.util.EntityUtils; import org.apache.http.entity.InputStreamEntity; public class SSLCliAuthExample { private static final Logger LOG = Logger.getLogger(SSLCliAuthExample.class.getName()); private static final String CA_KEYSTORE_TYPE = KeyStore.getDefaultType(); //"JKS"; private static final String CA_KEYSTORE_PATH = "./cacert.jks"; private static final String CA_KEYSTORE_PASS = "changeit"; private static final String CLIENT_KEYSTORE_TYPE = "PKCS12"; private static final String CLIENT_KEYSTORE_PATH = "./client.p12"; private static final String CLIENT_KEYSTORE_PASS = "changeit"; public static void main(String[] args) throws Exception { requestTimestamp(); } public final static void requestTimestamp() throws Exception { SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory( createSslCustomContext(), new String[]{"TLSv1"}, // Allow TLSv1 protocol only null, SSLConnectionSocketFactory.getDefaultHostnameVerifier()); try (CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(csf).build()) { HttpPost req = new HttpPost("https://changeit.com/changeit"); req.setConfig(configureRequest()); HttpEntity ent = new InputStreamEntity(new FileInputStream("./bytes.bin")); req.setEntity(ent); try (CloseableHttpResponse response = httpclient.execute(req)) { HttpEntity entity = response.getEntity(); LOG.log(Level.INFO, "*** Reponse status: {0}", response.getStatusLine()); EntityUtils.consume(entity); LOG.log(Level.INFO, "*** Response entity: {0}", entity.toString()); } } } public static RequestConfig configureRequest() { HttpHost proxy = new HttpHost("changeit.local", 8080, "http"); RequestConfig config = RequestConfig.custom() .setProxy(proxy) .build(); return config; } public static SSLContext createSslCustomContext() throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException, KeyManagementException, UnrecoverableKeyException { // Trusted CA keystore KeyStore tks = KeyStore.getInstance(CA_KEYSTORE_TYPE); tks.load(new FileInputStream(CA_KEYSTORE_PATH), CA_KEYSTORE_PASS.toCharArray()); // Client keystore KeyStore cks = KeyStore.getInstance(CLIENT_KEYSTORE_TYPE); cks.load(new FileInputStream(CLIENT_KEYSTORE_PATH), CLIENT_KEYSTORE_PASS.toCharArray()); SSLContext sslcontext = SSLContexts.custom() //.loadTrustMaterial(tks, new TrustSelfSignedStrategy()) // use it to customize .loadKeyMaterial(cks, CLIENT_KEYSTORE_PASS.toCharArray()) // load client certificate .build(); return sslcontext; } }


Otras respuestas muestran cómo configurar globalmente los certificados de cliente. Sin embargo, si desea definir mediante programación la clave del cliente para una conexión en particular, en lugar de definirla globalmente en cada aplicación que se ejecute en su JVM, puede configurar su propio SSLContext así:

String keyPassphrase = ""; KeyStore keyStore = KeyStore.getInstance("PKCS12"); keyStore.load(new FileInputStream("cert-key-pair.pfx"), keyPassphrase.toCharArray()); SSLContext sslContext = SSLContexts.custom() .loadKeyMaterial(keyStore, null) .build(); HttpClient httpClient = HttpClients.custom().setSSLContext(sslContext).build(); HttpResponse response = httpClient.execute(new HttpGet("https://example.com"));


Para aquellos de ustedes que simplemente desean configurar una autenticación bidireccional (certificados de servidor y cliente), una combinación de estos dos enlaces lo llevará allí:

Configuración de autenticación bidireccional:

https://linuxconfig.org/apache-web-server-ssl-authentication

No es necesario utilizar el archivo de configuración openssl que mencionan; Solo usa

  • $ openssl genrsa -des3 -out ca.key 4096

  • $ openssl req -new -x509 -days 365 -key ca.key -out ca.crt

para generar su propio certificado de CA, y luego generar y firmar las claves de servidor y cliente a través de:

  • $ openssl genrsa -des3 -out server.key 4096

  • $ openssl req -new -key server.key -out server.csr

  • $ openssl x509 -req -days 365 -in server.csr -CA ca.crt -CAkey ca.key -set_serial 100 -out server.crt

y

  • $ openssl genrsa -des3 -out client.key 4096

  • $ openssl req -new -key client.key -out client.csr

  • $ openssl x509 -req -days 365 -en client.csr -CA ca.crt -CAkey ca.key -set_serial 101 -out client.crt

Para el resto siga los pasos en el enlace. La administración de los certificados para Chrome funciona de la misma manera que en el ejemplo para Firefox que se menciona.

A continuación, configure el servidor a través de:

https://www.digitalocean.com/community/tutorials/how-to-create-a-ssl-certificate-on-apache-for-ubuntu-14-04

Tenga en cuenta que ya ha creado el servidor .crt y .key para que ya no tenga que realizar ese paso.