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:
Ross Nicoll 2017-07-19 14:03:34 +01:00 committed by GitHub
parent 298287fe28
commit c4c551dbd2
20 changed files with 209 additions and 96 deletions

View File

@ -1,10 +1,10 @@
package net.corda.core.flows
import co.paralleluniverse.fibers.Suspendable
import net.corda.core.identity.AnonymousPartyAndPath
import net.corda.core.identity.Party
import net.corda.core.utilities.ProgressTracker
import net.corda.core.utilities.unwrap
import net.corda.flows.AnonymisedIdentity
/**
* Very basic flow which exchanges transaction key and certificate paths between two parties in a transaction.
@ -14,31 +14,31 @@ import net.corda.flows.AnonymisedIdentity
@InitiatingFlow
class TransactionKeyFlow(val otherSide: Party,
val revocationEnabled: Boolean,
override val progressTracker: ProgressTracker) : FlowLogic<LinkedHashMap<Party, AnonymisedIdentity>>() {
override val progressTracker: ProgressTracker) : FlowLogic<LinkedHashMap<Party, AnonymousPartyAndPath>>() {
constructor(otherSide: Party) : this(otherSide, false, tracker())
companion object {
object AWAITING_KEY : ProgressTracker.Step("Awaiting key")
fun tracker() = ProgressTracker(AWAITING_KEY)
fun validateIdentity(otherSide: Party, anonymousOtherSide: AnonymisedIdentity): AnonymisedIdentity {
require(anonymousOtherSide.certificate.subject == otherSide.name)
fun validateIdentity(otherSide: Party, anonymousOtherSide: AnonymousPartyAndPath): AnonymousPartyAndPath {
require(anonymousOtherSide.name == otherSide.name)
return anonymousOtherSide
}
}
@Suspendable
override fun call(): LinkedHashMap<Party, AnonymisedIdentity> {
override fun call(): LinkedHashMap<Party, AnonymousPartyAndPath> {
progressTracker.currentStep = AWAITING_KEY
val legalIdentityAnonymous = serviceHub.keyManagementService.freshKeyAndCert(serviceHub.myInfo.legalIdentityAndCert, revocationEnabled)
// Special case that if we're both parties, a single identity is generated
val identities = LinkedHashMap<Party, AnonymisedIdentity>()
val identities = LinkedHashMap<Party, AnonymousPartyAndPath>()
if (otherSide == serviceHub.myInfo.legalIdentity) {
identities.put(otherSide, legalIdentityAnonymous)
} else {
val otherSideAnonymous = sendAndReceive<AnonymisedIdentity>(otherSide, legalIdentityAnonymous).unwrap { validateIdentity(otherSide, it) }
serviceHub.identityService.registerAnonymousIdentity(otherSideAnonymous.identity, otherSide, otherSideAnonymous.certPath)
val otherSideAnonymous = sendAndReceive<AnonymousPartyAndPath>(otherSide, legalIdentityAnonymous).unwrap { validateIdentity(otherSide, it) }
serviceHub.identityService.verifyAndRegisterAnonymousIdentity(otherSideAnonymous, otherSide)
identities.put(serviceHub.myInfo.legalIdentity, legalIdentityAnonymous)
identities.put(otherSide, otherSideAnonymous)
}

View File

@ -1,16 +0,0 @@
package net.corda.flows
import net.corda.core.identity.AnonymousParty
import net.corda.core.serialization.CordaSerializable
import org.bouncycastle.cert.X509CertificateHolder
import java.security.PublicKey
import java.security.cert.CertPath
@CordaSerializable
data class AnonymisedIdentity(
val certPath: CertPath,
val certificate: X509CertificateHolder,
val identity: AnonymousParty) {
constructor(certPath: CertPath, certificate: X509CertificateHolder, identity: PublicKey)
: this(certPath, certificate, AnonymousParty(identity))
}

View File

@ -2,6 +2,7 @@ package net.corda.core.identity
import net.corda.core.contracts.PartyAndReference
import net.corda.core.crypto.toBase58String
import net.corda.core.crypto.toStringShort
import net.corda.core.utilities.OpaqueBytes
import org.bouncycastle.asn1.x500.X500Name
import java.security.PublicKey
@ -13,7 +14,7 @@ import java.security.PublicKey
class AnonymousParty(owningKey: PublicKey) : AbstractParty(owningKey) {
// Use the key as the bulk of the toString(), but include a human readable identifier as well, so that [Party]
// can put in the key and actual name
override fun toString() = "${owningKey.toBase58String()} <Anonymous>"
override fun toString() = "${owningKey.toStringShort()} <Anonymous>"
override fun nameOrNull(): X500Name? = null

View File

@ -0,0 +1,45 @@
package net.corda.core.identity
import net.corda.core.crypto.subject
import net.corda.core.serialization.CordaSerializable
import org.bouncycastle.asn1.x500.X500Name
import java.security.PublicKey
import java.security.cert.CertPath
import java.security.cert.X509Certificate
/**
* A pair of an anonymous party and the certificate path to prove it is owned by a well known identity. This class
* does not validate the certificate path matches the party, and should not be trusted without being verified, for example
* using [IdentityService.verifyAnonymousIdentity].
*
* Although similar to [PartyAndCertificate], the two distinct types exist in order to minimise risk of mixing up
* confidential and well known identities. In contrast to [PartyAndCertificate] equality tests are based on the anonymous
* party's key rather than name, which is the appropriate test for confidential parties but not for well known identities.
*/
@CordaSerializable
data class AnonymousPartyAndPath(
val party: AnonymousParty,
val certPath: CertPath) {
constructor(party: PublicKey, certPath: CertPath)
: this(AnonymousParty(party), certPath)
init {
require(certPath.certificates.isNotEmpty()) { "Certificate path cannot be empty" }
}
/**
* Get the X.500 name of the certificate for the anonymous party.
*
* @return the X.500 name if the anonymous party's certificate is an X.509 certificate, or null otherwise.
*/
val name: X500Name?
get() = (certPath.certificates.first() as? X509Certificate)?.subject
override fun equals(other: Any?): Boolean {
return if (other is AnonymousPartyAndPath)
party == other.party
else
false
}
override fun hashCode(): Int = party.hashCode()
}

View File

@ -29,5 +29,12 @@ data class PartyAndCertificate(val party: Party,
}
override fun hashCode(): Int = party.hashCode()
/**
* Convert this party and certificate into an anomymised identity. This exists primarily for example cases which
* want to use well known identities as if they're anonymous identities.
*/
fun toAnonymisedIdentity(): AnonymousPartyAndPath {
return AnonymousPartyAndPath(party.owningKey, certPath)
}
override fun toString(): String = party.toString()
}

View File

@ -2,6 +2,7 @@ package net.corda.core.node.services
import net.corda.core.contracts.PartyAndReference
import net.corda.core.identity.*
import net.corda.core.identity.AnonymousPartyAndPath
import org.bouncycastle.asn1.x500.X500Name
import org.bouncycastle.cert.X509CertificateHolder
import java.security.InvalidAlgorithmParameterException
@ -29,15 +30,39 @@ interface IdentityService {
fun registerIdentity(party: PartyAndCertificate)
/**
* Verify and then store an identity.
* Verify and then store an anonymous identity.
*
* @param anonymousParty a party representing a legal entity in a transaction.
* @param path certificate path from the trusted root to the party.
* @param anonymousIdentity an anonymised identity representing a legal entity in a transaction.
* @param party well known party the anonymised party must represent.
* @throws IllegalArgumentException if the certificate path is invalid, or if there is already an existing
* certificate chain for the anonymous party.
*/
@Throws(CertificateExpiredException::class, CertificateNotYetValidException::class, InvalidAlgorithmParameterException::class)
fun registerAnonymousIdentity(anonymousParty: AnonymousParty, party: Party, path: CertPath)
@Deprecated("Use verifyAndRegisterAnonymousIdentity() instead, which is the same function with a better name")
fun registerAnonymousIdentity(anonymousIdentity: AnonymousPartyAndPath, party: Party): PartyAndCertificate
/**
* Verify and then store an anonymous identity.
*
* @param anonymousIdentity an anonymised identity representing a legal entity in a transaction.
* @param wellKnownIdentity well known party the anonymised party must represent.
* @throws IllegalArgumentException if the certificate path is invalid, or if there is already an existing
* certificate chain for the anonymous party.
*/
@Throws(CertificateExpiredException::class, CertificateNotYetValidException::class, InvalidAlgorithmParameterException::class)
fun verifyAndRegisterAnonymousIdentity(anonymousIdentity: AnonymousPartyAndPath, wellKnownIdentity: Party): PartyAndCertificate
/**
* Verify an anonymous identity.
*
* @param anonymousParty a party representing a legal entity in a transaction.
* @param party well known party the anonymised party must represent.
* @param path certificate path from the trusted root to the party.
* @return the full well known identity.
* @throws IllegalArgumentException if the certificate path is invalid.
*/
@Throws(CertificateExpiredException::class, CertificateNotYetValidException::class, InvalidAlgorithmParameterException::class)
fun verifyAnonymousIdentity(anonymousIdentity: AnonymousPartyAndPath, party: Party): PartyAndCertificate
/**
* Asserts that an anonymous party maps to the given full party, by looking up the certificate chain associated with
@ -54,6 +79,19 @@ interface IdentityService {
*/
fun getAllIdentities(): Iterable<PartyAndCertificate>
/**
* Get the certificate and path for a previously registered anonymous identity. These are used to prove an anonmyous
* identity is owned by a well known identity.
*/
fun anonymousFromKey(owningKey: PublicKey): AnonymousPartyAndPath?
/**
* Get the certificate and path for a well known identity's owning key.
*
* @return the party and certificate, or null if unknown.
*/
fun certificateFromKey(owningKey: PublicKey): PartyAndCertificate?
/**
* Get the certificate and path for a well known identity.
*
@ -68,6 +106,7 @@ interface IdentityService {
fun partyFromKey(key: PublicKey): Party?
@Deprecated("Use partyFromX500Name or partiesFromName")
fun partyFromName(name: String): Party?
fun partyFromX500Name(principal: X500Name): Party?
/**
@ -98,6 +137,7 @@ interface IdentityService {
/**
* Get the certificate chain showing an anonymous party is owned by the given party.
*/
@Deprecated("Use anonymousFromKey instead, which provides more detail and takes in a more relevant input", replaceWith = ReplaceWith("anonymousFromKey(anonymousParty.owningKey)"))
fun pathForAnonymous(anonymousParty: AnonymousParty): CertPath?
/**

View File

@ -2,8 +2,8 @@ package net.corda.core.node.services
import co.paralleluniverse.fibers.Suspendable
import net.corda.core.crypto.DigitalSignature
import net.corda.core.identity.AnonymousPartyAndPath
import net.corda.core.identity.PartyAndCertificate
import net.corda.flows.AnonymisedIdentity
import java.security.PublicKey
/**
@ -32,7 +32,7 @@ interface KeyManagementService {
* @return X.509 certificate and path to the trust root.
*/
@Suspendable
fun freshKeyAndCert(identity: PartyAndCertificate, revocationEnabled: Boolean): AnonymisedIdentity
fun freshKeyAndCert(identity: PartyAndCertificate, revocationEnabled: Boolean): AnonymousPartyAndPath
/**
* Filter some keys down to the set that this node owns (has private keys for).

View File

@ -2,8 +2,8 @@ package net.corda.core.flows
import net.corda.core.getOrThrow
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.AnonymousPartyAndPath
import net.corda.core.identity.Party
import net.corda.flows.AnonymisedIdentity
import net.corda.testing.ALICE
import net.corda.testing.BOB
import net.corda.testing.DUMMY_NOTARY
@ -49,22 +49,22 @@ class TransactionKeyFlowTests {
val requesterFlow = aliceNode.services.startFlow(TransactionKeyFlow(bob))
// Get the results
val actual: Map<Party, AnonymisedIdentity> = requesterFlow.resultFuture.getOrThrow().toMap()
val actual: Map<Party, AnonymousPartyAndPath> = requesterFlow.resultFuture.getOrThrow().toMap()
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)
assertNotEquals<AbstractParty>(alice, aliceAnonymousIdentity.party)
assertNotEquals<AbstractParty>(bob, bobAnonymousIdentity.party)
// Verify that the anonymous identities look sane
assertEquals(alice.name, aliceAnonymousIdentity.certificate.subject)
assertEquals(bob.name, bobAnonymousIdentity.certificate.subject)
assertEquals(alice.name, aliceAnonymousIdentity.name)
assertEquals(bob.name, bobAnonymousIdentity.name)
// Verify that the nodes have the right anonymous identities
assertTrue { aliceAnonymousIdentity.identity.owningKey in aliceNode.services.keyManagementService.keys }
assertTrue { bobAnonymousIdentity.identity.owningKey in bobNode.services.keyManagementService.keys }
assertFalse { aliceAnonymousIdentity.identity.owningKey in bobNode.services.keyManagementService.keys }
assertFalse { bobAnonymousIdentity.identity.owningKey in aliceNode.services.keyManagementService.keys }
assertTrue { aliceAnonymousIdentity.party.owningKey in aliceNode.services.keyManagementService.keys }
assertTrue { bobAnonymousIdentity.party.owningKey in bobNode.services.keyManagementService.keys }
assertFalse { aliceAnonymousIdentity.party.owningKey in bobNode.services.keyManagementService.keys }
assertFalse { bobAnonymousIdentity.party.owningKey in aliceNode.services.keyManagementService.keys }
}
}

View File

@ -8,8 +8,8 @@ import net.corda.core.contracts.TransactionType
import net.corda.core.contracts.issuedBy
import net.corda.core.flows.StartableByRPC
import net.corda.core.identity.Party
import net.corda.core.utilities.OpaqueBytes
import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.ProgressTracker
import java.util.*

View File

@ -8,6 +8,7 @@ import net.corda.core.contracts.issuedBy
import net.corda.core.flows.FinalityFlow
import net.corda.core.flows.StartableByRPC
import net.corda.core.flows.TransactionKeyFlow
import net.corda.core.identity.AnonymousPartyAndPath
import net.corda.core.identity.Party
import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.OpaqueBytes
@ -45,9 +46,9 @@ class CashIssueFlow(val amount: Amount<Currency>,
val txIdentities = if (anonymous) {
subFlow(TransactionKeyFlow(recipient))
} else {
emptyMap<Party, AnonymisedIdentity>()
emptyMap<Party, AnonymousPartyAndPath>()
}
val anonymousRecipient = txIdentities[recipient]?.identity ?: recipient
val anonymousRecipient = txIdentities[recipient]?.party ?: recipient
progressTracker.currentStep = GENERATING_TX
val builder: TransactionBuilder = TransactionType.General.Builder(notary = notary)
val issuer = serviceHub.myInfo.legalIdentity.ref(issueRef)

View File

@ -6,6 +6,7 @@ import net.corda.core.contracts.InsufficientBalanceException
import net.corda.core.contracts.TransactionType
import net.corda.core.flows.StartableByRPC
import net.corda.core.flows.TransactionKeyFlow
import net.corda.core.identity.AnonymousPartyAndPath
import net.corda.core.identity.Party
import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.ProgressTracker
@ -38,9 +39,9 @@ open class CashPaymentFlow(
val txIdentities = if (anonymous) {
subFlow(TransactionKeyFlow(recipient))
} else {
emptyMap<Party, AnonymisedIdentity>()
emptyMap<Party, AnonymousPartyAndPath>()
}
val anonymousRecipient = txIdentities.get(recipient)?.identity ?: recipient
val anonymousRecipient = txIdentities.get(recipient)?.party ?: recipient
progressTracker.currentStep = GENERATING_TX
val builder: TransactionBuilder = TransactionType.General.Builder(null as Party?)
// TODO: Have some way of restricting this to states the caller controls

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -3,6 +3,7 @@ package net.corda.testing.node
import net.corda.core.contracts.Attachment
import net.corda.core.crypto.*
import net.corda.core.flows.StateMachineRunId
import net.corda.core.identity.AnonymousPartyAndPath
import net.corda.core.identity.PartyAndCertificate
import net.corda.core.messaging.DataFeed
import net.corda.core.node.NodeInfo
@ -12,7 +13,6 @@ import net.corda.core.serialization.SerializeAsToken
import net.corda.core.serialization.SingletonSerializeAsToken
import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.NonEmptySet
import net.corda.flows.AnonymisedIdentity
import net.corda.node.VersionInfo
import net.corda.node.services.api.StateMachineRecordedTransactionMappingStorage
import net.corda.node.services.api.WritableTransactionStorage
@ -105,7 +105,7 @@ class MockKeyManagementService(val identityService: IdentityService,
override fun filterMyKeys(candidateKeys: Iterable<PublicKey>): Iterable<PublicKey> = candidateKeys.filter { it in this.keys }
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)
}