mirror of
https://github.com/corda/corda.git
synced 2025-02-02 09:18:13 +00:00
merge with origin
This commit is contained in:
commit
684033f99a
6
.ci/dev/regression/Jenkinsfile
vendored
6
.ci/dev/regression/Jenkinsfile
vendored
@ -122,8 +122,7 @@ pipeline {
|
|||||||
|
|
||||||
stage('All Tests') {
|
stage('All Tests') {
|
||||||
when {
|
when {
|
||||||
expression { params.DO_TEST }
|
expression { return false }
|
||||||
beforeAgent true
|
|
||||||
}
|
}
|
||||||
parallel {
|
parallel {
|
||||||
stage('Another agent') {
|
stage('Another agent') {
|
||||||
@ -194,6 +193,9 @@ pipeline {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
stage('Slow Integration Test') {
|
stage('Slow Integration Test') {
|
||||||
|
when {
|
||||||
|
expression { return false }
|
||||||
|
}
|
||||||
steps {
|
steps {
|
||||||
sh script: [
|
sh script: [
|
||||||
'./gradlew',
|
'./gradlew',
|
||||||
|
32
build.gradle
32
build.gradle
@ -1,5 +1,7 @@
|
|||||||
import com.r3.testing.DistributeTestsBy
|
import com.r3.testing.DistributeTestsBy
|
||||||
import com.r3.testing.PodLogLevel
|
import com.r3.testing.PodLogLevel
|
||||||
|
import java.nio.file.Files
|
||||||
|
import java.nio.file.Paths
|
||||||
|
|
||||||
import static org.gradle.api.JavaVersion.VERSION_11
|
import static org.gradle.api.JavaVersion.VERSION_11
|
||||||
import static org.gradle.api.JavaVersion.VERSION_1_8
|
import static org.gradle.api.JavaVersion.VERSION_1_8
|
||||||
@ -69,7 +71,7 @@ buildscript {
|
|||||||
ext.servlet_version = '4.0.1'
|
ext.servlet_version = '4.0.1'
|
||||||
ext.assertj_version = '3.12.2'
|
ext.assertj_version = '3.12.2'
|
||||||
ext.slf4j_version = '1.7.30'
|
ext.slf4j_version = '1.7.30'
|
||||||
ext.log4j_version = '2.17.1'
|
ext.log4j_version = '2.16.0'
|
||||||
ext.bouncycastle_version = constants.getProperty("bouncycastleVersion")
|
ext.bouncycastle_version = constants.getProperty("bouncycastleVersion")
|
||||||
ext.guava_version = constants.getProperty("guavaVersion")
|
ext.guava_version = constants.getProperty("guavaVersion")
|
||||||
ext.caffeine_version = constants.getProperty("caffeineVersion")
|
ext.caffeine_version = constants.getProperty("caffeineVersion")
|
||||||
@ -189,6 +191,20 @@ buildscript {
|
|||||||
mavenCentral()
|
mavenCentral()
|
||||||
jcenter()
|
jcenter()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
maven {
|
||||||
|
// TODO: fix conclaveRepo property
|
||||||
|
// (none of the conclave properties seem to be sourced from gradle.properties, they appear to be coming from somewhere else)
|
||||||
|
def path = Paths.get(rootDir.absolutePath).resolve("./repo").toAbsolutePath().normalize()
|
||||||
|
if (!Files.isDirectory(path.resolve("com"))) {
|
||||||
|
if (Files.isDirectory(Paths.get("/repo/com"))) {
|
||||||
|
path = Paths.get("/repo")
|
||||||
|
} else {
|
||||||
|
throw new Exception("Neither $path nor /repo seem to exist, or they aren't Maven repositories; it should be the SDK 'repo' subdirectory.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
url = path.toFile()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||||
@ -424,6 +440,20 @@ allprojects {
|
|||||||
}
|
}
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
jcenter()
|
jcenter()
|
||||||
|
|
||||||
|
maven {
|
||||||
|
// TODO: fix conclaveRepo property
|
||||||
|
// (none of the conclave properties seem to be sourced from gradle.properties, they appear to be coming from somewhere else)
|
||||||
|
def path = Paths.get(rootDir.absolutePath).resolve("./repo").toAbsolutePath().normalize()
|
||||||
|
if (!Files.isDirectory(path.resolve("com"))) {
|
||||||
|
if (Files.isDirectory(Paths.get("/repo/com"))) {
|
||||||
|
path = Paths.get("/repo")
|
||||||
|
} else {
|
||||||
|
throw new Exception("Neither $path nor /repo seem to exist, or they aren't Maven repositories; it should be the SDK 'repo' subdirectory.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
url = path.toFile()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
11
common/enclave/build.gradle
Normal file
11
common/enclave/build.gradle
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
apply plugin: 'kotlin'
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
// TODO: fix conclaveVersion property
|
||||||
|
// (none of the conclave properties seem to be sourced from gradle.properties, they appear to be coming from somewhere else)
|
||||||
|
implementation "com.r3.conclave:conclave-common:1.1"
|
||||||
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
|
||||||
|
implementation "net.i2p.crypto:eddsa:0.3.0"
|
||||||
|
|
||||||
|
implementation project(':core')
|
||||||
|
}
|
@ -0,0 +1,29 @@
|
|||||||
|
package com.r3.conclave.cordapp.common
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generic type for an enclave command.
|
||||||
|
* This type contains no properties or methods and simply instructs the enclave to perform a specific action.
|
||||||
|
*/
|
||||||
|
interface EnclaveCommand {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert this type to a [String].
|
||||||
|
* @return serialized [String] of this type.
|
||||||
|
*/
|
||||||
|
fun serialize(): String {
|
||||||
|
return this.javaClass.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deserialize an [EnclaveCommand].
|
||||||
|
* Convert a serialized string into an [EnclaveCommand] instance.
|
||||||
|
*/
|
||||||
|
fun String.toEnclaveCommand(): EnclaveCommand {
|
||||||
|
return Class.forName(this).newInstance() as EnclaveCommand
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An [EnclaveCommand] that instructs the enclave to register a host identity.
|
||||||
|
*/
|
||||||
|
class RegisterHostIdentity : EnclaveCommand
|
@ -0,0 +1,44 @@
|
|||||||
|
package com.r3.conclave.cordapp.common
|
||||||
|
|
||||||
|
import java.security.MessageDigest
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse [routingHint] of the format flowId:route and return a pair of flowId [String] and [EnclaveCommand] class.
|
||||||
|
* @param routingHint the routing hint from receiveMail to parse.
|
||||||
|
* @return a pair of flowId [String] and [EnclaveCommand].
|
||||||
|
* @throws [IllegalArgumentException] if the hint cannot be parsed, or if the enclave command is not known.
|
||||||
|
*/
|
||||||
|
fun getRoutingInfo(routingHint: String): Pair<String, EnclaveCommand> {
|
||||||
|
val routingElements = routingHint.split(":")
|
||||||
|
if (routingElements.size != 2) {
|
||||||
|
throw IllegalArgumentException("Expected routingHint in the form flowId:route")
|
||||||
|
}
|
||||||
|
|
||||||
|
val (flowId, routeName) = routingElements
|
||||||
|
try {
|
||||||
|
UUID.fromString(flowId)
|
||||||
|
} catch (e: IllegalArgumentException) {
|
||||||
|
throw IllegalArgumentException("Invalid flowId: $flowId")
|
||||||
|
}
|
||||||
|
|
||||||
|
val route = try {
|
||||||
|
routeName.trim().toEnclaveCommand()
|
||||||
|
} catch (e: IllegalArgumentException) {
|
||||||
|
throw java.lang.IllegalArgumentException("Unknown command: $routeName")
|
||||||
|
}
|
||||||
|
|
||||||
|
return Pair(flowId, route)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the hash of the input [String] using the given hashing algorithm.
|
||||||
|
* @param input the [String] to generate a hash of.
|
||||||
|
* @param algorithm the hashing algorithm to use.
|
||||||
|
* @return the generated hash [String].
|
||||||
|
*/
|
||||||
|
fun hashString(input: String, algorithm: String = "SHA-256"): String {
|
||||||
|
return MessageDigest.getInstance(algorithm)
|
||||||
|
.digest(input.toByteArray())
|
||||||
|
.fold("") { str, it -> str + "%02x".format(it) }
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
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
|
||||||
|
|
||||||
|
class VerifyUnencryptedTx : EnclaveCommand
|
@ -0,0 +1,23 @@
|
|||||||
|
package com.r3.conclave.cordapp.common
|
||||||
|
|
||||||
|
import java.security.PublicKey
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines the interface for querying the sender's identity properties. The SenderIdentity is accessible to the
|
||||||
|
* the Enclave and it can be used to uniquely identify the sender if the sender decided to share
|
||||||
|
* its verifiable identity. Any identity shared by the sender goes through a verification process to ensure the identity
|
||||||
|
* is part of the same certificate chain as the root certificate hardcoded into the enclave.
|
||||||
|
*/
|
||||||
|
interface SenderIdentity {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The verified X.500 subject name of the sender
|
||||||
|
*/
|
||||||
|
val name: String
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The verified public key of the sender's identity. Specifically, this is the public key from the sender's X.509 certificate.
|
||||||
|
* Note, this public key is different to the encryption key used in Mail ([EnclaveMail.authenticatedSender])
|
||||||
|
*/
|
||||||
|
val publicKey: PublicKey
|
||||||
|
}
|
@ -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<NotaryInfo>,
|
||||||
|
val maxMessageSize: Int,
|
||||||
|
val maxTransactionSize: Int,
|
||||||
|
val modifiedTime: Instant,
|
||||||
|
val epoch: Int,
|
||||||
|
val whitelistedContractImplementations: Array<Pair<String, Array<AttachmentId>>>,
|
||||||
|
val eventHorizon: Duration,
|
||||||
|
val packageOwnership: Array<Pair<String, PublicKey>>
|
||||||
|
)
|
@ -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?
|
||||||
|
)
|
@ -0,0 +1,8 @@
|
|||||||
|
package com.r3.conclave.cordapp.common.dto
|
||||||
|
|
||||||
|
import net.corda.core.serialization.CordaSerializable
|
||||||
|
|
||||||
|
@CordaSerializable
|
||||||
|
enum class VerificationStatus {
|
||||||
|
SUCCESS, VERIFICATION_ERROR
|
||||||
|
}
|
@ -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<StateAndRef<ContractState>>,
|
||||||
|
val attachments: Array<Attachment>,
|
||||||
|
val conclaveNetworkParameters: ConclaveNetworkParameters,
|
||||||
|
val references: Array<StateAndRef<ContractState>>,
|
||||||
|
val serializedInputs: Array<ByteArray>?,
|
||||||
|
val serializedReferences: Array<ByteArray>?
|
||||||
|
)
|
@ -0,0 +1,137 @@
|
|||||||
|
package com.r3.conclave.cordapp.common.internal
|
||||||
|
|
||||||
|
import com.r3.conclave.common.internal.SignatureSchemeEdDSA
|
||||||
|
import com.r3.conclave.cordapp.common.SenderIdentity
|
||||||
|
import net.i2p.crypto.eddsa.EdDSASecurityProvider
|
||||||
|
import java.io.ByteArrayInputStream
|
||||||
|
import java.io.ByteArrayOutputStream
|
||||||
|
import java.io.DataInputStream
|
||||||
|
import java.io.DataOutputStream
|
||||||
|
import java.security.PublicKey
|
||||||
|
import java.security.Security
|
||||||
|
import java.security.SignatureException
|
||||||
|
import java.security.cert.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements the [SenderIdentity] interface and allows a sender to prepare and send its identity to en Enclave, and
|
||||||
|
* allows the Enclave to privately and securely verify the sender X509 name and public key belonging to a network where
|
||||||
|
* parties are identified by a X509 certificate issued by the CA trusted by the Enclave.
|
||||||
|
*/
|
||||||
|
class SenderIdentityImpl(
|
||||||
|
private val signerCertPath: CertPath,
|
||||||
|
private val signatureData: ByteArray
|
||||||
|
) : SenderIdentity {
|
||||||
|
private val signerCertificate: X509Certificate get() = (signerCertPath.certificates[0] as X509Certificate)
|
||||||
|
|
||||||
|
init {
|
||||||
|
require(signerCertPath.type == "X.509") { "Only X.509 certificates supported" }
|
||||||
|
val certs = signerCertPath.certificates
|
||||||
|
require(certs.size >= 2) { "Certificate path must at least include subject and issuing certificates" }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifies the sender identified by the [signerCertificate] is the party that signed the [sharedSecret] producing
|
||||||
|
* the [signatureData]
|
||||||
|
*/
|
||||||
|
fun didSign(sharedSecret: ByteArray): Boolean {
|
||||||
|
return try {
|
||||||
|
signatureScheme.verify(signerCertificate.publicKey, signatureData, sharedSecret)
|
||||||
|
true
|
||||||
|
} catch (e: SignatureException) {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifies the [signerCertPath] is valid and issued by the given trusted root
|
||||||
|
*
|
||||||
|
* @param caRoot the root certificate that is used to validate the instance's certificate path [signerCertPath]
|
||||||
|
*/
|
||||||
|
fun isTrusted(caRoot: X509Certificate): Boolean {
|
||||||
|
val trustAnchor = TrustAnchor(caRoot, null)
|
||||||
|
val parameters = PKIXParameters(setOf(trustAnchor)).apply { isRevocationEnabled = false }
|
||||||
|
return try {
|
||||||
|
CertPathValidator.getInstance("PKIX").validate(signerCertPath, parameters)
|
||||||
|
true
|
||||||
|
} catch (e: CertPathValidatorException) {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The verified subject name of the sender
|
||||||
|
*/
|
||||||
|
override val name: String get() = signerCertificate.subjectX500Principal.name
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The verified public key of the sender
|
||||||
|
*/
|
||||||
|
override val publicKey: PublicKey get() = signerCertificate.publicKey
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serializes the [SenderIdentity] instance into a byte array.
|
||||||
|
*
|
||||||
|
* Used by the mail sender to serialize the [SenderIdentity] instance and privately send it to the Enclave message
|
||||||
|
* handler where is then deserialized.
|
||||||
|
*/
|
||||||
|
fun serialize(): ByteArray {
|
||||||
|
val baos = ByteArrayOutputStream()
|
||||||
|
val dos = DataOutputStream(baos)
|
||||||
|
|
||||||
|
serialize(dos)
|
||||||
|
|
||||||
|
return baos.toByteArray()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun serialize(dos: DataOutputStream) {
|
||||||
|
dos.writeIntLengthPrefixBytes(signerCertPath.getEncoded("PkiPath"))
|
||||||
|
dos.writeIntLengthPrefixBytes(signatureData)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val signatureScheme = SignatureSchemeEdDSA()
|
||||||
|
|
||||||
|
init {
|
||||||
|
Security.addProvider(EdDSASecurityProvider())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun deserializeCertPath(dis: DataInputStream): CertPath {
|
||||||
|
val certBytes = dis.readIntLengthPrefixBytes()
|
||||||
|
val certFactory = CertificateFactory.getInstance("X.509")
|
||||||
|
return certFactory.generateCertPath(ByteArrayInputStream(certBytes), "PkiPath")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun deserializeSignedSecret(dis: DataInputStream): ByteArray = dis.readIntLengthPrefixBytes()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deserialzes a serialized [SenderIdentityImpl] instance
|
||||||
|
*/
|
||||||
|
fun deserialize(from: ByteArray): SenderIdentityImpl {
|
||||||
|
val bais = ByteArrayInputStream(from)
|
||||||
|
val dis = DataInputStream(bais)
|
||||||
|
return deserialize(dis)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun deserialize(dis: DataInputStream): SenderIdentityImpl {
|
||||||
|
try {
|
||||||
|
val signerCertPath = deserializeCertPath(dis)
|
||||||
|
val signedSecret = deserializeSignedSecret(dis)
|
||||||
|
|
||||||
|
return SenderIdentityImpl(signerCertPath, signedSecret)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
throw IllegalArgumentException("Corrupted MailerIdentity bytes", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun DataOutputStream.writeIntLengthPrefixBytes(data: ByteArray) {
|
||||||
|
this.writeInt(data.size)
|
||||||
|
this.write(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun DataInputStream.readIntLengthPrefixBytes(): ByteArray {
|
||||||
|
val data = ByteArray(this.readInt())
|
||||||
|
this.readFully(data)
|
||||||
|
return data
|
||||||
|
}
|
@ -2,8 +2,8 @@
|
|||||||
# because some versions here need to be matched by app authors in
|
# because some versions here need to be matched by app authors in
|
||||||
# their own projects. So don't get fancy with syntax!
|
# their own projects. So don't get fancy with syntax!
|
||||||
|
|
||||||
cordaVersion=4.9
|
cordaVersion=4.8.5
|
||||||
versionSuffix=SNAPSHOT
|
versionSuffix=
|
||||||
gradlePluginsVersion=5.0.12
|
gradlePluginsVersion=5.0.12
|
||||||
kotlinVersion=1.2.71
|
kotlinVersion=1.2.71
|
||||||
java8MinUpdateVersion=171
|
java8MinUpdateVersion=171
|
||||||
|
@ -3,6 +3,7 @@ package net.corda.core.contracts
|
|||||||
import net.corda.core.KeepForDJVM
|
import net.corda.core.KeepForDJVM
|
||||||
import net.corda.core.crypto.CompositeKey
|
import net.corda.core.crypto.CompositeKey
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
|
import net.corda.core.serialization.ConstructorForDeserialization
|
||||||
import net.corda.core.serialization.CordaSerializable
|
import net.corda.core.serialization.CordaSerializable
|
||||||
import net.corda.core.utilities.exactAdd
|
import net.corda.core.utilities.exactAdd
|
||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
@ -39,7 +40,9 @@ interface TokenizableAssetInfo {
|
|||||||
*/
|
*/
|
||||||
@KeepForDJVM
|
@KeepForDJVM
|
||||||
@CordaSerializable
|
@CordaSerializable
|
||||||
data class Amount<T : Any>(val quantity: Long, val displayTokenSize: BigDecimal, val token: T) : Comparable<Amount<T>> {
|
data class Amount<T : Any>
|
||||||
|
@ConstructorForDeserialization
|
||||||
|
constructor(val quantity: Long, val displayTokenSize: BigDecimal, val token: T) : Comparable<Amount<T>> {
|
||||||
// TODO Proper lookup of currencies in a locale and context sensitive fashion is not supported and is left to the application.
|
// TODO Proper lookup of currencies in a locale and context sensitive fashion is not supported and is left to the application.
|
||||||
companion object {
|
companion object {
|
||||||
/**
|
/**
|
||||||
|
@ -4,6 +4,7 @@ package net.corda.core.contracts
|
|||||||
import net.corda.core.KeepForDJVM
|
import net.corda.core.KeepForDJVM
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.internal.requiredContractClassName
|
import net.corda.core.internal.requiredContractClassName
|
||||||
|
import net.corda.core.serialization.ConstructorForDeserialization
|
||||||
import net.corda.core.serialization.CordaSerializable
|
import net.corda.core.serialization.CordaSerializable
|
||||||
import net.corda.core.utilities.loggerFor
|
import net.corda.core.utilities.loggerFor
|
||||||
|
|
||||||
@ -15,7 +16,10 @@ typealias ContractClassName = String
|
|||||||
* This is the definitive state that is stored on the ledger and used in transaction outputs.
|
* This is the definitive state that is stored on the ledger and used in transaction outputs.
|
||||||
*/
|
*/
|
||||||
@CordaSerializable
|
@CordaSerializable
|
||||||
data class TransactionState<out T : ContractState> @JvmOverloads constructor(
|
data class TransactionState<out T : ContractState>
|
||||||
|
|
||||||
|
@ConstructorForDeserialization
|
||||||
|
constructor(
|
||||||
/** The custom contract state */
|
/** The custom contract state */
|
||||||
val data: T,
|
val data: T,
|
||||||
/**
|
/**
|
||||||
|
@ -3,6 +3,7 @@ package net.corda.core.contracts
|
|||||||
import net.corda.core.DeleteForDJVM
|
import net.corda.core.DeleteForDJVM
|
||||||
import net.corda.core.KeepForDJVM
|
import net.corda.core.KeepForDJVM
|
||||||
import net.corda.core.internal.VisibleForTesting
|
import net.corda.core.internal.VisibleForTesting
|
||||||
|
import net.corda.core.serialization.ConstructorForDeserialization
|
||||||
import net.corda.core.serialization.CordaSerializable
|
import net.corda.core.serialization.CordaSerializable
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
@ -20,7 +21,20 @@ import java.util.*
|
|||||||
*/
|
*/
|
||||||
@CordaSerializable
|
@CordaSerializable
|
||||||
@KeepForDJVM
|
@KeepForDJVM
|
||||||
data class UniqueIdentifier @JvmOverloads @DeleteForDJVM constructor(val externalId: String? = null, val id: UUID = UUID.randomUUID()) : Comparable<UniqueIdentifier> {
|
data class UniqueIdentifier
|
||||||
|
|
||||||
|
@ConstructorForDeserialization
|
||||||
|
constructor(val externalId: String?, val id: UUID) : Comparable<UniqueIdentifier> {
|
||||||
|
|
||||||
|
@DeleteForDJVM
|
||||||
|
constructor(externalId: String? = null) : this(externalId, UUID.randomUUID())
|
||||||
|
|
||||||
|
@DeleteForDJVM
|
||||||
|
constructor(id: UUID = UUID.randomUUID()) : this(null, id)
|
||||||
|
|
||||||
|
@DeleteForDJVM
|
||||||
|
constructor() : this(null, UUID.randomUUID())
|
||||||
|
|
||||||
override fun toString(): String = if (externalId != null) "${externalId}_$id" else id.toString()
|
override fun toString(): String = if (externalId != null) "${externalId}_$id" else id.toString()
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -4,6 +4,7 @@ import net.corda.core.contracts.ContractState
|
|||||||
import net.corda.core.internal.PlatformVersionSwitches
|
import net.corda.core.internal.PlatformVersionSwitches
|
||||||
import net.corda.core.internal.cordapp.targetPlatformVersion
|
import net.corda.core.internal.cordapp.targetPlatformVersion
|
||||||
import net.corda.core.internal.warnOnce
|
import net.corda.core.internal.warnOnce
|
||||||
|
import net.corda.core.utilities.SgxSupport
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
@ -26,6 +27,9 @@ object StateContractValidationEnforcementRule {
|
|||||||
private val targetVersionCache = ConcurrentHashMap<URL, Int>()
|
private val targetVersionCache = ConcurrentHashMap<URL, Int>()
|
||||||
|
|
||||||
fun shouldEnforce(state: ContractState): Boolean {
|
fun shouldEnforce(state: ContractState): Boolean {
|
||||||
|
if(SgxSupport.isInsideEnclave) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
val jarLocation = state::class.java.protectionDomain.codeSource.location
|
val jarLocation = state::class.java.protectionDomain.codeSource.location
|
||||||
|
|
||||||
if (jarLocation == null) {
|
if (jarLocation == null) {
|
||||||
|
@ -30,6 +30,7 @@ import net.corda.core.serialization.SerializationWhitelist
|
|||||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||||
import net.corda.core.serialization.internal.AttachmentURLStreamHandlerFactory.toUrl
|
import net.corda.core.serialization.internal.AttachmentURLStreamHandlerFactory.toUrl
|
||||||
import net.corda.core.serialization.withWhitelist
|
import net.corda.core.serialization.withWhitelist
|
||||||
|
import net.corda.core.utilities.SgxSupport
|
||||||
import net.corda.core.utilities.contextLogger
|
import net.corda.core.utilities.contextLogger
|
||||||
import net.corda.core.utilities.debug
|
import net.corda.core.utilities.debug
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
@ -353,7 +354,11 @@ object AttachmentsClassLoaderBuilder {
|
|||||||
val cache = attachmentsClassLoaderCache ?: fallBackCache
|
val cache = attachmentsClassLoaderCache ?: fallBackCache
|
||||||
val serializationContext = cache.computeIfAbsent(AttachmentsClassLoaderKey(attachmentIds, params), Function { key ->
|
val serializationContext = cache.computeIfAbsent(AttachmentsClassLoaderKey(attachmentIds, params), Function { key ->
|
||||||
// Create classloader and load serializers, whitelisted classes
|
// Create classloader and load serializers, whitelisted classes
|
||||||
val transactionClassLoader = AttachmentsClassLoader(attachments, key.params, txId, isAttachmentTrusted, parent)
|
val transactionClassLoader = if(SgxSupport.isInsideEnclave) {
|
||||||
|
SerializationFactory.defaultFactory.defaultContext.deserializationClassLoader
|
||||||
|
} else {
|
||||||
|
AttachmentsClassLoader(attachments, key.params, txId, isAttachmentTrusted, parent)
|
||||||
|
}
|
||||||
val serializers = try {
|
val serializers = try {
|
||||||
createInstancesOfClassesImplementing(transactionClassLoader, SerializationCustomSerializer::class.java,
|
createInstancesOfClassesImplementing(transactionClassLoader, SerializationCustomSerializer::class.java,
|
||||||
JDK1_2_CLASS_FILE_FORMAT_MAJOR_VERSION..JDK8_CLASS_FILE_FORMAT_MAJOR_VERSION)
|
JDK1_2_CLASS_FILE_FORMAT_MAJOR_VERSION..JDK8_CLASS_FILE_FORMAT_MAJOR_VERSION)
|
||||||
|
@ -222,6 +222,52 @@ private constructor(
|
|||||||
// All states must also deserialize using the correct SerializationContext.
|
// All states must also deserialize using the correct SerializationContext.
|
||||||
).also(LedgerTransaction::checkBaseInvariants)
|
).also(LedgerTransaction::checkBaseInvariants)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This factory function will create an instance of [LedgerTransaction]
|
||||||
|
* that will be used for contract verification. See [BasicVerifier] and
|
||||||
|
* [DeterministicVerifier][net.corda.node.internal.djvm.DeterministicVerifier].
|
||||||
|
*/
|
||||||
|
@CordaInternal
|
||||||
|
fun createForConclaveVerify(
|
||||||
|
inputs: List<StateAndRef<ContractState>>,
|
||||||
|
outputs: List<TransactionState<ContractState>>,
|
||||||
|
commands: List<CommandWithParties<CommandData>>,
|
||||||
|
attachments: List<Attachment>,
|
||||||
|
id: SecureHash,
|
||||||
|
notary: Party?,
|
||||||
|
timeWindow: TimeWindow?,
|
||||||
|
privacySalt: PrivacySalt,
|
||||||
|
networkParameters: NetworkParameters?,
|
||||||
|
references: List<StateAndRef<ContractState>>,
|
||||||
|
componentGroups: List<ComponentGroup>?,
|
||||||
|
serializedInputs: List<SerializedStateAndRef>?,
|
||||||
|
serializedReferences: List<SerializedStateAndRef>?,
|
||||||
|
digestService: DigestService): LedgerTransaction {
|
||||||
|
|
||||||
|
return LedgerTransaction(
|
||||||
|
inputs = protect(inputs),
|
||||||
|
outputs = protect(outputs),
|
||||||
|
commands = protect(commands),
|
||||||
|
attachments = protect(attachments),
|
||||||
|
id = id,
|
||||||
|
notary = notary,
|
||||||
|
timeWindow = timeWindow,
|
||||||
|
privacySalt = privacySalt,
|
||||||
|
networkParameters = networkParameters,
|
||||||
|
references = protect(references),
|
||||||
|
componentGroups = componentGroups,
|
||||||
|
serializedInputs = serializedInputs,
|
||||||
|
serializedReferences = serializedReferences,
|
||||||
|
isAttachmentTrusted = { true },
|
||||||
|
verifierFactory = ::BasicVerifier,
|
||||||
|
attachmentsClassLoaderCache = null,
|
||||||
|
digestService = digestService
|
||||||
|
// This check accesses input states and must run on the LedgerTransaction
|
||||||
|
// instance that is verified, not on the outer LedgerTransaction shell.
|
||||||
|
// All states must also deserialize using the correct SerializationContext.
|
||||||
|
).also(LedgerTransaction::checkBaseInvariants)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val inputStates: List<ContractState> get() = inputs.map { it.state.data }
|
val inputStates: List<ContractState> get() = inputs.map { it.state.data }
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
NODE_LIST=("dockerNode1" "dockerNode2" "dockerNode3")
|
NODE_LIST=("dockerNode1" "dockerNode2" "dockerNode3")
|
||||||
NETWORK_NAME=mininet
|
NETWORK_NAME=mininet
|
||||||
CORDAPP_VERSION="4.9-SNAPSHOT"
|
CORDAPP_VERSION="4.8.3"
|
||||||
DOCKER_IMAGE_VERSION="corda-zulu-4.9-snapshot"
|
DOCKER_IMAGE_VERSION="corda-zulu-4.8.3"
|
||||||
|
|
||||||
mkdir cordapps
|
mkdir cordapps
|
||||||
rm -f cordapps/*
|
rm -f cordapps/*
|
||||||
|
89
enclave/build.gradle
Normal file
89
enclave/build.gradle
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
plugins {
|
||||||
|
id 'kotlin'
|
||||||
|
id 'com.r3.conclave.enclave'
|
||||||
|
id 'org.jetbrains.kotlin.jvm'
|
||||||
|
id 'org.jetbrains.kotlin.plugin.allopen'
|
||||||
|
}
|
||||||
|
|
||||||
|
apply plugin: 'kotlin'
|
||||||
|
apply plugin: 'java'
|
||||||
|
apply plugin: 'kotlin-allopen'
|
||||||
|
apply plugin: 'kotlin-noarg'
|
||||||
|
|
||||||
|
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = "1.8"
|
||||||
|
javaParameters = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default Java version for enclaves is 11 so we need to set it to 8 for the CorDapp to work.
|
||||||
|
*/
|
||||||
|
tasks.withType(JavaCompile) {
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||||
|
targetCompatibility = JavaVersion.VERSION_1_8
|
||||||
|
options.encoding = 'UTF-8'
|
||||||
|
options.compilerArgs << '-parameters'
|
||||||
|
}
|
||||||
|
|
||||||
|
noArg {
|
||||||
|
annotations("net.corda.core.serialization.CordaSerializable", "net.corda.core.contracts.BelongsToContract")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* There is no Kotlin code in this project,
|
||||||
|
* but if there were, this section would be required.
|
||||||
|
* Note: Java versions above and here have to match.
|
||||||
|
*/
|
||||||
|
/*
|
||||||
|
tasks.withType(AbstractCompile) {
|
||||||
|
if (it.class.name.startsWith('org.jetbrains.kotlin.gradle.tasks.KotlinCompile')) {
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = JavaVersion.VERSION_1_8
|
||||||
|
apiVersion = '1.5'
|
||||||
|
languageVersion = '1.5'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation project(':common-enclave')
|
||||||
|
implementation project(':node-api')
|
||||||
|
|
||||||
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
|
||||||
|
implementation 'junit:junit:4.13.1'
|
||||||
|
|
||||||
|
implementation "com.r3.conclave:conclave-enclave"
|
||||||
|
|
||||||
|
implementation ("org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.72") {
|
||||||
|
exclude group: 'org.jetbrains.kotlinx', module: 'kotlinx-serialization-json'
|
||||||
|
}
|
||||||
|
|
||||||
|
implementation "org.jetbrains.kotlin:kotlin-reflect:1.3.72"
|
||||||
|
implementation "org.bouncycastle:bcprov-jdk15on:1.68"
|
||||||
|
implementation "com.github.ben-manes.caffeine:caffeine:2.7.0"
|
||||||
|
|
||||||
|
testImplementation(platform('org.junit:junit-bom:5.7.0'))
|
||||||
|
}
|
||||||
|
|
||||||
|
conclave {
|
||||||
|
productID = 1
|
||||||
|
revocationLevel = 0
|
||||||
|
|
||||||
|
simulation {
|
||||||
|
signingType = privateKey
|
||||||
|
signingKey = file("sample_private_key.pem")
|
||||||
|
}
|
||||||
|
|
||||||
|
debug {
|
||||||
|
signingType = privateKey
|
||||||
|
signingKey = file("sample_private_key.pem")
|
||||||
|
}
|
||||||
|
|
||||||
|
release {
|
||||||
|
signingType = privateKey
|
||||||
|
signingKey = file("sample_private_key.pem")
|
||||||
|
}
|
||||||
|
}
|
39
enclave/sample_private_key.pem
Normal file
39
enclave/sample_private_key.pem
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
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
|
||||||
|
-----END RSA PRIVATE KEY-----
|
@ -0,0 +1,114 @@
|
|||||||
|
package com.r3.conclave.cordapp.sample.enclave
|
||||||
|
|
||||||
|
import com.r3.conclave.cordapp.common.EnclaveCommand
|
||||||
|
import com.r3.conclave.cordapp.common.RegisterHostIdentity
|
||||||
|
import com.r3.conclave.cordapp.common.getRoutingInfo
|
||||||
|
import com.r3.conclave.cordapp.common.internal.SenderIdentityImpl
|
||||||
|
import com.r3.conclave.enclave.Enclave
|
||||||
|
import com.r3.conclave.mail.EnclaveMail
|
||||||
|
import com.r3.conclave.enclave.EnclavePostOffice
|
||||||
|
import java.lang.IllegalStateException
|
||||||
|
import java.security.PublicKey
|
||||||
|
import java.security.cert.CertificateFactory
|
||||||
|
import java.security.cert.X509Certificate
|
||||||
|
import java.util.logging.Logger
|
||||||
|
|
||||||
|
abstract class CommandEnclave : Enclave() {
|
||||||
|
|
||||||
|
// the public key of the authenticated and registered host
|
||||||
|
protected lateinit var authenticatedHostKey: PublicKey
|
||||||
|
|
||||||
|
// Post office to communicate with our host
|
||||||
|
// To be initialised when registerHostIdentity is called
|
||||||
|
protected lateinit var hostPostOffice: EnclavePostOffice
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val trustedRootCertificateResourcePath = "/trustedroot.cer"
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
val trustedRootCertificate: X509Certificate = run {
|
||||||
|
try {
|
||||||
|
EncryptedTxEnclave::class.java.getResourceAsStream(trustedRootCertificateResourcePath).use {
|
||||||
|
val certificateFactory = CertificateFactory.getInstance("X509")
|
||||||
|
certificateFactory.generateCertificate(it) as X509Certificate
|
||||||
|
}
|
||||||
|
} catch (exception: Exception) {
|
||||||
|
Logger.getGlobal().severe(
|
||||||
|
"Failed to load trusted root certificate. Please ensure the resource exists and it is not " +
|
||||||
|
"corrupted. Resource path: $trustedRootCertificateResourcePath"
|
||||||
|
)
|
||||||
|
throw exception
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Receive incoming mail and route according to the [EnclaveCommand] class contained in the [routingHint].
|
||||||
|
* @param mail the received [EnclaveMail].
|
||||||
|
* @param routingHint a [String] containing the executing flowId and enclave command to execute.
|
||||||
|
* @throws [IllegalArgumentException] if [routingHint] is null, or cannot be parsed.
|
||||||
|
* @throws [IllegalStateException] if attempting to execute a command when the host has not been registered.
|
||||||
|
*/
|
||||||
|
override fun receiveMail(id: Long, mail: EnclaveMail, routingHint: String?) {
|
||||||
|
routingHint ?: throw IllegalArgumentException(
|
||||||
|
"routingHint must be set for this enclave: ${this.javaClass.simpleName}")
|
||||||
|
|
||||||
|
val (flowId, route) = getRoutingInfo(routingHint)
|
||||||
|
|
||||||
|
val registeredHost = if (isHostRegistered()) authenticatedHostKey else null
|
||||||
|
println("Received route: $route, registered host: $registeredHost")
|
||||||
|
|
||||||
|
if (route is RegisterHostIdentity) {
|
||||||
|
registerHostIdentity(mail, flowId)
|
||||||
|
} else if (isHostRegistered()) {
|
||||||
|
receiveMail(mail, flowId, route)
|
||||||
|
} else {
|
||||||
|
throw IllegalStateException(
|
||||||
|
"Host must be registered before executing enclave commands"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract fun receiveMail(mail: EnclaveMail, flowId: String, route: EnclaveCommand)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deserialize the [SenderIdentityImpl] from the mail body and authenticate the contained identity.
|
||||||
|
* If the sender is trusted and is the signer of the shared secret, register the host's public key and initialise
|
||||||
|
* the [hostPostOffice] property. Respond with "ack" if the registration was successful or "nak" otherwise.
|
||||||
|
* @param mail an incoming [EnclaveMail] containing a serialized [SenderIdentityImpl] instance.
|
||||||
|
* @param flowId the ID of the flow executing on our host.
|
||||||
|
*/
|
||||||
|
protected fun registerHostIdentity(mail: EnclaveMail, flowId: String) {
|
||||||
|
val identity = SenderIdentityImpl.deserialize(mail.bodyAsBytes)
|
||||||
|
val sharedSecret = mail.authenticatedSender.encoded
|
||||||
|
|
||||||
|
val authenticated = authenticateIdentity(sharedSecret, identity)
|
||||||
|
if (authenticated) {
|
||||||
|
authenticatedHostKey = mail.authenticatedSender
|
||||||
|
hostPostOffice = postOffice(mail)
|
||||||
|
}
|
||||||
|
|
||||||
|
val responseString = if (authenticated) "ack" else "nak"
|
||||||
|
|
||||||
|
val reply = postOffice(mail).encryptMail(responseString.toByteArray(Charsets.UTF_8))
|
||||||
|
postMail(reply, "$flowId:${RegisterHostIdentity().serialize()}")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return true if a host has successfully registered and authenticated their identity.
|
||||||
|
* @return [Boolean] indicating if a host has registered with this enclave.
|
||||||
|
*/
|
||||||
|
private fun isHostRegistered() = this::authenticatedHostKey.isInitialized
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return true if [identity] is trusted and is the signer of [sharedSecret].
|
||||||
|
* @param sharedSecret check identity signature against this shared secret.
|
||||||
|
* @param identity the [SenderIdentityImpl] instance to authenticate.
|
||||||
|
*/
|
||||||
|
private fun authenticateIdentity(sharedSecret: ByteArray, identity: SenderIdentityImpl): Boolean {
|
||||||
|
val isTrusted = identity.isTrusted(trustedRootCertificate)
|
||||||
|
val didSign = identity.didSign(sharedSecret)
|
||||||
|
|
||||||
|
return isTrusted && didSign
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,117 @@
|
|||||||
|
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.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provide commands to exchange a randomly generated message and hash with another instance of this enclave.
|
||||||
|
*/
|
||||||
|
class EncryptedTxEnclave : CommandEnclave() {
|
||||||
|
|
||||||
|
private lateinit var remoteEnclavePostOffice: EnclavePostOffice
|
||||||
|
|
||||||
|
private val commandToCallableMap = mapOf(
|
||||||
|
InitPostOfficeToRemoteEnclave::class.java.name to ::initPostOfficeToRemoteEnclave,
|
||||||
|
VerifyUnencryptedTx::class.java.name to ::verifyUnencryptedTransaction
|
||||||
|
)
|
||||||
|
|
||||||
|
private val serializerFactoriesForContexts = Caffeine.newBuilder()
|
||||||
|
.maximumSize(128)
|
||||||
|
.build<SerializationFactoryCacheKey, SerializerFactory>()
|
||||||
|
.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)
|
||||||
|
} else {
|
||||||
|
throw IllegalArgumentException(
|
||||||
|
"No callable registered for command $route in enclave: ${this.javaClass.simpleName}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deserialize the enclave attestation and create a post office to the remote enclave.
|
||||||
|
* @param mail an incoming [EnclaveMail] containing the attestation to deserialize.
|
||||||
|
* @param flowId the ID of the flow executing on our host.
|
||||||
|
* @throws [IllegalStateException] if the remote enclave has already been initialised, or if the host identity has
|
||||||
|
* not been registered.
|
||||||
|
* @throws [IllegalArgumentException] if the mail sender is not the authenticated host key.
|
||||||
|
*/
|
||||||
|
private fun initPostOfficeToRemoteEnclave(mail: EnclaveMail, flowId: String) {
|
||||||
|
if (this::remoteEnclavePostOffice.isInitialized) {
|
||||||
|
throw IllegalStateException("Post office for remote enclave has already been initialized")
|
||||||
|
} else if (mail.authenticatedSender != authenticatedHostKey) {
|
||||||
|
throw IllegalArgumentException("Mail sender does not match the authenticated host key")
|
||||||
|
}
|
||||||
|
|
||||||
|
val attestation = EnclaveInstanceInfo.deserialize(mail.bodyAsBytes)
|
||||||
|
// TODO: should we check an enclave constraint?
|
||||||
|
|
||||||
|
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()}")
|
||||||
|
}
|
||||||
|
}
|
@ -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()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
13
enclave/src/main/resources/trustedroot.cer
Normal file
13
enclave/src/main/resources/trustedroot.cer
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIICCTCCAbCgAwIBAgIIcFe0qctqSucwCgYIKoZIzj0EAwIwWDEbMBkGA1UEAwwS
|
||||||
|
Q29yZGEgTm9kZSBSb290IENBMQswCQYDVQQKDAJSMzEOMAwGA1UECwwFY29yZGEx
|
||||||
|
DzANBgNVBAcMBkxvbmRvbjELMAkGA1UEBhMCVUswHhcNMTcwNTIyMDAwMDAwWhcN
|
||||||
|
MjcwNTIwMDAwMDAwWjBYMRswGQYDVQQDDBJDb3JkYSBOb2RlIFJvb3QgQ0ExCzAJ
|
||||||
|
BgNVBAoMAlIzMQ4wDAYDVQQLDAVjb3JkYTEPMA0GA1UEBwwGTG9uZG9uMQswCQYD
|
||||||
|
VQQGEwJVSzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABGlm6LFHrVkzfuUHin36
|
||||||
|
Jrm1aUMarX/NUZXw8n8gSiJmsZPlUEplJ+f/lzZMky5EZPTtCciG34pnOP0eiMd/
|
||||||
|
JTCjZDBiMB0GA1UdDgQWBBR8rqnfuUgBKxOJC5rmRYUcORcHczALBgNVHQ8EBAMC
|
||||||
|
AYYwIwYDVR0lBBwwGgYIKwYBBQUHAwEGCCsGAQUFBwMCBgRVHSUAMA8GA1UdEwEB
|
||||||
|
/wQFMAMBAf8wCgYIKoZIzj0EAwIDRwAwRAIgDaL4SguKsNeTT7SeUkFdoCBACeG8
|
||||||
|
GqO4M1KlfimphQwCICiq00hDanT5W8bTLqE7GIGuplf/O8AABlpWrUg6uiUB
|
||||||
|
-----END CERTIFICATE-----
|
@ -5,3 +5,5 @@ owasp.failOnError=false
|
|||||||
owasp.failBuildOnCVSS=11.0
|
owasp.failBuildOnCVSS=11.0
|
||||||
compilation.allWarningsAsErrors=false
|
compilation.allWarningsAsErrors=false
|
||||||
test.parallel=false
|
test.parallel=false
|
||||||
|
conclaveVersion=1.1
|
||||||
|
conclaveRepo=./repo
|
@ -194,6 +194,7 @@ fun registerCustomSerializers(factory: SerializerFactory) {
|
|||||||
register(net.corda.serialization.internal.amqp.custom.BitSetSerializer(this))
|
register(net.corda.serialization.internal.amqp.custom.BitSetSerializer(this))
|
||||||
register(net.corda.serialization.internal.amqp.custom.EnumSetSerializer(this))
|
register(net.corda.serialization.internal.amqp.custom.EnumSetSerializer(this))
|
||||||
register(net.corda.serialization.internal.amqp.custom.ContractAttachmentSerializer(this))
|
register(net.corda.serialization.internal.amqp.custom.ContractAttachmentSerializer(this))
|
||||||
|
register(net.corda.serialization.internal.amqp.custom.PairSerializer(this))
|
||||||
}
|
}
|
||||||
registerNonDeterministicSerializers(factory)
|
registerNonDeterministicSerializers(factory)
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,21 @@
|
|||||||
|
package net.corda.serialization.internal.amqp.custom
|
||||||
|
|
||||||
|
import net.corda.core.KeepForDJVM
|
||||||
|
import net.corda.serialization.internal.amqp.CustomSerializer
|
||||||
|
import net.corda.serialization.internal.amqp.SerializerFactory
|
||||||
|
|
||||||
|
class PairSerializer(
|
||||||
|
factory: SerializerFactory
|
||||||
|
) : CustomSerializer.Proxy<Pair<*, *>, PairSerializer.PairProxy>(
|
||||||
|
Pair::class.java,
|
||||||
|
PairProxy::class.java,
|
||||||
|
factory
|
||||||
|
) {
|
||||||
|
|
||||||
|
override fun toProxy(obj: Pair<*, *>): PairProxy = PairProxy(obj.first, obj.second)
|
||||||
|
|
||||||
|
override fun fromProxy(proxy: PairProxy): Pair<*, *> = Pair(proxy.first, proxy.second)
|
||||||
|
|
||||||
|
@KeepForDJVM
|
||||||
|
data class PairProxy(val first: Any?, val second: Any?)
|
||||||
|
}
|
@ -22,8 +22,16 @@ pluginManagement {
|
|||||||
mavenLocal()
|
mavenLocal()
|
||||||
gradlePluginPortal()
|
gradlePluginPortal()
|
||||||
maven { url "${artifactory_contextUrl}/corda-dependencies" }
|
maven { url "${artifactory_contextUrl}/corda-dependencies" }
|
||||||
|
maven { url "$settingsDir/repo" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
println("CONCLAVE VERSION: $conclaveVersion")
|
||||||
|
// TODO: fix conclaveVersion property
|
||||||
|
// (none of the conclave properties seem to be sourced from gradle.properties, they appear to be coming from somewhere else)
|
||||||
|
id 'com.r3.conclave.enclave' version '1.1' apply false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// The project is named 'corda-project' and not 'corda' because if this is named the same as the
|
// The project is named 'corda-project' and not 'corda' because if this is named the same as the
|
||||||
// output JAR from the capsule then the buildCordaJAR task goes into an infinite loop.
|
// output JAR from the capsule then the buildCordaJAR task goes into an infinite loop.
|
||||||
@ -102,6 +110,9 @@ include 'testing:cordapps:dbfailure:dbfworkflows'
|
|||||||
include 'testing:cordapps:missingmigration'
|
include 'testing:cordapps:missingmigration'
|
||||||
include 'testing:cordapps:sleeping'
|
include 'testing:cordapps:sleeping'
|
||||||
|
|
||||||
|
// Conclave
|
||||||
|
include 'enclave'
|
||||||
|
|
||||||
// Common libraries - start
|
// Common libraries - start
|
||||||
include 'common-validation'
|
include 'common-validation'
|
||||||
project(":common-validation").projectDir = new File("$settingsDir/common/validation")
|
project(":common-validation").projectDir = new File("$settingsDir/common/validation")
|
||||||
@ -112,6 +123,9 @@ project(":common-configuration-parsing").projectDir = new File("$settingsDir/com
|
|||||||
|
|
||||||
include 'common-logging'
|
include 'common-logging'
|
||||||
project(":common-logging").projectDir = new File("$settingsDir/common/logging")
|
project(":common-logging").projectDir = new File("$settingsDir/common/logging")
|
||||||
|
|
||||||
|
include 'common-enclave'
|
||||||
|
project(":common-enclave").projectDir = new File("$settingsDir/common/enclave")
|
||||||
// Common libraries - end
|
// Common libraries - end
|
||||||
|
|
||||||
apply from: 'buildCacheSettings.gradle'
|
apply from: 'buildCacheSettings.gradle'
|
||||||
|
Loading…
x
Reference in New Issue
Block a user