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:
szymonsztuka
2018-10-30 13:53:01 +00:00
committed by GitHub
parent 38078e3762
commit 1de56550b0
6 changed files with 40 additions and 19 deletions

View File

@ -9,6 +9,7 @@ import net.corda.confidential.SwapIdentitiesHandler
import net.corda.core.CordaException
import net.corda.core.concurrent.CordaFuture
import net.corda.core.context.InvocationContext
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.newSecureRandom
import net.corda.core.crypto.sign
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.api.*
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.rpc.NodeRpcOptions
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.vault.NodeVaultService
import net.corda.node.utilities.*
import net.corda.nodeapi.internal.DEV_CERTIFICATES
import net.corda.nodeapi.internal.NodeInfoAndSigned
import net.corda.nodeapi.internal.SignedNodeInfo
import net.corda.nodeapi.internal.config.CertificateStore
@ -514,12 +513,20 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
// CorDapp will be generated.
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(
configuration.cordappDirectories,
versionInfo,
extraCordapps = generatedCordapps,
blacklistedCerts = blacklistedCerts
signerKeyFingerprintBlacklist = blacklistedKeys
)
}

View File

@ -26,7 +26,6 @@ import java.lang.reflect.Modifier
import java.net.URL
import java.net.URLClassLoader
import java.nio.file.Path
import java.security.cert.X509Certificate
import java.util.*
import java.util.jar.JarInputStream
import kotlin.reflect.KClass
@ -40,7 +39,7 @@ import kotlin.streams.toList
class JarScanningCordappLoader private constructor(private val cordappJarPaths: List<RestrictedURL>,
private val versionInfo: VersionInfo = VersionInfo.UNKNOWN,
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 {
loadCordapps() + extraCordapps
@ -67,10 +66,10 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths:
fun fromDirectories(cordappDirs: Collection<Path>,
versionInfo: VersionInfo = VersionInfo.UNKNOWN,
extraCordapps: List<CordappImpl> = emptyList(),
blacklistedCerts: List<X509Certificate> = emptyList()): JarScanningCordappLoader {
signerKeyFingerprintBlacklist: List<SecureHash.SHA256> = emptyList()): JarScanningCordappLoader {
logger.info("Looking for CorDapps in ${cordappDirs.distinct().joinToString(", ", "[", "]")}")
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.
*/
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() }
return JarScanningCordappLoader(paths, versionInfo, extraCordapps, blacklistedCerts)
return JarScanningCordappLoader(paths, versionInfo, extraCordapps, cordappsSignerKeyFingerprintBlacklist)
}
private fun URL.restricted(rootPackageName: String? = null) = RestrictedURL(this, rootPackageName)
@ -110,15 +110,16 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths:
}
}
.filter {
if (blacklistedCordappSigners.isEmpty()) {
if (signerKeyFingerprintBlacklist.isEmpty()) {
true //Nothing blacklisted, no need to check
} else {
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
else {
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
}
}

View File

@ -13,6 +13,7 @@ import net.corda.node.services.config.rpc.NodeRpcOptions
import net.corda.nodeapi.BrokerRpcSslOptions
import net.corda.nodeapi.internal.config.*
import net.corda.nodeapi.internal.persistence.DatabaseConfig
import net.corda.nodeapi.internal.DEV_PUB_KEY_HASHES
import net.corda.tools.shell.SSHDConfiguration
import org.slf4j.Logger
import java.net.URL
@ -78,6 +79,8 @@ interface NodeConfiguration {
val cordappDirectories: List<Path>
val flowOverrides: FlowOverrideConfig?
val cordappSignerKeyFingerprintBlacklist: List<String>
fun validate(): List<String>
companion object {
@ -215,7 +218,8 @@ data class NodeConfigurationImpl(
override val flowMonitorSuspensionLoggingThresholdMillis: Duration = DEFAULT_FLOW_MONITOR_SUSPENSION_LOGGING_THRESHOLD_MILLIS,
override val cordappDirectories: List<Path> = listOf(baseDirectory / CORDAPPS_DIR_NAME_DEFAULT),
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 {
companion object {
private val logger = loggerFor<NodeConfigurationImpl>()

View File

@ -6,7 +6,7 @@ import net.corda.core.internal.packageName
import net.corda.node.VersionInfo
import net.corda.testing.node.internal.TestCordappDirectories
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.junit.Test
import java.nio.file.Paths
@ -147,21 +147,21 @@ class JarScanningCordappLoaderTest {
@Test
fun `cordapp classloader loads app signed by allowed certificate`() {
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)
}
@Test
fun `cordapp classloader does not load app signed by blacklisted certificate`() {
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)
}
@Test
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 loader = JarScanningCordappLoader.fromJarUrls(listOf(jar), blacklistedCerts = DEV_CERTIFICATES)
val loader = JarScanningCordappLoader.fromJarUrls(listOf(jar), cordappsSignerKeyFingerprintBlacklist = DEV_PUB_KEY_HASHES)
assertThat(loader.cordapps).hasSize(1)
}
}