diff --git a/common/enclave/src/main/kotlin/com/r3/conclave/cordapp/common/EncryptedTxEnclaveCommands.kt b/common/enclave/src/main/kotlin/com/r3/conclave/cordapp/common/EncryptedTxEnclaveCommands.kt index 50862a99f1..7f3a0b502a 100644 --- a/common/enclave/src/main/kotlin/com/r3/conclave/cordapp/common/EncryptedTxEnclaveCommands.kt +++ b/common/enclave/src/main/kotlin/com/r3/conclave/cordapp/common/EncryptedTxEnclaveCommands.kt @@ -4,4 +4,6 @@ package com.r3.conclave.cordapp.common * An [EnclaveCommand] that instructs the enclave to initialise a post office to a remote enclave using the serialized * attestation contained in the mail body (requires an enclave host to be registered). */ -class InitPostOfficeToRemoteEnclave : EnclaveCommand \ No newline at end of file +class InitPostOfficeToRemoteEnclave : EnclaveCommand + +class VerifyUnencryptedTx : EnclaveCommand \ No newline at end of file diff --git a/common/enclave/src/main/kotlin/com/r3/conclave/cordapp/common/dto/ConclaveNetworkParameters.kt b/common/enclave/src/main/kotlin/com/r3/conclave/cordapp/common/dto/ConclaveNetworkParameters.kt new file mode 100644 index 0000000000..c7b650ce8d --- /dev/null +++ b/common/enclave/src/main/kotlin/com/r3/conclave/cordapp/common/dto/ConclaveNetworkParameters.kt @@ -0,0 +1,21 @@ +package com.r3.conclave.cordapp.common.dto + +import net.corda.core.node.NotaryInfo +import net.corda.core.node.services.AttachmentId +import net.corda.core.serialization.CordaSerializable +import java.security.PublicKey +import java.time.Duration +import java.time.Instant + +@CordaSerializable +data class ConclaveNetworkParameters( + val minimumPlatformVersion: Int, + val notaries: Array, + val maxMessageSize: Int, + val maxTransactionSize: Int, + val modifiedTime: Instant, + val epoch: Int, + val whitelistedContractImplementations: Array>>, + val eventHorizon: Duration, + val packageOwnership: Array> +) diff --git a/common/enclave/src/main/kotlin/com/r3/conclave/cordapp/common/dto/ConclaveVerificationResponse.kt b/common/enclave/src/main/kotlin/com/r3/conclave/cordapp/common/dto/ConclaveVerificationResponse.kt new file mode 100644 index 0000000000..f173a8f97c --- /dev/null +++ b/common/enclave/src/main/kotlin/com/r3/conclave/cordapp/common/dto/ConclaveVerificationResponse.kt @@ -0,0 +1,9 @@ +package com.r3.conclave.cordapp.common.dto + +import net.corda.core.serialization.CordaSerializable + +@CordaSerializable +data class ConclaveVerificationResponse( + val verificationStatus: VerificationStatus, + val verificationErrorMessage: String? +) \ No newline at end of file diff --git a/common/enclave/src/main/kotlin/com/r3/conclave/cordapp/common/dto/VerificationStatus.kt b/common/enclave/src/main/kotlin/com/r3/conclave/cordapp/common/dto/VerificationStatus.kt new file mode 100644 index 0000000000..1f81be9ec6 --- /dev/null +++ b/common/enclave/src/main/kotlin/com/r3/conclave/cordapp/common/dto/VerificationStatus.kt @@ -0,0 +1,8 @@ +package com.r3.conclave.cordapp.common.dto + +import net.corda.core.serialization.CordaSerializable + +@CordaSerializable +enum class VerificationStatus { + SUCCESS, VERIFICATION_ERROR +} \ No newline at end of file diff --git a/common/enclave/src/main/kotlin/com/r3/conclave/cordapp/common/dto/WireTxAdditionalInfo.kt b/common/enclave/src/main/kotlin/com/r3/conclave/cordapp/common/dto/WireTxAdditionalInfo.kt new file mode 100644 index 0000000000..f84d7dd4fc --- /dev/null +++ b/common/enclave/src/main/kotlin/com/r3/conclave/cordapp/common/dto/WireTxAdditionalInfo.kt @@ -0,0 +1,18 @@ +package com.r3.conclave.cordapp.common.dto + +import net.corda.core.contracts.Attachment +import net.corda.core.contracts.ContractState +import net.corda.core.contracts.StateAndRef +import net.corda.core.serialization.CordaSerializable +import net.corda.core.transactions.WireTransaction + +@CordaSerializable +data class WireTxAdditionalInfo( + val wireTransaction: WireTransaction, + val inputStates: Array>, + val attachments: Array, + val conclaveNetworkParameters: ConclaveNetworkParameters, + val references: Array>, + val serializedInputs: Array?, + val serializedReferences: Array? +) \ No newline at end of file diff --git a/enclave/src/main/kotlin/com/r3/conclave/cordapp/sample/enclave/EncryptedTxEnclave.kt b/enclave/src/main/kotlin/com/r3/conclave/cordapp/sample/enclave/EncryptedTxEnclave.kt index e7dd34e818..e6fc74fb1f 100644 --- a/enclave/src/main/kotlin/com/r3/conclave/cordapp/sample/enclave/EncryptedTxEnclave.kt +++ b/enclave/src/main/kotlin/com/r3/conclave/cordapp/sample/enclave/EncryptedTxEnclave.kt @@ -1,10 +1,22 @@ package com.r3.conclave.cordapp.sample.enclave +import com.github.benmanes.caffeine.cache.Caffeine import com.r3.conclave.common.EnclaveInstanceInfo import com.r3.conclave.cordapp.common.* +import com.r3.conclave.cordapp.common.dto.ConclaveVerificationResponse +import com.r3.conclave.cordapp.common.dto.VerificationStatus +import com.r3.conclave.cordapp.common.dto.WireTxAdditionalInfo import com.r3.conclave.enclave.EnclavePostOffice import com.r3.conclave.mail.EnclaveMail +import net.corda.core.utilities.ByteSequence +import net.corda.nodeapi.internal.serialization.amqp.AMQPServerSerializationScheme +import net.corda.serialization.internal.AMQP_P2P_CONTEXT +import net.corda.serialization.internal.SerializationFactoryImpl +import net.corda.serialization.internal.amqp.SerializationFactoryCacheKey +import net.corda.serialization.internal.amqp.SerializerFactory +import org.bouncycastle.jce.provider.BouncyCastleProvider import java.lang.IllegalStateException +import java.security.Security import java.util.* /** @@ -15,9 +27,29 @@ class EncryptedTxEnclave : CommandEnclave() { private lateinit var remoteEnclavePostOffice: EnclavePostOffice private val commandToCallableMap = mapOf( - InitPostOfficeToRemoteEnclave::class.java.name to ::initPostOfficeToRemoteEnclave + InitPostOfficeToRemoteEnclave::class.java.name to ::initPostOfficeToRemoteEnclave, + VerifyUnencryptedTx::class.java.name to ::verifyUnencryptedTransaction ) + private val serializerFactoriesForContexts = Caffeine.newBuilder() + .maximumSize(128) + .build() + .asMap() + + private var serializationFactoryImpl: SerializationFactoryImpl + + init { + Security.addProvider(BouncyCastleProvider()) + + val serverScheme = AMQPServerSerializationScheme(emptyList(), serializerFactoriesForContexts) + val clientScheme = AMQPServerSerializationScheme(emptyList(), serializerFactoriesForContexts) + + serializationFactoryImpl = SerializationFactoryImpl().apply { + registerScheme(serverScheme) + registerScheme(clientScheme) + } + } + override fun receiveMail(mail: EnclaveMail, flowId: String, route: EnclaveCommand) { if (commandToCallableMap[route.javaClass.name] != null) { commandToCallableMap[route.javaClass.name]!!.invoke(mail, flowId) @@ -47,4 +79,39 @@ class EncryptedTxEnclave : CommandEnclave() { remoteEnclavePostOffice = postOffice(attestation, flowId) } + + /** + * Convert the serialized [WireTxAdditionalInfo] in the message body to a [LedgerTransaction] and verify. + * This command replies with a serialized [ConclaveVerificationResponse] indicating if the verification was a + * success or threw an error. + * @param mail an incoming [EnclaveMail] containing a serialized [WireTxAdditionalInfo]. + * @param flowId the ID of the flow executing on our host. + */ + private fun verifyUnencryptedTransaction(mail: EnclaveMail, flowId: String) { + val txBody = mail.bodyAsBytes + + val wireTx = serializationFactoryImpl.deserialize( + byteSequence = ByteSequence.of(txBody, 0, txBody.size), + clazz = WireTxAdditionalInfo::class.java, + context = AMQP_P2P_CONTEXT) + + val ledgerTx = LedgerTxHelper.toLedgerTxInternal(wireTx) + + val response = serializationFactoryImpl.asCurrent { + this.withCurrentContext(AMQP_P2P_CONTEXT) { + try { + ledgerTx.verify() + ConclaveVerificationResponse(VerificationStatus.SUCCESS, null) + } catch (e: Exception) { + println("Exception while verifying transaction $e") + ConclaveVerificationResponse(VerificationStatus.VERIFICATION_ERROR, e.message) + } + } + } + + val serializedResponse = serializationFactoryImpl.serialize(response, AMQP_P2P_CONTEXT).bytes + + val responseBytes = postOffice(mail).encryptMail(serializedResponse) + postMail(responseBytes, "$flowId:${VerifyUnencryptedTx().serialize()}") + } } \ No newline at end of file diff --git a/enclave/src/main/kotlin/com/r3/conclave/cordapp/sample/enclave/LedgerTxHelper.kt b/enclave/src/main/kotlin/com/r3/conclave/cordapp/sample/enclave/LedgerTxHelper.kt new file mode 100644 index 0000000000..c587a5a88f --- /dev/null +++ b/enclave/src/main/kotlin/com/r3/conclave/cordapp/sample/enclave/LedgerTxHelper.kt @@ -0,0 +1,67 @@ +package com.r3.conclave.cordapp.sample.enclave + +import com.r3.conclave.cordapp.common.dto.ConclaveNetworkParameters +import com.r3.conclave.cordapp.common.dto.WireTxAdditionalInfo +import net.corda.core.contracts.CommandWithParties +import net.corda.core.internal.SerializedStateAndRef +import net.corda.core.node.NetworkParameters +import net.corda.core.serialization.SerializedBytes +import net.corda.core.transactions.LedgerTransaction + +class LedgerTxHelper { + + companion object { + + @JvmStatic + fun toLedgerTxInternal(conclaveLedgerTxModel: WireTxAdditionalInfo): LedgerTransaction { + + val serializedResolvedInputs = conclaveLedgerTxModel.serializedInputs?.mapIndexed { index, ref -> + SerializedStateAndRef(SerializedBytes(ref), conclaveLedgerTxModel + .inputStates[index].ref) + } + + val serializedResolvedReferences = conclaveLedgerTxModel.serializedReferences?.mapIndexed { index, ref -> + SerializedStateAndRef(SerializedBytes(ref), conclaveLedgerTxModel.references[index].ref) + } + + return LedgerTransaction.createForConclaveVerify( + conclaveLedgerTxModel.inputStates.toList(), + conclaveLedgerTxModel.wireTransaction.outputs, + conclaveLedgerTxModel.wireTransaction.commands.map { + CommandWithParties(it.signers, emptyList(), it.value) + }, + conclaveLedgerTxModel.attachments.toList(), + conclaveLedgerTxModel.wireTransaction.id, + conclaveLedgerTxModel.wireTransaction.notary, + conclaveLedgerTxModel.wireTransaction.timeWindow, + conclaveLedgerTxModel.wireTransaction.privacySalt, + toNetworkParameters(conclaveLedgerTxModel.conclaveNetworkParameters), + conclaveLedgerTxModel.references.toList(), + conclaveLedgerTxModel.wireTransaction.componentGroups, + serializedResolvedInputs, + serializedResolvedReferences, + conclaveLedgerTxModel.wireTransaction.digestService + ) + } + + @JvmStatic + fun toNetworkParameters(conclaveNetworkParameters: ConclaveNetworkParameters): NetworkParameters { + + return NetworkParameters( + conclaveNetworkParameters.minimumPlatformVersion, + conclaveNetworkParameters.notaries.toList(), + conclaveNetworkParameters.maxMessageSize, + conclaveNetworkParameters.maxTransactionSize, + conclaveNetworkParameters.modifiedTime, + conclaveNetworkParameters.epoch, + conclaveNetworkParameters.whitelistedContractImplementations.map { + it.first to it.second.asList() + }.toMap(), + conclaveNetworkParameters.eventHorizon, + conclaveNetworkParameters.packageOwnership.toMap() + ) + } + } + + +} \ No newline at end of file