diff --git a/node/src/integration-test/kotlin/net/corda/node/NodeKeystoreCheckTest.kt b/node/src/integration-test/kotlin/net/corda/node/NodeKeystoreCheckTest.kt new file mode 100644 index 0000000000..9ea387ec9e --- /dev/null +++ b/node/src/integration-test/kotlin/net/corda/node/NodeKeystoreCheckTest.kt @@ -0,0 +1,63 @@ +package net.corda.node + +import net.corda.core.crypto.Crypto +import net.corda.core.identity.CordaX500Name +import net.corda.core.internal.cert +import net.corda.core.internal.div +import net.corda.core.utilities.getOrThrow +import net.corda.node.services.config.configureDevKeyAndTrustStores +import net.corda.nodeapi.config.SSLConfiguration +import net.corda.nodeapi.internal.crypto.* +import net.corda.testing.ALICE_NAME +import net.corda.testing.driver.driver +import org.junit.Test +import java.nio.file.Path +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith +import kotlin.test.assertTrue + +class NodeKeystoreCheckTest { + @Test + fun `node should throw exception if cert path doesn't chain to the trust root`() { + driver(startNodesInProcess = true) { + // This will fail because there are no keystore configured. + assertFailsWith(IllegalArgumentException::class) { + startNode(customOverrides = mapOf("devMode" to false)).getOrThrow() + }.apply { + assertTrue(message?.startsWith("Identity certificate not found. ") ?: false) + } + + // Create keystores + val keystorePassword = "password" + val config = object : SSLConfiguration { + override val keyStorePassword: String = keystorePassword + override val trustStorePassword: String = keystorePassword + override val certificatesDirectory: Path = baseDirectory(ALICE_NAME.toString()) / "certificates" + } + config.configureDevKeyAndTrustStores(ALICE_NAME) + + // This should pass with correct keystore. + val node = startNode(providedName = ALICE_NAME, customOverrides = mapOf("devMode" to false, + "keyStorePassword" to keystorePassword, + "trustStorePassword" to keystorePassword)).get() + node.stop() + + // Fiddle with node keystore. + val keystore = loadKeyStore(config.nodeKeystore, config.keyStorePassword) + + // Self signed root + val badRootKeyPair = Crypto.generateKeyPair() + val badRoot = X509Utilities.createSelfSignedCACertificate(CordaX500Name("Bad Root", "Lodnon", "GB"), badRootKeyPair) + val nodeCA = keystore.getCertificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA, config.keyStorePassword) + val badNodeCACert = X509Utilities.createCertificate(CertificateType.CLIENT_CA, badRoot, badRootKeyPair, ALICE_NAME, nodeCA.keyPair.public) + keystore.setKeyEntry(X509Utilities.CORDA_CLIENT_CA, nodeCA.keyPair.private, config.keyStorePassword.toCharArray(), arrayOf(badNodeCACert.cert, badRoot.cert)) + keystore.save(config.nodeKeystore, config.keyStorePassword) + + assertFailsWith(IllegalArgumentException::class) { + startNode(providedName = ALICE_NAME, customOverrides = mapOf("devMode" to false)).getOrThrow() + }.apply { + assertEquals("Client CA certificate must chain to the trusted root.", message) + } + } + } +} 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 81766c37d6..9699318f73 100644 --- a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt @@ -567,6 +567,17 @@ abstract class AbstractNode(val configuration: NodeConfiguration, "or if you don't have one yet, fill out the config file and run corda.jar --initial-registration. " + "Read more at: https://docs.corda.net/permissioning.html" } + + // Check all cert path chain to the trusted root + val sslKeystore = loadKeyStore(configuration.sslKeystore, configuration.keyStorePassword) + val identitiesKeystore = loadKeyStore(configuration.nodeKeystore, configuration.keyStorePassword) + val trustStore = loadKeyStore(configuration.trustStoreFile, configuration.trustStorePassword) + val sslRoot = sslKeystore.getCertificateChain(X509Utilities.CORDA_CLIENT_TLS).last() + val clientCARoot = identitiesKeystore.getCertificateChain(X509Utilities.CORDA_CLIENT_CA).last() + val trustRoot = trustStore.getCertificate(X509Utilities.CORDA_ROOT_CA) + + require(sslRoot == trustRoot) { "TLS certificate must chain to the trusted root." } + require(clientCARoot == trustRoot) { "Client CA certificate must chain to the trusted root." } } // Specific class so that MockNode can catch it. diff --git a/node/src/main/kotlin/net/corda/node/services/config/ConfigUtilities.kt b/node/src/main/kotlin/net/corda/node/services/config/ConfigUtilities.kt index 0582ce8768..92b113075a 100644 --- a/node/src/main/kotlin/net/corda/node/services/config/ConfigUtilities.kt +++ b/node/src/main/kotlin/net/corda/node/services/config/ConfigUtilities.kt @@ -50,7 +50,7 @@ fun NodeConfiguration.configureWithDevSSLCertificate() = configureDevKeyAndTrust fun SSLConfiguration.configureDevKeyAndTrustStores(myLegalName: CordaX500Name) { certificatesDirectory.createDirectories() if (!trustStoreFile.exists()) { - javaClass.classLoader.getResourceAsStream("net/corda/node/internal/certificates/cordatruststore.jks").copyTo(trustStoreFile) + loadKeyStore(javaClass.classLoader.getResourceAsStream("net/corda/node/internal/certificates/cordatruststore.jks"), "trustpass").save(trustStoreFile, trustStorePassword) } if (!sslKeystore.exists() || !nodeKeystore.exists()) { val caKeyStore = loadKeyStore(javaClass.classLoader.getResourceAsStream("net/corda/node/internal/certificates/cordadevcakeys.jks"), "cordacadevpass") 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 81cb4f75fb..e3f3cc233f 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 @@ -45,18 +45,17 @@ class InMemoryIdentityService(identities: Iterable, principalToParties.putAll(identities.associateBy { it.name }) } - // TODO: Check the certificate validation logic @Throws(CertificateExpiredException::class, CertificateNotYetValidException::class, InvalidAlgorithmParameterException::class) override fun verifyAndRegisterIdentity(identity: PartyAndCertificate): PartyAndCertificate? { // Validate the chain first, before we do anything clever with it try { identity.verify(trustAnchor) } catch (e: CertPathValidatorException) { - log.error("Certificate validation failed for ${identity.name} against trusted root ${trustAnchor.trustedCert.subjectX500Principal}.") - log.error("Certificate path :") + log.warn("Certificate validation failed for ${identity.name} against trusted root ${trustAnchor.trustedCert.subjectX500Principal}.") + log.warn("Certificate path :") identity.certPath.certificates.reversed().forEachIndexed { index, certificate -> val space = (0 until index).joinToString("") { " " } - log.error("$space${certificate.toX509CertHolder().subject}") + log.warn("$space${certificate.toX509CertHolder().subject}") } throw e } diff --git a/node/src/main/kotlin/net/corda/node/services/identity/PersistentIdentityService.kt b/node/src/main/kotlin/net/corda/node/services/identity/PersistentIdentityService.kt index 66a5ea4024..f26989e7bd 100644 --- a/node/src/main/kotlin/net/corda/node/services/identity/PersistentIdentityService.kt +++ b/node/src/main/kotlin/net/corda/node/services/identity/PersistentIdentityService.kt @@ -110,17 +110,16 @@ class PersistentIdentityService(override val trustRoot: X509Certificate, } } - // TODO: Check the certificate validation logic @Throws(CertificateExpiredException::class, CertificateNotYetValidException::class, InvalidAlgorithmParameterException::class) override fun verifyAndRegisterIdentity(identity: PartyAndCertificate): PartyAndCertificate? { // Validate the chain first, before we do anything clever with it try { identity.verify(trustAnchor) } catch (e: CertPathValidatorException) { - log.error(e.localizedMessage) - log.error("Path = ") + log.warn(e.localizedMessage) + log.warn("Path = ") identity.certPath.certificates.reversed().forEach { - log.error(it.toX509CertHolder().subject.toString()) + log.warn(it.toX509CertHolder().subject.toString()) } throw e } diff --git a/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapCache.kt b/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapCache.kt index 3efb461a81..aae71515ea 100644 --- a/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapCache.kt +++ b/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapCache.kt @@ -20,6 +20,7 @@ import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.core.serialization.serialize import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.contextLogger +import net.corda.core.utilities.loggerFor import net.corda.node.services.api.NetworkMapCacheBaseInternal import net.corda.node.services.api.NetworkMapCacheInternal import net.corda.nodeapi.internal.persistence.CordaPersistence @@ -37,13 +38,22 @@ class NetworkMapCacheImpl( networkMapCacheBase: NetworkMapCacheBaseInternal, private val identityService: IdentityService ) : NetworkMapCacheBaseInternal by networkMapCacheBase, NetworkMapCacheInternal { + companion object { + private val logger = loggerFor() + } + init { networkMapCacheBase.allNodes.forEach { it.legalIdentitiesAndCerts.forEach { identityService.verifyAndRegisterIdentity(it) } } networkMapCacheBase.changed.subscribe { mapChange -> // TODO how should we handle network map removal if (mapChange is MapChange.Added) { mapChange.node.legalIdentitiesAndCerts.forEach { - identityService.verifyAndRegisterIdentity(it) + try { + identityService.verifyAndRegisterIdentity(it) + } catch (ignore: Exception) { + // Log a warning to indicate node info is not added to the network map cache. + logger.warn("Node info for :'${it.name}' is not added to the network map due to verification error.") + } } } } diff --git a/node/src/test/kotlin/net/corda/node/services/network/NetworkMapCacheTest.kt b/node/src/test/kotlin/net/corda/node/services/network/NetworkMapCacheTest.kt index 861742ff2a..0597bfbd40 100644 --- a/node/src/test/kotlin/net/corda/node/services/network/NetworkMapCacheTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/network/NetworkMapCacheTest.kt @@ -3,7 +3,6 @@ package net.corda.node.services.network import net.corda.core.node.services.NetworkMapCache import net.corda.testing.ALICE_NAME import net.corda.testing.BOB_NAME -import net.corda.testing.DUMMY_NOTARY import net.corda.testing.node.MockNetwork import net.corda.testing.node.MockNodeParameters import net.corda.testing.singleIdentity