首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >Marshmallow上的SSLHandshakeException

Marshmallow上的SSLHandshakeException
EN

Stack Overflow用户
提问于 2018-03-29 09:27:54
回答 3查看 1.8K关注 0票数 1

Situation

我已经构建了一个小型安卓应用程序(sdk 21+),它连接到服务器,获取一些数据并显示出来。对于连接,我使用OkHttp库。在Android 7+中运行,一切都很好。

也应该提到,我是新的网络,还没有最大的知识。

问题

运行在Android 6上(在我的例子中是api 23),我得到了以下异常。

代码语言:javascript
复制
java.security.cert.CertificateException:
java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.

在我的network_security_config.xml中,我有3 certificates注册为我的trust-anchors

我不能对这个例外做太多,当我在网上搜索它时,我也找不到任何有用的东西。

问题

怎样才能解决这个问题,我如何解决呢?请尽量简单一点,这样我就能理解了。

EN

回答 3

Stack Overflow用户

发布于 2018-03-29 13:19:08

因此,我理解了错误发生的原因,以及如何有效和正确地修复它,而不是仅仅覆盖我的连接和忽略所有的证书,它是如何随处可见和被每个人推荐的。

原来,android:networkSecurityConfig中的application元素的标志AndroidManifest.xml只在api >= 24上工作。由于我的安卓6手机运行在23级,它没有在那里工作,trust anchors也没有加载。

代码语言:javascript
复制
java.security.cert.CertificateException:
java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.

为了解决我的问题,我从原始资源中的文件中手动加载了证书(我还分配了一个名称,使它更易于用户使用)。这就是我在这里使用Map的原因,从技术上讲,列表或数组就足够了)

代码语言:javascript
复制
private Map<String, Certificate> createCertificates() throws CertificateException {
    CertificateFactory factory = CertificateFactory.getInstance("X.509");
    InputStream inputProxy = getResources().openRawResource(R.raw.proxy);
    InputStream inputCa = getResources().openRawResource(R.raw.ca);
    Certificate certProxy = factory.generateCertificate(inputProxy);
    Certificate certCa = factory.generateCertificate(inputCa);
    try {
        inputProxy.close();
    } catch (IOException ignore) {
        // will be dumped anyways
    }
    try {
        inputCa.close();
    } catch (IOException ignore) {
        // will be dumped anyways
    }
    Map<String, Certificate> certificates = new HashMap<>();
    certificates.put("CA", certCa);
    certificates.put("PROXY", certProxy);
    return certificates;
}

然后,在运行任何网络操作之前,我检查api级别是否< 24。如果是这样的话,我创建了我的证书,并提示用户安装它们( KeyChain.EXTRA_NAME的数据不是必需的,但更适合用户使用)

代码语言:javascript
复制
if (Build.VERSION.SDK_INT < 24) {
    try {
       Map<String, Certificate> certificates = createCertificates();
       for (String key : certificates.keySet()) {
         Certificate cert = certificates.get(key);
         if (!isCertificateInstalled(cert.getPublicKey())) {
          Intent installIntent = KeyChain.createInstallIntent();
          installIntent.putExtra(KeyChain.EXTRA_CERTIFICATE, cert.getEncoded());
          installIntent.putExtra(KeyChain.EXTRA_NAME, key);
          startActivity(installIntent);
       }
     }
   } catch (CertificateException ignore) {
      // Netzwerkdialog wird später angezeigt
  }
}

但是,只有在尚未安装证书的情况下,我才会提示用户。我检查使用证书的PublicKey (理论上不是100%安全,但是有人用相同的公钥安装两个证书的可能性非常小)。

代码语言:javascript
复制
private boolean isCertificateInstalled(PublicKey pPublicKey) {
    try {
        TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        tmf.init((KeyStore) null);
        X509TrustManager xtm = (X509TrustManager) tmf.getTrustManagers()[0];
        for (X509Certificate cert : xtm.getAcceptedIssuers()) {
            if (cert.getPublicKey().equals(pPublicKey)) {
                return true;
            }
        }
    } catch (NoSuchAlgorithmException | KeyStoreException ignore) {
        // returns false
    }
    return false;
}
票数 0
EN

Stack Overflow用户

发布于 2018-11-30 06:02:53

我在使用截击的时候也遇到了同样的问题。没有HTTPS连接可以与Android Marshmallow和更低的连接一起工作。对于Nouget和上面的内容,一切都很好,因为我使用了下面的配置android:networkSecurityConfig="@xml/network_security_config"和所有特定于域的证书。

根据Android文档

默认情况下,来自所有应用程序的安全连接(如TLS和HTTPS)信任预先安装的系统CA,而针对Android6.0的应用程序(API级别23)以及更低的应用程序默认也信任用户添加的CA存储。应用程序可以使用base-config (用于应用程序范围内的自定义)或域-config(用于每个域的自定义)自定义自己的连接。

因此,在马斯马洛的情况下,事情的运作方式是不同的,这是有意义的。正如巴斯托在他的回答中所说:

AndroidManifest.xml中应用程序元素的networkSecurityConfig只在api >= 24上工作

在找到这个问题的答案之前,我偶然发现了这个精彩的教程。稍微搅乱了代码,我结束了将这些代码拼凑在一起,以便能够使用证书列表:

代码语言:javascript
复制
import android.content.Context;
import android.content.res.Resources;
import android.os.Build;
import android.util.Log;

import com.android.volley.RequestQueue;
import com.android.volley.toolbox.Volley;
import com.kitsord.R;

import java.io.IOException;
import java.io.InputStream;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;

public class ExternalConfig {
    private static final String TAG = "ExternalConfig";
    private static RequestQueue queue;

    public static RequestQueue getRequestQueue(final Context applicationContext) {
        if (queue == null) {
            queue = Volley.newRequestQueue(applicationContext);
            if (Build.VERSION.SDK_INT < 24) {
                useSSLCertificate(context.getResources(), R.raw.my_certificate1, R.raw.my_certificate2);
            }
        }

        return queue;
    }

    private static void useSSLCertificate(final Resources resources, final int ... rawCertificateResourceIds) {
        final CertificateFactory certificateFactory;
        try {
            certificateFactory = CertificateFactory.getInstance("X.509");
        } catch (final CertificateException exception) {
            Log.e(TAG, "Failed to get an instance of the CertificateFactory.", exception);
            return;
        }
        int i = 0;
        final Certificate[] certificates = new Certificate[rawCertificateResourceIds.length];
        for (final int rawCertificateResourceId : rawCertificateResourceIds) {
            final Certificate certificate;
            try (final InputStream certificateInputStream = resources.openRawResource(rawCertificateResourceId)) {
                certificate = certificateFactory.generateCertificate(certificateInputStream);
            } catch (final IOException | CertificateException exception) {
                Log.e(TAG, "Failed to retrieve the Certificate.", exception);
                return;
            }


            certificates[i] = certificate;
            i++;
        }

        final KeyStore keyStore;
        try {
            keyStore = buildKeyStore(certificates);
        } catch (final KeyStoreException | CertificateException | NoSuchAlgorithmException | IOException exception) {
            Log.e(TAG, "Failed to build the KeyStore with the Certificate.", exception);
            return;
        }

        final TrustManagerFactory trustManagerFactory;
        try {
            trustManagerFactory = buildTrustManager(keyStore);
        } catch (final KeyStoreException | NoSuchAlgorithmException exception) {
            Log.e(TAG, "Failed to build the TrustManagerFactory with the KeyStore.", exception);
            return;
        }

        final SSLContext sslContext;
        try {
            sslContext = buildSSLContext(trustManagerFactory);
        } catch (final KeyManagementException | NoSuchAlgorithmException exception) {
            Log.e(TAG, "Failed to build the SSLContext with the TrustManagerFactory.", exception);
            return;
        }

        HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory());
    }

    private static KeyStore buildKeyStore(final Certificate[] certificates) throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException {
        final String keyStoreType = KeyStore.getDefaultType();
        final KeyStore keyStore = KeyStore.getInstance(keyStoreType);
        keyStore.load(null, null);

        int i = 0;
        for (final Certificate certificate : certificates) {
            keyStore.setCertificateEntry("ca" + i, certificate);
            i++;
        }

        return keyStore;
    }

    private static TrustManagerFactory buildTrustManager(final KeyStore keyStore) throws KeyStoreException, NoSuchAlgorithmException {
        final String trustManagerAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
        final TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(trustManagerAlgorithm);
        trustManagerFactory.init(keyStore);

        return trustManagerFactory;
    }

    private static SSLContext buildSSLContext(final TrustManagerFactory trustManagerFactory) throws KeyManagementException, NoSuchAlgorithmException {
        final TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();

        final SSLContext sslContext = SSLContext.getInstance("TLS");
        sslContext.init(null, trustManagers, null);

        return sslContext;
    }
}

现在,每当我需要一个Volley队列时,这个方法不仅允许我每次使用相同的队列(不确定这是否是个好主意),而且还会为https连接添加我的证书。我相信这个代码可以被改进。

票数 0
EN

Stack Overflow用户

发布于 2018-03-29 11:03:29

只需将您的OkHttpClient替换为下面

代码语言:javascript
复制
private 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 X509Certificate[0];
                        }
                    }
            };

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

            return new OkHttpClient.Builder()
                    .sslSocketFactory(sslSocketFactory, (X509TrustManager) trustAllCerts[0])
                    .hostnameVerifier(new HostnameVerifier() {
                        @Override
                        public boolean verify(String hostname, SSLSession session) {
                            return true;
                        }
                    })
                    .connectTimeout(30, TimeUnit.SECONDS)
                    .writeTimeout(30, TimeUnit.SECONDS)
                    .retryOnConnectionFailure(true)
                    .readTimeout(30, TimeUnit.SECONDS).addInterceptor(new Interceptor() {
                        @Override
                        public okhttp3.Response intercept(Chain chain) throws IOException {
                            Request original = chain.request();

                            Request request = original.newBuilder()
                                    .build();
                            return chain.proceed(request);
                        }
                    }).build();


        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
票数 -1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/49552461

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档