mirror of
https://github.com/corda/corda.git
synced 2025-06-16 06:08:13 +00:00
Simplify identity registration API
* Merge identity registration of well known and confidential identities * Move verification logic into PartyAndCertificate from IdentityService * Add note about why PartyAndCertificate exists
This commit is contained in:
@ -1,8 +1,10 @@
|
||||
package net.corda.core.flows
|
||||
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import net.corda.core.identity.AnonymousPartyAndPath
|
||||
import net.corda.core.identity.AnonymousParty
|
||||
import net.corda.core.identity.PartyAndCertificate
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.node.services.IdentityService
|
||||
import net.corda.core.utilities.ProgressTracker
|
||||
import net.corda.core.utilities.unwrap
|
||||
|
||||
@ -14,33 +16,37 @@ import net.corda.core.utilities.unwrap
|
||||
@InitiatingFlow
|
||||
class TransactionKeyFlow(val otherSide: Party,
|
||||
val revocationEnabled: Boolean,
|
||||
override val progressTracker: ProgressTracker) : FlowLogic<LinkedHashMap<Party, AnonymousPartyAndPath>>() {
|
||||
override val progressTracker: ProgressTracker) : FlowLogic<LinkedHashMap<Party, AnonymousParty>>() {
|
||||
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: AnonymousPartyAndPath): AnonymousPartyAndPath {
|
||||
fun validateAndRegisterIdentity(identityService: IdentityService, otherSide: Party, anonymousOtherSide: PartyAndCertificate): PartyAndCertificate {
|
||||
require(anonymousOtherSide.name == otherSide.name)
|
||||
// Validate then store their identity so that we can prove the key in the transaction is owned by the
|
||||
// counterparty.
|
||||
identityService.verifyAndRegisterIdentity(anonymousOtherSide)
|
||||
return anonymousOtherSide
|
||||
}
|
||||
}
|
||||
|
||||
@Suspendable
|
||||
override fun call(): LinkedHashMap<Party, AnonymousPartyAndPath> {
|
||||
override fun call(): LinkedHashMap<Party, AnonymousParty> {
|
||||
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, AnonymousPartyAndPath>()
|
||||
val identities = LinkedHashMap<Party, AnonymousParty>()
|
||||
if (otherSide == serviceHub.myInfo.legalIdentity) {
|
||||
identities.put(otherSide, legalIdentityAnonymous)
|
||||
identities.put(otherSide, legalIdentityAnonymous.party.anonymise())
|
||||
} else {
|
||||
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)
|
||||
val anonymousOtherSide = sendAndReceive<PartyAndCertificate>(otherSide, legalIdentityAnonymous).unwrap { confidentialIdentity ->
|
||||
validateAndRegisterIdentity(serviceHub.identityService, otherSide, confidentialIdentity)
|
||||
}
|
||||
identities.put(serviceHub.myInfo.legalIdentity, legalIdentityAnonymous.party.anonymise())
|
||||
identities.put(otherSide, anonymousOtherSide.party.anonymise())
|
||||
}
|
||||
return identities
|
||||
}
|
||||
|
@ -1,45 +0,0 @@
|
||||
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()
|
||||
}
|
@ -30,5 +30,6 @@ class Party(val name: X500Name, owningKey: PublicKey) : AbstractParty(owningKey)
|
||||
override fun toString() = name.toString()
|
||||
override fun nameOrNull(): X500Name? = name
|
||||
|
||||
fun anonymise(): AnonymousParty = AnonymousParty(owningKey)
|
||||
override fun ref(bytes: OpaqueBytes): PartyAndReference = PartyAndReference(this, bytes)
|
||||
}
|
||||
|
@ -4,12 +4,14 @@ import net.corda.core.serialization.CordaSerializable
|
||||
import org.bouncycastle.asn1.x500.X500Name
|
||||
import org.bouncycastle.cert.X509CertificateHolder
|
||||
import java.security.PublicKey
|
||||
import java.security.cert.CertPath
|
||||
import java.security.cert.*
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* A full party plus the X.509 certificate and path linking the party back to a trust root. Equality of
|
||||
* [PartyAndCertificate] instances is based on the party only, as certificate and path are data associated with the party,
|
||||
* not part of the identifier themselves.
|
||||
* not part of the identifier themselves. While party and certificate can both be derived from the certificate path,
|
||||
* this class exists in order to ensure the implementation classes of certificates and party public keys are kept stable.
|
||||
*/
|
||||
@CordaSerializable
|
||||
data class PartyAndCertificate(val party: Party,
|
||||
@ -29,12 +31,19 @@ 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()
|
||||
|
||||
/**
|
||||
* Verify that the given certificate path is valid and leads to the owning key of the party.
|
||||
*/
|
||||
fun verify(trustAnchor: TrustAnchor): PKIXCertPathValidatorResult {
|
||||
require(certPath.certificates.first() is X509Certificate) { "Subject certificate must be an X.509 certificate" }
|
||||
require(Arrays.equals(party.owningKey.encoded, certificate.subjectPublicKeyInfo.encoded)) { "Certificate public key must match party owning key" }
|
||||
require(Arrays.equals(certPath.certificates.first().encoded, certificate.encoded)) { "Certificate path must link to certificate" }
|
||||
|
||||
val validatorParameters = PKIXParameters(setOf(trustAnchor))
|
||||
val validator = CertPathValidator.getInstance("PKIX")
|
||||
validatorParameters.isRevocationEnabled = false
|
||||
return validator.validate(certPath, validatorParameters) as PKIXCertPathValidatorResult
|
||||
}
|
||||
}
|
||||
|
@ -19,49 +19,23 @@ interface IdentityService {
|
||||
val caCertStore: CertStore
|
||||
|
||||
/**
|
||||
* Verify and then store a well known identity.
|
||||
* Verify and then store an identity.
|
||||
*
|
||||
* @param party a party representing a legal entity.
|
||||
* @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 registerIdentity(party: PartyAndCertificate)
|
||||
|
||||
/**
|
||||
* Verify and then store an anonymous identity.
|
||||
*
|
||||
* @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)
|
||||
@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.
|
||||
* @param party a party representing a legal entity and the certificate path linking them to the network trust root.
|
||||
* @throws IllegalArgumentException if the certificate path is invalid.
|
||||
*/
|
||||
@Throws(CertificateExpiredException::class, CertificateNotYetValidException::class, InvalidAlgorithmParameterException::class)
|
||||
fun verifyAnonymousIdentity(anonymousIdentity: AnonymousPartyAndPath, party: Party): PartyAndCertificate
|
||||
@Deprecated("Use verifyAndRegisterIdentity() instead, which is the same function with a better name")
|
||||
fun registerIdentity(party: PartyAndCertificate)
|
||||
|
||||
/**
|
||||
* Verify and then store an identity.
|
||||
*
|
||||
* @param identity a party representing a legal entity and the certificate path linking them to the network trust root.
|
||||
* @throws IllegalArgumentException if the certificate path is invalid.
|
||||
*/
|
||||
@Throws(CertificateExpiredException::class, CertificateNotYetValidException::class, InvalidAlgorithmParameterException::class)
|
||||
fun verifyAndRegisterIdentity(identity: PartyAndCertificate)
|
||||
|
||||
/**
|
||||
* Asserts that an anonymous party maps to the given full party, by looking up the certificate chain associated with
|
||||
@ -78,12 +52,6 @@ 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.
|
||||
*
|
||||
@ -133,12 +101,6 @@ interface IdentityService {
|
||||
*/
|
||||
fun requirePartyFromAnonymous(party: AbstractParty): Party
|
||||
|
||||
/**
|
||||
* 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?
|
||||
|
||||
/**
|
||||
* Returns a list of candidate matches for a given string, with optional fuzzy(ish) matching. Fuzzy matching may
|
||||
* get smarter with time e.g. to correct spelling errors, so you should not hard-code indexes into the results
|
||||
|
@ -4,7 +4,6 @@ import co.paralleluniverse.fibers.Suspendable
|
||||
import net.corda.core.crypto.DigitalSignature
|
||||
import net.corda.core.crypto.SignableData
|
||||
import net.corda.core.crypto.TransactionSignature
|
||||
import net.corda.core.identity.AnonymousPartyAndPath
|
||||
import net.corda.core.identity.PartyAndCertificate
|
||||
import java.security.PublicKey
|
||||
|
||||
@ -34,7 +33,7 @@ interface KeyManagementService {
|
||||
* @return X.509 certificate and path to the trust root.
|
||||
*/
|
||||
@Suspendable
|
||||
fun freshKeyAndCert(identity: PartyAndCertificate, revocationEnabled: Boolean): AnonymousPartyAndPath
|
||||
fun freshKeyAndCert(identity: PartyAndCertificate, revocationEnabled: Boolean): PartyAndCertificate
|
||||
|
||||
/**
|
||||
* Filter some keys down to the set that this node owns (has private keys for).
|
||||
|
@ -1,7 +1,7 @@
|
||||
package net.corda.core.flows
|
||||
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.identity.AnonymousPartyAndPath
|
||||
import net.corda.core.identity.AnonymousParty
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.testing.ALICE
|
||||
@ -26,32 +26,32 @@ class TransactionKeyFlowTests {
|
||||
val bobNode = mockNet.createPartyNode(notaryNode.network.myAddress, BOB.name)
|
||||
val alice: Party = aliceNode.services.myInfo.legalIdentity
|
||||
val bob: Party = bobNode.services.myInfo.legalIdentity
|
||||
aliceNode.services.identityService.registerIdentity(bobNode.info.legalIdentityAndCert)
|
||||
aliceNode.services.identityService.registerIdentity(notaryNode.info.legalIdentityAndCert)
|
||||
bobNode.services.identityService.registerIdentity(aliceNode.info.legalIdentityAndCert)
|
||||
bobNode.services.identityService.registerIdentity(notaryNode.info.legalIdentityAndCert)
|
||||
aliceNode.services.identityService.verifyAndRegisterIdentity(bobNode.info.legalIdentityAndCert)
|
||||
aliceNode.services.identityService.verifyAndRegisterIdentity(notaryNode.info.legalIdentityAndCert)
|
||||
bobNode.services.identityService.verifyAndRegisterIdentity(aliceNode.info.legalIdentityAndCert)
|
||||
bobNode.services.identityService.verifyAndRegisterIdentity(notaryNode.info.legalIdentityAndCert)
|
||||
|
||||
// Run the flows
|
||||
val requesterFlow = aliceNode.services.startFlow(TransactionKeyFlow(bob))
|
||||
|
||||
// Get the results
|
||||
val actual: Map<Party, AnonymousPartyAndPath> = requesterFlow.resultFuture.getOrThrow().toMap()
|
||||
val actual: Map<Party, AnonymousParty> = 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.party)
|
||||
assertNotEquals<AbstractParty>(bob, bobAnonymousIdentity.party)
|
||||
assertNotEquals<AbstractParty>(alice, aliceAnonymousIdentity)
|
||||
assertNotEquals<AbstractParty>(bob, bobAnonymousIdentity)
|
||||
|
||||
// Verify that the anonymous identities look sane
|
||||
assertEquals(alice.name, aliceAnonymousIdentity.name)
|
||||
assertEquals(bob.name, bobAnonymousIdentity.name)
|
||||
assertEquals(alice.name, aliceNode.services.identityService.partyFromAnonymous(aliceAnonymousIdentity)!!.name)
|
||||
assertEquals(bob.name, bobNode.services.identityService.partyFromAnonymous(bobAnonymousIdentity)!!.name)
|
||||
|
||||
// Verify that the nodes have the right anonymous identities
|
||||
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 }
|
||||
assertTrue { aliceAnonymousIdentity.owningKey in aliceNode.services.keyManagementService.keys }
|
||||
assertTrue { bobAnonymousIdentity.owningKey in bobNode.services.keyManagementService.keys }
|
||||
assertFalse { aliceAnonymousIdentity.owningKey in bobNode.services.keyManagementService.keys }
|
||||
assertFalse { bobAnonymousIdentity.owningKey in aliceNode.services.keyManagementService.keys }
|
||||
|
||||
mockNet.stopNodes()
|
||||
}
|
||||
|
Reference in New Issue
Block a user