Un módulo que estoy agregando a nuestra gran aplicación Java tiene que conversar con el sitio web con seguridad SSL de otra compañía. El problema es que el sitio usa un certificado autofirmado. Tengo una copia del certificado para verificar que no encuentro un ataque de intermediario y necesito incorporar este certificado a nuestro código de tal manera que la conexión al servidor sea exitosa.
Aquí está el código básico:
void sendRequest(String dataPacket) {
String urlStr = "https://host.example.com/";
URL url = new URL(urlStr);
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
conn.setMethod("POST");
conn.setRequestProperty("Content-Length", data.length());
conn.setDoOutput(true);
OutputStreamWriter o = new OutputStreamWriter(conn.getOutputStream());
o.write(data);
o.flush();
}
Sin ningún manejo adicional para el certificado autofirmado, esto muere en conn.getOutputStream () con la siguiente excepción:
Exception in thread "main" 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
....
Caused by: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
....
Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
Idealmente, mi código debe enseñarle a Java a aceptar este certificado autofirmado, para este punto en la aplicación y en ningún otro lugar.
Sé que puedo importar el certificado en el almacén de autoridad de certificación de JRE, y eso permitirá que Java lo acepte. Ese no es un enfoque que quiero adoptar si puedo ayudar; parece muy invasivo hacer en todas las máquinas de nuestros clientes un módulo que no pueden usar; afectaría a todas las demás aplicaciones Java que usan el mismo JRE, y eso no me gusta a pesar de que las posibilidades de que cualquier otra aplicación Java acceda a este sitio son nulas. Tampoco es una operación trivial: en UNIX tengo que obtener derechos de acceso para modificar el JRE de esta manera.
También he visto que puedo crear una instancia de TrustManager que realiza algunas comprobaciones personalizadas. Parece que incluso podría crear un TrustManager que delegue al TrustManager real en todas las instancias, excepto en este certificado. Pero parece que TrustManager se instala globalmente, y supongo que afectaría a todas las demás conexiones de nuestra aplicación, y eso tampoco me huele bien.
¿Cuál es la forma preferida, estándar o mejor para configurar una aplicación Java para aceptar un certificado autofirmado? ¿Puedo lograr todas las metas que tengo en mente arriba, o voy a tener que comprometerme? ¿Existe una opción que involucre archivos y directorios y configuraciones de configuración, y código de poco a nada?
Respuestas:
Cree una
SSLSocket
fábrica usted mismo y configúrelaHttpsURLConnection
antes de conectarse.Querrás crear uno
SSLSocketFactory
y mantenerlo a mano. Aquí hay un bosquejo de cómo inicializarlo:Si necesita ayuda para crear el almacén de claves, comente.
Aquí hay un ejemplo de cómo cargar el almacén de claves:
Para crear el almacén de claves con un certificado de formato PEM, puede escribir su propio código utilizando
CertificateFactory
, o simplemente importarlokeytool
desde el JDK (keytool no funcionará para una "entrada de clave", pero está bien para una "entrada de confianza" )fuente
Leí MUCHOS lugares en línea para resolver esto. Este es el código que escribí para que funcione:
app.certificateString es una cadena que contiene el certificado, por ejemplo:
He probado que puede poner cualquier carácter en la cadena del certificado, si es autofirmado, siempre que mantenga la estructura exacta anterior. Obtuve la cadena de certificado con la línea de comando Terminal de mi computadora portátil.
fuente
Si crear una
SSLSocketFactory
no es una opción, solo importe la clave en la JVMRecupere la clave pública:,
$openssl s_client -connect dev-server:443
luego cree un archivo dev-server.pem que se vea comoImportar la clave:
#keytool -import -alias dev-server -keystore $JAVA_HOME/jre/lib/security/cacerts -file dev-server.pem
. Contraseña: changeitReiniciar JVM
Fuente: ¿Cómo resolver javax.net.ssl.SSLHandshakeException?
fuente
Copiamos el almacén de confianza de JRE y agregamos nuestros certificados personalizados a ese almacén de confianza, luego le decimos a la aplicación que use el almacén de confianza personalizado con una propiedad del sistema. De esta forma, dejamos solo el almacén de confianza JRE predeterminado.
La desventaja es que cuando actualiza el JRE no obtiene su nuevo almacén de confianza combinado automáticamente con el personalizado.
Tal vez podría manejar este escenario teniendo un instalador o una rutina de inicio que verifique el almacén de confianza / jdk y verifique si no hay una coincidencia o actualiza automáticamente el almacén de confianza. No sé qué sucede si actualiza el almacén de confianza mientras se ejecuta la aplicación.
Esta solución no es 100% elegante ni infalible, pero es simple, funciona y no requiere código.
fuente
Tuve que hacer algo como esto cuando uso commons-httpclient para acceder a un servidor https interno con un certificado autofirmado. Sí, nuestra solución fue crear un TrustManager personalizado que simplemente pasara todo (registrando un mensaje de depuración).
Esto se reduce a tener nuestro propio SSLSocketFactory que crea sockets SSL a partir de nuestro SSLContext local, que está configurado para tener solo nuestro TrustManager local asociado. No necesita acercarse a un almacén de claves / certstore en absoluto.
Entonces esto está en nuestra LocalSSLSocketFactory:
Junto con otros métodos que implementan SecureProtocolSocketFactory. LocalSSLTrustManager es la implementación de administrador de confianza ficticia mencionada anteriormente.
fuente