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:
Ross Nicoll
2017-05-04 15:34:25 +01:00
parent af7ba082a4
commit edfc4dd7d9
7 changed files with 181 additions and 8 deletions

View File

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

View File

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