CORDA-2563: Assign external IDs when creating new keys (#4727)

* First pass at fixing 2563.

* In memory KMS now maps keys to IDs.

* CreateDatabaseAndMockServices now creates a persistent key management service and a can take a persistent identity service, so now the external id mapping works for mock services.

* * Created a helper for mock services which allows the creation of a mock services with persistent identity management service key management service and vault.
* MockNode now uses persistent key management service - not sure why it didn't do before?

* * MockNode now uses BasicHSMKeyManagementService
* Updated api-current file

* Little fix required after rebase to master.

* Fixed broken test.

* Added informative error messages to UnsupportedOperationExceptions thrown by E2ETestKeyManagementService.

* Removed redundant private constructor for mock services from api-current.txt.

* Addressed Rick's comments.
This commit is contained in:
Roger Willis 2019-02-14 15:18:37 +00:00 committed by Tommy Lillehagen
parent aad1ae5945
commit 060bbb0a9d
12 changed files with 273 additions and 130 deletions

View File

@ -7745,7 +7745,7 @@ public class net.corda.testing.node.MockServices extends java.lang.Object implem
public <init>(net.corda.core.identity.CordaX500Name, net.corda.core.node.services.IdentityService)
public <init>(net.corda.core.identity.CordaX500Name, net.corda.core.node.services.IdentityService, int, kotlin.jvm.internal.DefaultConstructorMarker)
public <init>(net.corda.core.identity.CordaX500Name, net.corda.core.node.services.IdentityService, java.security.KeyPair, java.security.KeyPair...)
public <init>(net.corda.core.identity.CordaX500Name, net.corda.core.node.services.IdentityService, java.security.KeyPair, java.security.KeyPair[], int, kotlin.jvm.internal.DefaultConstructorMarker)
public <init>(net.corda.node.cordapp.CordappLoader, net.corda.core.node.services.IdentityService, net.corda.core.node.NetworkParameters, net.corda.testing.core.TestIdentity, java.security.KeyPair[], net.corda.core.node.services.KeyManagementService, kotlin.jvm.internal.DefaultConstructorMarker)
public <init>(net.corda.testing.core.TestIdentity, net.corda.core.node.NetworkParameters, net.corda.testing.core.TestIdentity...)
public <init>(net.corda.testing.core.TestIdentity, net.corda.testing.core.TestIdentity...)
public final void addMockCordapp(String)

View File

@ -7,10 +7,12 @@ import net.corda.core.crypto.DigitalSignature
import net.corda.core.crypto.SignableData
import net.corda.core.crypto.TransactionSignature
import net.corda.core.identity.PartyAndCertificate
import net.corda.core.node.ServiceHub
import java.security.KeyPair
import java.security.PrivateKey
import java.security.PublicKey
import java.security.cert.X509Certificate
import java.util.*
/**
* The KMS is responsible for storing and using private keys to sign things. An implementation of this may, for example,
@ -31,8 +33,15 @@ interface KeyManagementService {
fun freshKey(): PublicKey
/**
* Generates a new random [KeyPair], adds it to the internal key storage, then generates a corresponding
* [X509Certificate] and adds it to the identity service.
* Generates a new random [KeyPair] and adds it to the internal key storage. Associates the public key to an external ID. Returns the
* public key part of the pair.
*/
@Suspendable
fun freshKey(externalId: UUID): PublicKey
/**
* Generates a new random [KeyPair], adds it to the internal key storage, then generates a corresponding [X509Certificate] and adds it
* to the identity service. Associates the public key to an external ID. Returns the public part of the pair.
*
* @param identity identity to generate a key and certificate for. Must be an identity this node has CA privileges for.
* @param revocationEnabled whether to check revocation status of certificates in the certificate path.
@ -41,6 +50,18 @@ interface KeyManagementService {
@Suspendable
fun freshKeyAndCert(identity: PartyAndCertificate, revocationEnabled: Boolean): PartyAndCertificate
/**
* Generates a new random [KeyPair], adds it to the internal key storage, then generates a corresponding
* [X509Certificate] and adds it to the identity service.
*
* @param identity identity to generate a key and certificate for. Must be an identity this node has CA privileges for.
* @param revocationEnabled whether to check revocation status of certificates in the certificate path.
* @param externalId ID to associate the newly created [PublicKey] with.
* @return X.509 certificate and path to the trust root.
*/
@Suspendable
fun freshKeyAndCert(identity: PartyAndCertificate, revocationEnabled: Boolean, externalId: UUID): PartyAndCertificate
/**
* Filter some keys down to the set that this node owns (has private keys for).
*
@ -69,4 +90,4 @@ interface KeyManagementService {
*/
@Suspendable
fun sign(signableData: SignableData, publicKey: PublicKey): TransactionSignature
}
}

View File

@ -49,26 +49,7 @@ class BasicHSMKeyManagementService(cacheFactory: NamedCacheFactory, val identity
: this(publicKey.toStringShort(), publicKey.encoded, privateKey.encoded)
}
@Entity
@Table(name = "pk_hash_to_ext_id_map", indexes = [Index(name = "pk_hash_to_xid_idx", columnList = "public_key_hash")])
class PublicKeyHashToExternalId(
@Id
@GeneratedValue
@Column(name = "id", unique = true, nullable = false)
val key: Long?,
@Column(name = "external_id", nullable = false)
@Type(type = "uuid-char")
val externalId: UUID,
@Column(name = "public_key_hash", nullable = false)
val publicKeyHash: String
) {
constructor(accountId: UUID, publicKey: PublicKey)
: this(null, accountId, publicKey.toStringShort())
}
companion object {
private companion object {
fun createKeyMap(cacheFactory: NamedCacheFactory): AppendOnlyPersistentMap<PublicKey, PrivateKey, PersistentKey, String> {
return AppendOnlyPersistentMap(
cacheFactory = cacheFactory,
@ -116,10 +97,22 @@ class BasicHSMKeyManagementService(cacheFactory: NamedCacheFactory, val identity
return keyPair.public
}
override fun freshKey(externalId: UUID): PublicKey {
val newKey = freshKey()
database.transaction { session.persist(PublicKeyHashToExternalId(externalId, newKey)) }
return newKey
}
override fun freshKeyAndCert(identity: PartyAndCertificate, revocationEnabled: Boolean): PartyAndCertificate {
return freshCertificate(identityService, freshKey(), identity, getSigner(identity.owningKey))
}
override fun freshKeyAndCert(identity: PartyAndCertificate, revocationEnabled: Boolean, externalId: UUID): PartyAndCertificate {
val newKeyWithCert = freshKeyAndCert(identity, revocationEnabled)
database.transaction { session.persist(PublicKeyHashToExternalId(externalId, newKeyWithCert.owningKey)) }
return newKeyWithCert
}
private fun getSigner(publicKey: PublicKey): ContentSigner {
val signingPublicKey = getSigningPublicKey(publicKey)
return if (signingPublicKey in originalKeysMap) {

View File

@ -61,10 +61,18 @@ class E2ETestKeyManagementService(val identityService: IdentityService, private
return keyPair.public
}
override fun freshKey(externalId: UUID): PublicKey {
throw UnsupportedOperationException("This operation is only supported by persistent key management service variants.")
}
override fun freshKeyAndCert(identity: PartyAndCertificate, revocationEnabled: Boolean): PartyAndCertificate {
return freshCertificate(identityService, freshKey(), identity, getSigner(identity.owningKey))
}
override fun freshKeyAndCert(identity: PartyAndCertificate, revocationEnabled: Boolean, externalId: UUID): PartyAndCertificate {
throw UnsupportedOperationException("This operation is only supported by persistent key management service variants.")
}
private fun getSigner(publicKey: PublicKey): ContentSigner = getSigner(getSigningKeyPair(publicKey))
private fun getSigningKeyPair(publicKey: PublicKey): KeyPair {

View File

@ -1,8 +1,32 @@
package net.corda.node.services.keys
import net.corda.core.crypto.toStringShort
import net.corda.core.node.services.KeyManagementService
import org.hibernate.annotations.Type
import java.security.KeyPair
import java.security.PublicKey
import java.util.*
import javax.persistence.*
interface KeyManagementServiceInternal : KeyManagementService {
fun start(initialKeyPairs: Set<KeyPair>)
}
@Entity
@Table(name = "pk_hash_to_ext_id_map", indexes = [Index(name = "pk_hash_to_xid_idx", columnList = "public_key_hash")])
class PublicKeyHashToExternalId(
@Id
@GeneratedValue
@Column(name = "id", unique = true, nullable = false)
val key: Long?,
@Column(name = "external_id", nullable = false)
@Type(type = "uuid-char")
val externalId: UUID,
@Column(name = "public_key_hash", nullable = false)
val publicKeyHash: String
) {
constructor(accountId: UUID, publicKey: PublicKey)
: this(null, accountId, publicKey.toStringShort())
}

View File

@ -14,6 +14,7 @@ import org.bouncycastle.operator.ContentSigner
import java.security.KeyPair
import java.security.PrivateKey
import java.security.PublicKey
import java.util.*
import javax.persistence.*
/**
@ -82,10 +83,22 @@ class PersistentKeyManagementService(cacheFactory: NamedCacheFactory, val identi
return keyPair.public
}
override fun freshKey(externalId: UUID): PublicKey {
val newKey = freshKey()
database.transaction { session.persist(PublicKeyHashToExternalId(externalId, newKey)) }
return newKey
}
override fun freshKeyAndCert(identity: PartyAndCertificate, revocationEnabled: Boolean): PartyAndCertificate {
return freshCertificate(identityService, freshKey(), identity, getSigner(identity.owningKey))
}
override fun freshKeyAndCert(identity: PartyAndCertificate, revocationEnabled: Boolean, externalId: UUID): PartyAndCertificate {
val newKeyWithCert = freshKeyAndCert(identity, revocationEnabled)
database.transaction { session.persist(PublicKeyHashToExternalId(externalId, newKeyWithCert.owningKey)) }
return newKeyWithCert
}
private fun getSigner(publicKey: PublicKey): ContentSigner = getSigner(getSigningKeyPair(publicKey))
//It looks for the PublicKey in the (potentially) CompositeKey that is ours, and then returns the associated PrivateKey to use in signing

View File

@ -15,6 +15,7 @@ import net.corda.node.services.events.NodeSchedulerService
import net.corda.node.services.identity.PersistentIdentityService
import net.corda.node.services.keys.BasicHSMKeyManagementService
import net.corda.node.services.keys.PersistentKeyManagementService
import net.corda.node.services.keys.PublicKeyHashToExternalId
import net.corda.node.services.messaging.P2PMessageDeduplicator
import net.corda.node.services.persistence.DBCheckpointStorage
import net.corda.node.services.persistence.DBTransactionStorage
@ -45,7 +46,7 @@ class NodeSchemaService(private val extraSchemas: Set<MappedSchema> = emptySet()
PersistentIdentityService.PersistentIdentityNames::class.java,
ContractUpgradeServiceImpl.DBContractUpgrade::class.java,
DBNetworkParametersStorage.PersistentNetworkParameters::class.java,
BasicHSMKeyManagementService.PublicKeyHashToExternalId::class.java
PublicKeyHashToExternalId::class.java
)) {
override val migrationResource = "node-core.changelog-master"
}
@ -97,3 +98,5 @@ class NodeSchemaService(private val extraSchemas: Set<MappedSchema> = emptySet()
schemaOptions.keys.map { schema -> crossReferencesToOtherMappedSchema(schema) }.flatMap { it.toList() }
}

View File

@ -2,7 +2,10 @@ package net.corda.node.services.persistence
import co.paralleluniverse.fibers.Suspendable
import net.corda.core.contracts.Amount
import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party
import net.corda.core.node.services.KeyManagementService
import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.getOrThrow
@ -11,52 +14,56 @@ import net.corda.finance.`issued by`
import net.corda.finance.contracts.asset.Cash
import net.corda.finance.flows.AbstractCashFlow
import net.corda.finance.issuedBy
import net.corda.node.migration.VaultStateMigrationTest.Companion.bankOfCorda
import net.corda.node.services.identity.PersistentIdentityService
import net.corda.node.services.keys.BasicHSMKeyManagementService
import net.corda.node.services.keys.E2ETestKeyManagementService
import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.core.BOC_NAME
import net.corda.testing.core.SerializationEnvironmentRule
import net.corda.testing.core.TestIdentity
import net.corda.testing.internal.TestingNamedCacheFactory
import net.corda.testing.node.InMemoryMessagingNetwork.ServicePeerAllocationStrategy.RoundRobin
import net.corda.testing.node.MockNetwork
import net.corda.testing.node.MockNetworkParameters
import net.corda.testing.node.MockServices
import net.corda.testing.node.StartedMockNode
import net.corda.testing.node.internal.FINANCE_CORDAPPS
import net.corda.testing.node.internal.InternalMockNetwork
import net.corda.testing.node.internal.TestStartedNode
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import java.util.*
import kotlin.test.assertEquals
class HibernateColumnConverterTests {
private lateinit var mockNet: MockNetwork
private lateinit var bankOfCordaNode: StartedMockNode
private lateinit var bankOfCorda: Party
private lateinit var notary: Party
class TestCashIssueFlow(private val amount: Amount<Currency>,
private val issuerBankPartyRef: OpaqueBytes,
private val notary: Party) : AbstractCashFlow<AbstractCashFlow.Result>(tracker()) {
@Suspendable
override fun call(): AbstractCashFlow.Result {
val builder = TransactionBuilder(notary)
val issuer = ourIdentity.ref(issuerBankPartyRef)
val signers = Cash().generateIssue(builder, amount.issuedBy(issuer), ourIdentity, notary)
val tx = serviceHub.signInitialTransaction(builder, signers)
serviceHub.recordTransactions(tx)
return Result(tx, ourIdentity)
}
}
@Rule
@JvmField
val testSerialization = SerializationEnvironmentRule()
private val cordapps = listOf("net.corda.finance")
private val myself = TestIdentity(CordaX500Name("Me", "London", "GB"))
private val notary = TestIdentity(CordaX500Name("NotaryService", "London", "GB"), 1337L)
lateinit var services: MockServices
lateinit var database: CordaPersistence
@Before
fun start() {
mockNet = MockNetwork(MockNetworkParameters(servicePeerAllocationStrategy = RoundRobin(), cordappsForAllNodes = FINANCE_CORDAPPS))
bankOfCordaNode = mockNet.createPartyNode(BOC_NAME)
bankOfCorda = bankOfCordaNode.info.identityFromX500Name(BOC_NAME)
notary = mockNet.defaultNotaryIdentity
}
@After
fun cleanUp() {
mockNet.stopNodes()
fun setUp() {
val (db, mockServices) = MockServices.makeTestDatabaseAndPersistentServices(
cordappPackages = cordapps,
initialIdentity = myself,
networkParameters = testNetworkParameters(minimumPlatformVersion = 4),
moreIdentities = setOf(notary.identity),
moreKeys = emptySet()
)
services = mockServices
database = db
}
// AbstractPartyToX500NameAsStringConverter could cause circular flush of Hibernate session because it is invoked during flush, and a
@ -68,20 +75,24 @@ class HibernateColumnConverterTests {
// Create parallel set of key and identity services so that the values are not cached, forcing the node caches to do a lookup.
val identityService = PersistentIdentityService(TestingNamedCacheFactory())
val originalIdentityService: PersistentIdentityService = bankOfCordaNode.services.identityService as PersistentIdentityService
val originalIdentityService: PersistentIdentityService = services.identityService as PersistentIdentityService
identityService.database = originalIdentityService.database
identityService.start(originalIdentityService.trustRoot)
val keyService = E2ETestKeyManagementService(identityService)
keyService.start((bankOfCordaNode.services.keyManagementService as E2ETestKeyManagementService).keyPairs)
keyService.start(setOf(myself.keyPair))
// New identity for a notary (doesn't matter that it's for Bank Of Corda... since not going to use it as an actual notary etc).
val newKeyAndCert = keyService.freshKeyAndCert(bankOfCordaNode.info.legalIdentitiesAndCerts[0], false)
val randomNotary = Party(BOC_NAME, newKeyAndCert.owningKey)
val newKeyAndCert = keyService.freshKeyAndCert(services.myInfo.legalIdentitiesAndCerts[0], false)
val randomNotary = Party(myself.name, newKeyAndCert.owningKey)
val future = bankOfCordaNode.startFlow(TestCashIssueFlow(expected, ref, randomNotary))
mockNet.runNetwork()
val issueTx = future.getOrThrow().stx
val output = issueTx.tx.outputsOfType<Cash.State>().single()
assertEquals(expected.`issued by`(bankOfCorda.ref(ref)), output.amount)
val ourIdentity = services.myInfo.legalIdentities.first()
val builder = TransactionBuilder(notary.party)
val issuer = services.myInfo.legalIdentities.first().ref(ref)
val signers = Cash().generateIssue(builder, expected.issuedBy(issuer), ourIdentity, randomNotary)
val tx: SignedTransaction = services.signInitialTransaction(builder, signers)
services.recordTransactions(tx)
val output = tx.tx.outputsOfType<Cash.State>().single()
assertEquals(expected.`issued by`(ourIdentity.ref(ref)), output.amount)
}
}

View File

@ -1,8 +1,5 @@
package net.corda.node.services.vault
import com.nhaarman.mockito_kotlin.doReturn
import com.nhaarman.mockito_kotlin.mock
import com.nhaarman.mockito_kotlin.whenever
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.AnonymousParty
import net.corda.core.identity.CordaX500Name
@ -10,15 +7,12 @@ import net.corda.core.node.services.queryBy
import net.corda.core.node.services.vault.QueryCriteria
import net.corda.core.node.services.vault.builder
import net.corda.core.transactions.TransactionBuilder
import net.corda.node.services.api.IdentityServiceInternal
import net.corda.node.services.keys.BasicHSMKeyManagementService
import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.contracts.DummyContract
import net.corda.testing.contracts.DummyState
import net.corda.testing.core.SerializationEnvironmentRule
import net.corda.testing.core.TestIdentity
import net.corda.testing.internal.rigorousMock
import net.corda.testing.node.MockServices
import org.junit.Before
import org.junit.Rule
@ -45,41 +39,17 @@ class ExternalIdMappingTest {
@Before
fun setUp() {
val (db, mockServices) = MockServices.makeTestDatabaseAndMockServices(
val (db, mockServices) = MockServices.makeTestDatabaseAndPersistentServices(
cordappPackages = cordapps,
identityService = mock<IdentityServiceInternal>().also {
doReturn(notary.party).whenever(it).partyFromKey(notary.publicKey)
doReturn(notary.party).whenever(it).wellKnownPartyFromAnonymous(notary.party)
doReturn(notary.party).whenever(it).wellKnownPartyFromX500Name(notary.name)
},
initialIdentity = myself,
networkParameters = testNetworkParameters(minimumPlatformVersion = 4)
networkParameters = testNetworkParameters(minimumPlatformVersion = 4),
moreIdentities = setOf(notary.identity),
moreKeys = emptySet()
)
services = mockServices
database = db
}
private fun freshKeyForExternalId(externalId: UUID): AnonymousParty {
val anonymousParty = freshKey()
database.transaction {
services.withEntityManager {
val mapping = BasicHSMKeyManagementService.PublicKeyHashToExternalId(externalId, anonymousParty.owningKey)
persist(mapping)
flush()
}
}
return anonymousParty
}
private fun freshKey(): AnonymousParty {
val key = services.keyManagementService.freshKey()
val anonymousParty = AnonymousParty(key)
// Add behaviour to the mock identity management service for dealing with the new key.
// It won't be able to resolve it as it's just an anonymous key that is not linked to an identity.
services.identityService.also { doReturn(null).whenever(it).wellKnownPartyFromAnonymous(anonymousParty) }
return anonymousParty
}
private fun createDummyState(participants: List<AbstractParty>): DummyState {
val tx = TransactionBuilder(notary = notary.party).apply {
addOutputState(DummyState(1, participants), DummyContract.PROGRAM_ID)
@ -95,11 +65,11 @@ class ExternalIdMappingTest {
val vaultService = services.vaultService
// Create new external ID and two keys mapped to it.
val id = UUID.randomUUID()
val keyOne = freshKeyForExternalId(id)
val keyTwo = freshKeyForExternalId(id)
val keyOne = services.keyManagementService.freshKeyAndCert(myself.identity, false, id)
val keyTwo = services.keyManagementService.freshKeyAndCert(myself.identity, false, id)
// Create states with a public key assigned to the new external ID.
val dummyStateOne = createDummyState(listOf(keyOne))
val dummyStateTwo = createDummyState(listOf(keyTwo))
val dummyStateOne = createDummyState(listOf(AnonymousParty(keyOne.owningKey)))
val dummyStateTwo = createDummyState(listOf(AnonymousParty(keyTwo.owningKey)))
// This query should return two states!
val result = database.transaction {
val externalId = builder { VaultSchemaV1.StateToExternalId::externalId.`in`(listOf(id)) }
@ -122,11 +92,11 @@ class ExternalIdMappingTest {
val vaultService = services.vaultService
// Create new external ID.
val idOne = UUID.randomUUID()
val keyOne = freshKeyForExternalId(idOne)
val keyOne = services.keyManagementService.freshKeyAndCert(myself.identity, false, idOne)
val idTwo = UUID.randomUUID()
val keyTwo = freshKeyForExternalId(idTwo)
val keyTwo = services.keyManagementService.freshKeyAndCert(myself.identity, false, idTwo)
// Create state with a public key assigned to the new external ID.
val dummyState = createDummyState(listOf(keyOne, keyTwo))
val dummyState = createDummyState(listOf(AnonymousParty(keyOne.owningKey), AnonymousParty(keyTwo.owningKey)))
// This query should return one state!
val result = database.transaction {
val externalId = builder { VaultSchemaV1.StateToExternalId::externalId.`in`(listOf(idOne, idTwo)) }

View File

@ -26,6 +26,8 @@ import net.corda.node.internal.ServicesForResolutionImpl
import net.corda.node.internal.cordapp.JarScanningCordappLoader
import net.corda.node.services.api.*
import net.corda.node.services.identity.InMemoryIdentityService
import net.corda.node.services.identity.PersistentIdentityService
import net.corda.node.services.keys.PersistentKeyManagementService
import net.corda.node.services.schema.NodeSchemaService
import net.corda.node.services.transactions.InMemoryTransactionVerifierService
import net.corda.node.services.vault.NodeVaultService
@ -36,6 +38,7 @@ import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.core.TestIdentity
import net.corda.testing.internal.DEV_ROOT_CA
import net.corda.testing.internal.MockCordappProvider
import net.corda.testing.internal.TestingNamedCacheFactory
import net.corda.testing.internal.configureDatabase
import net.corda.testing.node.internal.*
import net.corda.testing.services.MockAttachmentStorage
@ -71,7 +74,8 @@ open class MockServices private constructor(
override val identityService: IdentityService,
private val initialNetworkParameters: NetworkParameters,
private val initialIdentity: TestIdentity,
private val moreKeys: Array<out KeyPair>
private val moreKeys: Array<out KeyPair>,
override val keyManagementService: KeyManagementService = MockKeyManagementService(identityService, *arrayOf(initialIdentity.keyPair) + moreKeys)
) : ServiceHub {
companion object {
@ -116,32 +120,91 @@ open class MockServices private constructor(
val dataSourceProps = makeTestDataSourceProperties()
val schemaService = NodeSchemaService(cordappLoader.cordappSchemas)
val database = configureDatabase(dataSourceProps, DatabaseConfig(), identityService::wellKnownPartyFromX500Name, identityService::wellKnownPartyFromAnonymous, schemaService, schemaService.internalSchemas())
val keyManagementService = MockKeyManagementService(identityService, *arrayOf(initialIdentity.keyPair) + moreKeys)
val mockService = database.transaction {
object : MockServices(cordappLoader, identityService, networkParameters, initialIdentity, moreKeys) {
override var networkParametersService: NetworkParametersService = MockNetworkParametersStorage(networkParameters)
override val vaultService: VaultService = makeVaultService(schemaService, database, cordappLoader)
override fun recordTransactions(statesToRecord: StatesToRecord, txs: Iterable<SignedTransaction>) {
ServiceHubInternal.recordTransactions(statesToRecord, txs,
validatedTransactions as WritableTransactionStorage,
mockStateMachineRecordedTransactionMappingStorage,
vaultService as VaultServiceInternal,
database)
}
override fun jdbcSession(): Connection = database.createSession()
override fun <T : Any> withEntityManager(block: EntityManager.() -> T): T {
return block(contextTransaction.restrictedEntityManager)
}
override fun withEntityManager(block: Consumer<EntityManager>) {
return block.accept(contextTransaction.restrictedEntityManager)
}
}
makeMockMockServices(cordappLoader, identityService, networkParameters, initialIdentity, moreKeys.toSet(), keyManagementService, schemaService, database)
}
return Pair(database, mockService)
}
/**
* Makes database and persistent services appropriate for unit tests which require persistence across the vault, identity service
* and key managment service.
*
* @param cordappPackages A [List] of cordapp packages to scan for any cordapp code, e.g. contract verification code, flows and services.
* @param initialIdentity The first (typically sole) identity the services will represent.
* @param moreKeys A list of additional [KeyPair] instances to be used by [MockServices].
* @param moreIdentities A list of additional [KeyPair] instances to be used by [MockServices].
* @return A pair where the first element is the instance of [CordaPersistence] and the second is [MockServices].
*/
@JvmStatic
@JvmOverloads
fun makeTestDatabaseAndPersistentServices(
cordappPackages: List<String>,
initialIdentity: TestIdentity,
networkParameters: NetworkParameters = testNetworkParameters(modifiedTime = Instant.MIN),
moreKeys: Set<KeyPair>,
moreIdentities: Set<PartyAndCertificate>
): Pair<CordaPersistence, MockServices> {
val cordappLoader = cordappLoaderForPackages(cordappPackages)
val dataSourceProps = makeTestDataSourceProperties()
val schemaService = NodeSchemaService(cordappLoader.cordappSchemas)
val identityService = PersistentIdentityService(TestingNamedCacheFactory())
val persistence = configureDatabase(dataSourceProps, DatabaseConfig(), identityService::wellKnownPartyFromX500Name, identityService::wellKnownPartyFromAnonymous, schemaService, schemaService.internalSchemas())
// Create a persistent identity service and add all the supplied identities.
identityService.apply {
ourNames = setOf(initialIdentity.name)
database = persistence
start(DEV_ROOT_CA.certificate)
persistence.transaction { identityService.loadIdentities(moreIdentities + initialIdentity.identity) }
}
// Create a persistent key management service and add the key pair which was created for the TestIdentity.
// We only add the keypair for the initial identity and any other keys which this node may control. Note: We don't add the keys
// for the other identities.
val keyManagementService = PersistentKeyManagementService(TestingNamedCacheFactory(), identityService, persistence)
persistence.transaction { keyManagementService.start(moreKeys + initialIdentity.keyPair) }
val mockService = persistence.transaction {
makeMockMockServices(cordappLoader, identityService, networkParameters, initialIdentity, moreKeys, keyManagementService, schemaService, persistence)
}
return Pair(persistence, mockService)
}
private fun makeMockMockServices(
cordappLoader: CordappLoader,
identityService: IdentityService,
networkParameters: NetworkParameters,
initialIdentity: TestIdentity,
moreKeys: Set<KeyPair>,
keyManagementService: KeyManagementService,
schemaService: SchemaService,
persistence: CordaPersistence
): MockServices {
return object : MockServices(cordappLoader, identityService, networkParameters, initialIdentity, moreKeys.toTypedArray(), keyManagementService) {
override var networkParametersService: NetworkParametersService = MockNetworkParametersStorage(networkParameters)
override val vaultService: VaultService = makeVaultService(schemaService, persistence, cordappLoader)
override fun recordTransactions(statesToRecord: StatesToRecord, txs: Iterable<SignedTransaction>) {
ServiceHubInternal.recordTransactions(statesToRecord, txs,
validatedTransactions as WritableTransactionStorage,
mockStateMachineRecordedTransactionMappingStorage,
vaultService as VaultServiceInternal,
persistence)
}
override fun jdbcSession(): Connection = persistence.createSession()
override fun <T : Any> withEntityManager(block: EntityManager.() -> T): T {
return block(contextTransaction.restrictedEntityManager)
}
override fun withEntityManager(block: Consumer<EntityManager>) {
return block.accept(contextTransaction.restrictedEntityManager)
}
}
}
// Because Kotlin is dumb and makes not publicly visible objects public, thus changing the public API.
private val mockStateMachineRecordedTransactionMappingStorage = MockStateMachineRecordedTransactionMappingStorage()
@ -175,8 +238,18 @@ open class MockServices private constructor(
}
private constructor(cordappLoader: CordappLoader, identityService: IdentityService, networkParameters: NetworkParameters,
initialIdentity: TestIdentity, moreKeys: Array<out KeyPair>)
: this(cordappLoader, MockTransactionStorage(), identityService, networkParameters, initialIdentity, moreKeys)
initialIdentity: TestIdentity, moreKeys: Array<out KeyPair>, keyManagementService: KeyManagementService)
: this(cordappLoader, MockTransactionStorage(), identityService, networkParameters, initialIdentity, moreKeys, keyManagementService)
private constructor(cordappLoader: CordappLoader, identityService: IdentityService, networkParameters: NetworkParameters,
initialIdentity: TestIdentity, moreKeys: Array<out KeyPair>) : this(
cordappLoader,
MockTransactionStorage(),
identityService,
networkParameters,
initialIdentity,
moreKeys
)
/**
* Create a mock [ServiceHub] that looks for app code in the given package names, uses the provided identity service
@ -196,6 +269,14 @@ open class MockServices private constructor(
vararg moreKeys: KeyPair) :
this(cordappLoaderForPackages(cordappPackages), identityService, networkParameters, initialIdentity, moreKeys)
constructor(cordappPackages: Iterable<String>,
initialIdentity: TestIdentity,
identityService: IdentityService,
networkParameters: NetworkParameters,
vararg moreKeys: KeyPair,
keyManagementService: KeyManagementService) :
this(cordappLoaderForPackages(cordappPackages), identityService, networkParameters, initialIdentity, moreKeys, keyManagementService)
/**
* Create a mock [ServiceHub] that looks for app code in the given package names, uses the provided identity service
* (you can get one from [makeTestIdentityService]) and represents the given identity.
@ -298,7 +379,6 @@ open class MockServices private constructor(
get() = networkParametersService.run { lookup(currentHash)!! }
final override val attachments = MockAttachmentStorage()
override val keyManagementService: KeyManagementService by lazy { MockKeyManagementService(identityService, *arrayOf(initialIdentity.keyPair) + moreKeys) }
override val vaultService: VaultService get() = throw UnsupportedOperationException()
override val contractUpgradeService: ContractUpgradeService get() = throw UnsupportedOperationException()
override val networkMapCache: NetworkMapCache get() = throw UnsupportedOperationException()

View File

@ -34,7 +34,7 @@ import net.corda.node.services.api.ServiceHubInternal
import net.corda.node.services.api.StartedNodeServices
import net.corda.node.services.config.*
import net.corda.node.services.identity.PersistentIdentityService
import net.corda.node.services.keys.E2ETestKeyManagementService
import net.corda.node.services.keys.BasicHSMKeyManagementService
import net.corda.node.services.keys.KeyManagementServiceInternal
import net.corda.node.services.keys.cryptoservice.BCCryptoService
import net.corda.node.services.messaging.Message
@ -373,7 +373,7 @@ open class InternalMockNetwork(cordappPackages: List<String> = emptyList(),
}
override fun makeKeyManagementService(identityService: PersistentIdentityService): KeyManagementServiceInternal {
return E2ETestKeyManagementService(identityService, cryptoService)
return BasicHSMKeyManagementService(cacheFactory, identityService, database, cryptoService)
}
override fun startShell() {

View File

@ -11,6 +11,7 @@ import java.security.KeyPair
import java.security.PrivateKey
import java.security.PublicKey
import java.util.*
import java.util.concurrent.ConcurrentHashMap
/**
* A class which provides an implementation of [KeyManagementService] which is used in [MockServices]
@ -25,12 +26,31 @@ class MockKeyManagementService(val identityService: IdentityService,
private val nextKeys = LinkedList<KeyPair>()
val keysById: MutableMap<UUID, Set<PublicKey>> = ConcurrentHashMap()
override fun freshKey(): PublicKey {
val k = nextKeys.poll() ?: generateKeyPair()
keyStore[k.public] = k.private
return k.public
}
private fun mapKeyToId(publicKey: PublicKey, externalId: UUID) {
val keysForId = keysById.getOrPut(externalId) { emptySet() }
keysById[externalId] = keysForId + publicKey
}
override fun freshKey(externalId: UUID): PublicKey {
val key = freshKey()
mapKeyToId(key, externalId)
return key
}
override fun freshKeyAndCert(identity: PartyAndCertificate, revocationEnabled: Boolean, externalId: UUID): PartyAndCertificate {
val keyAndCert = freshKeyAndCert(identity, revocationEnabled)
mapKeyToId(keyAndCert.owningKey, externalId)
return keyAndCert
}
override fun filterMyKeys(candidateKeys: Iterable<PublicKey>): Iterable<PublicKey> = candidateKeys.filter { it in this.keys }
override fun freshKeyAndCert(identity: PartyAndCertificate, revocationEnabled: Boolean): PartyAndCertificate {