mirror of
https://github.com/corda/corda.git
synced 2024-12-28 16:58:55 +00:00
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
This commit is contained in:
parent
8c5f0ac0ca
commit
e6e2836119
@ -33,13 +33,17 @@ 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
|
||||
|
||||
:approveAll: Whether to approve all request (defaults to false), this is for debug only.
|
||||
|
||||
:approveInterval: How often to process Jira approved requests in seconds.
|
||||
|
||||
:jira: The Jira configuration
|
||||
|
||||
:address: The URL to use to connect to Jira
|
||||
|
||||
@ -49,7 +53,12 @@ Allowed parameters are:
|
||||
|
||||
:password: Password for Jira
|
||||
|
||||
:doneTransitionCode: Jira status to put approved tickets in
|
||||
|
||||
: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
|
||||
|
@ -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"
|
||||
|
||||
:keySpecifier: HSM key specifier. This parameter is vendor specific (see Utimaco docs).
|
||||
|
||||
:database: Database properties.
|
||||
|
||||
:dataSourceProperties: Data source properties. It should describe (or point to) the Doorman database.
|
||||
|
||||
:csrSigning: CSR signing process configuration parameters. If specified, the signing service will sign approved CSRs.
|
||||
|
||||
:validDays: Number of days issued signatures are valid for.
|
||||
|
||||
:rootKeyStoreFile: Location of the key store (trust store) containing the root certificate.
|
||||
|
||||
: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).
|
||||
:keyGroup: HSM key group for the doorman 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).
|
||||
:crlDistributionPoint: Certificate revocation list location for the node CA certificate.
|
||||
|
||||
:keySpecifier: HSM key specifier. This parameter is vendor specific (see Utimaco docs). Default value: 1.
|
||||
:authParameters: Authentication configuration for the CSR signing process.
|
||||
|
||||
:rootPrivateKeyPassword: Private key password for the root certificate.
|
||||
:mode: Authentication mode. Allowed values are: PASSWORD, CARD_READER and KEY_FILE
|
||||
|
||||
:csrPrivateKeyPassword: Private key password for the intermediate certificate used to sign certficate signing requests.
|
||||
:password: Key file password. Valid only if the authentication mode is set to KEY_FILE.
|
||||
|
||||
:csrCertCrlDistPoint: Certificate revocation list location for the node CA certificate.
|
||||
:keyFilePath: Key file path. Valid only if authentication mode is set to KEY_FILE.
|
||||
|
||||
: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.
|
||||
:threshold: Minimum authentication strength threshold required for certificate signing requests.
|
||||
|
||||
:databaseProperties: Database properties.
|
||||
:networkMapSigning: Network map signing process configuration parameters. If specified, the signing service will sign the network map.
|
||||
|
||||
:dataSourceProperties: Data source properties. It should describe (or point to) the Doorman database.
|
||||
:username: HSM username to be used when communicating with the HSM.
|
||||
|
||||
:networkMapPrivateKeyPassword: Private key password for the intermediate certificate used to sign the network map.
|
||||
:keyGroup: HSM key group for the network map certificate key. This parameter is vendor specific (see Utimaco docs).
|
||||
|
||||
:validDays: Number of days issued signatures are valid for.
|
||||
:authParameters: Authentication configuration for the CSR signing process.
|
||||
|
||||
:signAuthThreshold: Minimum authentication strength threshold required for certificate signing requests.
|
||||
Default value: 2
|
||||
:mode: Authentication mode. Allowed values are: PASSWORD and KEY_FILE
|
||||
|
||||
:keyGenAuthThreshold: Minimum authentication strength threshold required for key generation.
|
||||
Default value: 2
|
||||
: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.
|
||||
|
||||
: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
|
||||
:keyFilePath: Key file path. Valid only if authentication mode is set to KEY_FILE.
|
||||
|
||||
: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
|
||||
:threshold: Minimum authentication strength threshold required for certificate signing requests.
|
||||
|
||||
: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
|
||||
|
||||
:signInterval: Interval (in milliseconds) in which all automated signing happens. Default value: 60000 milliseconds
|
||||
|
||||
Expected behaviour and output upon the service start-up
|
||||
-------------------------------------------------------
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -1,19 +1,31 @@
|
||||
basedir = "."
|
||||
device = "3001@192.168.0.1"
|
||||
keySpecifier = -1
|
||||
authMode = PASSWORD
|
||||
|
||||
csrSigning {
|
||||
crlDistributionPoint = "http://test.com/revoked.crl"
|
||||
validDays = 3650
|
||||
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
|
||||
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
|
||||
|
@ -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<UserAuthenticationParameters> {
|
||||
return listOf(UserAuthenticationParameters(
|
||||
username = username,
|
||||
authMode = AuthMode.PASSWORD,
|
||||
authMode = GeneratorAuthMode.PASSWORD,
|
||||
authToken = "INTEGRATION_TEST",
|
||||
keyFilePassword = null))
|
||||
}
|
||||
@ -118,12 +122,27 @@ abstract class HsmBaseTest {
|
||||
dataSourceProperties = mock(),
|
||||
device = "${hsmSimulator.port}@${hsmSimulator.host}",
|
||||
keySpecifier = 1,
|
||||
csrSigning = DoormanCertificateParameters(
|
||||
rootKeyStoreFile = rootKeyStoreFile,
|
||||
rootKeyStorePassword = TRUSTSTORE_PASSWORD,
|
||||
doormanKeyGroup = DOORMAN_CERT_KEY_GROUP,
|
||||
networkMapKeyGroup = NETWORK_MAP_CERT_KEY_GROUP,
|
||||
keyGroup = DOORMAN_CERT_KEY_GROUP,
|
||||
validDays = 3650,
|
||||
csrCertCrlDistPoint = "http://test.com/revoked.crl"
|
||||
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
|
||||
)
|
||||
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -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),
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
)
|
||||
|
||||
|
@ -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))
|
||||
|
@ -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.
|
||||
|
@ -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.")
|
||||
}
|
||||
}
|
@ -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(
|
||||
|
@ -71,15 +71,15 @@ fun main(args: Array<String>) {
|
||||
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()
|
||||
|
@ -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)
|
||||
|
@ -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,
|
||||
|
@ -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<String>) {
|
||||
// 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)
|
||||
}
|
||||
|
||||
parseParameters(*args).run {
|
||||
try {
|
||||
run(parseParameters(*args))
|
||||
} catch (e: ShowHelpException) {
|
||||
e.errorMessage?.let(::println)
|
||||
e.parser.printHelpOn(System.out)
|
||||
// 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)
|
||||
|
||||
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<ApprovedCertificateRequestData>) -> Unit = {
|
||||
val signer = HsmCsrSigner(
|
||||
csrStorage,
|
||||
loadRootKeyStore(),
|
||||
csrCertCrlDistPoint,
|
||||
csrCertCrlIssuer,
|
||||
validDays,
|
||||
Authenticator(
|
||||
authMode,
|
||||
autoUsername,
|
||||
authKeyFilePath,
|
||||
authKeyFilePassword,
|
||||
signAuthThreshold,
|
||||
provider = createProvider(doormanKeyGroup)))
|
||||
signer.sign(it)
|
||||
// Create DB connection.
|
||||
val persistence = configureDatabase(dataSourceProperties, database)
|
||||
if (networkMapSigning != null) {
|
||||
NetworkMapProcessor(networkMapSigning, device, keySpecifier, persistence).run()
|
||||
}
|
||||
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({
|
||||
if (csrSigning != null) {
|
||||
try {
|
||||
networkMapSigner.signNetworkMap()
|
||||
CsrProcessor(csrSigning, device, keySpecifier, persistence).showMenu()
|
||||
} catch (e: ShowHelpException) {
|
||||
e.errorMessage?.let(::println)
|
||||
e.parser.printHelpOn(System.out)
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
log.error("Exception thrown while signing network map", e)
|
||||
logger.error("Error while starting the HSM Signing service.", 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<ApprovedCertificateRequestData>): 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<ApprovedCertificateRequestData>): List<ApprovedCertificateRequestData> {
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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 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 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 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
|
||||
|
@ -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<ApprovedCertificateRequestData>) -> 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<ApprovedCertificateRequestData>): 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<ApprovedCertificateRequestData>): List<ApprovedCertificateRequestData> {
|
||||
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()
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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<ApprovedCertificateRequestData>) {
|
||||
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}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user