Okhttp3 - 接受所有证书并使用 certificatePinner
Okhttp3 - Accept all certificates and use a certificatePinner
我正在尝试固定服务器的自签名证书。
我的 OkHttpClient 有两个参数,第一个是 ssl Socket Factory :
final TrustManager[] trustAllCerts = new TrustManager[] {
new X509TrustManager() {
@SuppressLint("TrustAllX509TrustManager")
@Override
public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException {}
@SuppressLint("TrustAllX509TrustManager")
@Override
public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException {}
@Override
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
}
};
// Install the all-trusting trust manager
SSLContext sslContext;
try {
sslContext = SSLContext.getInstance("SSL");
sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
} catch (NoSuchAlgorithmException | KeyManagementException e) {
e.printStackTrace();
FirebaseCrash.report(e);
return null;
}
// Create an ssl socket factory with our all-trusting manager
final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
第二个是证书pinner:
new CertificatePinner.Builder()
.add("bogus.com", "sha1/BOGUS")
.build()
注意:如果我不添加 certificatePinner,那么一切正常。问题是当请求被执行时,CertificatePinner.check() 被调用:
if (pins.isEmpty()) return;
显然,如果我确实设置了一个(非空的)certificatePinner,该方法不会就此停止并将继续。然后继续检查 "at least one of the certificates pinned for my hostname is a trusted certificate".
问题是我在 getAcceptedIssuers 中为我的 TrustManager 传递了一个空数组 - 这意味着自签名证书将触发异常,因为它在 "getAcceptedIssues" 中没有被明确信任。似乎不可能在 "getAcceptedIssuers".
中固定不受信任的证书
有什么办法可以解决这个问题吗?是设计使然吗?
这就是我构建 OkHttpClient 的方式:
OkHttpClient client = new OkHttpClient.Builder()
.sslSocketFactory(sslSocketFactory, (X509TrustManager) trustAllCerts[0])
.certificatePinner(certPinner)
.readTimeout(10, TimeUnit.SECONDS)
.connectTimeout(10, TimeUnit.SECONDS)
.build();
TrustManager、CertificatePinner 和 Hostname 验证都做不同但重要的事情。如果您想使用 self-signed 证书但仍具有安全性,而不是纯粹为了便于本地开发而使用 self-signed 证书,那么您可能想要创建一个有效的 TrustManager。
public static X509TrustManager load(List<File> serverCerts)
throws NoSuchAlgorithmException, KeyStoreException, IOException, CertificateException {
return trustManagerForKeyStore(keyStoreForCerts(serverCerts));
}
public static X509TrustManager trustManagerForKeyStore(KeyStore ks)
throws NoSuchAlgorithmException, KeyStoreException {
TrustManagerFactory tmf =
TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(ks);
return (X509TrustManager) tmf.getTrustManagers()[0];
}
public static KeyStore keyStoreForCerts(List<File> serverCerts)
throws CertificateException, KeyStoreException, IOException, NoSuchAlgorithmException {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
ks.load(null);
for (int i = 0; i < serverCerts.size(); i++) {
try (InputStream is = new FileInputStream(serverCerts.get(i))) {
X509Certificate caCert = (X509Certificate) cf.generateCertificate(is);
ks.setCertificateEntry("cacrt." + i, caCert);
}
}
return ks;
}
这将从加载系统证书开始,因此您的客户端仍可用于加载外部托管的图像等。
除此之外,您还可以使用 CertificatePinner 来要求您的域只使用您信任的 self-signed 证书。
private static OkHttpClient 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 new java.security.cert.X509Certificate[]{};
}
}
};
// 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;
}
});
OkHttpClient okHttpClient = builder
.connectTimeout(15, TimeUnit.SECONDS)
.writeTimeout(15, TimeUnit.SECONDS)
.readTimeout(15, TimeUnit.SECONDS)
.build();
return okHttpClient;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
我正在尝试固定服务器的自签名证书。 我的 OkHttpClient 有两个参数,第一个是 ssl Socket Factory :
final TrustManager[] trustAllCerts = new TrustManager[] {
new X509TrustManager() {
@SuppressLint("TrustAllX509TrustManager")
@Override
public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException {}
@SuppressLint("TrustAllX509TrustManager")
@Override
public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException {}
@Override
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
}
};
// Install the all-trusting trust manager
SSLContext sslContext;
try {
sslContext = SSLContext.getInstance("SSL");
sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
} catch (NoSuchAlgorithmException | KeyManagementException e) {
e.printStackTrace();
FirebaseCrash.report(e);
return null;
}
// Create an ssl socket factory with our all-trusting manager
final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
第二个是证书pinner:
new CertificatePinner.Builder()
.add("bogus.com", "sha1/BOGUS")
.build()
注意:如果我不添加 certificatePinner,那么一切正常。问题是当请求被执行时,CertificatePinner.check() 被调用:
if (pins.isEmpty()) return;
显然,如果我确实设置了一个(非空的)certificatePinner,该方法不会就此停止并将继续。然后继续检查 "at least one of the certificates pinned for my hostname is a trusted certificate".
问题是我在 getAcceptedIssuers 中为我的 TrustManager 传递了一个空数组 - 这意味着自签名证书将触发异常,因为它在 "getAcceptedIssues" 中没有被明确信任。似乎不可能在 "getAcceptedIssuers".
中固定不受信任的证书有什么办法可以解决这个问题吗?是设计使然吗?
这就是我构建 OkHttpClient 的方式:
OkHttpClient client = new OkHttpClient.Builder()
.sslSocketFactory(sslSocketFactory, (X509TrustManager) trustAllCerts[0])
.certificatePinner(certPinner)
.readTimeout(10, TimeUnit.SECONDS)
.connectTimeout(10, TimeUnit.SECONDS)
.build();
TrustManager、CertificatePinner 和 Hostname 验证都做不同但重要的事情。如果您想使用 self-signed 证书但仍具有安全性,而不是纯粹为了便于本地开发而使用 self-signed 证书,那么您可能想要创建一个有效的 TrustManager。
public static X509TrustManager load(List<File> serverCerts)
throws NoSuchAlgorithmException, KeyStoreException, IOException, CertificateException {
return trustManagerForKeyStore(keyStoreForCerts(serverCerts));
}
public static X509TrustManager trustManagerForKeyStore(KeyStore ks)
throws NoSuchAlgorithmException, KeyStoreException {
TrustManagerFactory tmf =
TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(ks);
return (X509TrustManager) tmf.getTrustManagers()[0];
}
public static KeyStore keyStoreForCerts(List<File> serverCerts)
throws CertificateException, KeyStoreException, IOException, NoSuchAlgorithmException {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
ks.load(null);
for (int i = 0; i < serverCerts.size(); i++) {
try (InputStream is = new FileInputStream(serverCerts.get(i))) {
X509Certificate caCert = (X509Certificate) cf.generateCertificate(is);
ks.setCertificateEntry("cacrt." + i, caCert);
}
}
return ks;
}
这将从加载系统证书开始,因此您的客户端仍可用于加载外部托管的图像等。
除此之外,您还可以使用 CertificatePinner 来要求您的域只使用您信任的 self-signed 证书。
private static OkHttpClient 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 new java.security.cert.X509Certificate[]{};
}
}
};
// 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;
}
});
OkHttpClient okHttpClient = builder
.connectTimeout(15, TimeUnit.SECONDS)
.writeTimeout(15, TimeUnit.SECONDS)
.readTimeout(15, TimeUnit.SECONDS)
.build();
return okHttpClient;
} catch (Exception e) {
throw new RuntimeException(e);
}
}