mirror of
https://github.com/corda/corda.git
synced 2024-12-18 20:47:57 +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 net.corda.core.crypto.toStringShort
|
||||
import net.corda.core.internal.NamedCacheFactory
|
||||
import net.corda.core.internal.hash
|
||||
import net.corda.core.utilities.contextLogger
|
||||
import net.corda.core.utilities.debug
|
||||
import net.corda.node.services.identity.PersistentIdentityService
|
||||
import net.corda.node.services.keys.BasicHSMKeyManagementService
|
||||
import net.corda.nodeapi.internal.KeyOwningIdentity
|
||||
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 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 {
|
||||
val criteriaBuilder = session.criteriaBuilder
|
||||
val criteriaQuery = criteriaBuilder.createQuery(Long::class.java)
|
||||
@ -55,7 +78,7 @@ class PublicKeyToOwningIdentityCacheImpl(private val database: CordaPersistence,
|
||||
)
|
||||
val query = session.createQuery(criteriaQuery)
|
||||
val uuid = query.uniqueResult()
|
||||
if (uuid != null || isKeyBelongingToNode(key)) {
|
||||
if (uuid != null || isKeyPartOfNodeKeyPairs(key) || isKeyIdentityKey(key)) {
|
||||
val signingEntity = KeyOwningIdentity.fromUUID(uuid)
|
||||
log.debug { "Database lookup for public key ${key.toStringShort()}, found signing entity $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.identity.PersistentIdentityService
|
||||
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.persistence.DBCheckpointStorage
|
||||
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,
|
||||
DBTransactionStorage.DBTransaction::class.java,
|
||||
BasicHSMKeyManagementService.PersistentKey::class.java,
|
||||
PersistentKeyManagementService.PersistentKey::class.java,
|
||||
NodeSchedulerService.PersistentScheduledState::class.java,
|
||||
NodeAttachmentService.DBAttachment::class.java,
|
||||
P2PMessageDeduplicator.ProcessedMessage::class.java,
|
||||
|
@ -118,4 +118,10 @@ class PublicKeyToOwningIdentityCacheImplTest {
|
||||
val keys = generateKeyPair()
|
||||
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.cordapp.CordappProvider
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.crypto.internal.AliasPrivateKey
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.flows.StateMachineRunId
|
||||
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.identity.InMemoryIdentityService
|
||||
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.schema.NodeSchemaService
|
||||
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
|
||||
// for the other identities.
|
||||
val pkToIdCache = PublicKeyToOwningIdentityCacheImpl(persistence, TestingNamedCacheFactory())
|
||||
val keyManagementService = PersistentKeyManagementService(TestingNamedCacheFactory(), identityService, persistence, pkToIdCache)
|
||||
persistence.transaction { keyManagementService.start(moreKeys + initialIdentity.keyPair) }
|
||||
val aliasKeyMap = mutableMapOf<String, 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 {
|
||||
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