Adding CRL support in the generated certificates. (#2932)

This commit is contained in:
Michal Kit 2018-04-05 16:39:41 +01:00 committed by GitHub
parent 53a0aae489
commit c8b58a601f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 83 additions and 11 deletions

View File

@ -8,12 +8,14 @@ import net.corda.core.internal.*
import net.corda.core.utilities.days
import net.corda.core.utilities.millis
import org.bouncycastle.asn1.*
import org.bouncycastle.asn1.x500.X500Name
import org.bouncycastle.asn1.x500.style.BCStyle
import org.bouncycastle.asn1.x509.*
import org.bouncycastle.asn1.x509.Extension
import org.bouncycastle.cert.X509CertificateHolder
import org.bouncycastle.cert.X509v3CertificateBuilder
import org.bouncycastle.cert.bc.BcX509ExtensionUtils
import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder
import org.bouncycastle.openssl.jcajce.JcaPEMWriter
import org.bouncycastle.operator.ContentSigner
@ -149,18 +151,25 @@ $trustedRoot""", e, certPath, e.index)
/**
* Build a partial X.509 certificate ready for signing.
*
* @param certificateType type of the certificate.
* @param issuer name of the issuing entity.
* @param issuerPublicKey public key of the issuing entity.
* @param subject name of the certificate subject.
* @param subjectPublicKey public key of the certificate subject.
* @param validityWindow the time period the certificate is valid for.
* @param nameConstraints any name constraints to impose on certificates signed by the generated certificate.
* @param crlDistPoint CRL distribution point.
* @param crlIssuer X500Name of the CRL issuer.
*/
fun createPartialCertificate(certificateType: CertificateType,
private fun createPartialCertificate(certificateType: CertificateType,
issuer: X500Principal,
issuerPublicKey: PublicKey,
subject: X500Principal,
subjectPublicKey: PublicKey,
validityWindow: Pair<Date, Date>,
nameConstraints: NameConstraints? = null): X509v3CertificateBuilder {
nameConstraints: NameConstraints? = null,
crlDistPoint: String? = null,
crlIssuer: X500Name? = null): X509v3CertificateBuilder {
val serial = BigInteger.valueOf(random63BitValue())
val keyPurposes = DERSequence(ASN1EncodableVector().apply { certificateType.purposes.forEach { add(it) } })
val subjectPublicKeyInfo = SubjectPublicKeyInfo.getInstance(ASN1Sequence.getInstance(subjectPublicKey.encoded))
@ -171,10 +180,12 @@ $trustedRoot""", e, certPath, e.index)
.addExtension(Extension.basicConstraints, true, BasicConstraints(certificateType.isCA))
.addExtension(Extension.keyUsage, false, certificateType.keyUsage)
.addExtension(Extension.extendedKeyUsage, false, keyPurposes)
.addExtension(Extension.authorityKeyIdentifier, false, JcaX509ExtensionUtils().createAuthorityKeyIdentifier(issuerPublicKey))
if (role != null) {
builder.addExtension(ASN1ObjectIdentifier(CordaOID.X509_EXTENSION_CORDA_ROLE), false, role)
}
addCrlInfo(builder, crlDistPoint, crlIssuer)
if (nameConstraints != null) {
builder.addExtension(Extension.nameConstraints, true, nameConstraints)
}
@ -185,11 +196,15 @@ $trustedRoot""", e, certPath, e.index)
/**
* Create a X509 v3 certificate using the given issuer certificate and key pair.
*
* @param certificateType type of the certificate.
* @param issuerCertificate The Public certificate of the root CA above this used to sign it.
* @param issuerKeyPair The KeyPair of the root CA above this used to sign it.
* @param subject subject of the generated certificate.
* @param subjectPublicKey subject's public key.
* @param validityWindow The certificate's validity window. Default to [DEFAULT_VALIDITY_WINDOW] if not provided.
* @param nameConstraints any name constraints to impose on certificates signed by the generated certificate.
* @param crlDistPoint CRL distribution point.
* @param crlIssuer X500Name of the CRL issuer.
* @return A data class is returned containing the new intermediate CA Cert and its KeyPair for signing downstream certificates.
* Note the generated certificate tree is capped at max depth of 1 below this to be in line with commercially available certificates.
*/
@ -200,7 +215,9 @@ $trustedRoot""", e, certPath, e.index)
subject: X500Principal,
subjectPublicKey: PublicKey,
validityWindow: Pair<Duration, Duration> = DEFAULT_VALIDITY_WINDOW,
nameConstraints: NameConstraints? = null): X509Certificate {
nameConstraints: NameConstraints? = null,
crlDistPoint: String? = null,
crlIssuer: X500Name? = null): X509Certificate {
val window = getCertificateValidityWindow(validityWindow.first, validityWindow.second, issuerCertificate)
return createCertificate(
certificateType,
@ -209,28 +226,36 @@ $trustedRoot""", e, certPath, e.index)
subject,
subjectPublicKey,
window,
nameConstraints
nameConstraints,
crlDistPoint,
crlIssuer
)
}
/**
* Build and sign an X.509 certificate with the given signer.
*
* @param certificateType type of the certificate.
* @param issuer name of the issuing entity.
* @param issuerSigner content signer to sign the certificate with.
* @param subject name of the certificate subject.
* @param subjectPublicKey public key of the certificate subject.
* @param validityWindow the time period the certificate is valid for.
* @param nameConstraints any name constraints to impose on certificates signed by the generated certificate.
* @param crlDistPoint CRL distribution point.
* @param crlIssuer X500Name of the CRL issuer.
*/
fun createCertificate(certificateType: CertificateType,
issuer: X500Principal,
issuerPublicKey: PublicKey,
issuerSigner: ContentSigner,
subject: X500Principal,
subjectPublicKey: PublicKey,
validityWindow: Pair<Date, Date>,
nameConstraints: NameConstraints? = null): X509Certificate {
val builder = createPartialCertificate(certificateType, issuer, subject, subjectPublicKey, validityWindow, nameConstraints)
nameConstraints: NameConstraints? = null,
crlDistPoint: String? = null,
crlIssuer: X500Name? = null): X509Certificate {
val builder = createPartialCertificate(certificateType, issuer, issuerPublicKey, subject, subjectPublicKey, validityWindow, nameConstraints, crlDistPoint, crlIssuer)
return builder.build(issuerSigner).run {
require(isValidOn(Date()))
toJca()
@ -240,6 +265,7 @@ $trustedRoot""", e, certPath, e.index)
/**
* Build and sign an X.509 certificate with CA cert private key.
*
* @param certificateType type of the certificate.
* @param issuer name of the issuing entity.
* @param issuerKeyPair the public & private key to sign the certificate with.
* @param subject name of the certificate subject.
@ -253,11 +279,21 @@ $trustedRoot""", e, certPath, e.index)
subject: X500Principal,
subjectPublicKey: PublicKey,
validityWindow: Pair<Date, Date>,
nameConstraints: NameConstraints? = null): X509Certificate {
nameConstraints: NameConstraints? = null,
crlDistPoint: String? = null,
crlIssuer: X500Name? = null): X509Certificate {
val signatureScheme = Crypto.findSignatureScheme(issuerKeyPair.private)
val provider = Crypto.findProvider(signatureScheme.providerName)
val signer = ContentSignerBuilder.build(signatureScheme, issuerKeyPair.private, provider)
val builder = createPartialCertificate(certificateType, issuer, subject, subjectPublicKey, validityWindow, nameConstraints)
val builder = createPartialCertificate(
certificateType,
issuer,
issuerKeyPair.public,
subject, subjectPublicKey,
validityWindow,
nameConstraints,
crlDistPoint,
crlIssuer)
return builder.build(signer).run {
require(isValidOn(Date()))
require(isSignatureValid(JcaContentVerifierProviderBuilder().build(issuerKeyPair.public)))
@ -302,6 +338,21 @@ $trustedRoot""", e, certPath, e.index)
fun buildCertPath(certificates: List<X509Certificate>): CertPath {
return X509CertificateFactory().generateCertPath(certificates)
}
private fun addCrlInfo(builder: X509v3CertificateBuilder, crlDistPoint: String?, crlIssuer: X500Name?) {
if (crlDistPoint != null) {
val distPointName = DistributionPointName(GeneralNames(GeneralName(GeneralName.uniformResourceIdentifier, crlDistPoint)))
val crlIssuerGeneralNames = crlIssuer?.let {
GeneralNames(GeneralName(crlIssuer))
}
// The second argument is flag that allows you to define what reason of certificate revocation is served by this distribution point see [ReasonFlags].
// The idea is that you have different revocation per revocation reason. Since we won't go into such a granularity, we can skip that parameter.
// The third argument allows you to specify the name of the CRL issuer, it needs to be consistent with the crl (IssuingDistributionPoint) extension and the idp argument.
// If idp == true, set it, if idp == false, leave it null as done here.
val distPoint = DistributionPoint(distPointName, null, crlIssuerGeneralNames)
builder.addExtension(Extension.cRLDistributionPoints, false, CRLDistPoint(arrayOf(distPoint)))
}
}
}
// Assuming cert type to role is 1:1

View File

@ -20,9 +20,7 @@ import net.corda.testing.core.BOB_NAME
import net.corda.testing.core.TestIdentity
import net.corda.testing.internal.createDevIntermediateCaCertPath
import org.assertj.core.api.Assertions.assertThat
import org.bouncycastle.asn1.x509.BasicConstraints
import org.bouncycastle.asn1.x509.Extension
import org.bouncycastle.asn1.x509.KeyUsage
import org.bouncycastle.asn1.x509.*
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
@ -103,6 +101,28 @@ class X509UtilitiesTest {
}
}
@Test
fun `create valid server certificate chain includes CRL info`() {
val caKey = generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
val caCert = X509Utilities.createSelfSignedCACertificate(X500Principal("CN=Test CA Cert,O=R3 Ltd,L=London,C=GB"), caKey)
val caSubjectKeyIdentifier = SubjectKeyIdentifier.getInstance(caCert.toBc().getExtension(Extension.subjectKeyIdentifier).parsedValue)
val keyPair = generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
val crlDistPoint = "http://test.com"
val serverCert = X509Utilities.createCertificate(
CertificateType.TLS,
caCert,
caKey,
X500Principal("CN=Server Cert,O=R3 Ltd,L=London,C=GB"),
keyPair.public,
crlDistPoint = crlDistPoint)
serverCert.toBc().run {
val certCrlDistPoint = CRLDistPoint.getInstance(getExtension(Extension.cRLDistributionPoints).parsedValue)
assertTrue(certCrlDistPoint.distributionPoints.first().distributionPoint.toString().contains(crlDistPoint))
val certCaAuthorityKeyIdentifier = AuthorityKeyIdentifier.getInstance(getExtension(Extension.authorityKeyIdentifier).parsedValue)
assertTrue(Arrays.equals(caSubjectKeyIdentifier.keyIdentifier, certCaAuthorityKeyIdentifier.keyIdentifier))
}
}
@Test
fun `storing EdDSA key in java keystore`() {
val tmpKeyStore = tempFile("keystore.jks")

View File

@ -40,6 +40,7 @@ fun freshCertificate(identityService: IdentityService,
val ourCertificate = X509Utilities.createCertificate(
CertificateType.CONFIDENTIAL_LEGAL_IDENTITY,
issuerCert.subjectX500Principal,
issuerCert.publicKey,
issuerSigner,
issuer.name.x500Principal,
subjectPublicKey,