mirror of
https://github.com/corda/corda.git
synced 2025-01-18 10:46:38 +00:00
ENT-5888: Resurrect node_hash_to_key (#6776)
This commit is contained in:
parent
551b3f0811
commit
401d8b8856
@ -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)
|
||||||
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
@ -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 })
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
@ -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>
|
||||||
|
@ -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()))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user