使用自签名证书签名的服务器上的调用API [英] Call API on server that is signed using Self-Signed Certificate
问题描述
我正在开发一个Android
应用程序,该应用程序在https
上调用了某些API
.使用retrofit2
和okhttp3
.在开发期间,我使用在服务器中生成的自签名证书.我在使用自签名证书时调用API
时遇到很多问题,我解决了所有问题,但都卡在了此异常SSLPeerUnverifiedException
上.
I'm working on an Android
application that call some API
s over https
. using retrofit2
and okhttp3
. During my development I use a self-signed certificate that I generate in server. I face a lot of problems in calling API
s as I'm using a self-signed certificate, I solve them all but stuck on this exception SSLPeerUnverifiedException
.
当前,我已将我的证书ServerCertificate.cer
复制到Download
目录,以便加载它并将其添加到允许的KeyStore
中.
我尝试了许多来自不同网站的解决方案.我尝试okhttp
CustomTrust 并从 Android开发人员网站
Currently I had copy my certificate ServerCertificate.cer
to Download
directory in order to load it and add it to allowed KeyStore
.
I try a lot of solutions from different websites. I try okhttp
CustomTrust and from Android developer website
我根据Android开发人员示例编写以下代码:
I write below code depending on Android developer example:
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
时,出现以下异常:
Currently when I call any API
I get following exception:
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)
我只能通过在OkHttpClient
中添加HostnameVerifier
并覆盖Verify
函数以始终返回true
来使自签名证书起作用,但是这种解决方案是不可接受的,我相信我会遇到这种情况我必须在客户服务器中使用自签名证书的位置(尽管不建议这样做).
I only managed to make self-signed certificate work by adding HostnameVerifier
to OkHttpClient
and override Verify
function to always return true
, but this solution is not acceptable, I believe that I will encounter a situations where I have to use a self-signed certificate in customer server (Although it is not recommended).
我正在使用网址呼叫服务器:https://ss_cem_5_4/Portal/api/GetHeartbeat
I'm calling Server using Url: https://ss_cem_5_4/Portal/api/GetHeartbeat
我还应该提到我无法通过服务器名称调用服务器,因此我修改了路径"/system/etc/"中的hosts
文件以为我的服务器添加映射. (我正在使用已植根的设备)
I also should mention that I was unable to call server through server name so I modified hosts
file in path '/system/etc/' to add mapping for my server. (I'm working on a rooted device)
推荐答案
许多小时后,终于找到了解决问题的方法.如果有人遇到同样的问题,我将在这里写下我的解决方案.
After many hours searching for a solution for the problem finally it has been solved. I will write here my solution in case anyone face same problem.
首先,在Self-Signed certificate
中存在问题,因为Patrick Mevzek在注释subjectAltNames
中提到的是空的,并且在检查OkHostnameVerifier
如何验证您所调用的服务器是否受信任之后,它会检查subjectAltNames
Url
中的服务器名称与证书subjectAltNames
中的任何名称匹配.
First there was a problem in Self-Signed certificate
, as Patrick Mevzek mention in the comments subjectAltNames
was empty, and after checking how OkHostnameVerifier
verify the server you are calling is trusted or not, it check subjectAltNames
that server name in the Url
match any name in certificate subjectAltNames
.
以前,我是使用IIS
生成我的自签名证书的,它似乎只填充证书的公用名CN
并保持subjectAltNames
为空.通过以下stackoverflow answer ,我对这个问题有一个提示.为了解决此问题,我通过在服务器PowerShell
上运行以下命令来生成新许可证.
Previously I was generating my self-signed certificate using IIS
and it appear that it only fill certificate Common Name CN
and keep subjectAltNames
empty. I had a hint regarding this problem from following stackoverflow answer. In order to solve this problem I generate a new license by running below command on server PowerShell
.
New-SelfSignedCertificate -DnsName "ss_cem_5_4" -CertStoreLocation "cert:\LocalMachine\My"
请确保以管理员身份运行PowerShell
,有关New-SelfSignedCertificate
命令参数的更多信息,您可以选中此
Please make sure to run PowerShell
as administrator, and for more information regarding New-SelfSignedCertificate
command parameters you can check this Microsoft website.
我的第二个问题(或要求)是信任我的自签名证书,我尝试了一些解决方案,但所有解决方案都导致仅信任我的证书,我无法调用具有受Android
信任的证书的网站默认情况下,因此我寻求扩展Android
受信任证书的解决方案,并借助此stackoverflow answer 设法做到这一点,下面是我的完整代码.
My second problem (or requirement) was to trust my self-signed certificate, I try some solutions but they all led to only trust my certificate, I was unable to call websites which have a certificate that is trusted by Android
by default, so I search for a solution to extend Android
trusted certificates, and with the help of this stackoverflow answer I manage to accomplish that, and below is my full code.
下面的类用于扩展Android
受信任的证书:
Below class is used to extend Android
trusted certificates:
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
Below code is to create my 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;
}
,并使用以下代码添加我的自定义SSLSocketFactory
:
and adding my custom SSLSocketFactory
using below code:
SSLSocketFactory factory = getSSLConfig();
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.sslSocketFactory(factory, (X509TrustManager) ((AdditionalKeyStoresSSLSocketFactory) factory).TrustManager[0])
.build();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(serverBaseUrl)
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
.build();
免责声明:我将证书复制到下载目录中,我认为这不是一个好主意,因为它存在很多安全隐患,我的代码仅用于演示
Disclaimer: I copy my certificates to the download directory, I do not think this is a good idea because it has a lot of security risks, my code is just for demonstration
这篇关于使用自签名证书签名的服务器上的调用API的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!