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

@ -20,7 +20,6 @@ import net.corda.core.serialization.OpaqueBytes
import net.corda.core.serialization.deserialize
import net.corda.core.serialization.serialize
import net.i2p.crypto.eddsa.EdDSAPublicKey
import org.bouncycastle.asn1.ASN1InputStream
import org.bouncycastle.asn1.x500.X500Name
import java.math.BigDecimal
import java.security.PublicKey
@ -39,28 +38,28 @@ object JacksonSupport {
interface PartyObjectMapper {
@Deprecated("Use partyFromX500Name instead")
fun partyFromName(partyName: String): Party?
fun partyFromPrincipal(principal: X500Name): Party?
fun partyFromX500Name(name: X500Name): Party?
fun partyFromKey(owningKey: PublicKey): Party?
}
class RpcObjectMapper(val rpc: CordaRPCOps, factory: JsonFactory) : PartyObjectMapper, ObjectMapper(factory) {
@Suppress("OverridingDeprecatedMember", "DEPRECATION")
override fun partyFromName(partyName: String): Party? = rpc.partyFromName(partyName)
override fun partyFromPrincipal(principal: X500Name): Party? = rpc.partyFromX500Name(principal)
override fun partyFromX500Name(name: X500Name): Party? = rpc.partyFromX500Name(name)
override fun partyFromKey(owningKey: PublicKey): Party? = rpc.partyFromKey(owningKey)
}
class IdentityObjectMapper(val identityService: IdentityService, factory: JsonFactory) : PartyObjectMapper, ObjectMapper(factory) {
@Suppress("OverridingDeprecatedMember", "DEPRECATION")
override fun partyFromName(partyName: String): Party? = identityService.partyFromName(partyName)
override fun partyFromPrincipal(principal: X500Name): Party? = identityService.partyFromX500Name(principal)
override fun partyFromX500Name(name: X500Name): Party? = identityService.partyFromX500Name(name)
override fun partyFromKey(owningKey: PublicKey): Party? = identityService.partyFromKey(owningKey)
}
class NoPartyObjectMapper(factory: JsonFactory) : PartyObjectMapper, ObjectMapper(factory) {
@Suppress("OverridingDeprecatedMember", "DEPRECATION")
override fun partyFromName(partyName: String): Party? = throw UnsupportedOperationException()
override fun partyFromPrincipal(principal: X500Name): Party? = throw UnsupportedOperationException()
override fun partyFromX500Name(name: X500Name): Party? = throw UnsupportedOperationException()
override fun partyFromKey(owningKey: PublicKey): Party? = throw UnsupportedOperationException()
}
@ -170,7 +169,7 @@ object JacksonSupport {
// how to parse the content
return if (parser.text.contains("=")) {
val principal = X500Name(parser.text)
mapper.partyFromPrincipal(principal) ?: throw JsonParseException(parser, "Could not find a Party with name ${principal}")
mapper.partyFromX500Name(principal) ?: throw JsonParseException(parser, "Could not find a Party with name ${principal}")
} else {
val key = try {
parsePublicKeyBase58(parser.text)

View File

@ -1,6 +1,8 @@
package net.corda.core.crypto
import net.corda.core.crypto.Crypto.generateKeyPair
import net.corda.core.identity.Party
import net.corda.core.node.ServiceHub
import org.bouncycastle.asn1.ASN1Encodable
import org.bouncycastle.asn1.x500.X500Name
import org.bouncycastle.asn1.x500.X500NameBuilder
@ -24,6 +26,7 @@ import java.time.temporal.ChronoUnit
import java.util.*
object X509Utilities {
val DEFAULT_IDENTITY_SIGNATURE_SCHEME = Crypto.EDDSA_ED25519_SHA512
val DEFAULT_TLS_SIGNATURE_SCHEME = Crypto.ECDSA_SECP256R1_SHA256
// Aliases for private keys and certificates.

View File

@ -0,0 +1,95 @@
package net.corda.core.flows
import co.paralleluniverse.fibers.Suspendable
import net.corda.core.identity.AnonymousParty
import net.corda.core.identity.Party
import net.corda.core.utilities.ProgressTracker
import net.corda.core.utilities.unwrap
import org.bouncycastle.asn1.x500.X500Name
import java.security.cert.CertPath
import java.security.cert.X509Certificate
/**
* Very basic flow which exchanges transaction key and certificate paths between two parties in a transaction.
* This is intended for use as a subflow of another flow.
*/
object TxKeyFlow {
abstract class AbstractIdentityFlow(val otherSide: Party, val revocationEnabled: Boolean): FlowLogic<Map<Party, AnonymousIdentity>>() {
fun validateIdentity(untrustedIdentity: Pair<X509Certificate, CertPath>): AnonymousIdentity {
val (wellKnownCert, certPath) = untrustedIdentity
val theirCert = certPath.certificates.last()
// TODO: Don't trust self-signed certificates
return if (theirCert is X509Certificate) {
val certName = X500Name(theirCert.subjectDN.name)
if (certName == otherSide.name) {
val anonymousParty = AnonymousParty(theirCert.publicKey)
serviceHub.identityService.registerPath(wellKnownCert, anonymousParty, certPath)
AnonymousIdentity(certPath, theirCert, anonymousParty)
} else
throw IllegalStateException("Expected certificate subject to be ${otherSide.name} but found ${certName}")
} else
throw IllegalStateException("Expected an X.509 certificate but received ${theirCert.javaClass.name}")
}
}
@StartableByRPC
@InitiatingFlow
class Requester(otherSide: Party,
revocationEnabled: Boolean,
override val progressTracker: ProgressTracker) : AbstractIdentityFlow(otherSide, revocationEnabled) {
constructor(otherSide: Party,
revocationEnabled: Boolean) : this(otherSide, revocationEnabled, tracker())
companion object {
object AWAITING_KEY : ProgressTracker.Step("Awaiting key")
fun tracker() = ProgressTracker(AWAITING_KEY)
}
@Suspendable
override fun call(): Map<Party, AnonymousIdentity> {
progressTracker.currentStep = AWAITING_KEY
val myIdentityFragment = serviceHub.keyManagementService.freshKeyAndCert(serviceHub.myInfo.legalIdentity, revocationEnabled)
val theirIdentity = receive<Pair<X509Certificate, CertPath>>(otherSide).unwrap { validateIdentity(it) }
send(otherSide, myIdentityFragment)
return mapOf(Pair(otherSide, AnonymousIdentity(myIdentityFragment)),
Pair(serviceHub.myInfo.legalIdentity, theirIdentity))
}
}
/**
* Flow which waits for a key request from a counterparty, generates a new key and then returns it to the
* counterparty and as the result from the flow.
*/
class Provider(otherSide: Party,
revocationEnabled: Boolean,
override val progressTracker: ProgressTracker) : AbstractIdentityFlow(otherSide,revocationEnabled) {
constructor(otherSide: Party,
revocationEnabled: Boolean = false) : this(otherSide, revocationEnabled, tracker())
companion object {
object SENDING_KEY : ProgressTracker.Step("Sending key")
fun tracker() = ProgressTracker(SENDING_KEY)
}
@Suspendable
override fun call(): Map<Party, AnonymousIdentity> {
progressTracker.currentStep = SENDING_KEY
val myIdentityFragment = serviceHub.keyManagementService.freshKeyAndCert(serviceHub.myInfo.legalIdentity, revocationEnabled)
send(otherSide, myIdentityFragment)
val theirIdentity = receive<Pair<X509Certificate, CertPath>>(otherSide).unwrap { validateIdentity(it) }
return mapOf(Pair(otherSide, AnonymousIdentity(myIdentityFragment)),
Pair(serviceHub.myInfo.legalIdentity, theirIdentity))
}
}
data class AnonymousIdentity(
val certPath: CertPath,
val certificate: X509Certificate,
val identity: AnonymousParty) {
constructor(myIdentity: Pair<X509Certificate, CertPath>) : this(myIdentity.second,
myIdentity.first,
AnonymousParty(myIdentity.second.certificates.last().publicKey))
}
}

View File

@ -1,6 +1,7 @@
package net.corda.core.node.services
import net.corda.core.contracts.PartyAndReference
import net.corda.core.crypto.toStringShort
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.AnonymousParty
import net.corda.core.identity.Party
@ -55,9 +56,31 @@ interface IdentityService {
fun partyFromName(name: String): Party?
fun partyFromX500Name(principal: X500Name): Party?
/**
* Resolve the well known identity of a party. If the party passed in is already a well known identity
* (i.e. a [Party]) this returns it as-is.
*
* @return the well known identity, or null if unknown.
*/
fun partyFromAnonymous(party: AbstractParty): Party?
/**
* Resolve the well known identity of a party. If the party passed in is already a well known identity
* (i.e. a [Party]) this returns it as-is.
*
* @return the well known identity, or null if unknown.
*/
fun partyFromAnonymous(partyRef: PartyAndReference) = partyFromAnonymous(partyRef.party)
/**
* Resolve the well known identity of a party. Throws an exception if the party cannot be identified.
* If the party passed in is already a well known identity (i.e. a [Party]) this returns it as-is.
*
* @return the well known identity.
* @throws IllegalArgumentException
*/
fun requirePartyFromAnonymous(party: AbstractParty): Party
/**
* Get the certificate chain showing an anonymous party is owned by the given party.
*/

View File

@ -3,10 +3,10 @@ package net.corda.core.node.services
import co.paralleluniverse.fibers.Suspendable
import com.google.common.util.concurrent.ListenableFuture
import net.corda.core.contracts.*
import net.corda.core.crypto.DigitalSignature
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.*
import net.corda.core.flows.FlowException
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.AnonymousParty
import net.corda.core.identity.Party
import net.corda.core.node.services.vault.PageSpecification
import net.corda.core.node.services.vault.QueryCriteria
@ -20,6 +20,8 @@ import net.corda.core.transactions.WireTransaction
import rx.Observable
import java.io.InputStream
import java.security.PublicKey
import java.security.cert.CertPath
import java.security.cert.X509Certificate
import java.time.Instant
import java.util.*
@ -385,6 +387,17 @@ interface KeyManagementService {
@Suspendable
fun freshKey(): PublicKey
/**
* 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 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.
*/
@Suspendable
fun freshKeyAndCert(identity: Party, revocationEnabled: Boolean): Pair<X509Certificate, CertPath>
/** Using the provided signing [PublicKey] internally looks up the matching [PrivateKey] and signs the data.
* @param bytes The data to sign over using the chosen key.
* @param publicKey The [PublicKey] partner to an internally held [PrivateKey], either derived from the node's primary identity,

View File

@ -1,41 +0,0 @@
package net.corda.flows
import co.paralleluniverse.fibers.Suspendable
import net.corda.core.flows.FlowLogic
import net.corda.core.identity.Party
import net.corda.core.serialization.CordaSerializable
import net.corda.core.utilities.unwrap
import java.security.PublicKey
import java.security.cert.Certificate
object TxKeyFlowUtilities {
/**
* Receive a key from a counterparty. This would normally be triggered by a flow as part of a transaction assembly
* process.
*/
@Suspendable
fun receiveKey(flow: FlowLogic<*>, otherSide: Party): Pair<PublicKey, Certificate?> {
val untrustedKey = flow.receive<ProvidedTransactionKey>(otherSide)
return untrustedKey.unwrap {
// TODO: Verify the certificate connects the given key to the counterparty, once we have certificates
Pair(it.key, it.certificate)
}
}
/**
* Generates a new key and then returns it to the counterparty and as the result from the function. Note that this
* is an expensive operation, and should only be called once the calling flow has confirmed it wants to be part of
* a transaction with the counterparty, in order to avoid a DoS risk.
*/
@Suspendable
fun provideKey(flow: FlowLogic<*>, otherSide: Party): PublicKey {
val key = flow.serviceHub.keyManagementService.freshKey()
// TODO: Generate and sign certificate for the key, once we have signing support for composite keys
// (in this case the legal identity key)
flow.send(otherSide, ProvidedTransactionKey(key, null))
return key
}
@CordaSerializable
data class ProvidedTransactionKey(val key: PublicKey, val certificate: Certificate?)
}

View File

@ -1,55 +0,0 @@
package net.corda.core.flows
import co.paralleluniverse.fibers.Suspendable
import net.corda.core.identity.Party
import net.corda.core.utilities.ProgressTracker
import net.corda.flows.TxKeyFlowUtilities
import java.security.PublicKey
import java.security.cert.Certificate
/**
* Very basic flow which requests a transaction key from a counterparty, used for testing [TxKeyFlowUtilities].
* This MUST not be provided on any real node, as the ability for arbitrary parties to request keys would enable
* DoS of the node, as key generation/storage is vastly more expensive than submitting a request.
*/
object TxKeyFlow {
@InitiatingFlow
class Requester(val otherSide: Party,
override val progressTracker: ProgressTracker) : FlowLogic<Pair<PublicKey, Certificate?>>() {
constructor(otherSide: Party) : this(otherSide, tracker())
companion object {
object AWAITING_KEY : ProgressTracker.Step("Awaiting key")
fun tracker() = ProgressTracker(AWAITING_KEY)
}
@Suspendable
override fun call(): Pair<PublicKey, Certificate?> {
progressTracker.currentStep = AWAITING_KEY
return TxKeyFlowUtilities.receiveKey(this, otherSide)
}
}
/**
* Flow which waits for a key request from a counterparty, generates a new key and then returns it to the
* counterparty and as the result from the flow.
*/
class Provider(val otherSide: Party,
override val progressTracker: ProgressTracker) : FlowLogic<PublicKey>() {
constructor(otherSide: Party) : this(otherSide, tracker())
companion object {
object SENDING_KEY : ProgressTracker.Step("Sending key")
fun tracker() = ProgressTracker(SENDING_KEY)
}
@Suspendable
override fun call(): PublicKey {
progressTracker.currentStep = SENDING_KEY
return TxKeyFlowUtilities.provideKey(this, otherSide)
}
}
}

View File

@ -0,0 +1,53 @@
package net.corda.core.flows
import net.corda.core.getOrThrow
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.Party
import net.corda.core.utilities.ALICE
import net.corda.core.utilities.BOB
import net.corda.core.utilities.DUMMY_NOTARY
import net.corda.testing.node.MockNetwork
import org.junit.Before
import org.junit.Test
import kotlin.test.assertEquals
import kotlin.test.assertNotEquals
class TxKeyFlowTests {
lateinit var net: MockNetwork
@Before
fun before() {
net = MockNetwork(false)
}
@Test
fun `issue key`() {
// We run this in parallel threads to help catch any race conditions that may exist.
net = MockNetwork(false, true)
// Set up values we'll need
val revocationEnabled = false
val notaryNode = net.createNotaryNode(null, DUMMY_NOTARY.name)
val aliceNode = net.createPartyNode(notaryNode.info.address, ALICE.name)
val bobNode = net.createPartyNode(notaryNode.info.address, BOB.name)
val alice: Party = aliceNode.services.myInfo.legalIdentity
val bob: Party = bobNode.services.myInfo.legalIdentity
aliceNode.services.identityService.registerIdentity(bob)
aliceNode.services.identityService.registerIdentity(notaryNode.info.legalIdentity)
bobNode.services.identityService.registerIdentity(alice)
bobNode.services.identityService.registerIdentity(notaryNode.info.legalIdentity)
// Run the flows
bobNode.registerServiceFlow(TxKeyFlow.Requester::class) { TxKeyFlow.Provider(it) }
val requesterFlow = aliceNode.services.startFlow(TxKeyFlow.Requester(bob, revocationEnabled))
// Get the results
val actual: Map<Party, TxKeyFlow.AnonymousIdentity> = requesterFlow.resultFuture.getOrThrow()
assertEquals(2, actual.size)
// Verify that the generated anonymous identities do not match the well known identities
val aliceAnonymousIdentity = actual[alice] ?: throw IllegalStateException()
val bobAnonymousIdentity = actual[bob] ?: throw IllegalStateException()
assertNotEquals<AbstractParty>(alice, aliceAnonymousIdentity.identity)
assertNotEquals<AbstractParty>(bob, bobAnonymousIdentity.identity)
}
}

View File

@ -1,40 +0,0 @@
package net.corda.core.flows
import net.corda.core.identity.Party
import net.corda.core.utilities.ALICE
import net.corda.core.utilities.BOB
import net.corda.core.utilities.DUMMY_NOTARY
import net.corda.testing.node.MockNetwork
import org.junit.Before
import org.junit.Test
import java.security.PublicKey
import kotlin.test.assertNotNull
class TxKeyFlowUtilitiesTests {
lateinit var net: MockNetwork
@Before
fun before() {
net = MockNetwork(false)
}
@Test
fun `issue key`() {
// We run this in parallel threads to help catch any race conditions that may exist.
net = MockNetwork(false, true)
// Set up values we'll need
val notaryNode = net.createNotaryNode(null, DUMMY_NOTARY.name)
val aliceNode = net.createPartyNode(notaryNode.info.address, ALICE.name)
val bobNode = net.createPartyNode(notaryNode.info.address, BOB.name)
val bobKey: Party = bobNode.services.myInfo.legalIdentity
// Run the flows
bobNode.registerServiceFlow(TxKeyFlow.Requester::class) { TxKeyFlow.Provider(it) }
val requesterFlow = aliceNode.services.startFlow(TxKeyFlow.Requester(bobKey))
// Get the results
val actual: PublicKey = requesterFlow.resultFuture.get().first
assertNotNull(actual)
}
}

View File

@ -58,7 +58,7 @@ class CashTests {
database = dataSourceAndDatabase.second
database.transaction {
services = object : MockServices() {
override val keyManagementService: MockKeyManagementService = MockKeyManagementService(MINI_CORP_KEY, MEGA_CORP_KEY, OUR_KEY)
override val keyManagementService: MockKeyManagementService = MockKeyManagementService(identityService, MINI_CORP_KEY, MEGA_CORP_KEY, OUR_KEY)
override val vaultService: VaultService = makeVaultService(dataSourceProps)
override fun recordTransactions(txs: Iterable<SignedTransaction>) {

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

View File

@ -171,8 +171,8 @@ class MockNetwork(private val networkSendManuallyPumped: Boolean = false,
override fun makeVaultService(dataSourceProperties: Properties): VaultService = NodeVaultService(services, dataSourceProperties)
override fun makeKeyManagementService(): KeyManagementService {
return E2ETestKeyManagementService(partyKeys + (overrideServices?.values ?: emptySet()))
override fun makeKeyManagementService(identityService: IdentityService): KeyManagementService {
return E2ETestKeyManagementService(identityService, partyKeys + (overrideServices?.values ?: emptySet()))
}
override fun startMessagingService(rpcOps: RPCOps) {
@ -370,6 +370,11 @@ class MockNetwork(private val networkSendManuallyPumped: Boolean = false,
repeat(numPartyNodes) {
nodes += createPartyNode(mapNode.info.address)
}
nodes.forEach { node ->
nodes.map { it.info.legalIdentity }.forEach { identity ->
node.services.identityService.registerIdentity(identity)
}
}
return BasketOfNodes(nodes, notaryNode, mapNode)
}

View File

@ -1,11 +1,8 @@
package net.corda.testing.node
import net.corda.core.contracts.Attachment
import net.corda.core.contracts.PartyAndReference
import net.corda.core.crypto.*
import net.corda.core.flows.StateMachineRunId
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.AnonymousParty
import net.corda.core.identity.Party
import net.corda.core.messaging.SingleMessageRecipient
import net.corda.core.node.NodeInfo
@ -15,6 +12,7 @@ import net.corda.core.serialization.SingletonSerializeAsToken
import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.DUMMY_NOTARY
import net.corda.node.services.identity.InMemoryIdentityService
import net.corda.node.services.keys.freshKeyAndCert
import net.corda.node.services.persistence.InMemoryStateMachineRecordedTransactionMappingStorage
import net.corda.node.services.schema.HibernateObserver
import net.corda.node.services.schema.NodeSchemaService
@ -23,7 +21,6 @@ import net.corda.node.services.vault.NodeVaultService
import net.corda.testing.MEGA_CORP
import net.corda.testing.MINI_CORP
import net.corda.testing.MOCK_VERSION_INFO
import org.bouncycastle.asn1.x500.X500Name
import rx.Observable
import rx.subjects.PublishSubject
import java.io.ByteArrayInputStream
@ -38,7 +35,6 @@ import java.security.cert.CertPath
import java.security.cert.X509Certificate
import java.time.Clock
import java.util.*
import java.util.concurrent.ConcurrentHashMap
import java.util.jar.JarInputStream
import javax.annotation.concurrent.ThreadSafe
@ -64,8 +60,8 @@ open class MockServices(vararg val keys: KeyPair) : ServiceHub {
}
override val storageService: TxWritableStorageService = MockStorageService()
override val identityService: IdentityService = InMemoryIdentityService(listOf(MEGA_CORP, MINI_CORP, DUMMY_NOTARY))
override val keyManagementService: KeyManagementService = MockKeyManagementService(*keys)
override final val identityService: IdentityService = InMemoryIdentityService(listOf(MEGA_CORP, MINI_CORP, DUMMY_NOTARY))
override val keyManagementService: KeyManagementService = MockKeyManagementService(identityService, *keys)
override val vaultService: VaultService get() = throw UnsupportedOperationException()
override val networkMapCache: NetworkMapCache get() = throw UnsupportedOperationException()
@ -81,7 +77,8 @@ open class MockServices(vararg val keys: KeyPair) : ServiceHub {
}
}
class MockKeyManagementService(vararg initialKeys: KeyPair) : SingletonSerializeAsToken(), KeyManagementService {
class MockKeyManagementService(val identityService: IdentityService,
vararg initialKeys: KeyPair) : SingletonSerializeAsToken(), KeyManagementService {
private val keyStore: MutableMap<PublicKey, PrivateKey> = initialKeys.associateByTo(HashMap(), { it.public }, { it.private })
override val keys: Set<PublicKey> get() = keyStore.keys
@ -94,6 +91,8 @@ class MockKeyManagementService(vararg initialKeys: KeyPair) : SingletonSerialize
return k.public
}
override fun freshKeyAndCert(identity: Party, revocationEnabled: Boolean): Pair<X509Certificate, CertPath> = freshKeyAndCert(this, identityService, identity, revocationEnabled)
private fun getSigningKeyPair(publicKey: PublicKey): KeyPair {
val pk = publicKey.keys.first { keyStore.containsKey(it) }
return KeyPair(pk, keyStore[pk]!!)

View File

@ -6,10 +6,12 @@ import com.google.common.util.concurrent.SettableFuture
import net.corda.core.crypto.commonName
import net.corda.core.crypto.generateKeyPair
import net.corda.core.messaging.RPCOps
import net.corda.core.node.services.IdentityService
import net.corda.core.node.services.KeyManagementService
import net.corda.node.services.RPCUserServiceImpl
import net.corda.node.services.api.MonitoringService
import net.corda.node.services.config.NodeConfiguration
import net.corda.node.services.identity.InMemoryIdentityService
import net.corda.node.services.keys.E2ETestKeyManagementService
import net.corda.node.services.messaging.ArtemisMessagingServer
import net.corda.node.services.messaging.NodeMessagingClient
@ -26,7 +28,7 @@ import kotlin.concurrent.thread
/**
* This is a bare-bones node which can only send and receive messages. It doesn't register with a network map service or
* any other such task that would make it functionable in a network and thus left to the user to do so manually.
* any other such task that would make it functional in a network and thus left to the user to do so manually.
*/
class SimpleNode(val config: NodeConfiguration, val address: HostAndPort = freeLocalHostAndPort(), rpcAddress: HostAndPort = freeLocalHostAndPort()) : AutoCloseable {
@ -35,7 +37,8 @@ class SimpleNode(val config: NodeConfiguration, val address: HostAndPort = freeL
val userService = RPCUserServiceImpl(config.rpcUsers)
val monitoringService = MonitoringService(MetricRegistry())
val identity: KeyPair = generateKeyPair()
val keyService: KeyManagementService = E2ETestKeyManagementService(setOf(identity))
val identityService: IdentityService = InMemoryIdentityService()
val keyService: KeyManagementService = E2ETestKeyManagementService(identityService, setOf(identity))
val executor = ServiceAffinityExecutor(config.myLegalName.commonName, 1)
val broker = ArtemisMessagingServer(config, address, rpcAddress, InMemoryNetworkMapCache(), userService)
val networkMapRegistrationFuture: SettableFuture<Unit> = SettableFuture.create<Unit>()