使用自签名证书签名的服务器上的调用API [英] Call API on server that is signed using Self-Signed Certificate

查看:120
本文介绍了使用自签名证书签名的服务器上的调用API的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在开发一个Android应用程序,该应用程序在https上调用了某些API.使用retrofit2okhttp3.在开发期间,我使用在服务器中生成的自签名证书.我在使用自签名证书时调用API时遇到很多问题,我解决了所有问题,但都卡在了此异常SSLPeerUnverifiedException上.

I'm working on an Android application that call some APIs 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 APIs 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆