mirror of
https://github.com/corda/corda.git
synced 2024-12-20 05:28:21 +00:00
CORDA-3968: Prepare keystore handling for certificate rotation [OS] (#6698)
* CORDA-3968: Move keystore logic to KeyStoreHandler. * CORDA-3967: Stop generating node legal identity in runtime. * CORDA-3969: Remove Node CA access at node normal runtime. * CORDA-3968: Fix handling of absent keystore file and wrong password.
This commit is contained in:
parent
acb82f77b4
commit
5c6acb0909
@ -108,7 +108,6 @@
|
|||||||
<ID>ComplexMethod:ClassCarpenter.kt$ClassCarpenterImpl$ private fun validateSchema(schema: Schema)</ID>
|
<ID>ComplexMethod:ClassCarpenter.kt$ClassCarpenterImpl$ private fun validateSchema(schema: Schema)</ID>
|
||||||
<ID>ComplexMethod:CompatibleTransactionTests.kt$CompatibleTransactionTests$@Test(timeout=300_000) fun `Command visibility tests`()</ID>
|
<ID>ComplexMethod:CompatibleTransactionTests.kt$CompatibleTransactionTests$@Test(timeout=300_000) fun `Command visibility tests`()</ID>
|
||||||
<ID>ComplexMethod:ConfigUtilities.kt$// For Iterables figure out the type parameter and apply the same logic as above on the individual elements. private fun Iterable<*>.toConfigIterable(field: Field): Iterable<Any?></ID>
|
<ID>ComplexMethod:ConfigUtilities.kt$// For Iterables figure out the type parameter and apply the same logic as above on the individual elements. private fun Iterable<*>.toConfigIterable(field: Field): Iterable<Any?></ID>
|
||||||
<ID>ComplexMethod:ConfigUtilities.kt$// TODO Move this to KeyStoreConfigHelpers. fun MutualSslConfiguration.configureDevKeyAndTrustStores(myLegalName: CordaX500Name, signingCertificateStore: FileBasedCertificateStoreSupplier, certificatesDirectory: Path, cryptoService: CryptoService? = null)</ID>
|
|
||||||
<ID>ComplexMethod:ConfigUtilities.kt$@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN") // Reflect over the fields of the receiver and generate a value Map that can use to create Config object. private fun Any.toConfigMap(): Map<String, Any></ID>
|
<ID>ComplexMethod:ConfigUtilities.kt$@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN") // Reflect over the fields of the receiver and generate a value Map that can use to create Config object. private fun Any.toConfigMap(): Map<String, Any></ID>
|
||||||
<ID>ComplexMethod:ConfigUtilities.kt$private fun convertValue(value: Any): Any</ID>
|
<ID>ComplexMethod:ConfigUtilities.kt$private fun convertValue(value: Any): Any</ID>
|
||||||
<ID>ComplexMethod:ConnectionStateMachine.kt$ConnectionStateMachine$override fun onConnectionFinal(event: Event)</ID>
|
<ID>ComplexMethod:ConnectionStateMachine.kt$ConnectionStateMachine$override fun onConnectionFinal(event: Event)</ID>
|
||||||
@ -215,7 +214,6 @@
|
|||||||
<ID>ForbiddenComment:AbstractCashSelection.kt$AbstractCashSelection$// TODO: future implementation to retrieve contract states from a Vault BLOB store</ID>
|
<ID>ForbiddenComment:AbstractCashSelection.kt$AbstractCashSelection$// TODO: future implementation to retrieve contract states from a Vault BLOB store</ID>
|
||||||
<ID>ForbiddenComment:AbstractCashSelection.kt$AbstractCashSelection$// TODO: make parameters configurable when we get CorDapp configuration.</ID>
|
<ID>ForbiddenComment:AbstractCashSelection.kt$AbstractCashSelection$// TODO: make parameters configurable when we get CorDapp configuration.</ID>
|
||||||
<ID>ForbiddenComment:AbstractCashSelection.kt$AbstractCashSelection$// TODO: revisit the back off strategy for contended spending.</ID>
|
<ID>ForbiddenComment:AbstractCashSelection.kt$AbstractCashSelection$// TODO: revisit the back off strategy for contended spending.</ID>
|
||||||
<ID>ForbiddenComment:AbstractNode.kt$AbstractNode$// TODO: Use configuration to indicate composite key should be used instead of public key for the identity.</ID>
|
|
||||||
<ID>ForbiddenComment:AbstractNode.kt$AbstractNode$// TODO: We need a good way of handling "nice to have" shutdown events, especially those that deal with the</ID>
|
<ID>ForbiddenComment:AbstractNode.kt$AbstractNode$// TODO: We need a good way of handling "nice to have" shutdown events, especially those that deal with the</ID>
|
||||||
<ID>ForbiddenComment:AbstractNode.kt$AbstractNode.<no name provided>$// TODO: Exponential backoff? It should reach max interval of eventHorizon/2.</ID>
|
<ID>ForbiddenComment:AbstractNode.kt$AbstractNode.<no name provided>$// TODO: Exponential backoff? It should reach max interval of eventHorizon/2.</ID>
|
||||||
<ID>ForbiddenComment:AbstractStateReplacementFlow.kt$AbstractStateReplacementFlow.Acceptor$// TODO: This step should not be necessary, as signatures are re-checked in verifyRequiredSignatures.</ID>
|
<ID>ForbiddenComment:AbstractStateReplacementFlow.kt$AbstractStateReplacementFlow.Acceptor$// TODO: This step should not be necessary, as signatures are re-checked in verifyRequiredSignatures.</ID>
|
||||||
@ -307,7 +305,6 @@
|
|||||||
<ID>ForbiddenComment:DriverDSLImpl.kt$DriverDSLImpl$//TODO: remove this once we can bundle quasar properly.</ID>
|
<ID>ForbiddenComment:DriverDSLImpl.kt$DriverDSLImpl$//TODO: remove this once we can bundle quasar properly.</ID>
|
||||||
<ID>ForbiddenComment:DriverDSLImpl.kt$DriverDSLImpl.LocalNetworkMap$// TODO: this object will copy NodeInfo files from started nodes to other nodes additional-node-infos/</ID>
|
<ID>ForbiddenComment:DriverDSLImpl.kt$DriverDSLImpl.LocalNetworkMap$// TODO: this object will copy NodeInfo files from started nodes to other nodes additional-node-infos/</ID>
|
||||||
<ID>ForbiddenComment:DummyFungibleContract.kt$DummyFungibleContract$// TODO: This doesn't work with the trader demo, so use the underlying key instead</ID>
|
<ID>ForbiddenComment:DummyFungibleContract.kt$DummyFungibleContract$// TODO: This doesn't work with the trader demo, so use the underlying key instead</ID>
|
||||||
<ID>ForbiddenComment:E2ETestKeyManagementService.kt$E2ETestKeyManagementService$// TODO: A full KeyManagementService implementation needs to record activity to the Audit Service and to limit</ID>
|
|
||||||
<ID>ForbiddenComment:EncodingUtils.kt$// TODO: follow the crypto-conditions ASN.1 spec, some changes are needed to be compatible with the condition</ID>
|
<ID>ForbiddenComment:EncodingUtils.kt$// TODO: follow the crypto-conditions ASN.1 spec, some changes are needed to be compatible with the condition</ID>
|
||||||
<ID>ForbiddenComment:Envelope.kt$Envelope$// TODO: don't recognise a type descriptor.</ID>
|
<ID>ForbiddenComment:Envelope.kt$Envelope$// TODO: don't recognise a type descriptor.</ID>
|
||||||
<ID>ForbiddenComment:Envelope.kt$Envelope$// TODO: make the schema parsing lazy since mostly schemas will have been seen before and we only need it if we</ID>
|
<ID>ForbiddenComment:Envelope.kt$Envelope$// TODO: make the schema parsing lazy since mostly schemas will have been seen before and we only need it if we</ID>
|
||||||
@ -339,7 +336,6 @@
|
|||||||
<ID>ForbiddenComment:IdentitySyncFlow.kt$IdentitySyncFlow.Send$// TODO: Can this be triggered automatically from [SendTransactionFlow]?</ID>
|
<ID>ForbiddenComment:IdentitySyncFlow.kt$IdentitySyncFlow.Send$// TODO: Can this be triggered automatically from [SendTransactionFlow]?</ID>
|
||||||
<ID>ForbiddenComment:IdentitySyncFlow.kt$IdentitySyncFlow.Send$// TODO: Consider if this too restrictive - we perhaps should be checking the name on the signing certificate in the certificate path instead</ID>
|
<ID>ForbiddenComment:IdentitySyncFlow.kt$IdentitySyncFlow.Send$// TODO: Consider if this too restrictive - we perhaps should be checking the name on the signing certificate in the certificate path instead</ID>
|
||||||
<ID>ForbiddenComment:InMemoryIdentityServiceTests.kt$InMemoryIdentityServiceTests$// TODO: Generate certificate with an EdDSA key rather than ECDSA</ID>
|
<ID>ForbiddenComment:InMemoryIdentityServiceTests.kt$InMemoryIdentityServiceTests$// TODO: Generate certificate with an EdDSA key rather than ECDSA</ID>
|
||||||
<ID>ForbiddenComment:InitialRegistrationCli.kt$InitialRegistration$// TODO: Move node identity generation logic from node to registration helper.</ID>
|
|
||||||
<ID>ForbiddenComment:InteractiveShell.kt$// TODO: Add a command to view last N lines/tail/control log4j2 loggers.</ID>
|
<ID>ForbiddenComment:InteractiveShell.kt$// TODO: Add a command to view last N lines/tail/control log4j2 loggers.</ID>
|
||||||
<ID>ForbiddenComment:InteractiveShell.kt$// TODO: Add command history.</ID>
|
<ID>ForbiddenComment:InteractiveShell.kt$// TODO: Add command history.</ID>
|
||||||
<ID>ForbiddenComment:InteractiveShell.kt$// TODO: Command completion.</ID>
|
<ID>ForbiddenComment:InteractiveShell.kt$// TODO: Command completion.</ID>
|
||||||
@ -439,7 +435,6 @@
|
|||||||
<ID>ForbiddenComment:SimmFlow.kt$SimmFlow.Requester$// TODO: The attachments need to be added somewhere</ID>
|
<ID>ForbiddenComment:SimmFlow.kt$SimmFlow.Requester$// TODO: The attachments need to be added somewhere</ID>
|
||||||
<ID>ForbiddenComment:SimmFlow.kt$SimmFlow.Requester$// TODO: handle failures</ID>
|
<ID>ForbiddenComment:SimmFlow.kt$SimmFlow.Requester$// TODO: handle failures</ID>
|
||||||
<ID>ForbiddenComment:SinglePartyNotaryService.kt$SinglePartyNotaryService$// TODO: Log the request here. Benchmarking shows that logging is expensive and we might get better performance</ID>
|
<ID>ForbiddenComment:SinglePartyNotaryService.kt$SinglePartyNotaryService$// TODO: Log the request here. Benchmarking shows that logging is expensive and we might get better performance</ID>
|
||||||
<ID>ForbiddenComment:StateMachineManagerUtils.kt$//TODO: instead of replacing the progress tracker after constructing the flow logic, we should inject it during fiber deserialization</ID>
|
|
||||||
<ID>ForbiddenComment:Structures.kt$MoveCommand$// TODO: Replace Class here with a general contract constraints object</ID>
|
<ID>ForbiddenComment:Structures.kt$MoveCommand$// TODO: Replace Class here with a general contract constraints object</ID>
|
||||||
<ID>ForbiddenComment:SwapData.kt$SwapData$// TODO: Fix below to be correct - change tenor and reference data</ID>
|
<ID>ForbiddenComment:SwapData.kt$SwapData$// TODO: Fix below to be correct - change tenor and reference data</ID>
|
||||||
<ID>ForbiddenComment:SwapDataView.kt$// TODO: Should be able to display an array ?</ID>
|
<ID>ForbiddenComment:SwapDataView.kt$// TODO: Should be able to display an array ?</ID>
|
||||||
@ -1285,8 +1280,6 @@
|
|||||||
<ID>SpreadOperator:HibernateQueryCriteriaParser.kt$AbstractQueryCriteriaParser$(*leftPredicates.toTypedArray(), *rightPredicates.toTypedArray())</ID>
|
<ID>SpreadOperator:HibernateQueryCriteriaParser.kt$AbstractQueryCriteriaParser$(*leftPredicates.toTypedArray(), *rightPredicates.toTypedArray())</ID>
|
||||||
<ID>SpreadOperator:HibernateQueryCriteriaParser.kt$AbstractQueryCriteriaParser$(*rightPredicates.toTypedArray())</ID>
|
<ID>SpreadOperator:HibernateQueryCriteriaParser.kt$AbstractQueryCriteriaParser$(*rightPredicates.toTypedArray())</ID>
|
||||||
<ID>SpreadOperator:HibernateQueryCriteriaParser.kt$HibernateAttachmentQueryCriteriaParser$(*predicateSet.toTypedArray())</ID>
|
<ID>SpreadOperator:HibernateQueryCriteriaParser.kt$HibernateAttachmentQueryCriteriaParser$(*predicateSet.toTypedArray())</ID>
|
||||||
<ID>SpreadOperator:HibernateQueryCriteriaParser.kt$HibernateQueryCriteriaParser$(*combinedPredicates.toTypedArray())</ID>
|
|
||||||
<ID>SpreadOperator:HibernateQueryCriteriaParser.kt$HibernateQueryCriteriaParser$(*joinPredicates.toTypedArray())</ID>
|
|
||||||
<ID>SpreadOperator:IRSDemo.kt$(*args)</ID>
|
<ID>SpreadOperator:IRSDemo.kt$(*args)</ID>
|
||||||
<ID>SpreadOperator:InstallShellExtensionsParser.kt$ShellExtensionsGenerator$(*commandAndArgs)</ID>
|
<ID>SpreadOperator:InstallShellExtensionsParser.kt$ShellExtensionsGenerator$(*commandAndArgs)</ID>
|
||||||
<ID>SpreadOperator:InteractiveShell.kt$InteractiveShell$(clazz, *args)</ID>
|
<ID>SpreadOperator:InteractiveShell.kt$InteractiveShell$(clazz, *args)</ID>
|
||||||
@ -1387,6 +1380,7 @@
|
|||||||
<ID>ThrowsCount:EnumTransforms.kt$EnumTransforms$ private fun validateNoCycles(constants: Map<String, Int>)</ID>
|
<ID>ThrowsCount:EnumTransforms.kt$EnumTransforms$ private fun validateNoCycles(constants: Map<String, Int>)</ID>
|
||||||
<ID>ThrowsCount:JacksonSupport.kt$JacksonSupport.PartyDeserializer$private fun lookupByNameSegment(mapper: PartyObjectMapper, parser: JsonParser): Party</ID>
|
<ID>ThrowsCount:JacksonSupport.kt$JacksonSupport.PartyDeserializer$private fun lookupByNameSegment(mapper: PartyObjectMapper, parser: JsonParser): Party</ID>
|
||||||
<ID>ThrowsCount:JarScanningCordappLoader.kt$JarScanningCordappLoader$private fun parseVersion(versionStr: String?, attributeName: String): Int</ID>
|
<ID>ThrowsCount:JarScanningCordappLoader.kt$JarScanningCordappLoader$private fun parseVersion(versionStr: String?, attributeName: String): Int</ID>
|
||||||
|
<ID>ThrowsCount:KeyStoreHandler.kt$KeyStoreHandler$private fun getCertificateStores(): AllCertificateStores</ID>
|
||||||
<ID>ThrowsCount:LedgerDSLInterpreter.kt$Verifies$ fun failsWith(expectedMessage: String?): EnforceVerifyOrFail</ID>
|
<ID>ThrowsCount:LedgerDSLInterpreter.kt$Verifies$ fun failsWith(expectedMessage: String?): EnforceVerifyOrFail</ID>
|
||||||
<ID>ThrowsCount:MockServices.kt$ fun <T : SerializeAsToken> createMockCordaService(serviceHub: MockServices, serviceConstructor: (AppServiceHub) -> T): T</ID>
|
<ID>ThrowsCount:MockServices.kt$ fun <T : SerializeAsToken> createMockCordaService(serviceHub: MockServices, serviceConstructor: (AppServiceHub) -> T): T</ID>
|
||||||
<ID>ThrowsCount:NetworkRegistrationHelper.kt$NetworkRegistrationHelper$private fun validateCertificates( registeringPublicKey: PublicKey, registeringLegalName: CordaX500Name, expectedCertRole: CertRole, certificates: List<X509Certificate> )</ID>
|
<ID>ThrowsCount:NetworkRegistrationHelper.kt$NetworkRegistrationHelper$private fun validateCertificates( registeringPublicKey: PublicKey, registeringLegalName: CordaX500Name, expectedCertRole: CertRole, certificates: List<X509Certificate> )</ID>
|
||||||
@ -1483,7 +1477,6 @@
|
|||||||
<ID>TooGenericExceptionCaught:InternalUtils.kt$ex: Exception</ID>
|
<ID>TooGenericExceptionCaught:InternalUtils.kt$ex: Exception</ID>
|
||||||
<ID>TooGenericExceptionCaught:InternalUtils.kt$th: Throwable</ID>
|
<ID>TooGenericExceptionCaught:InternalUtils.kt$th: Throwable</ID>
|
||||||
<ID>TooGenericExceptionCaught:IssueCash.kt$IssueCash$e: Exception</ID>
|
<ID>TooGenericExceptionCaught:IssueCash.kt$IssueCash$e: Exception</ID>
|
||||||
<ID>TooGenericExceptionCaught:JVMAgentUtil.kt$JVMAgentUtil$e: Throwable</ID>
|
|
||||||
<ID>TooGenericExceptionCaught:JacksonSupport.kt$JacksonSupport.PartyDeserializer$e: Exception</ID>
|
<ID>TooGenericExceptionCaught:JacksonSupport.kt$JacksonSupport.PartyDeserializer$e: Exception</ID>
|
||||||
<ID>TooGenericExceptionCaught:JacksonSupport.kt$JacksonSupport.PublicKeyDeserializer$e: Exception</ID>
|
<ID>TooGenericExceptionCaught:JacksonSupport.kt$JacksonSupport.PublicKeyDeserializer$e: Exception</ID>
|
||||||
<ID>TooGenericExceptionCaught:JacksonSupport.kt$JacksonSupport.SecureHashDeserializer$e: Exception</ID>
|
<ID>TooGenericExceptionCaught:JacksonSupport.kt$JacksonSupport.SecureHashDeserializer$e: Exception</ID>
|
||||||
@ -2005,7 +1998,6 @@
|
|||||||
<ID>WildcardImport:DummyFungibleContract.kt$import net.corda.core.contracts.*</ID>
|
<ID>WildcardImport:DummyFungibleContract.kt$import net.corda.core.contracts.*</ID>
|
||||||
<ID>WildcardImport:DummyLinearStateSchemaV1.kt$import javax.persistence.*</ID>
|
<ID>WildcardImport:DummyLinearStateSchemaV1.kt$import javax.persistence.*</ID>
|
||||||
<ID>WildcardImport:DummyLinearStateSchemaV2.kt$import javax.persistence.*</ID>
|
<ID>WildcardImport:DummyLinearStateSchemaV2.kt$import javax.persistence.*</ID>
|
||||||
<ID>WildcardImport:E2ETestKeyManagementService.kt$import net.corda.core.crypto.*</ID>
|
|
||||||
<ID>WildcardImport:EnumEvolvabilityTests.kt$import net.corda.core.serialization.*</ID>
|
<ID>WildcardImport:EnumEvolvabilityTests.kt$import net.corda.core.serialization.*</ID>
|
||||||
<ID>WildcardImport:EnumEvolvabilityTests.kt$import net.corda.serialization.internal.amqp.testutils.*</ID>
|
<ID>WildcardImport:EnumEvolvabilityTests.kt$import net.corda.serialization.internal.amqp.testutils.*</ID>
|
||||||
<ID>WildcardImport:ErrorFlowTransition.kt$import net.corda.node.services.statemachine.*</ID>
|
<ID>WildcardImport:ErrorFlowTransition.kt$import net.corda.node.services.statemachine.*</ID>
|
||||||
@ -2154,7 +2146,6 @@
|
|||||||
<ID>WildcardImport:NetworkMapTest.kt$import net.corda.core.internal.*</ID>
|
<ID>WildcardImport:NetworkMapTest.kt$import net.corda.core.internal.*</ID>
|
||||||
<ID>WildcardImport:NetworkMapTest.kt$import net.corda.testing.core.*</ID>
|
<ID>WildcardImport:NetworkMapTest.kt$import net.corda.testing.core.*</ID>
|
||||||
<ID>WildcardImport:NetworkMapTest.kt$import net.corda.testing.node.internal.*</ID>
|
<ID>WildcardImport:NetworkMapTest.kt$import net.corda.testing.node.internal.*</ID>
|
||||||
<ID>WildcardImport:NetworkParametersReader.kt$import net.corda.core.internal.*</ID>
|
|
||||||
<ID>WildcardImport:NetworkParametersReaderTest.kt$import net.corda.core.internal.*</ID>
|
<ID>WildcardImport:NetworkParametersReaderTest.kt$import net.corda.core.internal.*</ID>
|
||||||
<ID>WildcardImport:NetworkParametersReaderTest.kt$import net.corda.nodeapi.internal.network.*</ID>
|
<ID>WildcardImport:NetworkParametersReaderTest.kt$import net.corda.nodeapi.internal.network.*</ID>
|
||||||
<ID>WildcardImport:NetworkParametersTest.kt$import net.corda.testing.core.*</ID>
|
<ID>WildcardImport:NetworkParametersTest.kt$import net.corda.testing.core.*</ID>
|
||||||
|
@ -13,7 +13,6 @@ import net.corda.core.concurrent.CordaFuture
|
|||||||
import net.corda.core.context.InvocationContext
|
import net.corda.core.context.InvocationContext
|
||||||
import net.corda.core.crypto.DigitalSignature
|
import net.corda.core.crypto.DigitalSignature
|
||||||
import net.corda.core.crypto.SecureHash
|
import net.corda.core.crypto.SecureHash
|
||||||
import net.corda.core.crypto.internal.AliasPrivateKey
|
|
||||||
import net.corda.core.crypto.newSecureRandom
|
import net.corda.core.crypto.newSecureRandom
|
||||||
import net.corda.core.flows.ContractUpgradeFlow
|
import net.corda.core.flows.ContractUpgradeFlow
|
||||||
import net.corda.core.flows.FinalityFlow
|
import net.corda.core.flows.FinalityFlow
|
||||||
@ -147,15 +146,7 @@ import net.corda.node.utilities.NamedThreadFactory
|
|||||||
import net.corda.node.utilities.NotaryLoader
|
import net.corda.node.utilities.NotaryLoader
|
||||||
import net.corda.nodeapi.internal.NodeInfoAndSigned
|
import net.corda.nodeapi.internal.NodeInfoAndSigned
|
||||||
import net.corda.nodeapi.internal.SignedNodeInfo
|
import net.corda.nodeapi.internal.SignedNodeInfo
|
||||||
import net.corda.nodeapi.internal.config.CertificateStore
|
|
||||||
import net.corda.nodeapi.internal.cordapp.CordappLoader
|
import net.corda.nodeapi.internal.cordapp.CordappLoader
|
||||||
import net.corda.nodeapi.internal.crypto.CertificateType
|
|
||||||
import net.corda.nodeapi.internal.crypto.X509Utilities
|
|
||||||
import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_CLIENT_CA
|
|
||||||
import net.corda.nodeapi.internal.crypto.X509Utilities.DEFAULT_VALIDITY_WINDOW
|
|
||||||
import net.corda.nodeapi.internal.crypto.X509Utilities.DISTRIBUTED_NOTARY_COMPOSITE_KEY_ALIAS
|
|
||||||
import net.corda.nodeapi.internal.crypto.X509Utilities.DISTRIBUTED_NOTARY_KEY_ALIAS
|
|
||||||
import net.corda.nodeapi.internal.crypto.X509Utilities.NODE_IDENTITY_KEY_ALIAS
|
|
||||||
import net.corda.nodeapi.internal.cryptoservice.CryptoService
|
import net.corda.nodeapi.internal.cryptoservice.CryptoService
|
||||||
import net.corda.nodeapi.internal.cryptoservice.bouncycastle.BCCryptoService
|
import net.corda.nodeapi.internal.cryptoservice.bouncycastle.BCCryptoService
|
||||||
import net.corda.nodeapi.internal.lifecycle.NodeLifecycleEvent
|
import net.corda.nodeapi.internal.lifecycle.NodeLifecycleEvent
|
||||||
@ -178,8 +169,6 @@ import org.jolokia.jvmagent.JolokiaServerConfig
|
|||||||
import org.slf4j.Logger
|
import org.slf4j.Logger
|
||||||
import rx.Scheduler
|
import rx.Scheduler
|
||||||
import java.lang.reflect.InvocationTargetException
|
import java.lang.reflect.InvocationTargetException
|
||||||
import java.security.KeyPair
|
|
||||||
import java.security.cert.X509Certificate
|
|
||||||
import java.sql.Connection
|
import java.sql.Connection
|
||||||
import java.sql.Savepoint
|
import java.sql.Savepoint
|
||||||
import java.time.Clock
|
import java.time.Clock
|
||||||
@ -390,6 +379,8 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
|
|||||||
|
|
||||||
private val nodeLifecycleEventsDistributor = NodeLifecycleEventsDistributor().apply { add(checkpointDumper) }
|
private val nodeLifecycleEventsDistributor = NodeLifecycleEventsDistributor().apply { add(checkpointDumper) }
|
||||||
|
|
||||||
|
protected val keyStoreHandler = KeyStoreHandler(configuration, cryptoService)
|
||||||
|
|
||||||
private fun <T : Any> T.tokenize(): T {
|
private fun <T : Any> T.tokenize(): T {
|
||||||
tokenizableServices?.add(this as? SerializeAsToken ?:
|
tokenizableServices?.add(this as? SerializeAsToken ?:
|
||||||
throw IllegalStateException("${this::class.java} is expected to be extending from SerializeAsToken"))
|
throw IllegalStateException("${this::class.java} is expected to be extending from SerializeAsToken"))
|
||||||
@ -434,17 +425,17 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected open fun initKeyStores() = keyStoreHandler.init()
|
||||||
|
|
||||||
open fun generateAndSaveNodeInfo(): NodeInfo {
|
open fun generateAndSaveNodeInfo(): NodeInfo {
|
||||||
check(started == null) { "Node has already been started" }
|
check(started == null) { "Node has already been started" }
|
||||||
log.info("Generating nodeInfo ...")
|
log.info("Generating nodeInfo ...")
|
||||||
val trustRoot = configuration.initKeyStores(cryptoService)
|
val trustRoot = initKeyStores()
|
||||||
startDatabase()
|
startDatabase()
|
||||||
val (identity, identityKeyPair) = obtainIdentity()
|
identityService.start(trustRoot, keyStoreHandler.nodeIdentity, pkToIdCache = pkToIdCache)
|
||||||
val nodeCa = configuration.signingCertificateStore.get()[CORDA_CLIENT_CA]
|
|
||||||
identityService.start(trustRoot, listOf(identity.certificate, nodeCa), pkToIdCache = pkToIdCache)
|
|
||||||
return database.use {
|
return database.use {
|
||||||
it.transaction {
|
it.transaction {
|
||||||
val (_, nodeInfoAndSigned) = updateNodeInfo(identity, identityKeyPair, publish = false)
|
val nodeInfoAndSigned = updateNodeInfo(publish = false)
|
||||||
nodeInfoAndSigned.nodeInfo
|
nodeInfoAndSigned.nodeInfo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -488,7 +479,7 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
|
|||||||
logVendorString(database, log)
|
logVendorString(database, log)
|
||||||
if (allowHibernateToManageAppSchema) {
|
if (allowHibernateToManageAppSchema) {
|
||||||
Node.printBasicNodeInfo("Initialising CorDapps to get schemas created by hibernate")
|
Node.printBasicNodeInfo("Initialising CorDapps to get schemas created by hibernate")
|
||||||
val trustRoot = configuration.initKeyStores(cryptoService)
|
val trustRoot = initKeyStores()
|
||||||
networkMapClient?.start(trustRoot)
|
networkMapClient?.start(trustRoot)
|
||||||
val (netParams, signedNetParams) = NetworkParametersReader(trustRoot, networkMapClient, configuration.networkParametersPath).read()
|
val (netParams, signedNetParams) = NetworkParametersReader(trustRoot, networkMapClient, configuration.networkParametersPath).read()
|
||||||
log.info("Loaded network parameters: $netParams")
|
log.info("Loaded network parameters: $netParams")
|
||||||
@ -538,7 +529,7 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
|
|||||||
nodeLifecycleEventsDistributor.distributeEvent(NodeLifecycleEvent.BeforeNodeStart(nodeServicesContext))
|
nodeLifecycleEventsDistributor.distributeEvent(NodeLifecycleEvent.BeforeNodeStart(nodeServicesContext))
|
||||||
log.info("Node starting up ...")
|
log.info("Node starting up ...")
|
||||||
|
|
||||||
val trustRoot = configuration.initKeyStores(cryptoService)
|
val trustRoot = initKeyStores()
|
||||||
initialiseJolokia()
|
initialiseJolokia()
|
||||||
|
|
||||||
schemaService.mappedSchemasWarnings().forEach {
|
schemaService.mappedSchemasWarnings().forEach {
|
||||||
@ -563,14 +554,11 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
|
|||||||
networkMapCache.start(netParams.notaries)
|
networkMapCache.start(netParams.notaries)
|
||||||
|
|
||||||
startDatabase()
|
startDatabase()
|
||||||
val (identity, identityKeyPair) = obtainIdentity()
|
|
||||||
X509Utilities.validateCertPath(trustRoot, identity.certPath)
|
|
||||||
|
|
||||||
val nodeCa = configuration.signingCertificateStore.get()[CORDA_CLIENT_CA]
|
identityService.start(trustRoot, keyStoreHandler.nodeIdentity, netParams.notaries.map { it.identity }, pkToIdCache)
|
||||||
identityService.start(trustRoot, listOf(identity.certificate, nodeCa), netParams.notaries.map { it.identity }, pkToIdCache)
|
|
||||||
|
|
||||||
val (keyPairs, nodeInfoAndSigned, myNotaryIdentity) = database.transaction {
|
val nodeInfoAndSigned = database.transaction {
|
||||||
updateNodeInfo(identity, identityKeyPair, publish = true)
|
updateNodeInfo(publish = true)
|
||||||
}
|
}
|
||||||
|
|
||||||
val (nodeInfo, signedNodeInfo) = nodeInfoAndSigned
|
val (nodeInfo, signedNodeInfo) = nodeInfoAndSigned
|
||||||
@ -598,7 +586,7 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
|
|||||||
networkParametersHotloader)
|
networkParametersHotloader)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
startMessagingService(rpcOps, nodeInfo, myNotaryIdentity, netParams)
|
startMessagingService(rpcOps, nodeInfo, keyStoreHandler.notaryIdentity, netParams)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
// Try to stop any started messaging services.
|
// Try to stop any started messaging services.
|
||||||
stop()
|
stop()
|
||||||
@ -619,9 +607,9 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
|
|||||||
// Place the long term identity key in the KMS. Eventually, this is likely going to be separated again because
|
// Place the long term identity key in the KMS. Eventually, this is likely going to be separated again because
|
||||||
// the KMS is meant for derived temporary keys used in transactions, and we're not supposed to sign things with
|
// the KMS is meant for derived temporary keys used in transactions, and we're not supposed to sign things with
|
||||||
// the identity key. But the infrastructure to make that easy isn't here yet.
|
// the identity key. But the infrastructure to make that easy isn't here yet.
|
||||||
keyManagementService.start(keyPairs)
|
keyManagementService.start(keyStoreHandler.signingKeys.map { it.key to it.alias })
|
||||||
installCordaServices()
|
installCordaServices()
|
||||||
notaryService = maybeStartNotaryService(myNotaryIdentity)
|
notaryService = maybeStartNotaryService(keyStoreHandler.notaryIdentity)
|
||||||
contractUpgradeService.start()
|
contractUpgradeService.start()
|
||||||
vaultService.start()
|
vaultService.start()
|
||||||
ScheduledActivityObserver.install(vaultService, schedulerService, flowLogicRefFactory)
|
ScheduledActivityObserver.install(vaultService, schedulerService, flowLogicRefFactory)
|
||||||
@ -696,41 +684,15 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateNodeInfo(identity: PartyAndCertificate,
|
private fun updateNodeInfo(publish: Boolean): NodeInfoAndSigned {
|
||||||
identityKeyPair: KeyPair,
|
|
||||||
publish: Boolean): Triple<MutableSet<KeyPair>, NodeInfoAndSigned, PartyAndCertificate?> {
|
|
||||||
val keyPairs = mutableSetOf(identityKeyPair)
|
|
||||||
|
|
||||||
val myNotaryIdentity = configuration.notary?.let {
|
|
||||||
if (it.serviceLegalName != null) {
|
|
||||||
val (notaryIdentity, notaryIdentityKeyPair) = loadNotaryServiceIdentity(it.serviceLegalName)
|
|
||||||
keyPairs += notaryIdentityKeyPair
|
|
||||||
notaryIdentity
|
|
||||||
} else {
|
|
||||||
// The only case where the myNotaryIdentity will be the node's legal identity is for existing single notary services running
|
|
||||||
// an older version. Current single notary services (V4.6+) sign requests using a separate notary service identity so the
|
|
||||||
// notary identity will be different from the node's legal identity.
|
|
||||||
|
|
||||||
// This check is here to ensure that a user does not accidentally/intentionally remove the serviceLegalName configuration
|
|
||||||
// parameter after a notary has been registered. If that was possible then notary would start and sign incoming requests
|
|
||||||
// with the node's legal identity key, corrupting the data.
|
|
||||||
check (!cryptoService.containsKey(DISTRIBUTED_NOTARY_KEY_ALIAS)) {
|
|
||||||
"The notary service key exists in the key store but no notary service legal name has been configured. " +
|
|
||||||
"Either include the relevant 'notary.serviceLegalName' configuration or validate this key is not necessary " +
|
|
||||||
"and remove from the key store."
|
|
||||||
}
|
|
||||||
identity
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val potentialNodeInfo = NodeInfo(
|
val potentialNodeInfo = NodeInfo(
|
||||||
myAddresses(),
|
myAddresses(),
|
||||||
setOf(identity, myNotaryIdentity).filterNotNull(),
|
setOf(keyStoreHandler.nodeIdentity, keyStoreHandler.notaryIdentity).filterNotNull(),
|
||||||
versionInfo.platformVersion,
|
versionInfo.platformVersion,
|
||||||
serial = 0
|
serial = 0
|
||||||
)
|
)
|
||||||
|
|
||||||
val nodeInfoFromDb = getPreviousNodeInfoIfPresent(identity)
|
val nodeInfoFromDb = getPreviousNodeInfoIfPresent(keyStoreHandler.nodeIdentity)
|
||||||
|
|
||||||
val nodeInfo = if (potentialNodeInfo == nodeInfoFromDb?.copy(serial = 0)) {
|
val nodeInfo = if (potentialNodeInfo == nodeInfoFromDb?.copy(serial = 0)) {
|
||||||
// The node info hasn't changed. We use the one from the database to preserve the serial.
|
// The node info hasn't changed. We use the one from the database to preserve the serial.
|
||||||
@ -745,8 +707,8 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
|
|||||||
}
|
}
|
||||||
|
|
||||||
val nodeInfoAndSigned = NodeInfoAndSigned(nodeInfo) { publicKey, serialised ->
|
val nodeInfoAndSigned = NodeInfoAndSigned(nodeInfo) { publicKey, serialised ->
|
||||||
val privateKey = keyPairs.single { it.public == publicKey }.private
|
val alias = keyStoreHandler.signingKeys.single { it.key == publicKey }.alias
|
||||||
DigitalSignature(cryptoService.sign((privateKey as AliasPrivateKey).alias, serialised.bytes))
|
DigitalSignature(cryptoService.sign(alias, serialised.bytes))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write the node-info file even if nothing's changed, just in case the file has been deleted.
|
// Write the node-info file even if nothing's changed, just in case the file has been deleted.
|
||||||
@ -758,7 +720,7 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
|
|||||||
tryPublishNodeInfoAsync(nodeInfoAndSigned.signed, networkMapClient)
|
tryPublishNodeInfoAsync(nodeInfoAndSigned.signed, networkMapClient)
|
||||||
}
|
}
|
||||||
|
|
||||||
return Triple(keyPairs, nodeInfoAndSigned, myNotaryIdentity)
|
return nodeInfoAndSigned
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getPreviousNodeInfoIfPresent(identity: PartyAndCertificate): NodeInfo? {
|
private fun getPreviousNodeInfoIfPresent(identity: PartyAndCertificate): NodeInfo? {
|
||||||
@ -1084,127 +1046,6 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
|
|||||||
myNotaryIdentity: PartyAndCertificate?,
|
myNotaryIdentity: PartyAndCertificate?,
|
||||||
networkParameters: NetworkParameters)
|
networkParameters: NetworkParameters)
|
||||||
|
|
||||||
/**
|
|
||||||
* Loads or generates the node's legal identity and key-pair.
|
|
||||||
* Note that obtainIdentity returns a KeyPair with an [AliasPrivateKey].
|
|
||||||
*/
|
|
||||||
private fun obtainIdentity(): Pair<PartyAndCertificate, KeyPair> {
|
|
||||||
val legalIdentityPrivateKeyAlias = "$NODE_IDENTITY_KEY_ALIAS"
|
|
||||||
|
|
||||||
var signingCertificateStore = configuration.signingCertificateStore.get()
|
|
||||||
if (!cryptoService.containsKey(legalIdentityPrivateKeyAlias) && !signingCertificateStore.contains(legalIdentityPrivateKeyAlias)) {
|
|
||||||
// Directly use the X500 name to public key map, as the identity service requires the node identity to start correctly.
|
|
||||||
database.transaction {
|
|
||||||
val x500Map = PersistentIdentityService.createX500ToKeyMap(cacheFactory)
|
|
||||||
require(configuration.myLegalName !in x500Map) {
|
|
||||||
// There is already a party in the identity store for this node, but the key has been lost. If this node starts up, it will
|
|
||||||
// publish it's new key to the network map, which Corda cannot currently handle. To prevent this, stop the node from starting.
|
|
||||||
"Private key for the node legal identity not found (alias $legalIdentityPrivateKeyAlias) but the corresponding public key" +
|
|
||||||
" for it exists in the database. This suggests the identity for this node has been lost. Shutting down to prevent network map issues."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
log.info("$legalIdentityPrivateKeyAlias not found in key store, generating fresh key!")
|
|
||||||
createAndStoreLegalIdentity(legalIdentityPrivateKeyAlias)
|
|
||||||
signingCertificateStore = configuration.signingCertificateStore.get() // We need to resync after [createAndStoreLegalIdentity].
|
|
||||||
} else {
|
|
||||||
checkAliasMismatch(legalIdentityPrivateKeyAlias, signingCertificateStore)
|
|
||||||
}
|
|
||||||
val x509Cert = signingCertificateStore.query { getCertificate(legalIdentityPrivateKeyAlias) }
|
|
||||||
|
|
||||||
// TODO: Use configuration to indicate composite key should be used instead of public key for the identity.
|
|
||||||
val certificates: List<X509Certificate> = signingCertificateStore.query { getCertificateChain(legalIdentityPrivateKeyAlias) }
|
|
||||||
check(certificates.first() == x509Cert) {
|
|
||||||
"Certificates from key store do not line up!"
|
|
||||||
}
|
|
||||||
|
|
||||||
val subject = CordaX500Name.build(certificates.first().subjectX500Principal)
|
|
||||||
val legalName = configuration.myLegalName
|
|
||||||
if (subject != legalName) {
|
|
||||||
throw ConfigurationException("The configured legalName '$legalName' doesn't match what's in the key store: $subject")
|
|
||||||
}
|
|
||||||
|
|
||||||
return getPartyAndCertificatePlusAliasKeyPair(certificates, legalIdentityPrivateKeyAlias)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if a key alias exists only in one of the cryptoService and certSigningStore.
|
|
||||||
private fun checkAliasMismatch(alias: String, certificateStore: CertificateStore) {
|
|
||||||
if (cryptoService.containsKey(alias) != certificateStore.contains(alias)) {
|
|
||||||
val keyExistsIn: String = if (cryptoService.containsKey(alias)) "CryptoService" else "signingCertificateStore"
|
|
||||||
throw IllegalStateException("CryptoService and signingCertificateStore are not aligned, the entry for key-alias: $alias is only found in $keyExistsIn")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Loads notary service identity. In the case of the experimental RAFT and BFT notary clusters, this loads the pre-generated
|
|
||||||
* cluster identity that all worker nodes share. In the case of a simple single notary, this loads the notary service identity
|
|
||||||
* that is generated during initial registration and is used to sign notarisation requests.
|
|
||||||
* */
|
|
||||||
private fun loadNotaryServiceIdentity(serviceLegalName: CordaX500Name): Pair<PartyAndCertificate, KeyPair> {
|
|
||||||
val privateKeyAlias = "$DISTRIBUTED_NOTARY_KEY_ALIAS"
|
|
||||||
val compositeKeyAlias = "$DISTRIBUTED_NOTARY_COMPOSITE_KEY_ALIAS"
|
|
||||||
|
|
||||||
val signingCertificateStore = configuration.signingCertificateStore.get()
|
|
||||||
val privateKeyAliasCertChain = try {
|
|
||||||
signingCertificateStore.query { getCertificateChain(privateKeyAlias) }
|
|
||||||
} catch (e: Exception) {
|
|
||||||
throw IllegalStateException("Certificate-chain for $privateKeyAlias cannot be found", e)
|
|
||||||
}
|
|
||||||
// A composite key is only required for BFT notaries.
|
|
||||||
val certificates = if (cryptoService.containsKey(compositeKeyAlias) && signingCertificateStore.contains(compositeKeyAlias)) {
|
|
||||||
val certificate = signingCertificateStore[compositeKeyAlias]
|
|
||||||
// We have to create the certificate chain for the composite key manually, this is because we don't have a keystore
|
|
||||||
// provider that understand compositeKey-privateKey combo. The cert chain is created using the composite key certificate +
|
|
||||||
// the tail of the private key certificates, as they are both signed by the same certificate chain.
|
|
||||||
listOf(certificate) + privateKeyAliasCertChain.drop(1)
|
|
||||||
} else {
|
|
||||||
checkAliasMismatch(compositeKeyAlias, signingCertificateStore)
|
|
||||||
// If [compositeKeyAlias] does not exist, we assume the notary is CFT, and each cluster member shares the same notary key pair.
|
|
||||||
privateKeyAliasCertChain
|
|
||||||
}
|
|
||||||
|
|
||||||
val subject = CordaX500Name.build(certificates.first().subjectX500Principal)
|
|
||||||
if (subject != serviceLegalName) {
|
|
||||||
throw ConfigurationException("The name of the notary service '$serviceLegalName' doesn't " +
|
|
||||||
"match what's in the key store: $subject. You might need to adjust the configuration of `notary.serviceLegalName`.")
|
|
||||||
}
|
|
||||||
return getPartyAndCertificatePlusAliasKeyPair(certificates, privateKeyAlias)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Method to create a Pair<PartyAndCertificate, KeyPair>, where KeyPair uses an AliasPrivateKey.
|
|
||||||
private fun getPartyAndCertificatePlusAliasKeyPair(certificates: List<X509Certificate>, privateKeyAlias: String): Pair<PartyAndCertificate, KeyPair> {
|
|
||||||
val certPath = X509Utilities.buildCertPath(certificates)
|
|
||||||
val keyPair = KeyPair(cryptoService.getPublicKey(privateKeyAlias), AliasPrivateKey(privateKeyAlias))
|
|
||||||
return Pair(PartyAndCertificate(certPath), keyPair)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun createAndStoreLegalIdentity(alias: String): PartyAndCertificate {
|
|
||||||
val legalIdentityPublicKey = generateKeyPair(alias)
|
|
||||||
val signingCertificateStore = configuration.signingCertificateStore.get()
|
|
||||||
|
|
||||||
val nodeCaCertPath = signingCertificateStore.value.getCertificateChain(X509Utilities.CORDA_CLIENT_CA)
|
|
||||||
val nodeCaCert = nodeCaCertPath[0] // This should be the same with signingCertificateStore[alias].
|
|
||||||
|
|
||||||
val identityCert = X509Utilities.createCertificate(
|
|
||||||
CertificateType.LEGAL_IDENTITY,
|
|
||||||
nodeCaCert.subjectX500Principal,
|
|
||||||
nodeCaCert.publicKey,
|
|
||||||
cryptoService.getSigner(X509Utilities.CORDA_CLIENT_CA),
|
|
||||||
nodeCaCert.subjectX500Principal,
|
|
||||||
legalIdentityPublicKey,
|
|
||||||
// TODO this might be smaller than DEFAULT_VALIDITY_WINDOW, shall we strictly apply DEFAULT_VALIDITY_WINDOW?
|
|
||||||
X509Utilities.getCertificateValidityWindow(
|
|
||||||
DEFAULT_VALIDITY_WINDOW.first,
|
|
||||||
DEFAULT_VALIDITY_WINDOW.second,
|
|
||||||
nodeCaCert)
|
|
||||||
)
|
|
||||||
|
|
||||||
val identityCertPath = listOf(identityCert) + nodeCaCertPath
|
|
||||||
signingCertificateStore.setCertPathOnly(alias, identityCertPath)
|
|
||||||
return PartyAndCertificate(X509Utilities.buildCertPath(identityCertPath))
|
|
||||||
}
|
|
||||||
|
|
||||||
protected open fun generateKeyPair(alias: String) = cryptoService.generateKeyPair(alias, cryptoService.defaultIdentitySignatureScheme())
|
|
||||||
|
|
||||||
protected open fun makeVaultService(keyManagementService: KeyManagementService,
|
protected open fun makeVaultService(keyManagementService: KeyManagementService,
|
||||||
services: ServicesForResolution,
|
services: ServicesForResolution,
|
||||||
database: CordaPersistence,
|
database: CordaPersistence,
|
||||||
|
230
node/src/main/kotlin/net/corda/node/internal/KeyStoreHandler.kt
Normal file
230
node/src/main/kotlin/net/corda/node/internal/KeyStoreHandler.kt
Normal file
@ -0,0 +1,230 @@
|
|||||||
|
package net.corda.node.internal
|
||||||
|
|
||||||
|
import net.corda.core.crypto.toStringShort
|
||||||
|
import net.corda.core.identity.CordaX500Name
|
||||||
|
import net.corda.core.identity.PartyAndCertificate
|
||||||
|
import net.corda.core.internal.uncheckedCast
|
||||||
|
import net.corda.core.node.services.KeyManagementService
|
||||||
|
import net.corda.core.utilities.contextLogger
|
||||||
|
import net.corda.node.services.config.NodeConfiguration
|
||||||
|
import net.corda.node.services.config.configureWithDevSSLCertificate
|
||||||
|
import net.corda.nodeapi.internal.config.CertificateStore
|
||||||
|
import net.corda.nodeapi.internal.crypto.X509Utilities
|
||||||
|
import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_CLIENT_TLS
|
||||||
|
import net.corda.nodeapi.internal.crypto.X509Utilities.DISTRIBUTED_NOTARY_COMPOSITE_KEY_ALIAS
|
||||||
|
import net.corda.nodeapi.internal.crypto.X509Utilities.DISTRIBUTED_NOTARY_KEY_ALIAS
|
||||||
|
import net.corda.nodeapi.internal.crypto.X509Utilities.NODE_IDENTITY_KEY_ALIAS
|
||||||
|
import net.corda.nodeapi.internal.crypto.checkValidity
|
||||||
|
import net.corda.nodeapi.internal.cryptoservice.CryptoService
|
||||||
|
import net.corda.nodeapi.internal.cryptoservice.bouncycastle.BCCryptoService
|
||||||
|
import java.io.IOException
|
||||||
|
import java.math.BigInteger
|
||||||
|
import java.nio.file.NoSuchFileException
|
||||||
|
import java.security.GeneralSecurityException
|
||||||
|
import java.security.PublicKey
|
||||||
|
import java.security.cert.X509Certificate
|
||||||
|
|
||||||
|
data class KeyAndAlias(val key: PublicKey, val alias: String)
|
||||||
|
|
||||||
|
class KeyStoreHandler(private val configuration: NodeConfiguration, private val cryptoService: CryptoService) {
|
||||||
|
companion object {
|
||||||
|
private val log = contextLogger()
|
||||||
|
}
|
||||||
|
|
||||||
|
private lateinit var _nodeIdentity: PartyAndCertificate
|
||||||
|
val nodeIdentity: PartyAndCertificate get() = _nodeIdentity
|
||||||
|
|
||||||
|
private var _notaryIdentity: PartyAndCertificate? = null
|
||||||
|
val notaryIdentity: PartyAndCertificate? get() = _notaryIdentity
|
||||||
|
|
||||||
|
private val _signingKeys: MutableSet<KeyAndAlias> = mutableSetOf()
|
||||||
|
val signingKeys: Set<KeyAndAlias> get() = _signingKeys.toSet()
|
||||||
|
|
||||||
|
private lateinit var trustRoot: X509Certificate
|
||||||
|
|
||||||
|
private lateinit var nodeKeyStore: CertificateStore
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize key-stores and load identities.
|
||||||
|
* @param devModeKeyEntropy entropy for legal identity key derivation
|
||||||
|
* @return trust root certificate
|
||||||
|
*/
|
||||||
|
fun init(devModeKeyEntropy: BigInteger? = null): X509Certificate {
|
||||||
|
if (configuration.devMode) {
|
||||||
|
configuration.configureWithDevSSLCertificate(cryptoService, devModeKeyEntropy)
|
||||||
|
// configureWithDevSSLCertificate is a devMode process that writes directly to keystore files, so
|
||||||
|
// we should re-synchronise BCCryptoService with the updated keystore file.
|
||||||
|
if (cryptoService is BCCryptoService) {
|
||||||
|
cryptoService.resyncKeystore()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val certStores = getCertificateStores()
|
||||||
|
trustRoot = validateKeyStores(certStores)
|
||||||
|
nodeKeyStore = certStores.nodeKeyStore
|
||||||
|
loadIdentities()
|
||||||
|
return trustRoot
|
||||||
|
}
|
||||||
|
|
||||||
|
private data class AllCertificateStores(val trustStore: CertificateStore,
|
||||||
|
val sslKeyStore: CertificateStore,
|
||||||
|
val nodeKeyStore: CertificateStore)
|
||||||
|
|
||||||
|
private fun getCertificateStores(): AllCertificateStores {
|
||||||
|
try {
|
||||||
|
val sslKeyStore = configuration.p2pSslOptions.keyStore.get()
|
||||||
|
val nodeKeyStore = configuration.signingCertificateStore.get()
|
||||||
|
val trustStore = configuration.p2pSslOptions.trustStore.get()
|
||||||
|
return AllCertificateStores(trustStore, sslKeyStore, nodeKeyStore)
|
||||||
|
} catch (e: IOException) {
|
||||||
|
when {
|
||||||
|
e is NoSuchFileException -> throw IllegalArgumentException(
|
||||||
|
"One or more keyStores (identity or TLS) or trustStore not found. " +
|
||||||
|
"Please either copy your existing keys and certificates from another node, " +
|
||||||
|
"or if you don't have one yet, fill out the config file and run corda.jar initial-registration.", e)
|
||||||
|
e.cause is GeneralSecurityException -> throw IllegalArgumentException(
|
||||||
|
"At least one of the keystores or truststore passwords does not match configuration.", e)
|
||||||
|
else -> throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun validateKeyStores(certStores: AllCertificateStores): X509Certificate {
|
||||||
|
// Check that trustStore contains the correct key-alias entry.
|
||||||
|
require(X509Utilities.CORDA_ROOT_CA in certStores.trustStore) {
|
||||||
|
"Alias for trustRoot key not found. Please ensure you have an updated trustStore file."
|
||||||
|
}
|
||||||
|
val trustRoot = certStores.trustStore[X509Utilities.CORDA_ROOT_CA]
|
||||||
|
|
||||||
|
certStores.sslKeyStore.let {
|
||||||
|
val tlsKeyAlias = CORDA_CLIENT_TLS
|
||||||
|
|
||||||
|
// Check that TLS keyStore contains the correct key-alias entry.
|
||||||
|
require(tlsKeyAlias in it) {
|
||||||
|
"Alias for TLS key not found. Please ensure you have an updated TLS keyStore file."
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check TLS certificate validity and print warning for expiry within next 30 days.
|
||||||
|
it[tlsKeyAlias].checkValidity({
|
||||||
|
"TLS certificate for alias '$tlsKeyAlias' is expired."
|
||||||
|
}, { daysToExpiry ->
|
||||||
|
log.warn("TLS certificate for alias '$tlsKeyAlias' will expire in $daysToExpiry day(s).")
|
||||||
|
})
|
||||||
|
|
||||||
|
// Check TLS cert path chains to the trusted root.
|
||||||
|
val sslCertChainRoot = it.query { getCertificateChain(tlsKeyAlias) }.last()
|
||||||
|
require(sslCertChainRoot == trustRoot) { "TLS certificate must chain to the trusted root." }
|
||||||
|
}
|
||||||
|
|
||||||
|
return trustRoot
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads node legal identity and notary service identity.
|
||||||
|
*/
|
||||||
|
private fun loadIdentities() {
|
||||||
|
val identityKeyAlias = NODE_IDENTITY_KEY_ALIAS
|
||||||
|
|
||||||
|
_nodeIdentity = loadIdentity(identityKeyAlias, configuration.myLegalName)
|
||||||
|
_notaryIdentity = configuration.notary?.let {
|
||||||
|
loadNotaryIdentity(it.serviceLegalName, _nodeIdentity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load key from CryptoService, so it can be used by KeyManagementService.
|
||||||
|
*/
|
||||||
|
private fun loadKeyFromCryptoService(alias: String) {
|
||||||
|
check(cryptoService.containsKey(alias)) {
|
||||||
|
"Key for node identity alias '$alias' not found in CryptoService."
|
||||||
|
}
|
||||||
|
val key = cryptoService.getPublicKey(alias)!!
|
||||||
|
log.info("Loaded node identity key: ${key.toStringShort()}, alias: $alias")
|
||||||
|
_signingKeys.add(KeyAndAlias(key, alias))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the node's legal identity (or notary's service identity) certificate, public key and alias.
|
||||||
|
*
|
||||||
|
* If identity certificate has been renewed, the result will also contain previous public keys and aliases,
|
||||||
|
* so they can still be used by [KeyManagementService] for signing.
|
||||||
|
*/
|
||||||
|
private fun loadIdentity(alias: String, legalName: CordaX500Name): PartyAndCertificate {
|
||||||
|
require(alias in nodeKeyStore) {
|
||||||
|
"Alias '$alias' for node identity key is not in the keyStore file."
|
||||||
|
}
|
||||||
|
loadKeyFromCryptoService(alias)
|
||||||
|
|
||||||
|
val certificate = nodeKeyStore.query { getCertificate(alias) }
|
||||||
|
val certificates = nodeKeyStore.query { getCertificateChain(alias) }
|
||||||
|
check(certificates.first() == certificate) {
|
||||||
|
"Certificates from key store do not line up!"
|
||||||
|
}
|
||||||
|
check(certificates.last() == trustRoot) {
|
||||||
|
"Certificate for node identity must chain to the trusted root."
|
||||||
|
}
|
||||||
|
|
||||||
|
val subject = CordaX500Name.build(certificates.first().subjectX500Principal)
|
||||||
|
check(subject == legalName) {
|
||||||
|
"The configured legalName '$legalName' doesn't match what's in the key store: $subject"
|
||||||
|
}
|
||||||
|
|
||||||
|
val identity = PartyAndCertificate(X509Utilities.buildCertPath(certificates))
|
||||||
|
X509Utilities.validateCertPath(trustRoot, identity.certPath)
|
||||||
|
return identity
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads notary service identity. In the case of the experimental RAFT and BFT notary clusters, this loads the pre-generated
|
||||||
|
* cluster identity that all worker nodes share. In the case of a simple single notary, this loads the notary service identity
|
||||||
|
* that is generated during initial registration and is used to sign notarisation requests.
|
||||||
|
**/
|
||||||
|
private fun loadNotaryIdentity(serviceLegalName: CordaX500Name?, nodeIdentity: PartyAndCertificate): PartyAndCertificate {
|
||||||
|
val notaryKeyAlias = DISTRIBUTED_NOTARY_KEY_ALIAS
|
||||||
|
val notaryCompositeKeyAlias = DISTRIBUTED_NOTARY_COMPOSITE_KEY_ALIAS
|
||||||
|
|
||||||
|
if (serviceLegalName == null) {
|
||||||
|
// The only case where the notaryIdentity will be the node's legal identity is for existing single notary services running
|
||||||
|
// an older version. Current single notary services (V4.6+) sign requests using a separate notary service identity so the
|
||||||
|
// notary identity will be different from the node's legal identity.
|
||||||
|
|
||||||
|
// This check is here to ensure that a user does not accidentally/intentionally remove the serviceLegalName configuration
|
||||||
|
// parameter after a notary has been registered. If that was possible then notary would start and sign incoming requests
|
||||||
|
// with the node's legal identity key, corrupting the data.
|
||||||
|
check(!nodeKeyStore.contains(notaryKeyAlias)) {
|
||||||
|
"The notary service key exists in the key store but no notary service legal name has been configured. " +
|
||||||
|
"Either include the relevant 'notary.serviceLegalName' configuration or validate this key is not necessary " +
|
||||||
|
"and remove from the key store."
|
||||||
|
}
|
||||||
|
return nodeIdentity
|
||||||
|
}
|
||||||
|
|
||||||
|
// First load notary service identity that is generated during initial registration, then lookup for a composite identity.
|
||||||
|
// If alias for composite key does not exist, we assume the notary is CFT, and each cluster member shares the same notary key pair.
|
||||||
|
val serviceIdentity = loadIdentity(notaryKeyAlias, serviceLegalName)
|
||||||
|
if (notaryCompositeKeyAlias in nodeKeyStore) {
|
||||||
|
return loadCompositeIdentity(notaryCompositeKeyAlias, serviceLegalName, serviceIdentity)
|
||||||
|
}
|
||||||
|
return serviceIdentity
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads composite identity certificate for the provided [alias]. Composite identity can be stored as a certificate-only entry
|
||||||
|
* without associated signing key. Certificate chain is copied from the [baseIdentity].
|
||||||
|
**/
|
||||||
|
private fun loadCompositeIdentity(alias: String, legalName: CordaX500Name, baseIdentity: PartyAndCertificate): PartyAndCertificate {
|
||||||
|
val certificate = nodeKeyStore.query { getCertificate(alias) }
|
||||||
|
val subject = CordaX500Name.build(certificate.subjectX500Principal)
|
||||||
|
check(subject == legalName) {
|
||||||
|
"The configured legalName '$legalName' doesn't match what's in the key store: $subject"
|
||||||
|
}
|
||||||
|
|
||||||
|
// We have to create the certificate chain for the composite key manually, this is because we don't have a keystore
|
||||||
|
// provider that understand compositeKey-privateKey combo. The cert chain is created using the composite key certificate +
|
||||||
|
// the tail of the private key certificates, as they are both signed by the same certificate chain.
|
||||||
|
val certificates: List<X509Certificate> = uncheckedCast(listOf(certificate) + baseIdentity.certPath.certificates.drop(1))
|
||||||
|
|
||||||
|
val identity = PartyAndCertificate(X509Utilities.buildCertPath(certificates))
|
||||||
|
X509Utilities.validateCertPath(trustRoot, identity.certPath)
|
||||||
|
return identity
|
||||||
|
}
|
||||||
|
}
|
@ -1,76 +0,0 @@
|
|||||||
package net.corda.node.internal
|
|
||||||
|
|
||||||
import net.corda.core.utilities.loggerFor
|
|
||||||
import net.corda.node.services.config.NodeConfiguration
|
|
||||||
import net.corda.node.services.config.configureWithDevSSLCertificate
|
|
||||||
import net.corda.nodeapi.internal.config.CertificateStore
|
|
||||||
import net.corda.nodeapi.internal.crypto.X509Utilities
|
|
||||||
import net.corda.nodeapi.internal.cryptoservice.CryptoService
|
|
||||||
import net.corda.nodeapi.internal.cryptoservice.bouncycastle.BCCryptoService
|
|
||||||
import java.io.IOException
|
|
||||||
import java.security.KeyStoreException
|
|
||||||
import java.security.cert.X509Certificate
|
|
||||||
|
|
||||||
private data class AllCertificateStores(val trustStore: CertificateStore, val sslKeyStore: CertificateStore, val identitiesKeyStore: CertificateStore)
|
|
||||||
|
|
||||||
|
|
||||||
internal fun NodeConfiguration.initKeyStores(cryptoService: CryptoService): X509Certificate {
|
|
||||||
if (devMode) {
|
|
||||||
configureWithDevSSLCertificate(cryptoService)
|
|
||||||
// configureWithDevSSLCertificate is a devMode process that writes directly to keystore files, so
|
|
||||||
// we should re-synchronise BCCryptoService with the updated keystore file.
|
|
||||||
if (cryptoService is BCCryptoService) {
|
|
||||||
cryptoService.resyncKeystore()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return validateKeyStores()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun NodeConfiguration.validateKeyStores(): X509Certificate {
|
|
||||||
// Step 1. Check trustStore, sslKeyStore and identitiesKeyStore exist.
|
|
||||||
val certStores = try {
|
|
||||||
requireNotNull(getCertificateStores()) {
|
|
||||||
"One or more keyStores (identity or TLS) or trustStore not found. " +
|
|
||||||
"Please either copy your existing keys and certificates from another node, " +
|
|
||||||
"or if you don't have one yet, fill out the config file and run corda.jar initial-registration."
|
|
||||||
}
|
|
||||||
} catch (e: KeyStoreException) {
|
|
||||||
throw IllegalArgumentException("At least one of the keystores or truststore passwords does not match configuration.")
|
|
||||||
}
|
|
||||||
// Step 2. Check that trustStore contains the correct key-alias entry.
|
|
||||||
require(X509Utilities.CORDA_ROOT_CA in certStores.trustStore) {
|
|
||||||
"Alias for trustRoot key not found. Please ensure you have an updated trustStore file."
|
|
||||||
}
|
|
||||||
// Step 3. Check that tls keyStore contains the correct key-alias entry.
|
|
||||||
require(X509Utilities.CORDA_CLIENT_TLS in certStores.sslKeyStore) {
|
|
||||||
"Alias for TLS key not found. Please ensure you have an updated TLS keyStore file."
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 4. Check that identity keyStores contain the correct key-alias entry for Node CA.
|
|
||||||
require(X509Utilities.CORDA_CLIENT_CA in certStores.identitiesKeyStore) {
|
|
||||||
"Alias for Node CA key not found. Please ensure you have an updated identity keyStore file."
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 5. Check all cert paths chain to the trusted root.
|
|
||||||
val trustRoot = certStores.trustStore[X509Utilities.CORDA_ROOT_CA]
|
|
||||||
val sslCertChainRoot = certStores.sslKeyStore.query { getCertificateChain(X509Utilities.CORDA_CLIENT_TLS) }.last()
|
|
||||||
val nodeCaCertChainRoot = certStores.identitiesKeyStore.query { getCertificateChain(X509Utilities.CORDA_CLIENT_CA) }.last()
|
|
||||||
|
|
||||||
require(sslCertChainRoot == trustRoot) { "TLS certificate must chain to the trusted root." }
|
|
||||||
require(nodeCaCertChainRoot == trustRoot) { "Client CA certificate must chain to the trusted root." }
|
|
||||||
|
|
||||||
return trustRoot
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun NodeConfiguration.getCertificateStores(): AllCertificateStores? {
|
|
||||||
return try {
|
|
||||||
// The following will throw IOException if key file not found or KeyStoreException if keystore password is incorrect.
|
|
||||||
val sslKeyStore = p2pSslOptions.keyStore.get()
|
|
||||||
val signingCertificateStore = signingCertificateStore.get()
|
|
||||||
val trustStore = p2pSslOptions.trustStore.get()
|
|
||||||
AllCertificateStores(trustStore, sslKeyStore, signingCertificateStore)
|
|
||||||
} catch (e: IOException) {
|
|
||||||
loggerFor<NodeConfiguration>().error("IO exception while trying to validate keystores and truststore", e)
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
@ -76,10 +76,12 @@ class InitialRegistration(val baseDirectory: Path, private val networkRootTrustS
|
|||||||
HTTPNetworkRegistrationService(
|
HTTPNetworkRegistrationService(
|
||||||
requireNotNull(conf.networkServices),
|
requireNotNull(conf.networkServices),
|
||||||
versionInfo),
|
versionInfo),
|
||||||
nodeRegistration).generateKeysAndRegister()
|
nodeRegistration).apply {
|
||||||
|
generateKeysAndRegister()
|
||||||
|
generateNodeIdentity()
|
||||||
|
}
|
||||||
|
|
||||||
// Minimal changes to make registration tool create node identity.
|
// Minimal changes to make registration tool create node identity.
|
||||||
// TODO: Move node identity generation logic from node to registration helper.
|
|
||||||
val node = startup.createNode(conf, versionInfo)
|
val node = startup.createNode(conf, versionInfo)
|
||||||
if(!skipSchemaMigration) {
|
if(!skipSchemaMigration) {
|
||||||
node.runDatabaseMigrationScripts(updateCoreSchemas = true, updateAppSchemas = true, updateAppSchemasWithCheckpoints = false)
|
node.runDatabaseMigrationScripts(updateCoreSchemas = true, updateAppSchemas = true, updateAppSchemasWithCheckpoints = false)
|
||||||
|
@ -5,6 +5,7 @@ import com.typesafe.config.ConfigFactory
|
|||||||
import com.typesafe.config.ConfigParseOptions
|
import com.typesafe.config.ConfigParseOptions
|
||||||
import net.corda.cliutils.CordaSystemUtils
|
import net.corda.cliutils.CordaSystemUtils
|
||||||
import net.corda.common.configuration.parsing.internal.Configuration
|
import net.corda.common.configuration.parsing.internal.Configuration
|
||||||
|
import net.corda.core.crypto.Crypto
|
||||||
import net.corda.core.identity.CordaX500Name
|
import net.corda.core.identity.CordaX500Name
|
||||||
import net.corda.core.internal.VisibleForTesting
|
import net.corda.core.internal.VisibleForTesting
|
||||||
import net.corda.core.internal.createDirectories
|
import net.corda.core.internal.createDirectories
|
||||||
@ -17,12 +18,15 @@ import net.corda.nodeapi.internal.config.FileBasedCertificateStoreSupplier
|
|||||||
import net.corda.nodeapi.internal.config.MutualSslConfiguration
|
import net.corda.nodeapi.internal.config.MutualSslConfiguration
|
||||||
import net.corda.nodeapi.internal.config.toProperties
|
import net.corda.nodeapi.internal.config.toProperties
|
||||||
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.cryptoservice.CryptoService
|
import net.corda.nodeapi.internal.cryptoservice.CryptoService
|
||||||
import net.corda.nodeapi.internal.cryptoservice.bouncycastle.BCCryptoService
|
import net.corda.nodeapi.internal.cryptoservice.bouncycastle.BCCryptoService
|
||||||
import net.corda.nodeapi.internal.installDevNodeCaCertPath
|
import net.corda.nodeapi.internal.installDevNodeCaCertPath
|
||||||
import net.corda.nodeapi.internal.loadDevCaTrustStore
|
import net.corda.nodeapi.internal.loadDevCaTrustStore
|
||||||
import net.corda.nodeapi.internal.registerDevP2pCertificates
|
import net.corda.nodeapi.internal.registerDevP2pCertificates
|
||||||
|
import net.corda.nodeapi.internal.storeLegalIdentity
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
|
import java.math.BigInteger
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
|
|
||||||
@ -165,10 +169,16 @@ object ConfigHelper {
|
|||||||
* the CA certs in Node resources. Then provision KeyStores into certificates folder under node path.
|
* the CA certs in Node resources. Then provision KeyStores into certificates folder under node path.
|
||||||
*/
|
*/
|
||||||
// TODO Move this to KeyStoreConfigHelpers.
|
// TODO Move this to KeyStoreConfigHelpers.
|
||||||
fun NodeConfiguration.configureWithDevSSLCertificate(cryptoService: CryptoService? = null) = p2pSslOptions.configureDevKeyAndTrustStores(myLegalName, signingCertificateStore, certificatesDirectory, cryptoService)
|
fun NodeConfiguration.configureWithDevSSLCertificate(cryptoService: CryptoService? = null, entropy: BigInteger? = null) =
|
||||||
|
p2pSslOptions.configureDevKeyAndTrustStores(myLegalName, signingCertificateStore, certificatesDirectory, cryptoService, entropy)
|
||||||
|
|
||||||
// TODO Move this to KeyStoreConfigHelpers.
|
// TODO Move this to KeyStoreConfigHelpers.
|
||||||
fun MutualSslConfiguration.configureDevKeyAndTrustStores(myLegalName: CordaX500Name, signingCertificateStore: FileBasedCertificateStoreSupplier, certificatesDirectory: Path, cryptoService: CryptoService? = null) {
|
@Suppress("ComplexMethod")
|
||||||
|
fun MutualSslConfiguration.configureDevKeyAndTrustStores(myLegalName: CordaX500Name,
|
||||||
|
signingCertificateStore: FileBasedCertificateStoreSupplier,
|
||||||
|
certificatesDirectory: Path,
|
||||||
|
cryptoService: CryptoService? = null,
|
||||||
|
entropy: BigInteger? = null) {
|
||||||
val specifiedTrustStore = trustStore.getOptional()
|
val specifiedTrustStore = trustStore.getOptional()
|
||||||
|
|
||||||
val specifiedKeyStore = keyStore.getOptional()
|
val specifiedKeyStore = keyStore.getOptional()
|
||||||
@ -187,7 +197,15 @@ fun MutualSslConfiguration.configureDevKeyAndTrustStores(myLegalName: CordaX500N
|
|||||||
when (cryptoService) {
|
when (cryptoService) {
|
||||||
is BCCryptoService, null -> {
|
is BCCryptoService, null -> {
|
||||||
val signingKeyStore = FileBasedCertificateStoreSupplier(signingCertificateStore.path, signingCertificateStore.storePassword, signingCertificateStore.entryPassword).get(true)
|
val signingKeyStore = FileBasedCertificateStoreSupplier(signingCertificateStore.path, signingCertificateStore.storePassword, signingCertificateStore.entryPassword).get(true)
|
||||||
.also { it.installDevNodeCaCertPath(myLegalName) }
|
.also {
|
||||||
|
it.installDevNodeCaCertPath(myLegalName)
|
||||||
|
val keyPair = if (entropy != null) {
|
||||||
|
Crypto.deriveKeyPairFromEntropy(Crypto.DEFAULT_SIGNATURE_SCHEME, entropy)
|
||||||
|
} else {
|
||||||
|
Crypto.generateKeyPair()
|
||||||
|
}
|
||||||
|
it.storeLegalIdentity(X509Utilities.NODE_IDENTITY_KEY_ALIAS, keyPair)
|
||||||
|
}
|
||||||
|
|
||||||
// Move distributed service composite key (generated by IdentityGenerator.generateToDisk) to keystore if exists.
|
// Move distributed service composite key (generated by IdentityGenerator.generateToDisk) to keystore if exists.
|
||||||
val distributedServiceKeystore = certificatesDirectory / "distributedService.jks"
|
val distributedServiceKeystore = certificatesDirectory / "distributedService.jks"
|
||||||
|
@ -215,13 +215,15 @@ class PersistentIdentityService(cacheFactory: NamedCacheFactory) : SingletonSeri
|
|||||||
|
|
||||||
fun start(
|
fun start(
|
||||||
trustRoot: X509Certificate,
|
trustRoot: X509Certificate,
|
||||||
caCertificates: List<X509Certificate> = emptyList(),
|
ourIdentity: PartyAndCertificate,
|
||||||
notaryIdentities: List<Party> = emptyList(),
|
notaryIdentities: List<Party> = emptyList(),
|
||||||
pkToIdCache: WritablePublicKeyToOwningIdentityCache
|
pkToIdCache: WritablePublicKeyToOwningIdentityCache
|
||||||
) {
|
) {
|
||||||
_trustRoot = trustRoot
|
_trustRoot = trustRoot
|
||||||
_trustAnchor = TrustAnchor(trustRoot, null)
|
_trustAnchor = TrustAnchor(trustRoot, null)
|
||||||
_caCertStore = CertStore.getInstance("Collection", CollectionCertStoreParameters(caCertificates.toSet() + trustRoot))
|
// Extract Node CA certificate from node identity certificate path
|
||||||
|
val certificates = setOf(ourIdentity.certificate, ourIdentity.certPath.certificates[1], trustRoot)
|
||||||
|
_caCertStore = CertStore.getInstance("Collection", CollectionCertStoreParameters(certificates))
|
||||||
_pkToIdCache = pkToIdCache
|
_pkToIdCache = pkToIdCache
|
||||||
notaryIdentityCache.addAll(notaryIdentities)
|
notaryIdentityCache.addAll(notaryIdentities)
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package net.corda.node.services.keys
|
package net.corda.node.services.keys
|
||||||
|
|
||||||
import net.corda.core.crypto.*
|
import net.corda.core.crypto.*
|
||||||
import net.corda.core.crypto.internal.AliasPrivateKey
|
|
||||||
import net.corda.core.internal.NamedCacheFactory
|
import net.corda.core.internal.NamedCacheFactory
|
||||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||||
import net.corda.core.serialization.serialize
|
import net.corda.core.serialization.serialize
|
||||||
@ -21,7 +20,7 @@ import javax.persistence.*
|
|||||||
import kotlin.collections.LinkedHashSet
|
import kotlin.collections.LinkedHashSet
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A persistent re-implementation of [E2ETestKeyManagementService] to support CryptoService for initial keys and
|
* A persistent implementation of [KeyManagementServiceInternal] to support CryptoService for initial keys and
|
||||||
* database storage for anonymous fresh keys.
|
* database storage for anonymous fresh keys.
|
||||||
*
|
*
|
||||||
* This is not the long-term implementation. See the list of items in the above class.
|
* This is not the long-term implementation. See the list of items in the above class.
|
||||||
@ -74,10 +73,9 @@ class BasicHSMKeyManagementService(
|
|||||||
// A map for anonymous keys.
|
// A map for anonymous keys.
|
||||||
private val keysMap = createKeyMap(cacheFactory)
|
private val keysMap = createKeyMap(cacheFactory)
|
||||||
|
|
||||||
override fun start(initialKeyPairs: Set<KeyPair>) {
|
override fun start(initialKeysAndAliases: Iterable<Pair<PublicKey, String>>) {
|
||||||
initialKeyPairs.forEach {
|
initialKeysAndAliases.forEach {
|
||||||
require(it.private is AliasPrivateKey) { "${this.javaClass.name} supports AliasPrivateKeys only, but ${it.private.algorithm} key was found" }
|
originalKeysMap[Crypto.toSupportedPublicKey(it.first)] = it.second
|
||||||
originalKeysMap[Crypto.toSupportedPublicKey(it.public)] = (it.private as AliasPrivateKey).alias
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,91 +0,0 @@
|
|||||||
package net.corda.node.services.keys
|
|
||||||
|
|
||||||
import net.corda.core.crypto.*
|
|
||||||
import net.corda.core.crypto.internal.AliasPrivateKey
|
|
||||||
import net.corda.core.internal.ThreadBox
|
|
||||||
import net.corda.core.node.services.IdentityService
|
|
||||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
|
||||||
import net.corda.nodeapi.internal.cryptoservice.CryptoService
|
|
||||||
import net.corda.nodeapi.internal.cryptoservice.bouncycastle.BCCryptoService
|
|
||||||
import org.bouncycastle.operator.ContentSigner
|
|
||||||
import java.security.KeyPair
|
|
||||||
import java.security.PrivateKey
|
|
||||||
import java.security.PublicKey
|
|
||||||
import java.util.*
|
|
||||||
import javax.annotation.concurrent.ThreadSafe
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A simple in-memory KMS that doesn't bother saving keys to disk. A real implementation would:
|
|
||||||
*
|
|
||||||
* - Probably be accessed via the network layer as an internal node service i.e. via a message queue, so it can run
|
|
||||||
* on a separate/firewalled service.
|
|
||||||
* - Use the flow framework so requests to fetch keys can be suspended whilst a human signs off on the request.
|
|
||||||
* - Use deterministic key derivation.
|
|
||||||
* - Possibly have some sort of TREZOR-like two-factor authentication ability.
|
|
||||||
*
|
|
||||||
* etc.
|
|
||||||
*/
|
|
||||||
@ThreadSafe
|
|
||||||
class E2ETestKeyManagementService(override val identityService: IdentityService, private val cryptoService: CryptoService? = null) : SingletonSerializeAsToken(), KeyManagementServiceInternal {
|
|
||||||
|
|
||||||
private class InnerState {
|
|
||||||
val keys = HashMap<PublicKey, PrivateKey>()
|
|
||||||
}
|
|
||||||
|
|
||||||
private val mutex = ThreadBox(InnerState())
|
|
||||||
// Accessing this map clones it.
|
|
||||||
override val keys: Set<PublicKey> get() = mutex.locked { keys.keys }
|
|
||||||
// Maintain a map from PublicKey to alias for the initial keys.
|
|
||||||
|
|
||||||
val keyPairs: Set<KeyPair> get() = mutex.locked { keys.map { KeyPair(it.key, it.value) }.toSet() }
|
|
||||||
|
|
||||||
override fun start(initialKeyPairs: Set<KeyPair>) {
|
|
||||||
mutex.locked {
|
|
||||||
for (key in initialKeyPairs) {
|
|
||||||
var privateKey = key.private
|
|
||||||
if (privateKey is AliasPrivateKey && cryptoService is BCCryptoService) {
|
|
||||||
privateKey = cryptoService.certificateStore.query {
|
|
||||||
getPrivateKey((privateKey as AliasPrivateKey).alias, cryptoService.certificateStore.entryPassword)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
keys[key.public] = privateKey
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun freshKeyInternal(externalId: UUID?): PublicKey {
|
|
||||||
if (externalId != null) {
|
|
||||||
throw UnsupportedOperationException("This operation is only supported by persistent key management service variants.")
|
|
||||||
}
|
|
||||||
val keyPair = generateKeyPair()
|
|
||||||
mutex.locked {
|
|
||||||
keys[keyPair.public] = keyPair.private
|
|
||||||
}
|
|
||||||
return keyPair.public
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getSigner(publicKey: PublicKey): ContentSigner = getSigner(getSigningKeyPair(publicKey))
|
|
||||||
|
|
||||||
private fun getSigningKeyPair(publicKey: PublicKey): KeyPair {
|
|
||||||
return mutex.locked {
|
|
||||||
val pk = publicKey.keys.first { keys.containsKey(it) }
|
|
||||||
KeyPair(pk, keys[pk]!!)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun filterMyKeys(candidateKeys: Iterable<PublicKey>): Iterable<PublicKey> {
|
|
||||||
return mutex.locked { candidateKeys.filter { it in this.keys } }
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun sign(bytes: ByteArray, publicKey: PublicKey): DigitalSignature.WithKey {
|
|
||||||
val keyPair = getSigningKeyPair(publicKey)
|
|
||||||
return keyPair.sign(bytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: A full KeyManagementService implementation needs to record activity to the Audit Service and to limit
|
|
||||||
// signing to appropriately authorised contexts and initiating users.
|
|
||||||
override fun sign(signableData: SignableData, publicKey: PublicKey): TransactionSignature {
|
|
||||||
val keyPair = getSigningKeyPair(publicKey)
|
|
||||||
return keyPair.sign(signableData)
|
|
||||||
}
|
|
||||||
}
|
|
@ -4,7 +4,6 @@ import net.corda.core.identity.PartyAndCertificate
|
|||||||
import net.corda.core.node.services.IdentityService
|
import net.corda.core.node.services.IdentityService
|
||||||
import net.corda.core.node.services.KeyManagementService
|
import net.corda.core.node.services.KeyManagementService
|
||||||
import org.bouncycastle.operator.ContentSigner
|
import org.bouncycastle.operator.ContentSigner
|
||||||
import java.security.KeyPair
|
|
||||||
import java.security.PublicKey
|
import java.security.PublicKey
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
@ -12,7 +11,7 @@ interface KeyManagementServiceInternal : KeyManagementService {
|
|||||||
|
|
||||||
val identityService: IdentityService
|
val identityService: IdentityService
|
||||||
|
|
||||||
fun start(initialKeyPairs: Set<KeyPair>)
|
fun start(initialKeysAndAliases: Iterable<Pair<PublicKey, String>>)
|
||||||
|
|
||||||
fun freshKeyInternal(externalId: UUID?): PublicKey
|
fun freshKeyInternal(externalId: UUID?): PublicKey
|
||||||
|
|
||||||
|
@ -17,6 +17,7 @@ import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_CLIENT_CA
|
|||||||
import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_CLIENT_TLS
|
import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_CLIENT_TLS
|
||||||
import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_ROOT_CA
|
import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_ROOT_CA
|
||||||
import net.corda.nodeapi.internal.crypto.X509Utilities.DEFAULT_VALIDITY_WINDOW
|
import net.corda.nodeapi.internal.crypto.X509Utilities.DEFAULT_VALIDITY_WINDOW
|
||||||
|
import net.corda.nodeapi.internal.crypto.x509
|
||||||
import net.corda.nodeapi.internal.cryptoservice.CryptoService
|
import net.corda.nodeapi.internal.cryptoservice.CryptoService
|
||||||
import net.corda.nodeapi.internal.cryptoservice.bouncycastle.BCCryptoService
|
import net.corda.nodeapi.internal.cryptoservice.bouncycastle.BCCryptoService
|
||||||
import org.bouncycastle.asn1.x500.X500Name
|
import org.bouncycastle.asn1.x500.X500Name
|
||||||
@ -155,6 +156,50 @@ open class NetworkRegistrationHelper(
|
|||||||
requestIdStore.deleteIfExists()
|
requestIdStore.deleteIfExists()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun generateNodeIdentity() {
|
||||||
|
certificatesDirectory.safeSymbolicRead().createDirectories()
|
||||||
|
// We need this in case cryptoService and certificateStore share the same KeyStore (for backwards compatibility purposes).
|
||||||
|
// If we didn't, then an update to cryptoService wouldn't be reflected to certificateStore that is already loaded in memory.
|
||||||
|
val certStore: CertificateStore = if (cryptoService is BCCryptoService) cryptoService.certificateStore else certificateStore
|
||||||
|
|
||||||
|
if (!certStore.contains(nodeCaKeyAlias)) {
|
||||||
|
logProgress("Node CA key doesn't exist, program will now terminate...")
|
||||||
|
throw IllegalStateException("Node CA not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
val nodeIdentityAlias = X509Utilities.NODE_IDENTITY_KEY_ALIAS
|
||||||
|
if (certStore.contains(nodeIdentityAlias)) {
|
||||||
|
logProgress("Node identity already exists, Corda node will now terminate...")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
certStore.update {
|
||||||
|
logProgress("Generating node identity certificate.")
|
||||||
|
val nodeIdentityPublicKey = cryptoService.generateKeyPair(nodeIdentityAlias, cryptoService.defaultIdentitySignatureScheme())
|
||||||
|
val nodeCaCertChain = getCertificateChain(nodeCaKeyAlias)
|
||||||
|
val nodeCaCertificate = nodeCaCertChain.first()
|
||||||
|
val validityWindow = X509Utilities.getCertificateValidityWindow(DEFAULT_VALIDITY_WINDOW.first, DEFAULT_VALIDITY_WINDOW.second, nodeCaCertificate)
|
||||||
|
|
||||||
|
val nodeIdentityCert = X509Utilities.createCertificate(
|
||||||
|
CertificateType.LEGAL_IDENTITY,
|
||||||
|
nodeCaCertificate.subjectX500Principal,
|
||||||
|
nodeCaCertificate.x509.publicKey,
|
||||||
|
cryptoService.getSigner(nodeCaKeyAlias),
|
||||||
|
nodeCaCertificate.subjectX500Principal,
|
||||||
|
nodeIdentityPublicKey,
|
||||||
|
validityWindow,
|
||||||
|
crlDistPoint = null,
|
||||||
|
crlIssuer = null)
|
||||||
|
|
||||||
|
logger.info("Generated Node Identity certificate: $nodeIdentityCert")
|
||||||
|
|
||||||
|
val nodeIdentityCertificateChain: List<X509Certificate> = listOf(nodeIdentityCert) + nodeCaCertChain
|
||||||
|
X509Utilities.validateCertificateChain(rootCert, nodeIdentityCertificateChain)
|
||||||
|
certStore.setCertPathOnly(nodeIdentityAlias, nodeIdentityCertificateChain)
|
||||||
|
}
|
||||||
|
logProgress("Node identity private key and certificate chain stored in $nodeIdentityAlias.")
|
||||||
|
}
|
||||||
|
|
||||||
private fun loadOrGenerateKeyPair(keyAlias: String): PublicKey {
|
private fun loadOrGenerateKeyPair(keyAlias: String): PublicKey {
|
||||||
return if (cryptoService.containsKey(keyAlias)) {
|
return if (cryptoService.containsKey(keyAlias)) {
|
||||||
cryptoService.getPublicKey(keyAlias)!!
|
cryptoService.getPublicKey(keyAlias)!!
|
||||||
|
@ -0,0 +1,392 @@
|
|||||||
|
package net.corda.node.internal
|
||||||
|
|
||||||
|
import com.nhaarman.mockito_kotlin.doReturn
|
||||||
|
import com.nhaarman.mockito_kotlin.whenever
|
||||||
|
import net.corda.core.crypto.CompositeKey
|
||||||
|
import net.corda.core.crypto.Crypto
|
||||||
|
import net.corda.core.identity.CordaX500Name
|
||||||
|
import net.corda.core.internal.div
|
||||||
|
import net.corda.coretesting.internal.rigorousMock
|
||||||
|
import net.corda.coretesting.internal.stubs.CertificateStoreStubs
|
||||||
|
import net.corda.node.services.config.NodeConfiguration
|
||||||
|
import net.corda.node.services.config.NotaryConfig
|
||||||
|
import net.corda.node.services.config.configureDevKeyAndTrustStores
|
||||||
|
import net.corda.nodeapi.internal.DEV_CA_KEY_STORE_PASS
|
||||||
|
import net.corda.nodeapi.internal.DEV_INTERMEDIATE_CA
|
||||||
|
import net.corda.nodeapi.internal.DEV_ROOT_CA
|
||||||
|
import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair
|
||||||
|
import net.corda.nodeapi.internal.crypto.CertificateType
|
||||||
|
import net.corda.nodeapi.internal.crypto.X509Utilities
|
||||||
|
import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_CLIENT_CA
|
||||||
|
import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_CLIENT_TLS
|
||||||
|
import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_ROOT_CA
|
||||||
|
import net.corda.nodeapi.internal.crypto.X509Utilities.DISTRIBUTED_NOTARY_COMPOSITE_KEY_ALIAS
|
||||||
|
import net.corda.nodeapi.internal.crypto.X509Utilities.DISTRIBUTED_NOTARY_KEY_ALIAS
|
||||||
|
import net.corda.nodeapi.internal.crypto.X509Utilities.NODE_IDENTITY_KEY_ALIAS
|
||||||
|
import net.corda.nodeapi.internal.cryptoservice.CryptoService
|
||||||
|
import net.corda.nodeapi.internal.cryptoservice.bouncycastle.BCCryptoService
|
||||||
|
import net.corda.testing.core.ALICE_NAME
|
||||||
|
import net.corda.testing.core.BOB_NAME
|
||||||
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
|
import org.assertj.core.api.Assertions.assertThatThrownBy
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Rule
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.rules.TemporaryFolder
|
||||||
|
import java.security.KeyPair
|
||||||
|
import java.security.PublicKey
|
||||||
|
|
||||||
|
class KeyStoreHandlerTest {
|
||||||
|
@Rule
|
||||||
|
@JvmField
|
||||||
|
val tempFolder = TemporaryFolder()
|
||||||
|
|
||||||
|
private val certificateDir get() = tempFolder.root.toPath() / "certificates"
|
||||||
|
|
||||||
|
private val config = rigorousMock<NodeConfiguration>()
|
||||||
|
|
||||||
|
private val keyStore get() = config.signingCertificateStore.get()
|
||||||
|
|
||||||
|
private lateinit var cryptoService: BCCryptoService
|
||||||
|
|
||||||
|
private lateinit var keyStoreHandler: KeyStoreHandler
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun before() {
|
||||||
|
val signingCertificateStore = CertificateStoreStubs.Signing.withCertificatesDirectory(certificateDir)
|
||||||
|
val p2pSslOptions = CertificateStoreStubs.P2P.withCertificatesDirectory(certificateDir)
|
||||||
|
p2pSslOptions.configureDevKeyAndTrustStores(ALICE_NAME, signingCertificateStore, certificateDir)
|
||||||
|
|
||||||
|
config.also {
|
||||||
|
doReturn(false).whenever(it).devMode
|
||||||
|
doReturn(signingCertificateStore).whenever(it).signingCertificateStore
|
||||||
|
doReturn(p2pSslOptions).whenever(it).p2pSslOptions
|
||||||
|
doReturn(ALICE_NAME).whenever(it).myLegalName
|
||||||
|
doReturn(null).whenever(it).notary
|
||||||
|
}
|
||||||
|
cryptoService = BCCryptoService(ALICE_NAME.x500Principal, signingCertificateStore)
|
||||||
|
keyStoreHandler = KeyStoreHandler(config, cryptoService)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(timeout = 300_000)
|
||||||
|
fun `missing node keystore`() {
|
||||||
|
val signingCertificateStore = CertificateStoreStubs.Signing.withCertificatesDirectory(certificateDir,
|
||||||
|
certificateStoreFileName = "invalid.jks")
|
||||||
|
doReturn(signingCertificateStore).whenever(config).signingCertificateStore
|
||||||
|
|
||||||
|
assertThatThrownBy {
|
||||||
|
keyStoreHandler.init()
|
||||||
|
}.hasMessageContaining("One or more keyStores (identity or TLS) or trustStore not found.")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(timeout = 300_000)
|
||||||
|
fun `missing truststore`() {
|
||||||
|
val p2pSslOptions = CertificateStoreStubs.P2P.withCertificatesDirectory(certificateDir, trustStoreFileName = "invalid.jks")
|
||||||
|
doReturn(p2pSslOptions).whenever(config).p2pSslOptions
|
||||||
|
|
||||||
|
assertThatThrownBy {
|
||||||
|
keyStoreHandler.init()
|
||||||
|
}.hasMessageContaining("One or more keyStores (identity or TLS) or trustStore not found.")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(timeout = 300_000)
|
||||||
|
fun `missing TLS keystore`() {
|
||||||
|
val p2pSslOptions = CertificateStoreStubs.P2P.withCertificatesDirectory(certificateDir, keyStoreFileName = "invalid.jks")
|
||||||
|
doReturn(p2pSslOptions).whenever(config).p2pSslOptions
|
||||||
|
|
||||||
|
assertThatThrownBy {
|
||||||
|
keyStoreHandler.init()
|
||||||
|
}.hasMessageContaining("One or more keyStores (identity or TLS) or trustStore not found.")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(timeout = 300_000)
|
||||||
|
fun `invalid node keystore password`() {
|
||||||
|
val signingCertificateStore = CertificateStoreStubs.Signing.withCertificatesDirectory(certificateDir, password = "invalid")
|
||||||
|
doReturn(signingCertificateStore).whenever(config).signingCertificateStore
|
||||||
|
|
||||||
|
assertThatThrownBy {
|
||||||
|
keyStoreHandler.init()
|
||||||
|
}.hasMessageContaining("At least one of the keystores or truststore passwords does not match configuration")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(timeout = 300_000)
|
||||||
|
fun `invalid truststore password`() {
|
||||||
|
val p2pSslOptions = CertificateStoreStubs.P2P.withCertificatesDirectory(certificateDir, trustStorePassword = "invalid")
|
||||||
|
doReturn(p2pSslOptions).whenever(config).p2pSslOptions
|
||||||
|
|
||||||
|
assertThatThrownBy {
|
||||||
|
keyStoreHandler.init()
|
||||||
|
}.hasMessageContaining("At least one of the keystores or truststore passwords does not match configuration")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(timeout = 300_000)
|
||||||
|
fun `invalid TLS keystore password`() {
|
||||||
|
val p2pSslOptions = CertificateStoreStubs.P2P.withCertificatesDirectory(certificateDir, keyStorePassword = "invalid")
|
||||||
|
doReturn(p2pSslOptions).whenever(config).p2pSslOptions
|
||||||
|
|
||||||
|
assertThatThrownBy {
|
||||||
|
keyStoreHandler.init()
|
||||||
|
}.hasMessageContaining("At least one of the keystores or truststore passwords does not match configuration")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(timeout = 300_000)
|
||||||
|
fun `missing trusted root in a truststore`() {
|
||||||
|
config.p2pSslOptions.trustStore.get().update {
|
||||||
|
internal.deleteEntry(CORDA_ROOT_CA)
|
||||||
|
}
|
||||||
|
|
||||||
|
assertThatThrownBy {
|
||||||
|
keyStoreHandler.init()
|
||||||
|
}.hasMessageContaining("Alias for trustRoot key not found. Please ensure you have an updated trustStore file")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(timeout = 300_000)
|
||||||
|
fun `missing TLS alias`() {
|
||||||
|
config.p2pSslOptions.keyStore.get().update {
|
||||||
|
internal.deleteEntry(CORDA_CLIENT_TLS)
|
||||||
|
}
|
||||||
|
|
||||||
|
assertThatThrownBy {
|
||||||
|
keyStoreHandler.init()
|
||||||
|
}.hasMessageContaining("Alias for TLS key not found. Please ensure you have an updated TLS keyStore file")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(timeout = 300_000)
|
||||||
|
fun `load TLS certificate with untrusted root`() {
|
||||||
|
val keyPair = Crypto.generateKeyPair()
|
||||||
|
val tlsKeyPair = Crypto.generateKeyPair()
|
||||||
|
val untrustedRoot = X509Utilities.createSelfSignedCACertificate(ALICE_NAME.x500Principal, keyPair)
|
||||||
|
val tlsCert = X509Utilities.createCertificate(CertificateType.TLS, untrustedRoot, keyPair, ALICE_NAME.x500Principal,
|
||||||
|
tlsKeyPair.public)
|
||||||
|
|
||||||
|
config.p2pSslOptions.keyStore.get().update {
|
||||||
|
setPrivateKey(CORDA_CLIENT_TLS, tlsKeyPair.private, listOf(tlsCert, untrustedRoot), config.p2pSslOptions.keyStore.entryPassword)
|
||||||
|
}
|
||||||
|
|
||||||
|
assertThatThrownBy {
|
||||||
|
keyStoreHandler.init()
|
||||||
|
}.hasMessageContaining("TLS certificate must chain to the trusted root")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(timeout = 300_000)
|
||||||
|
fun `valid trust root is returned`() {
|
||||||
|
val expectedRoot = config.p2pSslOptions.trustStore.get()[CORDA_ROOT_CA]
|
||||||
|
val actualRoot = keyStoreHandler.init()
|
||||||
|
|
||||||
|
assertThat(actualRoot).isEqualTo(expectedRoot)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(timeout = 300_000)
|
||||||
|
fun `keystore creation in dev mode`() {
|
||||||
|
val devCertificateDir = tempFolder.root.toPath() / "certificates-dev"
|
||||||
|
val signingCertificateStore = CertificateStoreStubs.Signing.withCertificatesDirectory(devCertificateDir)
|
||||||
|
val p2pSslOptions = CertificateStoreStubs.P2P.withCertificatesDirectory(devCertificateDir)
|
||||||
|
val devCryptoService = BCCryptoService(config.myLegalName.x500Principal, signingCertificateStore)
|
||||||
|
|
||||||
|
doReturn(true).whenever(config).devMode
|
||||||
|
doReturn(signingCertificateStore).whenever(config).signingCertificateStore
|
||||||
|
doReturn(p2pSslOptions).whenever(config).p2pSslOptions
|
||||||
|
doReturn(devCertificateDir).whenever(config).certificatesDirectory
|
||||||
|
|
||||||
|
assertThat(devCryptoService.containsKey(NODE_IDENTITY_KEY_ALIAS)).isFalse()
|
||||||
|
|
||||||
|
KeyStoreHandler(config, devCryptoService).init()
|
||||||
|
|
||||||
|
assertThat(config.p2pSslOptions.trustStore.get().contains(CORDA_ROOT_CA)).isTrue()
|
||||||
|
assertThat(config.p2pSslOptions.keyStore.get().contains(CORDA_CLIENT_TLS)).isTrue()
|
||||||
|
assertThat(config.signingCertificateStore.get().contains(NODE_IDENTITY_KEY_ALIAS)).isTrue()
|
||||||
|
assertThat(devCryptoService.containsKey(NODE_IDENTITY_KEY_ALIAS)).isTrue()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(timeout = 300_000)
|
||||||
|
fun `load node identity`() {
|
||||||
|
keyStoreHandler.init()
|
||||||
|
|
||||||
|
val certificate = keyStore[NODE_IDENTITY_KEY_ALIAS]
|
||||||
|
assertThat(keyStoreHandler.nodeIdentity.certificate).isEqualTo(certificate)
|
||||||
|
assertThat(keyStoreHandler.notaryIdentity).isNull()
|
||||||
|
assertThat(keyStoreHandler.signingKeys).containsExactly(KeyAndAlias(certificate.publicKey, NODE_IDENTITY_KEY_ALIAS))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(timeout = 300_000)
|
||||||
|
fun `load node identity without node CA`() {
|
||||||
|
assertThat(keyStore[CORDA_CLIENT_CA]).isNotNull
|
||||||
|
keyStore.update { internal.deleteEntry(CORDA_CLIENT_CA) }
|
||||||
|
|
||||||
|
keyStoreHandler.init()
|
||||||
|
|
||||||
|
val certificate = keyStore[NODE_IDENTITY_KEY_ALIAS]
|
||||||
|
assertThat(keyStoreHandler.nodeIdentity.certificate).isEqualTo(certificate)
|
||||||
|
assertThat(keyStoreHandler.notaryIdentity).isNull()
|
||||||
|
assertThat(keyStoreHandler.signingKeys).containsExactly(KeyAndAlias(certificate.publicKey, NODE_IDENTITY_KEY_ALIAS))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(timeout = 300_000)
|
||||||
|
fun `load node identity with missing alias`() {
|
||||||
|
keyStore.update { internal.deleteEntry(NODE_IDENTITY_KEY_ALIAS) }
|
||||||
|
|
||||||
|
assertThatThrownBy {
|
||||||
|
keyStoreHandler.init()
|
||||||
|
}.hasMessageContaining("node identity key is not in the keyStore file")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(timeout = 300_000)
|
||||||
|
fun `load node identity with missing key in CryptoService`() {
|
||||||
|
val cryptoServiceMock = rigorousMock<CryptoService>()
|
||||||
|
doReturn(false).whenever(cryptoServiceMock).containsKey(NODE_IDENTITY_KEY_ALIAS)
|
||||||
|
keyStoreHandler = KeyStoreHandler(config, cryptoServiceMock)
|
||||||
|
|
||||||
|
assertThatThrownBy {
|
||||||
|
keyStoreHandler.init()
|
||||||
|
}.hasMessageContaining("Key for node identity alias '$NODE_IDENTITY_KEY_ALIAS' not found in CryptoService")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(timeout = 300_000)
|
||||||
|
fun `load node identity with untrusted root`() {
|
||||||
|
val untrustedRoot = X509Utilities.createSelfSignedCACertificate(ALICE_NAME.x500Principal, Crypto.generateKeyPair())
|
||||||
|
|
||||||
|
keyStore.update {
|
||||||
|
val privateKey = getPrivateKey(NODE_IDENTITY_KEY_ALIAS, DEV_CA_KEY_STORE_PASS)
|
||||||
|
val certificates = getCertificateChain(NODE_IDENTITY_KEY_ALIAS)
|
||||||
|
setPrivateKey(NODE_IDENTITY_KEY_ALIAS, privateKey, certificates.dropLast(1) + untrustedRoot, DEV_CA_KEY_STORE_PASS)
|
||||||
|
}
|
||||||
|
|
||||||
|
assertThatThrownBy {
|
||||||
|
keyStoreHandler.init()
|
||||||
|
}.hasMessageContaining("Certificate for node identity must chain to the trusted root")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(timeout = 300_000)
|
||||||
|
fun `load node identity with wrong legal name`() {
|
||||||
|
doReturn(BOB_NAME).whenever(config).myLegalName
|
||||||
|
|
||||||
|
assertThatThrownBy {
|
||||||
|
keyStoreHandler.init()
|
||||||
|
}.hasMessageContaining("The configured legalName").hasMessageContaining("doesn't match what's in the key store")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(timeout = 300_000)
|
||||||
|
fun `load node identity with wrong certificate path`() {
|
||||||
|
keyStore.update {
|
||||||
|
val privateKey = getPrivateKey(NODE_IDENTITY_KEY_ALIAS, DEV_CA_KEY_STORE_PASS)
|
||||||
|
val certificates = getCertificateChain(NODE_IDENTITY_KEY_ALIAS)
|
||||||
|
setPrivateKey(NODE_IDENTITY_KEY_ALIAS, privateKey, certificates.take(1) + certificates.drop(2), DEV_CA_KEY_STORE_PASS)
|
||||||
|
}
|
||||||
|
|
||||||
|
assertThatThrownBy {
|
||||||
|
keyStoreHandler.init()
|
||||||
|
}.hasMessageContaining("Cert path failed to validate")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(timeout = 300_000)
|
||||||
|
fun `load old style notary identity`() {
|
||||||
|
val notaryConfig = rigorousMock<NotaryConfig>()
|
||||||
|
doReturn(null).whenever(notaryConfig).serviceLegalName
|
||||||
|
doReturn(notaryConfig).whenever(config).notary
|
||||||
|
|
||||||
|
keyStoreHandler.init()
|
||||||
|
|
||||||
|
val certificate = keyStore[NODE_IDENTITY_KEY_ALIAS]
|
||||||
|
assertThat(keyStoreHandler.nodeIdentity.certificate).isEqualTo(certificate)
|
||||||
|
assertThat(keyStoreHandler.notaryIdentity).isNotNull
|
||||||
|
assertThat(keyStoreHandler.notaryIdentity!!.certificate).isEqualTo(certificate)
|
||||||
|
assertThat(keyStoreHandler.signingKeys).containsExactly(KeyAndAlias(certificate.publicKey, NODE_IDENTITY_KEY_ALIAS))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createNotaryCertificate(publicKey: PublicKey, name: CordaX500Name) = X509Utilities.createCertificate(
|
||||||
|
CertificateType.SERVICE_IDENTITY,
|
||||||
|
DEV_INTERMEDIATE_CA.certificate,
|
||||||
|
DEV_INTERMEDIATE_CA.keyPair,
|
||||||
|
name.x500Principal,
|
||||||
|
publicKey)
|
||||||
|
|
||||||
|
private fun generateIdentity(alias: String, name: CordaX500Name, type: CertificateType, parentAlias: String? = null): PublicKey {
|
||||||
|
val keyPair = Crypto.generateKeyPair()
|
||||||
|
val (parent, chain) = if (parentAlias != null) {
|
||||||
|
keyStore.query {
|
||||||
|
val parentCert = getCertificate(parentAlias)
|
||||||
|
val parentKey = getPrivateKey(parentAlias, DEV_CA_KEY_STORE_PASS)
|
||||||
|
CertificateAndKeyPair(parentCert, KeyPair(parentCert.publicKey, parentKey)) to getCertificateChain(parentAlias)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
DEV_INTERMEDIATE_CA to listOf(DEV_INTERMEDIATE_CA.certificate, DEV_ROOT_CA.certificate)
|
||||||
|
}
|
||||||
|
val certificate = X509Utilities.createCertificate(type, parent.certificate, parent.keyPair, name.x500Principal, keyPair.public)
|
||||||
|
keyStore.update {
|
||||||
|
setPrivateKey(alias, keyPair.private, listOf(certificate) + chain, DEV_CA_KEY_STORE_PASS)
|
||||||
|
}
|
||||||
|
cryptoService.resyncKeystore()
|
||||||
|
return keyPair.public
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(timeout = 300_000)
|
||||||
|
fun `load notary identity`() {
|
||||||
|
val notaryConfig = rigorousMock<NotaryConfig>()
|
||||||
|
doReturn(BOB_NAME).whenever(notaryConfig).serviceLegalName
|
||||||
|
doReturn(notaryConfig).whenever(config).notary
|
||||||
|
|
||||||
|
generateIdentity(DISTRIBUTED_NOTARY_KEY_ALIAS, BOB_NAME, CertificateType.SERVICE_IDENTITY)
|
||||||
|
|
||||||
|
keyStoreHandler.init()
|
||||||
|
|
||||||
|
val nodeCert = keyStore[NODE_IDENTITY_KEY_ALIAS]
|
||||||
|
val notaryCert = keyStore[DISTRIBUTED_NOTARY_KEY_ALIAS]
|
||||||
|
assertThat(keyStoreHandler.nodeIdentity.certificate).isEqualTo(nodeCert)
|
||||||
|
assertThat(keyStoreHandler.notaryIdentity).isNotNull
|
||||||
|
assertThat(keyStoreHandler.notaryIdentity!!.certificate).isEqualTo(notaryCert)
|
||||||
|
assertThat(keyStoreHandler.signingKeys).containsExactly(
|
||||||
|
KeyAndAlias(nodeCert.publicKey, NODE_IDENTITY_KEY_ALIAS),
|
||||||
|
KeyAndAlias(notaryCert.publicKey, DISTRIBUTED_NOTARY_KEY_ALIAS)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(timeout = 300_000)
|
||||||
|
fun `load notary identity with wrong legal name`() {
|
||||||
|
val notaryConfig = rigorousMock<NotaryConfig>()
|
||||||
|
doReturn(BOB_NAME).whenever(notaryConfig).serviceLegalName
|
||||||
|
doReturn(notaryConfig).whenever(config).notary
|
||||||
|
|
||||||
|
generateIdentity(DISTRIBUTED_NOTARY_KEY_ALIAS, ALICE_NAME, CertificateType.SERVICE_IDENTITY)
|
||||||
|
|
||||||
|
assertThatThrownBy {
|
||||||
|
keyStoreHandler.init()
|
||||||
|
}.hasMessageContaining("The configured legalName").hasMessageContaining("doesn't match what's in the key store")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(timeout = 300_000)
|
||||||
|
fun `load notary composite identity`() {
|
||||||
|
val notaryConfig = rigorousMock<NotaryConfig>()
|
||||||
|
doReturn(BOB_NAME).whenever(notaryConfig).serviceLegalName
|
||||||
|
doReturn(notaryConfig).whenever(config).notary
|
||||||
|
|
||||||
|
val notaryKey = generateIdentity(DISTRIBUTED_NOTARY_KEY_ALIAS, BOB_NAME, CertificateType.SERVICE_IDENTITY)
|
||||||
|
val compositeKey = CompositeKey.Builder().addKey(notaryKey).build()
|
||||||
|
keyStore[DISTRIBUTED_NOTARY_COMPOSITE_KEY_ALIAS] = createNotaryCertificate(compositeKey, BOB_NAME)
|
||||||
|
|
||||||
|
keyStoreHandler.init()
|
||||||
|
|
||||||
|
val nodeCert = keyStore[NODE_IDENTITY_KEY_ALIAS]
|
||||||
|
assertThat(keyStoreHandler.nodeIdentity.certificate).isEqualTo(nodeCert)
|
||||||
|
assertThat(keyStoreHandler.notaryIdentity).isNotNull
|
||||||
|
assertThat(keyStoreHandler.notaryIdentity!!.certificate).isEqualTo(keyStore[DISTRIBUTED_NOTARY_COMPOSITE_KEY_ALIAS])
|
||||||
|
assertThat(keyStoreHandler.signingKeys).containsExactly(
|
||||||
|
KeyAndAlias(nodeCert.publicKey, NODE_IDENTITY_KEY_ALIAS),
|
||||||
|
KeyAndAlias(notaryKey, DISTRIBUTED_NOTARY_KEY_ALIAS)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(timeout = 300_000)
|
||||||
|
fun `load notary composite identity with wrong legal name`() {
|
||||||
|
val notaryConfig = rigorousMock<NotaryConfig>()
|
||||||
|
doReturn(BOB_NAME).whenever(notaryConfig).serviceLegalName
|
||||||
|
doReturn(notaryConfig).whenever(config).notary
|
||||||
|
|
||||||
|
val notaryKey = generateIdentity(DISTRIBUTED_NOTARY_KEY_ALIAS, BOB_NAME, CertificateType.SERVICE_IDENTITY)
|
||||||
|
val compositeKey = CompositeKey.Builder().addKey(notaryKey).build()
|
||||||
|
keyStore[DISTRIBUTED_NOTARY_COMPOSITE_KEY_ALIAS] = createNotaryCertificate(compositeKey, ALICE_NAME)
|
||||||
|
|
||||||
|
assertThatThrownBy {
|
||||||
|
keyStoreHandler.init()
|
||||||
|
}.hasMessageContaining("The configured legalName").hasMessageContaining("doesn't match what's in the key store")
|
||||||
|
}
|
||||||
|
}
|
@ -1,157 +0,0 @@
|
|||||||
package net.corda.node.internal
|
|
||||||
|
|
||||||
import com.nhaarman.mockito_kotlin.any
|
|
||||||
import com.nhaarman.mockito_kotlin.doAnswer
|
|
||||||
import com.nhaarman.mockito_kotlin.mock
|
|
||||||
import com.nhaarman.mockito_kotlin.verify
|
|
||||||
import com.nhaarman.mockito_kotlin.whenever
|
|
||||||
import net.corda.node.services.config.NodeConfiguration
|
|
||||||
import net.corda.nodeapi.internal.config.CertificateStore
|
|
||||||
import net.corda.nodeapi.internal.config.FileBasedCertificateStoreSupplier
|
|
||||||
import net.corda.nodeapi.internal.config.MutualSslConfiguration
|
|
||||||
import net.corda.nodeapi.internal.crypto.X509KeyStore
|
|
||||||
import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_CLIENT_CA
|
|
||||||
import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_CLIENT_TLS
|
|
||||||
import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_ROOT_CA
|
|
||||||
import net.corda.nodeapi.internal.cryptoservice.CryptoService
|
|
||||||
import net.corda.nodeapi.internal.cryptoservice.bouncycastle.BCCryptoService
|
|
||||||
import net.corda.testing.core.ALICE_NAME
|
|
||||||
import org.assertj.core.api.Assertions.assertThat
|
|
||||||
import org.assertj.core.api.Assertions.assertThatThrownBy
|
|
||||||
import org.junit.Test
|
|
||||||
import java.io.IOException
|
|
||||||
import java.security.KeyStoreException
|
|
||||||
import java.security.cert.X509Certificate
|
|
||||||
|
|
||||||
class NodeKeyStoreUtilitiesTest {
|
|
||||||
@Test(timeout = 300_000)
|
|
||||||
fun `initializing key store in non-dev mode with no key store`() {
|
|
||||||
whenever(signingSupplier.get()).doAnswer { throw IOException() }
|
|
||||||
|
|
||||||
assertThatThrownBy {
|
|
||||||
config.initKeyStores(cryptoService)
|
|
||||||
}.hasMessageContaining("One or more keyStores (identity or TLS) or trustStore not found.")
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test(timeout = 300_000)
|
|
||||||
fun `initializing key store in non-dev mode with invalid password`() {
|
|
||||||
whenever(signingSupplier.get()).doAnswer { throw KeyStoreException() }
|
|
||||||
|
|
||||||
assertThatThrownBy {
|
|
||||||
config.initKeyStores(cryptoService)
|
|
||||||
}.hasMessageContaining("At least one of the keystores or truststore passwords does not match configuration")
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test(timeout = 300_000)
|
|
||||||
fun `initializing key store in non-dev mode without trusted root`() {
|
|
||||||
whenever(trustStore.contains(CORDA_ROOT_CA)).thenReturn(false)
|
|
||||||
|
|
||||||
assertThatThrownBy {
|
|
||||||
config.initKeyStores(cryptoService)
|
|
||||||
}.hasMessageContaining("Alias for trustRoot key not found. Please ensure you have an updated trustStore file")
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test(timeout = 300_000)
|
|
||||||
fun `initializing key store in non-dev mode without alias for TLS key`() {
|
|
||||||
whenever(keyStore.contains(CORDA_CLIENT_TLS)).thenReturn(false)
|
|
||||||
|
|
||||||
assertThatThrownBy {
|
|
||||||
config.initKeyStores(cryptoService)
|
|
||||||
}.hasMessageContaining("Alias for TLS key not found. Please ensure you have an updated TLS keyStore file")
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test(timeout = 300_000)
|
|
||||||
fun `initializing key store in non-dev mode without alias for node CA key`() {
|
|
||||||
whenever(signingStore.contains(CORDA_CLIENT_CA)).thenReturn(false)
|
|
||||||
|
|
||||||
assertThatThrownBy {
|
|
||||||
config.initKeyStores(cryptoService)
|
|
||||||
}.hasMessageContaining("Alias for Node CA key not found. Please ensure you have an updated identity keyStore file")
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test(timeout = 300_000)
|
|
||||||
fun `initializing key store should throw exception if cert path does not chain to the trust root`() {
|
|
||||||
val untrustedRoot = mock<X509Certificate>()
|
|
||||||
whenever(signingStore.query(any<X509KeyStore.() -> List<X509Certificate>>())).thenReturn(mutableListOf(untrustedRoot))
|
|
||||||
|
|
||||||
assertThatThrownBy {
|
|
||||||
config.initKeyStores(cryptoService)
|
|
||||||
}.hasMessageContaining("Client CA certificate must chain to the trusted root")
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test(timeout = 300_000)
|
|
||||||
fun `initializing key store should throw exception if TLS certificate does not chain to the trust root`() {
|
|
||||||
val untrustedRoot = mock<X509Certificate>()
|
|
||||||
whenever(keyStore.query(any<X509KeyStore.() -> List<X509Certificate>>())).thenReturn(mutableListOf(untrustedRoot))
|
|
||||||
|
|
||||||
assertThatThrownBy {
|
|
||||||
config.initKeyStores(cryptoService)
|
|
||||||
}.hasMessageContaining("TLS certificate must chain to the trusted root")
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test(timeout = 300_000)
|
|
||||||
fun `initializing key store should return valid certificate if certificate is valid`() {
|
|
||||||
val certificate = config.initKeyStores(cryptoService)
|
|
||||||
|
|
||||||
assertThat(certificate).isEqualTo(trustRoot)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test(timeout = 300_000)
|
|
||||||
fun `initializing key store in dev mode check te supplier`() {
|
|
||||||
whenever(config.devMode).thenReturn(true)
|
|
||||||
whenever(config.myLegalName).thenReturn(ALICE_NAME)
|
|
||||||
whenever(config.certificatesDirectory).thenReturn(mock())
|
|
||||||
whenever(trustSupplier.getOptional()).thenReturn(mock())
|
|
||||||
whenever(keySupplier.getOptional()).thenReturn(mock())
|
|
||||||
whenever(signingSupplier.getOptional()).thenReturn(mock())
|
|
||||||
|
|
||||||
config.initKeyStores(cryptoService)
|
|
||||||
|
|
||||||
verify(signingSupplier).getOptional()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test(timeout = 300_000)
|
|
||||||
fun `initializing key store in dev mode with BCCryptoService call resyncKeystore`() {
|
|
||||||
val bCryptoService = mock<BCCryptoService>()
|
|
||||||
whenever(config.devMode).thenReturn(true)
|
|
||||||
whenever(config.myLegalName).thenReturn(ALICE_NAME)
|
|
||||||
whenever(config.certificatesDirectory).thenReturn(mock())
|
|
||||||
whenever(trustSupplier.getOptional()).thenReturn(mock())
|
|
||||||
whenever(keySupplier.getOptional()).thenReturn(mock())
|
|
||||||
whenever(signingSupplier.getOptional()).thenReturn(mock())
|
|
||||||
|
|
||||||
config.initKeyStores(bCryptoService)
|
|
||||||
|
|
||||||
verify(bCryptoService).resyncKeystore()
|
|
||||||
}
|
|
||||||
|
|
||||||
private val config = mock<NodeConfiguration>()
|
|
||||||
|
|
||||||
private val trustStore = mock<CertificateStore>()
|
|
||||||
private val signingStore = mock<CertificateStore>()
|
|
||||||
private val keyStore = mock<CertificateStore>()
|
|
||||||
private val sslOptions = mock<MutualSslConfiguration>()
|
|
||||||
private val trustSupplier = mock<FileBasedCertificateStoreSupplier>()
|
|
||||||
private val signingSupplier = mock<FileBasedCertificateStoreSupplier>()
|
|
||||||
private val keySupplier = mock<FileBasedCertificateStoreSupplier>()
|
|
||||||
private val trustRoot = mock<X509Certificate>()
|
|
||||||
private val cryptoService = mock<CryptoService>()
|
|
||||||
|
|
||||||
init {
|
|
||||||
whenever(config.devMode).thenReturn(false)
|
|
||||||
|
|
||||||
whenever(sslOptions.keyStore).thenReturn(keySupplier)
|
|
||||||
whenever(sslOptions.trustStore).thenReturn(trustSupplier)
|
|
||||||
whenever(config.signingCertificateStore).thenReturn(signingSupplier)
|
|
||||||
whenever(trustSupplier.get()).thenReturn(trustStore)
|
|
||||||
whenever(signingSupplier.get()).thenReturn(signingStore)
|
|
||||||
whenever(keySupplier.get()).thenReturn(keyStore)
|
|
||||||
whenever(trustStore.contains(CORDA_ROOT_CA)).thenReturn(true)
|
|
||||||
whenever(keyStore.contains(CORDA_CLIENT_TLS)).thenReturn(true)
|
|
||||||
whenever(signingStore.contains(CORDA_CLIENT_CA)).thenReturn(true)
|
|
||||||
whenever(config.p2pSslOptions).thenReturn(sslOptions)
|
|
||||||
whenever(trustStore[CORDA_ROOT_CA]).thenReturn(trustRoot)
|
|
||||||
whenever(signingStore.query(any<X509KeyStore.() -> List<X509Certificate>>())).thenReturn(mutableListOf(trustRoot))
|
|
||||||
whenever(keyStore.query(any<X509KeyStore.() -> List<X509Certificate>>())).thenReturn(mutableListOf(trustRoot))
|
|
||||||
}
|
|
||||||
}
|
|
@ -65,7 +65,10 @@ class PersistentIdentityServiceTests {
|
|||||||
)
|
)
|
||||||
identityService.database = database
|
identityService.database = database
|
||||||
identityService.ourNames = setOf(ALICE_NAME)
|
identityService.ourNames = setOf(ALICE_NAME)
|
||||||
identityService.start(DEV_ROOT_CA.certificate, pkToIdCache = PublicKeyToOwningIdentityCacheImpl(database, cacheFactory))
|
identityService.start(DEV_ROOT_CA.certificate, alice.identity, pkToIdCache = PublicKeyToOwningIdentityCacheImpl(
|
||||||
|
database,
|
||||||
|
cacheFactory
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
@ -230,7 +233,7 @@ class PersistentIdentityServiceTests {
|
|||||||
// Create new identity service mounted onto same DB
|
// Create new identity service mounted onto same DB
|
||||||
val newPersistentIdentityService = PersistentIdentityService(TestingNamedCacheFactory()).also {
|
val newPersistentIdentityService = PersistentIdentityService(TestingNamedCacheFactory()).also {
|
||||||
it.database = database
|
it.database = database
|
||||||
it.start(DEV_ROOT_CA.certificate, pkToIdCache = PublicKeyToOwningIdentityCacheImpl(database, cacheFactory))
|
it.start(DEV_ROOT_CA.certificate, Companion.alice.identity, pkToIdCache = PublicKeyToOwningIdentityCacheImpl(database, cacheFactory))
|
||||||
}
|
}
|
||||||
|
|
||||||
newPersistentIdentityService.assertOwnership(alice.party, anonymousAlice.party.anonymise())
|
newPersistentIdentityService.assertOwnership(alice.party, anonymousAlice.party.anonymise())
|
||||||
|
@ -6,7 +6,6 @@ import net.corda.core.contracts.ContractClassName
|
|||||||
import net.corda.core.contracts.StateRef
|
import net.corda.core.contracts.StateRef
|
||||||
import net.corda.core.cordapp.CordappProvider
|
import net.corda.core.cordapp.CordappProvider
|
||||||
import net.corda.core.crypto.SecureHash
|
import net.corda.core.crypto.SecureHash
|
||||||
import net.corda.core.crypto.internal.AliasPrivateKey
|
|
||||||
import net.corda.core.flows.FlowLogic
|
import net.corda.core.flows.FlowLogic
|
||||||
import net.corda.core.flows.StateMachineRunId
|
import net.corda.core.flows.StateMachineRunId
|
||||||
import net.corda.core.identity.CordaX500Name
|
import net.corda.core.identity.CordaX500Name
|
||||||
@ -188,7 +187,7 @@ open class MockServices private constructor(
|
|||||||
identityService.apply {
|
identityService.apply {
|
||||||
ourNames = setOf(initialIdentity.name)
|
ourNames = setOf(initialIdentity.name)
|
||||||
database = persistence
|
database = persistence
|
||||||
start(DEV_ROOT_CA.certificate, pkToIdCache = pkToIdCache)
|
start(DEV_ROOT_CA.certificate, initialIdentity.identity, pkToIdCache = pkToIdCache)
|
||||||
persistence.transaction { identityService.loadIdentities(moreIdentities + initialIdentity.identity) }
|
persistence.transaction { identityService.loadIdentities(moreIdentities + initialIdentity.identity) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -199,11 +198,11 @@ open class MockServices private constructor(
|
|||||||
val aliasedMoreKeys = moreKeys.mapIndexed { index, keyPair ->
|
val aliasedMoreKeys = moreKeys.mapIndexed { index, keyPair ->
|
||||||
val alias = "Extra key $index"
|
val alias = "Extra key $index"
|
||||||
aliasKeyMap[alias] = keyPair
|
aliasKeyMap[alias] = keyPair
|
||||||
KeyPair(keyPair.public, AliasPrivateKey(alias))
|
keyPair.public to alias
|
||||||
}.toSet()
|
}
|
||||||
val identityAlias = "${initialIdentity.name} private key"
|
val identityAlias = "${initialIdentity.name} private key"
|
||||||
aliasKeyMap[identityAlias] = initialIdentity.keyPair
|
aliasKeyMap[identityAlias] = initialIdentity.keyPair
|
||||||
val aliasedIdentityKey = KeyPair(initialIdentity.publicKey, AliasPrivateKey(identityAlias))
|
val aliasedIdentityKey = initialIdentity.publicKey to identityAlias
|
||||||
val keyManagementService = BasicHSMKeyManagementService(
|
val keyManagementService = BasicHSMKeyManagementService(
|
||||||
TestingNamedCacheFactory(),
|
TestingNamedCacheFactory(),
|
||||||
identityService,
|
identityService,
|
||||||
|
@ -384,7 +384,10 @@ class DriverDSLImpl(
|
|||||||
NodeRegistrationConfiguration(config.corda),
|
NodeRegistrationConfiguration(config.corda),
|
||||||
HTTPNetworkRegistrationService(networkServicesConfig, versionInfo),
|
HTTPNetworkRegistrationService(networkServicesConfig, versionInfo),
|
||||||
NodeRegistrationOption(rootTruststorePath, rootTruststorePassword)
|
NodeRegistrationOption(rootTruststorePath, rootTruststorePassword)
|
||||||
).generateKeysAndRegister()
|
).apply {
|
||||||
|
generateKeysAndRegister()
|
||||||
|
generateNodeIdentity()
|
||||||
|
}
|
||||||
config
|
config
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -4,7 +4,6 @@ import com.nhaarman.mockito_kotlin.doReturn
|
|||||||
import com.nhaarman.mockito_kotlin.whenever
|
import com.nhaarman.mockito_kotlin.whenever
|
||||||
import net.corda.common.configuration.parsing.internal.ConfigurationWithOptions
|
import net.corda.common.configuration.parsing.internal.ConfigurationWithOptions
|
||||||
import net.corda.core.DoNotImplement
|
import net.corda.core.DoNotImplement
|
||||||
import net.corda.core.crypto.Crypto
|
|
||||||
import net.corda.core.crypto.SecureHash
|
import net.corda.core.crypto.SecureHash
|
||||||
import net.corda.core.crypto.random63BitValue
|
import net.corda.core.crypto.random63BitValue
|
||||||
import net.corda.core.flows.FlowLogic
|
import net.corda.core.flows.FlowLogic
|
||||||
@ -46,7 +45,6 @@ import net.corda.node.utilities.AffinityExecutor.ServiceAffinityExecutor
|
|||||||
import net.corda.node.utilities.DefaultNamedCacheFactory
|
import net.corda.node.utilities.DefaultNamedCacheFactory
|
||||||
import net.corda.nodeapi.internal.DevIdentityGenerator
|
import net.corda.nodeapi.internal.DevIdentityGenerator
|
||||||
import net.corda.nodeapi.internal.config.User
|
import net.corda.nodeapi.internal.config.User
|
||||||
import net.corda.nodeapi.internal.cryptoservice.bouncycastle.BCCryptoService
|
|
||||||
import net.corda.nodeapi.internal.network.NetworkParametersCopier
|
import net.corda.nodeapi.internal.network.NetworkParametersCopier
|
||||||
import net.corda.nodeapi.internal.persistence.CordaPersistence
|
import net.corda.nodeapi.internal.persistence.CordaPersistence
|
||||||
import net.corda.nodeapi.internal.persistence.DatabaseConfig
|
import net.corda.nodeapi.internal.persistence.DatabaseConfig
|
||||||
@ -65,10 +63,10 @@ import java.math.BigInteger
|
|||||||
import java.net.URLClassLoader
|
import java.net.URLClassLoader
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import java.nio.file.Paths
|
import java.nio.file.Paths
|
||||||
import java.security.PublicKey
|
|
||||||
import java.time.Clock
|
import java.time.Clock
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import java.util.concurrent.atomic.AtomicInteger
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
|
import java.util.concurrent.atomic.AtomicReference
|
||||||
|
|
||||||
val MOCK_VERSION_INFO = VersionInfo(PLATFORM_VERSION, "Mock release", "Mock revision", "Mock Vendor")
|
val MOCK_VERSION_INFO = VersionInfo(PLATFORM_VERSION, "Mock release", "Mock revision", "Mock Vendor")
|
||||||
|
|
||||||
@ -334,8 +332,7 @@ open class InternalMockNetwork(cordappPackages: List<String> = emptyList(),
|
|||||||
require(id >= 0) { "Node ID must be zero or positive, was passed: $id" }
|
require(id >= 0) { "Node ID must be zero or positive, was passed: $id" }
|
||||||
}
|
}
|
||||||
|
|
||||||
private val entropyRoot = args.entropyRoot
|
private val entropyCounter = AtomicReference(args.entropyRoot)
|
||||||
var counter = entropyRoot
|
|
||||||
override val log get() = staticLog
|
override val log get() = staticLog
|
||||||
override val transactionVerifierWorkerCount: Int get() = 1
|
override val transactionVerifierWorkerCount: Int get() = 1
|
||||||
|
|
||||||
@ -403,15 +400,7 @@ open class InternalMockNetwork(cordappPackages: List<String> = emptyList(),
|
|||||||
//No mock shell
|
//No mock shell
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is not thread safe, but node construction is done on a single thread, so that should always be fine
|
override fun initKeyStores() = keyStoreHandler.init(entropyCounter.updateAndGet { it.add(BigInteger.ONE) })
|
||||||
override fun generateKeyPair(alias: String): PublicKey {
|
|
||||||
require(cryptoService is BCCryptoService) { "MockNode supports BCCryptoService only, but it is ${cryptoService.javaClass.name}" }
|
|
||||||
counter = counter.add(BigInteger.ONE)
|
|
||||||
// The StartedMockNode specifically uses EdDSA keys as they are fixed and stored in json files for some tests (e.g IRSSimulation).
|
|
||||||
val keyPair = Crypto.deriveKeyPairFromEntropy(Crypto.EDDSA_ED25519_SHA512, counter)
|
|
||||||
(cryptoService as BCCryptoService).importKey(alias, keyPair)
|
|
||||||
return keyPair.public
|
|
||||||
}
|
|
||||||
|
|
||||||
// NodeInfo requires a non-empty addresses list and so we give it a dummy value for mock nodes.
|
// NodeInfo requires a non-empty addresses list and so we give it a dummy value for mock nodes.
|
||||||
// The non-empty addresses check is important to have and so we tolerate the ugliness here.
|
// The non-empty addresses check is important to have and so we tolerate the ugliness here.
|
||||||
|
@ -41,8 +41,8 @@ class MockKeyManagementService(
|
|||||||
|
|
||||||
override fun getSigner(publicKey: PublicKey): ContentSigner = net.corda.node.services.keys.getSigner(getSigningKeyPair(publicKey))
|
override fun getSigner(publicKey: PublicKey): ContentSigner = net.corda.node.services.keys.getSigner(getSigningKeyPair(publicKey))
|
||||||
|
|
||||||
override fun start(initialKeyPairs: Set<KeyPair>) {
|
override fun start(initialKeysAndAliases: Iterable<Pair<PublicKey, String>>) {
|
||||||
initialKeyPairs.forEach { keyStore[it.public] = it.private }
|
throw NotImplementedError()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getSigningKeyPair(publicKey: PublicKey): KeyPair {
|
private fun getSigningKeyPair(publicKey: PublicKey): KeyPair {
|
||||||
|
Loading…
Reference in New Issue
Block a user