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

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

  • Well-kown CA HTTPS запрос с использованием залпа
  • Принимать все SSL-сертификаты Отсутствие сертификатов peer Exception – Volley и Android с самоподписанным сертификатом
  • Node.js (Socket.io) Socket.io + SSL + самоподписанный сертификат CA дает ошибку при подключении
  • Самоподписанный сертификат «MANUALLY» импортирован: Андроид HTTP HTTP-запрос с использованием самозаверяющего сертификата и CA

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

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

Я также нашел этот блог, в котором много объяснений, но в конце я понял, что примеры используют устаревшие библиотеки с «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» на основе его сертификата. Я импортировал доверительный магазин 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: Trust anchor for Путь сертификации не найден.

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

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

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

Любая помощь приветствуется.

ОБНОВИТЬ

Я улучшил конструктор класса, сделав лучшую группировку операторов, а также используя 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: привязка доверия для пути сертификации не найдена.

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

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

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

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

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

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

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

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

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

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

С помощью 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); } } 

Я реализовал https, создав новый requestQueue в своем классе 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); } }