Подтвердить что ты не робот

Как заставить Android Volley выполнять HTTPS-запрос, используя сертификат, подписанный Unkown CA?

Прежде чем задать вопрос, я нашел несколько ссылок, которые я проверил один за другим, и ни один из них не дает мне решения:

Единственная ссылка, которую я нашел до сих пор, - это та, которая дает два подхода: Выполнение запроса HTTPS с использованием Android Volley

  • 1º Указывает на импорт некоторых классов в ваше приложение, когда действительно есть другие классы, которые необходимо импортировать, а классы используют устаревшие библиотеки с "apache.org"
  • 2º Пример для NUKE всех SSL-сертификатов (довольно плохая идея...)


Я также нашел этот блог, в котором есть много объяснений, но в конце я понял, что примеры используют устаревшие библиотеки с "apache.org", а также сам блог не имеет контента для Android Volley. https://nelenkov.blogspot.mx/2011/12/using-custom-certificate-trust-store-on.html


Существует также эта ссылка от Android и код раздела "Неизвестный центр сертификации", который дает хорошее представление об этом решении, но сам код не имеет чего-то в своей структуре (Android Studio жалуется...): https://developer.android.com/training/articles/security-ssl.html

Но эта цитата из ссылки кажется основной концепцией решения проблемы.

"TrustManager - это то, что система использует для проверки сертификатов с сервера и путем создания одного из KeyStore с одним или несколькими ЦС - это будут единственные CA, которым доверяет этот TrustManager. Учитывая новый TrustManager, в примере инициализируется новый SSLContext, который предоставляет SSLSocketFactory, который вы можете использовать для переопределения SSLSocketFactory по умолчанию из HttpsURLConnection. Таким образом, соединение будет использовать ваши CA для проверки сертификата.



И теперь, вот моя проблема: У меня есть веб-сервер, который использует самозаверяющий сертификат, и я создал "BKS truststore" на основе его сертификата. Я импортировал доверительный магазин BKS в свой Android-приложение и теперь у меня есть следующий код в моем приложении (я просто размещаю здесь MainActivity, который является единственным классом, который имеет отношение к этому вопросу до сих пор, я полагаю):

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

}

Что касается класса конструктора, я взял на себя смелость скопировать код, поместив некоторые комментарии о том, что я понимаю из каждой его части:

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

После этого в другом методе класса у меня есть RequestQueue, используя HttpClientStack, который я сконфигурировал в 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>()
    {
    ...
    ...
    }

Когда я запускаю свое приложение, предоставляя пользователю и паролю, ожидаемый моим WebServer, я могу видеть в Android Monitor от Android Studio следующие сообщения:

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: привязка доверия для пути сертификации не найдена.

После всего этого объяснения у меня возникает следующий вопрос:

  • Что еще нужно настроить, чтобы заставить Android принять SSL-сертификат из CA настраиваемого TrustManager, который я настроил в конструкторе класса?

Простите меня, но я начинаю программировать на Android, а также на Java, поэтому, возможно, я совершу ужасную ошибку...

Любая помощь, была бы очень признательна.

UPDATE

Я улучшил конструктор класса, сделав лучшую группировку операторов, а также используя KeyManagerFactory, который, кажется, очень важен для этого процесса. Здесь:

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

  }

...
...

}

Во всяком случае, у меня все еще возникают проблемы.

Ответ: com.android.volley.NoConnectionError: javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: привязка доверия для пути сертификации не найдена.

Опять же, любая помощь была бы очень признательна.

4b9b3361

Ответ 1

Я столкнулся с аналогичной проблемой в прошлом, и решение для того же самого, что работало, заключалось в установке промежуточного центра сертификации на стороне .

Интересно отметить, что посещение этого сервера в большинстве настольных браузеров не вызывает ошибки, как, например, полностью неизвестный CA или самозаверяющий сертификат сервера. Это связано с тем, что большинство кэшей настольных браузеров со временем доверяют промежуточным ЦС. После того, как браузер посетил и узнал о промежуточном ЦС с одного сайта, в следующий раз ему не потребуется иметь промежуточное СА, включенное в цепочку сертификатов.

Некоторые сайты делают это намеренно для вторичных веб-серверов, используемых для обслуживания ресурсов. Например, они могут иметь основную HTML-страницу, обслуживаемую сервером с полной цепочкой сертификатов, но серверы для таких ресурсов, как изображения, CSS или JavaScript, не включают CA, по-видимому, для экономии полосы пропускания. К сожалению, иногда эти серверы могут предоставлять веб-службу, которую вы пытаетесь вызвать из своего приложения для Android, что не так прощает.

Настройте сервер для включения промежуточного ЦС в цепочку серверов. Большинство центров сертификации предоставляют документацию о том, как это сделать для всех распространенных веб-серверов. Это единственный подход, если вам нужен сайт для работы с браузерами Android по умолчанию, по крайней мере, через Android 4.2.

Вы можете выполнить шаги, указанные здесь Отсутствует промежуточный центр сертификации

Другой пример Что такое промежуточный сертификат?

FYI trust-anchor-not-found-for-android-ssl-connection

Браузеры могут принимать полномочия корневого центра сертификации, но Android SDK может не делать этого, потому что браузеры кэшируют то же самое. Браузеры будут кэшировать промежуточные сертификаты и использовать их между разными сайтами. Из-за этого, если вам не хватает промежуточного сертификата, случайные пользователи получат ошибку доверия, а другие - нет. Получают ли промежуточные сертификаты в кеше в Firefox?

Ответ 2

Я реализовал https, создав новый запросQueue в моем классе volley по следующему коду

public RequestQueue getRequestQueue() {
    if (mRequestQueue == null) {
        mRequestQueue = Volley.newRequestQueue(getApplicationContext(), new HurlStack(null, newSslSocketFactory()));

    }

    return mRequestQueue;
}

private SSLSocketFactory newSslSocketFactory() {
    try {
        // Get an instance of the Bouncy Castle KeyStore format
        KeyStore trusted = KeyStore.getInstance("BKS");
        // Get the raw resource, which contains the keystore with
        // your trusted certificates (root and any intermediate certs)
        InputStream in = getApplicationContext().getResources().openRawResource(R.raw.keystore);
        try {
            // Initialize the keystore with the provided trusted certificates
            // Provide the password of the keystore
            trusted.load(in, KEYSTORE_PASSWORD);
        } finally {
            in.close();
        }

        String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
        TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
        tmf.init(trusted);

        SSLContext context = SSLContext.getInstance("TLS");
        context.init(null, tmf.getTrustManagers(), null);

        SSLSocketFactory sf = context.getSocketFactory();
        return sf;
    } catch (Exception e) {
        throw new AssertionError(e);
    }
}

Ответ 3

С помощью OkHttp вы можете создать клиент http, который принимает все сертификаты:

public static OkHttpClient.Builder getUnsafeOkHttpClient() {
    try {
        // Create a trust manager that does not validate certificate chains
        final TrustManager[] trustAllCerts = new TrustManager[]{
                new X509TrustManager() {
                    @Override
                    public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException {
                    }

                    @Override
                    public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException {
                    }

                    @Override
                    public java.security.cert.X509Certificate[] getAcceptedIssuers() {
                        return null;
                    }
                }
        };

        // Install the all-trusting trust manager
        final SSLContext sslContext = SSLContext.getInstance("SSL");
        sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
        // Create an ssl socket factory with our all-trusting manager
        final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();

        OkHttpClient.Builder builder = new OkHttpClient.Builder();
        builder.sslSocketFactory(sslSocketFactory, (X509TrustManager)trustAllCerts[0]);
        builder.hostnameVerifier(new HostnameVerifier() {
            @Override
            public boolean verify(String hostname, SSLSession session) {
                return true;
            }
        });

        return builder;
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}

Ответ 4

Я просто поставил этот код перед запросом на залп. работал на меня.

private void trustEveryone() { 
    try { 
            HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier(){ 
                    public boolean verify(String hostname, SSLSession session) { 
                            return true; 
                    }}); 
            SSLContext context = SSLContext.getInstance("TLS"); 
            context.init(null, new X509TrustManager[]{new X509TrustManager(){ 
                    public void checkClientTrusted(X509Certificate[] chain, 
                                String authType) throws CertificateException {} 
                    public void checkServerTrusted(X509Certificate[] chain, 
                                String authType) throws CertificateException {} 
                    public X509Certificate[] getAcceptedIssuers() { 
                            return new X509Certificate[0]; 
                    }}}, new SecureRandom()); 
            HttpsURLConnection.setDefaultSSLSocketFactory( 
                        context.getSocketFactory()); 
    } catch (Exception e) { // should never happen 
            e.printStackTrace(); 
    } 
}