mirror of
https://github.com/corda/corda.git
synced 2025-01-31 08:25:50 +00:00
[CORDA-2575] Allow users to whitelist attachments by public key config (#5035)
This commit is contained in:
parent
4607b0c151
commit
7a7c471ebf
@ -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>
|
||||
}
|
@ -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 }
|
||||
|
@ -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)
|
||||
|
@ -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.
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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) }
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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``.
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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")
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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())
|
||||
|
@ -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) {
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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() }
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user