Make Network registration process more verbose (#251)

* Make the network registration process more verbose
* removed gradle task for building standalone jar for the certificate signing request utility
* Added a flag "--initial-registration" to the corda jar to start the registration
This commit is contained in:
Patrick Kuo
2017-02-20 13:22:37 +00:00
committed by GitHub
parent 99721bf8f1
commit 9a0a9567f3
13 changed files with 192 additions and 189 deletions

View File

@ -9,14 +9,14 @@ as possible.
However this is not secure for the real network. This documentation will explain the procedure of obtaining a signed However this is not secure for the real network. This documentation will explain the procedure of obtaining a signed
certificate for TestNet. certificate for TestNet.
.. warning:: The TestNet has not been setup yet as of Milestone 6 release. You will not be able to connect to the .. warning:: The TestNet has not been setup yet as of Milestone 8 release. You will not be able to connect to the
certificate signing server. certificate signing server.
Certificate signing request utility Initial Registration
----------------------------------- --------------------
The utility creates certificate signing request based on node information obtained from the node configuration. The certificate signing request will be created based on node information obtained from the node configuration.
The following information from the node configuration file is needed to generate a certificate signing request. The following information from the node configuration file is needed to generate the request.
:myLegalName: Your company's legal name. e.g. "Mega Corp LLC". This needs to be unique on the network. If another node :myLegalName: Your company's legal name. e.g. "Mega Corp LLC". This needs to be unique on the network. If another node
has already been permissioned with this name then the permissioning server will automatically reject the request. The has already been permissioned with this name then the permissioning server will automatically reject the request. The
@ -32,40 +32,25 @@ The following information from the node configuration file is needed to generate
:certificateSigningService: Certificate signing server URL. A certificate signing server will be hosted by R3 in the near :certificateSigningService: Certificate signing server URL. A certificate signing server will be hosted by R3 in the near
future. e.g."https://testnet.certificate.corda.net" future. e.g."https://testnet.certificate.corda.net"
A new pair of private and public keys will be generated by the utility and will be used to create the request. A new pair of private and public keys generated by the Corda node will be used to create the request.
The utility will submit the request to the network permissioning server and poll for a result periodically to retrieve the certificates. The utility will submit the request to the network permissioning server and poll for a result periodically to retrieve the certificates.
Once the request has been approved and the certificates downloaded from the server, the utility will create the key store and trust store using the certificates and the generated private key. Once the request has been approved and the certificates downloaded from the server, the node will create the keystore and trust store using the certificates and the generated private key.
.. note:: You can exit the utility at any time if the approval process is taking longer than expected. The request process will resume on restart. .. note:: You can exit the utility at any time if the approval process is taking longer than expected. The request process will resume on restart.
This process only needs to be done once when the node connects to the network for the first time, or when the certificate expires. This process only is needed when the node connects to the network for the first time, or when the certificate expires.
Building the utility
--------------------
The utility will be created as part of the gradle ``:node`` module ``buildCordaJAR`` task.
You can also build the utility JAR by run the following command from the Corda project root directory.
**Windows**::
gradlew.bat :node:buildCertSigningRequestUtilityJAR
**Other**::
./gradlew :node:buildCertSigningRequestUtilityJAR
The utility JAR will be created in ``<Project Root Dir>/node/build/libs/certSigningRequestUtility.jar``
Running the utility Starting the Registration
------------------- -------------------------
You will need to specify the working directory of your Corda node using ``--base-dir`` flag. This is defaulted to current directory if left blank. You will need to specify the working directory of your Corda node using ``--base-dir`` flag. This is defaulted to current directory if left blank.
You can also specify the location of ``node.conf`` with ``--config-file`` flag if it's not in the working directory. You can also specify the location of ``node.conf`` with ``--config-file`` flag if it's not in the working directory.
**Running the Utility**:: **To start the registration**::
java -jar certSigningRequestUtility.jar --base-dir <<optional>> --config-file <<optional>> java -jar corda.jar --initial-registration --base-dir <<optional>> --config-file <<optional>>
A ``certificates`` folder containing the keystore and trust store will be created in the base directory when the process is completed. A ``certificates`` folder containing the keystore and trust store will be created in the base directory when the process is completed.

View File

@ -48,7 +48,7 @@ dependencies {
compile project(':node') compile project(':node')
} }
task buildCordaJAR(type: FatCapsule, dependsOn: ['buildCertSigningRequestUtilityJAR']) { task buildCordaJAR(type: FatCapsule) {
applicationClass 'net.corda.node.Corda' applicationClass 'net.corda.node.Corda'
archiveName "corda-${corda_version}.jar" archiveName "corda-${corda_version}.jar"
applicationSource = files(project.tasks.findByName('jar'), '../build/classes/main/CordaCaplet.class', 'config/dev/log4j2.xml') applicationSource = files(project.tasks.findByName('jar'), '../build/classes/main/CordaCaplet.class', 'config/dev/log4j2.xml')
@ -78,15 +78,6 @@ task buildCordaJAR(type: FatCapsule, dependsOn: ['buildCertSigningRequestUtility
} }
} }
task buildCertSigningRequestUtilityJAR(type: FatCapsule) {
applicationClass 'net.corda.node.utilities.certsigning.CertificateSignerKt'
archiveName 'certSigningRequestUtility.jar'
capsuleManifest {
systemProperties['log4j.configuration'] = 'log4j2.xml'
minJavaVersion = '1.8.0'
}
}
artifacts { artifacts {
runtimeArtifacts buildCordaJAR runtimeArtifacts buildCordaJAR
publish buildCordaJAR { publish buildCordaJAR {

View File

@ -29,6 +29,7 @@ class ArgsParser {
.defaultsTo(Level.INFO) .defaultsTo(Level.INFO)
private val logToConsoleArg = optionParser.accepts("log-to-console", "If set, prints logging to the console as well as to a file.") private val logToConsoleArg = optionParser.accepts("log-to-console", "If set, prints logging to the console as well as to a file.")
private val isWebserverArg = optionParser.accepts("webserver") private val isWebserverArg = optionParser.accepts("webserver")
private val isRegistrationArg = optionParser.accepts("initial-registration", "Start initial node registration with Corda network to obtain certificate from the permissioning server.")
private val helpArg = optionParser.accepts("help").forHelp() private val helpArg = optionParser.accepts("help").forHelp()
fun parse(vararg args: String): CmdLineOptions { fun parse(vararg args: String): CmdLineOptions {
@ -42,7 +43,8 @@ class ArgsParser {
val loggingLevel = optionSet.valueOf(loggerLevel) val loggingLevel = optionSet.valueOf(loggerLevel)
val logToConsole = optionSet.has(logToConsoleArg) val logToConsole = optionSet.has(logToConsoleArg)
val isWebserver = optionSet.has(isWebserverArg) val isWebserver = optionSet.has(isWebserverArg)
return CmdLineOptions(baseDirectory, configFile, help, loggingLevel, logToConsole, isWebserver) val isRegistration = optionSet.has(isRegistrationArg)
return CmdLineOptions(baseDirectory, configFile, help, loggingLevel, logToConsole, isWebserver, isRegistration)
} }
fun printHelp(sink: PrintStream) = optionParser.printHelpOn(sink) fun printHelp(sink: PrintStream) = optionParser.printHelpOn(sink)
@ -53,7 +55,8 @@ data class CmdLineOptions(val baseDirectory: Path,
val help: Boolean, val help: Boolean,
val loggingLevel: Level, val loggingLevel: Level,
val logToConsole: Boolean, val logToConsole: Boolean,
val isWebserver: Boolean) { val isWebserver: Boolean,
val isRegistration: Boolean) {
fun loadConfig(allowMissingConfig: Boolean = false, configOverrides: Map<String, Any?> = emptyMap()): Config { fun loadConfig(allowMissingConfig: Boolean = false, configOverrides: Map<String, Any?> = emptyMap()): Config {
return ConfigHelper.loadConfig(baseDirectory, configFile, allowMissingConfig, configOverrides) return ConfigHelper.loadConfig(baseDirectory, configFile, allowMissingConfig, configOverrides)
} }

View File

@ -8,6 +8,8 @@ import net.corda.core.utilities.Emoji
import net.corda.node.internal.Node import net.corda.node.internal.Node
import net.corda.node.services.config.FullNodeConfiguration import net.corda.node.services.config.FullNodeConfiguration
import net.corda.node.utilities.ANSIProgressObserver import net.corda.node.utilities.ANSIProgressObserver
import net.corda.node.utilities.registration.HTTPNetworkRegistrationService
import net.corda.node.utilities.registration.NetworkRegistrationHelper
import net.corda.node.webserver.WebServer import net.corda.node.webserver.WebServer
import org.fusesource.jansi.Ansi import org.fusesource.jansi.Ansi
import org.fusesource.jansi.AnsiConsole import org.fusesource.jansi.AnsiConsole
@ -72,6 +74,17 @@ fun main(args: Array<String>) {
exitProcess(2) exitProcess(2)
} }
if (cmdlineOptions.isRegistration) {
println()
println("******************************************************************")
println("* *")
println("* Registering as a new participant with Corda network *")
println("* *")
println("******************************************************************")
NetworkRegistrationHelper(conf, HTTPNetworkRegistrationService(conf.certificateSigningService)).buildKeystore()
exitProcess(0)
}
log.info("Main class: ${FullNodeConfiguration::class.java.protectionDomain.codeSource.location.toURI().path}") log.info("Main class: ${FullNodeConfiguration::class.java.protectionDomain.codeSource.location.toURI().path}")
val info = ManagementFactory.getRuntimeMXBean() val info = ManagementFactory.getRuntimeMXBean()
log.info("CommandLine Args: ${info.inputArguments.joinToString(" ")}") log.info("CommandLine Args: ${info.inputArguments.joinToString(" ")}")

View File

@ -191,7 +191,10 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
log.warn("Corda node is running in dev mode.") log.warn("Corda node is running in dev mode.")
configuration.configureWithDevSSLCertificate() configuration.configureWithDevSSLCertificate()
} }
require(hasSSLCertificates()) { "SSL certificates not found." } require(hasSSLCertificates()) { "Identity certificate not found. " +
"Please either copy your existing identity key and certificate from another node, " +
"or if you don't have one yet, fill out the config file and run corda.jar --initial-registration. " +
"Read more at: https://docs.corda.net/permissioning.html" }
log.info("Node starting up ...") log.info("Node starting up ...")

View File

@ -10,6 +10,7 @@ import net.corda.node.serialization.NodeClock
import net.corda.node.services.User import net.corda.node.services.User
import net.corda.node.services.network.NetworkMapService import net.corda.node.services.network.NetworkMapService
import net.corda.node.utilities.TestClock import net.corda.node.utilities.TestClock
import java.net.URL
import java.nio.file.Path import java.nio.file.Path
import java.util.* import java.util.*
@ -32,6 +33,7 @@ interface NodeConfiguration : SSLConfiguration {
val dataSourceProperties: Properties get() = Properties() val dataSourceProperties: Properties get() = Properties()
val rpcUsers: List<User> get() = emptyList() val rpcUsers: List<User> get() = emptyList()
val devMode: Boolean val devMode: Boolean
val certificateSigningService: URL
} }
/** /**
@ -46,6 +48,7 @@ class FullNodeConfiguration(override val baseDirectory: Path, val config: Config
override val trustStorePassword: String by config override val trustStorePassword: String by config
override val dataSourceProperties: Properties by config override val dataSourceProperties: Properties by config
override val devMode: Boolean by config.getOrElse { false } override val devMode: Boolean by config.getOrElse { false }
override val certificateSigningService: URL by config
override val networkMapService: NetworkMapInfo? = config.getOptionalConfig("networkMapService")?.run { override val networkMapService: NetworkMapInfo? = config.getOptionalConfig("networkMapService")?.run {
NetworkMapInfo( NetworkMapInfo(
HostAndPort.fromString(getString("address")), HostAndPort.fromString(getString("address")),

View File

@ -1,131 +0,0 @@
package net.corda.node.utilities.certsigning
import net.corda.core.*
import net.corda.core.crypto.X509Utilities
import net.corda.core.crypto.X509Utilities.CORDA_CLIENT_CA
import net.corda.core.crypto.X509Utilities.CORDA_CLIENT_CA_PRIVATE_KEY
import net.corda.core.crypto.X509Utilities.CORDA_ROOT_CA
import net.corda.core.crypto.X509Utilities.addOrReplaceCertificate
import net.corda.core.crypto.X509Utilities.addOrReplaceKey
import net.corda.core.utilities.loggerFor
import net.corda.node.ArgsParser
import net.corda.node.services.config.FullNodeConfiguration
import net.corda.node.services.config.NodeConfiguration
import net.corda.node.services.config.getValue
import net.corda.node.utilities.certsigning.CertificateSigner.Companion.log
import java.net.URL
import java.security.KeyPair
import java.security.cert.Certificate
import kotlin.system.exitProcess
/**
* This check the [certificatePath] for certificates required to connect to the Corda network.
* If the certificates are not found, a [PKCS10CertificationRequest] will be submitted to Corda network permissioning server using [CertificateSigningService].
* This process will enter a slow polling loop until the request has been approved, and then
* the certificate chain will be downloaded and stored in [KeyStore] reside in [certificatePath].
*/
class CertificateSigner(val config: NodeConfiguration, val certService: CertificateSigningService) {
companion object {
val pollInterval = 1.minutes
val log = loggerFor<CertificateSigner>()
}
fun buildKeyStore() {
config.certificatesDirectory.createDirectories()
val caKeyStore = X509Utilities.loadOrCreateKeyStore(config.keyStoreFile, config.keyStorePassword)
if (!caKeyStore.containsAlias(CORDA_CLIENT_CA)) {
// No certificate found in key store, create certificate signing request and post request to signing server.
log.info("No certificate found in key store, creating certificate signing request...")
// Create or load key pair from the key store.
val keyPair = X509Utilities.loadOrCreateKeyPairFromKeyStore(config.keyStoreFile, config.keyStorePassword,
config.keyStorePassword, CORDA_CLIENT_CA_PRIVATE_KEY) {
X509Utilities.createSelfSignedCACert(config.myLegalName)
}
log.info("Submitting certificate signing request to Corda certificate signing server.")
val requestId = submitCertificateSigningRequest(keyPair)
log.info("Successfully submitted request to Corda certificate signing server, request ID : $requestId")
log.info("Start polling server for certificate signing approval.")
val certificates = pollServerForCertificates(requestId)
log.info("Certificate signing request approved, installing new certificates.")
// Save private key and certificate chain to the key store.
caKeyStore.addOrReplaceKey(CORDA_CLIENT_CA_PRIVATE_KEY, keyPair.private,
config.keyStorePassword.toCharArray(), certificates)
// Assumes certificate chain always starts with client certificate and end with root certificate.
caKeyStore.addOrReplaceCertificate(CORDA_CLIENT_CA, certificates.first())
X509Utilities.saveKeyStore(caKeyStore, config.keyStoreFile, config.keyStorePassword)
// Save certificates to trust store.
val trustStore = X509Utilities.loadOrCreateKeyStore(config.trustStoreFile, config.trustStorePassword)
// Assumes certificate chain always starts with client certificate and end with root certificate.
trustStore.addOrReplaceCertificate(CORDA_ROOT_CA, certificates.last())
X509Utilities.saveKeyStore(trustStore, config.trustStoreFile, config.trustStorePassword)
} else {
log.trace("Certificate already exists, exiting certificate signer...")
}
}
/**
* Poll Certificate Signing Server for approved certificate,
* enter a slow polling loop if server return null.
* @param requestId Certificate signing request ID.
* @return Map of certificate chain.
*/
private fun pollServerForCertificates(requestId: String): Array<Certificate> {
// Poll server to download the signed certificate once request has been approved.
var certificates = certService.retrieveCertificates(requestId)
while (certificates == null) {
Thread.sleep(pollInterval.toMillis())
certificates = certService.retrieveCertificates(requestId)
}
return certificates
}
/**
* Submit Certificate Signing Request to Certificate signing service if request ID not found in file system
* New request ID will be stored in requestId.txt
* @param keyPair Public Private key pair generated for SSL certification.
* @return Request ID return from the server.
*/
private fun submitCertificateSigningRequest(keyPair: KeyPair): String {
val requestIdStore = config.certificatesDirectory / "certificate-request-id.txt"
// Retrieve request id from file if exists, else post a request to server.
return if (!requestIdStore.exists()) {
val request = X509Utilities.createCertificateSigningRequest(config.myLegalName, config.nearestCity, config.emailAddress, keyPair)
// Post request to signing server via http.
val requestId = certService.submitRequest(request)
// Persists request ID to file in case of node shutdown.
requestIdStore.writeLines(listOf(requestId))
requestId
} else {
requestIdStore.readLines { it.findFirst().get() }
}
}
}
fun main(args: Array<String>) {
val argsParser = ArgsParser()
val cmdlineOptions = try {
argsParser.parse(*args)
} catch (ex: Exception) {
log.error("Unable to parse args", ex)
argsParser.printHelp(System.out)
exitProcess(1)
}
val config = cmdlineOptions.loadConfig(allowMissingConfig = true)
val configuration = object : NodeConfiguration by FullNodeConfiguration(cmdlineOptions.baseDirectory, config) {
val certificateSigningService: URL by config
}
// TODO: Use HTTPS instead
CertificateSigner(configuration, HTTPCertificateSigningService(configuration.certificateSigningService)).buildKeyStore()
}

View File

@ -1,4 +1,4 @@
package net.corda.node.utilities.certsigning package net.corda.node.utilities.registration
import net.corda.core.crypto.CertificateStream import net.corda.core.crypto.CertificateStream
import org.apache.commons.io.IOUtils import org.apache.commons.io.IOUtils
@ -11,12 +11,13 @@ import java.security.cert.Certificate
import java.util.* import java.util.*
import java.util.zip.ZipInputStream import java.util.zip.ZipInputStream
class HTTPCertificateSigningService(val server: URL) : CertificateSigningService { class HTTPNetworkRegistrationService(val server: URL) : NetworkRegistrationService {
companion object { companion object {
// TODO: Propagate version information from gradle // TODO: Propagate version information from gradle
val clientVersion = "1.0" val clientVersion = "1.0"
} }
@Throws(CertificateRequestException::class)
override fun retrieveCertificates(requestId: String): Array<Certificate>? { override fun retrieveCertificates(requestId: String): Array<Certificate>? {
// Poll server to download the signed certificate once request has been approved. // Poll server to download the signed certificate once request has been approved.
val url = URL("$server/api/certificate/$requestId") val url = URL("$server/api/certificate/$requestId")
@ -34,7 +35,7 @@ class HTTPCertificateSigningService(val server: URL) : CertificateSigningService
certificates.toTypedArray() certificates.toTypedArray()
} }
HTTP_NO_CONTENT -> null HTTP_NO_CONTENT -> null
HTTP_UNAUTHORIZED -> throw IOException("Certificate signing request has been rejected: ${conn.errorMessage}") HTTP_UNAUTHORIZED -> throw CertificateRequestException("Certificate signing request has been rejected: ${conn.errorMessage}")
else -> throwUnexpectedResponseCode(conn) else -> throwUnexpectedResponseCode(conn)
} }
} }

View File

@ -0,0 +1,130 @@
package net.corda.node.utilities.registration
import net.corda.core.*
import net.corda.core.crypto.X509Utilities
import net.corda.core.crypto.X509Utilities.CORDA_CLIENT_CA
import net.corda.core.crypto.X509Utilities.CORDA_ROOT_CA
import net.corda.core.crypto.X509Utilities.addOrReplaceCertificate
import net.corda.core.crypto.X509Utilities.addOrReplaceKey
import net.corda.node.services.config.NodeConfiguration
import org.bouncycastle.openssl.jcajce.JcaPEMWriter
import org.bouncycastle.util.io.pem.PemObject
import java.io.StringWriter
import java.security.KeyPair
import java.security.cert.Certificate
import kotlin.system.exitProcess
/**
* This checks the [config.certificatesDirectory] for certificates required to connect to a Corda network.
* If the certificates are not found, a [PKCS10CertificationRequest] will be submitted to Corda network permissioning server using [NetworkRegistrationService].
* This process will enter a polling loop until the request has been approved, and then
* the certificate chain will be downloaded and stored in [Keystore] reside in [config.certificatesDirectory].
*/
class NetworkRegistrationHelper(val config: NodeConfiguration, val certService: NetworkRegistrationService) {
companion object {
val pollInterval = 10.seconds
val SELF_SIGNED_PRIVATE_KEY = "Self Signed Private Key"
}
private val requestIdStore = config.certificatesDirectory / "certificate-request-id.txt"
private val keystorePassword = config.keyStorePassword
// TODO: Use different password for private key.
private val privateKeyPassword = config.keyStorePassword
fun buildKeystore() {
config.certificatesDirectory.createDirectories()
val caKeyStore = X509Utilities.loadOrCreateKeyStore(config.keyStoreFile, keystorePassword)
if (!caKeyStore.containsAlias(CORDA_CLIENT_CA)) {
// Create or load self signed keypair from the key store.
// We use the self sign certificate to store the key temporarily in the keystore while waiting for the request approval.
if (!caKeyStore.containsAlias(SELF_SIGNED_PRIVATE_KEY)) {
val selfSignCert = X509Utilities.createSelfSignedCACert(config.myLegalName)
// Save to the key store.
caKeyStore.addOrReplaceKey(SELF_SIGNED_PRIVATE_KEY, selfSignCert.keyPair.private, privateKeyPassword.toCharArray(), arrayOf(selfSignCert.certificate))
X509Utilities.saveKeyStore(caKeyStore, config.keyStoreFile, keystorePassword)
}
val keyPair = X509Utilities.loadKeyPairFromKeyStore(config.keyStoreFile, keystorePassword, privateKeyPassword, SELF_SIGNED_PRIVATE_KEY)
val requestId = submitOrResumeCertificateSigningRequest(keyPair)
val certificates = try {
pollServerForCertificates(requestId)
} catch (e: CertificateRequestException) {
System.err.println(e.message)
println("Please make sure the details in configuration file are correct and try again.")
println("Corda node will now terminate.")
requestIdStore.deleteIfExists()
exitProcess(1)
}
println("Certificate signing request approved, storing private key with the certificate chain.")
// Save private key and certificate chain to the key store.
caKeyStore.addOrReplaceKey(CORDA_CLIENT_CA, keyPair.private, privateKeyPassword.toCharArray(), certificates)
caKeyStore.deleteEntry(SELF_SIGNED_PRIVATE_KEY)
X509Utilities.saveKeyStore(caKeyStore, config.keyStoreFile, keystorePassword)
// Save root certificates to trust store.
val trustStore = X509Utilities.loadOrCreateKeyStore(config.trustStoreFile, config.trustStorePassword)
// Assumes certificate chain always starts with client certificate and end with root certificate.
trustStore.addOrReplaceCertificate(CORDA_ROOT_CA, certificates.last())
X509Utilities.saveKeyStore(trustStore, config.trustStoreFile, config.trustStorePassword)
println("Certificate and private key stored in ${config.keyStoreFile}.")
// All done, clean up temp files.
requestIdStore.deleteIfExists()
} else {
println("Certificate already exists, Corda node will now terminate...")
}
}
/**
* Poll Certificate Signing Server for approved certificate,
* enter a slow polling loop if server return null.
* @param requestId Certificate signing request ID.
* @return Map of certificate chain.
*/
private fun pollServerForCertificates(requestId: String): Array<Certificate> {
println("Start polling server for certificate signing approval.")
// Poll server to download the signed certificate once request has been approved.
var certificates = certService.retrieveCertificates(requestId)
while (certificates == null) {
Thread.sleep(pollInterval.toMillis())
certificates = certService.retrieveCertificates(requestId)
}
return certificates
}
/**
* Submit Certificate Signing Request to Certificate signing service if request ID not found in file system
* New request ID will be stored in requestId.txt
* @param keyPair Public Private key pair generated for SSL certification.
* @return Request ID return from the server.
*/
private fun submitOrResumeCertificateSigningRequest(keyPair: KeyPair): String {
// Retrieve request id from file if exists, else post a request to server.
return if (!requestIdStore.exists()) {
val request = X509Utilities.createCertificateSigningRequest(config.myLegalName, config.nearestCity, config.emailAddress, keyPair)
val writer = StringWriter()
JcaPEMWriter(writer).use {
it.writeObject(PemObject("CERTIFICATE REQUEST", request.encoded))
}
println("Certificate signing request with the following information will be submitted to the Corda certificate signing server.")
println()
println("Legal Name: ${config.myLegalName}")
println("Nearest City: ${config.nearestCity}")
println("Email: ${config.emailAddress}")
println()
println("Public Key: ${keyPair.public}")
println()
println("$writer")
// Post request to signing server via http.
println("Submitting certificate signing request to Corda certificate signing server.")
val requestId = certService.submitRequest(request)
// Persists request ID to file in case of node shutdown.
requestIdStore.writeLines(listOf(requestId))
println("Successfully submitted request to Corda certificate signing server, request ID: $requestId.")
requestId
} else {
val requestId = requestIdStore.readLines { it.findFirst().get() }
println("Resuming from previous certificate signing request, request ID: $requestId.")
requestId
}
}
}

View File

@ -1,12 +1,15 @@
package net.corda.node.utilities.certsigning package net.corda.node.utilities.registration
import org.bouncycastle.pkcs.PKCS10CertificationRequest import org.bouncycastle.pkcs.PKCS10CertificationRequest
import java.security.cert.Certificate import java.security.cert.Certificate
interface CertificateSigningService { interface NetworkRegistrationService {
/** Submits a CSR to the signing service and returns an opaque request ID. */ /** Submits a CSR to the signing service and returns an opaque request ID. */
fun submitRequest(request: PKCS10CertificationRequest): String fun submitRequest(request: PKCS10CertificationRequest): String
/** Poll Certificate Signing Server for the request and returns a chain of certificates if request has been approved, null otherwise. */ /** Poll Certificate Signing Server for the request and returns a chain of certificates if request has been approved, null otherwise. */
@Throws(CertificateRequestException::class)
fun retrieveCertificates(requestId: String): Array<Certificate>? fun retrieveCertificates(requestId: String): Array<Certificate>?
} }
class CertificateRequestException(message: String) : Exception(message)

View File

@ -20,7 +20,8 @@ class ArgsParserTest {
help = false, help = false,
logToConsole = false, logToConsole = false,
loggingLevel = Level.INFO, loggingLevel = Level.INFO,
isWebserver = false)) isWebserver = false,
isRegistration = false))
} }
@Test @Test

View File

@ -1,13 +1,11 @@
package net.corda.node.utilities.certsigning package net.corda.node.utilities.registration
import com.nhaarman.mockito_kotlin.any import com.nhaarman.mockito_kotlin.any
import com.nhaarman.mockito_kotlin.eq import com.nhaarman.mockito_kotlin.eq
import com.nhaarman.mockito_kotlin.mock import com.nhaarman.mockito_kotlin.mock
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.X509Utilities import net.corda.core.crypto.X509Utilities
import net.corda.core.div
import net.corda.core.exists import net.corda.core.exists
import net.corda.core.readLines
import net.corda.testing.TestNodeConfiguration import net.corda.testing.TestNodeConfiguration
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
@ -16,7 +14,7 @@ import kotlin.test.assertEquals
import kotlin.test.assertFalse import kotlin.test.assertFalse
import kotlin.test.assertTrue import kotlin.test.assertTrue
class CertificateSignerTest { class NetworkRegistrationHelperTest {
@Rule @Rule
@JvmField @JvmField
val tempFolder = TemporaryFolder() val tempFolder = TemporaryFolder()
@ -29,7 +27,7 @@ class CertificateSignerTest {
X509Utilities.createSelfSignedCACert("CORDA_INTERMEDIATE_CA").certificate, X509Utilities.createSelfSignedCACert("CORDA_INTERMEDIATE_CA").certificate,
X509Utilities.createSelfSignedCACert("CORDA_ROOT_CA").certificate) X509Utilities.createSelfSignedCACert("CORDA_ROOT_CA").certificate)
val certService: CertificateSigningService = mock { val certService: NetworkRegistrationService = mock {
on { submitRequest(any()) }.then { id } on { submitRequest(any()) }.then { id }
on { retrieveCertificates(eq(id)) }.then { certs } on { retrieveCertificates(eq(id)) }.then { certs }
} }
@ -42,13 +40,15 @@ class CertificateSignerTest {
assertFalse(config.keyStoreFile.exists()) assertFalse(config.keyStoreFile.exists())
assertFalse(config.trustStoreFile.exists()) assertFalse(config.trustStoreFile.exists())
CertificateSigner(config, certService).buildKeyStore() NetworkRegistrationHelper(config, certService).buildKeystore()
assertTrue(config.keyStoreFile.exists()) assertTrue(config.keyStoreFile.exists())
assertTrue(config.trustStoreFile.exists()) assertTrue(config.trustStoreFile.exists())
X509Utilities.loadKeyStore(config.keyStoreFile, config.keyStorePassword).run { X509Utilities.loadKeyStore(config.keyStoreFile, config.keyStorePassword).run {
assertTrue(containsAlias(X509Utilities.CORDA_CLIENT_CA_PRIVATE_KEY)) assertFalse(containsAlias(X509Utilities.CORDA_CLIENT_CA_PRIVATE_KEY))
val certificateChain = getCertificateChain(X509Utilities.CORDA_CLIENT_CA)
assertEquals(3, certificateChain.size)
assertTrue(containsAlias(X509Utilities.CORDA_CLIENT_CA)) assertTrue(containsAlias(X509Utilities.CORDA_CLIENT_CA))
assertFalse(containsAlias(X509Utilities.CORDA_INTERMEDIATE_CA)) assertFalse(containsAlias(X509Utilities.CORDA_INTERMEDIATE_CA))
assertFalse(containsAlias(X509Utilities.CORDA_INTERMEDIATE_CA_PRIVATE_KEY)) assertFalse(containsAlias(X509Utilities.CORDA_INTERMEDIATE_CA_PRIVATE_KEY))
@ -64,7 +64,5 @@ class CertificateSignerTest {
assertTrue(containsAlias(X509Utilities.CORDA_ROOT_CA)) assertTrue(containsAlias(X509Utilities.CORDA_ROOT_CA))
assertFalse(containsAlias(X509Utilities.CORDA_ROOT_CA_PRIVATE_KEY)) assertFalse(containsAlias(X509Utilities.CORDA_ROOT_CA_PRIVATE_KEY))
} }
assertEquals(id, (config.certificatesDirectory / "certificate-request-id.txt").readLines { it.findFirst().get() })
} }
} }

View File

@ -1,5 +1,6 @@
@file:Suppress("UNUSED_PARAMETER", "UNCHECKED_CAST") @file:Suppress("UNUSED_PARAMETER", "UNCHECKED_CAST")
@file:JvmName("CoreTestUtils") @file:JvmName("CoreTestUtils")
package net.corda.testing package net.corda.testing
import com.google.common.net.HostAndPort import com.google.common.net.HostAndPort
@ -23,6 +24,7 @@ import net.corda.testing.node.MockIdentityService
import net.corda.testing.node.MockServices import net.corda.testing.node.MockServices
import net.corda.testing.node.makeTestDataSourceProperties import net.corda.testing.node.makeTestDataSourceProperties
import java.net.ServerSocket import java.net.ServerSocket
import java.net.URL
import java.nio.file.Path import java.nio.file.Path
import java.security.KeyPair import java.security.KeyPair
import java.util.* import java.util.*
@ -155,6 +157,7 @@ data class TestNodeConfiguration(
override val nearestCity: String = "Null Island", override val nearestCity: String = "Null Island",
override val emailAddress: String = "", override val emailAddress: String = "",
override val exportJMXto: String = "", override val exportJMXto: String = "",
override val devMode: Boolean = true) : NodeConfiguration override val devMode: Boolean = true,
override val certificateSigningService: URL = URL("http://localhost")) : NodeConfiguration
fun Config.getHostAndPort(name: String) = HostAndPort.fromString(getString(name)) fun Config.getHostAndPort(name: String) = HostAndPort.fromString(getString(name))