38

我正在使用 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)
4

12 回答 12

17

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

要使用 Retrofit 解决此问题,如果您使用 okhttp,则与另一个客户端非常相似。
你必须这样做:

一个)。创建一个包含 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..."

乙)。将文件 truststore mytruststore.bks 复制到项目的 res/raw 中 信任库位置

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();
}
.................
于 2015-02-28T19:31:58.040 回答
4

这可能有几个原因,包括:

  1. 颁发服务器证书的 CA 未知
  2. 服务器证书不是由 CA 签名的,而是自签名的
  3. 服务器配置缺少中间 CA

请查看此链接以获取解决方案: https ://developer.android.com/training/articles/security-ssl.html#CommonProblems

于 2015-02-26T18:52:50.657 回答
4

修复 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

于 2020-04-09T06:39:46.880 回答
3

嗨,我已经解决了同样的问题,你可以试试这个

java.security.cert.CertPathValidatorException:找不到证书路径的信任锚。NETWORK

 // SET SSL
public static OkClient setSSLFactoryForClient(OkHttpClient client) {
    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 null;
                    }
                }
        };

        // 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();


        client.setSslSocketFactory(sslSocketFactory);
        client.setHostnameVerifier(new HostnameVerifier() {
            @Override
            public boolean verify(String hostname, SSLSession session) {
                return true;
            }
        });

    } catch (Exception e) {
        throw new RuntimeException(e);
    }
    return new OkClient(client);
}
于 2016-08-19T06:28:42.270 回答
2

我知道有4种方法:

  • 将证书导入您的应用程序并将其用于连接
  • 禁用证书检查
  • 将您的证书添加到 Android 中的受信任系统证书
  • 购买 Android 接受的经过验证的证书

我假设您不想为此付费,所以我认为最优雅的解决方案是第一个,可以通过这种方式完成:

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

于 2014-01-10T15:12:05.440 回答
2

您基本上有四种可能的解决方案来修复 Android 上的“javax.net.ssl.SSLHandshakeException:”异常

  1. 信任所有证书。不要这样做,除非你真的知道你在做什么。
  2. 创建一个仅信任您的证书的自定义 SSLSocketFactory。只要您确切地知道要连接到哪些服务器,这就会起作用,但是一旦您需要使用不同的 SSL 证书连接到新服务器,您就需要更新您的应用程序。
  3. 创建一个包含 Android 证书“主列表”的 Keystore 文件,然后添加您自己的。如果这些证书中的任何一个过期,您有责任在您的应​​用程序中更新它们。我想不出这样做的理由。
  4. 创建一个使用内置证书 KeyStore 的自定义 SSLSocketFactory,但对于无法使用默认值验证的任何内容,将使用备用 KeyStore。这在点击这里得到了很好的解释

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

  1. 在 res 文件夹的 xml 文件夹中创建一个文件“network_security_config.xml”,内容如下。

       <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. 将“network_security_config.xml”添加到清单中的应用程序标记中:

    android:networkSecurityConfig="@xml/network_security_config"

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

于 2020-12-02T09:56:15.937 回答
1

SSL 配置不正确。这些 trustAnchor 错误通常意味着无法找到信任库。检查您的配置并确保您实际上指向信任存储并且它已就位。

确保您设置了-Djavax.net.ssl.trustStore系统属性,然后检查该路径是否实际通向信任库。

您还可以通过设置此系统属性来启用 SSL 调试-Djavax.net.debug=all。在调试输出中,您会注意到它指出它找不到信任库。

于 2014-01-10T14:59:17.630 回答
1

经过一番研究后,我找到了绕过 ssl 错误的方法 找不到证书路径的信任锚。 这可能不是一个好方法,但您可以将其用于测试目的。

 private HttpsURLConnection httpsUrlConnection( URL urlDownload) throws Exception {
  HttpsURLConnection connection=null;
        TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() {
            public java.security.cert.X509Certificate[] getAcceptedIssuers() {
                return null;
            }

            @SuppressLint("TrustAllX509TrustManager")
            public void checkClientTrusted(X509Certificate[] certs, String authType) {
            }

            @SuppressLint("TrustAllX509TrustManager")
            public void checkServerTrusted(X509Certificate[] certs, String authType) {
            }
        }
        };
        SSLContext sc = SSLContext.getInstance("SSL"); // Add in try catch block if you get error.
        sc.init(null, trustAllCerts, new java.security.SecureRandom()); // Add in try catch block if you get error.
        HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());

        HostnameVerifier usnoHostnameVerifier = new HostnameVerifier() {
            @Override
            public boolean verify(String hostname, SSLSession session) {
                return true;
            }
        };

        SSLSocketFactory sslSocketFactory = sc.getSocketFactory();

        connection = (HttpsURLConnection) urlDownload.openConnection();
        connection.setHostnameVerifier(usnoHostnameVerifier);
        connection.setSSLSocketFactory(sslSocketFactory);

        return connection;
    }
于 2020-11-02T11:03:07.137 回答
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;
    }
}
于 2018-05-13T13:43:32.657 回答
0

这是一个服务器端问题。

服务器端有.crt文件用于 HTTPS,这里我们要做组合

cat your_domain.**crt** your_domain.**ca-bundle** >> ssl_your_domain_.crt 

然后重新启动。

sudo service nginx restart

对我来说工作正常。

于 2020-09-18T13:46:45.510 回答
0

好的,所以我的 android 应用程序遇到了同样的问题,它具有安全域,即 HTTPS,

这些是步骤:

  1. 您需要 SSL 证书文件,即您的域的“.pem”文件。
  2. 将该文件放入资产文件夹
  3. 只需将此类复制并粘贴到您的项目中即可

公共类 SSLUtilsw {

public static SSLContext getSslContextForCertificateFile(Context context, String fileName){

    try {
        KeyStore keyStore = SSlUtilsw.getKeyStore(context, fileName);
        SSLContext sslContext = SSLContext.getInstance("SSL");
        TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        trustManagerFactory.init(keyStore);
        sslContext.init(null,trustManagerFactory.getTrustManagers(),new SecureRandom());
        return sslContext;

    }catch (Exception e){
        String msg = "Error during creating SslContext for certificate from assets";
        e.printStackTrace();
        throw new RuntimeException(msg);
    }
}

public static KeyStore getKeyStore(Context context,String fileName){
    KeyStore keyStore = null;
    try {
        AssetManager assetManager=context.getAssets();
        CertificateFactory cf = CertificateFactory.getInstance("X.509");
        InputStream caInput=assetManager.open(fileName);
        Certificate ca;
        try {
            ca=cf.generateCertificate(caInput);

        }finally {
            caInput.close();
        }
        String keyStoreType=KeyStore.getDefaultType();
        keyStore=KeyStore.getInstance(keyStoreType);
        keyStore.load(null,null);
        keyStore.setCertificateEntry("ca",ca);
    } catch (CertificateException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    } catch (KeyStoreException e) {
        e.printStackTrace();
    } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
    }
    return keyStore;
}}
  1. 在你的 http 客户端改造类中,添加这个

        val trustManagerFactory: TrustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
    trustManagerFactory.init(null as KeyStore?)
    val trustManagers: Array<TrustManager> = trustManagerFactory.trustManagers
    if (trustManagers.size != 1 || trustManagers[0] !is X509TrustManager) {
        throw IllegalStateException("Unexpected default trust managers:" + trustManagers.contentToString())
    }
    val trustManager = trustManagers[0] as X509TrustManager
    
    httpClient.sslSocketFactory(SSlUtils.getSslContextForCertificateFile(
            applicationContextHere, "yourcertificate.pem").socketFactory, trustManager)
    

就是这样。

于 2020-02-19T13:51:28.800 回答
0

我的回答可能无法解决您的问题,但它肯定会帮助其他寻找类似问题的人:javax.net.ssl.SSLHandshakeException:链验证失败

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

于 2020-03-09T16:11:57.107 回答