diff --git a/core/src/main/kotlin/net/corda/core/crypto/X509Utilities.kt b/core/src/main/kotlin/net/corda/core/crypto/X509Utilities.kt index d32c2ac13d..aba3573bf3 100644 --- a/core/src/main/kotlin/net/corda/core/crypto/X509Utilities.kt +++ b/core/src/main/kotlin/net/corda/core/crypto/X509Utilities.kt @@ -1,6 +1,7 @@ package net.corda.core.crypto import net.corda.core.crypto.Crypto.generateKeyPair +import org.bouncycastle.asn1.ASN1Encodable import org.bouncycastle.asn1.x500.X500Name import org.bouncycastle.asn1.x500.X500NameBuilder import org.bouncycastle.asn1.x500.style.BCStyle @@ -67,16 +68,33 @@ object X509Utilities { */ @Deprecated("Full legal names should be specified in all configurations") fun getDevX509Name(commonName: String): X500Name { - return X500NameBuilder(BCStyle.INSTANCE) - .addRDN(BCStyle.CN, commonName) - .addRDN(BCStyle.O, "R3") - .addRDN(BCStyle.OU, "corda") - .addRDN(BCStyle.L, "London") - .addRDN(BCStyle.C, "UK") - .build() + val nameBuilder = X500NameBuilder(BCStyle.INSTANCE) + nameBuilder.addRDN(BCStyle.CN, commonName) + nameBuilder.addRDN(BCStyle.O, "R3") + nameBuilder.addRDN(BCStyle.OU, "corda") + nameBuilder.addRDN(BCStyle.L, "London") + nameBuilder.addRDN(BCStyle.C, "UK") + return nameBuilder.build() } /** + * Generate a distinguished name from the provided values. + * + * @see [CoreTestUtils.getTestX509Name] for generating distinguished names for test cases. + */ + @JvmOverloads + @JvmStatic + fun getX509Name(myLegalName: String, nearestCity: String, email: String, country: String? = null): X500Name { + return X500NameBuilder(BCStyle.INSTANCE).let { builder -> + builder.addRDN(BCStyle.CN, myLegalName) + builder.addRDN(BCStyle.L, nearestCity) + country?.let { builder.addRDN(BCStyle.C, it) } + builder.addRDN(BCStyle.E, email) + builder.build() + } + } + + /* * Create a de novo root self-signed X509 v3 CA cert and [KeyPair]. * @param subject the cert Subject will be populated with the domain string * @param signatureScheme The signature scheme which will be used to generate keys and certificate. Default to [DEFAULT_TLS_SIGNATURE_SCHEME] if not provided. @@ -84,6 +102,7 @@ object X509Utilities { * @return A data class is returned containing the new root CA Cert and its [KeyPair] for signing downstream certificates. * Note the generated certificate tree is capped at max depth of 2 to be in line with commercially available certificates */ + @JvmStatic fun createSelfSignedCACert(subject: X500Name, signatureScheme: SignatureScheme = DEFAULT_TLS_SIGNATURE_SCHEME, validityWindow: Pair = DEFAULT_VALIDITY_WINDOW): CertificateAndKey { val keyPair = generateKeyPair(signatureScheme) val window = getCertificateValidityWindow(validityWindow.first, validityWindow.second) @@ -100,6 +119,7 @@ object X509Utilities { * @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 */ + @JvmStatic fun createIntermediateCert(subject: X500Name, ca: CertificateAndKey, signatureScheme: SignatureScheme = DEFAULT_TLS_SIGNATURE_SCHEME, validityWindow: Pair = DEFAULT_VALIDITY_WINDOW): CertificateAndKey { val keyPair = generateKeyPair(signatureScheme) val issuer = X509CertificateHolder(ca.certificate.encoded).subject @@ -120,6 +140,7 @@ object X509Utilities { * @return The generated X509Certificate suitable for use as a Server/Client certificate in TLS. * This certificate is not marked as a CA cert to be similar in nature to commercial certificates. */ + @JvmStatic fun createServerCert(subject: X500Name, publicKey: PublicKey, ca: CertificateAndKey, subjectAlternativeNameDomains: List, @@ -141,6 +162,7 @@ object X509Utilities { * @param x509Certificate certificate to save * @param filename Target filename */ + @JvmStatic fun saveCertificateAsPEMFile(x509Certificate: X509Certificate, filename: Path) { FileWriter(filename.toFile()).use { JcaPEMWriter(it).use { @@ -154,6 +176,7 @@ object X509Utilities { * @param filename Source filename * @return The X509Certificate that was encoded in the file */ + @JvmStatic fun loadCertificateFromPEMFile(filename: Path): X509Certificate { val reader = PemReader(FileReader(filename.toFile())) val pemObject = reader.readPemObject() @@ -203,6 +226,46 @@ object X509Utilities { fun createCertificateSigningRequest(subject: X500Name, keyPair: KeyPair, signatureScheme: SignatureScheme = DEFAULT_TLS_SIGNATURE_SCHEME) = Crypto.createCertificateSigningRequest(subject, keyPair, signatureScheme) } +/** + * Rebuild the distinguished name, adding a postfix to the common name. If no common name is present, this throws an + * exception + */ +@Throws(IllegalArgumentException::class) +fun X500Name.appendToCommonName(commonName: String): X500Name = mutateCommonName { attr -> attr.toString() + commonName } + +/** + * Rebuild the distinguished name, replacing the common name with the given value. If no common name is present, this + * adds one. + */ +@Throws(IllegalArgumentException::class) +fun X500Name.replaceCommonName(commonName: String): X500Name = mutateCommonName { attr -> commonName } + +/** + * Rebuild the distinguished name, replacing the common name with a value generated from the provided function. + * + * @param mutator a function to generate the new value from the previous one. + */ +@Throws(IllegalArgumentException::class) +private fun X500Name.mutateCommonName(mutator: (ASN1Encodable) -> String): X500Name { + val builder = X500NameBuilder(BCStyle.INSTANCE) + var matched = false + this.rdNs.forEach { rdn -> + rdn.typesAndValues.forEach { typeAndValue -> + when (typeAndValue.type) { + BCStyle.CN -> { + matched = true + builder.addRDN(typeAndValue.type, mutator(typeAndValue.value)) + } + else -> { + builder.addRDN(typeAndValue) + } + } + } + } + require(matched) { "Input X.500 name must include a common name (CN) attribute: ${this}" } + return builder.build() +} + val X500Name.commonName: String get() = getRDNs(BCStyle.CN).first().first.value.toString() val X500Name.location: String get() = getRDNs(BCStyle.L).first().first.value.toString() diff --git a/node/src/integration-test/kotlin/net/corda/node/services/BFTNotaryServiceTests.kt b/node/src/integration-test/kotlin/net/corda/node/services/BFTNotaryServiceTests.kt index 0c1e5b2a6f..d6383b6d7b 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/BFTNotaryServiceTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/BFTNotaryServiceTests.kt @@ -5,6 +5,7 @@ import net.corda.core.contracts.StateAndRef import net.corda.core.contracts.StateRef import net.corda.core.contracts.TransactionType import net.corda.core.crypto.Party +import net.corda.core.crypto.appendToCommonName import net.corda.core.crypto.commonName import net.corda.core.div import net.corda.core.getOrThrow @@ -21,8 +22,6 @@ import net.corda.node.utilities.ServiceIdentityGenerator import net.corda.node.utilities.transaction import net.corda.testing.node.NodeBasedTest import org.bouncycastle.asn1.x500.X500Name -import org.bouncycastle.asn1.x500.X500NameBuilder -import org.bouncycastle.asn1.x500.style.BCStyle import org.junit.Test import java.security.KeyPair import java.util.* @@ -34,15 +33,7 @@ class BFTNotaryServiceTests : NodeBasedTest() { val notaryCommonName = X500Name("CN=BFT Notary Server,O=R3,OU=corda,L=Zurich,C=CH") fun buildNodeName(it: Int, notaryName: X500Name): X500Name { - val builder = X500NameBuilder() - notaryName.rdNs.map { it.first }.forEach { attr -> - if (attr.type == BCStyle.CN) { - builder.addRDN(BCStyle.CN, "${attr.value}-$it") - } else { - builder.addRDN(attr) - } - } - return builder.build() + return notaryName.appendToCommonName("-$it") } } diff --git a/node/src/main/kotlin/net/corda/node/driver/Driver.kt b/node/src/main/kotlin/net/corda/node/driver/Driver.kt index ce2e5953af..17d31f227d 100644 --- a/node/src/main/kotlin/net/corda/node/driver/Driver.kt +++ b/node/src/main/kotlin/net/corda/node/driver/Driver.kt @@ -10,6 +10,7 @@ import net.corda.client.rpc.CordaRPCClient import net.corda.core.* import net.corda.core.crypto.Party import net.corda.core.crypto.X509Utilities +import net.corda.core.crypto.appendToCommonName import net.corda.core.crypto.commonName import net.corda.core.messaging.CordaRPCOps import net.corda.core.node.NodeInfo @@ -30,8 +31,6 @@ import net.corda.nodeapi.config.parseAs import okhttp3.OkHttpClient import okhttp3.Request import org.bouncycastle.asn1.x500.X500Name -import org.bouncycastle.asn1.x500.X500NameBuilder -import org.bouncycastle.asn1.x500.style.BCStyle import org.slf4j.Logger import java.io.File import java.net.* @@ -503,16 +502,7 @@ class DriverDSL( verifierType: VerifierType, rpcUsers: List ): ListenableFuture>> { - val nodeNames = (1..clusterSize).map { - val nameBuilder = X500NameBuilder(BCStyle.INSTANCE) - nameBuilder.addRDN(BCStyle.CN, "${DUMMY_NOTARY.name.commonName} $it") - DUMMY_NOTARY.name.rdNs.forEach { rdn -> - if (rdn.first.type != BCStyle.CN) { - nameBuilder.addRDN(rdn.first) - } - } - nameBuilder.build() - } + val nodeNames = (1..clusterSize).map { DUMMY_NOTARY.name.appendToCommonName(it.toString()) } val paths = nodeNames.map { driverDirectory / it.commonName } ServiceIdentityGenerator.generateToDisk(paths, type.id, notaryName) diff --git a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt index 58c01cab68..be2658969f 100644 --- a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt @@ -11,6 +11,7 @@ import net.corda.core.contracts.PartyAndReference import net.corda.core.crypto.KeyStoreUtilities import net.corda.core.crypto.Party import net.corda.core.crypto.X509Utilities +import net.corda.core.crypto.replaceCommonName import net.corda.core.flows.FlowInitiator import net.corda.core.flows.FlowLogic import net.corda.core.flows.FlowVersion @@ -339,7 +340,7 @@ abstract class AbstractNode(open val configuration: NodeConfiguration, protected open fun makeServiceEntries(): List { return advertisedServices.map { val serviceId = it.type.id - val serviceName = it.name ?: X500Name("CN=$serviceId,${configuration.myLegalName}") + val serviceName = it.name ?: configuration.myLegalName.replaceCommonName(serviceId) val identity = obtainKeyPair(configuration.baseDirectory, serviceId + "-private-key", serviceId + "-public", serviceName).first ServiceEntry(it, identity) }