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

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 nameOrNull(): X500Name? = name
fun anonymise(): AnonymousParty = AnonymousParty(owningKey)
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.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
}
}

View File

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

View File

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

View File

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

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

View File

@ -6,6 +6,8 @@ Here are release notes for each snapshot release from M9 onwards.
Unreleased
----------
* Merged handling of well known and confidential identities in the identity service.
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.StartableByRPC
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.transactions.TransactionBuilder
import net.corda.core.utilities.OpaqueBytes
@ -45,9 +45,9 @@ class CashIssueFlow(val amount: Amount<Currency>,
val txIdentities = if (anonymous) {
subFlow(TransactionKeyFlow(recipient))
} else {
emptyMap<Party, AnonymousPartyAndPath>()
emptyMap<Party, AnonymousParty>()
}
val anonymousRecipient = txIdentities[recipient]?.party ?: recipient
val anonymousRecipient = txIdentities[recipient] ?: recipient
progressTracker.currentStep = GENERATING_TX
val builder: TransactionBuilder = TransactionBuilder(notary)
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.flows.StartableByRPC
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.transactions.TransactionBuilder
import net.corda.core.utilities.ProgressTracker
@ -39,9 +39,9 @@ open class CashPaymentFlow(
val txIdentities = if (anonymous) {
subFlow(TransactionKeyFlow(recipient))
} else {
emptyMap<Party, AnonymousPartyAndPath>()
emptyMap<Party, AnonymousParty>()
}
val anonymousRecipient = txIdentities.get(recipient)?.party ?: recipient
val anonymousRecipient = txIdentities.get(recipient) ?: recipient
progressTracker.currentStep = GENERATING_TX
val builder: TransactionBuilder = TransactionBuilder(null as Party?)
// 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.requireThat
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.transactions.SignedTransaction
import net.corda.core.utilities.ProgressTracker
@ -87,9 +87,8 @@ 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<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(otherSideAnonymous, otherSide)
sendAndReceive<PartyAndCertificate>(otherSide, legalIdentityAnonymous).unwrap { confidentialIdentity ->
TransactionKeyFlow.validateAndRegisterIdentity(serviceHub.identityService, otherSide, confidentialIdentity)
}
}
}

View File

@ -1,11 +1,12 @@
package net.corda.node.services.identity
import net.corda.core.contracts.PartyAndReference
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.*
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.serialization.SingletonSerializeAsToken
import net.corda.core.utilities.loggerFor
@ -28,12 +29,12 @@ import kotlin.collections.LinkedHashSet
*/
@ThreadSafe
class InMemoryIdentityService(identities: Iterable<PartyAndCertificate> = emptySet(),
certPaths: Map<AnonymousParty, CertPath> = emptyMap(),
confidentialIdentities: Iterable<PartyAndCertificate> = emptySet(),
override val trustRoot: X509Certificate,
vararg caCertificates: X509Certificate) : SingletonSerializeAsToken(), IdentityService {
constructor(identities: Iterable<PartyAndCertificate> = emptySet(),
certPaths: Map<AnonymousParty, CertPath> = emptyMap(),
trustRoot: X509CertificateHolder) : this(identities, certPaths, trustRoot.cert)
constructor(wellKnownIdentities: Iterable<PartyAndCertificate> = emptySet(),
confidentialIdentities: Iterable<PartyAndCertificate> = emptySet(),
trustRoot: X509CertificateHolder) : this(wellKnownIdentities, confidentialIdentities, trustRoot.cert)
companion object {
private val log = loggerFor<InMemoryIdentityService>()
}
@ -45,41 +46,44 @@ class InMemoryIdentityService(identities: Iterable<PartyAndCertificate> = emptyS
override val trustRootHolder = X509CertificateHolder(trustRoot.encoded)
private val trustAnchor: TrustAnchor = TrustAnchor(trustRoot, null)
private val keyToParties = ConcurrentHashMap<PublicKey, PartyAndCertificate>()
private val keyToIssuingParty = ConcurrentHashMap<PublicKey, PartyAndCertificate>()
private val principalToParties = ConcurrentHashMap<X500Name, PartyAndCertificate>()
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 })
certPaths.forEach { (party, path) ->
partyToPath.put(party, Pair(path, X509CertificateHolder(path.certificates.first().encoded)))
confidentialIdentities.forEach { identity ->
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
@Throws(CertificateExpiredException::class, CertificateNotYetValidException::class, InvalidAlgorithmParameterException::class)
override fun registerIdentity(party: PartyAndCertificate) {
require(party.certPath.certificates.isNotEmpty()) { "Certificate path must contain at least one certificate" }
override fun verifyAndRegisterIdentity(identity: PartyAndCertificate) {
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
validateCertificatePath(party.party, party.certPath)
identity.verify(trustAnchor)
log.trace { "Registering identity $party" }
require(Arrays.equals(party.certificate.subjectPublicKeyInfo.encoded, party.owningKey.encoded)) { "Party certificate must end with party's public key" }
log.trace { "Registering identity $identity" }
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[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)
keyToParties[identity.owningKey] = identity
// TODO: This map should only be deanonymised parties, not all issuers, but we have no good way of checking for
// confidential vs anonymous identities
val issuer = keyToParties[identity.certPath.certificates[1].publicKey]
if (issuer != null) {
keyToIssuingParty[identity.owningKey] = issuer
}
// 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 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 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 requirePartyFromAnonymous(party: AbstractParty): Party {
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)
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" }
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()}." }
val target = path.certificates.first()
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
import net.corda.core.crypto.*
import net.corda.core.identity.AnonymousPartyAndPath
import net.corda.core.identity.PartyAndCertificate
import net.corda.core.internal.ThreadBox
import net.corda.core.node.services.IdentityService
@ -53,7 +52,7 @@ class E2ETestKeyManagementService(val identityService: IdentityService,
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)
}

View File

@ -3,7 +3,7 @@ package net.corda.node.services.keys
import net.corda.core.crypto.ContentSignerBuilder
import net.corda.core.crypto.Crypto
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.node.services.IdentityService
import net.corda.core.utilities.days
@ -32,15 +32,14 @@ fun freshCertificate(identityService: IdentityService,
subjectPublicKey: PublicKey,
issuer: PartyAndCertificate,
issuerSigner: ContentSigner,
revocationEnabled: Boolean = false): AnonymousPartyAndPath {
revocationEnabled: Boolean = false): PartyAndCertificate {
val issuerCertificate = issuer.certificate
val window = X509Utilities.getCertificateValidityWindow(Duration.ZERO, 3650.days, issuerCertificate)
val ourCertificate = X509Utilities.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)
val anonymisedIdentity = AnonymousPartyAndPath(subjectPublicKey, ourCertPath)
identityService.verifyAndRegisterAnonymousIdentity(anonymisedIdentity,
issuer.party)
val anonymisedIdentity = PartyAndCertificate(Party(issuer.name, subjectPublicKey), ourCertificate, ourCertPath)
identityService.verifyAndRegisterIdentity(anonymisedIdentity)
return anonymisedIdentity
}

View File

@ -1,18 +1,23 @@
package net.corda.node.services.keys
import net.corda.core.crypto.*
import net.corda.core.identity.AnonymousPartyAndPath
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.KeyManagementService
import net.corda.core.serialization.*
import net.corda.node.utilities.*
import net.corda.core.serialization.SerializationDefaults
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 java.security.KeyPair
import java.security.PrivateKey
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.
@ -71,7 +76,7 @@ class PersistentKeyManagementService(val identityService: IdentityService,
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)
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 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.`issued by`
import net.corda.contracts.asset.`owned by`
import net.corda.core.concurrent.CordaFuture
import net.corda.core.contracts.*
import net.corda.core.crypto.*
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.identity.AbstractParty
import net.corda.core.identity.AnonymousParty
import net.corda.core.identity.AnonymousPartyAndPath
import net.corda.core.identity.Party
import net.corda.core.internal.FlowStateMachine
import net.corda.core.internal.concurrent.map
@ -521,7 +520,7 @@ class TwoPartyTradeFlowTests {
assetToSell: StateAndRef<OwnableState>): RunResult {
val anonymousSeller = sellerNode.services.let { serviceHub ->
serviceHub.keyManagementService.freshKeyAndCert(serviceHub.myInfo.legalIdentityAndCert, false)
}
}.party.anonymise()
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)
@ -534,7 +533,7 @@ class TwoPartyTradeFlowTests {
val notary: NodeInfo,
val assetToSell: StateAndRef<OwnableState>,
val price: Amount<Currency>,
val me: AnonymousPartyAndPath) : FlowLogic<SignedTransaction>() {
val me: AnonymousParty) : FlowLogic<SignedTransaction>() {
@Suspendable
override fun call(): SignedTransaction {
send(buyer, Pair(notary.notaryIdentity, price))
@ -543,7 +542,7 @@ class TwoPartyTradeFlowTests {
notary,
assetToSell,
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.generateKeyPair
import net.corda.core.identity.AnonymousParty
import net.corda.core.identity.AnonymousPartyAndPath
import net.corda.core.identity.Party
import net.corda.core.identity.PartyAndCertificate
import net.corda.core.node.services.IdentityService
@ -30,13 +29,13 @@ class InMemoryIdentityServiceTests {
// Nothing registered, so empty set
assertNull(service.getAllIdentities().firstOrNull())
service.registerIdentity(ALICE_IDENTITY)
service.verifyAndRegisterIdentity(ALICE_IDENTITY)
var expected = setOf(ALICE)
var actual = service.getAllIdentities().map { it.party }.toHashSet()
assertEquals(expected, actual)
// Add a second party and check we get both back
service.registerIdentity(BOB_IDENTITY)
service.verifyAndRegisterIdentity(BOB_IDENTITY)
expected = setOf(ALICE, BOB)
actual = service.getAllIdentities().map { it.party }.toHashSet()
assertEquals(expected, actual)
@ -46,7 +45,7 @@ class InMemoryIdentityServiceTests {
fun `get identity by key`() {
val service = InMemoryIdentityService(trustRoot = DUMMY_CA.certificate)
assertNull(service.partyFromKey(ALICE_PUBKEY))
service.registerIdentity(ALICE_IDENTITY)
service.verifyAndRegisterIdentity(ALICE_IDENTITY)
assertEquals(ALICE, service.partyFromKey(ALICE_PUBKEY))
assertNull(service.partyFromKey(BOB_PUBKEY))
}
@ -61,10 +60,10 @@ class InMemoryIdentityServiceTests {
fun `get identity by substring match`() {
val trustRoot = DUMMY_CA
val service = InMemoryIdentityService(trustRoot = trustRoot.certificate)
service.registerIdentity(ALICE_IDENTITY)
service.registerIdentity(BOB_IDENTITY)
service.verifyAndRegisterIdentity(ALICE_IDENTITY)
service.verifyAndRegisterIdentity(BOB_IDENTITY)
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), service.partiesFromName("Alice Corp", true))
assertEquals(setOf(BOB), service.partiesFromName("Bob Plc", true))
@ -76,7 +75,7 @@ class InMemoryIdentityServiceTests {
val identities = listOf("Node A", "Node B", "Node C")
.map { getTestPartyAndCertificate(X500Name("CN=$it,O=R3,OU=corda,L=London,C=GB"), generateKeyPair().public) }
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)) }
}
@ -111,15 +110,15 @@ class InMemoryIdentityServiceTests {
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)
val service = InMemoryIdentityService(setOf(alice), emptySet(), trustRoot.certificate.cert)
service.verifyAndRegisterIdentity(aliceTxIdentity)
var actual = service.anonymousFromKey(aliceTxIdentity.party.owningKey)
var actual = service.certificateFromKey(aliceTxIdentity.party.owningKey)
assertEquals(aliceTxIdentity, actual!!)
assertNull(service.anonymousFromKey(bobTxIdentity.party.owningKey))
service.verifyAndRegisterAnonymousIdentity(bobTxIdentity, bob.party)
actual = service.anonymousFromKey(bobTxIdentity.party.owningKey)
assertNull(service.certificateFromKey(bobTxIdentity.party.owningKey))
service.verifyAndRegisterIdentity(bobTxIdentity)
actual = service.certificateFromKey(bobTxIdentity.party.owningKey)
assertEquals(bobTxIdentity, actual!!)
}
@ -135,36 +134,36 @@ class InMemoryIdentityServiceTests {
val (bob, anonymousBob) = createParty(BOB.name, trustRoot)
// 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.verifyAndRegisterAnonymousIdentity(anonymousBob, bob.party)
service.verifyAndRegisterIdentity(anonymousAlice)
service.verifyAndRegisterIdentity(anonymousBob)
// Verify that paths are verified
service.assertOwnership(alice.party, anonymousAlice.party)
service.assertOwnership(bob.party, anonymousBob.party)
service.assertOwnership(alice.party, anonymousAlice.party.anonymise())
service.assertOwnership(bob.party, anonymousBob.party.anonymise())
assertFailsWith<IllegalArgumentException> {
service.assertOwnership(alice.party, anonymousBob.party)
service.assertOwnership(alice.party, anonymousBob.party.anonymise())
}
assertFailsWith<IllegalArgumentException> {
service.assertOwnership(bob.party, anonymousAlice.party)
service.assertOwnership(bob.party, anonymousAlice.party.anonymise())
}
assertFailsWith<IllegalArgumentException> {
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 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, 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)
nodes.forEach { node ->
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 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)

View File

@ -356,7 +356,7 @@ class MockNetwork(private val networkSendManuallyPumped: Boolean = false,
nodes += createPartyNode(mapAddress)
}
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)
}

View File

@ -3,7 +3,6 @@ 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
@ -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 freshKeyAndCert(identity: PartyAndCertificate, revocationEnabled: Boolean): AnonymousPartyAndPath {
override fun freshKeyAndCert(identity: PartyAndCertificate, revocationEnabled: Boolean): PartyAndCertificate {
return freshCertificate(identityService, freshKey(), identity, getSigner(identity.owningKey), revocationEnabled)
}