您好,我们已经设置了一个小型服务,从 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 时错过了什么? 如果我仍然想使用让我们加密证书,有什么解决方法吗?
我不确定它是否有用,但是,
/etc/letsencrypt/live/<your domain>/README
文件说:
此目录包含您的密钥和证书。
:您的证书的私钥。privkey.pem
:大多数服务器软件使用的证书文件。fullchain.pem
:用于 Nginx >=1.3.7 中的 OCSP 装订。chain.pem
:会破坏许多服务器配置,不应该使用 无需阅读更多文档(请参阅下面的链接)。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 直接信任它,如前两个所示 部分。
希望有帮助。
在让我们加密用户指南中:
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中单独配置证书和链,所有平台都运行良好。
问题在于,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 X1和Let'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.
老实说,我不知道这个解决方法有什么价值,所以我很高兴听到任何批评或评论。