mirror of
https://github.com/corda/corda.git
synced 2024-12-29 09:18:58 +00:00
Uses config file instead of passing in arguments
Added H2 DB persistence Added background thread to approve request periodically
This commit is contained in:
parent
b9b90dffcd
commit
549a952cc0
@ -23,10 +23,25 @@ task buildCertSignerJAR(type: FatCapsule, dependsOn: 'jar') {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sourceSets {
|
||||||
|
main {
|
||||||
|
resources {
|
||||||
|
srcDir "../config/dev"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
test {
|
||||||
|
resources {
|
||||||
|
srcDir "../config/test"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
||||||
|
|
||||||
compile project(":core")
|
compile project(":core")
|
||||||
|
compile project(":node")
|
||||||
|
testCompile project(":test-utils")
|
||||||
|
|
||||||
// Log4J: logging framework (with SLF4J bindings)
|
// Log4J: logging framework (with SLF4J bindings)
|
||||||
compile "org.apache.logging.log4j:log4j-slf4j-impl:${log4j_version}"
|
compile "org.apache.logging.log4j:log4j-slf4j-impl:${log4j_version}"
|
||||||
@ -46,6 +61,9 @@ dependencies {
|
|||||||
// JOpt: for command line flags.
|
// JOpt: for command line flags.
|
||||||
compile "net.sf.jopt-simple:jopt-simple:5.0.2"
|
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.
|
// Unit testing helpers.
|
||||||
testCompile 'junit:junit:4.12'
|
testCompile 'junit:junit:4.12'
|
||||||
testCompile "org.assertj:assertj-core:${assertj_version}"
|
testCompile "org.assertj:assertj-core:${assertj_version}"
|
||||||
|
@ -2,11 +2,16 @@ package com.r3corda.netpermission
|
|||||||
|
|
||||||
import com.google.common.net.HostAndPort
|
import com.google.common.net.HostAndPort
|
||||||
import com.r3corda.core.crypto.X509Utilities
|
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.core.utilities.loggerFor
|
||||||
import com.r3corda.netpermission.internal.CertificateSigningService
|
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 joptsimple.OptionParser
|
||||||
|
import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest
|
||||||
import org.eclipse.jetty.server.Server
|
import org.eclipse.jetty.server.Server
|
||||||
import org.eclipse.jetty.server.ServerConnector
|
import org.eclipse.jetty.server.ServerConnector
|
||||||
import org.eclipse.jetty.server.handler.HandlerCollection
|
import org.eclipse.jetty.server.handler.HandlerCollection
|
||||||
@ -17,6 +22,7 @@ import org.glassfish.jersey.servlet.ServletContainer
|
|||||||
import java.io.Closeable
|
import java.io.Closeable
|
||||||
import java.net.InetSocketAddress
|
import java.net.InetSocketAddress
|
||||||
import java.nio.file.Paths
|
import java.nio.file.Paths
|
||||||
|
import kotlin.concurrent.thread
|
||||||
import kotlin.system.exitProcess
|
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]
|
* 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 {
|
class CertificateSigningServer(val webServerAddr: HostAndPort, val certSigningService: CertificateSigningService) : Closeable {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val log = loggerFor<CertificateSigningServer>()
|
val log = loggerFor<CertificateSigningServer>()
|
||||||
fun Server.hostAndPort(): HostAndPort {
|
fun Server.hostAndPort(): HostAndPort {
|
||||||
@ -68,20 +75,11 @@ class CertificateSigningServer(val webServerAddr: HostAndPort, val certSigningSe
|
|||||||
|
|
||||||
object ParamsSpec {
|
object ParamsSpec {
|
||||||
val parser = OptionParser()
|
val parser = OptionParser()
|
||||||
val host = parser.accepts("host", "The hostname permissioning server will be running on.")
|
val basedir: ArgumentAcceptingOptionSpec<String>? = parser.accepts("basedir", "Overriding configuration file path.")
|
||||||
.withRequiredArg().defaultsTo("localhost")
|
.withRequiredArg()
|
||||||
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()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun main(args: Array<String>) {
|
fun main(args: Array<String>) {
|
||||||
LogHelper.setLevel(CertificateSigningServer::class)
|
|
||||||
val log = CertificateSigningServer.log
|
val log = CertificateSigningServer.log
|
||||||
log.info("Starting certificate signing server.")
|
log.info("Starting certificate signing server.")
|
||||||
try {
|
try {
|
||||||
@ -91,17 +89,48 @@ fun main(args: Array<String>) {
|
|||||||
ParamsSpec.parser.printHelpOn(System.out)
|
ParamsSpec.parser.printHelpOn(System.out)
|
||||||
exitProcess(1)
|
exitProcess(1)
|
||||||
}.run {
|
}.run {
|
||||||
// Load keystore from input path, default to Dev keystore from jar resource if path not defined.
|
val basedir = Paths.get(valueOf(ParamsSpec.basedir) ?: ".")
|
||||||
val storePassword = valueOf(ParamsSpec.storePassword)
|
val config = ConfigHelper.loadConfig(basedir)
|
||||||
val keyPassword = valueOf(ParamsSpec.caKeyPassword)
|
|
||||||
val keystore = X509Utilities.loadKeyStore(Paths.get(valueOf(ParamsSpec.keystorePath)).normalize(), storePassword)
|
val keystore = X509Utilities.loadKeyStore(Paths.get(config.getString("keystorePath")).normalize(), config.getString("keyStorePassword"))
|
||||||
val intermediateCACertAndKey = X509Utilities.loadCertificateAndKey(keystore, keyPassword, X509Utilities.CORDA_INTERMEDIATE_CA_PRIVATE_KEY)
|
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()
|
val rootCA = keystore.getCertificateChain(X509Utilities.CORDA_INTERMEDIATE_CA_PRIVATE_KEY).last()
|
||||||
|
|
||||||
// TODO: Create a proper request storage using database or other storage technology.
|
// Create DB connection.
|
||||||
val service = CertificateSigningService(intermediateCACertAndKey, rootCA, InMemoryCertificationRequestStorage())
|
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()
|
it.server.join()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -53,14 +53,8 @@ class CertificateSigningService(val intermediateCACertAndKey: X509Utilities.CACe
|
|||||||
@Path("certificate/{var}")
|
@Path("certificate/{var}")
|
||||||
@Produces(MediaType.APPLICATION_OCTET_STREAM)
|
@Produces(MediaType.APPLICATION_OCTET_STREAM)
|
||||||
fun retrieveCert(@PathParam("var") requestId: String): Response {
|
fun retrieveCert(@PathParam("var") requestId: String): Response {
|
||||||
val data = storage.getApprovedRequest(requestId)
|
val clientCert = storage.getCertificate(requestId)
|
||||||
return if (data != null) {
|
return if (clientCert != 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))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Write certificate chain to a zip stream and extract the bit array output.
|
// Write certificate chain to a zip stream and extract the bit array output.
|
||||||
ByteArrayOutputStream().use {
|
ByteArrayOutputStream().use {
|
||||||
ZipOutputStream(it).use {
|
ZipOutputStream(it).use {
|
||||||
|
@ -2,6 +2,7 @@ package com.r3corda.netpermission.internal.persistence
|
|||||||
|
|
||||||
import org.bouncycastle.pkcs.PKCS10CertificationRequest
|
import org.bouncycastle.pkcs.PKCS10CertificationRequest
|
||||||
import java.security.cert.Certificate
|
import java.security.cert.Certificate
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provide certificate signing request storage for the certificate signing server.
|
* Provide certificate signing request storage for the certificate signing server.
|
||||||
*/
|
*/
|
||||||
@ -12,16 +13,25 @@ interface CertificationRequestStorage {
|
|||||||
fun saveRequest(certificationData: CertificationData): String
|
fun saveRequest(certificationData: CertificationData): String
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve approved certificate singing request and Host/IP information using [requestId].
|
* Retrieve certificate singing request and Host/IP information using [requestId].
|
||||||
* Returns [CertificationData] if request has been approved, else returns null.
|
|
||||||
*/
|
*/
|
||||||
fun getApprovedRequest(requestId: String): CertificationData?
|
fun getRequest(requestId: String): CertificationData?
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve client certificate with provided [requestId].
|
* 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<String>
|
||||||
}
|
}
|
||||||
|
|
||||||
data class CertificationData(val hostName: String, val ipAddr: String, val request: PKCS10CertificationRequest)
|
data class CertificationData(val hostName: String, val ipAddr: String, val request: PKCS10CertificationRequest)
|
@ -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<Certificate>(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<String> {
|
||||||
|
return databaseTransaction(database) { DataTable.select { DataTable.approvedTimestamp.isNull() }.map { it[DataTable.requestId] } }
|
||||||
|
}
|
||||||
|
}
|
@ -5,14 +5,24 @@ import java.security.cert.Certificate
|
|||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
class InMemoryCertificationRequestStorage : CertificationRequestStorage {
|
class InMemoryCertificationRequestStorage : CertificationRequestStorage {
|
||||||
val requestStore = HashMap<String, CertificationData>()
|
private val requestStore = HashMap<String, CertificationData>()
|
||||||
val certificateStore = HashMap<String, Certificate>()
|
private val certificateStore = HashMap<String, Certificate>()
|
||||||
|
|
||||||
override fun getOrElseCreateCertificate(requestId: String, certificateGenerator: () -> Certificate): Certificate {
|
override fun pendingRequestIds(): List<String> {
|
||||||
return certificateStore.getOrPut(requestId, certificateGenerator)
|
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]
|
return requestStore[requestId]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
14
netpermission/src/main/resources/reference.conf
Normal file
14
netpermission/src/main/resources/reference.conf
Normal file
@ -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
|
@ -4,11 +4,11 @@ import com.google.common.net.HostAndPort
|
|||||||
import com.nhaarman.mockito_kotlin.*
|
import com.nhaarman.mockito_kotlin.*
|
||||||
import com.r3corda.core.crypto.SecureHash
|
import com.r3corda.core.crypto.SecureHash
|
||||||
import com.r3corda.core.crypto.X509Utilities
|
import com.r3corda.core.crypto.X509Utilities
|
||||||
import com.r3corda.core.seconds
|
|
||||||
import com.r3corda.netpermission.CertificateSigningServer.Companion.hostAndPort
|
import com.r3corda.netpermission.CertificateSigningServer.Companion.hostAndPort
|
||||||
import com.r3corda.netpermission.internal.CertificateSigningService
|
import com.r3corda.netpermission.internal.CertificateSigningService
|
||||||
import com.r3corda.netpermission.internal.persistence.CertificationData
|
import com.r3corda.netpermission.internal.persistence.CertificationData
|
||||||
import com.r3corda.netpermission.internal.persistence.CertificationRequestStorage
|
import com.r3corda.netpermission.internal.persistence.CertificationRequestStorage
|
||||||
|
import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import sun.security.x509.X500Name
|
import sun.security.x509.X500Name
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
@ -20,11 +20,14 @@ import java.security.cert.X509Certificate
|
|||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.zip.ZipInputStream
|
import java.util.zip.ZipInputStream
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
|
import kotlin.test.assertNotNull
|
||||||
|
import kotlin.test.assertNull
|
||||||
|
|
||||||
class CertificateSigningServiceTest {
|
class CertificateSigningServiceTest {
|
||||||
|
val rootCA = X509Utilities.createSelfSignedCACert("Corda Node Root CA")
|
||||||
|
val intermediateCA = X509Utilities.createSelfSignedCACert("Corda Node Intermediate CA")
|
||||||
|
|
||||||
private fun getSigningServer(storage: CertificationRequestStorage): CertificateSigningServer {
|
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))
|
return CertificateSigningServer(HostAndPort.fromParts("localhost", 0), CertificateSigningService(intermediateCA, rootCA.certificate, storage))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,13 +63,18 @@ class CertificateSigningServiceTest {
|
|||||||
fun testRetrieveCertificate() {
|
fun testRetrieveCertificate() {
|
||||||
val keyPair = X509Utilities.generateECDSAKeyPairForSSL()
|
val keyPair = X509Utilities.generateECDSAKeyPairForSSL()
|
||||||
val id = SecureHash.randomSHA256().toString()
|
val id = SecureHash.randomSHA256().toString()
|
||||||
var count = 0
|
|
||||||
|
// Mock Storage behaviour.
|
||||||
|
val certificateStore = mutableMapOf<String, Certificate>()
|
||||||
val storage: CertificationRequestStorage = mock {
|
val storage: CertificationRequestStorage = mock {
|
||||||
on { getApprovedRequest(eq(id)) }.then {
|
on { getCertificate(eq(id)) }.then { certificateStore[id] }
|
||||||
if (count < 5) null else CertificationData("", "", X509Utilities.createCertificateSigningRequest("LegalName",
|
on { saveCertificate(eq(id), any()) }.then {
|
||||||
"London", "admin@test.com", keyPair))
|
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 {
|
getSigningServer(storage).use {
|
||||||
@ -91,15 +99,20 @@ class CertificateSigningServiceTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var certificates = poll()
|
assertNull(poll())
|
||||||
|
assertNull(poll())
|
||||||
|
|
||||||
while (certificates == null) {
|
storage.saveCertificate(id, {
|
||||||
Thread.sleep(1.seconds.toMillis())
|
JcaPKCS10CertificationRequest(it.request).run {
|
||||||
count++
|
X509Utilities.createServerCert(subject, publicKey, intermediateCA,
|
||||||
certificates = poll()
|
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)
|
assertEquals(3, certificates.size)
|
||||||
|
|
||||||
(certificates.first() as X509Certificate).run {
|
(certificates.first() as X509Certificate).run {
|
||||||
|
@ -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())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user