From a500084d385fd43eb78dd00c4640b48d486fca6e Mon Sep 17 00:00:00 2001 From: Oliver Knowles Date: Mon, 20 Jul 2020 10:59:08 +0100 Subject: [PATCH] CORDA-3201 - Enforce separate key for notary identity (#6308) --- detekt-baseline.xml | 2 +- .../net/corda/node/internal/AbstractNode.kt | 23 ++- .../node/services/config/NodeConfiguration.kt | 2 +- .../registration/NetworkRegistrationHelper.kt | 153 ++++++++++++------ .../NetworkRegistrationHelperTest.kt | 116 +++++++++++-- samples/attachment-demo/build.gradle | 6 +- samples/bank-of-corda-demo/build.gradle | 6 +- samples/cordapp-configuration/build.gradle | 6 +- samples/irs-demo/cordapp/build.gradle | 10 +- samples/network-verifier/build.gradle | 6 +- samples/notary-demo/build.gradle | 11 +- samples/simm-valuation-demo/build.gradle | 6 +- samples/trader-demo/build.gradle | 6 +- 13 files changed, 263 insertions(+), 90 deletions(-) diff --git a/detekt-baseline.xml b/detekt-baseline.xml index 8e72535cd4..974e679f57 100644 --- a/detekt-baseline.xml +++ b/detekt-baseline.xml @@ -1435,7 +1435,7 @@ ThrowsCount:JarScanningCordappLoader.kt$JarScanningCordappLoader$private fun parseVersion(versionStr: String?, attributeName: String): Int ThrowsCount:LedgerDSLInterpreter.kt$Verifies$ fun failsWith(expectedMessage: String?): EnforceVerifyOrFail ThrowsCount:MockServices.kt$ fun <T : SerializeAsToken> createMockCordaService(serviceHub: MockServices, serviceConstructor: (AppServiceHub) -> T): T - ThrowsCount:NetworkRegistrationHelper.kt$NetworkRegistrationHelper$private fun validateCertificates(registeringPublicKey: PublicKey, certificates: List<X509Certificate>) + ThrowsCount:NetworkRegistrationHelper.kt$NetworkRegistrationHelper$private fun validateCertificates( registeringPublicKey: PublicKey, registeringLegalName: CordaX500Name, expectedCertRole: CertRole, certificates: List<X509Certificate> ) ThrowsCount:NodeInfoFilesCopier.kt$NodeInfoFilesCopier$private fun atomicCopy(source: Path, destination: Path) ThrowsCount:NodeVaultService.kt$NodeVaultService$@Throws(VaultQueryException::class) private fun <T : ContractState> _queryBy(criteria: QueryCriteria, paging_: PageSpecification, sorting: Sort, contractStateType: Class<out T>, skipPagingChecks: Boolean): Vault.Page<T> ThrowsCount:NodeVaultService.kt$NodeVaultService$private fun makeUpdates(batch: Iterable<CoreTransaction>, statesToRecord: StatesToRecord, previouslySeen: Boolean): List<Vault.Update<ContractState>> 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 c115fd45a4..59b6b4fca7 100644 --- a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt @@ -614,11 +614,22 @@ abstract class AbstractNode(val configuration: NodeConfiguration, val myNotaryIdentity = configuration.notary?.let { if (it.serviceLegalName != null) { - val (notaryIdentity, notaryIdentityKeyPair) = loadNotaryClusterIdentity(it.serviceLegalName) + val (notaryIdentity, notaryIdentityKeyPair) = loadNotaryServiceIdentity(it.serviceLegalName) keyPairs += notaryIdentityKeyPair notaryIdentity } else { - // In case of a single notary service myNotaryIdentity will be the node's single identity. + // The only case where the myNotaryIdentity will be the node's legal identity is for existing single notary services running + // an older version. Current single notary services (V4.6+) sign requests using a separate notary service identity so the + // notary identity will be different from the node's legal identity. + + // This check is here to ensure that a user does not accidentally/intentionally remove the serviceLegalName configuration + // parameter after a notary has been registered. If that was possible then notary would start and sign incoming requests + // with the node's legal identity key, corrupting the data. + check (!cryptoService.containsKey(DISTRIBUTED_NOTARY_KEY_ALIAS)) { + "The notary service key exists in the key store but no notary service legal name has been configured. " + + "Either include the relevant 'notary.serviceLegalName' configuration or validate this key is not necessary " + + "and remove from the key store." + } identity } } @@ -1057,8 +1068,12 @@ abstract class AbstractNode(val configuration: NodeConfiguration, } } - /** Loads pre-generated notary service cluster identity. */ - private fun loadNotaryClusterIdentity(serviceLegalName: CordaX500Name): Pair { + /** + * Loads notary service identity. In the case of the experimental RAFT and BFT notary clusters, this loads the pre-generated + * cluster identity that all worker nodes share. In the case of a simple single notary, this loads the notary service identity + * that is generated during initial registration and is used to sign notarisation requests. + * */ + private fun loadNotaryServiceIdentity(serviceLegalName: CordaX500Name): Pair { val privateKeyAlias = "$DISTRIBUTED_NOTARY_KEY_ALIAS" val compositeKeyAlias = "$DISTRIBUTED_NOTARY_COMPOSITE_KEY_ALIAS" diff --git a/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt b/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt index f2dc3f16cb..a12989e169 100644 --- a/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt +++ b/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt @@ -151,7 +151,7 @@ fun NodeConfiguration.shouldInitCrashShell() = shouldStartLocalShell() || should data class NotaryConfig( /** Specifies whether the notary validates transactions or not. */ val validating: Boolean, - /** The legal name of cluster in case of a distributed notary service. */ + /** The legal name of the notary service identity. */ val serviceLegalName: CordaX500Name? = null, /** The name of the notary service class to load. */ val className: String? = null, diff --git a/node/src/main/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelper.kt b/node/src/main/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelper.kt index 45aa089f9e..8d2558ca8e 100644 --- a/node/src/main/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelper.kt +++ b/node/src/main/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelper.kt @@ -25,10 +25,10 @@ import org.bouncycastle.operator.ContentSigner import org.bouncycastle.util.io.pem.PemObject import java.io.IOException import java.io.StringWriter +import java.lang.IllegalStateException import java.net.ConnectException import java.net.URL import java.nio.file.Path -import java.security.KeyPair import java.security.PublicKey import java.security.cert.X509Certificate import java.time.Duration @@ -63,6 +63,7 @@ open class NetworkRegistrationHelper( private val requestIdStore = certificatesDirectory / "certificate-request-id.txt" protected val rootTrustStore: X509KeyStore protected val rootCert: X509Certificate + private val notaryServiceConfig: NotaryServiceConfig? = config.notaryServiceConfig init { require(networkRootTrustStorePath.exists()) { @@ -95,34 +96,70 @@ open class NetworkRegistrationHelper( return } + notaryServiceConfig?.let { validateNotaryServiceKeyAndCert(certStore, it.notaryServiceKeyAlias, it.notaryServiceLegalName) } + val tlsCrlIssuerCert = getTlsCrlIssuerCert() // We use SELF_SIGNED_PRIVATE_KEY as progress indicator so we just store a dummy key and cert. // When registration succeeds, this entry should be deleted. certStore.query { setPrivateKey(SELF_SIGNED_PRIVATE_KEY, AliasPrivateKey(SELF_SIGNED_PRIVATE_KEY), listOf(NOT_YET_REGISTERED_MARKER_KEYS_AND_CERTS.ECDSAR1_CERT), certificateStore.entryPassword) } - val nodeCaPublicKey = loadOrGenerateKeyPair() + val (entityPublicKey, receivedCertificates) = generateKeyPairAndCertificate(nodeCaKeyAlias, myLegalName, certRole, certStore) - val requestId = submitOrResumeCertificateSigningRequest(nodeCaPublicKey, cryptoService.getSigner(nodeCaKeyAlias)) - - val nodeCaCertificates = pollServerForCertificates(requestId) - validateCertificates(nodeCaPublicKey, nodeCaCertificates) - - certStore.setCertPathOnly(nodeCaKeyAlias, nodeCaCertificates) - certStore.value.internal.deleteEntry(SELF_SIGNED_PRIVATE_KEY) - certStore.value.save() - logProgress("Private key '$nodeCaKeyAlias' and its certificate-chain stored successfully.") - - onSuccess(nodeCaPublicKey, cryptoService.getSigner(nodeCaKeyAlias), nodeCaCertificates, tlsCrlIssuerCert?.subjectX500Principal?.toX500Name()) + onSuccess(entityPublicKey, cryptoService.getSigner(nodeCaKeyAlias), receivedCertificates, tlsCrlIssuerCert?.subjectX500Principal?.toX500Name()) // All done, clean up temp files. requestIdStore.deleteIfExists() } - private fun loadOrGenerateKeyPair(): PublicKey { - return if (cryptoService.containsKey(nodeCaKeyAlias)) { - cryptoService.getPublicKey(nodeCaKeyAlias)!! + private fun generateKeyPairAndCertificate(keyAlias: String, legalName: CordaX500Name, certificateRole: CertRole, certStore: CertificateStore): Pair> { + val entityPublicKey = loadOrGenerateKeyPair(keyAlias) + + val requestId = submitOrResumeCertificateSigningRequest(entityPublicKey, legalName, certificateRole, cryptoService.getSigner(keyAlias)) + + val receivedCertificates = pollServerForCertificates(requestId) + validateCertificates(entityPublicKey, legalName, certificateRole, receivedCertificates) + + certStore.setCertPathOnly(keyAlias, receivedCertificates) + certStore.value.internal.deleteEntry(SELF_SIGNED_PRIVATE_KEY) + certStore.value.save() + logProgress("Private key '$keyAlias' and its certificate-chain stored successfully.") + return Pair(entityPublicKey, receivedCertificates) + } + + /** + * Used when registering a notary to validate that the shared notary service key and certificate can be accessed. + * + * In the case that the notary service certificate and key is not available, a new key key is generated and a separate CSR is + * submitted to the Identity Manager. + * + * If this method successfully completes then the [cryptoService] will contain the notary service key and the [certStore] will contain + * the notary service certificate chain. + * + * @throws IllegalStateException If the notary service certificate already exists but the private key is not available. + */ + private fun validateNotaryServiceKeyAndCert(certStore: CertificateStore, notaryServiceKeyAlias: String, notaryServiceLegalName: CordaX500Name) { + if (certStore.contains(notaryServiceKeyAlias) && !cryptoService.containsKey(notaryServiceKeyAlias)) { + throw IllegalStateException("Notary service identity certificate exists but key pair missing. " + + "Please check no old certificates exist in the certificate store.") + } + + if (certStore.contains(notaryServiceKeyAlias)) { + logProgress("Notary service certificate already exists. Continuing with node registration...") + return + } + + logProgress("Generating notary service identity for $notaryServiceLegalName...") + generateKeyPairAndCertificate(notaryServiceKeyAlias, notaryServiceLegalName, CertRole.SERVICE_IDENTITY, certStore) + // The request id store is reused for the next step - registering the node identity. + // Therefore we can remove this to enable it to be reused. + requestIdStore.deleteIfExists() + } + + private fun loadOrGenerateKeyPair(keyAlias: String): PublicKey { + return if (cryptoService.containsKey(keyAlias)) { + cryptoService.getPublicKey(keyAlias)!! } else { - cryptoService.generateKeyPair(nodeCaKeyAlias, cryptoService.defaultTLSSignatureScheme()) + cryptoService.generateKeyPair(keyAlias, cryptoService.defaultTLSSignatureScheme()) } } @@ -137,26 +174,31 @@ open class NetworkRegistrationHelper( return tlsCrlIssuerCert } - private fun validateCertificates(registeringPublicKey: PublicKey, certificates: List) { - val nodeCACertificate = certificates.first() + private fun validateCertificates( + registeringPublicKey: PublicKey, + registeringLegalName: CordaX500Name, + expectedCertRole: CertRole, + certificates: List + ) { + val receivedCertificate = certificates.first() - val nodeCaSubject = try { - CordaX500Name.build(nodeCACertificate.subjectX500Principal) + val certificateSubject = try { + CordaX500Name.build(receivedCertificate.subjectX500Principal) } catch (e: IllegalArgumentException) { - throw CertificateRequestException("Received node CA cert has invalid subject name: ${e.message}") + throw CertificateRequestException("Received cert has invalid subject name: ${e.message}") } - if (nodeCaSubject != myLegalName) { - throw CertificateRequestException("Subject of received node CA cert doesn't match with node legal name: $nodeCaSubject") + if (certificateSubject != registeringLegalName) { + throw CertificateRequestException("Subject of received cert doesn't match with legal name: $certificateSubject") } - val nodeCaCertRole = try { - CertRole.extract(nodeCACertificate) + val receivedCertRole = try { + CertRole.extract(receivedCertificate) } catch (e: IllegalArgumentException) { - throw CertificateRequestException("Unable to extract cert role from received node CA cert: ${e.message}") + throw CertificateRequestException("Unable to extract cert role from received cert: ${e.message}") } - if (certRole != nodeCaCertRole) { - throw CertificateRequestException("Received certificate contains invalid cert role, expected '$certRole', got '$nodeCaCertRole'.") + if (expectedCertRole != receivedCertRole) { + throw CertificateRequestException("Received certificate contains invalid cert role, expected '$expectedCertRole', got '$receivedCertRole'.") } // Validate returned certificate is for the correct public key. @@ -169,22 +211,6 @@ open class NetworkRegistrationHelper( logProgress("Certificate signing request approved, storing private key with the certificate chain.") } - private fun CertificateStore.loadOrCreateKeyPair(alias: String, entryPassword: String = password): KeyPair { - // Create or load self signed keypair from the key store. - // We use the self sign certificate to store the key temporarily in the keystore while waiting for the request approval. - if (alias !in this) { - // NODE_CA should be TLS compatible due to the cert hierarchy structure. - val keyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) - val selfSignCert = X509Utilities.createSelfSignedCACertificate(myLegalName.x500Principal, keyPair) - // Save to the key store. - with(value) { - setPrivateKey(alias, keyPair.private, listOf(selfSignCert), keyPassword = entryPassword) - save() - } - } - return query { getCertificateAndKeyPair(alias, entryPassword) }.keyPair - } - /** * Poll Certificate Signing Server for approved certificate, * enter a slow polling loop if server return null. @@ -226,20 +252,27 @@ open class NetworkRegistrationHelper( * Submit Certificate Signing Request to Certificate signing service if request ID not found in file system. * New request ID will be stored in requestId.txt * @param publicKey public key for which we need a certificate. + * @param legalName legal name of the entity for which we need a certificate. + * @param certRole desired role of the entities certificate. * @param contentSigner the [ContentSigner] that will sign the CSR. * @return Request ID return from the server. */ - private fun submitOrResumeCertificateSigningRequest(publicKey: PublicKey, contentSigner: ContentSigner): String { + private fun submitOrResumeCertificateSigningRequest( + publicKey: PublicKey, + legalName: CordaX500Name, + certRole: CertRole, + contentSigner: ContentSigner + ): String { try { // Retrieve request id from file if exists, else post a request to server. return if (!requestIdStore.exists()) { - val request = X509Utilities.createCertificateSigningRequest(myLegalName.x500Principal, emailAddress, publicKey, contentSigner, certRole) + val request = X509Utilities.createCertificateSigningRequest(legalName.x500Principal, emailAddress, publicKey, contentSigner, certRole) val writer = StringWriter() JcaPEMWriter(writer).use { it.writeObject(PemObject("CERTIFICATE REQUEST", request.encoded)) } logProgress("Certificate signing request with the following information will be submitted to the Corda certificate signing server.") - logProgress("Legal Name: $myLegalName") + logProgress("Legal Name: $legalName") logProgress("Email: $emailAddress") logProgress("Public Key: $publicKey") logProgress("$writer") @@ -277,7 +310,8 @@ class NodeRegistrationConfiguration( val certificatesDirectory: Path, val emailAddress: String, val cryptoService: CryptoService, - val certificateStore: CertificateStore) { + val certificateStore: CertificateStore, + val notaryServiceConfig: NotaryServiceConfig? = null) { constructor(config: NodeConfiguration) : this( p2pSslOptions = config.p2pSslOptions, @@ -287,10 +321,29 @@ class NodeRegistrationConfiguration( certificatesDirectory = config.certificatesDirectory, emailAddress = config.emailAddress, cryptoService = BCCryptoService(config.myLegalName.x500Principal, config.signingCertificateStore), - certificateStore = config.signingCertificateStore.get(true) + certificateStore = config.signingCertificateStore.get(true), + notaryServiceConfig = config.notary?.let { + // Validation of the presence of the notary service legal name is only done here and not in the top level configuration + // file. This is to maintain backwards compatibility with older notaries using the legacy identity structure. Older + // notaries will be signing requests using the nodes legal identity key and therefore no separate notary service entity + // exists. Just having the validation here prevents any new notaries from being created with the legacy identity scheme + // but still allows drop in JAR replacements for old notaries. + requireNotNull(it.serviceLegalName) { + "The notary service legal name must be provided via the 'notary.serviceLegalName' configuration parameter" + } + require(it.serviceLegalName != config.myLegalName) { + "The notary service legal name must be different from the node legal name" + } + NotaryServiceConfig(X509Utilities.DISTRIBUTED_NOTARY_KEY_ALIAS, it.serviceLegalName!!) + } ) } +data class NotaryServiceConfig( + val notaryServiceKeyAlias: String, + val notaryServiceLegalName: CordaX500Name +) + class NodeRegistrationException( message: String?, cause: Throwable? diff --git a/node/src/test/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelperTest.kt b/node/src/test/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelperTest.kt index dde5082f6b..b17b437fad 100644 --- a/node/src/test/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelperTest.kt +++ b/node/src/test/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelperTest.kt @@ -28,6 +28,8 @@ import net.corda.testing.core.ALICE_NAME import net.corda.testing.internal.createDevIntermediateCaCertPath import net.corda.coretesting.internal.rigorousMock import net.corda.coretesting.internal.stubs.CertificateStoreStubs +import net.corda.node.services.config.NotaryConfig +import net.corda.testing.core.DUMMY_NOTARY_NAME import org.assertj.core.api.Assertions.* import org.bouncycastle.asn1.x509.GeneralName import org.bouncycastle.asn1.x509.GeneralSubtree @@ -37,6 +39,7 @@ import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest import org.junit.After import org.junit.Before import org.junit.Test +import java.lang.IllegalStateException import java.nio.file.Files import java.security.PublicKey import java.security.cert.CertPathValidatorException @@ -71,6 +74,7 @@ class NetworkRegistrationHelperTest { doReturn(null).whenever(it).tlsCertCrlDistPoint doReturn(null).whenever(it).tlsCertCrlIssuer doReturn(true).whenever(it).crlCheckSoftFail + doReturn(null).whenever(it).notary } } @@ -120,7 +124,7 @@ class NetworkRegistrationHelperTest { @Test(timeout=300_000) fun `missing truststore`() { - val nodeCaCertPath = createNodeCaCertPath() + val nodeCaCertPath = createCertPath() assertThatThrownBy { createFixedResponseRegistrationHelper(nodeCaCertPath) }.hasMessageContaining("This file must contain the root CA cert of your compatibility zone. Please contact your CZ operator.") @@ -128,7 +132,7 @@ class NetworkRegistrationHelperTest { @Test(timeout=300_000) fun `node CA with incorrect cert role`() { - val nodeCaCertPath = createNodeCaCertPath(type = CertificateType.TLS) + val nodeCaCertPath = createCertPath(type = CertificateType.TLS) saveNetworkTrustStore(CORDA_ROOT_CA to nodeCaCertPath.last()) val registrationHelper = createFixedResponseRegistrationHelper(nodeCaCertPath) assertThatExceptionOfType(CertificateRequestException::class.java) @@ -139,7 +143,7 @@ class NetworkRegistrationHelperTest { @Test(timeout=300_000) fun `node CA with incorrect subject`() { val invalidName = CordaX500Name("Foo", "MU", "GB") - val nodeCaCertPath = createNodeCaCertPath(legalName = invalidName) + val nodeCaCertPath = createCertPath(legalName = invalidName) saveNetworkTrustStore(CORDA_ROOT_CA to nodeCaCertPath.last()) val registrationHelper = createFixedResponseRegistrationHelper(nodeCaCertPath) assertThatExceptionOfType(CertificateRequestException::class.java) @@ -220,36 +224,118 @@ class NetworkRegistrationHelperTest { createRegistrationHelper(rootAndIntermediateCA = rootAndIntermediateCA).generateKeysAndRegister() } - private fun createNodeCaCertPath(type: CertificateType = CertificateType.NODE_CA, - legalName: CordaX500Name = nodeLegalName, - publicKey: PublicKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME).public, - rootAndIntermediateCA: Pair = createDevIntermediateCaCertPath()): List { + @Test(timeout=300_000) + fun `successful registration for notary node`() { + val notaryServiceLegalName = DUMMY_NOTARY_NAME + val notaryNodeConfig = createNotaryNodeConfiguration(notaryServiceLegalName = notaryServiceLegalName) + assertThat(notaryNodeConfig.notary).isNotNull + + val rootAndIntermediateCA = createDevIntermediateCaCertPath().also { + saveNetworkTrustStore(CORDA_ROOT_CA to it.first.certificate) + } + + // Mock out the registration service to ensure notary service registration is handled correctly + createRegistrationHelper(CertRole.NODE_CA, notaryNodeConfig) { + when { + it.subject == nodeLegalName.toX500Name() -> { + val certType = CertificateType.values().first { it.role == CertRole.NODE_CA } + createCertPath(rootAndIntermediateCA = rootAndIntermediateCA, publicKey = it.publicKey, type = certType) + } + it.subject == notaryServiceLegalName.toX500Name() -> { + val certType = CertificateType.values().first { it.role == CertRole.SERVICE_IDENTITY } + createCertPath(rootAndIntermediateCA = rootAndIntermediateCA, publicKey = it.publicKey, type = certType, legalName = notaryServiceLegalName) + } + else -> throw IllegalStateException("Unknown CSR") + } + }.generateKeysAndRegister() + + val nodeKeystore = config.signingCertificateStore.get() + + nodeKeystore.run { + assertFalse(contains(X509Utilities.CORDA_INTERMEDIATE_CA)) + assertFalse(contains(CORDA_ROOT_CA)) + assertFalse(contains(X509Utilities.CORDA_CLIENT_TLS)) + assertThat(CertRole.extract(this[X509Utilities.CORDA_CLIENT_CA])).isEqualTo(CertRole.NODE_CA) + assertThat(CertRole.extract(this[DISTRIBUTED_NOTARY_KEY_ALIAS])).isEqualTo(CertRole.SERVICE_IDENTITY) + } + } + + @Test(timeout=300_000) + fun `notary registration fails when no separate notary service identity configured`() { + val notaryNodeConfig = createNotaryNodeConfiguration(notaryServiceLegalName = null) + assertThat(notaryNodeConfig.notary).isNotNull + + assertThatThrownBy { + createRegistrationHelper(nodeConfig = notaryNodeConfig) + }.isInstanceOf(IllegalArgumentException::class.java) + .hasMessageContaining("notary service legal name must be provided") + } + + @Test(timeout=300_000) + fun `notary registration fails when notary service identity configured with same legal name as node`() { + val notaryNodeConfig = createNotaryNodeConfiguration(notaryServiceLegalName = config.myLegalName) + assertThat(notaryNodeConfig.notary).isNotNull + + assertThatThrownBy { + createRegistrationHelper(nodeConfig = notaryNodeConfig) + }.isInstanceOf(IllegalArgumentException::class.java) + .hasMessageContaining("notary service legal name must be different from the node") + } + + private fun createNotaryNodeConfiguration(notaryServiceLegalName: CordaX500Name?): NodeConfiguration { + return rigorousMock().also { + doReturn(config.baseDirectory).whenever(it).baseDirectory + doReturn(config.certificatesDirectory).whenever(it).certificatesDirectory + doReturn(CertificateStoreStubs.P2P.withCertificatesDirectory(config.certificatesDirectory)).whenever(it).p2pSslOptions + doReturn(CertificateStoreStubs.Signing.withCertificatesDirectory(config.certificatesDirectory)).whenever(it) + .signingCertificateStore + doReturn(nodeLegalName).whenever(it).myLegalName + doReturn("").whenever(it).emailAddress + doReturn(null).whenever(it).tlsCertCrlDistPoint + doReturn(null).whenever(it).tlsCertCrlIssuer + doReturn(true).whenever(it).crlCheckSoftFail + doReturn(NotaryConfig(validating = false, serviceLegalName = notaryServiceLegalName)).whenever(it).notary + } + } + + private fun createCertPath(type: CertificateType = CertificateType.NODE_CA, + legalName: CordaX500Name = nodeLegalName, + publicKey: PublicKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME).public, + rootAndIntermediateCA: Pair = createDevIntermediateCaCertPath()): List { val (rootCa, intermediateCa) = rootAndIntermediateCA val nameConstraints = if (type == CertificateType.NODE_CA) { NameConstraints(arrayOf(GeneralSubtree(GeneralName(GeneralName.directoryName, legalName.toX500Name()))), arrayOf()) } else { null } - val nodeCaCert = X509Utilities.createCertificate( + val cert = X509Utilities.createCertificate( type, intermediateCa.certificate, intermediateCa.keyPair, legalName.x500Principal, publicKey, nameConstraints = nameConstraints) - return listOf(nodeCaCert, intermediateCa.certificate, rootCa.certificate) + return listOf(cert, intermediateCa.certificate, rootCa.certificate) } private fun createFixedResponseRegistrationHelper(response: List, certRole: CertRole = CertRole.NODE_CA): NetworkRegistrationHelper { return createRegistrationHelper(certRole) { response } } - private fun createRegistrationHelper(certRole: CertRole = CertRole.NODE_CA, rootAndIntermediateCA: Pair = createDevIntermediateCaCertPath()) = createRegistrationHelper(certRole) { + private fun createRegistrationHelper( + certRole: CertRole = CertRole.NODE_CA, + rootAndIntermediateCA: Pair = createDevIntermediateCaCertPath(), + nodeConfig: NodeConfiguration = config + ) = createRegistrationHelper(certRole, nodeConfig) { val certType = CertificateType.values().first { it.role == certRole } - createNodeCaCertPath(rootAndIntermediateCA = rootAndIntermediateCA, publicKey = it.publicKey, type = certType) + createCertPath(rootAndIntermediateCA = rootAndIntermediateCA, publicKey = it.publicKey, type = certType) } - private fun createRegistrationHelper(certRole: CertRole = CertRole.NODE_CA, dynamicResponse: (JcaPKCS10CertificationRequest) -> List): NetworkRegistrationHelper { + private fun createRegistrationHelper( + certRole: CertRole = CertRole.NODE_CA, + nodeConfig: NodeConfiguration = config, + dynamicResponse: (JcaPKCS10CertificationRequest) -> List + ): NetworkRegistrationHelper { val certService = rigorousMock().also { val requests = mutableMapOf() doAnswer { @@ -265,11 +351,11 @@ class NetworkRegistrationHelperTest { } return when (certRole) { - CertRole.NODE_CA -> NodeRegistrationHelper(NodeRegistrationConfiguration(config), certService, NodeRegistrationOption(config.certificatesDirectory / networkRootTrustStoreFileName, networkRootTrustStorePassword)) + CertRole.NODE_CA -> NodeRegistrationHelper(NodeRegistrationConfiguration(nodeConfig), certService, NodeRegistrationOption(nodeConfig.certificatesDirectory / networkRootTrustStoreFileName, networkRootTrustStorePassword)) CertRole.SERVICE_IDENTITY -> NetworkRegistrationHelper( - NodeRegistrationConfiguration(config), + NodeRegistrationConfiguration(nodeConfig), certService, - config.certificatesDirectory / networkRootTrustStoreFileName, + nodeConfig.certificatesDirectory / networkRootTrustStoreFileName, networkRootTrustStorePassword, DISTRIBUTED_NOTARY_KEY_ALIAS, CertRole.SERVICE_IDENTITY) diff --git a/samples/attachment-demo/build.gradle b/samples/attachment-demo/build.gradle index e88c8fc431..43bb2c8cbd 100644 --- a/samples/attachment-demo/build.gradle +++ b/samples/attachment-demo/build.gradle @@ -92,8 +92,10 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar', nodeTask, cordapp project(':samples:attachment-demo:workflows') } node { - name "O=Notary Service,L=Zurich,C=CH" - notary = [validating: true] + name "O=Notary Node,L=Zurich,C=CH" + notary = [validating: true, + serviceLegalName: "O=Notary Service,L=Zurich,C=CH" + ] p2pPort 10002 cordapps = [] rpcUsers = ext.rpcUsers diff --git a/samples/bank-of-corda-demo/build.gradle b/samples/bank-of-corda-demo/build.gradle index e3ff1ad5c3..7749ba3cdd 100644 --- a/samples/bank-of-corda-demo/build.gradle +++ b/samples/bank-of-corda-demo/build.gradle @@ -50,8 +50,10 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar', nodeTask, cordapp project(':finance:contracts') } node { - name "O=Notary Service,L=Zurich,C=CH" - notary = [validating: true] + name "O=Notary Node,L=Zurich,C=CH" + notary = [validating: true, + serviceLegalName: "O=Notary Service,L=Zurich,C=CH" + ] p2pPort 10002 rpcSettings { address "localhost:10003" diff --git a/samples/cordapp-configuration/build.gradle b/samples/cordapp-configuration/build.gradle index 9d466ed986..723de77b5b 100644 --- a/samples/cordapp-configuration/build.gradle +++ b/samples/cordapp-configuration/build.gradle @@ -27,8 +27,10 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar', nodeTask, cordapp project(':samples:cordapp-configuration:workflows') } node { - name "O=Notary Service,L=Zurich,C=CH" - notary = [validating : true] + name "O=Notary Node,L=Zurich,C=CH" + notary = [validating : true, + serviceLegalName: "O=Notary Service,L=Zurich,C=CH" + ] p2pPort 10002 rpcSettings { port 10003 diff --git a/samples/irs-demo/cordapp/build.gradle b/samples/irs-demo/cordapp/build.gradle index 50474dd3e5..2fa1ca9f49 100644 --- a/samples/irs-demo/cordapp/build.gradle +++ b/samples/irs-demo/cordapp/build.gradle @@ -62,8 +62,10 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar', nodeTask]) cordapp project(':samples:irs-demo:cordapp:workflows-irs') } node { - name "O=Notary Service,L=Zurich,C=CH" - notary = [validating : true] + name "O=Notary Node,L=Zurich,C=CH" + notary = [validating : true, + serviceLegalName: "O=Notary Service,L=Zurich,C=CH" + ] p2pPort 10002 rpcSettings { address("localhost:10003") @@ -121,7 +123,9 @@ task prepareDockerNodes(type: net.corda.plugins.Dockerform, dependsOn: ['jar', n } node { name "O=Notary Service,L=Zurich,C=CH" - notary = [validating : true] + notary = [validating : true, + serviceLegalName: "O=Notary Service,L=Zurich,C=CH" + ] cordapps = ["${project(":finance").group}:contracts:$corda_release_version", "${project(":finance").group}:workflows:$corda_release_version"] rpcUsers = rpcUsersList useTestClock true diff --git a/samples/network-verifier/build.gradle b/samples/network-verifier/build.gradle index f7582c0069..92a2006e81 100644 --- a/samples/network-verifier/build.gradle +++ b/samples/network-verifier/build.gradle @@ -38,8 +38,10 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar', nodeTask]) cordapp project(':samples:network-verifier:workflows') } node { - name "O=Notary Service,L=Zurich,C=CH" - notary = [validating : false] + name "O=Notary Node,L=Zurich,C=CH" + notary = [validating : false, + serviceLegalName: "O=Notary Service,L=Zurich,C=CH" + ] p2pPort 10002 rpcSettings { port 10003 diff --git a/samples/notary-demo/build.gradle b/samples/notary-demo/build.gradle index a5a7a40117..ff2683b2a2 100644 --- a/samples/notary-demo/build.gradle +++ b/samples/notary-demo/build.gradle @@ -55,13 +55,15 @@ task deployNodesSingle(type: Cordform, dependsOn: ['jar', nodeTask, webTask]) { rpcUsers = [[user: "demou", password: "demop", permissions: ["ALL"]]] } node { - name "O=Notary Service,L=Zurich,C=CH" + name "O=Notary Node,L=Zurich,C=CH" p2pPort 10009 rpcSettings { address "localhost:10010" adminAddress "localhost:10110" } - notary = [validating: true] + notary = [validating: true, + serviceLegalName: "O=Notary Service,L=Zurich,C=CH" + ] } } @@ -85,7 +87,7 @@ task deployNodesCustom(type: Cordform, dependsOn: ['jar', nodeTask, webTask]) { rpcUsers = [[user: "demou", password: "demop", permissions: ["ALL"]]] } node { - name "O=Notary Service,L=Zurich,C=CH" + name "O=Notary Node,L=Zurich,C=CH" p2pPort 10009 rpcSettings { address "localhost:10010" @@ -93,7 +95,8 @@ task deployNodesCustom(type: Cordform, dependsOn: ['jar', nodeTask, webTask]) { } notary = [ validating: true, - className: "net.corda.notarydemo.MyCustomValidatingNotaryService" + className: "net.corda.notarydemo.MyCustomValidatingNotaryService", + serviceLegalName: "O=Notary Service,L=Zurich,C=CH" ] } } diff --git a/samples/simm-valuation-demo/build.gradle b/samples/simm-valuation-demo/build.gradle index f95a10716b..b0af7c3568 100644 --- a/samples/simm-valuation-demo/build.gradle +++ b/samples/simm-valuation-demo/build.gradle @@ -93,8 +93,10 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar', nodeTask, rpcUsers = [['username': "default", 'password': "default", 'permissions': [ 'ALL' ]]] } node { - name "O=Notary Service,L=Zurich,C=CH" - notary = [validating : true] + name "O=Notary Node,L=Zurich,C=CH" + notary = [validating : true, + serviceLegalName: "O=Notary Service,L=Zurich,C=CH" + ] p2pPort 10002 rpcSettings { address "localhost:10014" diff --git a/samples/trader-demo/build.gradle b/samples/trader-demo/build.gradle index 5eeea06740..5ac022fb30 100644 --- a/samples/trader-demo/build.gradle +++ b/samples/trader-demo/build.gradle @@ -83,8 +83,10 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar', nodeTask]) cordapp project(':samples:trader-demo:workflows-trader') } node { - name "O=Notary Service,L=Zurich,C=CH" - notary = [validating : true] + name "O=Notary Node,L=Zurich,C=CH" + notary = [validating : true, + serviceLegalName: "O=Notary Service,L=Zurich,C=CH" + ] p2pPort 10002 rpcSettings { address "localhost:10003"