diff --git a/.ci/api-current.txt b/.ci/api-current.txt index 09b766e46d..5fd0e2262b 100644 --- a/.ci/api-current.txt +++ b/.ci/api-current.txt @@ -1862,7 +1862,7 @@ public @interface net.corda.core.node.services.CordaService @org.jetbrains.annotations.NotNull public static final String ID_PREFIX = "corda.notary." ## public static final class net.corda.core.node.services.NotaryService$Companion extends java.lang.Object - @kotlin.Deprecated @org.jetbrains.annotations.NotNull public final String constructId(boolean, boolean, boolean, boolean) + @org.jetbrains.annotations.NotNull public final String constructId(boolean, boolean, boolean, boolean) ## public abstract class net.corda.core.node.services.PartyInfo extends java.lang.Object @org.jetbrains.annotations.NotNull public abstract net.corda.core.identity.Party getParty() diff --git a/core/src/main/kotlin/net/corda/core/identity/CordaX500Name.kt b/core/src/main/kotlin/net/corda/core/identity/CordaX500Name.kt index ad315c065e..0b6a3ddeb7 100644 --- a/core/src/main/kotlin/net/corda/core/identity/CordaX500Name.kt +++ b/core/src/main/kotlin/net/corda/core/identity/CordaX500Name.kt @@ -2,7 +2,7 @@ package net.corda.core.identity import com.google.common.collect.ImmutableSet import net.corda.core.internal.LegalNameValidator -import net.corda.core.internal.unspecifiedCountry +import net.corda.core.internal.VisibleForTesting import net.corda.core.internal.x500Name import net.corda.core.serialization.CordaSerializable import org.bouncycastle.asn1.ASN1Encodable @@ -36,9 +36,7 @@ data class CordaX500Name(val commonName: String?, val locality: String, val state: String?, val country: String) { - constructor(commonName: String, organisation: String, locality: String, country: String) : - this(commonName = commonName, organisationUnit = null, organisation = organisation, locality = locality, state = null, country = country) - + constructor(commonName: String, organisation: String, locality: String, country: String) : this(commonName = commonName, organisationUnit = null, organisation = organisation, locality = locality, state = null, country = country) /** * @param organisation name of the organisation. * @param locality locality of the organisation, typically nearest major city. @@ -81,6 +79,8 @@ data class CordaX500Name(val commonName: String?, const val MAX_LENGTH_ORGANISATION_UNIT = 64 const val MAX_LENGTH_COMMON_NAME = 64 private val supportedAttributes = setOf(BCStyle.O, BCStyle.C, BCStyle.L, BCStyle.CN, BCStyle.ST, BCStyle.OU) + @VisibleForTesting + val unspecifiedCountry = "ZZ" private val countryCodes: Set = ImmutableSet.copyOf(Locale.getISOCountries() + unspecifiedCountry) @JvmStatic fun build(principal: X500Principal): CordaX500Name { diff --git a/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt b/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt index 42145a2f26..36022463ff 100644 --- a/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt +++ b/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt @@ -5,7 +5,6 @@ package net.corda.core.internal import net.corda.core.cordapp.CordappProvider import net.corda.core.crypto.SecureHash import net.corda.core.crypto.sha256 -import net.corda.core.identity.CordaX500Name import net.corda.core.node.ServicesForResolution import net.corda.core.serialization.SerializationContext import net.corda.core.transactions.TransactionBuilder @@ -119,8 +118,6 @@ fun Path.isDirectory(vararg options: LinkOption): Boolean = Files.isDirectory(th inline val Path.size: Long get() = Files.size(this) inline fun Path.list(block: (Stream) -> R): R = Files.list(this).use(block) fun Path.deleteIfExists(): Boolean = Files.deleteIfExists(this) -fun Path.reader(charset: Charset = UTF_8): BufferedReader = Files.newBufferedReader(this, charset) -fun Path.writer(charset: Charset = UTF_8, vararg options: OpenOption): BufferedWriter = Files.newBufferedWriter(this, charset, *options) fun Path.readAll(): ByteArray = Files.readAllBytes(this) inline fun Path.read(vararg options: OpenOption, block: (InputStream) -> R): R = Files.newInputStream(this, *options).use(block) inline fun Path.write(createDirs: Boolean = false, vararg options: OpenOption = emptyArray(), block: (OutputStream) -> Unit) { @@ -319,8 +316,3 @@ fun ExecutorService.join() { // Try forever. Do not give up, tests use this method to assert the executor has no more tasks. } } - -@Suppress("unused") -@VisibleForTesting -val CordaX500Name.Companion.unspecifiedCountry - get() = "ZZ" diff --git a/core/src/main/kotlin/net/corda/core/node/services/NotaryService.kt b/core/src/main/kotlin/net/corda/core/node/services/NotaryService.kt index 8c6a160618..0c928a7b59 100644 --- a/core/src/main/kotlin/net/corda/core/node/services/NotaryService.kt +++ b/core/src/main/kotlin/net/corda/core/node/services/NotaryService.kt @@ -15,16 +15,22 @@ import java.security.PublicKey abstract class NotaryService : SingletonSerializeAsToken() { companion object { - @Deprecated("No longer used") const val ID_PREFIX = "corda.notary." - @Deprecated("No longer used") - fun constructId(validating: Boolean, raft: Boolean = false, bft: Boolean = false, custom: Boolean = false): String { - require(Booleans.countTrue(raft, bft, custom) <= 1) { "At most one of raft, bft or custom may be true" } + @JvmOverloads + fun constructId( + validating: Boolean, + raft: Boolean = false, + bft: Boolean = false, + custom: Boolean = false, + mysql: Boolean = false + ): String { + require(Booleans.countTrue(raft, bft, custom, mysql) <= 1) { "At most one of raft, bft, mysql or custom may be true" } return StringBuffer(ID_PREFIX).apply { append(if (validating) "validating" else "simple") if (raft) append(".raft") if (bft) append(".bft") if (custom) append(".custom") + if (mysql) append(".mysql") }.toString() } } diff --git a/core/src/test/kotlin/net/corda/core/crypto/CompositeKeyTests.kt b/core/src/test/kotlin/net/corda/core/crypto/CompositeKeyTests.kt index 751037b1ee..734505498d 100644 --- a/core/src/test/kotlin/net/corda/core/crypto/CompositeKeyTests.kt +++ b/core/src/test/kotlin/net/corda/core/crypto/CompositeKeyTests.kt @@ -9,8 +9,8 @@ import net.corda.core.serialization.serialize import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.toBase58String import net.corda.nodeapi.internal.crypto.* -import net.corda.testing.SerializationEnvironmentRule import net.corda.testing.internal.kryoSpecific +import net.corda.testing.SerializationEnvironmentRule import org.junit.Rule import org.junit.Test import org.junit.rules.TemporaryFolder @@ -24,7 +24,6 @@ class CompositeKeyTests { @Rule @JvmField val testSerialization = SerializationEnvironmentRule() - @Rule @JvmField val tempFolder: TemporaryFolder = TemporaryFolder() @@ -41,9 +40,9 @@ class CompositeKeyTests { private val secureHash = message.sha256() // By lazy is required so that the serialisers are configured before vals initialisation takes place (they internally invoke serialise). - private val aliceSignature by lazy { aliceKey.sign(SignableData(secureHash, SignatureMetadata(1, Crypto.findSignatureScheme(alicePublicKey).schemeNumberID))) } - private val bobSignature by lazy { bobKey.sign(SignableData(secureHash, SignatureMetadata(1, Crypto.findSignatureScheme(bobPublicKey).schemeNumberID))) } - private val charlieSignature by lazy { charlieKey.sign(SignableData(secureHash, SignatureMetadata(1, Crypto.findSignatureScheme(charliePublicKey).schemeNumberID))) } + val aliceSignature by lazy { aliceKey.sign(SignableData(secureHash, SignatureMetadata(1, Crypto.findSignatureScheme(alicePublicKey).schemeNumberID))) } + val bobSignature by lazy { bobKey.sign(SignableData(secureHash, SignatureMetadata(1, Crypto.findSignatureScheme(bobPublicKey).schemeNumberID))) } + val charlieSignature by lazy { charlieKey.sign(SignableData(secureHash, SignatureMetadata(1, Crypto.findSignatureScheme(charliePublicKey).schemeNumberID))) } @Test fun `(Alice) fulfilled by Alice signature`() { @@ -338,7 +337,7 @@ class CompositeKeyTests { val ca = X509Utilities.createSelfSignedCACertificate(caName, caKeyPair) // Sign the composite key with the self sign CA. - val compositeKeyCert = X509Utilities.createCertificate(CertificateType.LEGAL_IDENTITY, ca, caKeyPair, caName, compositeKey) + val compositeKeyCert = X509Utilities.createCertificate(CertificateType.LEGAL_IDENTITY, ca, caKeyPair, caName.copy(commonName = "CompositeKey"), compositeKey) // Store certificate to keystore. val keystorePath = tempFolder.root.toPath() / "keystore.jks" diff --git a/core/src/test/kotlin/net/corda/core/serialization/AttachmentSerializationTest.kt b/core/src/test/kotlin/net/corda/core/serialization/AttachmentSerializationTest.kt index d72d7068d4..940d959860 100644 --- a/core/src/test/kotlin/net/corda/core/serialization/AttachmentSerializationTest.kt +++ b/core/src/test/kotlin/net/corda/core/serialization/AttachmentSerializationTest.kt @@ -159,7 +159,7 @@ class AttachmentSerializationTest { private fun rebootClientAndGetAttachmentContent(checkAttachmentsOnLoad: Boolean = true): String { client.dispose() - client = mockNet.createNode(MockNodeParameters(client.internals.id, client.internals.configuration.myLegalName), { args -> + client = mockNet.createNode(MockNodeParameters(client.internals.id), { args -> object : MockNetwork.MockNode(args) { override fun start() = super.start().apply { attachments.checkAttachmentsOnLoad = checkAttachmentsOnLoad } } diff --git a/docs/source/generating-a-node.rst b/docs/source/generating-a-node.rst index eee15b3d5d..4a721e304d 100644 --- a/docs/source/generating-a-node.rst +++ b/docs/source/generating-a-node.rst @@ -92,6 +92,7 @@ nodes. Here is an example ``Cordform`` task called ``deployNodes`` that creates } node { name "O=PartyA,L=London,C=GB" + advertisedServices = [] p2pPort 10005 rpcPort 10006 webPort 10007 @@ -102,6 +103,7 @@ nodes. Here is an example ``Cordform`` task called ``deployNodes`` that creates } node { name "O=PartyB,L=New York,C=US" + advertisedServices = [] p2pPort 10009 rpcPort 10010 webPort 10011 diff --git a/docs/source/hello-world-running.rst b/docs/source/hello-world-running.rst index 4ac394d26c..592cfee44c 100644 --- a/docs/source/hello-world-running.rst +++ b/docs/source/hello-world-running.rst @@ -22,7 +22,7 @@ service. directory "./build/nodes" node { name "O=Controller,L=London,C=GB" - notary = [validating : true] + advertisedServices = ["corda.notary.validating"] p2pPort 10002 rpcPort 10003 cordapps = ["net.corda:corda-finance:$corda_release_version"] diff --git a/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/doorman/DoormanIntegrationTest.kt b/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/doorman/DoormanIntegrationTest.kt index 5993cef210..fe919f6114 100644 --- a/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/doorman/DoormanIntegrationTest.kt +++ b/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/doorman/DoormanIntegrationTest.kt @@ -3,6 +3,7 @@ package com.r3.corda.networkmanage.doorman import com.nhaarman.mockito_kotlin.doReturn import com.nhaarman.mockito_kotlin.whenever import com.r3.corda.networkmanage.common.persistence.configureDatabase +import com.r3.corda.networkmanage.common.utils.buildCertPath import com.r3.corda.networkmanage.common.utils.toX509Certificate import com.r3.corda.networkmanage.doorman.signer.LocalSigner import net.corda.core.crypto.Crypto @@ -29,6 +30,7 @@ import net.corda.testing.SerializationEnvironmentRule import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.internal.rigorousMock import org.bouncycastle.cert.X509CertificateHolder +import org.junit.Ignore import org.junit.Rule import org.junit.Test import org.junit.rules.TemporaryFolder @@ -76,7 +78,7 @@ class DoormanIntegrationTest { loadKeyStore(config.nodeKeystore, config.keyStorePassword).apply { assert(containsAlias(X509Utilities.CORDA_CLIENT_CA)) - assertEquals(ALICE_NAME.x500Principal, getX509Certificate(X509Utilities.CORDA_CLIENT_CA).subjectX500Principal) + assertEquals(ALICE_NAME.copy(commonName = X509Utilities.CORDA_CLIENT_CA_CN).x500Principal, getX509Certificate(X509Utilities.CORDA_CLIENT_CA).subjectX500Principal) assertEquals(listOf(intermediateCACert.cert, rootCACert.cert), getCertificateChain(X509Utilities.CORDA_CLIENT_CA).drop(1).toList()) } @@ -118,18 +120,13 @@ class DoormanIntegrationTest { // Publish NodeInfo val networkMapClient = NetworkMapClient(config.compatibilityZoneURL!!, rootCertAndKey.certificate.cert) - - val keyStore = loadKeyStore(config.nodeKeystore, config.keyStorePassword) - val clientCertPath = keyStore.getCertificateChain(X509Utilities.CORDA_CLIENT_CA) - val clientCA = keyStore.getCertificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA, config.keyStorePassword) - val identityKeyPair = Crypto.generateKeyPair() - val identityCert = X509Utilities.createCertificate(CertificateType.LEGAL_IDENTITY, clientCA.certificate, clientCA.keyPair, ALICE_NAME, identityKeyPair.public) - val certPath = X509CertificateFactory().generateCertPath(identityCert.cert, *clientCertPath) - val nodeInfo = NodeInfo(listOf(NetworkHostAndPort("my.company.com", 1234)), listOf(PartyAndCertificate(certPath)), 1, serial = 1L) + val certs = loadKeyStore(config.nodeKeystore, config.keyStorePassword).getCertificateChain(X509Utilities.CORDA_CLIENT_CA) + val keyPair = loadKeyStore(config.nodeKeystore, config.keyStorePassword).getKeyPair(X509Utilities.CORDA_CLIENT_CA, config.keyStorePassword) + val nodeInfo = NodeInfo(listOf(NetworkHostAndPort("my.company.com", 1234)), listOf(PartyAndCertificate(buildCertPath(*certs))), 1, serial = 1L) val nodeInfoBytes = nodeInfo.serialize() // When - val signedNodeInfo = SignedNodeInfo(nodeInfoBytes, listOf(identityKeyPair.private.sign(nodeInfoBytes.bytes))) + val signedNodeInfo = SignedNodeInfo(nodeInfoBytes, listOf(keyPair.sign(nodeInfoBytes))) networkMapClient.publish(signedNodeInfo) // Then @@ -140,7 +137,7 @@ class DoormanIntegrationTest { doorman.close() } - private fun createConfig(): NodeConfiguration { + fun createConfig(): NodeConfiguration { return rigorousMock().also { doReturn(tempFolder.root.toPath()).whenever(it).baseDirectory doReturn(ALICE_NAME).whenever(it).myLegalName diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/PersistentNodeInfoStorage.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/PersistentNodeInfoStorage.kt index 1d4af91246..7f1d331877 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/PersistentNodeInfoStorage.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/PersistentNodeInfoStorage.kt @@ -1,60 +1,65 @@ package com.r3.corda.networkmanage.common.persistence -import com.r3.corda.networkmanage.common.persistence.entity.CertificateDataEntity -import com.r3.corda.networkmanage.common.persistence.entity.CertificateSigningRequestEntity -import com.r3.corda.networkmanage.common.persistence.entity.NodeInfoEntity +import com.r3.corda.networkmanage.common.persistence.entity.* import com.r3.corda.networkmanage.common.utils.buildCertPath import net.corda.core.crypto.SecureHash import net.corda.core.crypto.sha256 -import net.corda.core.internal.CertRole +import net.corda.core.identity.CordaX500Name +import net.corda.core.serialization.SerializedBytes import net.corda.core.serialization.serialize import net.corda.nodeapi.internal.SignedNodeInfo import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.TransactionIsolationLevel import java.security.cert.CertPath +import java.security.cert.X509Certificate /** * Database implementation of the [NetworkMapStorage] interface */ class PersistentNodeInfoStorage(private val database: CordaPersistence) : NodeInfoStorage { - override fun putNodeInfo(signedNodeInfo: SignedNodeInfo): SecureHash { - return database.transaction(TransactionIsolationLevel.SERIALIZABLE) { - val nodeInfo = signedNodeInfo.verified() - val nodeCaCert = nodeInfo.legalIdentitiesAndCerts[0].certPath.certificates.find { CertRole.extract(it) == CertRole.NODE_CA } + override fun putNodeInfo(signedNodeInfo: SignedNodeInfo): SecureHash = database.transaction(TransactionIsolationLevel.SERIALIZABLE) { + val nodeInfo = signedNodeInfo.verified() + val orgName = nodeInfo.legalIdentities.first().name.organisation + // TODO: use cert extension to identify NodeCA cert when Ross's work is in. + val nodeCACert = nodeInfo.legalIdentitiesAndCerts.first().certPath.certificates.map { it as X509Certificate } + .find { CordaX500Name.build(it.issuerX500Principal).organisation != orgName && CordaX500Name.build(it.subjectX500Principal).organisation == orgName } - val request = nodeCaCert?.let { - singleRequestWhere(CertificateDataEntity::class.java) { builder, path -> - val certPublicKeyHashEq = builder.equal(path.get(CertificateDataEntity::publicKeyHash.name), it.publicKey.encoded.sha256().toString()) - val certStatusValid = builder.equal(path.get(CertificateDataEntity::certificateStatus.name), CertificateStatus.VALID) - builder.and(certPublicKeyHashEq, certStatusValid) - } + val request = nodeCACert?.let { + singleRequestWhere(CertificateDataEntity::class.java) { builder, path -> + val certPublicKeyHashEq = builder.equal(path.get(CertificateDataEntity::publicKeyHash.name), it.publicKey.encoded.sha256().toString()) + val certStatusValid = builder.equal(path.get(CertificateDataEntity::certificateStatus.name), CertificateStatus.VALID) + builder.and(certPublicKeyHashEq, certStatusValid) } - request ?: throw IllegalArgumentException("Unknown node info, this public key is not registered with the network management service.") - - /* - * Delete any previous [NodeInfoEntity] instance for this CSR - * Possibly it should be moved at the network signing process at the network signing process - * as for a while the network map will have invalid entries (i.e. hashes for node info which have been - * removed). Either way, there will be a period of time when the network map data will be invalid - * but it has been confirmed that this fact has been acknowledged at the design time and we are fine with it. - */ - deleteRequest(NodeInfoEntity::class.java) { builder, path -> - builder.equal(path.get(NodeInfoEntity::certificateSigningRequest.name), request.certificateSigningRequest) - } - val hash = signedNodeInfo.raw.hash - - val hashedNodeInfo = NodeInfoEntity( - nodeInfoHash = hash.toString(), - certificateSigningRequest = request.certificateSigningRequest, - signedNodeInfoBytes = signedNodeInfo.serialize().bytes) - session.save(hashedNodeInfo) - hash } + request ?: throw IllegalArgumentException("Unknown node info, this public key is not registered with the network management service.") + /* + * Delete any previous [NodeInfoEntity] instance for this CSR + * Possibly it should be moved at the network signing process at the network signing process + * as for a while the network map will have invalid entries (i.e. hashes for node info which have been + * removed). Either way, there will be a period of time when the network map data will be invalid + * but it has been confirmed that this fact has been acknowledged at the design time and we are fine with it. + */ + deleteRequest(NodeInfoEntity::class.java) { builder, path -> + builder.equal(path.get(NodeInfoEntity::certificateSigningRequest.name), request.certificateSigningRequest) + } + val hash = signedNodeInfo.raw.hash + + val hashedNodeInfo = NodeInfoEntity( + nodeInfoHash = hash.toString(), + certificateSigningRequest = request.certificateSigningRequest, + signedNodeInfoBytes = signedNodeInfo.serialize().bytes) + session.save(hashedNodeInfo) + hash } override fun getNodeInfo(nodeInfoHash: SecureHash): SignedNodeInfo? { return database.transaction { - session.find(NodeInfoEntity::class.java, nodeInfoHash.toString())?.signedNodeInfo() + val nodeInfoEntity = session.find(NodeInfoEntity::class.java, nodeInfoHash.toString()) + if (nodeInfoEntity == null) { + null + } else { + nodeInfoEntity.signedNodeInfo() + } } } diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/signer/LocalSigner.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/signer/LocalSigner.kt index 50a7b7f971..cb6e253536 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/signer/LocalSigner.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/signer/LocalSigner.kt @@ -2,10 +2,10 @@ package com.r3.corda.networkmanage.doorman.signer import com.r3.corda.networkmanage.common.signer.Signer import com.r3.corda.networkmanage.common.utils.buildCertPath +import com.r3.corda.networkmanage.common.utils.toX509Certificate import com.r3.corda.networkmanage.common.utils.withCert import net.corda.core.crypto.sign import net.corda.core.identity.CordaX500Name -import net.corda.core.internal.cert import net.corda.core.internal.toX509CertHolder import net.corda.nodeapi.internal.crypto.CertificateType import net.corda.nodeapi.internal.crypto.X509Utilities @@ -33,14 +33,13 @@ class LocalSigner(private val caKeyPair: KeyPair, private val caCertPath: Array< val nameConstraints = NameConstraints( arrayOf(GeneralSubtree(GeneralName(GeneralName.directoryName, request.subject))), arrayOf()) - val nodeCaCert = X509Utilities.createCertificate( - CertificateType.NODE_CA, + val clientCertificate = X509Utilities.createCertificate(CertificateType.NODE_CA, caCertPath.first().toX509CertHolder(), caKeyPair, - CordaX500Name.parse(request.subject.toString()), + CordaX500Name.parse(request.subject.toString()).copy(commonName = X509Utilities.CORDA_CLIENT_CA_CN), request.publicKey, - nameConstraints = nameConstraints) - return buildCertPath(nodeCaCert.cert, *caCertPath) + nameConstraints = nameConstraints).toX509Certificate() + return buildCertPath(clientCertificate, *caCertPath) } override fun sign(data: ByteArray): DigitalSignatureWithCert { diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/utils/X509Utils.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/utils/X509Utils.kt index 22e94ec1b5..671841746d 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/utils/X509Utils.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/utils/X509Utils.kt @@ -7,6 +7,7 @@ import net.corda.core.internal.toX509CertHolder import net.corda.core.internal.x500Name import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair import net.corda.nodeapi.internal.crypto.CertificateType +import net.corda.nodeapi.internal.crypto.X509Utilities import net.corda.nodeapi.internal.crypto.getX509Certificate import org.bouncycastle.asn1.ASN1EncodableVector import org.bouncycastle.asn1.ASN1Sequence @@ -183,7 +184,7 @@ object X509Utilities { val certificateType = CertificateType.NODE_CA val validityWindow = getCertificateValidityWindow(0, validDays, issuerCertificate.notBefore, issuerCertificate.notAfter) val serial = BigInteger.valueOf(random63BitValue(provider)) - val subject = CordaX500Name.parse(jcaRequest.subject.toString()).x500Name + val subject = CordaX500Name.parse(jcaRequest.subject.toString()).copy(commonName = X509Utilities.CORDA_CLIENT_CA_CN).x500Name val subjectPublicKeyInfo = SubjectPublicKeyInfo.getInstance(ASN1Sequence.getInstance(jcaRequest.publicKey.encoded)) val keyPurposes = DERSequence(ASN1EncodableVector().apply { certificateType.purposes.forEach { add(it) } }) val builder = JcaX509v3CertificateBuilder(issuerCertificate.subject, serial, validityWindow.first, validityWindow.second, subject, jcaRequest.publicKey) diff --git a/network-management/src/test/kotlin/com/r3/corda/networkmanage/common/persistence/DBNetworkMapStorageTest.kt b/network-management/src/test/kotlin/com/r3/corda/networkmanage/common/persistence/DBNetworkMapStorageTest.kt index b7d332a5fe..062d4c5c5a 100644 --- a/network-management/src/test/kotlin/com/r3/corda/networkmanage/common/persistence/DBNetworkMapStorageTest.kt +++ b/network-management/src/test/kotlin/com/r3/corda/networkmanage/common/persistence/DBNetworkMapStorageTest.kt @@ -1,21 +1,24 @@ package com.r3.corda.networkmanage.common.persistence import com.r3.corda.networkmanage.TestBase +import com.r3.corda.networkmanage.common.utils.buildCertPath +import com.r3.corda.networkmanage.common.utils.toX509Certificate import com.r3.corda.networkmanage.common.utils.withCert import net.corda.core.crypto.Crypto import net.corda.core.crypto.sign import net.corda.core.identity.CordaX500Name +import net.corda.core.identity.PartyAndCertificate import net.corda.core.internal.cert +import net.corda.core.node.NodeInfo import net.corda.core.serialization.serialize +import net.corda.core.utilities.NetworkHostAndPort import net.corda.nodeapi.internal.SignedNodeInfo import net.corda.nodeapi.internal.crypto.CertificateType -import net.corda.nodeapi.internal.crypto.X509CertificateFactory import net.corda.nodeapi.internal.crypto.X509Utilities import net.corda.nodeapi.internal.network.NetworkMap import net.corda.nodeapi.internal.network.SignedNetworkMap import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.testing.common.internal.testNetworkParameters -import net.corda.testing.internal.TestNodeInfoBuilder import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties import org.assertj.core.api.Assertions.assertThat import org.junit.After @@ -28,7 +31,6 @@ class DBNetworkMapStorageTest : TestBase() { private lateinit var requestStorage: CertificationRequestStorage private lateinit var nodeInfoStorage: NodeInfoStorage private lateinit var persistence: CordaPersistence - private val rootCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) private val rootCACert = X509Utilities.createSelfSignedCACertificate(CordaX500Name(commonName = "Corda Node Root CA", locality = "London", organisation = "R3 LTD", country = "GB"), rootCAKey) private val intermediateCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) @@ -51,8 +53,18 @@ class DBNetworkMapStorageTest : TestBase() { fun `signNetworkMap creates current network map`() { // given // Create node info. - val signedNodeInfo = createValidSignedNodeInfo("Test") - val nodeInfoHash = nodeInfoStorage.putNodeInfo(signedNodeInfo) + val organisation = "Test" + val requestId = requestStorage.saveRequest(createRequest(organisation).first) + requestStorage.markRequestTicketCreated(requestId) + requestStorage.approveRequest(requestId, "TestUser") + val keyPair = Crypto.generateKeyPair() + val clientCert = X509Utilities.createCertificate(CertificateType.NODE_CA, intermediateCACert, intermediateCAKey, CordaX500Name(organisation = organisation, locality = "London", country = "GB"), keyPair.public) + val certPath = buildCertPath(clientCert.toX509Certificate(), intermediateCACert.toX509Certificate(), rootCACert.toX509Certificate()) + requestStorage.putCertificatePath(requestId, certPath, emptyList()) + val nodeInfo = NodeInfo(listOf(NetworkHostAndPort("my.company.com", 1234)), listOf(PartyAndCertificate(certPath)), 1, serial = 1L) + // Put signed node info data + val nodeInfoBytes = nodeInfo.serialize() + val nodeInfoHash = nodeInfoStorage.putNodeInfo(SignedNodeInfo(nodeInfoBytes, listOf(keyPair.sign(nodeInfoBytes)))) // Create network parameters val networkParametersHash = networkMapStorage.saveNetworkParameters(testNetworkParameters(emptyList())) @@ -91,11 +103,13 @@ class DBNetworkMapStorageTest : TestBase() { // Create network parameters val networkMapParametersHash = networkMapStorage.saveNetworkParameters(createNetworkParameters(1)) // Create empty network map + val keyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) + val intermediateCert = X509Utilities.createCertificate(CertificateType.INTERMEDIATE_CA, intermediateCACert, intermediateCAKey, CordaX500Name(organisation = "Corda", locality = "London", country = "GB"), keyPair.public) // Sign network map making it current network map val networkMap = NetworkMap(emptyList(), networkMapParametersHash) val serializedNetworkMap = networkMap.serialize() - val signatureData = intermediateCAKey.sign(serializedNetworkMap).withCert(intermediateCACert.cert) + val signatureData = keyPair.sign(serializedNetworkMap).withCert(intermediateCert.cert) val signedNetworkMap = SignedNetworkMap(serializedNetworkMap, signatureData) networkMapStorage.saveNetworkMap(signedNetworkMap) @@ -112,19 +126,36 @@ class DBNetworkMapStorageTest : TestBase() { @Test fun `getValidNodeInfoHashes returns only valid and signed node info hashes`() { // given - // Create node infos. - val signedNodeInfoA = createValidSignedNodeInfo("TestA") - val signedNodeInfoB = createValidSignedNodeInfo("TestB") - + // Create node info. + val organisationA = "TestA" + val requestIdA = requestStorage.saveRequest(createRequest(organisationA).first) + requestStorage.markRequestTicketCreated(requestIdA) + requestStorage.approveRequest(requestIdA, "TestUser") + val keyPairA = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) + val clientCertA = X509Utilities.createCertificate(CertificateType.NODE_CA, intermediateCACert, intermediateCAKey, CordaX500Name(organisation = organisationA, locality = "London", country = "GB"), keyPairA.public) + val certPathA = buildCertPath(clientCertA.toX509Certificate(), intermediateCACert.toX509Certificate(), rootCACert.toX509Certificate()) + requestStorage.putCertificatePath(requestIdA, certPathA, emptyList()) + val organisationB = "TestB" + val requestIdB = requestStorage.saveRequest(createRequest(organisationB).first) + requestStorage.markRequestTicketCreated(requestIdB) + requestStorage.approveRequest(requestIdB, "TestUser") + val keyPairB = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) + val clientCertB = X509Utilities.createCertificate(CertificateType.NODE_CA, intermediateCACert, intermediateCAKey, CordaX500Name(organisation = organisationB, locality = "London", country = "GB"), keyPairB.public) + val certPathB = buildCertPath(clientCertB.toX509Certificate(), intermediateCACert.toX509Certificate(), rootCACert.toX509Certificate()) + requestStorage.putCertificatePath(requestIdB, certPathB, emptyList()) + val nodeInfoA = NodeInfo(listOf(NetworkHostAndPort("my.companyA.com", 1234)), listOf(PartyAndCertificate(certPathA)), 1, serial = 1L) + val nodeInfoB = NodeInfo(listOf(NetworkHostAndPort("my.companyB.com", 1234)), listOf(PartyAndCertificate(certPathB)), 1, serial = 1L) // Put signed node info data - val nodeInfoHashA = nodeInfoStorage.putNodeInfo(signedNodeInfoA) - val nodeInfoHashB = nodeInfoStorage.putNodeInfo(signedNodeInfoB) + val nodeInfoABytes = nodeInfoA.serialize() + val nodeInfoBBytes = nodeInfoB.serialize() + val nodeInfoHashA = nodeInfoStorage.putNodeInfo(SignedNodeInfo(nodeInfoABytes, listOf(keyPairA.sign(nodeInfoABytes)))) + val nodeInfoHashB = nodeInfoStorage.putNodeInfo(SignedNodeInfo(nodeInfoBBytes, listOf(keyPairB.sign(nodeInfoBBytes)))) // Create network parameters val networkParametersHash = networkMapStorage.saveNetworkParameters(createNetworkParameters()) val networkMap = NetworkMap(listOf(nodeInfoHashA), networkParametersHash) val serializedNetworkMap = networkMap.serialize() - val signatureData = intermediateCAKey.sign(serializedNetworkMap).withCert(intermediateCACert.cert) + val signatureData = keyPairA.sign(serializedNetworkMap).withCert(clientCertA.cert) val signedNetworkMap = SignedNetworkMap(serializedNetworkMap, signatureData) // Sign network map @@ -136,15 +167,4 @@ class DBNetworkMapStorageTest : TestBase() { // then assertThat(validNodeInfoHash).containsOnly(nodeInfoHashA, nodeInfoHashB) } - - private fun createValidSignedNodeInfo(organisation: String): SignedNodeInfo { - val nodeInfoBuilder = TestNodeInfoBuilder() - val requestId = requestStorage.saveRequest(createRequest(organisation).first) - requestStorage.markRequestTicketCreated(requestId) - requestStorage.approveRequest(requestId, "TestUser") - val (identity) = nodeInfoBuilder.addIdentity(CordaX500Name(organisation, "London", "GB")) - val nodeCaCertPath = X509CertificateFactory().generateCertPath(identity.certPath.certificates.drop(1)) - requestStorage.putCertificatePath(requestId, nodeCaCertPath, emptyList()) - return nodeInfoBuilder.buildWithSigned().second - } } \ No newline at end of file diff --git a/network-management/src/test/kotlin/com/r3/corda/networkmanage/common/persistence/PersitenceNodeInfoStorageTest.kt b/network-management/src/test/kotlin/com/r3/corda/networkmanage/common/persistence/PersitenceNodeInfoStorageTest.kt index e5b2e37699..1014cde074 100644 --- a/network-management/src/test/kotlin/com/r3/corda/networkmanage/common/persistence/PersitenceNodeInfoStorageTest.kt +++ b/network-management/src/test/kotlin/com/r3/corda/networkmanage/common/persistence/PersitenceNodeInfoStorageTest.kt @@ -3,34 +3,31 @@ package com.r3.corda.networkmanage.common.persistence import com.r3.corda.networkmanage.TestBase import com.r3.corda.networkmanage.common.utils.buildCertPath import com.r3.corda.networkmanage.common.utils.hashString +import com.r3.corda.networkmanage.common.utils.toX509Certificate import net.corda.core.crypto.Crypto import net.corda.core.crypto.SecureHash +import net.corda.core.crypto.sign import net.corda.core.identity.CordaX500Name -import net.corda.core.internal.cert +import net.corda.core.identity.PartyAndCertificate import net.corda.core.node.NodeInfo import net.corda.core.serialization.serialize +import net.corda.core.utilities.NetworkHostAndPort import net.corda.nodeapi.internal.SignedNodeInfo import net.corda.nodeapi.internal.crypto.CertificateType -import net.corda.nodeapi.internal.crypto.X509CertificateFactory import net.corda.nodeapi.internal.crypto.X509Utilities import net.corda.nodeapi.internal.persistence.CordaPersistence -import net.corda.testing.internal.TestNodeInfoBuilder -import net.corda.testing.internal.signWith import net.corda.testing.node.MockServices -import org.assertj.core.api.Assertions.assertThat import org.junit.After import org.junit.Before import org.junit.Test -import java.security.PrivateKey import kotlin.test.assertEquals import kotlin.test.assertNotNull import kotlin.test.assertNull class PersitenceNodeInfoStorageTest : TestBase() { private lateinit var requestStorage: CertificationRequestStorage - private lateinit var nodeInfoStorage: PersistentNodeInfoStorage + private lateinit var nodeInfoStorage: NodeInfoStorage private lateinit var persistence: CordaPersistence - private val rootCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) private val rootCACert = X509Utilities.createSelfSignedCACertificate(CordaX500Name(commonName = "Corda Node Root CA", locality = "London", organisation = "R3 LTD", country = "GB"), rootCAKey) private val intermediateCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) @@ -49,13 +46,14 @@ class PersitenceNodeInfoStorageTest : TestBase() { } @Test - fun `test getCertificatePath`() { + fun `test get CertificatePath`() { // Create node info. val keyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) - val name = CordaX500Name(organisation = "Test", locality = "London", country = "GB") - val nodeCaCert = X509Utilities.createCertificate(CertificateType.NODE_CA, intermediateCACert, intermediateCAKey, name, keyPair.public) + val clientCert = X509Utilities.createCertificate(CertificateType.NODE_CA, intermediateCACert, intermediateCAKey, CordaX500Name(organisation = "Test", locality = "London", country = "GB"), keyPair.public) + val certPath = buildCertPath(clientCert.toX509Certificate(), intermediateCACert.toX509Certificate(), rootCACert.toX509Certificate()) + val nodeInfo = NodeInfo(listOf(NetworkHostAndPort("my.company.com", 1234)), listOf(PartyAndCertificate(certPath)), 1, serial = 1L) - val request = X509Utilities.createCertificateSigningRequest(name, "my@mail.com", keyPair) + val request = X509Utilities.createCertificateSigningRequest(nodeInfo.legalIdentities.first().name, "my@mail.com", keyPair) val requestId = requestStorage.saveRequest(request) requestStorage.markRequestTicketCreated(requestId) @@ -63,73 +61,104 @@ class PersitenceNodeInfoStorageTest : TestBase() { assertNull(nodeInfoStorage.getCertificatePath(SecureHash.parse(keyPair.public.hashString()))) - requestStorage.putCertificatePath(requestId, buildCertPath(nodeCaCert.cert, intermediateCACert.cert, rootCACert.cert), listOf(CertificationRequestStorage.DOORMAN_SIGNATURE)) + requestStorage.putCertificatePath(requestId, buildCertPath(clientCert.toX509Certificate(), intermediateCACert.toX509Certificate(), rootCACert.toX509Certificate()), listOf(CertificationRequestStorage.DOORMAN_SIGNATURE)) val storedCertPath = nodeInfoStorage.getCertificatePath(SecureHash.parse(keyPair.public.hashString())) assertNotNull(storedCertPath) - assertEquals(nodeCaCert.cert, storedCertPath!!.certificates.first()) + assertEquals(clientCert.toX509Certificate(), storedCertPath!!.certificates.first()) } @Test - fun `getNodeInfo returns persisted SignedNodeInfo using the hash of just the NodeInfo`() { + fun `test getNodeInfoHash returns correct data`() { // given - val (nodeInfoA, signedNodeInfoA) = createValidSignedNodeInfo("TestA") - val (nodeInfoB, signedNodeInfoB) = createValidSignedNodeInfo("TestB") + val organisationA = "TestA" + val requestIdA = requestStorage.saveRequest(createRequest(organisationA).first) + requestStorage.markRequestTicketCreated(requestIdA) + requestStorage.approveRequest(requestIdA, "TestUser") + val keyPairA = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) + val clientCertA = X509Utilities.createCertificate(CertificateType.NODE_CA, intermediateCACert, intermediateCAKey, CordaX500Name(organisation = organisationA, locality = "London", country = "GB"), keyPairA.public) + val certPathA = buildCertPath(clientCertA.toX509Certificate(), intermediateCACert.toX509Certificate(), rootCACert.toX509Certificate()) + requestStorage.putCertificatePath(requestIdA, certPathA, emptyList()) + val organisationB = "TestB" + val requestIdB = requestStorage.saveRequest(createRequest(organisationB).first) + requestStorage.markRequestTicketCreated(requestIdB) + requestStorage.approveRequest(requestIdB, "TestUser") + val keyPairB = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) + val clientCertB = X509Utilities.createCertificate(CertificateType.NODE_CA, intermediateCACert, intermediateCAKey, CordaX500Name(organisation = organisationB, locality = "London", country = "GB"), keyPairB.public) + val certPathB = buildCertPath(clientCertB.toX509Certificate(), intermediateCACert.toX509Certificate(), rootCACert.toX509Certificate()) + requestStorage.putCertificatePath(requestIdB, certPathB, emptyList()) + val nodeInfoA = NodeInfo(listOf(NetworkHostAndPort("my.company.com", 1234)), listOf(PartyAndCertificate(certPathA)), 1, serial = 1L) + val nodeInfoB = NodeInfo(listOf(NetworkHostAndPort("my.company.com", 1234)), listOf(PartyAndCertificate(certPathB)), 1, serial = 1L) // Put signed node info data - nodeInfoStorage.putNodeInfo(signedNodeInfoA) - nodeInfoStorage.putNodeInfo(signedNodeInfoB) + val nodeInfoABytes = nodeInfoA.serialize() + val nodeInfoBBytes = nodeInfoB.serialize() + nodeInfoStorage.putNodeInfo(SignedNodeInfo(nodeInfoABytes, listOf(keyPairA.sign(nodeInfoABytes)))) + nodeInfoStorage.putNodeInfo(SignedNodeInfo(nodeInfoBBytes, listOf(keyPairB.sign(nodeInfoBBytes)))) // when - val persistedSignedNodeInfoA = nodeInfoStorage.getNodeInfo(nodeInfoA.serialize().hash) - val persistedSignedNodeInfoB = nodeInfoStorage.getNodeInfo(nodeInfoB.serialize().hash) + val persistedNodeInfoA = nodeInfoStorage.getNodeInfo(nodeInfoABytes.hash) + val persistedNodeInfoB = nodeInfoStorage.getNodeInfo(nodeInfoBBytes.hash) // then - assertEquals(persistedSignedNodeInfoA?.verified(), nodeInfoA) - assertEquals(persistedSignedNodeInfoB?.verified(), nodeInfoB) + assertNotNull(persistedNodeInfoA) + assertNotNull(persistedNodeInfoB) + assertEquals(persistedNodeInfoA!!.verified(), nodeInfoA) + assertEquals(persistedNodeInfoB!!.verified(), nodeInfoB) } @Test - fun `same public key with different node info`() { + fun `same pub key with different node info`() { // Create node info. - val (nodeInfo1, signedNodeInfo1, key) = createValidSignedNodeInfo("Test", serial = 1) - val nodeInfo2 = nodeInfo1.copy(serial = 2) - val signedNodeInfo2 = nodeInfo2.signWith(listOf(key)) - - val nodeInfo1Hash = nodeInfoStorage.putNodeInfo(signedNodeInfo1) - assertEquals(nodeInfo1, nodeInfoStorage.getNodeInfo(nodeInfo1Hash)?.verified()) - - // This should replace the node info. - nodeInfoStorage.putNodeInfo(signedNodeInfo2) - - // Old node info should be removed. - assertNull(nodeInfoStorage.getNodeInfo(nodeInfo1Hash)) - assertEquals(nodeInfo2, nodeInfoStorage.getNodeInfo(nodeInfo2.serialize().hash)?.verified()) - } - - @Test - fun `putNodeInfo persists SignedNodeInfo with its signature`() { - // given - val (_, signedNodeInfo) = createValidSignedNodeInfo("Test") - - // when - val nodeInfoHash = nodeInfoStorage.putNodeInfo(signedNodeInfo) - - // then - val persistedSignedNodeInfo = nodeInfoStorage.getNodeInfo(nodeInfoHash) - assertThat(persistedSignedNodeInfo?.signatures).isEqualTo(signedNodeInfo.signatures) - } - - private fun createValidSignedNodeInfo(organisation: String, serial: Long = 1): Triple { - val nodeInfoBuilder = TestNodeInfoBuilder() + val organisation = "Test" val requestId = requestStorage.saveRequest(createRequest(organisation).first) requestStorage.markRequestTicketCreated(requestId) requestStorage.approveRequest(requestId, "TestUser") - val (identity, key) = nodeInfoBuilder.addIdentity(CordaX500Name(organisation, "London", "GB")) - val nodeCaCertPath = X509CertificateFactory().generateCertPath(identity.certPath.certificates.drop(1)) - requestStorage.putCertificatePath(requestId, nodeCaCertPath, emptyList()) - val (nodeInfo, signedNodeInfo) = nodeInfoBuilder.buildWithSigned(serial) - return Triple(nodeInfo, signedNodeInfo, key) + val keyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) + val clientCert = X509Utilities.createCertificate(CertificateType.NODE_CA, intermediateCACert, intermediateCAKey, CordaX500Name(organisation = organisation, locality = "London", country = "GB"), keyPair.public) + val certPath = buildCertPath(clientCert.toX509Certificate(), intermediateCACert.toX509Certificate(), rootCACert.toX509Certificate()) + requestStorage.putCertificatePath(requestId, certPath, emptyList()) + + val nodeInfo = NodeInfo(listOf(NetworkHostAndPort("my.company.com", 1234)), listOf(PartyAndCertificate(certPath)), 1, serial = 1L) + val nodeInfoSamePubKey = NodeInfo(listOf(NetworkHostAndPort("my.company2.com", 1234)), listOf(PartyAndCertificate(certPath)), 1, serial = 1L) + val nodeInfoBytes = nodeInfo.serialize() + val nodeInfoHash = nodeInfoStorage.putNodeInfo(SignedNodeInfo(nodeInfoBytes, listOf(keyPair.sign(nodeInfoBytes)))) + assertEquals(nodeInfo, nodeInfoStorage.getNodeInfo(nodeInfoHash)?.verified()) + + val nodeInfoSamePubKeyBytes = nodeInfoSamePubKey.serialize() + // This should replace the node info. + nodeInfoStorage.putNodeInfo(SignedNodeInfo(nodeInfoSamePubKeyBytes, listOf(keyPair.sign(nodeInfoSamePubKeyBytes)))) + + // Old node info should be removed. + assertNull(nodeInfoStorage.getNodeInfo(nodeInfoHash)) + assertEquals(nodeInfoSamePubKey, nodeInfoStorage.getNodeInfo(nodeInfoSamePubKeyBytes.hash)?.verified()) + } + + @Test + fun `putNodeInfo persists node info data with its signature`() { + // given + // Create node info. + val organisation = "Test" + val requestId = requestStorage.saveRequest(createRequest(organisation).first) + requestStorage.markRequestTicketCreated(requestId) + requestStorage.approveRequest(requestId, "TestUser") + val keyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) + val clientCert = X509Utilities.createCertificate(CertificateType.NODE_CA, intermediateCACert, intermediateCAKey, CordaX500Name(organisation = organisation, locality = "London", country = "GB"), keyPair.public) + val certPath = buildCertPath(clientCert.toX509Certificate(), intermediateCACert.toX509Certificate(), rootCACert.toX509Certificate()) + requestStorage.putCertificatePath(requestId, certPath, emptyList()) + + val nodeInfo = NodeInfo(listOf(NetworkHostAndPort("my.company.com", 1234)), listOf(PartyAndCertificate(certPath)), 1, serial = 1L) + val nodeInfoBytes = nodeInfo.serialize() + val signature = keyPair.sign(nodeInfoBytes) + + // when + val nodeInfoHash = nodeInfoStorage.putNodeInfo(SignedNodeInfo(nodeInfoBytes, listOf(signature))) + + // then + val persistedNodeInfo = nodeInfoStorage.getNodeInfo(nodeInfoHash) + assertNotNull(persistedNodeInfo) + assertEquals(nodeInfo, persistedNodeInfo!!.verified()) + assertEquals(signature, persistedNodeInfo.signatures.firstOrNull()) } } \ No newline at end of file diff --git a/network-management/src/test/kotlin/com/r3/corda/networkmanage/doorman/NodeInfoWebServiceTest.kt b/network-management/src/test/kotlin/com/r3/corda/networkmanage/doorman/NodeInfoWebServiceTest.kt index 26452d45a9..096a05534f 100644 --- a/network-management/src/test/kotlin/com/r3/corda/networkmanage/doorman/NodeInfoWebServiceTest.kt +++ b/network-management/src/test/kotlin/com/r3/corda/networkmanage/doorman/NodeInfoWebServiceTest.kt @@ -1,18 +1,20 @@ package com.r3.corda.networkmanage.doorman +import com.nhaarman.mockito_kotlin.any import com.nhaarman.mockito_kotlin.mock import com.nhaarman.mockito_kotlin.times import com.nhaarman.mockito_kotlin.verify import com.r3.corda.networkmanage.common.persistence.NetworkMapStorage import com.r3.corda.networkmanage.common.persistence.NodeInfoStorage +import com.r3.corda.networkmanage.common.utils.buildCertPath +import com.r3.corda.networkmanage.common.utils.toX509Certificate import com.r3.corda.networkmanage.common.utils.withCert import com.r3.corda.networkmanage.doorman.webservice.NodeInfoWebService -import net.corda.core.crypto.Crypto -import net.corda.core.crypto.SecureHash -import net.corda.core.crypto.sha256 -import net.corda.core.crypto.sign +import net.corda.core.crypto.* import net.corda.core.identity.CordaX500Name +import net.corda.core.identity.PartyAndCertificate import net.corda.core.internal.cert +import net.corda.core.node.NodeInfo import net.corda.core.serialization.deserialize import net.corda.core.serialization.serialize import net.corda.core.utilities.NetworkHostAndPort @@ -23,7 +25,6 @@ import net.corda.nodeapi.internal.crypto.X509Utilities import net.corda.nodeapi.internal.network.NetworkMap import net.corda.nodeapi.internal.network.SignedNetworkMap import net.corda.testing.SerializationEnvironmentRule -import net.corda.testing.internal.createNodeInfoAndSigned import org.bouncycastle.asn1.x500.X500Name import org.junit.Rule import org.junit.Test @@ -40,17 +41,31 @@ class NodeInfoWebServiceTest { @JvmField val testSerialization = SerializationEnvironmentRule(true) - private val testNetwotkMapConfig = NetworkMapConfig(10.seconds.toMillis(), 10.seconds.toMillis()) + private val rootCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) + private val rootCACert = X509Utilities.createSelfSignedCACertificate(CordaX500Name(locality = "London", organisation = "R3 LTD", country = "GB", commonName = "Corda Node Root CA"), rootCAKey) + private val intermediateCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) + private val intermediateCACert = X509Utilities.createCertificate(CertificateType.INTERMEDIATE_CA, rootCACert, rootCAKey, X500Name("CN=Corda Node Intermediate CA,L=London"), intermediateCAKey.public) + private val testNetwotkMapConfig = NetworkMapConfig(10.seconds.toMillis(), 10.seconds.toMillis()) @Test fun `submit nodeInfo`() { // Create node info. - val (_, signedNodeInfo) = createNodeInfoAndSigned(CordaX500Name("Test", "London", "GB")) + val keyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) + val clientCert = X509Utilities.createCertificate(CertificateType.NODE_CA, intermediateCACert, intermediateCAKey, CordaX500Name(organisation = "Test", locality = "London", country = "GB"), keyPair.public) + val certPath = buildCertPath(clientCert.toX509Certificate(), intermediateCACert.toX509Certificate(), rootCACert.toX509Certificate()) + val nodeInfo = NodeInfo(listOf(NetworkHostAndPort("my.company.com", 1234)), listOf(PartyAndCertificate(certPath)), 1, serial = 1L) - NetworkManagementWebServer(NetworkHostAndPort("localhost", 0), NodeInfoWebService(mock(), mock(), testNetwotkMapConfig)).use { + // Create digital signature. + val digitalSignature = DigitalSignature.WithKey(keyPair.public, Crypto.doSign(keyPair.private, nodeInfo.serialize().bytes)) + + val nodeInfoStorage: NodeInfoStorage = mock { + on { getCertificatePath(any()) }.thenReturn(certPath) + } + + NetworkManagementWebServer(NetworkHostAndPort("localhost", 0), NodeInfoWebService(nodeInfoStorage, mock(), testNetwotkMapConfig)).use { it.start() val registerURL = URL("http://${it.hostAndPort}/${NodeInfoWebService.NETWORK_MAP_PATH}/publish") - val nodeInfoAndSignature = signedNodeInfo.serialize().bytes + val nodeInfoAndSignature = SignedNodeInfo(nodeInfo.serialize(), listOf(digitalSignature)).serialize().bytes // Post node info and signature to doorman, this should pass without any exception. doPost(registerURL, nodeInfoAndSignature) } @@ -58,11 +73,6 @@ class NodeInfoWebServiceTest { @Test fun `get network map`() { - val rootCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) - val rootCACert = X509Utilities.createSelfSignedCACertificate(CordaX500Name(locality = "London", organisation = "R3 LTD", country = "GB", commonName = "Corda Node Root CA"), rootCAKey) - val intermediateCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) - val intermediateCACert = X509Utilities.createCertificate(CertificateType.INTERMEDIATE_CA, rootCACert, rootCAKey, X500Name("CN=Corda Node Intermediate CA,L=London"), intermediateCAKey.public) - val networkMap = NetworkMap(listOf(SecureHash.randomSHA256(), SecureHash.randomSHA256()), SecureHash.randomSHA256()) val serializedNetworkMap = networkMap.serialize() val networkMapStorage: NetworkMapStorage = mock { @@ -79,12 +89,16 @@ class NodeInfoWebServiceTest { @Test fun `get node info`() { - val (nodeInfo, signedNodeInfo) = createNodeInfoAndSigned(CordaX500Name("Test", "London", "GB")) + val keyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) + val clientCert = X509Utilities.createCertificate(CertificateType.NODE_CA, intermediateCACert, intermediateCAKey, CordaX500Name(organisation = "Test", locality = "London", country = "GB"), keyPair.public) + val certPath = buildCertPath(clientCert.toX509Certificate(), intermediateCACert.toX509Certificate(), rootCACert.toX509Certificate()) + val nodeInfo = NodeInfo(listOf(NetworkHostAndPort("my.company.com", 1234)), listOf(PartyAndCertificate(certPath)), 1, serial = 1L) val nodeInfoHash = nodeInfo.serialize().sha256() val nodeInfoStorage: NodeInfoStorage = mock { - on { getNodeInfo(nodeInfoHash) }.thenReturn(signedNodeInfo) + val serializedNodeInfo = nodeInfo.serialize() + on { getNodeInfo(nodeInfoHash) }.thenReturn(SignedNodeInfo(serializedNodeInfo, listOf(keyPair.sign(serializedNodeInfo)))) } NetworkManagementWebServer(NetworkHostAndPort("localhost", 0), NodeInfoWebService(nodeInfoStorage, mock(), testNetwotkMapConfig)).use { diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/IdentityGenerator.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/ServiceIdentityGenerator.kt similarity index 53% rename from node-api/src/main/kotlin/net/corda/nodeapi/internal/IdentityGenerator.kt rename to node-api/src/main/kotlin/net/corda/nodeapi/internal/ServiceIdentityGenerator.kt index 97927e54db..65b60433c7 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/IdentityGenerator.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/ServiceIdentityGenerator.kt @@ -13,54 +13,42 @@ import org.slf4j.LoggerFactory import java.nio.file.Path import java.security.cert.X509Certificate -object IdentityGenerator { +object ServiceIdentityGenerator { private val log = LoggerFactory.getLogger(javaClass) - const val NODE_IDENTITY_ALIAS_PREFIX = "identity" - const val DISTRIBUTED_NOTARY_ALIAS_PREFIX = "distributed-notary" - - fun generateNodeIdentity(dir: Path, legalName: CordaX500Name, customRootCert: X509Certificate? = null): Party { - return generateToDisk(listOf(dir), legalName, NODE_IDENTITY_ALIAS_PREFIX, threshold = 1, customRootCert = customRootCert) - } - - fun generateDistributedNotaryIdentity(dirs: List, notaryName: CordaX500Name, threshold: Int = 1, customRootCert: X509Certificate? = null): Party { - return generateToDisk(dirs, notaryName, DISTRIBUTED_NOTARY_ALIAS_PREFIX, threshold, customRootCert) - } - /** * Generates signing key pairs and a common distributed service identity for a set of nodes. * The key pairs and the group identity get serialized to disk in the corresponding node directories. * This method should be called *before* any of the nodes are started. * * @param dirs List of node directories to place the generated identity and key pairs in. - * @param name The name of the identity. + * @param serviceName The legal name of the distributed service. * @param threshold The threshold for the generated group [CompositeKey]. - * @param customRootCert the certificate to use as the Corda root CA. If not specified the one in - * internal/certificates/cordadevcakeys.jks is used. + * @param customRootCert the certificate to use a Corda root CA. If not specified the one in + * certificates/cordadevcakeys.jks is used. */ - private fun generateToDisk(dirs: List, - name: CordaX500Name, - aliasPrefix: String, - threshold: Int, - customRootCert: X509Certificate?): Party { - log.trace { "Generating identity \"$name\" for nodes: ${dirs.joinToString()}" } + fun generateToDisk(dirs: List, + serviceName: CordaX500Name, + serviceId: String, + threshold: Int = 1, + customRootCert: X509Certificate? = null): Party { + log.trace { "Generating a group identity \"serviceName\" for nodes: ${dirs.joinToString()}" } val keyPairs = (1..dirs.size).map { generateKeyPair() } - val key = CompositeKey.Builder().addKeys(keyPairs.map { it.public }).build(threshold) + val notaryKey = CompositeKey.Builder().addKeys(keyPairs.map { it.public }).build(threshold) val caKeyStore = loadKeyStore(javaClass.classLoader.getResourceAsStream("certificates/cordadevcakeys.jks"), "cordacadevpass") val intermediateCa = caKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_INTERMEDIATE_CA, "cordacadevkeypass") val rootCert = customRootCert ?: caKeyStore.getCertificate(X509Utilities.CORDA_ROOT_CA) keyPairs.zip(dirs) { keyPair, dir -> - val serviceKeyCert = X509Utilities.createCertificate(CertificateType.SERVICE_IDENTITY, intermediateCa.certificate, intermediateCa.keyPair, name, keyPair.public) - val compositeKeyCert = X509Utilities.createCertificate(CertificateType.SERVICE_IDENTITY, intermediateCa.certificate, intermediateCa.keyPair, name, key) + val serviceKeyCert = X509Utilities.createCertificate(CertificateType.SERVICE_IDENTITY, intermediateCa.certificate, intermediateCa.keyPair, serviceName, keyPair.public) + val compositeKeyCert = X509Utilities.createCertificate(CertificateType.SERVICE_IDENTITY, intermediateCa.certificate, intermediateCa.keyPair, serviceName, notaryKey) val certPath = (dir / "certificates").createDirectories() / "distributedService.jks" val keystore = loadOrCreateKeyStore(certPath, "cordacadevpass") - keystore.setCertificateEntry("$aliasPrefix-composite-key", compositeKeyCert.cert) - keystore.setKeyEntry("$aliasPrefix-private-key", keyPair.private, "cordacadevkeypass".toCharArray(), arrayOf(serviceKeyCert.cert, intermediateCa.certificate.cert, rootCert)) + keystore.setCertificateEntry("$serviceId-composite-key", compositeKeyCert.cert) + keystore.setKeyEntry("$serviceId-private-key", keyPair.private, "cordacadevkeypass".toCharArray(), arrayOf(serviceKeyCert.cert, intermediateCa.certificate.cert, rootCert)) keystore.save(certPath, "cordacadevpass") } - - return Party(name, key) + return Party(serviceName, notaryKey) } } diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/X509Utilities.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/X509Utilities.kt index 563ddaa9f0..70617daf76 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/X509Utilities.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/X509Utilities.kt @@ -7,8 +7,7 @@ import net.corda.core.crypto.random63BitValue import net.corda.core.internal.CertRole import net.corda.core.identity.CordaX500Name import net.corda.core.internal.cert -import net.corda.core.internal.reader -import net.corda.core.internal.writer +import net.corda.core.internal.read import net.corda.core.internal.x500Name import net.corda.core.utilities.days import net.corda.core.utilities.millis @@ -49,6 +48,8 @@ object X509Utilities { const val CORDA_CLIENT_TLS = "cordaclienttls" const val CORDA_CLIENT_CA = "cordaclientca" + const val CORDA_CLIENT_CA_CN = "Corda Client CA Certificate" + private val DEFAULT_VALIDITY_WINDOW = Pair(0.millis, 3650.days) /** @@ -161,7 +162,7 @@ object X509Utilities { */ @JvmStatic fun saveCertificateAsPEMFile(x509Certificate: X509Certificate, file: Path) { - JcaPEMWriter(file.writer()).use { + JcaPEMWriter(file.toFile().writer()).use { it.writeObject(x509Certificate) } } @@ -173,8 +174,9 @@ object X509Utilities { */ @JvmStatic fun loadCertificateFromPEMFile(file: Path): X509Certificate { - return file.reader().use { - val pemObject = PemReader(it).readPemObject() + return file.read { + val reader = PemReader(it.reader()) + val pemObject = reader.readPemObject() val certHolder = X509CertificateHolder(pemObject.content) certHolder.isValidOn(Date()) certHolder.cert 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 28a04fcbd7..aaf2cb5e04 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 @@ -13,6 +13,7 @@ import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party import net.corda.core.internal.deleteIfExists import net.corda.core.internal.div +import net.corda.core.node.services.NotaryService import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.TransactionBuilder import net.corda.core.utilities.NetworkHostAndPort @@ -23,7 +24,7 @@ import net.corda.node.services.config.BFTSMaRtConfiguration import net.corda.node.services.config.NotaryConfig import net.corda.node.services.transactions.minClusterSize import net.corda.node.services.transactions.minCorrectReplicas -import net.corda.nodeapi.internal.IdentityGenerator +import net.corda.nodeapi.internal.ServiceIdentityGenerator import net.corda.nodeapi.internal.network.NetworkParametersCopier import net.corda.nodeapi.internal.network.NotaryInfo import net.corda.testing.IntegrationTest @@ -67,9 +68,10 @@ class BFTNotaryServiceTests : IntegrationTest() { (Paths.get("config") / "currentView").deleteIfExists() // XXX: Make config object warn if this exists? val replicaIds = (0 until clusterSize) - notary = IdentityGenerator.generateDistributedNotaryIdentity( + notary = ServiceIdentityGenerator.generateToDisk( replicaIds.map { mockNet.baseDirectory(mockNet.nextNodeId + it) }, - CordaX500Name("BFT", "Zurich", "CH")) + CordaX500Name("BFT", "Zurich", "CH"), + NotaryService.constructId(validating = false, bft = true)) val networkParameters = NetworkParametersCopier(testNetworkParameters(listOf(NotaryInfo(notary, false)))) diff --git a/node/src/integration-test/kotlin/net/corda/node/services/MySQLNotaryServiceTests.kt b/node/src/integration-test/kotlin/net/corda/node/services/MySQLNotaryServiceTests.kt index e36f7b35d0..f1f467d24e 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/MySQLNotaryServiceTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/MySQLNotaryServiceTests.kt @@ -14,7 +14,7 @@ import net.corda.core.transactions.TransactionBuilder import net.corda.core.utilities.getOrThrow import net.corda.node.internal.StartedNode import net.corda.node.services.config.NotaryConfig -import net.corda.nodeapi.internal.IdentityGenerator +import net.corda.nodeapi.internal.ServiceIdentityGenerator import net.corda.nodeapi.internal.network.NetworkParametersCopier import net.corda.nodeapi.internal.network.NotaryInfo import net.corda.testing.* @@ -49,7 +49,11 @@ class MySQLNotaryServiceTests : IntegrationTest() { @Before fun before() { mockNet = MockNetwork(cordappPackages = listOf("net.corda.testing.contracts")) - notaryParty = IdentityGenerator.generateNodeIdentity(mockNet.baseDirectory(mockNet.nextNodeId), notaryName) + notaryParty = ServiceIdentityGenerator.generateToDisk( + listOf(mockNet.baseDirectory(mockNet.nextNodeId)), + notaryName, + "identity" + ) val networkParameters = NetworkParametersCopier(testNetworkParameters(listOf(NotaryInfo(notaryParty, false)))) val notaryNodeUnstarted = createNotaryNode() val nodeUnstarted = mockNet.createUnstartedNode() diff --git a/node/src/integration-test/kotlin/net/corda/node/services/RaftNotaryServiceTests.kt b/node/src/integration-test/kotlin/net/corda/node/services/RaftNotaryServiceTests.kt index 8110118362..00f6124301 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/RaftNotaryServiceTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/RaftNotaryServiceTests.kt @@ -11,6 +11,7 @@ import net.corda.core.internal.concurrent.map import net.corda.core.transactions.TransactionBuilder import net.corda.core.utilities.getOrThrow import net.corda.node.internal.StartedNode +import net.corda.node.services.transactions.RaftValidatingNotaryService import net.corda.testing.* import net.corda.testing.contracts.DummyContract import net.corda.testing.driver.NodeHandle @@ -30,15 +31,15 @@ class RaftNotaryServiceTests : IntegrationTest() { val databaseSchemas = IntegrationTestSchemas( "RAFTNotaryService_0", "RAFTNotaryService_1", "RAFTNotaryService_2", DUMMY_BANK_A_NAME.toDatabaseSchemaName()) } - private val notaryName = CordaX500Name("RAFT Notary Service", "London", "GB") + private val notaryName = CordaX500Name(RaftValidatingNotaryService.id, "RAFT Notary Service", "London", "GB") @Test fun `detect double spend`() { driver( startNodesInProcess = true, extraCordappPackagesToScan = listOf("net.corda.testing.contracts"), - notarySpecs = listOf(NotarySpec(notaryName, cluster = ClusterSpec.Raft(clusterSize = 3))) - ) { + notarySpecs = listOf(NotarySpec(notaryName, cluster = ClusterSpec.Raft(clusterSize = 3)))) + { val bankA = startNode(providedName = DUMMY_BANK_A_NAME).map { (it as NodeHandle.InProcess).node }.getOrThrow() val inputState = issueState(bankA, defaultNotaryIdentity) diff --git a/node/src/integration-test/kotlin/net/corda/services/messaging/P2PMessagingTest.kt b/node/src/integration-test/kotlin/net/corda/services/messaging/P2PMessagingTest.kt index 189506d02b..5dfa2963b1 100644 --- a/node/src/integration-test/kotlin/net/corda/services/messaging/P2PMessagingTest.kt +++ b/node/src/integration-test/kotlin/net/corda/services/messaging/P2PMessagingTest.kt @@ -19,9 +19,6 @@ import net.corda.node.services.messaging.ReceivedMessage import net.corda.node.services.messaging.send import net.corda.node.services.transactions.RaftValidatingNotaryService import net.corda.testing.* -import net.corda.node.services.messaging.* -import net.corda.testing.ALICE_NAME -import net.corda.testing.chooseIdentity import net.corda.testing.driver.DriverDSL import net.corda.testing.driver.NodeHandle import net.corda.testing.driver.driver @@ -41,7 +38,7 @@ class P2PMessagingTest : IntegrationTest() { @ClassRule @JvmField val databaseSchemas = IntegrationTestSchemas(ALICE_NAME.toDatabaseSchemaName(), "DistributedService_0", "DistributedService_1") - val DISTRIBUTED_SERVICE_NAME = CordaX500Name("DistributedService", "London", "GB") + val DISTRIBUTED_SERVICE_NAME = CordaX500Name(RaftValidatingNotaryService.id, "DistributedService", "London", "GB") } @Test 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 2a1b1292f1..b25bb1df9d 100644 --- a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt @@ -60,12 +60,8 @@ import net.corda.node.services.vault.NodeVaultService import net.corda.node.services.vault.VaultSoftLockManager import net.corda.node.shell.InteractiveShell import net.corda.node.utilities.AffinityExecutor -import net.corda.nodeapi.internal.IdentityGenerator import net.corda.nodeapi.internal.SignedNodeInfo -import net.corda.nodeapi.internal.crypto.KeyStoreWrapper -import net.corda.nodeapi.internal.crypto.X509CertificateFactory -import net.corda.nodeapi.internal.crypto.X509Utilities -import net.corda.nodeapi.internal.crypto.loadKeyStore +import net.corda.nodeapi.internal.crypto.* import net.corda.nodeapi.internal.network.NETWORK_PARAMS_FILE_NAME import net.corda.nodeapi.internal.network.NetworkParameters import net.corda.nodeapi.internal.persistence.CordaPersistence @@ -141,19 +137,25 @@ abstract class AbstractNode(val configuration: NodeConfiguration, protected val services: ServiceHubInternal get() = _services private lateinit var _services: ServiceHubInternalImpl protected var myNotaryIdentity: PartyAndCertificate? = null - private lateinit var checkpointStorage: CheckpointStorage + protected lateinit var checkpointStorage: CheckpointStorage private lateinit var tokenizableServices: List protected lateinit var attachments: NodeAttachmentService protected lateinit var network: MessagingService protected val runOnStop = ArrayList<() -> Any?>() - private val _nodeReadyFuture = openFuture() + protected val _nodeReadyFuture = openFuture() protected var networkMapClient: NetworkMapClient? = null lateinit var securityManager: RPCSecurityManager get /** Completes once the node has successfully registered with the network map service * or has loaded network map data from local database */ - val nodeReadyFuture: CordaFuture get() = _nodeReadyFuture + val nodeReadyFuture: CordaFuture + get() = _nodeReadyFuture + /** A [CordaX500Name] with null common name. */ + protected val myLegalName: CordaX500Name by lazy { + val cert = loadKeyStore(configuration.nodeKeystore, configuration.keyStorePassword).getX509Certificate(X509Utilities.CORDA_CLIENT_CA) + CordaX500Name.build(cert.subjectX500Principal).copy(commonName = null) + } open val serializationWhitelists: List by lazy { cordappLoader.cordapps.flatMap { it.serializationWhitelists } @@ -328,7 +330,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration, ) // Check if we have already stored a version of 'our own' NodeInfo, this is to avoid regenerating it with // a different timestamp. - networkMapCache.getNodesByLegalName(configuration.myLegalName).firstOrNull()?.let { + networkMapCache.getNodesByLegalName(myLegalName).firstOrNull()?.let { if (info.copy(serial = it.serial) == it) { info = it } @@ -754,10 +756,13 @@ abstract class AbstractNode(val configuration: NodeConfiguration, val (id, singleName) = if (notaryConfig == null || !notaryConfig.isClusterConfig) { // Node's main identity or if it's a single node notary - Pair(IdentityGenerator.NODE_IDENTITY_ALIAS_PREFIX, configuration.myLegalName) + Pair("identity", myLegalName) } else { + val notaryId = notaryConfig.run { + NotaryService.constructId(validating, raft != null, bftSMaRt != null, custom, mysql != null) + } // The node is part of a distributed notary whose identity must already be generated beforehand. - Pair(IdentityGenerator.DISTRIBUTED_NOTARY_ALIAS_PREFIX, null) + Pair(notaryId, null) } // TODO: Integrate with Key management service? val privateKeyAlias = "$id-private-key" @@ -765,7 +770,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration, if (!keyStore.containsAlias(privateKeyAlias)) { singleName ?: throw IllegalArgumentException( "Unable to find in the key store the identity of the distributed notary ($id) the node is part of") - // TODO: Remove use of [IdentityGenerator.generateToDisk]. + // TODO: Remove use of [ServiceIdentityGenerator.generateToDisk]. log.info("$privateKeyAlias not found in key store ${configuration.nodeKeystore}, generating fresh key!") keyStore.signAndSaveNewKeyPair(singleName, privateKeyAlias, generateKeyPair()) } diff --git a/node/src/main/kotlin/net/corda/node/services/config/ConfigUtilities.kt b/node/src/main/kotlin/net/corda/node/services/config/ConfigUtilities.kt index ed6028f13c..7471d5d5ec 100644 --- a/node/src/main/kotlin/net/corda/node/services/config/ConfigUtilities.kt +++ b/node/src/main/kotlin/net/corda/node/services/config/ConfigUtilities.kt @@ -69,7 +69,7 @@ fun SSLConfiguration.configureDevKeyAndTrustStores(myLegalName: CordaX500Name) { val caKeyStore = loadKeyStore(javaClass.classLoader.getResourceAsStream("certificates/cordadevcakeys.jks"), "cordacadevpass") createKeystoreForCordaNode(sslKeystore, nodeKeystore, keyStorePassword, keyStorePassword, caKeyStore, "cordacadevkeypass", myLegalName) - // Move distributed service composite key (generated by IdentityGenerator.generateToDisk) to keystore if exists. + // Move distributed service composite key (generated by ServiceIdentityGenerator.generateToDisk) to keystore if exists. val distributedServiceKeystore = certificatesDirectory / "distributedService.jks" if (distributedServiceKeystore.exists()) { val serviceKeystore = loadKeyStore(distributedServiceKeystore, "cordacadevpass") @@ -111,17 +111,18 @@ fun createKeystoreForCordaNode(sslKeyStorePath: Path, val (intermediateCACert, intermediateCAKeyPair) = caKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_INTERMEDIATE_CA, caKeyPassword) val clientKey = Crypto.generateKeyPair(signatureScheme) + val clientName = legalName.copy(commonName = null) - val nameConstraints = NameConstraints(arrayOf(GeneralSubtree(GeneralName(GeneralName.directoryName, legalName.x500Name))), arrayOf()) + val nameConstraints = NameConstraints(arrayOf(GeneralSubtree(GeneralName(GeneralName.directoryName, clientName.x500Name))), arrayOf()) val clientCACert = X509Utilities.createCertificate(CertificateType.NODE_CA, intermediateCACert, intermediateCAKeyPair, - legalName, + clientName.copy(commonName = X509Utilities.CORDA_CLIENT_CA_CN), clientKey.public, nameConstraints = nameConstraints) val tlsKey = Crypto.generateKeyPair(signatureScheme) - val clientTLSCert = X509Utilities.createCertificate(CertificateType.TLS, clientCACert, clientKey, legalName, tlsKey.public) + val clientTLSCert = X509Utilities.createCertificate(CertificateType.TLS, clientCACert, clientKey, clientName, tlsKey.public) val keyPass = keyPassword.toCharArray() diff --git a/node/src/main/kotlin/net/corda/node/services/identity/InMemoryIdentityService.kt b/node/src/main/kotlin/net/corda/node/services/identity/InMemoryIdentityService.kt index 4654473ef1..bac9ef6902 100644 --- a/node/src/main/kotlin/net/corda/node/services/identity/InMemoryIdentityService.kt +++ b/node/src/main/kotlin/net/corda/node/services/identity/InMemoryIdentityService.kt @@ -24,7 +24,6 @@ import javax.annotation.concurrent.ThreadSafe * * @param identities initial set of identities for the service, typically only used for unit tests. */ -// TODO There is duplicated logic between this and PersistentIdentityService @ThreadSafe class InMemoryIdentityService(identities: Array, trustRoot: X509CertificateHolder) : SingletonSerializeAsToken(), IdentityServiceInternal { diff --git a/node/src/main/kotlin/net/corda/node/services/identity/PersistentIdentityService.kt b/node/src/main/kotlin/net/corda/node/services/identity/PersistentIdentityService.kt index 83e7b0b267..6dd98d0c63 100644 --- a/node/src/main/kotlin/net/corda/node/services/identity/PersistentIdentityService.kt +++ b/node/src/main/kotlin/net/corda/node/services/identity/PersistentIdentityService.kt @@ -26,7 +26,6 @@ import javax.persistence.Entity import javax.persistence.Id import javax.persistence.Lob -// TODO There is duplicated logic between this and InMemoryIdentityService @ThreadSafe class PersistentIdentityService(override val trustRoot: X509Certificate, vararg caCertificates: X509Certificate) : SingletonSerializeAsToken(), IdentityServiceInternal { diff --git a/node/src/main/kotlin/net/corda/node/services/transactions/BFTNonValidatingNotaryService.kt b/node/src/main/kotlin/net/corda/node/services/transactions/BFTNonValidatingNotaryService.kt index 9f6ffe1d08..780c4815c6 100644 --- a/node/src/main/kotlin/net/corda/node/services/transactions/BFTNonValidatingNotaryService.kt +++ b/node/src/main/kotlin/net/corda/node/services/transactions/BFTNonValidatingNotaryService.kt @@ -34,13 +34,12 @@ import kotlin.concurrent.thread * * A transaction is notarised when the consensus is reached by the cluster on its uniqueness, and time-window validity. */ -class BFTNonValidatingNotaryService( - override val services: ServiceHubInternal, - override val notaryIdentityKey: PublicKey, - private val bftSMaRtConfig: BFTSMaRtConfiguration, - cluster: BFTSMaRt.Cluster -) : NotaryService() { +class BFTNonValidatingNotaryService(override val services: ServiceHubInternal, + override val notaryIdentityKey: PublicKey, + private val bftSMaRtConfig: BFTSMaRtConfiguration, + cluster: BFTSMaRt.Cluster) : NotaryService() { companion object { + val id = constructId(validating = false, bft = true) private val log = contextLogger() } diff --git a/node/src/main/kotlin/net/corda/node/services/transactions/MySQLNotaryService.kt b/node/src/main/kotlin/net/corda/node/services/transactions/MySQLNotaryService.kt index adfe3e9744..0bf810b2ab 100644 --- a/node/src/main/kotlin/net/corda/node/services/transactions/MySQLNotaryService.kt +++ b/node/src/main/kotlin/net/corda/node/services/transactions/MySQLNotaryService.kt @@ -35,6 +35,9 @@ class MySQLNonValidatingNotaryService(services: ServiceHubInternal, notaryIdentityKey: PublicKey, dataSourceProperties: Properties, devMode: Boolean = false) : MySQLNotaryService(services, notaryIdentityKey, dataSourceProperties, devMode) { + companion object { + val id = constructId(validating = false, mysql = true) + } override fun createServiceFlow(otherPartySession: FlowSession): FlowLogic = NonValidatingNotaryFlow(otherPartySession, this) } @@ -42,5 +45,8 @@ class MySQLValidatingNotaryService(services: ServiceHubInternal, notaryIdentityKey: PublicKey, dataSourceProperties: Properties, devMode: Boolean = false) : MySQLNotaryService(services, notaryIdentityKey, dataSourceProperties, devMode) { + companion object { + val id = constructId(validating = true, mysql = true) + } override fun createServiceFlow(otherPartySession: FlowSession): FlowLogic = ValidatingNotaryFlow(otherPartySession, this) } \ No newline at end of file diff --git a/node/src/main/kotlin/net/corda/node/services/transactions/RaftNonValidatingNotaryService.kt b/node/src/main/kotlin/net/corda/node/services/transactions/RaftNonValidatingNotaryService.kt index e672380398..1433e71f85 100644 --- a/node/src/main/kotlin/net/corda/node/services/transactions/RaftNonValidatingNotaryService.kt +++ b/node/src/main/kotlin/net/corda/node/services/transactions/RaftNonValidatingNotaryService.kt @@ -8,13 +8,14 @@ import net.corda.core.node.services.TrustedAuthorityNotaryService import java.security.PublicKey /** A non-validating notary service operated by a group of mutually trusting parties, uses the Raft algorithm to achieve consensus. */ -class RaftNonValidatingNotaryService( - override val services: ServiceHub, - override val notaryIdentityKey: PublicKey, - override val uniquenessProvider: RaftUniquenessProvider -) : TrustedAuthorityNotaryService() { +class RaftNonValidatingNotaryService(override val services: ServiceHub, + override val notaryIdentityKey: PublicKey, + override val uniquenessProvider: RaftUniquenessProvider) : TrustedAuthorityNotaryService() { + companion object { + val id = constructId(validating = false, raft = true) + } + override val timeWindowChecker: TimeWindowChecker = TimeWindowChecker(services.clock) - override fun createServiceFlow(otherPartySession: FlowSession): NotaryFlow.Service { return NonValidatingNotaryFlow(otherPartySession, this) } diff --git a/node/src/main/kotlin/net/corda/node/services/transactions/RaftValidatingNotaryService.kt b/node/src/main/kotlin/net/corda/node/services/transactions/RaftValidatingNotaryService.kt index 3e4899ae0a..8fd3448512 100644 --- a/node/src/main/kotlin/net/corda/node/services/transactions/RaftValidatingNotaryService.kt +++ b/node/src/main/kotlin/net/corda/node/services/transactions/RaftValidatingNotaryService.kt @@ -8,13 +8,14 @@ import net.corda.core.node.services.TrustedAuthorityNotaryService import java.security.PublicKey /** A validating notary service operated by a group of mutually trusting parties, uses the Raft algorithm to achieve consensus. */ -class RaftValidatingNotaryService( - override val services: ServiceHub, - override val notaryIdentityKey: PublicKey, - override val uniquenessProvider: RaftUniquenessProvider -) : TrustedAuthorityNotaryService() { - override val timeWindowChecker: TimeWindowChecker = TimeWindowChecker(services.clock) +class RaftValidatingNotaryService(override val services: ServiceHub, + override val notaryIdentityKey: PublicKey, + override val uniquenessProvider: RaftUniquenessProvider) : TrustedAuthorityNotaryService() { + companion object { + val id = constructId(validating = true, raft = true) + } + override val timeWindowChecker: TimeWindowChecker = TimeWindowChecker(services.clock) override fun createServiceFlow(otherPartySession: FlowSession): NotaryFlow.Service { return ValidatingNotaryFlow(otherPartySession, this) } diff --git a/node/src/main/kotlin/net/corda/node/services/transactions/ValidatingNotaryService.kt b/node/src/main/kotlin/net/corda/node/services/transactions/ValidatingNotaryService.kt index 5e687c3b6d..6c7e36046b 100644 --- a/node/src/main/kotlin/net/corda/node/services/transactions/ValidatingNotaryService.kt +++ b/node/src/main/kotlin/net/corda/node/services/transactions/ValidatingNotaryService.kt @@ -9,8 +9,10 @@ import java.security.PublicKey /** A Notary service that validates the transaction chain of the submitted transaction before committing it */ class ValidatingNotaryService(override val services: ServiceHubInternal, override val notaryIdentityKey: PublicKey) : TrustedAuthorityNotaryService() { + companion object { + val id = constructId(validating = true) + } override val timeWindowChecker = TimeWindowChecker(services.clock) - override val uniquenessProvider = PersistentUniquenessProvider() override fun createServiceFlow(otherPartySession: FlowSession): NotaryFlow.Service = ValidatingNotaryFlow(otherPartySession, this) 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 698420ca0a..f24cf56f11 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 @@ -103,9 +103,10 @@ class NetworkRegistrationHelper(private val config: NodeConfiguration, private v println("Generating SSL certificate for node messaging service.") val sslKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) val caCert = caKeyStore.getX509Certificate(CORDA_CLIENT_CA).toX509CertHolder() - val sslCert = X509Utilities.createCertificate(CertificateType.TLS, caCert, keyPair, CordaX500Name.build(caCert.cert.subjectX500Principal), sslKey.public) + val sslCert = X509Utilities.createCertificate(CertificateType.TLS, caCert, keyPair, CordaX500Name.build(caCert.cert.subjectX500Principal).copy(commonName = null), sslKey.public) val sslKeyStore = loadOrCreateKeyStore(config.sslKeystore, keystorePassword) - sslKeyStore.addOrReplaceKey(CORDA_CLIENT_TLS, sslKey.private, privateKeyPassword.toCharArray(), arrayOf(sslCert.cert, *certificates)) + sslKeyStore.addOrReplaceKey(CORDA_CLIENT_TLS, sslKey.private, privateKeyPassword.toCharArray(), + arrayOf(sslCert.cert, *certificates)) sslKeyStore.save(config.sslKeystore, config.keyStorePassword) println("SSL private key and certificate stored in ${config.sslKeystore}.") // All done, clean up temp files. diff --git a/node/src/test/kotlin/net/corda/node/services/statemachine/FlowFrameworkTests.kt b/node/src/test/kotlin/net/corda/node/services/statemachine/FlowFrameworkTests.kt index 2006c397bc..1fe97568a8 100644 --- a/node/src/test/kotlin/net/corda/node/services/statemachine/FlowFrameworkTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/statemachine/FlowFrameworkTests.kt @@ -656,7 +656,7 @@ class FlowFrameworkTests { private inline fun > StartedNode.restartAndGetRestoredFlow() = internals.run { disableDBCloseOnStop() // Handover DB to new node copy stop() - val newNode = mockNet.createNode(MockNodeParameters(id, configuration.myLegalName)) + val newNode = mockNet.createNode(MockNodeParameters(id)) newNode.internals.acceptableLiveFiberCountOnStop = 1 manuallyCloseDB() mockNet.runNetwork() 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 209153fdb8..50922e9fde 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 @@ -1,7 +1,5 @@ package net.corda.node.utilities.registration -import com.google.common.jimfs.Configuration.unix -import com.google.common.jimfs.Jimfs import com.nhaarman.mockito_kotlin.any import com.nhaarman.mockito_kotlin.doReturn import com.nhaarman.mockito_kotlin.eq @@ -9,61 +7,68 @@ import com.nhaarman.mockito_kotlin.whenever import net.corda.core.crypto.Crypto import net.corda.core.crypto.SecureHash import net.corda.core.identity.CordaX500Name -import net.corda.core.internal.cert -import net.corda.core.internal.createDirectories +import net.corda.core.internal.* import net.corda.node.services.config.NodeConfiguration import net.corda.nodeapi.internal.crypto.* +import net.corda.nodeapi.internal.crypto.X509Utilities +import net.corda.nodeapi.internal.crypto.getX509Certificate +import net.corda.nodeapi.internal.crypto.loadKeyStore import net.corda.testing.ALICE_NAME import net.corda.testing.internal.rigorousMock -import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThatThrownBy -import org.junit.After import org.junit.Before +import org.junit.Rule import org.junit.Test +import org.junit.rules.TemporaryFolder import java.security.cert.Certificate -import java.security.cert.X509Certificate +import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertTrue class NetworkRegistrationHelperTest { - private val fs = Jimfs.newFileSystem(unix()) - private val requestId = SecureHash.randomSHA256().toString() - private val nodeLegalName = ALICE_NAME - private val intermediateCaName = CordaX500Name("CORDA_INTERMEDIATE_CA", "R3 Ltd", "London", "GB") - private val rootCaName = CordaX500Name("CORDA_ROOT_CA", "R3 Ltd", "London", "GB") - private val nodeCaCert = createCaCert(nodeLegalName) - private val intermediateCaCert = createCaCert(intermediateCaName) - private val rootCaCert = createCaCert(rootCaName) + @Rule + @JvmField + val tempFolder = TemporaryFolder() + private val requestId = SecureHash.randomSHA256().toString() private lateinit var config: NodeConfiguration + private val identities = listOf("CORDA_CLIENT_CA", + "CORDA_INTERMEDIATE_CA", + "CORDA_ROOT_CA") + .map { CordaX500Name(commonName = it, organisation = "R3 Ltd", locality = "London", country = "GB") } + private val certs = identities.map { X509Utilities.createSelfSignedCACertificate(it, Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)) } + .map { it.cert }.toTypedArray() + + private val certService = mockRegistrationResponse(*certs) + @Before fun init() { - val baseDirectory = fs.getPath("/baseDir").createDirectories() abstract class AbstractNodeConfiguration : NodeConfiguration config = rigorousMock().also { - doReturn(baseDirectory).whenever(it).baseDirectory + doReturn(tempFolder.root.toPath()).whenever(it).baseDirectory doReturn("trustpass").whenever(it).trustStorePassword doReturn("cordacadevpass").whenever(it).keyStorePassword - doReturn(nodeLegalName).whenever(it).myLegalName + doReturn(ALICE_NAME).whenever(it).myLegalName doReturn("").whenever(it).emailAddress } } - @After - fun cleanUp() { - fs.close() - } - @Test fun `successful registration`() { - assertThat(config.nodeKeystore).doesNotExist() - assertThat(config.sslKeystore).doesNotExist() - assertThat(config.trustStoreFile).doesNotExist() + assertFalse(config.nodeKeystore.exists()) + assertFalse(config.sslKeystore.exists()) + config.trustStoreFile.parent.createDirectories() + loadOrCreateKeyStore(config.trustStoreFile, config.trustStorePassword).also { + it.addOrReplaceCertificate(X509Utilities.CORDA_ROOT_CA, certs.last()) + it.save(config.trustStoreFile, config.trustStorePassword) + } - saveTrustStoreWithRootCa(rootCaCert) + NetworkRegistrationHelper(config, certService).buildKeystore() - createRegistrationHelper().buildKeystore() + assertTrue(config.nodeKeystore.exists()) + assertTrue(config.sslKeystore.exists()) + assertTrue(config.trustStoreFile.exists()) val nodeKeystore = loadKeyStore(config.nodeKeystore, config.keyStorePassword) val sslKeystore = loadKeyStore(config.sslKeystore, config.keyStorePassword) @@ -74,8 +79,9 @@ class NetworkRegistrationHelperTest { assertFalse(containsAlias(X509Utilities.CORDA_INTERMEDIATE_CA)) assertFalse(containsAlias(X509Utilities.CORDA_ROOT_CA)) assertFalse(containsAlias(X509Utilities.CORDA_CLIENT_TLS)) - val nodeCaCertChain = getCertificateChain(X509Utilities.CORDA_CLIENT_CA) - assertThat(nodeCaCertChain).containsExactly(nodeCaCert, intermediateCaCert, rootCaCert) + val certificateChain = getCertificateChain(X509Utilities.CORDA_CLIENT_CA) + assertEquals(3, certificateChain.size) + assertEquals(listOf("CORDA_CLIENT_CA", "CORDA_INTERMEDIATE_CA", "CORDA_ROOT_CA"), certificateChain.map { it.toX509CertHolder().subject.commonName }) } sslKeystore.run { @@ -83,55 +89,46 @@ class NetworkRegistrationHelperTest { assertFalse(containsAlias(X509Utilities.CORDA_INTERMEDIATE_CA)) assertFalse(containsAlias(X509Utilities.CORDA_ROOT_CA)) assertTrue(containsAlias(X509Utilities.CORDA_CLIENT_TLS)) - val nodeTlsCertChain = getCertificateChain(X509Utilities.CORDA_CLIENT_TLS) - assertThat(nodeTlsCertChain).hasSize(4) - // The TLS cert has the same subject as the node CA cert - assertThat(CordaX500Name.build((nodeTlsCertChain[0] as X509Certificate).subjectX500Principal)).isEqualTo(nodeLegalName) - assertThat(nodeTlsCertChain.drop(1)).containsExactly(nodeCaCert, intermediateCaCert, rootCaCert) + val certificateChain = getCertificateChain(X509Utilities.CORDA_CLIENT_TLS) + assertEquals(4, certificateChain.size) + assertEquals(listOf(CordaX500Name(organisation = "R3 Ltd", locality = "London", country = "GB").x500Name) + identities.map { it.x500Name }, + certificateChain.map { it.toX509CertHolder().subject }) + assertEquals(CordaX500Name(organisation = "R3 Ltd", locality = "London", country = "GB").x500Principal, + getX509Certificate(X509Utilities.CORDA_CLIENT_TLS).subjectX500Principal) } trustStore.run { assertFalse(containsAlias(X509Utilities.CORDA_CLIENT_CA)) assertFalse(containsAlias(X509Utilities.CORDA_INTERMEDIATE_CA)) assertTrue(containsAlias(X509Utilities.CORDA_ROOT_CA)) - val trustStoreRootCaCert = getCertificate(X509Utilities.CORDA_ROOT_CA) - assertThat(trustStoreRootCaCert).isEqualTo(rootCaCert) } } @Test fun `missing truststore`() { assertThatThrownBy { - createRegistrationHelper() + NetworkRegistrationHelper(config, certService).buildKeystore() }.hasMessageContaining("This file must contain the root CA cert of your compatibility zone. Please contact your CZ operator.") + .isInstanceOf(IllegalArgumentException::class.java) } @Test fun `wrong root cert in truststore`() { - saveTrustStoreWithRootCa(createCaCert(CordaX500Name("Foo", "MU", "GB"))) - val registrationHelper = createRegistrationHelper() + val someCert = X509Utilities.createSelfSignedCACertificate(CordaX500Name("Foo", "MU", "GB"), Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)).cert + config.trustStoreFile.parent.createDirectories() + loadOrCreateKeyStore(config.trustStoreFile, config.trustStorePassword).also { + it.addOrReplaceCertificate(X509Utilities.CORDA_ROOT_CA, someCert) + it.save(config.trustStoreFile, config.trustStorePassword) + } assertThatThrownBy { - registrationHelper.buildKeystore() + NetworkRegistrationHelper(config, certService).buildKeystore() }.isInstanceOf(WrongRootCertException::class.java) } - private fun createRegistrationHelper(): NetworkRegistrationHelper { - val certService = rigorousMock().also { + private fun mockRegistrationResponse(vararg response: Certificate): NetworkRegistrationService { + return rigorousMock().also { doReturn(requestId).whenever(it).submitRequest(any()) - doReturn(arrayOf(nodeCaCert, intermediateCaCert, rootCaCert)).whenever(it).retrieveCertificates(eq(requestId)) + doReturn(response).whenever(it).retrieveCertificates(eq(requestId)) } - return NetworkRegistrationHelper(config, certService) - } - - private fun saveTrustStoreWithRootCa(rootCa: X509Certificate) { - config.trustStoreFile.parent.createDirectories() - loadOrCreateKeyStore(config.trustStoreFile, config.trustStorePassword).also { - it.addOrReplaceCertificate(X509Utilities.CORDA_ROOT_CA, rootCa) - it.save(config.trustStoreFile, config.trustStorePassword) - } - } - - private fun createCaCert(name: CordaX500Name): X509Certificate { - return X509Utilities.createSelfSignedCACertificate(name, Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)).cert } } diff --git a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/BFTNotaryCordform.kt b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/BFTNotaryCordform.kt index 311a5c51b2..eb11a9b443 100644 --- a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/BFTNotaryCordform.kt +++ b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/BFTNotaryCordform.kt @@ -4,11 +4,13 @@ import net.corda.cordform.CordformContext import net.corda.cordform.CordformDefinition import net.corda.cordform.CordformNode import net.corda.core.identity.CordaX500Name +import net.corda.core.node.services.NotaryService import net.corda.core.utilities.NetworkHostAndPort import net.corda.node.services.config.BFTSMaRtConfiguration import net.corda.node.services.config.NotaryConfig +import net.corda.node.services.transactions.BFTNonValidatingNotaryService import net.corda.node.services.transactions.minCorrectReplicas -import net.corda.nodeapi.internal.IdentityGenerator +import net.corda.nodeapi.internal.ServiceIdentityGenerator import net.corda.testing.node.internal.demorun.* import net.corda.testing.ALICE_NAME import net.corda.testing.BOB_NAME @@ -22,7 +24,7 @@ private val notaryNames = createNotaryNames(clusterSize) // This is not the intended final design for how to use CordformDefinition, please treat this as experimental and DO // NOT use this as a design to copy. class BFTNotaryCordform : CordformDefinition() { - private val clusterName = CordaX500Name("BFT", "Zurich", "CH") + private val clusterName = CordaX500Name(BFTNonValidatingNotaryService.id, "BFT", "Zurich", "CH") init { nodesDirectory = Paths.get("build", "nodes", "nodesBFT") @@ -62,10 +64,10 @@ class BFTNotaryCordform : CordformDefinition() { } override fun setup(context: CordformContext) { - IdentityGenerator.generateDistributedNotaryIdentity( + ServiceIdentityGenerator.generateToDisk( notaryNames.map { context.baseDirectory(it.toString()) }, clusterName, - minCorrectReplicas(clusterSize) - ) + NotaryService.constructId(validating = false, bft = true), + minCorrectReplicas(clusterSize)) } } diff --git a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/RaftNotaryCordform.kt b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/RaftNotaryCordform.kt index 59384a412f..cfc21fa060 100644 --- a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/RaftNotaryCordform.kt +++ b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/RaftNotaryCordform.kt @@ -8,7 +8,8 @@ import net.corda.core.node.services.NotaryService import net.corda.core.utilities.NetworkHostAndPort import net.corda.node.services.config.NotaryConfig import net.corda.node.services.config.RaftConfig -import net.corda.nodeapi.internal.IdentityGenerator +import net.corda.node.services.transactions.RaftValidatingNotaryService +import net.corda.nodeapi.internal.ServiceIdentityGenerator import net.corda.testing.node.internal.demorun.* import net.corda.testing.ALICE_NAME import net.corda.testing.BOB_NAME @@ -23,7 +24,7 @@ private val notaryNames = createNotaryNames(3) // This is not the intended final design for how to use CordformDefinition, please treat this as experimental and DO // NOT use this as a design to copy. class RaftNotaryCordform : CordformDefinition() { - private val clusterName = CordaX500Name("Raft", "Zurich", "CH") + private val clusterName = CordaX500Name(RaftValidatingNotaryService.id, "Raft", "Zurich", "CH") init { nodesDirectory = Paths.get("build", "nodes", "nodesRaft") @@ -59,9 +60,9 @@ class RaftNotaryCordform : CordformDefinition() { } override fun setup(context: CordformContext) { - IdentityGenerator.generateDistributedNotaryIdentity( + ServiceIdentityGenerator.generateToDisk( notaryNames.map { context.baseDirectory(it.toString()) }, - clusterName - ) + clusterName, + NotaryService.constructId(validating = true, raft = true)) } } diff --git a/testing/node-driver/build.gradle b/testing/node-driver/build.gradle index a13ec48018..c50a027047 100644 --- a/testing/node-driver/build.gradle +++ b/testing/node-driver/build.gradle @@ -29,7 +29,7 @@ dependencies { compile "net.corda.plugins:cordform-common:$gradle_plugins_version" // Integration test helpers - testCompile "org.assertj:assertj-core:$assertj_version" + integrationTestCompile "org.assertj:assertj-core:${assertj_version}" integrationTestCompile "junit:junit:$junit_version" // Jetty dependencies for NetworkMapClient test. diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt index 84fe07acc5..34cd93d367 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt @@ -9,7 +9,6 @@ import net.corda.core.crypto.random63BitValue import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party import net.corda.core.identity.PartyAndCertificate -import net.corda.core.internal.VisibleForTesting import net.corda.core.internal.createDirectories import net.corda.core.internal.createDirectory import net.corda.core.internal.uncheckedCast @@ -38,7 +37,7 @@ import net.corda.node.services.transactions.BFTSMaRt import net.corda.node.services.transactions.InMemoryTransactionVerifierService import net.corda.node.utilities.AffinityExecutor import net.corda.node.utilities.AffinityExecutor.ServiceAffinityExecutor -import net.corda.nodeapi.internal.IdentityGenerator +import net.corda.nodeapi.internal.ServiceIdentityGenerator import net.corda.nodeapi.internal.config.User import net.corda.nodeapi.internal.network.NetworkParametersCopier import net.corda.nodeapi.internal.network.NotaryInfo @@ -126,14 +125,14 @@ data class MockNodeArgs( * By default a single notary node is automatically started, which forms part of the network parameters for all the nodes. * This node is available by calling [defaultNotaryNode]. */ -open class MockNetwork(private val cordappPackages: List, - defaultParameters: MockNetworkParameters = MockNetworkParameters(), - private val networkSendManuallyPumped: Boolean = defaultParameters.networkSendManuallyPumped, - private val threadPerNode: Boolean = defaultParameters.threadPerNode, - servicePeerAllocationStrategy: InMemoryMessagingNetwork.ServicePeerAllocationStrategy = defaultParameters.servicePeerAllocationStrategy, - private val defaultFactory: (MockNodeArgs) -> MockNode = defaultParameters.defaultFactory, - initialiseSerialization: Boolean = defaultParameters.initialiseSerialization, - private val notarySpecs: List = defaultParameters.notarySpecs) { +class MockNetwork(private val cordappPackages: List, + defaultParameters: MockNetworkParameters = MockNetworkParameters(), + private val networkSendManuallyPumped: Boolean = defaultParameters.networkSendManuallyPumped, + private val threadPerNode: Boolean = defaultParameters.threadPerNode, + servicePeerAllocationStrategy: InMemoryMessagingNetwork.ServicePeerAllocationStrategy = defaultParameters.servicePeerAllocationStrategy, + private val defaultFactory: (MockNodeArgs) -> MockNode = defaultParameters.defaultFactory, + initialiseSerialization: Boolean = defaultParameters.initialiseSerialization, + private val notarySpecs: List = defaultParameters.notarySpecs) { /** Helper constructor for creating a [MockNetwork] with custom parameters from Java. */ @JvmOverloads constructor(cordappPackages: List, parameters: MockNetworkParameters = MockNetworkParameters()) : this(cordappPackages, defaultParameters = parameters) @@ -142,7 +141,7 @@ open class MockNetwork(private val cordappPackages: List, // Apache SSHD for whatever reason registers a SFTP FileSystemProvider - which gets loaded by JimFS. // This SFTP support loads BouncyCastle, which we want to avoid. // Please see https://issues.apache.org/jira/browse/SSHD-736 - it's easier then to create our own fork of SSHD - SecurityUtils.setAPrioriDisabledProvider("BC", true) // XXX: Why isn't this static? + SecurityUtils.setAPrioriDisabledProvider("BC", true) } var nextNodeId = 0 @@ -160,7 +159,6 @@ open class MockNetwork(private val cordappPackages: List, throw IllegalStateException("Using more than one MockNetwork simultaneously is not supported.", e) } private val sharedUserCount = AtomicInteger(0) - /** A read only view of the current set of nodes. */ val nodes: List get() = _nodes @@ -174,29 +172,32 @@ open class MockNetwork(private val cordappPackages: List, * Returns the single notary node on the network. Throws if there are none or more than one. * @see notaryNodes */ - val defaultNotaryNode: StartedNode get() { - return when (notaryNodes.size) { - 0 -> throw IllegalStateException("There are no notaries defined on the network") - 1 -> notaryNodes[0] - else -> throw IllegalStateException("There is more than one notary defined on the network") + val defaultNotaryNode: StartedNode + get() { + return when (notaryNodes.size) { + 0 -> throw IllegalStateException("There are no notaries defined on the network") + 1 -> notaryNodes[0] + else -> throw IllegalStateException("There is more than one notary defined on the network") + } } - } /** * Return the identity of the default notary node. * @see defaultNotaryNode */ - val defaultNotaryIdentity: Party get() { - return defaultNotaryNode.info.legalIdentities.singleOrNull() ?: throw IllegalStateException("Default notary has multiple identities") - } + val defaultNotaryIdentity: Party + get() { + return defaultNotaryNode.info.legalIdentities.singleOrNull() ?: throw IllegalStateException("Default notary has multiple identities") + } /** * Return the identity of the default notary node. * @see defaultNotaryNode */ - val defaultNotaryIdentityAndCert: PartyAndCertificate get() { - return defaultNotaryNode.info.legalIdentitiesAndCerts.singleOrNull() ?: throw IllegalStateException("Default notary has multiple identities") - } + val defaultNotaryIdentityAndCert: PartyAndCertificate + get() { + return defaultNotaryNode.info.legalIdentitiesAndCerts.singleOrNull() ?: throw IllegalStateException("Default notary has multiple identities") + } /** * Because this executor is shared, we need to be careful about nodes shutting it down. @@ -221,31 +222,27 @@ open class MockNetwork(private val cordappPackages: List, } init { - try { - filesystem.getPath("/nodes").createDirectory() - val notaryInfos = generateNotaryIdentities() - // The network parameters must be serialised before starting any of the nodes - networkParameters = NetworkParametersCopier(testNetworkParameters(notaryInfos)) - @Suppress("LeakingThis") - notaryNodes = createNotaries() - } catch (t: Throwable) { - stopNodes() - throw t - } + filesystem.getPath("/nodes").createDirectory() + val notaryInfos = generateNotaryIdentities() + // The network parameters must be serialised before starting any of the nodes + networkParameters = NetworkParametersCopier(testNetworkParameters(notaryInfos)) + notaryNodes = createNotaries() } private fun generateNotaryIdentities(): List { return notarySpecs.mapIndexed { index, (name, validating) -> - val identity = IdentityGenerator.generateNodeIdentity(baseDirectory(nextNodeId + index), name) + val identity = ServiceIdentityGenerator.generateToDisk( + dirs = listOf(baseDirectory(nextNodeId + index)), + serviceName = name, + serviceId = "identity") NotaryInfo(identity, validating) } } - @VisibleForTesting - internal open fun createNotaries(): List> { - return notarySpecs.map { (name, validating) -> - createNode(MockNodeParameters(legalName = name, configOverrides = { - doReturn(NotaryConfig(validating)).whenever(it).notary + private fun createNotaries(): List> { + return notarySpecs.map { spec -> + createNode(MockNodeParameters(legalName = spec.name, configOverrides = { + doReturn(NotaryConfig(spec.validating)).whenever(it).notary })) } } @@ -302,7 +299,7 @@ open class MockNetwork(private val cordappPackages: List, id, serverThread, myNotaryIdentity, - configuration.myLegalName, + myLegalName, database).also { runOnStop += it::stop } } @@ -460,11 +457,8 @@ open class MockNetwork(private val cordappPackages: List, } fun stopNodes() { - try { - nodes.forEach { it.started?.dispose() } - } finally { - serializationEnv.unset() // Must execute even if other parts of this method fail. - } + nodes.forEach { it.started?.dispose() } + serializationEnv.unset() messagingNetwork.stop() } diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt index 5833840bb4..308ac4fe5a 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt @@ -19,6 +19,7 @@ import net.corda.core.internal.createDirectories import net.corda.core.internal.div import net.corda.core.messaging.CordaRPCOps import net.corda.core.node.services.NetworkMapCache +import net.corda.core.node.services.NotaryService import net.corda.core.toFuture import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.contextLogger @@ -29,9 +30,12 @@ import net.corda.node.internal.NodeStartup import net.corda.node.internal.StartedNode import net.corda.node.services.Permissions import net.corda.node.services.config.* +import net.corda.node.services.transactions.BFTNonValidatingNotaryService +import net.corda.node.services.transactions.RaftNonValidatingNotaryService +import net.corda.node.services.transactions.RaftValidatingNotaryService import net.corda.node.utilities.registration.HTTPNetworkRegistrationService import net.corda.node.utilities.registration.NetworkRegistrationHelper -import net.corda.nodeapi.internal.IdentityGenerator +import net.corda.nodeapi.internal.ServiceIdentityGenerator import net.corda.nodeapi.internal.addShutdownHook import net.corda.nodeapi.internal.config.User import net.corda.nodeapi.internal.config.parseAs @@ -241,9 +245,9 @@ class DriverDSLImpl( } private enum class ClusterType(val validating: Boolean, val clusterName: CordaX500Name) { - VALIDATING_RAFT(true, CordaX500Name("Raft", "Zurich", "CH")), - NON_VALIDATING_RAFT(false, CordaX500Name("Raft", "Zurich", "CH")), - NON_VALIDATING_BFT(false, CordaX500Name("BFT", "Zurich", "CH")) + VALIDATING_RAFT(true, CordaX500Name(RaftValidatingNotaryService.id, "Raft", "Zurich", "CH")), + NON_VALIDATING_RAFT(false, CordaX500Name(RaftNonValidatingNotaryService.id, "Raft", "Zurich", "CH")), + NON_VALIDATING_BFT(false, CordaX500Name(BFTNonValidatingNotaryService.id, "BFT", "Zurich", "CH")) } internal fun startCordformNodes(cordforms: List): CordaFuture<*> { @@ -266,15 +270,24 @@ class DriverDSLImpl( clusterNodes.put(ClusterType.NON_VALIDATING_BFT, name) } else { // We have all we need here to generate the identity for single node notaries - val identity = IdentityGenerator.generateNodeIdentity(baseDirectory(name), legalName = name) + val identity = ServiceIdentityGenerator.generateToDisk( + dirs = listOf(baseDirectory(name)), + serviceName = name, + serviceId = "identity" + ) notaryInfos += NotaryInfo(identity, notaryConfig.validating) } } clusterNodes.asMap().forEach { type, nodeNames -> - val identity = IdentityGenerator.generateDistributedNotaryIdentity( + val identity = ServiceIdentityGenerator.generateToDisk( dirs = nodeNames.map { baseDirectory(it) }, - notaryName = type.clusterName + serviceName = type.clusterName, + serviceId = NotaryService.constructId( + validating = type.validating, + raft = type in setOf(VALIDATING_RAFT, NON_VALIDATING_RAFT), + bft = type == ClusterType.NON_VALIDATING_BFT + ) ) notaryInfos += NotaryInfo(identity, type.validating) } @@ -356,11 +369,20 @@ class DriverDSLImpl( private fun generateNotaryIdentities(): List { return notarySpecs.map { spec -> val identity = if (spec.cluster == null) { - IdentityGenerator.generateNodeIdentity(baseDirectory(spec.name), spec.name, compatibilityZone?.rootCert) + ServiceIdentityGenerator.generateToDisk( + dirs = listOf(baseDirectory(spec.name)), + serviceName = spec.name, + serviceId = "identity", + customRootCert = compatibilityZone?.rootCert + ) } else { - IdentityGenerator.generateDistributedNotaryIdentity( + ServiceIdentityGenerator.generateToDisk( dirs = generateNodeNames(spec).map { baseDirectory(it) }, - notaryName = spec.name, + serviceName = spec.name, + serviceId = NotaryService.constructId( + validating = spec.validating, + raft = spec.cluster is ClusterSpec.Raft + ), customRootCert = compatibilityZone?.rootCert ) } diff --git a/testing/node-driver/src/test/kotlin/net/corda/testing/node/MockNetworkTests.kt b/testing/node-driver/src/test/kotlin/net/corda/testing/node/MockNetworkTests.kt deleted file mode 100644 index 10d9358243..0000000000 --- a/testing/node-driver/src/test/kotlin/net/corda/testing/node/MockNetworkTests.kt +++ /dev/null @@ -1,18 +0,0 @@ -package net.corda.testing.node - -import net.corda.core.serialization.internal.effectiveSerializationEnv -import org.assertj.core.api.Assertions.assertThatThrownBy -import org.junit.Test - -class MockNetworkTests { - @Test - fun `does not leak serialization env if init fails`() { - val e = Exception("didn't work") - assertThatThrownBy { - object : MockNetwork(emptyList(), initialiseSerialization = true) { - override fun createNotaries() = throw e - } - }.isSameAs(e) - assertThatThrownBy { effectiveSerializationEnv }.isInstanceOf(IllegalStateException::class.java) - } -} diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/CoreTestUtils.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/CoreTestUtils.kt index 860af415ac..11e572e2d7 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/CoreTestUtils.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/CoreTestUtils.kt @@ -10,7 +10,6 @@ import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party import net.corda.core.identity.PartyAndCertificate import net.corda.core.internal.cert -import net.corda.core.internal.unspecifiedCountry import net.corda.core.internal.x500Name import net.corda.core.node.NodeInfo import net.corda.core.utilities.NetworkHostAndPort @@ -92,13 +91,13 @@ fun getTestPartyAndCertificate(party: Party): PartyAndCertificate { val trustRoot: X509CertificateHolder = DEV_TRUST_ROOT val intermediate: CertificateAndKeyPair = DEV_CA - + val nodeCaName = party.name.copy(commonName = X509Utilities.CORDA_CLIENT_CA_CN) val nodeCaKeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) val nodeCaCert = X509Utilities.createCertificate( CertificateType.NODE_CA, intermediate.certificate, intermediate.keyPair, - party.name, + nodeCaName, nodeCaKeyPair.public, nameConstraints = NameConstraints(arrayOf(GeneralSubtree(GeneralName(GeneralName.directoryName, party.name.x500Name))), arrayOf())) diff --git a/tools/demobench/src/main/kotlin/net/corda/demobench/model/NodeController.kt b/tools/demobench/src/main/kotlin/net/corda/demobench/model/NodeController.kt index ee6b38fdb5..37e019b367 100644 --- a/tools/demobench/src/main/kotlin/net/corda/demobench/model/NodeController.kt +++ b/tools/demobench/src/main/kotlin/net/corda/demobench/model/NodeController.kt @@ -13,7 +13,7 @@ import net.corda.demobench.pty.R3Pty import net.corda.nodeapi.internal.network.NetworkParameters import net.corda.nodeapi.internal.network.NetworkParametersCopier import net.corda.nodeapi.internal.network.NotaryInfo -import net.corda.nodeapi.internal.IdentityGenerator +import net.corda.nodeapi.internal.ServiceIdentityGenerator import tornadofx.* import java.io.IOException import java.lang.management.ManagementFactory @@ -153,7 +153,10 @@ class NodeController(check: atRuntime = ::checkExists) : Controller() { // Generate notary identity and save it into node's directory. This identity will be used in network parameters. private fun getNotaryIdentity(config: NodeConfigWrapper): Party { - return IdentityGenerator.generateNodeIdentity(config.nodeDir, config.nodeConfig.myLegalName) + return ServiceIdentityGenerator.generateToDisk( + dirs = listOf(config.nodeDir), + serviceName = config.nodeConfig.myLegalName, + serviceId = "identity") } fun reset() {