mirror of
https://github.com/corda/corda.git
synced 2025-06-17 14:48:16 +00:00
Infrastructure for confidential identities
* De-anonymise parties in AbstractStateReplacementFlow flows * Convert transaction key negotiation to a subflow instead of utility functions * Add serialization support for CertPath * Restructure cash flows so that a counterparty flow can be added later
This commit is contained in:
@ -306,7 +306,7 @@ abstract class AbstractNode(open 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.
|
||||
keyManagement = makeKeyManagementService()
|
||||
keyManagement = makeKeyManagementService(identity)
|
||||
scheduler = NodeSchedulerService(services, database, unfinishedSchedules = busyNodeLatch)
|
||||
|
||||
val tokenizableServices = mutableListOf(storage, net, vault, keyManagement, identity, platformClock, scheduler)
|
||||
@ -501,7 +501,9 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
|
||||
"has any other map node been configured.")
|
||||
}
|
||||
|
||||
protected open fun makeKeyManagementService(): KeyManagementService = PersistentKeyManagementService(partyKeys)
|
||||
protected open fun makeKeyManagementService(identityService: IdentityService): KeyManagementService {
|
||||
return PersistentKeyManagementService(identityService, partyKeys)
|
||||
}
|
||||
|
||||
open protected fun makeNetworkMapService() {
|
||||
inNodeNetworkMapService = PersistentNetworkMapService(services, configuration.minimumPlatformVersion)
|
||||
|
@ -54,8 +54,17 @@ class InMemoryIdentityService(identities: Iterable<Party> = emptySet(),
|
||||
@Deprecated("Use partyFromX500Name")
|
||||
override fun partyFromName(name: String): Party? = principalToParties[X500Name(name)]
|
||||
override fun partyFromX500Name(principal: X500Name): Party? = principalToParties[principal]
|
||||
override fun partyFromAnonymous(party: AbstractParty): Party? = partyFromKey(party.owningKey)
|
||||
override fun partyFromAnonymous(party: AbstractParty): Party? {
|
||||
return if (party is Party) {
|
||||
party
|
||||
} else {
|
||||
partyFromKey(party.owningKey)
|
||||
}
|
||||
}
|
||||
override fun partyFromAnonymous(partyRef: PartyAndReference) = partyFromAnonymous(partyRef.party)
|
||||
override fun requirePartyFromAnonymous(party: AbstractParty): Party {
|
||||
return partyFromAnonymous(party) ?: throw IllegalStateException("Could not deanonymise party ${party.owningKey.toStringShort()}")
|
||||
}
|
||||
|
||||
@Throws(IdentityService.UnknownAnonymousPartyException::class)
|
||||
override fun assertOwnership(party: Party, anonymousParty: AnonymousParty) {
|
||||
|
@ -5,11 +5,15 @@ import net.corda.core.crypto.DigitalSignature
|
||||
import net.corda.core.crypto.generateKeyPair
|
||||
import net.corda.core.crypto.keys
|
||||
import net.corda.core.crypto.sign
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.node.services.IdentityService
|
||||
import net.corda.core.node.services.KeyManagementService
|
||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||
import java.security.KeyPair
|
||||
import java.security.PrivateKey
|
||||
import java.security.PublicKey
|
||||
import java.security.cert.CertPath
|
||||
import java.security.cert.X509Certificate
|
||||
import java.util.*
|
||||
import javax.annotation.concurrent.ThreadSafe
|
||||
|
||||
@ -25,7 +29,8 @@ import javax.annotation.concurrent.ThreadSafe
|
||||
* etc.
|
||||
*/
|
||||
@ThreadSafe
|
||||
class E2ETestKeyManagementService(initialKeys: Set<KeyPair>) : SingletonSerializeAsToken(), KeyManagementService {
|
||||
class E2ETestKeyManagementService(val identityService: IdentityService,
|
||||
initialKeys: Set<KeyPair>) : SingletonSerializeAsToken(), KeyManagementService {
|
||||
private class InnerState {
|
||||
val keys = HashMap<PublicKey, PrivateKey>()
|
||||
}
|
||||
@ -51,6 +56,8 @@ class E2ETestKeyManagementService(initialKeys: Set<KeyPair>) : SingletonSerializ
|
||||
return keyPair.public
|
||||
}
|
||||
|
||||
override fun freshKeyAndCert(identity: Party, revocationEnabled: Boolean): Pair<X509Certificate, CertPath> = freshKeyAndCert(this, identityService, identity, revocationEnabled)
|
||||
|
||||
private fun getSigningKeyPair(publicKey: PublicKey): KeyPair {
|
||||
return mutex.locked {
|
||||
val pk = publicKey.keys.first { keys.containsKey(it) }
|
||||
|
@ -0,0 +1,37 @@
|
||||
package net.corda.node.services.keys
|
||||
|
||||
import net.corda.core.crypto.CertificateType
|
||||
import net.corda.core.crypto.Crypto
|
||||
import net.corda.core.crypto.X509Utilities
|
||||
import net.corda.core.identity.AnonymousParty
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.node.services.IdentityService
|
||||
import net.corda.core.node.services.KeyManagementService
|
||||
import java.security.cert.CertPath
|
||||
import java.security.cert.X509Certificate
|
||||
|
||||
/**
|
||||
* Generates a new random [KeyPair], adds it to the internal key storage, then generates a corresponding
|
||||
* [X509Certificate] and adds it to the identity service.
|
||||
*
|
||||
* @param keyManagementService key service to use when generating the new key.
|
||||
* @param identityService identity service to use when registering the certificate.
|
||||
* @param identity identity to generate a key and certificate for. Must be an identity this node has CA privileges for.
|
||||
* @param revocationEnabled whether to check revocation status of certificates in the certificate path.
|
||||
* @return X.509 certificate and path to the trust root.
|
||||
*/
|
||||
fun freshKeyAndCert(keyManagementService: KeyManagementService,
|
||||
identityService: IdentityService,
|
||||
identity: Party,
|
||||
revocationEnabled: Boolean = false): Pair<X509Certificate, CertPath> {
|
||||
val ourPublicKey = keyManagementService.freshKey()
|
||||
// FIXME: Use the actual certificate for the identity the flow is presenting themselves as
|
||||
val issuerKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_IDENTITY_SIGNATURE_SCHEME)
|
||||
val issuerCertificate = X509Utilities.createSelfSignedCACertificate(identity.name, issuerKey)
|
||||
val ourCertificate = X509Utilities.createCertificate(CertificateType.IDENTITY, issuerCertificate, issuerKey, identity.name, ourPublicKey)
|
||||
val ourCertPath = X509Utilities.createCertificatePath(issuerCertificate, ourCertificate, revocationEnabled = revocationEnabled)
|
||||
identityService.registerPath(issuerCertificate,
|
||||
AnonymousParty(ourPublicKey),
|
||||
ourCertPath)
|
||||
return Pair(issuerCertificate, ourCertPath)
|
||||
}
|
@ -5,6 +5,8 @@ import net.corda.core.crypto.DigitalSignature
|
||||
import net.corda.core.crypto.generateKeyPair
|
||||
import net.corda.core.crypto.keys
|
||||
import net.corda.core.crypto.sign
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.node.services.IdentityService
|
||||
import net.corda.core.node.services.KeyManagementService
|
||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||
import net.corda.node.utilities.*
|
||||
@ -13,6 +15,8 @@ import org.jetbrains.exposed.sql.statements.InsertStatement
|
||||
import java.security.KeyPair
|
||||
import java.security.PrivateKey
|
||||
import java.security.PublicKey
|
||||
import java.security.cert.CertPath
|
||||
import java.security.cert.X509Certificate
|
||||
|
||||
/**
|
||||
* A persistent re-implementation of [E2ETestKeyManagementService] to support node re-start.
|
||||
@ -21,7 +25,8 @@ import java.security.PublicKey
|
||||
*
|
||||
* This class needs database transactions to be in-flight during method calls and init.
|
||||
*/
|
||||
class PersistentKeyManagementService(initialKeys: Set<KeyPair>) : SingletonSerializeAsToken(), KeyManagementService {
|
||||
class PersistentKeyManagementService(val identityService: IdentityService,
|
||||
initialKeys: Set<KeyPair>) : SingletonSerializeAsToken(), KeyManagementService {
|
||||
|
||||
private object Table : JDBCHashedTable("${NODE_DATABASE_PREFIX}our_key_pairs") {
|
||||
val publicKey = publicKey("public_key")
|
||||
@ -62,6 +67,8 @@ class PersistentKeyManagementService(initialKeys: Set<KeyPair>) : SingletonSeria
|
||||
return keyPair.public
|
||||
}
|
||||
|
||||
override fun freshKeyAndCert(identity: Party, revocationEnabled: Boolean): Pair<X509Certificate, CertPath> = freshKeyAndCert(this, identityService, identity, revocationEnabled)
|
||||
|
||||
private fun getSigningKeyPair(publicKey: PublicKey): KeyPair {
|
||||
return mutex.locked {
|
||||
val pk = publicKey.keys.first { keys.containsKey(it) }
|
||||
|
@ -348,7 +348,7 @@ class StateMachineManager(val serviceHub: ServiceHubInternal,
|
||||
val serviceFlowInfo = serviceHub.getServiceFlowFactory(sessionInit.clientFlowClass)
|
||||
if (serviceFlowInfo == null) {
|
||||
logger.warn("${sessionInit.clientFlowClass} has not been registered with a service flow: $sessionInit")
|
||||
sendSessionReject("Don't know ${sessionInit.clientFlowClass.name}")
|
||||
sendSessionReject("${sessionInit.clientFlowClass.name} has not been registered with a service flow")
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -12,6 +12,7 @@ import net.corda.core.serialization.SingletonSerializeAsToken
|
||||
import net.corda.core.utilities.ALICE_KEY
|
||||
import net.corda.core.utilities.DUMMY_NOTARY
|
||||
import net.corda.node.services.MockServiceHubInternal
|
||||
import net.corda.node.services.identity.InMemoryIdentityService
|
||||
import net.corda.node.services.persistence.DBCheckpointStorage
|
||||
import net.corda.node.services.statemachine.FlowLogicRefFactoryImpl
|
||||
import net.corda.node.services.statemachine.StateMachineManager
|
||||
@ -75,9 +76,10 @@ class NodeSchedulerServiceTest : SingletonSerializeAsToken() {
|
||||
val dataSourceAndDatabase = configureDatabase(dataSourceProps)
|
||||
dataSource = dataSourceAndDatabase.first
|
||||
database = dataSourceAndDatabase.second
|
||||
val identityService = InMemoryIdentityService()
|
||||
val kms = MockKeyManagementService(identityService, ALICE_KEY)
|
||||
|
||||
database.transaction {
|
||||
val kms = MockKeyManagementService(ALICE_KEY)
|
||||
val nullIdentity = X500Name("cn=None")
|
||||
val mockMessagingService = InMemoryMessagingNetwork(false).InMemoryMessaging(
|
||||
false,
|
||||
|
@ -115,4 +115,14 @@ class InMemoryIdentityServiceTests {
|
||||
service.assertOwnership(bob, anonymousAlice)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure if we feed in a full identity, we get the same identity back.
|
||||
*/
|
||||
@Test
|
||||
fun `deanonymising a well known identity`() {
|
||||
val expected = ALICE
|
||||
val actual = InMemoryIdentityService().partyFromAnonymous(expected)
|
||||
assertEquals(expected, actual)
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user