From 07b96aea18745070e5f1d3300553756a91d3fd4b Mon Sep 17 00:00:00 2001 From: willhr3 <48725248+willhr3@users.noreply.github.com> Date: Thu, 29 Aug 2019 11:57:07 +0100 Subject: [PATCH] CORDA-2925 Rebase identity service changes onto 4.3 (#5407) * CORDA-2925 Rebase identity service changes onto 4.3 * CORDA-2925 Move migration to after v13 * CORDA-2925 Update schema list * Change corda-version --- .../data/TransactionGenerator.kt | 10 +- .../contracts/ConstraintsPropagationTests.kt | 4 +- .../PackageOwnershipVerificationTests.kt | 4 +- .../coretests/crypto/PartialMerkleTreeTest.kt | 4 +- .../flows/CollectSignaturesFlowTests.kt | 4 +- .../LedgerTransactionQueryTests.kt | 4 +- .../transactions/ReferenceInputStateTests.kt | 4 +- .../TransactionEncumbranceTests.kt | 4 +- .../net/corda/core/identity/IdentityUtils.kt | 9 + .../core/node/services/IdentityService.kt | 17 +- .../corda/finance/contracts/universal/Cap.kt | 5 +- .../contracts/asset/CashTestsJava.java | 8 +- .../contracts/asset/ObligationTests.kt | 3 +- .../net/corda/node/internal/AbstractNode.kt | 2 +- .../migration/MigrationNamedCacheFactory.kt | 5 +- .../PersistentIdentityMigrationNewTable.kt | 198 ++++++++++++++++++ .../node/migration/VaultStateMigration.kt | 5 +- .../identity/InMemoryIdentityService.kt | 82 ++++++-- .../identity/PersistentIdentityService.kt | 176 ++++++++++++---- .../PublicKeyToOwningIdentityCacheImpl.kt | 4 +- .../node/services/schema/NodeSchemaService.kt | 5 +- .../corda/node/utilities/NodeNamedCache.kt | 5 +- .../migration/node-core.changelog-master.xml | 1 + .../migration/node-core.changelog-v12.xml | 1 - .../migration/node-core.changelog-v14.xml | 23 ++ .../services/vault/VaultQueryJavaTests.java | 11 +- ...entityServiceToStringShortMigrationTest.kt | 4 +- ...PersistentIdentityMigrationNewTableTest.kt | 140 +++++++++++++ .../node/migration/VaultStateMigrationTest.kt | 4 +- .../PersistentIdentityServiceTests.kt | 35 +++- .../persistence/HibernateConfigurationTest.kt | 9 +- .../services/vault/NodeVaultServiceTest.kt | 36 ++-- .../kotlin/net/corda/irs/contract/IRSTests.kt | 6 +- 33 files changed, 692 insertions(+), 140 deletions(-) create mode 100644 node/src/main/kotlin/net/corda/node/migration/PersistentIdentityMigrationNewTable.kt create mode 100644 node/src/main/resources/migration/node-core.changelog-v14.xml create mode 100644 node/src/test/kotlin/net/corda/node/migration/PersistentIdentityMigrationNewTableTest.kt diff --git a/core-deterministic/testing/data/src/test/kotlin/net/corda/deterministic/data/TransactionGenerator.kt b/core-deterministic/testing/data/src/test/kotlin/net/corda/deterministic/data/TransactionGenerator.kt index 4800c454a5..87b7dbb019 100644 --- a/core-deterministic/testing/data/src/test/kotlin/net/corda/deterministic/data/TransactionGenerator.kt +++ b/core-deterministic/testing/data/src/test/kotlin/net/corda/deterministic/data/TransactionGenerator.kt @@ -7,20 +7,20 @@ import net.corda.core.crypto.entropyToKeyPair import net.corda.core.identity.AnonymousParty import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party +import net.corda.core.node.services.IdentityService import net.corda.core.serialization.serialize import net.corda.deterministic.verifier.MockContractAttachment import net.corda.deterministic.verifier.SampleCommandData import net.corda.deterministic.verifier.TransactionVerificationRequest import net.corda.finance.POUNDS import net.corda.finance.`issued by` -import net.corda.finance.contracts.asset.Cash.* -import net.corda.finance.contracts.asset.Cash.Commands.* +import net.corda.finance.contracts.asset.Cash.Commands.Issue +import net.corda.finance.contracts.asset.Cash.Commands.Move import net.corda.finance.contracts.asset.Cash.Companion.PROGRAM_ID -import net.corda.node.services.api.IdentityServiceInternal +import net.corda.finance.contracts.asset.Cash.State import net.corda.testing.core.DUMMY_NOTARY_NAME import net.corda.testing.core.TestIdentity import net.corda.testing.core.getTestPartyAndCertificate -import net.corda.testing.internal.rigorousMock import net.corda.testing.node.MockServices import net.corda.testing.node.ledger import java.io.OutputStream @@ -40,7 +40,7 @@ object TransactionGenerator { private val MEGA_CORP_PUBKEY: PublicKey = megaCorp.keyPair.public private val MINI_CORP_PUBKEY: PublicKey = TestIdentity(CordaX500Name("MiniCorp", "London", "GB")).keyPair.public - private val ledgerServices = MockServices(emptyList(), MEGA_CORP.name, mock().also { + private val ledgerServices = MockServices(emptyList(), MEGA_CORP.name, mock().also { doReturn(MEGA_CORP).whenever(it).partyFromKey(MEGA_CORP_PUBKEY) doReturn(DUMMY_CASH_ISSUER.party).whenever(it).partyFromKey(DUMMY_CASH_ISSUER_KEY.public) }) diff --git a/core-tests/src/test/kotlin/net/corda/coretests/contracts/ConstraintsPropagationTests.kt b/core-tests/src/test/kotlin/net/corda/coretests/contracts/ConstraintsPropagationTests.kt index 8c6fcb0f6c..e061127638 100644 --- a/core-tests/src/test/kotlin/net/corda/coretests/contracts/ConstraintsPropagationTests.kt +++ b/core-tests/src/test/kotlin/net/corda/coretests/contracts/ConstraintsPropagationTests.kt @@ -16,13 +16,13 @@ import net.corda.core.internal.canBeTransitionedFrom import net.corda.core.internal.inputStream import net.corda.core.internal.toPath import net.corda.core.node.NotaryInfo +import net.corda.core.node.services.IdentityService import net.corda.core.transactions.LedgerTransaction import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.WireTransaction import net.corda.finance.POUNDS import net.corda.finance.`issued by` import net.corda.finance.contracts.asset.Cash -import net.corda.node.services.api.IdentityServiceInternal import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.core.ALICE_NAME import net.corda.testing.core.DUMMY_NOTARY_NAME @@ -82,7 +82,7 @@ class ConstraintsPropagationTests { ledgerServices = object : MockServices( cordappPackages = listOf("net.corda.finance.contracts.asset"), initialIdentity = ALICE, - identityService = mock().also { + identityService = mock().also { doReturn(ALICE_PARTY).whenever(it).partyFromKey(ALICE_PUBKEY) doReturn(BOB_PARTY).whenever(it).partyFromKey(BOB_PUBKEY) }, diff --git a/core-tests/src/test/kotlin/net/corda/coretests/contracts/PackageOwnershipVerificationTests.kt b/core-tests/src/test/kotlin/net/corda/coretests/contracts/PackageOwnershipVerificationTests.kt index 6106c9c8d2..96ad5d6b16 100644 --- a/core-tests/src/test/kotlin/net/corda/coretests/contracts/PackageOwnershipVerificationTests.kt +++ b/core-tests/src/test/kotlin/net/corda/coretests/contracts/PackageOwnershipVerificationTests.kt @@ -9,8 +9,8 @@ import net.corda.core.crypto.SecureHash import net.corda.core.identity.AbstractParty import net.corda.core.identity.CordaX500Name import net.corda.core.node.NotaryInfo +import net.corda.core.node.services.IdentityService import net.corda.core.transactions.LedgerTransaction -import net.corda.node.services.api.IdentityServiceInternal import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.core.DUMMY_NOTARY_NAME import net.corda.testing.core.SerializationEnvironmentRule @@ -41,7 +41,7 @@ class PackageOwnershipVerificationTests { private val ledgerServices = MockServices( cordappPackages = listOf("net.corda.finance.contracts.asset"), initialIdentity = ALICE, - identityService = mock().also { + identityService = mock().also { doReturn(ALICE_PARTY).whenever(it).partyFromKey(ALICE_PUBKEY) doReturn(BOB_PARTY).whenever(it).partyFromKey(BOB_PUBKEY) }, diff --git a/core-tests/src/test/kotlin/net/corda/coretests/crypto/PartialMerkleTreeTest.kt b/core-tests/src/test/kotlin/net/corda/coretests/crypto/PartialMerkleTreeTest.kt index 30c2916bcc..f029be1f25 100644 --- a/core-tests/src/test/kotlin/net/corda/coretests/crypto/PartialMerkleTreeTest.kt +++ b/core-tests/src/test/kotlin/net/corda/coretests/crypto/PartialMerkleTreeTest.kt @@ -10,6 +10,7 @@ import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party import net.corda.core.internal.accessLeafIndex import net.corda.core.node.NotaryInfo +import net.corda.core.node.services.IdentityService import net.corda.core.serialization.deserialize import net.corda.core.serialization.serialize import net.corda.core.transactions.ReferenceStateRef @@ -17,7 +18,6 @@ import net.corda.core.transactions.WireTransaction import net.corda.finance.DOLLARS import net.corda.finance.`issued by` import net.corda.finance.contracts.asset.Cash -import net.corda.node.services.api.IdentityServiceInternal import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.core.DUMMY_NOTARY_NAME import net.corda.testing.core.SerializationEnvironmentRule @@ -70,7 +70,7 @@ class PartialMerkleTreeTest { testLedger = MockServices( cordappPackages = emptyList(), initialIdentity = TestIdentity(MEGA_CORP.name), - identityService = mock().also { + identityService = mock().also { doReturn(MEGA_CORP).whenever(it).partyFromKey(MEGA_CORP_PUBKEY) }, networkParameters = testNetworkParameters(minimumPlatformVersion = 4, notaries = listOf(NotaryInfo(DUMMY_NOTARY, true))) diff --git a/core-tests/src/test/kotlin/net/corda/coretests/flows/CollectSignaturesFlowTests.kt b/core-tests/src/test/kotlin/net/corda/coretests/flows/CollectSignaturesFlowTests.kt index 02d9693e52..d6c82fcde6 100644 --- a/core-tests/src/test/kotlin/net/corda/coretests/flows/CollectSignaturesFlowTests.kt +++ b/core-tests/src/test/kotlin/net/corda/coretests/flows/CollectSignaturesFlowTests.kt @@ -202,7 +202,7 @@ class AnonymousSessionTestFlow(private val cis: List) : Flo for (ci in cis) { if (ci.name != ourIdentity.name) { - (serviceHub.identityService as IdentityServiceInternal).verifyAndRegisterIdentity(ci) + serviceHub.identityService.verifyAndRegisterIdentity(ci) } } val state = DummyContract.MultiOwnerState(owners = cis.map { AnonymousParty(it.owningKey) }) @@ -240,7 +240,7 @@ class MixAndMatchAnonymousSessionTestFlow(private val cis: List().also { + mock().also { doReturn(null).whenever(it).partyFromKey(keyPair.public) }, testNetworkParameters(notaries = listOf(NotaryInfo(DUMMY_NOTARY, true))), diff --git a/core-tests/src/test/kotlin/net/corda/coretests/transactions/ReferenceInputStateTests.kt b/core-tests/src/test/kotlin/net/corda/coretests/transactions/ReferenceInputStateTests.kt index 7588d9919a..e24ad8072c 100644 --- a/core-tests/src/test/kotlin/net/corda/coretests/transactions/ReferenceInputStateTests.kt +++ b/core-tests/src/test/kotlin/net/corda/coretests/transactions/ReferenceInputStateTests.kt @@ -10,12 +10,12 @@ import net.corda.core.identity.AbstractParty import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party import net.corda.core.node.NotaryInfo +import net.corda.core.node.services.IdentityService import net.corda.core.transactions.LedgerTransaction import net.corda.core.transactions.TransactionBuilder import net.corda.finance.DOLLARS import net.corda.finance.`issued by` import net.corda.finance.contracts.asset.Cash -import net.corda.node.services.api.IdentityServiceInternal import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.core.DUMMY_NOTARY_NAME import net.corda.testing.core.SerializationEnvironmentRule @@ -48,7 +48,7 @@ class ReferenceStateTests { private val ledgerServices = MockServices( cordappPackages = listOf("net.corda.coretests.transactions", "net.corda.finance.contracts.asset"), initialIdentity = ALICE, - identityService = mock().also { + identityService = mock().also { doReturn(ALICE_PARTY).whenever(it).partyFromKey(ALICE_PUBKEY) doReturn(BOB_PARTY).whenever(it).partyFromKey(BOB_PUBKEY) }, diff --git a/core-tests/src/test/kotlin/net/corda/coretests/transactions/TransactionEncumbranceTests.kt b/core-tests/src/test/kotlin/net/corda/coretests/transactions/TransactionEncumbranceTests.kt index 3fb91fb95a..0ed4b0758a 100644 --- a/core-tests/src/test/kotlin/net/corda/coretests/transactions/TransactionEncumbranceTests.kt +++ b/core-tests/src/test/kotlin/net/corda/coretests/transactions/TransactionEncumbranceTests.kt @@ -7,12 +7,12 @@ import net.corda.core.contracts.* import net.corda.core.identity.AbstractParty import net.corda.core.identity.CordaX500Name import net.corda.core.node.NotaryInfo +import net.corda.core.node.services.IdentityService import net.corda.core.transactions.LedgerTransaction import net.corda.core.transactions.TransactionBuilder import net.corda.finance.DOLLARS import net.corda.finance.`issued by` import net.corda.finance.contracts.asset.Cash -import net.corda.node.services.api.IdentityServiceInternal import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.core.DUMMY_NOTARY_NAME import net.corda.testing.core.SerializationEnvironmentRule @@ -58,7 +58,7 @@ class TransactionEncumbranceTests { val ledgerServices = MockServices( listOf("net.corda.core.transactions", "net.corda.finance.contracts.asset"), MEGA_CORP.name, - mock().also { + mock().also { doReturn(MEGA_CORP).whenever(it).partyFromKey(MEGA_CORP_PUBKEY) }, testNetworkParameters(notaries = listOf(NotaryInfo(DUMMY_NOTARY, true))) diff --git a/core/src/main/kotlin/net/corda/core/identity/IdentityUtils.kt b/core/src/main/kotlin/net/corda/core/identity/IdentityUtils.kt index ea0f3fbeed..f338ca391a 100644 --- a/core/src/main/kotlin/net/corda/core/identity/IdentityUtils.kt +++ b/core/src/main/kotlin/net/corda/core/identity/IdentityUtils.kt @@ -77,3 +77,12 @@ fun excludeHostNode(serviceHub: ServiceHub, map: Map): Map excludeNotary(map: Map, stx: SignedTransaction): Map = map.filterKeys { it != stx.notary } + +/** + * Check if [x500name] matches the [query]. + */ +fun x500Matches(query: String, exactMatch: Boolean, x500name: CordaX500Name): Boolean { + val components = listOfNotNull(x500name.commonName, x500name.organisationUnit, x500name.organisation, x500name.locality, x500name.state, x500name.country) + return components.any { (exactMatch && it == query) + || (!exactMatch && it.contains(query, ignoreCase = true)) } +} \ No newline at end of file diff --git a/core/src/main/kotlin/net/corda/core/node/services/IdentityService.kt b/core/src/main/kotlin/net/corda/core/node/services/IdentityService.kt index a5daf72f87..3afcdb14ed 100644 --- a/core/src/main/kotlin/net/corda/core/node/services/IdentityService.kt +++ b/core/src/main/kotlin/net/corda/core/node/services/IdentityService.kt @@ -7,7 +7,6 @@ import net.corda.core.crypto.toStringShort import net.corda.core.identity.* import net.corda.core.internal.hash import net.corda.core.utilities.contextLogger -import net.corda.core.utilities.debug import java.security.InvalidAlgorithmParameterException import java.security.PublicKey import java.security.cert.* @@ -69,6 +68,8 @@ interface IdentityService { * @param owningKey The [PublicKey] to determine well known identity for. * @return the party and certificate, or null if unknown. */ + @Deprecated("This method has been deprecated in favour of using a new way to generate and use confidential identities. See the new " + + "confidential identities repository.") fun certificateFromKey(owningKey: PublicKey): PartyAndCertificate? /** @@ -101,7 +102,7 @@ interface IdentityService { // The original version of this would return the party as-is if it was a Party (rather than AnonymousParty), // however that means that we don't verify that we know who owns the key. As such as now enforce turning the key // into a party, and from there figure out the well known party. - log.debug { "Attempting to find wellKnownParty for: ${party.owningKey.hash}" } + log.debug("Attempting to find wellKnownParty for: ${party.owningKey.hash}") val candidate = partyFromKey(party.owningKey) // TODO: This should be done via the network map cache, which is the authoritative source of well known identities return if (candidate != null) { @@ -146,6 +147,18 @@ interface IdentityService { * @param exactMatch If true, a case sensitive match is done against each component of each X.500 name. */ fun partiesFromName(query: String, exactMatch: Boolean): Set + + /** + * Registers a mapping in the database between the provided [PublicKey] and [Party] if one does not already exist. If an entry + * exists for the supplied [PublicKey] but the associated [Party] does not match the one supplied to the method then an exception will + * be thrown. + * + * @param key The public key that will be registered to the supplied [Party] + * @param party The party that the supplied public key will be registered to + * @throws IllegalArgumentException if the public key is already registered to a party that does not match the supplied party + */ + @Throws(IllegalArgumentException::class) + fun registerKeyToParty(key: PublicKey, party: Party) } class UnknownAnonymousPartyException(message: String) : CordaException(message) diff --git a/experimental/src/test/kotlin/net/corda/finance/contracts/universal/Cap.kt b/experimental/src/test/kotlin/net/corda/finance/contracts/universal/Cap.kt index 096e16da57..6813526df0 100644 --- a/experimental/src/test/kotlin/net/corda/finance/contracts/universal/Cap.kt +++ b/experimental/src/test/kotlin/net/corda/finance/contracts/universal/Cap.kt @@ -4,18 +4,17 @@ import com.nhaarman.mockito_kotlin.doReturn import com.nhaarman.mockito_kotlin.mock import com.nhaarman.mockito_kotlin.whenever import net.corda.core.identity.CordaX500Name +import net.corda.core.node.services.IdentityService import net.corda.finance.contracts.BusinessCalendar import net.corda.finance.contracts.FixOf import net.corda.finance.contracts.Frequency import net.corda.finance.contracts.Tenor -import net.corda.node.services.api.IdentityServiceInternal import net.corda.testing.core.DUMMY_NOTARY_NAME import net.corda.testing.core.SerializationEnvironmentRule import net.corda.testing.core.TestIdentity import net.corda.testing.dsl.EnforceVerifyOrFail import net.corda.testing.dsl.TransactionDSL import net.corda.testing.dsl.TransactionDSLInterpreter -import net.corda.testing.internal.rigorousMock import net.corda.testing.node.MockServices import net.corda.testing.node.transaction import org.junit.Ignore @@ -27,7 +26,7 @@ import java.time.LocalDate internal val DUMMY_NOTARY = TestIdentity(DUMMY_NOTARY_NAME, 20).party fun transaction(script: TransactionDSL.() -> EnforceVerifyOrFail) = run { MockServices(listOf("net.corda.finance.contracts.universal"), CordaX500Name("MegaCorp", "London", "GB"), - mock().also { + mock().also { listOf(acmeCorp, highStreetBank, momAndPop).forEach { party -> doReturn(null).whenever(it).partyFromKey(party.owningKey) } diff --git a/finance/contracts/src/test/java/net/corda/finance/contracts/asset/CashTestsJava.java b/finance/contracts/src/test/java/net/corda/finance/contracts/asset/CashTestsJava.java index 78db47fcec..cdcf1e5e87 100644 --- a/finance/contracts/src/test/java/net/corda/finance/contracts/asset/CashTestsJava.java +++ b/finance/contracts/src/test/java/net/corda/finance/contracts/asset/CashTestsJava.java @@ -4,21 +4,19 @@ import net.corda.core.contracts.PartyAndReference; import net.corda.core.identity.AnonymousParty; import net.corda.core.identity.CordaX500Name; import net.corda.core.identity.Party; -import net.corda.node.services.api.IdentityServiceInternal; +import net.corda.core.node.services.IdentityService; import net.corda.testing.core.DummyCommandData; import net.corda.testing.core.SerializationEnvironmentRule; import net.corda.testing.core.TestIdentity; import net.corda.testing.node.MockServices; import org.junit.Rule; import org.junit.Test; -import org.mockito.Mockito; import static java.util.Collections.emptyList; import static net.corda.finance.Currencies.DOLLARS; import static net.corda.finance.Currencies.issuedBy; -import static net.corda.testing.node.NodeTestUtils.transaction; -import static net.corda.testing.internal.RigorousMockKt.rigorousMock; import static net.corda.testing.core.TestConstants.DUMMY_NOTARY_NAME; +import static net.corda.testing.node.NodeTestUtils.transaction; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; @@ -37,7 +35,7 @@ public class CashTestsJava { @Test public void trivial() { - IdentityServiceInternal identityService = mock(IdentityServiceInternal.class); + IdentityService identityService = mock(IdentityService.class); doReturn(MEGA_CORP.getParty()).when(identityService).partyFromKey(MEGA_CORP.getPublicKey()); doReturn(MINI_CORP.getParty()).when(identityService).partyFromKey(MINI_CORP.getPublicKey()); transaction(new MockServices(emptyList(), MEGA_CORP.getName(), identityService), DUMMY_NOTARY, tx -> { diff --git a/finance/contracts/src/test/kotlin/net/corda/finance/contracts/asset/ObligationTests.kt b/finance/contracts/src/test/kotlin/net/corda/finance/contracts/asset/ObligationTests.kt index 13c1428a54..2ec81a31ad 100644 --- a/finance/contracts/src/test/kotlin/net/corda/finance/contracts/asset/ObligationTests.kt +++ b/finance/contracts/src/test/kotlin/net/corda/finance/contracts/asset/ObligationTests.kt @@ -21,7 +21,6 @@ import net.corda.finance.contracts.Commodity import net.corda.finance.contracts.NetType import net.corda.finance.contracts.asset.Obligation.Lifecycle import net.corda.finance.workflows.asset.ObligationUtils -import net.corda.node.services.api.IdentityServiceInternal import net.corda.testing.contracts.DummyContract import net.corda.testing.core.* import net.corda.testing.dsl.* @@ -87,7 +86,7 @@ class ObligationTests { } private val notaryServices = MockServices(emptyList(), MEGA_CORP.name, mock(), dummyNotary.keyPair) - private val identityService = mock().also { + private val identityService = mock().also { doReturn(null).whenever(it).partyFromKey(ALICE_PUBKEY) doReturn(null).whenever(it).partyFromKey(BOB_PUBKEY) doReturn(null).whenever(it).partyFromKey(CHARLIE.owningKey) diff --git a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt index 2457dbb4e1..fdf6d5a991 100644 --- a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt @@ -856,7 +856,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration, 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.createX500Map(cacheFactory) + 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. diff --git a/node/src/main/kotlin/net/corda/node/migration/MigrationNamedCacheFactory.kt b/node/src/main/kotlin/net/corda/node/migration/MigrationNamedCacheFactory.kt index bfec07dffd..2d2995d5f2 100644 --- a/node/src/main/kotlin/net/corda/node/migration/MigrationNamedCacheFactory.kt +++ b/node/src/main/kotlin/net/corda/node/migration/MigrationNamedCacheFactory.kt @@ -27,8 +27,9 @@ class MigrationNamedCacheFactory(private val metricRegistry: MetricRegistry?, "DBTransactionStorage_transactions" -> caffeine.maximumWeight( nodeConfiguration?.transactionCacheSizeBytes ?: NodeConfiguration.defaultTransactionCacheSize ) - "PersistentIdentityService_partyByKey" -> caffeine.maximumSize(defaultCacheSize) - "PersistentIdentityService_partyByName" -> caffeine.maximumSize(defaultCacheSize) + "PersistentIdentityService_keyToPartyAndCert" -> caffeine.maximumSize(defaultCacheSize) + "PersistentIdentityService_nameToKey" -> caffeine.maximumSize(defaultCacheSize) + "PersistentIdentityService_keyToName" -> caffeine.maximumSize(defaultCacheSize) "BasicHSMKeyManagementService_keys" -> caffeine.maximumSize(defaultCacheSize) "NodeAttachmentService_attachmentContent" -> caffeine.maximumWeight(defaultCacheSize) "NodeAttachmentService_attachmentPresence" -> caffeine.maximumSize(defaultCacheSize) diff --git a/node/src/main/kotlin/net/corda/node/migration/PersistentIdentityMigrationNewTable.kt b/node/src/main/kotlin/net/corda/node/migration/PersistentIdentityMigrationNewTable.kt new file mode 100644 index 0000000000..fc384ea450 --- /dev/null +++ b/node/src/main/kotlin/net/corda/node/migration/PersistentIdentityMigrationNewTable.kt @@ -0,0 +1,198 @@ +package net.corda.node.migration + +import liquibase.database.Database +import liquibase.database.jvm.JdbcConnection +import liquibase.exception.ValidationErrors +import liquibase.resource.ResourceAccessor +import net.corda.core.crypto.Crypto +import net.corda.core.crypto.SignatureScheme +import net.corda.core.identity.CordaX500Name +import net.corda.core.identity.Party +import net.corda.core.identity.PartyAndCertificate +import net.corda.core.schemas.MappedSchema +import net.corda.core.utilities.contextLogger +import net.corda.node.internal.DBNetworkParametersStorage +import net.corda.node.services.identity.PersistentIdentityService +import net.corda.node.services.keys.BasicHSMKeyManagementService +import net.corda.node.services.persistence.DBTransactionStorage +import net.corda.node.services.persistence.NodeAttachmentService +import net.corda.nodeapi.internal.DEV_INTERMEDIATE_CA +import net.corda.nodeapi.internal.DEV_ROOT_CA +import net.corda.nodeapi.internal.createDevNodeCa +import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair +import net.corda.nodeapi.internal.crypto.CertificateType +import net.corda.nodeapi.internal.crypto.X509CertificateFactory +import net.corda.nodeapi.internal.crypto.X509Utilities +import java.math.BigInteger +import java.security.KeyPair +import java.security.PublicKey +import java.security.cert.X509Certificate +import java.util.* + +/** + * Migration that reads data from the [PersistentIdentityCert] table, extracts the parameters required to insert into the [PersistentIdentity] table. + */ +class PersistentIdentityMigrationNewTable : CordaMigration() { + companion object { + private val logger = contextLogger() + } + + override fun execute(database: Database?) { + logger.info("Migrating persistent identities with certificates table into persistent table with no certificate data.") + + if (database == null) { + logger.error("Cannot migrate persistent states: Liquibase failed to provide a suitable database connection") + throw PersistentIdentitiesMigrationException("Cannot migrate persistent states as liquibase failed to provide a suitable database connection") + } + initialiseNodeServices(database, setOf(PersistentIdentitiesMigrationSchemaV1)) + + val connection = database.connection as JdbcConnection + + doAndTestMigration(connection) + } + + private fun doAndTestMigration(connection: JdbcConnection) { + val alice = TestIdentity(CordaX500Name("Alice Corp", "Madrid", "ES"), 70) + val pkHash = addTestMapping(connection, alice) + + // Extract data from old table needed to populate the new table + val keyPartiesMap = extractKeyParties(connection) + + keyPartiesMap.forEach { + insertEntry(connection, it) + } + + verifyTestMigration(connection, pkHash, alice.name.toString()) + deleteTestMapping(connection, pkHash) + } + + private fun extractKeyParties(connection: JdbcConnection): Map { + val keyParties = mutableMapOf() + connection.createStatement().use { + val rs = it.executeQuery("SELECT pk_hash, identity_value FROM node_identities WHERE pk_hash IS NOT NULL") + while (rs.next()) { + val key = rs.getString(1) + val partyBytes = rs.getBytes(2) + val name = PartyAndCertificate(X509CertificateFactory().delegate.generateCertPath(partyBytes.inputStream())).party.name + keyParties.put(key, name) + } + rs.close() + } + return keyParties + } + + private fun insertEntry(connection: JdbcConnection, entry: Map.Entry) { + val pk = entry.key + val name = entry.value.toString() + connection.prepareStatement("INSERT INTO node_identities_no_cert (pk_hash, name) VALUES (?,?)").use { + it.setString(1, pk) + it.setString(2, name) + it.executeUpdate() + } + } + + override fun setUp() { + } + + override fun setFileOpener(resourceAccessor: ResourceAccessor?) { + } + + override fun getConfirmationMessage(): String? { + return null + } + + override fun validate(database: Database?): ValidationErrors? { + return null + } + + private fun addTestMapping(connection: JdbcConnection, testIdentity: TestIdentity): String { + val pkHash = UUID.randomUUID().toString() + val cert = testIdentity.identity.certPath.encoded + + connection.prepareStatement("INSERT INTO node_identities (pk_hash, identity_value) VALUES (?,?)").use { + it.setString(1, pkHash) + it.setBytes(2, cert) + it.executeUpdate() + } + return pkHash + } + + private fun deleteTestMapping(connection: JdbcConnection, pkHash: String) { + connection.prepareStatement("DELETE FROM node_identities WHERE pk_hash = ?").use { + it.setString(1, pkHash) + it.executeUpdate() + } + } + + private fun verifyTestMigration(connection: JdbcConnection, pk: String, name: String) { + connection.createStatement().use { + try { + val rs = it.executeQuery("SELECT (pk_hash, name) FROM node_identities_no_cert") + while (rs.next()) { + val result = rs.getString(1) + require(result.contains(pk)) + require(result.contains(name)) + } + rs.close() + } catch (e: Exception) { + logger.error(e.localizedMessage) + } + } + } +} + +/** + * A minimal set of schema for retrieving data from the database. + * + * Note that adding an extra schema here may cause migrations to fail if it ends up creating a table before the same table + * is created in a migration script. As such, this migration must be run after the tables for the following have been created (and, + * if they are removed in the future, before they are deleted). + */ +object PersistentIdentitiesMigrationSchema + +object PersistentIdentitiesMigrationSchemaV1 : MappedSchema(schemaFamily = PersistentIdentitiesMigrationSchema.javaClass, version = 1, + mappedTypes = listOf( + DBTransactionStorage.DBTransaction::class.java, + PersistentIdentityService.PersistentPublicKeyHashToCertificate::class.java, + PersistentIdentityService.PersistentPartyToPublicKeyHash::class.java, + PersistentIdentityService.PersistentPublicKeyHashToParty::class.java, + BasicHSMKeyManagementService.PersistentKey::class.java, + NodeAttachmentService.DBAttachment::class.java, + DBNetworkParametersStorage.PersistentNetworkParameters::class.java + ) +) + +class PersistentIdentitiesMigrationException(msg: String, cause: Exception? = null) : Exception(msg, cause) + +/** + * A class that encapsulates a test identity containing a [CordaX500Name] and a [KeyPair]. Duplicate of [net.corda.testing.core.TestIdentity] + * to avoid circular dependencies. + */ +private class TestIdentity(val name: CordaX500Name, val keyPair: KeyPair) { + + /** Creates an identity with a deterministic [keyPair] i.e. same [entropy] same keyPair. */ + @JvmOverloads + constructor(name: CordaX500Name, entropy: Long, signatureScheme: SignatureScheme = Crypto.DEFAULT_SIGNATURE_SCHEME) + : this(name, Crypto.deriveKeyPairFromEntropy(signatureScheme, BigInteger.valueOf(entropy))) + + val publicKey: PublicKey get() = keyPair.public + val party: Party = Party(name, publicKey) + val identity: PartyAndCertificate by lazy { getTestPartyAndCertificate(party) } // Often not needed. + + fun getTestPartyAndCertificate(party: Party): PartyAndCertificate { + val trustRoot: X509Certificate = DEV_ROOT_CA.certificate + val intermediate: CertificateAndKeyPair = DEV_INTERMEDIATE_CA + + val (nodeCaCert, nodeCaKeyPair) = createDevNodeCa(intermediate, party.name) + + val identityCert = X509Utilities.createCertificate( + CertificateType.LEGAL_IDENTITY, + nodeCaCert, + nodeCaKeyPair, + party.name.x500Principal, + party.owningKey) + + val certPath = X509Utilities.buildCertPath(identityCert, nodeCaCert, intermediate.certificate, trustRoot) + return PartyAndCertificate(certPath) + } +} \ No newline at end of file diff --git a/node/src/main/kotlin/net/corda/node/migration/VaultStateMigration.kt b/node/src/main/kotlin/net/corda/node/migration/VaultStateMigration.kt index 779229d465..b8215fb14b 100644 --- a/node/src/main/kotlin/net/corda/node/migration/VaultStateMigration.kt +++ b/node/src/main/kotlin/net/corda/node/migration/VaultStateMigration.kt @@ -126,8 +126,9 @@ object VaultMigrationSchema object VaultMigrationSchemaV1 : MappedSchema(schemaFamily = VaultMigrationSchema.javaClass, version = 1, mappedTypes = listOf( DBTransactionStorage.DBTransaction::class.java, - PersistentIdentityService.PersistentIdentity::class.java, - PersistentIdentityService.PersistentIdentityNames::class.java, + PersistentIdentityService.PersistentPublicKeyHashToCertificate::class.java, + PersistentIdentityService.PersistentPartyToPublicKeyHash::class.java, + PersistentIdentityService.PersistentPublicKeyHashToParty::class.java, BasicHSMKeyManagementService.PersistentKey::class.java, NodeAttachmentService.DBAttachment::class.java, DBNetworkParametersStorage.PersistentNetworkParameters::class.java diff --git a/node/src/main/kotlin/net/corda/node/services/identity/InMemoryIdentityService.kt b/node/src/main/kotlin/net/corda/node/services/identity/InMemoryIdentityService.kt index 3b46ddaca1..a545deb2d0 100644 --- a/node/src/main/kotlin/net/corda/node/services/identity/InMemoryIdentityService.kt +++ b/node/src/main/kotlin/net/corda/node/services/identity/InMemoryIdentityService.kt @@ -3,10 +3,13 @@ package net.corda.node.services.identity import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party import net.corda.core.identity.PartyAndCertificate +import net.corda.core.identity.x500Matches +import net.corda.core.internal.CertRole +import net.corda.core.node.services.IdentityService import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.core.utilities.contextLogger import net.corda.core.utilities.trace -import net.corda.node.services.api.IdentityServiceInternal +import net.corda.nodeapi.internal.crypto.X509Utilities import net.corda.nodeapi.internal.crypto.x509Certificates import java.security.InvalidAlgorithmParameterException import java.security.PublicKey @@ -21,7 +24,7 @@ import javax.annotation.concurrent.ThreadSafe */ @ThreadSafe class InMemoryIdentityService(identities: List = emptyList(), - override val trustRoot: X509Certificate) : SingletonSerializeAsToken(), IdentityServiceInternal { + override val trustRoot: X509Certificate) : SingletonSerializeAsToken(), IdentityService { companion object { private val log = contextLogger() } @@ -31,43 +34,84 @@ class InMemoryIdentityService(identities: List = emptyList( */ override val caCertStore: CertStore = CertStore.getInstance("Collection", CollectionCertStoreParameters(setOf(trustRoot))) override val trustAnchor: TrustAnchor = TrustAnchor(trustRoot, null) - private val keyToParties = ConcurrentHashMap() - private val principalToParties = ConcurrentHashMap() + private val keyToPartyAndCerts = ConcurrentHashMap() + private val nameToKey = ConcurrentHashMap() + private val keyToName = ConcurrentHashMap() init { - keyToParties.putAll(identities.associateBy { it.owningKey }) - principalToParties.putAll(identities.associateBy { it.name }) + keyToPartyAndCerts.putAll(identities.associateBy { it.owningKey }) + nameToKey.putAll(identities.associateBy { it.name }.mapValues { it.value.owningKey }) + keyToName.putAll(identities.associateBy{ it.owningKey }.mapValues { it.value.party.name }) + } + + + @Throws(CertificateExpiredException::class, CertificateNotYetValidException::class, InvalidAlgorithmParameterException::class) + override fun verifyAndRegisterIdentity(identity: PartyAndCertificate): PartyAndCertificate? { + return verifyAndRegisterIdentity(trustAnchor, identity) } @Throws(CertificateExpiredException::class, CertificateNotYetValidException::class, InvalidAlgorithmParameterException::class) - override fun verifyAndRegisterIdentity(identity: PartyAndCertificate): PartyAndCertificate? = verifyAndRegisterIdentity(trustAnchor, identity) + private fun verifyAndRegisterIdentity(trustAnchor: TrustAnchor, identity: PartyAndCertificate): PartyAndCertificate? { + // Validate the chain first, before we do anything clever with it + val identityCertChain = identity.certPath.x509Certificates + try { + identity.verify(trustAnchor) + } catch (e: CertPathValidatorException) { + log.warn("Certificate validation failed for ${identity.name} against trusted root ${trustAnchor.trustedCert.subjectX500Principal}.") + log.warn("Certificate path :") + identityCertChain.reversed().forEachIndexed { index, certificate -> + val space = (0 until index).joinToString("") { " " } + log.warn("$space${certificate.subjectX500Principal}") + } + throw e + } + // Ensure we record the first identity of the same name, first + val wellKnownCert = identityCertChain.single { CertRole.extract(it)?.isWellKnown ?: false } + if (wellKnownCert != identity.certificate) { + val idx = identityCertChain.lastIndexOf(wellKnownCert) + val firstPath = X509Utilities.buildCertPath(identityCertChain.slice(idx until identityCertChain.size)) + verifyAndRegisterIdentity(trustAnchor, PartyAndCertificate(firstPath)) + } + return registerIdentity(identity) + } - @Throws(CertificateExpiredException::class, CertificateNotYetValidException::class, InvalidAlgorithmParameterException::class) - override fun verifyAndRegisterIdentity(identity: PartyAndCertificate, isNewRandomIdentity: Boolean): PartyAndCertificate? = verifyAndRegisterIdentity(trustAnchor, identity) - - override fun registerIdentity(identity: PartyAndCertificate, isNewRandomIdentity: Boolean): PartyAndCertificate? { + private fun registerIdentity(identity: PartyAndCertificate): PartyAndCertificate? { val identityCertChain = identity.certPath.x509Certificates log.trace { "Registering identity $identity" } - keyToParties[identity.owningKey] = identity + keyToPartyAndCerts[identity.owningKey] = identity // Always keep the first party we registered, as that's the well known identity - principalToParties.computeIfAbsent(identity.name) { identity } - return keyToParties[identityCertChain[1].publicKey] + nameToKey.computeIfAbsent(identity.name) {identity.owningKey} + keyToName.putIfAbsent(identity.owningKey, identity.name) + return keyToPartyAndCerts[identityCertChain[1].publicKey] } - override fun certificateFromKey(owningKey: PublicKey): PartyAndCertificate? = keyToParties[owningKey] + override fun certificateFromKey(owningKey: PublicKey): PartyAndCertificate? = keyToPartyAndCerts[owningKey] // We give the caller a copy of the data set to avoid any locking problems - override fun getAllIdentities(): Iterable = ArrayList(keyToParties.values) + override fun getAllIdentities(): Iterable = ArrayList(keyToPartyAndCerts.values) - override fun wellKnownPartyFromX500Name(name: CordaX500Name): Party? = principalToParties[name]?.party + override fun wellKnownPartyFromX500Name(name: CordaX500Name): Party? { + val key = nameToKey[name] + return if (key != null) { + keyToPartyAndCerts[key]?.party + } else + null + } override fun partiesFromName(query: String, exactMatch: Boolean): Set { val results = LinkedHashSet() - principalToParties.forEach { (x500name, partyAndCertificate) -> + nameToKey.forEach { (x500name, key) -> if (x500Matches(query, exactMatch, x500name)) { - results += partyAndCertificate.party + results += keyToPartyAndCerts[key]?.party ?: throw IllegalArgumentException("Could not find an entry in the database for the public key $key.") } } return results } + + override fun registerKeyToParty(key: PublicKey, party: Party) { + if (keyToName[key] == null) { + keyToName.putIfAbsent(key, party.name) + } + throw IllegalArgumentException("An entry for the public key: $key already exists.") + } } diff --git a/node/src/main/kotlin/net/corda/node/services/identity/PersistentIdentityService.kt b/node/src/main/kotlin/net/corda/node/services/identity/PersistentIdentityService.kt index d11f82dad9..a5fa2c3d71 100644 --- a/node/src/main/kotlin/net/corda/node/services/identity/PersistentIdentityService.kt +++ b/node/src/main/kotlin/net/corda/node/services/identity/PersistentIdentityService.kt @@ -2,16 +2,16 @@ package net.corda.node.services.identity import net.corda.core.crypto.toStringShort import net.corda.core.identity.* -import net.corda.core.internal.NamedCacheFactory -import net.corda.core.internal.toSet +import net.corda.core.internal.* +import net.corda.core.node.services.IdentityService import net.corda.core.node.services.UnknownAnonymousPartyException import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.core.utilities.MAX_HASH_HEX_SIZE import net.corda.core.utilities.contextLogger import net.corda.core.utilities.debug -import net.corda.node.services.api.IdentityServiceInternal import net.corda.node.utilities.AppendOnlyPersistentMap import net.corda.nodeapi.internal.crypto.X509CertificateFactory +import net.corda.nodeapi.internal.crypto.X509Utilities import net.corda.nodeapi.internal.crypto.x509Certificates import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX @@ -31,20 +31,22 @@ import kotlin.streams.toList * cached for efficient lookup. */ @ThreadSafe -class PersistentIdentityService(cacheFactory: NamedCacheFactory) : SingletonSerializeAsToken(), IdentityServiceInternal { +class PersistentIdentityService(cacheFactory: NamedCacheFactory) : SingletonSerializeAsToken(), IdentityService { + companion object { private val log = contextLogger() const val HASH_TO_IDENTITY_TABLE_NAME = "${NODE_DATABASE_PREFIX}identities" const val NAME_TO_HASH_TABLE_NAME = "${NODE_DATABASE_PREFIX}named_identities" + const val KEY_TO_NAME_TABLE_NAME = "${NODE_DATABASE_PREFIX}identities_no_cert" const val PK_HASH_COLUMN_NAME = "pk_hash" const val IDENTITY_COLUMN_NAME = "identity_value" const val NAME_COLUMN_NAME = "name" - fun createPKMap(cacheFactory: NamedCacheFactory): AppendOnlyPersistentMap { + fun createKeyToPartyAndCertMap(cacheFactory: NamedCacheFactory): AppendOnlyPersistentMap { return AppendOnlyPersistentMap( cacheFactory = cacheFactory, - name = "PersistentIdentityService_partyByKey", + name = "PersistentIdentityService_keyToPartyAndCert", toPersistentEntityKey = { it }, fromPersistentEntity = { Pair( @@ -53,33 +55,51 @@ class PersistentIdentityService(cacheFactory: NamedCacheFactory) : SingletonSeri ) }, toPersistentEntity = { key: String, value: PartyAndCertificate -> - PersistentIdentity(key, value.certPath.encoded) + PersistentPublicKeyHashToCertificate(key, value.certPath.encoded) }, - persistentEntityClass = PersistentIdentity::class.java + persistentEntityClass = PersistentPublicKeyHashToCertificate::class.java ) } - fun createX500Map(cacheFactory: NamedCacheFactory): AppendOnlyPersistentMap { + fun createX500ToKeyMap(cacheFactory: NamedCacheFactory): AppendOnlyPersistentMap { return AppendOnlyPersistentMap( cacheFactory = cacheFactory, - name = "PersistentIdentityService_partyByName", + name = "PersistentIdentityService_nameToKey", toPersistentEntityKey = { it.toString() }, fromPersistentEntity = { Pair(CordaX500Name.parse(it.name), it.publicKeyHash) }, toPersistentEntity = { key: CordaX500Name, value: String -> - PersistentIdentityNames(key.toString(), value) + PersistentPartyToPublicKeyHash(key.toString(), value) }, - persistentEntityClass = PersistentIdentityNames::class.java + persistentEntityClass = PersistentPartyToPublicKeyHash::class.java ) } + + fun createKeyToX500Map(cacheFactory: NamedCacheFactory): AppendOnlyPersistentMap { + return AppendOnlyPersistentMap( + cacheFactory = cacheFactory, + name = "PersistentIdentityService_keyToName", + toPersistentEntityKey = { it }, + fromPersistentEntity = { + Pair( + it.publicKeyHash, + CordaX500Name.parse(it.name) + ) + }, + toPersistentEntity = { key: String, value: CordaX500Name -> + PersistentPublicKeyHashToParty(key, value.toString()) + }, + persistentEntityClass = PersistentPublicKeyHashToParty::class.java) + } + private fun mapToKey(party: PartyAndCertificate) = party.owningKey.toStringShort() } @Entity @javax.persistence.Table(name = HASH_TO_IDENTITY_TABLE_NAME) - class PersistentIdentity( + class PersistentPublicKeyHashToCertificate( @Id @Column(name = PK_HASH_COLUMN_NAME, length = MAX_HASH_HEX_SIZE, nullable = false) var publicKeyHash: String = "", @@ -91,7 +111,7 @@ class PersistentIdentityService(cacheFactory: NamedCacheFactory) : SingletonSeri @Entity @javax.persistence.Table(name = NAME_TO_HASH_TABLE_NAME) - class PersistentIdentityNames( + class PersistentPartyToPublicKeyHash( @Id @Column(name = NAME_COLUMN_NAME, length = 128, nullable = false) var name: String = "", @@ -100,6 +120,17 @@ class PersistentIdentityService(cacheFactory: NamedCacheFactory) : SingletonSeri var publicKeyHash: String = "" ) + @Entity + @javax.persistence.Table(name = KEY_TO_NAME_TABLE_NAME) + class PersistentPublicKeyHashToParty( + @Id + @Column(name = PK_HASH_COLUMN_NAME, length = MAX_HASH_HEX_SIZE, nullable = false) + var publicKeyHash: String = "", + + @Column(name = NAME_COLUMN_NAME, length = 128, nullable = false) + var name: String = "" + ) + private lateinit var _caCertStore: CertStore override val caCertStore: CertStore get() = _caCertStore @@ -115,8 +146,9 @@ class PersistentIdentityService(cacheFactory: NamedCacheFactory) : SingletonSeri // CordaPersistence is not a c'tor parameter to work around the cyclic dependency lateinit var database: CordaPersistence - private val keyToParties = createPKMap(cacheFactory) - private val principalToParties = createX500Map(cacheFactory) + private val keyToPartyAndCert = createKeyToPartyAndCertMap(cacheFactory) + private val nameToKey = createX500ToKeyMap(cacheFactory) + private val keyToName = createKeyToX500Map(cacheFactory) fun start(trustRoot: X509Certificate, caCertificates: List = emptyList(), notaryIdentities: List = emptyList()) { _trustRoot = trustRoot @@ -128,51 +160,75 @@ class PersistentIdentityService(cacheFactory: NamedCacheFactory) : SingletonSeri fun loadIdentities(identities: Collection = emptySet(), confidentialIdentities: Collection = emptySet()) { identities.forEach { val key = mapToKey(it) - keyToParties.addWithDuplicatesAllowed(key, it, false) - principalToParties.addWithDuplicatesAllowed(it.name, key, false) + keyToPartyAndCert.addWithDuplicatesAllowed(key, it, false) + nameToKey.addWithDuplicatesAllowed(it.name, key, false) + keyToName.addWithDuplicatesAllowed(mapToKey(it), it.name, false) } confidentialIdentities.forEach { - keyToParties.addWithDuplicatesAllowed(mapToKey(it), it, false) + keyToName.addWithDuplicatesAllowed(mapToKey(it), it.name, false) } log.debug("Identities loaded") } @Throws(CertificateExpiredException::class, CertificateNotYetValidException::class, InvalidAlgorithmParameterException::class) override fun verifyAndRegisterIdentity(identity: PartyAndCertificate): PartyAndCertificate? { - return verifyAndRegisterIdentity(identity, false) + return verifyAndRegisterIdentity(trustAnchor, identity) } + /** + * Verifies that an identity is valid. If it is valid, it gets registered in the database and the [PartyAndCertificate] is returned. + * + * @param trustAnchor The trust anchor that will verify the identity's validity + * @param identity The identity to verify + * @param isNewRandomIdentity true if the identity will not have been registered before (e.g. because it is randomly generated by ourselves). + */ @Throws(CertificateExpiredException::class, CertificateNotYetValidException::class, InvalidAlgorithmParameterException::class) - override fun verifyAndRegisterIdentity(identity: PartyAndCertificate, isNewRandomIdentity: Boolean): PartyAndCertificate? { - return database.transaction { - verifyAndRegisterIdentity(trustAnchor, identity, isNewRandomIdentity) + private fun verifyAndRegisterIdentity(trustAnchor: TrustAnchor, identity: PartyAndCertificate): PartyAndCertificate? { + // Validate the chain first, before we do anything clever with it + val identityCertChain = identity.certPath.x509Certificates + try { + identity.verify(trustAnchor) + } catch (e: CertPathValidatorException) { + log.warn("Certificate validation failed for ${identity.name} against trusted root ${trustAnchor.trustedCert.subjectX500Principal}.") + log.warn("Certificate path :") + identityCertChain.reversed().forEachIndexed { index, certificate -> + val space = (0 until index).joinToString("") { " " } + log.warn("$space${certificate.subjectX500Principal}") + } + throw e } + // Ensure we record the first identity of the same name, first + val wellKnownCert = identityCertChain.single { CertRole.extract(it)?.isWellKnown ?: false } + if (wellKnownCert != identity.certificate) { + val idx = identityCertChain.lastIndexOf(wellKnownCert) + val firstPath = X509Utilities.buildCertPath(identityCertChain.slice(idx until identityCertChain.size)) + verifyAndRegisterIdentity(trustAnchor, PartyAndCertificate(firstPath)) + } + return registerIdentity(identity) } - override fun registerIdentity(identity: PartyAndCertificate, isNewRandomIdentity: Boolean): PartyAndCertificate? { + private fun registerIdentity(identity: PartyAndCertificate): PartyAndCertificate? { log.debug { "Registering identity $identity" } val identityCertChain = identity.certPath.x509Certificates val key = mapToKey(identity) - - if (isNewRandomIdentity) { - // Because this is supposed to be new and random, there's no way we have it in the database already, so skip the pessimistic check. - keyToParties[key] = identity - } else { - keyToParties.addWithDuplicatesAllowed(key, identity) - principalToParties.addWithDuplicatesAllowed(identity.name, key, false) + return database.transaction { + keyToPartyAndCert.addWithDuplicatesAllowed(key, identity, false) + nameToKey.addWithDuplicatesAllowed(identity.name, key, false) + keyToName.addWithDuplicatesAllowed(key, identity.name, false) + val parentId = identityCertChain[1].publicKey.toStringShort() + keyToPartyAndCert[parentId] } - - val parentId = identityCertChain[1].publicKey.toStringShort() - return keyToParties[parentId] } - override fun certificateFromKey(owningKey: PublicKey): PartyAndCertificate? = database.transaction { keyToParties[owningKey.toStringShort()] } + override fun certificateFromKey(owningKey: PublicKey): PartyAndCertificate? = database.transaction { + keyToPartyAndCert[owningKey.toStringShort()] + } private fun certificateFromCordaX500Name(name: CordaX500Name): PartyAndCertificate? { return database.transaction { - val partyId = principalToParties[name] + val partyId = nameToKey[name] if (partyId != null) { - keyToParties[partyId] + keyToPartyAndCert[partyId] } else null } } @@ -180,11 +236,12 @@ class PersistentIdentityService(cacheFactory: NamedCacheFactory) : SingletonSeri // We give the caller a copy of the data set to avoid any locking problems override fun getAllIdentities(): Iterable { return database.transaction { - keyToParties.allPersisted.use { it.map { it.second }.toList() } + keyToPartyAndCert.allPersisted.use { it.map { it.second }.toList() } } } - - override fun wellKnownPartyFromX500Name(name: CordaX500Name): Party? = certificateFromCordaX500Name(name)?.party + override fun wellKnownPartyFromX500Name(name: CordaX500Name): Party? = database.transaction { + certificateFromCordaX500Name(name)?.party + } override fun wellKnownPartyFromAnonymous(party: AbstractParty): Party? { // Skip database lookup if the party is a notary identity. @@ -193,14 +250,30 @@ class PersistentIdentityService(cacheFactory: NamedCacheFactory) : SingletonSeri return if (party is Party && party in notaryIdentityCache) { party } else { - database.transaction { super.wellKnownPartyFromAnonymous(party) } + database.transaction { + // Try and resolve the party from the table to public keys to party and certificates + // If we cannot find it then we perform a lookup on the public key to X500 name table + val legalIdentity = super.wellKnownPartyFromAnonymous(party) + if (legalIdentity == null) { + // If there is no entry in the legal keyToPartyAndCert table then the party must be a confidential identity so we perform + // a lookup in the keyToName table. If an entry for that public key exists, then we attempt + val name = keyToName[party.owningKey.toStringShort()] + if (name != null) { + wellKnownPartyFromX500Name(name) + } else { + null + } + } else { + legalIdentity + } + } } } override fun partiesFromName(query: String, exactMatch: Boolean): Set { return database.transaction { - principalToParties.allPersisted.use { - it.filter { x500Matches(query, exactMatch, it.first) }.map { keyToParties[it.second]!!.party }.toSet() + nameToKey.allPersisted.use { + it.filter { x500Matches(query, exactMatch, it.first) }.map { keyToPartyAndCert[it.second]!!.party }.toSet() } } } @@ -215,4 +288,19 @@ class PersistentIdentityService(cacheFactory: NamedCacheFactory) : SingletonSeri fun stripNotOurKeys(keys: Iterable): Iterable { return keys.filter { certificateFromKey(it)?.name in ourNames } } -} + + override fun registerKeyToParty(key: PublicKey, party: Party) { + return database.transaction { + val existingEntryForKey = keyToName[key.toStringShort()] + if (existingEntryForKey == null) { + log.info("Linking: ${key.hash} to ${party.name}") + keyToName[key.toStringShort()] = party.name + } else { + log.info("An existing entry for ${key.hash} already exists.") + if (party.name != keyToName[key.toStringShort()]) { + throw IllegalArgumentException("The public key ${key.hash} is already assigned to a different party than the supplied .") + } + } + } + } +} \ No newline at end of file diff --git a/node/src/main/kotlin/net/corda/node/services/persistence/PublicKeyToOwningIdentityCacheImpl.kt b/node/src/main/kotlin/net/corda/node/services/persistence/PublicKeyToOwningIdentityCacheImpl.kt index eb3627e722..a54af0937b 100644 --- a/node/src/main/kotlin/net/corda/node/services/persistence/PublicKeyToOwningIdentityCacheImpl.kt +++ b/node/src/main/kotlin/net/corda/node/services/persistence/PublicKeyToOwningIdentityCacheImpl.kt @@ -32,10 +32,10 @@ class PublicKeyToOwningIdentityCacheImpl(private val database: CordaPersistence, return database.transaction { val criteriaBuilder = session.criteriaBuilder val criteriaQuery = criteriaBuilder.createQuery(Long::class.java) - val queryRoot = criteriaQuery.from(PersistentIdentityService.PersistentIdentity::class.java) + val queryRoot = criteriaQuery.from(PersistentIdentityService.PersistentPublicKeyHashToCertificate::class.java) criteriaQuery.select(criteriaBuilder.count(queryRoot)) criteriaQuery.where( - criteriaBuilder.equal(queryRoot.get(PersistentIdentityService.PersistentIdentity::publicKeyHash.name), key.toStringShort()) + criteriaBuilder.equal(queryRoot.get(PersistentIdentityService.PersistentPublicKeyHashToCertificate::publicKeyHash.name), key.toStringShort()) ) val query = session.createQuery(criteriaQuery) query.uniqueResult() > 0 diff --git a/node/src/main/kotlin/net/corda/node/services/schema/NodeSchemaService.kt b/node/src/main/kotlin/net/corda/node/services/schema/NodeSchemaService.kt index 32c18e2224..675f294a07 100644 --- a/node/src/main/kotlin/net/corda/node/services/schema/NodeSchemaService.kt +++ b/node/src/main/kotlin/net/corda/node/services/schema/NodeSchemaService.kt @@ -40,8 +40,9 @@ class NodeSchemaService(private val extraSchemas: Set = emptySet() NodeSchedulerService.PersistentScheduledState::class.java, NodeAttachmentService.DBAttachment::class.java, P2PMessageDeduplicator.ProcessedMessage::class.java, - PersistentIdentityService.PersistentIdentity::class.java, - PersistentIdentityService.PersistentIdentityNames::class.java, + PersistentIdentityService.PersistentPublicKeyHashToCertificate::class.java, + PersistentIdentityService.PersistentPartyToPublicKeyHash::class.java, + PersistentIdentityService.PersistentPublicKeyHashToParty::class.java, ContractUpgradeServiceImpl.DBContractUpgrade::class.java, DBNetworkParametersStorage.PersistentNetworkParameters::class.java, PublicKeyHashToExternalId::class.java diff --git a/node/src/main/kotlin/net/corda/node/utilities/NodeNamedCache.kt b/node/src/main/kotlin/net/corda/node/utilities/NodeNamedCache.kt index 4653a4d9b4..78d11fe86e 100644 --- a/node/src/main/kotlin/net/corda/node/utilities/NodeNamedCache.kt +++ b/node/src/main/kotlin/net/corda/node/utilities/NodeNamedCache.kt @@ -45,8 +45,9 @@ open class DefaultNamedCacheFactory protected constructor(private val metricRegi name == "NodeAttachmentService_attachmentContent" -> caffeine.maximumWeight(attachmentContentCacheSizeBytes) name == "NodeAttachmentService_attachmentPresence" -> caffeine.maximumSize(attachmentCacheBound) name == "NodeAttachmentService_contractAttachmentVersions" -> caffeine.maximumSize(defaultCacheSize) - name == "PersistentIdentityService_partyByKey" -> caffeine.maximumSize(defaultCacheSize) - name == "PersistentIdentityService_partyByName" -> caffeine.maximumSize(defaultCacheSize) + name == "PersistentIdentityService_keyToPartyAndCert" -> caffeine.maximumSize(defaultCacheSize) + name == "PersistentIdentityService_nameToKey" -> caffeine.maximumSize(defaultCacheSize) + name == "PersistentIdentityService_keyToName" -> caffeine.maximumSize(defaultCacheSize) name == "PersistentNetworkMap_nodesByKey" -> caffeine.maximumSize(defaultCacheSize) name == "PersistentNetworkMap_idByLegalName" -> caffeine.maximumSize(defaultCacheSize) name == "PersistentKeyManagementService_keys" -> caffeine.maximumSize(defaultCacheSize) diff --git a/node/src/main/resources/migration/node-core.changelog-master.xml b/node/src/main/resources/migration/node-core.changelog-master.xml index e6d5f6c92e..e43c412cf5 100644 --- a/node/src/main/resources/migration/node-core.changelog-master.xml +++ b/node/src/main/resources/migration/node-core.changelog-master.xml @@ -20,5 +20,6 @@ created. --> + diff --git a/node/src/main/resources/migration/node-core.changelog-v12.xml b/node/src/main/resources/migration/node-core.changelog-v12.xml index 9eaf586563..0f607e5bbd 100644 --- a/node/src/main/resources/migration/node-core.changelog-v12.xml +++ b/node/src/main/resources/migration/node-core.changelog-v12.xml @@ -20,7 +20,6 @@ - diff --git a/node/src/main/resources/migration/node-core.changelog-v14.xml b/node/src/main/resources/migration/node-core.changelog-v14.xml new file mode 100644 index 0000000000..672ffff0bf --- /dev/null +++ b/node/src/main/resources/migration/node-core.changelog-v14.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/node/src/test/java/net/corda/node/services/vault/VaultQueryJavaTests.java b/node/src/test/java/net/corda/node/services/vault/VaultQueryJavaTests.java index cea9b63455..bfd36bd73c 100644 --- a/node/src/test/java/net/corda/node/services/vault/VaultQueryJavaTests.java +++ b/node/src/test/java/net/corda/node/services/vault/VaultQueryJavaTests.java @@ -16,23 +16,22 @@ import net.corda.core.node.services.AttachmentStorage; import net.corda.core.node.services.IdentityService; import net.corda.core.node.services.Vault; import net.corda.core.node.services.VaultService; +import net.corda.core.node.services.vault.AttachmentQueryCriteria.AttachmentsQueryCriteria; import net.corda.core.node.services.vault.*; import net.corda.core.node.services.vault.QueryCriteria.LinearStateQueryCriteria; import net.corda.core.node.services.vault.QueryCriteria.VaultCustomQueryCriteria; import net.corda.core.node.services.vault.QueryCriteria.VaultQueryCriteria; -import net.corda.core.node.services.vault.AttachmentQueryCriteria.AttachmentsQueryCriteria; import net.corda.finance.contracts.DealState; import net.corda.finance.contracts.asset.Cash; import net.corda.finance.schemas.CashSchemaV1; import net.corda.finance.test.SampleCashSchemaV2; -import net.corda.node.services.api.IdentityServiceInternal; import net.corda.node.services.persistence.NodeAttachmentService; import net.corda.nodeapi.internal.persistence.CordaPersistence; import net.corda.nodeapi.internal.persistence.DatabaseTransaction; -import net.corda.testing.core.internal.ContractJarTestUtils; -import net.corda.testing.core.internal.SelfCleaningDir; import net.corda.testing.core.SerializationEnvironmentRule; import net.corda.testing.core.TestIdentity; +import net.corda.testing.core.internal.ContractJarTestUtils; +import net.corda.testing.core.internal.SelfCleaningDir; import net.corda.testing.internal.TestingNamedCacheFactory; import net.corda.testing.internal.vault.DummyLinearContract; import net.corda.testing.internal.vault.VaultFiller; @@ -60,8 +59,8 @@ import static net.corda.core.node.services.vault.Builder.sum; import static net.corda.core.node.services.vault.QueryCriteriaUtils.*; import static net.corda.core.utilities.ByteArrays.toHexString; import static net.corda.testing.common.internal.ParametersUtilitiesKt.testNetworkParameters; -import static net.corda.testing.core.internal.ContractJarTestUtils.INSTANCE; import static net.corda.testing.core.TestConstants.*; +import static net.corda.testing.core.internal.ContractJarTestUtils.INSTANCE; import static net.corda.testing.node.MockServices.makeTestDatabaseAndMockServices; import static net.corda.testing.node.MockServicesKt.makeTestIdentityService; import static org.assertj.core.api.Assertions.assertThat; @@ -92,7 +91,7 @@ public class VaultQueryJavaTests { identitySvc, MEGA_CORP, DUMMY_NOTARY.getKeyPair()); - issuerServices = new MockServices(cordappPackages, DUMMY_CASH_ISSUER_INFO, mock(IdentityServiceInternal.class), BOC.getKeyPair()); + issuerServices = new MockServices(cordappPackages, DUMMY_CASH_ISSUER_INFO, mock(IdentityService.class), BOC.getKeyPair()); database = databaseAndServices.getFirst(); MockServices services = databaseAndServices.getSecond(); vaultFiller = new VaultFiller(services, DUMMY_NOTARY); diff --git a/node/src/test/kotlin/net/corda/node/migration/IdentityServiceToStringShortMigrationTest.kt b/node/src/test/kotlin/net/corda/node/migration/IdentityServiceToStringShortMigrationTest.kt index 7fa3e1e280..97d66b9a60 100644 --- a/node/src/test/kotlin/net/corda/node/migration/IdentityServiceToStringShortMigrationTest.kt +++ b/node/src/test/kotlin/net/corda/node/migration/IdentityServiceToStringShortMigrationTest.kt @@ -66,8 +66,8 @@ class IdentityServiceToStringShortMigrationTest { cordaDB.transaction { val groupedIdentities = identities.groupBy { it.name } groupedIdentities.forEach { name, certs -> - val persistentIDs = certs.map { PersistentIdentityService.PersistentIdentity(it.owningKey.hash.toString(), it.certPath.encoded) } - val persistentName = PersistentIdentityService.PersistentIdentityNames(name.toString(), certs.first().owningKey.hash.toString()) + val persistentIDs = certs.map { PersistentIdentityService.PersistentPublicKeyHashToCertificate(it.owningKey.hash.toString(), it.certPath.encoded) } + val persistentName = PersistentIdentityService.PersistentPartyToPublicKeyHash(name.toString(), certs.first().owningKey.hash.toString()) persistentIDs.forEach { session.persist(it) } diff --git a/node/src/test/kotlin/net/corda/node/migration/PersistentIdentityMigrationNewTableTest.kt b/node/src/test/kotlin/net/corda/node/migration/PersistentIdentityMigrationNewTableTest.kt new file mode 100644 index 0000000000..a89a48f363 --- /dev/null +++ b/node/src/test/kotlin/net/corda/node/migration/PersistentIdentityMigrationNewTableTest.kt @@ -0,0 +1,140 @@ +package net.corda.node.migration + +import liquibase.database.Database +import liquibase.database.jvm.JdbcConnection +import net.corda.core.crypto.SecureHash +import net.corda.core.identity.CordaX500Name +import net.corda.core.identity.PartyAndCertificate +import net.corda.core.internal.hash +import net.corda.core.internal.signWithCert +import net.corda.core.node.NetworkParameters +import net.corda.core.node.NotaryInfo +import net.corda.node.internal.DBNetworkParametersStorage +import net.corda.node.migration.VaultStateMigrationTest.Companion.CHARLIE +import net.corda.node.migration.VaultStateMigrationTest.Companion.DUMMY_NOTARY +import net.corda.node.services.identity.PersistentIdentityService +import net.corda.node.services.keys.BasicHSMKeyManagementService +import net.corda.nodeapi.internal.crypto.X509Utilities +import net.corda.nodeapi.internal.persistence.CordaPersistence +import net.corda.nodeapi.internal.persistence.DatabaseConfig +import net.corda.testing.core.* +import net.corda.testing.internal.configureDatabase +import net.corda.testing.node.MockServices +import net.corda.testing.node.makeTestIdentityService +import org.junit.After +import org.junit.Before +import org.junit.ClassRule +import org.junit.Test +import org.mockito.Mockito +import java.security.KeyPair +import java.time.Clock +import java.time.Duration + +class PersistentIdentityMigrationNewTableTest{ + companion object { + val alice = TestIdentity(ALICE_NAME, 70) + val bankOfCorda = TestIdentity(BOC_NAME) + val bob = TestIdentity(BOB_NAME, 80) + val dummyCashIssuer = TestIdentity(CordaX500Name("Snake Oil Issuer", "London", "GB"), 10) + val dummyNotary = TestIdentity(DUMMY_NOTARY_NAME, 20) + val ALICE_IDENTITY get() = alice.identity + val BOB_IDENTITY get() = bob.identity + val BOC_IDENTITY get() = bankOfCorda.identity + val BOC_KEY get() = bankOfCorda.keyPair + val bob2 = TestIdentity(BOB_NAME, 40) + val BOB2_IDENTITY = bob2.identity + + @ClassRule + @JvmField + val testSerialization = SerializationEnvironmentRule() + } + + lateinit var liquidBaseDB: Database + lateinit var cordaDB: CordaPersistence + lateinit var notaryServices: MockServices + + @Before + fun setUp() { + val identityService = makeTestIdentityService(PersistentIdentityMigrationNewTableTest.dummyNotary.identity, BOB_IDENTITY, ALICE_IDENTITY) + notaryServices = MockServices(listOf("net.corda.finance.contracts"), dummyNotary, identityService, dummyCashIssuer.keyPair, BOC_KEY) + // Runs migration tasks + cordaDB = configureDatabase( + MockServices.makeTestDataSourceProperties(), + DatabaseConfig(), + notaryServices.identityService::wellKnownPartyFromX500Name, + notaryServices.identityService::wellKnownPartyFromAnonymous, + ourName = BOB_IDENTITY.name) + val liquidbaseConnection = Mockito.mock(JdbcConnection::class.java) + Mockito.`when`(liquidbaseConnection.url).thenReturn(cordaDB.jdbcUrl) + Mockito.`when`(liquidbaseConnection.wrappedConnection).thenReturn(cordaDB.dataSource.connection) + liquidBaseDB = Mockito.mock(Database::class.java) + Mockito.`when`(liquidBaseDB.connection).thenReturn(liquidbaseConnection) + + cordaDB.dataSource.connection + saveOurKeys(listOf(bob.keyPair, bob2.keyPair)) + saveAllIdentities(listOf(BOB_IDENTITY, ALICE_IDENTITY, BOC_IDENTITY, dummyNotary.identity, BOB2_IDENTITY)) + addNetworkParameters() + } + + @After + fun `close`() { + cordaDB.close() + } + + @Test + fun `migrate identities to new table`() { + /** + * TODO - We have to mock every statement/ result to test this properly. + * + * The workaround for now is the [PersistentIdentitiesMigration.addTestMapping] and + * [PersistentIdentitiesMigration.deleteTestMapping] methods that allow us to see the migration occur properly during debugging. + * + * Since [PersistentIdentitiesMigration] implements [CordaMigration] the migration will run when the DB is setup. + */ + PersistentIdentityMigrationNewTable() + } + + private fun saveAllIdentities(identities: List) { + cordaDB.transaction { + identities.forEach { + session.save(PersistentIdentityService.PersistentPublicKeyHashToCertificate(it.owningKey.hash.toString(), it.certPath.encoded)) + } + } + } + + private fun saveOurKeys(keys: List) { + cordaDB.transaction { + keys.forEach { + val persistentKey = BasicHSMKeyManagementService.PersistentKey(it.public, it.private) + session.save(persistentKey) + } + } + } + + private fun addNetworkParameters() { + cordaDB.transaction { + val clock = Clock.systemUTC() + val params = NetworkParameters( + 1, + listOf(NotaryInfo(DUMMY_NOTARY, false), NotaryInfo(CHARLIE, false)), + 1, + 1, + clock.instant(), + 1, + mapOf(), + Duration.ZERO, + mapOf() + ) + val signedParams = params.signWithCert(bob.keyPair.private, BOB_IDENTITY.certificate) + val persistentParams = DBNetworkParametersStorage.PersistentNetworkParameters( + SecureHash.allOnesHash.toString(), + params.epoch, + signedParams.raw.bytes, + signedParams.sig.bytes, + signedParams.sig.by.encoded, + X509Utilities.buildCertPath(signedParams.sig.parentCertsChain).encoded + ) + session.save(persistentParams) + } + } +} \ No newline at end of file diff --git a/node/src/test/kotlin/net/corda/node/migration/VaultStateMigrationTest.kt b/node/src/test/kotlin/net/corda/node/migration/VaultStateMigrationTest.kt index 7d042c8f64..0482b218b1 100644 --- a/node/src/test/kotlin/net/corda/node/migration/VaultStateMigrationTest.kt +++ b/node/src/test/kotlin/net/corda/node/migration/VaultStateMigrationTest.kt @@ -194,8 +194,8 @@ class VaultStateMigrationTest { private fun saveAllIdentities(identities: List) { cordaDB.transaction { identities.groupBy { it.name }.forEach { name, certs -> - val persistentIDs = certs.map { PersistentIdentityService.PersistentIdentity(it.owningKey.toStringShort(), it.certPath.encoded) } - val persistentName = PersistentIdentityService.PersistentIdentityNames(name.toString(), certs.first().owningKey.toStringShort()) + val persistentIDs = certs.map { PersistentIdentityService.PersistentPublicKeyHashToCertificate(it.owningKey.toStringShort(), it.certPath.encoded) } + val persistentName = PersistentIdentityService.PersistentPartyToPublicKeyHash(name.toString(), certs.first().owningKey.toStringShort()) persistentIDs.forEach { session.save(it) } session.save(persistentName) } diff --git a/node/src/test/kotlin/net/corda/node/services/identity/PersistentIdentityServiceTests.kt b/node/src/test/kotlin/net/corda/node/services/identity/PersistentIdentityServiceTests.kt index 26886a011d..2259e9e4bd 100644 --- a/node/src/test/kotlin/net/corda/node/services/identity/PersistentIdentityServiceTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/identity/PersistentIdentityServiceTests.kt @@ -23,6 +23,8 @@ import org.junit.After import org.junit.Before import org.junit.Rule import org.junit.Test +import org.junit.jupiter.api.assertDoesNotThrow +import org.junit.jupiter.api.assertThrows import kotlin.test.assertEquals import kotlin.test.assertFailsWith import kotlin.test.assertNull @@ -235,6 +237,37 @@ class PersistentIdentityServiceTests { assertEquals(anonymousBob, bobReload!!) } + @Test + fun `ensure no exception when looking up an unregistered confidential identity`() { + val (alice, anonymousAlice) = createConfidentialIdentity(ALICE.name) + + // Ensure no exceptions are thrown if we attempt to look up an unregistered CI + assertNull(identityService.wellKnownPartyFromAnonymous(AnonymousParty(anonymousAlice.owningKey))) + } + + @Test + fun `register duplicate confidential identities`(){ + val (alice, anonymousAlice) = createConfidentialIdentity(ALICE.name) + + identityService.registerKeyToParty(anonymousAlice.owningKey, alice.party) + + // If an existing entry is found matching the party then the method call is idempotent + assertDoesNotThrow { + identityService.registerKeyToParty(anonymousAlice.owningKey, alice.party) + } + } + + @Test + fun `register incorrect party to public key `(){ + val (alice, anonymousAlice) = createConfidentialIdentity(ALICE.name) + + identityService.registerKeyToParty(anonymousAlice.owningKey, alice.party) + + assertThrows { + identityService.registerKeyToParty(anonymousAlice.owningKey, bob.party) + } + } + private fun createConfidentialIdentity(x500Name: CordaX500Name): Pair { val issuerKeyPair = generateKeyPair() val issuer = getTestPartyAndCertificate(x500Name, issuerKeyPair.public) @@ -272,4 +305,4 @@ class PersistentIdentityServiceTests { val actual = service.wellKnownPartyFromAnonymous(notAlice) assertNull(actual) } -} +} \ No newline at end of file diff --git a/node/src/test/kotlin/net/corda/node/services/persistence/HibernateConfigurationTest.kt b/node/src/test/kotlin/net/corda/node/services/persistence/HibernateConfigurationTest.kt index fa4e752f6a..b12c2e00ae 100644 --- a/node/src/test/kotlin/net/corda/node/services/persistence/HibernateConfigurationTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/persistence/HibernateConfigurationTest.kt @@ -23,19 +23,18 @@ import net.corda.finance.DOLLARS import net.corda.finance.POUNDS import net.corda.finance.SWISS_FRANCS import net.corda.finance.contracts.asset.Cash -import net.corda.node.testing.DummyFungibleContract +import net.corda.finance.contracts.utils.sumCash import net.corda.finance.schemas.CashSchemaV1 import net.corda.finance.test.SampleCashSchemaV1 import net.corda.finance.test.SampleCashSchemaV2 import net.corda.finance.test.SampleCashSchemaV3 -import net.corda.finance.contracts.utils.sumCash -import net.corda.node.services.api.IdentityServiceInternal import net.corda.node.services.api.WritableTransactionStorage import net.corda.node.services.schema.ContractStateAndRef import net.corda.node.services.schema.NodeSchemaService import net.corda.node.services.schema.PersistentStateService import net.corda.node.services.vault.NodeVaultService import net.corda.node.services.vault.VaultSchemaV1 +import net.corda.node.testing.DummyFungibleContract import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.DatabaseConfig import net.corda.nodeapi.internal.persistence.HibernateConfiguration @@ -119,8 +118,8 @@ class HibernateConfigurationTest { hibernateConfig = database.hibernateConfig // `consumeCash` expects we can self-notarise transactions - services = object : MockServices(cordappPackages, BOB_NAME, mock().also { - doNothing().whenever(it).justVerifyAndRegisterIdentity(argThat { name == BOB_NAME }, any()) + services = object : MockServices(cordappPackages, BOB_NAME, mock().also { + doReturn(null).whenever(it).verifyAndRegisterIdentity(argThat { name == BOB_NAME }) }, generateKeyPair(), dummyNotary.keyPair) { override val vaultService = NodeVaultService(Clock.systemUTC(), keyManagementService, servicesForResolution, database, schemaService, cordappClassloader).apply { start() } override fun recordTransactions(statesToRecord: StatesToRecord, txs: Iterable) { diff --git a/node/src/test/kotlin/net/corda/node/services/vault/NodeVaultServiceTest.kt b/node/src/test/kotlin/net/corda/node/services/vault/NodeVaultServiceTest.kt index a9c50a3ede..70e31a1355 100644 --- a/node/src/test/kotlin/net/corda/node/services/vault/NodeVaultServiceTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/vault/NodeVaultServiceTest.kt @@ -1,7 +1,10 @@ package net.corda.node.services.vault import co.paralleluniverse.fibers.Suspendable -import com.nhaarman.mockito_kotlin.* +import com.nhaarman.mockito_kotlin.argThat +import com.nhaarman.mockito_kotlin.doNothing +import com.nhaarman.mockito_kotlin.mock +import com.nhaarman.mockito_kotlin.whenever import net.corda.core.contracts.* import net.corda.core.crypto.NullKeys import net.corda.core.crypto.generateKeyPair @@ -26,7 +29,6 @@ import net.corda.finance.contracts.utils.sumCash import net.corda.finance.schemas.CashSchemaV1 import net.corda.finance.workflows.asset.CashUtils import net.corda.finance.workflows.getCashBalance -import net.corda.node.services.api.IdentityServiceInternal import net.corda.node.services.api.WritableTransactionStorage import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.testing.common.internal.testNetworkParameters @@ -40,9 +42,13 @@ import net.corda.testing.node.makeTestIdentityService import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThatExceptionOfType import org.junit.* +import org.mockito.Mockito.doReturn import rx.observers.TestSubscriber import java.math.BigDecimal import java.security.PublicKey +import java.security.cert.CertStore +import java.security.cert.TrustAnchor +import java.security.cert.X509Certificate import java.util.* import java.util.concurrent.CountDownLatch import java.util.concurrent.Executors @@ -565,17 +571,17 @@ class NodeVaultServiceTest { @Test fun `is ownable state relevant`() { - val myAnonymousIdentity = services.keyManagementService.freshKeyAndCert(identity, false) - val myKeys = services.keyManagementService.filterMyKeys(listOf(identity.owningKey, myAnonymousIdentity.owningKey)).toSet() + val myAnonymousIdentity = services.keyManagementService.freshKeyAndCert(identity, false) + val myKeys = services.keyManagementService.filterMyKeys(listOf(identity.owningKey, myAnonymousIdentity.owningKey)).toSet() - // Well-known owner - assertTrue { myKeys.isOwnableStateRelevant(identity.party, participants = emptyList()) } - // Anonymous owner - assertTrue { myKeys.isOwnableStateRelevant(myAnonymousIdentity.party, participants = emptyList()) } - // Unknown owner - assertFalse { myKeys.isOwnableStateRelevant(createUnknownIdentity(), participants = emptyList()) } - // Under target version 3 only the owner is relevant. This is to preserve backwards compatibility - assertFalse { myKeys.isOwnableStateRelevant(createUnknownIdentity(), participants = listOf(identity.party)) } + // Well-known owner + assertTrue { myKeys.isOwnableStateRelevant(identity.party, participants = emptyList()) } + // Anonymous owner + assertTrue { myKeys.isOwnableStateRelevant(myAnonymousIdentity.party, participants = emptyList()) } + // Unknown owner + assertFalse { myKeys.isOwnableStateRelevant(createUnknownIdentity(), participants = emptyList()) } + // Under target version 3 only the owner is relevant. This is to preserve backwards compatibility + assertFalse { myKeys.isOwnableStateRelevant(createUnknownIdentity(), participants = listOf(identity.party)) } } private fun createUnknownIdentity() = AnonymousParty(generateKeyPair().public) @@ -647,8 +653,8 @@ class NodeVaultServiceTest { val identity = services.myInfo.singleIdentityAndCert() assertEquals(services.identityService.partyFromKey(identity.owningKey), identity.party) val anonymousIdentity = services.keyManagementService.freshKeyAndCert(identity, false) - val thirdPartyServices = MockServices(emptyList(), MEGA_CORP.name, mock().also { - doNothing().whenever(it).justVerifyAndRegisterIdentity(argThat { name == MEGA_CORP.name }, any()) + val thirdPartyServices = MockServices(emptyList(), MEGA_CORP.name, mock().also { + doReturn(null).whenever(it).verifyAndRegisterIdentity(argThat { name == MEGA_CORP.name }) }) val thirdPartyIdentity = thirdPartyServices.keyManagementService.freshKeyAndCert(thirdPartyServices.myInfo.singleIdentityAndCert(), false) val amount = Amount(1000, Issued(BOC.ref(1), GBP)) @@ -945,4 +951,4 @@ class NodeVaultServiceTest { assertTrue(it) } } -} +} \ No newline at end of file diff --git a/samples/irs-demo/cordapp/contracts-irs/src/test/kotlin/net/corda/irs/contract/IRSTests.kt b/samples/irs-demo/cordapp/contracts-irs/src/test/kotlin/net/corda/irs/contract/IRSTests.kt index 8cc8b0f576..961ef7abdf 100644 --- a/samples/irs-demo/cordapp/contracts-irs/src/test/kotlin/net/corda/irs/contract/IRSTests.kt +++ b/samples/irs-demo/cordapp/contracts-irs/src/test/kotlin/net/corda/irs/contract/IRSTests.kt @@ -8,6 +8,7 @@ import net.corda.core.contracts.UniqueIdentifier import net.corda.core.crypto.generateKeyPair import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party +import net.corda.core.node.services.IdentityService import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.TransactionBuilder import net.corda.core.utilities.seconds @@ -15,7 +16,6 @@ import net.corda.finance.DOLLARS import net.corda.finance.EUR import net.corda.finance.contracts.* import net.corda.finance.workflows.utils.loadTestCalendar -import net.corda.node.services.api.IdentityServiceInternal import net.corda.testing.common.internal.addNotary import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.core.DUMMY_NOTARY_NAME @@ -235,7 +235,7 @@ class IRSTests { private val ledgerServices = MockServices( emptyList(), megaCorp, - mock().also { + mock().also { doReturn(megaCorp.party).whenever(it).partyFromKey(megaCorp.publicKey) doReturn(null).whenever(it).partyFromKey(ORACLE_PUBKEY) }, @@ -337,7 +337,7 @@ class IRSTests { @Test fun generateIRSandFixSome() { val services = MockServices(listOf("net.corda.irs.contract"), MEGA_CORP.name, - mock().also { + mock().also { listOf(MEGA_CORP, MINI_CORP).forEach { party -> doReturn(party).whenever(it).partyFromKey(party.owningKey) }