diff --git a/.ci/api-current.txt b/.ci/api-current.txt index d31f2cbd19..c44ad203da 100644 --- a/.ci/api-current.txt +++ b/.ci/api-current.txt @@ -1373,8 +1373,6 @@ public final class net.corda.core.crypto.CordaSecurityProvider extends java.secu public static final class net.corda.core.crypto.CordaSecurityProvider$Companion extends java.lang.Object ## public final class net.corda.core.crypto.CordaSecurityProviderKt extends java.lang.Object - @NotNull - public static final String CORDA_SECURE_RANDOM_ALGORITHM = "CordaPRNG" ## public final class net.corda.core.crypto.Crypto extends java.lang.Object @NotNull @@ -1509,13 +1507,6 @@ public final class net.corda.core.crypto.CryptoUtils extends java.lang.Object public static final boolean verify(java.security.PublicKey, byte[], net.corda.core.crypto.DigitalSignature) public static final boolean verify(java.security.PublicKey, byte[], byte[]) ## -public final class net.corda.core.crypto.DelegatingSecureRandomSpi extends java.security.SecureRandomSpi - public (kotlin.jvm.functions.Function0) - @Nullable - protected byte[] engineGenerateSeed(int) - protected void engineNextBytes(byte[]) - protected void engineSetSeed(byte[]) -## @CordaSerializable public class net.corda.core.crypto.DigitalSignature extends net.corda.core.utilities.OpaqueBytes public (byte[]) diff --git a/.idea/compiler.xml b/.idea/compiler.xml index 12df0021f7..d9e19c1d1d 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -165,6 +165,8 @@ + + diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index e7b03d89e0..12c30d2e80 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -64,6 +64,7 @@ see changes to this list. * Credit Suisse * cyrsis * Dan Newton (Accenture) +* Daniel Krajnik (BCS Technology International) * Daniel Roig (SEB) * Dave Hudson (R3) * David John Grundy (Dankse Bank) diff --git a/build.gradle b/build.gradle index 712c08788d..6fd3838406 100644 --- a/build.gradle +++ b/build.gradle @@ -77,7 +77,7 @@ buildscript { ext.snappy_version = '0.4' ext.class_graph_version = '4.2.12' ext.jcabi_manifests_version = '1.1' - ext.picocli_version = '3.6.1' + ext.picocli_version = '3.8.0' // Name of the IntelliJ SDK created for the deterministic Java rt.jar. // ext.deterministic_idea_sdk = '1.8 (Deterministic)' diff --git a/client/jackson/src/main/kotlin/net/corda/client/jackson/JacksonSupport.kt b/client/jackson/src/main/kotlin/net/corda/client/jackson/JacksonSupport.kt index 856091aa3b..c229b78e63 100644 --- a/client/jackson/src/main/kotlin/net/corda/client/jackson/JacksonSupport.kt +++ b/client/jackson/src/main/kotlin/net/corda/client/jackson/JacksonSupport.kt @@ -72,7 +72,8 @@ object JacksonSupport { override val isFullParties: Boolean = false) : PartyObjectMapper, ObjectMapper(factory) { override fun wellKnownPartyFromX500Name(name: CordaX500Name): Party? = rpc.wellKnownPartyFromX500Name(name) override fun partyFromKey(owningKey: PublicKey): Party? = rpc.partyFromKey(owningKey) - override fun partiesFromName(query: String) = rpc.partiesFromName(query, fuzzyIdentityMatch) + // Second parameter is exactMatch, so we have to invert the meaning here. + override fun partiesFromName(query: String) = rpc.partiesFromName(query, !fuzzyIdentityMatch) override fun nodeInfoFromParty(party: AbstractParty): NodeInfo? = rpc.nodeInfoFromParty(party) } diff --git a/core/src/main/kotlin/net/corda/core/crypto/CordaSecurityProvider.kt b/core/src/main/kotlin/net/corda/core/crypto/CordaSecurityProvider.kt index 61c611076e..4ef594062f 100644 --- a/core/src/main/kotlin/net/corda/core/crypto/CordaSecurityProvider.kt +++ b/core/src/main/kotlin/net/corda/core/crypto/CordaSecurityProvider.kt @@ -1,17 +1,12 @@ package net.corda.core.crypto -import io.netty.util.concurrent.FastThreadLocal import net.corda.core.KeepForDJVM import net.corda.core.StubOutForDJVM import net.corda.core.crypto.CordaObjectIdentifier.COMPOSITE_KEY import net.corda.core.crypto.CordaObjectIdentifier.COMPOSITE_SIGNATURE -import net.corda.core.internal.VisibleForTesting +import net.corda.core.crypto.internal.PlatformSecureRandomService import org.bouncycastle.asn1.ASN1ObjectIdentifier import java.security.Provider -import java.security.SecureRandom -import java.security.SecureRandomSpi - -internal const val CORDA_SECURE_RANDOM_ALGORITHM = "CordaPRNG" @KeepForDJVM class CordaSecurityProvider : Provider(PROVIDER_NAME, 0.1, "$PROVIDER_NAME security provider wrapper") { @@ -24,8 +19,12 @@ class CordaSecurityProvider : Provider(PROVIDER_NAME, 0.1, "$PROVIDER_NAME secur put("Signature.${CompositeSignature.SIGNATURE_ALGORITHM}", CompositeSignature::class.java.name) put("Alg.Alias.Signature.$COMPOSITE_SIGNATURE", CompositeSignature.SIGNATURE_ALGORITHM) put("Alg.Alias.Signature.OID.$COMPOSITE_SIGNATURE", CompositeSignature.SIGNATURE_ALGORITHM) - // Assuming this Provider is the first SecureRandom Provider, this algorithm is the SecureRandom default: - putService(DelegatingSecureRandomService(this)) + putPlatformSecureRandomService() + } + + @StubOutForDJVM + private fun putPlatformSecureRandomService() { + putService(PlatformSecureRandomService(this)) } } @@ -50,28 +49,3 @@ object CordaObjectIdentifier { @JvmField val COMPOSITE_SIGNATURE = ASN1ObjectIdentifier("2.25.30086077608615255153862931087626791003") } - -// Unlike all the NativePRNG algorithms, this doesn't use a global lock: -private class SunSecureRandom : SecureRandom(sun.security.provider.SecureRandom(), null) - -private class DelegatingSecureRandomService(provider: CordaSecurityProvider) : Provider.Service( - provider, type, CORDA_SECURE_RANDOM_ALGORITHM, DelegatingSecureRandomSpi::class.java.name, null, null) { - private companion object { - private const val type = "SecureRandom" - } - - internal val instance = DelegatingSecureRandomSpi(::SunSecureRandom) - override fun newInstance(constructorParameter: Any?) = instance -} - -internal class DelegatingSecureRandomSpi internal constructor(secureRandomFactory: () -> SecureRandom) : SecureRandomSpi() { - private val threadLocalSecureRandom = object : FastThreadLocal() { - override fun initialValue() = secureRandomFactory() - } - - override fun engineSetSeed(seed: ByteArray) = threadLocalSecureRandom.get().setSeed(seed) - override fun engineNextBytes(bytes: ByteArray) = threadLocalSecureRandom.get().nextBytes(bytes) - override fun engineGenerateSeed(numBytes: Int): ByteArray? = threadLocalSecureRandom.get().generateSeed(numBytes) - @VisibleForTesting - internal fun currentThreadSecureRandom() = threadLocalSecureRandom.get() -} diff --git a/core/src/main/kotlin/net/corda/core/crypto/internal/PlatformSecureRandom.kt b/core/src/main/kotlin/net/corda/core/crypto/internal/PlatformSecureRandom.kt index 076a45d168..22564a6f05 100644 --- a/core/src/main/kotlin/net/corda/core/crypto/internal/PlatformSecureRandom.kt +++ b/core/src/main/kotlin/net/corda/core/crypto/internal/PlatformSecureRandom.kt @@ -2,17 +2,52 @@ @file:DeleteForDJVM package net.corda.core.crypto.internal +import io.netty.util.concurrent.FastThreadLocal import net.corda.core.DeleteForDJVM -import net.corda.core.crypto.CORDA_SECURE_RANDOM_ALGORITHM import net.corda.core.crypto.DummySecureRandom import net.corda.core.utilities.SgxSupport +import org.apache.commons.lang.SystemUtils +import java.security.Provider import java.security.SecureRandom +import java.security.SecureRandomSpi /** * This has been migrated into a separate class so that it * is easier to delete from the core-deterministic module. */ -internal val platformSecureRandom = when { - SgxSupport.isInsideEnclave -> DummySecureRandom - else -> SecureRandom.getInstance(CORDA_SECURE_RANDOM_ALGORITHM) +internal val platformSecureRandom: () -> SecureRandom = when { + SgxSupport.isInsideEnclave -> { { DummySecureRandom } } + SystemUtils.IS_OS_LINUX -> { + { SunSecureRandom() } + } + else -> SecureRandom::getInstanceStrong } + +@DeleteForDJVM +class PlatformSecureRandomService(provider: Provider) + : Provider.Service(provider, "SecureRandom", algorithm, PlatformSecureRandomSpi::javaClass.name, null, null) { + + companion object { + const val algorithm = "CordaPRNG" + } + + private val instance: SecureRandomSpi = PlatformSecureRandomSpi() + override fun newInstance(constructorParameter: Any?) = instance +} + +@DeleteForDJVM +private class PlatformSecureRandomSpi : SecureRandomSpi() { + private val threadLocalSecureRandom = object : FastThreadLocal() { + override fun initialValue() = platformSecureRandom() + } + + private val secureRandom: SecureRandom = threadLocalSecureRandom.get() + + override fun engineSetSeed(seed: ByteArray) = secureRandom.setSeed(seed) + override fun engineNextBytes(bytes: ByteArray) = secureRandom.nextBytes(bytes) + override fun engineGenerateSeed(numBytes: Int): ByteArray = secureRandom.generateSeed(numBytes) +} + +// Enterprise performance tweak: Unlike all the NativePRNG algorithms, this doesn't use a global lock: +// TODO: This is using private Java API. Just replace this with an implementation that always reads /dev/urandom on Linux. +private class SunSecureRandom : SecureRandom(sun.security.provider.SecureRandom(), null) diff --git a/core/src/main/kotlin/net/corda/core/crypto/internal/ProviderMap.kt b/core/src/main/kotlin/net/corda/core/crypto/internal/ProviderMap.kt index 995b77aed2..bd45e7490b 100644 --- a/core/src/main/kotlin/net/corda/core/crypto/internal/ProviderMap.kt +++ b/core/src/main/kotlin/net/corda/core/crypto/internal/ProviderMap.kt @@ -18,12 +18,18 @@ import org.bouncycastle.pqc.jcajce.provider.BouncyCastlePQCProvider import java.security.SecureRandom import java.security.Security -internal val cordaSecurityProvider = CordaSecurityProvider().also { +val cordaSecurityProvider = CordaSecurityProvider().also { + // Among the others, we should register [CordaSecurityProvider] as the first provider, to ensure that when invoking [SecureRandom()] + // the [platformSecureRandom] is returned (which is registered in CordaSecurityProvider). + // Note that internally, [SecureRandom()] will look through all registered providers. + // Then it returns the first PRNG algorithm of the first provider that has registered a SecureRandom + // implementation (in our case [CordaSecurityProvider]), or null if none of the registered providers supplies + // a SecureRandom implementation. Security.insertProviderAt(it, 1) // The position is 1-based. } // OID taken from https://tools.ietf.org/html/draft-ietf-curdle-pkix-00 -internal val `id-Curve25519ph` = ASN1ObjectIdentifier("1.3.101.112") -internal val cordaBouncyCastleProvider = BouncyCastleProvider().apply { +val `id-Curve25519ph` = ASN1ObjectIdentifier("1.3.101.112") +val cordaBouncyCastleProvider = BouncyCastleProvider().apply { putAll(EdDSASecurityProvider()) // Override the normal EdDSA engine with one which can handle X509 keys. put("Signature.${EdDSAEngine.SIGNATURE_ALGORITHM}", X509EdDSAEngine::class.java.name) @@ -38,7 +44,7 @@ internal val cordaBouncyCastleProvider = BouncyCastleProvider().apply { // TODO: Find a way to make JKS work with bouncy castle provider or implement our own provide so we don't have to register bouncy castle provider. Security.addProvider(it) } -internal val bouncyCastlePQCProvider = BouncyCastlePQCProvider().apply { +val bouncyCastlePQCProvider = BouncyCastlePQCProvider().apply { require(name == "BCPQC") // The constant it comes from is not final. }.also { Security.addProvider(it) @@ -47,7 +53,7 @@ internal val bouncyCastlePQCProvider = BouncyCastlePQCProvider().apply { // that could cause unexpected and suspicious behaviour. // i.e. if someone removes a Provider and then he/she adds a new one with the same name. // The val is private to avoid any harmful state changes. -internal val providerMap = listOf(cordaBouncyCastleProvider, cordaSecurityProvider, bouncyCastlePQCProvider).map { it.name to it }.toMap() +val providerMap = listOf(cordaBouncyCastleProvider, cordaSecurityProvider, bouncyCastlePQCProvider).map { it.name to it }.toMap() @DeleteForDJVM -internal fun platformSecureRandomFactory(): SecureRandom = platformSecureRandom // To minimise diff of CryptoUtils against open-source. \ No newline at end of file +fun platformSecureRandomFactory(): SecureRandom = platformSecureRandom() // To minimise diff of CryptoUtils against open-source. diff --git a/core/src/test/kotlin/net/corda/core/crypto/CryptoUtilsTest.kt b/core/src/test/kotlin/net/corda/core/crypto/CryptoUtilsTest.kt index cd3a5a511e..2f7ea0d65b 100644 --- a/core/src/test/kotlin/net/corda/core/crypto/CryptoUtilsTest.kt +++ b/core/src/test/kotlin/net/corda/core/crypto/CryptoUtilsTest.kt @@ -6,6 +6,7 @@ import net.corda.core.crypto.Crypto.ECDSA_SECP256R1_SHA256 import net.corda.core.crypto.Crypto.EDDSA_ED25519_SHA512 import net.corda.core.crypto.Crypto.RSA_SHA256 import net.corda.core.crypto.Crypto.SPHINCS256_SHA256 +import net.corda.core.crypto.internal.PlatformSecureRandomService import net.corda.core.identity.CordaX500Name import net.corda.core.utilities.OpaqueBytes import net.corda.nodeapi.internal.DEV_INTERMEDIATE_CA @@ -21,10 +22,7 @@ import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec import org.apache.commons.lang.ArrayUtils.EMPTY_BYTE_ARRAY import org.bouncycastle.asn1.pkcs.PrivateKeyInfo -import org.bouncycastle.asn1.x500.X500Name import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo -import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter -import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey import org.bouncycastle.jce.ECNamedCurveTable @@ -36,9 +34,9 @@ import org.bouncycastle.pqc.jcajce.provider.sphincs.BCSphincs256PublicKey import org.junit.Assert.assertNotEquals import org.junit.Test import java.math.BigInteger -import java.security.KeyPair import java.security.KeyPairGenerator -import java.security.cert.X509Certificate +import java.security.SecureRandom +import java.security.Security import java.util.* import kotlin.test.* @@ -948,26 +946,25 @@ class CryptoUtilsTest { this.outputStream.close() } - private fun createCert(signer: ContentSigner, keyPair: KeyPair): X509Certificate { - val dname = X500Name("CN=TestEntity") - val startDate = Calendar.getInstance().let { cal -> - cal.time = Date() - cal.add(Calendar.HOUR, -1) - cal.time - } - val endDate = Calendar.getInstance().let { cal -> - cal.time = startDate - cal.add(Calendar.YEAR, 1) - cal.time - } - val certificate = JcaX509v3CertificateBuilder( - dname, - BigInteger.TEN, - startDate, - endDate, - dname, - keyPair.public - ).build(signer) - return JcaX509CertificateConverter().getCertificate(certificate) + @Test + fun `test default SecureRandom uses platformSecureRandom`() { + // Note than in Corda, [CordaSecurityProvider] is registered as the first provider. + + // Remove [CordaSecurityProvider] in case it is already registered. + Security.removeProvider(CordaSecurityProvider.PROVIDER_NAME) + // Try after removing CordaSecurityProvider. + val secureRandomNotRegisteredCordaProvider = SecureRandom() + assertNotEquals(PlatformSecureRandomService.algorithm, secureRandomNotRegisteredCordaProvider.algorithm) + + // Now register CordaSecurityProvider as last Provider. + Security.addProvider(CordaSecurityProvider()) + val secureRandomRegisteredLastCordaProvider = SecureRandom() + assertNotEquals(PlatformSecureRandomService.algorithm, secureRandomRegisteredLastCordaProvider.algorithm) + + // Remove Corda Provider again and add it as the first Provider entry. + Security.removeProvider(CordaSecurityProvider.PROVIDER_NAME) + Security.insertProviderAt(CordaSecurityProvider(), 1) // This is base-1. + val secureRandomRegisteredFirstCordaProvider = SecureRandom() + assertEquals(PlatformSecureRandomService.algorithm, secureRandomRegisteredFirstCordaProvider.algorithm) } } diff --git a/core/src/test/kotlin/net/corda/core/crypto/SecureRandomTest.kt b/core/src/test/kotlin/net/corda/core/crypto/SecureRandomTest.kt index dad0fbe099..916a4910a4 100644 --- a/core/src/test/kotlin/net/corda/core/crypto/SecureRandomTest.kt +++ b/core/src/test/kotlin/net/corda/core/crypto/SecureRandomTest.kt @@ -1,41 +1,9 @@ package net.corda.core.crypto -import net.corda.core.crypto.internal.cordaSecurityProvider -import net.corda.core.internal.concurrent.fork -import net.corda.core.internal.join -import net.corda.core.utilities.getOrThrow import org.junit.Test import java.security.SecureRandom -import java.util.concurrent.Executors -import kotlin.test.assertEquals -import kotlin.test.assertNotEquals -import kotlin.test.assertNotSame -import kotlin.test.assertSame class SecureRandomTest { - private companion object { - private val getSpi = SecureRandom::class.java.getDeclaredMethod("getSecureRandomSpi").apply { isAccessible = true } - private fun SecureRandom.spi() = getSpi.invoke(this) - - init { - newSecureRandom() // Ensure all globals installed before running tests. - } - } - - @Test - fun `newSecureRandom returns a global that delegates to thread-local`() { - val sr = newSecureRandom() - assertSame(sr, newSecureRandom()) - checkDelegatesToThreadLocal(sr) - } - - @Test - fun `regular SecureRandom delegates to thread-local`() { - val sr = SecureRandom() - assertSame(sr.spi(), SecureRandom().spi()) - checkDelegatesToThreadLocal(sr) - } - @Test(timeout = 1000) fun `regular SecureRandom does not spend a lot of time seeding itself`() { val bytes = ByteArray(1000) @@ -46,39 +14,4 @@ class SecureRandomTest { } } } - - @Test - fun `regular SecureRandom with seed delegates to thread-local`() { - val sr = SecureRandom(byteArrayOf(1, 2, 3)) - assertSame(sr.spi(), SecureRandom(byteArrayOf(4, 5, 6)).spi()) - checkDelegatesToThreadLocal(sr) - } - - @Test - fun `SecureRandom#getInstance makes a SecureRandom that delegates to thread-local`() { - CORDA_SECURE_RANDOM_ALGORITHM.let { - val sr = SecureRandom.getInstance(it) - assertEquals(it, sr.algorithm) - assertSame(sr.spi(), SecureRandom.getInstance(it).spi()) - checkDelegatesToThreadLocal(sr) - } - } - - private fun checkDelegatesToThreadLocal(sr: SecureRandom) { - val spi = sr.spi() as DelegatingSecureRandomSpi - val fg = spi.currentThreadSecureRandom() - val e = Executors.newSingleThreadExecutor() - val bg = e.fork(spi::currentThreadSecureRandom).getOrThrow() - assertNotSame(fg, bg) // Background thread got a distinct instance. - // Each thread always gets the same instance: - assertSame(fg, spi.currentThreadSecureRandom()) - assertSame(bg, e.fork(spi::currentThreadSecureRandom).getOrThrow()) - e.join() - assertSame(fg.provider, bg.provider) - assertNotSame(cordaSecurityProvider, fg.provider) - assertEquals(fg.algorithm, bg.algorithm) - assertNotEquals(CORDA_SECURE_RANDOM_ALGORITHM, fg.algorithm) - assertSame(cordaSecurityProvider, sr.provider) - assertEquals(CORDA_SECURE_RANDOM_ALGORITHM, sr.algorithm) - } } diff --git a/finance/src/main/kotlin/net/corda/finance/contracts/asset/Cash.kt b/finance/src/main/kotlin/net/corda/finance/contracts/asset/Cash.kt index 34e47a890f..93360e7155 100644 --- a/finance/src/main/kotlin/net/corda/finance/contracts/asset/Cash.kt +++ b/finance/src/main/kotlin/net/corda/finance/contracts/asset/Cash.kt @@ -188,8 +188,7 @@ class Cash : OnLedgerAsset() { // sum to more than the inputs. An issuance of zero size is not allowed. // // Note that this means literally anyone with access to the network can issue cash claims of arbitrary - // amounts! It is up to the recipient to decide if the backing party is trustworthy or not, via some - // as-yet-unwritten identity service. See ADP-22 for discussion. + // amounts! It is up to the recipient to decide if the backing party is trustworthy or not. // The grouping ensures that all outputs have the same deposit reference and currency. val inputAmount = inputs.sumCashOrZero(Issued(issuer, currency)) diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/DevIdentityGenerator.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/DevIdentityGenerator.kt index 562af4472f..d08cd750cb 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/DevIdentityGenerator.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/DevIdentityGenerator.kt @@ -18,6 +18,8 @@ import org.slf4j.LoggerFactory import java.nio.file.Path import java.security.KeyPair import java.security.PublicKey +import java.security.cert.X509Certificate +import javax.security.auth.x500.X500Principal /** * Contains utility methods for generating identities for a node. @@ -43,50 +45,67 @@ object DevIdentityGenerator { return identity.party } - fun generateDistributedNotaryCompositeIdentity(dirs: List, notaryName: CordaX500Name, threshold: Int = 1): Party { - require(dirs.isNotEmpty()) - - log.trace { "Generating composite identity \"$notaryName\" for nodes: ${dirs.joinToString()}" } - val keyPairs = (1..dirs.size).map { generateKeyPair() } - val notaryKey = CompositeKey.Builder().addKeys(keyPairs.map { it.public }).build(threshold) - keyPairs.zip(dirs) { keyPair, nodeDir -> - generateCertificates(keyPair, notaryKey, notaryName, nodeDir) - } - return Party(notaryName, notaryKey) - } - + /** Generates a CFT notary identity, where the entire cluster shares a key pair. */ fun generateDistributedNotarySingularIdentity(dirs: List, notaryName: CordaX500Name): Party { require(dirs.isNotEmpty()) log.trace { "Generating singular identity \"$notaryName\" for nodes: ${dirs.joinToString()}" } + val keyPair = generateKeyPair() val notaryKey = keyPair.public - dirs.forEach { dir -> - generateCertificates(keyPair, notaryKey, notaryName, dir) + + dirs.forEach { nodeDir -> + val keyStore = getKeyStore(nodeDir) + setPrivateKey(keyStore, keyPair, notaryName.x500Principal) } return Party(notaryName, notaryKey) } - private fun generateCertificates(keyPair: KeyPair, notaryKey: PublicKey, notaryName: CordaX500Name, nodeDir: Path) { - val (serviceKeyCert, compositeKeyCert) = listOf(keyPair.public, notaryKey).map { publicKey -> - X509Utilities.createCertificate( - CertificateType.SERVICE_IDENTITY, - DEV_INTERMEDIATE_CA.certificate, - DEV_INTERMEDIATE_CA.keyPair, - notaryName.x500Principal, - publicKey) - } - val distServKeyStoreFile = (nodeDir / "certificates").createDirectories() / "distributedService.jks" - X509KeyStore.fromFile(distServKeyStoreFile, DEV_CA_KEY_STORE_PASS, createNew = true).update { - setCertificate("$DISTRIBUTED_NOTARY_ALIAS_PREFIX-composite-key", compositeKeyCert) - setPrivateKey( - "$DISTRIBUTED_NOTARY_ALIAS_PREFIX-private-key", - keyPair.private, - listOf(serviceKeyCert, DEV_INTERMEDIATE_CA.certificate, DEV_ROOT_CA.certificate), - DEV_CA_KEY_STORE_PASS // Unfortunately we have to use the same password for private key due to Artemis limitation, for more details please see: - // org.apache.activemq.artemis.core.remoting.impl.ssl.SSLSupport.loadKeyManagerFactory - // where it is calling `KeyManagerFactory.init()` with store password - /*DEV_CA_PRIVATE_KEY_PASS*/) + /** Generates a BFT notary identity: individual key pairs for each cluster member, and a shared composite key. */ + fun generateDistributedNotaryCompositeIdentity(dirs: List, notaryName: CordaX500Name, threshold: Int = 1): Party { + require(dirs.isNotEmpty()) + + log.trace { "Generating composite identity \"$notaryName\" for nodes: ${dirs.joinToString()}" } + + val keyPairs = (1..dirs.size).map { generateKeyPair() } + val notaryKey = CompositeKey.Builder().addKeys(keyPairs.map { it.public }).build(threshold) + + keyPairs.zip(dirs) { keyPair, nodeDir -> + val keyStore = getKeyStore(nodeDir) + setPrivateKey(keyStore, keyPair, notaryName.x500Principal) + setCompositeKey(keyStore, notaryKey, notaryName.x500Principal) } + return Party(notaryName, notaryKey) + } + + private fun getKeyStore(nodeDir: Path): X509KeyStore { + val distServKeyStoreFile = nodeDir / "certificates/distributedService.jks" + return X509KeyStore.fromFile(distServKeyStoreFile, DEV_CA_KEY_STORE_PASS, createNew = true) + } + + private fun setPrivateKey(keyStore: X509KeyStore, keyPair: KeyPair, notaryPrincipal: X500Principal) { + val serviceKeyCert = createCertificate(keyPair.public, notaryPrincipal) + keyStore.setPrivateKey( + "$DISTRIBUTED_NOTARY_ALIAS_PREFIX-private-key", + keyPair.private, + listOf(serviceKeyCert, DEV_INTERMEDIATE_CA.certificate, DEV_ROOT_CA.certificate), + DEV_CA_KEY_STORE_PASS // Unfortunately we have to use the same password for private key due to Artemis limitation, for more details please see: + // org.apache.activemq.artemis.core.remoting.impl.ssl.SSLSupport.loadKeyManagerFactory + // where it is calling `KeyManagerFactory.init()` with store password + /*DEV_CA_PRIVATE_KEY_PASS*/) + } + + private fun setCompositeKey(keyStore: X509KeyStore, compositeKey: PublicKey, notaryPrincipal: X500Principal) { + val compositeKeyCert = createCertificate(compositeKey, notaryPrincipal) + keyStore.setCertificate("$DISTRIBUTED_NOTARY_ALIAS_PREFIX-composite-key", compositeKeyCert) + } + + private fun createCertificate(publicKey: PublicKey, principal: X500Principal): X509Certificate { + return X509Utilities.createCertificate( + CertificateType.SERVICE_IDENTITY, + DEV_INTERMEDIATE_CA.certificate, + DEV_INTERMEDIATE_CA.keyPair, + principal, + publicKey) } } diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/network/NetworkBootstrapper.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/network/NetworkBootstrapper.kt index c3a7fd71e9..52138854a4 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/network/NetworkBootstrapper.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/network/NetworkBootstrapper.kt @@ -33,6 +33,7 @@ import net.corda.serialization.internal.amqp.AbstractAMQPSerializationScheme import net.corda.serialization.internal.amqp.amqpMagic import java.io.File import java.io.InputStream +import java.nio.file.FileAlreadyExistsException import java.nio.file.Path import java.nio.file.StandardCopyOption.REPLACE_EXISTING import java.security.PublicKey @@ -226,7 +227,13 @@ internal constructor(private val initSerEnv: Boolean, println("Copying CorDapp JARs into node directories") for (nodeDir in nodeDirs) { val cordappsDir = (nodeDir / "cordapps").createDirectories() - cordappJars.forEach { it.copyToDirectory(cordappsDir) } + cordappJars.forEach { + try { + it.copyToDirectory(cordappsDir) + } catch (e: FileAlreadyExistsException) { + println("WARNING: ${it.fileName} already exists in $cordappsDir, ignoring and leaving existing CorDapp untouched") + } + } } } generateServiceIdentitiesForNotaryClusters(configs) 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 e2bec40012..41007cbd0a 100644 --- a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt @@ -891,13 +891,17 @@ abstract class AbstractNode(val configuration: NodeConfiguration, val compositeKeyAlias = "$DISTRIBUTED_NOTARY_ALIAS_PREFIX-composite-key" val signingCertificateStore = configuration.signingCertificateStore.get() + // A composite key is only required for BFT notaries. val certificates = if (cryptoService.containsKey(compositeKeyAlias)) { val certificate = signingCertificateStore[compositeKeyAlias] // We have to create the certificate chain for the composite key manually, this is because we don't have a keystore // provider that understand compositeKey-privateKey combo. The cert chain is created using the composite key certificate + // the tail of the private key certificates, as they are both signed by the same certificate chain. listOf(certificate) + signingCertificateStore.query { getCertificateChain(privateKeyAlias) }.drop(1) - } else throw IllegalStateException("The identity public key for the notary service $serviceLegalName was not found in the key store.") + } else { + // We assume the notary is CFT, and each cluster member shares the same notary key pair. + signingCertificateStore.query { getCertificateChain(privateKeyAlias) } + } val subject = CordaX500Name.build(certificates.first().subjectX500Principal) if (subject != serviceLegalName) { diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/TestCordappsUtils.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/TestCordappsUtils.kt index 099a99479a..7bb808ae73 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/TestCordappsUtils.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/TestCordappsUtils.kt @@ -67,7 +67,7 @@ fun TestCordappImpl.packageAsJar(file: Path) { scanResult.use { val manifest = createTestManifest(name, title, version, vendor, targetVersion) JarOutputStream(file.outputStream(), manifest).use { jos -> - val time = FileTime.from(Instant.now()) + val time = FileTime.from(Instant.EPOCH) // The same resource may be found in different locations (this will happen when running from gradle) so just // pick the first one found. scanResult.allResources.asMap().forEach { path, resourceList -> diff --git a/tools/demobench/src/main/kotlin/net/corda/demobench/views/NodeTabView.kt b/tools/demobench/src/main/kotlin/net/corda/demobench/views/NodeTabView.kt index ade5653585..fa33c73563 100644 --- a/tools/demobench/src/main/kotlin/net/corda/demobench/views/NodeTabView.kt +++ b/tools/demobench/src/main/kotlin/net/corda/demobench/views/NodeTabView.kt @@ -114,31 +114,43 @@ class NodeTabView : Fragment() { fieldset("Additional configuration") { styleClass.addAll("services-panel") - val extraServices = if (nodeController.hasNotary()) { - listOf(USD, GBP, CHF, EUR).map { CurrencyIssuer(it) } - } else { - listOf(NotaryService(true), NotaryService(false)) - } - - val servicesList = CheckListView(extraServices.observable()).apply { - vboxConstraints { vGrow = Priority.ALWAYS } - model.item.extraServices.set(checkModel.checkedItems) - if (!nodeController.hasNotary()) { - checkModel.check(0) - checkModel.checkedItems.addListener(ListChangeListener { change -> - while (change.next()) { - if (change.wasAdded()) { - val item = change.addedSubList.last() - val idx = checkModel.getItemIndex(item) - checkModel.checkedIndices.forEach { - if (it != idx) checkModel.clearCheck(it) + if (nodeController.hasNotary()) { + val extraServices: List = listOf(USD, GBP, CHF, EUR).map { CurrencyIssuer(it) } + val servicesList = CheckListView(extraServices.observable()).apply { + vboxConstraints { vGrow = Priority.ALWAYS } + model.item.extraServices.set(checkModel.checkedItems) + if (!nodeController.hasNotary()) { + checkModel.check(0) + checkModel.checkedItems.addListener(ListChangeListener { change -> + while (change.next()) { + if (change.wasAdded()) { + val item = change.addedSubList.last() + val idx = checkModel.getItemIndex(item) + checkModel.checkedIndices.forEach { + if (it != idx) checkModel.clearCheck(it) + } } } - } - }) + }) + } + } + add(servicesList) + } else { + val notaryTypes = listOf(NotaryService(true), NotaryService(false)) + val notaryTypeToggleGroup = togglegroup() + notaryTypeToggleGroup.selectedValueProperty().addListener { observValue, oldValue, newValue -> + oldValue?.let { + model.item.extraServices.removeAll(it) + } + newValue?.let { + model.item.extraServices.add(it) + } + } + notaryTypes.forEachIndexed { index, notaryType -> + val toggle = radiobutton(notaryType.toString(), notaryTypeToggleGroup, notaryType) + toggle.isSelected = index == 0 } } - add(servicesList) } } diff --git a/tools/shell/src/test/kotlin/net/corda/tools/shell/InteractiveShellTest.kt b/tools/shell/src/test/kotlin/net/corda/tools/shell/InteractiveShellTest.kt index b7b76ef401..7fb3af428a 100644 --- a/tools/shell/src/test/kotlin/net/corda/tools/shell/InteractiveShellTest.kt +++ b/tools/shell/src/test/kotlin/net/corda/tools/shell/InteractiveShellTest.kt @@ -36,7 +36,7 @@ class InteractiveShellTest { constructor(party: Party) : this(party.name.toString()) constructor(b: Int?, amount: Amount) : this("${(b ?: 0) + amount.quantity} ${amount.token}") constructor(b: Array) : this(b.joinToString("+")) - constructor(amounts: Array>) : this(amounts.map(Amount::toString).joinToString("++")) + constructor(amounts: Array>) : this(amounts.joinToString("++", transform = Amount::toString)) override val progressTracker = ProgressTracker() override fun call() = a