mirror of
https://github.com/corda/corda.git
synced 2025-01-17 10:20:02 +00:00
CORDA-1915 node rejects CorDapps signed by our dev keys in prod mode (#4041)
Related to CORDA-1915 Signing CorDapp JARs - Corda node rejects CorDapps signed by our development keys when running in production mode. This prevents Cordapps signed by our dev key (by default) running in production (node devMode=false).
This commit is contained in:
parent
0919b01271
commit
7e3aa7f30c
@ -28,6 +28,14 @@ object JarSignatureCollector {
|
|||||||
|
|
||||||
fun collectSigningParties(jar: JarInputStream): List<Party> = getSigners(jar).toPartiesOrderedByName()
|
fun collectSigningParties(jar: JarInputStream): List<Party> = getSigners(jar).toPartiesOrderedByName()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an ordered list of every [X509Certificate] which has signed every signable item in the given [JarInputStream].
|
||||||
|
*
|
||||||
|
* @param jar The open [JarInputStream] to collect signing parties from.
|
||||||
|
* @throws InvalidJarSignersException If the signer sets for any two signable items are different from each other.
|
||||||
|
*/
|
||||||
|
fun collectCertificates(jar: JarInputStream): List<X509Certificate> = getSigners(jar).toCertificates()
|
||||||
|
|
||||||
private fun getSigners(jar: JarInputStream): Set<CodeSigner> {
|
private fun getSigners(jar: JarInputStream): Set<CodeSigner> {
|
||||||
val signerSets = jar.fileSignerSets
|
val signerSets = jar.fileSignerSets
|
||||||
if (signerSets.isEmpty()) return emptySet()
|
if (signerSets.isEmpty()) return emptySet()
|
||||||
@ -71,6 +79,10 @@ object JarSignatureCollector {
|
|||||||
(it.signerCertPath.certificates[0] as X509Certificate).publicKey
|
(it.signerCertPath.certificates[0] as X509Certificate).publicKey
|
||||||
}.sortedBy { it.hash} // Sorted for determinism.
|
}.sortedBy { it.hash} // Sorted for determinism.
|
||||||
|
|
||||||
|
private fun Set<CodeSigner>.toCertificates(): List<X509Certificate> = map {
|
||||||
|
it.signerCertPath.certificates[0] as X509Certificate
|
||||||
|
}.sortedBy { it.toString() } // Sorted for determinism.
|
||||||
|
|
||||||
private val JarInputStream.entries get(): Sequence<JarEntry> = generateSequence(nextJarEntry) { nextJarEntry }
|
private val JarInputStream.entries get(): Sequence<JarEntry> = generateSequence(nextJarEntry) { nextJarEntry }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,6 +99,8 @@ 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)
|
||||||
|
|
||||||
// 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 {
|
||||||
fun loadDevCa(alias: String): CertificateAndKeyPair {
|
fun loadDevCa(alias: String): CertificateAndKeyPair {
|
||||||
|
@ -72,6 +72,7 @@ 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
|
||||||
@ -513,10 +514,12 @@ 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
|
||||||
return JarScanningCordappLoader.fromDirectories(
|
return JarScanningCordappLoader.fromDirectories(
|
||||||
configuration.cordappDirectories,
|
configuration.cordappDirectories,
|
||||||
versionInfo,
|
versionInfo,
|
||||||
extraCordapps = generatedCordapps
|
extraCordapps = generatedCordapps,
|
||||||
|
blacklistedCerts = blacklistedCerts
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,6 +26,7 @@ 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
|
||||||
@ -38,7 +39,8 @@ 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>) : CordappLoaderTemplate() {
|
extraCordapps: List<CordappImpl>,
|
||||||
|
private val blacklistedCordappSigners: List<X509Certificate> = emptyList()) : CordappLoaderTemplate() {
|
||||||
|
|
||||||
override val cordapps: List<CordappImpl> by lazy {
|
override val cordapps: List<CordappImpl> by lazy {
|
||||||
loadCordapps() + extraCordapps
|
loadCordapps() + extraCordapps
|
||||||
@ -64,10 +66,11 @@ 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()): JarScanningCordappLoader {
|
extraCordapps: List<CordappImpl> = emptyList(),
|
||||||
|
blacklistedCerts: List<X509Certificate> = 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)
|
return JarScanningCordappLoader(paths, versionInfo, extraCordapps, blacklistedCerts)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -75,9 +78,9 @@ 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()): JarScanningCordappLoader {
|
fun fromJarUrls(scanJars: List<URL>, versionInfo: VersionInfo = VersionInfo.UNKNOWN, extraCordapps: List<CordappImpl> = emptyList(), blacklistedCerts: List<X509Certificate> = emptyList()): JarScanningCordappLoader {
|
||||||
val paths = scanJars.map { it.restricted() }
|
val paths = scanJars.map { it.restricted() }
|
||||||
return JarScanningCordappLoader(paths, versionInfo, extraCordapps)
|
return JarScanningCordappLoader(paths, versionInfo, extraCordapps, blacklistedCerts)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun URL.restricted(rootPackageName: String? = null) = RestrictedURL(this, rootPackageName)
|
private fun URL.restricted(rootPackageName: String? = null) = RestrictedURL(this, rootPackageName)
|
||||||
@ -106,6 +109,20 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths:
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.filter {
|
||||||
|
if (blacklistedCordappSigners.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())
|
||||||
|
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 }}.")
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
cordapps.forEach { CordappInfoResolver.register(it.cordappClasses, it.info) }
|
cordapps.forEach { CordappInfoResolver.register(it.cordappClasses, it.info) }
|
||||||
return cordapps
|
return cordapps
|
||||||
}
|
}
|
||||||
|
@ -6,6 +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 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
|
||||||
@ -142,4 +143,25 @@ class JarScanningCordappLoaderTest {
|
|||||||
val loader = JarScanningCordappLoader.fromJarUrls(listOf(jar), VersionInfo.UNKNOWN.copy(platformVersion = 2))
|
val loader = JarScanningCordappLoader.fromJarUrls(listOf(jar), VersionInfo.UNKNOWN.copy(platformVersion = 2))
|
||||||
assertThat(loader.cordapps).hasSize(1)
|
assertThat(loader.cordapps).hasSize(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@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())
|
||||||
|
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)
|
||||||
|
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)
|
||||||
|
assertThat(loader.cordapps).hasSize(1)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Binary file not shown.
Binary file not shown.
Loading…
Reference in New Issue
Block a user