mirror of
https://github.com/corda/corda.git
synced 2025-03-15 00:36:49 +00:00
Merge remote-tracking branch 'open/master' into mike-merge-4d2d9b83
This commit is contained in:
commit
9594aea9f7
@ -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 <init>(kotlin.jvm.functions.Function0<? extends java.security.SecureRandom>)
|
||||
@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 <init>(byte[])
|
||||
|
2
.idea/compiler.xml
generated
2
.idea/compiler.xml
generated
@ -165,6 +165,8 @@
|
||||
<module name="ha-testing_integrationTest" target="1.8" />
|
||||
<module name="ha-testing_main" target="1.8" />
|
||||
<module name="ha-testing_test" target="1.8" />
|
||||
<module name="ha-utilities_main" target="1.8" />
|
||||
<module name="ha-utilities_test" target="1.8" />
|
||||
<module name="health-survey_main" target="1.8" />
|
||||
<module name="health-survey_test" target="1.8" />
|
||||
<module name="hsm-tool_main" target="1.8" />
|
||||
|
@ -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)
|
||||
|
@ -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)'
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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<SecureRandom>() {
|
||||
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()
|
||||
}
|
||||
|
@ -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<SecureRandom>() {
|
||||
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)
|
||||
|
@ -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.
|
||||
fun platformSecureRandomFactory(): SecureRandom = platformSecureRandom() // To minimise diff of CryptoUtils against open-source.
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -188,8 +188,7 @@ class Cash : OnLedgerAsset<Currency, Cash.Commands, Cash.State>() {
|
||||
// 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))
|
||||
|
@ -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<Path>, 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<Path>, 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<Path>, 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)
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -891,13 +891,17 @@ abstract class AbstractNode<S>(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) {
|
||||
|
@ -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 ->
|
||||
|
@ -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<ExtraService> = 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<NotaryService>().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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -36,7 +36,7 @@ class InteractiveShellTest {
|
||||
constructor(party: Party) : this(party.name.toString())
|
||||
constructor(b: Int?, amount: Amount<UserValue>) : this("${(b ?: 0) + amount.quantity} ${amount.token}")
|
||||
constructor(b: Array<String>) : this(b.joinToString("+"))
|
||||
constructor(amounts: Array<Amount<UserValue>>) : this(amounts.map(Amount<UserValue>::toString).joinToString("++"))
|
||||
constructor(amounts: Array<Amount<UserValue>>) : this(amounts.joinToString("++", transform = Amount<UserValue>::toString))
|
||||
|
||||
override val progressTracker = ProgressTracker()
|
||||
override fun call() = a
|
||||
|
Loading…
x
Reference in New Issue
Block a user