|
import mu.KotlinLogging |
|
import org.apache.hc.client5.http.classic.HttpClient |
|
import org.apache.hc.client5.http.classic.methods.HttpGet |
|
import org.apache.hc.client5.http.impl.classic.HttpClients |
|
import org.apache.hc.core5.http.HttpEntityContainer |
|
import org.apache.hc.core5.ssl.TrustStrategy |
|
import org.bouncycastle.asn1.ASN1IA5String |
|
import org.bouncycastle.asn1.ASN1InputStream |
|
import org.bouncycastle.asn1.DEROctetString |
|
import org.bouncycastle.asn1.x509.AuthorityInformationAccess |
|
import org.bouncycastle.asn1.x509.GeneralName |
|
import java.io.IOException |
|
import java.security.KeyStore |
|
import java.security.cert.CertificateException |
|
import java.security.cert.CertificateFactory |
|
import java.security.cert.X509Certificate |
|
import javax.net.ssl.TrustManagerFactory |
|
import javax.net.ssl.X509TrustManager |
|
|
|
class SslTrustStrategy(private val httpClient: HttpClient = HttpClients.createDefault()) : TrustStrategy { |
|
override fun isTrusted(chain: Array<out X509Certificate>, authType: String): Boolean { |
|
if (isCertificateChainTrusted(chain.toList())) { |
|
return true |
|
} |
|
return isCertificateChainTrusted(repairCertificateChain(chain)) |
|
} |
|
|
|
private fun repairCertificateChain(chain: Array<out X509Certificate>): List<X509Certificate> { |
|
// 证书连续补偿次数, 防止恶意的过长证书链来拖慢运行速度. |
|
val chainCompensationsLimit = 5 |
|
var chainCompensationsNumber = 0 |
|
val chainList = chain.toMutableList() |
|
var index = 0 |
|
while (index in 0 until chainList.lastIndex) { |
|
if (!certificateVerify(chainList[index + 1], chainList[index])) { |
|
if (chainCompensationsNumber > chainCompensationsLimit) { |
|
throw CertificateException("Too many supplementary intermediate certificates. " + |
|
"(Limit: $chainCompensationsLimit)") |
|
} |
|
|
|
val parentCertificate = getParentCertificateByAIA(chainList[index]) |
|
if (parentCertificate == null) { |
|
logger.warn { "无法获取中间证书的上级证书, 可能没有 AIA 信息." } |
|
break |
|
} |
|
chainList.add(index + 1, parentCertificate) |
|
chainCompensationsNumber ++ |
|
} else { |
|
chainCompensationsNumber = 0 |
|
} |
|
index ++ |
|
} |
|
|
|
return chainList.toList() |
|
} |
|
|
|
/** |
|
* 使用 Issuer 证书认证 Subject 证书. |
|
* @param issuer 颁发者证书. |
|
* @param subject 被颁发的证书. |
|
* @return 如果使用颁发者证书成功验证了 Subject, 则返回 `true`. |
|
*/ |
|
private fun certificateVerify(issuer: X509Certificate, subject: X509Certificate): Boolean { |
|
return try { |
|
subject.verify(issuer.publicKey) |
|
true |
|
} catch (e: Exception) { |
|
logger.warn { "Certificate Verify failed. " + |
|
"(Subject: `${subject.subjectDN.name}`, Issuer: `${subject.issuerDN.name}`, " + |
|
"IssuerCert: `${issuer.subjectDN}`)" } |
|
false |
|
} |
|
} |
|
|
|
/** |
|
* 通过 [AuthorityInformationAccess] 获取证书的颁发者证书并下载. |
|
* @param certificate 要下载颁发者证书的证书对象. |
|
* @return 如果成功下载, 返回颁发者证书(Issuer), 否则返回 `null`. |
|
*/ |
|
private fun getParentCertificateByAIA(certificate: X509Certificate): X509Certificate? { |
|
logger.debug { "正在获取证书的上级证书: ${certificate.subjectDN} (上级证书可能是: ${certificate.issuerDN})" } |
|
val extensionValue = certificate.getExtensionValue("1.3.6.1.5.5.7.1.1") ?: null |
|
val octetsValue = ASN1InputStream(extensionValue).use { |
|
(it.readObject() as DEROctetString).octets |
|
} |
|
val aia = AuthorityInformationAccess.getInstance(ASN1InputStream(octetsValue).readObject()) |
|
val parentCertificateUrlEncoded = aia.accessDescriptions.firstOrNull { |
|
logger.debug { "Method: ${it.accessMethod}, Location: ${it.accessLocation}" } |
|
it.accessMethod.id == accessMethodId && |
|
it.accessLocation.tagNo == GeneralName.uniformResourceIdentifier |
|
} ?: return null |
|
val parentCertificateUrl = ASN1IA5String.getInstance(parentCertificateUrlEncoded.accessLocation.name).string |
|
|
|
val httpRequest = HttpGet(parentCertificateUrl) |
|
val httpResponse = httpClient.execute(httpRequest) |
|
if (httpResponse.code != 200) { |
|
throw IOException("HTTP response reported an error: ${httpResponse.code}") |
|
} else if (httpResponse !is HttpEntityContainer) { |
|
throw IOException("HTTP response does not include entities.") |
|
} |
|
return httpResponse.entity.use { |
|
CertificateFactory.getInstance("X.509").generateCertificate(it.content) as X509Certificate |
|
} |
|
} |
|
|
|
private val trustManagers = getTrustManagers() |
|
|
|
/** |
|
* 使用 Java 提供的 [X509TrustManager] 检查证书链是否可信. |
|
* 通过逐层验证, 到证书链尾部如果都验证成功, 且尾部证书(CA Root 证书)为可信证书即为认证成功. |
|
* @param chain 证书链. |
|
* @return 如果证书链被验证为可信, 则返回 `true`. |
|
*/ |
|
private fun isCertificateChainTrusted(chain: List<X509Certificate>): Boolean { |
|
val chainArray = chain.toTypedArray() |
|
for (trustManager in trustManagers) { |
|
try { |
|
trustManager.checkServerTrusted(chainArray, "RSA") |
|
logger.debug { "TrustManager 认证证书链成功. (Impl: ${trustManager::class.java})" } |
|
return true |
|
} catch (e: CertificateException) { |
|
logger.debug { "TrustManager 认证证书链失败 (Impl: ${trustManager::class.java}): " + |
|
"${e::class.java}: ${e.message}" } |
|
} |
|
} |
|
logger.debug { "证书链认证失败." } |
|
return false |
|
} |
|
|
|
/** |
|
* 获取系统中的 [X509TrustManager], 用于认证 TLS 证书链. |
|
*/ |
|
private fun getTrustManagers(): List<X509TrustManager> { |
|
val trustManagerFactory = TrustManagerFactory.getInstance("PKIX") |
|
trustManagerFactory.init(null as KeyStore?) |
|
return trustManagerFactory.trustManagers.map { |
|
it as X509TrustManager |
|
} |
|
} |
|
|
|
private companion object { |
|
val logger = KotlinLogging.logger { } |
|
const val accessMethodId = "1.3.6.1.5.5.7.48.2" |
|
} |
|
|
|
} |