diff --git a/core/src/main/kotlin/net/corda/core/crypto/X509Utilities.kt b/core/src/main/kotlin/net/corda/core/crypto/X509Utilities.kt index 47c6361b4c..50bf8a3f8d 100644 --- a/core/src/main/kotlin/net/corda/core/crypto/X509Utilities.kt +++ b/core/src/main/kotlin/net/corda/core/crypto/X509Utilities.kt @@ -295,5 +295,6 @@ enum class CertificateType(val keyUsage: KeyUsage, vararg val purposes: KeyPurpo INTERMEDIATE_CA(KeyUsage(KeyUsage.digitalSignature or KeyUsage.keyCertSign or KeyUsage.cRLSign), KeyPurposeId.id_kp_serverAuth, KeyPurposeId.id_kp_clientAuth, KeyPurposeId.anyExtendedKeyUsage, isCA = true), CLIENT_CA(KeyUsage(KeyUsage.digitalSignature or KeyUsage.keyCertSign or KeyUsage.cRLSign), KeyPurposeId.id_kp_serverAuth, KeyPurposeId.id_kp_clientAuth, KeyPurposeId.anyExtendedKeyUsage, isCA = true), TLS(KeyUsage(KeyUsage.digitalSignature or KeyUsage.keyEncipherment or KeyUsage.keyAgreement), KeyPurposeId.id_kp_serverAuth, KeyPurposeId.id_kp_clientAuth, KeyPurposeId.anyExtendedKeyUsage, isCA = false), - IDENTITY(KeyUsage(KeyUsage.digitalSignature), KeyPurposeId.id_kp_serverAuth, KeyPurposeId.id_kp_clientAuth, KeyPurposeId.anyExtendedKeyUsage, isCA = false) + // TODO: Identity certs should have only limited depth (i.e. 1) CA signing capability, with tight name constraints + IDENTITY(KeyUsage(KeyUsage.digitalSignature or KeyUsage.keyCertSign), KeyPurposeId.id_kp_serverAuth, KeyPurposeId.id_kp_clientAuth, KeyPurposeId.anyExtendedKeyUsage, isCA = true) } \ No newline at end of file diff --git a/core/src/main/kotlin/net/corda/core/utilities/TestConstants.kt b/core/src/main/kotlin/net/corda/core/utilities/TestConstants.kt index d7c81378fb..384fdcab4f 100644 --- a/core/src/main/kotlin/net/corda/core/utilities/TestConstants.kt +++ b/core/src/main/kotlin/net/corda/core/utilities/TestConstants.kt @@ -69,8 +69,8 @@ val DUMMY_CA: CertificateAndKeyPair by lazy { /** * Build a test party with a nonsense certificate authority for testing purposes. */ -fun getTestPartyAndCertificate(name: X500Name, publicKey: PublicKey): PartyAndCertificate { - val cert = X509Utilities.createCertificate(CertificateType.IDENTITY, DUMMY_CA.certificate, DUMMY_CA.keyPair, name, publicKey) - val certPath = X509Utilities.createCertificatePath(DUMMY_CA.certificate, cert, revocationEnabled = false) +fun getTestPartyAndCertificate(name: X500Name, publicKey: PublicKey, ca: CertificateAndKeyPair = DUMMY_CA): PartyAndCertificate { + val cert = X509Utilities.createCertificate(CertificateType.IDENTITY, ca.certificate, ca.keyPair, name, publicKey) + val certPath = X509Utilities.createCertificatePath(ca.certificate, cert, revocationEnabled = false) return PartyAndCertificate(name, publicKey, cert, certPath) } diff --git a/node/src/main/kotlin/net/corda/node/services/identity/InMemoryIdentityService.kt b/node/src/main/kotlin/net/corda/node/services/identity/InMemoryIdentityService.kt index fa76acf90e..95117a34ef 100644 --- a/node/src/main/kotlin/net/corda/node/services/identity/InMemoryIdentityService.kt +++ b/node/src/main/kotlin/net/corda/node/services/identity/InMemoryIdentityService.kt @@ -117,14 +117,12 @@ class InMemoryIdentityService(identities: Iterable, @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) - } + val root: X509Certificate = path.certificates + .filterIsInstance() + .lastOrNull { it.publicKey == party.owningKey } ?: throw IllegalArgumentException("Certificate path must include a certificate for the party public key.") // 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}\"" } + val target = path.certificates.first() as X509Certificate + require(target.publicKey == anonymousParty.owningKey) { "Certificate path starts with a certificate for the anonymous party" } } override fun pathForAnonymous(anonymousParty: AnonymousParty): CertPath? = partyToPath[anonymousParty] diff --git a/node/src/test/kotlin/net/corda/node/services/network/InMemoryIdentityServiceTests.kt b/node/src/test/kotlin/net/corda/node/services/network/InMemoryIdentityServiceTests.kt index e18309d578..6b4451975b 100644 --- a/node/src/test/kotlin/net/corda/node/services/network/InMemoryIdentityServiceTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/network/InMemoryIdentityServiceTests.kt @@ -6,14 +6,13 @@ import net.corda.core.identity.Party import net.corda.core.identity.PartyAndCertificate import net.corda.core.node.services.IdentityService import net.corda.core.utilities.* +import net.corda.flows.TxKeyFlow 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 java.security.KeyPair -import java.security.cert.CertPath -import java.security.cert.X509Certificate +import java.security.cert.CertificateFactory import kotlin.test.assertEquals import kotlin.test.assertFailsWith import kotlin.test.assertNull @@ -57,10 +56,11 @@ class InMemoryIdentityServiceTests { @Test fun `get identity by substring match`() { - val service = InMemoryIdentityService(trustRoot = DUMMY_CA.certificate) + val trustRoot = DUMMY_CA + val service = InMemoryIdentityService(trustRoot = trustRoot.certificate) service.registerIdentity(ALICE_IDENTITY) service.registerIdentity(BOB_IDENTITY) - val (_, _, alicente) = createParty(X500Name("O=Alicente Worldwide,L=London,C=UK")) + val alicente = getTestPartyAndCertificate(X500Name("O=Alicente Worldwide,L=London,C=UK"), generateKeyPair().public) service.registerIdentity(alicente) assertEquals(setOf(ALICE, alicente.party), service.partiesFromName("Alice", false)) assertEquals(setOf(ALICE), service.partiesFromName("Alice Corp", true)) @@ -101,41 +101,44 @@ class InMemoryIdentityServiceTests { */ @Test fun `assert ownership`() { - val (aliceTxKey, aliceCertPath, alice) = createParty(ALICE.name) + val trustRoot = DUMMY_CA + val (alice, aliceTxIdentity) = createParty(ALICE.name, trustRoot) + val certFactory = CertificateFactory.getInstance("X509") val bobRootKey = Crypto.generateKeyPair() - val bobRootCert = X509Utilities.createSelfSignedCACertificate(BOB.name, bobRootKey) + val bobRoot = getTestPartyAndCertificate(BOB.name, bobRootKey.public) + val bobRootCert = bobRoot.certificate val bobTxKey = Crypto.generateKeyPair() val bobTxCert = X509Utilities.createCertificate(CertificateType.IDENTITY, bobRootCert, bobRootKey, BOB.name, bobTxKey.public) - val bobCertPath = X509Utilities.createCertificatePath(bobRootCert, bobTxCert, revocationEnabled = false) + val bobCertPath = certFactory.generateCertPath(listOf(bobTxCert.cert, bobRootCert.cert)) val bob = PartyAndCertificate(BOB.name, bobRootKey.public, bobRootCert, bobCertPath) // Now we have identities, construct the service and let it know about both - val service = InMemoryIdentityService(setOf(alice, bob), emptyMap(), null as X509Certificate?) - val anonymousAlice = AnonymousParty(aliceTxKey.public) - service.registerAnonymousIdentity(anonymousAlice, alice.party, aliceCertPath) + val service = InMemoryIdentityService(setOf(alice, bob), emptyMap(), trustRoot.certificate.cert) + service.registerAnonymousIdentity(aliceTxIdentity.identity, alice.party, aliceTxIdentity.certPath) val anonymousBob = AnonymousParty(bobTxKey.public) service.registerAnonymousIdentity(anonymousBob, bob.party, bobCertPath) // Verify that paths are verified - service.assertOwnership(alice.party, anonymousAlice) + service.assertOwnership(alice.party, aliceTxIdentity.identity) service.assertOwnership(bob.party, anonymousBob) assertFailsWith { service.assertOwnership(alice.party, anonymousBob) } assertFailsWith { - service.assertOwnership(bob.party, anonymousAlice) + service.assertOwnership(bob.party, aliceTxIdentity.identity) } } - private fun createParty(x500Name: X500Name): Triple { - val rootKey = Crypto.generateKeyPair() - val rootCert = X509Utilities.createSelfSignedCACertificate(x500Name, rootKey) + private fun createParty(x500Name: X500Name, ca: CertificateAndKeyPair): Pair { + 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, rootCert, rootKey, x500Name, txKey.public) - val certPath = X509Utilities.createCertificatePath(rootCert, txCert, revocationEnabled = false) - return Triple(txKey, certPath, PartyAndCertificate(x500Name, rootKey.public, rootCert, certPath)) + 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, TxKeyFlow.AnonymousIdentity(txCertPath, txCert, AnonymousParty(txKey.public))) } /** @@ -144,7 +147,7 @@ class InMemoryIdentityServiceTests { @Test fun `deanonymising a well known identity`() { val expected = ALICE - val actual = InMemoryIdentityService(trustRoot = null).partyFromAnonymous(expected) + val actual = InMemoryIdentityService(trustRoot = DUMMY_CA.certificate).partyFromAnonymous(expected) assertEquals(expected, actual) } }