mirror of
https://github.com/corda/corda.git
synced 2025-06-19 15:43:52 +00:00
Add certificate path storage to identity service
Add functionality for generating certificate paths from identity certificates to transaction certificates, validating, storing and retrieving those certificate paths.
This commit is contained in:
@ -1,6 +1,8 @@
|
||||
package net.corda.node.services.identity
|
||||
|
||||
import net.corda.core.contracts.PartyAndReference
|
||||
import net.corda.core.contracts.requireThat
|
||||
import net.corda.core.crypto.toStringShort
|
||||
import net.corda.core.identity.AnonymousParty
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.node.services.IdentityService
|
||||
@ -8,10 +10,13 @@ import net.corda.core.serialization.SingletonSerializeAsToken
|
||||
import net.corda.core.utilities.loggerFor
|
||||
import net.corda.core.utilities.trace
|
||||
import org.bouncycastle.asn1.x500.X500Name
|
||||
import java.security.InvalidAlgorithmParameterException
|
||||
import java.security.PublicKey
|
||||
import java.security.cert.*
|
||||
import java.util.*
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import javax.annotation.concurrent.ThreadSafe
|
||||
import javax.security.auth.x500.X500Principal
|
||||
|
||||
/**
|
||||
* Simple identity service which caches parties and provides functionality for efficient lookup.
|
||||
@ -24,6 +29,7 @@ class InMemoryIdentityService : SingletonSerializeAsToken(), IdentityService {
|
||||
|
||||
private val keyToParties = ConcurrentHashMap<PublicKey, Party>()
|
||||
private val principalToParties = ConcurrentHashMap<X500Name, Party>()
|
||||
private val partyToPath = ConcurrentHashMap<AnonymousParty, CertPath>()
|
||||
|
||||
override fun registerIdentity(party: Party) {
|
||||
log.trace { "Registering identity $party" }
|
||||
@ -40,4 +46,36 @@ class InMemoryIdentityService : SingletonSerializeAsToken(), IdentityService {
|
||||
override fun partyFromX500Name(principal: X500Name): Party? = principalToParties[principal]
|
||||
override fun partyFromAnonymous(party: AnonymousParty): Party? = partyFromKey(party.owningKey)
|
||||
override fun partyFromAnonymous(partyRef: PartyAndReference) = partyFromAnonymous(partyRef.party)
|
||||
|
||||
@Throws(IdentityService.UnknownAnonymousPartyException::class)
|
||||
override fun assertOwnership(party: Party, anonymousParty: AnonymousParty) {
|
||||
val path = partyToPath[anonymousParty] ?: throw IdentityService.UnknownAnonymousPartyException("Unknown anonymous party ${anonymousParty.owningKey.toStringShort()}")
|
||||
val target = path.certificates.last() as X509Certificate
|
||||
requireThat {
|
||||
"Certificate path ends with \"${target.issuerX500Principal}\" expected \"${party.name}\"" using (X500Name(target.subjectX500Principal.name) == party.name)
|
||||
"Certificate path ends with correct public key" using (target.publicKey == anonymousParty.owningKey)
|
||||
}
|
||||
// Verify there's a previous certificate in the path, which matches
|
||||
val root = path.certificates.first() as X509Certificate
|
||||
require(X500Name(root.issuerX500Principal.name) == party.name) { "Certificate path starts with \"${root.issuerX500Principal}\" expected \"${party.name}\"" }
|
||||
}
|
||||
|
||||
override fun pathForAnonymous(anonymousParty: AnonymousParty): CertPath? = partyToPath[anonymousParty]
|
||||
|
||||
@Throws(CertificateExpiredException::class, CertificateNotYetValidException::class, InvalidAlgorithmParameterException::class)
|
||||
override fun registerPath(trustedRoot: X509Certificate, anonymousParty: AnonymousParty, path: CertPath) {
|
||||
val expectedTrustAnchor = TrustAnchor(trustedRoot, null)
|
||||
require(path.certificates.isNotEmpty()) { "Certificate path must contain at least one certificate" }
|
||||
val target = path.certificates.last() as X509Certificate
|
||||
require(target.publicKey == anonymousParty.owningKey) { "Certificate path must end with anonymous party's public key" }
|
||||
val validator = CertPathValidator.getInstance("PKIX")
|
||||
val validatorParameters = PKIXParameters(setOf(expectedTrustAnchor)).apply {
|
||||
isRevocationEnabled = false
|
||||
}
|
||||
val result = validator.validate(path, validatorParameters) as PKIXCertPathValidatorResult
|
||||
require(result.trustAnchor == expectedTrustAnchor)
|
||||
require(result.publicKey == anonymousParty.owningKey)
|
||||
|
||||
partyToPath[anonymousParty] = path
|
||||
}
|
||||
}
|
||||
|
@ -1,23 +1,26 @@
|
||||
package net.corda.node.services.network
|
||||
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.crypto.CertificateAndKey
|
||||
import net.corda.core.crypto.X509Utilities
|
||||
import net.corda.core.crypto.generateKeyPair
|
||||
import net.corda.node.services.identity.InMemoryIdentityService
|
||||
import net.corda.core.identity.AnonymousParty
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.node.services.IdentityService
|
||||
import net.corda.core.utilities.ALICE
|
||||
import net.corda.core.utilities.BOB
|
||||
import net.corda.node.services.identity.InMemoryIdentityService
|
||||
import net.corda.testing.ALICE_PUBKEY
|
||||
import net.corda.testing.BOB_PUBKEY
|
||||
import org.bouncycastle.asn1.x500.X500Name
|
||||
import org.junit.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFailsWith
|
||||
import kotlin.test.assertNull
|
||||
|
||||
/**
|
||||
* Tests for the in memory identity service.
|
||||
*/
|
||||
class InMemoryIdentityServiceTests {
|
||||
|
||||
@Test
|
||||
fun `get all identities`() {
|
||||
val service = InMemoryIdentityService()
|
||||
@ -58,4 +61,54 @@ class InMemoryIdentityServiceTests {
|
||||
identities.forEach { service.registerIdentity(it) }
|
||||
identities.forEach { assertEquals(it, service.partyFromX500Name(it.name)) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a certificate path from a root CA, down to a transaction key, store and verify the association.
|
||||
*/
|
||||
@Test
|
||||
fun `assert unknown anonymous key is unrecognised`() {
|
||||
val rootCertAndKey = X509Utilities.createSelfSignedCACert(ALICE.name)
|
||||
val txCertAndKey = X509Utilities.createIntermediateCert(ALICE.name, rootCertAndKey)
|
||||
val service = InMemoryIdentityService()
|
||||
val rootKey = rootCertAndKey.keyPair
|
||||
// TODO: Generate certificate with an EdDSA key rather than ECDSA
|
||||
val identity = Party(rootCertAndKey)
|
||||
val txIdentity = AnonymousParty(txCertAndKey.keyPair.public)
|
||||
|
||||
assertFailsWith<IdentityService.UnknownAnonymousPartyException> {
|
||||
service.assertOwnership(identity, txIdentity)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a pair of certificate paths from a root CA, down to a transaction key, store and verify the associations.
|
||||
* Also checks that incorrect associations are rejected.
|
||||
*/
|
||||
@Test
|
||||
fun `assert ownership`() {
|
||||
val aliceRootCertAndKey = X509Utilities.createSelfSignedCACert(ALICE.name)
|
||||
val aliceTxCertAndKey = X509Utilities.createIntermediateCert(ALICE.name, aliceRootCertAndKey)
|
||||
val aliceCertPath = X509Utilities.createCertificatePath(aliceRootCertAndKey, aliceTxCertAndKey, false).certPath
|
||||
val bobRootCertAndKey = X509Utilities.createSelfSignedCACert(BOB.name)
|
||||
val bobTxCertAndKey = X509Utilities.createIntermediateCert(BOB.name, bobRootCertAndKey)
|
||||
val bobCertPath = X509Utilities.createCertificatePath(bobRootCertAndKey, bobTxCertAndKey, false).certPath
|
||||
val service = InMemoryIdentityService()
|
||||
val alice = Party(aliceRootCertAndKey)
|
||||
val anonymousAlice = AnonymousParty(aliceTxCertAndKey.keyPair.public)
|
||||
val bob = Party(bobRootCertAndKey)
|
||||
val anonymousBob = AnonymousParty(bobTxCertAndKey.keyPair.public)
|
||||
|
||||
service.registerPath(aliceRootCertAndKey.certificate, anonymousAlice, aliceCertPath)
|
||||
service.registerPath(bobRootCertAndKey.certificate, anonymousBob, bobCertPath)
|
||||
|
||||
// Verify that paths are verified
|
||||
service.assertOwnership(alice, anonymousAlice)
|
||||
service.assertOwnership(bob, anonymousBob)
|
||||
assertFailsWith<IllegalArgumentException> {
|
||||
service.assertOwnership(alice, anonymousBob)
|
||||
}
|
||||
assertFailsWith<IllegalArgumentException> {
|
||||
service.assertOwnership(bob, anonymousAlice)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user