使用 CertPathBuilder 构建证书路径时无法获得受信任的根 CA

2024-04-14

我有最终实体、一些中间 CA 和一些受信任 CA 的证书,并且我正在尝试使用CertPathBuilder找到最终实体和可信 CA 之一之间的认证路径。但是,我当前的实现包括任何中间 CA 和最终实体,但未能包括受信任的根。

我已经尝试过 BouncyCastle 提供商(CertPathBuilder.getInstance("PKIX", "BC"))和太阳的(CertPathBuilder.getInstance("PKIX")),但我得到了相同的结果。

这是一个独立的 Kotlin 代码片段,使用 Bouncy Castle (implementation("org.bouncycastle:bcpkix-jdk15on:1.66"))来生成证书。我的路径构建函数是buildCertificationPath.

package com.example.cert

import org.bouncycastle.asn1.ASN1Boolean
import org.bouncycastle.asn1.ASN1Encodable
import org.bouncycastle.asn1.ASN1EncodableVector
import org.bouncycastle.asn1.ASN1Integer
import org.bouncycastle.asn1.ASN1Primitive
import org.bouncycastle.asn1.DERBMPString
import org.bouncycastle.asn1.DEROctetString
import org.bouncycastle.asn1.DERSequence
import org.bouncycastle.asn1.x500.X500Name
import org.bouncycastle.asn1.x500.X500NameBuilder
import org.bouncycastle.asn1.x500.style.BCStyle
import org.bouncycastle.asn1.x509.AuthorityKeyIdentifier
import org.bouncycastle.asn1.x509.Extension
import org.bouncycastle.asn1.x509.SubjectKeyIdentifier
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo
import org.bouncycastle.cert.X509CertificateHolder
import org.bouncycastle.cert.X509v3CertificateBuilder
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter
import org.bouncycastle.jce.provider.BouncyCastleProvider
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder
import java.math.BigInteger
import java.security.KeyPair
import java.security.KeyPairGenerator
import java.security.MessageDigest
import java.security.PrivateKey
import java.security.PublicKey
import java.security.SecureRandom
import java.security.Security
import java.security.cert.CertPathBuilder
import java.security.cert.CertPathBuilderException
import java.security.cert.CertStore
import java.security.cert.CollectionCertStoreParameters
import java.security.cert.PKIXBuilderParameters
import java.security.cert.PKIXParameters
import java.security.cert.TrustAnchor
import java.security.cert.X509CertSelector
import java.security.cert.X509Certificate
import java.sql.Date
import java.time.ZonedDateTime

val bcToJavaCertificateConverter: JcaX509CertificateConverter =
    JcaX509CertificateConverter().setProvider(BouncyCastleProvider())

fun main() {
    Security.addProvider(BouncyCastleProvider())

    // Issue certificates using BouncyCastle
    val rootCAKeyPair = generateRSAKeyPair()
    val rootCACert = issueCertificate(
        "root",
        rootCAKeyPair.public,
        rootCAKeyPair.private,
        isCA = true,
        pathLenConstraint = 2
    )
    val intermediateCAKeyPair = generateRSAKeyPair()
    val intermediateCACert = issueCertificate(
        "intermediate",
        intermediateCAKeyPair.public,
        rootCAKeyPair.private,
        rootCACert,
        isCA = true,
        pathLenConstraint = 1
    )
    val endEntityKeyPair = generateRSAKeyPair()
    val endEntityCert = issueCertificate(
        "end",
        endEntityKeyPair.public,
        intermediateCAKeyPair.private,
        intermediateCACert,
        isCA = false,
        pathLenConstraint = 0
    )

    // Convert BouncyCastle certificates to Java ones:
    val javaRootCert = convertBCCertToJava(rootCACert)
    val javaInterCert = convertBCCertToJava(intermediateCACert)
    val javaEndCert = convertBCCertToJava(endEntityCert)

    val intermediateAndRootPath = buildCertificationPath(
        javaInterCert,
        emptySet(),
        setOf(javaRootCert)
    )
    if (intermediateAndRootPath.contentEquals(arrayOf("intermediate", "root"))) {
        println("Path between intermediate and root CA is OK")
    } else {
        println(
            "Path between intermediate and root CA is wrong: " +
                intermediateAndRootPath.joinToString(",")
        )
    }

    val endAndIntermediatePath = buildCertificationPath(
        javaEndCert,
        emptySet(),
        setOf(javaInterCert)
    )
    if (endAndIntermediatePath.contentEquals(arrayOf("end", "intermediate"))) {
        println("Path between end entity and intermediate CA is OK")
    } else {
        println(
            "Path between end entity and intermediate CA is wrong: " +
                endAndIntermediatePath.joinToString(",")
        )
    }

    val endAndRootPath = buildCertificationPath(
        javaEndCert,
        setOf(javaInterCert),
        setOf(javaRootCert)
    )
    if (endAndRootPath.contentEquals(arrayOf("end", "intermediate", "root"))) {
        println("Path between end entity and root CA is OK")
    } else {
        println("Path between end entity and root CA is wrong: " + endAndRootPath.joinToString(","))
    }
}

fun buildCertificationPath(
    endEntityCert: X509Certificate,
    intermediateCACerts: Set<X509Certificate>,
    trustedCACerts: Set<X509Certificate>
): Array<String> {
    val trustAnchors = trustedCACerts.map { TrustAnchor(it, null) }.toSet()

    val intermediateCertStore = CertStore.getInstance(
        "Collection",
        CollectionCertStoreParameters(intermediateCACerts),
        "BC"
    )

    val endEntitySelector = X509CertSelector()
    endEntitySelector.certificate = endEntityCert

    val parameters: PKIXParameters = PKIXBuilderParameters(trustAnchors, endEntitySelector)
    parameters.isRevocationEnabled = false // TODO: Needed?
    parameters.addCertStore(intermediateCertStore)

    // val pathBuilder: CertPathBuilder = CertPathBuilder.getInstance("PKIX")
    val pathBuilder: CertPathBuilder = CertPathBuilder.getInstance("PKIX", "BC")
    val pathBuilderResult = try {
        pathBuilder.build(parameters)
    } catch (exc: CertPathBuilderException) {
        exc.printStackTrace()
        return emptyArray()
    }
    val certificates = pathBuilderResult.certPath.certificates
    return certificates.map {
        X509CertificateHolder(it.encoded).subject.getRDNs(BCStyle.CN).first().first.value.toString()
    }.toTypedArray()
}

fun generateRSAKeyPair(): KeyPair {
    val keyGen = KeyPairGenerator.getInstance("RSA")
    keyGen.initialize(2048)
    return keyGen.generateKeyPair()
}

fun issueCertificate(
    subjectCommonName: String,
    subjectPublicKey: PublicKey,
    issuerPrivateKey: PrivateKey,
    issuerCertificate: X509CertificateHolder? = null,
    isCA: Boolean = false,
    pathLenConstraint: Int = 0
): X509CertificateHolder {
    val subjectDistinguishedName = buildDistinguishedName(subjectCommonName)
    val issuerDistinguishedName = if (issuerCertificate != null)
        issuerCertificate.subject
    else
        subjectDistinguishedName
    val subjectPublicKeyInfo = SubjectPublicKeyInfo.getInstance(subjectPublicKey.encoded)

    val now = ZonedDateTime.now()
    val builder = X509v3CertificateBuilder(
        issuerDistinguishedName,
        generateRandomBigInteger(),
        Date.from(now.toInstant()),
        Date.from(now.plusDays(1).toInstant()),
        subjectDistinguishedName,
        subjectPublicKeyInfo
    )

    val basicConstraints = BasicConstraintsExtension(
        isCA,
        pathLenConstraint
    )
    builder.addExtension(Extension.basicConstraints, true, basicConstraints)

    val subjectPublicKeyDigest = getSHA256Digest(subjectPublicKeyInfo.encoded)

    val issuerSKI = issuerCertificate?.getExtension(Extension.subjectKeyIdentifier)
    if (issuerSKI != null) {
        val aki = AuthorityKeyIdentifier((issuerSKI.parsedValue as DEROctetString).octets)
        builder.addExtension(Extension.authorityKeyIdentifier, false, aki)
    }

    val ski = SubjectKeyIdentifier(subjectPublicKeyDigest)
    builder.addExtension(Extension.subjectKeyIdentifier, false, ski)

    val signerBuilder = JcaContentSignerBuilder("SHA256WITHRSAANDMGF1").build(issuerPrivateKey)
    return builder.build(signerBuilder)
}

fun convertBCCertToJava(bcCert: X509CertificateHolder): X509Certificate =
    bcToJavaCertificateConverter.getCertificate(bcCert)

fun generateRandomBigInteger(): BigInteger {
    val random = SecureRandom()
    return BigInteger(64, random)
}

fun buildDistinguishedName(commonName: String): X500Name {
    val builder = X500NameBuilder(BCStyle.INSTANCE)
    builder.addRDN(BCStyle.CN, DERBMPString(commonName))
    return builder.build()
}

fun getSHA256Digest(input: ByteArray): ByteArray {
    val digest = MessageDigest.getInstance("SHA-256")
    return digest.digest(input)
}

class BasicConstraintsExtension(
    private val cA: Boolean,
    private val pathLenConstraint: Int
) : ASN1Encodable {
    init {
        if (pathLenConstraint < 0 || 2 < pathLenConstraint) {
            throw Exception(
                "pathLenConstraint should be between 0 and 2 (got $pathLenConstraint)"
            )
        }
        if (pathLenConstraint != 0 && !cA) {
            throw Exception(
                "Subject should be a CA if pathLenConstraint=$pathLenConstraint"
            )
        }
    }

    override fun toASN1Primitive(): ASN1Primitive {
        val sequence = ASN1EncodableVector(2)
        sequence.add(ASN1Boolean.getInstance(cA))
        sequence.add(ASN1Integer(pathLenConstraint.toLong()))
        return DERSequence(sequence)
    }
}

这是我得到的输出:

Path between intermediate and root CA is wrong: intermediate
Path between end entity and intermediate CA is wrong: end
Path between end entity and root CA is wrong: end,intermediate

作为一种解决方法,我最终可能会通过迭代受信任的 CA 来计算根,直到找到颁发路径中最后一个证书的 CA,但我希望这不是必需的。


Cast pathBuilderResult to java.security.cert.PKIXCertPathBuilderResult(需要实现“PKIX”才能返回实现此功能的结果)。然后,您会发现 getTrustAnchor() 方法可用,返回充当此结果的 TA 的证书。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

使用 CertPathBuilder 构建证书路径时无法获得受信任的根 CA 的相关文章

随机推荐