Android 上的 Let's Encrypt 给出 java.security.cert.CertPathValidatorException: 未找到证书路径的信任锚

问题描述 投票:0回答:3

您好,我们已经设置了一个小型服务,从 Let's encrypt 生成了一个免费证书,并将 Nginx 配置为使用该证书(fullchain.pem 和 privkey.pem)

但是,当我尝试从 Android 应用程序(使用 OkHttp3)拨打电话时 我收到此错误

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

Let's encrypt 根证书是否不受 Android 证书信任存储区信任?或者我在设置 nginx 时错过了什么? 如果我仍然想使用让我们加密证书,有什么解决方法吗?

android certificate root lets-encrypt
3个回答
5
投票

我不确定它是否有用,但是,

/etc/letsencrypt/live/<your domain>/README
文件说:

此目录包含您的密钥和证书。

privkey.pem
:您的证书的私钥。

fullchain.pem
:大多数服务器软件使用的证书文件。

chain.pem
:用于 Nginx >=1.3.7 中的 OCSP 装订。

cert.pem
:会破坏许多服务器配置,不应该使用 无需阅读更多文档(请参阅下面的链接)。

我们建议不要移动这些文件。欲了解更多信息,请参阅 Certbot 用户指南位于 https://certbot.eff.org/docs/using.html#where-are-my-certificates.

所以也许你应该使用

chain.pem

另一方面,对于那些甚至没有使用 Nginx 的人来说,我从 Android 中得到了同样的错误,因为我错误地使用了

chain.pem
而不是
fullchain.pem
Android 应用程序的解决方案之一要求您发送整个链证书(即:
fullchain.pem
),如下所述:

https://developer.android.com/training/articles/security-ssl.html#CommonHostnameProbs

有两种方法可以解决这个问题:

  • 配置服务器以在服务器中包含中间 CA 链。大多数 CA 都提供有关如何为所有常见问题执行此操作的文档 网络服务器。如果您需要网站正常运行,这是唯一的方法 至少在 Android 4.2 之前使用默认 Android 浏览器。

  • 或者,像对待任何其他未知 CA 一样对待中间 CA,并创建一个 TrustManager 直接信任它,如前两个所示 部分。

希望有帮助。


0
投票

在让我们加密用户指南中:

If you’re using OCSP stapling with Nginx >= 1.3.7, chain.pem should be provided as the ssl_trusted_certificate to validate OCSP responses.

对于其他使用 Apache 的人,请检查您的 apache 版本。 对于我设置的一台服务器。我正在使用 Apache < 2.4.8. In let's encrypt user guide:

cert.pem contains the server certificate by itself, and chain.pem contains the additional intermediate certificate... 
Apache < 2.4.8 needs these for SSLCertificateFile and SSLCertificateChainFile, respectively.

因此,对于 SSLCertificateFile,请使用 cert.pem; 对于 SSLCertificateChainFile 使用 chain.pem

我最初仅将 fullchain.pem 用于 SSLCertificateFile。它适用于大多数浏览器和 iOS。但 Android 却抱怨了上面的错误。

在Apache中单独配置证书和链,所有平台都运行良好。


0
投票

问题在于,let's crypt 最近对其信任链进行了更改。该公告是一年前发布的,但像你一样我错过了它:https://letsencrypt.org/2023/07/10/cross-sign-expiration.html

简而言之,旧的 Android 终端的信任库中证明 let's crypt 是受信任的证书颁发机构的证书不再被接受。此信任存储可以通过操作系统更新进行更新,但对于许多旧设备来说这是不可能的,因为它们已经过了制造商的保证支持日期。

但是,有一个解决方法:https://developer.android.com/privacy-and-security/security-config#TrustingAdditionalCas

首先从let's encrypt 官方网站下载两个pem格式的证书ISRG Root X1Let's Encrypt R3

将这两个文件放在 Android 项目的资源目录 (

raw
) 中名为
res
的子目录中。

然后,仍然在

res
目录中,创建一个
xml
目录,您将在其中放置包含以下内容的
network_security_configuration.xml
文件:

<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <base-config>
        <trust-anchors>
            <certificates src="@raw/lets_encrypt_r3" />
            <certificates src="@raw/isrgrootx1" />
            <certificates src="system" />
        </trust-anchors>
    </base-config>
</network-security-config>

最后,在您的

AndroidManifest.xml
文件中,向
android:networkSecurityConfig
标签添加
application
属性:

<application
        <!-- your stuf -->
        android:networkSecurityConfig="@xml/network_security_configuration"
        <!-- your stuf --> >
</application>

所有 API 24+ 终端的问题应该已经得到解决。

然后,对于具有 API 的终端 < 24, we need to manage the problem programmatically. Start by adding the following class to your project:

import android.content.Context
import com.teampulse.client.R
import java.security.KeyStore
import java.security.cert.Certificate
import java.security.cert.CertificateFactory
import javax.net.ssl.SSLContext
import javax.net.ssl.SSLSocketFactory
import javax.net.ssl.TrustManagerFactory
import javax.net.ssl.X509TrustManager

class TrustFactory {
    companion object {
        fun getTrustFactoryManager(context: Context): Pair<SSLSocketFactory, X509TrustManager> {
            val cf = CertificateFactory.getInstance("X.509")

            val letsEncryptInput = context.resources.openRawResource(R.raw.lets_encrypt_r3)
            val letsEncryptCertificate: Certificate = letsEncryptInput.use {
                cf.generateCertificate(it)
            }

            val isrgRootInput = context.resources.openRawResource(R.raw.isrgrootx1)
            val isrgRootCertificate: Certificate = isrgRootInput.use {
                cf.generateCertificate(it)
            }

            val keyStoreType = KeyStore.getDefaultType()
            val keyStore = KeyStore.getInstance(keyStoreType).apply {
                load(null, null)
                setCertificateEntry("lets_encrypt_r3", letsEncryptCertificate)
                setCertificateEntry("isrgrootx1", isrgRootCertificate)
            }

            val tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm()
            val tmf = TrustManagerFactory.getInstance(tmfAlgorithm).apply {
                init(keyStore)
            }

            val sslContext = SSLContext.getInstance("TLS").apply {
                init(null, tmf.trustManagers, null)
            }

            return Pair(sslContext.socketFactory, tmf.trustManagers[0] as X509TrustManager)
        }
    }
}

最后,创建您的 OkHttp 客户端,如下所示:

    private fun provideOkHttpClient(
        context: Context,
    ): OkHttpClient {
        val builder = OkHttpClient.Builder()

        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
            val (socketFactory, trustManager) = TrustFactory.getTrustFactoryManager(context)
            builder.sslSocketFactory(socketFactory, trustManager)
        }

        return builder.build()
    }

问题应该在API上解决< 24 terminals.

老实说,我不知道这个解决方法有什么价值,所以我很高兴听到任何批评或评论。

© www.soinside.com 2019 - 2024. All rights reserved.