mirror of
https://github.com/corda/corda.git
synced 2025-03-15 00:36:49 +00:00
Merge remote-tracking branch 'remotes/open/master' into merges/os-merge-2018-10-30
# Conflicts: # docs/source/corda-configuration-file.rst # node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt # node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt
This commit is contained in:
commit
fcd822f176
@ -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)
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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<out Enum<*>>): sandbox.java.util.Ma
|
||||
private val allEnums: sandbox.java.util.Map<Class<out Enum<*>>, Array<out Enum<*>>> = sandbox.java.util.LinkedHashMap()
|
||||
private val allEnumDirectories: sandbox.java.util.Map<Class<out Enum<*>>, sandbox.java.util.Map<String, out Enum<*>>> = 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: kotlin.Throwable> 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<java.lang.StackTraceElement>): Array<StackTraceElement> {
|
||||
private fun findEntryPointIndex(source: Array<java.lang.StackTraceElement>): Int {
|
||||
var idx = 0
|
||||
while (idx < source.size && !isEntryPoint(source[idx])) {
|
||||
++idx
|
||||
}
|
||||
return copyToDJVM(source, 0, idx)
|
||||
return idx
|
||||
}
|
||||
|
||||
private fun sanitiseToDJVM(source: Array<java.lang.StackTraceElement>): Array<StackTraceElement> {
|
||||
return copyToDJVM(source, 0, findEntryPointIndex(source))
|
||||
}
|
||||
|
||||
internal fun copyToDJVM(source: Array<java.lang.StackTraceElement>, fromIdx: Int, toIdx: Int): Array<StackTraceElement> {
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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<Object, Integer> executor = new DeterministicSandboxExecutor<>(ctx.getConfiguration());
|
||||
ExecutionSummaryWithResult<Integer> 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<Object, Integer> executor = new DeterministicSandboxExecutor<>(ctx.getConfiguration());
|
||||
ExecutionSummaryWithResult<Integer> 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<Object, Integer> 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<Object, Integer> executor = new DeterministicSandboxExecutor<>(ctx.getConfiguration());
|
||||
ExecutionSummaryWithResult<Integer> 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<Object, Integer> executor = new DeterministicSandboxExecutor<>(ctx.getConfiguration());
|
||||
ExecutionSummaryWithResult<Integer> output = WithJava.run(executor, HashCode.class, "Burble");
|
||||
assertThat(output.getResult()).isEqualTo("Burble".hashCode());
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
public static class ObjectInArrayHashCode implements Function<Object, Integer> {
|
||||
@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<Object, Integer> {
|
||||
@SuppressWarnings("all")
|
||||
@Override
|
||||
public Integer apply(Object obj) {
|
||||
return new Object[0].hashCode();
|
||||
}
|
||||
}
|
||||
|
||||
public static class HashCode implements Function<Object, Integer> {
|
||||
@Override
|
||||
public Integer apply(Object obj) {
|
||||
return obj.hashCode();
|
||||
}
|
||||
}
|
||||
}
|
@ -49,6 +49,7 @@ abstract class TestBase {
|
||||
HandleExceptionUnwrapper(),
|
||||
ReturnTypeWrapper(),
|
||||
RewriteClassMethods(),
|
||||
RewriteObjectMethods(),
|
||||
StringConstantWrapper(),
|
||||
ThrowExceptionWrapper()
|
||||
)
|
||||
|
@ -733,4 +733,32 @@ class SandboxExecutorTest : TestBase() {
|
||||
return cl.find()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test creating arrays of arrays`() = parentedSandbox {
|
||||
val contractExecutor = DeterministicSandboxExecutor<Any, Array<Any>>(configuration)
|
||||
contractExecutor.run<ArraysOfArrays>("THINGY").apply {
|
||||
assertThat(result).isEqualTo(arrayOf(arrayOf("THINGY")))
|
||||
}
|
||||
}
|
||||
|
||||
class ArraysOfArrays : Function<Any, Array<Any>> {
|
||||
override fun apply(input: Any): Array<Any> {
|
||||
return arrayOf(arrayOf(input))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test creating arrays of int arrays`() = parentedSandbox {
|
||||
val contractExecutor = DeterministicSandboxExecutor<Int, Array<IntArray>>(configuration)
|
||||
contractExecutor.run<ArrayOfIntArrays>(0).apply {
|
||||
assertThat(result).isEqualTo(arrayOf(intArrayOf(0)))
|
||||
}
|
||||
}
|
||||
|
||||
class ArrayOfIntArrays : Function<Int, Array<IntArray>> {
|
||||
override fun apply(input: Int): Array<IntArray> {
|
||||
return arrayOf(intArrayOf(input))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
--------
|
||||
|
||||
|
@ -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<X509Certificate> get() = listOf(DEV_INTERMEDIATE_CA.certificate, DEV_ROOT_CA.certificate)
|
||||
val DEV_PUB_KEY_HASHES: List<SecureHash.SHA256> 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 {
|
||||
|
@ -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}"
|
||||
|
@ -59,6 +59,7 @@ public class CordaCaplet extends Capsule {
|
||||
|
||||
@Override
|
||||
protected ProcessBuilder prelaunch(List<String> jvmArgs, List<String> 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.
|
||||
|
@ -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<S>(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
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -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<RestrictedURL>,
|
||||
private val versionInfo: VersionInfo = VersionInfo.UNKNOWN,
|
||||
extraCordapps: List<CordappImpl>,
|
||||
private val blacklistedCordappSigners: List<X509Certificate> = emptyList()) : CordappLoaderTemplate() {
|
||||
private val signerKeyFingerprintBlacklist: List<SecureHash.SHA256> = emptyList()) : CordappLoaderTemplate() {
|
||||
|
||||
override val cordapps: List<CordappImpl> by lazy {
|
||||
loadCordapps() + extraCordapps
|
||||
@ -69,10 +68,10 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths:
|
||||
fun fromDirectories(cordappDirs: Collection<Path>,
|
||||
versionInfo: VersionInfo = VersionInfo.UNKNOWN,
|
||||
extraCordapps: List<CordappImpl> = emptyList(),
|
||||
blacklistedCerts: List<X509Certificate> = emptyList()): JarScanningCordappLoader {
|
||||
signerKeyFingerprintBlacklist: List<SecureHash.SHA256> = 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<URL>, versionInfo: VersionInfo = VersionInfo.UNKNOWN, extraCordapps: List<CordappImpl> = emptyList(), blacklistedCerts: List<X509Certificate> = emptyList()): JarScanningCordappLoader {
|
||||
fun fromJarUrls(scanJars: List<URL>, versionInfo: VersionInfo = VersionInfo.UNKNOWN, extraCordapps: List<CordappImpl> = emptyList(),
|
||||
cordappsSignerKeyFingerprintBlacklist: List<SecureHash.SHA256> = 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
|
||||
}
|
||||
}
|
||||
|
@ -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<Path>
|
||||
val flowOverrides: FlowOverrideConfig?
|
||||
|
||||
val cordappSignerKeyFingerprintBlacklist: List<String>
|
||||
|
||||
fun validate(): List<String>
|
||||
|
||||
companion object {
|
||||
@ -234,7 +237,8 @@ data class NodeConfigurationImpl(
|
||||
override val cordappDirectories: List<Path> = 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<String> = DEV_PUB_KEY_HASHES.map { it.toString() }
|
||||
) : NodeConfiguration {
|
||||
companion object {
|
||||
private val logger = loggerFor<NodeConfigurationImpl>()
|
||||
|
@ -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<out T : Any> {
|
||||
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
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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"
|
||||
|
@ -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) {
|
||||
|
@ -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<String>) {
|
||||
|
@ -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"
|
||||
|
Loading…
x
Reference in New Issue
Block a user