[CORDA-3149] Update cache to check node identity keys in identity table (#5371)

This commit is contained in:
James Higgs 2019-08-19 20:03:51 +01:00 committed by Shams Asari
parent abd3a2db52
commit 101d978050
6 changed files with 118 additions and 119 deletions

View File

@ -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)
}
}

View File

@ -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

View File

@ -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,

View File

@ -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])
}
} }

View File

@ -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)

View File

@ -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
}
}