OkHttp javax.net.ssl.SSLPeerUnverifiedException: nombre de host dominio.com no verificado

8 minutos de lectura

Llevo días intentando que esto funcione. Estoy tratando de conectarme a mi servidor a través de https con un certificado autofirmado. No creo que haya páginas o ejemplos que no haya leído hasta ahora.

Que he hecho:

  1. Creé el almacén de claves bks siguiendo este tutorial: http://blog.crazybob.org/2010/02/android-trusting-ssl-certificates.html

Usa openssl s_client -connect domain.com:443 para obtener el certificado del servidor. Luego crea un almacén de claves bks usando un castillo hinchable.

  1. Leer el almacén de claves creado desde la carpeta sin procesar, agregarlo a sslfactory y luego a OkHttpClient. Me gusta esto:

    public ApiService() {
        mClient = new OkHttpClient();
        mClient.setConnectTimeout(TIMEOUT_SECONDS, TimeUnit.SECONDS);
        mClient.setReadTimeout(TIMEOUT_SECONDS, TimeUnit.SECONDS);
        mClient.setCache(getCache());
        mClient.setCertificatePinner(getPinnedCerts());
        mClient.setSslSocketFactory(getSSL());
    }
    
    protected SSLSocketFactory getSSL() {
        try {
            KeyStore trusted = KeyStore.getInstance("BKS");
            InputStream in = Beadict.getAppContext().getResources().openRawResource(R.raw.mytruststore);
            trusted.load(in, "pwd".toCharArray());
            SSLContext sslContext = SSLContext.getInstance("TLS");
            TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
            trustManagerFactory.init(trusted);
            sslContext.init(null, trustManagerFactory.getTrustManagers(), null);
            return sslContext.getSocketFactory();
        } catch(Exception e) {
            e.printStackTrace();
        }
        return null;
    }
    
    public CertificatePinner getPinnedCerts() {
        return new CertificatePinner.Builder()
                .add("domain.com", "sha1/theSha=")
                .build();
    }
    
  2. Esto por alguna razón esto siempre genera un SSLPeerUnverifiedException con o sin el almacén de claves. Y con o sin el CertificatePinner.

    javax.net.ssl.SSLPeerUnverifiedException: Hostname domain.com not verified: 0         
     W/System.err﹕ certificate: sha1/theSha=
     W/System.err﹕ DN: 1.2.840.113549.1.9.1=#1610696e666f40626561646963742e636f6d,CN=http://domain.com,OU=development,O=domain,L=Valencia,ST=Valencia,C=ES
     W/System.err﹕ subjectAltNames: []
     W/System.err﹕ at com.squareup.okhttp.internal.http.SocketConnector.connectTls(SocketConnector.java:124)
     W/System.err﹕ at com.squareup.okhttp.Connection.connect(Connection.java:143)
     W/System.err﹕ at com.squareup.okhttp.Connection.connectAndSetOwner(Connection.java:185)
     W/System.err﹕ at com.squareup.okhttp.OkHttpClient$1.connectAndSetOwner(OkHttpClient.java:128)
     W/System.err﹕ at com.squareup.okhttp.internal.http.HttpEngine.nextConnection(HttpEngine.java:341)
     W/System.err﹕ at com.squareup.okhttp.internal.http.HttpEngine.connect(HttpEngine.java:330)
     W/System.err﹕ at com.squareup.okhttp.internal.http.HttpEngine.sendRequest(HttpEngine.java:248)
     W/System.err﹕ at com.squareup.okhttp.Call.getResponse(Call.java:273)
     W/System.err﹕ at com.squareup.okhttp.Call$ApplicationInterceptorChain.proceed(Call.java:230)
     W/System.err﹕ at com.squareup.okhttp.Call.getResponseWithInterceptorChain(Call.java:201)
     W/System.err﹕ at com.squareup.okhttp.Call.execute(Call.java:81)
     ...
    

¿Qué estoy haciendo mal?

  • Verifique que su certificado contenga el nombre de host real y no la dirección IP (se supone que las direcciones IP deben estar en Subject Alternative Name campo del certificado). Como para HostnameVerifier que devuelve verdadero: hará que SSL sea inútil e inseguro (la respuesta más votada). En los Android contemporáneos, puede instalar su certificado autofirmado sin problemas a través de la configuración de Seguridad.

    – Boris Treukhov

    6 de julio de 2016 a las 9:59


Tuve el mismo problema; sin embargo, necesitaba que mi aplicación funcionara en varios entornos de ensayo, todos los cuales tenían certificados autofirmados. Para empeorar las cosas, podrían cambiar esos certificados sobre la marcha.

Para solucionar esto, cuando me conectaba solo a la etapa de ensayo, agregué un SSLSocketFactory que confiaba en todos los certificados. Esto solucionó el error de Java, sin embargo, me dejó con la excepción okhttp anotada en este ticket.

Para evitar este error, necesitaba agregar una personalización más a mi okHttpClient. Esto solucionó el error para mí.

okHttpClient.setHostnameVerifier(new HostnameVerifier() {
            @Override
            public boolean verify(String hostname, SSLSession session) {
                return true;
            }
        });

  • ¡Esto no es seguro! Sé que no especifiqué eso en mi pregunta. Sé que funciona, lo he usado antes. Pero ahora soy un adulto y quiero la seguridad total de https. 🙂

    – solo_usuario

    12/09/2016 a las 10:21

  • por supuesto, esto no es seguro; esto omite explícitamente la verificación del certificado https. Esto solo debe usarse en entornos previos a la producción en los que desea evitar la verificación SSL, nunca en una compilación de producción.

    – Jake Pasillo

    13/09/2016 a las 21:28

  • setHostnameVerifier no compilar

    – Lavekush Agrawal

    06/07/2017 a las 10:00

  • setHostnameVerifier() no es un método de OkHttpCliente. Creo que lo estás confundiendo con javax.net.ssl.HttpsURLConnection

    –Mike Harris

    6 de marzo de 2019 a las 16:52

  • Gracias. me ayudó a deshacerme del error y hacer mis pruebas en depuración

    – Reza

    19 de agosto de 2020 a las 17:48

avatar de usuario
solo_usuario

Finalmente conseguí que esto funcionara con una combinación de múltiples respuestas.

Primero, los certificados se hicieron incorrectamente, no estoy seguro de cómo. Pero al crearlos usando el script en esta respuesta, los hizo funcionar. Lo que se necesitaba era un certificado de servidor y una clave. Entonces el cliente necesitaba otro certificado.

Para usar el certificado en Android, convertí el archivo .pem en un archivo .crt como este:

openssl x509 -outform der -in client.pem  -out client.crt

En Android, agregué el certificado a mi cliente OkHttp de la siguiente manera:

public ApiService() {
    mClient = new OkHttpClient();
    mClient.setConnectTimeout(TIMEOUT_SECONDS, TimeUnit.SECONDS);
    mClient.setReadTimeout(TIMEOUT_SECONDS, TimeUnit.SECONDS);
    mClient.setCache(getCache());
    mClient.setSslSocketFactory(getSSL());
}

protected SSLSocketFactory getSSL() {
    try {
        CertificateFactory cf = CertificateFactory.getInstance("X.509");
        InputStream cert = getAppContext().getResources().openRawResource(R.raw.client);
        Certificate ca = cf.generateCertificate(cert);
        cert.close();

        // creating a KeyStore containing our trusted CAs
        String keyStoreType = KeyStore.getDefaultType();
        KeyStore keyStore = KeyStore.getInstance(keyStoreType);
        keyStore.load(null, null);
        keyStore.setCertificateEntry("ca", ca);

        return new AdditionalKeyStore(keyStore);
    } catch(Exception e) {
        e.printStackTrace();
    }
    return null;
}

La última parte con new AdditionalKeyStore() está tomado de esta respuesta muy bien escrita. Lo que agrega un almacén de claves alternativo.

¡Espero que esto pueda ayudar a alguien más! Esta es la forma más sencilla de hacer que HTTPS funcione con un certificado autofirmado que he encontrado. Otras formas incluyen tener un almacén de claves BouncyCastle que me parece excesivo.

  • ¿Cuál es el archivo (cliente) en bruto?

    – iSrinivasan27

    12 de septiembre de 2016 a las 7:33

  • eso es el client.crtarchivo generado en la línea openssl.

    – solo_usuario

    12 de septiembre de 2016 a las 10:19

  • ¿Cuál es el archivo client.pem que ha utilizado aquí? Ese script genera pocos archivos .pem. ¿Es el archivo chain.pem en el directorio del cliente?

    – Chrishan

    22 de marzo de 2017 a las 1:55

  • Tienes que crearlo como parte del certificado ssl que quieres usar para tu conexión: seguridad.stackexchange.com/questions/32768/…

    – solo_usuario

    22 de marzo de 2017 a las 7:36

  • ¿Es seguro tener el archivo .crt como recurso sin procesar en su apk?

    – PrashanD

    16 de diciembre de 2019 a las 4:45

avatar de usuario
Kay Kay

Este problema se resuelve configurando setHostNameVerifier para okHttpBuilder. Asegúrese de que el método de verificación devuelva verdadero.

Muestra:

okHttpClient.setHostnameVerifier(new HostnameVerifier() {
    @Override
    public boolean verify(String hostname, SSLSession session) {
        return true;
    }
});

OkHttpClient.Builder builder = new OkHttpClient.Builder();
    builder.hostnameVerifier(new HostnameVerifier() {
        @Override
        public boolean verify(String hostname, SSLSession session) {
            return true;
        }
    });
OkHttpClient client = builder.build();

  • ¿Qué tan seguro es esto? Si devuelve verdadero, ¿no se aceptan todos los certificados? Si es así, ¡debería evitarse!

    – solo_usuario

    13 de septiembre de 2016 a las 6:41

  • Estoy de acuerdo @just_user. Esto funciona, pero no es una buena solución. Derecha…?

    – Otziii

    9 de agosto de 2017 a las 11:14

  • @Otziii Sí, esto no es seguro. No sé por qué la gente sigue sugiriendo esta “solución”. Verifique mi respuesta anterior, esto proporciona una opción más segura.

    – solo_usuario

    21 de agosto de 2017 a las 8:07

  • Eso me ayudó mucho. ¡Gracias!

    – Javad Khan

    24 de agosto de 2017 a las 6:53

Verifique si el nombre CN en el certificado del cliente se agrega al nombre alternativo del Sujeto. yo tuve el mismo problema

avatar de usuario
qrtls

Durante la generación del certificado, el subjectAltName debe establecerse si el uri es una ip para no fallar en la validación.

“En algunos casos, el URI se especifica como una dirección IP en lugar de un nombre de host. En este caso, el IPAddress subjectAltName debe estar presente en el certificado y debe coincidir exactamente con la IP en el URI”. RFC (mencionado por Bas en el comentario)

En lugar de jugar con el lado del cliente HostnameVerifier o bien, reiusse el certificado autofirmado (sobre el cual tenemos control) a través de:

openssl req \
-newkey rsa:2048 \
-nodes \
-x509 \
-days 36500 -nodes \
-addext "subjectAltName = IP.1:1.2.3.4" \
-keyout /etc/ssl/private/nginx-selfsigned2.key \
-out /etc/ssl/certs/nginx-selfsigned2.crt

Complemento, si en Android uno también necesita confiar en el certificado:

the crt is pem format and can be imported into android via
<?xml version="1.0" encoding="utf-8"?>
    <base-config cleartextTrafficPermitted="false">
        <trust-anchors>
            <certificates src="@raw/nginx_selfsigned2" />
            <certificates src="system" />
        </trust-anchors>
    </base-config>
</network-security-config>

Por lo tanto, verificamos que el certificado proviene de una fuente confiable y, previamente, mediante la verificación del nombre de host (a través de SAN), nos aseguramos de que el servidor con el que hablamos presente el certificado correcto para su IP.

más aquí: https://developer.android.com/training/articles/security-config
https://developer.android.com/training/articles/security-ssl.html#SelfSigned

avatar de usuario
Pooya Chavoshi

si eres usado network_security_config en res/xml para resolver Cleartext HTTP traffic not permitted y cambiaste de dominio/dirección IP recuerda que debes cambiar includeSubdomains valor a la nueva dirección. Esto funcionó para mí.

  <network-security-config>
    <domain-config cleartextTrafficPermitted="true">
      <domain includeSubdomains="true">yourDomain.com</domain>
    </domain-config>
  </network-security-config>

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad