mirror of
https://github.com/corda/corda.git
synced 2025-02-10 12:51:37 +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
11
.idea/compiler.xml
generated
11
.idea/compiler.xml
generated
@ -28,6 +28,8 @@
|
|||||||
<module name="docs_source_example-code_main" target="1.8" />
|
<module name="docs_source_example-code_main" target="1.8" />
|
||||||
<module name="docs_source_example-code_test" target="1.8" />
|
<module name="docs_source_example-code_test" target="1.8" />
|
||||||
<module name="docs_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_main" target="1.8" />
|
||||||
<module name="experimental_test" target="1.8" />
|
<module name="experimental_test" target="1.8" />
|
||||||
<module name="explorer-capsule_main" target="1.6" />
|
<module name="explorer-capsule_main" target="1.6" />
|
||||||
@ -73,6 +75,12 @@
|
|||||||
<module name="samples_test" target="1.8" />
|
<module name="samples_test" target="1.8" />
|
||||||
<module name="sandbox_main" target="1.8" />
|
<module name="sandbox_main" target="1.8" />
|
||||||
<module name="sandbox_test" 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_integrationTest" target="1.8" />
|
||||||
<module name="simm-valuation-demo_main" target="1.8" />
|
<module name="simm-valuation-demo_main" target="1.8" />
|
||||||
<module name="simm-valuation-demo_test" 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_integrationTest" target="1.8" />
|
||||||
<module name="verifier_main" target="1.8" />
|
<module name="verifier_main" target="1.8" />
|
||||||
<module name="verifier_test" 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_main" target="1.6" />
|
||||||
<module name="webcapsule_test" target="1.6" />
|
<module name="webcapsule_test" target="1.6" />
|
||||||
<module name="webserver-webcapsule_main" target="1.6" />
|
<module name="webserver-webcapsule_main" target="1.6" />
|
||||||
|
@ -35,3 +35,4 @@ include 'samples:bank-of-corda-demo'
|
|||||||
include 'cordform-common'
|
include 'cordform-common'
|
||||||
include 'doorman'
|
include 'doorman'
|
||||||
include 'verify-enclave'
|
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