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:
Denis Rekalov 2020-09-17 10:15:46 +01:00 committed by GitHub
parent acb82f77b4
commit 5c6acb0909
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 741 additions and 553 deletions

View File

@ -108,7 +108,6 @@
<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:ConfigUtilities.kt$// For Iterables figure out the type parameter and apply the same logic as above on the individual elements. private fun Iterable&lt;*&gt;.toConfigIterable(field: Field): Iterable&lt;Any?&gt;</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&lt;String, Any&gt;</ID>
<ID>ComplexMethod:ConfigUtilities.kt$private fun convertValue(value: Any): Any</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: 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: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.&lt;no name provided&gt;$// 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>
@ -307,7 +305,6 @@
<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: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: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>
@ -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: 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: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 command history.</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: 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: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: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>
@ -1285,8 +1280,6 @@
<ID>SpreadOperator:HibernateQueryCriteriaParser.kt$AbstractQueryCriteriaParser$(*leftPredicates.toTypedArray(), *rightPredicates.toTypedArray())</ID>
<ID>SpreadOperator:HibernateQueryCriteriaParser.kt$AbstractQueryCriteriaParser$(*rightPredicates.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:InstallShellExtensionsParser.kt$ShellExtensionsGenerator$(*commandAndArgs)</ID>
<ID>SpreadOperator:InteractiveShell.kt$InteractiveShell$(clazz, *args)</ID>
@ -1387,6 +1380,7 @@
<ID>ThrowsCount:EnumTransforms.kt$EnumTransforms$ private fun validateNoCycles(constants: Map&lt;String, Int&gt;)</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:KeyStoreHandler.kt$KeyStoreHandler$private fun getCertificateStores(): AllCertificateStores</ID>
<ID>ThrowsCount:LedgerDSLInterpreter.kt$Verifies$ fun failsWith(expectedMessage: String?): EnforceVerifyOrFail</ID>
<ID>ThrowsCount:MockServices.kt$ fun &lt;T : SerializeAsToken&gt; createMockCordaService(serviceHub: MockServices, serviceConstructor: (AppServiceHub) -&gt; T): T</ID>
<ID>ThrowsCount:NetworkRegistrationHelper.kt$NetworkRegistrationHelper$private fun validateCertificates( registeringPublicKey: PublicKey, registeringLegalName: CordaX500Name, expectedCertRole: CertRole, certificates: List&lt;X509Certificate&gt; )</ID>
@ -1483,7 +1477,6 @@
<ID>TooGenericExceptionCaught:InternalUtils.kt$ex: Exception</ID>
<ID>TooGenericExceptionCaught:InternalUtils.kt$th: Throwable</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.PublicKeyDeserializer$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:DummyLinearStateSchemaV1.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.serialization.internal.amqp.testutils.*</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.testing.core.*</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.nodeapi.internal.network.*</ID>
<ID>WildcardImport:NetworkParametersTest.kt$import net.corda.testing.core.*</ID>

View File

@ -13,7 +13,6 @@ import net.corda.core.concurrent.CordaFuture
import net.corda.core.context.InvocationContext
import net.corda.core.crypto.DigitalSignature
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.internal.AliasPrivateKey
import net.corda.core.crypto.newSecureRandom
import net.corda.core.flows.ContractUpgradeFlow
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.nodeapi.internal.NodeInfoAndSigned
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.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.bouncycastle.BCCryptoService
import net.corda.nodeapi.internal.lifecycle.NodeLifecycleEvent
@ -178,8 +169,6 @@ import org.jolokia.jvmagent.JolokiaServerConfig
import org.slf4j.Logger
import rx.Scheduler
import java.lang.reflect.InvocationTargetException
import java.security.KeyPair
import java.security.cert.X509Certificate
import java.sql.Connection
import java.sql.Savepoint
import java.time.Clock
@ -390,6 +379,8 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
private val nodeLifecycleEventsDistributor = NodeLifecycleEventsDistributor().apply { add(checkpointDumper) }
protected val keyStoreHandler = KeyStoreHandler(configuration, cryptoService)
private fun <T : Any> T.tokenize(): T {
tokenizableServices?.add(this as? 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 {
check(started == null) { "Node has already been started" }
log.info("Generating nodeInfo ...")
val trustRoot = configuration.initKeyStores(cryptoService)
val trustRoot = initKeyStores()
startDatabase()
val (identity, identityKeyPair) = obtainIdentity()
val nodeCa = configuration.signingCertificateStore.get()[CORDA_CLIENT_CA]
identityService.start(trustRoot, listOf(identity.certificate, nodeCa), pkToIdCache = pkToIdCache)
identityService.start(trustRoot, keyStoreHandler.nodeIdentity, pkToIdCache = pkToIdCache)
return database.use {
it.transaction {
val (_, nodeInfoAndSigned) = updateNodeInfo(identity, identityKeyPair, publish = false)
val nodeInfoAndSigned = updateNodeInfo(publish = false)
nodeInfoAndSigned.nodeInfo
}
}
@ -488,7 +479,7 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
logVendorString(database, log)
if (allowHibernateToManageAppSchema) {
Node.printBasicNodeInfo("Initialising CorDapps to get schemas created by hibernate")
val trustRoot = configuration.initKeyStores(cryptoService)
val trustRoot = initKeyStores()
networkMapClient?.start(trustRoot)
val (netParams, signedNetParams) = NetworkParametersReader(trustRoot, networkMapClient, configuration.networkParametersPath).read()
log.info("Loaded network parameters: $netParams")
@ -538,7 +529,7 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
nodeLifecycleEventsDistributor.distributeEvent(NodeLifecycleEvent.BeforeNodeStart(nodeServicesContext))
log.info("Node starting up ...")
val trustRoot = configuration.initKeyStores(cryptoService)
val trustRoot = initKeyStores()
initialiseJolokia()
schemaService.mappedSchemasWarnings().forEach {
@ -563,14 +554,11 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
networkMapCache.start(netParams.notaries)
startDatabase()
val (identity, identityKeyPair) = obtainIdentity()
X509Utilities.validateCertPath(trustRoot, identity.certPath)
val nodeCa = configuration.signingCertificateStore.get()[CORDA_CLIENT_CA]
identityService.start(trustRoot, listOf(identity.certificate, nodeCa), netParams.notaries.map { it.identity }, pkToIdCache)
identityService.start(trustRoot, keyStoreHandler.nodeIdentity, netParams.notaries.map { it.identity }, pkToIdCache)
val (keyPairs, nodeInfoAndSigned, myNotaryIdentity) = database.transaction {
updateNodeInfo(identity, identityKeyPair, publish = true)
val nodeInfoAndSigned = database.transaction {
updateNodeInfo(publish = true)
}
val (nodeInfo, signedNodeInfo) = nodeInfoAndSigned
@ -598,7 +586,7 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
networkParametersHotloader)
try {
startMessagingService(rpcOps, nodeInfo, myNotaryIdentity, netParams)
startMessagingService(rpcOps, nodeInfo, keyStoreHandler.notaryIdentity, netParams)
} catch (e: Exception) {
// Try to stop any started messaging services.
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
// 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.
keyManagementService.start(keyPairs)
keyManagementService.start(keyStoreHandler.signingKeys.map { it.key to it.alias })
installCordaServices()
notaryService = maybeStartNotaryService(myNotaryIdentity)
notaryService = maybeStartNotaryService(keyStoreHandler.notaryIdentity)
contractUpgradeService.start()
vaultService.start()
ScheduledActivityObserver.install(vaultService, schedulerService, flowLogicRefFactory)
@ -696,41 +684,15 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
}
}
private fun updateNodeInfo(identity: PartyAndCertificate,
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
}
}
private fun updateNodeInfo(publish: Boolean): NodeInfoAndSigned {
val potentialNodeInfo = NodeInfo(
myAddresses(),
setOf(identity, myNotaryIdentity).filterNotNull(),
setOf(keyStoreHandler.nodeIdentity, keyStoreHandler.notaryIdentity).filterNotNull(),
versionInfo.platformVersion,
serial = 0
)
val nodeInfoFromDb = getPreviousNodeInfoIfPresent(identity)
val nodeInfoFromDb = getPreviousNodeInfoIfPresent(keyStoreHandler.nodeIdentity)
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.
@ -745,8 +707,8 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
}
val nodeInfoAndSigned = NodeInfoAndSigned(nodeInfo) { publicKey, serialised ->
val privateKey = keyPairs.single { it.public == publicKey }.private
DigitalSignature(cryptoService.sign((privateKey as AliasPrivateKey).alias, serialised.bytes))
val alias = keyStoreHandler.signingKeys.single { it.key == publicKey }.alias
DigitalSignature(cryptoService.sign(alias, serialised.bytes))
}
// 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)
}
return Triple(keyPairs, nodeInfoAndSigned, myNotaryIdentity)
return nodeInfoAndSigned
}
private fun getPreviousNodeInfoIfPresent(identity: PartyAndCertificate): NodeInfo? {
@ -1084,127 +1046,6 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
myNotaryIdentity: PartyAndCertificate?,
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,
services: ServicesForResolution,
database: CordaPersistence,

View 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
}
}

View File

@ -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
}
}

View File

@ -76,10 +76,12 @@ class InitialRegistration(val baseDirectory: Path, private val networkRootTrustS
HTTPNetworkRegistrationService(
requireNotNull(conf.networkServices),
versionInfo),
nodeRegistration).generateKeysAndRegister()
nodeRegistration).apply {
generateKeysAndRegister()
generateNodeIdentity()
}
// 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)
if(!skipSchemaMigration) {
node.runDatabaseMigrationScripts(updateCoreSchemas = true, updateAppSchemas = true, updateAppSchemasWithCheckpoints = false)

View File

@ -5,6 +5,7 @@ import com.typesafe.config.ConfigFactory
import com.typesafe.config.ConfigParseOptions
import net.corda.cliutils.CordaSystemUtils
import net.corda.common.configuration.parsing.internal.Configuration
import net.corda.core.crypto.Crypto
import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.VisibleForTesting
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.toProperties
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.bouncycastle.BCCryptoService
import net.corda.nodeapi.internal.installDevNodeCaCertPath
import net.corda.nodeapi.internal.loadDevCaTrustStore
import net.corda.nodeapi.internal.registerDevP2pCertificates
import net.corda.nodeapi.internal.storeLegalIdentity
import org.slf4j.LoggerFactory
import java.math.BigInteger
import java.nio.file.Path
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.
*/
// 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.
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 specifiedKeyStore = keyStore.getOptional()
@ -187,7 +197,15 @@ fun MutualSslConfiguration.configureDevKeyAndTrustStores(myLegalName: CordaX500N
when (cryptoService) {
is BCCryptoService, null -> {
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.
val distributedServiceKeystore = certificatesDirectory / "distributedService.jks"

View File

@ -215,13 +215,15 @@ class PersistentIdentityService(cacheFactory: NamedCacheFactory) : SingletonSeri
fun start(
trustRoot: X509Certificate,
caCertificates: List<X509Certificate> = emptyList(),
ourIdentity: PartyAndCertificate,
notaryIdentities: List<Party> = emptyList(),
pkToIdCache: WritablePublicKeyToOwningIdentityCache
) {
_trustRoot = trustRoot
_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
notaryIdentityCache.addAll(notaryIdentities)
}

View File

@ -1,7 +1,6 @@
package net.corda.node.services.keys
import net.corda.core.crypto.*
import net.corda.core.crypto.internal.AliasPrivateKey
import net.corda.core.internal.NamedCacheFactory
import net.corda.core.serialization.SingletonSerializeAsToken
import net.corda.core.serialization.serialize
@ -21,7 +20,7 @@ import javax.persistence.*
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.
*
* 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.
private val keysMap = createKeyMap(cacheFactory)
override fun start(initialKeyPairs: Set<KeyPair>) {
initialKeyPairs.forEach {
require(it.private is AliasPrivateKey) { "${this.javaClass.name} supports AliasPrivateKeys only, but ${it.private.algorithm} key was found" }
originalKeysMap[Crypto.toSupportedPublicKey(it.public)] = (it.private as AliasPrivateKey).alias
override fun start(initialKeysAndAliases: Iterable<Pair<PublicKey, String>>) {
initialKeysAndAliases.forEach {
originalKeysMap[Crypto.toSupportedPublicKey(it.first)] = it.second
}
}

View File

@ -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)
}
}

View File

@ -4,7 +4,6 @@ import net.corda.core.identity.PartyAndCertificate
import net.corda.core.node.services.IdentityService
import net.corda.core.node.services.KeyManagementService
import org.bouncycastle.operator.ContentSigner
import java.security.KeyPair
import java.security.PublicKey
import java.util.*
@ -12,7 +11,7 @@ interface KeyManagementServiceInternal : KeyManagementService {
val identityService: IdentityService
fun start(initialKeyPairs: Set<KeyPair>)
fun start(initialKeysAndAliases: Iterable<Pair<PublicKey, String>>)
fun freshKeyInternal(externalId: UUID?): PublicKey

View File

@ -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_ROOT_CA
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.bouncycastle.BCCryptoService
import org.bouncycastle.asn1.x500.X500Name
@ -155,6 +156,50 @@ open class NetworkRegistrationHelper(
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 {
return if (cryptoService.containsKey(keyAlias)) {
cryptoService.getPublicKey(keyAlias)!!

View File

@ -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")
}
}

View File

@ -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))
}
}

View File

@ -65,7 +65,10 @@ class PersistentIdentityServiceTests {
)
identityService.database = database
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
@ -230,7 +233,7 @@ class PersistentIdentityServiceTests {
// Create new identity service mounted onto same DB
val newPersistentIdentityService = PersistentIdentityService(TestingNamedCacheFactory()).also {
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())

View File

@ -6,7 +6,6 @@ import net.corda.core.contracts.ContractClassName
import net.corda.core.contracts.StateRef
import net.corda.core.cordapp.CordappProvider
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.internal.AliasPrivateKey
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.StateMachineRunId
import net.corda.core.identity.CordaX500Name
@ -188,7 +187,7 @@ open class MockServices private constructor(
identityService.apply {
ourNames = setOf(initialIdentity.name)
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) }
}
@ -199,11 +198,11 @@ open class MockServices private constructor(
val aliasedMoreKeys = moreKeys.mapIndexed { index, keyPair ->
val alias = "Extra key $index"
aliasKeyMap[alias] = keyPair
KeyPair(keyPair.public, AliasPrivateKey(alias))
}.toSet()
keyPair.public to alias
}
val identityAlias = "${initialIdentity.name} private key"
aliasKeyMap[identityAlias] = initialIdentity.keyPair
val aliasedIdentityKey = KeyPair(initialIdentity.publicKey, AliasPrivateKey(identityAlias))
val aliasedIdentityKey = initialIdentity.publicKey to identityAlias
val keyManagementService = BasicHSMKeyManagementService(
TestingNamedCacheFactory(),
identityService,

View File

@ -384,7 +384,10 @@ class DriverDSLImpl(
NodeRegistrationConfiguration(config.corda),
HTTPNetworkRegistrationService(networkServicesConfig, versionInfo),
NodeRegistrationOption(rootTruststorePath, rootTruststorePassword)
).generateKeysAndRegister()
).apply {
generateKeysAndRegister()
generateNodeIdentity()
}
config
}
} else {

View File

@ -4,7 +4,6 @@ import com.nhaarman.mockito_kotlin.doReturn
import com.nhaarman.mockito_kotlin.whenever
import net.corda.common.configuration.parsing.internal.ConfigurationWithOptions
import net.corda.core.DoNotImplement
import net.corda.core.crypto.Crypto
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.random63BitValue
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.nodeapi.internal.DevIdentityGenerator
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.persistence.CordaPersistence
import net.corda.nodeapi.internal.persistence.DatabaseConfig
@ -65,10 +63,10 @@ import java.math.BigInteger
import java.net.URLClassLoader
import java.nio.file.Path
import java.nio.file.Paths
import java.security.PublicKey
import java.time.Clock
import java.util.concurrent.TimeUnit
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")
@ -334,8 +332,7 @@ open class InternalMockNetwork(cordappPackages: List<String> = emptyList(),
require(id >= 0) { "Node ID must be zero or positive, was passed: $id" }
}
private val entropyRoot = args.entropyRoot
var counter = entropyRoot
private val entropyCounter = AtomicReference(args.entropyRoot)
override val log get() = staticLog
override val transactionVerifierWorkerCount: Int get() = 1
@ -403,15 +400,7 @@ open class InternalMockNetwork(cordappPackages: List<String> = emptyList(),
//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 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
}
override fun initKeyStores() = keyStoreHandler.init(entropyCounter.updateAndGet { it.add(BigInteger.ONE) })
// 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.

View File

@ -41,8 +41,8 @@ class MockKeyManagementService(
override fun getSigner(publicKey: PublicKey): ContentSigner = net.corda.node.services.keys.getSigner(getSigningKeyPair(publicKey))
override fun start(initialKeyPairs: Set<KeyPair>) {
initialKeyPairs.forEach { keyStore[it.public] = it.private }
override fun start(initialKeysAndAliases: Iterable<Pair<PublicKey, String>>) {
throw NotImplementedError()
}
private fun getSigningKeyPair(publicKey: PublicKey): KeyPair {