¿Cómo puedo hacer que Android Volley realice la solicitud HTTPS, utilizando un certificado autofirmado por una CA desconocida?

Antes de hacer la pregunta, encontré algunos enlaces, que revisé, uno por uno, y ninguno de ellos me da una solución:

CA bien conocida Solicitud HTTPS con voleaaceptar todos los certificados SSL Excepción sin certificado de igual: Volley y Android con certificado autofirmadoNode.js (Socket.io) El certificado CA autofirmado Socket.io + SSL + da error al conectarseCertificado autofirmado "MANUALMENTE" importado: Solicitud HTTP SSL de Android con certificado autofirmado y CA

El único enlace que he encontrado hasta ahora, es este, que ofrece dos enfoques:Hacer una solicitud HTTPS con Android Volley

1º Indica que se importen algunas clases a su aplicación, cuando de hecho, hay otras clases que deben importarse, y las clases están usando libs en desuso de "apache.org"2º Un ejemplo para NUKE todos los certificados SSL (muy mala idea ...)


También encontré este blog, que tiene muchas explicaciones, pero al final, me di cuenta de que los ejemplos están usando bibliotecas obsoletas de "apache.org" y, además, el blog en sí no tiene contenido para Android Volley.https://nelenkov.blogspot.mx/2011/12/using-custom-certificate-trust-store-on.html


También existe este enlace de Android y el código de la sección "Autoridad de certificación desconocida", que da una buena idea sobre la solución, pero el código en sí mismo carece de algo en su estructura (Android Studio se queja ...):https://developer.android.com/training/articles/security-ssl.html

Pero esta cita del enlace parece ser el concepto central para resolver el problema.

"Un TrustManager es lo que el sistema usa para validar certificados del servidor y, al crear uno desde un KeyStore con una o más CA, esas serán las únicas CA confiables por ese TrustManager. Dado el nuevo TrustManager, el ejemplo inicializa un nuevo SSLContext que proporciona un SSLSocketFactory que puede usar para anular el SSLSocketFactory predeterminado de HttpsURLConnection. De esta manera, la conexión utilizará sus CA para la validación del certificado ".



Y ahora, aquí está mi problema: Tengo un servidor web que utiliza un certificado autofirmado y he creado un "almacén de confianza BKS" basado en su certificado. Importé el almacén de confianza de BKS a mi aplicación de Android y ahora tengo el siguiente código en mi aplicación (solo estoy publicando aquí MainActivity, que es la única clase que tiene relevancia para este tema hasta ahora, supongo):

package com.domain.myapp;


import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;

import android.widget.EditText;
import android.widget.Toast;

import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.HurlStack;
import com.android.volley.toolbox.StringRequest;
import com.android.volley.toolbox.Volley;

import java.io.InputStream;
import java.security.KeyStore;
import java.util.HashMap;
import java.util.Map;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManagerFactory;


public class LoginScreen extends AppCompatActivity {

Context ctx          = null;
InputStream inStream = null;
HurlStack hurlStack  = null;

EditText username    = null;
EditText password    = null;
String loginStatus   = null;

public LoginScreen() {

    try {
        TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        KeyStore ks = KeyStore.getInstance("BKS");
        inStream = ctx.getApplicationContext().getResources().openRawResource(R.raw.mytruststore);
        ks.load(inStream, null);
        inStream.close();
        tmf.init(ks);
        SSLContext sslContext = SSLContext.getInstance("TLS");
        sslContext.init(null, tmf.getTrustManagers(), null);
        final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
        hurlStack = new HurlStack(null, sslSocketFactory);
    } catch (Exception e){
        Log.d("Exception:",e.toString());
    }
}

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_login_screen);
    username = (EditText) findViewById(R.id.user);
    password = (EditText) findViewById(R.id.passwd);
}

public void login(View view) {

    RequestQueue queue = Volley.newRequestQueue(this, hurlStack);
    final String url = "https://myserver.domain.com/app/login";

    StringRequest postRequest = new StringRequest(Request.Method.POST, url,
            new Response.Listener<String>()
            {
                @Override
                public void onResponse(String response) {
                    Log.d("Response", response);
                    loginStatus = "OK";
                }
            },
            new Response.ErrorListener()
            {
                @Override
                public void onErrorResponse(VolleyError error) {
                    Log.d("Error.Response", String.valueOf(error));
                    loginStatus = "NOK";
                }
            }
    ) {
        @Override
        protected Map<String, String> getParams()
        {
            Map<String, String>  params = new HashMap<String, String>();
            params.put("username", String.valueOf(user));
            params.put("domain", String.valueOf(passwd));

            return params;
        }
    };
    queue.add(postRequest);

    if (loginStatus == "OK") {
        Intent intent = new Intent(LoginScreen.this, OptionScreen.class);
        startActivity(intent);
    } else {
        Toast.makeText(getApplicationContext(), "Login failed",Toast.LENGTH_SHORT).show();
    }
}

}

Con respecto a la clase de constructor, me tomé la libertad de copiar el código, poniendo algunos comentarios sobre lo que entiendo de cada parte de él:

try {
// I have a TrustManagerFactory object
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
// I have a KeyStore considering BKS (BOUNCY CASTLE) KeyStore object
KeyStore ks = KeyStore.getInstance("BKS");
// I have configured a inputStream using my TrustStore file as a Raw Resource
inStream = ctx.getApplicationContext().getResources().openRawResource(R.raw.mytruststore);
// I have loaded my Raw Resource into the KeyStore object
ks.load(inStream, null);
inStream.close();
// I have initialiazed my Trust Manager Factory, using my Key Store Object
tmf.init(ks);
// I have created a new SSL Context object
SSLContext sslContext = SSLContext.getInstance("TLS");
// I have initialized my new SSL Context, with the configured Trust Managers found on my Trust Store
sslContext.init(null, tmf.getTrustManagers(), null);
// I have configured a HttpClientStack, using my brand new Socket Context
final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
hurlStack = new HurlStack(null, sslSocketFactory);
} catch (Exception e){
Log.d("Exception:",e.toString());
}

Después de esto, en otro Método de clase, tengo el RequestQueue, usando el HttpClientStack que he configurado en el Class COnstructor:

RequestQueue queue = Volley.newRequestQueue(this, hurlStack);
final String url = "https://myserver.domain.com/app/login";
StringRequest postRequest = new StringRequest(Request.Method.POST, url,new Response.Listener<String>()
    {
    ...
    ...
    }

Cuando ejecuto mi aplicación, proporcionando el usuario y la contraseña que espera mi servidor web, puedo ver en el monitor de Android de Android Studio los siguientes mensajes:

09-17 21: 57: 13.842 20617-20617 / com.domain.myapp D / Error.Response: com.android.volley.NoConnectionError: javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: ancla de confianza para ruta de certificación no encontrada.

Después de toda esta explicación, tengo la siguiente pregunta:

¿Qué más se debe configurar para que Android acepte el certificado SSL de la CA del TrustManager personalizado que he configurado en el constructor de la clase?

Perdóname, pero soy principiante en la programación de Android, así como en Java, así que tal vez estoy cometiendo un terrible error ...

Cualquier ayuda, sería muy apreciada.

ACTUALIZAR

He mejorado el constructor de la clase, agrupando mejor las declaraciones y también usando elKeyManagerFactory, que parece ser bastante importante en este proceso. Aquí va:

public class LoginScreen extends AppCompatActivity {

...
...

  public LoginScreen() {

    try {
        inStream = this.getApplicationContext().getResources().openRawResource(R.raw.mytruststore);

        KeyStore ks = KeyStore.getInstance("BKS");
        ks.load(inStream, "bks*password".toCharArray());
        inStream.close();

        KeyManagerFactory kmf = KeyManagerFactory.getInstance("X509");
        kmf.init(ks, "bks*password".toCharArray());

        TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509");
        tmf.init(ks);

        SSLContext sslContext = SSLContext.getInstance("TLS");
        sslContext.init(kmf.getKeyManagers(),tmf.getTrustManagers(), null);
        SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
        hurlStack = new HurlStack(null, sslSocketFactory);
    } catch (Exception e){
        Log.d("Exception:",e.toString());
    }

  }

...
...

}

De todos modos, sigo teniendo problemas ...

Respuesta: com.android.volley.NoConnectionError: javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: No se encontró el ancla de confianza para la ruta de certificación.

Respuestas a la pregunta(4)

Su respuesta a la pregunta