From e6e28361195d810d3dc01bbe5d646289cd564fdd Mon Sep 17 00:00:00 2001 From: Michal Kit Date: Thu, 8 Feb 2018 16:54:07 +0000 Subject: [PATCH] Implementing dual execution mode of the HSM signing service (#380) * Implementing dual execution mode for the hsm signing service * mend * Addressing review comments * Extracting processor classes --- docs/source/running-doorman.rst | 29 +-- docs/source/running-signing-service.rst | 77 ++++--- network-management/README.md | 16 +- network-management/doorman.conf | 6 +- network-management/hsm.conf | 38 ++-- .../corda/networkmanage/common/HsmBaseTest.kt | 37 +++- .../doorman/NodeRegistrationTest.kt | 2 +- .../networkmanage/hsm/HsmAuthenticatorTest.kt | 6 +- .../networkmanage/hsm/HsmPermissionTest.kt | 14 +- .../hsm/HsmSigningServiceTest.kt | 14 +- .../hsm/SigningServiceIntegrationTest.kt | 2 +- .../common/signer/NetworkMapSigner.kt | 12 +- .../doorman/DoormanParameters.kt | 11 +- .../r3/corda/networkmanage/doorman/Main.kt | 6 +- .../doorman/NetworkManagementServer.kt | 3 +- .../doorman/NetworkManagementUtilities.kt | 28 +-- .../com/r3/corda/networkmanage/hsm/Main.kt | 191 ++++-------------- .../hsm/authentication/Authenticator.kt | 16 +- .../hsm/configuration/Configuration.kt | 65 +++--- .../hsm/processor/CsrProcessor.kt | 117 +++++++++++ .../hsm/processor/NetworkMapProcessor.kt | 52 +++++ .../networkmanage/hsm/signer/HsmCsrSigner.kt | 31 ++- .../hsm/configuration/ConfigurationTest.kt | 16 +- 23 files changed, 468 insertions(+), 321 deletions(-) create mode 100644 network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/processor/CsrProcessor.kt create mode 100644 network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/processor/NetworkMapProcessor.kt diff --git a/docs/source/running-doorman.rst b/docs/source/running-doorman.rst index ddb69dd735..9867254c49 100644 --- a/docs/source/running-doorman.rst +++ b/docs/source/running-doorman.rst @@ -33,23 +33,32 @@ Allowed parameters are: :mode: must be one of: DOORMAN (default), CA_KEYGEN, ROOT_KEYGEN. -:approveAll: Whether to approve all request (defaults to false), this is for debug only. - :database: database properties. The same (including its default value) as for node configuration (see :doc:`corda-configuration-file`). :dataSourceProperties: datasource properties -:jiraConfig: The Jira configuration +:doorman: Doorman specific configuration - :address: The URL to use to connect to Jira + :approveAll: Whether to approve all request (defaults to false), this is for debug only. - :projectCode: The project code on Jira + :approveInterval: How often to process Jira approved requests in seconds. - :username: Username for Jira + :jira: The Jira configuration - :password: Password for Jira + :address: The URL to use to connect to Jira - :doneTransitionCode: Jira status to put approved tickets in + :projectCode: The project code on Jira + + :username: Username for Jira + + :password: Password for Jira + + +:networkMap: Network map specific configuration + + :cacheTimeout: Network map cache entry expiry time (in milliseconds) + + :signInterval: How often to sign the network map in seconds :keystorePath: Path for the keystore. If not set (or null is passed) doorman will NOT perform any signing. This is required in case of the HSM integration where signing process is offloaded (from doorman) to an external service @@ -57,10 +66,6 @@ Allowed parameters are: :rootStorePath: Path for the root keystore -:approveInterval: How often to process Jira approved requests in seconds. This will also be added to the http header, to be use as poll interval in Corda client. - -:signInterval: How often to sign the network map in seconds - Bootstrapping the network parameters ------------------------------------ When doorman is running it will serve the current network parameters. The first time doorman is diff --git a/docs/source/running-signing-service.rst b/docs/source/running-signing-service.rst index 18d69a1635..f208008212 100644 --- a/docs/source/running-signing-service.rst +++ b/docs/source/running-signing-service.rst @@ -4,10 +4,10 @@ Running the signing service The signing service is a bridge between the networking service and the HSM infrastructure. It is responsible for retrieving pending requests for signatures and managing the process of securing these signatures from an HSM infrastructure. -The signing service has a console-based user interface (designed for the manual signing process of the certificate signing requests), -which upon successful startup should display different options to the user. -At the same time, it connects to the database (which is expected to be shared with Doorman) -and periodically polls it and if needed automatically signs the following: network map and certificate revocation list. +The signing service has two execution modes. Each mode focuses on signing one of the two different types of data: certificate signing requests and network map. +Signing of the network map is an automatic process (i.e. does not require human intervention) that retrieves from the database the network map data to be signed. +Certificate signing requests, on the other hand, require human-in-the-loop to be processed and therefore the signing process relies on the console-based interface, that allows for user interaction. +Depending on the configuration each of those processes can be enabled or disabled (see below for more details). See the :doc:`signing-service` for a more detailed description of the service. @@ -28,52 +28,51 @@ Allowed parameters are: :device: HSM connection string. It is of the following format 3001@127.0.0.1, where 3001 is the port number. Default value: "3001@127.0.0.1" -:rootKeyStoreFile: Location of the key store (trust store) containing the root certificate. +:keySpecifier: HSM key specifier. This parameter is vendor specific (see Utimaco docs). -:rootKeyStorePassword: Password for the key store (trust store) containing the root certificate. - -:networkMapKeyGroup: HSM key group for the network map certificate key. This parameter is vendor specific (see Utimaco docs). - -:doormanKeyGroup: HSM key group for the doorman certificate key. This parameter is vendor specific (see Utimaco docs). - -:keySpecifier: HSM key specifier. This parameter is vendor specific (see Utimaco docs). Default value: 1. - -:rootPrivateKeyPassword: Private key password for the root certificate. - -:csrPrivateKeyPassword: Private key password for the intermediate certificate used to sign certficate signing requests. - -:csrCertCrlDistPoint: Certificate revocation list location for the node CA certificate. - -:csrCertCrlIssuer: Certificate revocation list issuer. The expected value is of the X500 name format - e.g. "L=London, C=GB, OU=Org Unit, CN=Service Name". - If not specified, the node CA certificate issuer is considered also as the CRL issuer. - -:databaseProperties: Database properties. +:database: Database properties. :dataSourceProperties: Data source properties. It should describe (or point to) the Doorman database. -:networkMapPrivateKeyPassword: Private key password for the intermediate certificate used to sign the network map. +:csrSigning: CSR signing process configuration parameters. If specified, the signing service will sign approved CSRs. -:validDays: Number of days issued signatures are valid for. + :validDays: Number of days issued signatures are valid for. -:signAuthThreshold: Minimum authentication strength threshold required for certificate signing requests. - Default value: 2 + :rootKeyStoreFile: Location of the key store (trust store) containing the root certificate. -:keyGenAuthThreshold: Minimum authentication strength threshold required for key generation. - Default value: 2 + :rootKeyStorePassword: Password for the key store (trust store) containing the root certificate. -:authMode: Authentication mode, used when validating a user for certificate signing request signature. - Allowed values are: - "PASSWORD" (default) - type-in password authentication - CARD_READER - smart card reader authentication - KEY_FILE - key file authentication + :keyGroup: HSM key group for the doorman certificate key. This parameter is vendor specific (see Utimaco docs). -:authKeyFilePath: Authentication key file. It is used when the 'authMode' is set to "KEY_FILE" - or for the automated signing process - e.g. network map, certificate revocation list. Default value: null + :crlDistributionPoint: Certificate revocation list location for the node CA certificate. -:authKeyFilePassword: Authentication key file password. It is used when the 'authMode' is set to "KEY_FILE" - or for the automated signing process - e.g. network map, certificate revocation list. Default value: null + :authParameters: Authentication configuration for the CSR signing process. + + :mode: Authentication mode. Allowed values are: PASSWORD, CARD_READER and KEY_FILE + + :password: Key file password. Valid only if the authentication mode is set to KEY_FILE. + + :keyFilePath: Key file path. Valid only if authentication mode is set to KEY_FILE. + + :threshold: Minimum authentication strength threshold required for certificate signing requests. + +:networkMapSigning: Network map signing process configuration parameters. If specified, the signing service will sign the network map. + + :username: HSM username to be used when communicating with the HSM. + + :keyGroup: HSM key group for the network map certificate key. This parameter is vendor specific (see Utimaco docs). + + :authParameters: Authentication configuration for the CSR signing process. + + :mode: Authentication mode. Allowed values are: PASSWORD and KEY_FILE + + :password: If the authentication mode is set to KEY_FILE, then it is the key file password. + If the authentication mode is set to PASSWORD, then it is the password string. + + :keyFilePath: Key file path. Valid only if authentication mode is set to KEY_FILE. + + :threshold: Minimum authentication strength threshold required for certificate signing requests. -:signInterval: Interval (in milliseconds) in which all automated signing happens. Default value: 60000 milliseconds Expected behaviour and output upon the service start-up ------------------------------------------------------- diff --git a/network-management/README.md b/network-management/README.md index 9a82144afc..574f1b4dce 100644 --- a/network-management/README.md +++ b/network-management/README.md @@ -106,28 +106,29 @@ The doorman service can use JIRA to manage the certificate signing request appro basedir = "." host = localhost port = 0 - -#For local signing rootStorePath = ${basedir}"/certificates/rootstore.jks" keystorePath = ${basedir}"/certificates/caKeystore.jks" keystorePassword = "password" caPrivateKeyPassword = "password" -# Database config dataSourceProperties { dataSourceClassName = org.h2.jdbcx.JdbcDataSource "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" = "" } + +database { + runMigration = true +} + h2port = 0 -# Doorman config # Comment out this section if running without doorman service -doormanConfig { +doorman { approveInterval = 10000 approveAll = false - jiraConfig { + jira { address = "https://doorman-jira-host.com/" projectCode = "TD" username = "username" @@ -135,9 +136,8 @@ doormanConfig { } } -# Network map config # Comment out this section if running without network map service -networkMapConfig { +networkMap { cacheTimeout = 600000 signInterval = 10000 } diff --git a/network-management/doorman.conf b/network-management/doorman.conf index f4a8b8d18f..0f44a1814a 100644 --- a/network-management/doorman.conf +++ b/network-management/doorman.conf @@ -20,10 +20,10 @@ database { h2port = 0 # Comment out this section if running without doorman service -doormanConfig{ +doorman { approveInterval = 10000 approveAll = false - jiraConfig{ + jira { address = "https://doorman-jira-host.com/" projectCode = "TD" username = "username" @@ -32,7 +32,7 @@ doormanConfig{ } # Comment out this section if running without network map service -networkMapConfig{ +networkMap { cacheTimeout = 600000 signInterval = 10000 } diff --git a/network-management/hsm.conf b/network-management/hsm.conf index 5802cb1eea..b85f7f110b 100644 --- a/network-management/hsm.conf +++ b/network-management/hsm.conf @@ -1,19 +1,31 @@ basedir = "." device = "3001@192.168.0.1" keySpecifier = -1 -authMode = PASSWORD -rootKeyStoreFile = "dummyfile.jks" -rootKeyStorePassword = "trustpass" -csrCertCrlDistPoint = "http://test.com/revoked.crl" -doormanKeyGroup = "DEV.CORDACONNECT.OPS.CERT" -networkMapKeyGroup = "DEV.CORDACONNECT.OPS.NETMAP" -validDays = 3650 -signAuthThreshold = 2 -keyGenAuthThreshold = 2 -authKeyFilePath = "./Administrator.key" -authKeyFilePassword = "Password" -autoUsername = "AUTO_USER" -signInterval = 10000 + +csrSigning { + crlDistributionPoint = "http://test.com/revoked.crl" + validDays = 3650 + rootKeyStoreFile = "dummyfile.jks" + rootKeyStorePassword = "trustpass" + keyGroup = "DEV.CORDACONNECT.OPS.CERT" + authParameters { + mode = PASSWORD + password = "PASSWORD" + threshold = 2 + } +} + +networkMapSigning { + username = "TEST_USERNAME", + keyGroup = "DEV.CORDACONNECT.OPS.NETMAP" + authParameters { + mode = KEY_FILE + password = "PASSWORD" + keyFilePath = "./Administrator.KEY" + threshold = 2 + } +} + h2port = 0 dataSourceProperties { "dataSourceClassName" = org.h2.jdbcx.JdbcDataSource diff --git a/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/common/HsmBaseTest.kt b/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/common/HsmBaseTest.kt index dfefef1a9d..b98ee2b040 100644 --- a/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/common/HsmBaseTest.kt +++ b/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/common/HsmBaseTest.kt @@ -5,8 +5,10 @@ import com.nhaarman.mockito_kotlin.mock import com.nhaarman.mockito_kotlin.whenever import com.r3.corda.networkmanage.HsmSimulator import com.r3.corda.networkmanage.hsm.authentication.InputReader +import com.r3.corda.networkmanage.hsm.configuration.AuthenticationParameters +import com.r3.corda.networkmanage.hsm.configuration.DoormanCertificateParameters +import com.r3.corda.networkmanage.hsm.configuration.NetworkMapCertificateParameters import com.r3.corda.networkmanage.hsm.configuration.Parameters -import com.r3.corda.networkmanage.hsm.generator.AuthMode import com.r3.corda.networkmanage.hsm.generator.CertificateConfiguration import com.r3.corda.networkmanage.hsm.generator.GeneratorParameters import com.r3.corda.networkmanage.hsm.generator.UserAuthenticationParameters @@ -18,6 +20,8 @@ import org.junit.Rule import org.junit.rules.TemporaryFolder import java.nio.file.Path import java.util.* +import com.r3.corda.networkmanage.hsm.authentication.AuthMode as SigningServiceAuthMode +import com.r3.corda.networkmanage.hsm.generator.AuthMode as GeneratorAuthMode abstract class HsmBaseTest { companion object { @@ -55,7 +59,7 @@ abstract class HsmBaseTest { private fun createHsmUserConfigs(username: String): List { return listOf(UserAuthenticationParameters( username = username, - authMode = AuthMode.PASSWORD, + authMode = GeneratorAuthMode.PASSWORD, authToken = "INTEGRATION_TEST", keyFilePassword = null)) } @@ -75,7 +79,7 @@ abstract class HsmBaseTest { @Before fun generateDbName() { - rootKeyStoreFile = tempFolder.root.toPath() / "truststore.jks" + rootKeyStoreFile = tempFolder.root.toPath() / "truststore.jks" dbName = random63BitValue().toString() } @@ -118,12 +122,27 @@ abstract class HsmBaseTest { dataSourceProperties = mock(), device = "${hsmSimulator.port}@${hsmSimulator.host}", keySpecifier = 1, - rootKeyStoreFile = rootKeyStoreFile, - rootKeyStorePassword = TRUSTSTORE_PASSWORD, - doormanKeyGroup = DOORMAN_CERT_KEY_GROUP, - networkMapKeyGroup = NETWORK_MAP_CERT_KEY_GROUP, - validDays = 3650, - csrCertCrlDistPoint = "http://test.com/revoked.crl" + csrSigning = DoormanCertificateParameters( + rootKeyStoreFile = rootKeyStoreFile, + keyGroup = DOORMAN_CERT_KEY_GROUP, + validDays = 3650, + rootKeyStorePassword = TRUSTSTORE_PASSWORD, + crlDistributionPoint = "http://test.com/revoked.crl", + authParameters = AuthenticationParameters( + mode = SigningServiceAuthMode.PASSWORD, + threshold = 2 + ) + ), + networkMapSigning = NetworkMapCertificateParameters( + username = "INTEGRATION_TEST", + keyGroup = NETWORK_MAP_CERT_KEY_GROUP, + authParameters = AuthenticationParameters( + mode = SigningServiceAuthMode.PASSWORD, + password = "INTEGRATION_TEST", + threshold = 2 + ) + + ) ) } diff --git a/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/doorman/NodeRegistrationTest.kt b/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/doorman/NodeRegistrationTest.kt index fb4e9dc8c5..3d61e21f32 100644 --- a/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/doorman/NodeRegistrationTest.kt +++ b/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/doorman/NodeRegistrationTest.kt @@ -144,7 +144,7 @@ class NodeRegistrationTest : IntegrationTest() { serverAddress, configureDatabase(makeTestDataSourceProperties(dbName), DatabaseConfig(runMigration = true)), CertPathAndKey(listOf(csrCa.certificate, rootCaCert), csrCa.keyPair.private), - DoormanConfig(approveAll = true, jiraConfig = null, approveInterval = timeoutMillis), + DoormanConfig(approveAll = true, jira = null, approveInterval = timeoutMillis), networkParameters?.let { NetworkMapStartParams( LocalSigner(networkMapCa), diff --git a/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/hsm/HsmAuthenticatorTest.kt b/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/hsm/HsmAuthenticatorTest.kt index 327f9df551..8a5b852aca 100644 --- a/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/hsm/HsmAuthenticatorTest.kt +++ b/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/hsm/HsmAuthenticatorTest.kt @@ -14,7 +14,11 @@ class HsmAuthenticatorTest : HsmBaseTest() { // given val userInput = givenHsmUserAuthenticationInput() val hsmSigningServiceConfig = createHsmSigningServiceConfig() - val authenticator = Authenticator(provider = hsmSigningServiceConfig.createProvider(hsmSigningServiceConfig.doormanKeyGroup), inputReader = userInput) + val doormanCertificateConfig = hsmSigningServiceConfig.csrSigning!! + val authenticator = Authenticator(provider = createProvider( + doormanCertificateConfig.keyGroup, + hsmSigningServiceConfig.keySpecifier, + hsmSigningServiceConfig.device), inputReader = userInput) val executed = AtomicBoolean(false) // when diff --git a/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/hsm/HsmPermissionTest.kt b/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/hsm/HsmPermissionTest.kt index 43924855dd..a654d23c02 100644 --- a/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/hsm/HsmPermissionTest.kt +++ b/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/hsm/HsmPermissionTest.kt @@ -49,12 +49,15 @@ class HsmPermissionTest : HsmBaseTest() { val hsmSigningServiceConfig = createHsmSigningServiceConfig() val signer = HsmCsrSigner( mock(), - hsmSigningServiceConfig.loadRootKeyStore(), + hsmSigningServiceConfig.csrSigning!!.loadRootKeyStore(), "", null, 3650, Authenticator( - provider = hsmSigningServiceConfig.createProvider(hsmSigningServiceConfig.doormanKeyGroup), + provider = createProvider( + hsmSigningServiceConfig.csrSigning!!.keyGroup, + hsmSigningServiceConfig.keySpecifier, + hsmSigningServiceConfig.device), inputReader = userInput) ) @@ -102,12 +105,15 @@ class HsmPermissionTest : HsmBaseTest() { val hsmSigningServiceConfig = createHsmSigningServiceConfig() val signer = HsmCsrSigner( mock(), - hsmSigningServiceConfig.loadRootKeyStore(), + hsmSigningServiceConfig.csrSigning!!.loadRootKeyStore(), "trustpass", null, 3650, Authenticator( - provider = hsmSigningServiceConfig.createProvider(hsmSigningServiceConfig.doormanKeyGroup), + provider = createProvider( + hsmSigningServiceConfig.csrSigning!!.keyGroup, + hsmSigningServiceConfig.keySpecifier, + hsmSigningServiceConfig.device), inputReader = userInput) ) diff --git a/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/hsm/HsmSigningServiceTest.kt b/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/hsm/HsmSigningServiceTest.kt index ff0ab8dca2..3f035df612 100644 --- a/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/hsm/HsmSigningServiceTest.kt +++ b/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/hsm/HsmSigningServiceTest.kt @@ -62,14 +62,18 @@ class HsmSigningServiceTest : HsmBaseTest() { // given HSM CSR signer val hsmSigningServiceConfig = createHsmSigningServiceConfig() + val doormanCertificateConfig = hsmSigningServiceConfig.csrSigning!! val signer = HsmCsrSigner( mock(), - hsmSigningServiceConfig.loadRootKeyStore(), + doormanCertificateConfig.loadRootKeyStore(), "", null, 3650, Authenticator( - provider = hsmSigningServiceConfig.createProvider(hsmSigningServiceConfig.doormanKeyGroup), + provider = createProvider( + doormanCertificateConfig.keyGroup, + hsmSigningServiceConfig.keySpecifier, + hsmSigningServiceConfig.device), inputReader = userInput) ) @@ -119,8 +123,12 @@ class HsmSigningServiceTest : HsmBaseTest() { // given HSM network map signer val hsmSigningServiceConfig = createHsmSigningServiceConfig() + val networkMapCertificateConfig = hsmSigningServiceConfig.networkMapSigning!! val hsmDataSigner = HsmSigner(Authenticator( - provider = hsmSigningServiceConfig.createProvider(hsmSigningServiceConfig.networkMapKeyGroup), + provider = createProvider( + networkMapCertificateConfig.keyGroup, + hsmSigningServiceConfig.keySpecifier, + hsmSigningServiceConfig.device), inputReader = userInput)) val database = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(runMigration = true)) diff --git a/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/hsm/SigningServiceIntegrationTest.kt b/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/hsm/SigningServiceIntegrationTest.kt index 3ca48799e1..f474603977 100644 --- a/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/hsm/SigningServiceIntegrationTest.kt +++ b/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/hsm/SigningServiceIntegrationTest.kt @@ -92,7 +92,7 @@ class SigningServiceIntegrationTest : HsmBaseTest() { hostAndPort = NetworkHostAndPort(HOST, 0), database = database, csrCertPathAndKey = null, - doormanServiceParameter = DoormanConfig(approveAll = true, approveInterval = 2.seconds.toMillis(), jiraConfig = null), + doormanServiceParameter = DoormanConfig(approveAll = true, approveInterval = 2.seconds.toMillis(), jira = null), startNetworkMap = null) val doormanHostAndPort = server.hostAndPort // Start Corda network registration. diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/signer/NetworkMapSigner.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/signer/NetworkMapSigner.kt index 9116b65e54..bfb128b7ca 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/signer/NetworkMapSigner.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/signer/NetworkMapSigner.kt @@ -21,23 +21,32 @@ class NetworkMapSigner(private val networkMapStorage: NetworkMapStorage, private // in current network map. val latestNetworkParameters = networkMapStorage.getLatestNetworkParameters() if (latestNetworkParameters == null) { - logger.info("No network parameters present") + logger.debug("No network parameters present") return } + logger.debug("Fetching current network map parameters...") val currentNetworkParameters = networkMapStorage.getNetworkParametersOfNetworkMap() logger.debug("Retrieved network map parameters: $currentNetworkParameters") if (currentNetworkParameters?.verified() != latestNetworkParameters) { persistSignedNetworkParameters(latestNetworkParameters) + } else { + logger.debug("Network map parameters up-to-date. Skipping signing.") } + logger.debug("Fetching current network map...") val currentSignedNetworkMap = networkMapStorage.getCurrentNetworkMap() + logger.debug("Fetching node info hashes with VALID certificates...") val nodeInfoHashes = networkMapStorage.getNodeInfoHashes(CertificateStatus.VALID) logger.debug("Retrieved node info hashes: $nodeInfoHashes") val newNetworkMap = NetworkMap(nodeInfoHashes, latestNetworkParameters.serialize().hash) val serialisedNetworkMap = newNetworkMap.serialize() if (serialisedNetworkMap != currentSignedNetworkMap?.raw) { logger.info("Signing a new network map: $newNetworkMap") + logger.debug("Creating a new signed network map: ${serialisedNetworkMap.hash}") val newSignedNetworkMap = SignedDataWithCert(serialisedNetworkMap, signer.signBytes(serialisedNetworkMap.bytes)) networkMapStorage.saveNetworkMap(newSignedNetworkMap) + logger.debug("Signed network map saved") + } else { + logger.debug("Current network map is up-to-date. Skipping signing.") } } @@ -45,5 +54,6 @@ class NetworkMapSigner(private val networkMapStorage: NetworkMapStorage, private logger.info("Signing and persisting network parameters: $networkParameters") val digitalSignature = signer.signObject(networkParameters).sig networkMapStorage.saveNetworkParameters(networkParameters, digitalSignature) + logger.info("Signed network map parameters saved.") } } \ No newline at end of file diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/DoormanParameters.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/DoormanParameters.kt index 0feefe13de..3361a85b06 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/DoormanParameters.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/DoormanParameters.kt @@ -19,8 +19,8 @@ data class NetworkManagementServerParameters(// TODO: Move local signing to sign val database: DatabaseConfig = DatabaseConfig(), val mode: Mode, - val doormanConfig: DoormanConfig?, - val networkMapConfig: NetworkMapConfig?, + val doorman: DoormanConfig?, + val networkMap: NetworkMapConfig?, val updateNetworkParameters: Path?, val trustStorePassword: String?, @@ -51,7 +51,7 @@ data class NetworkManagementServerParameters(// TODO: Move local signing to sign } data class DoormanConfig(val approveAll: Boolean = false, - val jiraConfig: JiraConfig? = null, + val jira: JiraConfig? = null, val approveInterval: Long = NetworkManagementServerParameters.DEFAULT_APPROVE_INTERVAL.toMillis()) data class NetworkMapConfig(val cacheTimeout: Long, @@ -59,9 +59,10 @@ data class NetworkMapConfig(val cacheTimeout: Long, val signInterval: Long = NetworkManagementServerParameters.DEFAULT_SIGN_INTERVAL.toMillis()) enum class Mode { - // TODO CA_KEYGEN now also generates the nework map cert, so it should be renamed. + // TODO CA_KEYGEN now also generates the network map cert, so it should be renamed. DOORMAN, - CA_KEYGEN, ROOT_KEYGEN + CA_KEYGEN, + ROOT_KEYGEN } data class JiraConfig( diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/Main.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/Main.kt index f8afc6cec6..a19d081f65 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/Main.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/Main.kt @@ -71,15 +71,15 @@ fun main(args: Array) { val networkManagementServer = NetworkManagementServer() val networkParameters = updateNetworkParameters?.let { // TODO This check shouldn't be needed. Fix up the config design. - requireNotNull(networkMapConfig) { "'networkMapConfig' config is required for applying network parameters" } + requireNotNull(networkMap) { "'networkMapConfig' config is required for applying network parameters" } println("Parsing network parameters from '${it.toAbsolutePath()}'...") parseNetworkParametersFrom(it) } - val networkMapStartParams = networkMapConfig?.let { + val networkMapStartParams = networkMap?.let { NetworkMapStartParams(csrAndNetworkMap?.second, networkParameters, it) } - networkManagementServer.start(NetworkHostAndPort(host, port), persistence, csrAndNetworkMap?.first, doormanConfig, networkMapStartParams) + networkManagementServer.start(NetworkHostAndPort(host, port), persistence, csrAndNetworkMap?.first, doorman, networkMapStartParams) Runtime.getRuntime().addShutdownHook(thread(start = false) { networkManagementServer.close() diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/NetworkManagementServer.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/NetworkManagementServer.kt index dc829e64b3..9b1a33aaa4 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/NetworkManagementServer.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/NetworkManagementServer.kt @@ -83,13 +83,14 @@ class NetworkManagementServer : Closeable { serverStatus: NetworkManagementServerStatus): RegistrationWebService { logger.info("Starting Doorman server.") val requestService = if (config.approveAll) { + require(config.jira == null) { "Jira configuration cannot be specified when the approveAll parameter is set to true." } logger.warn("Doorman server is in 'Approve All' mode, this will approve all incoming certificate signing requests.") ApproveAllCertificateRequestStorage(PersistentCertificateRequestStorage(database)) } else { PersistentCertificateRequestStorage(database) } - val jiraConfig = config.jiraConfig + val jiraConfig = config.jira val requestProcessor = if (jiraConfig != null) { val jiraWebAPI = AsynchronousJiraRestClientFactory().createWithBasicHttpAuthentication(URI(jiraConfig.address), jiraConfig.username, jiraConfig.password) val jiraClient = JiraClient(jiraWebAPI, jiraConfig.projectCode) diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/NetworkManagementUtilities.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/NetworkManagementUtilities.kt index a646f5dcff..1d31075b7e 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/NetworkManagementUtilities.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/NetworkManagementUtilities.kt @@ -7,7 +7,11 @@ import net.corda.core.internal.createDirectories import net.corda.core.internal.div import net.corda.nodeapi.internal.crypto.CertificateType import net.corda.nodeapi.internal.crypto.X509KeyStore -import net.corda.nodeapi.internal.crypto.X509Utilities +import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_INTERMEDIATE_CA +import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_ROOT_CA +import net.corda.nodeapi.internal.crypto.X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME +import net.corda.nodeapi.internal.crypto.X509Utilities.createCertificate +import net.corda.nodeapi.internal.crypto.X509Utilities.createSelfSignedCACertificate import java.nio.file.Path import javax.security.auth.x500.X500Principal import kotlin.system.exitProcess @@ -36,19 +40,19 @@ fun generateRootKeyPair(rootStoreFile: Path, rootKeystorePass: String?, rootPriv val rootStore = X509KeyStore.fromFile(rootStoreFile, rootKeystorePassword, createNew = true) val rootPrivateKeyPassword = rootPrivateKeyPass ?: readPassword("Root Private Key Password: ") - if (X509Utilities.CORDA_ROOT_CA in rootStore) { - println("${X509Utilities.CORDA_ROOT_CA} already exists in keystore, process will now terminate.") - println(rootStore.getCertificate(X509Utilities.CORDA_ROOT_CA)) + if (CORDA_ROOT_CA in rootStore) { + println("$CORDA_ROOT_CA already exists in keystore, process will now terminate.") + println(rootStore.getCertificate(CORDA_ROOT_CA)) exitProcess(1) } - val selfSignKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) + val selfSignKey = Crypto.generateKeyPair(DEFAULT_TLS_SIGNATURE_SCHEME) // TODO Make the cert subject configurable - val rootCert = X509Utilities.createSelfSignedCACertificate( + val rootCert = createSelfSignedCACertificate( X500Principal("CN=Corda Root CA,$CORDA_X500_BASE"), selfSignKey) rootStore.update { - setPrivateKey(X509Utilities.CORDA_ROOT_CA, selfSignKey.private, listOf(rootCert), rootPrivateKeyPassword) + setPrivateKey(CORDA_ROOT_CA, selfSignKey.private, listOf(rootCert), rootPrivateKeyPassword) } val trustStorePath = (rootStoreFile.parent / "distribute-nodes").createDirectories() / NETWORK_ROOT_TRUSTSTORE_FILENAME @@ -56,7 +60,7 @@ fun generateRootKeyPair(rootStoreFile: Path, rootKeystorePass: String?, rootPriv val networkRootTrustPassword = networkRootTrustPass ?: readPassword("Network Root Trust Store Password: ") X509KeyStore.fromFile(trustStorePath, networkRootTrustPassword, createNew = true).update { - setCertificate(X509Utilities.CORDA_ROOT_CA, rootCert) + setCertificate(CORDA_ROOT_CA, rootCert) } println("Trust store for distribution to nodes created in $trustStorePath") @@ -71,7 +75,7 @@ fun generateSigningKeyPairs(keystoreFile: Path, rootStoreFile: Path, rootKeystor val rootPrivateKeyPassword = rootPrivateKeyPass ?: readPassword("Root private key password: ") val rootKeyStore = X509KeyStore.fromFile(rootStoreFile, rootKeystorePassword) - val rootKeyPairAndCert = rootKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_ROOT_CA, rootPrivateKeyPassword) + val rootKeyPairAndCert = rootKeyStore.getCertificateAndKeyPair(CORDA_ROOT_CA, rootPrivateKeyPassword) val keyStorePassword = keystorePass ?: readPassword("Key store Password: ") val privateKeyPassword = caPrivateKeyPass ?: readPassword("Private key Password: ") @@ -87,7 +91,7 @@ fun generateSigningKeyPairs(keystoreFile: Path, rootStoreFile: Path, rootKeystor } val keyPair = Crypto.generateKeyPair(signatureScheme) - val cert = X509Utilities.createCertificate( + val cert = createCertificate( certificateType, rootKeyPairAndCert.certificate, rootKeyPairAndCert.keyPair, @@ -104,10 +108,10 @@ fun generateSigningKeyPairs(keystoreFile: Path, rootStoreFile: Path, rootKeystor } storeCertIfAbsent( - X509Utilities.CORDA_INTERMEDIATE_CA, + CORDA_INTERMEDIATE_CA, CertificateType.INTERMEDIATE_CA, X500Principal("CN=Corda Doorman CA,$CORDA_X500_BASE"), - X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) + DEFAULT_TLS_SIGNATURE_SCHEME) storeCertIfAbsent( CORDA_NETWORK_MAP, diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/Main.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/Main.kt index ddc665046d..2bc9a9345b 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/Main.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/Main.kt @@ -1,176 +1,53 @@ package com.r3.corda.networkmanage.hsm -import com.google.common.util.concurrent.MoreExecutors -import com.r3.corda.networkmanage.common.persistence.NetworkMapStorage -import com.r3.corda.networkmanage.common.persistence.PersistentNetworkMapStorage import com.r3.corda.networkmanage.common.persistence.configureDatabase -import com.r3.corda.networkmanage.common.signer.NetworkMapSigner import com.r3.corda.networkmanage.common.utils.ShowHelpException import com.r3.corda.networkmanage.common.utils.initialiseSerialization -import com.r3.corda.networkmanage.hsm.authentication.AuthMode -import com.r3.corda.networkmanage.hsm.authentication.Authenticator -import com.r3.corda.networkmanage.hsm.authentication.createProvider -import com.r3.corda.networkmanage.hsm.configuration.Parameters import com.r3.corda.networkmanage.hsm.configuration.parseParameters -import com.r3.corda.networkmanage.hsm.menu.Menu -import com.r3.corda.networkmanage.hsm.persistence.ApprovedCertificateRequestData -import com.r3.corda.networkmanage.hsm.persistence.DBSignedCertificateRequestStorage -import com.r3.corda.networkmanage.hsm.signer.HsmCsrSigner -import com.r3.corda.networkmanage.hsm.signer.HsmSigner -import com.r3.corda.networkmanage.hsm.utils.mapCryptoServerException +import com.r3.corda.networkmanage.hsm.processor.CsrProcessor +import com.r3.corda.networkmanage.hsm.processor.NetworkMapProcessor import org.apache.logging.log4j.LogManager import org.bouncycastle.jce.provider.BouncyCastleProvider import java.security.Security -import java.time.Duration -import java.util.concurrent.Executors -import java.util.concurrent.ScheduledExecutorService -import java.util.concurrent.TimeUnit.MILLISECONDS -import java.util.concurrent.TimeUnit.SECONDS import javax.crypto.Cipher -private val log = LogManager.getLogger("com.r3.corda.networkmanage.hsm.Main") +private val logger = LogManager.getLogger("com.r3.corda.networkmanage.hsm.Main") fun main(args: Array) { - // Grabbed from https://stackoverflow.com/questions/7953567/checking-if-unlimited-cryptography-is-available - if (Cipher.getMaxAllowedKeyLength("AES") < 256) { - System.err.println("Unlimited Strength Jurisdiction Policy Files must be installed, see http://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html") - System.exit(1) - } - - try { - run(parseParameters(*args)) - } catch (e: ShowHelpException) { - e.errorMessage?.let(::println) - e.parser.printHelpOn(System.out) - } -} - -fun run(parameters: Parameters) { - parameters.run { - // Ensure the BouncyCastle provider is installed - if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) { - Security.addProvider(BouncyCastleProvider()) - } - - // Create DB connection. - checkNotNull(dataSourceProperties) - initialiseSerialization() - val database = configureDatabase(dataSourceProperties, databaseConfig) - val csrStorage = DBSignedCertificateRequestStorage(database) - val hsmSigner = HsmSigner( - Authenticator( - AuthMode.KEY_FILE, - autoUsername, - authKeyFilePath, - authKeyFilePassword, - signAuthThreshold, - provider = createProvider(networkMapKeyGroup))) - - val networkMapStorage = PersistentNetworkMapStorage(database) - val scheduler = Executors.newSingleThreadScheduledExecutor() - startNetworkingMapSigningPolling(networkMapStorage, hsmSigner, scheduler, Duration.ofMillis(signInterval)) - - val sign: (List) -> Unit = { - val signer = HsmCsrSigner( - csrStorage, - loadRootKeyStore(), - csrCertCrlDistPoint, - csrCertCrlIssuer, - validDays, - Authenticator( - authMode, - autoUsername, - authKeyFilePath, - authKeyFilePassword, - signAuthThreshold, - provider = createProvider(doormanKeyGroup))) - signer.sign(it) - } - Menu().withExceptionHandler(::processError).addItem("1", "Sign all approved and unsigned CSRs", { - val approved = csrStorage.getApprovedRequests() - if (approved.isNotEmpty()) { - if (confirmedSign(approved)) { - sign(approved) - } - } else { - println("There is no approved CSR") - } - }).addItem("2", "List all approved and unsigned CSRs", { - val approved = csrStorage.getApprovedRequests() - if (approved.isNotEmpty()) { - println("Approved CSRs:") - approved.forEachIndexed { index, item -> println("${index + 1}. ${item.request.subject}") } - Menu().withExceptionHandler(::processError).setExitOption("3", "Go back"). - addItem("1", "Sign all listed CSRs", { - if (confirmedSign(approved)) { - sign(approved) - } - }, isTerminating = true). - addItem("2", "Select and sign CSRs", { - val selectedItems = getSelection(approved) - if (confirmedSign(selectedItems)) { - sign(selectedItems) - } - }, isTerminating = true).showMenu() - } else { - println("There is no approved and unsigned CSR") - } - }).showMenu() - - MoreExecutors.shutdownAndAwaitTermination(scheduler, 30, SECONDS) - } -} - -private fun startNetworkingMapSigningPolling(networkMapStorage: NetworkMapStorage, - signer: HsmSigner, - executor: ScheduledExecutorService, - signingPeriod: Duration) { - val networkMapSigner = NetworkMapSigner(networkMapStorage, signer) - log.info("Starting the network map signing thread: sign interval ${signingPeriod.toMillis()} ms") - executor.scheduleAtFixedRate({ + parseParameters(*args).run { try { - networkMapSigner.signNetworkMap() - } catch (e: Exception) { - log.error("Exception thrown while signing network map", e) - } - }, signingPeriod.toMillis(), signingPeriod.toMillis(), MILLISECONDS) -} - -private fun processError(exception: Exception) { - val processed = mapCryptoServerException(exception) - System.err.println("An error occurred:") - processed.printStackTrace() -} - -private fun confirmedSign(selectedItems: List): Boolean { - println("Are you sure you want to sign the following requests:") - selectedItems.forEachIndexed { index, data -> - println("${index + 1} ${data.request.subject}") - } - var result = false - Menu().addItem("Y", "Yes", { result = true }, true).setExitOption("N", "No").showMenu() - return result -} - -private fun getSelection(toSelect: List): List { - print("CSRs to be signed (comma separated list): ") - val line = readLine() - if (line == null) { - println("EOF reached") - return emptyList() - } - return try { - line.split(",").map { - val result = it.toInt() - 1 - if (result > toSelect.size - 1) { - throw IllegalArgumentException("Selected ${result + 1} item is out of bounds") - } else { - toSelect[result] + // Validate + // Grabbed from https://stackoverflow.com/questions/7953567/checking-if-unlimited-cryptography-is-available + require(Cipher.getMaxAllowedKeyLength("AES") >= 256) { + "Unlimited Strength Jurisdiction Policy Files must be installed, see http://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html" } + require(csrSigning != null || networkMapSigning != null) { + "Either network map or certificate signing request certificate parameters must be specified." + } + requireNotNull(dataSourceProperties) + + // Ensure the BouncyCastle provider is installed + if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) { + Security.addProvider(BouncyCastleProvider()) + } + + initialiseSerialization() + // Create DB connection. + val persistence = configureDatabase(dataSourceProperties, database) + if (networkMapSigning != null) { + NetworkMapProcessor(networkMapSigning, device, keySpecifier, persistence).run() + } + if (csrSigning != null) { + try { + CsrProcessor(csrSigning, device, keySpecifier, persistence).showMenu() + } catch (e: ShowHelpException) { + e.errorMessage?.let(::println) + e.parser.printHelpOn(System.out) + } + } + } catch (e: Exception) { + logger.error("Error while starting the HSM Signing service.", e) } - } catch (exception: Exception) { - println(exception.message) - emptyList() } } diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/authentication/Authenticator.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/authentication/Authenticator.kt index 64a64476cb..862e0a9b1c 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/authentication/Authenticator.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/authentication/Authenticator.kt @@ -2,7 +2,6 @@ package com.r3.corda.networkmanage.hsm.authentication import CryptoServerJCE.CryptoServerProvider import com.r3.corda.networkmanage.common.signer.AuthenticationException -import com.r3.corda.networkmanage.hsm.configuration.Parameters import java.io.ByteArrayInputStream import java.io.ByteArrayOutputStream import java.nio.file.Path @@ -116,8 +115,14 @@ data class CryptoServerProviderConfig( /** * Creates an instance of [CryptoServerProvider] that corresponds to the HSM. + * + * @param keyGroup HSM key group. + * @param keySpecifier HSM key specifier. + * @param device HSM device address. + * + * @return preconfigured instance of [CryptoServerProvider] */ -fun Parameters.createProvider(keyGroup: String): CryptoServerProvider { +fun createProvider(keyGroup: String, keySpecifier: Int, device: String): CryptoServerProvider { val config = CryptoServerProviderConfig( Device = device, KeyGroup = keyGroup, @@ -126,6 +131,13 @@ fun Parameters.createProvider(keyGroup: String): CryptoServerProvider { return createProvider(config) } +/** + * Creates an instance of [CryptoServerProvider] configured accordingly to the passed configuration. + * + * @param config crypto server provider configuration. + * + * @return preconfigured instance of [CryptoServerProvider] + */ fun createProvider(config: CryptoServerProviderConfig): CryptoServerProvider { val cfgBuffer = ByteArrayOutputStream() val writer = cfgBuffer.writer(Charsets.UTF_8) diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/configuration/Configuration.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/configuration/Configuration.kt index 73d94df294..37eb8e9a65 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/configuration/Configuration.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/configuration/Configuration.kt @@ -6,7 +6,6 @@ import com.typesafe.config.ConfigFactory import com.typesafe.config.ConfigParseOptions import net.corda.core.internal.div import net.corda.core.internal.isRegularFile -import net.corda.core.utilities.minutes import net.corda.nodeapi.internal.config.parseAs import net.corda.nodeapi.internal.crypto.X509KeyStore import net.corda.nodeapi.internal.persistence.DatabaseConfig @@ -15,47 +14,45 @@ import java.nio.file.Paths import java.util.* /** - * Configuration parameters. + * Configuration parameters. Those are general configuration parameters shared with both + * network map and certificate signing requests processes. */ data class Parameters(val dataSourceProperties: Properties, - val databaseConfig: DatabaseConfig = DatabaseConfig(), - val device: String = DEFAULT_DEVICE, - // TODO this needs cleaning up after the config-file-only support is implemented - val rootKeyStoreFile: Path, - val rootKeyStorePassword: String, - val doormanKeyGroup: String, - val networkMapKeyGroup: String, - val keySpecifier: Int = DEFAULT_KEY_SPECIFIER, - val csrCertCrlDistPoint: String, - val csrCertCrlIssuer: String? = DEFAULT_CSR_CERT_CRL_ISSUER, // X500 name of the issuing authority e.g. "L=New York, C=US, OU=Org Unit, CN=Service Name", - // if null parent CA is is considered as an issuer. - val validDays: Int, - val signAuthThreshold: Int = DEFAULT_SIGN_AUTH_THRESHOLD, - val keyGenAuthThreshold: Int = DEFAULT_KEY_GEN_AUTH_THRESHOLD, - val authMode: AuthMode = DEFAULT_AUTH_MODE, - val authKeyFilePath: Path? = DEFAULT_KEY_FILE_PATH, - val authKeyFilePassword: String? = DEFAULT_KEY_FILE_PASSWORD, - val autoUsername: String? = DEFAULT_AUTO_USERNAME, - // TODO Change this to Duration in the future. - val signInterval: Long = DEFAULT_SIGN_INTERVAL) { - companion object { - val DEFAULT_DEVICE = "3001@127.0.0.1" - val DEFAULT_AUTH_MODE = AuthMode.PASSWORD - val DEFAULT_SIGN_AUTH_THRESHOLD = 2 - val DEFAULT_KEY_GEN_AUTH_THRESHOLD = 2 - val DEFAULT_KEY_SPECIFIER = 1 - val DEFAULT_KEY_FILE_PATH: Path? = null //Paths.get("/Users/michalkit/WinDev1706Eval/Shared/TEST4.key") - val DEFAULT_KEY_FILE_PASSWORD: String? = null - val DEFAULT_AUTO_USERNAME: String? = null - val DEFAULT_SIGN_INTERVAL = 1.minutes.toMillis() - val DEFAULT_CSR_CERT_CRL_ISSUER: String? = null - } + val database: DatabaseConfig = DatabaseConfig(), + val device: String, + val keySpecifier: Int, + val networkMapSigning: NetworkMapCertificateParameters? = null, + val csrSigning: DoormanCertificateParameters? = null) +/** + * Network map signing process specific parameters. + */ +data class NetworkMapCertificateParameters(val username: String, + val keyGroup: String, + val authParameters: AuthenticationParameters) + +/** + * Certificate signing requests process specific parameters. + */ +data class DoormanCertificateParameters(val crlDistributionPoint: String, + val keyGroup:String, + val validDays: Int, + val rootKeyStoreFile: Path, + val rootKeyStorePassword: String, + val authParameters: AuthenticationParameters) { fun loadRootKeyStore(createNew: Boolean = false): X509KeyStore { return X509KeyStore.fromFile(rootKeyStoreFile, rootKeyStorePassword, createNew) } } +/** + * Authentication related parameters. + */ +data class AuthenticationParameters(val mode: AuthMode, + val password: String? = null, // This is either HSM password or key file password, depending on the mode. + val keyFilePath: Path? = null, + val threshold: Int) + /** * Parses the list of arguments and produces an instance of [Parameters]. * @param args list of strings corresponding to program arguments diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/processor/CsrProcessor.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/processor/CsrProcessor.kt new file mode 100644 index 0000000000..74273cd113 --- /dev/null +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/processor/CsrProcessor.kt @@ -0,0 +1,117 @@ +package com.r3.corda.networkmanage.hsm.processor + +import com.r3.corda.networkmanage.hsm.authentication.Authenticator +import com.r3.corda.networkmanage.hsm.authentication.createProvider +import com.r3.corda.networkmanage.hsm.configuration.DoormanCertificateParameters +import com.r3.corda.networkmanage.hsm.menu.Menu +import com.r3.corda.networkmanage.hsm.persistence.ApprovedCertificateRequestData +import com.r3.corda.networkmanage.hsm.persistence.DBSignedCertificateRequestStorage +import com.r3.corda.networkmanage.hsm.signer.HsmCsrSigner +import com.r3.corda.networkmanage.hsm.utils.mapCryptoServerException +import net.corda.core.utilities.contextLogger +import net.corda.nodeapi.internal.persistence.CordaPersistence + +class CsrProcessor(private val parameters: DoormanCertificateParameters, + private val device: String, + private val keySpecifier: Int, + private val database: CordaPersistence) { + companion object { + val logger = contextLogger() + } + + private val auth = parameters.authParameters + + fun showMenu() { + val csrStorage = DBSignedCertificateRequestStorage(database) + val sign: (List) -> Unit = { + val signer = parameters.run { + HsmCsrSigner( + csrStorage, + loadRootKeyStore(), + crlDistributionPoint, + null, + validDays, + Authenticator( + provider = createProvider(parameters.keyGroup, keySpecifier, device), + mode = auth.mode, + authStrengthThreshold = auth.threshold)) + } + logger.debug("Signing requests: $it") + signer.sign(it) + } + Menu().withExceptionHandler(this::processError).setExitOption("3", "Quit").addItem("1", "Sign all approved and unsigned CSRs", + { + logger.debug("Fetching approved requests...") + val approved = csrStorage.getApprovedRequests() + logger.debug("Approved requests fetched: $approved") + if (approved.isNotEmpty()) { + if (confirmedSign(approved)) { + sign(approved) + } + } else { + println("There is no approved CSR") + } + }).addItem("2", "List all approved and unsigned CSRs", + { + logger.debug("Fetching approved requests...") + val approved = csrStorage.getApprovedRequests() + logger.debug("Approved requests fetched: $approved") + if (approved.isNotEmpty()) { + println("Approved CSRs:") + approved.forEachIndexed { index, item -> println("${index + 1}. ${item.request.subject}") } + Menu().withExceptionHandler(this::processError).setExitOption("3", "Go back"). + addItem("1", "Sign all listed CSRs", { + if (confirmedSign(approved)) { + sign(approved) + } + }, isTerminating = true). + addItem("2", "Select and sign CSRs", { + val selectedItems = getSelection(approved) + if (confirmedSign(selectedItems)) { + sign(selectedItems) + } + }, isTerminating = true).showMenu() + } else { + println("There is no approved and unsigned CSR") + } + }).showMenu() + } + + private fun confirmedSign(selectedItems: List): Boolean { + println("Are you sure you want to sign the following requests:") + selectedItems.forEachIndexed { index, data -> + println("${index + 1} ${data.request.subject}") + } + var result = false + Menu().addItem("Y", "Yes", { result = true }, true).setExitOption("N", "No").showMenu() + return result + } + + private fun getSelection(toSelect: List): List { + print("CSRs to be signed (comma separated list): ") + val line = readLine() + if (line == null) { + println("EOF reached") + return emptyList() + } + return try { + line.split(",").map { + val result = it.toInt() - 1 + if (result > toSelect.size - 1) { + throw IllegalArgumentException("Selected ${result + 1} item is out of bounds") + } else { + toSelect[result] + } + } + } catch (exception: Exception) { + println(exception.message) + emptyList() + } + } + + fun processError(exception: Exception) { + val processed = mapCryptoServerException(exception) + System.err.println("An error occurred:") + processed.printStackTrace() + } +} \ No newline at end of file diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/processor/NetworkMapProcessor.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/processor/NetworkMapProcessor.kt new file mode 100644 index 0000000000..db2bbace4f --- /dev/null +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/processor/NetworkMapProcessor.kt @@ -0,0 +1,52 @@ +package com.r3.corda.networkmanage.hsm.processor + +import com.r3.corda.networkmanage.common.persistence.PersistentNetworkMapStorage +import com.r3.corda.networkmanage.common.signer.NetworkMapSigner +import com.r3.corda.networkmanage.hsm.authentication.AuthMode +import com.r3.corda.networkmanage.hsm.authentication.Authenticator +import com.r3.corda.networkmanage.hsm.authentication.createProvider +import com.r3.corda.networkmanage.hsm.configuration.NetworkMapCertificateParameters +import com.r3.corda.networkmanage.hsm.signer.HsmSigner +import net.corda.core.utilities.contextLogger +import net.corda.nodeapi.internal.persistence.CordaPersistence + +class NetworkMapProcessor(private val parameters: NetworkMapCertificateParameters, + private val device: String, + private val keySpecifier: Int, + private val database: CordaPersistence) { + companion object { + val logger = contextLogger() + } + + init { + parameters.authParameters.run { + requireNotNull(password) + require(mode != AuthMode.CARD_READER) + if (mode == AuthMode.KEY_FILE) { + require(keyFilePath != null) { "Key file path cannot be null when authentication mode is ${AuthMode.KEY_FILE}" } + } + } + } + + fun run() { + logger.info("Starting network map processor.") + parameters.run { + val networkMapStorage = PersistentNetworkMapStorage(database) + val signer = HsmSigner( + Authenticator( + AuthMode.KEY_FILE, + username, + authParameters.keyFilePath, + authParameters.password, + authParameters.threshold, + provider = createProvider(keyGroup, keySpecifier, device))) + val networkMapSigner = NetworkMapSigner(networkMapStorage, signer) + try { + logger.info("Executing network map signing...") + networkMapSigner.signNetworkMap() + } catch (e: Exception) { + logger.error("Exception thrown while signing network map", e) + } + } + } +} \ No newline at end of file diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/signer/HsmCsrSigner.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/signer/HsmCsrSigner.kt index 59be927f47..f7c0771f00 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/signer/HsmCsrSigner.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/signer/HsmCsrSigner.kt @@ -6,10 +6,14 @@ import com.r3.corda.networkmanage.hsm.persistence.SignedCertificateRequestStorag import com.r3.corda.networkmanage.hsm.utils.HsmX509Utilities.createClientCertificate import com.r3.corda.networkmanage.hsm.utils.HsmX509Utilities.getAndInitializeKeyStore import com.r3.corda.networkmanage.hsm.utils.HsmX509Utilities.retrieveCertAndKeyPair +import net.corda.core.utilities.contextLogger import net.corda.nodeapi.internal.crypto.CertificateType import net.corda.nodeapi.internal.crypto.X509KeyStore -import net.corda.nodeapi.internal.crypto.X509Utilities +import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_INTERMEDIATE_CA +import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_ROOT_CA +import net.corda.nodeapi.internal.crypto.X509Utilities.buildCertPath import org.bouncycastle.asn1.x500.X500Name +import java.io.PrintStream /** * Encapsulates certificate signing logic @@ -19,7 +23,12 @@ class HsmCsrSigner(private val storage: SignedCertificateRequestStorage, private val csrCertCrlDistPoint: String, private val csrCertCrlIssuer: String?, private val validDays: Int, - private val authenticator: Authenticator) : CertificateSigningRequestSigner { + private val authenticator: Authenticator, + private val printStream: PrintStream = System.out) : CertificateSigningRequestSigner { + + companion object { + val logger = contextLogger() + } /** * Signs the provided list of approved certificate signing requests. By signature we mean creation of the client-level certificate @@ -33,11 +42,12 @@ class HsmCsrSigner(private val storage: SignedCertificateRequestStorage, */ override fun sign(toSign: List) { authenticator.connectAndAuthenticate { provider, signers -> - // This should be changed once we allow for more certificates in the chain. Preferably we should use - // keyStore.getCertificateChain(String) and assume entire chain is stored in the HSM (depending on the support). - val rootCert = rootKeyStore.getCertificate(X509Utilities.CORDA_ROOT_CA) + logger.debug("Retrieving the root certificate ${CORDA_ROOT_CA} from HSM...") + val rootCert = rootKeyStore.getCertificate(CORDA_ROOT_CA) + logger.debug("Initializing doorman key store...") val keyStore = getAndInitializeKeyStore(provider) - val doormanCertAndKey = retrieveCertAndKeyPair(X509Utilities.CORDA_INTERMEDIATE_CA, keyStore) + logger.debug("Retrieving the doorman certificate $CORDA_INTERMEDIATE_CA from HSM...") + val doormanCertAndKey = retrieveCertAndKeyPair(CORDA_INTERMEDIATE_CA, keyStore) toSign.forEach { val nodeCaCert = createClientCertificate( CertificateType.NODE_CA, @@ -47,12 +57,15 @@ class HsmCsrSigner(private val storage: SignedCertificateRequestStorage, provider, csrCertCrlDistPoint, csrCertCrlIssuer?.let { X500Name(it) }) - it.certPath = X509Utilities.buildCertPath(nodeCaCert, doormanCertAndKey.certificate, rootCert) + it.certPath = buildCertPath(nodeCaCert, doormanCertAndKey.certificate, rootCert) } + logger.debug("Storing signed CSRs...") storage.store(toSign, signers) - println("The following certificates have been signed by $signers:") + printStream.println("The following certificates have been signed by $signers:") + logger.debug("The following certificates have been signed by $signers:") toSign.forEachIndexed { index, data -> - println("${index + 1} ${data.request.subject}") + printStream.println("${index + 1} ${data.request.subject}") + logger.debug("${index + 1} ${data.request.subject}") } } } diff --git a/network-management/src/test/kotlin/com/r3/corda/networkmanage/hsm/configuration/ConfigurationTest.kt b/network-management/src/test/kotlin/com/r3/corda/networkmanage/hsm/configuration/ConfigurationTest.kt index e9a9f81ad8..3eee5a6c9e 100644 --- a/network-management/src/test/kotlin/com/r3/corda/networkmanage/hsm/configuration/ConfigurationTest.kt +++ b/network-management/src/test/kotlin/com/r3/corda/networkmanage/hsm/configuration/ConfigurationTest.kt @@ -6,6 +6,7 @@ import com.typesafe.config.ConfigException import org.assertj.core.api.Assertions import org.junit.Test import java.io.File +import java.nio.file.Paths import kotlin.test.assertEquals import kotlin.test.assertFailsWith @@ -15,9 +16,18 @@ class ConfigurationTest : TestBase() { @Test fun `config file is parsed correctly`() { - val paramsWithPassword = parseParameters("--config-file", validConfigPath) - assertEquals(AuthMode.PASSWORD, paramsWithPassword.authMode) - assertEquals("3001@192.168.0.1", paramsWithPassword.device) + val parameters = parseParameters("--config-file", validConfigPath) + assertEquals("3001@192.168.0.1", parameters.device) + val doormanCertParameters = parameters.csrSigning!! + assertEquals(AuthMode.PASSWORD, doormanCertParameters.authParameters.mode) + assertEquals(2, doormanCertParameters.authParameters.threshold) + assertEquals(3650, doormanCertParameters.validDays) + val nmParams = parameters.networkMapSigning!! + assertEquals(AuthMode.KEY_FILE, nmParams.authParameters.mode) + assertEquals(Paths.get("./Administrator.KEY"), nmParams.authParameters.keyFilePath) + assertEquals(2, nmParams.authParameters.threshold) + assertEquals("PASSWORD", nmParams.authParameters.password) + assertEquals("TEST_USERNAME", nmParams.username) } @Test