mirror of
https://github.com/corda/corda.git
synced 2024-12-18 20:47:57 +00:00
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
This commit is contained in:
parent
b97062bacc
commit
07b96aea18
@ -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<IdentityServiceInternal>().also {
|
||||
private val ledgerServices = MockServices(emptyList(), MEGA_CORP.name, mock<IdentityService>().also {
|
||||
doReturn(MEGA_CORP).whenever(it).partyFromKey(MEGA_CORP_PUBKEY)
|
||||
doReturn(DUMMY_CASH_ISSUER.party).whenever(it).partyFromKey(DUMMY_CASH_ISSUER_KEY.public)
|
||||
})
|
||||
|
@ -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<IdentityServiceInternal>().also {
|
||||
identityService = mock<IdentityService>().also {
|
||||
doReturn(ALICE_PARTY).whenever(it).partyFromKey(ALICE_PUBKEY)
|
||||
doReturn(BOB_PARTY).whenever(it).partyFromKey(BOB_PUBKEY)
|
||||
},
|
||||
|
@ -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<IdentityServiceInternal>().also {
|
||||
identityService = mock<IdentityService>().also {
|
||||
doReturn(ALICE_PARTY).whenever(it).partyFromKey(ALICE_PUBKEY)
|
||||
doReturn(BOB_PARTY).whenever(it).partyFromKey(BOB_PUBKEY)
|
||||
},
|
||||
|
@ -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<IdentityServiceInternal>().also {
|
||||
identityService = mock<IdentityService>().also {
|
||||
doReturn(MEGA_CORP).whenever(it).partyFromKey(MEGA_CORP_PUBKEY)
|
||||
},
|
||||
networkParameters = testNetworkParameters(minimumPlatformVersion = 4, notaries = listOf(NotaryInfo(DUMMY_NOTARY, true)))
|
||||
|
@ -202,7 +202,7 @@ class AnonymousSessionTestFlow(private val cis: List<PartyAndCertificate>) : 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<PartyAndCertific
|
||||
|
||||
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) })
|
||||
|
@ -9,9 +9,9 @@ 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.node.services.api.IdentityServiceInternal
|
||||
import net.corda.testing.common.internal.testNetworkParameters
|
||||
import net.corda.testing.contracts.DummyContract
|
||||
import net.corda.testing.core.*
|
||||
@ -36,7 +36,7 @@ class LedgerTransactionQueryTests {
|
||||
private val services = MockServices(
|
||||
listOf("net.corda.testing.contracts"),
|
||||
TestIdentity(CordaX500Name("MegaCorp", "London", "GB"), keyPair),
|
||||
mock<IdentityServiceInternal>().also {
|
||||
mock<IdentityService>().also {
|
||||
doReturn(null).whenever(it).partyFromKey(keyPair.public)
|
||||
},
|
||||
testNetworkParameters(notaries = listOf(NotaryInfo(DUMMY_NOTARY, true))),
|
||||
|
@ -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<IdentityServiceInternal>().also {
|
||||
identityService = mock<IdentityService>().also {
|
||||
doReturn(ALICE_PARTY).whenever(it).partyFromKey(ALICE_PUBKEY)
|
||||
doReturn(BOB_PARTY).whenever(it).partyFromKey(BOB_PUBKEY)
|
||||
},
|
||||
|
@ -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<IdentityServiceInternal>().also {
|
||||
mock<IdentityService>().also {
|
||||
doReturn(MEGA_CORP).whenever(it).partyFromKey(MEGA_CORP_PUBKEY)
|
||||
},
|
||||
testNetworkParameters(notaries = listOf(NotaryInfo(DUMMY_NOTARY, true)))
|
||||
|
@ -77,3 +77,12 @@ fun <T> excludeHostNode(serviceHub: ServiceHub, map: Map<Party, T>): Map<Party,
|
||||
* @return a new copy of the map, with the well known [Party] for the notary removed.
|
||||
*/
|
||||
fun <T> excludeNotary(map: Map<Party, T>, stx: SignedTransaction): Map<Party, T> = 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)) }
|
||||
}
|
@ -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<Party>
|
||||
|
||||
/**
|
||||
* 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)
|
||||
|
@ -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<TransactionDSLInterpreter>.() -> EnforceVerifyOrFail) = run {
|
||||
MockServices(listOf("net.corda.finance.contracts.universal"), CordaX500Name("MegaCorp", "London", "GB"),
|
||||
mock<IdentityServiceInternal>().also {
|
||||
mock<IdentityService>().also {
|
||||
listOf(acmeCorp, highStreetBank, momAndPop).forEach { party ->
|
||||
doReturn(null).whenever(it).partyFromKey(party.owningKey)
|
||||
}
|
||||
|
@ -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 -> {
|
||||
|
@ -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<IdentityServiceInternal>().also {
|
||||
private val identityService = mock<IdentityService>().also {
|
||||
doReturn(null).whenever(it).partyFromKey(ALICE_PUBKEY)
|
||||
doReturn(null).whenever(it).partyFromKey(BOB_PUBKEY)
|
||||
doReturn(null).whenever(it).partyFromKey(CHARLIE.owningKey)
|
||||
|
@ -856,7 +856,7 @@ abstract class AbstractNode<S>(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.
|
||||
|
@ -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)
|
||||
|
@ -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<String, CordaX500Name> {
|
||||
val keyParties = mutableMapOf<String, CordaX500Name>()
|
||||
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<String, CordaX500Name>) {
|
||||
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)
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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<PartyAndCertificate> = 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<PartyAndCertificate> = emptyList(
|
||||
*/
|
||||
override val caCertStore: CertStore = CertStore.getInstance("Collection", CollectionCertStoreParameters(setOf(trustRoot)))
|
||||
override val trustAnchor: TrustAnchor = TrustAnchor(trustRoot, null)
|
||||
private val keyToParties = ConcurrentHashMap<PublicKey, PartyAndCertificate>()
|
||||
private val principalToParties = ConcurrentHashMap<CordaX500Name, PartyAndCertificate>()
|
||||
private val keyToPartyAndCerts = ConcurrentHashMap<PublicKey, PartyAndCertificate>()
|
||||
private val nameToKey = ConcurrentHashMap<CordaX500Name, PublicKey>()
|
||||
private val keyToName = ConcurrentHashMap<PublicKey, CordaX500Name>()
|
||||
|
||||
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<PartyAndCertificate> = ArrayList(keyToParties.values)
|
||||
override fun getAllIdentities(): Iterable<PartyAndCertificate> = 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<Party> {
|
||||
val results = LinkedHashSet<Party>()
|
||||
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.")
|
||||
}
|
||||
}
|
||||
|
@ -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<String, PartyAndCertificate, PersistentIdentity, String> {
|
||||
fun createKeyToPartyAndCertMap(cacheFactory: NamedCacheFactory): AppendOnlyPersistentMap<String, PartyAndCertificate, PersistentPublicKeyHashToCertificate, String> {
|
||||
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<CordaX500Name, String, PersistentIdentityNames, String> {
|
||||
fun createX500ToKeyMap(cacheFactory: NamedCacheFactory): AppendOnlyPersistentMap<CordaX500Name, String, PersistentPartyToPublicKeyHash, String> {
|
||||
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<String, CordaX500Name, PersistentPublicKeyHashToParty, String> {
|
||||
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<X509Certificate> = emptyList(), notaryIdentities: List<Party> = emptyList()) {
|
||||
_trustRoot = trustRoot
|
||||
@ -128,51 +160,75 @@ class PersistentIdentityService(cacheFactory: NamedCacheFactory) : SingletonSeri
|
||||
fun loadIdentities(identities: Collection<PartyAndCertificate> = emptySet(), confidentialIdentities: Collection<PartyAndCertificate> = 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<PartyAndCertificate> {
|
||||
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<Party> {
|
||||
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<PublicKey>): Iterable<PublicKey> {
|
||||
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 .")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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<String>(PersistentIdentityService.PersistentIdentity::publicKeyHash.name), key.toStringShort())
|
||||
criteriaBuilder.equal(queryRoot.get<String>(PersistentIdentityService.PersistentPublicKeyHashToCertificate::publicKeyHash.name), key.toStringShort())
|
||||
)
|
||||
val query = session.createQuery(criteriaQuery)
|
||||
query.uniqueResult() > 0
|
||||
|
@ -40,8 +40,9 @@ class NodeSchemaService(private val extraSchemas: Set<MappedSchema> = 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
|
||||
|
@ -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)
|
||||
|
@ -20,5 +20,6 @@
|
||||
created. -->
|
||||
<include file="migration/node-core.changelog-v13.xml"/>
|
||||
<!-- This must run after node-core.changelog-init.xml, to prevent database columns being created twice. -->
|
||||
<include file="migration/node-core.changelog-v14.xml"/>
|
||||
<include file="migration/vault-schema.changelog-v9.xml"/>
|
||||
</databaseChangeLog>
|
||||
|
@ -20,7 +20,6 @@
|
||||
|
||||
</changeSet>
|
||||
|
||||
|
||||
<changeSet author="R3.Corda" id="migrate_identity_service_to_use_publicKey.toShortString()">
|
||||
<customChange class="net.corda.node.migration.PersistentIdentityMigration">
|
||||
</customChange>
|
||||
|
@ -0,0 +1,23 @@
|
||||
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
|
||||
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.5.xsd"
|
||||
logicalFilePath="migration/node-services.changelog-init.xml">
|
||||
|
||||
<changeSet author="R3.Corda" id="add-new-persistence-table">
|
||||
<createTable tableName="identities_no_cert">
|
||||
<column name="pk_hash" type="NVARCHAR(130)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="name" type="NVARCHAR(128)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</createTable>
|
||||
</changeSet>
|
||||
|
||||
<changeSet author="R3.Corda" id="transfer-to-new-persistence-table">
|
||||
<customChange class="net.corda.node.migration.PersistentIdentityMigrationNewTable">
|
||||
</customChange>
|
||||
</changeSet>
|
||||
|
||||
</databaseChangeLog>
|
@ -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);
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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<PartyAndCertificate>) {
|
||||
cordaDB.transaction {
|
||||
identities.forEach {
|
||||
session.save(PersistentIdentityService.PersistentPublicKeyHashToCertificate(it.owningKey.hash.toString(), it.certPath.encoded))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun saveOurKeys(keys: List<KeyPair>) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
@ -194,8 +194,8 @@ class VaultStateMigrationTest {
|
||||
private fun saveAllIdentities(identities: List<PartyAndCertificate>) {
|
||||
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)
|
||||
}
|
||||
|
@ -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<IllegalArgumentException> {
|
||||
identityService.registerKeyToParty(anonymousAlice.owningKey, bob.party)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createConfidentialIdentity(x500Name: CordaX500Name): Pair<PartyAndCertificate, PartyAndCertificate> {
|
||||
val issuerKeyPair = generateKeyPair()
|
||||
val issuer = getTestPartyAndCertificate(x500Name, issuerKeyPair.public)
|
||||
@ -272,4 +305,4 @@ class PersistentIdentityServiceTests {
|
||||
val actual = service.wellKnownPartyFromAnonymous(notAlice)
|
||||
assertNull(actual)
|
||||
}
|
||||
}
|
||||
}
|
@ -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<IdentityServiceInternal>().also {
|
||||
doNothing().whenever(it).justVerifyAndRegisterIdentity(argThat { name == BOB_NAME }, any())
|
||||
services = object : MockServices(cordappPackages, BOB_NAME, mock<IdentityService>().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<SignedTransaction>) {
|
||||
|
@ -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<IdentityServiceInternal>().also {
|
||||
doNothing().whenever(it).justVerifyAndRegisterIdentity(argThat { name == MEGA_CORP.name }, any())
|
||||
val thirdPartyServices = MockServices(emptyList(), MEGA_CORP.name, mock<IdentityService>().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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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<IdentityServiceInternal>().also {
|
||||
mock<IdentityService>().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<IdentityServiceInternal>().also {
|
||||
mock<IdentityService>().also {
|
||||
listOf(MEGA_CORP, MINI_CORP).forEach { party ->
|
||||
doReturn(party).whenever(it).partyFromKey(party.owningKey)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user