ENT-5888: Resurrect node_hash_to_key (#6776)

This commit is contained in:
Denis Rekalov 2020-10-19 12:56:12 +03:00 committed by GitHub
parent 551b3f0811
commit 401d8b8856
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 88 additions and 235 deletions

View File

@ -30,6 +30,7 @@ class MigrationNamedCacheFactory(private val metricRegistry: MetricRegistry?,
"PersistentIdentityService_keyToPartyAndCert" -> caffeine.maximumSize(defaultCacheSize) "PersistentIdentityService_keyToPartyAndCert" -> caffeine.maximumSize(defaultCacheSize)
"PersistentIdentityService_nameToParty" -> caffeine.maximumSize(defaultCacheSize) "PersistentIdentityService_nameToParty" -> caffeine.maximumSize(defaultCacheSize)
"PersistentIdentityService_keyToParty" -> caffeine.maximumSize(defaultCacheSize) "PersistentIdentityService_keyToParty" -> caffeine.maximumSize(defaultCacheSize)
"PersistentIdentityService_hashToKey" -> caffeine.maximumSize(defaultCacheSize)
"PersistentNetworkMap_nodesByKey" -> caffeine.maximumSize(defaultCacheSize) "PersistentNetworkMap_nodesByKey" -> caffeine.maximumSize(defaultCacheSize)
"PersistentNetworkMap_idByLegalName" -> caffeine.maximumSize(defaultCacheSize) "PersistentNetworkMap_idByLegalName" -> caffeine.maximumSize(defaultCacheSize)
"BasicHSMKeyManagementService_keys" -> caffeine.maximumSize(defaultCacheSize) "BasicHSMKeyManagementService_keys" -> caffeine.maximumSize(defaultCacheSize)

View File

@ -1,102 +0,0 @@
package net.corda.node.migration
import liquibase.change.custom.CustomTaskChange
import liquibase.database.Database
import liquibase.database.jvm.JdbcConnection
import liquibase.exception.ValidationErrors
import liquibase.resource.ResourceAccessor
import net.corda.core.identity.PartyAndCertificate
import net.corda.core.utilities.contextLogger
import net.corda.nodeapi.internal.crypto.X509CertificateFactory
import java.sql.ResultSet
class NodeIdentitiesNoCertMigration : CustomTaskChange {
companion object {
private val logger = contextLogger()
const val UNRESOLVED = "Unresolved"
}
@Suppress("MagicNumber")
override fun execute(database: Database) {
val connection = database.connection as JdbcConnection
logger.info("Preparing to migrate node_identities_no_cert.")
val nodeKeysByHash = mutableMapOf<String, ByteArray>()
connection.queryAll("SELECT pk_hash, identity_value FROM node_identities") { resultSet ->
val hash = resultSet.getString(1)
val certificateBytes = resultSet.getBytes(2)
nodeKeysByHash[hash] = certificateBytes.toKeyBytes()
}
val nodeKeyHashesByName = mutableMapOf<String, String>()
connection.queryAll("SELECT name, pk_hash FROM node_named_identities") { resultSet ->
val name = resultSet.getString(1)
val hash = resultSet.getString(2)
nodeKeyHashesByName[name] = hash
}
logger.info("Starting to migrate node_identities_no_cert.")
var count = 0
connection.queryAll("SELECT pk_hash, name FROM node_identities_no_cert") { resultSet ->
val hash = resultSet.getString(1)
val name = resultSet.getString(2)
val partyKeyHash = nodeKeysByHash[hash]?.let { hash }
?: nodeKeyHashesByName[name]
?: UNRESOLVED.also { logger.warn("Unable to find party key hash for [$name] [$hash]") }
val key = nodeKeysByHash[hash]
?: connection.query("SELECT public_key FROM v_our_key_pairs WHERE public_key_hash = ?", hash)
?: connection.query("SELECT public_key FROM node_hash_to_key WHERE pk_hash = ?", hash)
?: UNRESOLVED.toByteArray().also { logger.warn("Unable to find key for [$name] [$hash]") }
connection.prepareStatement("UPDATE node_identities_no_cert SET party_pk_hash = ?, public_key = ? WHERE pk_hash = ?").use {
it.setString(1, partyKeyHash)
it.setBytes(2, key)
it.setString(3, hash)
it.executeUpdate()
}
count++
}
logger.info("Migrated $count node_identities_no_cert entries.")
}
private fun JdbcConnection.queryAll(statement: String, action: (ResultSet) -> Unit) = createStatement().use {
it.executeQuery(statement).use { resultSet ->
while (resultSet.next()) {
action.invoke(resultSet)
}
}
}
private fun JdbcConnection.query(statement: String, key: String): ByteArray? = prepareStatement(statement).use {
it.setString(1, key)
it.executeQuery().use { resultSet ->
if (resultSet.next()) resultSet.getBytes(1) else null
}
}
private fun ByteArray.toKeyBytes(): ByteArray {
val certPath = X509CertificateFactory().delegate.generateCertPath(inputStream())
val partyAndCertificate = PartyAndCertificate(certPath)
return partyAndCertificate.party.owningKey.encoded
}
override fun setUp() {
}
override fun setFileOpener(resourceAccessor: ResourceAccessor?) {
}
override fun getConfirmationMessage(): String? {
return null
}
override fun validate(database: Database?): ValidationErrors? {
return null
}
}

View File

@ -19,6 +19,7 @@ import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.debug import net.corda.core.utilities.debug
import net.corda.node.internal.schemas.NodeInfoSchemaV1 import net.corda.node.internal.schemas.NodeInfoSchemaV1
import net.corda.node.services.api.IdentityServiceInternal import net.corda.node.services.api.IdentityServiceInternal
import net.corda.node.services.keys.BasicHSMKeyManagementService
import net.corda.node.services.network.NotaryUpdateListener import net.corda.node.services.network.NotaryUpdateListener
import net.corda.node.services.persistence.PublicKeyHashToExternalId import net.corda.node.services.persistence.PublicKeyHashToExternalId
import net.corda.node.services.persistence.WritablePublicKeyToOwningIdentityCache import net.corda.node.services.persistence.WritablePublicKeyToOwningIdentityCache
@ -81,15 +82,13 @@ class PersistentIdentityService(cacheFactory: NamedCacheFactory) : SingletonSeri
} }
/** /**
* Link anonymous public key to well known party (substituting well-known party public key with its hash). * [Party] with owning key replaced by its hash.
* Public key for well-known party is linked to itself.
*/ */
private data class KeyToParty(val publicKey: PublicKey, val name: CordaX500Name, val partyPublicKeyHash: String) { private data class PartyWithHash(val name: CordaX500Name, val owningKeyHash: String) {
constructor(party: Party, publicKey: PublicKey = party.owningKey) : this(publicKey, party.name, party.owningKey.toStringShort()) constructor(party: Party) : this(party.name, party.owningKey.toStringShort())
val party get() = Party(name, publicKey)
} }
private fun createKeyToPartyMap(cacheFactory: NamedCacheFactory): AppendOnlyPersistentMap<String, KeyToParty, private fun createKeyToPartyMap(cacheFactory: NamedCacheFactory): AppendOnlyPersistentMap<String, PartyWithHash,
PersistentPublicKeyHashToParty, String> { PersistentPublicKeyHashToParty, String> {
return AppendOnlyPersistentMap( return AppendOnlyPersistentMap(
cacheFactory = cacheFactory, cacheFactory = cacheFactory,
@ -98,11 +97,11 @@ class PersistentIdentityService(cacheFactory: NamedCacheFactory) : SingletonSeri
fromPersistentEntity = { fromPersistentEntity = {
Pair( Pair(
it.publicKeyHash, it.publicKeyHash,
KeyToParty(Crypto.decodePublicKey(it.publicKey), CordaX500Name.parse(it.name), it.partyPublicKeyHash) PartyWithHash(CordaX500Name.parse(it.name), it.owningKeyHash)
) )
}, },
toPersistentEntity = { key: String, value: KeyToParty -> toPersistentEntity = { key: String, value: PartyWithHash ->
PersistentPublicKeyHashToParty(key, value.name.toString(), value.partyPublicKeyHash, value.publicKey.encoded) PersistentPublicKeyHashToParty(key, value.name.toString(), value.owningKeyHash)
}, },
persistentEntityClass = PersistentPublicKeyHashToParty::class.java) persistentEntityClass = PersistentPublicKeyHashToParty::class.java)
} }
@ -118,6 +117,24 @@ class PersistentIdentityService(cacheFactory: NamedCacheFactory) : SingletonSeri
) )
} }
fun createHashToKeyMap(cacheFactory: NamedCacheFactory): AppendOnlyPersistentMap<String, PublicKey, PersistentHashToPublicKey,
String> {
return AppendOnlyPersistentMap(
cacheFactory = cacheFactory,
name = "PersistentIdentityService_hashToKey",
toPersistentEntityKey = { it },
fromPersistentEntity = {
Pair(
it.publicKeyHash,
Crypto.decodePublicKey(it.publicKey)
)
},
toPersistentEntity = { key: String, value: PublicKey ->
PersistentHashToPublicKey(key, value.encoded)
},
persistentEntityClass = PersistentHashToPublicKey::class.java)
}
private fun mapToKey(party: PartyAndCertificate) = party.owningKey.toStringShort() private fun mapToKey(party: PartyAndCertificate) = party.owningKey.toStringShort()
} }
@ -136,7 +153,6 @@ class PersistentIdentityService(cacheFactory: NamedCacheFactory) : SingletonSeri
@Entity @Entity
@javax.persistence.Table(name = "${NODE_DATABASE_PREFIX}identities_no_cert") @javax.persistence.Table(name = "${NODE_DATABASE_PREFIX}identities_no_cert")
class PersistentPublicKeyHashToParty( class PersistentPublicKeyHashToParty(
@Suppress("Unused")
@Id @Id
@Column(name = "pk_hash", length = MAX_HASH_HEX_SIZE, nullable = false) @Column(name = "pk_hash", length = MAX_HASH_HEX_SIZE, nullable = false)
var publicKeyHash: String = "", var publicKeyHash: String = "",
@ -144,8 +160,16 @@ class PersistentIdentityService(cacheFactory: NamedCacheFactory) : SingletonSeri
@Column(name = "name", length = 128, nullable = false) @Column(name = "name", length = 128, nullable = false)
var name: String = "", var name: String = "",
@Column(name = "party_pk_hash", length = MAX_HASH_HEX_SIZE, nullable = false) @Column(name = "owning_pk_hash", length = MAX_HASH_HEX_SIZE, nullable = false)
var partyPublicKeyHash: String = "", var owningKeyHash: String = ""
)
@Entity
@javax.persistence.Table(name = "${NODE_DATABASE_PREFIX}hash_to_key")
class PersistentHashToPublicKey(
@Id
@Column(name = "pk_hash", length = MAX_HASH_HEX_SIZE, nullable = false)
var publicKeyHash: String = "",
@Type(type = "corda-blob") @Type(type = "corda-blob")
@Column(name = "public_key", nullable = false) @Column(name = "public_key", nullable = false)
@ -175,6 +199,7 @@ class PersistentIdentityService(cacheFactory: NamedCacheFactory) : SingletonSeri
private val keyToPartyAndCert = createKeyToPartyAndCertMap(cacheFactory) private val keyToPartyAndCert = createKeyToPartyAndCertMap(cacheFactory)
private val keyToParty = createKeyToPartyMap(cacheFactory) private val keyToParty = createKeyToPartyMap(cacheFactory)
private val nameToParty = createNameToPartyMap(cacheFactory) private val nameToParty = createNameToPartyMap(cacheFactory)
private val hashToKey = createHashToKeyMap(cacheFactory)
fun start( fun start(
trustRoot: X509Certificate, trustRoot: X509Certificate,
@ -196,7 +221,7 @@ class PersistentIdentityService(cacheFactory: NamedCacheFactory) : SingletonSeri
identities.forEach { identities.forEach {
val key = mapToKey(it) val key = mapToKey(it)
keyToPartyAndCert.addWithDuplicatesAllowed(key, it, false) keyToPartyAndCert.addWithDuplicatesAllowed(key, it, false)
keyToParty.addWithDuplicatesAllowed(it.owningKey.toStringShort(), KeyToParty(it.party), false) keyToParty.addWithDuplicatesAllowed(it.owningKey.toStringShort(), PartyWithHash(it.party), false)
nameToParty.asMap()[it.name] = Optional.of(it.party) nameToParty.asMap()[it.name] = Optional.of(it.party)
} }
log.debug("Identities loaded") log.debug("Identities loaded")
@ -250,7 +275,7 @@ class PersistentIdentityService(cacheFactory: NamedCacheFactory) : SingletonSeri
// keyToParty is already registered via KMS freshKeyInternal() // keyToParty is already registered via KMS freshKeyInternal()
} else { } else {
keyToPartyAndCert.addWithDuplicatesAllowed(key, identity, false) keyToPartyAndCert.addWithDuplicatesAllowed(key, identity, false)
keyToParty.addWithDuplicatesAllowed(identity.owningKey.toStringShort(), KeyToParty(identity.party), false) keyToParty.addWithDuplicatesAllowed(identity.owningKey.toStringShort(), PartyWithHash(identity.party), false)
} }
val parentId = identityCertChain[1].publicKey.toStringShort() val parentId = identityCertChain[1].publicKey.toStringShort()
keyToPartyAndCert[parentId] keyToPartyAndCert[parentId]
@ -265,16 +290,11 @@ class PersistentIdentityService(cacheFactory: NamedCacheFactory) : SingletonSeri
keyToPartyAndCert[owningKey.toStringShort()] keyToPartyAndCert[owningKey.toStringShort()]
} }
override fun partyFromKey(key: PublicKey): Party? = database.transaction { override fun partyFromKey(key: PublicKey): Party? {
keyToParty[key.toStringShort()]?.let { return certificateFromKey(key)?.takeIf { CertRole.extract(it.certificate)?.isWellKnown == false }?.party ?:
if (it.partyPublicKeyHash == key.toStringShort()) { database.transaction {
// Well-known party is linked to itself. keyToParty[key.toStringShort()]
it.party }?.let { wellKnownPartyFromX500Name(it.name) }
} else {
// Anonymous party is linked to well-known party.
keyToParty[it.partyPublicKeyHash]?.party
}
}
} }
// We give the caller a copy of the data set to avoid any locking problems // We give the caller a copy of the data set to avoid any locking problems
@ -333,20 +353,21 @@ class PersistentIdentityService(cacheFactory: NamedCacheFactory) : SingletonSeri
override fun registerKey(publicKey: PublicKey, party: Party, externalId: UUID?) { override fun registerKey(publicKey: PublicKey, party: Party, externalId: UUID?) {
return database.transaction { return database.transaction {
val publicKeyHash = publicKey.toStringShort()
// EVERY key should be mapped to a Party in the "keyToName" table. Therefore if there is already a record in that table for the // EVERY key should be mapped to a Party in the "keyToName" table. Therefore if there is already a record in that table for the
// specified key then it's either our key which has been stored prior or another node's key which we have previously mapped. // specified key then it's either our key which has been stored prior or another node's key which we have previously mapped.
val existingEntryForKey = keyToParty[publicKey.toStringShort()] val existingEntryForKey = keyToParty[publicKeyHash]
if (existingEntryForKey == null) { if (existingEntryForKey == null) {
// Update the three tables as necessary. We definitely store the public key and map it to a party and we optionally update // Update the three tables as necessary. We definitely store the public key and map it to a party and we optionally update
// the public key to external ID mapping table. This block will only ever be reached when registering keys generated on // the public key to external ID mapping table. This block will only ever be reached when registering keys generated on
// other because when a node generates its own keys "registerKeyToParty" is automatically called by // other because when a node generates its own keys "registerKeyToParty" is automatically called by
// KeyManagementService.freshKey. // KeyManagementService.freshKey.
registerKeyToParty(publicKey, party) registerKeyToParty(publicKey, party)
hashToKey[publicKeyHash] = publicKey
if (externalId != null) { if (externalId != null) {
registerKeyToExternalId(publicKey, externalId) registerKeyToExternalId(publicKey, externalId)
} }
} else { } else {
val publicKeyHash = publicKey.toStringShort()
log.info("An existing entry for $publicKeyHash already exists.") log.info("An existing entry for $publicKeyHash already exists.")
if (party.name != existingEntryForKey.name) { if (party.name != existingEntryForKey.name) {
throw IllegalStateException("The public publicKey $publicKeyHash is already assigned to a different party than the " + throw IllegalStateException("The public publicKey $publicKeyHash is already assigned to a different party than the " +
@ -360,7 +381,7 @@ class PersistentIdentityService(cacheFactory: NamedCacheFactory) : SingletonSeri
fun registerKeyToParty(publicKey: PublicKey, party: Party = ourParty) { fun registerKeyToParty(publicKey: PublicKey, party: Party = ourParty) {
return database.transaction { return database.transaction {
log.info("Linking: ${publicKey.hash} to ${party.name}") log.info("Linking: ${publicKey.hash} to ${party.name}")
keyToParty[publicKey.toStringShort()] = KeyToParty(party, publicKey) keyToParty[publicKey.toStringShort()] = PartyWithHash(party)
if (party == ourParty) { if (party == ourParty) {
_pkToIdCache[publicKey] = KeyOwningIdentity.UnmappedIdentity _pkToIdCache[publicKey] = KeyOwningIdentity.UnmappedIdentity
} }
@ -376,12 +397,12 @@ class PersistentIdentityService(cacheFactory: NamedCacheFactory) : SingletonSeri
return _pkToIdCache[publicKey].uuid return _pkToIdCache[publicKey].uuid
} }
override fun publicKeysForExternalId(externalId: UUID): Iterable<PublicKey> { private fun publicKeysForExternalId(externalId: UUID, table: Class<*>): List<PublicKey> {
return database.transaction { return database.transaction {
val query = session.createQuery( val query = session.createQuery(
""" """
select a.publicKey select a.publicKey
from ${PersistentPublicKeyHashToParty::class.java.name} a, ${PublicKeyHashToExternalId::class.java.name} b from ${table.name} a, ${PublicKeyHashToExternalId::class.java.name} b
where b.externalId = :uuid where b.externalId = :uuid
and b.publicKeyHash = a.publicKeyHash and b.publicKeyHash = a.publicKeyHash
""", """,
@ -392,6 +413,16 @@ class PersistentIdentityService(cacheFactory: NamedCacheFactory) : SingletonSeri
} }
} }
override fun publicKeysForExternalId(externalId: UUID): Iterable<PublicKey> {
// If the externalId was created by this node then we'll find the keys in the KMS, otherwise they'll be in the IdentityService.
val keys = publicKeysForExternalId(externalId, BasicHSMKeyManagementService.PersistentKey::class.java)
return if (keys.isEmpty()) {
publicKeysForExternalId(externalId, PersistentHashToPublicKey::class.java)
} else {
keys
}
}
override fun onNewNotaryList(notaries: List<NotaryInfo>) { override fun onNewNotaryList(notaries: List<NotaryInfo>) {
notaryIdentityCache = HashSet(notaries.map { it.identity }) notaryIdentityCache = HashSet(notaries.map { it.identity })
} }

View File

@ -46,6 +46,7 @@ class NodeSchemaService(private val extraSchemas: Set<MappedSchema> = emptySet()
P2PMessageDeduplicator.ProcessedMessage::class.java, P2PMessageDeduplicator.ProcessedMessage::class.java,
PersistentIdentityService.PersistentPublicKeyHashToCertificate::class.java, PersistentIdentityService.PersistentPublicKeyHashToCertificate::class.java,
PersistentIdentityService.PersistentPublicKeyHashToParty::class.java, PersistentIdentityService.PersistentPublicKeyHashToParty::class.java,
PersistentIdentityService.PersistentHashToPublicKey::class.java,
ContractUpgradeServiceImpl.DBContractUpgrade::class.java, ContractUpgradeServiceImpl.DBContractUpgrade::class.java,
DBNetworkParametersStorage.PersistentNetworkParameters::class.java, DBNetworkParametersStorage.PersistentNetworkParameters::class.java,
PublicKeyHashToExternalId::class.java PublicKeyHashToExternalId::class.java

View File

@ -48,6 +48,7 @@ open class DefaultNamedCacheFactory protected constructor(private val metricRegi
name == "PersistentIdentityService_keyToPartyAndCert" -> caffeine.maximumSize(defaultCacheSize) name == "PersistentIdentityService_keyToPartyAndCert" -> caffeine.maximumSize(defaultCacheSize)
name == "PersistentIdentityService_nameToParty" -> caffeine.maximumSize(defaultCacheSize) name == "PersistentIdentityService_nameToParty" -> caffeine.maximumSize(defaultCacheSize)
name == "PersistentIdentityService_keyToParty" -> caffeine.maximumSize(defaultCacheSize) name == "PersistentIdentityService_keyToParty" -> caffeine.maximumSize(defaultCacheSize)
name == "PersistentIdentityService_hashToKey" -> caffeine.maximumSize(defaultCacheSize)
name == "PersistentNetworkMap_nodesByKey" -> caffeine.maximumSize(defaultCacheSize) name == "PersistentNetworkMap_nodesByKey" -> caffeine.maximumSize(defaultCacheSize)
name == "PersistentNetworkMap_idByLegalName" -> caffeine.maximumSize(defaultCacheSize) name == "PersistentNetworkMap_idByLegalName" -> caffeine.maximumSize(defaultCacheSize)
name == "PersistentKeyManagementService_keys" -> caffeine.maximumSize(defaultCacheSize) name == "PersistentKeyManagementService_keys" -> caffeine.maximumSize(defaultCacheSize)

View File

@ -4,51 +4,18 @@
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.5.xsd" 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"> logicalFilePath="migration/node-services.changelog-init.xml">
<changeSet author="R3.Corda" id="identity_service_key_rotation-start" dbms="!postgresql"> <changeSet author="R3.Corda" id="identity_service_key_rotation">
<addColumn tableName="node_identities_no_cert"> <addColumn tableName="node_identities_no_cert">
<column name="party_pk_hash" type="NVARCHAR(130)"> <column name="owning_pk_hash" type="NVARCHAR(130)">
<constraints nullable="true"/>
</column>
<column name="public_key" type="blob">
<constraints nullable="true"/> <constraints nullable="true"/>
</column> </column>
</addColumn> </addColumn>
<createView viewName="v_our_key_pairs"> <sql>
select public_key_hash, public_key from node_our_key_pairs UPDATE node_identities_no_cert SET owning_pk_hash =
</createView> (SELECT pk_hash FROM node_named_identities WHERE node_named_identities.name = node_identities_no_cert.name)
</changeSet> </sql>
<sql>UPDATE node_identities_no_cert SET owning_pk_hash = 'unresolved' WHERE owning_pk_hash IS NULL</sql>
<changeSet author="R3.Corda" id="identity_service_key_rotation-start-postgres" dbms="postgresql"> <addNotNullConstraint tableName="node_identities_no_cert" columnName="owning_pk_hash" columnDataType="NVARCHAR(130)"/>
<addColumn tableName="node_identities_no_cert">
<column name="party_pk_hash" type="NVARCHAR(130)">
<constraints nullable="true"/>
</column>
<column name="public_key" type="varbinary(64000)">
<constraints nullable="true"/>
</column>
</addColumn>
<createView viewName="v_our_key_pairs">
select public_key_hash, lo_get(public_key) public_key from node_our_key_pairs
</createView>
</changeSet>
<changeSet author="R3.Corda" id="identity_service_key_rotation-custom">
<customChange class="net.corda.node.migration.NodeIdentitiesNoCertMigration"/>
</changeSet>
<changeSet author="R3.Corda" id="identity_service_key_rotation-end" dbms="!postgresql">
<addNotNullConstraint tableName="node_identities_no_cert" columnName="party_pk_hash" columnDataType="NVARCHAR(130)"/>
<addNotNullConstraint tableName="node_identities_no_cert" columnName="public_key" columnDataType="blob"/>
<dropTable tableName="node_hash_to_key"/>
<dropTable tableName="node_named_identities"/> <dropTable tableName="node_named_identities"/>
<dropView viewName="v_our_key_pairs"/>
</changeSet>
<changeSet author="R3.Corda" id="identity_service_key_rotation-end-postgres" dbms="postgresql">
<addNotNullConstraint tableName="node_identities_no_cert" columnName="party_pk_hash" columnDataType="NVARCHAR(130)"/>
<addNotNullConstraint tableName="node_identities_no_cert" columnName="public_key" columnDataType="varbinary(64000)"/>
<dropTable tableName="node_hash_to_key"/>
<dropTable tableName="node_named_identities"/>
<dropView viewName="v_our_key_pairs"/>
</changeSet> </changeSet>
</databaseChangeLog> </databaseChangeLog>

View File

@ -12,13 +12,8 @@ import net.corda.core.crypto.Crypto
import net.corda.core.crypto.toStringShort import net.corda.core.crypto.toStringShort
import net.corda.core.identity.CordaX500Name import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.identity.PartyAndCertificate
import net.corda.coretesting.internal.rigorousMock import net.corda.coretesting.internal.rigorousMock
import net.corda.node.migration.NodeIdentitiesNoCertMigration.Companion.UNRESOLVED
import net.corda.node.services.api.SchemaService import net.corda.node.services.api.SchemaService
import net.corda.nodeapi.internal.crypto.CertificateType
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.CordaPersistence
import net.corda.nodeapi.internal.persistence.DatabaseConfig import net.corda.nodeapi.internal.persistence.DatabaseConfig
import net.corda.nodeapi.internal.persistence.contextTransactionOrNull import net.corda.nodeapi.internal.persistence.contextTransactionOrNull
@ -66,27 +61,10 @@ class IdenityServiceKeyRotationMigrationTest {
entries.forEach { session.persist(it) } entries.forEach { session.persist(it) }
} }
private fun PartyAndCertificate.dbCertificate() = IdentityTestSchemaV1.NodeIdentities(owningKey.toStringShort(), certPath.encoded)
private fun Party.dbParty() = IdentityTestSchemaV1.NodeIdentitiesNoCert(owningKey.toStringShort(), name.toString()) private fun Party.dbParty() = IdentityTestSchemaV1.NodeIdentitiesNoCert(owningKey.toStringShort(), name.toString())
private fun TestIdentity.dbName() = IdentityTestSchemaV1.NodeNamedIdentities(name.toString(), publicKey.toStringShort()) private fun TestIdentity.dbName() = IdentityTestSchemaV1.NodeNamedIdentities(name.toString(), publicKey.toStringShort())
private fun TestIdentity.dbKey() = IdentityTestSchemaV1.NodeHashToKey(publicKey.toStringShort(), publicKey.encoded)
private fun TestIdentity.dbKeyPair() =
KMSTestSchemaV1.PersistentKey(publicKey.toStringShort(), publicKey.encoded, keyPair.private.encoded)
private fun TestIdentity.generateConfidentialIdentityWithCert(): PartyAndCertificate {
val certificate = X509Utilities.createCertificate(
CertificateType.CONFIDENTIAL_LEGAL_IDENTITY,
identity.certificate,
keyPair,
name.x500Principal,
Crypto.generateKeyPair().public)
return PartyAndCertificate(X509Utilities.buildCertPath(certificate, identity.certPath.x509Certificates))
}
@Test(timeout = 300_000) @Test(timeout = 300_000)
fun `test migration`() { fun `test migration`() {
val alice = TestIdentity(ALICE_NAME, 70) val alice = TestIdentity(ALICE_NAME, 70)
@ -94,67 +72,43 @@ class IdenityServiceKeyRotationMigrationTest {
val charlie = TestIdentity(CHARLIE_NAME, 90) val charlie = TestIdentity(CHARLIE_NAME, 90)
val alice2 = TestIdentity(ALICE_NAME, 71) val alice2 = TestIdentity(ALICE_NAME, 71)
val alice3 = TestIdentity(ALICE_NAME, 72)
val aliceCiWithCert = alice.generateConfidentialIdentityWithCert()
val bob2 = TestIdentity(BOB_NAME, 81) val bob2 = TestIdentity(BOB_NAME, 81)
val bob3 = TestIdentity(BOB_NAME, 82)
val charlie2 = TestIdentity(CHARLIE_NAME, 91) val charlie2 = TestIdentity(CHARLIE_NAME, 91)
val charlie3 = TestIdentity(CHARLIE_NAME, 92)
val charlieCiWithCert = charlie.generateConfidentialIdentityWithCert()
persist(alice.identity.dbCertificate(), alice.party.dbParty(), alice.dbName()) persist(alice.party.dbParty(), alice.dbName())
persist(charlie.identity.dbCertificate(), charlie.party.dbParty()) persist(charlie.party.dbParty())
persist(bob.identity.dbCertificate(), bob.party.dbParty(), bob.dbName()) persist(bob.party.dbParty(), bob.dbName())
persist(alice2.party.dbParty(), alice2.dbKeyPair()) persist(alice2.party.dbParty())
persist(alice3.party.dbParty()) persist(bob2.party.dbParty())
persist(aliceCiWithCert.dbCertificate(), aliceCiWithCert.party.dbParty()) persist(charlie2.party.dbParty())
persist(bob2.party.dbParty(), bob2.dbKey())
persist(bob3.party.dbParty())
persist(charlie2.party.dbParty(), charlie2.dbKey())
persist(charlie3.party.dbParty())
persist(charlieCiWithCert.dbCertificate(), charlieCiWithCert.party.dbParty())
Liquibase("migration/node-core.changelog-v20.xml", object : ClassLoaderResourceAccessor() { Liquibase("migration/node-core.changelog-v20.xml", object : ClassLoaderResourceAccessor() {
override fun getResourcesAsStream(path: String) = super.getResourcesAsStream(path)?.firstOrNull()?.let { setOf(it) } override fun getResourcesAsStream(path: String) = super.getResourcesAsStream(path)?.firstOrNull()?.let { setOf(it) }
}, liquibaseDB).update(Contexts().toString()) }, liquibaseDB).update(Contexts().toString())
val dummyKey = Crypto.generateKeyPair().public val dummyKey = Crypto.generateKeyPair().public
val results = mutableMapOf<String, List<*>>() val results = mutableMapOf<String, Pair<CordaX500Name, String>>()
cordaDB.transaction { cordaDB.transaction {
connection.prepareStatement("SELECT pk_hash, name, party_pk_hash, public_key FROM node_identities_no_cert").use { ps -> connection.prepareStatement("SELECT pk_hash, name, owning_pk_hash FROM node_identities_no_cert").use { ps ->
ps.executeQuery().use { rs -> ps.executeQuery().use { rs ->
while (rs.next()) { while (rs.next()) {
val partyKeyHash = rs.getString(3).takeUnless { it == UNRESOLVED } ?: dummyKey.toStringShort() val partyKeyHash = rs.getString(3).takeUnless { it == "unresolved" } ?: dummyKey.toStringShort()
val key = if (UNRESOLVED.toByteArray().contentEquals(rs.getBytes(4))) { results[rs.getString(1)] = CordaX500Name.parse(rs.getString(2)) to partyKeyHash
dummyKey
} else {
Crypto.decodePublicKey(rs.getBytes(4))
}
results[rs.getString(1)] = listOf(key, CordaX500Name.parse(rs.getString(2)), partyKeyHash)
} }
} }
} }
} }
assertEquals(11, results.size) assertEquals(6, results.size)
listOf(alice.identity, bob.identity, charlie.identity, aliceCiWithCert, charlieCiWithCert).forEach { assertEquals(results[alice.publicKey.toStringShort()], ALICE_NAME to alice.publicKey.toStringShort())
assertEquals(results[it.owningKey.toStringShort()], listOf(it.owningKey, it.party.name, it.owningKey.toStringShort())) assertEquals(results[bob.publicKey.toStringShort()], BOB_NAME to bob.publicKey.toStringShort())
} assertEquals(results[charlie.publicKey.toStringShort()], CHARLIE_NAME to dummyKey.toStringShort())
assertEquals(results[alice2.publicKey.toStringShort()], listOf(alice2.publicKey, ALICE_NAME, alice.publicKey.toStringShort())) assertEquals(results[alice2.publicKey.toStringShort()], ALICE_NAME to alice.publicKey.toStringShort())
assertEquals(results[alice3.publicKey.toStringShort()], listOf(dummyKey, ALICE_NAME, alice.publicKey.toStringShort())) assertEquals(results[bob2.publicKey.toStringShort()], BOB_NAME to bob.publicKey.toStringShort())
assertEquals(results[charlie2.publicKey.toStringShort()], CHARLIE_NAME to dummyKey.toStringShort())
assertEquals(results[bob2.publicKey.toStringShort()], listOf(bob2.publicKey, BOB_NAME, bob.publicKey.toStringShort()))
assertEquals(results[bob3.publicKey.toStringShort()], listOf(dummyKey, BOB_NAME, bob.publicKey.toStringShort()))
assertEquals(results[charlie2.publicKey.toStringShort()], listOf(charlie2.publicKey, CHARLIE_NAME, dummyKey.toStringShort()))
assertEquals(results[charlie3.publicKey.toStringShort()], listOf(dummyKey, CHARLIE_NAME, dummyKey.toStringShort()))
} }
} }