如何在改造中使用 ssl 证书发出 https 请求

Posted

技术标签:

【中文标题】如何在改造中使用 ssl 证书发出 https 请求【英文标题】:How to make https request with ssl certificate in Retrofit 【发布时间】:2015-08-27 01:47:06 【问题描述】:

我有一个 .p12 证书文件,我使用 SSL Converter 将其转换为 .pem 证书文件。然后我在我的 android 代码中使用该 pem 证书文件,如下所示:

OkHttpClient okHttpClient = new OkHttpClient();
        try 
            CertificateFactory cf = CertificateFactory.getInstance("X.509");
            InputStream instream = context.getResources().openRawResource(R.raw.pem_certificate);
            Certificate ca;
            ca = cf.generateCertificate(instream);
            KeyStore kStore = KeyStore.getInstance(KeyStore.getDefaultType());
            kStore.load(null, null);
            kStore.setCertificateEntry("ca", ca);
            TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
            tmf.init(kStore);
            SSLContext sslContext = SSLContext.getInstance("TLS");
            sslContext.init(null, tmf.getTrustManagers(), null);
            okHttpClient.setSslSocketFactory(sslContext.getSocketFactory());
         catch (CertificateException
                | KeyStoreException
                | NoSuchAlgorithmException
                | IOException
                | KeyManagementException e) 
            e.printStackTrace();
        

        baseURL = endpoint;
        RestAdapter restAdapter = new RestAdapter.Builder()
                .setEndpoint(baseURL)
                .setClient(new OkClient(okHttpClient))
                .build();

        service = restAdapter.create(DishService.class);

但是这段代码不起作用。它在“ca = cf.generateCertificate(instream);”行失败带有 CertificateException 消息。

【问题讨论】:

请看这个答案:***.com/a/31436459/4261176. gist.github.com/erikcaffrey/10af1cc0b99a54f5b9a8a7614cca6f0a希望对大家有所帮助! 使用这个最简单的解决方案:***.com/a/45855405/3448003 【参考方案1】:
public class RetrofitBuilder 

private static Retrofit retrofit = null;
private static final String BASE_URL = BuildConfig.BASE_URL;
private static final String API_VERSION = BuildConfig.VERSION;

private static OkHttpClient.Builder httpClientBuilder = null;

public static Retrofit getInstance(Context context) 
    if (retrofit == null) 

        httpClientBuilder = new OkHttpClient.Builder().readTimeout(5, TimeUnit.SECONDS);
        initHttpLogging();
        initSSL(context);

        Retrofit.Builder builder = new Retrofit.Builder()
                .baseUrl(BASE_URL + API_VERSION)
                .addConverterFactory(GsonConverterFactory.create())
                .client(httpClientBuilder.build());


        retrofit = builder.build();

    
    return retrofit;



private static void initHttpLogging() 
    HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
    logging.setLevel(HttpLoggingInterceptor.Level.BODY);
    if (BuildConfig.DEBUG) httpClientBuilder.addInterceptor(logging);


private static void initSSL(Context context) 

    SSLContext sslContext = null;
    try 
        sslContext = createCertificate(context.getResources().openRawResource(R.raw.cert));
     catch (CertificateException | IOException | KeyStoreException | KeyManagementException | NoSuchAlgorithmException e) 
        e.printStackTrace();
    

    if(sslContext!=null)
        httpClientBuilder.sslSocketFactory(sslContext.getSocketFactory(), systemDefaultTrustManager());
    



private static SSLContext createCertificate(InputStream trustedCertificateIS) throws CertificateException, IOException, KeyStoreException, KeyManagementException, NoSuchAlgorithmException

    CertificateFactory cf = CertificateFactory.getInstance("X.509");
    Certificate ca;
    try 
        ca = cf.generateCertificate(trustedCertificateIS);
     finally 
        trustedCertificateIS.close();
    

    // creating a KeyStore containing our trusted CAs
    String keyStoreType = KeyStore.getDefaultType();
    KeyStore keyStore = KeyStore.getInstance(keyStoreType);
    keyStore.load(null, null);
    keyStore.setCertificateEntry("ca", ca);

    // creating a TrustManager that trusts the CAs in our KeyStore
    String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
    TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
    tmf.init(keyStore);

    // creating an SSLSocketFactory that uses our TrustManager
    SSLContext sslContext = SSLContext.getInstance("TLS");
    sslContext.init(null, tmf.getTrustManagers(), null);
    return sslContext;



private static X509TrustManager systemDefaultTrustManager() 

    try 
        TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        trustManagerFactory.init((KeyStore) null);
        TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
        if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) 
            throw new IllegalStateException("Unexpected default trust managers:" + Arrays.toString(trustManagers));
        
        return (X509TrustManager) trustManagers[0];
     catch (GeneralSecurityException e) 
        throw new AssertionError(); // The system has no TLS. Just give up.
    




在阅读了很多帖子、博客和要点后,我终于找到了一种方法。这对我有用。

【讨论】:

在哪里可以找到证书文件? 在哪里可以找到 kotlin 版本?【参考方案2】:

也许你的 R.raw.pem_certificate 有问题...

1) 尝试使用 openssl 从服务器获取原始公共证书: openssl s_client -connect HOSTNAME:PORT -showcerts

(详情请看这里:https://superuser.com/questions/97201/how-to-save-a-remote-server-ssl-certificate-locally-as-a-file)

2) 如何使用自定义 SSL 证书设置 Retrofit2 https://adiyatmubarak.wordpress.com/tag/add-ssl-certificate-in-retrofit-2/

或改造1: https://number1.co.za/use-retrofit-self-signed-unknown-ssl-certificate-android/

PS:它适用于我,请不要将 PEM 文件转换为 BKS。

【讨论】:

【参考方案3】:

Jay 的回答让我开始了,但我收到了这个错误:Trust anchor for certification path not found

我设法使用KeyTool command 将我拥有的 p12 证书导入到密钥库中。

更具体地说:

步骤 1. 使用默认证书生成新的密钥库,然后将其删除

keytool -genkey -alias mycert -keyalg RSA -keysize 2048 -keystore mykeystore
keytool -delete -alias mycert -keystore mykeystore
keytool -v -list -keystore mykeystore

第 2 步。导入证书

keytool -v -importkeystore -srckeystore <path to certificate> -srcstoretype PKCS12 -destkeystore mykeystore -deststoretype PKCS12
keytool -v -list -keystore mykeystore

第 3 步:然后我使用以下代码加载 SSLContext:

    // open the keystore
    KeyStore keyStore = KeyStore.getInstance(type);
    try 
        keyStore.load(keystoreInputStream, password.toCharArray());
     finally 
        try 
            keystoreInputStream.close();
         catch (IOException e) 
            // swallow this
        
    

    // create and initialise a key manager factory
    KeyManagerFactory kmf = KeyManagerFactory.getInstance("X509");
    if (password != null && !password.trim().equals("")) 
        kmf.init(keyStore, password.toCharArray());
     else 
        kmf.init(keyStore, null);
    

    // build an SSL context
    KeyManager[] keyManagers = kmf.getKeyManagers();
    SSLContext sslContext = SSLContext.getInstance("TLS");
    sslContext.init(keyManagers, null, null);

感谢这里的代码:https://chariotsolutions.com/blog/post/https-with-client-certificates-on/

【讨论】:

【参考方案4】:

第 1 步: 将您的安全证书放在 src/main/res/raw/client_certificate.cer 等原始目录中

第二步:用这个证书创建一个SSLContext对象,比如

SSLContext sslContext = createCertificate(getApplicationContext().getResources().openRawResource(R.raw.client_certificate));

第 3 步:将创建的 SSLContext 对象添加到 OkHttpClient 构建器。

addClientCertificate(okHttpClientBuilder);

第四步:设置 OkHttpClient 构建器来改造客户端。

retrofitBuilder.client(okHttpClientBuilder.build());

这是 RetrofitManager.java 文件中上述所有步骤的完整实现。

RetrofitManager.java

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

import java.io.IOException;
import java.io.InputStream;
import java.security.GeneralSecurityException;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.concurrent.TimeUnit;

import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
import java.security.cert.Certificate;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.logging.HttpLoggingInterceptor;
import retrofit2.Retrofit;
import retrofit2.adapter.rxjava.RxJavaCallAdapterFactory;
import retrofit2.converter.gson.GsonConverterFactory;

/**
 * Created by Rajoo Kannaujiya on 02/16/2020.
 */

public class RetrofitManager 
    private static final String URL = "https://***.com"; // Fetch url from config file.
    private static final String TLS = "TLS";
    private static final String SSL = "SSL";
    private static final String CA = "ca";
    private static final String X_509 = "X.509";
    private static final String PROD = "prod";
    private static X509TrustManager trustManager = null;
    private static final String CONTENT_TYPE = "Content-Type";
    private static final String CONTENT_TYPE_VALUE = "application/json";
    public static final String BUILD_TYPE = "prod"; // Fetch build type from config file (Whether it is a dev, qa, stage or prod build).

    public static Retrofit getRetrofitForAPIUrl() 
        return getRetrofit(URL);
    

    private static Retrofit getRetrofit(String url) 
        Gson gson = new GsonBuilder().setLenient().create();

        Retrofit.Builder retrofitBuilder = new Retrofit.Builder()
                .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                .addConverterFactory(GsonConverterFactory.create(gson))
                .baseUrl(url);

        HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
        logging.level(HttpLoggingInterceptor.Level.BODY);

        OkHttpClient.Builder okHttpClientBuilder = new OkHttpClient.Builder();
        okHttpClientBuilder.addInterceptor(chain -> 
            Request origReq = chain.request();
            Request.Builder requestBuilder = origReq.newBuilder()
                    .addHeader(CONTENT_TYPE, CONTENT_TYPE_VALUE);

            Request request = requestBuilder.build();
            return chain.proceed(request);
        );

        addClientCertificate(okHttpClientBuilder);

        OkHttpClient.Builder okHttpBuilder = okHttpClientBuilder
                .connectTimeout(10, TimeUnit.SECONDS)
                .writeTimeout(20, TimeUnit.SECONDS)
                .readTimeout(30, TimeUnit.SECONDS)
                .addInterceptor(logging);
        retrofitBuilder.client(okHttpBuilder.build());

        return retrofitBuilder.build();
    

    /**
     * Method to create SSLContext object for the client certificate and return it.
     *
     * @param clientCertificate
     * @return SSLContext Object
     */
    private static SSLContext getSSLContextForClientCertificate(InputStream clientCertificate) 
        SSLContext sslContext = null;
        // If given client certificate is for production environment only, Then check put check for build type.
        if (PROD.contentEquals(BUILD_TYPE)) 
            // Creating X.509 certificate factory instance
            CertificateFactory cf = null;
            try 
                cf = CertificateFactory.getInstance(X_509);
             catch (CertificateException e) 
                e.printStackTrace();
            
            // Generating client certificate
            Certificate ca = null;
            try 
                try 
                    if (cf != null) 
                        ca = cf.generateCertificate(clientCertificate);
                    
                 catch (CertificateException e) 
                    e.printStackTrace();
                
             finally 
                try 
                    clientCertificate.close();
                 catch (IOException e) 
                    e.printStackTrace();
                
            
            // Creating a KeyStore containing our trusted CAs
            String keyStoreType = KeyStore.getDefaultType();
            KeyStore keyStore = null;
            try 
                keyStore = KeyStore.getInstance(keyStoreType);
             catch (KeyStoreException e) 
                e.printStackTrace();
            
            if (keyStore != null) 
                try 
                    keyStore.load(null, null);
                 catch (CertificateException | IOException | NoSuchAlgorithmException e) 
                    e.printStackTrace();
                
                try 
                    keyStore.setCertificateEntry(CA, ca);
                 catch (KeyStoreException e) 
                    e.printStackTrace();
                
            
            // Creating a TrustManager that trusts the CAs in our KeyStore
            String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
            TrustManagerFactory tmf = null;
            try 
                tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
             catch (NoSuchAlgorithmException e) 
                e.printStackTrace();
            
            try 
                if (tmf != null) 
                    tmf.init(keyStore);
                
             catch (KeyStoreException e) 
                e.printStackTrace();
            
            // Creating an SSLContext instance that uses our TrustManager
            try 
                sslContext = SSLContext.getInstance(TLS);
             catch (NoSuchAlgorithmException e) 
                e.printStackTrace();
            
            try 
                if (sslContext != null && tmf != null) 
                    sslContext.init(null, tmf.getTrustManagers(), null);
                
             catch (KeyManagementException e) 
                e.printStackTrace();
            
         else  // For non prod environment build. This section is useful only if you don't have client certificate for lower environment.
            try 
                sslContext = SSLContext.getInstance(SSL);
                sslContext.init(null, new TrustManager[]getTrustManager(), new java.security.SecureRandom());
             catch (NoSuchAlgorithmException | KeyManagementException e) 
                e.printStackTrace();
            
        
        trustManager = getTrustManager();
        return sslContext;
    

    /**
     * Method to add client certificate to OkHttpClient.Builder object.
     *
     * @param okHttpClientBuilder
     * @return void
     */
    private static void addClientCertificate(OkHttpClient.Builder okHttpClientBuilder) 
        SSLContext sslContext = getSSLContextForClientCertificate(getApplicationContext().getResources().openRawResource(R.raw.myaccount_coxbusiness_com));
        if (sslContext != null) 
            okHttpClientBuilder.sslSocketFactory(sslContext.getSocketFactory(), trustManager);
            okHttpClientBuilder.hostnameVerifier((hostname, session) -> true);
        
    

    private static X509TrustManager getTrustManager() 
        X509TrustManager trustManager = null;
        if (PROD.contentEquals(BUILD_TYPE))  // If client certificate is only available for prod environment.
            try 
                TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
                trustManagerFactory.init((KeyStore) null);
                TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
                if (trustManagers[0] instanceof X509TrustManager) 
                    trustManager = (X509TrustManager) trustManagers[0];
                
             catch (GeneralSecurityException e) 
                throw new AssertionError();
            
         else  // For non prod environment build. This section is useful only if you don't have client certificate for lower environment.
            trustManager = new X509TrustManager() 
                @Override
                public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException 

                

                @Override
                public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException 

                

                @Override
                public X509Certificate[] getAcceptedIssuers() 
                    return new X509Certificate[0];
                
            ;
        
        return trustManager;
    

【讨论】:

对不起,没有用的评论,但你的方法 getSSLContextForClientCertificate() 是我一生中见过的最丑陋的代码,我只是想让你知道。【参考方案5】:

按照@Jay的answer,这里是kotlin版本:

    private var retrofit: Retrofit? = null
    private val BASE_URL: String = BuildConfig.BASE_URL
    private val API_VERSION: String = BuildConfig.VERSION
    private var httpClientBuilder: Builder? = null
    fun getInstance(context: Context): Retrofit? 
        if (retrofit == null) 
            httpClientBuilder = Builder().readTimeout(5, TimeUnit.SECONDS)
            initHttpLogging()
            initSSL(context)
            val builder = Retrofit.Builder()
                .baseUrl(BASE_URL + API_VERSION)
                .addConverterFactory(GsonConverterFactory.create())
                .client(httpClientBuilder.build())
            retrofit = builder.build()
        
        return retrofit
    

    private fun initHttpLogging() 
        val logging = HttpLoggingInterceptor()
        logging.setLevel(HttpLoggingInterceptor.Level.BODY)
        if (BuildConfig.DEBUG) httpClientBuilder.addInterceptor(logging)
    

    private fun initSSL(context: Context) 
        var sslContext: SSLContext? = null
        try 
            sslContext = createCertificate(context.resources.openRawResource(R.raw.cert))
         catch (e: CertificateException) 
            e.printStackTrace()
         catch (e: IOException) 
            e.printStackTrace()
         catch (e: KeyStoreException) 
            e.printStackTrace()
         catch (e: KeyManagementException) 
            e.printStackTrace()
         catch (e: NoSuchAlgorithmException) 
            e.printStackTrace()
        
        if (sslContext != null) 
            httpClientBuilder.sslSocketFactory(
                sslContext.socketFactory,
                systemDefaultTrustManager()
            )
        
    

    @Throws(
        CertificateException::class,
        IOException::class,
        KeyStoreException::class,
        KeyManagementException::class,
        NoSuchAlgorithmException::class
    )
    private fun createCertificate(trustedCertificateIS: InputStream): SSLContext 
        val cf = CertificateFactory.getInstance("X.509")
        val ca: Certificate
        ca = try 
            cf.generateCertificate(trustedCertificateIS)
         finally 
            trustedCertificateIS.close()
        

        // creating a KeyStore containing our trusted CAs
        val keyStoreType = KeyStore.getDefaultType()
        val keyStore = KeyStore.getInstance(keyStoreType)
        keyStore.load(null, null)
        keyStore.setCertificateEntry("ca", ca)

        // creating a TrustManager that trusts the CAs in our KeyStore
        val tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm()
        val tmf = TrustManagerFactory.getInstance(tmfAlgorithm)
        tmf.init(keyStore)

        // creating an SSLSocketFactory that uses our TrustManager
        val sslContext = SSLContext.getInstance("TLS")
        sslContext.init(null, tmf.trustManagers, null)
        return sslContext
    

    private fun systemDefaultTrustManager(): X509TrustManager 
        return try 
            val trustManagerFactory =
                TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
            trustManagerFactory.init(null as KeyStore?)
            val trustManagers = trustManagerFactory.trustManagers
            check(!(trustManagers.size != 1 || trustManagers[0] !is X509TrustManager)) 
                "Unexpected default trust managers:" + Arrays.toString(
                    trustManagers
                )
            
            trustManagers[0] as X509TrustManager
         catch (e: GeneralSecurityException) 
            throw AssertionError() // The system has no TLS. Just give up.
        
    

【讨论】:

【参考方案6】:

伙计们进行了大量研究,终于找到了这个解决方案的核心。

步骤 1. 创建一个新的 java 类,如下所示

public class UnsafeOkHttpClient 

public static OkHttpClient getUnsafeOkHttpClient() 
    try 
        // Create a trust manager that does not validate certificate chains
        final TrustManager[] trustAllCerts = new TrustManager[]
                new X509TrustManager() 
                    @Override
                    public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException 
                    

                    @Override
                    public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException 
                    

                    @Override
                    public java.security.cert.X509Certificate[] getAcceptedIssuers() 
                        return new java.security.cert.X509Certificate[];
                    
                
        ;

        // Install the all-trusting trust manager
        final SSLContext sslContext = SSLContext.getInstance("SSL");
        sslContext.init(null, trustAllCerts, new java.security.SecureRandom());

        // Create an ssl socket factory with our all-trusting manager
        final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();

        OkHttpClient.Builder builder = new OkHttpClient.Builder();
        builder.sslSocketFactory(sslSocketFactory, (X509TrustManager) trustAllCerts[0]);
        builder.hostnameVerifier(new HostnameVerifier() 
            @Override
            public boolean verify(String hostname, SSLSession session) 
                return true;
            
        );

        OkHttpClient okHttpClient = builder.build();
        return okHttpClient;
     catch (Exception e) 
        throw new RuntimeException(e);
    

第 2 步。将上述类方法 getUnsafeOkHttpClient() 添加到您的 API 客户端中。

OkHttpClient clients = UnsafeOkHttpClient.getUnsafeOkHttpClient();

    if (retrofit == null) 
        retrofit = new Retrofit.Builder().baseUrl(BASE_URL)
                .client(clients)
                .addConverterFactory(ScalarsConverterFactory.create())
                .addConverterFactory(GsonConverterFactory.create(gson))
                .build();
    

PS:它适用于我,请正确添加功能。该死的肯定会工作的。

【讨论】:

以上是关于如何在改造中使用 ssl 证书发出 https 请求的主要内容,如果未能解决你的问题,请参考以下文章

如何在ionic中为https请求添加android ssl证书?

如何配置 axios 使用 SSL 证书?

如何配置 axios 使用 SSL 证书?

如何在aws中为cname设置ssl证书?

如何在Nginx中添加SSL证书以支持HTTPS协议访问

在 Laravel artisan serve Https 中添加 SSL 证书