INFRA-505: Move integration tests to unit tests (#6530)

This commit is contained in:
Yiftach Kaplan 2020-08-06 15:16:27 +01:00 committed by GitHub
parent 02b71845bb
commit 849d51c8cd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 240 additions and 134 deletions

5
.gitignore vendored
View File

@ -103,4 +103,7 @@ virtualenv/
# Files you may find useful to have in your working directory. # Files you may find useful to have in your working directory.
PLAN PLAN
NOTES NOTES
TODO TODO
# gradle-dependx plugin
.dependx/

View File

@ -1,62 +0,0 @@
package net.corda.node
import net.corda.core.crypto.Crypto
import net.corda.core.internal.div
import net.corda.core.utilities.getOrThrow
import net.corda.node.services.config.configureDevKeyAndTrustStores
import net.corda.nodeapi.internal.crypto.CertificateType
import net.corda.nodeapi.internal.crypto.X509Utilities
import net.corda.testing.core.ALICE_NAME
import net.corda.testing.driver.DriverParameters
import net.corda.testing.driver.driver
import net.corda.coretesting.internal.stubs.CertificateStoreStubs
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.Test
import javax.security.auth.x500.X500Principal
class NodeKeystoreCheckTest {
@Test(timeout=300_000)
fun `starting node in non-dev mode with no key store`() {
driver(DriverParameters(startNodesInProcess = true, notarySpecs = emptyList(), cordappsForAllNodes = emptyList(), allowHibernateToManageAppSchema = false)) {
assertThatThrownBy {
startNode(customOverrides = mapOf("devMode" to false)).getOrThrow()
}.hasMessageContaining("One or more keyStores (identity or TLS) or trustStore not found.")
}
}
@Test(timeout=300_000)
fun `node should throw exception if cert path does not chain to the trust root`() {
driver(DriverParameters(startNodesInProcess = true, notarySpecs = emptyList(), cordappsForAllNodes = emptyList(), allowHibernateToManageAppSchema = false)) {
// Create keystores.
val keystorePassword = "password"
val certificatesDirectory = baseDirectory(ALICE_NAME) / "certificates"
val signingCertStore = CertificateStoreStubs.Signing.withCertificatesDirectory(certificatesDirectory, keystorePassword)
val p2pSslConfig = CertificateStoreStubs.P2P.withCertificatesDirectory(certificatesDirectory, keyStorePassword = keystorePassword, trustStorePassword = keystorePassword)
p2pSslConfig.configureDevKeyAndTrustStores(ALICE_NAME, signingCertStore, certificatesDirectory)
// This should pass with correct keystore.
val node = startNode(
providedName = ALICE_NAME,
customOverrides = mapOf("devMode" to false,
"keyStorePassword" to keystorePassword,
"trustStorePassword" to keystorePassword)
).getOrThrow()
node.stop()
// Fiddle with node keystore.
signingCertStore.get().update {
// Self signed root.
val badRootKeyPair = Crypto.generateKeyPair()
val badRoot = X509Utilities.createSelfSignedCACertificate(X500Principal("O=Bad Root,L=Lodnon,C=GB"), badRootKeyPair)
val nodeCA = getCertificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA, signingCertStore.entryPassword)
val badNodeCACert = X509Utilities.createCertificate(CertificateType.NODE_CA, badRoot, badRootKeyPair, ALICE_NAME.x500Principal, nodeCA.keyPair.public)
setPrivateKey(X509Utilities.CORDA_CLIENT_CA, nodeCA.keyPair.private, listOf(badNodeCACert, badRoot), signingCertStore.entryPassword)
}
assertThatThrownBy {
startNode(providedName = ALICE_NAME, customOverrides = mapOf("devMode" to false)).getOrThrow()
}.hasMessage("Client CA certificate must chain to the trusted root.")
}
}
}

View File

@ -94,7 +94,6 @@ import net.corda.node.services.api.VaultServiceInternal
import net.corda.node.services.api.WritableTransactionStorage import net.corda.node.services.api.WritableTransactionStorage
import net.corda.node.services.attachments.NodeAttachmentTrustCalculator import net.corda.node.services.attachments.NodeAttachmentTrustCalculator
import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.config.NodeConfiguration
import net.corda.node.services.config.configureWithDevSSLCertificate
import net.corda.node.services.config.rpc.NodeRpcOptions import net.corda.node.services.config.rpc.NodeRpcOptions
import net.corda.node.services.config.shell.determineUnsafeUsers import net.corda.node.services.config.shell.determineUnsafeUsers
import net.corda.node.services.config.shell.toShellConfig import net.corda.node.services.config.shell.toShellConfig
@ -149,8 +148,6 @@ import net.corda.nodeapi.internal.cordapp.CordappLoader
import net.corda.nodeapi.internal.crypto.CertificateType import net.corda.nodeapi.internal.crypto.CertificateType
import net.corda.nodeapi.internal.crypto.X509Utilities import net.corda.nodeapi.internal.crypto.X509Utilities
import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_CLIENT_CA import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_CLIENT_CA
import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_CLIENT_TLS
import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_ROOT_CA
import net.corda.nodeapi.internal.crypto.X509Utilities.DEFAULT_VALIDITY_WINDOW import net.corda.nodeapi.internal.crypto.X509Utilities.DEFAULT_VALIDITY_WINDOW
import net.corda.nodeapi.internal.crypto.X509Utilities.DISTRIBUTED_NOTARY_COMPOSITE_KEY_ALIAS import net.corda.nodeapi.internal.crypto.X509Utilities.DISTRIBUTED_NOTARY_COMPOSITE_KEY_ALIAS
import net.corda.nodeapi.internal.crypto.X509Utilities.DISTRIBUTED_NOTARY_KEY_ALIAS import net.corda.nodeapi.internal.crypto.X509Utilities.DISTRIBUTED_NOTARY_KEY_ALIAS
@ -176,10 +173,8 @@ import org.jolokia.jvmagent.JolokiaServer
import org.jolokia.jvmagent.JolokiaServerConfig import org.jolokia.jvmagent.JolokiaServerConfig
import org.slf4j.Logger import org.slf4j.Logger
import rx.Scheduler import rx.Scheduler
import java.io.IOException
import java.lang.reflect.InvocationTargetException import java.lang.reflect.InvocationTargetException
import java.security.KeyPair import java.security.KeyPair
import java.security.KeyStoreException
import java.security.cert.X509Certificate import java.security.cert.X509Certificate
import java.sql.Connection import java.sql.Connection
import java.sql.Savepoint import java.sql.Savepoint
@ -434,18 +429,6 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
return proxies.fold(ops) { delegate, decorate -> decorate(delegate) } return proxies.fold(ops) { delegate, decorate -> decorate(delegate) }
} }
private fun initKeyStores(): X509Certificate {
if (configuration.devMode) {
configuration.configureWithDevSSLCertificate(cryptoService)
// configureWithDevSSLCertificate is a devMode process that writes directly to keystore files, so
// we should re-synchronise BCCryptoService with the updated keystore file.
if (cryptoService is BCCryptoService) {
cryptoService.resyncKeystore()
}
}
return validateKeyStores()
}
private fun quasarExcludePackages(nodeConfiguration: NodeConfiguration) { private fun quasarExcludePackages(nodeConfiguration: NodeConfiguration) {
val quasarInstrumentor = Retransform.getInstrumentor() val quasarInstrumentor = Retransform.getInstrumentor()
@ -457,7 +440,7 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
open fun generateAndSaveNodeInfo(): NodeInfo { open fun generateAndSaveNodeInfo(): NodeInfo {
check(started == null) { "Node has already been started" } check(started == null) { "Node has already been started" }
log.info("Generating nodeInfo ...") log.info("Generating nodeInfo ...")
val trustRoot = initKeyStores() val trustRoot = configuration.initKeyStores(cryptoService)
startDatabase() startDatabase()
val (identity, identityKeyPair) = obtainIdentity() val (identity, identityKeyPair) = obtainIdentity()
val nodeCa = configuration.signingCertificateStore.get()[CORDA_CLIENT_CA] val nodeCa = configuration.signingCertificateStore.get()[CORDA_CLIENT_CA]
@ -497,7 +480,7 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
logVendorString(database, log) logVendorString(database, log)
if (allowHibernateToManageAppSchema) { if (allowHibernateToManageAppSchema) {
Node.printBasicNodeInfo("Initialising CorDapps to get schemas created by hibernate") Node.printBasicNodeInfo("Initialising CorDapps to get schemas created by hibernate")
val trustRoot = initKeyStores() val trustRoot = configuration.initKeyStores(cryptoService)
networkMapClient?.start(trustRoot) networkMapClient?.start(trustRoot)
val (netParams, signedNetParams) = NetworkParametersReader(trustRoot, networkMapClient, configuration.baseDirectory).read() val (netParams, signedNetParams) = NetworkParametersReader(trustRoot, networkMapClient, configuration.baseDirectory).read()
log.info("Loaded network parameters: $netParams") log.info("Loaded network parameters: $netParams")
@ -536,7 +519,7 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
nodeLifecycleEventsDistributor.distributeEvent(NodeLifecycleEvent.BeforeNodeStart(nodeServicesContext)) nodeLifecycleEventsDistributor.distributeEvent(NodeLifecycleEvent.BeforeNodeStart(nodeServicesContext))
log.info("Node starting up ...") log.info("Node starting up ...")
val trustRoot = initKeyStores() val trustRoot = configuration.initKeyStores(cryptoService)
initialiseJolokia() initialiseJolokia()
schemaService.mappedSchemasWarnings().forEach { schemaService.mappedSchemasWarnings().forEach {
@ -980,57 +963,6 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
@VisibleForTesting @VisibleForTesting
protected open fun acceptableLiveFiberCountOnStop(): Int = 0 protected open fun acceptableLiveFiberCountOnStop(): Int = 0
private fun getCertificateStores(): AllCertificateStores? {
return try {
// The following will throw IOException if key file not found or KeyStoreException if keystore password is incorrect.
val sslKeyStore = configuration.p2pSslOptions.keyStore.get()
val signingCertificateStore = configuration.signingCertificateStore.get()
val trustStore = configuration.p2pSslOptions.trustStore.get()
AllCertificateStores(trustStore, sslKeyStore, signingCertificateStore)
} catch (e: IOException) {
log.error("IO exception while trying to validate keystores and truststore", e)
null
}
}
private data class AllCertificateStores(val trustStore: CertificateStore, val sslKeyStore: CertificateStore, val identitiesKeyStore: CertificateStore)
private fun validateKeyStores(): X509Certificate {
// Step 1. Check trustStore, sslKeyStore and identitiesKeyStore exist.
val certStores = try {
requireNotNull(getCertificateStores()) {
"One or more keyStores (identity or TLS) or trustStore not found. " +
"Please either copy your existing keys and certificates from another node, " +
"or if you don't have one yet, fill out the config file and run corda.jar initial-registration."
}
} catch (e: KeyStoreException) {
throw IllegalArgumentException("At least one of the keystores or truststore passwords does not match configuration.")
}
// Step 2. Check that trustStore contains the correct key-alias entry.
require(CORDA_ROOT_CA in certStores.trustStore) {
"Alias for trustRoot key not found. Please ensure you have an updated trustStore file."
}
// Step 3. Check that tls keyStore contains the correct key-alias entry.
require(CORDA_CLIENT_TLS in certStores.sslKeyStore) {
"Alias for TLS key not found. Please ensure you have an updated TLS keyStore file."
}
// Step 4. Check that identity keyStores contain the correct key-alias entry for Node CA.
require(CORDA_CLIENT_CA in certStores.identitiesKeyStore) {
"Alias for Node CA key not found. Please ensure you have an updated identity keyStore file."
}
// Step 5. Check all cert paths chain to the trusted root.
val trustRoot = certStores.trustStore[CORDA_ROOT_CA]
val sslCertChainRoot = certStores.sslKeyStore.query { getCertificateChain(CORDA_CLIENT_TLS) }.last()
val nodeCaCertChainRoot = certStores.identitiesKeyStore.query { getCertificateChain(CORDA_CLIENT_CA) }.last()
require(sslCertChainRoot == trustRoot) { "TLS certificate must chain to the trusted root." }
require(nodeCaCertChainRoot == trustRoot) { "Client CA certificate must chain to the trusted root." }
return trustRoot
}
// Specific class so that MockNode can catch it. // Specific class so that MockNode can catch it.
class DatabaseConfigurationException(message: String) : CordaException(message) class DatabaseConfigurationException(message: String) : CordaException(message)

View File

@ -0,0 +1,76 @@
package net.corda.node.internal
import net.corda.core.utilities.loggerFor
import net.corda.node.services.config.NodeConfiguration
import net.corda.node.services.config.configureWithDevSSLCertificate
import net.corda.nodeapi.internal.config.CertificateStore
import net.corda.nodeapi.internal.crypto.X509Utilities
import net.corda.nodeapi.internal.cryptoservice.CryptoService
import net.corda.nodeapi.internal.cryptoservice.bouncycastle.BCCryptoService
import java.io.IOException
import java.security.KeyStoreException
import java.security.cert.X509Certificate
private data class AllCertificateStores(val trustStore: CertificateStore, val sslKeyStore: CertificateStore, val identitiesKeyStore: CertificateStore)
internal fun NodeConfiguration.initKeyStores(cryptoService: CryptoService): X509Certificate {
if (devMode) {
configureWithDevSSLCertificate(cryptoService)
// configureWithDevSSLCertificate is a devMode process that writes directly to keystore files, so
// we should re-synchronise BCCryptoService with the updated keystore file.
if (cryptoService is BCCryptoService) {
cryptoService.resyncKeystore()
}
}
return validateKeyStores()
}
private fun NodeConfiguration.validateKeyStores(): X509Certificate {
// Step 1. Check trustStore, sslKeyStore and identitiesKeyStore exist.
val certStores = try {
requireNotNull(getCertificateStores()) {
"One or more keyStores (identity or TLS) or trustStore not found. " +
"Please either copy your existing keys and certificates from another node, " +
"or if you don't have one yet, fill out the config file and run corda.jar initial-registration."
}
} catch (e: KeyStoreException) {
throw IllegalArgumentException("At least one of the keystores or truststore passwords does not match configuration.")
}
// Step 2. Check that trustStore contains the correct key-alias entry.
require(X509Utilities.CORDA_ROOT_CA in certStores.trustStore) {
"Alias for trustRoot key not found. Please ensure you have an updated trustStore file."
}
// Step 3. Check that tls keyStore contains the correct key-alias entry.
require(X509Utilities.CORDA_CLIENT_TLS in certStores.sslKeyStore) {
"Alias for TLS key not found. Please ensure you have an updated TLS keyStore file."
}
// Step 4. Check that identity keyStores contain the correct key-alias entry for Node CA.
require(X509Utilities.CORDA_CLIENT_CA in certStores.identitiesKeyStore) {
"Alias for Node CA key not found. Please ensure you have an updated identity keyStore file."
}
// Step 5. Check all cert paths chain to the trusted root.
val trustRoot = certStores.trustStore[X509Utilities.CORDA_ROOT_CA]
val sslCertChainRoot = certStores.sslKeyStore.query { getCertificateChain(X509Utilities.CORDA_CLIENT_TLS) }.last()
val nodeCaCertChainRoot = certStores.identitiesKeyStore.query { getCertificateChain(X509Utilities.CORDA_CLIENT_CA) }.last()
require(sslCertChainRoot == trustRoot) { "TLS certificate must chain to the trusted root." }
require(nodeCaCertChainRoot == trustRoot) { "Client CA certificate must chain to the trusted root." }
return trustRoot
}
private fun NodeConfiguration.getCertificateStores(): AllCertificateStores? {
return try {
// The following will throw IOException if key file not found or KeyStoreException if keystore password is incorrect.
val sslKeyStore = p2pSslOptions.keyStore.get()
val signingCertificateStore = signingCertificateStore.get()
val trustStore = p2pSslOptions.trustStore.get()
AllCertificateStores(trustStore, sslKeyStore, signingCertificateStore)
} catch (e: IOException) {
loggerFor<NodeConfiguration>().error("IO exception while trying to validate keystores and truststore", e)
null
}
}

View File

@ -0,0 +1,157 @@
package net.corda.node.internal
import com.nhaarman.mockito_kotlin.any
import com.nhaarman.mockito_kotlin.doAnswer
import com.nhaarman.mockito_kotlin.mock
import com.nhaarman.mockito_kotlin.verify
import com.nhaarman.mockito_kotlin.whenever
import net.corda.node.services.config.NodeConfiguration
import net.corda.nodeapi.internal.config.CertificateStore
import net.corda.nodeapi.internal.config.FileBasedCertificateStoreSupplier
import net.corda.nodeapi.internal.config.MutualSslConfiguration
import net.corda.nodeapi.internal.crypto.X509KeyStore
import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_CLIENT_CA
import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_CLIENT_TLS
import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_ROOT_CA
import net.corda.nodeapi.internal.cryptoservice.CryptoService
import net.corda.nodeapi.internal.cryptoservice.bouncycastle.BCCryptoService
import net.corda.testing.core.ALICE_NAME
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.Test
import java.io.IOException
import java.security.KeyStoreException
import java.security.cert.X509Certificate
class NodeKeyStoreUtilitiesTest {
@Test(timeout = 300_000)
fun `initializing key store in non-dev mode with no key store`() {
whenever(signingSupplier.get()).doAnswer { throw IOException() }
assertThatThrownBy {
config.initKeyStores(cryptoService)
}.hasMessageContaining("One or more keyStores (identity or TLS) or trustStore not found.")
}
@Test(timeout = 300_000)
fun `initializing key store in non-dev mode with invalid password`() {
whenever(signingSupplier.get()).doAnswer { throw KeyStoreException() }
assertThatThrownBy {
config.initKeyStores(cryptoService)
}.hasMessageContaining("At least one of the keystores or truststore passwords does not match configuration")
}
@Test(timeout = 300_000)
fun `initializing key store in non-dev mode without trusted root`() {
whenever(trustStore.contains(CORDA_ROOT_CA)).thenReturn(false)
assertThatThrownBy {
config.initKeyStores(cryptoService)
}.hasMessageContaining("Alias for trustRoot key not found. Please ensure you have an updated trustStore file")
}
@Test(timeout = 300_000)
fun `initializing key store in non-dev mode without alias for TLS key`() {
whenever(keyStore.contains(CORDA_CLIENT_TLS)).thenReturn(false)
assertThatThrownBy {
config.initKeyStores(cryptoService)
}.hasMessageContaining("Alias for TLS key not found. Please ensure you have an updated TLS keyStore file")
}
@Test(timeout = 300_000)
fun `initializing key store in non-dev mode without alias for node CA key`() {
whenever(signingStore.contains(CORDA_CLIENT_CA)).thenReturn(false)
assertThatThrownBy {
config.initKeyStores(cryptoService)
}.hasMessageContaining("Alias for Node CA key not found. Please ensure you have an updated identity keyStore file")
}
@Test(timeout = 300_000)
fun `initializing key store should throw exception if cert path does not chain to the trust root`() {
val untrustedRoot = mock<X509Certificate>()
whenever(signingStore.query(any<X509KeyStore.() -> List<X509Certificate>>())).thenReturn(mutableListOf(untrustedRoot))
assertThatThrownBy {
config.initKeyStores(cryptoService)
}.hasMessageContaining("Client CA certificate must chain to the trusted root")
}
@Test(timeout = 300_000)
fun `initializing key store should throw exception if TLS certificate does not chain to the trust root`() {
val untrustedRoot = mock<X509Certificate>()
whenever(keyStore.query(any<X509KeyStore.() -> List<X509Certificate>>())).thenReturn(mutableListOf(untrustedRoot))
assertThatThrownBy {
config.initKeyStores(cryptoService)
}.hasMessageContaining("TLS certificate must chain to the trusted root")
}
@Test(timeout = 300_000)
fun `initializing key store should return valid certificate if certificate is valid`() {
val certificate = config.initKeyStores(cryptoService)
assertThat(certificate).isEqualTo(trustRoot)
}
@Test(timeout = 300_000)
fun `initializing key store in dev mode check te supplier`() {
whenever(config.devMode).thenReturn(true)
whenever(config.myLegalName).thenReturn(ALICE_NAME)
whenever(config.certificatesDirectory).thenReturn(mock())
whenever(trustSupplier.getOptional()).thenReturn(mock())
whenever(keySupplier.getOptional()).thenReturn(mock())
whenever(signingSupplier.getOptional()).thenReturn(mock())
config.initKeyStores(cryptoService)
verify(signingSupplier).getOptional()
}
@Test(timeout = 300_000)
fun `initializing key store in dev mode with BCCryptoService call resyncKeystore`() {
val bCryptoService = mock<BCCryptoService>()
whenever(config.devMode).thenReturn(true)
whenever(config.myLegalName).thenReturn(ALICE_NAME)
whenever(config.certificatesDirectory).thenReturn(mock())
whenever(trustSupplier.getOptional()).thenReturn(mock())
whenever(keySupplier.getOptional()).thenReturn(mock())
whenever(signingSupplier.getOptional()).thenReturn(mock())
config.initKeyStores(bCryptoService)
verify(bCryptoService).resyncKeystore()
}
private val config = mock<NodeConfiguration>()
private val trustStore = mock<CertificateStore>()
private val signingStore = mock<CertificateStore>()
private val keyStore = mock<CertificateStore>()
private val sslOptions = mock<MutualSslConfiguration>()
private val trustSupplier = mock<FileBasedCertificateStoreSupplier>()
private val signingSupplier = mock<FileBasedCertificateStoreSupplier>()
private val keySupplier = mock<FileBasedCertificateStoreSupplier>()
private val trustRoot = mock<X509Certificate>()
private val cryptoService = mock<CryptoService>()
init {
whenever(config.devMode).thenReturn(false)
whenever(sslOptions.keyStore).thenReturn(keySupplier)
whenever(sslOptions.trustStore).thenReturn(trustSupplier)
whenever(config.signingCertificateStore).thenReturn(signingSupplier)
whenever(trustSupplier.get()).thenReturn(trustStore)
whenever(signingSupplier.get()).thenReturn(signingStore)
whenever(keySupplier.get()).thenReturn(keyStore)
whenever(trustStore.contains(CORDA_ROOT_CA)).thenReturn(true)
whenever(keyStore.contains(CORDA_CLIENT_TLS)).thenReturn(true)
whenever(signingStore.contains(CORDA_CLIENT_CA)).thenReturn(true)
whenever(config.p2pSslOptions).thenReturn(sslOptions)
whenever(trustStore[CORDA_ROOT_CA]).thenReturn(trustRoot)
whenever(signingStore.query(any<X509KeyStore.() -> List<X509Certificate>>())).thenReturn(mutableListOf(trustRoot))
whenever(keyStore.query(any<X509KeyStore.() -> List<X509Certificate>>())).thenReturn(mutableListOf(trustRoot))
}
}