Fix the command line notary demos (#875)

* The fancy classloader setup in Cordform.loadCordformDefinition is defeated by certificates in 2 ways
** Class cast when creating a cert (via TestConstants) in a CordformDefinition
** Some issue when persisting a cert in ServiceIdentityGenerator
* This PR is a quickfix to get the demos working again
* Proper fix (not this PR) is to work out why it's not enough for eddsa to be in the project runtime classpath - if it can be removed from the gradle environment that ought to properly fix the above problems
This commit is contained in:
Andrzej Cichocki 2017-06-21 10:25:11 +01:00 committed by GitHub
parent 8aa325d9f7
commit ef74a0a618
9 changed files with 34 additions and 45 deletions

View File

@ -23,8 +23,8 @@ val DUMMY_KEY_2: KeyPair by lazy { generateKeyPair() }
val DUMMY_NOTARY_KEY: KeyPair by lazy { entropyToKeyPair(BigInteger.valueOf(20)) } val DUMMY_NOTARY_KEY: KeyPair by lazy { entropyToKeyPair(BigInteger.valueOf(20)) }
/** Dummy notary identity for tests and simulations */ /** Dummy notary identity for tests and simulations */
val DUMMY_NOTARY_IDENTITY: PartyAndCertificate get() = getTestPartyAndCertificate(X500Name("CN=Notary Service,O=R3,OU=corda,L=Zurich,C=CH"), DUMMY_NOTARY_KEY.public) val DUMMY_NOTARY_IDENTITY: PartyAndCertificate get() = getTestPartyAndCertificate(DUMMY_NOTARY)
val DUMMY_NOTARY: Party get() = DUMMY_NOTARY_IDENTITY.party val DUMMY_NOTARY: Party get() = Party(X500Name("CN=Notary Service,O=R3,OU=corda,L=Zurich,C=CH"), DUMMY_NOTARY_KEY.public)
val DUMMY_MAP_KEY: KeyPair by lazy { entropyToKeyPair(BigInteger.valueOf(30)) } val DUMMY_MAP_KEY: KeyPair by lazy { entropyToKeyPair(BigInteger.valueOf(30)) }
/** Dummy network map service identity for tests and simulations */ /** Dummy network map service identity for tests and simulations */
@ -44,13 +44,13 @@ val DUMMY_BANK_C: Party get() = Party(X500Name("CN=Bank C,O=Bank C,L=Tokyo,C=JP"
val ALICE_KEY: KeyPair by lazy { entropyToKeyPair(BigInteger.valueOf(70)) } val ALICE_KEY: KeyPair by lazy { entropyToKeyPair(BigInteger.valueOf(70)) }
/** Dummy individual identity for tests and simulations */ /** Dummy individual identity for tests and simulations */
val ALICE_IDENTITY: PartyAndCertificate get() = getTestPartyAndCertificate(X500Name("CN=Alice Corp,O=Alice Corp,L=Madrid,C=ES"), ALICE_KEY.public) val ALICE_IDENTITY: PartyAndCertificate get() = getTestPartyAndCertificate(ALICE)
val ALICE: Party get() = ALICE_IDENTITY.party val ALICE: Party get() = Party(X500Name("CN=Alice Corp,O=Alice Corp,L=Madrid,C=ES"), ALICE_KEY.public)
val BOB_KEY: KeyPair by lazy { entropyToKeyPair(BigInteger.valueOf(80)) } val BOB_KEY: KeyPair by lazy { entropyToKeyPair(BigInteger.valueOf(80)) }
/** Dummy individual identity for tests and simulations */ /** Dummy individual identity for tests and simulations */
val BOB_IDENTITY: PartyAndCertificate get() = getTestPartyAndCertificate(X500Name("CN=Bob Plc,O=Bob Plc,L=Rome,C=IT"), BOB_KEY.public) val BOB_IDENTITY: PartyAndCertificate get() = getTestPartyAndCertificate(BOB)
val BOB: Party get() = BOB_IDENTITY.party val BOB: Party get() = Party(X500Name("CN=Bob Plc,O=Bob Plc,L=Rome,C=IT"), BOB_KEY.public)
val CHARLIE_KEY: KeyPair by lazy { entropyToKeyPair(BigInteger.valueOf(90)) } val CHARLIE_KEY: KeyPair by lazy { entropyToKeyPair(BigInteger.valueOf(90)) }
/** Dummy individual identity for tests and simulations */ /** Dummy individual identity for tests and simulations */
@ -70,9 +70,11 @@ val DUMMY_CA: CertificateAndKeyPair by lazy {
/** /**
* Build a test party with a nonsense certificate authority for testing purposes. * Build a test party with a nonsense certificate authority for testing purposes.
*/ */
fun getTestPartyAndCertificate(name: X500Name, publicKey: PublicKey, trustRoot: CertificateAndKeyPair = DUMMY_CA): PartyAndCertificate { fun getTestPartyAndCertificate(name: X500Name, publicKey: PublicKey, trustRoot: CertificateAndKeyPair = DUMMY_CA) = getTestPartyAndCertificate(Party(name, publicKey), trustRoot)
fun getTestPartyAndCertificate(party: Party, trustRoot: CertificateAndKeyPair = DUMMY_CA): PartyAndCertificate {
val certFactory = CertificateFactory.getInstance("X509") val certFactory = CertificateFactory.getInstance("X509")
val certHolder = X509Utilities.createCertificate(CertificateType.IDENTITY, trustRoot.certificate, trustRoot.keyPair, name, publicKey) val certHolder = X509Utilities.createCertificate(CertificateType.IDENTITY, trustRoot.certificate, trustRoot.keyPair, party.name, party.owningKey)
val certPath = certFactory.generateCertPath(listOf(certHolder.cert, trustRoot.certificate.cert)) val certPath = certFactory.generateCertPath(listOf(certHolder.cert, trustRoot.certificate.cert))
return PartyAndCertificate(name, publicKey, certHolder, certPath) return PartyAndCertificate(party, certHolder, certPath)
} }

View File

@ -10,7 +10,6 @@ import net.corda.core.crypto.appendToCommonName
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.node.services.ServiceInfo import net.corda.core.node.services.ServiceInfo
import net.corda.core.utilities.ALICE import net.corda.core.utilities.ALICE
import net.corda.core.utilities.DUMMY_CA
import net.corda.core.utilities.DUMMY_NOTARY import net.corda.core.utilities.DUMMY_NOTARY
import net.corda.flows.NotaryError import net.corda.flows.NotaryError
import net.corda.flows.NotaryException import net.corda.flows.NotaryException
@ -39,9 +38,8 @@ class BFTNotaryServiceTests : NodeBasedTest() {
val replicaNames = replicaIds.map { DUMMY_NOTARY.name.appendToCommonName(" $it") } val replicaNames = replicaIds.map { DUMMY_NOTARY.name.appendToCommonName(" $it") }
val party = ServiceIdentityGenerator.generateToDisk( val party = ServiceIdentityGenerator.generateToDisk(
replicaNames.map { baseDirectory(it) }, replicaNames.map { baseDirectory(it) },
DUMMY_CA,
serviceType.id, serviceType.id,
clusterName).party clusterName)
val advertisedServices = setOf(ServiceInfo(serviceType, clusterName)) val advertisedServices = setOf(ServiceInfo(serviceType, clusterName))
val config = mapOf("notaryClusterAddresses" to replicaIds.map { "localhost:${11000 + it * 10}" }) val config = mapOf("notaryClusterAddresses" to replicaIds.map { "localhost:${11000 + it * 10}" })
return Futures.allAsList(replicaIds.map { return Futures.allAsList(replicaIds.map {

View File

@ -64,7 +64,6 @@ class P2PMessagingTest : NodeBasedTest() {
fun `communicating with a distributed service which the network map node is part of`() { fun `communicating with a distributed service which the network map node is part of`() {
ServiceIdentityGenerator.generateToDisk( ServiceIdentityGenerator.generateToDisk(
listOf(DUMMY_MAP.name, SERVICE_2_NAME).map { baseDirectory(it) }, listOf(DUMMY_MAP.name, SERVICE_2_NAME).map { baseDirectory(it) },
DUMMY_CA,
RaftValidatingNotaryService.type.id, RaftValidatingNotaryService.type.id,
DISTRIBUTED_SERVICE_NAME) DISTRIBUTED_SERVICE_NAME)

View File

@ -23,7 +23,9 @@ import net.corda.core.serialization.SerializeAsToken
import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.core.serialization.SingletonSerializeAsToken
import net.corda.core.serialization.deserialize import net.corda.core.serialization.deserialize
import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.DUMMY_CA
import net.corda.core.utilities.debug import net.corda.core.utilities.debug
import net.corda.core.utilities.getTestPartyAndCertificate
import net.corda.flows.* import net.corda.flows.*
import net.corda.node.services.* import net.corda.node.services.*
import net.corda.node.services.api.* import net.corda.node.services.api.*
@ -57,7 +59,6 @@ import net.corda.node.utilities.configureDatabase
import net.corda.node.utilities.transaction import net.corda.node.utilities.transaction
import org.apache.activemq.artemis.utils.ReusableLatch import org.apache.activemq.artemis.utils.ReusableLatch
import org.bouncycastle.asn1.x500.X500Name import org.bouncycastle.asn1.x500.X500Name
import org.bouncycastle.cert.X509CertificateHolder
import org.jetbrains.exposed.sql.Database import org.jetbrains.exposed.sql.Database
import org.slf4j.Logger import org.slf4j.Logger
import rx.Observable import rx.Observable
@ -215,8 +216,7 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
// Do all of this in a database transaction so anything that might need a connection has one. // Do all of this in a database transaction so anything that might need a connection has one.
initialiseDatabasePersistence { initialiseDatabasePersistence {
val keyStoreWrapper = KeyStoreWrapper(configuration.trustStoreFile, configuration.trustStorePassword) val tokenizableServices = makeServices()
val tokenizableServices = makeServices(keyStoreWrapper)
smm = StateMachineManager(services, smm = StateMachineManager(services,
checkpointStorage, checkpointStorage,
@ -439,8 +439,7 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
* Builds node internal, advertised, and plugin services. * Builds node internal, advertised, and plugin services.
* Returns a list of tokenizable services to be added to the serialisation context. * Returns a list of tokenizable services to be added to the serialisation context.
*/ */
private fun makeServices(keyStoreWrapper: KeyStoreWrapper): MutableList<Any> { private fun makeServices(): MutableList<Any> {
val keyStore = keyStoreWrapper.keyStore
val storageServices = initialiseStorageService(configuration.baseDirectory) val storageServices = initialiseStorageService(configuration.baseDirectory)
storage = storageServices.first storage = storageServices.first
checkpointStorage = storageServices.second checkpointStorage = storageServices.second
@ -778,7 +777,7 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
// Check that the identity in the config file matches the identity file we have stored to disk. // Check that the identity in the config file matches the identity file we have stored to disk.
// This is just a sanity check. It shouldn't fail unless the admin has fiddled with the files and messed // This is just a sanity check. It shouldn't fail unless the admin has fiddled with the files and messed
// things up for us. // things up for us.
val myIdentity = pubIdentityFile.readAll().deserialize<PartyAndCertificate>() val myIdentity = pubIdentityFile.readAll().deserialize<Party>()
if (myIdentity.name != serviceName) if (myIdentity.name != serviceName)
throw ConfigurationException("The legal name in the config file doesn't match the stored identity file:" + throw ConfigurationException("The legal name in the config file doesn't match the stored identity file:" +
"$serviceName vs ${myIdentity.name}") "$serviceName vs ${myIdentity.name}")
@ -787,7 +786,13 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
if (myIdentity.owningKey !is CompositeKey) { // TODO: Support case where owningKey is a composite key. if (myIdentity.owningKey !is CompositeKey) { // TODO: Support case where owningKey is a composite key.
keyStore.save(serviceName, privateKeyAlias, keyPair) keyStore.save(serviceName, privateKeyAlias, keyPair)
} }
Pair(myIdentity, keyPair) val partyAndCertificate = getTestPartyAndCertificate(myIdentity)
// Sanity check the certificate and path
val validatorParameters = PKIXParameters(setOf(TrustAnchor(DUMMY_CA.certificate.cert, null)))
val validator = CertPathValidator.getInstance("PKIX")
validatorParameters.isRevocationEnabled = false
validator.validate(partyAndCertificate.certPath, validatorParameters) as PKIXCertPathValidatorResult
Pair(partyAndCertificate, keyPair)
} else { } else {
val clientCertPath = keyStore.keyStore.getCertificateChain(X509Utilities.CORDA_CLIENT_CA) val clientCertPath = keyStore.keyStore.getCertificateChain(X509Utilities.CORDA_CLIENT_CA)
val clientCA = keyStore.certificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA)!! val clientCA = keyStore.certificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA)!!

View File

@ -1,7 +1,9 @@
package net.corda.node.utilities package net.corda.node.utilities
import net.corda.core.crypto.* import net.corda.core.crypto.CompositeKey
import net.corda.core.identity.PartyAndCertificate import net.corda.core.crypto.X509Utilities
import net.corda.core.crypto.generateKeyPair
import net.corda.core.identity.Party
import net.corda.core.serialization.serialize import net.corda.core.serialization.serialize
import net.corda.core.serialization.storageKryo import net.corda.core.serialization.storageKryo
import net.corda.core.utilities.loggerFor import net.corda.core.utilities.loggerFor
@ -9,7 +11,6 @@ import net.corda.core.utilities.trace
import org.bouncycastle.asn1.x500.X500Name import org.bouncycastle.asn1.x500.X500Name
import java.nio.file.Files import java.nio.file.Files
import java.nio.file.Path import java.nio.file.Path
import java.security.cert.*
object ServiceIdentityGenerator { object ServiceIdentityGenerator {
private val log = loggerFor<ServiceIdentityGenerator>() private val log = loggerFor<ServiceIdentityGenerator>()
@ -20,35 +21,23 @@ object ServiceIdentityGenerator {
* This method should be called *before* any of the nodes are started. * 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 dirs List of node directories to place the generated identity and key pairs in.
* @param serviceCa Certificate authority to use when signing identity certificates.
* @param serviceId The service id of the distributed service. * @param serviceId The service id of the distributed service.
* @param serviceName The legal name of the distributed service. * @param serviceName The legal name of the distributed service.
* @param threshold The threshold for the generated group [CompositeKey]. * @param threshold The threshold for the generated group [CompositeKey].
*/ */
// TODO: This needs to write out to the key store, not just files on disk // TODO: This needs to write out to the key store, not just files on disk
fun generateToDisk(dirs: List<Path>, fun generateToDisk(dirs: List<Path>,
serviceCa: CertificateAndKeyPair,
serviceId: String, serviceId: String,
serviceName: X500Name, serviceName: X500Name,
threshold: Int = 1): PartyAndCertificate { threshold: Int = 1): Party {
log.trace { "Generating a group identity \"serviceName\" for nodes: ${dirs.joinToString()}" } log.trace { "Generating a group identity \"serviceName\" for nodes: ${dirs.joinToString()}" }
val keyPairs = (1..dirs.size).map { generateKeyPair() } val keyPairs = (1..dirs.size).map { generateKeyPair() }
val notaryKey = CompositeKey.Builder().addKeys(keyPairs.map { it.public }).build(threshold) val notaryKey = CompositeKey.Builder().addKeys(keyPairs.map { it.public }).build(threshold)
val certFactory = CertificateFactory.getInstance("X509") // Avoid adding complexity! This class is a hack that needs to stay runnable in the gradle environment.
val notaryCert = X509Utilities.createCertificate(CertificateType.IDENTITY, serviceCa.certificate, val notaryParty = Party(serviceName, notaryKey)
serviceCa.keyPair, serviceName, notaryKey)
val notaryCertPath = certFactory.generateCertPath(listOf(notaryCert.cert, serviceCa.certificate.cert))
val notaryParty = PartyAndCertificate(serviceName, notaryKey, notaryCert, notaryCertPath)
val notaryPartyBytes = notaryParty.serialize() val notaryPartyBytes = notaryParty.serialize()
val privateKeyFile = "$serviceId-private-key" val privateKeyFile = "$serviceId-private-key"
val publicKeyFile = "$serviceId-public" val publicKeyFile = "$serviceId-public"
// Sanity check the certificate and path
val validatorParameters = PKIXParameters(setOf(TrustAnchor(serviceCa.certificate.cert, null)))
val validator = CertPathValidator.getInstance("PKIX")
validatorParameters.isRevocationEnabled = false
validator.validate(notaryCertPath, validatorParameters) as PKIXCertPathValidatorResult
keyPairs.zip(dirs) { keyPair, dir -> keyPairs.zip(dirs) { keyPair, dir ->
Files.createDirectories(dir) Files.createDirectories(dir)
notaryPartyBytes.writeToFile(dir.resolve(publicKeyFile)) notaryPartyBytes.writeToFile(dir.resolve(publicKeyFile))

View File

@ -5,7 +5,6 @@ import net.corda.core.div
import net.corda.core.node.services.ServiceInfo import net.corda.core.node.services.ServiceInfo
import net.corda.core.utilities.ALICE import net.corda.core.utilities.ALICE
import net.corda.core.utilities.BOB import net.corda.core.utilities.BOB
import net.corda.core.utilities.DUMMY_CA
import net.corda.demorun.util.* import net.corda.demorun.util.*
import net.corda.demorun.runNodes import net.corda.demorun.runNodes
import net.corda.node.services.transactions.BFTNonValidatingNotaryService import net.corda.node.services.transactions.BFTNonValidatingNotaryService
@ -66,6 +65,6 @@ object BFTNotaryCordform : CordformDefinition("build" / "notary-demo-nodes", not
} }
override fun setup(context: CordformContext) { override fun setup(context: CordformContext) {
ServiceIdentityGenerator.generateToDisk(notaryNames.map { context.baseDirectory(it) }, DUMMY_CA, advertisedService.type.id, clusterName, minCorrectReplicas(clusterSize)) ServiceIdentityGenerator.generateToDisk(notaryNames.map { context.baseDirectory(it) }, advertisedService.type.id, clusterName, minCorrectReplicas(clusterSize))
} }
} }

View File

@ -6,7 +6,6 @@ import net.corda.core.div
import net.corda.core.node.services.ServiceInfo import net.corda.core.node.services.ServiceInfo
import net.corda.core.utilities.ALICE import net.corda.core.utilities.ALICE
import net.corda.core.utilities.BOB import net.corda.core.utilities.BOB
import net.corda.core.utilities.DUMMY_CA
import net.corda.core.utilities.DUMMY_NOTARY import net.corda.core.utilities.DUMMY_NOTARY
import net.corda.demorun.util.* import net.corda.demorun.util.*
import net.corda.node.services.transactions.RaftValidatingNotaryService import net.corda.node.services.transactions.RaftValidatingNotaryService
@ -66,6 +65,6 @@ object RaftNotaryCordform : CordformDefinition("build" / "notary-demo-nodes", no
} }
override fun setup(context: CordformContext) { override fun setup(context: CordformContext) {
ServiceIdentityGenerator.generateToDisk(notaryNames.map { context.baseDirectory(it) }, DUMMY_CA, advertisedService.type.id, clusterName) ServiceIdentityGenerator.generateToDisk(notaryNames.map { context.baseDirectory(it) }, advertisedService.type.id, clusterName)
} }
} }

View File

@ -613,7 +613,7 @@ class DriverDSL(
): ListenableFuture<Pair<Party, List<NodeHandle>>> { ): ListenableFuture<Pair<Party, List<NodeHandle>>> {
val nodeNames = (0 until clusterSize).map { DUMMY_NOTARY.name.appendToCommonName(" $it") } val nodeNames = (0 until clusterSize).map { DUMMY_NOTARY.name.appendToCommonName(" $it") }
val paths = nodeNames.map { baseDirectory(it) } val paths = nodeNames.map { baseDirectory(it) }
ServiceIdentityGenerator.generateToDisk(paths, DUMMY_CA, type.id, notaryName) ServiceIdentityGenerator.generateToDisk(paths, type.id, notaryName)
val advertisedServices = setOf(ServiceInfo(type, notaryName)) val advertisedServices = setOf(ServiceInfo(type, notaryName))
val notaryClusterAddress = portAllocation.nextHostAndPort() val notaryClusterAddress = portAllocation.nextHostAndPort()

View File

@ -9,7 +9,6 @@ import net.corda.core.crypto.appendToCommonName
import net.corda.core.crypto.commonName import net.corda.core.crypto.commonName
import net.corda.core.node.services.ServiceInfo import net.corda.core.node.services.ServiceInfo
import net.corda.core.node.services.ServiceType import net.corda.core.node.services.ServiceType
import net.corda.core.utilities.DUMMY_CA
import net.corda.core.utilities.DUMMY_MAP import net.corda.core.utilities.DUMMY_MAP
import net.corda.core.utilities.WHITESPACE import net.corda.core.utilities.WHITESPACE
import net.corda.node.internal.Node import net.corda.node.internal.Node
@ -113,7 +112,6 @@ abstract class NodeBasedTest {
serviceType: ServiceType = RaftValidatingNotaryService.type): ListenableFuture<List<Node>> { serviceType: ServiceType = RaftValidatingNotaryService.type): ListenableFuture<List<Node>> {
ServiceIdentityGenerator.generateToDisk( ServiceIdentityGenerator.generateToDisk(
(0 until clusterSize).map { baseDirectory(notaryName.appendToCommonName("-$it")) }, (0 until clusterSize).map { baseDirectory(notaryName.appendToCommonName("-$it")) },
DUMMY_CA,
serviceType.id, serviceType.id,
notaryName) notaryName)