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:
Ross Nicoll
2017-08-15 00:17:21 +01:00
committed by GitHub
parent 532deffca0
commit 62576b12b3
22 changed files with 158 additions and 261 deletions

View File

@ -1,8 +1,10 @@
package net.corda.core.flows package net.corda.core.flows
import co.paralleluniverse.fibers.Suspendable 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.identity.Party
import net.corda.core.node.services.IdentityService
import net.corda.core.utilities.ProgressTracker import net.corda.core.utilities.ProgressTracker
import net.corda.core.utilities.unwrap import net.corda.core.utilities.unwrap
@ -14,33 +16,37 @@ import net.corda.core.utilities.unwrap
@InitiatingFlow @InitiatingFlow
class TransactionKeyFlow(val otherSide: Party, class TransactionKeyFlow(val otherSide: Party,
val revocationEnabled: Boolean, 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()) constructor(otherSide: Party) : this(otherSide, false, tracker())
companion object { companion object {
object AWAITING_KEY : ProgressTracker.Step("Awaiting key") object AWAITING_KEY : ProgressTracker.Step("Awaiting key")
fun tracker() = ProgressTracker(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) 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 return anonymousOtherSide
} }
} }
@Suspendable @Suspendable
override fun call(): LinkedHashMap<Party, AnonymousPartyAndPath> { override fun call(): LinkedHashMap<Party, AnonymousParty> {
progressTracker.currentStep = AWAITING_KEY progressTracker.currentStep = AWAITING_KEY
val legalIdentityAnonymous = serviceHub.keyManagementService.freshKeyAndCert(serviceHub.myInfo.legalIdentityAndCert, revocationEnabled) val legalIdentityAnonymous = serviceHub.keyManagementService.freshKeyAndCert(serviceHub.myInfo.legalIdentityAndCert, revocationEnabled)
// Special case that if we're both parties, a single identity is generated // 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) { if (otherSide == serviceHub.myInfo.legalIdentity) {
identities.put(otherSide, legalIdentityAnonymous) identities.put(otherSide, legalIdentityAnonymous.party.anonymise())
} else { } else {
val otherSideAnonymous = sendAndReceive<AnonymousPartyAndPath>(otherSide, legalIdentityAnonymous).unwrap { validateIdentity(otherSide, it) } val anonymousOtherSide = sendAndReceive<PartyAndCertificate>(otherSide, legalIdentityAnonymous).unwrap { confidentialIdentity ->
serviceHub.identityService.verifyAndRegisterAnonymousIdentity(otherSideAnonymous, otherSide) validateAndRegisterIdentity(serviceHub.identityService, otherSide, confidentialIdentity)
identities.put(serviceHub.myInfo.legalIdentity, legalIdentityAnonymous) }
identities.put(otherSide, otherSideAnonymous) identities.put(serviceHub.myInfo.legalIdentity, legalIdentityAnonymous.party.anonymise())
identities.put(otherSide, anonymousOtherSide.party.anonymise())
} }
return identities return identities
} }

View File

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

View File

@ -30,5 +30,6 @@ class Party(val name: X500Name, owningKey: PublicKey) : AbstractParty(owningKey)
override fun toString() = name.toString() override fun toString() = name.toString()
override fun nameOrNull(): X500Name? = name override fun nameOrNull(): X500Name? = name
fun anonymise(): AnonymousParty = AnonymousParty(owningKey)
override fun ref(bytes: OpaqueBytes): PartyAndReference = PartyAndReference(this, bytes) override fun ref(bytes: OpaqueBytes): PartyAndReference = PartyAndReference(this, bytes)
} }

View File

@ -4,12 +4,14 @@ import net.corda.core.serialization.CordaSerializable
import org.bouncycastle.asn1.x500.X500Name import org.bouncycastle.asn1.x500.X500Name
import org.bouncycastle.cert.X509CertificateHolder import org.bouncycastle.cert.X509CertificateHolder
import java.security.PublicKey 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 * 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, * [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 @CordaSerializable
data class PartyAndCertificate(val party: Party, data class PartyAndCertificate(val party: Party,
@ -29,12 +31,19 @@ data class PartyAndCertificate(val party: Party,
} }
override fun hashCode(): Int = party.hashCode() 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() 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
}
} }

View File

@ -19,49 +19,23 @@ interface IdentityService {
val caCertStore: CertStore 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. * @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, 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.
* @throws IllegalArgumentException if the certificate path is invalid. * @throws IllegalArgumentException if the certificate path is invalid.
*/ */
@Throws(CertificateExpiredException::class, CertificateNotYetValidException::class, InvalidAlgorithmParameterException::class) @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 * 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> 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. * Get the certificate and path for a well known identity's owning key.
* *
@ -133,12 +101,6 @@ interface IdentityService {
*/ */
fun requirePartyFromAnonymous(party: AbstractParty): Party 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 * 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 * get smarter with time e.g. to correct spelling errors, so you should not hard-code indexes into the results

View File

@ -4,7 +4,6 @@ import co.paralleluniverse.fibers.Suspendable
import net.corda.core.crypto.DigitalSignature import net.corda.core.crypto.DigitalSignature
import net.corda.core.crypto.SignableData import net.corda.core.crypto.SignableData
import net.corda.core.crypto.TransactionSignature import net.corda.core.crypto.TransactionSignature
import net.corda.core.identity.AnonymousPartyAndPath
import net.corda.core.identity.PartyAndCertificate import net.corda.core.identity.PartyAndCertificate
import java.security.PublicKey import java.security.PublicKey
@ -34,7 +33,7 @@ interface KeyManagementService {
* @return X.509 certificate and path to the trust root. * @return X.509 certificate and path to the trust root.
*/ */
@Suspendable @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). * Filter some keys down to the set that this node owns (has private keys for).

View File

@ -1,7 +1,7 @@
package net.corda.core.flows package net.corda.core.flows
import net.corda.core.identity.AbstractParty 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.identity.Party
import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.getOrThrow
import net.corda.testing.ALICE import net.corda.testing.ALICE
@ -26,32 +26,32 @@ class TransactionKeyFlowTests {
val bobNode = mockNet.createPartyNode(notaryNode.network.myAddress, BOB.name) val bobNode = mockNet.createPartyNode(notaryNode.network.myAddress, BOB.name)
val alice: Party = aliceNode.services.myInfo.legalIdentity val alice: Party = aliceNode.services.myInfo.legalIdentity
val bob: Party = bobNode.services.myInfo.legalIdentity val bob: Party = bobNode.services.myInfo.legalIdentity
aliceNode.services.identityService.registerIdentity(bobNode.info.legalIdentityAndCert) aliceNode.services.identityService.verifyAndRegisterIdentity(bobNode.info.legalIdentityAndCert)
aliceNode.services.identityService.registerIdentity(notaryNode.info.legalIdentityAndCert) aliceNode.services.identityService.verifyAndRegisterIdentity(notaryNode.info.legalIdentityAndCert)
bobNode.services.identityService.registerIdentity(aliceNode.info.legalIdentityAndCert) bobNode.services.identityService.verifyAndRegisterIdentity(aliceNode.info.legalIdentityAndCert)
bobNode.services.identityService.registerIdentity(notaryNode.info.legalIdentityAndCert) bobNode.services.identityService.verifyAndRegisterIdentity(notaryNode.info.legalIdentityAndCert)
// Run the flows // Run the flows
val requesterFlow = aliceNode.services.startFlow(TransactionKeyFlow(bob)) val requesterFlow = aliceNode.services.startFlow(TransactionKeyFlow(bob))
// Get the results // 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) assertEquals(2, actual.size)
// Verify that the generated anonymous identities do not match the well known identities // Verify that the generated anonymous identities do not match the well known identities
val aliceAnonymousIdentity = actual[alice] ?: throw IllegalStateException() val aliceAnonymousIdentity = actual[alice] ?: throw IllegalStateException()
val bobAnonymousIdentity = actual[bob] ?: throw IllegalStateException() val bobAnonymousIdentity = actual[bob] ?: throw IllegalStateException()
assertNotEquals<AbstractParty>(alice, aliceAnonymousIdentity.party) assertNotEquals<AbstractParty>(alice, aliceAnonymousIdentity)
assertNotEquals<AbstractParty>(bob, bobAnonymousIdentity.party) assertNotEquals<AbstractParty>(bob, bobAnonymousIdentity)
// Verify that the anonymous identities look sane // Verify that the anonymous identities look sane
assertEquals(alice.name, aliceAnonymousIdentity.name) assertEquals(alice.name, aliceNode.services.identityService.partyFromAnonymous(aliceAnonymousIdentity)!!.name)
assertEquals(bob.name, bobAnonymousIdentity.name) assertEquals(bob.name, bobNode.services.identityService.partyFromAnonymous(bobAnonymousIdentity)!!.name)
// Verify that the nodes have the right anonymous identities // Verify that the nodes have the right anonymous identities
assertTrue { aliceAnonymousIdentity.party.owningKey in aliceNode.services.keyManagementService.keys } assertTrue { aliceAnonymousIdentity.owningKey in aliceNode.services.keyManagementService.keys }
assertTrue { bobAnonymousIdentity.party.owningKey in bobNode.services.keyManagementService.keys } assertTrue { bobAnonymousIdentity.owningKey in bobNode.services.keyManagementService.keys }
assertFalse { aliceAnonymousIdentity.party.owningKey in bobNode.services.keyManagementService.keys } assertFalse { aliceAnonymousIdentity.owningKey in bobNode.services.keyManagementService.keys }
assertFalse { bobAnonymousIdentity.party.owningKey in aliceNode.services.keyManagementService.keys } assertFalse { bobAnonymousIdentity.owningKey in aliceNode.services.keyManagementService.keys }
mockNet.stopNodes() mockNet.stopNodes()
} }

View File

@ -35,6 +35,11 @@ UNRELEASED
* Trader demo now issues cash and commercial paper directly from the bank node, rather than the seller node self-issuing * Trader demo now issues cash and commercial paper directly from the bank node, rather than the seller node self-issuing
commercial paper but labelling it as if issued by the bank. commercial paper but labelling it as if issued by the bank.
* Merged handling of well known and confidential identities in the identity service. Registration now takes in an identity
(either type) plus supporting certificate path, and de-anonymisation simply returns the issuing identity where known.
If you specifically need well known identities, use the network map, which is the authoritative source of current well
known identities.
Milestone 14 Milestone 14
------------ ------------

View File

@ -6,6 +6,8 @@ Here are release notes for each snapshot release from M9 onwards.
Unreleased Unreleased
---------- ----------
* Merged handling of well known and confidential identities in the identity service.
Milestone 14 Milestone 14
------------ ------------

View File

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

View File

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

View File

@ -6,7 +6,7 @@ import net.corda.core.contracts.UpgradeCommand
import net.corda.core.contracts.UpgradedContract import net.corda.core.contracts.UpgradedContract
import net.corda.core.contracts.requireThat import net.corda.core.contracts.requireThat
import net.corda.core.flows.* import net.corda.core.flows.*
import net.corda.core.identity.AnonymousPartyAndPath import net.corda.core.identity.PartyAndCertificate
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.ProgressTracker import net.corda.core.utilities.ProgressTracker
@ -87,9 +87,8 @@ class TransactionKeyHandler(val otherSide: Party, val revocationEnabled: Boolean
val revocationEnabled = false val revocationEnabled = false
progressTracker.currentStep = SENDING_KEY progressTracker.currentStep = SENDING_KEY
val legalIdentityAnonymous = serviceHub.keyManagementService.freshKeyAndCert(serviceHub.myInfo.legalIdentityAndCert, revocationEnabled) val legalIdentityAnonymous = serviceHub.keyManagementService.freshKeyAndCert(serviceHub.myInfo.legalIdentityAndCert, revocationEnabled)
val otherSideAnonymous = sendAndReceive<AnonymousPartyAndPath>(otherSide, legalIdentityAnonymous).unwrap { TransactionKeyFlow.validateIdentity(otherSide, it) } sendAndReceive<PartyAndCertificate>(otherSide, legalIdentityAnonymous).unwrap { confidentialIdentity ->
// Validate then store their identity so that we can prove the key in the transaction is owned by the TransactionKeyFlow.validateAndRegisterIdentity(serviceHub.identityService, otherSide, confidentialIdentity)
// counterparty. }
serviceHub.identityService.registerAnonymousIdentity(otherSideAnonymous, otherSide)
} }
} }

View File

@ -1,11 +1,12 @@
package net.corda.node.services.identity package net.corda.node.services.identity
import net.corda.core.contracts.PartyAndReference import net.corda.core.contracts.PartyAndReference
import net.corda.core.crypto.Crypto
import net.corda.core.crypto.cert import net.corda.core.crypto.cert
import net.corda.core.crypto.subject
import net.corda.core.crypto.toStringShort import net.corda.core.crypto.toStringShort
import net.corda.core.identity.* 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.node.services.IdentityService import net.corda.core.node.services.IdentityService
import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.core.serialization.SingletonSerializeAsToken
import net.corda.core.utilities.loggerFor import net.corda.core.utilities.loggerFor
@ -28,12 +29,12 @@ import kotlin.collections.LinkedHashSet
*/ */
@ThreadSafe @ThreadSafe
class InMemoryIdentityService(identities: Iterable<PartyAndCertificate> = emptySet(), class InMemoryIdentityService(identities: Iterable<PartyAndCertificate> = emptySet(),
certPaths: Map<AnonymousParty, CertPath> = emptyMap(), confidentialIdentities: Iterable<PartyAndCertificate> = emptySet(),
override val trustRoot: X509Certificate, override val trustRoot: X509Certificate,
vararg caCertificates: X509Certificate) : SingletonSerializeAsToken(), IdentityService { vararg caCertificates: X509Certificate) : SingletonSerializeAsToken(), IdentityService {
constructor(identities: Iterable<PartyAndCertificate> = emptySet(), constructor(wellKnownIdentities: Iterable<PartyAndCertificate> = emptySet(),
certPaths: Map<AnonymousParty, CertPath> = emptyMap(), confidentialIdentities: Iterable<PartyAndCertificate> = emptySet(),
trustRoot: X509CertificateHolder) : this(identities, certPaths, trustRoot.cert) trustRoot: X509CertificateHolder) : this(wellKnownIdentities, confidentialIdentities, trustRoot.cert)
companion object { companion object {
private val log = loggerFor<InMemoryIdentityService>() private val log = loggerFor<InMemoryIdentityService>()
} }
@ -45,41 +46,44 @@ class InMemoryIdentityService(identities: Iterable<PartyAndCertificate> = emptyS
override val trustRootHolder = X509CertificateHolder(trustRoot.encoded) override val trustRootHolder = X509CertificateHolder(trustRoot.encoded)
private val trustAnchor: TrustAnchor = TrustAnchor(trustRoot, null) private val trustAnchor: TrustAnchor = TrustAnchor(trustRoot, null)
private val keyToParties = ConcurrentHashMap<PublicKey, PartyAndCertificate>() private val keyToParties = ConcurrentHashMap<PublicKey, PartyAndCertificate>()
private val keyToIssuingParty = ConcurrentHashMap<PublicKey, PartyAndCertificate>()
private val principalToParties = ConcurrentHashMap<X500Name, PartyAndCertificate>() private val principalToParties = ConcurrentHashMap<X500Name, PartyAndCertificate>()
private val partyToPath = ConcurrentHashMap<AbstractParty, Pair<CertPath, X509CertificateHolder>>()
init { init {
val caCertificatesWithRoot: Set<X509Certificate> = caCertificates.toSet() + trustRoot val caCertificatesWithRoot: Set<X509Certificate> = caCertificates.toSet() + trustRoot
caCertStore = CertStore.getInstance("Collection", CollectionCertStoreParameters(caCertificatesWithRoot)) caCertStore = CertStore.getInstance("Collection", CollectionCertStoreParameters(caCertificatesWithRoot))
keyToParties.putAll(identities.associateBy { it.owningKey } ) keyToParties.putAll(identities.associateBy { it.owningKey } )
principalToParties.putAll(identities.associateBy { it.name }) principalToParties.putAll(identities.associateBy { it.name })
certPaths.forEach { (party, path) -> confidentialIdentities.forEach { identity ->
partyToPath.put(party, Pair(path, X509CertificateHolder(path.certificates.first().encoded))) require(identity.certPath.certificates.size >= 2) { "Certificate path must at least include subject and issuing certificates" }
keyToIssuingParty[identity.owningKey] = keyToParties[identity.certPath.certificates[1].publicKey]!!
principalToParties.computeIfAbsent(identity.name) { identity }
} }
} }
override fun registerIdentity(party: PartyAndCertificate) = verifyAndRegisterIdentity(party)
// TODO: Check the certificate validation logic // TODO: Check the certificate validation logic
@Throws(CertificateExpiredException::class, CertificateNotYetValidException::class, InvalidAlgorithmParameterException::class) @Throws(CertificateExpiredException::class, CertificateNotYetValidException::class, InvalidAlgorithmParameterException::class)
override fun registerIdentity(party: PartyAndCertificate) { override fun verifyAndRegisterIdentity(identity: PartyAndCertificate) {
require(party.certPath.certificates.isNotEmpty()) { "Certificate path must contain at least one certificate" } require(identity.certPath.certificates.size >= 2) { "Certificate path must at least include subject and issuing certificates" }
// Validate the chain first, before we do anything clever with it // Validate the chain first, before we do anything clever with it
validateCertificatePath(party.party, party.certPath) identity.verify(trustAnchor)
log.trace { "Registering identity $party" } log.trace { "Registering identity $identity" }
require(Arrays.equals(party.certificate.subjectPublicKeyInfo.encoded, party.owningKey.encoded)) { "Party certificate must end with party's public key" } require(Arrays.equals(identity.certificate.subjectPublicKeyInfo.encoded, identity.owningKey.encoded)) { "Party certificate must end with party's public key" }
partyToPath[party.party] = Pair(party.certPath, party.certificate) keyToParties[identity.owningKey] = identity
keyToParties[party.owningKey] = party // TODO: This map should only be deanonymised parties, not all issuers, but we have no good way of checking for
principalToParties[party.name] = party // confidential vs anonymous identities
} val issuer = keyToParties[identity.certPath.certificates[1].publicKey]
if (issuer != null) {
override fun anonymousFromKey(owningKey: PublicKey): AnonymousPartyAndPath? { keyToIssuingParty[identity.owningKey] = issuer
val anonymousParty = AnonymousParty(owningKey)
val path = partyToPath[anonymousParty]
return path?.let { it ->
AnonymousPartyAndPath(anonymousParty, it.first)
} }
// Always keep the first party we registered, as that's the well known identity
principalToParties.computeIfAbsent(identity.name) { identity }
} }
override fun certificateFromKey(owningKey: PublicKey): PartyAndCertificate? = keyToParties[owningKey] override fun certificateFromKey(owningKey: PublicKey): PartyAndCertificate? = keyToParties[owningKey]
override fun certificateFromParty(party: Party): PartyAndCertificate? = principalToParties[party.name] override fun certificateFromParty(party: Party): PartyAndCertificate? = principalToParties[party.name]
@ -88,7 +92,7 @@ class InMemoryIdentityService(identities: Iterable<PartyAndCertificate> = emptyS
override fun partyFromKey(key: PublicKey): Party? = keyToParties[key]?.party override fun partyFromKey(key: PublicKey): Party? = keyToParties[key]?.party
override fun partyFromX500Name(principal: X500Name): Party? = principalToParties[principal]?.party override fun partyFromX500Name(principal: X500Name): Party? = principalToParties[principal]?.party
override fun partyFromAnonymous(party: AbstractParty) = party as? Party ?: partyFromKey(party.owningKey) override fun partyFromAnonymous(party: AbstractParty) = party as? Party ?: keyToIssuingParty[party.owningKey]?.party
override fun partyFromAnonymous(partyRef: PartyAndReference) = partyFromAnonymous(partyRef.party) override fun partyFromAnonymous(partyRef: PartyAndReference) = partyFromAnonymous(partyRef.party)
override fun requirePartyFromAnonymous(party: AbstractParty): Party { override fun requirePartyFromAnonymous(party: AbstractParty): Party {
return partyFromAnonymous(party) ?: throw IllegalStateException("Could not deanonymise party ${party.owningKey.toStringShort()}") return partyFromAnonymous(party) ?: throw IllegalStateException("Could not deanonymise party ${party.owningKey.toStringShort()}")
@ -119,56 +123,11 @@ class InMemoryIdentityService(identities: Iterable<PartyAndCertificate> = emptyS
@Throws(IdentityService.UnknownAnonymousPartyException::class) @Throws(IdentityService.UnknownAnonymousPartyException::class)
override fun assertOwnership(party: Party, anonymousParty: AnonymousParty) { override fun assertOwnership(party: Party, anonymousParty: AnonymousParty) {
val path = partyToPath[anonymousParty]?.first ?: throw IdentityService.UnknownAnonymousPartyException("Unknown anonymous party ${anonymousParty.owningKey.toStringShort()}") val path = keyToParties[anonymousParty.owningKey]?.certPath ?: throw IdentityService.UnknownAnonymousPartyException("Unknown anonymous party ${anonymousParty.owningKey.toStringShort()}")
require(path.certificates.size > 1) { "Certificate path must contain at least two certificates" } require(path.certificates.size > 1) { "Certificate path must contain at least two certificates" }
val actual = path.certificates[1] 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()}." } require(actual is X509Certificate && actual.publicKey == party.owningKey) { "Next certificate in the path must match the party key ${party.owningKey.toStringShort()}." }
val target = path.certificates.first() val target = path.certificates.first()
require(target is X509Certificate && target.publicKey == anonymousParty.owningKey) { "Certificate path starts with a certificate for the anonymous party" } 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]?.first
override fun registerAnonymousIdentity(anonymousIdentity: AnonymousPartyAndPath, party: Party): PartyAndCertificate = verifyAndRegisterAnonymousIdentity(anonymousIdentity, party)
@Throws(CertificateExpiredException::class, CertificateNotYetValidException::class, InvalidAlgorithmParameterException::class)
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" }
return fullParty
}
/**
* Verify that the given certificate path is valid and leads to the owning key of the party.
*/
private fun validateCertificatePath(party: AbstractParty, path: CertPath): PKIXCertPathValidatorResult {
// Check that the path ends with a certificate for the correct party.
val endCertificate = path.certificates.first()
// Ensure the key is in the correct format for comparison.
// TODO: Replace with a Bouncy Castle cert path so we can avoid Sun internal classes appearing unexpectedly.
// For now we have to deal with this potentially being an [X509Key] which is Sun's equivalent to
// [SubjectPublicKeyInfo] but doesn't compare properly with [PublicKey].
val endKey = Crypto.decodePublicKey(endCertificate.publicKey.encoded)
require(endKey == party.owningKey) { "Certificate path validation must end at owning key ${party.owningKey.toStringShort()}, found ${endKey.toStringShort()}" }
val validatorParameters = PKIXParameters(setOf(trustAnchor))
val validator = CertPathValidator.getInstance("PKIX")
validatorParameters.isRevocationEnabled = false
return validator.validate(path, validatorParameters) as PKIXCertPathValidatorResult
}
} }

View File

@ -1,7 +1,6 @@
package net.corda.node.services.keys package net.corda.node.services.keys
import net.corda.core.crypto.* import net.corda.core.crypto.*
import net.corda.core.identity.AnonymousPartyAndPath
import net.corda.core.identity.PartyAndCertificate import net.corda.core.identity.PartyAndCertificate
import net.corda.core.internal.ThreadBox import net.corda.core.internal.ThreadBox
import net.corda.core.node.services.IdentityService import net.corda.core.node.services.IdentityService
@ -53,7 +52,7 @@ class E2ETestKeyManagementService(val identityService: IdentityService,
return keyPair.public return keyPair.public
} }
override fun freshKeyAndCert(identity: PartyAndCertificate, revocationEnabled: Boolean): AnonymousPartyAndPath { override fun freshKeyAndCert(identity: PartyAndCertificate, revocationEnabled: Boolean): PartyAndCertificate {
return freshCertificate(identityService, freshKey(), identity, getSigner(identity.owningKey), revocationEnabled) return freshCertificate(identityService, freshKey(), identity, getSigner(identity.owningKey), revocationEnabled)
} }

View File

@ -3,7 +3,7 @@ package net.corda.node.services.keys
import net.corda.core.crypto.ContentSignerBuilder import net.corda.core.crypto.ContentSignerBuilder
import net.corda.core.crypto.Crypto import net.corda.core.crypto.Crypto
import net.corda.core.crypto.cert import net.corda.core.crypto.cert
import net.corda.core.identity.AnonymousPartyAndPath import net.corda.core.identity.Party
import net.corda.core.identity.PartyAndCertificate import net.corda.core.identity.PartyAndCertificate
import net.corda.core.node.services.IdentityService import net.corda.core.node.services.IdentityService
import net.corda.core.utilities.days import net.corda.core.utilities.days
@ -32,15 +32,14 @@ fun freshCertificate(identityService: IdentityService,
subjectPublicKey: PublicKey, subjectPublicKey: PublicKey,
issuer: PartyAndCertificate, issuer: PartyAndCertificate,
issuerSigner: ContentSigner, issuerSigner: ContentSigner,
revocationEnabled: Boolean = false): AnonymousPartyAndPath { revocationEnabled: Boolean = false): PartyAndCertificate {
val issuerCertificate = issuer.certificate val issuerCertificate = issuer.certificate
val window = X509Utilities.getCertificateValidityWindow(Duration.ZERO, 3650.days, issuerCertificate) val window = X509Utilities.getCertificateValidityWindow(Duration.ZERO, 3650.days, issuerCertificate)
val ourCertificate = X509Utilities.createCertificate(CertificateType.IDENTITY, issuerCertificate.subject, issuerSigner, issuer.name, subjectPublicKey, window) val ourCertificate = X509Utilities.createCertificate(CertificateType.IDENTITY, issuerCertificate.subject, issuerSigner, issuer.name, subjectPublicKey, window)
val certFactory = CertificateFactory.getInstance("X509") val certFactory = CertificateFactory.getInstance("X509")
val ourCertPath = certFactory.generateCertPath(listOf(ourCertificate.cert) + issuer.certPath.certificates) val ourCertPath = certFactory.generateCertPath(listOf(ourCertificate.cert) + issuer.certPath.certificates)
val anonymisedIdentity = AnonymousPartyAndPath(subjectPublicKey, ourCertPath) val anonymisedIdentity = PartyAndCertificate(Party(issuer.name, subjectPublicKey), ourCertificate, ourCertPath)
identityService.verifyAndRegisterAnonymousIdentity(anonymisedIdentity, identityService.verifyAndRegisterIdentity(anonymisedIdentity)
issuer.party)
return anonymisedIdentity return anonymisedIdentity
} }

View File

@ -1,18 +1,23 @@
package net.corda.node.services.keys package net.corda.node.services.keys
import net.corda.core.crypto.* import net.corda.core.crypto.*
import net.corda.core.identity.AnonymousPartyAndPath
import net.corda.core.identity.PartyAndCertificate import net.corda.core.identity.PartyAndCertificate
import net.corda.core.internal.ThreadBox
import net.corda.core.node.services.IdentityService import net.corda.core.node.services.IdentityService
import net.corda.core.node.services.KeyManagementService import net.corda.core.node.services.KeyManagementService
import net.corda.core.serialization.* import net.corda.core.serialization.SerializationDefaults
import net.corda.node.utilities.* import net.corda.core.serialization.SingletonSerializeAsToken
import net.corda.core.serialization.deserialize
import net.corda.core.serialization.serialize
import net.corda.node.utilities.AppendOnlyPersistentMap
import net.corda.node.utilities.NODE_DATABASE_PREFIX
import org.bouncycastle.operator.ContentSigner import org.bouncycastle.operator.ContentSigner
import java.security.KeyPair import java.security.KeyPair
import java.security.PrivateKey import java.security.PrivateKey
import java.security.PublicKey import java.security.PublicKey
import javax.persistence.* import javax.persistence.Column
import javax.persistence.Entity
import javax.persistence.Id
import javax.persistence.Lob
/** /**
* A persistent re-implementation of [E2ETestKeyManagementService] to support node re-start. * A persistent re-implementation of [E2ETestKeyManagementService] to support node re-start.
@ -71,7 +76,7 @@ class PersistentKeyManagementService(val identityService: IdentityService,
return keyPair.public return keyPair.public
} }
override fun freshKeyAndCert(identity: PartyAndCertificate, revocationEnabled: Boolean): AnonymousPartyAndPath = override fun freshKeyAndCert(identity: PartyAndCertificate, revocationEnabled: Boolean): PartyAndCertificate =
freshCertificate(identityService, freshKey(), identity, getSigner(identity.owningKey), revocationEnabled) freshCertificate(identityService, freshKey(), identity, getSigner(identity.owningKey), revocationEnabled)
private fun getSigner(publicKey: PublicKey): ContentSigner = getSigner(getSigningKeyPair(publicKey)) private fun getSigner(publicKey: PublicKey): ContentSigner = getSigner(getSigningKeyPair(publicKey))

View File

@ -2,11 +2,11 @@ package net.corda.node.messaging
import co.paralleluniverse.fibers.Suspendable import co.paralleluniverse.fibers.Suspendable
import net.corda.contracts.CommercialPaper import net.corda.contracts.CommercialPaper
import net.corda.core.concurrent.CordaFuture
import net.corda.contracts.asset.CASH import net.corda.contracts.asset.CASH
import net.corda.contracts.asset.Cash import net.corda.contracts.asset.Cash
import net.corda.contracts.asset.`issued by` import net.corda.contracts.asset.`issued by`
import net.corda.contracts.asset.`owned by` import net.corda.contracts.asset.`owned by`
import net.corda.core.concurrent.CordaFuture
import net.corda.core.contracts.* import net.corda.core.contracts.*
import net.corda.core.crypto.* import net.corda.core.crypto.*
import net.corda.core.flows.FlowLogic import net.corda.core.flows.FlowLogic
@ -15,7 +15,6 @@ import net.corda.core.flows.InitiatingFlow
import net.corda.core.flows.StateMachineRunId import net.corda.core.flows.StateMachineRunId
import net.corda.core.identity.AbstractParty import net.corda.core.identity.AbstractParty
import net.corda.core.identity.AnonymousParty import net.corda.core.identity.AnonymousParty
import net.corda.core.identity.AnonymousPartyAndPath
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.internal.FlowStateMachine import net.corda.core.internal.FlowStateMachine
import net.corda.core.internal.concurrent.map import net.corda.core.internal.concurrent.map
@ -521,7 +520,7 @@ class TwoPartyTradeFlowTests {
assetToSell: StateAndRef<OwnableState>): RunResult { assetToSell: StateAndRef<OwnableState>): RunResult {
val anonymousSeller = sellerNode.services.let { serviceHub -> val anonymousSeller = sellerNode.services.let { serviceHub ->
serviceHub.keyManagementService.freshKeyAndCert(serviceHub.myInfo.legalIdentityAndCert, false) serviceHub.keyManagementService.freshKeyAndCert(serviceHub.myInfo.legalIdentityAndCert, false)
} }.party.anonymise()
val buyerFlows: Observable<BuyerAcceptor> = buyerNode.registerInitiatedFlow(BuyerAcceptor::class.java) val buyerFlows: Observable<BuyerAcceptor> = buyerNode.registerInitiatedFlow(BuyerAcceptor::class.java)
val firstBuyerFiber = buyerFlows.toFuture().map { it.stateMachine } val firstBuyerFiber = buyerFlows.toFuture().map { it.stateMachine }
val seller = SellerInitiator(buyerNode.info.legalIdentity, notaryNode.info, assetToSell, 1000.DOLLARS, anonymousSeller) val seller = SellerInitiator(buyerNode.info.legalIdentity, notaryNode.info, assetToSell, 1000.DOLLARS, anonymousSeller)
@ -534,7 +533,7 @@ class TwoPartyTradeFlowTests {
val notary: NodeInfo, val notary: NodeInfo,
val assetToSell: StateAndRef<OwnableState>, val assetToSell: StateAndRef<OwnableState>,
val price: Amount<Currency>, val price: Amount<Currency>,
val me: AnonymousPartyAndPath) : FlowLogic<SignedTransaction>() { val me: AnonymousParty) : FlowLogic<SignedTransaction>() {
@Suspendable @Suspendable
override fun call(): SignedTransaction { override fun call(): SignedTransaction {
send(buyer, Pair(notary.notaryIdentity, price)) send(buyer, Pair(notary.notaryIdentity, price))
@ -543,7 +542,7 @@ class TwoPartyTradeFlowTests {
notary, notary,
assetToSell, assetToSell,
price, price,
me.party)) me))
} }
} }

View File

@ -5,7 +5,6 @@ import net.corda.core.crypto.Crypto
import net.corda.core.crypto.cert import net.corda.core.crypto.cert
import net.corda.core.crypto.generateKeyPair import net.corda.core.crypto.generateKeyPair
import net.corda.core.identity.AnonymousParty import net.corda.core.identity.AnonymousParty
import net.corda.core.identity.AnonymousPartyAndPath
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.identity.PartyAndCertificate import net.corda.core.identity.PartyAndCertificate
import net.corda.core.node.services.IdentityService import net.corda.core.node.services.IdentityService
@ -30,13 +29,13 @@ class InMemoryIdentityServiceTests {
// Nothing registered, so empty set // Nothing registered, so empty set
assertNull(service.getAllIdentities().firstOrNull()) assertNull(service.getAllIdentities().firstOrNull())
service.registerIdentity(ALICE_IDENTITY) service.verifyAndRegisterIdentity(ALICE_IDENTITY)
var expected = setOf(ALICE) var expected = setOf(ALICE)
var actual = service.getAllIdentities().map { it.party }.toHashSet() var actual = service.getAllIdentities().map { it.party }.toHashSet()
assertEquals(expected, actual) assertEquals(expected, actual)
// Add a second party and check we get both back // Add a second party and check we get both back
service.registerIdentity(BOB_IDENTITY) service.verifyAndRegisterIdentity(BOB_IDENTITY)
expected = setOf(ALICE, BOB) expected = setOf(ALICE, BOB)
actual = service.getAllIdentities().map { it.party }.toHashSet() actual = service.getAllIdentities().map { it.party }.toHashSet()
assertEquals(expected, actual) assertEquals(expected, actual)
@ -46,7 +45,7 @@ class InMemoryIdentityServiceTests {
fun `get identity by key`() { fun `get identity by key`() {
val service = InMemoryIdentityService(trustRoot = DUMMY_CA.certificate) val service = InMemoryIdentityService(trustRoot = DUMMY_CA.certificate)
assertNull(service.partyFromKey(ALICE_PUBKEY)) assertNull(service.partyFromKey(ALICE_PUBKEY))
service.registerIdentity(ALICE_IDENTITY) service.verifyAndRegisterIdentity(ALICE_IDENTITY)
assertEquals(ALICE, service.partyFromKey(ALICE_PUBKEY)) assertEquals(ALICE, service.partyFromKey(ALICE_PUBKEY))
assertNull(service.partyFromKey(BOB_PUBKEY)) assertNull(service.partyFromKey(BOB_PUBKEY))
} }
@ -61,10 +60,10 @@ class InMemoryIdentityServiceTests {
fun `get identity by substring match`() { fun `get identity by substring match`() {
val trustRoot = DUMMY_CA val trustRoot = DUMMY_CA
val service = InMemoryIdentityService(trustRoot = trustRoot.certificate) val service = InMemoryIdentityService(trustRoot = trustRoot.certificate)
service.registerIdentity(ALICE_IDENTITY) service.verifyAndRegisterIdentity(ALICE_IDENTITY)
service.registerIdentity(BOB_IDENTITY) service.verifyAndRegisterIdentity(BOB_IDENTITY)
val alicente = getTestPartyAndCertificate(X500Name("O=Alicente Worldwide,L=London,C=GB"), generateKeyPair().public) val alicente = getTestPartyAndCertificate(X500Name("O=Alicente Worldwide,L=London,C=GB"), generateKeyPair().public)
service.registerIdentity(alicente) service.verifyAndRegisterIdentity(alicente)
assertEquals(setOf(ALICE, alicente.party), service.partiesFromName("Alice", false)) assertEquals(setOf(ALICE, alicente.party), service.partiesFromName("Alice", false))
assertEquals(setOf(ALICE), service.partiesFromName("Alice Corp", true)) assertEquals(setOf(ALICE), service.partiesFromName("Alice Corp", true))
assertEquals(setOf(BOB), service.partiesFromName("Bob Plc", true)) assertEquals(setOf(BOB), service.partiesFromName("Bob Plc", true))
@ -76,7 +75,7 @@ class InMemoryIdentityServiceTests {
val identities = listOf("Node A", "Node B", "Node C") val identities = listOf("Node A", "Node B", "Node C")
.map { getTestPartyAndCertificate(X500Name("CN=$it,O=R3,OU=corda,L=London,C=GB"), generateKeyPair().public) } .map { getTestPartyAndCertificate(X500Name("CN=$it,O=R3,OU=corda,L=London,C=GB"), generateKeyPair().public) }
assertNull(service.partyFromX500Name(identities.first().name)) assertNull(service.partyFromX500Name(identities.first().name))
identities.forEach { service.registerIdentity(it) } identities.forEach { service.verifyAndRegisterIdentity(it) }
identities.forEach { assertEquals(it.party, service.partyFromX500Name(it.name)) } identities.forEach { assertEquals(it.party, service.partyFromX500Name(it.name)) }
} }
@ -111,15 +110,15 @@ class InMemoryIdentityServiceTests {
val (bob, bobTxIdentity) = createParty(ALICE.name, trustRoot) val (bob, bobTxIdentity) = createParty(ALICE.name, trustRoot)
// Now we have identities, construct the service and let it know about both // Now we have identities, construct the service and let it know about both
val service = InMemoryIdentityService(setOf(alice), emptyMap(), trustRoot.certificate.cert) val service = InMemoryIdentityService(setOf(alice), emptySet(), trustRoot.certificate.cert)
service.verifyAndRegisterAnonymousIdentity(aliceTxIdentity, alice.party) service.verifyAndRegisterIdentity(aliceTxIdentity)
var actual = service.anonymousFromKey(aliceTxIdentity.party.owningKey) var actual = service.certificateFromKey(aliceTxIdentity.party.owningKey)
assertEquals(aliceTxIdentity, actual!!) assertEquals(aliceTxIdentity, actual!!)
assertNull(service.anonymousFromKey(bobTxIdentity.party.owningKey)) assertNull(service.certificateFromKey(bobTxIdentity.party.owningKey))
service.verifyAndRegisterAnonymousIdentity(bobTxIdentity, bob.party) service.verifyAndRegisterIdentity(bobTxIdentity)
actual = service.anonymousFromKey(bobTxIdentity.party.owningKey) actual = service.certificateFromKey(bobTxIdentity.party.owningKey)
assertEquals(bobTxIdentity, actual!!) assertEquals(bobTxIdentity, actual!!)
} }
@ -135,36 +134,36 @@ class InMemoryIdentityServiceTests {
val (bob, anonymousBob) = createParty(BOB.name, trustRoot) val (bob, anonymousBob) = createParty(BOB.name, trustRoot)
// Now we have identities, construct the service and let it know about both // Now we have identities, construct the service and let it know about both
val service = InMemoryIdentityService(setOf(alice, bob), emptyMap(), trustRoot.certificate.cert) val service = InMemoryIdentityService(setOf(alice, bob), emptySet(), trustRoot.certificate.cert)
service.verifyAndRegisterAnonymousIdentity(anonymousAlice, alice.party) service.verifyAndRegisterIdentity(anonymousAlice)
service.verifyAndRegisterAnonymousIdentity(anonymousBob, bob.party) service.verifyAndRegisterIdentity(anonymousBob)
// Verify that paths are verified // Verify that paths are verified
service.assertOwnership(alice.party, anonymousAlice.party) service.assertOwnership(alice.party, anonymousAlice.party.anonymise())
service.assertOwnership(bob.party, anonymousBob.party) service.assertOwnership(bob.party, anonymousBob.party.anonymise())
assertFailsWith<IllegalArgumentException> { assertFailsWith<IllegalArgumentException> {
service.assertOwnership(alice.party, anonymousBob.party) service.assertOwnership(alice.party, anonymousBob.party.anonymise())
} }
assertFailsWith<IllegalArgumentException> { assertFailsWith<IllegalArgumentException> {
service.assertOwnership(bob.party, anonymousAlice.party) service.assertOwnership(bob.party, anonymousAlice.party.anonymise())
} }
assertFailsWith<IllegalArgumentException> { assertFailsWith<IllegalArgumentException> {
val owningKey = Crypto.decodePublicKey(trustRoot.certificate.subjectPublicKeyInfo.encoded) val owningKey = Crypto.decodePublicKey(trustRoot.certificate.subjectPublicKeyInfo.encoded)
service.assertOwnership(Party(trustRoot.certificate.subject, owningKey), anonymousAlice.party) service.assertOwnership(Party(trustRoot.certificate.subject, owningKey), anonymousAlice.party.anonymise())
} }
} }
} }
private fun createParty(x500Name: X500Name, ca: CertificateAndKeyPair): Pair<PartyAndCertificate, AnonymousPartyAndPath> { private fun createParty(x500Name: X500Name, ca: CertificateAndKeyPair): Pair<PartyAndCertificate, PartyAndCertificate> {
val certFactory = CertificateFactory.getInstance("X509") val certFactory = CertificateFactory.getInstance("X509")
val issuerKeyPair = generateKeyPair() val issuerKeyPair = generateKeyPair()
val issuer = getTestPartyAndCertificate(x500Name, issuerKeyPair.public, ca) val issuer = getTestPartyAndCertificate(x500Name, issuerKeyPair.public, ca)
val txKey = Crypto.generateKeyPair() val txKey = Crypto.generateKeyPair()
val txCert = X509Utilities.createCertificate(CertificateType.IDENTITY, issuer.certificate, issuerKeyPair, x500Name, txKey.public) val txCert = X509Utilities.createCertificate(CertificateType.IDENTITY, issuer.certificate, issuerKeyPair, x500Name, txKey.public)
val txCertPath = certFactory.generateCertPath(listOf(txCert.cert) + issuer.certPath.certificates) val txCertPath = certFactory.generateCertPath(listOf(txCert.cert) + issuer.certPath.certificates)
return Pair(issuer, AnonymousPartyAndPath(AnonymousParty(txKey.public), txCertPath)) return Pair(issuer, PartyAndCertificate(Party(x500Name, txKey.public), txCert, txCertPath))
} }
/** /**

View File

@ -89,7 +89,7 @@ class FlowFrameworkTests {
val nodes = listOf(node1, node2, notary1, notary2) val nodes = listOf(node1, node2, notary1, notary2)
nodes.forEach { node -> nodes.forEach { node ->
nodes.map { it.services.myInfo.legalIdentityAndCert }.forEach { identity -> nodes.map { it.services.myInfo.legalIdentityAndCert }.forEach { identity ->
node.services.identityService.registerIdentity(identity) node.services.identityService.verifyAndRegisterIdentity(identity)
} }
} }
} }

View File

@ -89,7 +89,7 @@ val BIG_CORP_PARTY_REF = BIG_CORP.ref(OpaqueBytes.of(1)).reference
val ALL_TEST_KEYS: List<KeyPair> get() = listOf(MEGA_CORP_KEY, MINI_CORP_KEY, ALICE_KEY, BOB_KEY, DUMMY_NOTARY_KEY) val ALL_TEST_KEYS: List<KeyPair> get() = listOf(MEGA_CORP_KEY, MINI_CORP_KEY, ALICE_KEY, BOB_KEY, DUMMY_NOTARY_KEY)
val MOCK_IDENTITIES = listOf(MEGA_CORP_IDENTITY, MINI_CORP_IDENTITY, DUMMY_NOTARY_IDENTITY) val MOCK_IDENTITIES = listOf(MEGA_CORP_IDENTITY, MINI_CORP_IDENTITY, DUMMY_NOTARY_IDENTITY)
val MOCK_IDENTITY_SERVICE: IdentityService get() = InMemoryIdentityService(MOCK_IDENTITIES, emptyMap(), DUMMY_CA.certificate.cert) val MOCK_IDENTITY_SERVICE: IdentityService get() = InMemoryIdentityService(MOCK_IDENTITIES, emptySet(), DUMMY_CA.certificate.cert)
val MOCK_HOST_AND_PORT = NetworkHostAndPort("mockHost", 30000) val MOCK_HOST_AND_PORT = NetworkHostAndPort("mockHost", 30000)

View File

@ -356,7 +356,7 @@ class MockNetwork(private val networkSendManuallyPumped: Boolean = false,
nodes += createPartyNode(mapAddress) nodes += createPartyNode(mapAddress)
} }
nodes.forEach { itNode -> nodes.forEach { itNode ->
nodes.map { it.info.legalIdentityAndCert }.forEach(itNode.services.identityService::registerIdentity) nodes.map { it.info.legalIdentityAndCert }.forEach(itNode.services.identityService::verifyAndRegisterIdentity)
} }
return BasketOfNodes(nodes, notaryNode, mapNode) return BasketOfNodes(nodes, notaryNode, mapNode)
} }

View File

@ -3,7 +3,6 @@ package net.corda.testing.node
import net.corda.core.contracts.Attachment import net.corda.core.contracts.Attachment
import net.corda.core.crypto.* import net.corda.core.crypto.*
import net.corda.core.flows.StateMachineRunId import net.corda.core.flows.StateMachineRunId
import net.corda.core.identity.AnonymousPartyAndPath
import net.corda.core.identity.PartyAndCertificate import net.corda.core.identity.PartyAndCertificate
import net.corda.core.messaging.DataFeed import net.corda.core.messaging.DataFeed
import net.corda.core.node.NodeInfo import net.corda.core.node.NodeInfo
@ -115,7 +114,7 @@ class MockKeyManagementService(val identityService: IdentityService,
override fun filterMyKeys(candidateKeys: Iterable<PublicKey>): Iterable<PublicKey> = candidateKeys.filter { it in this.keys } override fun filterMyKeys(candidateKeys: Iterable<PublicKey>): Iterable<PublicKey> = candidateKeys.filter { it in this.keys }
override fun freshKeyAndCert(identity: PartyAndCertificate, revocationEnabled: Boolean): AnonymousPartyAndPath { override fun freshKeyAndCert(identity: PartyAndCertificate, revocationEnabled: Boolean): PartyAndCertificate {
return freshCertificate(identityService, freshKey(), identity, getSigner(identity.owningKey), revocationEnabled) return freshCertificate(identityService, freshKey(), identity, getSigner(identity.owningKey), revocationEnabled)
} }