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:
Ross Nicoll
2017-05-16 15:30:26 +01:00
parent 851cccbf7e
commit a8d4dccea4
21 changed files with 295 additions and 164 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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