2009-11-03 27 views
170

Soy bastante nuevo en HTTPS/SSL/TLS y estoy un poco confundido sobre qué se supone que los clientes deben presentar al autenticar con certificados.autenticación de certificado de cliente Java HTTPS

Estoy escribiendo un cliente de Java que necesita hacer una simple POST de datos a una URL en 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 con el soporte HTTPS incorporado de Java), pero estoy atascado en la autenticación con certificados de cliente. Me di cuenta de que ya hay una pregunta muy similar aquí, que aún no probé con mi código (lo haré lo suficientemente pronto). Mi problema actual es que, haga lo que haga, el cliente de Java nunca envía el certificado (puedo verificarlo con volcados de PCAP).

Me gustaría saber qué es exactamente lo que se supone que el cliente debe presentar al servidor cuando se autentica con certificados (específicamente para Java, si eso importa en absoluto)? ¿Es este un archivo JKS, o PKCS # 12? Lo que se supone que debe estar en ellos; solo el certificado del cliente, o una clave? Si es así, ¿qué tecla? Existe una gran confusión sobre 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 algo de información de fondo también (no tiene que ser un ensayo; me conformaré con enlaces a buenos artículos).

Respuesta

186

finalmente logró resolver todos los problemas, así que voy a responder a mi propia pregunta.Estas son las configuraciones/archivos que he usado para resolver mis problemas particulares;

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

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

Para generarlo, utilicé OpenSSL's pkcs12 comando, por ejemplo;

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

Consejo: asegurarse de obtener la última versión de OpenSSL, no versión 0.9.8h ya que parece sufrir de un error que no le permiten generar correctamente archivos PKCS # 12.

Este archivo PKCS # 12 será utilizada por el cliente Java para presentar el certificado de cliente al servidor cuando el servidor haya solicitado expresamente el cliente para autenticar. Ver el Wikipedia article on TLS para una visión general de cómo el protocolo para la autenticación de certificado de cliente realmente funciona (también explica por qué necesitamos la clave privada del cliente aquí). almacén de confianza

del cliente es un archivo de formato sencillo JKS que contiene la raíz o certificados de CA intermedios. Estos certificados CA determinarán qué criterios de valoración que se le permita comunicarse con, en este caso se le permitirá a su cliente para conectarse a cualquier servidor presenta un certificado firmado por una de CA del almacén de confianza del.

para generarlo puede utilizar la herramienta de claves 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 

El uso de este almacén de confianza, su cliente va a tratar de hacer un protocolo de enlace SSL completa con todos los servidores que presenten un certificado firmado por la CA identificado por myca.crt.

los archivos anteriores son estrictamente sólo para el cliente. Cuando quiera configurar un servidor también, el servidor necesita sus propios archivos de claves y de almacén de confianza. Puede encontrar un gran recorrido para configurar un ejemplo completamente funcional para un cliente y servidor Java (utilizando Tomcat) en this website.

Problemas/Observaciones/Sugerencias

  1. autenticación de certificados de cliente sólo pueden ser ejecutadas por el servidor.
  2. ()) Cuando el servidor solicita un certificado de cliente (como parte del intercambio de información TLS), también proporciona 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 esfirmado por uno de estos CA de, 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 supusimos que el problema estaba en mi extremo por no proporcionar correctamente el certificado del cliente en la solicitud.
  3. Obtén Wireshark. Tiene un gran análisis de paquetes SSL/HTTPS y será una gran ayuda para la eliminación de fallas y para encontrar el 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 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 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) : pom.xml

    -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
+3

"Cuando el certificado del cliente que desea presentar para la autenticación no está firmado por una de estas CA, no se presentará en absoluto". Los certificados no se presentan porque el cliente sabe que no serán aceptados por el servidor. Además, su certificado puede ser firmado por una CA intermedia "ICA", y el servidor puede presentar a su cliente la CA raíz "RCA", y su navegador web aún le permitirá elegir su certificado aunque esté firmado por ICA y no por RCA. – KyleM

+2

Como ejemplo del comentario anterior, considere una situación en la que tiene una CA raíz (RCA1) y dos CA intermedias (ICA1 e ICA2). En Apache Tomcat si importa RCA1 en el almacén de confianza, su navegador web presentará TODOS los certificados firmados por ICA1 e ICA2, aunque no estén en su tienda de confianza. Esto se debe a que es la cadena lo que no importa cert individuales. – KyleM

+2

"en mi opinión, este comportamiento es extraño, pero estoy seguro de que hay una razón para ello". La razón es que eso es lo que dice en RFC 2246. Nada raro sobre eso. Permitir que los clientes presenten certificados que no serán aceptados por el servidor es lo que sería extraño, y una completa pérdida de tiempo y espacio. – EJP

24

El archivo JKS es solo un contenedor para certificados y pares de claves. En un escenario de autenticación de cliente, las diferentes partes de las teclas se encuentran aquí: tienda

  • Los cliente contendrá par de claves privada y pública del cliente. Se llama keystore.
  • La tienda del servidor contendrá la clave pública del cliente. Se llama truststore.

La separación de truststore y keystore 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, utilice 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 una archivo, para que pueda copiarlo en el servidor, use

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

Para importar las publicaciones del cliente c clave en almacén de claves del servidor, el uso (como el cartel mencionado, esto ya se ha hecho por los administradores del servidor)

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

Probablemente debería mencionar que no tengo control sobre el servidor. El servidor ha importado nuestro certificado público. Los administradores de ese sistema me han dicho que necesito proporcionar explícitamente el certificado para que pueda enviarse durante el intercambio (su servidor lo solicita explícitamente). – tmbrggmn

+0

Necesitará claves públicas y privadas para su certificado público (aquel conocido por el servidor) como un archivo JKS. – sfussenegger

+0

Gracias por el código de ejemplo. En el código anterior, ¿qué es "mykey-public.cer" exactamente? ¿Es este el certificado público del cliente (usamos certificados autofirmados)? – tmbrggmn

8

Maven: código

<?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> 

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; 
} 

} 
+0

Si prefiere tener el certificado disponible para todas las aplicaciones que usan una instalación de JVM en particular, siga [esta respuesta] (http: //stackoverflow.com/a/16397662/1134080) en su lugar. – ADTC

+0

es el método 'configureRequest()' para configurar el proxy del proyecto del cliente ¿verdad? – shareef

+0

sí, es la configuración del cliente http y es la configuración del proxy en este caso – wildloop

26

Otras respuestas muestran cómo configurar globalmente certificados de cliente. Sin embargo, si se quiere definir mediante programación la clave de cliente para una conexión particular, en lugar de a nivel mundial lo definen a través de cada aplicación que se ejecuta en la JVM, a continuación, puede configurar su propia 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")); 
+0

Tuve que usar 'sslContext = SSLContexts.custom(). LoadTrustMaterial (keyFile, PASSWORD) .build();'. No pude hacer que funcione con 'loadKeyMaterial (...)'. –

+1

El material @ConorSvensson Trust es para el cliente que confía en el certificado de servidor remoto, el material clave es que el servidor confíe en el cliente. – Magnus

+1

Me gusta mucho esta respuesta concisa y puntual. En caso de que las personas estén interesadas, proporciono un ejemplo trabajado aquí con instrucciones de compilación. https://stackoverflow.com/a/46821977/154527 –

0

Creo que la solución aquí estaba 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 manera de saber que representa a un cliente en el otro extremo.

Cuestiones relacionadas