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" +}