使用自签名证书签名的服务器上的调用API

时间:2019-09-02 16:40:52

标签: java android retrofit2 okhttp self-signed-certificate

我正在开发一个Android应用程序,该应用程序在API上调用了一些https。使用retrofit2okhttp3。在开发期间,我使用在服务器中生成的自签名证书。我在使用自签名证书时调用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文件来为我的服务器添加映射。 (我正在使用已植根的设备)

1 个答案:

答案 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;
    }

免责声明:我将证书复制到下载目录中,我认为这不是一个好主意,因为它存在很多安全风险,我的代码仅用于演示