From 549a952cc07f67e12e23b981b692b0bb3bbf9421 Mon Sep 17 00:00:00 2001 From: Patrick Kuo Date: Tue, 1 Nov 2016 10:23:26 +0000 Subject: [PATCH 1/4] Uses config file instead of passing in arguments Added H2 DB persistence Added background thread to approve request periodically --- netpermission/build.gradle | 18 +++++ .../kotlin/com/r3corda/netpermission/Main.kt | 71 +++++++++++----- .../internal/CertificateSigningService.kt | 10 +-- .../CertificationRequestStorage.kt | 20 +++-- .../DBCertificateRequestStorage.kt | 79 ++++++++++++++++++ .../InMemoryCertificationRequestStorage.kt | 20 +++-- .../src/main/resources/reference.conf | 14 ++++ .../CertificateSigningServiceTest.kt | 43 ++++++---- .../DBCertificateRequestStorageTest.kt | 81 +++++++++++++++++++ 9 files changed, 302 insertions(+), 54 deletions(-) create mode 100644 netpermission/src/main/kotlin/com/r3corda/netpermission/internal/persistence/DBCertificateRequestStorage.kt create mode 100644 netpermission/src/main/resources/reference.conf create mode 100644 netpermission/src/test/kotlin/com/r3corda/netpermission/internal/persistence/DBCertificateRequestStorageTest.kt diff --git a/netpermission/build.gradle b/netpermission/build.gradle index c712f3ece4..d48d0242eb 100644 --- a/netpermission/build.gradle +++ b/netpermission/build.gradle @@ -23,10 +23,25 @@ task buildCertSignerJAR(type: FatCapsule, dependsOn: 'jar') { } } +sourceSets { + main { + resources { + srcDir "../config/dev" + } + } + test { + resources { + srcDir "../config/test" + } + } +} + dependencies { compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" compile project(":core") + compile project(":node") + testCompile project(":test-utils") // Log4J: logging framework (with SLF4J bindings) compile "org.apache.logging.log4j:log4j-slf4j-impl:${log4j_version}" @@ -46,6 +61,9 @@ dependencies { // 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" + // Unit testing helpers. testCompile 'junit:junit:4.12' testCompile "org.assertj:assertj-core:${assertj_version}" diff --git a/netpermission/src/main/kotlin/com/r3corda/netpermission/Main.kt b/netpermission/src/main/kotlin/com/r3corda/netpermission/Main.kt index 9771201f81..3f9afcef43 100644 --- a/netpermission/src/main/kotlin/com/r3corda/netpermission/Main.kt +++ b/netpermission/src/main/kotlin/com/r3corda/netpermission/Main.kt @@ -2,11 +2,16 @@ package com.r3corda.netpermission import com.google.common.net.HostAndPort import com.r3corda.core.crypto.X509Utilities -import com.r3corda.core.utilities.LogHelper +import com.r3corda.core.utilities.debug import com.r3corda.core.utilities.loggerFor import com.r3corda.netpermission.internal.CertificateSigningService -import com.r3corda.netpermission.internal.persistence.InMemoryCertificationRequestStorage +import com.r3corda.netpermission.internal.persistence.DBCertificateRequestStorage +import com.r3corda.node.services.config.ConfigHelper +import com.r3corda.node.services.config.getProperties +import com.r3corda.node.utilities.configureDatabase +import joptsimple.ArgumentAcceptingOptionSpec import joptsimple.OptionParser +import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest import org.eclipse.jetty.server.Server import org.eclipse.jetty.server.ServerConnector import org.eclipse.jetty.server.handler.HandlerCollection @@ -17,6 +22,7 @@ import org.glassfish.jersey.servlet.ServletContainer import java.io.Closeable import java.net.InetSocketAddress import java.nio.file.Paths +import kotlin.concurrent.thread import kotlin.system.exitProcess /** @@ -25,6 +31,7 @@ import kotlin.system.exitProcess * The Intermediate CA certificate,Intermediate CA private key and Root CA Certificate should use alias name specified in [X509Utilities] */ class CertificateSigningServer(val webServerAddr: HostAndPort, val certSigningService: CertificateSigningService) : Closeable { + companion object { val log = loggerFor() fun Server.hostAndPort(): HostAndPort { @@ -68,20 +75,11 @@ class CertificateSigningServer(val webServerAddr: HostAndPort, val certSigningSe object ParamsSpec { val parser = OptionParser() - val host = parser.accepts("host", "The hostname permissioning server will be running on.") - .withRequiredArg().defaultsTo("localhost") - val port = parser.accepts("port", "The port number permissioning server will be running on.") - .withRequiredArg().ofType(Int::class.java).defaultsTo(0) - val keystorePath = parser.accepts("keystore", "The path to the keyStore containing and root certificate, intermediate CA certificate and private key.") - .withRequiredArg().required() - val storePassword = parser.accepts("storePassword", "Keystore's password.") - .withRequiredArg().required() - val caKeyPassword = parser.accepts("caKeyPassword", "Intermediate CA private key password.") - .withRequiredArg().required() + val basedir: ArgumentAcceptingOptionSpec? = parser.accepts("basedir", "Overriding configuration file path.") + .withRequiredArg() } fun main(args: Array) { - LogHelper.setLevel(CertificateSigningServer::class) val log = CertificateSigningServer.log log.info("Starting certificate signing server.") try { @@ -91,17 +89,48 @@ fun main(args: Array) { ParamsSpec.parser.printHelpOn(System.out) exitProcess(1) }.run { - // Load keystore from input path, default to Dev keystore from jar resource if path not defined. - val storePassword = valueOf(ParamsSpec.storePassword) - val keyPassword = valueOf(ParamsSpec.caKeyPassword) - val keystore = X509Utilities.loadKeyStore(Paths.get(valueOf(ParamsSpec.keystorePath)).normalize(), storePassword) - val intermediateCACertAndKey = X509Utilities.loadCertificateAndKey(keystore, keyPassword, X509Utilities.CORDA_INTERMEDIATE_CA_PRIVATE_KEY) + val basedir = Paths.get(valueOf(ParamsSpec.basedir) ?: ".") + val config = ConfigHelper.loadConfig(basedir) + + val keystore = X509Utilities.loadKeyStore(Paths.get(config.getString("keystorePath")).normalize(), config.getString("keyStorePassword")) + val intermediateCACertAndKey = X509Utilities.loadCertificateAndKey(keystore, config.getString("caKeyPassword"), X509Utilities.CORDA_INTERMEDIATE_CA_PRIVATE_KEY) val rootCA = keystore.getCertificateChain(X509Utilities.CORDA_INTERMEDIATE_CA_PRIVATE_KEY).last() - // TODO: Create a proper request storage using database or other storage technology. - val service = CertificateSigningService(intermediateCACertAndKey, rootCA, InMemoryCertificationRequestStorage()) + // Create DB connection. + val (datasource, database) = configureDatabase(config.getProperties("dataSourceProperties")) - CertificateSigningServer(HostAndPort.fromParts(valueOf(ParamsSpec.host), valueOf(ParamsSpec.port)), service).use { + val storage = DBCertificateRequestStorage(database) + val service = CertificateSigningService(intermediateCACertAndKey, rootCA, storage) + + // Background thread approving all request periodically. + var stopSigner = false + val certSinger = if (config.getBoolean("approveAll")) { + thread { + while (!stopSigner) { + Thread.sleep(1000) + for (id in storage.pendingRequestIds()) { + storage.saveCertificate(id, { + JcaPKCS10CertificationRequest(it.request).run { + X509Utilities.createServerCert(subject, publicKey, intermediateCACertAndKey, + if (it.ipAddr == it.hostName) listOf() else listOf(it.hostName), listOf(it.ipAddr)) + } + }) + log.debug { "Approved $id" } + } + } + log.debug { "Certificate Signer thread stopped." } + } + } else { + null + } + + CertificateSigningServer(HostAndPort.fromParts(config.getString("host"), config.getInt("port")), service).use { + Runtime.getRuntime().addShutdownHook(thread(false) { + stopSigner = true + certSinger?.join() + it.close() + datasource.close() + }) it.server.join() } } diff --git a/netpermission/src/main/kotlin/com/r3corda/netpermission/internal/CertificateSigningService.kt b/netpermission/src/main/kotlin/com/r3corda/netpermission/internal/CertificateSigningService.kt index d83b86ae93..9d34e61836 100644 --- a/netpermission/src/main/kotlin/com/r3corda/netpermission/internal/CertificateSigningService.kt +++ b/netpermission/src/main/kotlin/com/r3corda/netpermission/internal/CertificateSigningService.kt @@ -53,14 +53,8 @@ class CertificateSigningService(val intermediateCACertAndKey: X509Utilities.CACe @Path("certificate/{var}") @Produces(MediaType.APPLICATION_OCTET_STREAM) fun retrieveCert(@PathParam("var") requestId: String): Response { - val data = storage.getApprovedRequest(requestId) - return if (data != null) { - val clientCert = storage.getOrElseCreateCertificate(requestId) { - JcaPKCS10CertificationRequest(data.request).run { - X509Utilities.createServerCert(subject, publicKey, intermediateCACertAndKey, - if (data.ipAddr == data.hostName) listOf() else listOf(data.hostName), listOf(data.ipAddr)) - } - } + val clientCert = storage.getCertificate(requestId) + return if (clientCert != null) { // Write certificate chain to a zip stream and extract the bit array output. ByteArrayOutputStream().use { ZipOutputStream(it).use { diff --git a/netpermission/src/main/kotlin/com/r3corda/netpermission/internal/persistence/CertificationRequestStorage.kt b/netpermission/src/main/kotlin/com/r3corda/netpermission/internal/persistence/CertificationRequestStorage.kt index f9b0034441..650d486721 100644 --- a/netpermission/src/main/kotlin/com/r3corda/netpermission/internal/persistence/CertificationRequestStorage.kt +++ b/netpermission/src/main/kotlin/com/r3corda/netpermission/internal/persistence/CertificationRequestStorage.kt @@ -2,6 +2,7 @@ package com.r3corda.netpermission.internal.persistence import org.bouncycastle.pkcs.PKCS10CertificationRequest import java.security.cert.Certificate + /** * Provide certificate signing request storage for the certificate signing server. */ @@ -12,16 +13,25 @@ interface CertificationRequestStorage { fun saveRequest(certificationData: CertificationData): String /** - * Retrieve approved certificate singing request and Host/IP information using [requestId]. - * Returns [CertificationData] if request has been approved, else returns null. + * Retrieve certificate singing request and Host/IP information using [requestId]. */ - fun getApprovedRequest(requestId: String): CertificationData? + fun getRequest(requestId: String): CertificationData? /** * Retrieve client certificate with provided [requestId]. - * Generate new certificate and store in storage using provided [certificateGenerator] if certificate does not exist. */ - fun getOrElseCreateCertificate(requestId: String, certificateGenerator: () -> Certificate): Certificate + fun getCertificate(requestId: String): Certificate? + + /** + * Generate new certificate and store in storage using provided [certificateGenerator]. + */ + fun saveCertificate(requestId: String, certificateGenerator: (CertificationData) -> Certificate) + + /** + * Retrieve list of request IDs waiting for approval. + * TODO : This is use for the background thread to approve request automatically without KYC checks, should be removed after testnet. + */ + fun pendingRequestIds(): List } data class CertificationData(val hostName: String, val ipAddr: String, val request: PKCS10CertificationRequest) \ No newline at end of file diff --git a/netpermission/src/main/kotlin/com/r3corda/netpermission/internal/persistence/DBCertificateRequestStorage.kt b/netpermission/src/main/kotlin/com/r3corda/netpermission/internal/persistence/DBCertificateRequestStorage.kt new file mode 100644 index 0000000000..a7e62f8a37 --- /dev/null +++ b/netpermission/src/main/kotlin/com/r3corda/netpermission/internal/persistence/DBCertificateRequestStorage.kt @@ -0,0 +1,79 @@ +package com.r3corda.netpermission.internal.persistence + +import com.r3corda.core.crypto.SecureHash +import com.r3corda.node.utilities.databaseTransaction +import com.r3corda.node.utilities.deserializeFromBlob +import com.r3corda.node.utilities.localDateTime +import com.r3corda.node.utilities.serializeToBlob +import org.jetbrains.exposed.sql.* +import java.security.cert.Certificate +import java.time.LocalDateTime + +class DBCertificateRequestStorage(private val database: Database) : CertificationRequestStorage { + + private object DataTable : Table("certificate_signing_request") { + val requestId = varchar("request_id", 64).index().primaryKey() + val hostName = varchar("hostName", 100) + val ipAddress = varchar("ip_address", 15) + // TODO : Do we need to store this in column? or is it ok with blob. + val request = blob("request") + val requestTimestamp = localDateTime("request_timestamp") + val approvedTimestamp = localDateTime("approved_timestamp").nullable() + val certificate = blob("certificate").nullable() + } + + init { + // Create table if not exists. + databaseTransaction(database) { + SchemaUtils.create(DataTable) + } + } + + override fun getCertificate(requestId: String): Certificate? { + return databaseTransaction(database) { DataTable.select { DataTable.requestId.eq(requestId) }.map { it[DataTable.certificate] }.filterNotNull().map { deserializeFromBlob(it) }.firstOrNull() } + } + + override fun saveCertificate(requestId: String, certificateGenerator: (CertificationData) -> Certificate) { + databaseTransaction(database) { + val finalizables = mutableListOf<() -> Unit>() + try { + getRequest(requestId)?.let { + val clientCert = certificateGenerator(it) + DataTable.update({ DataTable.requestId eq requestId }) { + it[approvedTimestamp] = LocalDateTime.now() + it[certificate] = serializeToBlob(clientCert, finalizables) + } + } + } finally { + finalizables.forEach { it() } + } + } + } + + override fun getRequest(requestId: String): CertificationData? { + return databaseTransaction(database) { DataTable.select { DataTable.requestId eq requestId }.map { CertificationData(it[DataTable.hostName], it[DataTable.ipAddress], deserializeFromBlob(it[DataTable.request])) }.firstOrNull() } + } + + override fun saveRequest(certificationData: CertificationData): String { + return databaseTransaction(database) { + val finalizables = mutableListOf<() -> Unit>() + try { + val requestId = SecureHash.randomSHA256().toString() + DataTable.insert { + it[DataTable.requestId] = requestId + it[hostName] = certificationData.hostName + it[ipAddress] = certificationData.ipAddr + it[DataTable.request] = serializeToBlob(certificationData.request, finalizables) + it[requestTimestamp] = LocalDateTime.now() + } + requestId + } finally { + finalizables.forEach { it() } + } + } + } + + override fun pendingRequestIds(): List { + return databaseTransaction(database) { DataTable.select { DataTable.approvedTimestamp.isNull() }.map { it[DataTable.requestId] } } + } +} \ No newline at end of file diff --git a/netpermission/src/main/kotlin/com/r3corda/netpermission/internal/persistence/InMemoryCertificationRequestStorage.kt b/netpermission/src/main/kotlin/com/r3corda/netpermission/internal/persistence/InMemoryCertificationRequestStorage.kt index cc49d2c689..91e71c6933 100644 --- a/netpermission/src/main/kotlin/com/r3corda/netpermission/internal/persistence/InMemoryCertificationRequestStorage.kt +++ b/netpermission/src/main/kotlin/com/r3corda/netpermission/internal/persistence/InMemoryCertificationRequestStorage.kt @@ -5,14 +5,24 @@ import java.security.cert.Certificate import java.util.* class InMemoryCertificationRequestStorage : CertificationRequestStorage { - val requestStore = HashMap() - val certificateStore = HashMap() + private val requestStore = HashMap() + private val certificateStore = HashMap() - override fun getOrElseCreateCertificate(requestId: String, certificateGenerator: () -> Certificate): Certificate { - return certificateStore.getOrPut(requestId, certificateGenerator) + override fun pendingRequestIds(): List { + return requestStore.keys.filter { !certificateStore.keys.contains(it) } } - override fun getApprovedRequest(requestId: String): CertificationData? { + override fun getCertificate(requestId: String): Certificate? { + return certificateStore[requestId] + } + + override fun saveCertificate(requestId: String, certificateGenerator: (CertificationData) -> Certificate) { + requestStore[requestId]?.let { + certificateStore.putIfAbsent(requestId, certificateGenerator(it)) + } + } + + override fun getRequest(requestId: String): CertificationData? { return requestStore[requestId] } diff --git a/netpermission/src/main/resources/reference.conf b/netpermission/src/main/resources/reference.conf new file mode 100644 index 0000000000..f958129472 --- /dev/null +++ b/netpermission/src/main/resources/reference.conf @@ -0,0 +1,14 @@ +host = localhost +port = 0 +keystorePath = ${basedir}"/certificates/keystore.jks" +keyStorePassword = "password" +caKeyPassword = "password" +approveAll = true + +dataSourceProperties { + dataSourceClassName = org.h2.jdbcx.JdbcDataSource + "dataSource.url" = "jdbc:h2:file:"${basedir}"/persistence;DB_CLOSE_ON_EXIT=FALSE;LOCK_TIMEOUT=10000;MVCC=true;MV_STORE=true;WRITE_DELAY=0;AUTO_SERVER_PORT="${h2port} + "dataSource.user" = sa + "dataSource.password" = "" +} +h2port = 0 \ No newline at end of file diff --git a/netpermission/src/test/kotlin/com/r3corda/netpermission/CertificateSigningServiceTest.kt b/netpermission/src/test/kotlin/com/r3corda/netpermission/CertificateSigningServiceTest.kt index e8ccb4f3bb..c84e593f73 100644 --- a/netpermission/src/test/kotlin/com/r3corda/netpermission/CertificateSigningServiceTest.kt +++ b/netpermission/src/test/kotlin/com/r3corda/netpermission/CertificateSigningServiceTest.kt @@ -4,11 +4,11 @@ import com.google.common.net.HostAndPort import com.nhaarman.mockito_kotlin.* import com.r3corda.core.crypto.SecureHash import com.r3corda.core.crypto.X509Utilities -import com.r3corda.core.seconds import com.r3corda.netpermission.CertificateSigningServer.Companion.hostAndPort import com.r3corda.netpermission.internal.CertificateSigningService import com.r3corda.netpermission.internal.persistence.CertificationData import com.r3corda.netpermission.internal.persistence.CertificationRequestStorage +import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest import org.junit.Test import sun.security.x509.X500Name import java.io.IOException @@ -20,11 +20,14 @@ import java.security.cert.X509Certificate import java.util.* import java.util.zip.ZipInputStream import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.assertNull class CertificateSigningServiceTest { + val rootCA = X509Utilities.createSelfSignedCACert("Corda Node Root CA") + val intermediateCA = X509Utilities.createSelfSignedCACert("Corda Node Intermediate CA") + private fun getSigningServer(storage: CertificationRequestStorage): CertificateSigningServer { - val rootCA = X509Utilities.createSelfSignedCACert("Corda Node Root CA") - val intermediateCA = X509Utilities.createSelfSignedCACert("Corda Node Intermediate CA") return CertificateSigningServer(HostAndPort.fromParts("localhost", 0), CertificateSigningService(intermediateCA, rootCA.certificate, storage)) } @@ -60,13 +63,18 @@ class CertificateSigningServiceTest { fun testRetrieveCertificate() { val keyPair = X509Utilities.generateECDSAKeyPairForSSL() val id = SecureHash.randomSHA256().toString() - var count = 0 + + // Mock Storage behaviour. + val certificateStore = mutableMapOf() val storage: CertificationRequestStorage = mock { - on { getApprovedRequest(eq(id)) }.then { - if (count < 5) null else CertificationData("", "", X509Utilities.createCertificateSigningRequest("LegalName", - "London", "admin@test.com", keyPair)) + on { getCertificate(eq(id)) }.then { certificateStore[id] } + on { saveCertificate(eq(id), any()) }.then { + val certGen = it.arguments[1] as (CertificationData) -> Certificate + val request = CertificationData("", "", X509Utilities.createCertificateSigningRequest("LegalName", "London", "admin@test.com", keyPair)) + certificateStore[id] = certGen(request) + Unit } - on { getOrElseCreateCertificate(eq(id), any()) }.thenAnswer { (it.arguments[1] as () -> Certificate)() } + on { pendingRequestIds() }.then { listOf(id) } } getSigningServer(storage).use { @@ -91,15 +99,20 @@ class CertificateSigningServiceTest { } } - var certificates = poll() + assertNull(poll()) + assertNull(poll()) - while (certificates == null) { - Thread.sleep(1.seconds.toMillis()) - count++ - certificates = poll() - } + storage.saveCertificate(id, { + JcaPKCS10CertificationRequest(it.request).run { + X509Utilities.createServerCert(subject, publicKey, intermediateCA, + if (it.ipAddr == it.hostName) listOf() else listOf(it.hostName), listOf(it.ipAddr)) + } + }) + + val certificates = assertNotNull(poll()) + + verify(storage, times(3)).getCertificate(any()) - verify(storage, times(6)).getApprovedRequest(any()) assertEquals(3, certificates.size) (certificates.first() as X509Certificate).run { diff --git a/netpermission/src/test/kotlin/com/r3corda/netpermission/internal/persistence/DBCertificateRequestStorageTest.kt b/netpermission/src/test/kotlin/com/r3corda/netpermission/internal/persistence/DBCertificateRequestStorageTest.kt new file mode 100644 index 0000000000..cc3e68a2f5 --- /dev/null +++ b/netpermission/src/test/kotlin/com/r3corda/netpermission/internal/persistence/DBCertificateRequestStorageTest.kt @@ -0,0 +1,81 @@ +package com.r3corda.netpermission.internal.persistence + +import com.r3corda.core.crypto.X509Utilities +import com.r3corda.node.utilities.configureDatabase +import com.r3corda.testing.node.makeTestDataSourceProperties +import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest +import org.junit.Test +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.assertNull +import kotlin.test.assertTrue + +class DBCertificateRequestStorageTest { + + val intermediateCA = X509Utilities.createSelfSignedCACert("Corda Node Intermediate CA") + + @Test + fun testSaveRequest() { + val keyPair = X509Utilities.generateECDSAKeyPairForSSL() + val request = CertificationData("", "", X509Utilities.createCertificateSigningRequest("LegalName", "London", "admin@test.com", keyPair)) + + val (connection, db) = configureDatabase(makeTestDataSourceProperties()) + connection.use { + val storage = DBCertificateRequestStorage(db) + val requestId = storage.saveRequest(request) + + assertNotNull(storage.getRequest(requestId)).apply { + assertEquals(request.hostName, hostName) + assertEquals(request.ipAddr, ipAddr) + assertEquals(request.request, this.request) + } + } + } + + @Test + fun testPendingRequest() { + val keyPair = X509Utilities.generateECDSAKeyPairForSSL() + val request = CertificationData("", "", X509Utilities.createCertificateSigningRequest("LegalName", "London", "admin@test.com", keyPair)) + + val (connection, db) = configureDatabase(makeTestDataSourceProperties()) + connection.use { + val storage = DBCertificateRequestStorage(db) + val requestId = storage.saveRequest(request) + storage.pendingRequestIds().apply { + assertTrue(isNotEmpty()) + assertEquals(1, size) + assertEquals(requestId, first()) + } + } + } + + @Test + fun testSaveCertificate() { + val keyPair = X509Utilities.generateECDSAKeyPairForSSL() + val request = CertificationData("", "", X509Utilities.createCertificateSigningRequest("LegalName", "London", "admin@test.com", keyPair)) + + val (connection, db) = configureDatabase(makeTestDataSourceProperties()) + connection.use { + val storage = DBCertificateRequestStorage(db) + // Add request to DB. + val requestId = storage.saveRequest(request) + // Pending request should equals to 1. + assertEquals(1, storage.pendingRequestIds().size) + // Certificate should be empty. + assertNull(storage.getCertificate(requestId)) + // Store certificate to DB. + storage.saveCertificate(requestId, { + JcaPKCS10CertificationRequest(it.request).run { + X509Utilities.createServerCert(subject, publicKey, intermediateCA, + if (it.ipAddr == it.hostName) listOf() else listOf(it.hostName), listOf(it.ipAddr)) + } + }) + // Check certificate is stored in DB correctly. + assertNotNull(storage.getCertificate(requestId)).apply { + assertEquals(keyPair.public, this.publicKey) + } + // Pending request should be empty. + assertTrue(storage.pendingRequestIds().isEmpty()) + } + } +} \ No newline at end of file From ce41d6a7a9d312aa4e4e552e7c7f0b53a430c591 Mon Sep 17 00:00:00 2001 From: Patrick Kuo Date: Fri, 4 Nov 2016 11:43:28 +0000 Subject: [PATCH 2/4] addressed PR issues, added withFinalizable to remove try->finally boiler plate code --- .../kotlin/com/r3corda/netpermission/Main.kt | 1 - .../persistence/CertificationRequestStorage.kt | 2 +- .../persistence/DBCertificateRequestStorage.kt | 16 +++------------- netpermission/src/main/resources/reference.conf | 2 +- .../CertificateSigningServiceTest.kt | 2 -- .../DBCertificateRequestStorageTest.kt | 1 - .../r3corda/node/utilities/DatabaseSupport.kt | 11 +++++++++++ 7 files changed, 16 insertions(+), 19 deletions(-) diff --git a/netpermission/src/main/kotlin/com/r3corda/netpermission/Main.kt b/netpermission/src/main/kotlin/com/r3corda/netpermission/Main.kt index 3f9afcef43..540aff3b5e 100644 --- a/netpermission/src/main/kotlin/com/r3corda/netpermission/Main.kt +++ b/netpermission/src/main/kotlin/com/r3corda/netpermission/Main.kt @@ -31,7 +31,6 @@ import kotlin.system.exitProcess * The Intermediate CA certificate,Intermediate CA private key and Root CA Certificate should use alias name specified in [X509Utilities] */ class CertificateSigningServer(val webServerAddr: HostAndPort, val certSigningService: CertificateSigningService) : Closeable { - companion object { val log = loggerFor() fun Server.hostAndPort(): HostAndPort { diff --git a/netpermission/src/main/kotlin/com/r3corda/netpermission/internal/persistence/CertificationRequestStorage.kt b/netpermission/src/main/kotlin/com/r3corda/netpermission/internal/persistence/CertificationRequestStorage.kt index 650d486721..83c13a6240 100644 --- a/netpermission/src/main/kotlin/com/r3corda/netpermission/internal/persistence/CertificationRequestStorage.kt +++ b/netpermission/src/main/kotlin/com/r3corda/netpermission/internal/persistence/CertificationRequestStorage.kt @@ -29,7 +29,7 @@ interface CertificationRequestStorage { /** * Retrieve list of request IDs waiting for approval. - * TODO : This is use for the background thread to approve request automatically without KYC checks, should be removed after testnet. + * TODO : This is used for the background thread to approve request automatically without KYC checks, should be removed after testnet. */ fun pendingRequestIds(): List } diff --git a/netpermission/src/main/kotlin/com/r3corda/netpermission/internal/persistence/DBCertificateRequestStorage.kt b/netpermission/src/main/kotlin/com/r3corda/netpermission/internal/persistence/DBCertificateRequestStorage.kt index a7e62f8a37..48675ff395 100644 --- a/netpermission/src/main/kotlin/com/r3corda/netpermission/internal/persistence/DBCertificateRequestStorage.kt +++ b/netpermission/src/main/kotlin/com/r3corda/netpermission/internal/persistence/DBCertificateRequestStorage.kt @@ -1,16 +1,12 @@ package com.r3corda.netpermission.internal.persistence import com.r3corda.core.crypto.SecureHash -import com.r3corda.node.utilities.databaseTransaction -import com.r3corda.node.utilities.deserializeFromBlob -import com.r3corda.node.utilities.localDateTime -import com.r3corda.node.utilities.serializeToBlob +import com.r3corda.node.utilities.* import org.jetbrains.exposed.sql.* import java.security.cert.Certificate import java.time.LocalDateTime class DBCertificateRequestStorage(private val database: Database) : CertificationRequestStorage { - private object DataTable : Table("certificate_signing_request") { val requestId = varchar("request_id", 64).index().primaryKey() val hostName = varchar("hostName", 100) @@ -35,8 +31,7 @@ class DBCertificateRequestStorage(private val database: Database) : Certificatio override fun saveCertificate(requestId: String, certificateGenerator: (CertificationData) -> Certificate) { databaseTransaction(database) { - val finalizables = mutableListOf<() -> Unit>() - try { + withFinalizables { finalizables -> getRequest(requestId)?.let { val clientCert = certificateGenerator(it) DataTable.update({ DataTable.requestId eq requestId }) { @@ -44,8 +39,6 @@ class DBCertificateRequestStorage(private val database: Database) : Certificatio it[certificate] = serializeToBlob(clientCert, finalizables) } } - } finally { - finalizables.forEach { it() } } } } @@ -56,8 +49,7 @@ class DBCertificateRequestStorage(private val database: Database) : Certificatio override fun saveRequest(certificationData: CertificationData): String { return databaseTransaction(database) { - val finalizables = mutableListOf<() -> Unit>() - try { + withFinalizables { finalizables -> val requestId = SecureHash.randomSHA256().toString() DataTable.insert { it[DataTable.requestId] = requestId @@ -67,8 +59,6 @@ class DBCertificateRequestStorage(private val database: Database) : Certificatio it[requestTimestamp] = LocalDateTime.now() } requestId - } finally { - finalizables.forEach { it() } } } } diff --git a/netpermission/src/main/resources/reference.conf b/netpermission/src/main/resources/reference.conf index f958129472..d0629fc739 100644 --- a/netpermission/src/main/resources/reference.conf +++ b/netpermission/src/main/resources/reference.conf @@ -7,7 +7,7 @@ approveAll = true dataSourceProperties { dataSourceClassName = org.h2.jdbcx.JdbcDataSource - "dataSource.url" = "jdbc:h2:file:"${basedir}"/persistence;DB_CLOSE_ON_EXIT=FALSE;LOCK_TIMEOUT=10000;MVCC=true;MV_STORE=true;WRITE_DELAY=0;AUTO_SERVER_PORT="${h2port} + "dataSource.url" = "jdbc:h2:file:"${basedir}"/persistence;DB_CLOSE_ON_EXIT=FALSE;LOCK_TIMEOUT=10000;WRITE_DELAY=0;AUTO_SERVER_PORT="${h2port} "dataSource.user" = sa "dataSource.password" = "" } diff --git a/netpermission/src/test/kotlin/com/r3corda/netpermission/CertificateSigningServiceTest.kt b/netpermission/src/test/kotlin/com/r3corda/netpermission/CertificateSigningServiceTest.kt index c84e593f73..a4b4bdb360 100644 --- a/netpermission/src/test/kotlin/com/r3corda/netpermission/CertificateSigningServiceTest.kt +++ b/netpermission/src/test/kotlin/com/r3corda/netpermission/CertificateSigningServiceTest.kt @@ -110,9 +110,7 @@ class CertificateSigningServiceTest { }) val certificates = assertNotNull(poll()) - verify(storage, times(3)).getCertificate(any()) - assertEquals(3, certificates.size) (certificates.first() as X509Certificate).run { diff --git a/netpermission/src/test/kotlin/com/r3corda/netpermission/internal/persistence/DBCertificateRequestStorageTest.kt b/netpermission/src/test/kotlin/com/r3corda/netpermission/internal/persistence/DBCertificateRequestStorageTest.kt index cc3e68a2f5..452a765d61 100644 --- a/netpermission/src/test/kotlin/com/r3corda/netpermission/internal/persistence/DBCertificateRequestStorageTest.kt +++ b/netpermission/src/test/kotlin/com/r3corda/netpermission/internal/persistence/DBCertificateRequestStorageTest.kt @@ -11,7 +11,6 @@ import kotlin.test.assertNull import kotlin.test.assertTrue class DBCertificateRequestStorageTest { - val intermediateCA = X509Utilities.createSelfSignedCACert("Corda Node Intermediate CA") @Test diff --git a/node/src/main/kotlin/com/r3corda/node/utilities/DatabaseSupport.kt b/node/src/main/kotlin/com/r3corda/node/utilities/DatabaseSupport.kt index 134fe0e498..a11b25b5a7 100644 --- a/node/src/main/kotlin/com/r3corda/node/utilities/DatabaseSupport.kt +++ b/node/src/main/kotlin/com/r3corda/node/utilities/DatabaseSupport.kt @@ -30,6 +30,15 @@ fun databaseTransaction(db: Database, statement: Transaction.() -> T): T { return org.jetbrains.exposed.sql.transactions.transaction(Connection.TRANSACTION_REPEATABLE_READ, 1, statement) } +fun withFinalizables(statement: (MutableList<() -> Unit>) -> T): T { + val finalizables = mutableListOf<() -> Unit>() + return try { + statement(finalizables) + } finally { + finalizables.forEach { it() } + } +} + fun createDatabaseTransaction(db: Database): Transaction { // We need to set the database for the current [Thread] or [Fiber] here as some tests share threads across databases. StrandLocalTransactionManager.database = db @@ -138,12 +147,14 @@ class StrandLocalTransactionManager(initWithDatabase: Database) : TransactionMan // Composite columns for use with below Exposed helpers. data class PartyColumns(val name: Column, val owningKey: Column) + data class StateRefColumns(val txId: Column, val index: Column) /** * [Table] column helpers for use with Exposed, as per [varchar] etc. */ fun Table.publicKey(name: String) = this.registerColumn(name, PublicKeyColumnType) + fun Table.secureHash(name: String) = this.registerColumn(name, SecureHashColumnType) fun Table.party(nameColumnName: String, keyColumnName: String) = PartyColumns(this.varchar(nameColumnName, length = 255), this.publicKey(keyColumnName)) fun Table.uuidString(name: String) = this.registerColumn(name, UUIDStringColumnType) From ed557f53917418a9f89eecae9dfbecbade20431c Mon Sep 17 00:00:00 2001 From: Patrick Kuo Date: Fri, 4 Nov 2016 11:48:59 +0000 Subject: [PATCH 3/4] reverted newline added by intellij auto format --- .../main/kotlin/com/r3corda/node/utilities/DatabaseSupport.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/node/src/main/kotlin/com/r3corda/node/utilities/DatabaseSupport.kt b/node/src/main/kotlin/com/r3corda/node/utilities/DatabaseSupport.kt index a11b25b5a7..e7d430f7ce 100644 --- a/node/src/main/kotlin/com/r3corda/node/utilities/DatabaseSupport.kt +++ b/node/src/main/kotlin/com/r3corda/node/utilities/DatabaseSupport.kt @@ -147,14 +147,12 @@ class StrandLocalTransactionManager(initWithDatabase: Database) : TransactionMan // Composite columns for use with below Exposed helpers. data class PartyColumns(val name: Column, val owningKey: Column) - data class StateRefColumns(val txId: Column, val index: Column) /** * [Table] column helpers for use with Exposed, as per [varchar] etc. */ fun Table.publicKey(name: String) = this.registerColumn(name, PublicKeyColumnType) - fun Table.secureHash(name: String) = this.registerColumn(name, SecureHashColumnType) fun Table.party(nameColumnName: String, keyColumnName: String) = PartyColumns(this.varchar(nameColumnName, length = 255), this.publicKey(keyColumnName)) fun Table.uuidString(name: String) = this.registerColumn(name, UUIDStringColumnType) From 57283c96c305fc41a24566e811c219407fac51e4 Mon Sep 17 00:00:00 2001 From: Patrick Kuo Date: Tue, 8 Nov 2016 10:15:36 +0000 Subject: [PATCH 4/4] addressed PR issues --- .../r3corda/netpermission/CertificateSigningServiceTest.kt | 4 ++-- .../internal/persistence/DBCertificateRequestStorageTest.kt | 6 +++--- .../kotlin/com/r3corda/node/utilities/DatabaseSupport.kt | 3 +++ 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/netpermission/src/test/kotlin/com/r3corda/netpermission/CertificateSigningServiceTest.kt b/netpermission/src/test/kotlin/com/r3corda/netpermission/CertificateSigningServiceTest.kt index a4b4bdb360..9389739e25 100644 --- a/netpermission/src/test/kotlin/com/r3corda/netpermission/CertificateSigningServiceTest.kt +++ b/netpermission/src/test/kotlin/com/r3corda/netpermission/CertificateSigningServiceTest.kt @@ -32,7 +32,7 @@ class CertificateSigningServiceTest { } @Test - fun testSubmitRequest() { + fun `test submit request`() { val id = SecureHash.randomSHA256().toString() val storage: CertificationRequestStorage = mock { @@ -60,7 +60,7 @@ class CertificateSigningServiceTest { } @Test - fun testRetrieveCertificate() { + fun `test retrieve certificate`() { val keyPair = X509Utilities.generateECDSAKeyPairForSSL() val id = SecureHash.randomSHA256().toString() diff --git a/netpermission/src/test/kotlin/com/r3corda/netpermission/internal/persistence/DBCertificateRequestStorageTest.kt b/netpermission/src/test/kotlin/com/r3corda/netpermission/internal/persistence/DBCertificateRequestStorageTest.kt index 452a765d61..454c3695f4 100644 --- a/netpermission/src/test/kotlin/com/r3corda/netpermission/internal/persistence/DBCertificateRequestStorageTest.kt +++ b/netpermission/src/test/kotlin/com/r3corda/netpermission/internal/persistence/DBCertificateRequestStorageTest.kt @@ -14,7 +14,7 @@ class DBCertificateRequestStorageTest { val intermediateCA = X509Utilities.createSelfSignedCACert("Corda Node Intermediate CA") @Test - fun testSaveRequest() { + fun `test save request`() { val keyPair = X509Utilities.generateECDSAKeyPairForSSL() val request = CertificationData("", "", X509Utilities.createCertificateSigningRequest("LegalName", "London", "admin@test.com", keyPair)) @@ -32,7 +32,7 @@ class DBCertificateRequestStorageTest { } @Test - fun testPendingRequest() { + fun `test pending request`() { val keyPair = X509Utilities.generateECDSAKeyPairForSSL() val request = CertificationData("", "", X509Utilities.createCertificateSigningRequest("LegalName", "London", "admin@test.com", keyPair)) @@ -49,7 +49,7 @@ class DBCertificateRequestStorageTest { } @Test - fun testSaveCertificate() { + fun `test save certificate`() { val keyPair = X509Utilities.generateECDSAKeyPairForSSL() val request = CertificationData("", "", X509Utilities.createCertificateSigningRequest("LegalName", "London", "admin@test.com", keyPair)) diff --git a/node/src/main/kotlin/com/r3corda/node/utilities/DatabaseSupport.kt b/node/src/main/kotlin/com/r3corda/node/utilities/DatabaseSupport.kt index e7d430f7ce..990ed19375 100644 --- a/node/src/main/kotlin/com/r3corda/node/utilities/DatabaseSupport.kt +++ b/node/src/main/kotlin/com/r3corda/node/utilities/DatabaseSupport.kt @@ -30,6 +30,9 @@ fun databaseTransaction(db: Database, statement: Transaction.() -> T): T { return org.jetbrains.exposed.sql.transactions.transaction(Connection.TRANSACTION_REPEATABLE_READ, 1, statement) } +/** + * Helper method wrapping code in try finally block. A mutable list is used to keep track of functions that need to be executed in finally block. + */ fun withFinalizables(statement: (MutableList<() -> Unit>) -> T): T { val finalizables = mutableListOf<() -> Unit>() return try {