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,23 +33,32 @@ Allowed parameters are:
|
|||||||
|
|
||||||
:mode: must be one of: DOORMAN (default), CA_KEYGEN, ROOT_KEYGEN.
|
: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`).
|
:database: database properties. The same (including its default value) as for node configuration (see :doc:`corda-configuration-file`).
|
||||||
|
|
||||||
:dataSourceProperties: datasource properties
|
: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.
|
: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
|
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
|
: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
|
Bootstrapping the network parameters
|
||||||
------------------------------------
|
------------------------------------
|
||||||
When doorman is running it will serve the current network parameters. The first time doorman is
|
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
|
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.
|
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),
|
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.
|
||||||
which upon successful startup should display different options to the user.
|
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.
|
||||||
At the same time, it connects to the database (which is expected to be shared with Doorman)
|
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.
|
||||||
and periodically polls it and if needed automatically signs the following: network map and certificate revocation list.
|
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.
|
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.
|
: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"
|
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.
|
:database: Database properties.
|
||||||
|
|
||||||
: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.
|
|
||||||
|
|
||||||
:dataSourceProperties: Data source properties. It should describe (or point to) the Doorman database.
|
: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.
|
:rootKeyStoreFile: Location of the key store (trust store) containing the root certificate.
|
||||||
Default value: 2
|
|
||||||
|
|
||||||
:keyGenAuthThreshold: Minimum authentication strength threshold required for key generation.
|
:rootKeyStorePassword: Password for the key store (trust store) containing the root certificate.
|
||||||
Default value: 2
|
|
||||||
|
|
||||||
:authMode: Authentication mode, used when validating a user for certificate signing request signature.
|
:keyGroup: HSM key group for the doorman certificate key. This parameter is vendor specific (see Utimaco docs).
|
||||||
Allowed values are:
|
|
||||||
"PASSWORD" (default) - type-in password authentication
|
|
||||||
CARD_READER - smart card reader authentication
|
|
||||||
KEY_FILE - key file authentication
|
|
||||||
|
|
||||||
:authKeyFilePath: Authentication key file. It is used when the 'authMode' is set to "KEY_FILE"
|
:crlDistributionPoint: Certificate revocation list location for the node CA certificate.
|
||||||
or for the automated signing process - e.g. network map, certificate revocation list. Default value: null
|
|
||||||
|
|
||||||
:authKeyFilePassword: Authentication key file password. It is used when the 'authMode' is set to "KEY_FILE"
|
:authParameters: Authentication configuration for the CSR signing process.
|
||||||
or for the automated signing process - e.g. network map, certificate revocation list. Default value: null
|
|
||||||
|
: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
|
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 = "."
|
basedir = "."
|
||||||
host = localhost
|
host = localhost
|
||||||
port = 0
|
port = 0
|
||||||
|
|
||||||
#For local signing
|
|
||||||
rootStorePath = ${basedir}"/certificates/rootstore.jks"
|
rootStorePath = ${basedir}"/certificates/rootstore.jks"
|
||||||
keystorePath = ${basedir}"/certificates/caKeystore.jks"
|
keystorePath = ${basedir}"/certificates/caKeystore.jks"
|
||||||
keystorePassword = "password"
|
keystorePassword = "password"
|
||||||
caPrivateKeyPassword = "password"
|
caPrivateKeyPassword = "password"
|
||||||
|
|
||||||
# Database config
|
|
||||||
dataSourceProperties {
|
dataSourceProperties {
|
||||||
dataSourceClassName = org.h2.jdbcx.JdbcDataSource
|
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.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.user" = sa
|
||||||
"dataSource.password" = ""
|
"dataSource.password" = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
database {
|
||||||
|
runMigration = true
|
||||||
|
}
|
||||||
|
|
||||||
h2port = 0
|
h2port = 0
|
||||||
|
|
||||||
# Doorman config
|
|
||||||
# Comment out this section if running without doorman service
|
# Comment out this section if running without doorman service
|
||||||
doormanConfig {
|
doorman {
|
||||||
approveInterval = 10000
|
approveInterval = 10000
|
||||||
approveAll = false
|
approveAll = false
|
||||||
jiraConfig {
|
jira {
|
||||||
address = "https://doorman-jira-host.com/"
|
address = "https://doorman-jira-host.com/"
|
||||||
projectCode = "TD"
|
projectCode = "TD"
|
||||||
username = "username"
|
username = "username"
|
||||||
@ -135,9 +136,8 @@ doormanConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# Network map config
|
|
||||||
# Comment out this section if running without network map service
|
# Comment out this section if running without network map service
|
||||||
networkMapConfig {
|
networkMap {
|
||||||
cacheTimeout = 600000
|
cacheTimeout = 600000
|
||||||
signInterval = 10000
|
signInterval = 10000
|
||||||
}
|
}
|
||||||
|
@ -20,10 +20,10 @@ database {
|
|||||||
h2port = 0
|
h2port = 0
|
||||||
|
|
||||||
# Comment out this section if running without doorman service
|
# Comment out this section if running without doorman service
|
||||||
doormanConfig{
|
doorman {
|
||||||
approveInterval = 10000
|
approveInterval = 10000
|
||||||
approveAll = false
|
approveAll = false
|
||||||
jiraConfig{
|
jira {
|
||||||
address = "https://doorman-jira-host.com/"
|
address = "https://doorman-jira-host.com/"
|
||||||
projectCode = "TD"
|
projectCode = "TD"
|
||||||
username = "username"
|
username = "username"
|
||||||
@ -32,7 +32,7 @@ doormanConfig{
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Comment out this section if running without network map service
|
# Comment out this section if running without network map service
|
||||||
networkMapConfig{
|
networkMap {
|
||||||
cacheTimeout = 600000
|
cacheTimeout = 600000
|
||||||
signInterval = 10000
|
signInterval = 10000
|
||||||
}
|
}
|
||||||
|
@ -1,19 +1,31 @@
|
|||||||
basedir = "."
|
basedir = "."
|
||||||
device = "3001@192.168.0.1"
|
device = "3001@192.168.0.1"
|
||||||
keySpecifier = -1
|
keySpecifier = -1
|
||||||
authMode = PASSWORD
|
|
||||||
rootKeyStoreFile = "dummyfile.jks"
|
csrSigning {
|
||||||
rootKeyStorePassword = "trustpass"
|
crlDistributionPoint = "http://test.com/revoked.crl"
|
||||||
csrCertCrlDistPoint = "http://test.com/revoked.crl"
|
validDays = 3650
|
||||||
doormanKeyGroup = "DEV.CORDACONNECT.OPS.CERT"
|
rootKeyStoreFile = "dummyfile.jks"
|
||||||
networkMapKeyGroup = "DEV.CORDACONNECT.OPS.NETMAP"
|
rootKeyStorePassword = "trustpass"
|
||||||
validDays = 3650
|
keyGroup = "DEV.CORDACONNECT.OPS.CERT"
|
||||||
signAuthThreshold = 2
|
authParameters {
|
||||||
keyGenAuthThreshold = 2
|
mode = PASSWORD
|
||||||
authKeyFilePath = "./Administrator.key"
|
password = "PASSWORD"
|
||||||
authKeyFilePassword = "Password"
|
threshold = 2
|
||||||
autoUsername = "AUTO_USER"
|
}
|
||||||
signInterval = 10000
|
}
|
||||||
|
|
||||||
|
networkMapSigning {
|
||||||
|
username = "TEST_USERNAME",
|
||||||
|
keyGroup = "DEV.CORDACONNECT.OPS.NETMAP"
|
||||||
|
authParameters {
|
||||||
|
mode = KEY_FILE
|
||||||
|
password = "PASSWORD"
|
||||||
|
keyFilePath = "./Administrator.KEY"
|
||||||
|
threshold = 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
h2port = 0
|
h2port = 0
|
||||||
dataSourceProperties {
|
dataSourceProperties {
|
||||||
"dataSourceClassName" = org.h2.jdbcx.JdbcDataSource
|
"dataSourceClassName" = org.h2.jdbcx.JdbcDataSource
|
||||||
|
@ -5,8 +5,10 @@ import com.nhaarman.mockito_kotlin.mock
|
|||||||
import com.nhaarman.mockito_kotlin.whenever
|
import com.nhaarman.mockito_kotlin.whenever
|
||||||
import com.r3.corda.networkmanage.HsmSimulator
|
import com.r3.corda.networkmanage.HsmSimulator
|
||||||
import com.r3.corda.networkmanage.hsm.authentication.InputReader
|
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.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.CertificateConfiguration
|
||||||
import com.r3.corda.networkmanage.hsm.generator.GeneratorParameters
|
import com.r3.corda.networkmanage.hsm.generator.GeneratorParameters
|
||||||
import com.r3.corda.networkmanage.hsm.generator.UserAuthenticationParameters
|
import com.r3.corda.networkmanage.hsm.generator.UserAuthenticationParameters
|
||||||
@ -18,6 +20,8 @@ import org.junit.Rule
|
|||||||
import org.junit.rules.TemporaryFolder
|
import org.junit.rules.TemporaryFolder
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import java.util.*
|
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 {
|
abstract class HsmBaseTest {
|
||||||
companion object {
|
companion object {
|
||||||
@ -55,7 +59,7 @@ abstract class HsmBaseTest {
|
|||||||
private fun createHsmUserConfigs(username: String): List<UserAuthenticationParameters> {
|
private fun createHsmUserConfigs(username: String): List<UserAuthenticationParameters> {
|
||||||
return listOf(UserAuthenticationParameters(
|
return listOf(UserAuthenticationParameters(
|
||||||
username = username,
|
username = username,
|
||||||
authMode = AuthMode.PASSWORD,
|
authMode = GeneratorAuthMode.PASSWORD,
|
||||||
authToken = "INTEGRATION_TEST",
|
authToken = "INTEGRATION_TEST",
|
||||||
keyFilePassword = null))
|
keyFilePassword = null))
|
||||||
}
|
}
|
||||||
@ -75,7 +79,7 @@ abstract class HsmBaseTest {
|
|||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun generateDbName() {
|
fun generateDbName() {
|
||||||
rootKeyStoreFile = tempFolder.root.toPath() / "truststore.jks"
|
rootKeyStoreFile = tempFolder.root.toPath() / "truststore.jks"
|
||||||
dbName = random63BitValue().toString()
|
dbName = random63BitValue().toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -118,12 +122,27 @@ abstract class HsmBaseTest {
|
|||||||
dataSourceProperties = mock(),
|
dataSourceProperties = mock(),
|
||||||
device = "${hsmSimulator.port}@${hsmSimulator.host}",
|
device = "${hsmSimulator.port}@${hsmSimulator.host}",
|
||||||
keySpecifier = 1,
|
keySpecifier = 1,
|
||||||
rootKeyStoreFile = rootKeyStoreFile,
|
csrSigning = DoormanCertificateParameters(
|
||||||
rootKeyStorePassword = TRUSTSTORE_PASSWORD,
|
rootKeyStoreFile = rootKeyStoreFile,
|
||||||
doormanKeyGroup = DOORMAN_CERT_KEY_GROUP,
|
keyGroup = DOORMAN_CERT_KEY_GROUP,
|
||||||
networkMapKeyGroup = NETWORK_MAP_CERT_KEY_GROUP,
|
validDays = 3650,
|
||||||
validDays = 3650,
|
rootKeyStorePassword = TRUSTSTORE_PASSWORD,
|
||||||
csrCertCrlDistPoint = "http://test.com/revoked.crl"
|
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,
|
serverAddress,
|
||||||
configureDatabase(makeTestDataSourceProperties(dbName), DatabaseConfig(runMigration = true)),
|
configureDatabase(makeTestDataSourceProperties(dbName), DatabaseConfig(runMigration = true)),
|
||||||
CertPathAndKey(listOf(csrCa.certificate, rootCaCert), csrCa.keyPair.private),
|
CertPathAndKey(listOf(csrCa.certificate, rootCaCert), csrCa.keyPair.private),
|
||||||
DoormanConfig(approveAll = true, jiraConfig = null, approveInterval = timeoutMillis),
|
DoormanConfig(approveAll = true, jira = null, approveInterval = timeoutMillis),
|
||||||
networkParameters?.let {
|
networkParameters?.let {
|
||||||
NetworkMapStartParams(
|
NetworkMapStartParams(
|
||||||
LocalSigner(networkMapCa),
|
LocalSigner(networkMapCa),
|
||||||
|
@ -14,7 +14,11 @@ class HsmAuthenticatorTest : HsmBaseTest() {
|
|||||||
// given
|
// given
|
||||||
val userInput = givenHsmUserAuthenticationInput()
|
val userInput = givenHsmUserAuthenticationInput()
|
||||||
val hsmSigningServiceConfig = createHsmSigningServiceConfig()
|
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)
|
val executed = AtomicBoolean(false)
|
||||||
|
|
||||||
// when
|
// when
|
||||||
|
@ -49,12 +49,15 @@ class HsmPermissionTest : HsmBaseTest() {
|
|||||||
val hsmSigningServiceConfig = createHsmSigningServiceConfig()
|
val hsmSigningServiceConfig = createHsmSigningServiceConfig()
|
||||||
val signer = HsmCsrSigner(
|
val signer = HsmCsrSigner(
|
||||||
mock(),
|
mock(),
|
||||||
hsmSigningServiceConfig.loadRootKeyStore(),
|
hsmSigningServiceConfig.csrSigning!!.loadRootKeyStore(),
|
||||||
"",
|
"",
|
||||||
null,
|
null,
|
||||||
3650,
|
3650,
|
||||||
Authenticator(
|
Authenticator(
|
||||||
provider = hsmSigningServiceConfig.createProvider(hsmSigningServiceConfig.doormanKeyGroup),
|
provider = createProvider(
|
||||||
|
hsmSigningServiceConfig.csrSigning!!.keyGroup,
|
||||||
|
hsmSigningServiceConfig.keySpecifier,
|
||||||
|
hsmSigningServiceConfig.device),
|
||||||
inputReader = userInput)
|
inputReader = userInput)
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -102,12 +105,15 @@ class HsmPermissionTest : HsmBaseTest() {
|
|||||||
val hsmSigningServiceConfig = createHsmSigningServiceConfig()
|
val hsmSigningServiceConfig = createHsmSigningServiceConfig()
|
||||||
val signer = HsmCsrSigner(
|
val signer = HsmCsrSigner(
|
||||||
mock(),
|
mock(),
|
||||||
hsmSigningServiceConfig.loadRootKeyStore(),
|
hsmSigningServiceConfig.csrSigning!!.loadRootKeyStore(),
|
||||||
"trustpass",
|
"trustpass",
|
||||||
null,
|
null,
|
||||||
3650,
|
3650,
|
||||||
Authenticator(
|
Authenticator(
|
||||||
provider = hsmSigningServiceConfig.createProvider(hsmSigningServiceConfig.doormanKeyGroup),
|
provider = createProvider(
|
||||||
|
hsmSigningServiceConfig.csrSigning!!.keyGroup,
|
||||||
|
hsmSigningServiceConfig.keySpecifier,
|
||||||
|
hsmSigningServiceConfig.device),
|
||||||
inputReader = userInput)
|
inputReader = userInput)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -62,14 +62,18 @@ class HsmSigningServiceTest : HsmBaseTest() {
|
|||||||
|
|
||||||
// given HSM CSR signer
|
// given HSM CSR signer
|
||||||
val hsmSigningServiceConfig = createHsmSigningServiceConfig()
|
val hsmSigningServiceConfig = createHsmSigningServiceConfig()
|
||||||
|
val doormanCertificateConfig = hsmSigningServiceConfig.csrSigning!!
|
||||||
val signer = HsmCsrSigner(
|
val signer = HsmCsrSigner(
|
||||||
mock(),
|
mock(),
|
||||||
hsmSigningServiceConfig.loadRootKeyStore(),
|
doormanCertificateConfig.loadRootKeyStore(),
|
||||||
"",
|
"",
|
||||||
null,
|
null,
|
||||||
3650,
|
3650,
|
||||||
Authenticator(
|
Authenticator(
|
||||||
provider = hsmSigningServiceConfig.createProvider(hsmSigningServiceConfig.doormanKeyGroup),
|
provider = createProvider(
|
||||||
|
doormanCertificateConfig.keyGroup,
|
||||||
|
hsmSigningServiceConfig.keySpecifier,
|
||||||
|
hsmSigningServiceConfig.device),
|
||||||
inputReader = userInput)
|
inputReader = userInput)
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -119,8 +123,12 @@ class HsmSigningServiceTest : HsmBaseTest() {
|
|||||||
|
|
||||||
// given HSM network map signer
|
// given HSM network map signer
|
||||||
val hsmSigningServiceConfig = createHsmSigningServiceConfig()
|
val hsmSigningServiceConfig = createHsmSigningServiceConfig()
|
||||||
|
val networkMapCertificateConfig = hsmSigningServiceConfig.networkMapSigning!!
|
||||||
val hsmDataSigner = HsmSigner(Authenticator(
|
val hsmDataSigner = HsmSigner(Authenticator(
|
||||||
provider = hsmSigningServiceConfig.createProvider(hsmSigningServiceConfig.networkMapKeyGroup),
|
provider = createProvider(
|
||||||
|
networkMapCertificateConfig.keyGroup,
|
||||||
|
hsmSigningServiceConfig.keySpecifier,
|
||||||
|
hsmSigningServiceConfig.device),
|
||||||
inputReader = userInput))
|
inputReader = userInput))
|
||||||
|
|
||||||
val database = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(runMigration = true))
|
val database = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(runMigration = true))
|
||||||
|
@ -92,7 +92,7 @@ class SigningServiceIntegrationTest : HsmBaseTest() {
|
|||||||
hostAndPort = NetworkHostAndPort(HOST, 0),
|
hostAndPort = NetworkHostAndPort(HOST, 0),
|
||||||
database = database,
|
database = database,
|
||||||
csrCertPathAndKey = null,
|
csrCertPathAndKey = null,
|
||||||
doormanServiceParameter = DoormanConfig(approveAll = true, approveInterval = 2.seconds.toMillis(), jiraConfig = null),
|
doormanServiceParameter = DoormanConfig(approveAll = true, approveInterval = 2.seconds.toMillis(), jira = null),
|
||||||
startNetworkMap = null)
|
startNetworkMap = null)
|
||||||
val doormanHostAndPort = server.hostAndPort
|
val doormanHostAndPort = server.hostAndPort
|
||||||
// Start Corda network registration.
|
// Start Corda network registration.
|
||||||
|
@ -21,23 +21,32 @@ class NetworkMapSigner(private val networkMapStorage: NetworkMapStorage, private
|
|||||||
// in current network map.
|
// in current network map.
|
||||||
val latestNetworkParameters = networkMapStorage.getLatestNetworkParameters()
|
val latestNetworkParameters = networkMapStorage.getLatestNetworkParameters()
|
||||||
if (latestNetworkParameters == null) {
|
if (latestNetworkParameters == null) {
|
||||||
logger.info("No network parameters present")
|
logger.debug("No network parameters present")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
logger.debug("Fetching current network map parameters...")
|
||||||
val currentNetworkParameters = networkMapStorage.getNetworkParametersOfNetworkMap()
|
val currentNetworkParameters = networkMapStorage.getNetworkParametersOfNetworkMap()
|
||||||
logger.debug("Retrieved network map parameters: $currentNetworkParameters")
|
logger.debug("Retrieved network map parameters: $currentNetworkParameters")
|
||||||
if (currentNetworkParameters?.verified() != latestNetworkParameters) {
|
if (currentNetworkParameters?.verified() != latestNetworkParameters) {
|
||||||
persistSignedNetworkParameters(latestNetworkParameters)
|
persistSignedNetworkParameters(latestNetworkParameters)
|
||||||
|
} else {
|
||||||
|
logger.debug("Network map parameters up-to-date. Skipping signing.")
|
||||||
}
|
}
|
||||||
|
logger.debug("Fetching current network map...")
|
||||||
val currentSignedNetworkMap = networkMapStorage.getCurrentNetworkMap()
|
val currentSignedNetworkMap = networkMapStorage.getCurrentNetworkMap()
|
||||||
|
logger.debug("Fetching node info hashes with VALID certificates...")
|
||||||
val nodeInfoHashes = networkMapStorage.getNodeInfoHashes(CertificateStatus.VALID)
|
val nodeInfoHashes = networkMapStorage.getNodeInfoHashes(CertificateStatus.VALID)
|
||||||
logger.debug("Retrieved node info hashes: $nodeInfoHashes")
|
logger.debug("Retrieved node info hashes: $nodeInfoHashes")
|
||||||
val newNetworkMap = NetworkMap(nodeInfoHashes, latestNetworkParameters.serialize().hash)
|
val newNetworkMap = NetworkMap(nodeInfoHashes, latestNetworkParameters.serialize().hash)
|
||||||
val serialisedNetworkMap = newNetworkMap.serialize()
|
val serialisedNetworkMap = newNetworkMap.serialize()
|
||||||
if (serialisedNetworkMap != currentSignedNetworkMap?.raw) {
|
if (serialisedNetworkMap != currentSignedNetworkMap?.raw) {
|
||||||
logger.info("Signing a new network map: $newNetworkMap")
|
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))
|
val newSignedNetworkMap = SignedDataWithCert(serialisedNetworkMap, signer.signBytes(serialisedNetworkMap.bytes))
|
||||||
networkMapStorage.saveNetworkMap(newSignedNetworkMap)
|
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")
|
logger.info("Signing and persisting network parameters: $networkParameters")
|
||||||
val digitalSignature = signer.signObject(networkParameters).sig
|
val digitalSignature = signer.signObject(networkParameters).sig
|
||||||
networkMapStorage.saveNetworkParameters(networkParameters, digitalSignature)
|
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 database: DatabaseConfig = DatabaseConfig(),
|
||||||
val mode: Mode,
|
val mode: Mode,
|
||||||
|
|
||||||
val doormanConfig: DoormanConfig?,
|
val doorman: DoormanConfig?,
|
||||||
val networkMapConfig: NetworkMapConfig?,
|
val networkMap: NetworkMapConfig?,
|
||||||
|
|
||||||
val updateNetworkParameters: Path?,
|
val updateNetworkParameters: Path?,
|
||||||
val trustStorePassword: String?,
|
val trustStorePassword: String?,
|
||||||
@ -51,7 +51,7 @@ data class NetworkManagementServerParameters(// TODO: Move local signing to sign
|
|||||||
}
|
}
|
||||||
|
|
||||||
data class DoormanConfig(val approveAll: Boolean = false,
|
data class DoormanConfig(val approveAll: Boolean = false,
|
||||||
val jiraConfig: JiraConfig? = null,
|
val jira: JiraConfig? = null,
|
||||||
val approveInterval: Long = NetworkManagementServerParameters.DEFAULT_APPROVE_INTERVAL.toMillis())
|
val approveInterval: Long = NetworkManagementServerParameters.DEFAULT_APPROVE_INTERVAL.toMillis())
|
||||||
|
|
||||||
data class NetworkMapConfig(val cacheTimeout: Long,
|
data class NetworkMapConfig(val cacheTimeout: Long,
|
||||||
@ -59,9 +59,10 @@ data class NetworkMapConfig(val cacheTimeout: Long,
|
|||||||
val signInterval: Long = NetworkManagementServerParameters.DEFAULT_SIGN_INTERVAL.toMillis())
|
val signInterval: Long = NetworkManagementServerParameters.DEFAULT_SIGN_INTERVAL.toMillis())
|
||||||
|
|
||||||
enum class Mode {
|
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,
|
DOORMAN,
|
||||||
CA_KEYGEN, ROOT_KEYGEN
|
CA_KEYGEN,
|
||||||
|
ROOT_KEYGEN
|
||||||
}
|
}
|
||||||
|
|
||||||
data class JiraConfig(
|
data class JiraConfig(
|
||||||
|
@ -71,15 +71,15 @@ fun main(args: Array<String>) {
|
|||||||
val networkManagementServer = NetworkManagementServer()
|
val networkManagementServer = NetworkManagementServer()
|
||||||
val networkParameters = updateNetworkParameters?.let {
|
val networkParameters = updateNetworkParameters?.let {
|
||||||
// TODO This check shouldn't be needed. Fix up the config design.
|
// 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()}'...")
|
println("Parsing network parameters from '${it.toAbsolutePath()}'...")
|
||||||
parseNetworkParametersFrom(it)
|
parseNetworkParametersFrom(it)
|
||||||
}
|
}
|
||||||
val networkMapStartParams = networkMapConfig?.let {
|
val networkMapStartParams = networkMap?.let {
|
||||||
NetworkMapStartParams(csrAndNetworkMap?.second, networkParameters, it)
|
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) {
|
Runtime.getRuntime().addShutdownHook(thread(start = false) {
|
||||||
networkManagementServer.close()
|
networkManagementServer.close()
|
||||||
|
@ -83,13 +83,14 @@ class NetworkManagementServer : Closeable {
|
|||||||
serverStatus: NetworkManagementServerStatus): RegistrationWebService {
|
serverStatus: NetworkManagementServerStatus): RegistrationWebService {
|
||||||
logger.info("Starting Doorman server.")
|
logger.info("Starting Doorman server.")
|
||||||
val requestService = if (config.approveAll) {
|
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.")
|
logger.warn("Doorman server is in 'Approve All' mode, this will approve all incoming certificate signing requests.")
|
||||||
ApproveAllCertificateRequestStorage(PersistentCertificateRequestStorage(database))
|
ApproveAllCertificateRequestStorage(PersistentCertificateRequestStorage(database))
|
||||||
} else {
|
} else {
|
||||||
PersistentCertificateRequestStorage(database)
|
PersistentCertificateRequestStorage(database)
|
||||||
}
|
}
|
||||||
|
|
||||||
val jiraConfig = config.jiraConfig
|
val jiraConfig = config.jira
|
||||||
val requestProcessor = if (jiraConfig != null) {
|
val requestProcessor = if (jiraConfig != null) {
|
||||||
val jiraWebAPI = AsynchronousJiraRestClientFactory().createWithBasicHttpAuthentication(URI(jiraConfig.address), jiraConfig.username, jiraConfig.password)
|
val jiraWebAPI = AsynchronousJiraRestClientFactory().createWithBasicHttpAuthentication(URI(jiraConfig.address), jiraConfig.username, jiraConfig.password)
|
||||||
val jiraClient = JiraClient(jiraWebAPI, jiraConfig.projectCode)
|
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.core.internal.div
|
||||||
import net.corda.nodeapi.internal.crypto.CertificateType
|
import net.corda.nodeapi.internal.crypto.CertificateType
|
||||||
import net.corda.nodeapi.internal.crypto.X509KeyStore
|
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 java.nio.file.Path
|
||||||
import javax.security.auth.x500.X500Principal
|
import javax.security.auth.x500.X500Principal
|
||||||
import kotlin.system.exitProcess
|
import kotlin.system.exitProcess
|
||||||
@ -36,19 +40,19 @@ fun generateRootKeyPair(rootStoreFile: Path, rootKeystorePass: String?, rootPriv
|
|||||||
val rootStore = X509KeyStore.fromFile(rootStoreFile, rootKeystorePassword, createNew = true)
|
val rootStore = X509KeyStore.fromFile(rootStoreFile, rootKeystorePassword, createNew = true)
|
||||||
val rootPrivateKeyPassword = rootPrivateKeyPass ?: readPassword("Root Private Key Password: ")
|
val rootPrivateKeyPassword = rootPrivateKeyPass ?: readPassword("Root Private Key Password: ")
|
||||||
|
|
||||||
if (X509Utilities.CORDA_ROOT_CA in rootStore) {
|
if (CORDA_ROOT_CA in rootStore) {
|
||||||
println("${X509Utilities.CORDA_ROOT_CA} already exists in keystore, process will now terminate.")
|
println("$CORDA_ROOT_CA already exists in keystore, process will now terminate.")
|
||||||
println(rootStore.getCertificate(X509Utilities.CORDA_ROOT_CA))
|
println(rootStore.getCertificate(CORDA_ROOT_CA))
|
||||||
exitProcess(1)
|
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
|
// TODO Make the cert subject configurable
|
||||||
val rootCert = X509Utilities.createSelfSignedCACertificate(
|
val rootCert = createSelfSignedCACertificate(
|
||||||
X500Principal("CN=Corda Root CA,$CORDA_X500_BASE"),
|
X500Principal("CN=Corda Root CA,$CORDA_X500_BASE"),
|
||||||
selfSignKey)
|
selfSignKey)
|
||||||
rootStore.update {
|
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
|
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: ")
|
val networkRootTrustPassword = networkRootTrustPass ?: readPassword("Network Root Trust Store Password: ")
|
||||||
|
|
||||||
X509KeyStore.fromFile(trustStorePath, networkRootTrustPassword, createNew = true).update {
|
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")
|
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 rootPrivateKeyPassword = rootPrivateKeyPass ?: readPassword("Root private key password: ")
|
||||||
val rootKeyStore = X509KeyStore.fromFile(rootStoreFile, rootKeystorePassword)
|
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 keyStorePassword = keystorePass ?: readPassword("Key store Password: ")
|
||||||
val privateKeyPassword = caPrivateKeyPass ?: readPassword("Private key 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 keyPair = Crypto.generateKeyPair(signatureScheme)
|
||||||
val cert = X509Utilities.createCertificate(
|
val cert = createCertificate(
|
||||||
certificateType,
|
certificateType,
|
||||||
rootKeyPairAndCert.certificate,
|
rootKeyPairAndCert.certificate,
|
||||||
rootKeyPairAndCert.keyPair,
|
rootKeyPairAndCert.keyPair,
|
||||||
@ -104,10 +108,10 @@ fun generateSigningKeyPairs(keystoreFile: Path, rootStoreFile: Path, rootKeystor
|
|||||||
}
|
}
|
||||||
|
|
||||||
storeCertIfAbsent(
|
storeCertIfAbsent(
|
||||||
X509Utilities.CORDA_INTERMEDIATE_CA,
|
CORDA_INTERMEDIATE_CA,
|
||||||
CertificateType.INTERMEDIATE_CA,
|
CertificateType.INTERMEDIATE_CA,
|
||||||
X500Principal("CN=Corda Doorman CA,$CORDA_X500_BASE"),
|
X500Principal("CN=Corda Doorman CA,$CORDA_X500_BASE"),
|
||||||
X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
|
DEFAULT_TLS_SIGNATURE_SCHEME)
|
||||||
|
|
||||||
storeCertIfAbsent(
|
storeCertIfAbsent(
|
||||||
CORDA_NETWORK_MAP,
|
CORDA_NETWORK_MAP,
|
||||||
|
@ -1,176 +1,53 @@
|
|||||||
package com.r3.corda.networkmanage.hsm
|
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.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.ShowHelpException
|
||||||
import com.r3.corda.networkmanage.common.utils.initialiseSerialization
|
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.configuration.parseParameters
|
||||||
import com.r3.corda.networkmanage.hsm.menu.Menu
|
import com.r3.corda.networkmanage.hsm.processor.CsrProcessor
|
||||||
import com.r3.corda.networkmanage.hsm.persistence.ApprovedCertificateRequestData
|
import com.r3.corda.networkmanage.hsm.processor.NetworkMapProcessor
|
||||||
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 org.apache.logging.log4j.LogManager
|
import org.apache.logging.log4j.LogManager
|
||||||
import org.bouncycastle.jce.provider.BouncyCastleProvider
|
import org.bouncycastle.jce.provider.BouncyCastleProvider
|
||||||
import java.security.Security
|
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
|
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>) {
|
fun main(args: Array<String>) {
|
||||||
// Grabbed from https://stackoverflow.com/questions/7953567/checking-if-unlimited-cryptography-is-available
|
parseParameters(*args).run {
|
||||||
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<ApprovedCertificateRequestData>) -> 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({
|
|
||||||
try {
|
try {
|
||||||
networkMapSigner.signNetworkMap()
|
// Validate
|
||||||
} catch (e: Exception) {
|
// Grabbed from https://stackoverflow.com/questions/7953567/checking-if-unlimited-cryptography-is-available
|
||||||
log.error("Exception thrown while signing network map", e)
|
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"
|
||||||
}, 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]
|
|
||||||
}
|
}
|
||||||
|
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()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,7 +2,6 @@ package com.r3.corda.networkmanage.hsm.authentication
|
|||||||
|
|
||||||
import CryptoServerJCE.CryptoServerProvider
|
import CryptoServerJCE.CryptoServerProvider
|
||||||
import com.r3.corda.networkmanage.common.signer.AuthenticationException
|
import com.r3.corda.networkmanage.common.signer.AuthenticationException
|
||||||
import com.r3.corda.networkmanage.hsm.configuration.Parameters
|
|
||||||
import java.io.ByteArrayInputStream
|
import java.io.ByteArrayInputStream
|
||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
@ -116,8 +115,14 @@ data class CryptoServerProviderConfig(
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an instance of [CryptoServerProvider] that corresponds to the HSM.
|
* 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(
|
val config = CryptoServerProviderConfig(
|
||||||
Device = device,
|
Device = device,
|
||||||
KeyGroup = keyGroup,
|
KeyGroup = keyGroup,
|
||||||
@ -126,6 +131,13 @@ fun Parameters.createProvider(keyGroup: String): CryptoServerProvider {
|
|||||||
return createProvider(config)
|
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 {
|
fun createProvider(config: CryptoServerProviderConfig): CryptoServerProvider {
|
||||||
val cfgBuffer = ByteArrayOutputStream()
|
val cfgBuffer = ByteArrayOutputStream()
|
||||||
val writer = cfgBuffer.writer(Charsets.UTF_8)
|
val writer = cfgBuffer.writer(Charsets.UTF_8)
|
||||||
|
@ -6,7 +6,6 @@ import com.typesafe.config.ConfigFactory
|
|||||||
import com.typesafe.config.ConfigParseOptions
|
import com.typesafe.config.ConfigParseOptions
|
||||||
import net.corda.core.internal.div
|
import net.corda.core.internal.div
|
||||||
import net.corda.core.internal.isRegularFile
|
import net.corda.core.internal.isRegularFile
|
||||||
import net.corda.core.utilities.minutes
|
|
||||||
import net.corda.nodeapi.internal.config.parseAs
|
import net.corda.nodeapi.internal.config.parseAs
|
||||||
import net.corda.nodeapi.internal.crypto.X509KeyStore
|
import net.corda.nodeapi.internal.crypto.X509KeyStore
|
||||||
import net.corda.nodeapi.internal.persistence.DatabaseConfig
|
import net.corda.nodeapi.internal.persistence.DatabaseConfig
|
||||||
@ -15,47 +14,45 @@ import java.nio.file.Paths
|
|||||||
import java.util.*
|
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,
|
data class Parameters(val dataSourceProperties: Properties,
|
||||||
val databaseConfig: DatabaseConfig = DatabaseConfig(),
|
val database: DatabaseConfig = DatabaseConfig(),
|
||||||
val device: String = DEFAULT_DEVICE,
|
val device: String,
|
||||||
// TODO this needs cleaning up after the config-file-only support is implemented
|
val keySpecifier: Int,
|
||||||
val rootKeyStoreFile: Path,
|
val networkMapSigning: NetworkMapCertificateParameters? = null,
|
||||||
val rootKeyStorePassword: String,
|
val csrSigning: DoormanCertificateParameters? = null)
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 {
|
fun loadRootKeyStore(createNew: Boolean = false): X509KeyStore {
|
||||||
return X509KeyStore.fromFile(rootKeyStoreFile, rootKeyStorePassword, createNew)
|
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].
|
* Parses the list of arguments and produces an instance of [Parameters].
|
||||||
* @param args list of strings corresponding to program arguments
|
* @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.createClientCertificate
|
||||||
import com.r3.corda.networkmanage.hsm.utils.HsmX509Utilities.getAndInitializeKeyStore
|
import com.r3.corda.networkmanage.hsm.utils.HsmX509Utilities.getAndInitializeKeyStore
|
||||||
import com.r3.corda.networkmanage.hsm.utils.HsmX509Utilities.retrieveCertAndKeyPair
|
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.CertificateType
|
||||||
import net.corda.nodeapi.internal.crypto.X509KeyStore
|
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 org.bouncycastle.asn1.x500.X500Name
|
||||||
|
import java.io.PrintStream
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encapsulates certificate signing logic
|
* Encapsulates certificate signing logic
|
||||||
@ -19,7 +23,12 @@ class HsmCsrSigner(private val storage: SignedCertificateRequestStorage,
|
|||||||
private val csrCertCrlDistPoint: String,
|
private val csrCertCrlDistPoint: String,
|
||||||
private val csrCertCrlIssuer: String?,
|
private val csrCertCrlIssuer: String?,
|
||||||
private val validDays: Int,
|
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
|
* 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>) {
|
override fun sign(toSign: List<ApprovedCertificateRequestData>) {
|
||||||
authenticator.connectAndAuthenticate { provider, signers ->
|
authenticator.connectAndAuthenticate { provider, signers ->
|
||||||
// This should be changed once we allow for more certificates in the chain. Preferably we should use
|
logger.debug("Retrieving the root certificate ${CORDA_ROOT_CA} from HSM...")
|
||||||
// keyStore.getCertificateChain(String) and assume entire chain is stored in the HSM (depending on the support).
|
val rootCert = rootKeyStore.getCertificate(CORDA_ROOT_CA)
|
||||||
val rootCert = rootKeyStore.getCertificate(X509Utilities.CORDA_ROOT_CA)
|
logger.debug("Initializing doorman key store...")
|
||||||
val keyStore = getAndInitializeKeyStore(provider)
|
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 {
|
toSign.forEach {
|
||||||
val nodeCaCert = createClientCertificate(
|
val nodeCaCert = createClientCertificate(
|
||||||
CertificateType.NODE_CA,
|
CertificateType.NODE_CA,
|
||||||
@ -47,12 +57,15 @@ class HsmCsrSigner(private val storage: SignedCertificateRequestStorage,
|
|||||||
provider,
|
provider,
|
||||||
csrCertCrlDistPoint,
|
csrCertCrlDistPoint,
|
||||||
csrCertCrlIssuer?.let { X500Name(it) })
|
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)
|
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 ->
|
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.assertj.core.api.Assertions
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.nio.file.Paths
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
import kotlin.test.assertFailsWith
|
import kotlin.test.assertFailsWith
|
||||||
|
|
||||||
@ -15,9 +16,18 @@ class ConfigurationTest : TestBase() {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `config file is parsed correctly`() {
|
fun `config file is parsed correctly`() {
|
||||||
val paramsWithPassword = parseParameters("--config-file", validConfigPath)
|
val parameters = parseParameters("--config-file", validConfigPath)
|
||||||
assertEquals(AuthMode.PASSWORD, paramsWithPassword.authMode)
|
assertEquals("3001@192.168.0.1", parameters.device)
|
||||||
assertEquals("3001@192.168.0.1", paramsWithPassword.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
|
@Test
|
||||||
|
Loading…
Reference in New Issue
Block a user