diff --git a/core/src/main/kotlin/net/corda/core/node/services/IdentityService.kt b/core/src/main/kotlin/net/corda/core/node/services/IdentityService.kt index ce5d5f9262..3e35feb6f2 100644 --- a/core/src/main/kotlin/net/corda/core/node/services/IdentityService.kt +++ b/core/src/main/kotlin/net/corda/core/node/services/IdentityService.kt @@ -2,13 +2,11 @@ package net.corda.core.node.services import net.corda.core.contracts.PartyAndReference import net.corda.core.identity.* -import net.corda.core.node.NodeInfo import org.bouncycastle.asn1.x500.X500Name +import org.bouncycastle.cert.X509CertificateHolder import java.security.InvalidAlgorithmParameterException import java.security.PublicKey -import java.security.cert.CertPath -import java.security.cert.CertificateExpiredException -import java.security.cert.CertificateNotYetValidException +import java.security.cert.* /** * An identity service maintains a directory of parties by their associated distinguished name/public keys and thus @@ -16,6 +14,10 @@ import java.security.cert.CertificateNotYetValidException * identities back to the well known identity (i.e. the identity in the network map) of a party. */ interface IdentityService { + val trustRoot: X509Certificate + val trustRootHolder: X509CertificateHolder + val caCertStore: CertStore + /** * Verify and then store a well known identity. * diff --git a/core/src/main/kotlin/net/corda/core/serialization/DefaultKryoCustomizer.kt b/core/src/main/kotlin/net/corda/core/serialization/DefaultKryoCustomizer.kt index a88e2ec073..7963fb549e 100644 --- a/core/src/main/kotlin/net/corda/core/serialization/DefaultKryoCustomizer.kt +++ b/core/src/main/kotlin/net/corda/core/serialization/DefaultKryoCustomizer.kt @@ -73,6 +73,7 @@ object DefaultKryoCustomizer { noReferencesWithin() + register(sun.security.ec.ECPublicKeyImpl::class.java, PublicKeySerializer) register(EdDSAPublicKey::class.java, Ed25519PublicKeySerializer) register(EdDSAPrivateKey::class.java, Ed25519PrivateKeySerializer) diff --git a/node/src/integration-test/kotlin/net/corda/services/messaging/P2PSecurityTest.kt b/node/src/integration-test/kotlin/net/corda/services/messaging/P2PSecurityTest.kt index a0eaea5923..fa9c503c3f 100644 --- a/node/src/integration-test/kotlin/net/corda/services/messaging/P2PSecurityTest.kt +++ b/node/src/integration-test/kotlin/net/corda/services/messaging/P2PSecurityTest.kt @@ -2,14 +2,12 @@ package net.corda.services.messaging import com.google.common.util.concurrent.ListenableFuture import net.corda.core.crypto.X509Utilities +import net.corda.core.crypto.cert import net.corda.core.getOrThrow import net.corda.core.node.NodeInfo import net.corda.core.random63BitValue import net.corda.core.seconds -import net.corda.core.utilities.BOB -import net.corda.core.utilities.DUMMY_BANK_A -import net.corda.core.utilities.DUMMY_BANK_B -import net.corda.core.utilities.getTestPartyAndCertificate +import net.corda.core.utilities.* import net.corda.node.internal.NetworkMapInfo import net.corda.node.services.config.configureWithDevSSLCertificate import net.corda.node.services.messaging.sendRequest @@ -24,8 +22,8 @@ import net.corda.testing.node.SimpleNode import org.assertj.core.api.Assertions.assertThatExceptionOfType import org.assertj.core.api.Assertions.assertThatThrownBy import org.bouncycastle.asn1.x500.X500Name -import org.bouncycastle.cert.X509CertificateHolder import org.junit.Test +import java.security.cert.X509Certificate import java.time.Instant import java.util.concurrent.TimeoutException @@ -46,7 +44,7 @@ class P2PSecurityTest : NodeBasedTest() { @Test fun `register with the network map service using a legal name different from the TLS CN`() { - startSimpleNode(DUMMY_BANK_A.name).use { + startSimpleNode(DUMMY_BANK_A.name, DUMMY_CA.certificate.cert).use { // Register with the network map using a different legal name val response = it.registerWithNetworkMap(DUMMY_BANK_B.name) // We don't expect a response because the network map's host verification will prevent a connection back @@ -58,7 +56,7 @@ class P2PSecurityTest : NodeBasedTest() { } private fun startSimpleNode(legalName: X500Name, - trustRoot: X509CertificateHolder? = null): SimpleNode { + trustRoot: X509Certificate): SimpleNode { val config = TestNodeConfiguration( baseDirectory = baseDirectory(legalName), myLegalName = legalName, diff --git a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt index c0d7053e62..74c6a7ef81 100644 --- a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt @@ -221,7 +221,8 @@ abstract class AbstractNode(open val configuration: NodeConfiguration, // Do all of this in a database transaction so anything that might need a connection has one. initialiseDatabasePersistence { - val tokenizableServices = makeServices() + val keyStoreWrapper = KeyStoreWrapper(configuration.trustStoreFile, configuration.trustStorePassword) + val tokenizableServices = makeServices(keyStoreWrapper) smm = StateMachineManager(services, checkpointStorage, @@ -452,7 +453,8 @@ abstract class AbstractNode(open val configuration: NodeConfiguration, * Builds node internal, advertised, and plugin services. * Returns a list of tokenizable services to be added to the serialisation context. */ - private fun makeServices(): MutableList { + private fun makeServices(keyStoreWrapper: KeyStoreWrapper): MutableList { + val keyStore = keyStoreWrapper.keyStore val storageServices = initialiseStorageService(configuration.baseDirectory) storage = storageServices.first checkpointStorage = storageServices.second @@ -465,7 +467,9 @@ abstract class AbstractNode(open val configuration: NodeConfiguration, auditService = DummyAuditService() info = makeInfo() - identity = makeIdentityService() + identity = makeIdentityService(keyStore.getCertificate(X509Utilities.CORDA_ROOT_CA)!! as X509Certificate, + keyStoreWrapper.certificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA), + info.legalIdentityAndCert) // Place the long term identity key in the KMS. Eventually, this is likely going to be separated again because // the KMS is meant for derived temporary keys used in transactions, and we're not supposed to sign things with // the identity key. But the infrastructure to make that easy isn't here yet. @@ -701,10 +705,13 @@ abstract class AbstractNode(open val configuration: NodeConfiguration, protected abstract fun makeUniquenessProvider(type: ServiceType): UniquenessProvider - protected open fun makeIdentityService(): IdentityService { - val keyStore = KeyStoreUtilities.loadKeyStore(configuration.trustStoreFile, configuration.trustStorePassword) - val trustRoot = keyStore.getCertificate(X509Utilities.CORDA_ROOT_CA) as? X509Certificate - val service = InMemoryIdentityService(setOf(info.legalIdentityAndCert), trustRoot = trustRoot) + protected open fun makeIdentityService(trustRoot: X509Certificate, + clientCa: CertificateAndKeyPair?, + legalIdentity: PartyAndCertificate): IdentityService { + val caCertificates: Array = listOf(legalIdentity.certificate.cert, clientCa?.certificate?.cert) + .filterNotNull() + .toTypedArray() + val service = InMemoryIdentityService(setOf(info.legalIdentityAndCert), trustRoot = trustRoot, caCertificates = *caCertificates) services.networkMapCache.partyNodes.forEach { service.registerIdentity(it.legalIdentityAndCert) } netMapCache.changed.subscribe { mapChange -> // TODO how should we handle network map removal 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 ff8553bc84..4438f2e9c5 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 @@ -31,22 +31,30 @@ import kotlin.collections.ArrayList * @param certPaths initial set of certificate paths for the service, typically only used for unit tests. */ @ThreadSafe -class InMemoryIdentityService(identities: Iterable, +class InMemoryIdentityService(identities: Iterable = emptySet(), certPaths: Map = emptyMap(), - val trustRoot: X509Certificate?) : SingletonSerializeAsToken(), IdentityService { + override val trustRoot: X509Certificate, + vararg caCertificates: X509Certificate) : SingletonSerializeAsToken(), IdentityService { constructor(identities: Iterable = emptySet(), certPaths: Map = emptyMap(), - trustRoot: X509CertificateHolder?) : this(identities, certPaths, trustRoot?.cert) + trustRoot: X509CertificateHolder) : this(identities, certPaths, trustRoot.cert) companion object { private val log = loggerFor() } - private val trustAnchor: TrustAnchor? = trustRoot?.let { cert -> TrustAnchor(cert, null) } + /** + * Certificate store for certificate authority and intermediary certificates. + */ + override val caCertStore: CertStore + override val trustRootHolder = X509CertificateHolder(trustRoot.encoded) + private val trustAnchor: TrustAnchor = TrustAnchor(trustRoot, null) private val keyToParties = ConcurrentHashMap() private val principalToParties = ConcurrentHashMap() private val partyToPath = ConcurrentHashMap() init { + val caCertificatesWithRoot: Set = caCertificates.toSet() + trustRoot + caCertStore = CertStore.getInstance("Collection", CollectionCertStoreParameters(caCertificatesWithRoot)) keyToParties.putAll(identities.associateBy { it.owningKey } ) principalToParties.putAll(identities.associateBy { it.name }) partyToPath.putAll(certPaths) @@ -57,7 +65,7 @@ class InMemoryIdentityService(identities: Iterable, override fun registerIdentity(party: PartyAndCertificate) { require(party.certPath.certificates.isNotEmpty()) { "Certificate path must contain at least one certificate" } // Validate the chain first, before we do anything clever with it - if (trustRoot != null) validateCertificatePath(party.party, party.certPath) + validateCertificatePath(party.party, party.certPath) log.trace { "Registering identity $party" } require(Arrays.equals(party.certificate.subjectPublicKeyInfo.encoded, party.owningKey.encoded)) { "Party certificate must end with party's public key" } @@ -122,7 +130,7 @@ class InMemoryIdentityService(identities: Iterable, 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 - if (trustRoot != null) validateCertificatePath(anonymousParty, path) + 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" } diff --git a/samples/irs-demo/src/main/resources/net/corda/irs/simulation/trade.json b/samples/irs-demo/src/main/resources/net/corda/irs/simulation/trade.json index ba8168b1a9..c08790043a 100644 --- a/samples/irs-demo/src/main/resources/net/corda/irs/simulation/trade.json +++ b/samples/irs-demo/src/main/resources/net/corda/irs/simulation/trade.json @@ -1,6 +1,6 @@ { "fixedLeg": { - "fixedRatePayer": "8Kqd4oWdx4KQAVcA8RDJXNvzFMvBkqWTZPhRtg9dSpNs6T6eZ4cGJWA7FWK", + "fixedRatePayer": "8Kqd4oWdx4KQGHGR7xcgpFf9JmP6HiXqTf85NpSgdSu431EGEhujA6ePaFD", "notional": "$25000000", "paymentFrequency": "SemiAnnual", "effectiveDate": "2016-03-11", @@ -22,7 +22,7 @@ "interestPeriodAdjustment": "Adjusted" }, "floatingLeg": { - "floatingRatePayer": "8Kqd4oWdx4KQAVc3Si48msuQrMJPGpA3TnGGuWTqXyoshjL25wzzdxtGyjq", + "floatingRatePayer": "8Kqd4oWdx4KQGHGJSFTX4kdZukmHohBRN3gvPekticL4eHTdmbJTVZNZJUj", "notional": { "quantity": 2500000000, "token": "USD" diff --git a/test-utils/src/main/kotlin/net/corda/testing/CoreTestUtils.kt b/test-utils/src/main/kotlin/net/corda/testing/CoreTestUtils.kt index 950c680422..1f7fb0d0db 100644 --- a/test-utils/src/main/kotlin/net/corda/testing/CoreTestUtils.kt +++ b/test-utils/src/main/kotlin/net/corda/testing/CoreTestUtils.kt @@ -5,11 +5,7 @@ package net.corda.testing import com.google.common.net.HostAndPort import net.corda.core.contracts.StateRef -import net.corda.core.crypto.SecureHash -import net.corda.core.crypto.X509Utilities -import net.corda.core.crypto.commonName -import net.corda.core.crypto.generateKeyPair -import net.corda.core.identity.AnonymousParty +import net.corda.core.crypto.* import net.corda.core.identity.Party import net.corda.core.identity.PartyAndCertificate import net.corda.core.node.ServiceHub @@ -33,7 +29,6 @@ import java.nio.file.Files import java.nio.file.Path import java.security.KeyPair import java.security.PublicKey -import java.security.cert.CertPath import java.util.* import java.util.concurrent.atomic.AtomicInteger @@ -89,7 +84,7 @@ val BIG_CORP_PARTY_REF = BIG_CORP.ref(OpaqueBytes.of(1)).reference val ALL_TEST_KEYS: List 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) +val MOCK_IDENTITY_SERVICE: IdentityService get() = InMemoryIdentityService(MOCK_IDENTITIES, emptyMap(), DUMMY_CA.certificate.cert) val MOCK_VERSION_INFO = VersionInfo(1, "Mock release", "Mock revision", "Mock Vendor") diff --git a/test-utils/src/main/kotlin/net/corda/testing/node/MockNode.kt b/test-utils/src/main/kotlin/net/corda/testing/node/MockNode.kt index 55ddd92b92..5980df134f 100644 --- a/test-utils/src/main/kotlin/net/corda/testing/node/MockNode.kt +++ b/test-utils/src/main/kotlin/net/corda/testing/node/MockNode.kt @@ -5,8 +5,9 @@ import com.google.common.jimfs.Jimfs import com.google.common.util.concurrent.Futures import com.google.common.util.concurrent.ListenableFuture import net.corda.core.* +import net.corda.core.crypto.CertificateAndKeyPair +import net.corda.core.crypto.cert import net.corda.core.crypto.entropyToKeyPair -import net.corda.flows.TxKeyFlow import net.corda.core.identity.PartyAndCertificate import net.corda.core.messaging.MessageRecipients import net.corda.core.messaging.RPCOps @@ -18,6 +19,7 @@ import net.corda.core.node.services.* import net.corda.core.utilities.DUMMY_NOTARY_KEY import net.corda.core.utilities.getTestPartyAndCertificate import net.corda.core.utilities.loggerFor +import net.corda.flows.TxKeyFlow import net.corda.node.internal.AbstractNode import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.identity.InMemoryIdentityService @@ -166,9 +168,14 @@ class MockNetwork(private val networkSendManuallyPumped: Boolean = false, .getOrThrow() } - // TODO: Specify a CA to validate registration against - override fun makeIdentityService(): IdentityService { - return InMemoryIdentityService((mockNet.identities + info.legalIdentityAndCert).toSet(), trustRoot = null as X509Certificate?) + override fun makeIdentityService(trustRoot: X509Certificate, + clientCa: CertificateAndKeyPair?, + legalIdentity: PartyAndCertificate): IdentityService { + val caCertificates: Array = listOf(legalIdentity.certificate.cert, clientCa?.certificate?.cert) + .filterNotNull() + .toTypedArray() + return InMemoryIdentityService((mockNet.identities + info.legalIdentityAndCert).toSet(), + trustRoot = trustRoot, caCertificates = *caCertificates) } override fun makeVaultService(dataSourceProperties: Properties): VaultService = NodeVaultService(services, dataSourceProperties) diff --git a/test-utils/src/main/kotlin/net/corda/testing/node/SimpleNode.kt b/test-utils/src/main/kotlin/net/corda/testing/node/SimpleNode.kt index 0104fd981c..70f66961ca 100644 --- a/test-utils/src/main/kotlin/net/corda/testing/node/SimpleNode.kt +++ b/test-utils/src/main/kotlin/net/corda/testing/node/SimpleNode.kt @@ -21,10 +21,10 @@ import net.corda.node.utilities.configureDatabase import net.corda.node.utilities.transaction import net.corda.testing.MOCK_VERSION_INFO import net.corda.testing.freeLocalHostAndPort -import org.bouncycastle.cert.X509CertificateHolder import org.jetbrains.exposed.sql.Database import java.io.Closeable import java.security.KeyPair +import java.security.cert.X509Certificate import kotlin.concurrent.thread /** @@ -33,7 +33,7 @@ import kotlin.concurrent.thread */ class SimpleNode(val config: NodeConfiguration, val address: HostAndPort = freeLocalHostAndPort(), rpcAddress: HostAndPort = freeLocalHostAndPort(), - trustRoot: X509CertificateHolder? = null) : AutoCloseable { + trustRoot: X509Certificate) : AutoCloseable { private val databaseWithCloseable: Pair = configureDatabase(config.dataSourceProperties) val database: Database get() = databaseWithCloseable.second