证书路径的信任锚点未找到。在安卓上使用自签名客户端证书。

3

我在树莓派上搭建了一个简单的 Web API,它位于同一台树莓派上的 Nginx 服务器后面。我使用自签名客户端证书来验证 Android 应用程序的调用。这在过去完全正常运行,但最近在重新构建硬件后回到这个项目时,在我的 Pixel 2 上运行 Android 8.1 时,会出现以下异常:

javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
    at com.android.org.conscrypt.ConscryptFileDescriptorSocket.startHandshake(ConscryptFileDescriptorSocket.java:219)
    at okhttp3.internal.connection.RealConnection.connectTls(RealConnection.java:268)
    ...

我根据http://nategood.com/client-side-certificate-authentication-in-ngi生成了证书和密钥。

使用curl测试没有问题。

我使用以下命令为应用程序创建了密钥库:

openssl pkcs12 -export -clcerts -in client.crt -inkey client.key -out client.p12

我按照以下文章设置了Android客户端证书并连接到服务器:http://chariotsolutions.com/blog/post/https-with-client-certificates-on/

但是我使用了Kotlin和OkHttp进行了重写:

private const val SERVER = "https://my.server"

/**
* trustManagers is used to authorize the server's self-signed cert
*/
private val trustManagers by lazy {
    val cert = CertificateFactory.getInstance("X.509")
            .generateCertificate(appCtx.assets.open("ca.crt")) as X509Certificate

    val trustStore = KeyStore.getInstance(KeyStore.getDefaultType()).apply {
        load(null, null)
        setCertificateEntry(cert.subjectX500Principal.name, cert)
    }

    TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()).apply {
        init(trustStore)
    }.trustManagers
}

/**
* keyManagers is used to load the client-authentication cert
*/
private val keyManagers by lazy {
    // assuming this can only be called after Application is created

    val keyStore = KeyStore.getInstance("PKCS12").apply {
        load(appCtx.assets.open("client.p12"), "".toCharArray())
    }

    KeyManagerFactory.getInstance("X509").apply {
        init(keyStore, "".toCharArray())
    }.keyManagers
}

/**
* sslContext for opening TLS connection to server
*/
private val sslContext by lazy {
    SSLContext.getInstance("TLS").apply {
        init(keyManagers, trustManagers, null)
    }
}

/**
* pass an HTTPS request to server
*/
suspend fun request(url: String): ByteArray? {
    return try {
        val request = Request.Builder()
                .url(url)
                .build()
        val client = OkHttpClient.Builder()
                .sslSocketFactory(sslContext.socketFactory, trustManagers[0] as X509TrustManager)
                .build()
        client.newCall(request).await().body().byteStream().readBytes()
    } catch (e: Exception) {
        err(e) { "failed to send request" }
        null
    }
}

这个程序以前是可以工作的,但现在不行了。我花了一天半的时间寻找答案,并尝试了以下方法:

  • 尝试使用HttpURLConnection而不是OkHttp。
  • 尝试从头开始重新创建所有证书/密钥。
  • 尝试使用新的“网络安全配置”:

<?xml version="1.0" encoding="utf-8"?> <network-security-config> <base-config> <trust-anchors> <!-- 另外信任用户添加的CA --> <certificates src="user" /> <certificates src="@raw/ca"/> </trust-anchors> </base-config> </network-security-config>

我已经阅读了所有关于创建自定义信任管理器的示例,它们几乎都是相同的,甚至包括https://developer.android.com/training/articles/security-ssl.html#UnknownCa

我尝试的每件事都产生了相同的异常,我错过了什么吗?


请注意,您需要在清单中注册该网络安全配置XML。顺便说一下,这是我用于测试使用网络安全配置的自签名证书的内容 - CommonsWare
我在清单文件中包含了它,但我想我在问题中漏掉了它。 - bj0
嗨,bj0,我也遇到了同样的问题。有解决方案吗? - Firnaz
我无法让它与CA一起工作,但是可以通过直接使用服务器证书来解决问题。 - bj0
1个回答

0

这是我如何在我的项目中使用GoDaddy上的自签名证书使其工作的方法:

  1. 在Android Studio中的项目中创建一个名为“raw”的新目录(不要使用单引号)
  2. 在raw文件夹中创建一个带有任何名称和.crt扩展名的新文本文件(例如gdCert.crt)
  3. 在GoDaddy cPanel下,点击“安全”下的“SSL/TLS”。点击“证书”下面的链接,然后它应该显示您已经创建的证书。点击“编辑”链接。选择并复制证书的内容,将其粘贴到您在Android Studio“raw”文件夹中创建的文本文件中,并保存。
  4. 在Android Studio中,右键单击“res”文件夹,选择New > XML > App Actions XML File。将此文件命名为“network_security_config”(不要使用单引号)
  5. 将以下内容添加到“network_security_config.xml”文件中:

<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <debug-overrides>
        <trust-anchors>
            <certificates src="user"/>
            <certificates src="@raw/gdcert"/>
        </trust-anchors>
    </debug-overrides>
</network-security-config>

在你的AndroidManifest.xml文件中,在节点下添加以下内容:

android:networkSecurityConfig="@xml/network_security_config"

使用以下Java代码来解决问题:

SSLContext sc = SSLContext.getInstance("SSL");
sc.init(null, trustAllCerts, new java.security.SecureRandom());
SSLSocketFactory ssf = sc.getSocketFactory();
SSLSocket sslsocket = (SSLSocket) ssf.createSocket("yourwebsitename.com", 443);

接着是你尝试运行以连接服务器的任何代码。

  1. 请注意,第二行调用了一个新方法,应该添加到你的类中:

TrustManager[] trustAllCerts = new TrustManager[] {new X509TrustManager() {
        @Override
        public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {

        }

        @Override
        public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {

        }

        @Override
        public X509Certificate[] getAcceptedIssuers() {
            return null;
        }
    }};

保存你的项目并运行它。


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