棉花糖上的SSLHandshakeException

时间:2018-03-29 09:27:54

标签: android certificate sslhandshakeexception

场合
我已经构建了一个小型Android应用程序(sdk 21+),它连接到服务器,获取一些数据并显示它。对于连接,我使用OkHttp库。在Android 7+中运行一切正常 还应该提一下,我是网络新手,还没有最大的知识。

问题
在Android 6上运行(在我的情况下是api 23)我得到以下异常。

java.security.cert.CertificateException:
java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.

在我的network_security_config.xml我有3 certificates注册为我的trust-anchors
我无法解决这个例外问题,在互联网上搜索时我也找不到任何有用的东西。

问题
有什么可以解决这个问题,我该如何解决?请尽量保持简单,以便我能理解。

3 个答案:

答案 0 :(得分:0)

所以我想出了错误发生的原因以及如何有效和正确地修复错误,而不仅仅是覆盖我的连接,而忽略了所有证书如何在任何地方以及每个人的建议。

原来android:networkSecurityConfigapplication元素的标记AndroidManifest.xml仅适用于api> = 24.由于我的Android 6手机在23级运行,因此没有&# 39;在那里工作,trust anchors没有加载。

java.security.cert.CertificateException:
java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.

为了解决我的问题,我手动加载了原始资源中的文件的证书(我还分配了一个名称,使其更加用户友好。这就是为什么我在这里使用Map,技术上一个List或一个数组会很好足够)

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级别是否为&lt; 24.如果是这样,我创建了我的证书并提示用户安装它们(KeyChain.EXTRA_NAME的数据不是必要的,但更加用户友好)

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%安全,但是有人安装具有相同公钥的两个证书的可能性非常小)

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;
}

答案 1 :(得分:0)

在与Volley合作时,我遇到了同样的问题。 Android Marshmallow及更低版本无法使用HTTPS连接。对于Nouget和更高版本,一切都很好,因为我将以下配置android:networkSecurityConfig="@xml/network_security_config"与我所有的域特定证书一起使用。

根据the Android documentation

  

默认情况下,默认情况下,所有应用程序的安全连接(使用TLS和HTTPS等协议)信任预安装的系统CA,而面向Android 6.0(API级别23)及更低版本的应用程序也信任用户添加的CA存储。应用程序可以使用base-config(用于应用程序范围的自定义)或domain-config(用于每个域的自定义)来自定义其自己的连接。

因此,在棉花糖下事情变得不同有意义。正如@Bastu在回答中所说:

  

找出AndroidManifest.xml中application元素的标志android:networkSecurityConfig仅适用于api> = 24

在找到该问题的答案之前,我偶然发现了this wonderful tutorial。稍微弄乱了代码,我结束了这段代码的合并,以便能够使用证书列表:

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连接添加证书。我确信可以对这段代码进行改进,例如支持同时添加更多证书。

答案 2 :(得分:-1)

只需用下面的

替换你的OkHttpClient
{{1}}