Итак, я понял, почему возникает ошибка и как ее эффективно и правильно исправить, вместо того, чтобы просто переопределять мое соединение и игнорировать все сертификаты, как это предлагается везде и всеми.
Оказывается, флаг android:networkSecurityConfig
элемента application
в AndroidManifest.xml
работает только на API >= 24. Поскольку мой телефон Android 6 работал на уровне 23, он там не работал, а trust anchors
не загружались.
java.security.cert.CertificateException:
java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
Чтобы решить мою проблему, я вручную загрузил сертификаты из файлов в необработанном ресурсе (я также присвоил имя, чтобы сделать его более удобным для пользователя. Вот почему я использую здесь карту, технически Список или Массив были бы достаточно хороши)
private Map<String, Certificate> createCertificates() throws CertificateException {
CertificateFactory factory = CertificateFactory.getInstance("X.509");
InputStream inputProxy = getResources().openRawResource(R.raw.proxy);
InputStream inputCa = getResources().openRawResource(R.raw.ca);
Certificate certProxy = factory.generateCertificate(inputProxy);
Certificate certCa = factory.generateCertificate(inputCa);
try {
inputProxy.close();
} catch (IOException ignore) {
// will be dumped anyways
}
try {
inputCa.close();
} catch (IOException ignore) {
// will be dumped anyways
}
Map<String, Certificate> certificates = new HashMap<>();
certificates.put("CA", certCa);
certificates.put("PROXY", certProxy);
return certificates;
}
Затем, прежде чем запускать какие-либо сетевые операции, я проверил, соответствует ли уровень API ‹ 24. Если это так, я создал свои сертификаты и предложил пользователю установить их (данные для KeyChain.EXTRA_NAME
не нужны, но более удобны для пользователя)
if (Build.VERSION.SDK_INT < 24) {
try {
Map<String, Certificate> certificates = createCertificates();
for (String key : certificates.keySet()) {
Certificate cert = certificates.get(key);
if (!isCertificateInstalled(cert.getPublicKey())) {
Intent installIntent = KeyChain.createInstallIntent();
installIntent.putExtra(KeyChain.EXTRA_CERTIFICATE, cert.getEncoded());
installIntent.putExtra(KeyChain.EXTRA_NAME, key);
startActivity(installIntent);
}
}
} catch (CertificateException ignore) {
// Netzwerkdialog wird später angezeigt
}
}
Но я запрашиваю пользователя только в том случае, если сертификат еще не установлен. Я проверяю это с помощью PublicKey
сертификата (теоретически не на 100% безопасно, но вероятность того, что кто-то установит два сертификата с одним и тем же открытым ключом, очень мала)
private boolean isCertificateInstalled(PublicKey pPublicKey) {
try {
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init((KeyStore) null);
X509TrustManager xtm = (X509TrustManager) tmf.getTrustManagers()[0];
for (X509Certificate cert : xtm.getAcceptedIssuers()) {
if (cert.getPublicKey().equals(pPublicKey)) {
return true;
}
}
} catch (NoSuchAlgorithmException | KeyStoreException ignore) {
// returns false
}
return false;
}
person
Basti
schedule
29.03.2018