Adding sockets (#570)

* Adding sockets

* Addressing review comments

* Adding review comments

* Addressing review comments
This commit is contained in:
Michal Kit 2018-03-23 07:43:13 +00:00 committed by GitHub
parent e26db5faa6
commit c9caaf8be4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 376 additions and 18 deletions

View File

@ -4,7 +4,7 @@ keySpecifier = -1
doorman {
crlDistributionPoint = "http://test.com/revoked.crl"
compatibilityZoneURL = "http://test.com/api"
crlServerSocketAddress = "test.com:2333"
crlUpdatePeriod = 200000
validDays = 3650
rootKeyStoreFile = "dummyfile.jks"

View File

@ -24,6 +24,7 @@ import com.r3.corda.networkmanage.hsm.generator.GeneratorParameters
import com.r3.corda.networkmanage.hsm.generator.UserAuthenticationParameters
import net.corda.core.crypto.random63BitValue
import net.corda.core.internal.div
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.nodeapi.internal.crypto.CertificateType
import org.junit.Before
import org.junit.Rule
@ -146,7 +147,7 @@ abstract class HsmBaseTest {
validDays = 3650,
rootKeyStorePassword = TRUSTSTORE_PASSWORD,
crlDistributionPoint = URL("http://test.com/revoked.crl"),
compatibilityZoneURL = URL("http://test.com/api"),
crlServerSocketAddress = NetworkHostAndPort("test.com", 4555),
crlUpdatePeriod = 1000,
authParameters = AuthParametersConfig(
mode = SigningServiceAuthMode.PASSWORD,

View File

@ -0,0 +1,10 @@
package com.r3.corda.networkmanage.common.sockets
import net.corda.core.serialization.CordaSerializable
import java.security.cert.X509CRL
import java.time.Instant
@CordaSerializable
data class CertificateRevocationListSubmission(val list: X509CRL,
val signer: String,
val revocationTime: Instant)

View File

@ -0,0 +1,28 @@
package com.r3.corda.networkmanage.common.sockets
import com.r3.corda.networkmanage.common.persistence.RequestStatus
import net.corda.core.serialization.CordaSerializable
import java.security.cert.X509CRL
@CordaSerializable
interface CrrSocketMessage
/**
* CRL retrieval message type
*/
class CrlRetrievalMessage : CrrSocketMessage
/**
* CRL response message type
*/
data class CrlResponseMessage(val crl: X509CRL?) : CrrSocketMessage
/**
* CRL submission message type
*/
class CrlSubmissionMessage : CrrSocketMessage
/**
* By status CRRs retrieval message type
*/
data class CrrsByStatusMessage(val status: RequestStatus) : CrrSocketMessage

View File

@ -0,0 +1,26 @@
package com.r3.corda.networkmanage.common.utils
import net.corda.core.serialization.deserialize
import net.corda.core.serialization.serialize
import java.io.DataInputStream
import java.io.DataOutputStream
import java.io.InputStream
import java.io.OutputStream
inline fun <reified T : Any> InputStream.readObject(): T {
DataInputStream(this).run {
val messageSize = this.readInt()
val messageBytes = ByteArray(messageSize)
this.read(messageBytes)
return messageBytes.deserialize()
}
}
fun OutputStream.writeObject(message: Any) {
DataOutputStream(this).run {
val messageBytes = message.serialize().bytes
this.writeInt(messageBytes.size)
this.write(messageBytes)
this.flush()
}
}

View File

@ -0,0 +1,87 @@
package com.r3.corda.networkmanage.doorman.sockets
import com.google.common.util.concurrent.MoreExecutors.shutdownAndAwaitTermination
import com.r3.corda.networkmanage.common.persistence.CertificateRevocationListStorage
import com.r3.corda.networkmanage.common.persistence.CertificateRevocationRequestStorage
import com.r3.corda.networkmanage.common.persistence.CrlIssuer
import com.r3.corda.networkmanage.common.sockets.*
import com.r3.corda.networkmanage.common.utils.readObject
import com.r3.corda.networkmanage.common.utils.writeObject
import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.seconds
import java.net.ServerSocket
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
class CertificateRevocationSocketServer(val port: Int,
private val crlStorage: CertificateRevocationListStorage,
private val crrStorage: CertificateRevocationRequestStorage) : AutoCloseable {
private companion object {
private val logger = contextLogger()
private val RECONNECT_INTERVAL = 10.seconds.toMillis()
}
private val executor = Executors.newSingleThreadScheduledExecutor()
@Volatile
private var isRunning = false
fun start() {
check(!isRunning) { "The server has already been started." }
isRunning = true
executor.submit {
while (isRunning) {
try {
listen()
} catch (e: Exception) {
logger.error("Socket execution error.", e)
if (isRunning) {
logger.info("Server socket will be recreated in $RECONNECT_INTERVAL milliseconds")
Thread.sleep(RECONNECT_INTERVAL)
logger.info("Recreating server socket...")
}
}
}
}
}
override fun close() {
isRunning = false
shutdownAndAwaitTermination(executor, RECONNECT_INTERVAL, TimeUnit.MILLISECONDS)
}
private fun listen() {
ServerSocket(port).use {
logger.info("Server socket is running.")
while (isRunning) {
logger.debug("Waiting for socket data...")
val acceptedSocket = it.accept()
acceptedSocket.use {
val message = it.getInputStream().readObject<CrrSocketMessage>()
logger.debug("Received message type is $message.")
acceptedSocket.getOutputStream().let {
when (message) {
is CrlRetrievalMessage -> {
val crl = crlStorage.getCertificateRevocationList(CrlIssuer.DOORMAN)
it.writeObject(CrlResponseMessage(crl))
logger.debug("Sending the current certificate revocation list.")
}
is CrrsByStatusMessage -> {
it.writeObject(crrStorage.getRevocationRequests(message.status))
logger.debug("Sending ${message.status.name} certificate revocation requests.")
}
is CrlSubmissionMessage -> {
val crlSubmission = acceptedSocket.getInputStream().readObject<CertificateRevocationListSubmission>()
crlStorage.saveCertificateRevocationList(crlSubmission.list, CrlIssuer.DOORMAN, crlSubmission.signer, crlSubmission.revocationTime)
}
else -> logger.warn("Unknown message type $message")
}
}
}
}
}
}
}

View File

@ -6,7 +6,6 @@ import com.r3.corda.networkmanage.common.persistence.CertificateRevocationListSt
import com.r3.corda.networkmanage.common.persistence.CrlIssuer
import com.r3.corda.networkmanage.doorman.webservice.CertificateRevocationListWebService.Companion.CRL_PATH
import net.corda.core.utilities.contextLogger
import java.util.concurrent.ExecutionException
import java.util.concurrent.TimeUnit
import javax.ws.rs.GET
import javax.ws.rs.Path

View File

@ -62,6 +62,6 @@ fun main(args: Array<String>) {
} else if (config.doorman != null) {
CsrProcessor(config.doorman, config.device, config.keySpecifier, persistence).showMenu()
} else if (config.doorman != null) {
CrrProcessor(config.doorman, config.device, config.keySpecifier, persistence).showMenu()
CrrProcessor(config.doorman, config.device, config.keySpecifier).showMenu()
}
}

View File

@ -20,9 +20,11 @@ import joptsimple.OptionParser
import joptsimple.util.PathConverter
import joptsimple.util.PathProperties
import net.corda.core.internal.div
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.nodeapi.internal.config.parseAs
import net.corda.nodeapi.internal.crypto.X509KeyStore
import net.corda.nodeapi.internal.persistence.DatabaseConfig
import java.net.InetAddress
import java.net.URL
import java.nio.file.Path
import java.nio.file.Paths
@ -56,7 +58,7 @@ data class NetworkMapCertificateConfig(val username: String,
* Certificate signing requests process specific parameters.
*/
data class DoormanCertificateConfig(val crlDistributionPoint: URL,
val compatibilityZoneURL: URL,
val crlServerSocketAddress: NetworkHostAndPort,
val crlUpdatePeriod: Long,
val keyGroup:String,
val validDays: Int,

View File

@ -1,26 +1,22 @@
package com.r3.corda.networkmanage.hsm.processor
import com.google.common.net.HostAndPort
import com.r3.corda.networkmanage.common.persistence.CrlIssuer
import com.r3.corda.networkmanage.common.persistence.PersistentCertificateRevocationListStorage
import com.r3.corda.networkmanage.common.persistence.PersistentCertificateRevocationRequestStorage
import com.r3.corda.networkmanage.common.persistence.RequestStatus
import com.r3.corda.networkmanage.common.signer.CertificateRevocationListSigner
import com.r3.corda.networkmanage.hsm.authentication.Authenticator
import com.r3.corda.networkmanage.hsm.authentication.createProvider
import com.r3.corda.networkmanage.hsm.configuration.DoormanCertificateConfig
import com.r3.corda.networkmanage.hsm.menu.Menu
import com.r3.corda.networkmanage.hsm.signer.HsmSigner
import com.r3.corda.networkmanage.hsm.sockets.SocketCertificateRevocationList
import com.r3.corda.networkmanage.hsm.sockets.CertificateRevocationRequestRetriever
import com.r3.corda.networkmanage.hsm.utils.HsmX509Utilities.getAndInitializeKeyStore
import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_INTERMEDIATE_CA
import net.corda.nodeapi.internal.crypto.getX509Certificate
import net.corda.nodeapi.internal.persistence.CordaPersistence
import java.time.Duration
class CrrProcessor(private val config: DoormanCertificateConfig,
private val device: String,
private val keySpecifier: Int,
private val persistance: CordaPersistence) : Processor() {
private val keySpecifier: Int) : Processor() {
private companion object {
private const val RESET = "\u001B[0m"
private const val BLACK = "\u001B[30m"
@ -40,12 +36,12 @@ class CrrProcessor(private val config: DoormanCertificateConfig,
mode = auth.mode,
authStrengthThreshold = auth.threshold)
Menu().withExceptionHandler(this::processError).setExitOption("2", "Quit").addItem("1", "View current and sign a new certificate revocation list", {
val crlStorage = PersistentCertificateRevocationListStorage(persistance)
val currentCrl = crlStorage.getCertificateRevocationList(CrlIssuer.DOORMAN)
val crlTransceiver = SocketCertificateRevocationList(config.crlServerSocketAddress)
val currentCrl = crlTransceiver.getCertificateRevocationList(CrlIssuer.DOORMAN)
printlnColor("Current CRL:")
printlnColor(currentCrl.toString(), YELLOW)
val crrStorage = PersistentCertificateRevocationRequestStorage(persistance)
val approvedRequests = crrStorage.getRevocationRequests(RequestStatus.APPROVED)
val crrRetriever = CertificateRevocationRequestRetriever(config.crlServerSocketAddress)
val approvedRequests = crrRetriever.retrieveApprovedCertificateRevocationRequests()
if (approvedRequests.isEmpty()) {
printlnColor("There are no approved Certificate Revocation Requests.", GREEN)
} else {
@ -60,12 +56,12 @@ class CrrProcessor(private val config: DoormanCertificateConfig,
val keyStore = getAndInitializeKeyStore(provider)
val issuerCertificate = keyStore.getX509Certificate(CORDA_INTERMEDIATE_CA)
val crlSigner = CertificateRevocationListSigner(
revocationListStorage = crlStorage,
revocationListStorage = crlTransceiver,
issuerCertificate = issuerCertificate,
updateInterval = Duration.ofMillis(config.crlUpdatePeriod),
endpoint = config.crlDistributionPoint,
signer = HsmSigner(provider = provider, keyName = CORDA_INTERMEDIATE_CA))
val currentRequests = crrStorage.getRevocationRequests(RequestStatus.DONE)
val currentRequests = crrRetriever.retrieveDoneCertificateRevocationRequests()
crlSigner.createSignedCRL(approvedRequests, currentRequests, signers.toString())
}
})

View File

@ -0,0 +1,36 @@
package com.r3.corda.networkmanage.hsm.sockets
import com.r3.corda.networkmanage.common.persistence.CertificateRevocationRequestData
import com.r3.corda.networkmanage.common.persistence.RequestStatus
import com.r3.corda.networkmanage.common.sockets.CrrsByStatusMessage
import com.r3.corda.networkmanage.common.utils.readObject
import com.r3.corda.networkmanage.common.utils.writeObject
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.contextLogger
import java.net.Socket
class CertificateRevocationRequestRetriever(private val serverHostAndPort: NetworkHostAndPort) {
private companion object {
private val logger = contextLogger()
}
fun retrieveApprovedCertificateRevocationRequests(): List<CertificateRevocationRequestData> {
return retrieveCertificateRevocationRequests(RequestStatus.APPROVED)
}
fun retrieveDoneCertificateRevocationRequests(): List<CertificateRevocationRequestData> {
return retrieveCertificateRevocationRequests(RequestStatus.DONE)
}
private fun retrieveCertificateRevocationRequests(status: RequestStatus): List<CertificateRevocationRequestData> {
require(status == RequestStatus.DONE || status == RequestStatus.APPROVED) { "Allowed status values: APPROVED, DONE" }
return Socket(serverHostAndPort.host, serverHostAndPort.port).use {
it.getOutputStream().let {
logger.debug("Requesting $status certificate revocation requests...")
it.writeObject(CrrsByStatusMessage(status))
}
it.getInputStream().readObject()
}
}
}

View File

@ -0,0 +1,40 @@
package com.r3.corda.networkmanage.hsm.sockets
import com.r3.corda.networkmanage.common.persistence.CertificateRevocationListStorage
import com.r3.corda.networkmanage.common.persistence.CrlIssuer
import com.r3.corda.networkmanage.common.sockets.CertificateRevocationListSubmission
import com.r3.corda.networkmanage.common.sockets.CrlResponseMessage
import com.r3.corda.networkmanage.common.sockets.CrlRetrievalMessage
import com.r3.corda.networkmanage.common.sockets.CrlSubmissionMessage
import com.r3.corda.networkmanage.common.utils.readObject
import com.r3.corda.networkmanage.common.utils.writeObject
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.contextLogger
import java.net.Socket
import java.security.cert.X509CRL
import java.time.Instant
class SocketCertificateRevocationList(private val serverHostAndPort: NetworkHostAndPort) : CertificateRevocationListStorage {
private companion object {
private val logger = contextLogger()
}
override fun getCertificateRevocationList(crlIssuer: CrlIssuer): X509CRL? {
return Socket(serverHostAndPort.host, serverHostAndPort.port).use {
logger.debug("Requesting the current revocation list...")
it.getOutputStream().writeObject(CrlRetrievalMessage())
it.getInputStream().readObject<CrlResponseMessage>().crl
}
}
override fun saveCertificateRevocationList(crl: X509CRL, crlIssuer: CrlIssuer, signedBy: String, revokedAt: Instant) {
Socket(serverHostAndPort.host, serverHostAndPort.port).use {
it.getOutputStream().let {
it.writeObject(CrlSubmissionMessage())
logger.debug("Submitting a new revocation list...")
it.writeObject(CertificateRevocationListSubmission(crl, signedBy, revokedAt))
}
}
}
}

View File

@ -0,0 +1,123 @@
package com.r3.corda.networkmanage.doorman.sockets
import com.nhaarman.mockito_kotlin.eq
import com.nhaarman.mockito_kotlin.mock
import com.r3.corda.networkmanage.common.persistence.*
import com.r3.corda.networkmanage.common.sockets.CrlResponseMessage
import com.r3.corda.networkmanage.common.sockets.CrlRetrievalMessage
import com.r3.corda.networkmanage.common.sockets.CrrsByStatusMessage
import com.r3.corda.networkmanage.common.utils.readObject
import com.r3.corda.networkmanage.common.utils.writeObject
import net.corda.core.crypto.Crypto
import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.x500Name
import net.corda.nodeapi.internal.crypto.ContentSignerBuilder
import net.corda.testing.core.SerializationEnvironmentRule
import net.corda.testing.core.freePort
import net.corda.testing.internal.DEV_INTERMEDIATE_CA
import org.bouncycastle.cert.X509v2CRLBuilder
import org.bouncycastle.cert.jcajce.JcaX509CRLConverter
import org.bouncycastle.jce.provider.BouncyCastleProvider
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import java.math.BigInteger
import java.net.Socket
import java.security.cert.CRLReason
import java.security.cert.X509CRL
import java.time.Instant
import java.util.*
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
class CertificateRevocationSocketServerTest {
@Rule
@JvmField
val testSerialization = SerializationEnvironmentRule(true)
private lateinit var crlStorage: CertificateRevocationListStorage
private lateinit var crrStorage: CertificateRevocationRequestStorage
private lateinit var server: CertificateRevocationSocketServer
@Before
fun setUp() {
crlStorage = mock {
on { getCertificateRevocationList(eq(CrlIssuer.DOORMAN)) }.then { createCrl() }
}
crrStorage = mock {
on { getRevocationRequests(eq(RequestStatus.APPROVED)) }.then { listOf(createCertificateRevocationRequest(RequestStatus.APPROVED)) }
on { getRevocationRequests(eq(RequestStatus.DONE)) }.then { listOf(createCertificateRevocationRequest(RequestStatus.DONE)) }
}
server = CertificateRevocationSocketServer(freePort(), crlStorage, crrStorage)
server.start()
Thread.sleep(100)
}
@After
fun tearDown() {
server.close()
}
@Test
fun `crl is served correctly`() {
// given
// when
val crl = Socket("localhost", server.port).use {
it.getOutputStream().writeObject(CrlRetrievalMessage())
it.getInputStream().readObject<CrlResponseMessage>().crl
}
// then
assertNotNull(crl)
}
@Test
fun `approved requests are served correctly`() {
// given
// when
val approvedRequests = Socket("localhost", server.port).use {
it.getOutputStream().writeObject(CrrsByStatusMessage(RequestStatus.APPROVED))
it.getInputStream().readObject<List<CertificateRevocationRequestData>>()
}
// then
assertNotNull(approvedRequests)
assertEquals(RequestStatus.APPROVED, approvedRequests.first().status)
}
@Test
fun `done requests are served correctly`() {
// given
// when
val doneRequests = Socket("localhost", server.port).use {
it.getOutputStream().writeObject(CrrsByStatusMessage(RequestStatus.DONE))
it.getInputStream().readObject<List<CertificateRevocationRequestData>>()
}
// then
assertNotNull(doneRequests)
assertEquals(RequestStatus.DONE, doneRequests.first().status)
}
private fun createCrl(): X509CRL {
val builder = X509v2CRLBuilder(CordaX500Name.build(DEV_INTERMEDIATE_CA.certificate.issuerX500Principal).x500Name, Date())
val provider = BouncyCastleProvider()
val crlHolder = builder.build(ContentSignerBuilder.build(Crypto.RSA_SHA256, Crypto.generateKeyPair(Crypto.RSA_SHA256).private, provider))
return JcaX509CRLConverter().setProvider(provider).getCRL(crlHolder)
}
private fun createCertificateRevocationRequest(status: RequestStatus): CertificateRevocationRequestData {
return CertificateRevocationRequestData(
requestId = "123",
legalName = CordaX500Name.parse("CN=Test Corp, O=Test, L=London, C=GB"),
reporter = "Test",
certificateSerialNumber = BigInteger.TEN,
status = status,
reason = CRLReason.KEY_COMPROMISE,
modifiedAt = Instant.now()
)
}
}

View File

@ -1,3 +1,13 @@
/*
* R3 Proprietary and Confidential
*
* Copyright (c) 2018 R3 Limited. All rights reserved.
*
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
*
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
*/
package net.corda.nodeapi.internal.serialization.amqp.custom
import net.corda.nodeapi.internal.crypto.X509CertificateFactory