1

我正在开发一个Android调用 some APIs over的应用程序https。使用retrofit2okhttp3。在我的开发过程中,我使用在服务器中生成的自签名证书。API当我使用自签名证书时,我在调用 s 时遇到了很多问题,我解决了所有问题,但遇到了这个异常SSLPeerUnverifiedException

目前,我已将证书复制ServerCertificate.cerDownload目录以加载它并将其添加到 allowed KeyStore。我尝试了很多来自不同网站的解决方案。我尝试okhttp 使用 CustomTrustAndroid 开发者网站

我根据 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来使自签名证书工作始终返回,但是这种解决方案是不可接受的,我相信我会遇到必须在客户服务器中使用自签名证书的情况(虽然不推荐)。OkHttpClientVerifytrue

我正在使用 URL 调用服务器:https://ss_cem_5_4/Portal/api/GetHeartbeat

我还应该提到我无法通过服务器名称调用服务器,因此我修改hosts了路径“/system/etc/”中的文件以添加我的服务器的映射。(我正在使用有根设备)

4

1 回答 1

0

经过几个小时寻找问题的解决方案终于解决了。如果有人遇到同样的问题,我会在这里写下我的解决方案。

首先有一个问题Self-Signed certificate,因为 Patrick Mevzek 在评论中提到的subjectAltNames是空的,并且在检查了如何OkHostnameVerifier验证您调用的服务器是否受信任之后,它会检查subjectAltNames该服务器名称是否Url与证书中的任何名称匹配subjectAltNames

以前我正在使用生成我的自签名证书IIS,它似乎只填写证书公用名CN并保持为subjectAltNames空。我从以下 stackoverflow答案中得到了关于这个问题的提示。为了解决这个问题,我通过在服务器上运行以下命令来生成一个新许可证PowerShell

New-SelfSignedCertificate -DnsName "ss_cem_5_4" -CertStoreLocation "cert:\LocalMachine\My"

请确保以PowerShell管理员身份运行,有关New-SelfSignedCertificate命令参数的更多信息,您可以查看此Microsoft 网站

我的第二个问题(或要求)是信任我的自签名证书,我尝试了一些解决方案,但它们都导致只信任我的证书,我无法调用具有Android默认信任证书的网站,所以我搜索对于扩展Android受信任证书的解决方案,在这个 stackoverflow答案的帮助下,我设法实现了这一点,下面是我的完整代码。

下面的类用于扩展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;
    }

SSLSocketFactory并使用以下代码添加我的自定义:

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();

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

于 2019-09-04T11:55:45.670 回答