我正在开发一个Android
应用程序,该应用程序在API
上调用了一些https
。使用retrofit2
和okhttp3
。在开发期间,我使用在服务器中生成的自签名证书。我在使用自签名证书时调用API
时遇到很多问题,我解决了所有问题,但遇到了SSLPeerUnverifiedException
异常。
当前,我已将证书ServerCertificate.cer
复制到Download
目录以便加载它并将其添加到允许的KeyStore
中。
我尝试了许多来自不同网站的解决方案。我尝试okhttp
CustomTrust并从Android developer网站上尝试
我根据Android开发人员示例编写以下代码:
X509TrustManager mTrustManager = null;
private Retrofit getRetrofit(String identity, String serverBaseUrl) {
Retrofit retrofit = null;
try {
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.sslSocketFactory(getSSLConfig().getSocketFactory(), mTrustManager)
.addInterceptor(new CustomInterceptor(identity))
.addInterceptor((new okhttp3.logging.HttpLoggingInterceptor())
.setLevel(okhttp3.logging.HttpLoggingInterceptor.Level.BODY))
.connectTimeout(60, TimeUnit.SECONDS)
.readTimeout(60, TimeUnit.SECONDS)
.writeTimeout(60, TimeUnit.SECONDS)
.build();
retrofit = new Retrofit.Builder()
.baseUrl(serverBaseUrl)
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
.build();
} catch (Exception ex) {
}
return retrofit;
}
private SSLContext getSSLConfig() throws Exception {
FileHelper fileHelper = FileHelper.getInstance();
String cerFilePath = "/storage/emulated/0/Download/ServerCertificate.cer";
CertificateFactory cf = CertificateFactory.getInstance("X.509");
InputStream caInput = new FileInputStream(cerFilePath);
Certificate ca;
try {
ca = cf.generateCertificate(caInput);
//Below line print: ca=CN=SS_CEM_5_4
System.out.println("ca=" + ((X509Certificate) ca).getSubjectDN());
} finally {
caInput.close();
}
String keyStoreType = KeyStore.getDefaultType();
KeyStore keyStore = KeyStore.getInstance(keyStoreType);
keyStore.load(null, null);
keyStore.setCertificateEntry("ca", ca);
String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
tmf.init(keyStore);
SSLContext context = SSLContext.getInstance("TLS");
context.init(null, tmf.getTrustManagers(), null);
mTrustManager = (X509TrustManager) tmf.getTrustManagers()[0];
return context;
}
当前,当我呼叫任何API
时,我得到以下异常:
Exception occurred while calling heartbeat
javax.net.ssl.SSLPeerUnverifiedException: Hostname ss_cem_5_4 not verified:
certificate: sha256/OUxkHCacC0q0+ZQpL/3V1jFgV57CXweub/lSSUXsAZw=
DN: CN=\00S\00S\00_\00C\00E\00M\00_\005\00_\004
subjectAltNames: []
at okhttp3.internal.connection.RealConnection.connectTls(RealConnection.java:330)
at okhttp3.internal.connection.RealConnection.establishProtocol(RealConnection.java:283)
at okhttp3.internal.connection.RealConnection.connect(RealConnection.java:168)
at okhttp3.internal.connection.StreamAllocation.findConnection(StreamAllocation.java:257)
at okhttp3.internal.connection.StreamAllocation.findHealthyConnection(StreamAllocation.java:135)
at okhttp3.internal.connection.StreamAllocation.newStream(StreamAllocation.java:114)
at okhttp3.internal.connection.ConnectInterceptor.intercept(ConnectInterceptor.java:42)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:147)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:121)
at okhttp3.internal.cache.CacheInterceptor.intercept(CacheInterceptor.java:93)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:147)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:121)
at okhttp3.internal.http.BridgeInterceptor.intercept(BridgeInterceptor.java:93)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:147)
at okhttp3.internal.http.RetryAndFollowUpInterceptor.intercept(RetryAndFollowUpInterceptor.java:126)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:147)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:121)
at co.sedco.sevicesbase.managementproxy.webproxy.CustomInterceptor.intercept(CustomInterceptor.java:39)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:147)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:121)
at okhttp3.RealCall.getResponseWithInterceptorChain(RealCall.java:254)
at okhttp3.RealCall.execute(RealCall.java:92)
at retrofit2.OkHttpCall.execute(OkHttpCall.java:186)
at retrofit2.ExecutorCallAdapterFactory$ExecutorCallbackCall.execute(ExecutorCallAdapterFactory.java:92)
at co.sedco.sevicesbase.managementproxy.webproxy.ManagementWebProxy.callHeartbeat(ManagementWebProxy.java:271)
at co.sedco.sevicesbase.heartbeat.HeartbeatManager$CallHeartbeatTimerTask.run(HeartbeatManager.java:91)
at java.util.TimerThread.mainLoop(Timer.java:555)
at java.util.TimerThread.run(Timer.java:505)
我只能通过向HostnameVerifier
添加OkHttpClient
并覆盖Verify
函数以始终返回true
来使自签名证书起作用,但是这种解决方案是不可接受的,我相信我会遇到必须在客户服务器中使用自签名证书的情况(尽管不建议这样做)。
我使用网址https://ss_cem_5_4/Portal/api/GetHeartbeat
我还应该提到我无法通过服务器名称调用服务器,因此我修改了路径“ / system / etc /”中的hosts
文件来为我的服务器添加映射。 (我正在使用已植根的设备)
答案 0 :(得分:0)
许多小时后,终于找到了解决问题的办法。如果有人遇到同样的问题,我将在这里写下我的解决方案。
首先Self-Signed certificate
中有一个问题,因为Patrick Mevzek在评论subjectAltNames
中是空的,并且在检查了OkHostnameVerifier
如何验证您呼叫的服务器是否受信任之后,它检查subjectAltNames
中的服务器名称是否与Url
中的服务器名称匹配。{p}
以前,我是使用subjectAltNames
生成我的自签名证书的,它似乎只填充证书的公用名IIS
并保留CN
为空。我从以下stackoverflow answer中获得了有关此问题的提示。为了解决此问题,我通过在服务器subjectAltNames
上运行以下命令来生成新许可证。
PowerShell
请确保以管理员身份运行New-SelfSignedCertificate -DnsName "ss_cem_5_4" -CertStoreLocation "cert:\LocalMachine\My"
,有关PowerShell
命令参数的更多信息,请选中此Microsoft website。
我的第二个问题(或要求)是信任我的自签名证书,我尝试了一些解决方案,但所有解决方案都导致仅信任我的证书,我无法调用具有{{1 }}默认情况下,因此我寻求扩展New-SelfSignedCertificate
受信任证书的解决方案,并借助此堆栈溢出answer设法做到这一点,下面是我的完整代码。
下面的类用于扩展Android
受信任的证书:
Android
下面的代码是创建我的Android
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
public class AdditionalKeyStoresSSLSocketFactory extends SSLSocketFactory {
protected SSLContext sslContext = SSLContext.getInstance("TLS");
public TrustManager[] TrustManager;
public AdditionalKeyStoresSSLSocketFactory(KeyStore keyStore) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
//super(null, null, null, null, null, null);
super();
TrustManager = new TrustManager[]{new AdditionalKeyStoresTrustManager(keyStore)};
sslContext.init(null, TrustManager, null);
}
@Override
public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException {
return sslContext.getSocketFactory().createSocket(socket, host, port, autoClose);
}
@Override
public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException {
return sslContext.getSocketFactory().createSocket(host, port, localHost, localPort);
}
@Override
public Socket createSocket(String host, int port) throws IOException {
return sslContext.getSocketFactory().createSocket(host, port);
}
@Override
public Socket createSocket(InetAddress host, int port) throws IOException {
return sslContext.getSocketFactory().createSocket(host, port);
}
@Override
public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
return sslContext.getSocketFactory().createSocket(address, port, localAddress, localPort);
}
@Override
public Socket createSocket() throws IOException {
return sslContext.getSocketFactory().createSocket();
}
@Override
public String[] getDefaultCipherSuites() {
return sslContext.getSocketFactory().getDefaultCipherSuites();
}
@Override
public String[] getSupportedCipherSuites() {
return sslContext.getSocketFactory().getSupportedCipherSuites();
}
/**
* Based on http://download.oracle.com/javase/1.5.0/docs/guide/security/jsse/JSSERefGuide.html#X509TrustManager
*/
public static class AdditionalKeyStoresTrustManager implements X509TrustManager {
protected ArrayList<X509TrustManager> x509TrustManagers = new ArrayList<X509TrustManager>();
protected AdditionalKeyStoresTrustManager(KeyStore... additionalkeyStores) {
final ArrayList<TrustManagerFactory> factories = new ArrayList<TrustManagerFactory>();
try {
// The default Trustmanager with default keystore
final TrustManagerFactory original = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
original.init((KeyStore) null);
factories.add(original);
for (KeyStore keyStore : additionalkeyStores) {
final TrustManagerFactory additionalCerts = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
additionalCerts.init(keyStore);
factories.add(additionalCerts);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
/*
* Iterate over the returned trustmanagers, and hold on
* to any that are X509TrustManagers
*/
for (TrustManagerFactory tmf : factories)
for (TrustManager tm : tmf.getTrustManagers())
if (tm instanceof X509TrustManager)
x509TrustManagers.add((X509TrustManager) tm);
if (x509TrustManagers.size() == 0)
throw new RuntimeException("Couldn't find any X509TrustManagers");
}
/*
* Delegate to the default trust manager.
*/
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
final X509TrustManager defaultX509TrustManager = x509TrustManagers.get(0);
defaultX509TrustManager.checkClientTrusted(chain, authType);
}
/*
* Loop over the trustmanagers until we find one that accepts our server
*/
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
for (X509TrustManager tm : x509TrustManagers) {
try {
tm.checkServerTrusted(chain, authType);
return;
} catch (CertificateException e) {
// ignore
}
}
throw new CertificateException();
}
public X509Certificate[] getAcceptedIssuers() {
final ArrayList<X509Certificate> list = new ArrayList<X509Certificate>();
for (X509TrustManager tm : x509TrustManagers)
list.addAll(Arrays.asList(tm.getAcceptedIssuers()));
return list.toArray(new X509Certificate[list.size()]);
}
}
}
并使用以下代码添加我的自定义AdditionalKeyStoresSSLSocketFactory
:
private Certificate getCertificate(String cerFilePath) throws Exception {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
InputStream caInput = new FileInputStream(cerFilePath);
Certificate ca;
try {
ca = cf.generateCertificate(caInput);
System.out.println("ca=" + ((X509Certificate) ca).getSubjectDN());
} finally {
caInput.close();
}
return ca;
}
private SSLSocketFactory getSSLConfig() throws Exception {
FileHelper fileHelper = FileHelper.getInstance();
String downloadPath = fileHelper.getDeviceDownloadPath() + File.separator;
String[] cerFilePath = new String[]{downloadPath + "ServerCertificate.cer", downloadPath + "ServerCertificate2.cer", downloadPath + "ServerCertificate3.cer", downloadPath + "ServerCertificate4.cer"};
String keyStoreType = KeyStore.getDefaultType();
KeyStore keyStore = KeyStore.getInstance(keyStoreType);
keyStore.load(null, null);
for (int i = 0; i < cerFilePath.length; i++) {
Certificate ca = getCertificate(cerFilePath[i]);
keyStore.setCertificateEntry("ca" + i, ca);
}
// Create a TrustManager that trusts the CAs in our KeyStore
String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
tmf.init(keyStore);
mTrustManager = (X509TrustManager) tmf.getTrustManagers()[0];
AdditionalKeyStoresSSLSocketFactory factory = new AdditionalKeyStoresSSLSocketFactory(keyStore);
return factory;
}
免责声明:我将证书复制到下载目录中,我认为这不是一个好主意,因为它存在很多安全风险,我的代码仅用于演示