mirror of
https://github.com/corda/corda.git
synced 2025-01-27 06:39:38 +00:00
Add a tool that connects to the HSM and creates signatures suitable for SGX enclave signing.
This commit is contained in:
parent
3d8581a946
commit
294dc41fe6
13
.idea/compiler.xml
generated
13
.idea/compiler.xml
generated
@ -28,6 +28,8 @@
|
||||
<module name="docs_source_example-code_main" target="1.8" />
|
||||
<module name="docs_source_example-code_test" target="1.8" />
|
||||
<module name="docs_test" target="1.8" />
|
||||
<module name="doorman_main" target="1.8" />
|
||||
<module name="doorman_test" target="1.8" />
|
||||
<module name="experimental_main" target="1.8" />
|
||||
<module name="experimental_test" target="1.8" />
|
||||
<module name="explorer-capsule_main" target="1.6" />
|
||||
@ -73,6 +75,12 @@
|
||||
<module name="samples_test" target="1.8" />
|
||||
<module name="sandbox_main" target="1.8" />
|
||||
<module name="sandbox_test" target="1.8" />
|
||||
<module name="sgx-jvm_main" target="1.8" />
|
||||
<module name="sgx-jvm_sgx-signtool_main" target="1.8" />
|
||||
<module name="sgx-jvm_sgx-signtool_test" target="1.8" />
|
||||
<module name="sgx-jvm_test" target="1.8" />
|
||||
<module name="sgx-signtool_main" target="1.8" />
|
||||
<module name="sgx-signtool_test" target="1.8" />
|
||||
<module name="simm-valuation-demo_integrationTest" target="1.8" />
|
||||
<module name="simm-valuation-demo_main" target="1.8" />
|
||||
<module name="simm-valuation-demo_test" target="1.8" />
|
||||
@ -86,6 +94,9 @@
|
||||
<module name="verifier_integrationTest" target="1.8" />
|
||||
<module name="verifier_main" target="1.8" />
|
||||
<module name="verifier_test" target="1.8" />
|
||||
<module name="verify-enclave_integrationTest" target="1.8" />
|
||||
<module name="verify-enclave_main" target="1.8" />
|
||||
<module name="verify-enclave_test" target="1.8" />
|
||||
<module name="webcapsule_main" target="1.6" />
|
||||
<module name="webcapsule_test" target="1.6" />
|
||||
<module name="webserver-webcapsule_main" target="1.6" />
|
||||
@ -98,4 +109,4 @@
|
||||
<component name="JavacSettings">
|
||||
<option name="ADDITIONAL_OPTIONS_STRING" value="-parameters" />
|
||||
</component>
|
||||
</project>
|
||||
</project>
|
@ -35,3 +35,4 @@ include 'samples:bank-of-corda-demo'
|
||||
include 'cordform-common'
|
||||
include 'doorman'
|
||||
include 'verify-enclave'
|
||||
include 'sgx-jvm/sgx-signtool'
|
||||
|
57
sgx-jvm/sgx-signtool/build.gradle
Normal file
57
sgx-jvm/sgx-signtool/build.gradle
Normal file
@ -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
|
||||
|
||||
|
||||
|
BIN
sgx-jvm/sgx-signtool/libs/CryptoServerCXI.jar
Normal file
BIN
sgx-jvm/sgx-signtool/libs/CryptoServerCXI.jar
Normal file
Binary file not shown.
BIN
sgx-jvm/sgx-signtool/libs/CryptoServerJCE.jar
Normal file
BIN
sgx-jvm/sgx-signtool/libs/CryptoServerJCE.jar
Normal file
Binary file not shown.
@ -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<String>) : 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()
|
||||
}
|
||||
|
||||
}
|
@ -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<String>) {
|
||||
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
|
||||
}
|
||||
|
@ -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 <T> Config.getValue(receiver: Any, metadata: KProperty<*>): T {
|
||||
return getValueInternal(metadata.name, metadata.returnType)
|
||||
}
|
||||
|
||||
fun <T : Any> Config.parseAs(clazz: KClass<T>): 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<Any>(property.name, param.type)
|
||||
}
|
||||
return constructor.callBy(args)
|
||||
}
|
||||
|
||||
inline fun <reified T : Any> 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 <T> 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<Any> {
|
||||
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<Any> = 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<Proxy.Type>, name) // Any enum will do
|
||||
|
||||
private fun <T : Enum<T>> enumBridge(clazz: Class<T>, name: String): T = java.lang.Enum.valueOf(clazz, name)
|
||||
|
@ -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"
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user