mirror of
https://github.com/corda/corda.git
synced 2025-04-07 11:27:01 +00:00
CORDA-2595 - check that all attachments are trusted before loading (#4763)
CORDA-2595 - Fix test and api. CORDA-2595 add test CORDA-2595 fix tests CORDA-2595 fix test and address code review comments CORDA-2595 address code review comments
This commit is contained in:
parent
37f9eb31f7
commit
3d362e066c
@ -1094,7 +1094,7 @@ public static final class net.corda.core.contracts.TransactionVerificationExcept
|
||||
@NotNull
|
||||
public final net.corda.core.crypto.SecureHash getAttachmentHash()
|
||||
@NotNull
|
||||
public final String getContractClass()
|
||||
public final String getInvalidClassName()
|
||||
@NotNull
|
||||
public final String getPackageName()
|
||||
##
|
||||
|
@ -86,7 +86,7 @@ class JacksonSupportTest(@Suppress("unused") private val name: String, factory:
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
val unsignedAttachment = object : AbstractAttachment({ byteArrayOf() }) {
|
||||
val unsignedAttachment = object : AbstractAttachment({ byteArrayOf() }, "test") {
|
||||
override val id: SecureHash get() = throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
|
@ -1,21 +1,19 @@
|
||||
package net.corda.deterministic.verifier
|
||||
|
||||
import net.corda.core.contracts.Attachment
|
||||
import net.corda.core.contracts.ContractClassName
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.internal.AbstractAttachment
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.InputStream
|
||||
import java.security.PublicKey
|
||||
|
||||
// A valid zip file with 1 entry.
|
||||
val simpleZip = byteArrayOf(80, 75, 3, 4, 20, 0, 8, 8, 8, 0, 15, 113, 79, 78, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 4, 0, 47, 97, -2, -54, 0, 0, 75, 4, 0, 80, 75, 7, 8, 67, -66, -73, -24, 3, 0, 0, 0, 1, 0, 0, 0, 80, 75, 1, 2, 20, 0, 20, 0, 8, 8, 8, 0, 15, 113, 79, 78, 67, -66, -73, -24, 3, 0, 0, 0, 1, 0, 0, 0, 2, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 47, 97, -2, -54, 0, 0, 80, 75, 5, 6, 0, 0, 0, 0, 1, 0, 1, 0, 52, 0, 0, 0, 55, 0, 0, 0, 0, 0)
|
||||
|
||||
@CordaSerializable
|
||||
class MockContractAttachment(
|
||||
override val id: SecureHash = SecureHash.zeroHash,
|
||||
val contract: ContractClassName,
|
||||
override val signerKeys: List<PublicKey> = emptyList(),
|
||||
override val signers: List<Party> = emptyList()
|
||||
) : Attachment {
|
||||
override fun open(): InputStream = ByteArrayInputStream(id.bytes)
|
||||
override val size = id.size
|
||||
}
|
||||
) : AbstractAttachment({ simpleZip }, "app")
|
@ -275,11 +275,15 @@ abstract class TransactionVerificationException(val txId: SecureHash, message: S
|
||||
* and because attachment classloaders are reused this is independent of any particular transaction.
|
||||
*/
|
||||
@CordaSerializable
|
||||
class PackageOwnershipException(txId: SecureHash, val attachmentHash: AttachmentId, val contractClass: String, val packageName: String) : TransactionVerificationException(txId,
|
||||
"""The Contract attachment JAR: $attachmentHash containing the contract: $contractClass is not signed by the owner of package $packageName specified in the network parameters.
|
||||
class PackageOwnershipException(txId: SecureHash, val attachmentHash: AttachmentId, val invalidClassName: String, val packageName: String) : TransactionVerificationException(txId,
|
||||
"""The attachment JAR: $attachmentHash containing the class: $invalidClassName is not signed by the owner of package $packageName specified in the network parameters.
|
||||
Please check the source of this attachment and if it is malicious contact your zone operator to report this incident.
|
||||
For details see: https://docs.corda.net/network-map.html#network-parameters""".trimIndent(), null)
|
||||
|
||||
@CordaSerializable
|
||||
class InvalidAttachmentException(txId: SecureHash, attachmentHash: AttachmentId) : TransactionVerificationException(txId,
|
||||
"The attachment $attachmentHash is not a valid ZIP or JAR file.".trimIndent(), null)
|
||||
|
||||
// TODO: Make this descend from TransactionVerificationException so that untrusted attachments cause flows to be hospitalized.
|
||||
/** Thrown during classloading upon encountering an untrusted attachment (eg. not in the [TRUSTED_UPLOADERS] list) */
|
||||
@KeepForDJVM
|
||||
|
@ -19,6 +19,7 @@ import java.util.jar.JarInputStream
|
||||
const val DEPLOYED_CORDAPP_UPLOADER = "app"
|
||||
const val RPC_UPLOADER = "rpc"
|
||||
const val P2P_UPLOADER = "p2p"
|
||||
const val TESTDSL_UPLOADER = "TestDSL"
|
||||
const val UNKNOWN_UPLOADER = "unknown"
|
||||
|
||||
// We whitelist sources of transaction JARs for now as a temporary state until the DJVM and other security sandboxes
|
||||
@ -26,12 +27,12 @@ const val UNKNOWN_UPLOADER = "unknown"
|
||||
// can be removed. Because we ARE downloading attachments over the P2P network in anticipation of this upgrade, we
|
||||
// track the source of each attachment in our store. TestDSL is used by LedgerDSLInterpreter when custom attachments
|
||||
// are added in unit test code.
|
||||
val TRUSTED_UPLOADERS = listOf(DEPLOYED_CORDAPP_UPLOADER, RPC_UPLOADER, "TestDSL")
|
||||
val TRUSTED_UPLOADERS = listOf(DEPLOYED_CORDAPP_UPLOADER, RPC_UPLOADER, TESTDSL_UPLOADER)
|
||||
|
||||
fun isUploaderTrusted(uploader: String?): Boolean = uploader in TRUSTED_UPLOADERS
|
||||
|
||||
@KeepForDJVM
|
||||
abstract class AbstractAttachment(dataLoader: () -> ByteArray) : Attachment {
|
||||
abstract class AbstractAttachment(dataLoader: () -> ByteArray, val uploader: String?) : Attachment {
|
||||
companion object {
|
||||
/**
|
||||
* Returns a function that knows how to load an attachment.
|
||||
|
@ -148,16 +148,18 @@ sealed class FetchDataFlow<T : NamedByHash, in W : Any>(
|
||||
class FetchAttachmentsFlow(requests: Set<SecureHash>,
|
||||
otherSide: FlowSession) : FetchDataFlow<Attachment, ByteArray>(requests, otherSide, DataType.ATTACHMENT) {
|
||||
|
||||
private val uploader = "$P2P_UPLOADER:${otherSideSession.counterparty.name}"
|
||||
|
||||
override fun load(txid: SecureHash): Attachment? = serviceHub.attachments.openAttachment(txid)
|
||||
|
||||
override fun convert(wire: ByteArray): Attachment = FetchedAttachment({ wire })
|
||||
override fun convert(wire: ByteArray): Attachment = FetchedAttachment({ wire }, uploader)
|
||||
|
||||
override fun maybeWriteToDisk(downloaded: List<Attachment>) {
|
||||
for (attachment in downloaded) {
|
||||
with(serviceHub.attachments) {
|
||||
if (!hasAttachment(attachment.id)) {
|
||||
try {
|
||||
importAttachment(attachment.open(), "$P2P_UPLOADER:${otherSideSession.counterparty.name}", null)
|
||||
importAttachment(attachment.open(), uploader, null)
|
||||
} catch (e: FileAlreadyExistsException) {
|
||||
// This can happen when another transaction will insert the same attachment during this transaction.
|
||||
// The outcome is the same (the attachment is imported), so we can ignore this exception.
|
||||
@ -170,14 +172,14 @@ class FetchAttachmentsFlow(requests: Set<SecureHash>,
|
||||
}
|
||||
}
|
||||
|
||||
private class FetchedAttachment(dataLoader: () -> ByteArray) : AbstractAttachment(dataLoader), SerializeAsToken {
|
||||
private class FetchedAttachment(dataLoader: () -> ByteArray, uploader: String?) : AbstractAttachment(dataLoader, uploader), SerializeAsToken {
|
||||
override val id: SecureHash by lazy { attachmentData.sha256() }
|
||||
|
||||
private class Token(private val id: SecureHash) : SerializationToken {
|
||||
override fun fromToken(context: SerializeAsTokenContext) = FetchedAttachment(context.attachmentDataLoader(id))
|
||||
private class Token(private val id: SecureHash, private val uploader: String?) : SerializationToken {
|
||||
override fun fromToken(context: SerializeAsTokenContext) = FetchedAttachment(context.attachmentDataLoader(id), uploader)
|
||||
}
|
||||
|
||||
override fun toToken(context: SerializeAsTokenContext) = Token(id)
|
||||
override fun toToken(context: SerializeAsTokenContext) = Token(id, uploader)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -107,13 +107,47 @@ class AttachmentsClassLoader(attachments: List<Attachment>,
|
||||
}
|
||||
|
||||
init {
|
||||
val untrusted = attachments.mapNotNull { it as? ContractAttachment }.filterNot { isUploaderTrusted(it.uploader) }
|
||||
.map(ContractAttachment::id)
|
||||
|
||||
// Make some preliminary checks to ensure that we're not loading invalid attachments.
|
||||
|
||||
// All attachments need to be valid JAR or ZIP files.
|
||||
for (attachment in attachments) {
|
||||
if (!isZipOrJar(attachment)) throw TransactionVerificationException.InvalidAttachmentException(sampleTxId, attachment.id)
|
||||
}
|
||||
|
||||
// Until we have a sandbox to run untrusted code we need to make sure that any loaded class file was whitelisted by the node administrator.
|
||||
val untrusted = attachments
|
||||
.filter(::containsClasses)
|
||||
.filterNot { attachment ->
|
||||
when (attachment) {
|
||||
is ContractAttachment -> isUploaderTrusted(attachment.uploader)
|
||||
is AbstractAttachment -> isUploaderTrusted(attachment.uploader)
|
||||
else -> false // This should not happen on normal code paths.
|
||||
}
|
||||
}
|
||||
.map(Attachment::id)
|
||||
|
||||
if (untrusted.isNotEmpty())
|
||||
throw TransactionVerificationException.UntrustedAttachmentsException(sampleTxId, untrusted)
|
||||
|
||||
// Enforce the no-overlap and package ownership rules.
|
||||
checkAttachments(attachments)
|
||||
}
|
||||
|
||||
private fun isZipOrJar(attachment: Attachment) = attachment.openAsJAR().use { jar ->
|
||||
jar.nextEntry != null
|
||||
}
|
||||
|
||||
private fun containsClasses(attachment: Attachment): Boolean {
|
||||
attachment.openAsJAR().use { jar ->
|
||||
while (true) {
|
||||
val entry = jar.nextJarEntry ?: return false
|
||||
if(entry.name.endsWith(".class", ignoreCase = true)) return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// This function attempts to strike a balance between security and usability when it comes to the no-overlap rule.
|
||||
// TODO - investigate potential exploits.
|
||||
private fun shouldCheckForNoOverlap(path: String, targetPlatformVersion: Int): Boolean {
|
||||
@ -199,7 +233,7 @@ class AttachmentsClassLoader(attachments: List<Attachment>,
|
||||
if (path.endsWith(".class")) {
|
||||
// Get the package name from the file name. Inner classes separate their names with $ not /
|
||||
// in file names so they are not a problem.
|
||||
val pkgName= path
|
||||
val pkgName = path
|
||||
.dropLast(".class".length)
|
||||
.replace('/', '.')
|
||||
.split('.')
|
||||
@ -241,7 +275,6 @@ class AttachmentsClassLoader(attachments: List<Attachment>,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Required to prevent classes that were excluded from the no-overlap check from being loaded by contract code.
|
||||
* As it can lead to non-determinism.
|
||||
|
@ -115,7 +115,7 @@ class WireTransaction(componentGroups: List<ComponentGroup>, val privacySalt: Pr
|
||||
// Helper for deprecated toLedgerTransaction
|
||||
// TODO: revisit once Deterministic JVM code updated
|
||||
private val missingAttachment: Attachment by lazy {
|
||||
object : AbstractAttachment({ byteArrayOf() }) {
|
||||
object : AbstractAttachment({ byteArrayOf() }, DEPLOYED_CORDAPP_UPLOADER ) {
|
||||
override val id: SecureHash get() = throw UnsupportedOperationException()
|
||||
}
|
||||
}
|
||||
|
@ -155,7 +155,7 @@ class ResolveTransactionsFlowTest {
|
||||
}
|
||||
// TODO: this operation should not require an explicit transaction
|
||||
val id = megaCorpNode.transaction {
|
||||
megaCorpNode.services.attachments.importAttachment(makeJar(), "test", null)
|
||||
megaCorpNode.services.attachments.importAttachment(makeJar(), TESTDSL_UPLOADER, null)
|
||||
}
|
||||
val stx2 = makeTransactions(withAttachment = id).second
|
||||
val p = TestFlow(stx2, megaCorp)
|
||||
|
@ -42,7 +42,6 @@ class AttachmentsClassLoaderTests {
|
||||
private val networkParameters = testNetworkParameters()
|
||||
private fun make(attachments: List<Attachment>, params: NetworkParameters = networkParameters) = AttachmentsClassLoader(attachments, params, SecureHash.zeroHash)
|
||||
|
||||
|
||||
@Test
|
||||
fun `Loading AnotherDummyContract without using the AttachmentsClassLoader fails`() {
|
||||
assertFailsWith<ClassNotFoundException> {
|
||||
@ -123,9 +122,9 @@ class AttachmentsClassLoaderTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Overlapping rules for META-INF serializationwhitelist files`() {
|
||||
val att1 = importAttachment(fakeAttachment("meta-inf/services/net.corda.core.serialization.serializationwhitelist", "some data").inputStream(), "app", "file1.jar")
|
||||
val att2 = importAttachment(fakeAttachment("meta-inf/services/net.corda.core.serialization.serializationwhitelist", "some other data").inputStream(), "app", "file2.jar")
|
||||
fun `Overlapping rules for META-INF SerializationWhitelist files`() {
|
||||
val att1 = importAttachment(fakeAttachment("meta-inf/services/net.corda.core.serialization.SerializationWhitelist", "some data").inputStream(), "app", "file1.jar")
|
||||
val att2 = importAttachment(fakeAttachment("meta-inf/services/net.corda.core.serialization.SerializationWhitelist", "some other data").inputStream(), "app", "file2.jar")
|
||||
|
||||
make(arrayOf(att1, att2).map { storage.openAttachment(it)!! })
|
||||
}
|
||||
@ -179,6 +178,20 @@ class AttachmentsClassLoaderTests {
|
||||
assertArrayEquals("some other data".toByteArray(), data2b)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Allow loading untrusted resource jars but only trusted jars that contain class files`() {
|
||||
val trustedResourceJar = importAttachment(fakeAttachment("file1.txt", "some data").inputStream(), "app", "file0.jar")
|
||||
val untrustedResourceJar = importAttachment(fakeAttachment("file2.txt", "some malicious data").inputStream(), "untrusted", "file1.jar")
|
||||
val untrustedClassJar = importAttachment(fakeAttachment("/com/example/something/MaliciousClass.class", "some malicious data").inputStream(), "untrusted", "file2.jar")
|
||||
val trustedClassJar = importAttachment(fakeAttachment("/com/example/something/VirtuousClass.class", "some other data").inputStream(), "app", "file3.jar")
|
||||
|
||||
make(arrayOf(trustedResourceJar, untrustedResourceJar, trustedClassJar).map { storage.openAttachment(it)!! })
|
||||
|
||||
assertFailsWith(TransactionVerificationException.UntrustedAttachmentsException::class) {
|
||||
make(arrayOf(trustedResourceJar, untrustedResourceJar, trustedClassJar, untrustedClassJar).map { storage.openAttachment(it)!! })
|
||||
}
|
||||
}
|
||||
|
||||
private fun importAttachment(jar: InputStream, uploader: String, filename: String?): AttachmentId {
|
||||
return jar.use { storage.importAttachment(jar, uploader, filename) }
|
||||
}
|
||||
|
@ -158,13 +158,13 @@ class TransactionBuilderTest {
|
||||
assertThat(wtx.outputs).containsOnly(outputState.copy(constraint = expectedConstraint))
|
||||
}
|
||||
|
||||
private val unsignedAttachment = ContractAttachment(object : AbstractAttachment({ byteArrayOf() }) {
|
||||
private val unsignedAttachment = ContractAttachment(object : AbstractAttachment({ byteArrayOf() }, "test") {
|
||||
override val id: SecureHash get() = throw UnsupportedOperationException()
|
||||
|
||||
override val signerKeys: List<PublicKey> get() = emptyList()
|
||||
}, DummyContract.PROGRAM_ID)
|
||||
|
||||
private fun signedAttachment(vararg parties: Party) = ContractAttachment.create(object : AbstractAttachment({ byteArrayOf() }) {
|
||||
private fun signedAttachment(vararg parties: Party) = ContractAttachment.create(object : AbstractAttachment({ byteArrayOf() }, "test") {
|
||||
override val id: SecureHash get() = throw UnsupportedOperationException()
|
||||
|
||||
override val signerKeys: List<PublicKey> get() = parties.map { it.owningKey }
|
||||
|
@ -6,6 +6,8 @@ import net.corda.core.contracts.*
|
||||
import net.corda.core.crypto.*
|
||||
import net.corda.core.crypto.CompositeKey
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.internal.AbstractAttachment
|
||||
import net.corda.core.internal.TESTDSL_UPLOADER
|
||||
import net.corda.core.node.NotaryInfo
|
||||
import net.corda.testing.common.internal.testNetworkParameters
|
||||
import net.corda.testing.contracts.DummyContract
|
||||
@ -124,7 +126,6 @@ class TransactionTests {
|
||||
doReturn(SecureHash.zeroHash).whenever(it).id
|
||||
doReturn(fakeAttachment("nothing", "nada").inputStream()).whenever(it).open()
|
||||
}, DummyContract.PROGRAM_ID, uploader = "app"))
|
||||
attachments.first().openAsJAR()
|
||||
val id = SecureHash.randomSHA256()
|
||||
val timeWindow: TimeWindow? = null
|
||||
val privacySalt = PrivacySalt()
|
||||
@ -168,8 +169,9 @@ class TransactionTests {
|
||||
val inputs = listOf(StateAndRef(inState, StateRef(SecureHash.randomSHA256(), 0)))
|
||||
val outputs = listOf(outState)
|
||||
val commands = emptyList<CommandWithParties<CommandData>>()
|
||||
val attachments = listOf(object : Attachment {
|
||||
override fun open(): InputStream = AttachmentsClassLoaderTests::class.java.getResource("isolated-4.0.jar").openStream()
|
||||
val attachments = listOf(object : AbstractAttachment( {
|
||||
AttachmentsClassLoaderTests::class.java.getResource("isolated-4.0.jar").openStream().readBytes()
|
||||
}, TESTDSL_UPLOADER) {
|
||||
@Suppress("OverridingDeprecatedMember")
|
||||
override val signers: List<Party> = emptyList()
|
||||
override val signerKeys: List<PublicKey> = emptyList()
|
||||
|
@ -231,13 +231,13 @@ object DefaultKryoCustomizer {
|
||||
val attachment = attachmentStorage.openAttachment(attachmentHash)
|
||||
?: throw MissingAttachmentsException(listOf(attachmentHash))
|
||||
attachment.open().readFully()
|
||||
}) {
|
||||
}, uploader) {
|
||||
override val id = attachmentHash
|
||||
}
|
||||
|
||||
return ContractAttachment.create(lazyAttachment, contract, additionalContracts, uploader, signers, version)
|
||||
} else {
|
||||
val attachment = GeneratedAttachment(input.readBytesWithLength())
|
||||
val attachment = GeneratedAttachment(input.readBytesWithLength(), "generated")
|
||||
val contract = input.readString()
|
||||
val additionalContracts = kryo.readClassAndObject(input) as Set<ContractClassName>
|
||||
val uploader = input.readString()
|
||||
|
@ -204,18 +204,18 @@ class NodeAttachmentService(
|
||||
}
|
||||
}
|
||||
|
||||
private class AttachmentImpl(override val id: SecureHash, dataLoader: () -> ByteArray, private val checkOnLoad: Boolean) : AbstractAttachment(dataLoader), SerializeAsToken {
|
||||
private class AttachmentImpl(override val id: SecureHash, dataLoader: () -> ByteArray, private val checkOnLoad: Boolean, uploader: String?) : AbstractAttachment(dataLoader, uploader), SerializeAsToken {
|
||||
override fun open(): InputStream {
|
||||
val stream = super.open()
|
||||
// This is just an optional safety check. If it slows things down too much it can be disabled.
|
||||
return if (checkOnLoad && id is SecureHash.SHA256) HashCheckingStream(id, attachmentData.size, stream) else stream
|
||||
}
|
||||
|
||||
private class Token(private val id: SecureHash, private val checkOnLoad: Boolean) : SerializationToken {
|
||||
override fun fromToken(context: SerializeAsTokenContext) = AttachmentImpl(id, context.attachmentDataLoader(id), checkOnLoad)
|
||||
private class Token(private val id: SecureHash, private val checkOnLoad: Boolean, private val uploader: String?) : SerializationToken {
|
||||
override fun fromToken(context: SerializeAsTokenContext) = AttachmentImpl(id, context.attachmentDataLoader(id), checkOnLoad, uploader)
|
||||
}
|
||||
|
||||
override fun toToken(context: SerializeAsTokenContext) = Token(id, checkOnLoad)
|
||||
override fun toToken(context: SerializeAsTokenContext) = Token(id, checkOnLoad, uploader)
|
||||
}
|
||||
|
||||
// slightly complex 2 level approach to attachment caching:
|
||||
@ -241,7 +241,7 @@ class NodeAttachmentService(
|
||||
return database.transaction {
|
||||
val attachment = currentDBSession().get(NodeAttachmentService.DBAttachment::class.java, id.toString())
|
||||
?: return@transaction null
|
||||
val attachmentImpl = AttachmentImpl(id, { attachment.content }, checkAttachmentsOnLoad).let {
|
||||
val attachmentImpl = AttachmentImpl(id, { attachment.content }, checkAttachmentsOnLoad, attachment.uploader).let {
|
||||
val contracts = attachment.contractClassNames
|
||||
if (contracts != null && contracts.isNotEmpty()) {
|
||||
ContractAttachment.create(it, contracts.first(), contracts.drop(1).toSet(), attachment.uploader, attachment.signers?.toList()
|
||||
|
@ -5,6 +5,6 @@ import net.corda.core.crypto.sha256
|
||||
import net.corda.core.internal.AbstractAttachment
|
||||
|
||||
@KeepForDJVM
|
||||
class GeneratedAttachment(val bytes: ByteArray) : AbstractAttachment({ bytes }) {
|
||||
class GeneratedAttachment(val bytes: ByteArray, uploader: String?) : AbstractAttachment({ bytes }, uploader) {
|
||||
override val id = bytes.sha256()
|
||||
}
|
||||
|
@ -24,7 +24,7 @@ class ContractAttachmentSerializer(factory: SerializerFactory) : CustomSerialize
|
||||
} catch (e: Exception) {
|
||||
throw MissingAttachmentsException(listOf(obj.id))
|
||||
}
|
||||
return ContractAttachmentProxy(GeneratedAttachment(bytes), obj.contract, obj.additionalContracts, obj.uploader, obj.signerKeys, obj.version)
|
||||
return ContractAttachmentProxy(GeneratedAttachment(bytes, obj.uploader), obj.contract, obj.additionalContracts, obj.uploader, obj.signerKeys, obj.version)
|
||||
}
|
||||
|
||||
override fun fromProxy(proxy: ContractAttachmentProxy): ContractAttachment {
|
||||
|
@ -40,7 +40,7 @@ class ContractAttachmentSerializerTest {
|
||||
|
||||
@Test
|
||||
fun `write contract attachment and read it back`() {
|
||||
val contractAttachment = ContractAttachment(GeneratedAttachment(EMPTY_BYTE_ARRAY), DummyContract.PROGRAM_ID)
|
||||
val contractAttachment = ContractAttachment(GeneratedAttachment(EMPTY_BYTE_ARRAY, "test"), DummyContract.PROGRAM_ID)
|
||||
// no token context so will serialize the whole attachment
|
||||
val serialized = contractAttachment.checkpointSerialize()
|
||||
val deserialized = serialized.checkpointDeserialize()
|
||||
@ -53,7 +53,7 @@ class ContractAttachmentSerializerTest {
|
||||
|
||||
@Test
|
||||
fun `write contract attachment and read it back using token context`() {
|
||||
val attachment = GeneratedAttachment("test".toByteArray())
|
||||
val attachment = GeneratedAttachment("test".toByteArray(), "test")
|
||||
|
||||
mockServices.attachments.importAttachment(attachment.open(), "test", null)
|
||||
|
||||
@ -70,7 +70,7 @@ class ContractAttachmentSerializerTest {
|
||||
@Test
|
||||
fun `check only serialize attachment id and contract class name when using token context`() {
|
||||
val largeAttachmentSize = 1024 * 1024
|
||||
val attachment = GeneratedAttachment(ByteArray(largeAttachmentSize))
|
||||
val attachment = GeneratedAttachment(ByteArray(largeAttachmentSize), "test")
|
||||
|
||||
mockServices.attachments.importAttachment(attachment.open(), "test", null)
|
||||
|
||||
@ -82,7 +82,7 @@ class ContractAttachmentSerializerTest {
|
||||
|
||||
@Test
|
||||
fun `throws when missing attachment when using token context`() {
|
||||
val attachment = GeneratedAttachment("test".toByteArray())
|
||||
val attachment = GeneratedAttachment("test".toByteArray(), "test")
|
||||
|
||||
// don't importAttachment in mockService
|
||||
|
||||
@ -95,7 +95,7 @@ class ContractAttachmentSerializerTest {
|
||||
|
||||
@Test
|
||||
fun `check attachment in deserialize is lazy loaded when using token context`() {
|
||||
val attachment = GeneratedAttachment(EMPTY_BYTE_ARRAY)
|
||||
val attachment = GeneratedAttachment(EMPTY_BYTE_ARRAY, "test")
|
||||
// don't importAttachment in mockService
|
||||
|
||||
val contractAttachment = ContractAttachment(attachment, DummyContract.PROGRAM_ID)
|
||||
|
@ -1304,7 +1304,7 @@ class SerializationOutputTests(private val compression: CordaSerializationEncodi
|
||||
)
|
||||
factory2.register(net.corda.serialization.internal.amqp.custom.ContractAttachmentSerializer(factory2))
|
||||
|
||||
val obj = ContractAttachment(GeneratedAttachment("test".toByteArray()), DummyContract.PROGRAM_ID)
|
||||
val obj = ContractAttachment(GeneratedAttachment("test".toByteArray(), "test"), DummyContract.PROGRAM_ID)
|
||||
val obj2 = serdes(obj, factory, factory2, expectedEqual = false, expectDeserializedEqual = false)
|
||||
assertEquals(obj.id, obj2.attachment.id)
|
||||
assertEquals(obj.contract, obj2.contract)
|
||||
@ -1324,7 +1324,7 @@ class SerializationOutputTests(private val compression: CordaSerializationEncodi
|
||||
)
|
||||
factory2.register(net.corda.serialization.internal.amqp.custom.ContractAttachmentSerializer(factory2))
|
||||
|
||||
val obj = ContractAttachment(object : AbstractAttachment({ throw Exception() }) {
|
||||
val obj = ContractAttachment(object : AbstractAttachment({ throw Exception() }, "test") {
|
||||
override val id = SecureHash.zeroHash
|
||||
}, DummyContract.PROGRAM_ID)
|
||||
|
||||
|
@ -87,7 +87,7 @@ class MockAttachmentStorage : AttachmentStorage, SingletonSerializeAsToken() {
|
||||
|
||||
fun getAttachmentIdAndBytes(jar: InputStream): Pair<AttachmentId, ByteArray> = jar.readFully().let { bytes -> Pair(bytes.sha256(), bytes) }
|
||||
|
||||
private class MockAttachment(dataLoader: () -> ByteArray, override val id: SecureHash, override val signerKeys: List<PublicKey>) : AbstractAttachment(dataLoader)
|
||||
private class MockAttachment(dataLoader: () -> ByteArray, override val id: SecureHash, override val signerKeys: List<PublicKey>, uploader: String) : AbstractAttachment(dataLoader, uploader)
|
||||
|
||||
private fun importAttachmentInternal(jar: InputStream, uploader: String, contractClassNames: List<ContractClassName>? = null, attachmentId: AttachmentId? = null, signers: List<PublicKey> = emptyList()): AttachmentId {
|
||||
// JIS makes read()/readBytes() return bytes of the current file, but we want to hash the entire container here.
|
||||
@ -97,7 +97,7 @@ class MockAttachmentStorage : AttachmentStorage, SingletonSerializeAsToken() {
|
||||
|
||||
val sha256 = attachmentId ?: bytes.sha256()
|
||||
if (sha256 !in files.keys) {
|
||||
val baseAttachment = MockAttachment({ bytes }, sha256, signers)
|
||||
val baseAttachment = MockAttachment({ bytes }, sha256, signers, uploader)
|
||||
val version = try { Integer.parseInt(baseAttachment.openAsJAR().manifest?.mainAttributes?.getValue(Attributes.Name.IMPLEMENTATION_VERSION)) } catch (e: Exception) { DEFAULT_CORDAPP_VERSION }
|
||||
val attachment =
|
||||
if (contractClassNames == null || contractClassNames.isEmpty()) baseAttachment
|
||||
|
Loading…
x
Reference in New Issue
Block a user