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

3
.gitignore vendored
View File

@ -104,3 +104,6 @@ virtualenv/
PLAN
NOTES
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.attachments.NodeAttachmentTrustCalculator
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.shell.determineUnsafeUsers
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.X509Utilities
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.DISTRIBUTED_NOTARY_COMPOSITE_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.slf4j.Logger
import rx.Scheduler
import java.io.IOException
import java.lang.reflect.InvocationTargetException
import java.security.KeyPair
import java.security.KeyStoreException
import java.security.cert.X509Certificate
import java.sql.Connection
import java.sql.Savepoint
@ -434,18 +429,6 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
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) {
val quasarInstrumentor = Retransform.getInstrumentor()
@ -457,7 +440,7 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
open fun generateAndSaveNodeInfo(): NodeInfo {
check(started == null) { "Node has already been started" }
log.info("Generating nodeInfo ...")
val trustRoot = initKeyStores()
val trustRoot = configuration.initKeyStores(cryptoService)
startDatabase()
val (identity, identityKeyPair) = obtainIdentity()
val nodeCa = configuration.signingCertificateStore.get()[CORDA_CLIENT_CA]
@ -497,7 +480,7 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
logVendorString(database, log)
if (allowHibernateToManageAppSchema) {
Node.printBasicNodeInfo("Initialising CorDapps to get schemas created by hibernate")
val trustRoot = initKeyStores()
val trustRoot = configuration.initKeyStores(cryptoService)
networkMapClient?.start(trustRoot)
val (netParams, signedNetParams) = NetworkParametersReader(trustRoot, networkMapClient, configuration.baseDirectory).read()
log.info("Loaded network parameters: $netParams")
@ -536,7 +519,7 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
nodeLifecycleEventsDistributor.distributeEvent(NodeLifecycleEvent.BeforeNodeStart(nodeServicesContext))
log.info("Node starting up ...")
val trustRoot = initKeyStores()
val trustRoot = configuration.initKeyStores(cryptoService)
initialiseJolokia()
schemaService.mappedSchemasWarnings().forEach {
@ -980,57 +963,6 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
@VisibleForTesting
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.
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))
}
}