繁体   English   中英

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

[英]Call API on server that is signed using Self-Signed Certificate

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

目前,我已将证书ServerCertificate.cer复制到“ Download目录,以便加载它并将其添加到允许的KeyStore 我尝试了许多来自不同网站的解决方案。 我尝试okhttp CustomTrust并从Android开发人员网站尝试

我根据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文件以为服务器添加映射。 (我正在使用植根设备)

经过数小时的努力,终于找到了解决问题的办法。 如果有人遇到同样的问题,我将在这里写下我的解决方案。

首先, Self-Signed certificate存在问题,正如Patrick Mevzek在评论中提到的subjectAltNames为空,然后在检查OkHostnameVerifier如何验证所调用的服务器是否受信任之后,它会检查subjectAltNamesUrl中的服务器名称是否与任何名称匹配在证书subjectAltNames

以前,我是使用IIS生成我的自签名证书的,它似乎只填充证书的“公用名CNsubjectAltNames空。 我从以下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();

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM