mirror of
https://github.com/corda/corda.git
synced 2025-06-17 22:58:19 +00:00
Replaced KeyStoreWrapper with X509KeyStore, which is still a wrapper but assumes only X509 certs and has better APIs (#2411)
This commit is contained in:
@ -5,7 +5,8 @@ import net.corda.core.internal.div
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.node.services.config.configureDevKeyAndTrustStores
|
||||
import net.corda.nodeapi.internal.config.SSLConfiguration
|
||||
import net.corda.nodeapi.internal.crypto.*
|
||||
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.driver
|
||||
import org.assertj.core.api.Assertions.assertThatThrownBy
|
||||
@ -45,15 +46,14 @@ class NodeKeystoreCheckTest {
|
||||
node.stop()
|
||||
|
||||
// Fiddle with node keystore.
|
||||
val keystore = loadKeyStore(config.nodeKeystore, config.keyStorePassword)
|
||||
|
||||
// Self signed root
|
||||
val badRootKeyPair = Crypto.generateKeyPair()
|
||||
val badRoot = X509Utilities.createSelfSignedCACertificate(X500Principal("O=Bad Root,L=Lodnon,C=GB"), badRootKeyPair)
|
||||
val nodeCA = keystore.getCertificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA, config.keyStorePassword)
|
||||
val badNodeCACert = X509Utilities.createCertificate(CertificateType.NODE_CA, badRoot, badRootKeyPair, ALICE_NAME.x500Principal, nodeCA.keyPair.public)
|
||||
keystore.setKeyEntry(X509Utilities.CORDA_CLIENT_CA, nodeCA.keyPair.private, config.keyStorePassword.toCharArray(), arrayOf(badNodeCACert, badRoot))
|
||||
keystore.save(config.nodeKeystore, config.keyStorePassword)
|
||||
config.loadNodeKeyStore().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)
|
||||
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))
|
||||
}
|
||||
|
||||
assertThatThrownBy {
|
||||
startNode(providedName = ALICE_NAME, customOverrides = mapOf("devMode" to false)).getOrThrow()
|
||||
|
@ -15,7 +15,6 @@ import net.corda.node.services.config.*
|
||||
import net.corda.node.services.messaging.ArtemisMessagingClient
|
||||
import net.corda.node.services.messaging.ArtemisMessagingServer
|
||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent
|
||||
import net.corda.nodeapi.internal.crypto.loadKeyStore
|
||||
import net.corda.testing.core.*
|
||||
import net.corda.testing.internal.rigorousMock
|
||||
import org.apache.activemq.artemis.api.core.Message.HDR_DUPLICATE_DETECTION_ID
|
||||
@ -229,16 +228,14 @@ class AMQPBridgeTest {
|
||||
}
|
||||
serverConfig.configureWithDevSSLCertificate()
|
||||
|
||||
val serverTruststore = loadKeyStore(serverConfig.trustStoreFile, serverConfig.trustStorePassword)
|
||||
val serverKeystore = loadKeyStore(serverConfig.sslKeystore, serverConfig.keyStorePassword)
|
||||
val amqpServer = AMQPServer("0.0.0.0",
|
||||
return AMQPServer("0.0.0.0",
|
||||
amqpPort,
|
||||
ArtemisMessagingComponent.PEER_USER,
|
||||
ArtemisMessagingComponent.PEER_USER,
|
||||
serverKeystore,
|
||||
serverConfig.loadSslKeyStore().internal,
|
||||
serverConfig.keyStorePassword,
|
||||
serverTruststore,
|
||||
trace = true)
|
||||
return amqpServer
|
||||
serverConfig.loadTrustStore().internal,
|
||||
trace = true
|
||||
)
|
||||
}
|
||||
}
|
@ -21,7 +21,6 @@ import net.corda.node.services.messaging.ArtemisMessagingClient
|
||||
import net.corda.node.services.messaging.ArtemisMessagingServer
|
||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.P2P_PREFIX
|
||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.PEER_USER
|
||||
import net.corda.nodeapi.internal.crypto.loadKeyStore
|
||||
import net.corda.testing.core.*
|
||||
import net.corda.testing.internal.rigorousMock
|
||||
import org.apache.activemq.artemis.api.core.RoutingType
|
||||
@ -253,8 +252,8 @@ class ProtonWrapperTests {
|
||||
}
|
||||
clientConfig.configureWithDevSSLCertificate()
|
||||
|
||||
val clientTruststore = loadKeyStore(clientConfig.trustStoreFile, clientConfig.trustStorePassword)
|
||||
val clientKeystore = loadKeyStore(clientConfig.sslKeystore, clientConfig.keyStorePassword)
|
||||
val clientTruststore = clientConfig.loadTrustStore().internal
|
||||
val clientKeystore = clientConfig.loadSslKeyStore().internal
|
||||
return AMQPClient(
|
||||
listOf(NetworkHostAndPort("localhost", serverPort),
|
||||
NetworkHostAndPort("localhost", serverPort2),
|
||||
@ -276,8 +275,8 @@ class ProtonWrapperTests {
|
||||
}
|
||||
clientConfig.configureWithDevSSLCertificate()
|
||||
|
||||
val clientTruststore = loadKeyStore(clientConfig.trustStoreFile, clientConfig.trustStorePassword)
|
||||
val clientKeystore = loadKeyStore(clientConfig.sslKeystore, clientConfig.keyStorePassword)
|
||||
val clientTruststore = clientConfig.loadTrustStore().internal
|
||||
val clientKeystore = clientConfig.loadSslKeyStore().internal
|
||||
return AMQPClient(
|
||||
listOf(NetworkHostAndPort("localhost", serverPort)),
|
||||
setOf(ALICE_NAME),
|
||||
@ -297,8 +296,8 @@ class ProtonWrapperTests {
|
||||
}
|
||||
serverConfig.configureWithDevSSLCertificate()
|
||||
|
||||
val serverTruststore = loadKeyStore(serverConfig.trustStoreFile, serverConfig.trustStorePassword)
|
||||
val serverKeystore = loadKeyStore(serverConfig.sslKeystore, serverConfig.keyStorePassword)
|
||||
val serverTruststore = serverConfig.loadTrustStore().internal
|
||||
val serverKeystore = serverConfig.loadSslKeyStore().internal
|
||||
return AMQPServer(
|
||||
"0.0.0.0",
|
||||
port,
|
||||
|
@ -12,7 +12,8 @@ import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.PEER_USER
|
||||
import net.corda.nodeapi.internal.DEV_INTERMEDIATE_CA
|
||||
import net.corda.nodeapi.internal.DEV_ROOT_CA
|
||||
import net.corda.nodeapi.internal.config.SSLConfiguration
|
||||
import net.corda.nodeapi.internal.crypto.*
|
||||
import net.corda.nodeapi.internal.crypto.CertificateType
|
||||
import net.corda.nodeapi.internal.crypto.X509Utilities
|
||||
import org.apache.activemq.artemis.api.config.ActiveMQDefaultConfiguration
|
||||
import org.apache.activemq.artemis.api.core.ActiveMQClusterSecurityException
|
||||
import org.apache.activemq.artemis.api.core.ActiveMQNotConnectedException
|
||||
@ -115,22 +116,19 @@ class MQSecurityAsNodeTest : P2PMQSecurityTest() {
|
||||
CordaX500Name("MiniCorp", "London", "GB").x500Principal,
|
||||
tlsKeyPair.public)
|
||||
|
||||
val keyPass = keyStorePassword.toCharArray()
|
||||
val clientCAKeystore = loadOrCreateKeyStore(nodeKeystore, keyStorePassword)
|
||||
clientCAKeystore.addOrReplaceKey(
|
||||
X509Utilities.CORDA_CLIENT_CA,
|
||||
clientKeyPair.private,
|
||||
keyPass,
|
||||
arrayOf(clientCACert, DEV_INTERMEDIATE_CA.certificate, DEV_ROOT_CA.certificate))
|
||||
clientCAKeystore.save(nodeKeystore, keyStorePassword)
|
||||
loadNodeKeyStore(createNew = true).update {
|
||||
setPrivateKey(
|
||||
X509Utilities.CORDA_CLIENT_CA,
|
||||
clientKeyPair.private,
|
||||
listOf(clientCACert, DEV_INTERMEDIATE_CA.certificate, DEV_ROOT_CA.certificate))
|
||||
}
|
||||
|
||||
val tlsKeystore = loadOrCreateKeyStore(sslKeystore, keyStorePassword)
|
||||
tlsKeystore.addOrReplaceKey(
|
||||
X509Utilities.CORDA_CLIENT_TLS,
|
||||
tlsKeyPair.private,
|
||||
keyPass,
|
||||
arrayOf(clientTLSCert, clientCACert, DEV_INTERMEDIATE_CA.certificate, DEV_ROOT_CA.certificate))
|
||||
tlsKeystore.save(sslKeystore, keyStorePassword)
|
||||
loadSslKeyStore(createNew = true).update {
|
||||
setPrivateKey(
|
||||
X509Utilities.CORDA_CLIENT_TLS,
|
||||
tlsKeyPair.private,
|
||||
listOf(clientTLSCert, clientCACert, DEV_INTERMEDIATE_CA.certificate, DEV_ROOT_CA.certificate))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -36,7 +36,10 @@ import net.corda.node.services.ContractUpgradeHandler
|
||||
import net.corda.node.services.FinalityHandler
|
||||
import net.corda.node.services.NotaryChangeHandler
|
||||
import net.corda.node.services.api.*
|
||||
import net.corda.node.services.config.*
|
||||
import net.corda.node.services.config.BFTSMaRtConfiguration
|
||||
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.events.NodeSchedulerService
|
||||
import net.corda.node.services.events.ScheduledActivityObserver
|
||||
import net.corda.node.services.identity.PersistentIdentityService
|
||||
@ -55,16 +58,15 @@ import net.corda.node.shell.InteractiveShell
|
||||
import net.corda.node.utilities.AffinityExecutor
|
||||
import net.corda.nodeapi.internal.DevIdentityGenerator
|
||||
import net.corda.nodeapi.internal.SignedNodeInfo
|
||||
import net.corda.nodeapi.internal.crypto.KeyStoreWrapper
|
||||
import net.corda.nodeapi.internal.crypto.X509CertificateFactory
|
||||
import net.corda.nodeapi.internal.crypto.X509Utilities
|
||||
import net.corda.nodeapi.internal.crypto.loadKeyStore
|
||||
import net.corda.nodeapi.internal.network.NETWORK_PARAMS_FILE_NAME
|
||||
import net.corda.nodeapi.internal.network.NetworkParameters
|
||||
import net.corda.nodeapi.internal.network.verifiedNetworkMapCert
|
||||
import net.corda.nodeapi.internal.persistence.CordaPersistence
|
||||
import net.corda.nodeapi.internal.persistence.DatabaseConfig
|
||||
import net.corda.nodeapi.internal.persistence.HibernateConfiguration
|
||||
import net.corda.nodeapi.internal.storeLegalIdentity
|
||||
import org.apache.activemq.artemis.utils.ReusableLatch
|
||||
import org.hibernate.type.descriptor.java.JavaTypeDescriptorRegistry
|
||||
import org.slf4j.Logger
|
||||
@ -140,7 +142,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
|
||||
private val _nodeReadyFuture = openFuture<Unit>()
|
||||
protected var networkMapClient: NetworkMapClient? = null
|
||||
|
||||
lateinit var securityManager: RPCSecurityManager get
|
||||
lateinit var securityManager: RPCSecurityManager
|
||||
|
||||
/** Completes once the node has successfully registered with the network map service
|
||||
* or has loaded network map data from local database */
|
||||
@ -568,9 +570,9 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
|
||||
private fun validateKeystore() {
|
||||
val containCorrectKeys = try {
|
||||
// This will throw IOException if key file not found or KeyStoreException if keystore password is incorrect.
|
||||
val sslKeystore = loadKeyStore(configuration.sslKeystore, configuration.keyStorePassword)
|
||||
val identitiesKeystore = loadKeyStore(configuration.nodeKeystore, configuration.keyStorePassword)
|
||||
sslKeystore.containsAlias(X509Utilities.CORDA_CLIENT_TLS) && identitiesKeystore.containsAlias(X509Utilities.CORDA_CLIENT_CA)
|
||||
val sslKeystore = configuration.loadSslKeyStore()
|
||||
val identitiesKeystore = configuration.loadNodeKeyStore()
|
||||
X509Utilities.CORDA_CLIENT_TLS in sslKeystore && X509Utilities.CORDA_CLIENT_CA in identitiesKeystore
|
||||
} catch (e: KeyStoreException) {
|
||||
log.warn("Certificate key store found but key store password does not match configuration.")
|
||||
false
|
||||
@ -585,15 +587,12 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
|
||||
}
|
||||
|
||||
// Check all cert path chain to the trusted root
|
||||
val sslKeystore = loadKeyStore(configuration.sslKeystore, configuration.keyStorePassword)
|
||||
val identitiesKeystore = loadKeyStore(configuration.nodeKeystore, configuration.keyStorePassword)
|
||||
val trustStore = loadKeyStore(configuration.trustStoreFile, configuration.trustStorePassword)
|
||||
val sslRoot = sslKeystore.getCertificateChain(X509Utilities.CORDA_CLIENT_TLS).last()
|
||||
val clientCARoot = identitiesKeystore.getCertificateChain(X509Utilities.CORDA_CLIENT_CA).last()
|
||||
val trustRoot = trustStore.getCertificate(X509Utilities.CORDA_ROOT_CA)
|
||||
val sslCertChainRoot = configuration.loadSslKeyStore().getCertificateChain(X509Utilities.CORDA_CLIENT_TLS).last()
|
||||
val nodeCaCertChainRoot = configuration.loadNodeKeyStore().getCertificateChain(X509Utilities.CORDA_CLIENT_CA).last()
|
||||
val trustRoot = configuration.loadTrustStore().getCertificate(X509Utilities.CORDA_ROOT_CA)
|
||||
|
||||
require(sslRoot == trustRoot) { "TLS certificate must chain to the trusted root." }
|
||||
require(clientCARoot == trustRoot) { "Client CA certificate must chain to the trusted root." }
|
||||
require(sslCertChainRoot == trustRoot) { "TLS certificate must chain to the trusted root." }
|
||||
require(nodeCaCertChainRoot == trustRoot) { "Client CA certificate must chain to the trusted root." }
|
||||
}
|
||||
|
||||
// Specific class so that MockNode can catch it.
|
||||
@ -684,12 +683,9 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
|
||||
}
|
||||
|
||||
private fun makeIdentityService(identityCert: X509Certificate): PersistentIdentityService {
|
||||
val trustStore = KeyStoreWrapper(configuration.trustStoreFile, configuration.trustStorePassword)
|
||||
val caKeyStore = KeyStoreWrapper(configuration.nodeKeystore, configuration.keyStorePassword)
|
||||
val trustRoot = trustStore.getX509Certificate(X509Utilities.CORDA_ROOT_CA)
|
||||
val clientCa = caKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA)
|
||||
val caCertificates = arrayOf(identityCert, clientCa.certificate)
|
||||
return PersistentIdentityService(trustRoot, *caCertificates)
|
||||
val trustRoot = configuration.loadTrustStore().getCertificate(X509Utilities.CORDA_ROOT_CA)
|
||||
val nodeCa = configuration.loadNodeKeyStore().getCertificate(X509Utilities.CORDA_CLIENT_CA)
|
||||
return PersistentIdentityService(trustRoot, identityCert, nodeCa)
|
||||
}
|
||||
|
||||
protected abstract fun makeTransactionVerifierService(): TransactionVerifierService
|
||||
@ -713,7 +709,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
|
||||
protected abstract fun startMessagingService(rpcOps: RPCOps)
|
||||
|
||||
private fun obtainIdentity(notaryConfig: NotaryConfig?): Pair<PartyAndCertificate, KeyPair> {
|
||||
val keyStore = KeyStoreWrapper(configuration.nodeKeystore, configuration.keyStorePassword)
|
||||
val keyStore = configuration.loadNodeKeyStore()
|
||||
|
||||
val (id, singleName) = if (notaryConfig == null || !notaryConfig.isClusterConfig) {
|
||||
// Node's main identity or if it's a single node notary
|
||||
@ -725,19 +721,20 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
|
||||
// TODO: Integrate with Key management service?
|
||||
val privateKeyAlias = "$id-private-key"
|
||||
|
||||
if (!keyStore.containsAlias(privateKeyAlias)) {
|
||||
if (privateKeyAlias !in keyStore) {
|
||||
singleName ?: throw IllegalArgumentException(
|
||||
"Unable to find in the key store the identity of the distributed notary ($id) the node is part of")
|
||||
// TODO: Remove use of [IdentityGenerator.generateToDisk].
|
||||
"Unable to find in the key store the identity of the distributed notary the node is part of")
|
||||
log.info("$privateKeyAlias not found in key store ${configuration.nodeKeystore}, generating fresh key!")
|
||||
keyStore.storeLegalIdentity(singleName, privateKeyAlias, generateKeyPair())
|
||||
// TODO This check shouldn't be needed
|
||||
check(singleName == configuration.myLegalName)
|
||||
keyStore.storeLegalIdentity(privateKeyAlias, generateKeyPair())
|
||||
}
|
||||
|
||||
val (x509Cert, keyPair) = keyStore.getCertificateAndKeyPair(privateKeyAlias)
|
||||
|
||||
// TODO: Use configuration to indicate composite key should be used instead of public key for the identity.
|
||||
val compositeKeyAlias = "$id-composite-key"
|
||||
val certificates = if (keyStore.containsAlias(compositeKeyAlias)) {
|
||||
val certificates = if (compositeKeyAlias in keyStore) {
|
||||
// Use composite key instead if it exists
|
||||
val certificate = keyStore.getCertificate(compositeKeyAlias)
|
||||
// We have to create the certificate chain for the composite key manually, this is because we don't have a keystore
|
||||
@ -747,12 +744,11 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
|
||||
} else {
|
||||
keyStore.getCertificateChain(privateKeyAlias).let {
|
||||
check(it[0] == x509Cert) { "Certificates from key store do not line up!" }
|
||||
it.asList()
|
||||
it
|
||||
}
|
||||
}
|
||||
|
||||
val nodeCert = certificates[0] as? X509Certificate ?: throw ConfigurationException("Node certificate must be an X.509 certificate")
|
||||
val subject = CordaX500Name.build(nodeCert.subjectX500Principal)
|
||||
val subject = CordaX500Name.build(certificates[0].subjectX500Principal)
|
||||
// TODO Include the name of the distributed notary, which the node is part of, in the notary config so that we
|
||||
// can cross-check the identity we get from the key store
|
||||
if (singleName != null && subject != singleName) {
|
||||
|
@ -10,7 +10,9 @@ import net.corda.core.internal.div
|
||||
import net.corda.core.internal.exists
|
||||
import net.corda.nodeapi.internal.config.SSLConfiguration
|
||||
import net.corda.nodeapi.internal.createDevKeyStores
|
||||
import net.corda.nodeapi.internal.crypto.*
|
||||
import net.corda.nodeapi.internal.crypto.X509KeyStore
|
||||
import net.corda.nodeapi.internal.crypto.loadKeyStore
|
||||
import net.corda.nodeapi.internal.crypto.save
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.nio.file.Path
|
||||
|
||||
@ -52,22 +54,21 @@ fun SSLConfiguration.configureDevKeyAndTrustStores(myLegalName: CordaX500Name) {
|
||||
loadKeyStore(javaClass.classLoader.getResourceAsStream("certificates/cordatruststore.jks"), "trustpass").save(trustStoreFile, trustStorePassword)
|
||||
}
|
||||
if (!sslKeystore.exists() || !nodeKeystore.exists()) {
|
||||
createDevKeyStores(myLegalName)
|
||||
val (nodeKeyStore) = createDevKeyStores(myLegalName)
|
||||
|
||||
// Move distributed service composite key (generated by IdentityGenerator.generateToDisk) to keystore if exists.
|
||||
val distributedServiceKeystore = certificatesDirectory / "distributedService.jks"
|
||||
if (distributedServiceKeystore.exists()) {
|
||||
val serviceKeystore = loadKeyStore(distributedServiceKeystore, "cordacadevpass")
|
||||
val cordaNodeKeystore = loadKeyStore(nodeKeystore, keyStorePassword)
|
||||
|
||||
serviceKeystore.aliases().iterator().forEach {
|
||||
if (serviceKeystore.isKeyEntry(it)) {
|
||||
cordaNodeKeystore.setKeyEntry(it, serviceKeystore.getKey(it, "cordacadevkeypass".toCharArray()), keyStorePassword.toCharArray(), serviceKeystore.getCertificateChain(it))
|
||||
} else {
|
||||
cordaNodeKeystore.setCertificateEntry(it, serviceKeystore.getCertificate(it))
|
||||
val serviceKeystore = X509KeyStore.fromFile(distributedServiceKeystore, "cordacadevpass")
|
||||
nodeKeyStore.update {
|
||||
serviceKeystore.aliases().forEach {
|
||||
if (serviceKeystore.internal.isKeyEntry(it)) {
|
||||
setPrivateKey(it, serviceKeystore.getPrivateKey(it, "cordacadevkeypass"), serviceKeystore.getCertificateChain(it))
|
||||
} else {
|
||||
setCertificate(it, serviceKeystore.getCertificate(it))
|
||||
}
|
||||
}
|
||||
}
|
||||
cordaNodeKeystore.save(nodeKeystore, keyStorePassword)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -14,7 +14,6 @@ import net.corda.nodeapi.internal.ArtemisMessagingComponent
|
||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.NODE_USER
|
||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.PEER_USER
|
||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent.RemoteInboxAddress.Companion.translateLocalQueueToInboxAddress
|
||||
import net.corda.nodeapi.internal.crypto.loadKeyStore
|
||||
import org.apache.activemq.artemis.api.core.SimpleString
|
||||
import org.apache.activemq.artemis.api.core.client.ActiveMQClient.DEFAULT_ACK_BATCH_SIZE
|
||||
import org.apache.activemq.artemis.api.core.client.ClientConsumer
|
||||
@ -38,9 +37,9 @@ internal class AMQPBridgeManager(val config: NodeConfiguration, val p2pAddress:
|
||||
private val lock = ReentrantLock()
|
||||
private val bridgeNameToBridgeMap = mutableMapOf<String, AMQPBridge>()
|
||||
private var sharedEventLoopGroup: EventLoopGroup? = null
|
||||
private val keyStore = loadKeyStore(config.sslKeystore, config.keyStorePassword)
|
||||
private val keyStore = config.loadSslKeyStore().internal
|
||||
private val keyStorePrivateKeyPassword: String = config.keyStorePassword
|
||||
private val trustStore = loadKeyStore(config.trustStoreFile, config.trustStorePassword)
|
||||
private val trustStore = config.loadTrustStore().internal
|
||||
private var artemis: ArtemisMessagingClient? = null
|
||||
|
||||
companion object {
|
||||
|
@ -34,6 +34,8 @@ import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.P2P_PREFIX
|
||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.PEERS_PREFIX
|
||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.PEER_USER
|
||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent.NodeAddress
|
||||
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.loadKeyStore
|
||||
import net.corda.nodeapi.internal.requireOnDefaultFileSystem
|
||||
import org.apache.activemq.artemis.api.core.SimpleString
|
||||
@ -205,8 +207,8 @@ class ArtemisMessagingServer(private val config: NodeConfiguration,
|
||||
|
||||
@Throws(IOException::class, KeyStoreException::class)
|
||||
private fun createArtemisSecurityManager(loginListener: LoginListener): ActiveMQJAASSecurityManager {
|
||||
val keyStore = loadKeyStore(config.sslKeystore, config.keyStorePassword)
|
||||
val trustStore = loadKeyStore(config.trustStoreFile, config.trustStorePassword)
|
||||
val keyStore = config.loadSslKeyStore().internal
|
||||
val trustStore = config.loadTrustStore().internal
|
||||
|
||||
val defaultCertPolicies = mapOf(
|
||||
PEER_ROLE to CertificateChainCheckPolicy.RootMustMatch,
|
||||
|
@ -23,6 +23,7 @@ import org.apache.activemq.artemis.core.server.ActiveMQServer
|
||||
import org.apache.activemq.artemis.core.server.cluster.Transformer
|
||||
import org.apache.activemq.artemis.spi.core.remoting.*
|
||||
import org.apache.activemq.artemis.utils.ConfigurationHelper
|
||||
import java.security.cert.X509Certificate
|
||||
import java.time.Duration
|
||||
import java.util.concurrent.Executor
|
||||
import java.util.concurrent.ScheduledExecutorService
|
||||
@ -161,7 +162,7 @@ class VerifyingNettyConnectorFactory : NettyConnectorFactory() {
|
||||
"Peer has wrong subject name in the certificate - expected $expectedLegalNames but got $peerCertificateName. This is either a fatal " +
|
||||
"misconfiguration by the remote peer or an SSL man-in-the-middle attack!"
|
||||
}
|
||||
X509Utilities.validateCertificateChain(session.localCertificates.last() as java.security.cert.X509Certificate, *session.peerCertificates)
|
||||
X509Utilities.validateCertificateChain(session.localCertificates.last() as X509Certificate, *session.peerCertificates)
|
||||
} catch (e: IllegalArgumentException) {
|
||||
connection.close()
|
||||
log.error(e.message)
|
||||
|
@ -8,8 +8,6 @@ import net.corda.node.internal.security.RPCSecurityManager
|
||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.NODE_USER
|
||||
import net.corda.nodeapi.internal.config.SSLConfiguration
|
||||
import net.corda.nodeapi.internal.crypto.X509Utilities
|
||||
import net.corda.nodeapi.internal.crypto.getX509Certificate
|
||||
import net.corda.nodeapi.internal.crypto.loadKeyStore
|
||||
import org.apache.activemq.artemis.api.core.management.ActiveMQServerControl
|
||||
|
||||
class RPCMessagingClient(private val config: SSLConfiguration, serverAddress: NetworkHostAndPort, maxMessageSize: Int) : SingletonSerializeAsToken(), AutoCloseable {
|
||||
@ -18,7 +16,7 @@ class RPCMessagingClient(private val config: SSLConfiguration, serverAddress: Ne
|
||||
|
||||
fun start(rpcOps: RPCOps, securityManager: RPCSecurityManager) = synchronized(this) {
|
||||
val locator = artemis.start().sessionFactory.serverLocator
|
||||
val myCert = loadKeyStore(config.sslKeystore, config.keyStorePassword).getX509Certificate(X509Utilities.CORDA_CLIENT_TLS)
|
||||
val myCert = config.loadSslKeyStore().getCertificate(X509Utilities.CORDA_CLIENT_TLS)
|
||||
rpcServer = RPCServer(rpcOps, NODE_USER, NODE_USER, locator, securityManager, CordaX500Name.build(myCert.subjectX500Principal))
|
||||
}
|
||||
|
||||
|
@ -9,7 +9,7 @@ import java.io.IOException
|
||||
import java.net.HttpURLConnection
|
||||
import java.net.HttpURLConnection.*
|
||||
import java.net.URL
|
||||
import java.security.cert.Certificate
|
||||
import java.security.cert.X509Certificate
|
||||
import java.util.*
|
||||
import java.util.zip.ZipInputStream
|
||||
|
||||
@ -22,19 +22,19 @@ class HTTPNetworkRegistrationService(compatibilityZoneURL: URL) : NetworkRegistr
|
||||
}
|
||||
|
||||
@Throws(CertificateRequestException::class)
|
||||
override fun retrieveCertificates(requestId: String): Array<Certificate>? {
|
||||
override fun retrieveCertificates(requestId: String): List<X509Certificate>? {
|
||||
// Poll server to download the signed certificate once request has been approved.
|
||||
val conn = URL("$registrationURL/$requestId").openHttpConnection()
|
||||
conn.requestMethod = "GET"
|
||||
|
||||
return when (conn.responseCode) {
|
||||
HTTP_OK -> ZipInputStream(conn.inputStream).use {
|
||||
val certificates = ArrayList<Certificate>()
|
||||
val certificates = ArrayList<X509Certificate>()
|
||||
val factory = X509CertificateFactory()
|
||||
while (it.nextEntry != null) {
|
||||
certificates += factory.generateCertificate(it)
|
||||
}
|
||||
certificates.toTypedArray()
|
||||
certificates
|
||||
}
|
||||
HTTP_NO_CONTENT -> null
|
||||
HTTP_UNAUTHORIZED -> throw CertificateRequestException("Certificate signing request has been rejected: ${conn.errorMessage}")
|
||||
|
@ -5,7 +5,8 @@ import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.internal.*
|
||||
import net.corda.core.utilities.seconds
|
||||
import net.corda.node.services.config.NodeConfiguration
|
||||
import net.corda.nodeapi.internal.crypto.*
|
||||
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
|
||||
@ -14,7 +15,6 @@ import org.bouncycastle.util.io.pem.PemObject
|
||||
import java.io.StringWriter
|
||||
import java.security.KeyPair
|
||||
import java.security.KeyStore
|
||||
import java.security.cert.Certificate
|
||||
import java.security.cert.X509Certificate
|
||||
|
||||
/**
|
||||
@ -28,10 +28,8 @@ class NetworkRegistrationHelper(private val config: NodeConfiguration, private v
|
||||
}
|
||||
|
||||
private val requestIdStore = config.certificatesDirectory / "certificate-request-id.txt"
|
||||
private val keystorePassword = config.keyStorePassword
|
||||
// TODO: Use different password for private key.
|
||||
private val privateKeyPassword = config.keyStorePassword
|
||||
private val trustStore: KeyStore
|
||||
private val rootCert: X509Certificate
|
||||
|
||||
init {
|
||||
@ -39,8 +37,7 @@ class NetworkRegistrationHelper(private val config: NodeConfiguration, private v
|
||||
"${config.trustStoreFile} does not exist. This file must contain the root CA cert of your compatibility zone. " +
|
||||
"Please contact your CZ operator."
|
||||
}
|
||||
trustStore = loadKeyStore(config.trustStoreFile, config.trustStorePassword)
|
||||
val rootCert = trustStore.getCertificate(CORDA_ROOT_CA)
|
||||
val rootCert = config.loadTrustStore().internal.getCertificate(CORDA_ROOT_CA)
|
||||
require(rootCert != null) {
|
||||
"${config.trustStoreFile} does not contain a certificate with the key $CORDA_ROOT_CA." +
|
||||
"This file must contain the root CA cert of your compatibility zone. " +
|
||||
@ -62,24 +59,23 @@ class NetworkRegistrationHelper(private val config: NodeConfiguration, private v
|
||||
*/
|
||||
fun buildKeystore() {
|
||||
config.certificatesDirectory.createDirectories()
|
||||
val nodeKeyStore = loadOrCreateKeyStore(config.nodeKeystore, keystorePassword)
|
||||
if (nodeKeyStore.containsAlias(CORDA_CLIENT_CA)) {
|
||||
val nodeKeyStore = config.loadNodeKeyStore(createNew = true)
|
||||
if (CORDA_CLIENT_CA in nodeKeyStore) {
|
||||
println("Certificate already exists, Corda node will now terminate...")
|
||||
return
|
||||
}
|
||||
|
||||
// Create or load self signed keypair from the key store.
|
||||
// We use the self sign certificate to store the key temporarily in the keystore while waiting for the request approval.
|
||||
if (!nodeKeyStore.containsAlias(SELF_SIGNED_PRIVATE_KEY)) {
|
||||
if (SELF_SIGNED_PRIVATE_KEY !in nodeKeyStore) {
|
||||
val keyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
|
||||
val selfSignCert = X509Utilities.createSelfSignedCACertificate(config.myLegalName.x500Principal, keyPair)
|
||||
// Save to the key store.
|
||||
nodeKeyStore.addOrReplaceKey(SELF_SIGNED_PRIVATE_KEY, keyPair.private, privateKeyPassword.toCharArray(),
|
||||
arrayOf(selfSignCert))
|
||||
nodeKeyStore.save(config.nodeKeystore, keystorePassword)
|
||||
nodeKeyStore.setPrivateKey(SELF_SIGNED_PRIVATE_KEY, keyPair.private, listOf(selfSignCert), keyPassword = privateKeyPassword)
|
||||
nodeKeyStore.save()
|
||||
}
|
||||
|
||||
val keyPair = nodeKeyStore.getKeyPair(SELF_SIGNED_PRIVATE_KEY, privateKeyPassword)
|
||||
val keyPair = nodeKeyStore.getCertificateAndKeyPair(SELF_SIGNED_PRIVATE_KEY, privateKeyPassword).keyPair
|
||||
val requestId = submitOrResumeCertificateSigningRequest(keyPair)
|
||||
|
||||
val certificates = try {
|
||||
@ -92,7 +88,7 @@ class NetworkRegistrationHelper(private val config: NodeConfiguration, private v
|
||||
throw certificateRequestException
|
||||
}
|
||||
|
||||
val nodeCaCert = certificates[0] as X509Certificate
|
||||
val nodeCaCert = certificates[0]
|
||||
|
||||
val nodeCaSubject = try {
|
||||
CordaX500Name.build(nodeCaCert.subjectX500Principal)
|
||||
@ -113,26 +109,26 @@ class NetworkRegistrationHelper(private val config: NodeConfiguration, private v
|
||||
}
|
||||
|
||||
println("Checking root of the certificate path is what we expect.")
|
||||
X509Utilities.validateCertificateChain(rootCert, *certificates)
|
||||
X509Utilities.validateCertificateChain(rootCert, *certificates.toTypedArray())
|
||||
|
||||
println("Certificate signing request approved, storing private key with the certificate chain.")
|
||||
// Save private key and certificate chain to the key store.
|
||||
nodeKeyStore.addOrReplaceKey(CORDA_CLIENT_CA, keyPair.private, privateKeyPassword.toCharArray(), certificates)
|
||||
nodeKeyStore.deleteEntry(SELF_SIGNED_PRIVATE_KEY)
|
||||
nodeKeyStore.save(config.nodeKeystore, keystorePassword)
|
||||
nodeKeyStore.setPrivateKey(CORDA_CLIENT_CA, keyPair.private, certificates, keyPassword = privateKeyPassword)
|
||||
nodeKeyStore.internal.deleteEntry(SELF_SIGNED_PRIVATE_KEY)
|
||||
nodeKeyStore.save()
|
||||
println("Node private key and certificate stored in ${config.nodeKeystore}.")
|
||||
|
||||
println("Generating SSL certificate for node messaging service.")
|
||||
val sslKeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
|
||||
val sslCert = X509Utilities.createCertificate(
|
||||
CertificateType.TLS,
|
||||
nodeCaCert,
|
||||
keyPair,
|
||||
config.myLegalName.x500Principal,
|
||||
sslKeyPair.public)
|
||||
val sslKeyStore = loadOrCreateKeyStore(config.sslKeystore, keystorePassword)
|
||||
sslKeyStore.addOrReplaceKey(CORDA_CLIENT_TLS, sslKeyPair.private, privateKeyPassword.toCharArray(), arrayOf(sslCert, *certificates))
|
||||
sslKeyStore.save(config.sslKeystore, config.keyStorePassword)
|
||||
config.loadSslKeyStore(createNew = true).update {
|
||||
println("Generating SSL certificate for node messaging service.")
|
||||
val sslKeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
|
||||
val sslCert = X509Utilities.createCertificate(
|
||||
CertificateType.TLS,
|
||||
nodeCaCert,
|
||||
keyPair,
|
||||
config.myLegalName.x500Principal,
|
||||
sslKeyPair.public)
|
||||
setPrivateKey(CORDA_CLIENT_TLS, sslKeyPair.private, listOf(sslCert) + certificates)
|
||||
}
|
||||
println("SSL private key and certificate stored in ${config.sslKeystore}.")
|
||||
|
||||
// All done, clean up temp files.
|
||||
@ -145,7 +141,7 @@ class NetworkRegistrationHelper(private val config: NodeConfiguration, private v
|
||||
* @param requestId Certificate signing request ID.
|
||||
* @return Map of certificate chain.
|
||||
*/
|
||||
private fun pollServerForCertificates(requestId: String): Array<Certificate> {
|
||||
private fun pollServerForCertificates(requestId: String): List<X509Certificate> {
|
||||
println("Start polling server for certificate signing approval.")
|
||||
// Poll server to download the signed certificate once request has been approved.
|
||||
var certificates = certService.retrieveCertificates(requestId)
|
||||
|
@ -3,7 +3,7 @@ package net.corda.node.utilities.registration
|
||||
import net.corda.core.CordaException
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
import org.bouncycastle.pkcs.PKCS10CertificationRequest
|
||||
import java.security.cert.Certificate
|
||||
import java.security.cert.X509Certificate
|
||||
|
||||
interface NetworkRegistrationService {
|
||||
/** Submits a CSR to the signing service and returns an opaque request ID. */
|
||||
@ -11,7 +11,7 @@ interface NetworkRegistrationService {
|
||||
|
||||
/** Poll Certificate Signing Server for the request and returns a chain of certificates if request has been approved, null otherwise. */
|
||||
@Throws(CertificateRequestException::class)
|
||||
fun retrieveCertificates(requestId: String): Array<Certificate>?
|
||||
fun retrieveCertificates(requestId: String): List<X509Certificate>?
|
||||
}
|
||||
|
||||
@CordaSerializable
|
||||
|
@ -12,7 +12,8 @@ import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.internal.createDirectories
|
||||
import net.corda.core.internal.x500Name
|
||||
import net.corda.node.services.config.NodeConfiguration
|
||||
import net.corda.nodeapi.internal.crypto.*
|
||||
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.internal.createDevIntermediateCaCertPath
|
||||
import net.corda.testing.internal.rigorousMock
|
||||
@ -27,7 +28,6 @@ import java.security.cert.CertPathValidatorException
|
||||
import java.security.cert.X509Certificate
|
||||
import javax.security.auth.x500.X500Principal
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class NetworkRegistrationHelperTest {
|
||||
private val fs = Jimfs.newFileSystem(unix())
|
||||
@ -65,34 +65,31 @@ class NetworkRegistrationHelperTest {
|
||||
saveTrustStoreWithRootCa(nodeCaCertPath.last())
|
||||
createRegistrationHelper(nodeCaCertPath).buildKeystore()
|
||||
|
||||
val nodeKeystore = loadKeyStore(config.nodeKeystore, config.keyStorePassword)
|
||||
val sslKeystore = loadKeyStore(config.sslKeystore, config.keyStorePassword)
|
||||
val trustStore = loadKeyStore(config.trustStoreFile, config.trustStorePassword)
|
||||
val nodeKeystore = config.loadNodeKeyStore()
|
||||
val sslKeystore = config.loadSslKeyStore()
|
||||
val trustStore = config.loadTrustStore()
|
||||
|
||||
nodeKeystore.run {
|
||||
assertTrue(containsAlias(X509Utilities.CORDA_CLIENT_CA))
|
||||
assertFalse(containsAlias(X509Utilities.CORDA_INTERMEDIATE_CA))
|
||||
assertFalse(containsAlias(X509Utilities.CORDA_ROOT_CA))
|
||||
assertFalse(containsAlias(X509Utilities.CORDA_CLIENT_TLS))
|
||||
assertThat(getCertificateChain(X509Utilities.CORDA_CLIENT_CA)).containsExactly(*nodeCaCertPath)
|
||||
assertFalse(contains(X509Utilities.CORDA_INTERMEDIATE_CA))
|
||||
assertFalse(contains(X509Utilities.CORDA_ROOT_CA))
|
||||
assertFalse(contains(X509Utilities.CORDA_CLIENT_TLS))
|
||||
assertThat(getCertificateChain(X509Utilities.CORDA_CLIENT_CA)).containsExactlyElementsOf(nodeCaCertPath)
|
||||
}
|
||||
|
||||
sslKeystore.run {
|
||||
assertFalse(containsAlias(X509Utilities.CORDA_CLIENT_CA))
|
||||
assertFalse(containsAlias(X509Utilities.CORDA_INTERMEDIATE_CA))
|
||||
assertFalse(containsAlias(X509Utilities.CORDA_ROOT_CA))
|
||||
assertTrue(containsAlias(X509Utilities.CORDA_CLIENT_TLS))
|
||||
assertFalse(contains(X509Utilities.CORDA_CLIENT_CA))
|
||||
assertFalse(contains(X509Utilities.CORDA_INTERMEDIATE_CA))
|
||||
assertFalse(contains(X509Utilities.CORDA_ROOT_CA))
|
||||
val nodeTlsCertChain = getCertificateChain(X509Utilities.CORDA_CLIENT_TLS)
|
||||
assertThat(nodeTlsCertChain).hasSize(4)
|
||||
// The TLS cert has the same subject as the node CA cert
|
||||
assertThat(CordaX500Name.build((nodeTlsCertChain[0] as X509Certificate).subjectX500Principal)).isEqualTo(nodeLegalName)
|
||||
assertThat(nodeTlsCertChain.drop(1)).containsExactly(*nodeCaCertPath)
|
||||
assertThat(CordaX500Name.build(nodeTlsCertChain[0].subjectX500Principal)).isEqualTo(nodeLegalName)
|
||||
assertThat(nodeTlsCertChain.drop(1)).containsExactlyElementsOf(nodeCaCertPath)
|
||||
}
|
||||
|
||||
trustStore.run {
|
||||
assertFalse(containsAlias(X509Utilities.CORDA_CLIENT_CA))
|
||||
assertFalse(containsAlias(X509Utilities.CORDA_INTERMEDIATE_CA))
|
||||
assertTrue(containsAlias(X509Utilities.CORDA_ROOT_CA))
|
||||
assertFalse(contains(X509Utilities.CORDA_CLIENT_CA))
|
||||
assertFalse(contains(X509Utilities.CORDA_INTERMEDIATE_CA))
|
||||
assertThat(getCertificate(X509Utilities.CORDA_ROOT_CA)).isEqualTo(nodeCaCertPath.last())
|
||||
}
|
||||
}
|
||||
@ -139,7 +136,7 @@ class NetworkRegistrationHelperTest {
|
||||
}
|
||||
|
||||
private fun createNodeCaCertPath(type: CertificateType = CertificateType.NODE_CA,
|
||||
legalName: CordaX500Name = nodeLegalName): Array<X509Certificate> {
|
||||
legalName: CordaX500Name = nodeLegalName): List<X509Certificate> {
|
||||
val (rootCa, intermediateCa) = createDevIntermediateCaCertPath()
|
||||
val keyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
|
||||
val nameConstraints = NameConstraints(arrayOf(GeneralSubtree(GeneralName(GeneralName.directoryName, legalName.x500Name))), arrayOf())
|
||||
@ -150,10 +147,10 @@ class NetworkRegistrationHelperTest {
|
||||
legalName.x500Principal,
|
||||
keyPair.public,
|
||||
nameConstraints = nameConstraints)
|
||||
return arrayOf(nodeCaCert, intermediateCa.certificate, rootCa.certificate)
|
||||
return listOf(nodeCaCert, intermediateCa.certificate, rootCa.certificate)
|
||||
}
|
||||
|
||||
private fun createRegistrationHelper(response: Array<X509Certificate>): NetworkRegistrationHelper {
|
||||
private fun createRegistrationHelper(response: List<X509Certificate>): NetworkRegistrationHelper {
|
||||
val certService = rigorousMock<NetworkRegistrationService>().also {
|
||||
doReturn(requestId).whenever(it).submitRequest(any())
|
||||
doReturn(response).whenever(it).retrieveCertificates(eq(requestId))
|
||||
@ -163,9 +160,8 @@ class NetworkRegistrationHelperTest {
|
||||
|
||||
private fun saveTrustStoreWithRootCa(rootCert: X509Certificate) {
|
||||
config.certificatesDirectory.createDirectories()
|
||||
loadOrCreateKeyStore(config.trustStoreFile, config.trustStorePassword).also {
|
||||
it.addOrReplaceCertificate(X509Utilities.CORDA_ROOT_CA, rootCert)
|
||||
it.save(config.trustStoreFile, config.trustStorePassword)
|
||||
config.loadTrustStore(createNew = true).update {
|
||||
setCertificate(X509Utilities.CORDA_ROOT_CA, rootCert)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user