mirror of
https://github.com/corda/corda.git
synced 2024-12-24 07:06:44 +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:
parent
851cccbf7e
commit
a8d4dccea4
@ -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)
|
||||
|
@ -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.
|
||||
|
95
core/src/main/kotlin/net/corda/core/flows/TxKeyFlow.kt
Normal file
95
core/src/main/kotlin/net/corda/core/flows/TxKeyFlow.kt
Normal 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))
|
||||
}
|
||||
}
|
@ -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.
|
||||
*/
|
||||
|
@ -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,
|
||||
|
@ -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?)
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
53
core/src/test/kotlin/net/corda/core/flows/TxKeyFlowTests.kt
Normal file
53
core/src/test/kotlin/net/corda/core/flows/TxKeyFlowTests.kt
Normal 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)
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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>) {
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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]!!)
|
||||
|
@ -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>()
|
||||
|
Loading…
Reference in New Issue
Block a user