Add a tool that connects to the HSM and creates signatures suitable for SGX enclave signing.

This commit is contained in:
Matthew Nesbit 2017-06-07 17:26:32 +01:00 committed by Andras Slemmer
parent 3d8581a946
commit 294dc41fe6
9 changed files with 367 additions and 1 deletions

13
.idea/compiler.xml generated
View File

@ -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>

View File

@ -35,3 +35,4 @@ include 'samples:bank-of-corda-demo'
include 'cordform-common'
include 'doorman'
include 'verify-enclave'
include 'sgx-jvm/sgx-signtool'

View 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

Binary file not shown.

Binary file not shown.

View File

@ -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()
}
}

View File

@ -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
}

View File

@ -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)

View File

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