场合
我已经构建了一个小型Android应用程序(sdk 21+),它连接到服务器,获取一些数据并显示它。对于连接,我使用OkHttp
库。在Android 7+中运行一切正常
还应该提一下,我是网络新手,还没有最大的知识。
问题
在Android 6上运行(在我的情况下是api 23)我得到以下异常。
java.security.cert.CertificateException:
java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
在我的network_security_config.xml
我有3 certificates
注册为我的trust-anchors
我无法解决这个例外问题,在互联网上搜索时我也找不到任何有用的东西。
问题
有什么可以解决这个问题,我该如何解决?请尽量保持简单,以便我能理解。
答案 0 :(得分:0)
所以我想出了错误发生的原因以及如何有效和正确地修复错误,而不仅仅是覆盖我的连接,而忽略了所有证书如何在任何地方以及每个人的建议。
原来android:networkSecurityConfig
中application
元素的标记AndroidManifest.xml
仅适用于api> = 24.由于我的Android 6手机在23级运行,因此没有&# 39;在那里工作,trust anchors
没有加载。
java.security.cert.CertificateException:
java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
为了解决我的问题,我手动加载了原始资源中的文件的证书(我还分配了一个名称,使其更加用户友好。这就是为什么我在这里使用Map,技术上一个List或一个数组会很好足够)
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级别是否为&lt; 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;
}
答案 1 :(得分:0)
在与Volley合作时,我遇到了同样的问题。 Android Marshmallow及更低版本无法使用HTTPS连接。对于Nouget和更高版本,一切都很好,因为我将以下配置android:networkSecurityConfig="@xml/network_security_config"
与我所有的域特定证书一起使用。
默认情况下,默认情况下,所有应用程序的安全连接(使用TLS和HTTPS等协议)信任预安装的系统CA,而面向Android 6.0(API级别23)及更低版本的应用程序也信任用户添加的CA存储。应用程序可以使用base-config(用于应用程序范围的自定义)或domain-config(用于每个域的自定义)来自定义其自己的连接。
因此,在棉花糖下事情变得不同有意义。正如@Bastu在回答中所说:
找出AndroidManifest.xml中application元素的标志android:networkSecurityConfig仅适用于api> = 24
在找到该问题的答案之前,我偶然发现了this wonderful tutorial。稍微弄乱了代码,我结束了这段代码的合并,以便能够使用证书列表:
import android.content.Context;
import android.content.res.Resources;
import android.os.Build;
import android.util.Log;
import com.android.volley.RequestQueue;
import com.android.volley.toolbox.Volley;
import com.kitsord.R;
import java.io.IOException;
import java.io.InputStream;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
public class ExternalConfig {
private static final String TAG = "ExternalConfig";
private static RequestQueue queue;
public static RequestQueue getRequestQueue(final Context applicationContext) {
if (queue == null) {
queue = Volley.newRequestQueue(applicationContext);
if (Build.VERSION.SDK_INT < 24) {
useSSLCertificate(context.getResources(), R.raw.my_certificate1, R.raw.my_certificate2);
}
}
return queue;
}
private static void useSSLCertificate(final Resources resources, final int ... rawCertificateResourceIds) {
final CertificateFactory certificateFactory;
try {
certificateFactory = CertificateFactory.getInstance("X.509");
} catch (final CertificateException exception) {
Log.e(TAG, "Failed to get an instance of the CertificateFactory.", exception);
return;
}
int i = 0;
final Certificate[] certificates = new Certificate[rawCertificateResourceIds.length];
for (final int rawCertificateResourceId : rawCertificateResourceIds) {
final Certificate certificate;
try (final InputStream certificateInputStream = resources.openRawResource(rawCertificateResourceId)) {
certificate = certificateFactory.generateCertificate(certificateInputStream);
} catch (final IOException | CertificateException exception) {
Log.e(TAG, "Failed to retrieve the Certificate.", exception);
return;
}
certificates[i] = certificate;
i++;
}
final KeyStore keyStore;
try {
keyStore = buildKeyStore(certificates);
} catch (final KeyStoreException | CertificateException | NoSuchAlgorithmException | IOException exception) {
Log.e(TAG, "Failed to build the KeyStore with the Certificate.", exception);
return;
}
final TrustManagerFactory trustManagerFactory;
try {
trustManagerFactory = buildTrustManager(keyStore);
} catch (final KeyStoreException | NoSuchAlgorithmException exception) {
Log.e(TAG, "Failed to build the TrustManagerFactory with the KeyStore.", exception);
return;
}
final SSLContext sslContext;
try {
sslContext = buildSSLContext(trustManagerFactory);
} catch (final KeyManagementException | NoSuchAlgorithmException exception) {
Log.e(TAG, "Failed to build the SSLContext with the TrustManagerFactory.", exception);
return;
}
HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory());
}
private static KeyStore buildKeyStore(final Certificate[] certificates) throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException {
final String keyStoreType = KeyStore.getDefaultType();
final KeyStore keyStore = KeyStore.getInstance(keyStoreType);
keyStore.load(null, null);
int i = 0;
for (final Certificate certificate : certificates) {
keyStore.setCertificateEntry("ca" + i, certificate);
i++;
}
return keyStore;
}
private static TrustManagerFactory buildTrustManager(final KeyStore keyStore) throws KeyStoreException, NoSuchAlgorithmException {
final String trustManagerAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
final TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(trustManagerAlgorithm);
trustManagerFactory.init(keyStore);
return trustManagerFactory;
}
private static SSLContext buildSSLContext(final TrustManagerFactory trustManagerFactory) throws KeyManagementException, NoSuchAlgorithmException {
final TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
final SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, trustManagers, null);
return sslContext;
}
}
现在,每当我需要Volley队列时,此方法不仅会让我每次都使用相同的队列(不确定这是否是个坏主意),而且还会为https连接添加证书。我确信可以对这段代码进行改进,例如支持同时添加更多证书。
答案 2 :(得分:-1)
只需用下面的
替换你的OkHttpClient{{1}}