[CORDA-2575] Allow users to whitelist attachments by public key config (#5035)

This commit is contained in:
JamesHR3 2019-04-25 16:55:43 +01:00 committed by Shams Asari
parent 4607b0c151
commit 7a7c471ebf
20 changed files with 180 additions and 33 deletions

View File

@ -0,0 +1,14 @@
package net.corda.core.internal
import net.corda.core.DeleteForDJVM
import net.corda.core.crypto.SecureHash
import net.corda.core.node.ServicesForResolution
@DeleteForDJVM
interface ServicesForResolutionInternal : ServicesForResolution {
/**
* If an attachment is signed with a public key with one of these hashes, it will automatically be trusted.
*/
val whitelistedKeysForAttachments: Collection<SecureHash>
}

View File

@ -14,6 +14,7 @@ import net.corda.core.serialization.*
import net.corda.core.serialization.internal.AttachmentURLStreamHandlerFactory.toUrl
import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.debug
import net.corda.core.utilities.toSHA256Bytes
import java.io.ByteArrayOutputStream
import java.io.IOException
import java.io.InputStream
@ -31,10 +32,13 @@ import java.util.*
* @property sampleTxId The transaction ID that triggered the creation of this classloader. Because classloaders are cached
* this tx may be stale, that is, classloading might be triggered by the verification of some other transaction
* if not all code is invoked every time, however we want a txid for errors in case of attachment bogusness.
* @property whitelistedPublicKeys A collection of public key hashes. An attachment signed by a public key with one of these hashes
* will automatically be trusted.
*/
class AttachmentsClassLoader(attachments: List<Attachment>,
val params: NetworkParameters,
private val sampleTxId: SecureHash,
private val whitelistedPublicKeys: Collection<SecureHash>,
parent: ClassLoader = ClassLoader.getSystemClassLoader()) :
URLClassLoader(attachments.map(::toUrl).toTypedArray(), parent) {
@ -118,8 +122,8 @@ class AttachmentsClassLoader(attachments: List<Attachment>,
.filter(::containsClasses)
.filterNot { attachment ->
when (attachment) {
is ContractAttachment -> isUploaderTrusted(attachment.uploader)
is AbstractAttachment -> isUploaderTrusted(attachment.uploader)
is ContractAttachment -> isUploaderTrusted(attachment.uploader) || attachmentSignedByTrustedKey(attachment)
is AbstractAttachment -> isUploaderTrusted(attachment.uploader) || attachmentSignedByTrustedKey(attachment)
else -> false // This should not happen on normal code paths.
}
}
@ -136,6 +140,10 @@ class AttachmentsClassLoader(attachments: List<Attachment>,
checkAttachments(attachments)
}
private fun attachmentSignedByTrustedKey(attachment: Attachment): Boolean {
return attachment.signerKeys.map { it.hash }.any { whitelistedPublicKeys.contains(it) }
}
private fun isZipOrJar(attachment: Attachment) = attachment.openAsJAR().use { jar ->
jar.nextEntry != null
}
@ -311,12 +319,17 @@ object AttachmentsClassLoaderBuilder {
*
* @param txId The transaction ID that triggered this request; it's unused except for error messages and exceptions that can occur during setup.
*/
fun <T> withAttachmentsClassloaderContext(attachments: List<Attachment>, params: NetworkParameters, txId: SecureHash, parent: ClassLoader = ClassLoader.getSystemClassLoader(), block: (ClassLoader) -> T): T {
fun <T> withAttachmentsClassloaderContext(attachments: List<Attachment>,
params: NetworkParameters,
txId: SecureHash,
whitelistedPublicKeys: Collection<SecureHash>,
parent: ClassLoader = ClassLoader.getSystemClassLoader(),
block: (ClassLoader) -> T): T {
val attachmentIds = attachments.map { it.id }.toSet()
val serializationContext = cache.computeIfAbsent(Key(attachmentIds, params)) {
// Create classloader and load serializers, whitelisted classes
val transactionClassLoader = AttachmentsClassLoader(attachments, params, txId, parent)
val transactionClassLoader = AttachmentsClassLoader(attachments, params, txId, whitelistedPublicKeys, parent)
val serializers = createInstancesOfClassesImplementing(transactionClassLoader, SerializationCustomSerializer::class.java)
val whitelistedClasses = ServiceLoader.load(SerializationWhitelist::class.java, transactionClassLoader)
.flatMap { it.whitelist }

View File

@ -9,6 +9,7 @@ import net.corda.core.crypto.componentHash
import net.corda.core.crypto.computeNonce
import net.corda.core.identity.Party
import net.corda.core.internal.AttachmentWithContext
import net.corda.core.internal.ServicesForResolutionInternal
import net.corda.core.internal.combinedHash
import net.corda.core.node.NetworkParameters
import net.corda.core.node.ServicesForResolution
@ -144,8 +145,13 @@ data class ContractUpgradeWireTransaction(
?: throw MissingContractAttachments(emptyList())
val upgradedAttachment = services.attachments.openAttachment(upgradedContractAttachmentId)
?: throw MissingContractAttachments(emptyList())
val whitelistedPublicKeys = (services as? ServicesForResolutionInternal)?.whitelistedKeysForAttachments ?: listOf()
return AttachmentsClassLoaderBuilder.withAttachmentsClassloaderContext(listOf(legacyAttachment, upgradedAttachment), params, id) { transactionClassLoader ->
return AttachmentsClassLoaderBuilder.withAttachmentsClassloaderContext(
listOf(legacyAttachment, upgradedAttachment),
params,
id,
whitelistedPublicKeys) { transactionClassLoader ->
val resolvedInput = binaryInput.deserialize()
val upgradedContract = upgradedContract(upgradedContractClassName, transactionClassLoader)
val outputState = calculateUpgradedState(resolvedInput, upgradedContract, upgradedAttachment)

View File

@ -75,6 +75,7 @@ private constructor(
private var componentGroups: List<ComponentGroup>? = null
private var serializedInputs: List<SerializedStateAndRef>? = null
private var serializedReferences: List<SerializedStateAndRef>? = null
private var whitelistedKeysForAttachments: Collection<SecureHash> = listOf()
init {
if (timeWindow != null) check(notary != null) { "Transactions with time-windows must be notarised" }
@ -98,12 +99,14 @@ private constructor(
references: List<StateAndRef<ContractState>>,
componentGroups: List<ComponentGroup>? = null,
serializedInputs: List<SerializedStateAndRef>? = null,
serializedReferences: List<SerializedStateAndRef>? = null
serializedReferences: List<SerializedStateAndRef>? = null,
whitelistedKeysForAttachments: Collection<SecureHash> = listOf()
): LedgerTransaction {
return LedgerTransaction(inputs, outputs, commands, attachments, id, notary, timeWindow, privacySalt, networkParameters, references).apply {
this.componentGroups = componentGroups
this.serializedInputs = serializedInputs
this.serializedReferences = serializedReferences
this.whitelistedKeysForAttachments = whitelistedKeysForAttachments
}
}
}
@ -141,7 +144,11 @@ private constructor(
internal fun internalPrepareVerify(extraAttachments: List<Attachment>): Verifier {
// Switch thread local deserialization context to using a cached attachments classloader. This classloader enforces various rules
// like no-overlap, package namespace ownership and (in future) deterministic Java.
return AttachmentsClassLoaderBuilder.withAttachmentsClassloaderContext(this.attachments + extraAttachments, getParamsWithGoo(), id) { transactionClassLoader ->
return AttachmentsClassLoaderBuilder.withAttachmentsClassloaderContext(
this.attachments + extraAttachments,
getParamsWithGoo(),
id,
whitelistedPublicKeys = whitelistedKeysForAttachments) { transactionClassLoader ->
// Create a copy of the outer LedgerTransaction which deserializes all fields inside the [transactionClassLoader].
// Only the copy will be used for verification, and the outer shell will be discarded.
// This artifice is required to preserve backwards compatibility.

View File

@ -99,6 +99,7 @@ class WireTransaction(componentGroups: List<ComponentGroup>, val privacySalt: Pr
@Throws(AttachmentResolutionException::class, TransactionResolutionException::class)
@DeleteForDJVM
fun toLedgerTransaction(services: ServicesForResolution): LedgerTransaction {
val whitelistedKeysForAttachments = (services as? ServicesForResolutionInternal)?.whitelistedKeysForAttachments ?: listOf()
return toLedgerTransactionInternal(
resolveIdentity = { services.identityService.partyFromKey(it) },
resolveAttachment = { services.attachments.openAttachment(it) },
@ -107,7 +108,8 @@ class WireTransaction(componentGroups: List<ComponentGroup>, val privacySalt: Pr
val hashToResolve = it ?: services.networkParametersService.defaultHash
services.networkParametersService.lookup(hashToResolve)
},
resolveContractAttachment = { services.loadContractAttachment(it) }
resolveContractAttachment = { services.loadContractAttachment(it) },
whitelistedKeys = whitelistedKeysForAttachments
)
}
@ -142,11 +144,14 @@ class WireTransaction(componentGroups: List<ComponentGroup>, val privacySalt: Pr
{ stateRef -> resolveStateRef(stateRef)?.serialize() },
{ null },
// Returning a dummy `missingAttachment` Attachment allows this deprecated method to work and it disables "contract version no downgrade rule" as a dummy Attachment returns version 1
{ resolveAttachment(it.txhash) ?: missingAttachment }
{ resolveAttachment(it.txhash) ?: missingAttachment },
listOf()
)
}
// Especially crafted for TransactionVerificationRequest
// Especially crafted for TransactionVerificationRequest.
// Note that whitelisted keys do not need to be passed here. The DJVM automatically assumes all attachments are provided by a trusted
// uploader, and so all attachments will be trusted when this is called from the DJVM.
@CordaInternal
internal fun toLtxDjvmInternalBridge(
resolveAttachment: (SecureHash) -> Attachment?,
@ -158,7 +163,8 @@ class WireTransaction(componentGroups: List<ComponentGroup>, val privacySalt: Pr
resolveAttachment,
{ stateRef -> resolveStateRef(stateRef)?.serialize() },
resolveParameters,
{ resolveAttachment(it.txhash) ?: missingAttachment }
{ resolveAttachment(it.txhash) ?: missingAttachment },
listOf()
)
}
@ -167,7 +173,8 @@ class WireTransaction(componentGroups: List<ComponentGroup>, val privacySalt: Pr
resolveAttachment: (SecureHash) -> Attachment?,
resolveStateRefAsSerialized: (StateRef) -> SerializedBytes<TransactionState<ContractState>>?,
resolveParameters: (SecureHash?) -> NetworkParameters?,
resolveContractAttachment: (StateRef) -> Attachment
resolveContractAttachment: (StateRef) -> Attachment,
whitelistedKeys: Collection<SecureHash>
): LedgerTransaction {
// Look up public keys to authenticated identities.
val authenticatedCommands = commands.lazyMapped { cmd, _ ->
@ -202,7 +209,8 @@ class WireTransaction(componentGroups: List<ComponentGroup>, val privacySalt: Pr
resolvedReferences,
componentGroups,
serializedResolvedInputs,
serializedResolvedReferences
serializedResolvedReferences,
whitelistedKeysForAttachments = whitelistedKeys
)
checkTransactionSize(ltx, resolvedNetworkParameters.maxTransactionSize, serializedResolvedInputs, serializedResolvedReferences)

View File

@ -47,7 +47,11 @@ class AttachmentsClassLoaderSerializationTests {
val att1 = storage.importAttachment(fakeAttachment("file1.txt", "some data").inputStream(), "app", "file1.jar")
val att2 = storage.importAttachment(fakeAttachment("file2.txt", "some other data").inputStream(), "app", "file2.jar")
val serialisedState = AttachmentsClassLoaderBuilder.withAttachmentsClassloaderContext(arrayOf(isolatedId, att1, att2).map { storage.openAttachment(it)!! }, testNetworkParameters(), SecureHash.zeroHash) { classLoader ->
val serialisedState = AttachmentsClassLoaderBuilder.withAttachmentsClassloaderContext(
arrayOf(isolatedId, att1, att2).map { storage.openAttachment(it)!! },
testNetworkParameters(),
SecureHash.zeroHash,
listOf()) { classLoader ->
val contractClass = Class.forName(ISOLATED_CONTRACT_CLASS_NAME, true, classLoader)
val contract = contractClass.newInstance() as Contract
assertEquals("helloworld", contract.declaredField<Any?>("magicString").value)

View File

@ -3,12 +3,15 @@ package net.corda.core.transactions
import net.corda.core.contracts.Attachment
import net.corda.core.contracts.Contract
import net.corda.core.contracts.TransactionVerificationException
import net.corda.core.crypto.Crypto
import net.corda.core.crypto.SecureHash
import net.corda.core.internal.declaredField
import net.corda.core.internal.hash
import net.corda.core.internal.inputStream
import net.corda.core.node.NetworkParameters
import net.corda.core.node.services.AttachmentId
import net.corda.core.serialization.internal.AttachmentsClassLoader
import net.corda.nodeapi.internal.cryptoservice.CryptoService
import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.core.internal.ContractJarTestUtils.signContractJar
import net.corda.testing.internal.fakeAttachment
@ -40,7 +43,11 @@ class AttachmentsClassLoaderTests {
private val storage = MockAttachmentStorage()
private val networkParameters = testNetworkParameters()
private fun make(attachments: List<Attachment>, params: NetworkParameters = networkParameters) = AttachmentsClassLoader(attachments, params, SecureHash.zeroHash)
private fun make(attachments: List<Attachment>,
params: NetworkParameters = networkParameters,
whitelistedKeys: List<SecureHash> = listOf()): AttachmentsClassLoader {
return AttachmentsClassLoader(attachments, params, SecureHash.zeroHash, whitelistedPublicKeys = whitelistedKeys)
}
@Test
fun `Loading AnotherDummyContract without using the AttachmentsClassLoader fails`() {
@ -192,6 +199,22 @@ class AttachmentsClassLoaderTests {
}
}
@Test
fun `Allow loading an untrusted contract jar if signed by a trusted public key`() {
val keyPair = Crypto.generateKeyPair()
val classJar = fakeAttachment("/com/example/something/UntrustedClass.class", "Signed by someone trusted").inputStream()
val attachment = classJar.use { storage.importContractAttachment(listOf("UntrustedClass.class"), "untrusted", classJar, signers = listOf(keyPair.public))}
// Check that without the public key whitelisted, building the AttachmentsClassLoader fails. The AttachmentsClassLoader is responsible
// for checking what attachments are trusted at the point that it is constructed.
assertFailsWith(TransactionVerificationException.UntrustedAttachmentsException::class) {
make(arrayOf(attachment).map { storage.openAttachment(it)!! })
}
// Check that with the public key whitelisted, the AttachmentsClassLoader can be built (i.e. the attachment trusted check passes)
make(arrayOf(attachment).map { storage.openAttachment(it)!! }, whitelistedKeys = listOf(keyPair.public.hash))
}
private fun importAttachment(jar: InputStream, uploader: String, filename: String?): AttachmentId {
return jar.use { storage.importAttachment(jar, uploader, filename) }
}

View File

@ -4,6 +4,16 @@ Changelog
Here's a summary of what's changed in each Corda release. For guidance on how to upgrade code from the previous
release, see :doc:`app-upgrade-notes`.
.. _changelog_v4.2:
Version 4.2
-----------
* Added the ``whitelistedKeysForAttachments`` configuration option. This is a list of SHA-256 hashes of public keys. Attachments signed by
any keys in this list will automatically be trusted by the node. This change removes the requirement to have every version of a CorDapp
present in the node in order to verify a chain of transactions using different versions of the same CorDapp - instead the signing key can
be whitelisted.
.. _changelog_v4.0:
Version 4.0

View File

@ -545,6 +545,16 @@ verfierType
*Default:* InMemory
whitelistedKeysForAttachments
A list of SHA256 hashes of public keys. Any attachments that are signed by a key that hashes to one of the items in this list will be
treated as trusted by the node, even if it was received by an untrusted source (for example, over the network).
.. note:: In the future, the DJVM will be integrated with Corda and all attachments will be loaded inside a DJVM sandbox. At this point,
all attachments would be considered trusted, and so this configuration option would be ignored.
*Default:* not defined
Reference.conf
--------------
A set of default configuration options are loaded from the built-in resource file ``/node/src/main/resources/reference.conf``.

View File

@ -14,12 +14,14 @@ import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.unwrap
import net.corda.testing.common.internal.checkNotOnClasspath
import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.BOB_NAME
import net.corda.testing.core.DUMMY_NOTARY_NAME
import net.corda.testing.core.singleIdentity
import net.corda.testing.driver.DriverDSL
import net.corda.testing.driver.DriverParameters
import net.corda.testing.driver.NodeParameters
import net.corda.testing.driver.driver
import net.corda.testing.node.NotarySpec
import net.corda.testing.node.internal.enclosedCordapp
@ -27,6 +29,7 @@ import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.Test
import java.net.URL
import java.net.URLClassLoader
import java.util.jar.JarInputStream
class AttachmentLoadingTests {
private companion object {
@ -87,6 +90,49 @@ class AttachmentLoadingTests {
}
}
@Test
fun `contract is not executed if signing key is not whitelisted and uploader is untrusted`() {
driver(DriverParameters(
startNodesInProcess = false,
notarySpecs = listOf(NotarySpec(DUMMY_NOTARY_NAME, validating = false)),
cordappsForAllNodes = listOf(enclosedCordapp()),
networkParameters = testNetworkParameters(minimumPlatformVersion = 4)
)) {
installIsolatedCordapp(ALICE_NAME)
val (alice, bob) = listOf(
startNode(providedName = ALICE_NAME),
startNode(NodeParameters(providedName = BOB_NAME))
).transpose().getOrThrow()
val stateRef = alice.rpc.startFlowDynamic(issuanceFlowClass, 1234).returnValue.getOrThrow()
assertThatThrownBy { alice.rpc.startFlow(::ConsumeAndBroadcastFlow, stateRef, bob.nodeInfo.singleIdentity()).returnValue.getOrThrow() }
.hasMessage(TransactionVerificationException.UntrustedAttachmentsException::class.java.name)
}
}
@Test
fun `contract is executed if signing key is whitelisted`() {
driver(DriverParameters(
startNodesInProcess = false,
notarySpecs = listOf(NotarySpec(DUMMY_NOTARY_NAME, validating = false)),
cordappsForAllNodes = listOf(enclosedCordapp()),
networkParameters = testNetworkParameters(minimumPlatformVersion = 4)
)) {
installIsolatedCordapp(ALICE_NAME)
val signingKeys = JarSignatureCollector.collectSigners(JarInputStream(isolatedJar.openStream()))
val bobOverrides = mapOf("whitelistedKeysForAttachments" to signingKeys.map{ it.hash.toString() })
val (alice, bob) = listOf(
startNode(providedName = ALICE_NAME),
startNode(NodeParameters(providedName = BOB_NAME).withCustomOverrides(bobOverrides))
).transpose().getOrThrow()
val stateRef = alice.rpc.startFlowDynamic(issuanceFlowClass, 1234).returnValue.getOrThrow()
alice.rpc.startFlow(::ConsumeAndBroadcastFlow, stateRef, bob.nodeInfo.singleIdentity()).returnValue.getOrThrow()
}
}
private fun DriverDSL.installIsolatedCordapp(name: CordaX500Name) {
val cordappsDir = (baseDirectory(name) / "cordapps").createDirectories()
isolatedJar.toPath().copyToDirectory(cordappsDir)

View File

@ -178,7 +178,7 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
val cordappProvider = CordappProviderImpl(cordappLoader, CordappConfigFileProvider(configuration.cordappDirectories), attachments).tokenize()
@Suppress("LeakingThis")
val keyManagementService = makeKeyManagementService(identityService).tokenize()
val servicesForResolution = ServicesForResolutionImpl(identityService, attachments, cordappProvider, networkParametersStorage, transactionStorage).also {
val servicesForResolution = ServicesForResolutionImpl(identityService, attachments, cordappProvider, networkParametersStorage, transactionStorage, configuration.whitelistedKeysForAttachments).also {
attachments.servicesForResolution = it
}
@Suppress("LeakingThis")
@ -959,7 +959,7 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
}
}
inner class ServiceHubInternalImpl : SingletonSerializeAsToken(), ServiceHubInternal, ServicesForResolution by servicesForResolution {
inner class ServiceHubInternalImpl : SingletonSerializeAsToken(), ServiceHubInternal, ServicesForResolutionInternal by servicesForResolution {
override val rpcFlows = ArrayList<Class<out FlowLogic<*>>>()
override val stateMachineRecordedTransactionMapping = DBTransactionMappingStorage(database)
override val identityService: IdentityService get() = this@AbstractNode.identityService

View File

@ -2,7 +2,9 @@ package net.corda.node.internal
import net.corda.core.contracts.*
import net.corda.core.cordapp.CordappProvider
import net.corda.core.crypto.SecureHash
import net.corda.core.internal.SerializedStateAndRef
import net.corda.core.internal.ServicesForResolutionInternal
import net.corda.core.node.NetworkParameters
import net.corda.core.node.ServicesForResolution
import net.corda.core.node.services.AttachmentStorage
@ -19,8 +21,9 @@ data class ServicesForResolutionImpl(
override val attachments: AttachmentStorage,
override val cordappProvider: CordappProvider,
override val networkParametersService: NetworkParametersService,
private val validatedTransactions: TransactionStorage
) : ServicesForResolution {
private val validatedTransactions: TransactionStorage,
override val whitelistedKeysForAttachments: Collection<SecureHash>
) : ServicesForResolutionInternal {
override val networkParameters: NetworkParameters get() = networkParametersService.lookup(networkParametersService.currentHash) ?:
throw IllegalArgumentException("No current parameters in network parameters storage")

View File

@ -107,7 +107,7 @@ class MigrationServicesForResolution(
private fun extractStateFromTx(tx: WireTransaction, stateIndices: Collection<Int>): List<TransactionState<ContractState>> {
return try {
val attachments = tx.attachments.mapNotNull { attachments.openAttachment(it)}
val states = AttachmentsClassLoaderBuilder.withAttachmentsClassloaderContext(attachments, networkParameters, tx.id, cordappLoader.appClassLoader) {
val states = AttachmentsClassLoaderBuilder.withAttachmentsClassloaderContext(attachments, networkParameters, tx.id, listOf(), cordappLoader.appClassLoader) {
deserialiseComponentGroup(tx.componentGroups, TransactionState::class, ComponentGroupEnum.OUTPUTS_GROUP, forceDeserialize = true)
}
states.filterIndexed {index, _ -> stateIndices.contains(index)}.toList()

View File

@ -4,6 +4,7 @@ import com.typesafe.config.Config
import net.corda.common.configuration.parsing.internal.Configuration
import net.corda.common.validation.internal.Validated
import net.corda.core.context.AuthServiceId
import net.corda.core.crypto.SecureHash
import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.TimedFlow
import net.corda.core.internal.notary.NotaryServiceFlow
@ -84,6 +85,7 @@ interface NodeConfiguration {
val cordappSignerKeyFingerprintBlacklist: List<String>
val networkParameterAcceptanceSettings: NetworkParameterAcceptanceSettings
val whitelistedKeysForAttachments: List<SecureHash>
companion object {
// default to at least 8MB and a bit extra for larger heap sizes

View File

@ -1,6 +1,7 @@
package net.corda.node.services.config
import com.typesafe.config.ConfigException
import net.corda.core.crypto.SecureHash
import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.div
import net.corda.core.utilities.NetworkHostAndPort
@ -75,7 +76,8 @@ data class NodeConfigurationImpl(
override val jmxReporterType: JmxReporterType? = Defaults.jmxReporterType,
override val flowOverrides: FlowOverrideConfig?,
override val cordappSignerKeyFingerprintBlacklist: List<String> = Defaults.cordappSignerKeyFingerprintBlacklist,
override val networkParameterAcceptanceSettings: NetworkParameterAcceptanceSettings = Defaults.networkParameterAcceptanceSettings
override val networkParameterAcceptanceSettings: NetworkParameterAcceptanceSettings = Defaults.networkParameterAcceptanceSettings,
override val whitelistedKeysForAttachments: List<SecureHash> = listOf()
) : NodeConfiguration {
internal object Defaults {
val jmxMonitoringHttpPort: Int? = null

View File

@ -6,6 +6,7 @@ import com.typesafe.config.ConfigUtil
import net.corda.common.configuration.parsing.internal.Configuration
import net.corda.common.validation.internal.Validated.Companion.invalid
import net.corda.common.validation.internal.Validated.Companion.valid
import net.corda.core.crypto.SecureHash
import net.corda.core.identity.CordaX500Name
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.node.services.config.Valid
@ -33,6 +34,8 @@ internal fun toPrincipal(rawValue: String) = attempt<X500Principal, IllegalArgum
internal fun toPath(rawValue: String) = attempt<Path, InvalidPathException> { Paths.get(rawValue) }
internal fun toSecureHash(rawValue: String) = attempt<SecureHash, IllegalArgumentException> { SecureHash.parse(rawValue)}
private inline fun <RESULT, reified ERROR : Exception> attempt(action: () -> RESULT, message: (ERROR) -> String): Valid<RESULT> {
return try {
valid(action.invoke())

View File

@ -17,14 +17,7 @@ import net.corda.node.services.config.NodeConfigurationImpl
import net.corda.node.services.config.NodeConfigurationImpl.Defaults
import net.corda.node.services.config.Valid
import net.corda.node.services.config.VerifierType
import net.corda.node.services.config.schema.parsers.badValue
import net.corda.node.services.config.schema.parsers.toCordaX500Name
import net.corda.node.services.config.schema.parsers.toNetworkHostAndPort
import net.corda.node.services.config.schema.parsers.toPath
import net.corda.node.services.config.schema.parsers.toPrincipal
import net.corda.node.services.config.schema.parsers.toProperties
import net.corda.node.services.config.schema.parsers.toURL
import net.corda.node.services.config.schema.parsers.toUUID
import net.corda.node.services.config.schema.parsers.*
internal object V1NodeConfigurationSpec : Configuration.Specification<NodeConfiguration>("NodeConfiguration") {
private val myLegalName by string().mapValid(::toCordaX500Name)
@ -73,6 +66,7 @@ internal object V1NodeConfigurationSpec : Configuration.Specification<NodeConfig
private val jarDirs by string().list().optional().withDefaultValue(Defaults.jarDirs)
private val cordappDirectories by string().mapValid(::toPath).list().optional()
private val cordappSignerKeyFingerprintBlacklist by string().list().optional().withDefaultValue(Defaults.cordappSignerKeyFingerprintBlacklist)
private val whitelistedKeysForAttachments by string().mapValid(::toSecureHash).list().optional().withDefaultValue(listOf())
@Suppress("unused")
private val custom by nestedObject().optional()
@ -128,7 +122,8 @@ internal object V1NodeConfigurationSpec : Configuration.Specification<NodeConfig
h2port = configuration[h2port],
jarDirs = configuration[jarDirs],
cordappDirectories = cordappDirectories,
cordappSignerKeyFingerprintBlacklist = configuration[cordappSignerKeyFingerprintBlacklist]
cordappSignerKeyFingerprintBlacklist = configuration[cordappSignerKeyFingerprintBlacklist],
whitelistedKeysForAttachments = configuration[whitelistedKeysForAttachments]
))
} catch (e: Exception) {
return when (e) {

View File

@ -213,7 +213,7 @@ class CordaClassResolverTests {
fun `Annotation does not work in conjunction with AttachmentClassLoader annotation`() {
val storage = MockAttachmentStorage()
val attachmentHash = importJar(storage)
val classLoader = AttachmentsClassLoader(arrayOf(attachmentHash).map { storage.openAttachment(it)!! }, testNetworkParameters(), SecureHash.zeroHash)
val classLoader = AttachmentsClassLoader(arrayOf(attachmentHash).map { storage.openAttachment(it)!! }, testNetworkParameters(), SecureHash.zeroHash, whitelistedPublicKeys = listOf())
val attachedClass = Class.forName("net.corda.isolated.contracts.AnotherDummyContract", true, classLoader)
CordaClassResolver(emptyWhitelistContext).getRegistration(attachedClass)
}
@ -222,7 +222,7 @@ class CordaClassResolverTests {
fun `Attempt to load contract attachment with untrusted uploader should fail with UntrustedAttachmentsException`() {
val storage = MockAttachmentStorage()
val attachmentHash = importJar(storage, "some_uploader")
val classLoader = AttachmentsClassLoader(arrayOf(attachmentHash).map { storage.openAttachment(it)!! }, testNetworkParameters(), SecureHash.zeroHash)
val classLoader = AttachmentsClassLoader(arrayOf(attachmentHash).map { storage.openAttachment(it)!! }, testNetworkParameters(), SecureHash.zeroHash, whitelistedPublicKeys = listOf())
val attachedClass = Class.forName("net.corda.isolated.contracts.AnotherDummyContract", true, classLoader)
CordaClassResolver(emptyWhitelistContext).getRegistration(attachedClass)
}

View File

@ -393,7 +393,7 @@ open class MockServices private constructor(
override var networkParametersService: NetworkParametersService = MockNetworkParametersStorage(initialNetworkParameters)
protected val servicesForResolution: ServicesForResolution
get() = ServicesForResolutionImpl(identityService, attachments, cordappProvider, networkParametersService, validatedTransactions)
get() = ServicesForResolutionImpl(identityService, attachments, cordappProvider, networkParametersService, validatedTransactions, listOf())
internal fun makeVaultService(schemaService: SchemaService, database: CordaPersistence, cordappLoader: CordappLoader): VaultServiceInternal {
return NodeVaultService(clock, keyManagementService, servicesForResolution, database, schemaService, cordappLoader.appClassLoader).apply { start() }

View File

@ -614,6 +614,7 @@ private fun mockNodeConfiguration(certificatesDirectory: Path): NodeConfiguratio
doReturn(5.seconds.toMillis()).whenever(it).additionalNodeInfoPollingFrequencyMsec
doReturn(null).whenever(it).devModeOptions
doReturn(NetworkParameterAcceptanceSettings()).whenever(it).networkParameterAcceptanceSettings
doReturn(emptyList<SecureHash>()).whenever(it).whitelistedKeysForAttachments
}
}