mirror of
https://github.com/corda/corda.git
synced 2024-12-24 07:06:44 +00:00
[CORDA-3149] Update cache to check node identity keys in identity table (#5371)
This commit is contained in:
parent
abd3a2db52
commit
101d978050
@ -1,112 +0,0 @@
|
|||||||
package net.corda.node.services.keys
|
|
||||||
|
|
||||||
import net.corda.core.crypto.*
|
|
||||||
import net.corda.core.internal.NamedCacheFactory
|
|
||||||
import net.corda.core.internal.toSet
|
|
||||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
|
||||||
import net.corda.core.utilities.MAX_HASH_HEX_SIZE
|
|
||||||
import net.corda.node.services.identity.PersistentIdentityService
|
|
||||||
import net.corda.node.services.persistence.WritablePublicKeyToOwningIdentityCache
|
|
||||||
import net.corda.node.utilities.AppendOnlyPersistentMap
|
|
||||||
import net.corda.nodeapi.internal.KeyOwningIdentity
|
|
||||||
import net.corda.nodeapi.internal.persistence.CordaPersistence
|
|
||||||
import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX
|
|
||||||
import org.apache.commons.lang3.ArrayUtils.EMPTY_BYTE_ARRAY
|
|
||||||
import org.bouncycastle.operator.ContentSigner
|
|
||||||
import java.security.KeyPair
|
|
||||||
import java.security.PrivateKey
|
|
||||||
import java.security.PublicKey
|
|
||||||
import java.util.*
|
|
||||||
import javax.persistence.*
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A persistent re-implementation of [E2ETestKeyManagementService] to support node re-start.
|
|
||||||
*
|
|
||||||
* This is not the long-term implementation. See the list of items in the above class.
|
|
||||||
*
|
|
||||||
* This class needs database transactions to be in-flight during method calls and init.
|
|
||||||
*/
|
|
||||||
@Deprecated("Superseded by net.corda.node.services.keys.BasicHSMKeyManagementService")
|
|
||||||
class PersistentKeyManagementService(cacheFactory: NamedCacheFactory,
|
|
||||||
override val identityService: PersistentIdentityService,
|
|
||||||
private val database: CordaPersistence,
|
|
||||||
private val pkToIdCache: WritablePublicKeyToOwningIdentityCache) : SingletonSerializeAsToken(), KeyManagementServiceInternal {
|
|
||||||
@Entity
|
|
||||||
@Table(name = "${NODE_DATABASE_PREFIX}our_key_pairs")
|
|
||||||
class PersistentKey(
|
|
||||||
@Id
|
|
||||||
@Column(name = "public_key_hash", length = MAX_HASH_HEX_SIZE, nullable = false)
|
|
||||||
var publicKeyHash: String,
|
|
||||||
|
|
||||||
@Lob
|
|
||||||
@Column(name = "public_key", nullable = false)
|
|
||||||
var publicKey: ByteArray = EMPTY_BYTE_ARRAY,
|
|
||||||
@Lob
|
|
||||||
@Column(name = "private_key", nullable = false)
|
|
||||||
var privateKey: ByteArray = EMPTY_BYTE_ARRAY
|
|
||||||
) {
|
|
||||||
constructor(publicKey: PublicKey, privateKey: PrivateKey)
|
|
||||||
: this(publicKey.toStringShort(), publicKey.encoded, privateKey.encoded)
|
|
||||||
}
|
|
||||||
|
|
||||||
private companion object {
|
|
||||||
fun createKeyMap(cacheFactory: NamedCacheFactory): AppendOnlyPersistentMap<PublicKey, PrivateKey, PersistentKey, String> {
|
|
||||||
return AppendOnlyPersistentMap(
|
|
||||||
cacheFactory = cacheFactory,
|
|
||||||
name = "PersistentKeyManagementService_keys",
|
|
||||||
toPersistentEntityKey = { it.toStringShort() },
|
|
||||||
fromPersistentEntity = {
|
|
||||||
Pair(Crypto.decodePublicKey(it.publicKey),
|
|
||||||
Crypto.decodePrivateKey(it.privateKey))
|
|
||||||
},
|
|
||||||
toPersistentEntity = { key: PublicKey, value: PrivateKey ->
|
|
||||||
PersistentKey(key, value)
|
|
||||||
},
|
|
||||||
persistentEntityClass = PersistentKey::class.java
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val keysMap = createKeyMap(cacheFactory)
|
|
||||||
|
|
||||||
override fun start(initialKeyPairs: Set<KeyPair>) {
|
|
||||||
initialKeyPairs.forEach { keysMap.addWithDuplicatesAllowed(it.public, it.private) }
|
|
||||||
}
|
|
||||||
|
|
||||||
override val keys: Set<PublicKey> get() = database.transaction { keysMap.allPersisted.use { it.map { it.first }.toSet() } }
|
|
||||||
|
|
||||||
override fun filterMyKeys(candidateKeys: Iterable<PublicKey>): Iterable<PublicKey> = database.transaction {
|
|
||||||
identityService.stripNotOurKeys(candidateKeys)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun freshKeyInternal(externalId: UUID?): PublicKey {
|
|
||||||
val keyPair = generateKeyPair()
|
|
||||||
database.transaction {
|
|
||||||
keysMap[keyPair.public] = keyPair.private
|
|
||||||
pkToIdCache[keyPair.public] = KeyOwningIdentity.fromUUID(externalId)
|
|
||||||
}
|
|
||||||
return keyPair.public
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getSigner(publicKey: PublicKey): ContentSigner = getSigner(getSigningKeyPair(publicKey))
|
|
||||||
|
|
||||||
//It looks for the PublicKey in the (potentially) CompositeKey that is ours, and then returns the associated PrivateKey to use in signing
|
|
||||||
private fun getSigningKeyPair(publicKey: PublicKey): KeyPair {
|
|
||||||
return database.transaction {
|
|
||||||
val pk = publicKey.keys.first { keysMap[it] != null } //TODO here for us to re-write this using an actual query if publicKey.keys.size > 1
|
|
||||||
KeyPair(pk, keysMap[pk]!!)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun sign(bytes: ByteArray, publicKey: PublicKey): DigitalSignature.WithKey {
|
|
||||||
val keyPair = getSigningKeyPair(publicKey)
|
|
||||||
return keyPair.sign(bytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: A full KeyManagementService implementation needs to record activity to the Audit Service and to limit
|
|
||||||
// signing to appropriately authorised contexts and initiating users.
|
|
||||||
override fun sign(signableData: SignableData, publicKey: PublicKey): TransactionSignature {
|
|
||||||
val keyPair = getSigningKeyPair(publicKey)
|
|
||||||
return keyPair.sign(signableData)
|
|
||||||
}
|
|
||||||
}
|
|
@ -3,8 +3,10 @@ package net.corda.node.services.persistence
|
|||||||
import com.github.benmanes.caffeine.cache.Caffeine
|
import com.github.benmanes.caffeine.cache.Caffeine
|
||||||
import net.corda.core.crypto.toStringShort
|
import net.corda.core.crypto.toStringShort
|
||||||
import net.corda.core.internal.NamedCacheFactory
|
import net.corda.core.internal.NamedCacheFactory
|
||||||
|
import net.corda.core.internal.hash
|
||||||
import net.corda.core.utilities.contextLogger
|
import net.corda.core.utilities.contextLogger
|
||||||
import net.corda.core.utilities.debug
|
import net.corda.core.utilities.debug
|
||||||
|
import net.corda.node.services.identity.PersistentIdentityService
|
||||||
import net.corda.node.services.keys.BasicHSMKeyManagementService
|
import net.corda.node.services.keys.BasicHSMKeyManagementService
|
||||||
import net.corda.nodeapi.internal.KeyOwningIdentity
|
import net.corda.nodeapi.internal.KeyOwningIdentity
|
||||||
import net.corda.nodeapi.internal.persistence.CordaPersistence
|
import net.corda.nodeapi.internal.persistence.CordaPersistence
|
||||||
@ -23,7 +25,28 @@ class PublicKeyToOwningIdentityCacheImpl(private val database: CordaPersistence,
|
|||||||
|
|
||||||
private val cache = cacheFactory.buildNamed<PublicKey, KeyOwningIdentity>(Caffeine.newBuilder(), "PublicKeyToOwningIdentityCache_cache")
|
private val cache = cacheFactory.buildNamed<PublicKey, KeyOwningIdentity>(Caffeine.newBuilder(), "PublicKeyToOwningIdentityCache_cache")
|
||||||
|
|
||||||
private fun isKeyBelongingToNode(key: PublicKey): Boolean {
|
/**
|
||||||
|
* Establish whether a public key is one of the node's identity keys, by looking in the node's identity database table.
|
||||||
|
*/
|
||||||
|
private fun isKeyIdentityKey(key: PublicKey): Boolean {
|
||||||
|
return database.transaction {
|
||||||
|
val criteriaBuilder = session.criteriaBuilder
|
||||||
|
val criteriaQuery = criteriaBuilder.createQuery(Long::class.java)
|
||||||
|
val queryRoot = criteriaQuery.from(PersistentIdentityService.PersistentIdentity::class.java)
|
||||||
|
criteriaQuery.select(criteriaBuilder.count(queryRoot))
|
||||||
|
criteriaQuery.where(
|
||||||
|
criteriaBuilder.equal(queryRoot.get<String>(PersistentIdentityService.PersistentIdentity::publicKeyHash.name), key.hash.toString())
|
||||||
|
)
|
||||||
|
val query = session.createQuery(criteriaQuery)
|
||||||
|
query.uniqueResult() > 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check to see if the key belongs to one of the key pairs in the node_our_key_pairs table. These keys may relate to confidential
|
||||||
|
* identities.
|
||||||
|
*/
|
||||||
|
private fun isKeyPartOfNodeKeyPairs(key: PublicKey): Boolean {
|
||||||
return database.transaction {
|
return database.transaction {
|
||||||
val criteriaBuilder = session.criteriaBuilder
|
val criteriaBuilder = session.criteriaBuilder
|
||||||
val criteriaQuery = criteriaBuilder.createQuery(Long::class.java)
|
val criteriaQuery = criteriaBuilder.createQuery(Long::class.java)
|
||||||
@ -55,7 +78,7 @@ class PublicKeyToOwningIdentityCacheImpl(private val database: CordaPersistence,
|
|||||||
)
|
)
|
||||||
val query = session.createQuery(criteriaQuery)
|
val query = session.createQuery(criteriaQuery)
|
||||||
val uuid = query.uniqueResult()
|
val uuid = query.uniqueResult()
|
||||||
if (uuid != null || isKeyBelongingToNode(key)) {
|
if (uuid != null || isKeyPartOfNodeKeyPairs(key) || isKeyIdentityKey(key)) {
|
||||||
val signingEntity = KeyOwningIdentity.fromUUID(uuid)
|
val signingEntity = KeyOwningIdentity.fromUUID(uuid)
|
||||||
log.debug { "Database lookup for public key ${key.toStringShort()}, found signing entity $signingEntity" }
|
log.debug { "Database lookup for public key ${key.toStringShort()}, found signing entity $signingEntity" }
|
||||||
signingEntity
|
signingEntity
|
||||||
|
@ -14,7 +14,6 @@ import net.corda.node.services.api.SchemaService.SchemaOptions
|
|||||||
import net.corda.node.services.events.NodeSchedulerService
|
import net.corda.node.services.events.NodeSchedulerService
|
||||||
import net.corda.node.services.identity.PersistentIdentityService
|
import net.corda.node.services.identity.PersistentIdentityService
|
||||||
import net.corda.node.services.keys.BasicHSMKeyManagementService
|
import net.corda.node.services.keys.BasicHSMKeyManagementService
|
||||||
import net.corda.node.services.keys.PersistentKeyManagementService
|
|
||||||
import net.corda.node.services.messaging.P2PMessageDeduplicator
|
import net.corda.node.services.messaging.P2PMessageDeduplicator
|
||||||
import net.corda.node.services.persistence.DBCheckpointStorage
|
import net.corda.node.services.persistence.DBCheckpointStorage
|
||||||
import net.corda.node.services.persistence.DBTransactionStorage
|
import net.corda.node.services.persistence.DBTransactionStorage
|
||||||
@ -38,7 +37,6 @@ class NodeSchemaService(private val extraSchemas: Set<MappedSchema> = emptySet()
|
|||||||
mappedTypes = listOf(DBCheckpointStorage.DBCheckpoint::class.java,
|
mappedTypes = listOf(DBCheckpointStorage.DBCheckpoint::class.java,
|
||||||
DBTransactionStorage.DBTransaction::class.java,
|
DBTransactionStorage.DBTransaction::class.java,
|
||||||
BasicHSMKeyManagementService.PersistentKey::class.java,
|
BasicHSMKeyManagementService.PersistentKey::class.java,
|
||||||
PersistentKeyManagementService.PersistentKey::class.java,
|
|
||||||
NodeSchedulerService.PersistentScheduledState::class.java,
|
NodeSchedulerService.PersistentScheduledState::class.java,
|
||||||
NodeAttachmentService.DBAttachment::class.java,
|
NodeAttachmentService.DBAttachment::class.java,
|
||||||
P2PMessageDeduplicator.ProcessedMessage::class.java,
|
P2PMessageDeduplicator.ProcessedMessage::class.java,
|
||||||
|
@ -118,4 +118,10 @@ class PublicKeyToOwningIdentityCacheImplTest {
|
|||||||
val keys = generateKeyPair()
|
val keys = generateKeyPair()
|
||||||
assertEquals(null, testCache[keys.public])
|
assertEquals(null, testCache[keys.public])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `can request initial identity key`() {
|
||||||
|
val key = alice.publicKey
|
||||||
|
assertEquals(KeyOwningIdentity.UnmappedIdentity, testCache[key])
|
||||||
|
}
|
||||||
}
|
}
|
@ -6,6 +6,7 @@ import net.corda.core.contracts.ContractClassName
|
|||||||
import net.corda.core.contracts.StateRef
|
import net.corda.core.contracts.StateRef
|
||||||
import net.corda.core.cordapp.CordappProvider
|
import net.corda.core.cordapp.CordappProvider
|
||||||
import net.corda.core.crypto.SecureHash
|
import net.corda.core.crypto.SecureHash
|
||||||
|
import net.corda.core.crypto.internal.AliasPrivateKey
|
||||||
import net.corda.core.flows.FlowLogic
|
import net.corda.core.flows.FlowLogic
|
||||||
import net.corda.core.flows.StateMachineRunId
|
import net.corda.core.flows.StateMachineRunId
|
||||||
import net.corda.core.identity.CordaX500Name
|
import net.corda.core.identity.CordaX500Name
|
||||||
@ -26,7 +27,7 @@ import net.corda.node.internal.cordapp.JarScanningCordappLoader
|
|||||||
import net.corda.node.services.api.*
|
import net.corda.node.services.api.*
|
||||||
import net.corda.node.services.identity.InMemoryIdentityService
|
import net.corda.node.services.identity.InMemoryIdentityService
|
||||||
import net.corda.node.services.identity.PersistentIdentityService
|
import net.corda.node.services.identity.PersistentIdentityService
|
||||||
import net.corda.node.services.keys.PersistentKeyManagementService
|
import net.corda.node.services.keys.BasicHSMKeyManagementService
|
||||||
import net.corda.node.services.persistence.PublicKeyToOwningIdentityCacheImpl
|
import net.corda.node.services.persistence.PublicKeyToOwningIdentityCacheImpl
|
||||||
import net.corda.node.services.schema.NodeSchemaService
|
import net.corda.node.services.schema.NodeSchemaService
|
||||||
import net.corda.node.services.transactions.InMemoryTransactionVerifierService
|
import net.corda.node.services.transactions.InMemoryTransactionVerifierService
|
||||||
@ -173,8 +174,23 @@ open class MockServices private constructor(
|
|||||||
// 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
|
// 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.
|
// for the other identities.
|
||||||
val pkToIdCache = PublicKeyToOwningIdentityCacheImpl(persistence, TestingNamedCacheFactory())
|
val pkToIdCache = PublicKeyToOwningIdentityCacheImpl(persistence, TestingNamedCacheFactory())
|
||||||
val keyManagementService = PersistentKeyManagementService(TestingNamedCacheFactory(), identityService, persistence, pkToIdCache)
|
val aliasKeyMap = mutableMapOf<String, KeyPair>()
|
||||||
persistence.transaction { keyManagementService.start(moreKeys + initialIdentity.keyPair) }
|
val aliasedMoreKeys = moreKeys.mapIndexed { index, keyPair ->
|
||||||
|
val alias = "Extra key $index"
|
||||||
|
aliasKeyMap[alias] = keyPair
|
||||||
|
KeyPair(keyPair.public, AliasPrivateKey(alias))
|
||||||
|
}.toSet()
|
||||||
|
val identityAlias = "${initialIdentity.name} private key"
|
||||||
|
aliasKeyMap[identityAlias] = initialIdentity.keyPair
|
||||||
|
val aliasedIdentityKey = KeyPair(initialIdentity.publicKey, AliasPrivateKey(identityAlias))
|
||||||
|
val keyManagementService = BasicHSMKeyManagementService(
|
||||||
|
TestingNamedCacheFactory(),
|
||||||
|
identityService,
|
||||||
|
persistence,
|
||||||
|
MockCryptoService(aliasKeyMap),
|
||||||
|
pkToIdCache
|
||||||
|
)
|
||||||
|
persistence.transaction { keyManagementService.start(aliasedMoreKeys + aliasedIdentityKey) }
|
||||||
|
|
||||||
val mockService = persistence.transaction {
|
val mockService = persistence.transaction {
|
||||||
makeMockMockServices(cordappLoader, identityService, networkParameters, initialIdentity, moreKeys, keyManagementService, schemaService, persistence)
|
makeMockMockServices(cordappLoader, identityService, networkParameters, initialIdentity, moreKeys, keyManagementService, schemaService, persistence)
|
||||||
|
@ -0,0 +1,68 @@
|
|||||||
|
package net.corda.testing.node.internal
|
||||||
|
|
||||||
|
import net.corda.core.crypto.Crypto
|
||||||
|
import net.corda.core.crypto.SignatureScheme
|
||||||
|
import net.corda.core.crypto.internal.cordaBouncyCastleProvider
|
||||||
|
import net.corda.core.crypto.newSecureRandom
|
||||||
|
import net.corda.core.crypto.sha256
|
||||||
|
import net.corda.nodeapi.internal.crypto.ContentSignerBuilder
|
||||||
|
import net.corda.nodeapi.internal.cryptoservice.CryptoService
|
||||||
|
import net.corda.nodeapi.internal.cryptoservice.CryptoServiceException
|
||||||
|
import org.bouncycastle.operator.ContentSigner
|
||||||
|
import java.security.KeyPair
|
||||||
|
import java.security.PublicKey
|
||||||
|
import java.security.Signature
|
||||||
|
|
||||||
|
class MockCryptoService(initialKeyPairs: Map<String, KeyPair>) : CryptoService {
|
||||||
|
|
||||||
|
private val aliasToKey: MutableMap<String, KeyPair> = mutableMapOf()
|
||||||
|
|
||||||
|
init {
|
||||||
|
initialKeyPairs.forEach {
|
||||||
|
aliasToKey[it.key] = it.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun containsKey(alias: String): Boolean {
|
||||||
|
return aliasToKey.containsKey(alias)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getPublicKey(alias: String): PublicKey? {
|
||||||
|
return aliasToKey[alias]?.public
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun sign(alias: String, data: ByteArray, signAlgorithm: String?): ByteArray {
|
||||||
|
try {
|
||||||
|
return when(signAlgorithm) {
|
||||||
|
null -> Crypto.doSign(aliasToKey[alias]!!.private, data)
|
||||||
|
else -> signWithAlgorithm(alias, data, signAlgorithm)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
throw CryptoServiceException("Cannot sign using the key with alias $alias. SHA256 of data to be signed: ${data.sha256()}", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun signWithAlgorithm(alias: String, data: ByteArray, signAlgorithm: String): ByteArray {
|
||||||
|
val privateKey = aliasToKey[alias]!!.private
|
||||||
|
val signature = Signature.getInstance(signAlgorithm, cordaBouncyCastleProvider)
|
||||||
|
signature.initSign(privateKey, newSecureRandom())
|
||||||
|
signature.update(data)
|
||||||
|
return signature.sign()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getSigner(alias: String): ContentSigner {
|
||||||
|
try {
|
||||||
|
val privateKey = aliasToKey[alias]!!.private
|
||||||
|
val signatureScheme = Crypto.findSignatureScheme(privateKey)
|
||||||
|
return ContentSignerBuilder.build(signatureScheme, privateKey, Crypto.findProvider(signatureScheme.providerName), newSecureRandom())
|
||||||
|
} catch (e: Exception) {
|
||||||
|
throw CryptoServiceException("Cannot get Signer for key with alias $alias", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun generateKeyPair(alias: String, scheme: SignatureScheme): PublicKey {
|
||||||
|
val keyPair = Crypto.generateKeyPair(scheme)
|
||||||
|
aliasToKey[alias] = keyPair
|
||||||
|
return keyPair.public
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user