帮公司的安卓小哥来提个问题:之前项目是没有配置证书的,可以被抓包,现在为了防止抓包,就需要配置 https 证书。首先静态配置证书是属于写到 network_security_config_app.xml 这个证书,直接配置到网络认证文件里,这个种情况有个不好就是如果哪天证书过期了,那就无法通过网络认证了,但是不希望如此,希望如果过期了那就忽略认证,所以需要动态配置 ssl 不直接配置到文件中,通过代码去认证,先去判断证书有效,如果无效或者不能找到证书直接走信任证书,如果有效那就走证书认证,目前没配置的表现就是可以被抓包。请问大家,有过这方面的经验吗? GPT 代码帮忙解决了,但是因为是知识盲区想请教一下诸位。package com.wlld.common.network
import android.content.Contextimport android.util.Logimport com.wlld.common.utils.LogUtilsimport okhttp3.OkHttpClientimport javax.net.ssl.SSLContextimport javax.net.ssl.TrustManagerimport javax.net.ssl.X509TrustManagerimport java.security.SecureRandom
/**
OkHttp SSL 配置工具类
提供动态证书校验支持*/object SSLConfigUtil {
private const val TAG = "SSLConfigUtil"private var dynamicTrustManager: DynamicSSLTrustManager? = null
/**
配置 OkHttp 使用动态 SSL 证书校验*/fun OkHttpClient.Builder.configureDynamicSSL(context: Context): OkHttpClient.Builder {LogUtils.d(TAG, "🔧 开始配置动态 SSL...")return try {// 创建动态信任管理器dynamicTrustManager = DynamicSSLTrustManager.create(context)LogUtils.d(TAG, "✅ 动态信任管理器创建成功")
// 创建 SSL 上下文 val sslContext = SSLContext.getInstance("TLS") sslContext.init(null, arrayOf<TrustManager>(dynamicTrustManager!!), SecureRandom()) LogUtils.d(TAG, "✅ SSL 上下文初始化完成") // 配置 SSL Socket Factory sslSocketFactory(sslContext.socketFactory, dynamicTrustManager!!) LogUtils.d(TAG, "✅ SSL Socket Factory 配置完成") LogUtils.d(TAG, "🎉 动态 SSL 配置应用成功!") this} catch (e: Exception) {Log.e(TAG, "❌ 动态 SSL 配置失败", e)// 降级到不安全的配置(仅用于紧急情况)configureUnsafeSSL()}}
/**
配置不安全的 SSL (信任所有证书)
仅在紧急情况下使用*/private fun OkHttpClient.Builder.configureUnsafeSSL(): OkHttpClient.Builder {Log.w(TAG, "Using unsafe SSL configuration - trusts all certificates")return try {val trustAllCerts = arrayOf<TrustManager>(object : X509TrustManager {override fun checkClientTrusted(chain: Array<out java.security.cert.X509Certificate>, authType: String) {}override fun checkServerTrusted(chain: Array<out java.security.cert.X509Certificate>, authType: String) {}override fun getAcceptedIssuers(): Array<java.security.cert.X509Certificate> = arrayOf()})
val sslContext = SSLContext.getInstance("SSL") sslContext.init(null, trustAllCerts, SecureRandom()) sslSocketFactory(sslContext.socketFactory, trustAllCerts[0] as X509TrustManager) hostnameVerifier { _, _ -> true } this} catch (e: Exception) {Log.e(TAG, "Failed to configure unsafe SSL", e)this}}
/**
- 动态添加证书*/fun addCertificate(certificate: java.security.cert.X509Certificate) {dynamicTrustManager?.addCertificate(certificate)}
/**
- 从字节数组添加证书*/fun addCertificateFromBytes(certificateBytes: ByteArray) {dynamicTrustManager?.addCertificateFromBytes(certificateBytes)}
/**
- 设置是否跳过证书校验*/fun setSkipValidation(skip: Boolean) {dynamicTrustManager?.setSkipValidation(skip)}
/**
- 检查是否跳过校验模式*/fun isSkipValidationEnabled(): Boolean {return dynamicTrustManager?.isSkipValidationEnabled() ?: false}
/**
- 获取证书信息*/fun getCertificateInfo(): List<String> {return dynamicTrustManager?.getCertificateInfo() ?: emptyList()}
/**
- 检查证书是否过期*/fun isCertificateExpired(certificate: java.security.cert.X509Certificate): Boolean {return DynamicSSLTrustManager.isCertificateExpired(certificate)}
/**
- 重置 SSL 配置*/fun reset() {dynamicTrustManager = nullLogUtils.d(TAG, "SSL configuration reset")}}
package com.wlld.common.network
import android.content.Contextimport android.util.Logimport com.wlld.common.Rimport java.io.ByteArrayInputStreamimport java.io.InputStreamimport java.security.KeyStoreimport java.security.KeyStoreExceptionimport java.security.NoSuchAlgorithmExceptionimport java.security.PrivateKeyimport java.security.cert.Certificateimport java.security.cert.CertificateExceptionimport java.security.cert.CertificateFactoryimport java.security.cert.X509Certificateimport java.util.Dateimport javax.net.ssl.TrustManagerFactoryimport javax.net.ssl.X509TrustManager
/**
动态 HTTPS 证书校验工具类
当证书过期时跳过校验,确保应用正常运行*/class DynamicSSLTrustManager(private val context: Context) : X509TrustManager {
private val defaultTrustManager: X509TrustManagerprivate val customCertificates: MutableList<X509Certificate> = mutableListOf()private var skipValidation = false
companion object {private const val TAG = "DynamicSSLTrustManager"
// 检查证书是否过期 fun isCertificateExpired(certificate: X509Certificate): Boolean { return try { val expirationDate = certificate.notAfter expirationDate.before(Date()) } catch (e: Exception) { Log.e(TAG, "证书过期检查出错", e) true // 如果无法检查,默认认为过期 } } // 创建动态信任管理器 fun create(context: Context): DynamicSSLTrustManager { return DynamicSSLTrustManager(context) }}
init {Log.d(TAG, "🚀 DynamicSSLTrustManager 初始化开始")
// 初始化默认信任管理器 defaultTrustManager = createDefaultTrustManager() Log.d(TAG, "✅ 默认信任管理器初始化完成") // 加载自定义证书 loadCustomCertificates() Log.d(TAG, "🎯 DynamicSSLTrustManager 初始化完成 - 跳过校验模式: $skipValidation, 自定义证书数量: ${customCertificates.size}")}
private fun createDefaultTrustManager(): X509TrustManager {return try {val trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())trustManagerFactory.init(null as KeyStore?)val trustManagers = trustManagerFactory.trustManagerstrustManagers.firstOrNull { it is X509TrustManager } as? X509TrustManager?: throw IllegalStateException("Default X509TrustManager not found")} catch (e: Exception) {Log.e(TAG, "Failed to create default trust manager", e)throw RuntimeException("Failed to initialize SSL trust manager", e)}}
private fun loadCustomCertificates() {try {// 加载 assets 中的证书文件loadCertificateFromAssets("weilaiqiyuan.cer")
// 可以继续加载其他证书 // loadCertificateFromAssets("other_certificate.cer") Log.d(TAG, "已加载 ${customCertificates.size} 个自定义证书") // 如果没有找到任何证书,启用跳过校验模式 if (customCertificates.isEmpty()) { Log.w(TAG, "未找到任何自定义证书,启用跳过校验模式") skipValidation = true } } catch (e: Exception) { Log.e(TAG, "加载自定义证书失败,启用跳过校验模式", e) skipValidation = true }}
private fun loadCertificateFromAssets(fileName: String) {try {context.assets.open(fileName).use { inputStream ->val certificate = loadCertificateFromStream(inputStream)if (certificate != null) {customCertificates.add(certificate)Log.d(TAG, "已加载证书: $fileName, 过期时间: ${certificate.notAfter}")
// 检查证书是否过期 if (isCertificateExpired(certificate)) { Log.w(TAG, "证书 $fileName 已过期,启用跳过校验模式") skipValidation = true } } } } catch (e: Exception) { Log.e(TAG, "从 assets 加载证书失败: $fileName", e) }}
private fun loadCertificateFromStream(inputStream: InputStream): X509Certificate? {return try {val certificateFactory = CertificateFactory.getInstance("X.509")certificateFactory.generateCertificate(inputStream) as X509Certificate} catch (e: Exception) {Log.e(TAG, "解析证书失败", e)null}}
/**
动态添加证书*/fun addCertificate(certificate: X509Certificate) {customCertificates.add(certificate)Log.d(TAG, "已添加新证书, 过期时间: ${certificate.notAfter}")
// 检查新添加的证书是否过期if (isCertificateExpired(certificate)) {Log.w(TAG, "新添加的证书已过期,启用跳过校验模式")skipValidation = true}}
/**
- 动态添加证书从字节数组*/fun addCertificateFromBytes(certificateBytes: ByteArray) {try {val inputStream = ByteArrayInputStream(certificateBytes)val certificate = loadCertificateFromStream(inputStream)certificate?.let { addCertificate(it) }} catch (e: Exception) {Log.e(TAG, "从字节数组添加证书失败", e)}}
/**
- 设置是否跳过校验*/fun setSkipValidation(skip: Boolean) {skipValidation = skipLog.i(TAG, "跳过校验模式: $skip")}
/**
- 跳过校验模式是否开启*/fun isSkipValidationEnabled(): Boolean {return skipValidation}
/**
- 检查证书链中的所有证书是否有效*/private fun checkCertificatesValidity(chain: Array<X509Certificate>): Boolean {return chain.all { certificate ->val isValid = !isCertificateExpired(certificate)if (!isValid) {Log.w(TAG, "证书已过期: ${certificate.subjectDN}, 过期时间: ${certificate.notAfter}")}isValid}}
override fun checkClientTrusted(chain: Array<out X509Certificate>, authType: String) {// if (skipValidation) {// Log.w(TAG, "⚠️ 跳过客户端证书校验(证书过期或未找到)- 这可能导致抓包风险!")// return// }//// // 先检查自定义证书// if (validateCustomCertificates(chain)) {// Log.d(TAG, "使用有效的自定义证书")// return// }//// // 使用默认校验// try {// defaultTrustManager.checkClientTrusted(chain, authType)// } catch (e: CertificateException) {// Log.w(TAG, "默认客户端证书校验失败", e)//// // 检查是否因为过期导致的失败// if (!checkCertificatesValidity(chain as Array<X509Certificate>)) {// Log.w(TAG, "客户端证书已过期,启用跳过校验模式")// skipValidation = true// return// }//// throw e// }}
override fun checkServerTrusted(chain: Array<out X509Certificate>, authType: String) {Log.d(TAG, "🔍 checkServerTrusted 被调用 - 验证服务器证书,authType: $authType")if (skipValidation) {Log.i(TAG, "跳过服务端证书校验(证书过期或未找到)")return}
// 先检查自定义证书 if (validateCustomCertificates(chain)) { return } // 使用默认校验 try { defaultTrustManager.checkServerTrusted(chain, authType) } catch (e: CertificateException) { Log.w(TAG, "默认服务端证书校验失败", e) // 检查是否因为过期导致的失败 if (!checkCertificatesValidity(chain as Array<X509Certificate>)) { Log.w(TAG, "服务端证书已过期,启用跳过校验模式") skipValidation = true return } throw e }}
override fun getAcceptedIssuers(): Array<X509Certificate> {return if (skipValidation) {// 跳过校验时返回空数组,接受所有证书emptyArray()} else {// 返回默认接受的证书颁发机构defaultTrustManager.acceptedIssuers + customCertificates.toTypedArray()}}
/**
- 验证自定义证书链*/private fun validateCustomCertificates(chain: Array<out X509Certificate>): Boolean {if (customCertificates.isEmpty()) {return false}
// // 如果自定义证书有效,优先使用// for (customCert in customCertificates) {// if (!isCertificateExpired(customCert)) {// // 检查自定义证书是否覆盖 weilaiqiyuan.com 域名// val subjectCN = extractCommonName(customCert.subjectDN.toString())// if (subjectCN.contains("*.weilaiqiyuan.com") || subjectCN.contains("weilaiqiyuan.com")) {// Log.d(TAG, "使用有效的自定义证书验证 weilaiqiyuan.com 域名: $subjectCN")// return true// }// }// }
// 检查链中的证书是否与自定义证书匹配 for (cert in chain) { for (customCert in customCertificates) { if (cert.subjectDN == customCert.subjectDN) { // 找到匹配的自定义证书 if (!isCertificateExpired(customCert)) { // 🔍 严格验证证书:比较公钥指纹,防止证书伪造 if (verifyCertificateFingerprint(cert, customCert)) { Log.d(TAG, "✅ 证书验证通过(指纹匹配): ${cert.subjectDN}") return true } else { Log.w(TAG, "❌ 证书验证失败(指纹不匹配)- 可能是抓包攻击!") // 指纹不匹配,拒绝连接 throw CertificateException("证书指纹验证失败,可能存在中间人攻击") } } else { Log.w(TAG, "自定义证书已过期: ${cert.subjectDN}") return false } } } } return false}/** * 验证证书指纹,防止证书伪造 */private fun verifyCertificateFingerprint(cert1: X509Certificate, cert2: X509Certificate): Boolean { return try { // 比较公钥的 SHA-256 指纹 val pubkey1 = cert1.publicKey.encoded val pubkey2 = cert2.publicKey.encoded val digest1 = java.security.MessageDigest.getInstance("SHA-256").digest(pubkey1) val digest2 = java.security.MessageDigest.getInstance("SHA-256").digest(pubkey2) val fingerprint1 = digest1.joinToString("") { "%02X".format(it) } val fingerprint2 = digest2.joinToString("") { "%02X".format(it) } val isValid = fingerprint1 == fingerprint2 Log.d(TAG, "证书指纹对比: $fingerprint1 vs $fingerprint2, 匹配: $isValid") isValid } catch (e: Exception) { Log.e(TAG, "证书指纹验证失败", e) false }}/** * 从 SubjectDN 中提取 Common Name */private fun extractCommonName(subjectDN: String): String { val cnPattern = "CN=([^,]+)".toRegex() val match = cnPattern.find(subjectDN) return match?.groupValues?.get(1) ?: ""}/** * 获取自定义证书信息 */fun getCertificateInfo(): List<String> { return customCertificates.map { cert -> "Subject: ${cert.subjectDN}, Issuer: ${cert.issuerDN}, Expires: ${cert.notAfter}, Expired: ${isCertificateExpired(cert)}" }}}
