mirror of
https://github.com/corda/corda.git
synced 2024-12-18 20:47:57 +00:00
Identity service refactor for confidential-identities and accounts (#5434)
* Removed IdentityServiceInternal as it is no longer used. * Removed externalIdForPublicKey API from KMS and added it to IdentityService. Added a registerKeyToExternalId API on IdentityService. * Fix remaining compile errors. * Removed "registerKeyToParty" and in its place added a new registerKey method which takes a PublicKey, Party and optionally a UUID. Added a cache to the "PersistentIdentityService" to store other node's public keys. Added the cache and new hibernate entity to all teh places where one needs to add them. New keys created by teh node now automatically get associated entries in the KEY -> PARTY map and optionally the KEy -> EXT ID map. Added a test. * Removed old comments and TODOs. * Fixed broken test. Added comments/explanations for what's going on in IdentityService. Updated kdocs. * First try at Implementing publicKeysForExternalId. * Fixed broken test. * Added migration. Amended existing persistent identity service migration to handle new migration. Addressed some review comments. * Fixed broken test - whoops! * Implemented mock identity service methods. * Added back exception when remapping a key to a different party. * Fixed compile errors. Fixed broken tests. * Use set instead of first entry in ourNames.
This commit is contained in:
parent
4ef032071d
commit
1c2c3d3fed
@ -10,7 +10,6 @@ import net.corda.core.identity.*
|
||||
import net.corda.core.node.services.IdentityService
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
import net.corda.node.services.api.IdentityServiceInternal
|
||||
import net.corda.testing.contracts.DummyContract
|
||||
import net.corda.testing.core.*
|
||||
import net.corda.testing.internal.matchers.flow.willReturn
|
||||
|
@ -1,5 +1,6 @@
|
||||
package net.corda.core.node.services
|
||||
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import net.corda.core.CordaException
|
||||
import net.corda.core.DoNotImplement
|
||||
import net.corda.core.contracts.PartyAndReference
|
||||
@ -10,6 +11,7 @@ import net.corda.core.utilities.contextLogger
|
||||
import java.security.InvalidAlgorithmParameterException
|
||||
import java.security.PublicKey
|
||||
import java.security.cert.*
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* An identity service maintains a directory of parties by their associated distinguished name/public keys and thus
|
||||
@ -69,8 +71,6 @@ 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?
|
||||
|
||||
/**
|
||||
@ -153,15 +153,32 @@ interface IdentityService {
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* exists for the supplied [PublicKey] but the associated [Party] does not match the one supplied to the method then a warning will be
|
||||
* logged and the operation will not be carried out as a key can only ever be registered to one [Party].
|
||||
*
|
||||
* @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
|
||||
* This method also optionally adds a mapping from [PublicKey] to external ID if one is provided. Lastly, the [PublicKey] is
|
||||
* also stored (as well as the [PublicKey] hash).
|
||||
*
|
||||
* @param publicKey The public publicKey that will be registered to the supplied [Party]
|
||||
* @param party The party that the supplied public publicKey will be registered to
|
||||
* @param externalId The [UUID] that the supplied public key can be optionally registered to
|
||||
* @throws IllegalArgumentException if the public publicKey is already registered to a party that does not match the supplied party
|
||||
*/
|
||||
@Throws(IllegalArgumentException::class)
|
||||
fun registerKeyToParty(key: PublicKey, party: Party)
|
||||
fun registerKey(publicKey: PublicKey, party: Party, externalId: UUID? = null)
|
||||
|
||||
/**
|
||||
* This method allows lookups of [PublicKey]s to an associated "external ID" / [UUID]. Providing a [PublicKey] that is unknown by the node
|
||||
* or is not mapped to an external ID will return null. Otherwise, if the [PublicKey] has been mapped to an external ID, then the [UUID]
|
||||
* for that external ID will be returned. The method looks up keys generated by the node as well as keys generated on other nodes and
|
||||
* registered with the [IdentityService].
|
||||
*
|
||||
* @param publicKey the [PublicKey] used to perform the lookup to external ID
|
||||
*/
|
||||
@Suspendable
|
||||
fun externalIdForPublicKey(publicKey: PublicKey): UUID?
|
||||
|
||||
fun publicKeysForExternalId(externalId: UUID): Iterable<PublicKey>
|
||||
}
|
||||
|
||||
class UnknownAnonymousPartyException(message: String) : CordaException(message)
|
||||
|
@ -90,13 +90,4 @@ interface KeyManagementService {
|
||||
*/
|
||||
@Suspendable
|
||||
fun sign(signableData: SignableData, publicKey: PublicKey): TransactionSignature
|
||||
|
||||
/**
|
||||
* This method allows lookups of [PublicKey]s to an associated "external ID" / [UUID]. Providing a [PublicKey] that is unknown by the node
|
||||
* or is not mapped to an external ID will return null. Otherwise, if the [PublicKey] has been mapped to an external ID, then the [UUID]
|
||||
* for that external ID will be returned.
|
||||
* @param publicKey the [PublicKey] used to perform the lookup to external ID
|
||||
*/
|
||||
@Suspendable
|
||||
fun externalIdForPublicKey(publicKey: PublicKey): UUID?
|
||||
}
|
@ -304,7 +304,7 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
|
||||
startDatabase()
|
||||
val (identity, identityKeyPair) = obtainIdentity()
|
||||
val nodeCa = configuration.signingCertificateStore.get()[CORDA_CLIENT_CA]
|
||||
identityService.start(trustRoot, listOf(identity.certificate, nodeCa))
|
||||
identityService.start(trustRoot, listOf(identity.certificate, nodeCa), pkToIdCache = pkToIdCache)
|
||||
return database.use {
|
||||
it.transaction {
|
||||
val (_, nodeInfoAndSigned) = updateNodeInfo(identity, identityKeyPair, publish = false)
|
||||
@ -359,7 +359,7 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
|
||||
X509Utilities.validateCertPath(trustRoot, identity.certPath)
|
||||
|
||||
val nodeCa = configuration.signingCertificateStore.get()[CORDA_CLIENT_CA]
|
||||
identityService.start(trustRoot, listOf(identity.certificate, nodeCa), netParams.notaries.map { it.identity })
|
||||
identityService.start(trustRoot, listOf(identity.certificate, nodeCa), netParams.notaries.map { it.identity }, pkToIdCache)
|
||||
|
||||
val (keyPairs, nodeInfoAndSigned, myNotaryIdentity) = database.transaction {
|
||||
updateNodeInfo(identity, identityKeyPair, publish = true)
|
||||
@ -842,7 +842,7 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
|
||||
// Place the long term identity key in the KMS. Eventually, this is likely going to be separated again because
|
||||
// the KMS is meant for derived temporary keys used in transactions, and we're not supposed to sign things with
|
||||
// the identity key. But the infrastructure to make that easy isn't here yet.
|
||||
return BasicHSMKeyManagementService(cacheFactory, identityService, database, cryptoService, pkToIdCache)
|
||||
return BasicHSMKeyManagementService(cacheFactory, identityService, database, cryptoService)
|
||||
}
|
||||
|
||||
open fun stop() {
|
||||
|
@ -30,6 +30,7 @@ class MigrationNamedCacheFactory(private val metricRegistry: MetricRegistry?,
|
||||
"PersistentIdentityService_keyToPartyAndCert" -> caffeine.maximumSize(defaultCacheSize)
|
||||
"PersistentIdentityService_nameToKey" -> caffeine.maximumSize(defaultCacheSize)
|
||||
"PersistentIdentityService_keyToName" -> caffeine.maximumSize(defaultCacheSize)
|
||||
"PersistentIdentityService_hashToKey" -> caffeine.maximumSize(defaultCacheSize)
|
||||
"BasicHSMKeyManagementService_keys" -> caffeine.maximumSize(defaultCacheSize)
|
||||
"NodeAttachmentService_attachmentContent" -> caffeine.maximumWeight(defaultCacheSize)
|
||||
"NodeAttachmentService_attachmentPresence" -> caffeine.maximumSize(defaultCacheSize)
|
||||
|
@ -45,7 +45,12 @@ class PersistentIdentityMigration : CustomSqlChange {
|
||||
val oldPkHash = resultSet.getString(1)
|
||||
val identityBytes = resultSet.getBytes(2)
|
||||
val partyAndCertificate = PartyAndCertificate(X509CertificateFactory().delegate.generateCertPath(identityBytes.inputStream()))
|
||||
generatedStatements.addAll(MigrationData(oldPkHash, partyAndCertificate).let { listOf(updateHashToIdentityRow(it, dataSource), updateNameToHashRow(it, dataSource)) })
|
||||
generatedStatements.addAll(MigrationData(oldPkHash, partyAndCertificate).let {
|
||||
listOf(
|
||||
updateHashToIdentityRow(it, dataSource),
|
||||
updateNameToHashRow(it, dataSource)
|
||||
)
|
||||
})
|
||||
}
|
||||
return generatedStatements.toTypedArray()
|
||||
}
|
||||
|
@ -4,10 +4,7 @@ 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
|
||||
@ -16,18 +13,8 @@ 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.
|
||||
@ -41,42 +28,54 @@ class PersistentIdentityMigrationNewTable : CordaMigration() {
|
||||
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")
|
||||
logger.error("Cannot migrate persistent identities: Liquibase failed to provide a suitable database connection")
|
||||
throw PersistentIdentitiesMigrationException("Cannot migrate persistent identities as liquibase failed to provide a suitable database connection")
|
||||
}
|
||||
initialiseNodeServices(database, setOf(PersistentIdentitiesMigrationSchemaBuilder.getMappedSchema()))
|
||||
|
||||
val connection = database.connection as JdbcConnection
|
||||
val keyPartiesMap = extractKeyParties(connection)
|
||||
val hashToKeyAndName = extractKeyAndName(connection)
|
||||
|
||||
keyPartiesMap.forEach {
|
||||
hashToKeyAndName.forEach {
|
||||
insertEntry(connection, it)
|
||||
}
|
||||
}
|
||||
|
||||
private fun extractKeyParties(connection: JdbcConnection): Map<String, CordaX500Name> {
|
||||
val keyParties = mutableMapOf<String, CordaX500Name>()
|
||||
private fun extractKeyAndName(connection: JdbcConnection): Map<String, Pair<CordaX500Name, PublicKey>> {
|
||||
val rows = mutableMapOf<String, Pair<CordaX500Name, PublicKey>>()
|
||||
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)
|
||||
val publicKeyHash = rs.getString(1)
|
||||
val certificateBytes = rs.getBytes(2)
|
||||
// Deserialise certificate.
|
||||
val certPath = X509CertificateFactory().delegate.generateCertPath(certificateBytes.inputStream())
|
||||
val partyAndCertificate = PartyAndCertificate(certPath)
|
||||
// Record name and public key.
|
||||
val publicKey = partyAndCertificate.certificate.publicKey
|
||||
val name = partyAndCertificate.name
|
||||
rows[publicKeyHash] = Pair(name, publicKey)
|
||||
}
|
||||
rs.close()
|
||||
}
|
||||
return keyParties
|
||||
return rows
|
||||
}
|
||||
|
||||
private fun insertEntry(connection: JdbcConnection, entry: Map.Entry<String, CordaX500Name>) {
|
||||
val pk = entry.key
|
||||
val name = entry.value.toString()
|
||||
private fun insertEntry(connection: JdbcConnection, entry: Map.Entry<String, Pair<CordaX500Name, PublicKey>>) {
|
||||
val publicKeyHash = entry.key
|
||||
val (name, publicKey) = entry.value
|
||||
connection.prepareStatement("INSERT INTO node_identities_no_cert (pk_hash, name) VALUES (?,?)").use {
|
||||
it.setString(1, pk)
|
||||
it.setString(2, name)
|
||||
it.setString(1, publicKeyHash)
|
||||
it.setString(2, name.toString())
|
||||
it.executeUpdate()
|
||||
}
|
||||
if (name !in identityService.ourNames) {
|
||||
connection.prepareStatement("INSERT INTO node_hash_to_key (pk_hash, public_key) VALUES (?,?)").use {
|
||||
it.setString(1, publicKeyHash)
|
||||
it.setBytes(2, publicKey.encoded)
|
||||
it.executeUpdate()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun setUp() {
|
||||
@ -111,6 +110,7 @@ object PersistentIdentitiesMigrationSchemaBuilder {
|
||||
PersistentIdentityService.PersistentPublicKeyHashToCertificate::class.java,
|
||||
PersistentIdentityService.PersistentPartyToPublicKeyHash::class.java,
|
||||
PersistentIdentityService.PersistentPublicKeyHashToParty::class.java,
|
||||
PersistentIdentityService.PersistentHashToPublicKey::class.java,
|
||||
BasicHSMKeyManagementService.PersistentKey::class.java,
|
||||
NodeAttachmentService.DBAttachment::class.java,
|
||||
DBNetworkParametersStorage.PersistentNetworkParameters::class.java
|
||||
|
@ -129,6 +129,7 @@ object VaultMigrationSchemaV1 : MappedSchema(schemaFamily = VaultMigrationSchema
|
||||
PersistentIdentityService.PersistentPublicKeyHashToCertificate::class.java,
|
||||
PersistentIdentityService.PersistentPartyToPublicKeyHash::class.java,
|
||||
PersistentIdentityService.PersistentPublicKeyHashToParty::class.java,
|
||||
PersistentIdentityService.PersistentHashToPublicKey::class.java,
|
||||
BasicHSMKeyManagementService.PersistentKey::class.java,
|
||||
NodeAttachmentService.DBAttachment::class.java,
|
||||
DBNetworkParametersStorage.PersistentNetworkParameters::class.java
|
||||
|
@ -1,84 +0,0 @@
|
||||
package net.corda.node.services.api
|
||||
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.identity.PartyAndCertificate
|
||||
import net.corda.core.internal.CertRole
|
||||
import net.corda.core.node.services.IdentityService
|
||||
import net.corda.core.utilities.contextLogger
|
||||
import net.corda.nodeapi.internal.crypto.X509Utilities
|
||||
import net.corda.nodeapi.internal.crypto.x509Certificates
|
||||
import java.security.InvalidAlgorithmParameterException
|
||||
import java.security.cert.CertPathValidatorException
|
||||
import java.security.cert.CertificateExpiredException
|
||||
import java.security.cert.CertificateNotYetValidException
|
||||
import java.security.cert.TrustAnchor
|
||||
|
||||
interface IdentityServiceInternal : IdentityService {
|
||||
|
||||
private companion object {
|
||||
val log = contextLogger()
|
||||
}
|
||||
|
||||
/** This method exists so it can be mocked with doNothing, rather than having to make up a possibly invalid return value. */
|
||||
fun justVerifyAndRegisterIdentity(identity: PartyAndCertificate, isNewRandomIdentity: Boolean = false) {
|
||||
verifyAndRegisterIdentity(identity, isNewRandomIdentity)
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify and then store an identity.
|
||||
*
|
||||
* @param identity a party and the certificate path linking them to the network trust root.
|
||||
* @param isNewRandomIdentity true if the identity will not have been registered before (e.g. because it is randomly generated by ourselves).
|
||||
* @return the issuing entity, if known.
|
||||
* @throws IllegalArgumentException if the certificate path is invalid.
|
||||
*/
|
||||
@Throws(CertificateExpiredException::class, CertificateNotYetValidException::class, InvalidAlgorithmParameterException::class)
|
||||
fun verifyAndRegisterIdentity(identity: PartyAndCertificate, isNewRandomIdentity: Boolean): PartyAndCertificate?
|
||||
|
||||
// We can imagine this being a query over a lucene index in future.
|
||||
//
|
||||
// Kostas says: When exactMatch = false, we can easily use the Jaro-Winkler distance metric as it is best suited for short
|
||||
// strings such as entity/company names, and to detect small typos. We can also apply it for city
|
||||
// or any keyword related search in lists of records (not raw text - for raw text we need indexing)
|
||||
// and we can return results in hierarchical order (based on normalised String similarity 0.0-1.0).
|
||||
/** 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)) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that an identity is valid.
|
||||
*
|
||||
* @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)
|
||||
fun verifyAndRegisterIdentity(trustAnchor: TrustAnchor, identity: PartyAndCertificate, isNewRandomIdentity: Boolean = false): 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 && !isNewRandomIdentity) {
|
||||
val idx = identityCertChain.lastIndexOf(wellKnownCert)
|
||||
val firstPath = X509Utilities.buildCertPath(identityCertChain.slice(idx until identityCertChain.size))
|
||||
verifyAndRegisterIdentity(trustAnchor, PartyAndCertificate(firstPath))
|
||||
}
|
||||
return registerIdentity(identity, isNewRandomIdentity)
|
||||
}
|
||||
|
||||
fun registerIdentity(identity: PartyAndCertificate, isNewRandomIdentity: Boolean = false): PartyAndCertificate?
|
||||
}
|
@ -1,21 +1,27 @@
|
||||
package net.corda.node.services.identity
|
||||
|
||||
import net.corda.core.crypto.toStringShort
|
||||
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.internal.hash
|
||||
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.persistence.WritablePublicKeyToOwningIdentityCache
|
||||
import net.corda.nodeapi.internal.crypto.X509Utilities
|
||||
import net.corda.nodeapi.internal.crypto.x509Certificates
|
||||
import java.security.InvalidAlgorithmParameterException
|
||||
import java.security.PublicKey
|
||||
import java.security.cert.*
|
||||
import java.util.*
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import javax.annotation.concurrent.ThreadSafe
|
||||
import kotlin.collections.ArrayList
|
||||
import kotlin.collections.LinkedHashSet
|
||||
|
||||
/**
|
||||
* Simple identity service which caches parties and provides functionality for efficient lookup.
|
||||
@ -23,8 +29,10 @@ import javax.annotation.concurrent.ThreadSafe
|
||||
* @param identities initial set of identities for the service, typically only used for unit tests.
|
||||
*/
|
||||
@ThreadSafe
|
||||
class InMemoryIdentityService(identities: List<PartyAndCertificate> = emptyList(),
|
||||
override val trustRoot: X509Certificate) : SingletonSerializeAsToken(), IdentityService {
|
||||
class InMemoryIdentityService(
|
||||
identities: List<PartyAndCertificate> = emptyList(),
|
||||
override val trustRoot: X509Certificate
|
||||
) : SingletonSerializeAsToken(), IdentityService {
|
||||
companion object {
|
||||
private val log = contextLogger()
|
||||
}
|
||||
@ -34,14 +42,16 @@ 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 keyToExternalId = ConcurrentHashMap<String, UUID>()
|
||||
private val keyToPartyAndCerts = ConcurrentHashMap<PublicKey, PartyAndCertificate>()
|
||||
private val nameToKey = ConcurrentHashMap<CordaX500Name, PublicKey>()
|
||||
private val keyToName = ConcurrentHashMap<PublicKey, CordaX500Name>()
|
||||
private val keyToName = ConcurrentHashMap<String, CordaX500Name>()
|
||||
private val hashToKey = ConcurrentHashMap<String, PublicKey>()
|
||||
|
||||
init {
|
||||
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 })
|
||||
keyToName.putAll(identities.associateBy{ it.owningKey.toStringShort() }.mapValues { it.value.party.name })
|
||||
}
|
||||
|
||||
|
||||
@ -81,7 +91,7 @@ class InMemoryIdentityService(identities: List<PartyAndCertificate> = emptyList(
|
||||
keyToPartyAndCerts[identity.owningKey] = identity
|
||||
// Always keep the first party we registered, as that's the well known identity
|
||||
nameToKey.computeIfAbsent(identity.name) {identity.owningKey}
|
||||
keyToName.putIfAbsent(identity.owningKey, identity.name)
|
||||
keyToName.putIfAbsent(identity.owningKey.toStringShort(), identity.name)
|
||||
return keyToPartyAndCerts[identityCertChain[1].publicKey]
|
||||
}
|
||||
|
||||
@ -108,10 +118,34 @@ class InMemoryIdentityService(identities: List<PartyAndCertificate> = emptyList(
|
||||
return results
|
||||
}
|
||||
|
||||
override fun registerKeyToParty(key: PublicKey, party: Party) {
|
||||
if (keyToName[key] == null) {
|
||||
keyToName.putIfAbsent(key, party.name)
|
||||
override fun registerKey(publicKey: PublicKey, party: Party, externalId: UUID?) {
|
||||
val publicKeyHash = publicKey.toStringShort()
|
||||
val existingEntry = keyToName[publicKeyHash]
|
||||
if (existingEntry == null) {
|
||||
registerKeyToParty(publicKey, party)
|
||||
hashToKey[publicKeyHash] = publicKey
|
||||
if (externalId != null) {
|
||||
registerKeyToExternalId(publicKey, externalId)
|
||||
}
|
||||
} else {
|
||||
if (party.name != existingEntry) {
|
||||
}
|
||||
}
|
||||
throw IllegalArgumentException("An entry for the public key: $key already exists.")
|
||||
}
|
||||
|
||||
override fun externalIdForPublicKey(publicKey: PublicKey): UUID? {
|
||||
return keyToExternalId[publicKey.toStringShort()]
|
||||
}
|
||||
|
||||
fun registerKeyToExternalId(key: PublicKey, externalId: UUID) {
|
||||
keyToExternalId[key.toStringShort()] = externalId
|
||||
}
|
||||
|
||||
fun registerKeyToParty(publicKey: PublicKey, party: Party) {
|
||||
keyToName[publicKey.toStringShort()] = party.name
|
||||
}
|
||||
|
||||
override fun publicKeysForExternalId(externalId: UUID): Iterable<PublicKey> {
|
||||
throw NotImplementedError("This method is not implemented in the InMemoryIdentityService at it requires access to CordaPersistence.")
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
package net.corda.node.services.identity
|
||||
|
||||
import net.corda.core.crypto.Crypto
|
||||
import net.corda.core.crypto.toStringShort
|
||||
import net.corda.core.identity.*
|
||||
import net.corda.core.internal.CertRole
|
||||
@ -12,22 +13,29 @@ 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.keys.BasicHSMKeyManagementService
|
||||
import net.corda.node.services.persistence.PublicKeyHashToExternalId
|
||||
import net.corda.node.services.persistence.WritablePublicKeyToOwningIdentityCache
|
||||
import net.corda.node.utilities.AppendOnlyPersistentMap
|
||||
import net.corda.nodeapi.internal.KeyOwningIdentity
|
||||
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
|
||||
import org.apache.commons.lang3.ArrayUtils
|
||||
import org.hibernate.annotations.Type
|
||||
import org.hibernate.internal.util.collections.ArrayHelper.EMPTY_BYTE_ARRAY
|
||||
import java.security.InvalidAlgorithmParameterException
|
||||
import java.security.PublicKey
|
||||
import java.security.cert.*
|
||||
import java.util.*
|
||||
import javax.annotation.concurrent.ThreadSafe
|
||||
import javax.persistence.Column
|
||||
import javax.persistence.Entity
|
||||
import javax.persistence.Id
|
||||
import javax.persistence.Lob
|
||||
import kotlin.IllegalStateException
|
||||
import kotlin.collections.HashSet
|
||||
import kotlin.streams.toList
|
||||
|
||||
/**
|
||||
@ -43,6 +51,7 @@ class PersistentIdentityService(cacheFactory: NamedCacheFactory) : SingletonSeri
|
||||
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 HASH_TO_KEY_TABLE_NAME = "${NODE_DATABASE_PREFIX}hash_to_key"
|
||||
const val PK_HASH_COLUMN_NAME = "pk_hash"
|
||||
const val IDENTITY_COLUMN_NAME = "identity_value"
|
||||
const val NAME_COLUMN_NAME = "name"
|
||||
@ -80,7 +89,6 @@ class PersistentIdentityService(cacheFactory: NamedCacheFactory) : SingletonSeri
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
fun createKeyToX500Map(cacheFactory: NamedCacheFactory): AppendOnlyPersistentMap<String, CordaX500Name, PersistentPublicKeyHashToParty, String> {
|
||||
return AppendOnlyPersistentMap(
|
||||
cacheFactory = cacheFactory,
|
||||
@ -98,6 +106,23 @@ class PersistentIdentityService(cacheFactory: NamedCacheFactory) : SingletonSeri
|
||||
persistentEntityClass = PersistentPublicKeyHashToParty::class.java)
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
@ -135,6 +160,18 @@ class PersistentIdentityService(cacheFactory: NamedCacheFactory) : SingletonSeri
|
||||
var name: String = ""
|
||||
)
|
||||
|
||||
@Entity
|
||||
@javax.persistence.Table(name = HASH_TO_KEY_TABLE_NAME)
|
||||
class PersistentHashToPublicKey(
|
||||
@Id
|
||||
@Column(name = PK_HASH_COLUMN_NAME, length = MAX_HASH_HEX_SIZE, nullable = false)
|
||||
var publicKeyHash: String = "",
|
||||
|
||||
@Type(type = "corda-blob")
|
||||
@Column(name = "public_key", nullable = false)
|
||||
var publicKey: ByteArray = ArrayUtils.EMPTY_BYTE_ARRAY
|
||||
)
|
||||
|
||||
private lateinit var _caCertStore: CertStore
|
||||
override val caCertStore: CertStore get() = _caCertStore
|
||||
|
||||
@ -150,14 +187,23 @@ class PersistentIdentityService(cacheFactory: NamedCacheFactory) : SingletonSeri
|
||||
// CordaPersistence is not a c'tor parameter to work around the cyclic dependency
|
||||
lateinit var database: CordaPersistence
|
||||
|
||||
private lateinit var _pkToIdCache: WritablePublicKeyToOwningIdentityCache
|
||||
|
||||
private val keyToPartyAndCert = createKeyToPartyAndCertMap(cacheFactory)
|
||||
private val nameToKey = createX500ToKeyMap(cacheFactory)
|
||||
private val keyToName = createKeyToX500Map(cacheFactory)
|
||||
private val hashToKey = createHashToKeyMap(cacheFactory)
|
||||
|
||||
fun start(trustRoot: X509Certificate, caCertificates: List<X509Certificate> = emptyList(), notaryIdentities: List<Party> = emptyList()) {
|
||||
fun start(
|
||||
trustRoot: X509Certificate,
|
||||
caCertificates: List<X509Certificate> = emptyList(),
|
||||
notaryIdentities: List<Party> = emptyList(),
|
||||
pkToIdCache: WritablePublicKeyToOwningIdentityCache
|
||||
) {
|
||||
_trustRoot = trustRoot
|
||||
_trustAnchor = TrustAnchor(trustRoot, null)
|
||||
_caCertStore = CertStore.getInstance("Collection", CollectionCertStoreParameters(caCertificates.toSet() + trustRoot))
|
||||
_pkToIdCache = pkToIdCache
|
||||
notaryIdentityCache.addAll(notaryIdentities)
|
||||
}
|
||||
|
||||
@ -216,9 +262,9 @@ class PersistentIdentityService(cacheFactory: NamedCacheFactory) : SingletonSeri
|
||||
val identityCertChain = identity.certPath.x509Certificates
|
||||
val key = mapToKey(identity)
|
||||
return database.transaction {
|
||||
keyToPartyAndCert.addWithDuplicatesAllowed(key, identity, false)
|
||||
nameToKey.addWithDuplicatesAllowed(identity.name, key, false)
|
||||
keyToName.addWithDuplicatesAllowed(key, identity.name, false)
|
||||
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]
|
||||
}
|
||||
@ -260,9 +306,12 @@ class PersistentIdentityService(cacheFactory: NamedCacheFactory) : SingletonSeri
|
||||
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
|
||||
// a lookup in the keyToName table. If an entry for that public key exists, then we attempt look up the associated node's
|
||||
// PartyAndCertificate.
|
||||
val name = keyToName[party.owningKey.toStringShort()]
|
||||
if (name != null) {
|
||||
// This should never return null as this node would not be able to communicate with the node providing a confidential
|
||||
// identity unless its NodeInfo/PartyAndCertificate were available.
|
||||
wellKnownPartyFromX500Name(name)
|
||||
} else {
|
||||
null
|
||||
@ -293,18 +342,71 @@ class PersistentIdentityService(cacheFactory: NamedCacheFactory) : SingletonSeri
|
||||
return keys.filter { (@Suppress("DEPRECATION") certificateFromKey(it))?.name in ourNames }
|
||||
}
|
||||
|
||||
override fun registerKeyToParty(key: PublicKey, party: Party) {
|
||||
override fun registerKey(publicKey: PublicKey, party: Party, externalId: UUID?) {
|
||||
return database.transaction {
|
||||
val existingEntryForKey = keyToName[key.toStringShort()]
|
||||
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
|
||||
// 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 = keyToName[publicKeyHash]
|
||||
if (existingEntryForKey == null) {
|
||||
log.info("Linking: ${key.hash} to ${party.name}")
|
||||
keyToName[key.toStringShort()] = party.name
|
||||
// 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
|
||||
// other because when a node generates its own keys "registerKeyToParty" is automatically called by KeyManagementService.freshKey.
|
||||
registerKeyToParty(publicKey, party)
|
||||
hashToKey[publicKeyHash] = publicKey
|
||||
if (externalId != null) {
|
||||
registerKeyToExternalId(publicKey, externalId)
|
||||
}
|
||||
} 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 .")
|
||||
log.info("An existing entry for $publicKeyHash already exists.")
|
||||
if (party.name != existingEntryForKey) {
|
||||
throw IllegalStateException("The public publicKey $publicKeyHash is already assigned to a different party than the " +
|
||||
"supplied party.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Internal function used by the KMS to register a public key to a Corda Party.
|
||||
fun registerKeyToParty(publicKey: PublicKey, party: Party) {
|
||||
return database.transaction {
|
||||
log.info("Linking: ${publicKey.hash} to ${party.name}")
|
||||
keyToName[publicKey.toStringShort()] = party.name
|
||||
}
|
||||
}
|
||||
|
||||
// Internal function used by the KMS to register a public key to an external ID.
|
||||
fun registerKeyToExternalId(publicKey: PublicKey, externalId: UUID) {
|
||||
_pkToIdCache[publicKey] = KeyOwningIdentity.fromUUID(externalId)
|
||||
}
|
||||
|
||||
override fun externalIdForPublicKey(publicKey: PublicKey): UUID? {
|
||||
return _pkToIdCache[publicKey]?.uuid
|
||||
}
|
||||
|
||||
private fun publicKeysForExternalId(externalId: UUID, table: Class<*>): List<PublicKey> {
|
||||
return database.transaction {
|
||||
val query = session.createQuery(
|
||||
"""
|
||||
select a.publicKey
|
||||
from ${table.name} a, ${PublicKeyHashToExternalId::class.java.name} b
|
||||
where b.externalId = :uuid
|
||||
and b.publicKeyHash = a.publicKeyHash
|
||||
""",
|
||||
ByteArray::class.java
|
||||
)
|
||||
query.setParameter("uuid", externalId)
|
||||
query.resultList.map { Crypto.decodePublicKey(it) }
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
@ -30,11 +30,12 @@ import kotlin.collections.LinkedHashSet
|
||||
*
|
||||
* This class needs database transactions to be in-flight during method calls and init.
|
||||
*/
|
||||
class BasicHSMKeyManagementService(cacheFactory: NamedCacheFactory,
|
||||
override val identityService: PersistentIdentityService,
|
||||
private val database: CordaPersistence,
|
||||
private val cryptoService: SignOnlyCryptoService,
|
||||
private val pkToIdCache: WritablePublicKeyToOwningIdentityCache) : SingletonSerializeAsToken(), KeyManagementServiceInternal {
|
||||
class BasicHSMKeyManagementService(
|
||||
cacheFactory: NamedCacheFactory,
|
||||
override val identityService: PersistentIdentityService,
|
||||
private val database: CordaPersistence,
|
||||
private val cryptoService: SignOnlyCryptoService
|
||||
) : SingletonSerializeAsToken(), KeyManagementServiceInternal {
|
||||
|
||||
@Entity
|
||||
@Table(name = "${NODE_DATABASE_PREFIX}our_key_pairs")
|
||||
@ -103,7 +104,14 @@ class BasicHSMKeyManagementService(cacheFactory: NamedCacheFactory,
|
||||
val keyPair = generateKeyPair()
|
||||
database.transaction {
|
||||
keysMap[keyPair.public] = keyPair.private
|
||||
pkToIdCache[keyPair.public] = KeyOwningIdentity.fromUUID(externalId)
|
||||
// Register the key to our identity.
|
||||
val ourIdentity = identityService.wellKnownPartyFromX500Name(identityService.ourNames.first())
|
||||
?: throw IllegalStateException("Could not lookup node Identity.")
|
||||
// No checks performed here as entries for the new key couldn't have existed before in the maps.
|
||||
identityService.registerKeyToParty(keyPair.public, ourIdentity)
|
||||
if (externalId != null) {
|
||||
identityService.registerKeyToExternalId(keyPair.public, externalId)
|
||||
}
|
||||
}
|
||||
return keyPair.public
|
||||
}
|
||||
@ -157,8 +165,4 @@ class BasicHSMKeyManagementService(cacheFactory: NamedCacheFactory,
|
||||
keyPair.sign(signableData)
|
||||
}
|
||||
}
|
||||
|
||||
override fun externalIdForPublicKey(publicKey: PublicKey): UUID? {
|
||||
return pkToIdCache[publicKey]?.uuid
|
||||
}
|
||||
}
|
||||
|
@ -88,8 +88,4 @@ class E2ETestKeyManagementService(override val identityService: IdentityService,
|
||||
val keyPair = getSigningKeyPair(publicKey)
|
||||
return keyPair.sign(signableData)
|
||||
}
|
||||
|
||||
override fun externalIdForPublicKey(publicKey: PublicKey): UUID? {
|
||||
throw UnsupportedOperationException("This operation is only supported by persistent key management service variants.")
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,6 @@ import net.corda.core.identity.PartyAndCertificate
|
||||
import net.corda.core.internal.CertRole
|
||||
import net.corda.core.node.services.IdentityService
|
||||
import net.corda.core.utilities.days
|
||||
import net.corda.node.services.api.IdentityServiceInternal
|
||||
import net.corda.nodeapi.internal.crypto.CertificateType
|
||||
import net.corda.nodeapi.internal.crypto.ContentSignerBuilder
|
||||
import net.corda.nodeapi.internal.crypto.X509Utilities
|
||||
@ -45,11 +44,7 @@ fun freshCertificate(identityService: IdentityService,
|
||||
window)
|
||||
val ourCertPath = X509Utilities.buildCertPath(ourCertificate, issuer.certPath.x509Certificates)
|
||||
val anonymisedIdentity = PartyAndCertificate(ourCertPath)
|
||||
if (identityService is IdentityServiceInternal) {
|
||||
identityService.justVerifyAndRegisterIdentity(anonymisedIdentity, true)
|
||||
} else {
|
||||
identityService.verifyAndRegisterIdentity(anonymisedIdentity)
|
||||
}
|
||||
identityService.verifyAndRegisterIdentity(anonymisedIdentity)
|
||||
return anonymisedIdentity
|
||||
}
|
||||
|
||||
|
@ -43,6 +43,7 @@ class NodeSchemaService(private val extraSchemas: Set<MappedSchema> = emptySet()
|
||||
PersistentIdentityService.PersistentPublicKeyHashToCertificate::class.java,
|
||||
PersistentIdentityService.PersistentPartyToPublicKeyHash::class.java,
|
||||
PersistentIdentityService.PersistentPublicKeyHashToParty::class.java,
|
||||
PersistentIdentityService.PersistentHashToPublicKey::class.java,
|
||||
ContractUpgradeServiceImpl.DBContractUpgrade::class.java,
|
||||
DBNetworkParametersStorage.PersistentNetworkParameters::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_nameToKey" -> caffeine.maximumSize(defaultCacheSize)
|
||||
name == "PersistentIdentityService_keyToName" -> caffeine.maximumSize(defaultCacheSize)
|
||||
name == "PersistentIdentityService_hashToKey" -> caffeine.maximumSize(defaultCacheSize)
|
||||
name == "PersistentNetworkMap_nodesByKey" -> caffeine.maximumSize(defaultCacheSize)
|
||||
name == "PersistentNetworkMap_idByLegalName" -> caffeine.maximumSize(defaultCacheSize)
|
||||
name == "PersistentKeyManagementService_keys" -> caffeine.maximumSize(defaultCacheSize)
|
||||
|
@ -23,8 +23,11 @@
|
||||
vault-schema.changelog-v9.xml), as that will use the current hibernate mappings, and those require all DB columns to be
|
||||
created. -->
|
||||
<include file="migration/node-core.changelog-v13.xml"/>
|
||||
<!-- This change should be done before the v14-data migration. -->
|
||||
<include file="migration/node-core.changelog-v15-table.xml"/>
|
||||
<include file="migration/node-core.changelog-v14-data.xml"/>
|
||||
<include file="migration/node-core.changelog-v15.xml"/>
|
||||
<!-- This must run after node-core.changelog-init.xml, to prevent database columns being created twice. -->
|
||||
<include file="migration/vault-schema.changelog-v9.xml"/>
|
||||
|
||||
</databaseChangeLog>
|
||||
|
@ -0,0 +1,19 @@
|
||||
<?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-pk-hash-to-pk-table">
|
||||
<createTable tableName="node_hash_to_key">
|
||||
<column name="pk_hash" type="NVARCHAR(130)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="public_key" type="blob">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</createTable>
|
||||
<addPrimaryKey columnNames="pk_hash" constraintName="node_hash_to_key_pk_hash" tableName="node_hash_to_key"/>
|
||||
</changeSet>
|
||||
|
||||
</databaseChangeLog>
|
@ -7,6 +7,7 @@ import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.identity.PartyAndCertificate
|
||||
import net.corda.core.node.services.UnknownAnonymousPartyException
|
||||
import net.corda.node.services.persistence.PublicKeyToOwningIdentityCacheImpl
|
||||
import net.corda.core.serialization.serialize
|
||||
import net.corda.nodeapi.internal.crypto.CertificateType
|
||||
import net.corda.nodeapi.internal.crypto.X509Utilities
|
||||
@ -45,12 +46,14 @@ class PersistentIdentityServiceTests {
|
||||
@Rule
|
||||
@JvmField
|
||||
val testSerialization = SerializationEnvironmentRule()
|
||||
private val cacheFactory = TestingNamedCacheFactory()
|
||||
private lateinit var database: CordaPersistence
|
||||
private lateinit var identityService: PersistentIdentityService
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
identityService = PersistentIdentityService(TestingNamedCacheFactory())
|
||||
val cacheFactory = TestingNamedCacheFactory()
|
||||
identityService = PersistentIdentityService(cacheFactory = cacheFactory)
|
||||
database = configureDatabase(
|
||||
makeTestDataSourceProperties(),
|
||||
DatabaseConfig(),
|
||||
@ -59,7 +62,7 @@ class PersistentIdentityServiceTests {
|
||||
)
|
||||
identityService.database = database
|
||||
identityService.ourNames = setOf(ALICE_NAME)
|
||||
identityService.start(DEV_ROOT_CA.certificate)
|
||||
identityService.start(DEV_ROOT_CA.certificate, pkToIdCache = PublicKeyToOwningIdentityCacheImpl(database, cacheFactory))
|
||||
}
|
||||
|
||||
@After
|
||||
@ -224,7 +227,7 @@ class PersistentIdentityServiceTests {
|
||||
// Create new identity service mounted onto same DB
|
||||
val newPersistentIdentityService = PersistentIdentityService(TestingNamedCacheFactory()).also {
|
||||
it.database = database
|
||||
it.start(DEV_ROOT_CA.certificate)
|
||||
it.start(DEV_ROOT_CA.certificate, pkToIdCache = PublicKeyToOwningIdentityCacheImpl(database, cacheFactory))
|
||||
}
|
||||
|
||||
newPersistentIdentityService.assertOwnership(alice.party, anonymousAlice.party.anonymise())
|
||||
@ -250,23 +253,22 @@ class PersistentIdentityServiceTests {
|
||||
fun `register duplicate confidential identities`(){
|
||||
val (alice, anonymousAlice) = createConfidentialIdentity(ALICE.name)
|
||||
|
||||
identityService.registerKeyToParty(anonymousAlice.owningKey, alice.party)
|
||||
identityService.registerKey(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)
|
||||
identityService.registerKey(anonymousAlice.owningKey, alice.party)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `register incorrect party to public key `(){
|
||||
database.transaction { identityService.verifyAndRegisterIdentity(ALICE_IDENTITY) }
|
||||
val (alice, anonymousAlice) = createConfidentialIdentity(ALICE.name)
|
||||
|
||||
identityService.registerKeyToParty(anonymousAlice.owningKey, alice.party)
|
||||
|
||||
assertThrows<IllegalArgumentException> {
|
||||
identityService.registerKeyToParty(anonymousAlice.owningKey, bob.party)
|
||||
}
|
||||
identityService.registerKey(anonymousAlice.owningKey, alice.party)
|
||||
// Should have no side effect but logs a warning that we tried to overwrite an existing mapping.
|
||||
assertFailsWith<IllegalStateException> { identityService.registerKey(anonymousAlice.owningKey, bob.party) }
|
||||
assertEquals(ALICE, identityService.wellKnownPartyFromAnonymous(AnonymousParty(anonymousAlice.owningKey)))
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -103,7 +103,7 @@ class NetworkMapUpdaterTest {
|
||||
server.networkParameters.serialize().hash,
|
||||
ourNodeInfo,
|
||||
networkParameters,
|
||||
MockKeyManagementService(makeTestIdentityService(), ourKeyPair, pkToIdCache = MockPublicKeyToOwningIdentityCache()),
|
||||
MockKeyManagementService(makeTestIdentityService(), ourKeyPair),
|
||||
NetworkParameterAcceptanceSettings(autoAcceptNetworkParameters, excludedAutoAcceptNetworkParameters))
|
||||
}
|
||||
|
||||
|
@ -50,7 +50,7 @@ class NodeInfoWatcherTest {
|
||||
fun start() {
|
||||
nodeInfoAndSigned = createNodeInfoAndSigned(ALICE_NAME)
|
||||
val identityService = makeTestIdentityService()
|
||||
keyManagementService = MockKeyManagementService(identityService, pkToIdCache = MockPublicKeyToOwningIdentityCache())
|
||||
keyManagementService = MockKeyManagementService(identityService)
|
||||
nodeInfoWatcher = NodeInfoWatcher(tempFolder.root.toPath(), scheduler)
|
||||
nodeInfoPath = tempFolder.root.toPath() / NODE_INFO_DIRECTORY
|
||||
}
|
||||
|
@ -57,10 +57,11 @@ class HibernateColumnConverterTests {
|
||||
val ref = OpaqueBytes.of(0x01)
|
||||
|
||||
// Create parallel set of key and identity services so that the values are not cached, forcing the node caches to do a lookup.
|
||||
val identityService = PersistentIdentityService(TestingNamedCacheFactory())
|
||||
val cacheFactory = TestingNamedCacheFactory()
|
||||
val identityService = PersistentIdentityService(cacheFactory)
|
||||
val originalIdentityService: PersistentIdentityService = services.identityService as PersistentIdentityService
|
||||
identityService.database = originalIdentityService.database
|
||||
identityService.start(originalIdentityService.trustRoot)
|
||||
identityService.start(originalIdentityService.trustRoot, pkToIdCache = PublicKeyToOwningIdentityCacheImpl(database, cacheFactory))
|
||||
val keyService = E2ETestKeyManagementService(identityService)
|
||||
keyService.start(setOf(myself.keyPair))
|
||||
|
||||
|
@ -1,11 +1,11 @@
|
||||
package net.corda.node.services.vault
|
||||
|
||||
import net.corda.core.crypto.Crypto
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.identity.AnonymousParty
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.node.services.queryBy
|
||||
import net.corda.core.node.services.vault.QueryCriteria
|
||||
import net.corda.core.node.services.vault.builder
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
import net.corda.nodeapi.internal.persistence.CordaPersistence
|
||||
import net.corda.testing.common.internal.testNetworkParameters
|
||||
@ -19,6 +19,8 @@ import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import java.util.*
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFails
|
||||
import kotlin.test.assertFailsWith
|
||||
|
||||
class ExternalIdMappingTest {
|
||||
|
||||
@ -34,6 +36,9 @@ class ExternalIdMappingTest {
|
||||
private val myself = TestIdentity(CordaX500Name("Me", "London", "GB"))
|
||||
private val notary = TestIdentity(CordaX500Name("NotaryService", "London", "GB"), 1337L)
|
||||
|
||||
private val alice = TestIdentity.fresh("ALICE")
|
||||
private val bob = TestIdentity.fresh("BOB")
|
||||
|
||||
lateinit var services: MockServices
|
||||
lateinit var database: CordaPersistence
|
||||
|
||||
@ -43,7 +48,7 @@ class ExternalIdMappingTest {
|
||||
cordappPackages = cordapps,
|
||||
initialIdentity = myself,
|
||||
networkParameters = testNetworkParameters(minimumPlatformVersion = 4),
|
||||
moreIdentities = setOf(notary.identity),
|
||||
moreIdentities = setOf(notary.identity, alice.identity, bob.identity),
|
||||
moreKeys = emptySet()
|
||||
)
|
||||
services = mockServices
|
||||
@ -72,17 +77,13 @@ class ExternalIdMappingTest {
|
||||
val dummyStateTwo = createDummyState(listOf(AnonymousParty(keyTwo.owningKey)))
|
||||
// This query should return two states!
|
||||
val result = database.transaction {
|
||||
val externalId = builder { VaultSchemaV1.StateToExternalId::externalId.`in`(listOf(id)) }
|
||||
val queryCriteria = QueryCriteria.VaultCustomQueryCriteria(externalId)
|
||||
vaultService.queryBy<DummyState>(queryCriteria).states
|
||||
vaultService.queryBy<DummyState>(QueryCriteria.VaultQueryCriteria(externalIds = listOf(id))).states
|
||||
}
|
||||
assertEquals(setOf(dummyStateOne, dummyStateTwo), result.map { it.state.data }.toSet())
|
||||
|
||||
// This query should return two states!
|
||||
val resultTwo = database.transaction {
|
||||
val externalId = builder { VaultSchemaV1.StateToExternalId::externalId.equal(id) }
|
||||
val queryCriteria = QueryCriteria.VaultCustomQueryCriteria(externalId)
|
||||
vaultService.queryBy<DummyState>(queryCriteria).states
|
||||
vaultService.queryBy<DummyState>(QueryCriteria.VaultQueryCriteria(externalIds = listOf(id))).states
|
||||
}
|
||||
assertEquals(setOf(dummyStateOne, dummyStateTwo), resultTwo.map { it.state.data }.toSet())
|
||||
}
|
||||
@ -140,11 +141,72 @@ class ExternalIdMappingTest {
|
||||
val dummyState = createDummyState(listOf(AnonymousParty(keyOne.owningKey), AnonymousParty(keyTwo.owningKey)))
|
||||
// This query should return one state!
|
||||
val result = database.transaction {
|
||||
val externalId = builder { VaultSchemaV1.StateToExternalId::externalId.`in`(listOf(idOne, idTwo)) }
|
||||
val queryCriteria = QueryCriteria.VaultCustomQueryCriteria(externalId)
|
||||
vaultService.queryBy<DummyState>(queryCriteria).states
|
||||
vaultService.queryBy<DummyState>(QueryCriteria.VaultQueryCriteria(externalIds = listOf(idOne, idTwo))).states
|
||||
}
|
||||
assertEquals(dummyState, result.single().state.data)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `roger uber keys test`() {
|
||||
// IDs.
|
||||
val id = UUID.randomUUID()
|
||||
val idTwo = UUID.randomUUID()
|
||||
val idThree = UUID.randomUUID()
|
||||
val idFour = UUID.randomUUID()
|
||||
|
||||
// Keys.
|
||||
val A = services.keyManagementService.freshKey(id) // Automatically calls registerKeyToParty and registerKeyToExternalId
|
||||
val B = services.keyManagementService.freshKey(id) // Automatically calls registerKeyToParty and registerKeyToExternalId
|
||||
val C = services.keyManagementService.freshKey(idTwo) // Automatically calls registerKeyToParty and registerKeyToExternalId
|
||||
val D = services.keyManagementService.freshKey() // Automatically calls registerKeyToParty and registerKeyToExternalId
|
||||
val E = Crypto.generateKeyPair().public
|
||||
val F = Crypto.generateKeyPair().public
|
||||
val G = Crypto.generateKeyPair().public
|
||||
|
||||
// Check we can lookup the Party and external ID (if there is one).
|
||||
assertEquals(id, services.identityService.externalIdForPublicKey(A))
|
||||
assertEquals(id, services.identityService.externalIdForPublicKey(B))
|
||||
assertEquals(idTwo, services.identityService.externalIdForPublicKey(C))
|
||||
assertEquals(null, services.identityService.externalIdForPublicKey(D))
|
||||
assertEquals(myself.party, services.identityService.wellKnownPartyFromAnonymous(AnonymousParty(A)))
|
||||
assertEquals(myself.party, services.identityService.wellKnownPartyFromAnonymous(AnonymousParty(B)))
|
||||
assertEquals(myself.party, services.identityService.wellKnownPartyFromAnonymous(AnonymousParty(C)))
|
||||
assertEquals(myself.party, services.identityService.wellKnownPartyFromAnonymous(AnonymousParty(D)))
|
||||
|
||||
// Register some keys generated on another node.
|
||||
services.identityService.registerKey(E, alice.party, idThree)
|
||||
services.identityService.registerKey(F, alice.party, idFour)
|
||||
services.identityService.registerKey(G, bob.party)
|
||||
|
||||
// Try to override existing mappings.
|
||||
services.identityService.registerKey(A, myself.party) // Idempotent call.
|
||||
assertFailsWith<IllegalStateException> { services.identityService.registerKey(A, alice.party) }
|
||||
services.identityService.registerKey(B, myself.party, UUID.randomUUID()) // Idempotent call.
|
||||
assertFailsWith<IllegalStateException> { services.identityService.registerKey(B, alice.party) }
|
||||
assertFailsWith<IllegalStateException> { services.identityService.registerKey(C, bob.party, UUID.randomUUID()) }
|
||||
|
||||
// Check the above calls didn't change anything.
|
||||
assertEquals(myself.party, services.identityService.wellKnownPartyFromAnonymous(AnonymousParty(A)))
|
||||
assertEquals(myself.party, services.identityService.wellKnownPartyFromAnonymous(AnonymousParty(B)))
|
||||
assertEquals(myself.party, services.identityService.wellKnownPartyFromAnonymous(AnonymousParty(C)))
|
||||
|
||||
assertEquals(id, services.identityService.externalIdForPublicKey(A))
|
||||
assertEquals(id, services.identityService.externalIdForPublicKey(B))
|
||||
assertEquals(idTwo, services.identityService.externalIdForPublicKey(C))
|
||||
assertEquals(idThree, services.identityService.externalIdForPublicKey(E))
|
||||
assertEquals(idFour, services.identityService.externalIdForPublicKey(F))
|
||||
|
||||
// ALICE and BOB PartyAndCertificates need to be present in the nameToCert table.
|
||||
assertEquals(alice.party, services.identityService.wellKnownPartyFromAnonymous(AnonymousParty(E)))
|
||||
assertEquals(alice.party, services.identityService.wellKnownPartyFromAnonymous(AnonymousParty(F)))
|
||||
assertEquals(bob.party, services.identityService.wellKnownPartyFromAnonymous(AnonymousParty(G)))
|
||||
|
||||
// Check we can look up keys by external ID.
|
||||
assertEquals(setOf(A, B), services.identityService.publicKeysForExternalId(id).toSet())
|
||||
assertEquals(C, services.identityService.publicKeysForExternalId(idTwo).single())
|
||||
assertEquals(E, services.identityService.publicKeysForExternalId(idThree).single())
|
||||
assertEquals(F, services.identityService.publicKeysForExternalId(idFour).single())
|
||||
assertEquals(emptyList(), services.identityService.publicKeysForExternalId(UUID.randomUUID()))
|
||||
}
|
||||
|
||||
}
|
@ -79,8 +79,7 @@ open class MockServices private constructor(
|
||||
private val moreKeys: Array<out KeyPair>,
|
||||
override val keyManagementService: KeyManagementService = MockKeyManagementService(
|
||||
identityService,
|
||||
*arrayOf(initialIdentity.keyPair) + moreKeys,
|
||||
pkToIdCache = MockPublicKeyToOwningIdentityCache()
|
||||
*arrayOf(initialIdentity.keyPair) + moreKeys
|
||||
)
|
||||
) : ServiceHub {
|
||||
|
||||
@ -128,8 +127,7 @@ open class MockServices private constructor(
|
||||
val database = configureDatabase(dataSourceProps, DatabaseConfig(), identityService::wellKnownPartyFromX500Name, identityService::wellKnownPartyFromAnonymous, schemaService, schemaService.internalSchemas())
|
||||
val keyManagementService = MockKeyManagementService(
|
||||
identityService,
|
||||
*arrayOf(initialIdentity.keyPair) + moreKeys,
|
||||
pkToIdCache = MockPublicKeyToOwningIdentityCache()
|
||||
*arrayOf(initialIdentity.keyPair) + moreKeys
|
||||
)
|
||||
val mockService = database.transaction {
|
||||
makeMockMockServices(cordappLoader, identityService, networkParameters, initialIdentity, moreKeys.toSet(), keyManagementService, schemaService, database)
|
||||
@ -160,20 +158,28 @@ open class MockServices private constructor(
|
||||
val dataSourceProps = makeTestDataSourceProperties()
|
||||
val schemaService = NodeSchemaService(cordappLoader.cordappSchemas)
|
||||
val identityService = PersistentIdentityService(TestingNamedCacheFactory())
|
||||
val persistence = configureDatabase(dataSourceProps, DatabaseConfig(), identityService::wellKnownPartyFromX500Name, identityService::wellKnownPartyFromAnonymous, schemaService, schemaService.internalSchemas())
|
||||
val persistence = configureDatabase(
|
||||
hikariProperties = dataSourceProps,
|
||||
databaseConfig = DatabaseConfig(),
|
||||
wellKnownPartyFromX500Name = identityService::wellKnownPartyFromX500Name,
|
||||
wellKnownPartyFromAnonymous = identityService::wellKnownPartyFromAnonymous,
|
||||
schemaService = schemaService,
|
||||
internalSchemas = schemaService.internalSchemas()
|
||||
)
|
||||
|
||||
val pkToIdCache = PublicKeyToOwningIdentityCacheImpl(persistence, TestingNamedCacheFactory())
|
||||
|
||||
// Create a persistent identity service and add all the supplied identities.
|
||||
identityService.apply {
|
||||
ourNames = setOf(initialIdentity.name)
|
||||
database = persistence
|
||||
start(DEV_ROOT_CA.certificate)
|
||||
start(DEV_ROOT_CA.certificate, pkToIdCache = pkToIdCache)
|
||||
persistence.transaction { identityService.loadIdentities(moreIdentities + initialIdentity.identity) }
|
||||
}
|
||||
|
||||
// Create a persistent key management service and add the key pair which was created for the TestIdentity.
|
||||
// We only add the keypair for the initial identity and any other keys which this node may control. Note: We don't add the keys
|
||||
// for the other identities.
|
||||
val pkToIdCache = PublicKeyToOwningIdentityCacheImpl(persistence, TestingNamedCacheFactory())
|
||||
val aliasKeyMap = mutableMapOf<String, KeyPair>()
|
||||
val aliasedMoreKeys = moreKeys.mapIndexed { index, keyPair ->
|
||||
val alias = "Extra key $index"
|
||||
@ -187,8 +193,7 @@ open class MockServices private constructor(
|
||||
TestingNamedCacheFactory(),
|
||||
identityService,
|
||||
persistence,
|
||||
MockCryptoService(aliasKeyMap),
|
||||
pkToIdCache
|
||||
MockCryptoService(aliasKeyMap)
|
||||
)
|
||||
persistence.transaction { keyManagementService.start(aliasedMoreKeys + aliasedIdentityKey) }
|
||||
|
||||
|
@ -373,7 +373,7 @@ open class InternalMockNetwork(cordappPackages: List<String> = emptyList(),
|
||||
}
|
||||
|
||||
override fun makeKeyManagementService(identityService: PersistentIdentityService): KeyManagementServiceInternal {
|
||||
return BasicHSMKeyManagementService(cacheFactory, identityService, database, cryptoService, pkToIdCache)
|
||||
return BasicHSMKeyManagementService(cacheFactory, identityService, database, cryptoService)
|
||||
}
|
||||
|
||||
override fun startShell() {
|
||||
|
@ -4,6 +4,8 @@ import net.corda.core.crypto.*
|
||||
import net.corda.core.node.services.IdentityService
|
||||
import net.corda.core.node.services.KeyManagementService
|
||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||
import net.corda.node.services.identity.InMemoryIdentityService
|
||||
import net.corda.node.services.identity.PersistentIdentityService
|
||||
import net.corda.node.services.keys.KeyManagementServiceInternal
|
||||
import net.corda.node.services.persistence.WritablePublicKeyToOwningIdentityCache
|
||||
import net.corda.nodeapi.internal.KeyOwningIdentity
|
||||
@ -18,10 +20,10 @@ import java.util.*
|
||||
*
|
||||
* @property identityService The [IdentityService] which contains the given identities.
|
||||
*/
|
||||
class MockKeyManagementService(override val identityService: IdentityService,
|
||||
vararg initialKeys: KeyPair,
|
||||
private val pkToIdCache: WritablePublicKeyToOwningIdentityCache) : SingletonSerializeAsToken(), KeyManagementServiceInternal {
|
||||
|
||||
class MockKeyManagementService(
|
||||
override val identityService: IdentityService,
|
||||
vararg initialKeys: KeyPair
|
||||
) : SingletonSerializeAsToken(), KeyManagementServiceInternal {
|
||||
|
||||
private val keyStore: MutableMap<PublicKey, PrivateKey> = initialKeys.associateByTo(HashMap(), { it.public }, { it.private })
|
||||
|
||||
@ -32,7 +34,9 @@ class MockKeyManagementService(override val identityService: IdentityService,
|
||||
override fun freshKeyInternal(externalId: UUID?): PublicKey {
|
||||
val k = nextKeys.poll() ?: generateKeyPair()
|
||||
keyStore[k.public] = k.private
|
||||
pkToIdCache[k.public] = KeyOwningIdentity.fromUUID(externalId)
|
||||
if (externalId != null) {
|
||||
(identityService as InMemoryIdentityService).registerKeyToExternalId(k.public, externalId)
|
||||
}
|
||||
return k.public
|
||||
}
|
||||
|
||||
@ -59,8 +63,4 @@ class MockKeyManagementService(override val identityService: IdentityService,
|
||||
val keyPair = getSigningKeyPair(publicKey)
|
||||
return keyPair.sign(signableData)
|
||||
}
|
||||
|
||||
override fun externalIdForPublicKey(publicKey: PublicKey): UUID? {
|
||||
return pkToIdCache[publicKey]?.uuid
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user