mirror of
https://github.com/corda/corda.git
synced 2025-01-18 02:39:51 +00:00
Optionally allow the node in production mode to accept Cordapps signed by dev Key (CORDA-1915) (#4133)
By default Cordaps build by corda-gradle-plugins are signed by Corda development key. In dev mode any key can be used to sign Cordapp JAR . In production node Corda dev keys were forbidden. This code change allows to opt-out by setting node option cordappSignerKeyFingerprintBlacklist=[] or specify more public keys to blacklist. The option is used in production only mode.
This commit is contained in:
parent
38078e3762
commit
1de56550b0
@ -239,6 +239,12 @@ absolute path to the node's base directory.
|
|||||||
.. _Dropwizard: https://metrics.dropwizard.io/3.2.3/manual/third-party.html
|
.. _Dropwizard: https://metrics.dropwizard.io/3.2.3/manual/third-party.html
|
||||||
.. _Introduction to New Relic for Java: https://docs.newrelic.com/docs/agents/java-agent/getting-started/introduction-new-relic-java
|
.. _Introduction to New Relic for Java: https://docs.newrelic.com/docs/agents/java-agent/getting-started/introduction-new-relic-java
|
||||||
|
|
||||||
|
:cordappSignerKeyFingerprintBlacklist: List of public keys fingerprints (SHA-256 of public key hash) not allowed as Cordapp JARs signers.
|
||||||
|
Node will not load Cordapps signed by those keys.
|
||||||
|
The option takes effect only in production mode and defaults to Corda development keys (``["56CA54E803CB87C8472EBD3FBC6A2F1876E814CEEBF74860BD46997F40729367",
|
||||||
|
"83088052AF16700457AE2C978A7D8AC38DD6A7C713539D00B897CD03A5E5D31D"]``), in development mode any key is allowed to sign Cordpapp JARs.
|
||||||
|
|
||||||
|
|
||||||
Examples
|
Examples
|
||||||
--------
|
--------
|
||||||
|
|
||||||
|
@ -2,8 +2,11 @@ package net.corda.nodeapi.internal
|
|||||||
|
|
||||||
import net.corda.core.crypto.Crypto
|
import net.corda.core.crypto.Crypto
|
||||||
import net.corda.core.crypto.Crypto.generateKeyPair
|
import net.corda.core.crypto.Crypto.generateKeyPair
|
||||||
|
import net.corda.core.crypto.SecureHash
|
||||||
|
import net.corda.core.crypto.sha256
|
||||||
import net.corda.core.identity.CordaX500Name
|
import net.corda.core.identity.CordaX500Name
|
||||||
import net.corda.core.identity.PartyAndCertificate
|
import net.corda.core.identity.PartyAndCertificate
|
||||||
|
import net.corda.core.internal.hash
|
||||||
import net.corda.core.internal.toX500Name
|
import net.corda.core.internal.toX500Name
|
||||||
import net.corda.nodeapi.internal.config.CertificateStore
|
import net.corda.nodeapi.internal.config.CertificateStore
|
||||||
import net.corda.nodeapi.internal.crypto.*
|
import net.corda.nodeapi.internal.crypto.*
|
||||||
@ -99,7 +102,7 @@ const val DEV_CA_TRUST_STORE_FILE: String = "cordatruststore.jks"
|
|||||||
const val DEV_CA_TRUST_STORE_PASS: String = "trustpass"
|
const val DEV_CA_TRUST_STORE_PASS: String = "trustpass"
|
||||||
const val DEV_CA_TRUST_STORE_PRIVATE_KEY_PASS: String = "trustpasskeypass"
|
const val DEV_CA_TRUST_STORE_PRIVATE_KEY_PASS: String = "trustpasskeypass"
|
||||||
|
|
||||||
val DEV_CERTIFICATES: List<X509Certificate> get() = listOf(DEV_INTERMEDIATE_CA.certificate, DEV_ROOT_CA.certificate)
|
val DEV_PUB_KEY_HASHES: List<SecureHash.SHA256> get() = listOf(DEV_INTERMEDIATE_CA.certificate, DEV_ROOT_CA.certificate).map { it.publicKey.hash.sha256() }
|
||||||
|
|
||||||
// We need a class so that we can get hold of the class loader
|
// We need a class so that we can get hold of the class loader
|
||||||
internal object DevCaHelper {
|
internal object DevCaHelper {
|
||||||
|
@ -9,6 +9,7 @@ import net.corda.confidential.SwapIdentitiesHandler
|
|||||||
import net.corda.core.CordaException
|
import net.corda.core.CordaException
|
||||||
import net.corda.core.concurrent.CordaFuture
|
import net.corda.core.concurrent.CordaFuture
|
||||||
import net.corda.core.context.InvocationContext
|
import net.corda.core.context.InvocationContext
|
||||||
|
import net.corda.core.crypto.SecureHash
|
||||||
import net.corda.core.crypto.newSecureRandom
|
import net.corda.core.crypto.newSecureRandom
|
||||||
import net.corda.core.crypto.sign
|
import net.corda.core.crypto.sign
|
||||||
import net.corda.core.flows.*
|
import net.corda.core.flows.*
|
||||||
@ -48,7 +49,6 @@ import net.corda.node.services.FinalityHandler
|
|||||||
import net.corda.node.services.NotaryChangeHandler
|
import net.corda.node.services.NotaryChangeHandler
|
||||||
import net.corda.node.services.api.*
|
import net.corda.node.services.api.*
|
||||||
import net.corda.node.services.config.NodeConfiguration
|
import net.corda.node.services.config.NodeConfiguration
|
||||||
import net.corda.node.services.config.NotaryConfig
|
|
||||||
import net.corda.node.services.config.configureWithDevSSLCertificate
|
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.toShellConfig
|
import net.corda.node.services.config.shell.toShellConfig
|
||||||
@ -72,7 +72,6 @@ import net.corda.node.services.transactions.SimpleNotaryService
|
|||||||
import net.corda.node.services.upgrade.ContractUpgradeServiceImpl
|
import net.corda.node.services.upgrade.ContractUpgradeServiceImpl
|
||||||
import net.corda.node.services.vault.NodeVaultService
|
import net.corda.node.services.vault.NodeVaultService
|
||||||
import net.corda.node.utilities.*
|
import net.corda.node.utilities.*
|
||||||
import net.corda.nodeapi.internal.DEV_CERTIFICATES
|
|
||||||
import net.corda.nodeapi.internal.NodeInfoAndSigned
|
import net.corda.nodeapi.internal.NodeInfoAndSigned
|
||||||
import net.corda.nodeapi.internal.SignedNodeInfo
|
import net.corda.nodeapi.internal.SignedNodeInfo
|
||||||
import net.corda.nodeapi.internal.config.CertificateStore
|
import net.corda.nodeapi.internal.config.CertificateStore
|
||||||
@ -514,12 +513,20 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
|
|||||||
// CorDapp will be generated.
|
// CorDapp will be generated.
|
||||||
generatedCordapps += VirtualCordapp.generateSimpleNotaryCordapp(versionInfo)
|
generatedCordapps += VirtualCordapp.generateSimpleNotaryCordapp(versionInfo)
|
||||||
}
|
}
|
||||||
val blacklistedCerts = if (configuration.devMode) emptyList() else DEV_CERTIFICATES
|
val blacklistedKeys = if (configuration.devMode) emptyList()
|
||||||
|
else configuration.cordappSignerKeyFingerprintBlacklist.mapNotNull {
|
||||||
|
try {
|
||||||
|
SecureHash.parse(it)
|
||||||
|
} catch (e: IllegalArgumentException) {
|
||||||
|
log.error("Error while adding key fingerprint $it to cordappSignerKeyFingerprintBlacklist due to ${e.message}", e)
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
return JarScanningCordappLoader.fromDirectories(
|
return JarScanningCordappLoader.fromDirectories(
|
||||||
configuration.cordappDirectories,
|
configuration.cordappDirectories,
|
||||||
versionInfo,
|
versionInfo,
|
||||||
extraCordapps = generatedCordapps,
|
extraCordapps = generatedCordapps,
|
||||||
blacklistedCerts = blacklistedCerts
|
signerKeyFingerprintBlacklist = blacklistedKeys
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,7 +26,6 @@ import java.lang.reflect.Modifier
|
|||||||
import java.net.URL
|
import java.net.URL
|
||||||
import java.net.URLClassLoader
|
import java.net.URLClassLoader
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import java.security.cert.X509Certificate
|
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.jar.JarInputStream
|
import java.util.jar.JarInputStream
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
@ -40,7 +39,7 @@ import kotlin.streams.toList
|
|||||||
class JarScanningCordappLoader private constructor(private val cordappJarPaths: List<RestrictedURL>,
|
class JarScanningCordappLoader private constructor(private val cordappJarPaths: List<RestrictedURL>,
|
||||||
private val versionInfo: VersionInfo = VersionInfo.UNKNOWN,
|
private val versionInfo: VersionInfo = VersionInfo.UNKNOWN,
|
||||||
extraCordapps: List<CordappImpl>,
|
extraCordapps: List<CordappImpl>,
|
||||||
private val blacklistedCordappSigners: List<X509Certificate> = emptyList()) : CordappLoaderTemplate() {
|
private val signerKeyFingerprintBlacklist: List<SecureHash.SHA256> = emptyList()) : CordappLoaderTemplate() {
|
||||||
|
|
||||||
override val cordapps: List<CordappImpl> by lazy {
|
override val cordapps: List<CordappImpl> by lazy {
|
||||||
loadCordapps() + extraCordapps
|
loadCordapps() + extraCordapps
|
||||||
@ -67,10 +66,10 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths:
|
|||||||
fun fromDirectories(cordappDirs: Collection<Path>,
|
fun fromDirectories(cordappDirs: Collection<Path>,
|
||||||
versionInfo: VersionInfo = VersionInfo.UNKNOWN,
|
versionInfo: VersionInfo = VersionInfo.UNKNOWN,
|
||||||
extraCordapps: List<CordappImpl> = emptyList(),
|
extraCordapps: List<CordappImpl> = emptyList(),
|
||||||
blacklistedCerts: List<X509Certificate> = emptyList()): JarScanningCordappLoader {
|
signerKeyFingerprintBlacklist: List<SecureHash.SHA256> = emptyList()): JarScanningCordappLoader {
|
||||||
logger.info("Looking for CorDapps in ${cordappDirs.distinct().joinToString(", ", "[", "]")}")
|
logger.info("Looking for CorDapps in ${cordappDirs.distinct().joinToString(", ", "[", "]")}")
|
||||||
val paths = cordappDirs.distinct().flatMap(this::jarUrlsInDirectory).map { it.restricted() }
|
val paths = cordappDirs.distinct().flatMap(this::jarUrlsInDirectory).map { it.restricted() }
|
||||||
return JarScanningCordappLoader(paths, versionInfo, extraCordapps, blacklistedCerts)
|
return JarScanningCordappLoader(paths, versionInfo, extraCordapps, signerKeyFingerprintBlacklist)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -78,9 +77,10 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths:
|
|||||||
*
|
*
|
||||||
* @param scanJars Uses the JAR URLs provided for classpath scanning and Cordapp detection.
|
* @param scanJars Uses the JAR URLs provided for classpath scanning and Cordapp detection.
|
||||||
*/
|
*/
|
||||||
fun fromJarUrls(scanJars: List<URL>, versionInfo: VersionInfo = VersionInfo.UNKNOWN, extraCordapps: List<CordappImpl> = emptyList(), blacklistedCerts: List<X509Certificate> = emptyList()): JarScanningCordappLoader {
|
fun fromJarUrls(scanJars: List<URL>, versionInfo: VersionInfo = VersionInfo.UNKNOWN, extraCordapps: List<CordappImpl> = emptyList(),
|
||||||
|
cordappsSignerKeyFingerprintBlacklist: List<SecureHash.SHA256> = emptyList()): JarScanningCordappLoader {
|
||||||
val paths = scanJars.map { it.restricted() }
|
val paths = scanJars.map { it.restricted() }
|
||||||
return JarScanningCordappLoader(paths, versionInfo, extraCordapps, blacklistedCerts)
|
return JarScanningCordappLoader(paths, versionInfo, extraCordapps, cordappsSignerKeyFingerprintBlacklist)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun URL.restricted(rootPackageName: String? = null) = RestrictedURL(this, rootPackageName)
|
private fun URL.restricted(rootPackageName: String? = null) = RestrictedURL(this, rootPackageName)
|
||||||
@ -110,15 +110,16 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.filter {
|
.filter {
|
||||||
if (blacklistedCordappSigners.isEmpty()) {
|
if (signerKeyFingerprintBlacklist.isEmpty()) {
|
||||||
true //Nothing blacklisted, no need to check
|
true //Nothing blacklisted, no need to check
|
||||||
} else {
|
} else {
|
||||||
val certificates = it.jarPath.openStream().let(::JarInputStream).use(JarSignatureCollector::collectCertificates)
|
val certificates = it.jarPath.openStream().let(::JarInputStream).use(JarSignatureCollector::collectCertificates)
|
||||||
if (certificates.isEmpty() || (certificates - blacklistedCordappSigners).isNotEmpty())
|
val blockedCertificates = certificates.filter { it.publicKey.hash.sha256() in signerKeyFingerprintBlacklist }
|
||||||
|
if (certificates.isEmpty() || (certificates - blockedCertificates).isNotEmpty())
|
||||||
true // Cordapp is not signed or it is signed by at least one non-blacklisted certificate
|
true // Cordapp is not signed or it is signed by at least one non-blacklisted certificate
|
||||||
else {
|
else {
|
||||||
logger.warn("Not loading CorDapp ${it.info.shortName} (${it.info.vendor}) as it is signed by development key(s) only: " +
|
logger.warn("Not loading CorDapp ${it.info.shortName} (${it.info.vendor}) as it is signed by development key(s) only: " +
|
||||||
"${certificates.intersect(blacklistedCordappSigners).map { it.publicKey }}.")
|
"${blockedCertificates.map { it.publicKey }}.")
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,7 @@ import net.corda.node.services.config.rpc.NodeRpcOptions
|
|||||||
import net.corda.nodeapi.BrokerRpcSslOptions
|
import net.corda.nodeapi.BrokerRpcSslOptions
|
||||||
import net.corda.nodeapi.internal.config.*
|
import net.corda.nodeapi.internal.config.*
|
||||||
import net.corda.nodeapi.internal.persistence.DatabaseConfig
|
import net.corda.nodeapi.internal.persistence.DatabaseConfig
|
||||||
|
import net.corda.nodeapi.internal.DEV_PUB_KEY_HASHES
|
||||||
import net.corda.tools.shell.SSHDConfiguration
|
import net.corda.tools.shell.SSHDConfiguration
|
||||||
import org.slf4j.Logger
|
import org.slf4j.Logger
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
@ -78,6 +79,8 @@ interface NodeConfiguration {
|
|||||||
val cordappDirectories: List<Path>
|
val cordappDirectories: List<Path>
|
||||||
val flowOverrides: FlowOverrideConfig?
|
val flowOverrides: FlowOverrideConfig?
|
||||||
|
|
||||||
|
val cordappSignerKeyFingerprintBlacklist: List<String>
|
||||||
|
|
||||||
fun validate(): List<String>
|
fun validate(): List<String>
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@ -215,7 +218,8 @@ data class NodeConfigurationImpl(
|
|||||||
override val flowMonitorSuspensionLoggingThresholdMillis: Duration = DEFAULT_FLOW_MONITOR_SUSPENSION_LOGGING_THRESHOLD_MILLIS,
|
override val flowMonitorSuspensionLoggingThresholdMillis: Duration = DEFAULT_FLOW_MONITOR_SUSPENSION_LOGGING_THRESHOLD_MILLIS,
|
||||||
override val cordappDirectories: List<Path> = listOf(baseDirectory / CORDAPPS_DIR_NAME_DEFAULT),
|
override val cordappDirectories: List<Path> = listOf(baseDirectory / CORDAPPS_DIR_NAME_DEFAULT),
|
||||||
override val jmxReporterType: JmxReporterType? = JmxReporterType.JOLOKIA,
|
override val jmxReporterType: JmxReporterType? = JmxReporterType.JOLOKIA,
|
||||||
override val flowOverrides: FlowOverrideConfig?
|
override val flowOverrides: FlowOverrideConfig?,
|
||||||
|
override val cordappSignerKeyFingerprintBlacklist: List<String> = DEV_PUB_KEY_HASHES.map { it.toString() }
|
||||||
) : NodeConfiguration {
|
) : NodeConfiguration {
|
||||||
companion object {
|
companion object {
|
||||||
private val logger = loggerFor<NodeConfigurationImpl>()
|
private val logger = loggerFor<NodeConfigurationImpl>()
|
||||||
|
@ -6,7 +6,7 @@ import net.corda.core.internal.packageName
|
|||||||
import net.corda.node.VersionInfo
|
import net.corda.node.VersionInfo
|
||||||
import net.corda.testing.node.internal.TestCordappDirectories
|
import net.corda.testing.node.internal.TestCordappDirectories
|
||||||
import net.corda.testing.node.internal.cordappForPackages
|
import net.corda.testing.node.internal.cordappForPackages
|
||||||
import net.corda.nodeapi.internal.DEV_CERTIFICATES
|
import net.corda.nodeapi.internal.DEV_PUB_KEY_HASHES
|
||||||
import org.assertj.core.api.Assertions.assertThat
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import java.nio.file.Paths
|
import java.nio.file.Paths
|
||||||
@ -147,21 +147,21 @@ class JarScanningCordappLoaderTest {
|
|||||||
@Test
|
@Test
|
||||||
fun `cordapp classloader loads app signed by allowed certificate`() {
|
fun `cordapp classloader loads app signed by allowed certificate`() {
|
||||||
val jar = JarScanningCordappLoaderTest::class.java.getResource("signed/signed-by-dev-key.jar")!!
|
val jar = JarScanningCordappLoaderTest::class.java.getResource("signed/signed-by-dev-key.jar")!!
|
||||||
val loader = JarScanningCordappLoader.fromJarUrls(listOf(jar), blacklistedCerts = emptyList())
|
val loader = JarScanningCordappLoader.fromJarUrls(listOf(jar), cordappsSignerKeyFingerprintBlacklist = emptyList())
|
||||||
assertThat(loader.cordapps).hasSize(1)
|
assertThat(loader.cordapps).hasSize(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `cordapp classloader does not load app signed by blacklisted certificate`() {
|
fun `cordapp classloader does not load app signed by blacklisted certificate`() {
|
||||||
val jar = JarScanningCordappLoaderTest::class.java.getResource("signed/signed-by-dev-key.jar")!!
|
val jar = JarScanningCordappLoaderTest::class.java.getResource("signed/signed-by-dev-key.jar")!!
|
||||||
val loader = JarScanningCordappLoader.fromJarUrls(listOf(jar), blacklistedCerts = DEV_CERTIFICATES)
|
val loader = JarScanningCordappLoader.fromJarUrls(listOf(jar), cordappsSignerKeyFingerprintBlacklist = DEV_PUB_KEY_HASHES)
|
||||||
assertThat(loader.cordapps).hasSize(0)
|
assertThat(loader.cordapps).hasSize(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `cordapp classloader loads app signed by both allowed and non-blacklisted certificate`() {
|
fun `cordapp classloader loads app signed by both allowed and non-blacklisted certificate`() {
|
||||||
val jar = JarScanningCordappLoaderTest::class.java.getResource("signed/signed-by-two-keys.jar")!!
|
val jar = JarScanningCordappLoaderTest::class.java.getResource("signed/signed-by-two-keys.jar")!!
|
||||||
val loader = JarScanningCordappLoader.fromJarUrls(listOf(jar), blacklistedCerts = DEV_CERTIFICATES)
|
val loader = JarScanningCordappLoader.fromJarUrls(listOf(jar), cordappsSignerKeyFingerprintBlacklist = DEV_PUB_KEY_HASHES)
|
||||||
assertThat(loader.cordapps).hasSize(1)
|
assertThat(loader.cordapps).hasSize(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user