【问题标题】:How to make https request with ssl certificate in Retrofit如何在改造中使用 ssl 证书发出 https 请求
【发布时间】: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 消息。

【问题讨论】:

标签: android ssl https retrofit okhttp


【解决方案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://stackoverflow.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】:

      按照@Jayanswer,这里是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:它适用于我,请正确添加功能。该死的肯定会工作的。

        【讨论】:

          猜你喜欢
          • 2016-07-06
          • 1970-01-01
          • 1970-01-01
          • 2014-10-12
          • 1970-01-01
          • 2012-07-25
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多