mirror of
https://github.com/corda/corda.git
synced 2025-01-31 00:24:59 +00:00
An implementation of a Security Provider that replaces the default TrustManager with one that checks certificate names against a manually controlled white list. This isn't activated anywhere yet, but the network map should register the valid node dns names.
This commit is contained in:
parent
e5777fd999
commit
ed52f2b35d
@ -0,0 +1,168 @@
|
||||
package com.r3corda.core.crypto
|
||||
|
||||
import sun.security.util.HostnameChecker
|
||||
import java.net.InetAddress
|
||||
import java.net.Socket
|
||||
import java.net.UnknownHostException
|
||||
import java.security.KeyStore
|
||||
import java.security.Provider
|
||||
import java.security.Security
|
||||
import java.security.cert.CertificateException
|
||||
import java.security.cert.X509Certificate
|
||||
import javax.net.ssl.*
|
||||
|
||||
/**
|
||||
* Call this to change the default verification algorithm and this use the WhitelistTrustManager
|
||||
* implementation. This is a work around to the fact that ArtemisMQ and probably many other libraries
|
||||
* don't correctly configure the SSLParameters with setEndpointIdentificationAlgorithm and thus don't check
|
||||
* that the certificate matches with the DNS entry requested. This exposes us to man in the middle attacks.
|
||||
*/
|
||||
fun registerWhitelistTrustManager() {
|
||||
if (Security.getProvider("WhitelistTrustManager") == null) {
|
||||
Security.addProvider(WhitelistTrustManagerProvider)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom Securtity Provider that forces the TrustManagerFactory to be our custom one.
|
||||
* Also holds the identity of the original TrustManager algorithm so
|
||||
* that we can delegate most of the checking to the proper Java code. We simply add some more checks.
|
||||
*
|
||||
* The whitelist automatically includes the local server DNS name and IP address
|
||||
*
|
||||
*/
|
||||
object WhitelistTrustManagerProvider : Provider("WhitelistTrustManager",
|
||||
1.0,
|
||||
"Provider for custom trust manager that always validates certificate names") {
|
||||
|
||||
val originalTrustProviderAlgorithm = Security.getProperty("ssl.TrustManagerFactory.algorithm")
|
||||
|
||||
private val _whitelist = mutableSetOf<String>()
|
||||
val whitelist: Set<String> get() = _whitelist.toSet() // The acceptable IP and DNS names for clients and servers.
|
||||
|
||||
init {
|
||||
// Add ourselves to whitelist
|
||||
val host = InetAddress.getLocalHost()
|
||||
addWhitelistEntry(host.hostName)
|
||||
|
||||
// Register our custom TrustManagerFactorySpi
|
||||
put("TrustManagerFactory.whitelistTrustManager", "com.r3corda.core.crypto.WhitelistTrustManagerSpi")
|
||||
|
||||
// Forcibly change the TrustManagerFactory defaultAlgorithm to be us
|
||||
Security.setProperty("ssl.TrustManagerFactory.algorithm", "whitelistTrustManager")
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an extra name to the whitelist if not already present
|
||||
*/
|
||||
fun addWhitelistEntry(serverName: String) {
|
||||
if(!_whitelist.contains(serverName)) {
|
||||
addWhitelistEntries(listOf(serverName))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a list of servers to the whitelist and also adds their fully resolved name after DNS lookup
|
||||
*/
|
||||
fun addWhitelistEntries(serverNames: List<String>) {
|
||||
_whitelist.addAll(serverNames)
|
||||
for(serveName in serverNames) {
|
||||
try {
|
||||
val addresses = InetAddress.getAllByName(serveName).toList()
|
||||
_whitelist.addAll(addresses.map { y -> y.canonicalHostName })
|
||||
_whitelist.addAll(addresses.map { y -> y.hostAddress })
|
||||
} catch (ex: UnknownHostException) {
|
||||
// Ignore if the server name is not resolvable e.g. for wildcard addresses, or addresses that can only be resolved externally
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registered TrustManagerFactorySpi
|
||||
*/
|
||||
class WhitelistTrustManagerSpi : TrustManagerFactorySpi() {
|
||||
//Get the original implementation to delegate to (can't use Kotlin delegation on abstract classes unfortunately).
|
||||
val originalProvider = TrustManagerFactory.getInstance(WhitelistTrustManagerProvider.originalTrustProviderAlgorithm)
|
||||
|
||||
override fun engineInit(keyStore: KeyStore?) {
|
||||
originalProvider.init(keyStore)
|
||||
}
|
||||
|
||||
override fun engineInit(managerFactoryParameters: ManagerFactoryParameters?) {
|
||||
originalProvider.init(managerFactoryParameters)
|
||||
}
|
||||
|
||||
override fun engineGetTrustManagers(): Array<out TrustManager> {
|
||||
val parent = originalProvider.trustManagers.first() as X509ExtendedTrustManager
|
||||
//Wrap original provider in ours and return
|
||||
return arrayOf(WhitelistTrustManager(parent))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Our TrustManager extension takes the standard certificate checker and first delegates all the
|
||||
* chain checking to that. If everything is well formed we then simply add a check against our whitelist
|
||||
*/
|
||||
class WhitelistTrustManager(val originalProvider: X509ExtendedTrustManager) : X509ExtendedTrustManager() {
|
||||
// Use same Helper class as standard HTTPS library validator
|
||||
val checker = HostnameChecker.getInstance(HostnameChecker.TYPE_TLS)
|
||||
|
||||
private fun checkIdentity(hostname: String?, cert: X509Certificate) {
|
||||
// Based on standard code in sun.security.ssl.X509TrustManagerImpl.checkIdentity
|
||||
if ((hostname != null) && hostname.startsWith("[") && hostname.endsWith("]")) {
|
||||
checker.match(hostname.substring(1, hostname.length - 1), cert)
|
||||
} else {
|
||||
checker.match(hostname, cert)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* scan whitelist and confirm the certificate matches at least one entry
|
||||
*/
|
||||
private fun checkWhitelist(cert: X509Certificate) {
|
||||
for (whiteListEntry in WhitelistTrustManagerProvider.whitelist) {
|
||||
try {
|
||||
checkIdentity(whiteListEntry, cert)
|
||||
return // if we get here without throwing we had a match
|
||||
} catch(ex: CertificateException) {
|
||||
//
|
||||
}
|
||||
}
|
||||
throw CertificateException("Certificate not on whitelist ${cert.subjectDN}")
|
||||
}
|
||||
|
||||
override fun checkClientTrusted(chain: Array<out X509Certificate>, authType: String, socket: Socket?) {
|
||||
originalProvider.checkClientTrusted(chain, authType, socket)
|
||||
checkWhitelist(chain[0])
|
||||
}
|
||||
|
||||
override fun checkClientTrusted(chain: Array<out X509Certificate>, authType: String, engine: SSLEngine?) {
|
||||
originalProvider.checkClientTrusted(chain, authType, engine)
|
||||
checkWhitelist(chain[0])
|
||||
}
|
||||
|
||||
override fun checkClientTrusted(chain: Array<out X509Certificate>, authType: String) {
|
||||
originalProvider.checkClientTrusted(chain, authType)
|
||||
checkWhitelist(chain[0])
|
||||
}
|
||||
|
||||
override fun checkServerTrusted(chain: Array<out X509Certificate>, authType: String, socket: Socket?) {
|
||||
originalProvider.checkServerTrusted(chain, authType, socket)
|
||||
checkWhitelist(chain[0])
|
||||
}
|
||||
|
||||
override fun checkServerTrusted(chain: Array<out X509Certificate>, authType: String, engine: SSLEngine?) {
|
||||
originalProvider.checkServerTrusted(chain, authType, engine)
|
||||
checkWhitelist(chain[0])
|
||||
}
|
||||
|
||||
override fun checkServerTrusted(chain: Array<out X509Certificate>, authType: String) {
|
||||
originalProvider.checkServerTrusted(chain, authType)
|
||||
checkWhitelist(chain[0])
|
||||
}
|
||||
|
||||
override fun getAcceptedIssuers(): Array<out X509Certificate> {
|
||||
return originalProvider.acceptedIssuers
|
||||
}
|
||||
}
|
@ -0,0 +1,203 @@
|
||||
package com.r3corda.core.crypto
|
||||
|
||||
import org.junit.BeforeClass
|
||||
import org.junit.Test
|
||||
import java.net.Socket
|
||||
import java.security.KeyStore
|
||||
import java.security.cert.CertificateException
|
||||
import java.security.cert.X509Certificate
|
||||
import javax.net.ssl.SSLEngine
|
||||
import javax.net.ssl.TrustManagerFactory
|
||||
import javax.net.ssl.X509ExtendedTrustManager
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFailsWith
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class WhitelistTrustManagerTest {
|
||||
companion object {
|
||||
@BeforeClass
|
||||
@JvmStatic
|
||||
fun registerTrustManager() {
|
||||
// Validate original factory
|
||||
assertEquals("PKIX", TrustManagerFactory.getDefaultAlgorithm())
|
||||
|
||||
//register for all tests
|
||||
registerWhitelistTrustManager()
|
||||
}
|
||||
}
|
||||
|
||||
private fun getTrustmanagerAndCert(whitelist: String, certificateName: String): Pair<X509ExtendedTrustManager, X509Certificate> {
|
||||
WhitelistTrustManagerProvider.addWhitelistEntry(whitelist)
|
||||
|
||||
val caCertAndKey = X509Utilities.createSelfSignedCACert(certificateName)
|
||||
|
||||
val keyStore = KeyStore.getInstance(KeyStore.getDefaultType())
|
||||
keyStore.load(null, null)
|
||||
keyStore.setCertificateEntry("cacert", caCertAndKey.certificate)
|
||||
|
||||
val trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
|
||||
trustManagerFactory.init(keyStore)
|
||||
|
||||
return Pair(trustManagerFactory.trustManagers.first() as X509ExtendedTrustManager, caCertAndKey.certificate)
|
||||
}
|
||||
|
||||
private fun getTrustmanagerAndUntrustedChainCert(): Pair<X509ExtendedTrustManager, X509Certificate> {
|
||||
WhitelistTrustManagerProvider.addWhitelistEntry("test.r3corda.com")
|
||||
|
||||
val otherCaCertAndKey = X509Utilities.createSelfSignedCACert("bad root")
|
||||
|
||||
val caCertAndKey = X509Utilities.createSelfSignedCACert("good root")
|
||||
|
||||
val subject = X509Utilities.getDevX509Name("test.r3corda.com")
|
||||
val serverKey = X509Utilities.generateECDSAKeyPairForSSL()
|
||||
val serverCert = X509Utilities.createServerCert(subject,
|
||||
serverKey.public,
|
||||
otherCaCertAndKey,
|
||||
listOf(),
|
||||
listOf())
|
||||
|
||||
val keyStore = KeyStore.getInstance(KeyStore.getDefaultType())
|
||||
keyStore.load(null, null)
|
||||
keyStore.setCertificateEntry("cacert", caCertAndKey.certificate)
|
||||
|
||||
val trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
|
||||
trustManagerFactory.init(keyStore)
|
||||
|
||||
return Pair(trustManagerFactory.trustManagers.first() as X509ExtendedTrustManager, serverCert)
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun `getDefaultAlgorithm TrustManager is WhitelistTrustManager`() {
|
||||
registerWhitelistTrustManager() // Check double register is safe
|
||||
|
||||
assertEquals("whitelistTrustManager", TrustManagerFactory.getDefaultAlgorithm())
|
||||
|
||||
val trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
|
||||
|
||||
trustManagerFactory.init(null as KeyStore?)
|
||||
|
||||
val trustManagers = trustManagerFactory.trustManagers
|
||||
|
||||
assertTrue { trustManagers.all { it is WhitelistTrustManager } }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `check certificate works for whitelisted certificate and specific domain`() {
|
||||
val (trustManager, cert) = getTrustmanagerAndCert("test.r3corda.com", "test.r3corda.com")
|
||||
|
||||
trustManager.checkServerTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM)
|
||||
|
||||
trustManager.checkServerTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM, null as Socket?)
|
||||
|
||||
trustManager.checkServerTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM, null as SSLEngine?)
|
||||
|
||||
trustManager.checkClientTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM)
|
||||
|
||||
trustManager.checkClientTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM, null as Socket?)
|
||||
|
||||
trustManager.checkClientTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM, null as SSLEngine?)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `check certificate works for specific certificate and wildcard permitted domain`() {
|
||||
val (trustManager, cert) = getTrustmanagerAndCert("*.r3corda.com", "test.r3corda.com")
|
||||
|
||||
trustManager.checkServerTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM)
|
||||
|
||||
trustManager.checkServerTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM, null as Socket?)
|
||||
|
||||
trustManager.checkServerTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM, null as SSLEngine?)
|
||||
|
||||
trustManager.checkClientTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM)
|
||||
|
||||
trustManager.checkClientTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM, null as Socket?)
|
||||
|
||||
trustManager.checkClientTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM, null as SSLEngine?)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `check certificate works for wildcard certificate and non wildcard domain`() {
|
||||
val (trustManager, cert) = getTrustmanagerAndCert("*.r3corda.com", "test.r3corda.com")
|
||||
|
||||
trustManager.checkServerTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM)
|
||||
|
||||
trustManager.checkServerTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM, null as Socket?)
|
||||
|
||||
trustManager.checkServerTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM, null as SSLEngine?)
|
||||
|
||||
trustManager.checkClientTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM)
|
||||
|
||||
trustManager.checkClientTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM, null as Socket?)
|
||||
|
||||
trustManager.checkClientTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM, null as SSLEngine?)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `check unknown certificate rejected`() {
|
||||
val (trustManager, cert) = getTrustmanagerAndCert("test.r3corda.com", "test.notr3.com")
|
||||
|
||||
assertFailsWith<CertificateException> { trustManager.checkServerTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM) }
|
||||
|
||||
assertFailsWith<CertificateException> { trustManager.checkServerTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM, null as Socket?) }
|
||||
|
||||
assertFailsWith<CertificateException> { trustManager.checkServerTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM, null as SSLEngine?) }
|
||||
|
||||
assertFailsWith<CertificateException> { trustManager.checkClientTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM) }
|
||||
|
||||
assertFailsWith<CertificateException> { trustManager.checkClientTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM, null as Socket?) }
|
||||
|
||||
assertFailsWith<CertificateException> { trustManager.checkClientTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM, null as SSLEngine?) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `check unknown wildcard certificate rejected`() {
|
||||
val (trustManager, cert) = getTrustmanagerAndCert("test.r3corda.com", "*.notr3.com")
|
||||
|
||||
assertFailsWith<CertificateException> { trustManager.checkServerTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM) }
|
||||
|
||||
assertFailsWith<CertificateException> { trustManager.checkServerTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM, null as Socket?) }
|
||||
|
||||
assertFailsWith<CertificateException> { trustManager.checkServerTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM, null as SSLEngine?) }
|
||||
|
||||
assertFailsWith<CertificateException> { trustManager.checkClientTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM) }
|
||||
|
||||
assertFailsWith<CertificateException> { trustManager.checkClientTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM, null as Socket?) }
|
||||
|
||||
assertFailsWith<CertificateException> { trustManager.checkClientTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM, null as SSLEngine?) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `check unknown certificate rejected against mismatched wildcard`() {
|
||||
val (trustManager, cert) = getTrustmanagerAndCert("*.r3corda.com", "test.notr3.com")
|
||||
|
||||
assertFailsWith<java.security.cert.CertificateException> { trustManager.checkServerTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM) }
|
||||
|
||||
assertFailsWith<java.security.cert.CertificateException> { trustManager.checkServerTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM, null as Socket?) }
|
||||
|
||||
assertFailsWith<java.security.cert.CertificateException> { trustManager.checkServerTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM, null as SSLEngine?) }
|
||||
|
||||
assertFailsWith<java.security.cert.CertificateException> { trustManager.checkClientTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM) }
|
||||
|
||||
assertFailsWith<java.security.cert.CertificateException> { trustManager.checkClientTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM, null as Socket?) }
|
||||
|
||||
assertFailsWith<java.security.cert.CertificateException> { trustManager.checkClientTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM, null as SSLEngine?) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `check certificate signed by untrusted root is still rejected, despite matched name`() {
|
||||
val (trustManager, cert) = getTrustmanagerAndUntrustedChainCert()
|
||||
|
||||
assertFailsWith<java.security.cert.CertificateException> { trustManager.checkServerTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM) }
|
||||
|
||||
assertFailsWith<java.security.cert.CertificateException> { trustManager.checkServerTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM, null as Socket?) }
|
||||
|
||||
assertFailsWith<java.security.cert.CertificateException> { trustManager.checkServerTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM, null as SSLEngine?) }
|
||||
|
||||
assertFailsWith<java.security.cert.CertificateException> { trustManager.checkClientTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM) }
|
||||
|
||||
assertFailsWith<java.security.cert.CertificateException> { trustManager.checkClientTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM, null as Socket?) }
|
||||
|
||||
assertFailsWith<java.security.cert.CertificateException> { trustManager.checkClientTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM, null as SSLEngine?) }
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user