【问题标题】:CertPathValidatorException : Trust anchor for certificate path not found - Retrofit AndroidCertPathValidatorException:找不到证书路径的信任锚 - Retrofit Android
【发布时间】:2015-05-30 04:04:42
【问题描述】:

我正在创建一个使用https 与服务器通信的android 应用程序。我正在使用retrofitOkHttp 提出请求。这些适用于标准http 请求。以下是我遵循的步骤。

第 1 步: 使用命令从服务器获取证书文件

echo -n | openssl s_client -connect api.****.tk:443 | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > gtux.cert

第 2 步: 使用以下命令将证书转换为 BKS 格式

keytool -importcert -v -trustcacerts -file "gtux.cert" -alias imeto_alias -keystore "my_keystore.bks" -provider org.bouncycastle.jce.provider.BouncyCastleProvider -providerpath "bcprov-jdk16-146.jar" -storetype BKS

它要求我输入密码并且文件已成功创建。

第 3 步:

创建一个 OkHttpClient 并使用它来发出 https 请求

public class MySSLTrust {
public static OkHttpClient trustcert(Context context){
    OkHttpClient okHttpClient = new OkHttpClient();
    try {
        KeyStore ksTrust = KeyStore.getInstance("BKS");
        InputStream instream = context.getResources().openRawResource(R.raw.my_keystore);
        ksTrust.load(instream, "secret".toCharArray());
        // TrustManager decides which certificate authorities to use.
        TrustManagerFactory tmf = TrustManagerFactory
                .getInstance(TrustManagerFactory.getDefaultAlgorithm());
        tmf.init(ksTrust);
        SSLContext sslContext = SSLContext.getInstance("TLS");
        sslContext.init(null, tmf.getTrustManagers(), null);
        okHttpClient.setSslSocketFactory(sslContext.getSocketFactory());
    } catch (KeyStoreException | IOException | NoSuchAlgorithmException | CertificateException | KeyManagementException e) {
        e.printStackTrace();
    }
    return okHttpClient;
}
}

第 4 步:

必须创建 RestAdapter

RestAdapter.Builder()
.setRequestInterceptor(intercept)
.setEndpoint("https://api.****.tk")
.setClient(new OkClient(this))
.setLogLevel(RestAdapter.LogLevel.FULL)
.setLog(new AndroidLog("RETROFIT"))
.build();

但最后,当运行应用程序时,它把我扔了CertPathValidatorException : Trust anchor for certificate path not found。请帮我解决这个问题。谢谢你。

其他失败尝试: 试图在我的 Xperia Z2 中安装证书,它说文件已安装,但是当我运行应用程序时,抛出了相同的异常。

错误日志 这是我执行的错误日志...

Error Log

贴在那里以便于阅读..

【问题讨论】:

    标签: android ssl retrofit okhttp


    【解决方案1】:

    免责声明:此答案来自 2015 年 7 月,从那时起使用 RetrofitOkHttp
    查看this link 了解有关 Retrofit v2 的更多信息,查看this one 了解当前 OkHttp 方法。

    好的,我使用 Android Developers guide 让它工作了。

    就像 OP 一样,我正在尝试使用 RetrofitOkHttp 连接到启用自签名 SSL 的服务器。

    这是让事情正常运行的代码(我已经删除了 try/catch 块):

    public static RestAdapter createAdapter(Context context) {
      // loading CAs from an InputStream
      CertificateFactory cf = CertificateFactory.getInstance("X.509");
      InputStream cert = context.getResources().openRawResource(R.raw.my_cert);
      Certificate ca;
      try {
        ca = cf.generateCertificate(cert);
      } finally { cert.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);
    
      // creating an OkHttpClient that uses our SSLSocketFactory
      OkHttpClient okHttpClient = new OkHttpClient();
      okHttpClient.setSslSocketFactory(sslContext.getSocketFactory());
    
      // creating a RestAdapter that uses this custom client
      return new RestAdapter.Builder()
                  .setEndpoint(UrlRepository.API_BASE)
                  .setClient(new OkClient(okHttpClient))
                  .build();
    }
    

    为了帮助调试,我还在我的 RestAdapter 创建命令中添加了.setLogLevel(RestAdapter.LogLevel.FULL),我可以看到它正在连接并从服务器获取响应。

    只需要保存在main/res/raw 中的原始.crt 文件。 .crt 文件,即证书,是您使用openssl 创建证书时创建的两个文件之一。一般是 .crt 或 .cert 文件,另一个是 .key 文件。

    Afaik,.crt 文件是您的公钥,.key 文件是您的私钥。

    如我所见,您已经有一个 .cert 文件,它是一样的,所以尝试使用它。


    PS:对于那些以后阅读它并且只有一个.pem文件的人,根据this answer,你只需要这个将一个转换为其他:

    openssl x509 -outform der -in your-cert.pem -out your-cert.crt
    

    PS²:对于那些根本没有任何文件的人,您可以使用以下命令 (bash) 从任何服务器提取公钥(又名证书):

    echo -n | openssl s_client -connect your.server.com:443 | \
      sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > ~/my_cert.crt
    

    只需替换 your.server.com 和端口(如果它不是标准 HTTPS)并为要创建的输出文件选择有效路径。

    【讨论】:

    • 我以为你有一个 .pem 文件。更改了答案以澄清事实并非如此。但是你确实有一个 .cert 文件,应该是一样的。尝试使用它并告诉我。 :)
    • 它也适用于我。但是把证书文件放在客户端,够安全吗?
    • 是的,因为那是公钥,而不是私钥。您可以使用 OpenSSL 随时从任何服务器上获取它,而不仅仅是您的服务器。私钥,即 。 key 文件,仅在服务器中。请参阅fdmanana.wordpress.com/2008/07/01/… 了解更多信息。
    • @PauloAvelar 感谢您的意见!我注意到问题是我需要重新启动 wamp 服务器才能将新生成的证书与域名一起使用,因为我没有这样做,问题是我在 android 中使用了新证书,但 wamp 使用的是缓存版本旧的
    • 如果okHttpClient.setSslSocketFactory(...)方法无法解析,可以使用okHttpClient= new OkHttpClient.Builder() .sslSocketFactory(new TLSSocketFactory()) .build();
    【解决方案2】:
     Use the below code to solve the CertPathValidatorException issue.
    
    
     Retrofit retrofit = new Retrofit.Builder()
            .baseUrl(YOUR_BASE_URL)
            .client(getUnsafeOkHttpClient().build())
            .build();
    
    
      public static OkHttpClient.Builder 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;
                }
            });
            return builder;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    

    更多详情请访问https://mobikul.com/android-retrofit-handling-sslhandshakeexception/

    【讨论】:

    • @vishnuc156 ,我仍然遇到同样的错误...你能帮我解决同样的问题吗
    • 感谢您的解决方案
    • 如果您不会在本地下载证书,这是可行的解决方案
    • 这不只是禁用证书上的所有验证吗?那么任何证书都有效吗?
    • 这个实现是不安全的。不要将其用于生产代码:stackoverflow.com/a/39032433/3808228
    【解决方案3】:

    我不使用 Retrofit,对于 OkHttp,这是唯一适合我的自签名证书解决方案:

    1. 像 Gowtham 的问题一样从我们的网站获取证书并将其放入项目的 res/raw 目录中:

      echo -n | openssl s_client -connect elkews.com:443 | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > ./res/raw/elkews_cert.crt
      
    2. 使用 Paulo answer 设置 ssl 工厂(现在使用 OkHttpClient.Builder())但不创建 RestAdapter

    3. 然后添加the following解决方案修复:SSLPeerUnverifiedException: Hostname not verify

    因此,对我有用的 Paulo 代码的结尾(在 sslContext 初始化之后)如下所示:

    ...
    OkHttpClient.Builder builder = new OkHttpClient.Builder().sslSocketFactory(sslContext.getSocketFactory());
    builder.hostnameVerifier(new HostnameVerifier() {
      @Override
      public boolean verify(String hostname, SSLSession session) {
        return "secure.elkews.com".equalsIgnoreCase(hostname);
    });
    OkHttpClient okHttpClient = builder.build();
    

    【讨论】:

    • 感谢您补充答案。但是有一个问题:通过只检查没有 SSL 会话的主机名,您不会受到中间人攻击吗?我不是其他验证,但我记得您覆盖的验证非常重要。
    【解决方案4】:

    改造 2.3.0

        // Load CAs from an InputStream
        CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
    
        InputStream inputStream = context.getResources().openRawResource(R.raw.ssl_certificate); //(.crt)
        Certificate certificate = certificateFactory.generateCertificate(inputStream);
        inputStream.close();
    
        // Create a KeyStore containing our trusted CAs
        String keyStoreType = KeyStore.getDefaultType();
        KeyStore keyStore = KeyStore.getInstance(keyStoreType);
        keyStore.load(null, null);
        keyStore.setCertificateEntry("ca", certificate);
    
        // Create a TrustManager that trusts the CAs in our KeyStore.
        String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
        TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(tmfAlgorithm);
        trustManagerFactory.init(keyStore);
    
        TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
        X509TrustManager x509TrustManager = (X509TrustManager) trustManagers[0];
    
    
        // Create an SSLSocketFactory that uses our TrustManager
        SSLContext sslContext = SSLContext.getInstance("SSL");
        sslContext.init(null, new TrustManager[]{x509TrustManager}, null);
        sslSocketFactory = sslContext.getSocketFactory();
    
        //create Okhttp client
        OkHttpClient client = new OkHttpClient.Builder()
                    .sslSocketFactory(sslSocketFactory,x509TrustManager)
                    .build();
    
        Retrofit retrofit = new Retrofit.Builder()
                        .baseUrl(url)
                        .addConverterFactory(GsonConverterFactory.create())
                        .client(client)
                        .build();
    

    【讨论】:

      【解决方案5】:

      这是 Kotlin 版本。好的http 4.9.0
      谢谢你:)

               fun unSafeOkHttpClient() :OkHttpClient.Builder {
                  val okHttpClient = OkHttpClient.Builder()
                  try {
                      // Create a trust manager that does not validate certificate chains
                      val trustAllCerts:  Array<TrustManager> = arrayOf(object : X509TrustManager {
                          override fun checkClientTrusted(chain: Array<out X509Certificate>?, authType: String?){}
                          override fun checkServerTrusted(chain: Array<out X509Certificate>?, authType: String?) {}
                          override fun getAcceptedIssuers(): Array<X509Certificate>  = arrayOf()
                      })
      
                      // Install the all-trusting trust manager
                      val  sslContext = SSLContext.getInstance("SSL")
                      sslContext.init(null, trustAllCerts, SecureRandom())
      
                      // Create an ssl socket factory with our all-trusting manager
                      val sslSocketFactory = sslContext.socketFactory
                      if (trustAllCerts.isNotEmpty() &&  trustAllCerts.first() is X509TrustManager) {
                          okHttpClient.sslSocketFactory(sslSocketFactory, trustAllCerts.first() as X509TrustManager)
                          okHttpClient.hostnameVerifier {HostnameVerifier { _, _ -> true } }
                      }
      
                      return okHttpClient
                  } catch (e: Exception) {
                      return okHttpClient
                  }
              }
      

      【讨论】:

      • 问题不在于如何禁用 SSL 证书检查
      【解决方案6】:

      您正在将证书转换为 BKS 密钥库,为什么不直接使用 .cert,来自 https://developer.android.com/training/articles/security-ssl.html

      CertificateFactory cf = CertificateFactory.getInstance("X.509");
      InputStream instream = context.getResources().openRawResource(R.raw.gtux_cert);
      Certificate ca;
      try {
          ca = cf.generateCertificate(instream);
      } finally {
          caInput.close();
      }
      
      KeyStore kStore = KeyStore.getInstance(KeyStore.getDefaultType());
      kStore.load(null, null);
      kStore.setCertificateEntry("ca", ca);
      
      TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm(););
      tmf.init(kStore);
      
      SSLContext context = SSLContext.getInstance("TLS");
      context.init(null, tmf.getTrustManagers(), null);
      
      okHttpClient.setSslSocketFactory(context.getSocketFactory());
      

      【讨论】:

      • 我应该将 gtux.cert 保存在我的 android 应用程序中的哪里...?
      • 我的方法没有成功有什么特别的原因吗?
      • @moonzai 如果证书的 CN 为“example.com”,我无法使此代码工作 拥有一个 CN 作为域的证书我总是收到错误“CertPathValidatorException:信任锚找不到证书路径。”它似乎只有在 CN 设置为公司名称时才有效,但在这种情况下如何验证主机名?
      • @Steve 可能有多种原因,但最好在 Google 官方页面上阅读:developer.android.com/training/articles/…
      【解决方案7】:

      在 Kotlin 中的实现:改造 2.3.0

      private fun getUnsafeOkHttpClient(mContext: Context) : 
      OkHttpClient.Builder? {
      
      
      var mCertificateFactory : CertificateFactory = 
      CertificateFactory.getInstance("X.509")
      var mInputStream = mContext.resources.openRawResource(R.raw.cert)
                  var mCertificate : Certificate = mCertificateFactory.generateCertificate(mInputStream)
              mInputStream.close()
      val mKeyStoreType = KeyStore.getDefaultType()
      val mKeyStore = KeyStore.getInstance(mKeyStoreType)
      mKeyStore.load(null, null)
      mKeyStore.setCertificateEntry("ca", mCertificate)
      
      val mTmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm()
      val mTrustManagerFactory = TrustManagerFactory.getInstance(mTmfAlgorithm)
      mTrustManagerFactory.init(mKeyStore)
      
      val mTrustManagers = mTrustManagerFactory.trustManagers
      
      val mSslContext = SSLContext.getInstance("SSL")
      mSslContext.init(null, mTrustManagers, null)
      val mSslSocketFactory = mSslContext.socketFactory
      
      val builder = OkHttpClient.Builder()
      builder.sslSocketFactory(mSslSocketFactory, mTrustManagers[0] as X509TrustManager)
      builder.hostnameVerifier { _, _ -> true }
      return builder
      

      }

      【讨论】:

        【解决方案8】:

        经过长时间的研究和深入挖掘,我找到了在 android 中固定证书的解决方案,是的,它与我们需要证书本身的 iOS 不同,但在 android 中我们只需要一个哈希 pin 就可以了。

        如何获取证书的哈希密码?

        最初只是使用了错误的哈希引脚,您的 java 类将使用正确的哈希引脚或引脚链抛出错误,只需复制并粘贴到您的代码中即可。

        这个解决方案解决了我的问题:https://stackoverflow.com/a/45853669/3448003

        【讨论】:

          猜你喜欢
          • 2017-10-03
          • 2021-03-18
          • 1970-01-01
          • 2014-12-15
          • 1970-01-01
          • 2017-01-08
          • 2019-02-24
          • 1970-01-01
          相关资源
          最近更新 更多