我在使用 Volley 时遇到了同样的问题。没有 HTTPS 连接适用于 Android Marshmallow 及更低版本。对于 Nouget 及更高版本,一切都很好,因为我将以下配置 android:networkSecurityConfig="@xml/network_security_config" 与我的所有域特定证书一起使用。
根据the Android documentation:
默认情况下,来自所有应用的安全连接(使用 TLS 和 HTTPS 等协议)信任预安装的系统 CA,而面向 Android 6.0(API 级别 23)及更低版本的应用也默认信任用户添加的 CA 商店。应用可以使用 base-config(用于应用范围的自定义)或 domain-config(用于每个域的自定义)自定义其自己的连接。
所以,在 Marshmallow 下,事情的运作方式有所不同是有道理的。正如@Bastu 在他的回答中所说:
原来 AndroidManifest.xml 中应用程序元素的标志 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 连接添加我的证书。我确信这段代码可以改进。