Addressing multiple key groups. Removing redundant config parameters for private key passwords (#409)

This commit is contained in:
Michal Kit 2018-01-25 16:40:42 +00:00 committed by GitHub
parent 5f1590d97f
commit 83ea4611ca
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 414 additions and 258 deletions

View File

@ -46,6 +46,8 @@ Certificate Configuration
:certificateType: Type of the certificate to be created. Allowed values are:
ROOT_CA, INTERMEDIATE_CA, NETWORK_MAP.
:rootKeyGroup: This is an HSM specific parameter that corresponds to key name spacing for the root key. It is ignored if the certificateType value is ROOT_CA. See Utimaco documentation for more details.
:subject: X500Name formatted string to be used as the certificate public key subject.
:validDays: Days number for certificate validity.
@ -64,7 +66,7 @@ Certificate Configuration
:keySpecifier: This is an HSM specific parameter that corresponds to key name spacing. See Utimaco documentation for more details.
:keyGroup: This is an HSM specific parameter that corresponds to key name spacing. See Utimaco documentation for more details.
:keyGroup: This is an HSM specific parameter that corresponds to key name spacing for the generated key. See Utimaco documentation for more details.
User Authentication Configuration
@ -78,6 +80,6 @@ Allowed parameters are:
CARD_READER - Smart card reader authentication
KEY_FILE - Key file based authentication.
:authToken: Depending on the authMode it is either user's password or path to the authentication key file.
:authToken: Depending on the authMode it is either user's password or path to the authentication key file. In case of the CARD_READER authMode value, this can be omitted.
:keyFilePassword: Only relevant, if authMode == KEY_FILE. It is the key file password.

View File

@ -28,7 +28,11 @@ 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"
:keyGroup: HSM key group. This parameter is vendor specific (see Utimaco docs).
:rootKeyGroup: HSM key group for the root certificate key. This parameter is vendor specific (see Utimaco docs).
: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.

View File

@ -4,7 +4,7 @@ trustStoreDirectory = "."
trustStorePassword = "trustpass"
certConfig {
subject = "CN=Corda Root, O=R3Cev, L=London, C=GB"
subject = "CN=Corda Root, O=R3 HoldCo LLC, OU=Corda, L=New York, C=US"
certificateType = ROOT_CA
validDays = 3650
keyOverride = 0

View File

@ -1,15 +1,14 @@
basedir = "."
device = "3001@127.0.0.1"
keyGroup = "DEV.DOORMAN"
device = "3001@192.168.0.1"
keySpecifier = -1
authMode = PASSWORD
rootCertificateName = "corda_root_ca"
rootPrivateKeyPassword = "Password"
csrPrivateKeyPassword = "Password"
csrCertificateName = "intermediate_ca"
csrCertCrlDistPoint = "http://test.com/revoked.crl"
networkMapCertificateName = "intermediate_ca"
networkMapPrivateKeyPassword = "Password"
rootKeyGroup = "DEV.CORDACONNECT.ROOT"
doormanKeyGroup = "DEV.CORDACONNECT.OPS.CERT"
networkMapKeyGroup = "DEV.CORDACONNECT.OPS.NETMAP"
validDays = 3650
signAuthThreshold = 2
keyGenAuthThreshold = 2

View File

@ -0,0 +1,24 @@
package com.r3.corda.networkmanage.hsm
import com.r3.corda.networkmanage.hsm.authentication.Authenticator
import com.r3.corda.networkmanage.hsm.authentication.createProvider
import org.junit.Test
import java.util.concurrent.atomic.AtomicBoolean
import kotlin.test.assertTrue
class HsmAuthenticatorTest : HsmCertificateTest() {
@Test
fun `Authenticator executes the block once user is successfully authenticated`() {
// given
val userInput = givenHsmUserAuthenticationInput()
val authenticator = Authenticator(provider = hsmSigningServiceConfig.createProvider(hsmSigningServiceConfig.rootKeyGroup), inputReader = userInput)
val executed = AtomicBoolean(false)
// when
authenticator.connectAndAuthenticate { _, _, _ -> executed.set(true) }
// then
assertTrue(executed.get())
}
}

View File

@ -0,0 +1,91 @@
package com.r3.corda.networkmanage.hsm
import com.nhaarman.mockito_kotlin.any
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.CryptoServerProviderConfig
import com.r3.corda.networkmanage.hsm.authentication.InputReader
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
import net.corda.nodeapi.internal.crypto.CertificateType
import org.junit.Rule
import org.junit.rules.TemporaryFolder
abstract class HsmCertificateTest {
companion object {
val ROOT_CERT_KEY_GROUP = "DEV.CORDACONNECT.ROOT"
val NETWORK_MAP_CERT_KEY_GROUP = "DEV.CORDACONNECT.OPS.NETMAP"
val DOORMAN_CERT_KEY_GROUP = "DEV.CORDACONNECT.OPS.CERT"
val ROOT_CERT_SUBJECT = "CN=Corda Root CA, O=R3 HoldCo LLC, OU=Corda, L=New York, C=US"
val NETWORK_MAP_CERT_SUBJECT = "CN=Corda Network Map, O=R3 HoldCo LLC, OU=Corda, L=New York, C=US"
val DOORMAN_CERT_SUBJECT = "CN=Corda Doorman CA, O=R3 HoldCo LLC, OU=Corda, L=New York, C=US"
}
@Rule
@JvmField
val tempFolder = TemporaryFolder()
@Rule
@JvmField
val hsmSimulator: HsmSimulator = HsmSimulator()
protected val rootCertParameters: GeneratorParameters by lazy {
GeneratorParameters(
hsmHost = hsmSimulator.host,
hsmPort = hsmSimulator.port,
trustStoreDirectory = tempFolder.root.toPath(),
trustStorePassword = "",
userConfigs = listOf(UserAuthenticationParameters(
username = "INTEGRATION_TEST",
authMode = AuthMode.PASSWORD,
authToken = "INTEGRATION_TEST",
keyFilePassword = null
)),
certConfig = CertificateConfiguration(
keySpecifier = 1,
keyGroup = ROOT_CERT_KEY_GROUP,
storeKeysExternal = false,
rootKeyGroup = null,
subject = ROOT_CERT_SUBJECT,
validDays = 3650,
keyCurve = "NIST-P256",
certificateType = CertificateType.ROOT_CA,
keyExport = 0,
keyGenMechanism = 4,
keyOverride = 0,
crlIssuer = null,
crlDistributionUrl = null
)
)
}
protected val providerConfig: CryptoServerProviderConfig by lazy {
CryptoServerProviderConfig(
Device = "${rootCertParameters.hsmPort}@${rootCertParameters.hsmHost}",
KeySpecifier = rootCertParameters.certConfig.keySpecifier,
KeyGroup = rootCertParameters.certConfig.keyGroup,
StoreKeysExternal = rootCertParameters.certConfig.storeKeysExternal)
}
protected val hsmSigningServiceConfig = Parameters(
dataSourceProperties = mock(),
device = "${hsmSimulator.port}@${hsmSimulator.host}",
keySpecifier = 1,
rootKeyGroup = ROOT_CERT_KEY_GROUP,
doormanKeyGroup = DOORMAN_CERT_KEY_GROUP,
networkMapKeyGroup = NETWORK_MAP_CERT_KEY_GROUP,
validDays = 3650,
csrCertCrlDistPoint = "http://test.com/revoked.crl"
)
protected fun givenHsmUserAuthenticationInput(): InputReader {
val inputReader = mock<InputReader>()
whenever(inputReader.readLine()).thenReturn(hsmSimulator.cryptoUserCredentials().username)
whenever(inputReader.readPassword(any())).thenReturn(hsmSimulator.cryptoUserCredentials().password)
return inputReader
}
}

View File

@ -1,111 +1,82 @@
package com.r3.corda.networkmanage.hsm
import com.r3.corda.networkmanage.HsmSimulator
import com.nhaarman.mockito_kotlin.any
import com.nhaarman.mockito_kotlin.mock
import com.nhaarman.mockito_kotlin.whenever
import com.r3.corda.networkmanage.common.utils.CORDA_NETWORK_MAP
import com.r3.corda.networkmanage.hsm.authentication.CryptoServerProviderConfig
import com.r3.corda.networkmanage.hsm.generator.*
import com.r3.corda.networkmanage.hsm.authentication.InputReader
import com.r3.corda.networkmanage.hsm.generator.AutoAuthenticator
import com.r3.corda.networkmanage.hsm.generator.run
import com.r3.corda.networkmanage.hsm.utils.HsmX509Utilities
import net.corda.core.identity.CordaX500Name
import net.corda.nodeapi.internal.crypto.CertificateType
import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_INTERMEDIATE_CA
import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_ROOT_CA
import org.junit.Rule
import org.junit.Before
import org.junit.Test
import org.junit.rules.TemporaryFolder
import java.security.cert.X509Certificate
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
class HsmKeyGenerationTest {
class HsmKeyGenerationTest : HsmCertificateTest() {
@Rule
@JvmField
val tempFolder = TemporaryFolder()
private lateinit var inputReader: InputReader
@Rule
@JvmField
val hsmSimulator: HsmSimulator = HsmSimulator()
private val rootCertParameters: GeneratorParameters by lazy {
GeneratorParameters(
hsmHost = hsmSimulator.host,
hsmPort = hsmSimulator.port,
trustStoreDirectory = tempFolder.root.toPath(),
trustStorePassword = "",
userConfigs = listOf(UserAuthenticationParameters(
username = "INTEGRATION_TEST",
authMode = AuthMode.PASSWORD,
authToken = "INTEGRATION_TEST",
keyFilePassword = null
)),
certConfig = CertificateConfiguration(
keySpecifier = 1,
keyGroup = "DEV.DOORMAN",
storeKeysExternal = false,
subject = "CN=Corda Root, O=R3Cev, L=London, C=GB",
validDays = 3650,
keyCurve = "NIST-P256",
certificateType = CertificateType.ROOT_CA,
keyExport = 0,
keyGenMechanism = 4,
keyOverride = 0,
crlIssuer = null,
crlDistributionUrl = null
)
)
}
private val providerConfig: CryptoServerProviderConfig by lazy {
CryptoServerProviderConfig(
Device = "${rootCertParameters.hsmPort}@${rootCertParameters.hsmHost}",
KeySpecifier = rootCertParameters.certConfig.keySpecifier,
KeyGroup = rootCertParameters.certConfig.keyGroup,
StoreKeysExternal = rootCertParameters.certConfig.storeKeysExternal)
@Before
fun setUp() {
inputReader = mock()
whenever(inputReader.readLine()).thenReturn(hsmSimulator.cryptoUserCredentials().username)
whenever(inputReader.readPassword(any())).thenReturn(hsmSimulator.cryptoUserCredentials().password)
}
@Test
fun `Authenticator executes the block once user is successfully authenticated`() {
// given
val authenticator = AutoAuthenticator(providerConfig, rootCertParameters.userConfigs)
val rootCertGenerator = KeyCertificateGenerator(rootCertParameters)
fun `Root and network map certificates have different namespace`() {
// when root cert is created
authenticator.connectAndAuthenticate { provider ->
rootCertGenerator.generate(provider)
// then
run(rootCertParameters)
// when network map cert is created
run(rootCertParameters.copy(
certConfig = rootCertParameters.certConfig.copy(
keyGroup = NETWORK_MAP_CERT_KEY_GROUP,
rootKeyGroup = ROOT_CERT_KEY_GROUP,
certificateType = CertificateType.NETWORK_MAP,
subject = NETWORK_MAP_CERT_SUBJECT
)
))
// when doorman cert is created
run(rootCertParameters.copy(
certConfig = rootCertParameters.certConfig.copy(
keyGroup = DOORMAN_CERT_KEY_GROUP,
rootKeyGroup = ROOT_CERT_KEY_GROUP,
certificateType = CertificateType.INTERMEDIATE_CA,
subject = DOORMAN_CERT_SUBJECT
)
))
// then root cert is persisted in the HSM
AutoAuthenticator(providerConfig, rootCertParameters.userConfigs).connectAndAuthenticate { provider ->
val keyStore = HsmX509Utilities.getAndInitializeKeyStore(provider)
val rootCert = keyStore.getCertificate(CORDA_ROOT_CA) as X509Certificate
assertEquals(rootCert.issuerX500Principal, rootCert.subjectX500Principal)
}
// when network map cert is created
val networkMapCertGenerator = KeyCertificateGenerator(rootCertParameters.copy(
certConfig = rootCertParameters.certConfig.copy(
certificateType = CertificateType.NETWORK_MAP,
subject = "CN=Corda NM, O=R3Cev, L=London, C=GB"
)
))
authenticator.connectAndAuthenticate { provider ->
networkMapCertGenerator.generate(provider)
// then
// then network map cert is persisted in the HSM
AutoAuthenticator(providerConfig.copy(KeyGroup = NETWORK_MAP_CERT_KEY_GROUP), rootCertParameters.userConfigs)
.connectAndAuthenticate { provider ->
val keyStore = HsmX509Utilities.getAndInitializeKeyStore(provider)
val rootCert = keyStore.getCertificate(CORDA_ROOT_CA) as X509Certificate
val networkMapCert = keyStore.getCertificate(CORDA_NETWORK_MAP) as X509Certificate
assertNotNull(networkMapCert)
assertEquals(rootCert.subjectX500Principal, networkMapCert.issuerX500Principal)
assertEquals(CordaX500Name.parse(ROOT_CERT_SUBJECT).x500Principal, networkMapCert.issuerX500Principal)
}
// when csr cert is created
val csrCertGenerator = KeyCertificateGenerator(rootCertParameters.copy(
certConfig = rootCertParameters.certConfig.copy(
certificateType = CertificateType.INTERMEDIATE_CA,
subject = "CN=Corda CSR, O=R3Cev, L=London, C=GB"
)
))
authenticator.connectAndAuthenticate { provider ->
csrCertGenerator.generate(provider)
// then
// then doorman cert is persisted in the HSM
AutoAuthenticator(providerConfig.copy(KeyGroup = DOORMAN_CERT_KEY_GROUP), rootCertParameters.userConfigs)
.connectAndAuthenticate { provider ->
val keyStore = HsmX509Utilities.getAndInitializeKeyStore(provider)
val rootCert = keyStore.getCertificate(CORDA_ROOT_CA) as X509Certificate
val csrCert = keyStore.getCertificate(CORDA_INTERMEDIATE_CA) as X509Certificate
assertNotNull(csrCert)
assertEquals(rootCert.subjectX500Principal, csrCert.issuerX500Principal)
val networkMapCert = keyStore.getCertificate(CORDA_INTERMEDIATE_CA) as X509Certificate
assertNotNull(networkMapCert)
assertEquals(CordaX500Name.parse(ROOT_CERT_SUBJECT).x500Principal, networkMapCert.issuerX500Principal)
}
}
}

View File

@ -0,0 +1,120 @@
package com.r3.corda.networkmanage.hsm
import com.nhaarman.mockito_kotlin.mock
import com.r3.corda.networkmanage.hsm.authentication.Authenticator
import com.r3.corda.networkmanage.hsm.authentication.createProvider
import com.r3.corda.networkmanage.hsm.generator.run
import com.r3.corda.networkmanage.hsm.persistence.ApprovedCertificateRequestData
import com.r3.corda.networkmanage.hsm.signer.HsmCsrSigner
import com.r3.corda.networkmanage.hsm.signer.HsmNetworkMapSigner
import net.corda.core.crypto.Crypto.generateKeyPair
import net.corda.core.crypto.secureRandomBytes
import net.corda.core.identity.CordaX500Name.Companion.parse
import net.corda.nodeapi.internal.crypto.CertificateType
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.createCertificateSigningRequest
import org.junit.Test
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
class HsmSigningServiceTest : HsmCertificateTest() {
@Test
fun `HSM signing service can sign network map data`() {
// when root cert is created
run(rootCertParameters)
// when network map cert is created
run(rootCertParameters.copy(
certConfig = rootCertParameters.certConfig.copy(
keyGroup = NETWORK_MAP_CERT_KEY_GROUP,
rootKeyGroup = ROOT_CERT_KEY_GROUP,
certificateType = CertificateType.NETWORK_MAP,
subject = NETWORK_MAP_CERT_SUBJECT
)
))
// when doorman cert is created
run(rootCertParameters.copy(
certConfig = rootCertParameters.certConfig.copy(
keyGroup = DOORMAN_CERT_KEY_GROUP,
rootKeyGroup = ROOT_CERT_KEY_GROUP,
certificateType = CertificateType.INTERMEDIATE_CA,
subject = DOORMAN_CERT_SUBJECT
)
))
// given authenticated user
val userInput = givenHsmUserAuthenticationInput()
// given HSM network map signer
val signer = HsmNetworkMapSigner(Authenticator(
provider = hsmSigningServiceConfig.createProvider(hsmSigningServiceConfig.networkMapKeyGroup),
inputReader = userInput))
// give random data to sign
val toSign = secureRandomBytes(10)
// when
signer.signBytes(toSign)
// No exception is thrown
}
@Test
fun `HSM signing service can sign CSR data`() {
// when root cert is created
run(rootCertParameters)
// when network map cert is created
run(rootCertParameters.copy(
certConfig = rootCertParameters.certConfig.copy(
keyGroup = NETWORK_MAP_CERT_KEY_GROUP,
rootKeyGroup = ROOT_CERT_KEY_GROUP,
certificateType = CertificateType.NETWORK_MAP,
subject = NETWORK_MAP_CERT_SUBJECT
)
))
// when doorman cert is created
run(rootCertParameters.copy(
certConfig = rootCertParameters.certConfig.copy(
keyGroup = DOORMAN_CERT_KEY_GROUP,
rootKeyGroup = ROOT_CERT_KEY_GROUP,
certificateType = CertificateType.INTERMEDIATE_CA,
subject = DOORMAN_CERT_SUBJECT
)
))
// given authenticated user
val userInput = givenHsmUserAuthenticationInput()
// given HSM CSR signer
val signer = HsmCsrSigner(
mock(),
CORDA_INTERMEDIATE_CA,
"",
null,
CORDA_ROOT_CA,
3650,
Authenticator(
provider = hsmSigningServiceConfig.createProvider(hsmSigningServiceConfig.doormanKeyGroup),
rootProvider = hsmSigningServiceConfig.createProvider(hsmSigningServiceConfig.rootKeyGroup),
inputReader = userInput)
)
// give random data to sign
val toSign = ApprovedCertificateRequestData(
"test",
createCertificateSigningRequest(
parse("O=R3Cev,L=London,C=GB").x500Principal,
"my@mail.com",
generateKeyPair(DEFAULT_TLS_SIGNATURE_SCHEME)))
// when
signer.sign(listOf(toSign))
// then
assertNotNull(toSign.certPath)
val certificates = toSign.certPath!!.certificates
assertEquals(3, certificates.size)
}
}

View File

@ -1,61 +0,0 @@
package com.r3.corda.networkmanage.hsm
import com.nhaarman.mockito_kotlin.any
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.Authenticator
import com.r3.corda.networkmanage.hsm.authentication.InputReader
import com.r3.corda.networkmanage.hsm.authentication.createProvider
import com.r3.corda.networkmanage.hsm.configuration.Parameters
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
import java.util.concurrent.atomic.AtomicBoolean
import kotlin.test.assertTrue
class HsmTest {
@Rule
@JvmField
val hsmSimulator: HsmSimulator = HsmSimulator()
@Rule
@JvmField
val tempFolder = TemporaryFolder()
private val testParameters = Parameters(
dataSourceProperties = mock(),
device = "${hsmSimulator.port}@${hsmSimulator.host}",
keySpecifier = 1,
csrPrivateKeyPassword = "",
networkMapPrivateKeyPassword = "",
rootPrivateKeyPassword = "",
keyGroup = "DEV.DOORMAN",
validDays = 3650,
csrCertCrlDistPoint = "http://test.com/revoked.crl"
)
private lateinit var inputReader: InputReader
@Before
fun setUp() {
inputReader = mock()
whenever(inputReader.readLine()).thenReturn(hsmSimulator.cryptoUserCredentials().username)
whenever(inputReader.readPassword(any())).thenReturn(hsmSimulator.cryptoUserCredentials().password)
}
@Test
fun `Authenticator executes the block once user is successfully authenticated`() {
// given
val authenticator = Authenticator(testParameters.createProvider(), inputReader = inputReader)
val executed = AtomicBoolean(false)
// when
authenticator.connectAndAuthenticate { _, _ -> executed.set(true) }
// then
assertTrue(executed.get())
}
}

View File

@ -57,8 +57,13 @@ fun run(parameters: Parameters) {
val database = configureDatabase(dataSourceProperties, databaseConfig)
val csrStorage = DBSignedCertificateRequestStorage(database)
val hsmSigner = HsmNetworkMapSigner(
networkMapPrivateKeyPassword,
Authenticator(createProvider(), AuthMode.KEY_FILE, autoUsername, authKeyFilePath, authKeyFilePassword, signAuthThreshold))
Authenticator(
AuthMode.KEY_FILE,
autoUsername,
authKeyFilePath,
authKeyFilePassword,
signAuthThreshold,
provider = createProvider(networkMapKeyGroup)))
val networkMapStorage = PersistentNetworkMapStorage(database)
val scheduler = Executors.newSingleThreadScheduledExecutor()
@ -68,12 +73,18 @@ fun run(parameters: Parameters) {
val signer = HsmCsrSigner(
csrStorage,
csrCertificateName,
csrPrivateKeyPassword,
csrCertCrlDistPoint,
csrCertCrlIssuer,
rootCertificateName,
validDays,
Authenticator(createProvider(), authMode, autoUsername, authKeyFilePath, authKeyFilePassword, signAuthThreshold))
Authenticator(
authMode,
autoUsername,
authKeyFilePath,
authKeyFilePassword,
signAuthThreshold,
provider = createProvider(doormanKeyGroup),
rootProvider = createProvider(rootKeyGroup)))
signer.sign(it)
}
Menu().withExceptionHandler(::processError).addItem("1", "Sign all approved and unsigned CSRs", {
@ -141,13 +152,6 @@ private fun confirmedSign(selectedItems: List<ApprovedCertificateRequestData>):
return result
}
private fun confirmedKeyGen(): Boolean {
println("Are you sure you want to generate new keys/certificates (it will overwrite the existing ones):")
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()

View File

@ -11,22 +11,24 @@ import kotlin.reflect.full.memberProperties
/**
* Performs user authentication against the HSM
*/
class Authenticator(private val provider: CryptoServerProvider,
private val mode: AuthMode = AuthMode.PASSWORD,
class Authenticator(private val mode: AuthMode = AuthMode.PASSWORD,
private val autoUsername: String? = null,
private val authKeyFilePath: Path? = null,
private val authKeyFilePass: String? = null,
private val authStrengthThreshold: Int = 2,
inputReader: InputReader = ConsoleInputReader()) : InputReader by inputReader {
inputReader: InputReader = ConsoleInputReader(),
private val provider: CryptoServerProvider,
private val rootProvider: CryptoServerProvider? = null) : InputReader by inputReader {
/**
* Interactively (using console) authenticates a user against the HSM. Once authentication is successful the
* [block] is executed.
* @param block to be executed once the authentication process succeeds. The block should take 2 parameters:
* 1) [CryptoServerProvider] instance
* @param block to be executed once the authentication process succeeds. The block should take 3 parameters:
* 1) [CryptoServerProvider] instance of the certificate provider
* 2) [CryptoServerProvider] instance of the root certificate provider
* 2) List of strings that corresponds to user names authenticated against the HSM.
*/
fun <T : Any> connectAndAuthenticate(block: (CryptoServerProvider, List<String>) -> T): T {
fun <T : Any> connectAndAuthenticate(block: (CryptoServerProvider, CryptoServerProvider?, List<String>) -> T): T {
return try {
val authenticated = mutableListOf<String>()
loop@ while (true) {
@ -45,7 +47,12 @@ class Authenticator(private val provider: CryptoServerProvider,
when (mode) {
AuthMode.CARD_READER -> {
println("Authenticating using card reader")
println("Accessing the certificate key group data...")
provider.loginSign(user, ":cs2:cyb:USB0", null)
if (rootProvider != null) {
println("Accessing the root certificate key group data...")
rootProvider.loginSign(user, ":cs2:cyb:USB0", null)
}
}
AuthMode.KEY_FILE -> {
println("Authenticating using preconfigured key file $authKeyFilePath")
@ -60,7 +67,12 @@ class Authenticator(private val provider: CryptoServerProvider,
} else {
authKeyFilePass
}
println("Accessing the certificate key group data...")
provider.loginSign(user, authKeyFilePath.toString(), password)
if (rootProvider != null) {
println("Accessing the root certificate key group data...")
rootProvider.loginSign(user, authKeyFilePath.toString(), password)
}
}
AuthMode.PASSWORD -> {
println("Authenticating using password")
@ -69,7 +81,12 @@ class Authenticator(private val provider: CryptoServerProvider,
authenticated.clear()
break@loop
}
println("Accessing the certificate key group data...")
provider.loginPassword(user, password)
if (rootProvider != null) {
println("Accessing the root certificate key group data...")
rootProvider.loginPassword(user, password)
}
}
}
authenticated.add(user!!)
@ -82,7 +99,7 @@ class Authenticator(private val provider: CryptoServerProvider,
}
}
if (!authenticated.isEmpty()) {
block(provider, authenticated)
block(provider, rootProvider, authenticated)
} else {
throw AuthenticationException()
}
@ -114,7 +131,7 @@ data class CryptoServerProviderConfig(
/**
* Creates an instance of [CryptoServerProvider] that corresponds to the HSM.
*/
fun Parameters.createProvider(): CryptoServerProvider {
fun Parameters.createProvider(keyGroup: String): CryptoServerProvider {
val config = CryptoServerProviderConfig(
Device = device,
KeyGroup = keyGroup,

View File

@ -20,15 +20,14 @@ 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 keyGroup: String,
val rootKeyGroup: String,
val doormanKeyGroup:String,
val networkMapKeyGroup: String,
val keySpecifier: Int = DEFAULT_KEY_SPECIFIER,
val rootPrivateKeyPassword: String,
val csrPrivateKeyPassword: String,
val csrCertificateName: String = DEFAULT_CSR_CERTIFICATE_NAME,
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 networkMapPrivateKeyPassword: String,
val rootCertificateName: String = DEFAULT_ROOT_CERTIFICATE_NAME,
val validDays: Int,
val signAuthThreshold: Int = DEFAULT_SIGN_AUTH_THRESHOLD,

View File

@ -19,7 +19,7 @@ class AutoAuthenticator(providerConfig: CryptoServerProviderConfig,
fun connectAndAuthenticate(block: (CryptoServerProvider) -> Unit) {
try {
for (userConfig in userConfigs) {
when(userConfig.authMode) {
when (userConfig.authMode) {
AuthMode.PASSWORD -> provider.loginPassword(userConfig.username, userConfig.authToken)
AuthMode.CARD_READER -> provider.loginSign(userConfig.username, ":cs2:cyb:USB0", null)
AuthMode.KEY_FILE -> provider.loginSign(userConfig.username, userConfig.keyFilePassword, userConfig.authToken)

View File

@ -15,7 +15,7 @@ import java.nio.file.Paths
*/
data class UserAuthenticationParameters(val username: String,
val authMode: AuthMode,
val authToken: String, // password or path to the key file, depending on the [authMode]
val authToken: String?, // password or path to the key file, depending on the [authMode]
val keyFilePassword: String?) // used only if authMode == [AuthMode.KEY_FILE]
/**
@ -42,6 +42,7 @@ data class CertificateConfiguration(val keyGroup: String,
val keySpecifier: Int,
val storeKeysExternal: Boolean,
val certificateType: CertificateType,
val rootKeyGroup: String?,
val subject: String, // it is certificate [X500Name] subject
val validDays: Int,
val crlDistributionUrl: String?,

View File

@ -4,7 +4,6 @@ import CryptoServerCXI.CryptoServerCXI.KEY_ALGO_ECDSA
import CryptoServerCXI.CryptoServerCXI.KeyAttributes
import CryptoServerJCE.CryptoServerProvider
import com.r3.corda.networkmanage.common.utils.CORDA_NETWORK_MAP
import com.r3.corda.networkmanage.doorman.NETWORK_ROOT_TRUSTSTORE_FILENAME
import com.r3.corda.networkmanage.hsm.utils.HsmX509Utilities.createIntermediateCert
import com.r3.corda.networkmanage.hsm.utils.HsmX509Utilities.createSelfSignedCACert
import com.r3.corda.networkmanage.hsm.utils.HsmX509Utilities.getAndInitializeKeyStore
@ -15,10 +14,13 @@ import net.corda.core.internal.div
import net.corda.core.internal.isDirectory
import net.corda.core.internal.x500Name
import net.corda.core.utilities.contextLogger
import net.corda.nodeapi.internal.crypto.*
import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair
import net.corda.nodeapi.internal.crypto.CertificateType.*
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.addOrReplaceCertificate
import net.corda.nodeapi.internal.crypto.loadOrCreateKeyStore
import net.corda.nodeapi.internal.crypto.save
import java.nio.file.Path
import java.security.Key
import java.security.KeyPair
@ -27,7 +29,6 @@ import java.security.PrivateKey
import java.security.cert.Certificate
import java.security.cert.X509Certificate
data class CertificateNameAndPass(val certificateName: String, val privateKeyPassword: String)
/**
* Encapsulates logic for key and certificate generation.
*
@ -37,7 +38,7 @@ class KeyCertificateGenerator(private val parameters: GeneratorParameters) {
val logger = contextLogger()
}
fun generate(provider: CryptoServerProvider) {
fun generate(provider: CryptoServerProvider, rootProvider: CryptoServerProvider? = null) {
parameters.run {
require(trustStoreDirectory.isDirectory()) { "trustStoreDirectory must point to a directory." }
val keyName = when (certConfig.certificateType) {
@ -48,10 +49,11 @@ class KeyCertificateGenerator(private val parameters: GeneratorParameters) {
}
val keyStore = getAndInitializeKeyStore(provider)
val keyPair = certConfig.generateEcdsaKeyPair(keyName, provider, keyStore)
val certChain = if (certConfig.certificateType == ROOT_CA) {
val certChain = if (rootProvider == null) {
certConfig.generateRootCert(provider, keyPair, trustStoreDirectory, trustStorePassword)
} else {
certConfig.generateIntermediateCert(provider, keyPair, keyStore)
val rootKeyStore = getAndInitializeKeyStore(rootProvider)
certConfig.generateIntermediateCert(rootProvider, keyPair, rootKeyStore)
}
keyStore.addOrReplaceKey(keyName, keyPair.private, null, certChain)
logger.info("New certificate and key pair named $keyName have been generated and stored in HSM")
@ -77,12 +79,13 @@ class KeyCertificateGenerator(private val parameters: GeneratorParameters) {
provider,
crlDistributionUrl,
crlIssuer).certificate
val networkRootTruststorePath = networkRootTrustStoreDirectory / NETWORK_ROOT_TRUSTSTORE_FILENAME
val networkRootTruststore = loadOrCreateKeyStore(networkRootTruststorePath, networkRootTrustStorePassword)
logger.info("Trust store for distribution to nodes created in $networkRootTruststorePath")
networkRootTruststore.addOrReplaceCertificate(CORDA_ROOT_CA, certificate)
logger.info("Certificate $CORDA_ROOT_CA has been added to $networkRootTruststorePath")
networkRootTruststore.save(networkRootTruststorePath, networkRootTrustStorePassword)
logger.info("Certificate for $subject created.")
val trustStorePath = networkRootTrustStoreDirectory / "truststore.jks"
val trustStore = loadOrCreateKeyStore(trustStorePath, networkRootTrustStorePassword)
logger.info("Trust store for distribution to nodes created in $trustStore")
trustStore.addOrReplaceCertificate(CORDA_ROOT_CA, certificate)
logger.info("Certificate $CORDA_ROOT_CA has been added to $trustStore")
trustStore.save(trustStorePath, networkRootTrustStorePassword)
logger.info("Trust store has been persisted. Ready for distribution.")
return arrayOf(certificate)
}
@ -90,8 +93,10 @@ class KeyCertificateGenerator(private val parameters: GeneratorParameters) {
private fun CertificateConfiguration.generateIntermediateCert(
provider: CryptoServerProvider,
keyPair: KeyPair,
keyStore: KeyStore): Array<X509Certificate> {
val rootKeysAndCertChain = retrieveKeysAndCertificateChain(CORDA_ROOT_CA, keyStore)
rootKeyStore: KeyStore): Array<X509Certificate> {
logger.info("Retrieving the root key pair.")
val rootKeysAndCertChain = retrieveKeysAndCertificateChain(CORDA_ROOT_CA,
rootKeyStore)
val certificateAndKeyPair = createIntermediateCert(
certificateType,
CordaX500Name.parse(subject).x500Name,
@ -101,6 +106,7 @@ class KeyCertificateGenerator(private val parameters: GeneratorParameters) {
provider,
crlDistributionUrl,
crlIssuer)
logger.info("Certificate for $subject created.")
return arrayOf(certificateAndKeyPair.certificate, *rootKeysAndCertChain.certificateChain)
}
@ -114,8 +120,9 @@ class KeyCertificateGenerator(private val parameters: GeneratorParameters) {
name = keyName
setCurve(keyCurve)
}
logger.info("Generating key $keyName")
logger.info("Generating key $keyName.")
provider.cryptoServer.generateKey(keyOverride, keyAttributes, keyGenMechanism)
logger.info("$keyName key generated.")
}
private fun CertificateConfiguration.generateEcdsaKeyPair(keyName: String, provider: CryptoServerProvider, keyStore: KeyStore): KeyPair {

View File

@ -2,24 +2,40 @@ package com.r3.corda.networkmanage.hsm.generator
import com.r3.corda.networkmanage.hsm.authentication.CryptoServerProviderConfig
import com.r3.corda.networkmanage.hsm.utils.mapCryptoServerException
import net.corda.nodeapi.internal.crypto.CertificateType.ROOT_CA
import org.apache.logging.log4j.LogManager
import java.nio.file.Paths
private val log = LogManager.getLogger("com.r3.corda.networkmanage.hsm.generator.Main")
fun main(args: Array<String>) {
val commandLineOptions = parseCommandLine(*args)
parseParameters(commandLineOptions.configFile).run {
run(parseParameters(parseCommandLine(*args).configFile))
}
fun run(parameters: GeneratorParameters) {
parameters.run {
val providerConfig = CryptoServerProviderConfig(
Device = "$hsmPort@$hsmHost",
KeySpecifier = certConfig.keySpecifier,
KeyGroup = certConfig.keyGroup,
StoreKeysExternal = certConfig.storeKeysExternal)
try {
val authenticator = AutoAuthenticator(providerConfig, userConfigs)
authenticator.connectAndAuthenticate { provider ->
AutoAuthenticator(providerConfig, userConfigs).connectAndAuthenticate { provider ->
val generator = KeyCertificateGenerator(this)
if (certConfig.certificateType == ROOT_CA) {
generator.generate(provider)
} else {
requireNotNull(certConfig.rootKeyGroup)
val rootProviderConfig = CryptoServerProviderConfig(
Device = "$hsmPort@$hsmHost",
KeySpecifier = certConfig.keySpecifier,
KeyGroup = certConfig.rootKeyGroup!!,
StoreKeysExternal = certConfig.storeKeysExternal)
AutoAuthenticator(rootProviderConfig, userConfigs).connectAndAuthenticate { rootProvider ->
generator.generate(provider, rootProvider)
rootProvider.logoff()
}
}
provider.logoff()
}
} catch (e: Exception) {
log.error(mapCryptoServerException(e))

View File

@ -14,7 +14,6 @@ import net.corda.nodeapi.internal.crypto.CertificateType
*/
class HsmCsrSigner(private val storage: SignedCertificateRequestStorage,
private val intermediateCertAlias: String,
private val intermediateCertPrivateKeyPass: String?,
private val csrCertCrlDistPoint: String,
private val csrCertCrlIssuer: String?,
private val rootCertAlias: String,
@ -32,22 +31,22 @@ class HsmCsrSigner(private val storage: SignedCertificateRequestStorage,
* @param toSign list of approved certificates to be signed
*/
override fun sign(toSign: List<ApprovedCertificateRequestData>) {
authenticator.connectAndAuthenticate { provider, signers ->
val keyStore = getAndInitializeKeyStore(provider)
authenticator.connectAndAuthenticate { provider, rootProvider, signers ->
val rootKeyStore = getAndInitializeKeyStore(rootProvider!!)
// 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 = keyStore.getCertificate(rootCertAlias)
val intermediatePrivateKeyPass = intermediateCertPrivateKeyPass ?: authenticator.readPassword("CA Private Key Password: ")
val intermediateCertAndKey = retrieveCertificateAndKeys(intermediateCertAlias, intermediatePrivateKeyPass, keyStore)
val rootCert = rootKeyStore.getCertificate(rootCertAlias)
val keyStore = getAndInitializeKeyStore(provider)
val doormanCertAndKey = retrieveCertificateAndKeys(intermediateCertAlias, keyStore)
toSign.forEach {
it.certPath = buildCertPath(createClientCertificate(
CertificateType.NODE_CA,
intermediateCertAndKey,
doormanCertAndKey,
it.request,
validDays,
provider,
csrCertCrlDistPoint,
csrCertCrlIssuer), rootCert)
csrCertCrlIssuer), doormanCertAndKey.certificate, rootCert)
}
storage.store(toSign, signers)
println("The following certificates have been signed by $signers:")

View File

@ -15,18 +15,17 @@ import java.security.Signature
* Signer which connects to a HSM using the given [authenticator] to sign bytes.
*/
// TODO Rename this to HsmSigner
class HsmNetworkMapSigner(private val privateKeyPassword: String,
private val authenticator: Authenticator) : Signer {
class HsmNetworkMapSigner(private val authenticator: Authenticator) : Signer {
/**
* Signs given data using [CryptoServerJCE.CryptoServerProvider], which connects to the underlying HSM.
*/
override fun signBytes(data: ByteArray): DigitalSignatureWithCert {
return authenticator.connectAndAuthenticate { provider, _ ->
return authenticator.connectAndAuthenticate { provider, _, _ ->
val keyStore = getAndInitializeKeyStore(provider)
val certificate = keyStore.getX509Certificate(CORDA_NETWORK_MAP)
// Don't worry this is not a real private key but a pointer to one that resides in the HSM. It only works
// when used with the given provider.
val key = keyStore.getKey(CORDA_NETWORK_MAP, privateKeyPassword.toCharArray()) as PrivateKey
val key = keyStore.getKey(CORDA_NETWORK_MAP, null) as PrivateKey
val signature = Signature.getInstance(HsmX509Utilities.SIGNATURE_ALGORITHM, provider).run {
initSign(key)
update(data)

View File

@ -101,12 +101,11 @@ object HsmX509Utilities {
* Retrieves a certificate and keys from the given key store. Also, the keys retrieved are cleaned in a sense of the
* [getCleanEcdsaKeyPair] method.
* @param certificateKeyName certificate and key name (alias) to be used when querying the key store.
* @param privateKeyPassword password for the private key.
* @param keyStore key store that holds the certificate with its keys.
* @return instance of [CertificateAndKeyPair] holding the retrieved certificate with its keys.
*/
fun retrieveCertificateAndKeys(certificateKeyName: String, privateKeyPassword: String, keyStore: KeyStore): CertificateAndKeyPair {
val privateKey = keyStore.getKey(certificateKeyName, privateKeyPassword.toCharArray()) as PrivateKey
fun retrieveCertificateAndKeys(certificateKeyName: String, keyStore: KeyStore): CertificateAndKeyPair {
val privateKey = keyStore.getKey(certificateKeyName, null) as PrivateKey
val publicKey = keyStore.getCertificate(certificateKeyName).publicKey
val certificate = keyStore.getX509Certificate(certificateKeyName)
return CertificateAndKeyPair(certificate, getCleanEcdsaKeyPair(publicKey, privateKey))

View File

@ -1,17 +0,0 @@
device = "3001@127.0.0.1"
keyGroup = "*"
keySpecifier = -1
authMode = PASSWORD
csrPrivateKeyPassword = ""
networkMapPrivateKeyPassword = ""
rootPrivateKeyPassword = ""
keyGroup = "DEV.DOORMAN"
validDays = 3650
h2port = 0
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" = ""
}

View File

@ -28,7 +28,7 @@ class AuthenticatorTest : TestBase() {
// when
assertFailsWith<AuthenticationException> {
Authenticator(provider = provider, inputReader = inputReader).connectAndAuthenticate { _, _ -> }
Authenticator(provider = provider, inputReader = inputReader).connectAndAuthenticate { _, _, _ -> }
}
//then
@ -47,7 +47,7 @@ class AuthenticatorTest : TestBase() {
var executed = false
// when
Authenticator(provider = provider, inputReader = inputReader).connectAndAuthenticate { _, _ -> executed = true }
Authenticator(provider = provider, inputReader = inputReader).connectAndAuthenticate { _, _, _ -> executed = true }
// then
verify(provider).loginPassword(username, password)
@ -64,7 +64,7 @@ class AuthenticatorTest : TestBase() {
var executed = false
// when
Authenticator(provider = provider, inputReader = inputReader, mode = AuthMode.CARD_READER).connectAndAuthenticate { _, _ -> executed = true }
Authenticator(provider = provider, inputReader = inputReader, mode = AuthMode.CARD_READER).connectAndAuthenticate { _, _, _ -> executed = true }
// then
verify(provider).loginSign(username, ":cs2:cyb:USB0", null)
@ -83,7 +83,7 @@ class AuthenticatorTest : TestBase() {
var executed = false
// when
Authenticator(provider = provider, inputReader = inputReader).connectAndAuthenticate { _, _ -> executed = true }
Authenticator(provider = provider, inputReader = inputReader).connectAndAuthenticate { _, _, _ -> executed = true }
// then
verify(provider, times(3)).loginPassword(username, password)

View File

@ -10,7 +10,7 @@ import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
class ConfigurationTest : TestBase() {
private val validConfigPath = File(javaClass.getResource("/hsm.conf").toURI()).absolutePath
private val validConfigPath = File("./hsm.conf").absolutePath
private val invalidConfigPath = File(javaClass.getResource("/hsm_fail.conf").toURI()).absolutePath
@Test

View File

@ -1,18 +0,0 @@
device = "3001@192.168.0.1"
keyGroup = "DEV.DOORMAN"
keySpecifier = -1
authMode = PASSWORD
csrPrivateKeyPassword = ""
csrCertCrlDistPoint = "http://test.com/revoked.crl"
networkMapPrivateKeyPassword = ""
rootPrivateKeyPassword = ""
keyGroup = "DEV.DOORMAN"
validDays = 3650
h2port = 0
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" = ""
}