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

View File

@ -1,6 +1,8 @@
package net.corda.core.crypto package net.corda.core.crypto
import net.corda.core.crypto.Crypto.generateKeyPair 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.ASN1Encodable
import org.bouncycastle.asn1.x500.X500Name import org.bouncycastle.asn1.x500.X500Name
import org.bouncycastle.asn1.x500.X500NameBuilder import org.bouncycastle.asn1.x500.X500NameBuilder
@ -24,6 +26,7 @@ import java.time.temporal.ChronoUnit
import java.util.* import java.util.*
object X509Utilities { object X509Utilities {
val DEFAULT_IDENTITY_SIGNATURE_SCHEME = Crypto.EDDSA_ED25519_SHA512
val DEFAULT_TLS_SIGNATURE_SCHEME = Crypto.ECDSA_SECP256R1_SHA256 val DEFAULT_TLS_SIGNATURE_SCHEME = Crypto.ECDSA_SECP256R1_SHA256
// Aliases for private keys and certificates. // 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 package net.corda.core.node.services
import net.corda.core.contracts.PartyAndReference import net.corda.core.contracts.PartyAndReference
import net.corda.core.crypto.toStringShort
import net.corda.core.identity.AbstractParty import net.corda.core.identity.AbstractParty
import net.corda.core.identity.AnonymousParty import net.corda.core.identity.AnonymousParty
import net.corda.core.identity.Party import net.corda.core.identity.Party
@ -55,9 +56,31 @@ interface IdentityService {
fun partyFromName(name: String): Party? fun partyFromName(name: String): Party?
fun partyFromX500Name(principal: X500Name): 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? 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) 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. * 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 co.paralleluniverse.fibers.Suspendable
import com.google.common.util.concurrent.ListenableFuture import com.google.common.util.concurrent.ListenableFuture
import net.corda.core.contracts.* import net.corda.core.contracts.*
import net.corda.core.crypto.DigitalSignature import net.corda.core.crypto.*
import net.corda.core.crypto.SecureHash
import net.corda.core.flows.FlowException import net.corda.core.flows.FlowException
import net.corda.core.identity.AbstractParty import net.corda.core.identity.AbstractParty
import net.corda.core.identity.AnonymousParty
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.node.services.vault.PageSpecification import net.corda.core.node.services.vault.PageSpecification
import net.corda.core.node.services.vault.QueryCriteria import net.corda.core.node.services.vault.QueryCriteria
@ -20,6 +20,8 @@ import net.corda.core.transactions.WireTransaction
import rx.Observable import rx.Observable
import java.io.InputStream import java.io.InputStream
import java.security.PublicKey import java.security.PublicKey
import java.security.cert.CertPath
import java.security.cert.X509Certificate
import java.time.Instant import java.time.Instant
import java.util.* import java.util.*
@ -385,6 +387,17 @@ interface KeyManagementService {
@Suspendable @Suspendable
fun freshKey(): PublicKey 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. /** 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 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, * @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 = dataSourceAndDatabase.second
database.transaction { database.transaction {
services = object : MockServices() { 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 val vaultService: VaultService = makeVaultService(dataSourceProps)
override fun recordTransactions(txs: Iterable<SignedTransaction>) { 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 // 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 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. // 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) scheduler = NodeSchedulerService(services, database, unfinishedSchedules = busyNodeLatch)
val tokenizableServices = mutableListOf(storage, net, vault, keyManagement, identity, platformClock, scheduler) 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.") "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() { open protected fun makeNetworkMapService() {
inNodeNetworkMapService = PersistentNetworkMapService(services, configuration.minimumPlatformVersion) inNodeNetworkMapService = PersistentNetworkMapService(services, configuration.minimumPlatformVersion)

View File

@ -54,8 +54,17 @@ class InMemoryIdentityService(identities: Iterable<Party> = emptySet(),
@Deprecated("Use partyFromX500Name") @Deprecated("Use partyFromX500Name")
override fun partyFromName(name: String): Party? = principalToParties[X500Name(name)] override fun partyFromName(name: String): Party? = principalToParties[X500Name(name)]
override fun partyFromX500Name(principal: X500Name): Party? = principalToParties[principal] 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 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) @Throws(IdentityService.UnknownAnonymousPartyException::class)
override fun assertOwnership(party: Party, anonymousParty: AnonymousParty) { 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.generateKeyPair
import net.corda.core.crypto.keys import net.corda.core.crypto.keys
import net.corda.core.crypto.sign 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.node.services.KeyManagementService
import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.core.serialization.SingletonSerializeAsToken
import java.security.KeyPair import java.security.KeyPair
import java.security.PrivateKey import java.security.PrivateKey
import java.security.PublicKey import java.security.PublicKey
import java.security.cert.CertPath
import java.security.cert.X509Certificate
import java.util.* import java.util.*
import javax.annotation.concurrent.ThreadSafe import javax.annotation.concurrent.ThreadSafe
@ -25,7 +29,8 @@ import javax.annotation.concurrent.ThreadSafe
* etc. * etc.
*/ */
@ThreadSafe @ThreadSafe
class E2ETestKeyManagementService(initialKeys: Set<KeyPair>) : SingletonSerializeAsToken(), KeyManagementService { class E2ETestKeyManagementService(val identityService: IdentityService,
initialKeys: Set<KeyPair>) : SingletonSerializeAsToken(), KeyManagementService {
private class InnerState { private class InnerState {
val keys = HashMap<PublicKey, PrivateKey>() val keys = HashMap<PublicKey, PrivateKey>()
} }
@ -51,6 +56,8 @@ class E2ETestKeyManagementService(initialKeys: Set<KeyPair>) : SingletonSerializ
return keyPair.public return keyPair.public
} }
override fun freshKeyAndCert(identity: Party, revocationEnabled: Boolean): Pair<X509Certificate, CertPath> = freshKeyAndCert(this, identityService, identity, revocationEnabled)
private fun getSigningKeyPair(publicKey: PublicKey): KeyPair { private fun getSigningKeyPair(publicKey: PublicKey): KeyPair {
return mutex.locked { return mutex.locked {
val pk = publicKey.keys.first { keys.containsKey(it) } 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.generateKeyPair
import net.corda.core.crypto.keys import net.corda.core.crypto.keys
import net.corda.core.crypto.sign 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.node.services.KeyManagementService
import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.core.serialization.SingletonSerializeAsToken
import net.corda.node.utilities.* import net.corda.node.utilities.*
@ -13,6 +15,8 @@ import org.jetbrains.exposed.sql.statements.InsertStatement
import java.security.KeyPair import java.security.KeyPair
import java.security.PrivateKey import java.security.PrivateKey
import java.security.PublicKey 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. * 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. * 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") { private object Table : JDBCHashedTable("${NODE_DATABASE_PREFIX}our_key_pairs") {
val publicKey = publicKey("public_key") val publicKey = publicKey("public_key")
@ -62,6 +67,8 @@ class PersistentKeyManagementService(initialKeys: Set<KeyPair>) : SingletonSeria
return keyPair.public return keyPair.public
} }
override fun freshKeyAndCert(identity: Party, revocationEnabled: Boolean): Pair<X509Certificate, CertPath> = freshKeyAndCert(this, identityService, identity, revocationEnabled)
private fun getSigningKeyPair(publicKey: PublicKey): KeyPair { private fun getSigningKeyPair(publicKey: PublicKey): KeyPair {
return mutex.locked { return mutex.locked {
val pk = publicKey.keys.first { keys.containsKey(it) } 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) val serviceFlowInfo = serviceHub.getServiceFlowFactory(sessionInit.clientFlowClass)
if (serviceFlowInfo == null) { if (serviceFlowInfo == null) {
logger.warn("${sessionInit.clientFlowClass} has not been registered with a service flow: $sessionInit") 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 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.ALICE_KEY
import net.corda.core.utilities.DUMMY_NOTARY import net.corda.core.utilities.DUMMY_NOTARY
import net.corda.node.services.MockServiceHubInternal 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.persistence.DBCheckpointStorage
import net.corda.node.services.statemachine.FlowLogicRefFactoryImpl import net.corda.node.services.statemachine.FlowLogicRefFactoryImpl
import net.corda.node.services.statemachine.StateMachineManager import net.corda.node.services.statemachine.StateMachineManager
@ -75,9 +76,10 @@ class NodeSchedulerServiceTest : SingletonSerializeAsToken() {
val dataSourceAndDatabase = configureDatabase(dataSourceProps) val dataSourceAndDatabase = configureDatabase(dataSourceProps)
dataSource = dataSourceAndDatabase.first dataSource = dataSourceAndDatabase.first
database = dataSourceAndDatabase.second database = dataSourceAndDatabase.second
val identityService = InMemoryIdentityService()
val kms = MockKeyManagementService(identityService, ALICE_KEY)
database.transaction { database.transaction {
val kms = MockKeyManagementService(ALICE_KEY)
val nullIdentity = X500Name("cn=None") val nullIdentity = X500Name("cn=None")
val mockMessagingService = InMemoryMessagingNetwork(false).InMemoryMessaging( val mockMessagingService = InMemoryMessagingNetwork(false).InMemoryMessaging(
false, false,

View File

@ -115,4 +115,14 @@ class InMemoryIdentityServiceTests {
service.assertOwnership(bob, anonymousAlice) 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 makeVaultService(dataSourceProperties: Properties): VaultService = NodeVaultService(services, dataSourceProperties)
override fun makeKeyManagementService(): KeyManagementService { override fun makeKeyManagementService(identityService: IdentityService): KeyManagementService {
return E2ETestKeyManagementService(partyKeys + (overrideServices?.values ?: emptySet())) return E2ETestKeyManagementService(identityService, partyKeys + (overrideServices?.values ?: emptySet()))
} }
override fun startMessagingService(rpcOps: RPCOps) { override fun startMessagingService(rpcOps: RPCOps) {
@ -370,6 +370,11 @@ class MockNetwork(private val networkSendManuallyPumped: Boolean = false,
repeat(numPartyNodes) { repeat(numPartyNodes) {
nodes += createPartyNode(mapNode.info.address) 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) return BasketOfNodes(nodes, notaryNode, mapNode)
} }

View File

@ -1,11 +1,8 @@
package net.corda.testing.node package net.corda.testing.node
import net.corda.core.contracts.Attachment import net.corda.core.contracts.Attachment
import net.corda.core.contracts.PartyAndReference
import net.corda.core.crypto.* import net.corda.core.crypto.*
import net.corda.core.flows.StateMachineRunId 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.identity.Party
import net.corda.core.messaging.SingleMessageRecipient import net.corda.core.messaging.SingleMessageRecipient
import net.corda.core.node.NodeInfo 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.transactions.SignedTransaction
import net.corda.core.utilities.DUMMY_NOTARY import net.corda.core.utilities.DUMMY_NOTARY
import net.corda.node.services.identity.InMemoryIdentityService 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.persistence.InMemoryStateMachineRecordedTransactionMappingStorage
import net.corda.node.services.schema.HibernateObserver import net.corda.node.services.schema.HibernateObserver
import net.corda.node.services.schema.NodeSchemaService 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.MEGA_CORP
import net.corda.testing.MINI_CORP import net.corda.testing.MINI_CORP
import net.corda.testing.MOCK_VERSION_INFO import net.corda.testing.MOCK_VERSION_INFO
import org.bouncycastle.asn1.x500.X500Name
import rx.Observable import rx.Observable
import rx.subjects.PublishSubject import rx.subjects.PublishSubject
import java.io.ByteArrayInputStream import java.io.ByteArrayInputStream
@ -38,7 +35,6 @@ import java.security.cert.CertPath
import java.security.cert.X509Certificate import java.security.cert.X509Certificate
import java.time.Clock import java.time.Clock
import java.util.* import java.util.*
import java.util.concurrent.ConcurrentHashMap
import java.util.jar.JarInputStream import java.util.jar.JarInputStream
import javax.annotation.concurrent.ThreadSafe import javax.annotation.concurrent.ThreadSafe
@ -64,8 +60,8 @@ open class MockServices(vararg val keys: KeyPair) : ServiceHub {
} }
override val storageService: TxWritableStorageService = MockStorageService() override val storageService: TxWritableStorageService = MockStorageService()
override val identityService: IdentityService = InMemoryIdentityService(listOf(MEGA_CORP, MINI_CORP, DUMMY_NOTARY)) override final val identityService: IdentityService = InMemoryIdentityService(listOf(MEGA_CORP, MINI_CORP, DUMMY_NOTARY))
override val keyManagementService: KeyManagementService = MockKeyManagementService(*keys) override val keyManagementService: KeyManagementService = MockKeyManagementService(identityService, *keys)
override val vaultService: VaultService get() = throw UnsupportedOperationException() override val vaultService: VaultService get() = throw UnsupportedOperationException()
override val networkMapCache: NetworkMapCache 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 }) private val keyStore: MutableMap<PublicKey, PrivateKey> = initialKeys.associateByTo(HashMap(), { it.public }, { it.private })
override val keys: Set<PublicKey> get() = keyStore.keys override val keys: Set<PublicKey> get() = keyStore.keys
@ -94,6 +91,8 @@ class MockKeyManagementService(vararg initialKeys: KeyPair) : SingletonSerialize
return k.public return k.public
} }
override fun freshKeyAndCert(identity: Party, revocationEnabled: Boolean): Pair<X509Certificate, CertPath> = freshKeyAndCert(this, identityService, identity, revocationEnabled)
private fun getSigningKeyPair(publicKey: PublicKey): KeyPair { private fun getSigningKeyPair(publicKey: PublicKey): KeyPair {
val pk = publicKey.keys.first { keyStore.containsKey(it) } val pk = publicKey.keys.first { keyStore.containsKey(it) }
return KeyPair(pk, keyStore[pk]!!) 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.commonName
import net.corda.core.crypto.generateKeyPair import net.corda.core.crypto.generateKeyPair
import net.corda.core.messaging.RPCOps import net.corda.core.messaging.RPCOps
import net.corda.core.node.services.IdentityService
import net.corda.core.node.services.KeyManagementService import net.corda.core.node.services.KeyManagementService
import net.corda.node.services.RPCUserServiceImpl import net.corda.node.services.RPCUserServiceImpl
import net.corda.node.services.api.MonitoringService import net.corda.node.services.api.MonitoringService
import net.corda.node.services.config.NodeConfiguration 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.keys.E2ETestKeyManagementService
import net.corda.node.services.messaging.ArtemisMessagingServer import net.corda.node.services.messaging.ArtemisMessagingServer
import net.corda.node.services.messaging.NodeMessagingClient 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 * 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 { 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 userService = RPCUserServiceImpl(config.rpcUsers)
val monitoringService = MonitoringService(MetricRegistry()) val monitoringService = MonitoringService(MetricRegistry())
val identity: KeyPair = generateKeyPair() 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 executor = ServiceAffinityExecutor(config.myLegalName.commonName, 1)
val broker = ArtemisMessagingServer(config, address, rpcAddress, InMemoryNetworkMapCache(), userService) val broker = ArtemisMessagingServer(config, address, rpcAddress, InMemoryNetworkMapCache(), userService)
val networkMapRegistrationFuture: SettableFuture<Unit> = SettableFuture.create<Unit>() val networkMapRegistrationFuture: SettableFuture<Unit> = SettableFuture.create<Unit>()