Merging signing service and doorman (#72)

* Merging signing service and doorman

* Addressing review comments

* Removing redundant package name space from method call

* Adding description field to gradle
This commit is contained in:
mkit 2017-10-20 17:19:50 +01:00 committed by GitHub
parent 0ae205ec25
commit dfb226fbbb
58 changed files with 372 additions and 5190 deletions

View File

@ -1,54 +0,0 @@
package com.r3.corda.doorman
import com.typesafe.config.Config
import com.typesafe.config.ConfigFactory
import joptsimple.ArgumentAcceptingOptionSpec
import joptsimple.OptionParser
import net.corda.core.crypto.sha256
import org.bouncycastle.cert.X509CertificateHolder
import java.io.ByteArrayInputStream
import java.security.PublicKey
import java.security.cert.CertPath
import java.security.cert.Certificate
import java.security.cert.CertificateFactory
import java.security.cert.X509Certificate
/**
* Convert commandline arguments to [Config] object will allow us to use kotlin delegate with [ConfigHelper].
*/
object OptionParserHelper {
fun Array<out String>.toConfigWithOptions(registerOptions: OptionParser.() -> Unit): Config {
val parser = OptionParser()
val helpOption = parser.acceptsAll(listOf("h", "?", "help"), "show help").forHelp();
registerOptions(parser)
val optionSet = parser.parse(*this)
// Print help and exit on help option.
if (optionSet.has(helpOption)) {
throw ShowHelpException(parser)
}
// Convert all command line options to Config.
return ConfigFactory.parseMap(parser.recognizedOptions().mapValues {
val optionSpec = it.value
if (optionSpec is ArgumentAcceptingOptionSpec<*> && !optionSpec.requiresArgument() && optionSet.has(optionSpec)) null else optionSpec.value(optionSet)
}.filterValues { it != null })
}
}
class ShowHelpException(val parser: OptionParser) : Exception()
object CertificateUtilities {
fun toX509Certificate(byteArray: ByteArray): X509Certificate {
return CertificateFactory.getInstance("X509").generateCertificate(ByteArrayInputStream(byteArray)) as X509Certificate
}
}
fun X509CertificateHolder.toX509Certificate(): Certificate = CertificateUtilities.toX509Certificate(encoded)
fun buildCertPath(vararg certificates: Certificate): CertPath {
return CertificateFactory.getInstance("X509").generateCertPath(certificates.asList())
}
fun buildCertPath(certPathBytes: ByteArray): CertPath = CertificateFactory.getInstance("X509").generateCertPath(certPathBytes.inputStream())
// TODO: replace this with Crypto.hash when its available.
fun PublicKey.hash() = encoded.sha256().toString()

View File

@ -6,6 +6,8 @@ ext {
version "$corda_dependency_version"
description 'Network management module encapsulating components such as Doorman, HSM Signing Service and Network Map'
apply plugin: 'us.kirchmeier.capsule'
apply plugin: 'kotlin'
@ -21,12 +23,12 @@ repositories {
}
}
configurations{
configurations {
integrationTestCompile.extendsFrom testCompile
integrationTestRuntime.extendsFrom testRuntime
}
sourceSets{
sourceSets {
integrationTest {
kotlin {
compileClasspath += main.output + test.output
@ -46,8 +48,8 @@ sourceSets{
task buildDoormanJAR(type: FatCapsule, dependsOn: 'jar') {
group = 'build'
applicationClass 'com.r3.corda.doorman.MainKt'
applicationClass 'com.r3.corda.networkmanage.doorman.MainKt'
archiveName "doorman-${version}-capsule.jar"
capsuleManifest {
applicationVersion = corda_dependency_version
systemProperties['visualvm.display.name'] = 'Doorman'
@ -60,7 +62,29 @@ task buildDoormanJAR(type: FatCapsule, dependsOn: 'jar') {
reallyExecutable { trampolining() }
}
task buildHsmJAR(type: FatCapsule, dependsOn: 'jar') {
group = 'build'
applicationClass 'com.r3.corda.networkmanage.hsm.MainKt'
archiveName "hsm-${version}-capsule.jar"
capsuleManifest {
applicationVersion = corda_dependency_version
systemProperties['visualvm.display.name'] = 'HSM Signing Service'
minJavaVersion = '1.8.0'
jvmArgs = ['-XX:+UseG1GC']
}
// Make the resulting JAR file directly executable on UNIX by prepending a shell script to it.
// This lets you run the file like so: ./corda.jar
// Other than being slightly less typing, this has one big advantage: Ctrl-C works properly in the terminal.
reallyExecutable { trampolining() }
}
task integrationTest(type: Test) {
testClassesDirs = sourceSets.integrationTest.output.classesDirs
classpath = sourceSets.integrationTest.runtimeClasspath
}
dependencies {
compile fileTree(dir: 'libs', include: '*.jar')
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
compile "net.corda:corda-core:$corda_dependency_version"
@ -104,4 +128,4 @@ dependencies {
}
// Needed by jira rest client
compile "com.atlassian.fugue:fugue:2.6.1"
}
}

View File

@ -1,8 +1,9 @@
package com.r3.corda.doorman
package com.r3.corda.networkmanage.doorman
import com.nhaarman.mockito_kotlin.whenever
import com.r3.corda.doorman.persistence.DoormanSchemaService
import com.r3.corda.doorman.signer.Signer
import com.r3.corda.networkmanage.common.persistence.SchemaService
import com.r3.corda.networkmanage.common.utils.toX509Certificate
import com.r3.corda.networkmanage.doorman.signer.Signer
import net.corda.core.crypto.Crypto
import net.corda.core.crypto.SecureHash
import net.corda.core.identity.CordaX500Name
@ -36,7 +37,7 @@ class DoormanIntegrationTest {
val database = configureDatabase(makeTestDataSourceProperties(), null, {
// Identity service not needed doorman, corda persistence is not very generic.
throw UnsupportedOperationException()
}, DoormanSchemaService())
}, SchemaService())
val signer = Signer(intermediateCAKey, arrayOf(intermediateCACert.toX509Certificate(), rootCACert.toX509Certificate()))
//Start doorman server

View File

@ -1,10 +1,10 @@
package com.r3.corda.signing
package com.r3.corda.networkmanage.hsm
import com.r3.corda.signing.configuration.Parameters
import com.r3.corda.networkmanage.hsm.SigningServiceIntegrationTest.Companion.DB_NAME
import com.r3.corda.networkmanage.hsm.SigningServiceIntegrationTest.Companion.H2_TCP_PORT
import com.r3.corda.networkmanage.hsm.SigningServiceIntegrationTest.Companion.HOST
import com.r3.corda.networkmanage.hsm.configuration.Parameters
import java.util.*
import com.r3.corda.signing.SigningServiceIntegrationTest.Companion.DB_NAME
import com.r3.corda.signing.SigningServiceIntegrationTest.Companion.HOST
import com.r3.corda.signing.SigningServiceIntegrationTest.Companion.H2_TCP_PORT
/**
* The main method for an interactive HSM signing service test/demo. It is supposed to be executed with the

View File

@ -1,17 +1,17 @@
package com.r3.corda.signing
package com.r3.corda.networkmanage.hsm
import com.nhaarman.mockito_kotlin.any
import com.nhaarman.mockito_kotlin.mock
import com.nhaarman.mockito_kotlin.verify
import com.nhaarman.mockito_kotlin.whenever
import com.r3.corda.doorman.buildCertPath
import com.r3.corda.doorman.persistence.DoormanSchemaService
import com.r3.corda.doorman.startDoorman
import com.r3.corda.doorman.toX509Certificate
import com.r3.corda.signing.hsm.HsmSigner
import com.r3.corda.signing.persistence.ApprovedCertificateRequestData
import com.r3.corda.signing.persistence.DBCertificateRequestStorage
import com.r3.corda.signing.persistence.SigningServerSchemaService
import com.r3.corda.networkmanage.common.persistence.SchemaService
import com.r3.corda.networkmanage.common.utils.buildCertPath
import com.r3.corda.networkmanage.common.utils.toX509Certificate
import com.r3.corda.networkmanage.doorman.startDoorman
import com.r3.corda.networkmanage.hsm.persistence.CertificateRequestData
import com.r3.corda.networkmanage.hsm.persistence.DBSignedCertificateRequestStorage
import com.r3.corda.networkmanage.hsm.persistence.SignedCertificateRequestStorage
import com.r3.corda.networkmanage.hsm.signer.HsmSigner
import net.corda.core.crypto.Crypto
import net.corda.core.identity.CordaX500Name
import net.corda.core.utilities.NetworkHostAndPort
@ -58,7 +58,7 @@ class SigningServiceIntegrationTest {
timer.cancel()
}
private fun givenSignerSigningAllRequests(storage: DBCertificateRequestStorage): HsmSigner {
private fun givenSignerSigningAllRequests(storage: SignedCertificateRequestStorage): HsmSigner {
// Create all certificates
val rootCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
val rootCACert = X509Utilities.createSelfSignedCACertificate(CordaX500Name(commonName = "Integration Test Corda Node Root CA",
@ -71,14 +71,14 @@ class SigningServiceIntegrationTest {
return mock {
on { sign(any()) }.then {
@Suppress("UNCHECKED_CAST")
val toSign = it.arguments[0] as List<ApprovedCertificateRequestData>
val toSign = it.arguments[0] as List<CertificateRequestData>
toSign.forEach {
JcaPKCS10CertificationRequest(it.request).run {
val certificate = X509Utilities.createCertificate(CertificateType.TLS, intermediateCACert, intermediateCAKey, subject, publicKey).toX509Certificate()
it.certPath = buildCertPath(certificate, rootCACert.toX509Certificate())
}
}
storage.sign(toSign, listOf("TEST"))
storage.store(toSign, listOf("TEST"))
}
}
}
@ -89,7 +89,7 @@ class SigningServiceIntegrationTest {
val database = configureDatabase(makeTestDataSourceProperties(), null, {
// Identity service not needed doorman, corda persistence is not very generic.
throw UnsupportedOperationException()
}, DoormanSchemaService())
}, SchemaService())
val doorman = startDoorman(NetworkHostAndPort(HOST, 0), database, approveAll = true)
// Start Corda network registration.
@ -99,10 +99,10 @@ class SigningServiceIntegrationTest {
whenever(it.certificateSigningService).thenReturn(URL("http://$HOST:${doorman.hostAndPort.port}"))
}
val signingServiceStorage = DBCertificateRequestStorage(configureDatabase(makeTestDataSourceProperties(), makeNotInitialisingTestDatabaseProperties(), {
val signingServiceStorage = DBSignedCertificateRequestStorage(configureDatabase(makeTestDataSourceProperties(), makeNotInitialisingTestDatabaseProperties(), {
// Identity service not needed doorman, corda persistence is not very generic.
throw UnsupportedOperationException()
}, SigningServerSchemaService()))
}, SchemaService()))
val hsmSigner = givenSignerSigningAllRequests(signingServiceStorage)
// Poll the database for approved requests
@ -136,7 +136,7 @@ class SigningServiceIntegrationTest {
val database = configureDatabase(makeTestDataSourceProperties(), null, {
// Identity service not needed doorman, corda persistence is not very generic.
throw UnsupportedOperationException()
}, SigningServerSchemaService())
}, SchemaService())
val doorman = startDoorman(NetworkHostAndPort(HOST, 0), database, approveAll = true)
thread(start = true, isDaemon = true) {

View File

@ -1,10 +1,7 @@
package com.r3.corda.doorman.persistence
package com.r3.corda.networkmanage.common.persistence
import org.bouncycastle.pkcs.PKCS10CertificationRequest
import org.hibernate.envers.Audited
import java.security.cert.CertPath
import java.time.Instant
import javax.persistence.*
/**
* Provide certificate signing request storage for the certificate signing server.
@ -58,65 +55,6 @@ interface CertificationRequestStorage {
fun putCertificatePath(requestId: String, certificates: CertPath, signedBy: List<String>)
}
@Entity
@Table(name = "certificate_signing_request", indexes = arrayOf(Index(name = "IDX_PUB_KEY_HASH", columnList = "public_key_hash")))
class CertificateSigningRequest(
@Id
@Column(name = "request_id", length = 64)
var requestId: String = "",
// TODO: Store X500Name with a proper schema.
@Column(name = "legal_name", length = 256)
var legalName: String = "",
@Lob
@Column
var request: ByteArray = ByteArray(0),
@Audited
@Column(name = "status")
@Enumerated(EnumType.STRING)
var status: RequestStatus = RequestStatus.New,
@Audited
@Column(name = "modified_by", length = 512)
@ElementCollection(targetClass = String::class, fetch = FetchType.EAGER)
var modifiedBy: List<String> = emptyList(),
@Audited
@Column(name = "modified_at")
var modifiedAt: Instant? = Instant.now(),
@Audited
@Column(name = "remark", length = 256, nullable = true)
var remark: String? = null,
// TODO: The certificate data can have its own table.
@Embedded
var certificateData: CertificateData? = null
)
@Embeddable
class CertificateData(
@Column(name = "public_key_hash", length = 64, nullable = true)
var publicKeyHash: String? = null,
@Lob
@Column(nullable = true)
var certificatePath: ByteArray? = null,
@Column(name = "certificate_status", nullable = true)
var certificateStatus: CertificateStatus? = null
)
enum class CertificateStatus {
VALID, SUSPENDED, REVOKED
}
enum class RequestStatus {
New, Approved, Rejected, Signed
}
sealed class CertificateResponse {
object NotReady : CertificateResponse()
data class Ready(val certificatePath: CertPath) : CertificateResponse()

View File

@ -1,7 +1,6 @@
package com.r3.corda.doorman.persistence
package com.r3.corda.networkmanage.common.persistence
import com.r3.corda.doorman.hash
import com.r3.corda.doorman.persistence.RequestStatus.*
import com.r3.corda.networkmanage.common.persistence.RequestStatus.*
import net.corda.core.crypto.SecureHash
import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.x500Name

View File

@ -0,0 +1,87 @@
package com.r3.corda.networkmanage.common.persistence
import org.hibernate.envers.Audited
import java.time.Instant
import javax.persistence.*
@Entity
@Table(name = "certificate_signing_request", indexes = arrayOf(Index(name = "IDX_PUB_KEY_HASH", columnList = "public_key_hash")))
class CertificateSigningRequest(
@Id
@Column(name = "request_id", length = 64)
var requestId: String = "",
// TODO: Store X500Name with a proper schema.
@Column(name = "legal_name", length = 256)
var legalName: String = "",
@Lob
@Column
var request: ByteArray = ByteArray(0),
@Audited
@Column(name = "status")
@Enumerated(EnumType.STRING)
var status: RequestStatus = RequestStatus.New,
@Audited
@Column(name = "modified_by", length = 512)
@ElementCollection(targetClass = String::class, fetch = FetchType.EAGER)
var modifiedBy: List<String> = emptyList(),
@Audited
@Column(name = "modified_at")
var modifiedAt: Instant? = Instant.now(),
@Audited
@Column(name = "remark", length = 256, nullable = true)
var remark: String? = null,
// TODO: The certificate data can have its own table.
@Embedded
var certificateData: CertificateData? = null
)
@Embeddable
class CertificateData(
@Column(name = "public_key_hash", length = 64, nullable = true)
var publicKeyHash: String? = null,
@Lob
@Column(nullable = true)
var certificatePath: ByteArray? = null,
@Column(name = "certificate_status", nullable = true)
var certificateStatus: CertificateStatus? = null
)
enum class CertificateStatus {
VALID, SUSPENDED, REVOKED
}
enum class RequestStatus {
New, Approved, Rejected, Signed
}
@Entity
@Table(name = "node_info")
class NodeInfoEntity(
@Id
@Column(name = "node_info_hash", length = 64)
var nodeInfoHash: String = "",
@Lob
@Column(name = "node_info")
var nodeInfo: ByteArray = ByteArray(0)
)
@Entity
@Table(name = "public_key_node_info_link")
class PublicKeyNodeInfoLink(
@Id
@Column(name = "public_key_hash", length = 64)
var publicKeyHash: String = "",
@Column(name = "node_info_hash", length = 64)
var nodeInfoHash: String = ""
)

View File

@ -1,8 +1,7 @@
package com.r3.corda.doorman.persistence
package com.r3.corda.networkmanage.common.persistence
import net.corda.core.node.NodeInfo
import java.security.cert.CertPath
import javax.persistence.*
interface NodeInfoStorage {
/**
@ -27,27 +26,4 @@ interface NodeInfoStorage {
* The [nodeInfo] is keyed by the public key, old node info with the same public key will be replaced by the new node info.
*/
fun putNodeInfo(nodeInfo: NodeInfo)
}
@Entity
@Table(name = "node_info")
class NodeInfoEntity(
@Id
@Column(name = "node_info_hash", length = 64)
var nodeInfoHash: String = "",
@Lob
@Column(name = "node_info")
var nodeInfo: ByteArray = ByteArray(0)
)
@Entity
@Table(name = "public_key_node_info_link")
class PublicKeyNodeInfoLink(
@Id
@Column(name = "public_key_hash", length = 64)
var publicKeyHash: String = "",
@Column(name = "node_info_hash", length = 64)
var nodeInfoHash: String = ""
)
}

View File

@ -1,7 +1,6 @@
package com.r3.corda.doorman.persistence
package com.r3.corda.networkmanage.common.persistence
import com.r3.corda.doorman.buildCertPath
import com.r3.corda.doorman.hash
import com.r3.corda.networkmanage.common.utils.buildCertPath
import net.corda.core.crypto.sha256
import net.corda.core.node.NodeInfo
import net.corda.core.serialization.deserialize

View File

@ -0,0 +1,7 @@
package com.r3.corda.networkmanage.common.persistence
import net.corda.core.crypto.sha256
import java.security.PublicKey
// TODO: replace this with Crypto.hash when its available.
fun PublicKey.hash() = encoded.sha256().toString()

View File

@ -1,15 +1,15 @@
package com.r3.corda.doorman.persistence
package com.r3.corda.networkmanage.common.persistence
import net.corda.core.contracts.ContractState
import net.corda.core.schemas.MappedSchema
import net.corda.core.schemas.PersistentState
import net.corda.node.services.api.SchemaService
class DoormanSchemaService : SchemaService {
class SchemaService : SchemaService {
// Entities for compulsory services
object DoormanServices
object SchemaServices
object DoormanServicesV1 : MappedSchema(schemaFamily = DoormanServices.javaClass, version = 1,
object DoormanServicesV1 : MappedSchema(schemaFamily = SchemaServices.javaClass, version = 1,
mappedTypes = listOf(CertificateSigningRequest::class.java, NodeInfoEntity::class.java, PublicKeyNodeInfoLink::class.java))
override var schemaOptions: Map<MappedSchema, SchemaService.SchemaOptions> = mapOf(Pair(DoormanServicesV1, SchemaService.SchemaOptions()))

View File

@ -0,0 +1,44 @@
package com.r3.corda.networkmanage.common.utils
import com.typesafe.config.Config
import com.typesafe.config.ConfigFactory
import joptsimple.ArgumentAcceptingOptionSpec
import joptsimple.OptionParser
import org.bouncycastle.cert.X509CertificateHolder
import java.io.ByteArrayInputStream
import java.security.cert.CertPath
import java.security.cert.Certificate
import java.security.cert.CertificateFactory
import java.security.cert.X509Certificate
fun Array<out String>.toConfigWithOptions(registerOptions: OptionParser.() -> Unit): Config {
val parser = OptionParser()
val helpOption = parser.acceptsAll(listOf("h", "?", "help"), "show help").forHelp();
registerOptions(parser)
val optionSet = parser.parse(*this)
// Print help and exit on help option.
if (optionSet.has(helpOption)) {
throw ShowHelpException(parser)
}
// Convert all command line options to Config.
return ConfigFactory.parseMap(parser.recognizedOptions().mapValues {
val optionSpec = it.value
if (optionSpec is ArgumentAcceptingOptionSpec<*> && !optionSpec.requiresArgument() && optionSet.has(optionSpec)) true else optionSpec.value(optionSet)
}.filterValues { it != null })
}
class ShowHelpException(val parser: OptionParser) : Exception()
object CertificateUtilities {
fun toX509Certificate(byteArray: ByteArray): X509Certificate {
return CertificateFactory.getInstance("X509").generateCertificate(ByteArrayInputStream(byteArray)) as X509Certificate
}
}
fun X509CertificateHolder.toX509Certificate(): Certificate = CertificateUtilities.toX509Certificate(encoded)
fun buildCertPath(vararg certificates: Certificate): CertPath {
return CertificateFactory.getInstance("X509").generateCertPath(certificates.asList())
}
fun buildCertPath(certPathBytes: ByteArray): CertPath = CertificateFactory.getInstance("X509").generateCertPath(certPathBytes.inputStream())

View File

@ -1,6 +1,6 @@
package com.r3.corda.doorman
package com.r3.corda.networkmanage.doorman
import com.r3.corda.doorman.OptionParserHelper.toConfigWithOptions
import com.r3.corda.networkmanage.common.utils.toConfigWithOptions
import com.typesafe.config.ConfigFactory
import com.typesafe.config.ConfigParseOptions
import net.corda.core.internal.div

View File

@ -1,4 +1,4 @@
package com.r3.corda.doorman
package com.r3.corda.networkmanage.doorman
import com.atlassian.jira.rest.client.api.JiraRestClient
import com.atlassian.jira.rest.client.api.domain.Field

View File

@ -1,17 +1,18 @@
package com.r3.corda.doorman
package com.r3.corda.networkmanage.doorman
import com.atlassian.jira.rest.client.internal.async.AsynchronousJiraRestClientFactory
import com.r3.corda.doorman.DoormanServer.Companion.logger
import com.r3.corda.doorman.persistence.CertificationRequestStorage
import com.r3.corda.doorman.persistence.CertificationRequestStorage.Companion.DOORMAN_SIGNATURE
import com.r3.corda.doorman.persistence.DBCertificateRequestStorage
import com.r3.corda.doorman.persistence.DoormanSchemaService
import com.r3.corda.doorman.persistence.PersistenceNodeInfoStorage
import com.r3.corda.doorman.signer.DefaultCsrHandler
import com.r3.corda.doorman.signer.JiraCsrHandler
import com.r3.corda.doorman.signer.Signer
import com.r3.corda.doorman.webservice.NodeInfoWebService
import com.r3.corda.doorman.webservice.RegistrationWebService
import com.r3.corda.networkmanage.common.persistence.CertificationRequestStorage
import com.r3.corda.networkmanage.common.persistence.CertificationRequestStorage.Companion.DOORMAN_SIGNATURE
import com.r3.corda.networkmanage.common.persistence.DBCertificateRequestStorage
import com.r3.corda.networkmanage.common.persistence.PersistenceNodeInfoStorage
import com.r3.corda.networkmanage.common.persistence.SchemaService
import com.r3.corda.networkmanage.common.utils.ShowHelpException
import com.r3.corda.networkmanage.doorman.DoormanServer.Companion.logger
import com.r3.corda.networkmanage.doorman.signer.DefaultCsrHandler
import com.r3.corda.networkmanage.doorman.signer.JiraCsrHandler
import com.r3.corda.networkmanage.doorman.signer.Signer
import com.r3.corda.networkmanage.doorman.webservice.NodeInfoWebService
import com.r3.corda.networkmanage.doorman.webservice.RegistrationWebService
import net.corda.core.crypto.Crypto
import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.createDirectories
@ -238,7 +239,7 @@ fun main(args: Array<String>) {
keystorePassword,
caPrivateKeyPassword)
DoormanParameters.Mode.DOORMAN -> {
val database = configureDatabase(dataSourceProperties, databaseProperties, { throw UnsupportedOperationException() }, DoormanSchemaService())
val database = configureDatabase(dataSourceProperties, databaseProperties, { throw UnsupportedOperationException() }, SchemaService())
val signer = buildLocalSigner(this)
startDoorman(NetworkHostAndPort(host, port), database, approveAll, signer, jiraConfig)
}

View File

@ -1,11 +1,11 @@
package com.r3.corda.doorman.signer
package com.r3.corda.networkmanage.doorman.signer
import com.r3.corda.doorman.JiraClient
import com.r3.corda.doorman.buildCertPath
import com.r3.corda.doorman.persistence.CertificateResponse
import com.r3.corda.doorman.persistence.CertificationRequestStorage
import com.r3.corda.doorman.persistence.CertificationRequestStorage.Companion.DOORMAN_SIGNATURE
import com.r3.corda.doorman.persistence.RequestStatus
import com.r3.corda.networkmanage.common.persistence.CertificateResponse
import com.r3.corda.networkmanage.common.persistence.CertificationRequestStorage
import com.r3.corda.networkmanage.common.persistence.CertificationRequestStorage.Companion.DOORMAN_SIGNATURE
import com.r3.corda.networkmanage.common.persistence.RequestStatus
import com.r3.corda.networkmanage.common.utils.buildCertPath
import com.r3.corda.networkmanage.doorman.JiraClient
import org.bouncycastle.pkcs.PKCS10CertificationRequest
interface CsrHandler {

View File

@ -1,7 +1,7 @@
package com.r3.corda.doorman.signer
package com.r3.corda.networkmanage.doorman.signer
import com.r3.corda.doorman.buildCertPath
import com.r3.corda.doorman.toX509Certificate
import com.r3.corda.networkmanage.common.utils.buildCertPath
import com.r3.corda.networkmanage.common.utils.toX509Certificate
import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.toX509CertHolder
import net.corda.core.internal.x500Name

View File

@ -1,7 +1,7 @@
package com.r3.corda.doorman.webservice
package com.r3.corda.networkmanage.doorman.webservice
import com.r3.corda.doorman.persistence.NodeInfoStorage
import com.r3.corda.doorman.webservice.NodeInfoWebService.Companion.networkMapPath
import com.r3.corda.networkmanage.common.persistence.NodeInfoStorage
import com.r3.corda.networkmanage.doorman.webservice.NodeInfoWebService.Companion.networkMapPath
import net.corda.core.crypto.Crypto
import net.corda.core.crypto.SignedData
import net.corda.core.node.NodeInfo
@ -25,6 +25,7 @@ class NodeInfoWebService(private val nodeInfoStorage: NodeInfoStorage) {
companion object {
const val networkMapPath = "network-map"
}
@POST
@Path("register")
@Consumes(MediaType.APPLICATION_OCTET_STREAM)

View File

@ -1,8 +1,8 @@
package com.r3.corda.doorman.webservice
package com.r3.corda.networkmanage.doorman.webservice
import com.r3.corda.doorman.DoormanServerStatus
import com.r3.corda.doorman.persistence.CertificateResponse
import com.r3.corda.doorman.signer.CsrHandler
import com.r3.corda.networkmanage.common.persistence.CertificateResponse
import com.r3.corda.networkmanage.doorman.DoormanServerStatus
import com.r3.corda.networkmanage.doorman.signer.CsrHandler
import net.corda.node.utilities.X509Utilities.CORDA_CLIENT_CA
import net.corda.node.utilities.X509Utilities.CORDA_INTERMEDIATE_CA
import net.corda.node.utilities.X509Utilities.CORDA_ROOT_CA

View File

@ -1,16 +1,16 @@
package com.r3.corda.signing
package com.r3.corda.networkmanage.hsm
import com.r3.corda.signing.authentication.Authenticator
import com.r3.corda.signing.authentication.createProvider
import com.r3.corda.signing.configuration.Parameters
import com.r3.corda.signing.configuration.parseParameters
import com.r3.corda.signing.generator.KeyCertificateGenerator
import com.r3.corda.signing.hsm.HsmSigner
import com.r3.corda.signing.menu.Menu
import com.r3.corda.signing.persistence.ApprovedCertificateRequestData
import com.r3.corda.signing.persistence.DBCertificateRequestStorage
import com.r3.corda.signing.persistence.SigningServerSchemaService
import com.r3.corda.signing.utils.mapCryptoServerException
import com.r3.corda.networkmanage.common.persistence.SchemaService
import com.r3.corda.networkmanage.hsm.authentication.Authenticator
import com.r3.corda.networkmanage.hsm.authentication.createProvider
import com.r3.corda.networkmanage.hsm.configuration.Parameters
import com.r3.corda.networkmanage.hsm.configuration.parseParameters
import com.r3.corda.networkmanage.hsm.generator.KeyCertificateGenerator
import com.r3.corda.networkmanage.hsm.menu.Menu
import com.r3.corda.networkmanage.hsm.persistence.CertificateRequestData
import com.r3.corda.networkmanage.hsm.persistence.DBSignedCertificateRequestStorage
import com.r3.corda.networkmanage.hsm.signer.HsmSigner
import com.r3.corda.networkmanage.hsm.utils.mapCryptoServerException
import net.corda.node.utilities.configureDatabase
fun main(args: Array<String>) {
@ -24,11 +24,11 @@ fun run(parameters: Parameters) {
val database = configureDatabase(dataSourceProperties, databaseProperties, {
// Identity service not needed
throw UnsupportedOperationException()
}, SigningServerSchemaService())
}, SchemaService())
val storage = DBCertificateRequestStorage(database)
val storage = DBSignedCertificateRequestStorage(database)
val provider = createProvider()
val sign: (List<ApprovedCertificateRequestData>) -> Unit = {
val sign: (List<CertificateRequestData>) -> Unit = {
val signer = HsmSigner(
storage,
certificateName,
@ -85,7 +85,7 @@ private fun processError(exception: Exception) {
println("An error occured: ${processed.message}")
}
private fun confirmedSign(selectedItems: List<ApprovedCertificateRequestData>): Boolean {
private fun confirmedSign(selectedItems: List<CertificateRequestData>): Boolean {
println("Are you sure you want to sign the following requests:")
selectedItems.forEachIndexed { index, data ->
println("${index + 1} ${data.request.subject}")
@ -102,7 +102,7 @@ private fun confirmedKeyGen(): Boolean {
return result
}
private fun getSelection(toSelect: List<ApprovedCertificateRequestData>): List<ApprovedCertificateRequestData> {
private fun getSelection(toSelect: List<CertificateRequestData>): List<CertificateRequestData> {
print("CSRs to be signed (comma separated list): ")
val line = readLine()
if (line == null) {

View File

@ -1,4 +1,4 @@
package com.r3.corda.signing.authentication
package com.r3.corda.networkmanage.hsm.authentication
/*
* Supported authentication modes

View File

@ -1,7 +1,7 @@
package com.r3.corda.signing.authentication
package com.r3.corda.networkmanage.hsm.authentication
import CryptoServerJCE.CryptoServerProvider
import com.r3.corda.signing.configuration.Parameters
import com.r3.corda.networkmanage.hsm.configuration.Parameters
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.Console

View File

@ -1,36 +1,16 @@
package com.r3.corda.signing.configuration
package com.r3.corda.networkmanage.hsm.configuration
import com.typesafe.config.Config
import com.r3.corda.networkmanage.common.utils.toConfigWithOptions
import com.r3.corda.networkmanage.hsm.authentication.AuthMode
import com.typesafe.config.ConfigFactory
import com.typesafe.config.ConfigParseOptions
import joptsimple.ArgumentAcceptingOptionSpec
import joptsimple.OptionParser
import net.corda.core.internal.div
import net.corda.node.utilities.X509Utilities
import net.corda.nodeapi.config.parseAs
import com.r3.corda.signing.authentication.AuthMode
import java.nio.file.Path
import java.nio.file.Paths
import java.util.*
class ShowHelpException(val parser: OptionParser) : Exception()
fun Array<out String>.toConfigWithOptions(registerOptions: OptionParser.() -> Unit): Config {
val parser = OptionParser()
val helpOption = parser.acceptsAll(listOf("h", "?", "help"), "show help").forHelp();
registerOptions(parser)
val optionSet = parser.parse(*this)
// Print help and exit on help option.
if (optionSet.has(helpOption)) {
throw ShowHelpException(parser)
}
// Convert all command line options to Config.
return ConfigFactory.parseMap(parser.recognizedOptions().mapValues {
val optionSpec = it.value
if (optionSpec is ArgumentAcceptingOptionSpec<*> && !optionSpec.requiresArgument() && optionSet.has(optionSpec)) true else optionSpec.value(optionSet)
}.filterValues { it != null })
}
/**
* Configuration parameters.
*/

View File

@ -1,14 +1,14 @@
package com.r3.corda.signing.generator
package com.r3.corda.networkmanage.hsm.generator
import CryptoServerCXI.CryptoServerCXI
import CryptoServerJCE.CryptoServerProvider
import com.r3.corda.networkmanage.hsm.authentication.Authenticator
import com.r3.corda.networkmanage.hsm.utils.X509Utilities.createIntermediateCert
import com.r3.corda.networkmanage.hsm.utils.X509Utilities.createSelfSignedCACert
import com.r3.corda.networkmanage.hsm.utils.X509Utilities.getAndInitializeKeyStore
import com.r3.corda.networkmanage.hsm.utils.X509Utilities.getCleanEcdsaKeyPair
import com.r3.corda.networkmanage.hsm.utils.X509Utilities.retrieveCertificateAndKeys
import net.corda.node.utilities.addOrReplaceKey
import com.r3.corda.signing.authentication.Authenticator
import com.r3.corda.signing.utils.X509Utilities.createIntermediateCert
import com.r3.corda.signing.utils.X509Utilities.createSelfSignedCACert
import com.r3.corda.signing.utils.X509Utilities.getAndInitializeKeyStore
import com.r3.corda.signing.utils.X509Utilities.getCleanEcdsaKeyPair
import com.r3.corda.signing.utils.X509Utilities.retrieveCertificateAndKeys
import java.security.KeyPair
import java.security.KeyStore
import java.security.PrivateKey

View File

@ -1,4 +1,4 @@
package com.r3.corda.signing.menu
package com.r3.corda.networkmanage.hsm.menu
data class MenuItem(val key: String, val label: String, val action: () -> Unit, val isTerminating: Boolean = false)

View File

@ -0,0 +1,27 @@
package com.r3.corda.networkmanage.hsm.persistence
import com.r3.corda.networkmanage.common.persistence.CertificateSigningRequest
import com.r3.corda.networkmanage.common.persistence.DBCertificateRequestStorage
import com.r3.corda.networkmanage.common.persistence.RequestStatus
import net.corda.node.utilities.CordaPersistence
import org.bouncycastle.pkcs.PKCS10CertificationRequest
import java.security.cert.CertPath
data class CertificateRequestData(val requestId: String, val request: PKCS10CertificationRequest, var certPath: CertPath? = null)
class DBSignedCertificateRequestStorage(database: CordaPersistence) : SignedCertificateRequestStorage {
private val storage = DBCertificateRequestStorage(database)
override fun store(requests: List<CertificateRequestData>, signers: List<String>) {
for ((requestId, _, certPath) in requests) {
storage.putCertificatePath(requestId, certPath!!, signers)
}
}
override fun getApprovedRequests(): List<CertificateRequestData> {
return storage.getRequests(RequestStatus.Approved).map { it.toRequestData() }
}
private fun CertificateSigningRequest.toRequestData() = CertificateRequestData(requestId, PKCS10CertificationRequest(request))
}

View File

@ -1,19 +1,20 @@
package com.r3.corda.signing.persistence
package com.r3.corda.networkmanage.hsm.persistence
/**
* Provides an API for database level manipulations of CSRs (Certificate Signing Requests).
* Provides an API for storing signed CSRs (Certificate Signing Requests).
*/
interface CertificateRequestStorage {
interface SignedCertificateRequestStorage {
/**
* Returns all certificate signing requests that have been approved for signing.
*/
fun getApprovedRequests(): List<ApprovedCertificateRequestData>
fun getApprovedRequests(): List<CertificateRequestData>
/**
* Marks the database CSR entries as signed. Also it persists the certificate and the signature in the database.
*
* @param requests Requests that are to be marked as signed.
* @param requests Signed requests that are to be stored.
* @param signers List of user names that signed those requests. To be specific, each request has been signed by all of those users.
*/
fun sign(requests: List<ApprovedCertificateRequestData>, signers: List<String>)
fun store(requests: List<CertificateRequestData>, signers: List<String>)
}

View File

@ -1,18 +1,18 @@
package com.r3.corda.signing.hsm
package com.r3.corda.networkmanage.hsm.signer
import com.r3.corda.signing.authentication.Authenticator
import com.r3.corda.signing.authentication.readPassword
import com.r3.corda.signing.persistence.ApprovedCertificateRequestData
import com.r3.corda.signing.persistence.DBCertificateRequestStorage
import com.r3.corda.signing.utils.X509Utilities.buildCertPath
import com.r3.corda.signing.utils.X509Utilities.createClientCertificate
import com.r3.corda.signing.utils.X509Utilities.getAndInitializeKeyStore
import com.r3.corda.signing.utils.X509Utilities.retrieveCertificateAndKeys
import com.r3.corda.networkmanage.hsm.authentication.Authenticator
import com.r3.corda.networkmanage.hsm.authentication.readPassword
import com.r3.corda.networkmanage.hsm.persistence.CertificateRequestData
import com.r3.corda.networkmanage.hsm.persistence.SignedCertificateRequestStorage
import com.r3.corda.networkmanage.hsm.utils.X509Utilities.buildCertPath
import com.r3.corda.networkmanage.hsm.utils.X509Utilities.createClientCertificate
import com.r3.corda.networkmanage.hsm.utils.X509Utilities.getAndInitializeKeyStore
import com.r3.corda.networkmanage.hsm.utils.X509Utilities.retrieveCertificateAndKeys
/**
* Encapsulates certificate signing logic
*/
class HsmSigner(private val storage: DBCertificateRequestStorage,
class HsmSigner(private val storage: SignedCertificateRequestStorage,
private val caCertificateName: String,
private val caPrivateKeyPass: String?,
private val caParentCertificateName: String,
@ -29,7 +29,7 @@ class HsmSigner(private val storage: DBCertificateRequestStorage,
* and sets the certificate field with an appropriate value.
* @param toSign list of approved certificates to be signed
*/
override fun sign(toSign: List<ApprovedCertificateRequestData>) {
override fun sign(toSign: List<CertificateRequestData>) {
authenticator.connectAndAuthenticate { provider, signers ->
val keyStore = getAndInitializeKeyStore(provider, keyStorePassword)
// This should be changed once we allow for more certificates in the chain. Preferably we should use
@ -40,10 +40,10 @@ class HsmSigner(private val storage: DBCertificateRequestStorage,
toSign.forEach {
it.certPath = buildCertPath(createClientCertificate(caCertAndKey, it.request, validDays, provider), caParentCertificate)
}
storage.sign(toSign, signers)
storage.store(toSign, signers)
println("The following certificates have been signed by $signers:")
toSign.forEachIndexed { index, data ->
println("${index+1} ${data.request.subject}")
println("${index + 1} ${data.request.subject}")
}
}
}

View File

@ -0,0 +1,15 @@
package com.r3.corda.networkmanage.hsm.signer
import com.r3.corda.networkmanage.hsm.persistence.CertificateRequestData
/**
* Encapsulates the logic related to the certificate signing process.
*/
interface Signer {
/**
* Signs the provided list of [CertificateRequestData]
*/
fun sign(toSign: List<CertificateRequestData>)
}

View File

@ -1,7 +1,7 @@
package com.r3.corda.signing.utils
package com.r3.corda.networkmanage.hsm.utils
import CryptoServerAPI.CryptoServerException
import java.util.HashMap
import java.util.*
/**
* CryptoServer error translator object.

View File

@ -1,4 +1,4 @@
package com.r3.corda.signing.utils
package com.r3.corda.networkmanage.hsm.utils
import CryptoServerJCE.CryptoServerProvider
import net.corda.core.identity.CordaX500Name
@ -74,7 +74,7 @@ object X509Utilities {
purposes.add(KeyPurposeId.id_kp_serverAuth)
purposes.add(KeyPurposeId.id_kp_clientAuth)
purposes.add(KeyPurposeId.anyExtendedKeyUsage)
builder.addExtension(Extension.extendedKeyUsage, false, DERSequence(purposes))
builder.addExtension(Extension.extendedKeyUsage, false, DERSequence(purposes).toASN1Primitive())
val cert = signCertificate(builder, keyPair.private, provider)

View File

@ -1,11 +1,8 @@
package com.r3.corda.doorman.internal.persistence
package com.r3.corda.networkmanage.common.persistence
import com.r3.corda.doorman.buildCertPath
import com.r3.corda.doorman.persistence.CertificateSigningRequest
import com.r3.corda.doorman.persistence.DBCertificateRequestStorage
import com.r3.corda.doorman.persistence.DoormanSchemaService
import com.r3.corda.doorman.persistence.RequestStatus
import com.r3.corda.doorman.toX509Certificate
import com.r3.corda.networkmanage.common.persistence.CertificationRequestStorage.Companion.DOORMAN_SIGNATURE
import com.r3.corda.networkmanage.common.utils.buildCertPath
import com.r3.corda.networkmanage.common.utils.toX509Certificate
import net.corda.core.crypto.Crypto
import net.corda.core.crypto.SecureHash
import net.corda.core.identity.CordaX500Name
@ -24,7 +21,6 @@ import org.junit.Test
import java.security.KeyPair
import java.util.*
import kotlin.test.*
import com.r3.corda.doorman.persistence.CertificationRequestStorage.Companion.DOORMAN_SIGNATURE
class DBCertificateRequestStorageTest {
private lateinit var storage: DBCertificateRequestStorage
@ -32,7 +28,7 @@ class DBCertificateRequestStorageTest {
@Before
fun startDb() {
persistence = configureDatabase(makeTestDataSourceProperties(), makeTestDatabaseProperties(), { throw UnsupportedOperationException() }, DoormanSchemaService())
persistence = configureDatabase(makeTestDataSourceProperties(), makeTestDatabaseProperties(), { throw UnsupportedOperationException() }, SchemaService())
storage = DBCertificateRequestStorage(persistence)
}

View File

@ -1,10 +1,8 @@
package com.r3.corda.doorman.internal.persistence
package com.r3.corda.networkmanage.common.persistence
import com.r3.corda.doorman.buildCertPath
import com.r3.corda.doorman.hash
import com.r3.corda.doorman.persistence.*
import com.r3.corda.doorman.persistence.CertificationRequestStorage.Companion.DOORMAN_SIGNATURE
import com.r3.corda.doorman.toX509Certificate
import com.r3.corda.networkmanage.common.persistence.CertificationRequestStorage.Companion.DOORMAN_SIGNATURE
import com.r3.corda.networkmanage.common.utils.buildCertPath
import com.r3.corda.networkmanage.common.utils.toX509Certificate
import net.corda.core.crypto.Crypto
import net.corda.core.crypto.sha256
import net.corda.core.identity.CordaX500Name
@ -59,7 +57,7 @@ class PersistenceNodeInfoStorageTest {
@Before
fun startDb() {
persistence = configureDatabase(makeTestDataSourceProperties(), makeTestDatabaseProperties(), { throw UnsupportedOperationException() }, DoormanSchemaService())
persistence = configureDatabase(makeTestDataSourceProperties(), makeTestDatabaseProperties(), { throw UnsupportedOperationException() }, SchemaService())
nodeInfoStorage = PersistenceNodeInfoStorage(persistence)
requestStorage = DBCertificateRequestStorage(persistence)
}

View File

@ -1,12 +1,14 @@
package com.r3.corda.doorman
package com.r3.corda.networkmanage.doorman
import com.nhaarman.mockito_kotlin.any
import com.nhaarman.mockito_kotlin.mock
import com.nhaarman.mockito_kotlin.times
import com.nhaarman.mockito_kotlin.verify
import com.r3.corda.doorman.persistence.*
import com.r3.corda.doorman.signer.DefaultCsrHandler
import com.r3.corda.doorman.signer.Signer
import com.r3.corda.networkmanage.common.persistence.*
import com.r3.corda.networkmanage.common.utils.buildCertPath
import com.r3.corda.networkmanage.common.utils.toX509Certificate
import com.r3.corda.networkmanage.doorman.signer.DefaultCsrHandler
import com.r3.corda.networkmanage.doorman.signer.Signer
import net.corda.core.crypto.Crypto
import net.corda.core.identity.CordaX500Name
import net.corda.node.utilities.X509Utilities

View File

@ -1,4 +1,4 @@
package com.r3.corda.doorman
package com.r3.corda.networkmanage.doorman
import com.typesafe.config.ConfigException
import org.junit.Test
@ -9,8 +9,8 @@ import kotlin.test.assertFailsWith
class DoormanParametersTest {
private val testDummyPath = ".${File.separator}testDummyPath.jks"
private val validConfigPath = File(javaClass.getResource("/node.conf").toURI()).absolutePath
private val invalidConfigPath = File(javaClass.getResource("/node_fail.conf").toURI()).absolutePath
private val validConfigPath = File(javaClass.getResource("/doorman.conf").toURI()).absolutePath
private val invalidConfigPath = File(javaClass.getResource("/doorman_fail.conf").toURI()).absolutePath
@Test
fun `parse mode flag arg correctly`() {

View File

@ -1,11 +1,13 @@
package com.r3.corda.doorman
package com.r3.corda.networkmanage.doorman
import com.nhaarman.mockito_kotlin.any
import com.nhaarman.mockito_kotlin.mock
import com.nhaarman.mockito_kotlin.times
import com.nhaarman.mockito_kotlin.verify
import com.r3.corda.doorman.persistence.NodeInfoStorage
import com.r3.corda.doorman.webservice.NodeInfoWebService
import com.r3.corda.networkmanage.common.persistence.NodeInfoStorage
import com.r3.corda.networkmanage.common.utils.buildCertPath
import com.r3.corda.networkmanage.common.utils.toX509Certificate
import com.r3.corda.networkmanage.doorman.webservice.NodeInfoWebService
import net.corda.core.crypto.*
import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.PartyAndCertificate

View File

@ -1,9 +1,11 @@
package com.r3.corda.doorman
package com.r3.corda.networkmanage.doorman
import com.nhaarman.mockito_kotlin.*
import com.r3.corda.doorman.persistence.CertificateResponse
import com.r3.corda.doorman.signer.CsrHandler
import com.r3.corda.doorman.webservice.RegistrationWebService
import com.r3.corda.networkmanage.common.persistence.CertificateResponse
import com.r3.corda.networkmanage.common.utils.buildCertPath
import com.r3.corda.networkmanage.common.utils.toX509Certificate
import com.r3.corda.networkmanage.doorman.signer.CsrHandler
import com.r3.corda.networkmanage.doorman.webservice.RegistrationWebService
import net.corda.core.crypto.Crypto
import net.corda.core.crypto.SecureHash
import net.corda.core.identity.CordaX500Name

View File

@ -1,4 +1,4 @@
package com.r3.corda.signing.authentication
package com.r3.corda.networkmanage.hsm.authentication
import CryptoServerCXI.CryptoServerCXI
import CryptoServerJCE.CryptoServerProvider

View File

@ -1,15 +1,15 @@
package com.r3.corda.signing.configuration
package com.r3.corda.networkmanage.hsm.configuration
import com.r3.corda.networkmanage.hsm.authentication.AuthMode
import com.typesafe.config.ConfigException
import com.r3.corda.signing.authentication.AuthMode
import org.junit.Test
import java.io.File
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
class ConfigurationTest {
private val validConfigPath = File(javaClass.getResource("/signing_service.conf").toURI()).absolutePath
private val invalidConfigPath = File(javaClass.getResource("/signing_service_fail.conf").toURI()).absolutePath
private val validConfigPath = File(javaClass.getResource("/hsm.conf").toURI()).absolutePath
private val invalidConfigPath = File(javaClass.getResource("/hsm_fail.conf").toURI()).absolutePath
@Test
fun `authMode is parsed correctly`() {

View File

@ -43,9 +43,8 @@ include 'samples:simm-valuation-demo'
include 'samples:notary-demo'
include 'samples:bank-of-corda-demo'
include 'cordform-common'
include 'doorman'
include 'network-management'
include 'verify-enclave'
include 'sgx-jvm/hsm-tool'
include 'signing-server'
include 'perftestcordapp'

View File

@ -1,91 +0,0 @@
ext {
// We use Corda release artifact dependencies instead of project dependencies to make sure each doorman releases are
// align with the corresponding Corda release.
corda_dependency_version = '2.0-20171017.135310-6'
}
version "$corda_dependency_version"
apply plugin: 'us.kirchmeier.capsule'
apply plugin: 'kotlin'
repositories {
mavenLocal()
mavenCentral()
maven {
url 'http://oss.sonatype.org/content/repositories/snapshots'
}
jcenter()
maven {
url 'http://ci-artifactory.corda.r3cev.com/artifactory/corda-dev'
}
}
configurations{
integrationTestCompile.extendsFrom testCompile
integrationTestRuntime.extendsFrom testRuntime
}
sourceSets{
integrationTest {
kotlin {
compileClasspath += main.output + test.output
runtimeClasspath += main.output + test.output
srcDir file('src/integration-test/kotlin')
}
java {
compileClasspath += main.output + test.output
runtimeClasspath += main.output + test.output
srcDir file('src/integration-test/java')
}
resources {
srcDir file('src/integration-test/resources')
}
}
}
task buildSigningServerJAR(type: FatCapsule, dependsOn: 'jar') {
group = 'build'
applicationClass 'com.r3.corda.signing.MainKt'
capsuleManifest {
applicationVersion = corda_dependency_version
systemProperties['visualvm.display.name'] = 'Signing Server'
minJavaVersion = '1.8.0'
jvmArgs = ['-XX:+UseG1GC']
}
// Make the resulting JAR file directly executable on UNIX by prepending a shell script to it.
// This lets you run the file like so: ./corda.jar
// Other than being slightly less typing, this has one big advantage: Ctrl-C works properly in the terminal.
reallyExecutable { trampolining() }
}
dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
compile fileTree(dir: 'libs', include: '*.jar')
compile "net.corda:corda-core:$corda_dependency_version"
compile "net.corda:corda-node:$corda_dependency_version"
compile "net.corda:corda-node-api:$corda_dependency_version"
testCompile "net.corda:corda-test-utils:$corda_dependency_version"
testCompile "net.corda:corda-node-driver:$corda_dependency_version"
// Log4J: logging framework (with SLF4J bindings)
compile "org.apache.logging.log4j:log4j-slf4j-impl:${log4j_version}"
compile "org.apache.logging.log4j:log4j-core:${log4j_version}"
compile "org.apache.logging.log4j:log4j-web:${log4j_version}"
// JOpt: for command line flags.
compile "net.sf.jopt-simple:jopt-simple:5.0.2"
// TypeSafe Config: for simple and human friendly config files.
compile "com.typesafe:config:1.3.0"
// Hibernate audit plugin
compile "org.hibernate:hibernate-envers:5.2.11.Final"
// Unit testing helpers.
testCompile 'junit:junit:4.12'
testCompile "org.assertj:assertj-core:${assertj_version}"
integrationTestCompile project(':doorman')
}

View File

@ -1,15 +0,0 @@
package com.r3.corda.signing.hsm
import com.r3.corda.signing.persistence.ApprovedCertificateRequestData
/**
* Encapsulates the logic related to the certificate signing process.
*/
interface Signer {
/**
* Signs the provided list of [ApprovedCertificateRequestData]
*/
fun sign(toSign: List<ApprovedCertificateRequestData>)
}

View File

@ -1,102 +0,0 @@
package com.r3.corda.signing.persistence
import net.corda.node.utilities.CordaPersistence
import org.bouncycastle.pkcs.PKCS10CertificationRequest
import org.hibernate.envers.Audited
import java.security.cert.CertPath
import java.sql.Connection
import java.time.Instant
import javax.persistence.*
import javax.persistence.criteria.CriteriaBuilder
import javax.persistence.criteria.Path
import javax.persistence.criteria.Predicate
data class ApprovedCertificateRequestData(val requestId: String, val request: PKCS10CertificationRequest, var certPath: CertPath? = null)
class DBCertificateRequestStorage(private val database: CordaPersistence) : CertificateRequestStorage {
enum class Status {
Approved, Signed
}
@Entity
@Table(name = "certificate_signing_request")
class CertificateSigningRequest(
@Id
@Column(name = "request_id", length = 64)
var requestId: String = "",
@Lob
@Column
var request: ByteArray = ByteArray(0),
@Lob
@Column(nullable = true)
var certificatePath: ByteArray? = null,
@Audited
@Column(name = "status")
@Enumerated(EnumType.STRING)
var status: Status = Status.Approved,
@Audited
@Column(name = "modified_by", length = 512)
@ElementCollection(targetClass = String::class, fetch = FetchType.EAGER)
var modifiedBy: List<String> = emptyList(),
@Audited
@Column(name = "modified_at")
var modifiedAt: Instant? = Instant.now()
)
override fun getApprovedRequests(): List<ApprovedCertificateRequestData> {
return getRequestIdsByStatus(Status.Approved)
}
override fun sign(requests: List<ApprovedCertificateRequestData>, signers: List<String>) {
requests.forEach {
database.transaction(Connection.TRANSACTION_SERIALIZABLE) {
val request = singleRequestWhere { builder, path ->
builder.and(
builder.equal(path.get<String>(CertificateSigningRequest::requestId.name), it.requestId),
builder.equal(path.get<String>(CertificateSigningRequest::status.name), Status.Approved)
)
}
if (request != null) {
val now = Instant.now()
request.certificatePath = it.certPath?.encoded
request.status = Status.Signed
request.modifiedAt = now
request.modifiedBy = signers
session.update(request)
}
}
}
}
private fun singleRequestWhere(predicate: (CriteriaBuilder, Path<CertificateSigningRequest>) -> Predicate): CertificateSigningRequest? {
return database.transaction {
val builder = session.criteriaBuilder
val criteriaQuery = builder.createQuery(CertificateSigningRequest::class.java)
val query = criteriaQuery.from(CertificateSigningRequest::class.java).run {
criteriaQuery.where(predicate(builder, this))
}
session.createQuery(query).uniqueResultOptional().orElse(null)
}
}
private fun getRequestIdsByStatus(status: Status): List<ApprovedCertificateRequestData> {
return database.transaction {
val builder = session.criteriaBuilder
val query = builder.createQuery(CertificateSigningRequest::class.java).run {
from(CertificateSigningRequest::class.java).run {
where(builder.equal(get<Status>(CertificateSigningRequest::status.name), status))
}
}
session.createQuery(query).setLockMode(LockModeType.PESSIMISTIC_WRITE).resultList.map { it.toRequestData() }
}
}
private fun CertificateSigningRequest.toRequestData() = ApprovedCertificateRequestData(requestId, PKCS10CertificationRequest(request))
}

View File

@ -1,20 +0,0 @@
package com.r3.corda.signing.persistence
import net.corda.core.contracts.ContractState
import net.corda.core.schemas.MappedSchema
import net.corda.core.schemas.PersistentState
import net.corda.node.services.api.SchemaService
class SigningServerSchemaService: SchemaService {
// Entities for compulsory services
object SigningServerServices
object SigningServerServicesV1 : MappedSchema(schemaFamily = SigningServerServices.javaClass, version = 1,
mappedTypes = listOf(DBCertificateRequestStorage.CertificateSigningRequest::class.java))
override val schemaOptions: Map<MappedSchema, SchemaService.SchemaOptions> = mapOf(Pair(SigningServerServicesV1, SchemaService.SchemaOptions()))
override fun selectSchemas(state: ContractState): Iterable<MappedSchema> = setOf(SigningServerServicesV1)
override fun generateMappedObject(state: ContractState, schema: MappedSchema): PersistentState = throw UnsupportedOperationException()
}

View File

@ -1,172 +0,0 @@
package com.r3.corda.signing.persistence
import com.r3.corda.signing.persistence.DBCertificateRequestStorage.CertificateSigningRequest
import com.r3.corda.signing.persistence.DBCertificateRequestStorage.Status
import com.r3.corda.signing.utils.X509Utilities.buildCertPath
import net.corda.core.crypto.Crypto
import net.corda.core.crypto.SecureHash
import net.corda.core.identity.CordaX500Name
import net.corda.node.utilities.CertificateType
import net.corda.node.utilities.CordaPersistence
import net.corda.node.utilities.X509Utilities
import net.corda.node.utilities.configureDatabase
import org.bouncycastle.asn1.x509.GeneralName
import org.bouncycastle.asn1.x509.GeneralSubtree
import org.bouncycastle.asn1.x509.NameConstraints
import org.bouncycastle.cert.X509CertificateHolder
import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest
import org.junit.After
import org.junit.Before
import org.junit.Test
import java.io.ByteArrayInputStream
import java.security.cert.Certificate
import java.security.cert.CertificateFactory
import java.security.cert.X509Certificate
import java.util.*
import javax.persistence.criteria.CriteriaBuilder
import javax.persistence.criteria.Path
import javax.persistence.criteria.Predicate
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
class DBCertificateRequestStorageTest {
private val intermediateCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
private val intermediateCACert = X509Utilities.createSelfSignedCACertificate(CordaX500Name(commonName = "Corda Node Intermediate CA", organisation = "R3 Ltd", locality = "London", country = "GB"), intermediateCAKey)
private lateinit var storage: DBCertificateRequestStorage
private lateinit var persistence: CordaPersistence
@Before
fun startDb() {
persistence = configureDatabase(makeTestDataSourceProperties(), makeTestDatabaseProperties(), { throw UnsupportedOperationException() }, SigningServerSchemaService())
storage = DBCertificateRequestStorage(persistence)
}
@After
fun closeDb() {
persistence.close()
}
@Test
fun `getApprovedRequests returns only requests with status APPROVED`() {
// given
(1..10).forEach {
createAndPersistRequest("Bank$it", Status.Approved)
}
(11..15).forEach {
createAndPersistRequest("Bank$it", Status.Signed)
}
// when
val result = storage.getApprovedRequests()
// then
assertEquals(10, result.size)
result.forEach {
val request = getRequestById(it.requestId)
assertNotNull(request)
assertEquals(Status.Approved, request?.status)
}
}
@Test
fun `sign changes the status of requests to SIGNED`() {
// given
(1..10).map {
createAndPersistRequest("Bank$it")
}
val requests = storage.getApprovedRequests()
// Create a signed certificate
requests.forEach { certifyAndSign(it) }
val signers = listOf("TestUserA", "TestUserB")
// when
storage.sign(requests, signers)
// then
requests.forEach {
val request = getRequestById(it.requestId)
assertNotNull(request)
assertEquals(Status.Signed, request?.status)
assertEquals(signers.toString(), request?.modifiedBy.toString())
assertNotNull(request?.certificatePath)
}
}
private fun certifyAndSign(approvedRequestData: ApprovedCertificateRequestData) {
JcaPKCS10CertificationRequest(approvedRequestData.request).run {
val nameConstraints = NameConstraints(arrayOf(GeneralSubtree(GeneralName(GeneralName.directoryName, subject))), arrayOf())
approvedRequestData.certPath = buildCertPath(
X509Utilities.createCertificate(
CertificateType.CLIENT_CA,
intermediateCACert,
intermediateCAKey,
subject,
publicKey,
nameConstraints = nameConstraints).toX509Certificate())
}
}
private fun getRequestById(requestId: String): CertificateSigningRequest? {
return persistence.transaction {
singleRequestWhere { builder, path ->
builder.equal(path.get<String>(CertificateSigningRequest::requestId.name), requestId)
}
}
}
private fun singleRequestWhere(predicate: (CriteriaBuilder, Path<CertificateSigningRequest>) -> Predicate): CertificateSigningRequest? {
return persistence.transaction {
val builder = session.criteriaBuilder
val criteriaQuery = builder.createQuery(CertificateSigningRequest::class.java)
val query = criteriaQuery.from(CertificateSigningRequest::class.java).run {
criteriaQuery.where(predicate(builder, this))
}
session.createQuery(query).uniqueResultOptional().orElse(null)
}
}
private fun createAndPersistRequest(legalName: String, status: Status = Status.Approved): String {
val requestId = SecureHash.randomSHA256().toString()
persistence.transaction {
val keyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
val x500Name = CordaX500Name(organisation = legalName, locality = "London", country = "GB")
session.save(CertificateSigningRequest(
requestId = requestId,
status = status,
request = X509Utilities.createCertificateSigningRequest(x500Name, "my@mail.com", keyPair).encoded
))
}
return requestId
}
private fun makeTestDataSourceProperties(nodeName: String = SecureHash.randomSHA256().toString()): Properties {
val props = Properties()
props.setProperty("dataSourceClassName", "org.h2.jdbcx.JdbcDataSource")
props.setProperty("dataSource.url", "jdbc:h2:mem:${nodeName}_persistence;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE")
props.setProperty("dataSource.user", "sa")
props.setProperty("dataSource.password", "")
return props
}
private fun makeTestDatabaseProperties(key: String? = null, value: String? = null): Properties {
val props = Properties()
props.setProperty("transactionIsolationLevel", "repeatableRead") //for other possible values see net.corda.node.utilities.CordaPeristence.parserTransactionIsolationLevel(String)
if (key != null) {
props.setProperty(key, value)
}
return props
}
}
private object CertificateUtilities {
fun toX509Certificate(byteArray: ByteArray): X509Certificate {
return CertificateFactory.getInstance("X509").generateCertificate(ByteArrayInputStream(byteArray)) as X509Certificate
}
}
/**
* Converts [X509CertificateHolder] to standard Java [Certificate]
*/
private fun X509CertificateHolder.toX509Certificate(): Certificate = CertificateUtilities.toX509Certificate(encoded)