SSL握手异常:java.security.cert.CertPathValidatorException:找不到证书路径的信任锚点

41

我正在使用Retrofit访问我的REST API。但是,当我将API置于ssl后,并通过http://myhost/myapi进行访问时,我会收到以下错误:

现在我的API在SSL后面,我需要做些额外的工作吗?

以下是我的连接方式:

private final String API = "https://myhost/myapi";

private final RestAdapter REST_ADAPTER = new RestAdapter.Builder()
        .setServer(API)
        .setLogLevel(RestAdapter.LogLevel.FULL)
        .build();

01-10 09:49:55.621    2076-2100/com.myapp.mobile D/Retrofit﹕ javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
            at org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:401)
            at libcore.net.http.HttpConnection.setupSecureSocket(HttpConnection.java:209)
            at libcore.net.http.HttpsURLConnectionImpl$HttpsEngine.makeSslConnection(HttpsURLConnectionImpl.java:478)
            at libcore.net.http.HttpsURLConnectionImpl$HttpsEngine.connect(HttpsURLConnectionImpl.java:433)
            at libcore.net.http.HttpEngine.sendSocketRequest(HttpEngine.java:290)
            at libcore.net.http.HttpEngine.sendRequest(HttpEngine.java:240)
            at libcore.net.http.HttpURLConnectionImpl.getResponse(HttpURLConnectionImpl.java:282)
            at libcore.net.http.HttpURLConnectionImpl.getResponseCode(HttpURLConnectionImpl.java:497)
            at libcore.net.http.HttpsURLConnectionImpl.getResponseCode(HttpsURLConnectionImpl.java:134)
            at retrofit.client.UrlConnectionClient.readResponse(UrlConnectionClient.java:90)
            at retrofit.client.UrlConnectionClient.execute(UrlConnectionClient.java:48)
            at retrofit.RestAdapter$RestHandler.invokeRequest(RestAdapter.java:287)
            at retrofit.RestAdapter$RestHandler.invoke(RestAdapter.java:222)
            at $Proxy12.signin(Native Method)
            at com.myapp.loginactivity$3.doInBackground(LoginActivity.java:143)
            at com.myapp.loginactivity$3.doInBackground(LoginActivity.java:136)
            at android.os.AsyncTask$2.call(AsyncTask.java:287)
            at java.util.concurrent.FutureTask.run(FutureTask.java:234)
            at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:230)
            at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1080)
            at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:573)
            at java.lang.Thread.run(Thread.java:841)
     Caused by: java.security.cert.CertificateException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
            at org.apache.harmony.xnet.provider.jsse.TrustManagerImpl.checkTrusted(TrustManagerImpl.java:282)
            at org.apache.harmony.xnet.provider.jsse.TrustManagerImpl.checkServerTrusted(TrustManagerImpl.java:202)
            at org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl.verifyCertificateChain(OpenSSLSocketImpl.java:595)
            at org.apache.harmony.xnet.provider.jsse.NativeCrypto.SSL_do_handshake(Native Method)
            at org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:398)
            at libcore.net.http.HttpConnection.setupSecureSocket(HttpConnection.java:209)
            at libcore.net.http.HttpsURLConnectionImpl$HttpsEngine.makeSslConnection(HttpsURLConnectionImpl.java:478)
            at libcore.net.http.HttpsURLConnectionImpl$HttpsEngine.connect(HttpsURLConnectionImpl.java:433)
            at libcore.net.http.HttpEngine.sendSocketRequest(HttpEngine.java:290)
            at libcore.net.http.HttpEngine.sendRequest(HttpEngine.java:240)
            at libcore.net.http.HttpURLConnectionImpl.getResponse(HttpURLConnectionImpl.java:282)
            at libcore.net.http.HttpURLConnectionImpl.getResponseCode(HttpURLConnectionImpl.java:497)
            at libcore.net.http.HttpsURLConnectionImpl.getResponseCode(HttpsURLConnectionImpl.java:134)
            at retrofit.client.UrlConnectionClient.readResponse(UrlConnectionClient.java:90)
            at retrofit.client.UrlConnectionClient.execute(UrlConnectionClient.java:48)
            at retrofit.RestAdapter$RestHandler.invokeRequest(RestAdapter.java:287)
            at retrofit.RestAdapter$RestHandler.invoke(RestAdapter.java:222)
            at $Proxy12.signin(Native Method)
            at com.myapp.LoginActivity$3.doInBackground(LoginActivity.java:143)
            at com.myapp.LoginActivity$3.doInBackground(LoginActivity.java:136)
            at android.os.AsyncTask$2.call(AsyncTask.java:287)
            at java.util.concurrent.FutureTask.run(FutureTask.java:234)
            at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:230)
            at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1080)
            at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:573)
            at java.lang.Thread.run(Thread.java:841)

你解决了这个问题吗? - Xianfeng.Cai
如果您还没有解决这个问题,请参考:https://dev59.com/SF4b5IYBdhLWcg3wWwTQ#31436459 - Paulo Avelar
9个回答

17

发生这种情况的原因是JVM / Dalvik对系统中的CA证书或用户证书存储区域没有信任。

要解决此问题,使用Retrofit,如果您正在使用okhttp,则与其他客户端非常相似。你需要执行以下操作:

A)创建一个包含CA公钥的证书存储区。为此,您需要在*nix上启动下一个脚本。 您需要在计算机上安装openssl,并从https://www.bouncycastle.org/下载jar bcprov-jdk16-1.46.jar。只能下载这个版本,版本1.5x与android 4.0.4不兼容。

#!/bin/bash

if [ -z $1 ]; then
  echo "Usage: cert2Android<CA cert PEM file>"
  exit 1
fi

CACERT=$1
BCJAR=bcprov-jdk16-1.46.jar

TRUSTSTORE=mytruststore.bks
ALIAS=`openssl x509 -inform PEM -subject_hash -noout -in $CACERT`

if [ -f $TRUSTSTORE ]; then
    rm $TRUSTSTORE || exit 1
fi

echo "Adding certificate to $TRUSTSTORE..."
keytool -import -v -trustcacerts -alias $ALIAS \
      -file $CACERT \
      -keystore $TRUSTSTORE -storetype BKS \
      -providerclass org.bouncycastle.jce.provider.BouncyCastleProvider \
      -providerpath $BCJAR \
      -storepass secret

echo "" 
echo "Added '$CACERT' with alias '$ALIAS' to $TRUSTSTORE..."

B). 将名为 mytruststore.bks 的 truststore 文件复制到您的项目的 res/raw 目录中。 truststore location

C). 设置连接的 SSLContext:

.............
okHttpClient = new OkHttpClient();
try {
    KeyStore ksTrust = KeyStore.getInstance("BKS");
    InputStream instream = context.getResources().openRawResource(R.raw.mytruststore);
    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();
}
.................

如果您正在使用 retrofit,则必须按照此处建议的方式附加此 OkHttpClient:https://github.com/square/retrofit/issues/265。另外,我发现只有使用 bcprov-jdk16-1.46.jar 才能正常工作,否则会出现“错误版本的密钥库”错误。 - tread
无法解决方法 okHttpClient.setSslSocketFactory(..),现在应该使用 client = new OkHttpClient.Builder().sslSocketFactory(sslContext.getSocketFactory()).build(); - G. Ciardini
@Fernando,你能帮忙处理服务器部分吗?在那里应该使用什么证书,需要是相同的吗? - inrob
我无法生成文件,我创建了Bash文件,但它只能进入if语句...这是什么条件? - babbin tandukar

6

是的,当时我使用了Retrofit + OkHttp。 - Defuera
1
我曾经遇到过同样的问题,问题出在第三点:“服务器配置缺少中间CA”,添加中间CA解决了这个问题。 - Carlos B. Flores

6
你基本上有四种可能的解决方案来修复Android上的“javax.net.ssl.SSLHandshakeException:”异常:
  1. 信任所有证书。除非你确实知道自己在做什么,否则不要这样做。
  2. 创建一个自定义的SSLSocketFactory,只信任你的证书。只要你精确地知道将要连接到哪些服务器,这个方法就可以工作,但是一旦你需要连接到具有不同SSL证书的新服务器,你就需要更新你的应用程序。
  3. 创建一个包含Android“主列表”证书的密钥库文件,然后添加你自己的证书。如果其中任何证书在将来过期,你负责在你的应用程序中更新它们。我想不出为什么要这样做。
  4. 创建一个自定义的SSLSocketFactory,使用内置证书KeyStore,但是对于无法通过默认验证的内容,会借助备用KeyStore进行回退。 详细见:点击此处

另外,我想对第1点进行更详细的说明。 我们可以使用清单网络配置有选择地跳过某些域,如下所述:

  1. Create a file "network_security_config.xml" in xml folder in res folder with following content.

       <network-security-config xmlns:tools="http://schemas.android.com/tools"
         xmlns:android="http://schemas.android.com/apk/res/android">
             <domain-config>
              <domain includeSubdomains="true">191.1.1.0</domain>
              <domain includeSubdomains="true">your_domain</domain>
             <trust-anchors>
                 <certificates src="system" />
                 <certificates src="user" />
             </trust-anchors>
         </domain-config>
     </network-security-config>
    
  2. Add "network_security_config.xml" to application tag in manifest as:

    android:networkSecurityConfig="@xml/network_security_config"

就是这样..完成了!!。您成功跳过了SSL证书。


3
无法在安卓7.0上运行。 - Gaurav Mandlik
请详细说明,@GauravMandlik。到目前为止,这在Android 6的每个版本中都可以工作。 - taranjeetsapra
例如,我的域名是https://www.google.com,那么我应该在“your_domain”字段中添加什么? - Kishan Solanki
@KishanSolanki,你应该将www.google.com添加到你的域名中。 - taranjeetsapra
@taranjeetsapra 我尝试了不同的方法,包括你提供的方法,但在Android 7上没有起作用。这个问题似乎只出现在我的Android 7上。 - Kishan Solanki
你如何创建一个自定义的SSLSocketFactory,只信任你的证书? - Mike6679

5

Android N及以上版本的修复方法: 我遇到过类似的问题,并通过按照https://developer.android.com/training/articles/security-config中描述的步骤解决了它。

但是,配置更改仅适用于Android版本24及以上,而不需要任何复杂的代码逻辑。

适用于包括N版本在内的所有版本的修复方法: 因此,对于低于N(版本24)的Android,解决方案是按照上述方式进行代码更改。如果您正在使用OkHttp,则可以使用CustomTrust来进行操作: https://github.com/square/okhttp/blob/master/samples/guide/src/main/java/okhttp3/recipes/CustomTrust.java


2
我知道4种方法:
  • 将证书导入您的应用程序并在连接中使用它
  • 禁用证书检查
  • 将您的证书添加到Android系统的受信任证书中
  • 购买一个被Android接受的经过验证的证书
我猜您不想付费,所以我认为最优雅的解决方案是第一种方法,可以通过以下方式实现:

http://blog.crazybob.org/2010/02/android-trusting-ssl-certificates.html


5
我该如何禁用证书检查?只想在测试环境中这样做。 - theblang
你如何将“证书添加到 Android 可信系统证书中”? - IgorGanapolsky
互联网上有很多关于这方面的教程。这里有一个给你:http://www.guyrutenberg.com/2013/03/16/manually-install-ssl-certificate-in-android-jelly-bean/ - kupsef

1
SSL 配置不正确。那些 trustAnchor 错误通常意味着找不到信任存储库。检查您的配置,并确保实际上指向了信任存储库并且已经放置在正确位置。
确保设置了 -Djavax.net.ssl.trustStore 系统属性,然后检查路径是否实际上导向了信任存储库。
您也可以通过设置此系统属性 -Djavax.net.debug=all 来启用 SSL 调试。在调试输出中,您将注意到它声明找不到信任存储库。

1
对等证书未在信任存储中找到或受信任,但已找到。 - user207421

1
这是一个服务器端问题。
服务器端有HTTPS的.crt文件,我们需要进行合并。
cat your_domain.**crt** your_domain.**ca-bundle** >> ssl_your_domain_.crt 

然后重新启动。

sudo service nginx restart

对我来说工作正常。

谢谢!是的,我在我的Node.js HTTPS服务器中没有设置ca文件。它在浏览器上只使用keycert就可以工作,但似乎Java需要ca捆绑包(有两个或三个----BEGIN ...部分)文件。 :) - Aidin

0

我使用这个类,没有任何问题。

public class WCFs
{
    // https://192.168.30.8/myservice.svc?wsdl
    private static final String NAMESPACE = "http://tempuri.org/";
    private static final String URL = "192.168.30.8";
    private static final String SERVICE = "/myservice.svc?wsdl";
    private static String SOAP_ACTION = "http://tempuri.org/iWCFserviceMe/";


    public static Thread myMethod(Runnable rp)
    {
        String METHOD_NAME = "myMethod";

        SoapObject request = new SoapObject(NAMESPACE, METHOD_NAME);

        request.addProperty("Message", "Https WCF Running...");
        return _call(rp,METHOD_NAME, request);
    }

    protected static HandlerThread _call(final RunProcess rp,final String METHOD_NAME, SoapObject soapReq)
    {
        final SoapSerializationEnvelope envelope = new SoapSerializationEnvelope(SoapEnvelope.VER11);
        int TimeOut = 5*1000;

        envelope.dotNet = true;
        envelope.bodyOut = soapReq;
        envelope.setOutputSoapObject(soapReq);

        final HttpsTransportSE httpTransport_net = new HttpsTransportSE(URL, 443, SERVICE, TimeOut);

        try
        {
            HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() // use this section if crt file is handmake
            {
                @Override
                public boolean verify(String hostname, SSLSession session)
                {
                    return true;
                }
            });

            KeyStore k = getFromRaw(R.raw.key, "PKCS12", "password");
            ((HttpsServiceConnectionSE) httpTransport_net.getServiceConnection()).setSSLSocketFactory(getSSLSocketFactory(k, "SSL"));


        }
        catch(Exception e){}

        HandlerThread thread = new HandlerThread("wcfTd"+ Generator.getRandomNumber())
        {
            @Override
            public void run()
            {
                Handler h = new Handler(Looper.getMainLooper());
                Object response = null;

                for(int i=0; i<4; i++)
                {
                    response = send(envelope, httpTransport_net , METHOD_NAME, null);

                    try
                    {if(Thread.currentThread().isInterrupted()) return;}catch(Exception e){}

                    if(response != null)
                        break;

                    ThreadHelper.threadSleep(250);
                }

                if(response != null)
                {
                    if(rp != null)
                    {
                        rp.setArguments(response.toString());
                        h.post(rp);
                    }
                }
                else
                {
                    if(Thread.currentThread().isInterrupted())
                        return;

                    if(rp != null)
                    {
                        rp.setExceptionState(true);
                        h.post(rp);
                    }
                }

                ThreadHelper.stopThread(this);
            }
        };

        thread.start();

        return thread;
    }


    private static Object send(SoapSerializationEnvelope envelope, HttpTransportSE androidHttpTransport, String METHOD_NAME, List<HeaderProperty> headerList)
    {
        try
        {
            if(headerList != null)
                androidHttpTransport.call(SOAP_ACTION + METHOD_NAME, envelope, headerList);
            else
                androidHttpTransport.call(SOAP_ACTION + METHOD_NAME, envelope);

            Object res = envelope.getResponse();

            if(res instanceof SoapPrimitive)
                return (SoapPrimitive) envelope.getResponse();
            else if(res instanceof SoapObject)
                return ((SoapObject) envelope.getResponse());
        }
        catch(Exception e)
        {}

        return null;
    }

    public static KeyStore getFromRaw(@RawRes int id, String algorithm, String filePassword)
    {
        try
        {
            InputStream inputStream = ResourceMaster.openRaw(id);
            KeyStore keystore = KeyStore.getInstance(algorithm);
            keystore.load(inputStream, filePassword.toCharArray());
            inputStream.close();

            return keystore;
        }
        catch(Exception e)
        {}

        return null;
    }

    public static SSLSocketFactory getSSLSocketFactory(KeyStore trustKey, String SSLAlgorithm)
    {
        try
        {
            TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
            tmf.init(trustKey);

            SSLContext context = SSLContext.getInstance(SSLAlgorithm);//"SSL" "TLS"
            context.init(null, tmf.getTrustManagers(), null);

            return context.getSocketFactory();
        }
        catch(Exception e){}

        return null;
    }
}

0

我的回答可能不是你问题的解决方案,但它肯定会帮助其他寻找类似问题的人: javax.net.ssl.SSLHandshakeException: Chain validation failed

你只需要检查你的Android设备的日期和时间,这应该可以解决问题。 这解决了我的问题。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接