diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index aa4befbc67..f2add88db0 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -120,6 +120,7 @@ see changes to this list. * Lucas Salmen (Itau) * Lulu Ren (Monad-Labs) * Maksymilian Pawlak (R3) +* Manila Gauns (Persistent Systems Limited) * Marek Scocovsky (ABSA) * marekdapps * Mark Lauer (Westpac) diff --git a/djvm/src/main/kotlin/net/corda/djvm/rules/implementation/AlwaysInheritFromSandboxedObject.kt b/djvm/src/main/kotlin/net/corda/djvm/rules/implementation/AlwaysInheritFromSandboxedObject.kt index 327b1474a3..14d0d3857c 100644 --- a/djvm/src/main/kotlin/net/corda/djvm/rules/implementation/AlwaysInheritFromSandboxedObject.kt +++ b/djvm/src/main/kotlin/net/corda/djvm/rules/implementation/AlwaysInheritFromSandboxedObject.kt @@ -24,8 +24,12 @@ class AlwaysInheritFromSandboxedObject : ClassDefinitionProvider, Emitter { override fun emit(context: EmitterContext, instruction: Instruction) = context.emit { if (instruction is TypeInstruction && - instruction.typeName == OBJECT_NAME) { + instruction.typeName == OBJECT_NAME && + instruction.operation != Opcodes.ANEWARRAY && + instruction.operation != Opcodes.MULTIANEWARRAY) { // When creating new objects, make sure the sandboxed type gets used. + // However, an array is always [java.lang.Object] so we must exclude + // arrays from this so that we can still support arrays of arrays. new(SANDBOX_OBJECT_NAME, instruction.operation) preventDefault() } diff --git a/djvm/src/main/kotlin/net/corda/djvm/rules/implementation/RewriteObjectMethods.kt b/djvm/src/main/kotlin/net/corda/djvm/rules/implementation/RewriteObjectMethods.kt new file mode 100644 index 0000000000..055c1593e1 --- /dev/null +++ b/djvm/src/main/kotlin/net/corda/djvm/rules/implementation/RewriteObjectMethods.kt @@ -0,0 +1,29 @@ +package net.corda.djvm.rules.implementation + +import net.corda.djvm.code.Emitter +import net.corda.djvm.code.EmitterContext +import net.corda.djvm.code.Instruction +import net.corda.djvm.code.instructions.MemberAccessInstruction +import org.objectweb.asm.Opcodes.* + +/** + * We cannot wrap Java array objects - and potentially others - and so these would still + * use the non-deterministic [java.lang.Object.hashCode] by default. Therefore we intercept + * these invocations and redirect them to our [sandbox.java.lang.DJVM] object. + */ +class RewriteObjectMethods : Emitter { + override fun emit(context: EmitterContext, instruction: Instruction) = context.emit { + if (instruction is MemberAccessInstruction && instruction.owner == "java/lang/Object") { + when (instruction.operation) { + INVOKEVIRTUAL -> if (instruction.memberName == "hashCode" && instruction.signature == "()I") { + invokeStatic( + owner = "sandbox/java/lang/DJVM", + name = "hashCode", + descriptor = "(Ljava/lang/Object;)I" + ) + preventDefault() + } + } + } + } +} diff --git a/djvm/src/main/kotlin/sandbox/java/lang/DJVM.kt b/djvm/src/main/kotlin/sandbox/java/lang/DJVM.kt index 5aaeb6adee..bd19a7428a 100644 --- a/djvm/src/main/kotlin/sandbox/java/lang/DJVM.kt +++ b/djvm/src/main/kotlin/sandbox/java/lang/DJVM.kt @@ -43,14 +43,15 @@ fun Any.sandbox(): Any { private fun Array<*>.fromDJVMArray(): Array<*> = Object.fromDJVM(this) /** - * Use the sandbox's classloader explicitly, because this class - * might belong to the shared parent classloader. + * Use [Class.forName] so that we can also fetch classes for arrays of primitive types. + * Also use the sandbox's classloader explicitly here, because this invoking class + * might belong to a shared parent classloader. */ @Throws(ClassNotFoundException::class) -internal fun Class<*>.toDJVMType(): Class<*> = SandboxRuntimeContext.instance.classLoader.loadClass(name.toSandboxPackage()) +internal fun Class<*>.toDJVMType(): Class<*> = Class.forName(name.toSandboxPackage(), false, SandboxRuntimeContext.instance.classLoader) @Throws(ClassNotFoundException::class) -internal fun Class<*>.fromDJVMType(): Class<*> = SandboxRuntimeContext.instance.classLoader.loadClass(name.fromSandboxPackage()) +internal fun Class<*>.fromDJVMType(): Class<*> = Class.forName(name.fromSandboxPackage(), false, SandboxRuntimeContext.instance.classLoader) private fun kotlin.String.toSandboxPackage(): kotlin.String { return if (startsWith(SANDBOX_PREFIX)) { @@ -139,6 +140,21 @@ private fun createEnumDirectory(clazz: Class>): sandbox.java.util.Ma private val allEnums: sandbox.java.util.Map>, Array>> = sandbox.java.util.LinkedHashMap() private val allEnumDirectories: sandbox.java.util.Map>, sandbox.java.util.Map>> = sandbox.java.util.LinkedHashMap() +/** + * Replacement function for Object.hashCode(), because some objects + * (i.e. arrays) cannot be replaced by [sandbox.java.lang.Object]. + */ +fun hashCode(obj: Any?): Int { + return if (obj is Object) { + obj.hashCode() + } else if (obj != null) { + System.identityHashCode(obj) + } else { + // Throw the same exception that the JVM would throw in this case. + throw NullPointerException().sanitise() + } +} + /** * Replacement functions for Class<*>.forName(...) which protect * against users loading classes from outside the sandbox. @@ -161,7 +177,7 @@ fun classForName(className: kotlin.String, initialize: kotlin.Boolean, classLoad */ private fun toSandbox(className: kotlin.String): kotlin.String { if (bannedClasses.any { it.matches(className) }) { - throw ClassNotFoundException(className) + throw ClassNotFoundException(className).sanitise() } return SANDBOX_PREFIX + className } @@ -200,7 +216,7 @@ fun fromDJVM(t: Throwable?): kotlin.Throwable { .newInstance(t) as kotlin.Throwable } } catch (e: Exception) { - RuleViolationError(e.message) + RuleViolationError(e.message).sanitise() } } } @@ -225,10 +241,20 @@ fun catch(t: kotlin.Throwable): Throwable { try { return t.toDJVMThrowable() } catch (e: Exception) { - throw RuleViolationError(e.message) + throw RuleViolationError(e.message).sanitise() } } +/** + * Clean up exception stack trace for throwing. + */ +private fun T.sanitise(): T { + stackTrace = stackTrace.let { + it.sliceArray(1 until findEntryPointIndex(it)) + } + return this +} + /** * Worker functions to convert [java.lang.Throwable] into [sandbox.java.lang.Throwable]. */ @@ -266,12 +292,16 @@ private fun Class<*>.createJavaThrowable(t: Throwable): kotlin.Throwable { } } -private fun sanitiseToDJVM(source: Array): Array { +private fun findEntryPointIndex(source: Array): Int { var idx = 0 while (idx < source.size && !isEntryPoint(source[idx])) { ++idx } - return copyToDJVM(source, 0, idx) + return idx +} + +private fun sanitiseToDJVM(source: Array): Array { + return copyToDJVM(source, 0, findEntryPointIndex(source)) } internal fun copyToDJVM(source: Array, fromIdx: Int, toIdx: Int): Array { diff --git a/djvm/src/test/java/net/corda/djvm/WithJava.java b/djvm/src/test/java/net/corda/djvm/WithJava.java index 3e8cf05145..77f3f8db96 100644 --- a/djvm/src/test/java/net/corda/djvm/WithJava.java +++ b/djvm/src/test/java/net/corda/djvm/WithJava.java @@ -1,6 +1,7 @@ package net.corda.djvm; import net.corda.djvm.execution.ExecutionSummaryWithResult; +import net.corda.djvm.execution.SandboxException; import net.corda.djvm.execution.SandboxExecutor; import net.corda.djvm.source.ClassSource; @@ -13,12 +14,15 @@ public interface WithJava { try { return executor.run(ClassSource.fromClassName(task.getName(), null), input); } catch (Exception e) { - if (e instanceof RuntimeException) { - throw (RuntimeException) e; + if (e instanceof SandboxException) { + throw asRuntime(e.getCause()); } else { - throw new RuntimeException(e.getMessage(), e); + throw asRuntime(e); } } } + static RuntimeException asRuntime(Throwable t) { + return (t instanceof RuntimeException) ? (RuntimeException) t : new RuntimeException(t.getMessage(), t); + } } diff --git a/djvm/src/test/java/net/corda/djvm/execution/SandboxObjectHashCodeJavaTest.java b/djvm/src/test/java/net/corda/djvm/execution/SandboxObjectHashCodeJavaTest.java new file mode 100644 index 0000000000..34ee436673 --- /dev/null +++ b/djvm/src/test/java/net/corda/djvm/execution/SandboxObjectHashCodeJavaTest.java @@ -0,0 +1,91 @@ +package net.corda.djvm.execution; + +import net.corda.djvm.TestBase; +import net.corda.djvm.WithJava; +import org.junit.Test; + +import java.util.function.Function; + +import static net.corda.djvm.messages.Severity.WARNING; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +public class SandboxObjectHashCodeJavaTest extends TestBase { + + @Test + public void testHashForArray() { + parentedSandbox(WARNING, true, ctx -> { + SandboxExecutor executor = new DeterministicSandboxExecutor<>(ctx.getConfiguration()); + ExecutionSummaryWithResult output = WithJava.run(executor, ArrayHashCode.class, null); + assertThat(output.getResult()).isEqualTo(0xfed_c0de + 1); + return null; + }); + } + + @Test + public void testHashForObjectInArray() { + parentedSandbox(WARNING, true, ctx -> { + SandboxExecutor executor = new DeterministicSandboxExecutor<>(ctx.getConfiguration()); + ExecutionSummaryWithResult output = WithJava.run(executor, ObjectInArrayHashCode.class, null); + assertThat(output.getResult()).isEqualTo(0xfed_c0de + 1); + return null; + }); + } + + @Test + public void testHashForNullObject() { + assertThatExceptionOfType(NullPointerException.class) + .isThrownBy(() -> new HashCode().apply(null)); + + parentedSandbox(WARNING, true, ctx -> { + SandboxExecutor executor = new DeterministicSandboxExecutor<>(ctx.getConfiguration()); + assertThatExceptionOfType(NullPointerException.class) + .isThrownBy(() -> WithJava.run(executor, HashCode.class, null)); + return null; + }); + } + + @Test + public void testHashForWrappedInteger() { + parentedSandbox(WARNING, true, ctx -> { + SandboxExecutor executor = new DeterministicSandboxExecutor<>(ctx.getConfiguration()); + ExecutionSummaryWithResult output = WithJava.run(executor, HashCode.class, 1234); + assertThat(output.getResult()).isEqualTo(Integer.hashCode(1234)); + return null; + }); + } + + @Test + public void testHashForWrappedString() { + parentedSandbox(WARNING, true, ctx -> { + SandboxExecutor executor = new DeterministicSandboxExecutor<>(ctx.getConfiguration()); + ExecutionSummaryWithResult output = WithJava.run(executor, HashCode.class, "Burble"); + assertThat(output.getResult()).isEqualTo("Burble".hashCode()); + return null; + }); + } + + public static class ObjectInArrayHashCode implements Function { + @Override + public Integer apply(Object obj) { + Object[] arr = new Object[1]; + arr[0] = new Object(); + return arr[0].hashCode(); + } + } + + public static class ArrayHashCode implements Function { + @SuppressWarnings("all") + @Override + public Integer apply(Object obj) { + return new Object[0].hashCode(); + } + } + + public static class HashCode implements Function { + @Override + public Integer apply(Object obj) { + return obj.hashCode(); + } + } +} diff --git a/djvm/src/test/kotlin/net/corda/djvm/TestBase.kt b/djvm/src/test/kotlin/net/corda/djvm/TestBase.kt index 155015930d..514a493567 100644 --- a/djvm/src/test/kotlin/net/corda/djvm/TestBase.kt +++ b/djvm/src/test/kotlin/net/corda/djvm/TestBase.kt @@ -49,6 +49,7 @@ abstract class TestBase { HandleExceptionUnwrapper(), ReturnTypeWrapper(), RewriteClassMethods(), + RewriteObjectMethods(), StringConstantWrapper(), ThrowExceptionWrapper() ) diff --git a/djvm/src/test/kotlin/net/corda/djvm/execution/SandboxExecutorTest.kt b/djvm/src/test/kotlin/net/corda/djvm/execution/SandboxExecutorTest.kt index 169b956e85..0708af248d 100644 --- a/djvm/src/test/kotlin/net/corda/djvm/execution/SandboxExecutorTest.kt +++ b/djvm/src/test/kotlin/net/corda/djvm/execution/SandboxExecutorTest.kt @@ -733,4 +733,32 @@ class SandboxExecutorTest : TestBase() { return cl.find() } } + + @Test + fun `test creating arrays of arrays`() = parentedSandbox { + val contractExecutor = DeterministicSandboxExecutor>(configuration) + contractExecutor.run("THINGY").apply { + assertThat(result).isEqualTo(arrayOf(arrayOf("THINGY"))) + } + } + + class ArraysOfArrays : Function> { + override fun apply(input: Any): Array { + return arrayOf(arrayOf(input)) + } + } + + @Test + fun `test creating arrays of int arrays`() = parentedSandbox { + val contractExecutor = DeterministicSandboxExecutor>(configuration) + contractExecutor.run(0).apply { + assertThat(result).isEqualTo(arrayOf(intArrayOf(0))) + } + } + + class ArrayOfIntArrays : Function> { + override fun apply(input: Int): Array { + return arrayOf(intArrayOf(input)) + } + } } diff --git a/docs/source/corda-configuration-file.rst b/docs/source/corda-configuration-file.rst index 03cb18f329..69c25beccf 100644 --- a/docs/source/corda-configuration-file.rst +++ b/docs/source/corda-configuration-file.rst @@ -320,6 +320,12 @@ absolute path to the node's base directory. Valid values for this property are between 4 (that is the number used for the single threaded state machine in open source) and the number of flow threads. +:cordappSignerKeyFingerprintBlacklist: List of public keys fingerprints (SHA-256 of public key hash) not allowed as Cordapp JARs signers. + Node will not load Cordapps signed by those keys. + The option takes effect only in production mode and defaults to Corda development keys (``["56CA54E803CB87C8472EBD3FBC6A2F1876E814CEEBF74860BD46997F40729367", + "83088052AF16700457AE2C978A7D8AC38DD6A7C713539D00B897CD03A5E5D31D"]``), in development mode any key is allowed to sign Cordpapp JARs. + + Examples -------- diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/KeyStoreConfigHelpers.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/KeyStoreConfigHelpers.kt index d6cb0f5877..b35ea6a762 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/KeyStoreConfigHelpers.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/KeyStoreConfigHelpers.kt @@ -2,8 +2,11 @@ package net.corda.nodeapi.internal import net.corda.core.crypto.Crypto import net.corda.core.crypto.Crypto.generateKeyPair +import net.corda.core.crypto.SecureHash +import net.corda.core.crypto.sha256 import net.corda.core.identity.CordaX500Name import net.corda.core.identity.PartyAndCertificate +import net.corda.core.internal.hash import net.corda.core.internal.toX500Name import net.corda.nodeapi.internal.config.CertificateStore import net.corda.nodeapi.internal.crypto.* @@ -99,7 +102,7 @@ const val DEV_CA_TRUST_STORE_FILE: String = "cordatruststore.jks" const val DEV_CA_TRUST_STORE_PASS: String = "trustpass" const val DEV_CA_TRUST_STORE_PRIVATE_KEY_PASS: String = "trustpasskeypass" -val DEV_CERTIFICATES: List get() = listOf(DEV_INTERMEDIATE_CA.certificate, DEV_ROOT_CA.certificate) +val DEV_PUB_KEY_HASHES: List get() = listOf(DEV_INTERMEDIATE_CA.certificate, DEV_ROOT_CA.certificate).map { it.publicKey.hash.sha256() } // We need a class so that we can get hold of the class loader internal object DevCaHelper { diff --git a/node/build.gradle b/node/build.gradle index 070527cd56..86f1423035 100644 --- a/node/build.gradle +++ b/node/build.gradle @@ -96,9 +96,6 @@ dependencies { // For async logging compile "com.lmax:disruptor:$disruptor_version" - // JOpt: for command line flags. - compile "net.sf.jopt-simple:jopt-simple:$jopt_simple_version" - // Artemis: for reliable p2p message queues. // TODO: remove the forced update of commons-collections and beanutils when artemis updates them compile "org.apache.commons:commons-collections4:${commons_collections_version}" diff --git a/node/src/main/java/CordaCaplet.java b/node/src/main/java/CordaCaplet.java index b9d2751b61..eeeb91870c 100644 --- a/node/src/main/java/CordaCaplet.java +++ b/node/src/main/java/CordaCaplet.java @@ -59,6 +59,7 @@ public class CordaCaplet extends Capsule { @Override protected ProcessBuilder prelaunch(List jvmArgs, List args) { + checkJavaVersion(); nodeConfig = parseConfigFile(args); return super.prelaunch(jvmArgs, args); } @@ -164,6 +165,14 @@ public class CordaCaplet extends Capsule { } } + private static void checkJavaVersion() { + String version = System.getProperty("java.version"); + if (version == null || !version.startsWith("1.8")) { + System.err.printf("Error: Unsupported Java version %s; currently only version 1.8 is supported.\n", version); + System.exit(1); + } + } + private void requireCordappsDirExists(File dir) { try { if (!dir.mkdir() && !dir.exists()) { // It is unlikely to enter this if-branch, but just in case. 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 5592c6c42f..f07e5024f0 100644 --- a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt @@ -9,6 +9,7 @@ import net.corda.confidential.SwapIdentitiesHandler import net.corda.core.CordaException import net.corda.core.concurrent.CordaFuture import net.corda.core.context.InvocationContext +import net.corda.core.crypto.SecureHash import net.corda.core.crypto.isCRLDistributionPointBlacklisted import net.corda.core.crypto.newSecureRandom import net.corda.core.crypto.sign @@ -69,7 +70,6 @@ import net.corda.node.services.transactions.SimpleNotaryService import net.corda.node.services.upgrade.ContractUpgradeServiceImpl import net.corda.node.services.vault.NodeVaultService import net.corda.node.utilities.* -import net.corda.nodeapi.internal.DEV_CERTIFICATES import net.corda.nodeapi.internal.NodeInfoAndSigned import net.corda.nodeapi.internal.SignedNodeInfo import net.corda.nodeapi.internal.config.CertificateStore @@ -526,12 +526,20 @@ abstract class AbstractNode(val configuration: NodeConfiguration, // CorDapp will be generated. generatedCordapps += VirtualCordapp.generateSimpleNotaryCordapp(versionInfo) } - val blacklistedCerts = if (configuration.devMode) emptyList() else DEV_CERTIFICATES + val blacklistedKeys = if (configuration.devMode) emptyList() + else configuration.cordappSignerKeyFingerprintBlacklist.mapNotNull { + try { + SecureHash.parse(it) + } catch (e: IllegalArgumentException) { + log.error("Error while adding key fingerprint $it to cordappSignerKeyFingerprintBlacklist due to ${e.message}", e) + throw e + } + } return JarScanningCordappLoader.fromDirectories( configuration.cordappDirectories, versionInfo, extraCordapps = generatedCordapps, - blacklistedCerts = blacklistedCerts + signerKeyFingerprintBlacklist = blacklistedKeys ) } diff --git a/node/src/main/kotlin/net/corda/node/internal/cordapp/JarScanningCordappLoader.kt b/node/src/main/kotlin/net/corda/node/internal/cordapp/JarScanningCordappLoader.kt index 135976355a..eae8f429af 100644 --- a/node/src/main/kotlin/net/corda/node/internal/cordapp/JarScanningCordappLoader.kt +++ b/node/src/main/kotlin/net/corda/node/internal/cordapp/JarScanningCordappLoader.kt @@ -28,7 +28,6 @@ import java.lang.reflect.Modifier import java.net.URL import java.net.URLClassLoader import java.nio.file.Path -import java.security.cert.X509Certificate import java.util.* import java.util.jar.JarInputStream import kotlin.reflect.KClass @@ -42,7 +41,7 @@ import kotlin.streams.toList class JarScanningCordappLoader private constructor(private val cordappJarPaths: List, private val versionInfo: VersionInfo = VersionInfo.UNKNOWN, extraCordapps: List, - private val blacklistedCordappSigners: List = emptyList()) : CordappLoaderTemplate() { + private val signerKeyFingerprintBlacklist: List = emptyList()) : CordappLoaderTemplate() { override val cordapps: List by lazy { loadCordapps() + extraCordapps @@ -69,10 +68,10 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths: fun fromDirectories(cordappDirs: Collection, versionInfo: VersionInfo = VersionInfo.UNKNOWN, extraCordapps: List = emptyList(), - blacklistedCerts: List = emptyList()): JarScanningCordappLoader { + signerKeyFingerprintBlacklist: List = emptyList()): JarScanningCordappLoader { logger.info("Looking for CorDapps in ${cordappDirs.distinct().joinToString(", ", "[", "]")}") val paths = cordappDirs.distinct().flatMap(this::jarUrlsInDirectory).map { it.restricted() } - return JarScanningCordappLoader(paths, versionInfo, extraCordapps, blacklistedCerts) + return JarScanningCordappLoader(paths, versionInfo, extraCordapps, signerKeyFingerprintBlacklist) } /** @@ -80,9 +79,10 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths: * * @param scanJars Uses the JAR URLs provided for classpath scanning and Cordapp detection. */ - fun fromJarUrls(scanJars: List, versionInfo: VersionInfo = VersionInfo.UNKNOWN, extraCordapps: List = emptyList(), blacklistedCerts: List = emptyList()): JarScanningCordappLoader { + fun fromJarUrls(scanJars: List, versionInfo: VersionInfo = VersionInfo.UNKNOWN, extraCordapps: List = emptyList(), + cordappsSignerKeyFingerprintBlacklist: List = emptyList()): JarScanningCordappLoader { val paths = scanJars.map { it.restricted() } - return JarScanningCordappLoader(paths, versionInfo, extraCordapps, blacklistedCerts) + return JarScanningCordappLoader(paths, versionInfo, extraCordapps, cordappsSignerKeyFingerprintBlacklist) } private fun URL.restricted(rootPackageName: String? = null) = RestrictedURL(this, rootPackageName) @@ -112,15 +112,16 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths: } } .filter { - if (blacklistedCordappSigners.isEmpty()) { + if (signerKeyFingerprintBlacklist.isEmpty()) { true //Nothing blacklisted, no need to check } else { val certificates = it.jarPath.openStream().let(::JarInputStream).use(JarSignatureCollector::collectCertificates) - if (certificates.isEmpty() || (certificates - blacklistedCordappSigners).isNotEmpty()) + val blockedCertificates = certificates.filter { it.publicKey.hash.sha256() in signerKeyFingerprintBlacklist } + if (certificates.isEmpty() || (certificates - blockedCertificates).isNotEmpty()) true // Cordapp is not signed or it is signed by at least one non-blacklisted certificate else { logger.warn("Not loading CorDapp ${it.info.shortName} (${it.info.vendor}) as it is signed by development key(s) only: " + - "${certificates.intersect(blacklistedCordappSigners).map { it.publicKey }}.") + "${blockedCertificates.map { it.publicKey }}.") false } } diff --git a/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt b/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt index 596ca27ff8..8a9ab7629c 100644 --- a/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt +++ b/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt @@ -14,6 +14,7 @@ import net.corda.nodeapi.BrokerRpcSslOptions import net.corda.nodeapi.internal.config.* import net.corda.nodeapi.internal.persistence.CordaPersistence.DataSourceConfigTag import net.corda.nodeapi.internal.persistence.DatabaseConfig +import net.corda.nodeapi.internal.DEV_PUB_KEY_HASHES import net.corda.tools.shell.SSHDConfiguration import org.slf4j.Logger import java.net.URL @@ -84,6 +85,8 @@ interface NodeConfiguration { val cordappDirectories: List val flowOverrides: FlowOverrideConfig? + val cordappSignerKeyFingerprintBlacklist: List + fun validate(): List companion object { @@ -234,7 +237,8 @@ data class NodeConfigurationImpl( override val cordappDirectories: List = listOf(baseDirectory / CORDAPPS_DIR_NAME_DEFAULT), override val jmxReporterType: JmxReporterType? = JmxReporterType.JOLOKIA, private val useOpenSsl: Boolean = false, - override val flowOverrides: FlowOverrideConfig? + override val flowOverrides: FlowOverrideConfig?, + override val cordappSignerKeyFingerprintBlacklist: List = DEV_PUB_KEY_HASHES.map { it.toString() } ) : NodeConfiguration { companion object { private val logger = loggerFor() diff --git a/node/src/main/kotlin/net/corda/node/utilities/AbstractArgsParser.kt b/node/src/main/kotlin/net/corda/node/utilities/AbstractArgsParser.kt deleted file mode 100644 index eeba175842..0000000000 --- a/node/src/main/kotlin/net/corda/node/utilities/AbstractArgsParser.kt +++ /dev/null @@ -1,39 +0,0 @@ -package net.corda.node.utilities - -import joptsimple.OptionException -import joptsimple.OptionParser -import joptsimple.OptionSet -import kotlin.system.exitProcess - -abstract class AbstractArgsParser { - protected val optionParser = OptionParser() - private val helpOption = optionParser.acceptsAll(listOf("h", "help"), "show help").forHelp() - - /** - * Parses the given [args] or exits the process if unable to, printing the help output to stderr. - * If the help option is specified then the process is also shutdown after printing the help output to stdout. - */ - fun parseOrExit(vararg args: String): T { - try { - val optionSet = optionParser.parse(*args) - if (optionSet.has(helpOption)) { - optionParser.printHelpOn(System.out) - exitProcess(0) - } - return doParse(optionSet) - } catch (e: Exception) { - when (e) { - is OptionException, is IllegalArgumentException -> { - System.err.println(e.message ?: "Unable to parse arguments.") - optionParser.printHelpOn(System.err) - exitProcess(1) - } - else -> throw e - } - } - } - - fun parse(vararg args: String): T = doParse(optionParser.parse(*args)) - - protected abstract fun doParse(optionSet: OptionSet): T -} \ No newline at end of file diff --git a/node/src/test/kotlin/net/corda/node/internal/cordapp/JarScanningCordappLoaderTest.kt b/node/src/test/kotlin/net/corda/node/internal/cordapp/JarScanningCordappLoaderTest.kt index feb4e7b476..9c0c418ea7 100644 --- a/node/src/test/kotlin/net/corda/node/internal/cordapp/JarScanningCordappLoaderTest.kt +++ b/node/src/test/kotlin/net/corda/node/internal/cordapp/JarScanningCordappLoaderTest.kt @@ -6,7 +6,7 @@ import net.corda.core.internal.packageName import net.corda.node.VersionInfo import net.corda.testing.node.internal.TestCordappDirectories import net.corda.testing.node.internal.cordappForPackages -import net.corda.nodeapi.internal.DEV_CERTIFICATES +import net.corda.nodeapi.internal.DEV_PUB_KEY_HASHES import org.assertj.core.api.Assertions.assertThat import org.junit.Test import java.nio.file.Paths @@ -147,21 +147,21 @@ class JarScanningCordappLoaderTest { @Test fun `cordapp classloader loads app signed by allowed certificate`() { val jar = JarScanningCordappLoaderTest::class.java.getResource("signed/signed-by-dev-key.jar")!! - val loader = JarScanningCordappLoader.fromJarUrls(listOf(jar), blacklistedCerts = emptyList()) + val loader = JarScanningCordappLoader.fromJarUrls(listOf(jar), cordappsSignerKeyFingerprintBlacklist = emptyList()) assertThat(loader.cordapps).hasSize(1) } @Test fun `cordapp classloader does not load app signed by blacklisted certificate`() { val jar = JarScanningCordappLoaderTest::class.java.getResource("signed/signed-by-dev-key.jar")!! - val loader = JarScanningCordappLoader.fromJarUrls(listOf(jar), blacklistedCerts = DEV_CERTIFICATES) + val loader = JarScanningCordappLoader.fromJarUrls(listOf(jar), cordappsSignerKeyFingerprintBlacklist = DEV_PUB_KEY_HASHES) assertThat(loader.cordapps).hasSize(0) } @Test fun `cordapp classloader loads app signed by both allowed and non-blacklisted certificate`() { val jar = JarScanningCordappLoaderTest::class.java.getResource("signed/signed-by-two-keys.jar")!! - val loader = JarScanningCordappLoader.fromJarUrls(listOf(jar), blacklistedCerts = DEV_CERTIFICATES) + val loader = JarScanningCordappLoader.fromJarUrls(listOf(jar), cordappsSignerKeyFingerprintBlacklist = DEV_PUB_KEY_HASHES) assertThat(loader.cordapps).hasSize(1) } } diff --git a/samples/irs-demo/web/build.gradle b/samples/irs-demo/web/build.gradle index 9f92026c58..d79abbbc92 100644 --- a/samples/irs-demo/web/build.gradle +++ b/samples/irs-demo/web/build.gradle @@ -71,6 +71,10 @@ dependencies { compile project(":client:jackson") compile project(":test-utils") compile project(path: ":samples:irs-demo:cordapp", configuration: "demoArtifacts") + + // JOpt: for command line flags. + compile "net.sf.jopt-simple:jopt-simple:$jopt_simple_version" + testCompile('org.springframework.boot:spring-boot-starter-test') { exclude module: "spring-boot-starter-logging" exclude module: "logback-classic" diff --git a/tools/explorer/build.gradle b/tools/explorer/build.gradle index 9dd20ddc01..2e57e61b6e 100644 --- a/tools/explorer/build.gradle +++ b/tools/explorer/build.gradle @@ -51,6 +51,9 @@ dependencies { compile 'org.controlsfx:controlsfx:8.40.12' // This provide com.apple.eawt stub for non-mac system. compile 'com.yuvimasory:orange-extensions:1.3.0' + + // JOpt: for command line flags. + compile "net.sf.jopt-simple:jopt-simple:$jopt_simple_version" } tasks.withType(JavaCompile) { diff --git a/tools/explorer/src/main/kotlin/net/corda/explorer/Main.kt b/tools/explorer/src/main/kotlin/net/corda/explorer/Main.kt index d8b0d21718..df337dc5c8 100644 --- a/tools/explorer/src/main/kotlin/net/corda/explorer/Main.kt +++ b/tools/explorer/src/main/kotlin/net/corda/explorer/Main.kt @@ -137,10 +137,10 @@ class Main : App(MainView::class) { } /** - * This main method will starts 5 nodes (Notary, USA Bank, UK Bank, Bob and Alice) locally for UI testing, - * they will be on localhost ports 20005, 20008, 20011, 20014 and 20017 respectively. + * This main method will start 5 nodes (Notary, USA Bank, UK Bank, Bob and Alice) locally for UI testing, + * which will bind to ports 20005, 20008, 20011, 20014 and 20017 locally. * - * The simulation start with pre-allocating chunks of cash to each of the party in 2 currencies (USD, GBP), then it enter a loop to generate random events. + * The simulation starts by pre-allocating chunks of cash to each of the parties in 2 currencies (USD, GBP), then it enters a loop which generates random events. * On each iteration, the issuers will execute a Cash Issue or Cash Exit flow (at a 9:1 ratio) and a random party will execute a move of cash to another random party. */ fun main(args: Array) { diff --git a/tools/shell/build.gradle b/tools/shell/build.gradle index 13338150fc..1887bdb5fe 100644 --- a/tools/shell/build.gradle +++ b/tools/shell/build.gradle @@ -39,10 +39,6 @@ dependencies { // Jackson support: serialisation to/from JSON, YAML, etc. compile project(':client:jackson') - - // JOpt: for command line flags. - compile "net.sf.jopt-simple:jopt-simple:$jopt_simple_version" - // CRaSH: An embeddable monitoring and admin shell with support for adding new commands written in Groovy. compile("com.github.corda.crash:crash.shell:$crash_version") { exclude group: "org.slf4j", module: "slf4j-jdk14"