Usar un almacén de confianza personalizado en Java así como el predeterminado

Estoy escribiendo una aplicación en Java que se conecta a dos servidores web a través de HTTPS. Uno recibió un certificado de confianza a través de la cadena de confianza predeterminada, el otro usa un certificado autofirmado. Por supuesto, conectarse al primer servidor funcionó de la caja, mientras que la conexión al servidor con el certificado autofirmado no funcionó hasta que creé un almacén de confianza con el certificado de ese servidor. Sin embargo, la conexión al servidor de confianza por defecto ya no funciona, porque al parecer se ignora el trustStore predeterminado una vez que creé el mío.

Una solución que encontré fue agregar los certificados de la tienda de confianza predeterminada a la mía. Sin embargo, esta solución no me gusta, porque me obliga a seguir administrando esa tienda de confianza. (No puedo suponer que estos certificados permanecen estáticos en el futuro previsible, ¿no?)

Aparte de eso, encontré dos hilos de 5 años con un problema similar:

Registrar múltiples almacenes de claves en JVM

¿Cómo puedo tener múltiples certificados SSL para un servidor Java?

Ambos profundizan en la infraestructura de Java SSL. Esperaba que ahora hay una solución más conveniente que puedo explicar fácilmente en una revisión de seguridad de mi código.

    Podría usar un patrón similar al que mencioné en una respuesta anterior (para un problema diferente).

    Básicamente, obtenga el administrador de confianza predeterminado, cree un segundo administrador de confianza que use su propio depósito de confianza. Envuelva ambos en una implementación de administrador de confianza personalizada que los delegates llamen a ambos (retrocediendo en el otro cuando falla).

    TrustManagerFactory tmf = TrustManagerFactory .getInstance(TrustManagerFactory.getDefaultAlgorithm()); // Using null here initialises the TMF with the default trust store. tmf.init((KeyStore) null); // Get hold of the default trust manager X509TrustManager defaultTm = null; for (TrustManager tm : tmf.getTrustManagers()) { if (tm instanceof X509TrustManager) { defaultTm = (X509TrustManager) tm; break; } } FileInputStream myKeys = new FileInputStream("truststore.jks"); // Do the same with your trust store this time // Adapt how you load the keystore to your needs KeyStore myTrustStore = KeyStore.getInstance(KeyStore.getDefaultType()); myTrustStore.load(myKeys, "password".toCharArray()); myKeys.close(); tmf = TrustManagerFactory .getInstance(TrustManagerFactory.getDefaultAlgorithm()); tmf.init(myTrustStore); // Get hold of the default trust manager X509TrustManager myTm = null; for (TrustManager tm : tmf.getTrustManagers()) { if (tm instanceof X509TrustManager) { myTm = (X509TrustManager) tm; break; } } // Wrap it in your own class. final X509TrustManager finalDefaultTm = defaultTm; final X509TrustManager finalMyTm = myTm; X509TrustManager customTm = new X509TrustManager() { @Override public X509Certificate[] getAcceptedIssuers() { // If you're planning to use client-cert auth, // merge results from "defaultTm" and "myTm". return finalDefaultTm.getAcceptedIssuers(); } @Override public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { try { finalMyTm.checkServerTrusted(chain, authType); } catch (CertificateException e) { // This will throw another CertificateException if this fails too. finalDefaultTm.checkServerTrusted(chain, authType); } } @Override public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { // If you're planning to use client-cert auth, // do the same as checking the server. finalDefaultTm.checkClientTrusted(chain, authType); } }; SSLContext sslContext = SSLContext.getInstance("TLS"); sslContext.init(null, new TrustManager[] { customTm }, null); // You don't have to set this as the default context, // it depends on the library you're using. SSLContext.setDefault(sslContext); 

    No tiene que establecer ese contexto como el contexto predeterminado. Cómo lo usa depende de la biblioteca del cliente que esté utilizando (y de dónde obtiene sus fábricas de sockets).


    Dicho esto, en principio, siempre tendrías que actualizar el almacén de confianza de todos modos. La Guía de referencia JSSE de Java 7 tenía una “nota importante” acerca de esto, ahora degradada a solo una “nota” en la versión 8 de la misma guía :

    El JDK se envía con un número limitado de certificados raíz de confianza en el archivo java-home / lib / security / cacerts. Tal como se documenta en las páginas de referencia de keytool, es su responsabilidad mantener (es decir, agregar y eliminar) los certificados contenidos en este archivo si usa este archivo como almacén de confianza.

    Dependiendo de la configuración del certificado de los servidores con los que se contacte, puede necesitar agregar certificados raíz adicionales. Obtenga los certificados raíz específicos necesarios del proveedor apropiado.