diff --git a/.idea/compiler.xml b/.idea/compiler.xml
index d37c3b63c3..d0973508de 100644
--- a/.idea/compiler.xml
+++ b/.idea/compiler.xml
@@ -28,6 +28,8 @@
+
+
@@ -73,6 +75,12 @@
+
+
+
+
+
+
@@ -86,6 +94,9 @@
+
+
+
@@ -98,4 +109,4 @@
-
+
\ No newline at end of file
diff --git a/settings.gradle b/settings.gradle
index 4fca9add5c..96df1826c8 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -35,3 +35,4 @@ include 'samples:bank-of-corda-demo'
include 'cordform-common'
include 'doorman'
include 'verify-enclave'
+include 'sgx-jvm/sgx-signtool'
diff --git a/sgx-jvm/sgx-signtool/build.gradle b/sgx-jvm/sgx-signtool/build.gradle
new file mode 100644
index 0000000000..b0960e78e9
--- /dev/null
+++ b/sgx-jvm/sgx-signtool/build.gradle
@@ -0,0 +1,57 @@
+apply plugin: 'java'
+apply plugin: 'kotlin'
+
+group 'com.r3cev.sgx'
+version '1.0-SNAPSHOT'
+
+buildscript {
+ repositories {
+ mavenCentral()
+ }
+ dependencies {
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
+ }
+}
+
+
+sourceCompatibility = 1.8
+
+repositories {
+ mavenCentral()
+
+}
+
+dependencies {
+
+ compile fileTree(dir: 'libs', include: '*.jar')
+ compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
+ compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
+ compile "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
+
+ compile "org.bouncycastle:bcprov-jdk15on:${bouncycastle_version}"
+ compile "org.bouncycastle:bcpkix-jdk15on:${bouncycastle_version}"
+ compile "com.typesafe:config:$typesafe_config_version"
+ compile "net.sf.jopt-simple:jopt-simple:$jopt_simple_version"
+
+ testCompile group: 'junit', name: 'junit', version: '4.11'
+
+}
+
+jar {
+ from(configurations.runtime.collect { it.isDirectory() ? it : zipTree(it) })
+ exclude 'META-INF/*.SF', 'META-INF/*.DSA', 'META-INF/*.RSA', 'META-INF/*.MF'
+
+ manifest {
+ attributes('Main-Class' : 'com.r3cev.sgx.signtool.MainKt')
+ }
+}
+
+task copyConfig(type: Copy) {
+ from 'config'
+ into "$buildDir/libs"
+}
+
+build.dependsOn copyConfig
+
+
+
diff --git a/sgx-jvm/sgx-signtool/libs/CryptoServerCXI.jar b/sgx-jvm/sgx-signtool/libs/CryptoServerCXI.jar
new file mode 100644
index 0000000000..11e6412b14
Binary files /dev/null and b/sgx-jvm/sgx-signtool/libs/CryptoServerCXI.jar differ
diff --git a/sgx-jvm/sgx-signtool/libs/CryptoServerJCE.jar b/sgx-jvm/sgx-signtool/libs/CryptoServerJCE.jar
new file mode 100644
index 0000000000..913d72af30
Binary files /dev/null and b/sgx-jvm/sgx-signtool/libs/CryptoServerJCE.jar differ
diff --git a/sgx-jvm/sgx-signtool/src/main/kotlin/com/r3cev/sgx/config/ToolConfig.kt b/sgx-jvm/sgx-signtool/src/main/kotlin/com/r3cev/sgx/config/ToolConfig.kt
new file mode 100644
index 0000000000..b3b79227ec
--- /dev/null
+++ b/sgx-jvm/sgx-signtool/src/main/kotlin/com/r3cev/sgx/config/ToolConfig.kt
@@ -0,0 +1,87 @@
+package com.r3cev.sgx.config
+
+import com.r3cev.sgx.utils.getValue
+import com.typesafe.config.Config
+import com.typesafe.config.ConfigFactory
+import joptsimple.OptionParser
+import java.nio.file.Files
+import java.nio.file.Path
+import java.nio.file.Paths
+import kotlin.system.exitProcess
+
+data class ToolConfig(val config: Config) {
+ constructor(args: Array) : this({
+ val parser = OptionParser()
+ val sourcePathArg = parser.accepts("source").withRequiredArg().required()
+ val configPathArg = parser.accepts("config").withRequiredArg()
+ val profileArg = parser.accepts("profile").withRequiredArg().defaultsTo("dev")
+ val publicKeyOutputPathArg = parser.accepts("pubkey").withRequiredArg().defaultsTo("./pubkey.pem")
+ val signatureOutputPathArg = parser.accepts("signature").withRequiredArg().defaultsTo("./signature.sha256")
+ val deviceArg = parser.accepts("device").withRequiredArg()
+ val keyNameArg = parser.accepts("keyName").withRequiredArg()
+ val keyGroupArg = parser.accepts("keyGroup").withRequiredArg()
+ val keySpecifierArg = parser.accepts("keySpecifier").withRequiredArg()
+ val options = try {
+ parser.parse(*args)
+ } catch (e: Exception) {
+ println(e.message)
+ parser.printHelpOn(System.out)
+ exitProcess(1)
+ }
+
+ val sourcePath = options.valueOf(sourcePathArg)!!
+ val publicKeyOutputPath = options.valueOf(publicKeyOutputPathArg)!!
+ val signatureOutputPath = options.valueOf(signatureOutputPathArg)!!
+ val baseConfig = if (options.hasArgument(configPathArg)) {
+ val configPath = Paths.get(options.valueOf(configPathArg)!!)
+ require(Files.exists(configPath)) { "Config file $sourcePath not found" }
+ ConfigFactory.parseFile(configPath.toFile())
+ } else {
+ ConfigFactory.parseResources(ToolConfig::class.java, "sgxtool.cfg")
+ }
+
+ val profile = options.valueOf(profileArg)!!.toLowerCase()
+ val overrideMap = mutableMapOf("profile" to profile, "sourcePath" to sourcePath,
+ "publicKeyOutputPath" to publicKeyOutputPath,
+ "signatureOutputPath" to signatureOutputPath)
+ if (options.hasArgument(deviceArg)) {
+ overrideMap["$profile.device"] = options.valueOf(deviceArg)
+ }
+ if (options.hasArgument(keyNameArg)) {
+ overrideMap["$profile.keyName"] = options.valueOf(keyNameArg)
+ }
+ if (options.hasArgument(keyGroupArg)) {
+ overrideMap["$profile.keyGroup"] = options.valueOf(keyGroupArg)
+ }
+ if (options.hasArgument(keySpecifierArg)) {
+ overrideMap["$profile.keySpecifier"] = options.valueOf(keySpecifierArg)
+ }
+ val overrideConf = ConfigFactory.parseMap(overrideMap)
+ val final = overrideConf.withFallback(baseConfig).resolve()
+ final!!
+ }())
+
+ val profile: String by config
+ val profileConfig: Config get() = config.getConfig(profile)
+ val device: String by profileConfig
+ val keyName: String by profileConfig
+ val keyGroup: String by profileConfig
+ val keySpecifier: String by profileConfig
+ val sourcePath: Path by config
+ val publicKeyOutputPath: Path by config
+ val signatureOutputPath: Path by config
+
+ override fun toString(): String {
+ val sb = StringBuilder()
+ sb.append("profile: $profile\n")
+ sb.append("device: $device\n")
+ sb.append("keyName: $keyName\n")
+ sb.append("keyGroup: $keyGroup\n")
+ sb.append("keySpecifier: $keySpecifier\n")
+ sb.append("sourcePath: $sourcePath\n")
+ sb.append("publicKeyOutputPath: $publicKeyOutputPath\n")
+ sb.append("signatureOutputPath: $signatureOutputPath\n")
+ return sb.toString()
+ }
+
+}
diff --git a/sgx-jvm/sgx-signtool/src/main/kotlin/com/r3cev/sgx/signtool/main.kt b/sgx-jvm/sgx-signtool/src/main/kotlin/com/r3cev/sgx/signtool/main.kt
new file mode 100644
index 0000000000..8e81c53ebd
--- /dev/null
+++ b/sgx-jvm/sgx-signtool/src/main/kotlin/com/r3cev/sgx/signtool/main.kt
@@ -0,0 +1,91 @@
+package com.r3cev.sgx.signtool
+
+import CryptoServerJCE.CryptoServerProvider
+import com.r3cev.sgx.config.ToolConfig
+import org.bouncycastle.openssl.jcajce.JcaPEMWriter
+import java.io.ByteArrayInputStream
+import java.io.ByteArrayOutputStream
+import java.io.FileWriter
+import java.nio.file.Files
+import java.nio.file.Path
+import java.nio.file.StandardOpenOption
+import java.security.*
+import java.security.spec.X509EncodedKeySpec
+
+fun main(args: Array) {
+ println("SGX Tool started")
+ val config = ToolConfig(args)
+ require(Files.exists(config.sourcePath)) { "Input ${config.sourcePath} not found" }
+ println(config)
+ println("Connect to ${config.device}")
+ val provider = createProvider(config.device, config.keyGroup, config.keySpecifier)
+ while (true) {
+ print("Enter User Name: ")
+ val user = readLine()
+ println("Login user: $user")
+ provider.loginSign(user, ":cs2:cyb:USB0", null)
+ val auth = provider.cryptoServer.authState
+ if ((auth and 0x0000000F) >= 0x00000002) {
+ println("Auth Sufficient")
+ break
+ }
+ println("Need more permissions. Add extra login")
+ }
+ val keyStore = KeyStore.getInstance("CryptoServer", provider)
+ keyStore.load(null, null)
+ val aliases = keyStore.aliases().toList()
+ println(aliases)
+ val sgxKey = keyStore.getKey(config.keyName, null) as PrivateKey
+ val data = Files.readAllBytes(config.sourcePath)
+ val signer = Signature.getInstance("SHA256WithRSA", provider)
+ signer.initSign(sgxKey)
+ signer.update(data)
+ val signature = signer.sign()
+
+ val javaFactory = KeyFactory.getInstance("RSA")
+ val sgxPub = keyStore.getCertificate(config.keyName).publicKey
+ val sgxPubReImport = javaFactory.generatePublic(X509EncodedKeySpec(sgxPub.encoded))
+ val verify = Signature.getInstance("SHA256WithRSA")
+ verify.initVerify(sgxPubReImport)
+ verify.update(data)
+
+ require(verify.verify(signature)) { "Signature didn't independently verify" }
+ System.setProperty("line.separator", "\n") // Ensure UNIX line endings in PEM files
+ saveSignatureAsFile(signature, config.signatureOutputPath)
+ savePublicKeyAsPEMFile(sgxPubReImport, config.publicKeyOutputPath)
+
+ provider.logoff()
+}
+
+
+private fun saveSignatureAsFile(signature: ByteArray, filename: Path) {
+ Files.write(filename, signature, StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)
+}
+
+private fun savePublicKeyAsPEMFile(key: PublicKey, filename: Path) {
+ FileWriter(filename.toFile()).use {
+ JcaPEMWriter(it).use {
+ it.writeObject(key)
+ }
+ }
+}
+
+private fun createProvider(device: String, keyGroup: String, keySpecifier: String): CryptoServerProvider {
+ val cfgBuffer = ByteArrayOutputStream()
+ val writer = cfgBuffer.writer(Charsets.UTF_8)
+ writer.write("Device = $device\n")
+ writer.write("ConnectionTimeout = 3000\n")
+ writer.write("Timeout = 30000\n")
+ writer.write("EndSessionOnShutdown = 1\n")
+ writer.write("KeepSessionAlive = 0\n")
+ writer.write("KeyGroup = $keyGroup\n")
+ writer.write("KeySpecifier = $keySpecifier\n")
+ writer.write("StoreKeysExternal = false\n")
+ writer.close()
+ val cfg = ByteArrayInputStream(cfgBuffer.toByteArray())
+ cfgBuffer.close()
+ val provider = CryptoServerProvider(cfg)
+ cfg.close()
+ return provider
+}
+
diff --git a/sgx-jvm/sgx-signtool/src/main/kotlin/com/r3cev/sgx/utils/ConfigUtilities.kt b/sgx-jvm/sgx-signtool/src/main/kotlin/com/r3cev/sgx/utils/ConfigUtilities.kt
new file mode 100644
index 0000000000..1bbd25040b
--- /dev/null
+++ b/sgx-jvm/sgx-signtool/src/main/kotlin/com/r3cev/sgx/utils/ConfigUtilities.kt
@@ -0,0 +1,105 @@
+package com.r3cev.sgx.utils
+
+import com.typesafe.config.Config
+import com.typesafe.config.ConfigUtil
+import java.net.Proxy
+import java.net.URL
+import java.nio.file.Path
+import java.nio.file.Paths
+import java.time.Instant
+import java.time.LocalDate
+import java.util.*
+import kotlin.reflect.KClass
+import kotlin.reflect.KProperty
+import kotlin.reflect.KType
+import kotlin.reflect.full.memberProperties
+import kotlin.reflect.full.primaryConstructor
+import kotlin.reflect.jvm.jvmErasure
+
+// TODO Move other config parsing to use parseAs and remove this
+operator fun Config.getValue(receiver: Any, metadata: KProperty<*>): T {
+ return getValueInternal(metadata.name, metadata.returnType)
+}
+
+fun Config.parseAs(clazz: KClass): T {
+ require(clazz.isData) { "Only Kotlin data classes can be parsed" }
+ val constructor = clazz.primaryConstructor!!
+ val args = constructor.parameters
+ .filterNot { it.isOptional && !hasPath(it.name!!) }
+ .associateBy({ it }) { param ->
+ // Get the matching property for this parameter
+ val property = clazz.memberProperties.first { it.name == param.name }
+ getValueInternal(property.name, param.type)
+ }
+ return constructor.callBy(args)
+}
+
+inline fun Config.parseAs(): T = parseAs(T::class)
+
+fun Config.toProperties(): Properties {
+ return entrySet().associateByTo(
+ Properties(),
+ { ConfigUtil.splitPath(it.key).joinToString(".") },
+ { it.value.unwrapped().toString() })
+}
+
+@Suppress("UNCHECKED_CAST")
+private fun Config.getValueInternal(path: String, type: KType): T {
+ return (if (type.arguments.isEmpty()) getSingleValue(path, type) else getCollectionValue(path, type)) as T
+}
+
+private fun Config.getSingleValue(path: String, type: KType): Any? {
+ if (type.isMarkedNullable && !hasPath(path)) return null
+ val typeClass = type.jvmErasure
+ return when (typeClass) {
+ String::class -> getString(path)
+ Int::class -> getInt(path)
+ Long::class -> getLong(path)
+ Double::class -> getDouble(path)
+ Boolean::class -> getBoolean(path)
+ LocalDate::class -> LocalDate.parse(getString(path))
+ Instant::class -> Instant.parse(getString(path))
+ Path::class -> Paths.get(getString(path))
+ URL::class -> URL(getString(path))
+ Properties::class -> getConfig(path).toProperties()
+ else -> if (typeClass.java.isEnum) {
+ parseEnum(typeClass.java, getString(path))
+ } else {
+ getConfig(path).parseAs(typeClass)
+ }
+ }
+}
+
+private fun Config.getCollectionValue(path: String, type: KType): Collection {
+ val typeClass = type.jvmErasure
+ require(typeClass == List::class || typeClass == Set::class) { "$typeClass is not supported" }
+ val elementClass = type.arguments[0].type?.jvmErasure ?: throw IllegalArgumentException("Cannot work with star projection: $type")
+ if (!hasPath(path)) {
+ return if (typeClass == List::class) emptyList() else emptySet()
+ }
+ val values: List = when (elementClass) {
+ String::class -> getStringList(path)
+ Int::class -> getIntList(path)
+ Long::class -> getLongList(path)
+ Double::class -> getDoubleList(path)
+ Boolean::class -> getBooleanList(path)
+ LocalDate::class -> getStringList(path).map(LocalDate::parse)
+ Instant::class -> getStringList(path).map(Instant::parse)
+ Path::class -> getStringList(path).map { Paths.get(it) }
+ URL::class -> getStringList(path).map(::URL)
+ Properties::class -> getConfigList(path).map(Config::toProperties)
+ else -> if (elementClass.java.isEnum) {
+ getStringList(path).map { parseEnum(elementClass.java, it) }
+ } else {
+ getConfigList(path).map { it.parseAs(elementClass) }
+ }
+ }
+ return if (typeClass == Set::class) values.toSet() else values
+}
+
+
+@Suppress("UNCHECKED_CAST")
+private fun parseEnum(enumType: Class<*>, name: String): Enum<*> = enumBridge(enumType as Class, name) // Any enum will do
+
+private fun > enumBridge(clazz: Class, name: String): T = java.lang.Enum.valueOf(clazz, name)
+
diff --git a/sgx-jvm/sgx-signtool/src/main/resources/com/r3cev/sgx/config/sgxtool.cfg b/sgx-jvm/sgx-signtool/src/main/resources/com/r3cev/sgx/config/sgxtool.cfg
new file mode 100644
index 0000000000..a87006559b
--- /dev/null
+++ b/sgx-jvm/sgx-signtool/src/main/resources/com/r3cev/sgx/config/sgxtool.cfg
@@ -0,0 +1,14 @@
+profile = "dev"
+dev : {
+ device = "3001@127.0.0.1"
+ keyName = "DEV_SGX"
+ keyGroup = "DEV.SGX"
+ keySpecifier = "1"
+}
+
+prod : {
+ device = "TCP:10.18.0.47"
+ keyName = "PROD_SGX"
+ keyGroup = "PROD.SGX"
+ keySpecifier = "1"
+}