mirror of
https://github.com/corda/corda.git
synced 2025-06-17 14:48:16 +00:00
Confidential identities API improvements
* Registering anonymous identities now takes in AnonymisedIdentity * AnonymousParty.toString() now uses toStringShort() to match other toString() functions * Add verifyAnonymousIdentity() function to verify without storing an identity * Replace pathForAnonymous() with anonymousFromKey() which matches actual use-cases better * Add unit test for fetching the anonymous identity from a key * Update verifyAnonymousIdentity() function signature to match registerAnonymousIdentity() * Rename AnonymisedIdentity to AnonymousPartyAndPath * Remove certificate from AnonymousPartyAndPath as it's not actually used. * Rename registerAnonymousIdentity() to verifyAndRegisterAnonymousIdentity()
This commit is contained in:
@ -7,11 +7,11 @@ import net.corda.core.contracts.UpgradedContract
|
||||
import net.corda.core.contracts.requireThat
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.flows.*
|
||||
import net.corda.core.identity.AnonymousPartyAndPath
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.utilities.ProgressTracker
|
||||
import net.corda.core.utilities.unwrap
|
||||
import net.corda.flows.*
|
||||
|
||||
/**
|
||||
* This class sets up network message handlers for requests from peers for data keyed by hash. It is a piece of simple
|
||||
@ -138,10 +138,9 @@ class TransactionKeyHandler(val otherSide: Party, val revocationEnabled: Boolean
|
||||
val revocationEnabled = false
|
||||
progressTracker.currentStep = SENDING_KEY
|
||||
val legalIdentityAnonymous = serviceHub.keyManagementService.freshKeyAndCert(serviceHub.myInfo.legalIdentityAndCert, revocationEnabled)
|
||||
val otherSideAnonymous = sendAndReceive<AnonymisedIdentity>(otherSide, legalIdentityAnonymous).unwrap { TransactionKeyFlow.validateIdentity(otherSide, it) }
|
||||
val (certPath, theirCert, txIdentity) = otherSideAnonymous
|
||||
val otherSideAnonymous = sendAndReceive<AnonymousPartyAndPath>(otherSide, legalIdentityAnonymous).unwrap { TransactionKeyFlow.validateIdentity(otherSide, it) }
|
||||
// Validate then store their identity so that we can prove the key in the transaction is owned by the
|
||||
// counterparty.
|
||||
serviceHub.identityService.registerAnonymousIdentity(txIdentity, otherSide, certPath)
|
||||
serviceHub.identityService.registerAnonymousIdentity(otherSideAnonymous, otherSide)
|
||||
}
|
||||
}
|
@ -5,10 +5,7 @@ import net.corda.core.crypto.Crypto
|
||||
import net.corda.core.crypto.cert
|
||||
import net.corda.core.crypto.subject
|
||||
import net.corda.core.crypto.toStringShort
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.identity.AnonymousParty
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.identity.PartyAndCertificate
|
||||
import net.corda.core.identity.*
|
||||
import net.corda.core.node.services.IdentityService
|
||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||
import net.corda.core.utilities.loggerFor
|
||||
@ -21,8 +18,6 @@ import java.security.cert.*
|
||||
import java.util.*
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import javax.annotation.concurrent.ThreadSafe
|
||||
import javax.security.auth.x500.X500Principal
|
||||
import kotlin.collections.ArrayList
|
||||
|
||||
/**
|
||||
* Simple identity service which caches parties and provides functionality for efficient lookup.
|
||||
@ -50,14 +45,16 @@ class InMemoryIdentityService(identities: Iterable<PartyAndCertificate> = emptyS
|
||||
private val trustAnchor: TrustAnchor = TrustAnchor(trustRoot, null)
|
||||
private val keyToParties = ConcurrentHashMap<PublicKey, PartyAndCertificate>()
|
||||
private val principalToParties = ConcurrentHashMap<X500Name, PartyAndCertificate>()
|
||||
private val partyToPath = ConcurrentHashMap<AbstractParty, CertPath>()
|
||||
private val partyToPath = ConcurrentHashMap<AbstractParty, Pair<CertPath, X509CertificateHolder>>()
|
||||
|
||||
init {
|
||||
val caCertificatesWithRoot: Set<X509Certificate> = caCertificates.toSet() + trustRoot
|
||||
caCertStore = CertStore.getInstance("Collection", CollectionCertStoreParameters(caCertificatesWithRoot))
|
||||
keyToParties.putAll(identities.associateBy { it.owningKey } )
|
||||
principalToParties.putAll(identities.associateBy { it.name })
|
||||
partyToPath.putAll(certPaths)
|
||||
certPaths.forEach { (party, path) ->
|
||||
partyToPath.put(party, Pair(path, X509CertificateHolder(path.certificates.first().encoded)))
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Check the certificate validation logic
|
||||
@ -70,15 +67,23 @@ class InMemoryIdentityService(identities: Iterable<PartyAndCertificate> = emptyS
|
||||
log.trace { "Registering identity $party" }
|
||||
require(Arrays.equals(party.certificate.subjectPublicKeyInfo.encoded, party.owningKey.encoded)) { "Party certificate must end with party's public key" }
|
||||
|
||||
partyToPath[party.party] = party.certPath
|
||||
partyToPath[party.party] = Pair(party.certPath, party.certificate)
|
||||
keyToParties[party.owningKey] = party
|
||||
principalToParties[party.name] = party
|
||||
}
|
||||
|
||||
override fun anonymousFromKey(owningKey: PublicKey): AnonymousPartyAndPath? {
|
||||
val anonymousParty = AnonymousParty(owningKey)
|
||||
val path = partyToPath[anonymousParty]
|
||||
return path?.let { it ->
|
||||
AnonymousPartyAndPath(anonymousParty, it.first)
|
||||
}
|
||||
}
|
||||
override fun certificateFromKey(owningKey: PublicKey): PartyAndCertificate? = keyToParties[owningKey]
|
||||
override fun certificateFromParty(party: Party): PartyAndCertificate? = principalToParties[party.name]
|
||||
|
||||
// We give the caller a copy of the data set to avoid any locking problems
|
||||
override fun getAllIdentities(): Iterable<PartyAndCertificate> = ArrayList(keyToParties.values)
|
||||
override fun getAllIdentities(): Iterable<PartyAndCertificate> = java.util.ArrayList(keyToParties.values)
|
||||
|
||||
override fun partyFromKey(key: PublicKey): Party? = keyToParties[key]?.party
|
||||
@Deprecated("Use partyFromX500Name")
|
||||
@ -115,7 +120,7 @@ class InMemoryIdentityService(identities: Iterable<PartyAndCertificate> = emptyS
|
||||
|
||||
@Throws(IdentityService.UnknownAnonymousPartyException::class)
|
||||
override fun assertOwnership(party: Party, anonymousParty: AnonymousParty) {
|
||||
val path = partyToPath[anonymousParty] ?: throw IdentityService.UnknownAnonymousPartyException("Unknown anonymous party ${anonymousParty.owningKey.toStringShort()}")
|
||||
val path = partyToPath[anonymousParty]?.first ?: throw IdentityService.UnknownAnonymousPartyException("Unknown anonymous party ${anonymousParty.owningKey.toStringShort()}")
|
||||
require(path.certificates.size > 1) { "Certificate path must contain at least two certificates" }
|
||||
val actual = path.certificates[1]
|
||||
require(actual is X509Certificate && actual.publicKey == party.owningKey) { "Next certificate in the path must match the party key ${party.owningKey.toStringShort()}." }
|
||||
@ -123,22 +128,30 @@ class InMemoryIdentityService(identities: Iterable<PartyAndCertificate> = emptyS
|
||||
require(target is X509Certificate && target.publicKey == anonymousParty.owningKey) { "Certificate path starts with a certificate for the anonymous party" }
|
||||
}
|
||||
|
||||
override fun pathForAnonymous(anonymousParty: AnonymousParty): CertPath? = partyToPath[anonymousParty]
|
||||
override fun pathForAnonymous(anonymousParty: AnonymousParty): CertPath? = partyToPath[anonymousParty]?.first
|
||||
|
||||
override fun registerAnonymousIdentity(anonymousIdentity: AnonymousPartyAndPath, party: Party): PartyAndCertificate = verifyAndRegisterAnonymousIdentity(anonymousIdentity, party)
|
||||
|
||||
@Throws(CertificateExpiredException::class, CertificateNotYetValidException::class, InvalidAlgorithmParameterException::class)
|
||||
override fun registerAnonymousIdentity(anonymousParty: AnonymousParty, party: Party, path: CertPath) {
|
||||
override fun verifyAndRegisterAnonymousIdentity(anonymousIdentity: AnonymousPartyAndPath, wellKnownIdentity: Party): PartyAndCertificate {
|
||||
val fullParty = verifyAnonymousIdentity(anonymousIdentity, wellKnownIdentity)
|
||||
val certificate = X509CertificateHolder(anonymousIdentity.certPath.certificates.first().encoded)
|
||||
log.trace { "Registering identity $fullParty" }
|
||||
|
||||
partyToPath[anonymousIdentity.party] = Pair(anonymousIdentity.certPath, certificate)
|
||||
keyToParties[anonymousIdentity.party.owningKey] = fullParty
|
||||
return fullParty
|
||||
}
|
||||
|
||||
override fun verifyAnonymousIdentity(anonymousIdentity: AnonymousPartyAndPath, party: Party): PartyAndCertificate {
|
||||
val (anonymousParty, path) = anonymousIdentity
|
||||
val fullParty = certificateFromParty(party) ?: throw IllegalArgumentException("Unknown identity ${party.name}")
|
||||
require(path.certificates.isNotEmpty()) { "Certificate path must contain at least one certificate" }
|
||||
// Validate the chain first, before we do anything clever with it
|
||||
validateCertificatePath(anonymousParty, path)
|
||||
val subjectCertificate = path.certificates.first()
|
||||
require(subjectCertificate is X509Certificate && subjectCertificate.subject == fullParty.name) { "Subject of the transaction certificate must match the well known identity" }
|
||||
|
||||
log.trace { "Registering identity $fullParty" }
|
||||
|
||||
partyToPath[anonymousParty] = path
|
||||
keyToParties[anonymousParty.owningKey] = fullParty
|
||||
principalToParties[fullParty.name] = fullParty
|
||||
return fullParty
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -5,11 +5,11 @@ 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.AnonymousPartyAndPath
|
||||
import net.corda.core.identity.PartyAndCertificate
|
||||
import net.corda.core.node.services.IdentityService
|
||||
import net.corda.core.node.services.KeyManagementService
|
||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||
import net.corda.flows.AnonymisedIdentity
|
||||
import org.bouncycastle.operator.ContentSigner
|
||||
import java.security.KeyPair
|
||||
import java.security.PrivateKey
|
||||
@ -56,7 +56,7 @@ class E2ETestKeyManagementService(val identityService: IdentityService,
|
||||
return keyPair.public
|
||||
}
|
||||
|
||||
override fun freshKeyAndCert(identity: PartyAndCertificate, revocationEnabled: Boolean): AnonymisedIdentity {
|
||||
override fun freshKeyAndCert(identity: PartyAndCertificate, revocationEnabled: Boolean): AnonymousPartyAndPath {
|
||||
return freshCertificate(identityService, freshKey(), identity, getSigner(identity.owningKey), revocationEnabled)
|
||||
}
|
||||
|
||||
|
@ -1,11 +1,10 @@
|
||||
package net.corda.node.services.keys
|
||||
|
||||
import net.corda.core.crypto.*
|
||||
import net.corda.core.identity.AnonymousParty
|
||||
import net.corda.core.identity.AnonymousPartyAndPath
|
||||
import net.corda.core.identity.PartyAndCertificate
|
||||
import net.corda.core.node.services.IdentityService
|
||||
import net.corda.core.utilities.days
|
||||
import net.corda.flows.AnonymisedIdentity
|
||||
import org.bouncycastle.operator.ContentSigner
|
||||
import java.security.KeyPair
|
||||
import java.security.PublicKey
|
||||
@ -29,16 +28,16 @@ fun freshCertificate(identityService: IdentityService,
|
||||
subjectPublicKey: PublicKey,
|
||||
issuer: PartyAndCertificate,
|
||||
issuerSigner: ContentSigner,
|
||||
revocationEnabled: Boolean = false): AnonymisedIdentity {
|
||||
revocationEnabled: Boolean = false): AnonymousPartyAndPath {
|
||||
val issuerCertificate = issuer.certificate
|
||||
val window = X509Utilities.getCertificateValidityWindow(Duration.ZERO, 3650.days, issuerCertificate)
|
||||
val ourCertificate = Crypto.createCertificate(CertificateType.IDENTITY, issuerCertificate.subject, issuerSigner, issuer.name, subjectPublicKey, window)
|
||||
val certFactory = CertificateFactory.getInstance("X509")
|
||||
val ourCertPath = certFactory.generateCertPath(listOf(ourCertificate.cert) + issuer.certPath.certificates)
|
||||
identityService.registerAnonymousIdentity(AnonymousParty(subjectPublicKey),
|
||||
issuer.party,
|
||||
ourCertPath)
|
||||
return AnonymisedIdentity(ourCertPath, issuerCertificate, subjectPublicKey)
|
||||
val anonymisedIdentity = AnonymousPartyAndPath(subjectPublicKey, ourCertPath)
|
||||
identityService.verifyAndRegisterAnonymousIdentity(anonymisedIdentity,
|
||||
issuer.party)
|
||||
return anonymisedIdentity
|
||||
}
|
||||
|
||||
fun getSigner(issuerKeyPair: KeyPair): ContentSigner {
|
||||
|
@ -5,11 +5,11 @@ 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.AnonymousPartyAndPath
|
||||
import net.corda.core.identity.PartyAndCertificate
|
||||
import net.corda.core.node.services.IdentityService
|
||||
import net.corda.core.node.services.KeyManagementService
|
||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||
import net.corda.flows.AnonymisedIdentity
|
||||
import net.corda.node.utilities.*
|
||||
import org.bouncycastle.operator.ContentSigner
|
||||
import org.jetbrains.exposed.sql.ResultRow
|
||||
@ -71,7 +71,7 @@ class PersistentKeyManagementService(val identityService: IdentityService,
|
||||
return keyPair.public
|
||||
}
|
||||
|
||||
override fun freshKeyAndCert(identity: PartyAndCertificate, revocationEnabled: Boolean): AnonymisedIdentity {
|
||||
override fun freshKeyAndCert(identity: PartyAndCertificate, revocationEnabled: Boolean): AnonymousPartyAndPath {
|
||||
return freshCertificate(identityService, freshKey(), identity, getSigner(identity.owningKey), revocationEnabled)
|
||||
}
|
||||
|
||||
|
@ -503,7 +503,7 @@ class TwoPartyTradeFlowTests {
|
||||
}
|
||||
val buyerFlows: Observable<BuyerAcceptor> = buyerNode.registerInitiatedFlow(BuyerAcceptor::class.java)
|
||||
val firstBuyerFiber = buyerFlows.toFuture().map { it.stateMachine }
|
||||
val seller = SellerInitiator(buyerNode.info.legalIdentity, notaryNode.info, assetToSell, 1000.DOLLARS, anonymousSeller.identity)
|
||||
val seller = SellerInitiator(buyerNode.info.legalIdentity, notaryNode.info, assetToSell, 1000.DOLLARS, anonymousSeller.party)
|
||||
val sellerResult = sellerNode.services.startFlow(seller).resultFuture
|
||||
return RunResult(firstBuyerFiber, sellerResult, seller.stateMachine.id)
|
||||
}
|
||||
|
@ -1,11 +1,11 @@
|
||||
package net.corda.node.services.network
|
||||
|
||||
import net.corda.core.crypto.*
|
||||
import net.corda.core.identity.AnonymousPartyAndPath
|
||||
import net.corda.core.identity.AnonymousParty
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.identity.PartyAndCertificate
|
||||
import net.corda.core.node.services.IdentityService
|
||||
import net.corda.flows.AnonymisedIdentity
|
||||
import net.corda.node.services.identity.InMemoryIdentityService
|
||||
import net.corda.testing.*
|
||||
import org.bouncycastle.asn1.x500.X500Name
|
||||
@ -93,6 +93,29 @@ class InMemoryIdentityServiceTests {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a pair of certificate paths from a root CA, down to a transaction key, store and verify the associations.
|
||||
* Also checks that incorrect associations are rejected.
|
||||
*/
|
||||
@Test
|
||||
fun `get anonymous identity by key`() {
|
||||
val trustRoot = DUMMY_CA
|
||||
val (alice, aliceTxIdentity) = createParty(ALICE.name, trustRoot)
|
||||
val (bob, bobTxIdentity) = createParty(ALICE.name, trustRoot)
|
||||
|
||||
// Now we have identities, construct the service and let it know about both
|
||||
val service = InMemoryIdentityService(setOf(alice), emptyMap(), trustRoot.certificate.cert)
|
||||
service.verifyAndRegisterAnonymousIdentity(aliceTxIdentity, alice.party)
|
||||
|
||||
var actual = service.anonymousFromKey(aliceTxIdentity.party.owningKey)
|
||||
assertEquals<AnonymousPartyAndPath>(aliceTxIdentity, actual!!)
|
||||
|
||||
assertNull(service.anonymousFromKey(bobTxIdentity.party.owningKey))
|
||||
service.verifyAndRegisterAnonymousIdentity(bobTxIdentity, bob.party)
|
||||
actual = service.anonymousFromKey(bobTxIdentity.party.owningKey)
|
||||
assertEquals<AnonymousPartyAndPath>(bobTxIdentity, actual!!)
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a pair of certificate paths from a root CA, down to a transaction key, store and verify the associations.
|
||||
* Also checks that incorrect associations are rejected.
|
||||
@ -113,35 +136,35 @@ class InMemoryIdentityServiceTests {
|
||||
|
||||
// Now we have identities, construct the service and let it know about both
|
||||
val service = InMemoryIdentityService(setOf(alice, bob), emptyMap(), trustRoot.certificate.cert)
|
||||
service.registerAnonymousIdentity(aliceTxIdentity.identity, alice.party, aliceTxIdentity.certPath)
|
||||
service.verifyAndRegisterAnonymousIdentity(aliceTxIdentity, alice.party)
|
||||
|
||||
val anonymousBob = AnonymousParty(bobTxKey.public)
|
||||
service.registerAnonymousIdentity(anonymousBob, bob.party, bobCertPath)
|
||||
val anonymousBob = AnonymousPartyAndPath(AnonymousParty(bobTxKey.public),bobCertPath)
|
||||
service.verifyAndRegisterAnonymousIdentity(anonymousBob, bob.party)
|
||||
|
||||
// Verify that paths are verified
|
||||
service.assertOwnership(alice.party, aliceTxIdentity.identity)
|
||||
service.assertOwnership(bob.party, anonymousBob)
|
||||
service.assertOwnership(alice.party, aliceTxIdentity.party)
|
||||
service.assertOwnership(bob.party, anonymousBob.party)
|
||||
assertFailsWith<IllegalArgumentException> {
|
||||
service.assertOwnership(alice.party, anonymousBob)
|
||||
service.assertOwnership(alice.party, anonymousBob.party)
|
||||
}
|
||||
assertFailsWith<IllegalArgumentException> {
|
||||
service.assertOwnership(bob.party, aliceTxIdentity.identity)
|
||||
service.assertOwnership(bob.party, aliceTxIdentity.party)
|
||||
}
|
||||
|
||||
assertFailsWith<IllegalArgumentException> {
|
||||
val owningKey = Crypto.decodePublicKey(trustRoot.certificate.subjectPublicKeyInfo.encoded)
|
||||
service.assertOwnership(Party(trustRoot.certificate.subject, owningKey), aliceTxIdentity.identity)
|
||||
service.assertOwnership(Party(trustRoot.certificate.subject, owningKey), aliceTxIdentity.party)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createParty(x500Name: X500Name, ca: CertificateAndKeyPair): Pair<PartyAndCertificate, AnonymisedIdentity> {
|
||||
private fun createParty(x500Name: X500Name, ca: CertificateAndKeyPair): Pair<PartyAndCertificate, AnonymousPartyAndPath> {
|
||||
val certFactory = CertificateFactory.getInstance("X509")
|
||||
val issuerKeyPair = generateKeyPair()
|
||||
val issuer = getTestPartyAndCertificate(x500Name, issuerKeyPair.public, ca)
|
||||
val txKey = Crypto.generateKeyPair()
|
||||
val txCert = X509Utilities.createCertificate(CertificateType.IDENTITY, issuer.certificate, issuerKeyPair, x500Name, txKey.public)
|
||||
val txCertPath = certFactory.generateCertPath(listOf(txCert.cert) + issuer.certPath.certificates)
|
||||
return Pair(issuer, AnonymisedIdentity(txCertPath, txCert, AnonymousParty(txKey.public)))
|
||||
return Pair(issuer, AnonymousPartyAndPath(AnonymousParty(txKey.public), txCertPath))
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -431,7 +431,7 @@ class NodeVaultServiceTest {
|
||||
assertTrue { service.isRelevant(wellKnownCash, services.keyManagementService.keys) }
|
||||
|
||||
val anonymousIdentity = services.keyManagementService.freshKeyAndCert(services.myInfo.legalIdentityAndCert, false)
|
||||
val anonymousCash = Cash.State(amount, anonymousIdentity.identity)
|
||||
val anonymousCash = Cash.State(amount, anonymousIdentity.party)
|
||||
assertTrue { service.isRelevant(anonymousCash, services.keyManagementService.keys) }
|
||||
|
||||
val thirdPartyIdentity = AnonymousParty(generateKeyPair().public)
|
||||
@ -455,7 +455,7 @@ class NodeVaultServiceTest {
|
||||
// Issue then move some cash
|
||||
val issueTx = TransactionBuilder(TransactionType.General, services.myInfo.legalIdentity).apply {
|
||||
Cash().generateIssue(this,
|
||||
amount, anonymousIdentity.identity, services.myInfo.legalIdentity)
|
||||
amount, anonymousIdentity.party, services.myInfo.legalIdentity)
|
||||
}.toWireTransaction()
|
||||
val cashState = StateAndRef(issueTx.outputs.single(), StateRef(issueTx.id, 0))
|
||||
|
||||
|
Reference in New Issue
Block a user