From a49baddd4b5e17d2b78eac7d5809d6467aed1620 Mon Sep 17 00:00:00 2001 From: Shams Asari Date: Thu, 13 Jul 2017 13:59:45 +0100 Subject: [PATCH] Moved KeyStoreUtilities out of core and into node --- .../net/corda/core/crypto/X509Utilities.kt | 56 +--------- .../corda/core/crypto/CompositeKeyTests.kt | 14 ++- .../core/crypto/X509NameConstraintsTest.kt | 7 +- .../corda/core/crypto/X509UtilitiesTest.kt | 35 +++--- .../messaging/MQSecurityAsNodeTest.kt | 16 ++- .../net/corda/node/internal/AbstractNode.kt | 12 +- .../node/services/config/ConfigUtilities.kt | 68 ++++++++++-- .../messaging/ArtemisMessagingServer.kt | 6 +- .../node/utilities}/KeyStoreUtilities.kt | 105 +++++++++--------- .../registration/NetworkRegistrationHelper.kt | 18 ++- .../NetworkisRegistrationHelperTest.kt | 8 +- 11 files changed, 182 insertions(+), 163 deletions(-) rename {core/src/main/kotlin/net/corda/core/crypto => node/src/main/kotlin/net/corda/node/utilities}/KeyStoreUtilities.kt (67%) diff --git a/core/src/main/kotlin/net/corda/core/crypto/X509Utilities.kt b/core/src/main/kotlin/net/corda/core/crypto/X509Utilities.kt index 79973b514f..ec20ada436 100644 --- a/core/src/main/kotlin/net/corda/core/crypto/X509Utilities.kt +++ b/core/src/main/kotlin/net/corda/core/crypto/X509Utilities.kt @@ -1,11 +1,12 @@ package net.corda.core.crypto -import net.corda.core.crypto.Crypto.generateKeyPair import org.bouncycastle.asn1.ASN1Encodable import org.bouncycastle.asn1.x500.X500Name import org.bouncycastle.asn1.x500.X500NameBuilder import org.bouncycastle.asn1.x500.style.BCStyle -import org.bouncycastle.asn1.x509.* +import org.bouncycastle.asn1.x509.KeyPurposeId +import org.bouncycastle.asn1.x509.KeyUsage +import org.bouncycastle.asn1.x509.NameConstraints import org.bouncycastle.cert.X509CertificateHolder import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter import org.bouncycastle.openssl.jcajce.JcaPEMWriter @@ -15,10 +16,8 @@ import java.io.FileWriter import java.io.InputStream import java.nio.file.Path import java.security.KeyPair -import java.security.KeyStore import java.security.PublicKey import java.security.cert.* -import java.security.cert.Certificate import java.time.Duration import java.time.Instant import java.time.temporal.ChronoUnit @@ -167,55 +166,6 @@ object X509Utilities { } } - /** - * An all in wrapper to manufacture a server certificate and keys all stored in a KeyStore suitable for running TLS on the local machine. - * @param sslKeyStorePath KeyStore path to save ssl key and cert to. - * @param clientCAKeystorePath KeyStore path to save client CA key and cert to. - * @param storePassword access password for KeyStore. - * @param keyPassword PrivateKey access password for the generated keys. - * It is recommended that this is the same as the storePassword as most TLS libraries assume they are the same. - * @param caKeyStore KeyStore containing CA keys generated by createCAKeyStoreAndTrustStore. - * @param caKeyPassword password to unlock private keys in the CA KeyStore. - * @return The KeyStore created containing a private key, certificate chain and root CA public cert for use in TLS applications. - */ - fun createKeystoreForCordaNode(sslKeyStorePath: Path, - clientCAKeystorePath: Path, - storePassword: String, - keyPassword: String, - caKeyStore: KeyStore, - caKeyPassword: String, - legalName: X500Name, - signatureScheme: SignatureScheme = DEFAULT_TLS_SIGNATURE_SCHEME) { - - val rootCACert = caKeyStore.getX509Certificate(CORDA_ROOT_CA) - val (intermediateCACert, intermediateCAKeyPair) = caKeyStore.getCertificateAndKeyPair(CORDA_INTERMEDIATE_CA, caKeyPassword) - - val clientKey = generateKeyPair(signatureScheme) - val nameConstraints = NameConstraints(arrayOf(GeneralSubtree(GeneralName(GeneralName.directoryName, legalName))), arrayOf()) - val clientCACert = createCertificate(CertificateType.INTERMEDIATE_CA, intermediateCACert, intermediateCAKeyPair, legalName, clientKey.public, nameConstraints = nameConstraints) - - val tlsKey = generateKeyPair(signatureScheme) - val clientTLSCert = createCertificate(CertificateType.TLS, clientCACert, clientKey, legalName, tlsKey.public) - - val keyPass = keyPassword.toCharArray() - - val clientCAKeystore = KeyStoreUtilities.loadOrCreateKeyStore(clientCAKeystorePath, storePassword) - clientCAKeystore.addOrReplaceKey( - CORDA_CLIENT_CA, - clientKey.private, - keyPass, - org.bouncycastle.cert.path.CertPath(arrayOf(clientCACert, intermediateCACert, rootCACert))) - clientCAKeystore.save(clientCAKeystorePath, storePassword) - - val tlsKeystore = KeyStoreUtilities.loadOrCreateKeyStore(sslKeyStorePath, storePassword) - tlsKeystore.addOrReplaceKey( - CORDA_CLIENT_TLS, - tlsKey.private, - keyPass, - org.bouncycastle.cert.path.CertPath(arrayOf(clientTLSCert, clientCACert, intermediateCACert, rootCACert))) - tlsKeystore.save(sslKeyStorePath, storePassword) - } - fun createCertificateSigningRequest(subject: X500Name, keyPair: KeyPair, signatureScheme: SignatureScheme = DEFAULT_TLS_SIGNATURE_SCHEME) = Crypto.createCertificateSigningRequest(subject, keyPair, signatureScheme) } diff --git a/core/src/test/kotlin/net/corda/core/crypto/CompositeKeyTests.kt b/core/src/test/kotlin/net/corda/core/crypto/CompositeKeyTests.kt index 5294898acd..c0816b10ed 100644 --- a/core/src/test/kotlin/net/corda/core/crypto/CompositeKeyTests.kt +++ b/core/src/test/kotlin/net/corda/core/crypto/CompositeKeyTests.kt @@ -6,10 +6,14 @@ import net.corda.core.crypto.composite.CompositeSignaturesWithKeys import net.corda.core.div import net.corda.core.serialization.serialize import net.corda.core.utilities.OpaqueBytes +import net.corda.node.utilities.loadKeyStore +import net.corda.node.utilities.loadOrCreateKeyStore +import net.corda.node.utilities.save import org.bouncycastle.asn1.x500.X500Name import org.junit.Rule import org.junit.Test import org.junit.rules.TemporaryFolder +import java.security.PublicKey import kotlin.test.assertEquals import kotlin.test.assertFailsWith import kotlin.test.assertFalse @@ -24,9 +28,9 @@ class CompositeKeyTests { val bobKey = generateKeyPair() val charlieKey = generateKeyPair() - val alicePublicKey = aliceKey.public - val bobPublicKey = bobKey.public - val charliePublicKey = charlieKey.public + val alicePublicKey: PublicKey = aliceKey.public + val bobPublicKey: PublicKey = bobKey.public + val charliePublicKey: PublicKey = charlieKey.public val message = OpaqueBytes("Transaction".toByteArray()) @@ -332,12 +336,12 @@ class CompositeKeyTests { // Store certificate to keystore. val keystorePath = tempFolder.root.toPath() / "keystore.jks" - val keystore = KeyStoreUtilities.loadOrCreateKeyStore(keystorePath, "password") + val keystore = loadOrCreateKeyStore(keystorePath, "password") keystore.setCertificateEntry("CompositeKey", compositeKeyCert.cert) keystore.save(keystorePath, "password") // Load keystore from disk. - val keystore2 = KeyStoreUtilities.loadKeyStore(keystorePath, "password") + val keystore2 = loadKeyStore(keystorePath, "password") assertTrue { keystore2.containsAlias("CompositeKey") } val key = keystore2.getCertificate("CompositeKey").publicKey diff --git a/core/src/test/kotlin/net/corda/core/crypto/X509NameConstraintsTest.kt b/core/src/test/kotlin/net/corda/core/crypto/X509NameConstraintsTest.kt index 30292b00e7..974f449473 100644 --- a/core/src/test/kotlin/net/corda/core/crypto/X509NameConstraintsTest.kt +++ b/core/src/test/kotlin/net/corda/core/crypto/X509NameConstraintsTest.kt @@ -1,6 +1,9 @@ package net.corda.core.crypto import net.corda.core.toTypedArray +import net.corda.node.utilities.KEYSTORE_TYPE +import net.corda.node.utilities.addOrReplaceCertificate +import net.corda.node.utilities.addOrReplaceKey import org.bouncycastle.asn1.x500.X500Name import org.bouncycastle.asn1.x509.GeneralName import org.bouncycastle.asn1.x509.GeneralSubtree @@ -26,14 +29,14 @@ class X509NameConstraintsTest { val clientCACert = X509Utilities.createCertificate(CertificateType.INTERMEDIATE_CA, intermediateCACert, intermediateCAKeyPair, X509Utilities.getX509Name("Corda Client CA","London","demo@r3.com",null), clientCAKeyPair.public, nameConstraints = nameConstraints) val keyPass = "password" - val trustStore = KeyStore.getInstance(KeyStoreUtilities.KEYSTORE_TYPE) + val trustStore = KeyStore.getInstance(KEYSTORE_TYPE) trustStore.load(null, keyPass.toCharArray()) trustStore.addOrReplaceCertificate(X509Utilities.CORDA_ROOT_CA, rootCACert.cert) val tlsKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) val tlsCert = X509Utilities.createCertificate(CertificateType.TLS, clientCACert, clientCAKeyPair, subjectName, tlsKey.public) - val keyStore = KeyStore.getInstance(KeyStoreUtilities.KEYSTORE_TYPE) + val keyStore = KeyStore.getInstance(KEYSTORE_TYPE) keyStore.load(null, keyPass.toCharArray()) keyStore.addOrReplaceKey(X509Utilities.CORDA_CLIENT_TLS, tlsKey.private, keyPass.toCharArray(), Stream.of(tlsCert, clientCACert, intermediateCACert, rootCACert).map { it.cert }.toTypedArray()) diff --git a/core/src/test/kotlin/net/corda/core/crypto/X509UtilitiesTest.kt b/core/src/test/kotlin/net/corda/core/crypto/X509UtilitiesTest.kt index 780c300044..e2247edb7d 100644 --- a/core/src/test/kotlin/net/corda/core/crypto/X509UtilitiesTest.kt +++ b/core/src/test/kotlin/net/corda/core/crypto/X509UtilitiesTest.kt @@ -6,6 +6,8 @@ import net.corda.core.crypto.X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME import net.corda.core.crypto.X509Utilities.createSelfSignedCACertificate import net.corda.core.div import net.corda.core.toTypedArray +import net.corda.node.services.config.createKeystoreForCordaNode +import net.corda.node.utilities.* import net.corda.testing.MEGA_CORP import net.corda.testing.getTestX509Name import org.bouncycastle.asn1.x500.X500Name @@ -89,13 +91,13 @@ class X509UtilitiesTest { assertTrue(Arrays.equals(selfSignCert.subjectPublicKeyInfo.encoded, keyPair.public.encoded)) // Save the EdDSA private key with self sign cert in the keystore. - val keyStore = KeyStoreUtilities.loadOrCreateKeyStore(tmpKeyStore, "keystorepass") + val keyStore = loadOrCreateKeyStore(tmpKeyStore, "keystorepass") keyStore.setKeyEntry("Key", keyPair.private, "password".toCharArray(), Stream.of(selfSignCert).map { it.cert }.toTypedArray()) keyStore.save(tmpKeyStore, "keystorepass") // Load the keystore from file and make sure keys are intact. - val keyStore2 = KeyStoreUtilities.loadOrCreateKeyStore(tmpKeyStore, "keystorepass") + val keyStore2 = loadOrCreateKeyStore(tmpKeyStore, "keystorepass") val privateKey = keyStore2.getKey("Key", "password".toCharArray()) val pubKey = keyStore2.getCertificate("Key").publicKey @@ -114,13 +116,13 @@ class X509UtilitiesTest { val edDSACert = X509Utilities.createCertificate(CertificateType.TLS, ecDSACert, ecDSAKey, X500Name("CN=TestEdDSA"), edDSAKeypair.public) // Save the EdDSA private key with cert chains. - val keyStore = KeyStoreUtilities.loadOrCreateKeyStore(tmpKeyStore, "keystorepass") + val keyStore = loadOrCreateKeyStore(tmpKeyStore, "keystorepass") keyStore.setKeyEntry("Key", edDSAKeypair.private, "password".toCharArray(), Stream.of(ecDSACert, edDSACert).map { it.cert }.toTypedArray()) keyStore.save(tmpKeyStore, "keystorepass") // Load the keystore from file and make sure keys are intact. - val keyStore2 = KeyStoreUtilities.loadOrCreateKeyStore(tmpKeyStore, "keystorepass") + val keyStore2 = loadOrCreateKeyStore(tmpKeyStore, "keystorepass") val privateKey = keyStore2.getKey("Key", "password".toCharArray()) val certs = keyStore2.getCertificateChain("Key") @@ -142,8 +144,8 @@ class X509UtilitiesTest { createCAKeyStoreAndTrustStore(tmpKeyStore, "keystorepass", "keypass", tmpTrustStore, "trustpass") // Load back generated root CA Cert and private key from keystore and check against copy in truststore - val keyStore = KeyStoreUtilities.loadKeyStore(tmpKeyStore, "keystorepass") - val trustStore = KeyStoreUtilities.loadKeyStore(tmpTrustStore, "trustpass") + val keyStore = loadKeyStore(tmpKeyStore, "keystorepass") + val trustStore = loadKeyStore(tmpTrustStore, "trustpass") val rootCaCert = keyStore.getCertificate(X509Utilities.CORDA_ROOT_CA) as X509Certificate val rootCaPrivateKey = keyStore.getKey(X509Utilities.CORDA_ROOT_CA, "keypass".toCharArray()) as PrivateKey val rootCaFromTrustStore = trustStore.getCertificate(X509Utilities.CORDA_ROOT_CA) as X509Certificate @@ -182,14 +184,14 @@ class X509UtilitiesTest { "trustpass") // Load signing intermediate CA cert - val caKeyStore = KeyStoreUtilities.loadKeyStore(tmpCAKeyStore, "cakeystorepass") + val caKeyStore = loadKeyStore(tmpCAKeyStore, "cakeystorepass") val caCertAndKey = caKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_INTERMEDIATE_CA, "cakeypass") // Generate server cert and private key and populate another keystore suitable for SSL - X509Utilities.createKeystoreForCordaNode(tmpSSLKeyStore, tmpServerKeyStore, "serverstorepass", "serverkeypass", caKeyStore, "cakeypass", MEGA_CORP.name) + createKeystoreForCordaNode(tmpSSLKeyStore, tmpServerKeyStore, "serverstorepass", "serverkeypass", caKeyStore, "cakeypass", MEGA_CORP.name) // Load back server certificate - val serverKeyStore = KeyStoreUtilities.loadKeyStore(tmpServerKeyStore, "serverstorepass") + val serverKeyStore = loadKeyStore(tmpServerKeyStore, "serverstorepass") val serverCertAndKey = serverKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA, "serverkeypass") serverCertAndKey.certificate.isValidOn(Date()) @@ -198,7 +200,7 @@ class X509UtilitiesTest { assertTrue { serverCertAndKey.certificate.subject.toString().contains(MEGA_CORP.name.commonName) } // Load back server certificate - val sslKeyStore = KeyStoreUtilities.loadKeyStore(tmpSSLKeyStore, "serverstorepass") + val sslKeyStore = loadKeyStore(tmpSSLKeyStore, "serverstorepass") val sslCertAndKey = sslKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_CLIENT_TLS, "serverkeypass") sslCertAndKey.certificate.isValidOn(Date()) @@ -227,9 +229,9 @@ class X509UtilitiesTest { "trustpass") // Generate server cert and private key and populate another keystore suitable for SSL - X509Utilities.createKeystoreForCordaNode(tmpSSLKeyStore, tmpServerKeyStore, "serverstorepass", "serverstorepass", caKeyStore, "cakeypass", MEGA_CORP.name) - val keyStore = KeyStoreUtilities.loadKeyStore(tmpSSLKeyStore, "serverstorepass") - val trustStore = KeyStoreUtilities.loadKeyStore(tmpTrustStore, "trustpass") + createKeystoreForCordaNode(tmpSSLKeyStore, tmpServerKeyStore, "serverstorepass", "serverstorepass", caKeyStore, "cakeypass", MEGA_CORP.name) + val keyStore = loadKeyStore(tmpSSLKeyStore, "serverstorepass") + val trustStore = loadKeyStore(tmpTrustStore, "trustpass") val context = SSLContext.getInstance("TLS") val keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()) @@ -348,7 +350,7 @@ class X509UtilitiesTest { val intermediateCACert = X509Utilities.createCertificate(CertificateType.INTERMEDIATE_CA, rootCACert, rootCAKey, X509Utilities.getX509Name("Corda Node Intermediate CA","London","demo@r3.com",null), intermediateCAKeyPair.public) val keyPass = keyPassword.toCharArray() - val keyStore = KeyStoreUtilities.loadOrCreateKeyStore(keyStoreFilePath, storePassword) + val keyStore = loadOrCreateKeyStore(keyStoreFilePath, storePassword) keyStore.addOrReplaceKey(X509Utilities.CORDA_ROOT_CA, rootCAKey.private, keyPass, arrayOf(rootCACert.cert)) @@ -359,7 +361,7 @@ class X509UtilitiesTest { keyStore.save(keyStoreFilePath, storePassword) - val trustStore = KeyStoreUtilities.loadOrCreateKeyStore(trustStoreFilePath, trustStorePassword) + val trustStore = loadOrCreateKeyStore(trustStoreFilePath, trustStorePassword) trustStore.addOrReplaceCertificate(X509Utilities.CORDA_ROOT_CA, rootCACert.cert) trustStore.addOrReplaceCertificate(X509Utilities.CORDA_INTERMEDIATE_CA, intermediateCACert.cert) @@ -373,7 +375,7 @@ class X509UtilitiesTest { fun `Get correct private key type from Keystore`() { val keyPair = generateKeyPair(Crypto.ECDSA_SECP256R1_SHA256) val selfSignCert = createSelfSignedCACertificate(X500Name("CN=Test"), keyPair) - val keyStore = KeyStoreUtilities.loadOrCreateKeyStore(tempFile("testKeystore.jks"), "keystorepassword") + val keyStore = loadOrCreateKeyStore(tempFile("testKeystore.jks"), "keystorepassword") keyStore.setKeyEntry("Key", keyPair.private, "keypassword".toCharArray(), arrayOf(selfSignCert.cert)) val keyFromKeystore = keyStore.getKey("Key", "keypassword".toCharArray()) @@ -382,5 +384,4 @@ class X509UtilitiesTest { assertTrue(keyFromKeystore is java.security.interfaces.ECPrivateKey) // by default JKS returns SUN EC key assertTrue(keyFromKeystoreCasted is org.bouncycastle.jce.interfaces.ECPrivateKey) } - } diff --git a/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityAsNodeTest.kt b/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityAsNodeTest.kt index d4bb517abf..a5abf5f336 100644 --- a/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityAsNodeTest.kt +++ b/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityAsNodeTest.kt @@ -2,8 +2,11 @@ package net.corda.services.messaging import net.corda.core.copyTo import net.corda.core.createDirectories -import net.corda.core.crypto.* +import net.corda.core.crypto.CertificateType +import net.corda.core.crypto.Crypto +import net.corda.core.crypto.X509Utilities import net.corda.core.exists +import net.corda.node.utilities.* import net.corda.nodeapi.ArtemisMessagingComponent.Companion.NODE_USER import net.corda.nodeapi.ArtemisMessagingComponent.Companion.PEER_USER import net.corda.nodeapi.RPCApi @@ -94,7 +97,9 @@ class MQSecurityAsNodeTest : MQSecurityTest() { javaClass.classLoader.getResourceAsStream("net/corda/node/internal/certificates/cordatruststore.jks").copyTo(trustStoreFile) } - val caKeyStore = KeyStoreUtilities.loadKeyStore(javaClass.classLoader.getResourceAsStream("net/corda/node/internal/certificates/cordadevcakeys.jks"), "cordacadevpass") + val caKeyStore = loadKeyStore( + javaClass.classLoader.getResourceAsStream("net/corda/node/internal/certificates/cordadevcakeys.jks"), + "cordacadevpass") val rootCACert = caKeyStore.getX509Certificate(X509Utilities.CORDA_ROOT_CA) val intermediateCA = caKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_INTERMEDIATE_CA, "cordacadevkeypass") @@ -102,12 +107,13 @@ class MQSecurityAsNodeTest : MQSecurityTest() { // Set name constrain to the legal name. val nameConstraints = NameConstraints(arrayOf(GeneralSubtree(GeneralName(GeneralName.directoryName, legalName))), arrayOf()) - val clientCACert = X509Utilities.createCertificate(CertificateType.INTERMEDIATE_CA, intermediateCA.certificate, intermediateCA.keyPair, legalName, clientKey.public, nameConstraints = nameConstraints) + val clientCACert = X509Utilities.createCertificate(CertificateType.INTERMEDIATE_CA, intermediateCA.certificate, + intermediateCA.keyPair, legalName, clientKey.public, nameConstraints = nameConstraints) val tlsKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) // Using different x500 name in the TLS cert which is not allowed in the name constraints. val clientTLSCert = X509Utilities.createCertificate(CertificateType.TLS, clientCACert, clientKey, MINI_CORP.name, tlsKey.public) val keyPass = keyStorePassword.toCharArray() - val clientCAKeystore = KeyStoreUtilities.loadOrCreateKeyStore(nodeKeystore, keyStorePassword) + val clientCAKeystore = loadOrCreateKeyStore(nodeKeystore, keyStorePassword) clientCAKeystore.addOrReplaceKey( X509Utilities.CORDA_CLIENT_CA, clientKey.private, @@ -115,7 +121,7 @@ class MQSecurityAsNodeTest : MQSecurityTest() { CertPath(arrayOf(clientCACert, intermediateCA.certificate, rootCACert))) clientCAKeystore.save(nodeKeystore, keyStorePassword) - val tlsKeystore = KeyStoreUtilities.loadOrCreateKeyStore(sslKeystore, keyStorePassword) + val tlsKeystore = loadOrCreateKeyStore(sslKeystore, keyStorePassword) tlsKeystore.addOrReplaceKey( X509Utilities.CORDA_CLIENT_TLS, tlsKey.private, diff --git a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt index c3c12f93a9..51165a5719 100644 --- a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt @@ -59,10 +59,8 @@ import net.corda.node.services.vault.CashBalanceAsMetricsObserver import net.corda.node.services.vault.HibernateVaultQueryImpl import net.corda.node.services.vault.NodeVaultService import net.corda.node.services.vault.VaultSoftLockManager +import net.corda.node.utilities.* import net.corda.node.utilities.AddOrRemove.ADD -import net.corda.node.utilities.AffinityExecutor -import net.corda.node.utilities.configureDatabase -import net.corda.node.utilities.transaction import org.apache.activemq.artemis.utils.ReusableLatch import org.bouncycastle.asn1.x500.X500Name import org.jetbrains.exposed.sql.Database @@ -520,8 +518,8 @@ abstract class AbstractNode(open 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 = KeyStoreUtilities.loadKeyStore(configuration.sslKeystore, configuration.keyStorePassword) - val identitiesKeystore = KeyStoreUtilities.loadKeyStore(configuration.nodeKeystore, configuration.keyStorePassword) + 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) } catch (e: KeyStoreException) { log.warn("Certificate key store found but key store password does not match configuration.") @@ -535,7 +533,7 @@ abstract class AbstractNode(open val configuration: NodeConfiguration, "or if you don't have one yet, fill out the config file and run corda.jar --initial-registration. " + "Read more at: https://docs.corda.net/permissioning.html" } - val identitiesKeystore = KeyStoreUtilities.loadKeyStore(configuration.sslKeystore, configuration.keyStorePassword) + val identitiesKeystore = loadKeyStore(configuration.sslKeystore, configuration.keyStorePassword) val tlsIdentity = identitiesKeystore.getX509Certificate(X509Utilities.CORDA_CLIENT_TLS).subject require(tlsIdentity == configuration.myLegalName) { @@ -839,7 +837,7 @@ abstract class AbstractNode(open val configuration: NodeConfiguration, } private class KeyStoreWrapper(val keyStore: KeyStore, val storePath: Path, private val storePassword: String) { - constructor(storePath: Path, storePassword: String) : this(KeyStoreUtilities.loadKeyStore(storePath, storePassword), storePath, storePassword) + constructor(storePath: Path, storePassword: String) : this(loadKeyStore(storePath, storePassword), storePath, storePassword) fun certificateAndKeyPair(alias: String): CertificateAndKeyPair? { return if (keyStore.containsAlias(alias)) keyStore.getCertificateAndKeyPair(alias, storePassword) else null diff --git a/node/src/main/kotlin/net/corda/node/services/config/ConfigUtilities.kt b/node/src/main/kotlin/net/corda/node/services/config/ConfigUtilities.kt index 8fdae16bdb..882de35155 100644 --- a/node/src/main/kotlin/net/corda/node/services/config/ConfigUtilities.kt +++ b/node/src/main/kotlin/net/corda/node/services/config/ConfigUtilities.kt @@ -1,6 +1,3 @@ -// TODO: Remove when configureTestSSL() is moved. -@file:JvmName("ConfigUtilities") - package net.corda.node.services.config import com.typesafe.config.Config @@ -9,17 +6,21 @@ import com.typesafe.config.ConfigParseOptions import com.typesafe.config.ConfigRenderOptions import net.corda.core.copyTo import net.corda.core.createDirectories -import net.corda.core.crypto.KeyStoreUtilities -import net.corda.core.crypto.X509Utilities +import net.corda.core.crypto.* import net.corda.core.div import net.corda.core.exists import net.corda.core.utilities.loggerFor +import net.corda.node.utilities.* import net.corda.nodeapi.config.SSLConfiguration import org.bouncycastle.asn1.x500.X500Name +import org.bouncycastle.asn1.x509.GeneralName +import org.bouncycastle.asn1.x509.GeneralSubtree +import org.bouncycastle.asn1.x509.NameConstraints import java.nio.file.Path +import java.security.KeyStore -fun configOf(vararg pairs: Pair) = ConfigFactory.parseMap(mapOf(*pairs)) -operator fun Config.plus(overrides: Map) = ConfigFactory.parseMap(overrides).withFallback(this) +fun configOf(vararg pairs: Pair): Config = ConfigFactory.parseMap(mapOf(*pairs)) +operator fun Config.plus(overrides: Map): Config = ConfigFactory.parseMap(overrides).withFallback(this) object ConfigHelper { private val log = loggerFor() @@ -55,7 +56,56 @@ fun SSLConfiguration.configureDevKeyAndTrustStores(myLegalName: X500Name) { javaClass.classLoader.getResourceAsStream("net/corda/node/internal/certificates/cordatruststore.jks").copyTo(trustStoreFile) } if (!sslKeystore.exists() || !nodeKeystore.exists()) { - val caKeyStore = KeyStoreUtilities.loadKeyStore(javaClass.classLoader.getResourceAsStream("net/corda/node/internal/certificates/cordadevcakeys.jks"), "cordacadevpass") - X509Utilities.createKeystoreForCordaNode(sslKeystore, nodeKeystore, keyStorePassword, keyStorePassword, caKeyStore, "cordacadevkeypass", myLegalName) + val caKeyStore = loadKeyStore(javaClass.classLoader.getResourceAsStream("net/corda/node/internal/certificates/cordadevcakeys.jks"), "cordacadevpass") + createKeystoreForCordaNode(sslKeystore, nodeKeystore, keyStorePassword, keyStorePassword, caKeyStore, "cordacadevkeypass", myLegalName) } } + +/** + * An all in wrapper to manufacture a server certificate and keys all stored in a KeyStore suitable for running TLS on the local machine. + * @param sslKeyStorePath KeyStore path to save ssl key and cert to. + * @param clientCAKeystorePath KeyStore path to save client CA key and cert to. + * @param storePassword access password for KeyStore. + * @param keyPassword PrivateKey access password for the generated keys. + * It is recommended that this is the same as the storePassword as most TLS libraries assume they are the same. + * @param caKeyStore KeyStore containing CA keys generated by createCAKeyStoreAndTrustStore. + * @param caKeyPassword password to unlock private keys in the CA KeyStore. + * @return The KeyStore created containing a private key, certificate chain and root CA public cert for use in TLS applications. + */ +fun createKeystoreForCordaNode(sslKeyStorePath: Path, + clientCAKeystorePath: Path, + storePassword: String, + keyPassword: String, + caKeyStore: KeyStore, + caKeyPassword: String, + legalName: X500Name, + signatureScheme: SignatureScheme = X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) { + + val rootCACert = caKeyStore.getX509Certificate(X509Utilities.CORDA_ROOT_CA) + val (intermediateCACert, intermediateCAKeyPair) = caKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_INTERMEDIATE_CA, caKeyPassword) + + val clientKey = Crypto.generateKeyPair(signatureScheme) + val nameConstraints = NameConstraints(arrayOf(GeneralSubtree(GeneralName(GeneralName.directoryName, legalName))), arrayOf()) + val clientCACert = X509Utilities.createCertificate(CertificateType.INTERMEDIATE_CA, intermediateCACert, intermediateCAKeyPair, legalName, clientKey.public, nameConstraints = nameConstraints) + + val tlsKey = Crypto.generateKeyPair(signatureScheme) + val clientTLSCert = X509Utilities.createCertificate(CertificateType.TLS, clientCACert, clientKey, legalName, tlsKey.public) + + val keyPass = keyPassword.toCharArray() + + val clientCAKeystore = loadOrCreateKeyStore(clientCAKeystorePath, storePassword) + clientCAKeystore.addOrReplaceKey( + X509Utilities.CORDA_CLIENT_CA, + clientKey.private, + keyPass, + org.bouncycastle.cert.path.CertPath(arrayOf(clientCACert, intermediateCACert, rootCACert))) + clientCAKeystore.save(clientCAKeystorePath, storePassword) + + val tlsKeystore = loadOrCreateKeyStore(sslKeyStorePath, storePassword) + tlsKeystore.addOrReplaceKey( + X509Utilities.CORDA_CLIENT_TLS, + tlsKey.private, + keyPass, + org.bouncycastle.cert.path.CertPath(arrayOf(clientTLSCert, clientCACert, intermediateCACert, rootCACert))) + tlsKeystore.save(sslKeyStorePath, storePassword) +} \ No newline at end of file diff --git a/node/src/main/kotlin/net/corda/node/services/messaging/ArtemisMessagingServer.kt b/node/src/main/kotlin/net/corda/node/services/messaging/ArtemisMessagingServer.kt index 66b2da18f6..c300cfd69a 100644 --- a/node/src/main/kotlin/net/corda/node/services/messaging/ArtemisMessagingServer.kt +++ b/node/src/main/kotlin/net/corda/node/services/messaging/ArtemisMessagingServer.kt @@ -20,6 +20,8 @@ import net.corda.node.services.messaging.NodeLoginModule.Companion.NODE_ROLE import net.corda.node.services.messaging.NodeLoginModule.Companion.PEER_ROLE import net.corda.node.services.messaging.NodeLoginModule.Companion.RPC_ROLE import net.corda.node.services.messaging.NodeLoginModule.Companion.VERIFIER_ROLE +import net.corda.node.utilities.getX509Certificate +import net.corda.node.utilities.loadKeyStore import net.corda.nodeapi.* import net.corda.nodeapi.ArtemisMessagingComponent.Companion.NODE_USER import net.corda.nodeapi.ArtemisMessagingComponent.Companion.PEER_USER @@ -264,8 +266,8 @@ class ArtemisMessagingServer(override val config: NodeConfiguration, @Throws(IOException::class, KeyStoreException::class) private fun createArtemisSecurityManager(): ActiveMQJAASSecurityManager { - val keyStore = KeyStoreUtilities.loadKeyStore(config.sslKeystore, config.keyStorePassword) - val trustStore = KeyStoreUtilities.loadKeyStore(config.trustStoreFile, config.trustStorePassword) + val keyStore = loadKeyStore(config.sslKeystore, config.keyStorePassword) + val trustStore = loadKeyStore(config.trustStoreFile, config.trustStorePassword) val ourCertificate = keyStore.getX509Certificate(CORDA_CLIENT_TLS) // This is a sanity check and should not fail unless things have been misconfigured diff --git a/core/src/main/kotlin/net/corda/core/crypto/KeyStoreUtilities.kt b/node/src/main/kotlin/net/corda/node/utilities/KeyStoreUtilities.kt similarity index 67% rename from core/src/main/kotlin/net/corda/core/crypto/KeyStoreUtilities.kt rename to node/src/main/kotlin/net/corda/node/utilities/KeyStoreUtilities.kt index 0c88ee2f27..f4a26d5b28 100644 --- a/core/src/main/kotlin/net/corda/core/crypto/KeyStoreUtilities.kt +++ b/node/src/main/kotlin/net/corda/node/utilities/KeyStoreUtilities.kt @@ -1,5 +1,8 @@ -package net.corda.core.crypto +package net.corda.node.utilities +import net.corda.core.crypto.CertificateAndKeyPair +import net.corda.core.crypto.Crypto +import net.corda.core.crypto.cert import net.corda.core.exists import net.corda.core.read import net.corda.core.write @@ -12,60 +15,58 @@ import java.nio.file.Path import java.security.* import java.security.cert.Certificate -object KeyStoreUtilities { - val KEYSTORE_TYPE = "JKS" +val KEYSTORE_TYPE = "JKS" - /** - * Helper method to either open an existing keystore for modification, or create a new blank keystore. - * @param keyStoreFilePath location of KeyStore file. - * @param storePassword password to open the store. This does not have to be the same password as any keys stored, - * but for SSL purposes this is recommended. - * @return returns the KeyStore opened/created. - */ - fun loadOrCreateKeyStore(keyStoreFilePath: Path, storePassword: String): KeyStore { - val pass = storePassword.toCharArray() - val keyStore = KeyStore.getInstance(KEYSTORE_TYPE) - if (keyStoreFilePath.exists()) { - keyStoreFilePath.read { keyStore.load(it, pass) } - } else { - keyStore.load(null, pass) - keyStoreFilePath.write { keyStore.store(it, pass) } - } - return keyStore +/** + * Helper method to either open an existing keystore for modification, or create a new blank keystore. + * @param keyStoreFilePath location of KeyStore file. + * @param storePassword password to open the store. This does not have to be the same password as any keys stored, + * but for SSL purposes this is recommended. + * @return returns the KeyStore opened/created. + */ +fun loadOrCreateKeyStore(keyStoreFilePath: Path, storePassword: String): KeyStore { + val pass = storePassword.toCharArray() + val keyStore = KeyStore.getInstance(KEYSTORE_TYPE) + if (keyStoreFilePath.exists()) { + keyStoreFilePath.read { keyStore.load(it, pass) } + } else { + keyStore.load(null, pass) + keyStoreFilePath.write { keyStore.store(it, pass) } } + return keyStore +} - /** - * Helper method to open an existing keystore for modification/read. - * @param keyStoreFilePath location of KeyStore file which must exist, or this will throw FileNotFoundException. - * @param storePassword password to open the store. This does not have to be the same password as any keys stored, - * but for SSL purposes this is recommended. - * @return returns the KeyStore opened. - * @throws IOException if there was an error reading the key store from the file. - * @throws KeyStoreException if the password is incorrect or the key store is damaged. - */ - @Throws(KeyStoreException::class, IOException::class) - fun loadKeyStore(keyStoreFilePath: Path, storePassword: String): KeyStore { - return keyStoreFilePath.read { loadKeyStore(it, storePassword) } - } +/** + * Helper method to open an existing keystore for modification/read. + * @param keyStoreFilePath location of KeyStore file which must exist, or this will throw FileNotFoundException. + * @param storePassword password to open the store. This does not have to be the same password as any keys stored, + * but for SSL purposes this is recommended. + * @return returns the KeyStore opened. + * @throws IOException if there was an error reading the key store from the file. + * @throws KeyStoreException if the password is incorrect or the key store is damaged. + */ +@Throws(KeyStoreException::class, IOException::class) +fun loadKeyStore(keyStoreFilePath: Path, storePassword: String): KeyStore { + return keyStoreFilePath.read { loadKeyStore(it, storePassword) } +} - /** - * Helper method to open an existing keystore for modification/read. - * @param input stream containing a KeyStore e.g. loaded from a resource file. - * @param storePassword password to open the store. This does not have to be the same password as any keys stored, - * but for SSL purposes this is recommended. - * @return returns the KeyStore opened. - * @throws IOException if there was an error reading the key store from the stream. - * @throws KeyStoreException if the password is incorrect or the key store is damaged. - */ - @Throws(KeyStoreException::class, IOException::class) - fun loadKeyStore(input: InputStream, storePassword: String): KeyStore { - val pass = storePassword.toCharArray() - val keyStore = KeyStore.getInstance(KEYSTORE_TYPE) - input.use { - keyStore.load(input, pass) - } - return keyStore +/** + * Helper method to open an existing keystore for modification/read. + * @param input stream containing a KeyStore e.g. loaded from a resource file. + * @param storePassword password to open the store. This does not have to be the same password as any keys stored, + * but for SSL purposes this is recommended. + * @return returns the KeyStore opened. + * @throws IOException if there was an error reading the key store from the stream. + * @throws KeyStoreException if the password is incorrect or the key store is damaged. + */ +@Throws(KeyStoreException::class, IOException::class) +fun loadKeyStore(input: InputStream, storePassword: String): KeyStore { + val pass = storePassword.toCharArray() + val keyStore = KeyStore.getInstance(KEYSTORE_TYPE) + input.use { + keyStore.load(input, pass) } + return keyStore } /** @@ -107,7 +108,6 @@ fun KeyStore.addOrReplaceCertificate(alias: String, cert: Certificate) { this.setCertificateEntry(alias, cert) } - /** * Helper method save KeyStore to storage. * @param keyStoreFilePath the file location to save to. @@ -118,7 +118,6 @@ fun KeyStore.save(keyStoreFilePath: Path, storePassword: String) = keyStoreFileP fun KeyStore.store(out: OutputStream, password: String) = store(out, password.toCharArray()) - /** * Extract public and private keys from a KeyStore file assuming storage alias is known. * @param alias The name to lookup the Key and Certificate chain from. @@ -146,7 +145,7 @@ fun KeyStore.getCertificateAndKeyPair(alias: String, keyPassword: String): Certi * @return The X509Certificate found in the KeyStore under the specified alias. */ fun KeyStore.getX509Certificate(alias: String): X509CertificateHolder { - val encoded = getCertificate(alias)?.encoded ?: throw IllegalArgumentException("No certificate under alias \"${alias}\"") + val encoded = getCertificate(alias)?.encoded ?: throw IllegalArgumentException("No certificate under alias \"$alias\"") return X509CertificateHolder(encoded) } diff --git a/node/src/main/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelper.kt b/node/src/main/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelper.kt index 9701203d45..b362153d6d 100644 --- a/node/src/main/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelper.kt +++ b/node/src/main/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelper.kt @@ -1,24 +1,30 @@ package net.corda.node.utilities.registration import net.corda.core.* -import net.corda.core.crypto.* +import net.corda.core.crypto.CertificateType +import net.corda.core.crypto.Crypto +import net.corda.core.crypto.X509Utilities import net.corda.core.crypto.X509Utilities.CORDA_CLIENT_CA import net.corda.core.crypto.X509Utilities.CORDA_CLIENT_TLS import net.corda.core.crypto.X509Utilities.CORDA_ROOT_CA +import net.corda.core.crypto.cert import net.corda.node.services.config.NodeConfiguration +import net.corda.node.utilities.* import org.bouncycastle.cert.path.CertPath import org.bouncycastle.openssl.jcajce.JcaPEMWriter 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 kotlin.system.exitProcess /** * This checks the config.certificatesDirectory field for certificates required to connect to a Corda network. * If the certificates are not found, a [org.bouncycastle.pkcs.PKCS10CertificationRequest] will be submitted to - * Corda network permissioning server using [NetworkRegistrationService]. This process will enter a polling loop until the request has been approved, and then - * the certificate chain will be downloaded and stored in [Keystore] reside in the certificates directory. + * Corda network permissioning server using [NetworkRegistrationService]. This process will enter a polling loop until + * the request has been approved, and then the certificate chain will be downloaded and stored in [KeyStore] reside in + * the certificates directory. */ class NetworkRegistrationHelper(val config: NodeConfiguration, val certService: NetworkRegistrationService) { companion object { @@ -33,7 +39,7 @@ class NetworkRegistrationHelper(val config: NodeConfiguration, val certService: fun buildKeystore() { config.certificatesDirectory.createDirectories() - val caKeyStore = KeyStoreUtilities.loadOrCreateKeyStore(config.nodeKeystore, keystorePassword) + val caKeyStore = loadOrCreateKeyStore(config.nodeKeystore, keystorePassword) if (!caKeyStore.containsAlias(CORDA_CLIENT_CA)) { // 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. @@ -64,7 +70,7 @@ class NetworkRegistrationHelper(val config: NodeConfiguration, val certService: caKeyStore.deleteEntry(SELF_SIGNED_PRIVATE_KEY) caKeyStore.save(config.nodeKeystore, keystorePassword) // Save root certificates to trust store. - val trustStore = KeyStoreUtilities.loadOrCreateKeyStore(config.trustStoreFile, config.trustStorePassword) + val trustStore = loadOrCreateKeyStore(config.trustStoreFile, config.trustStorePassword) // Assumes certificate chain always starts with client certificate and end with root certificate. trustStore.addOrReplaceCertificate(CORDA_ROOT_CA, certificates.last()) trustStore.save(config.trustStoreFile, config.trustStorePassword) @@ -74,7 +80,7 @@ class NetworkRegistrationHelper(val config: NodeConfiguration, val certService: val sslKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) val caCert = caKeyStore.getX509Certificate(CORDA_CLIENT_CA) val sslCert = X509Utilities.createCertificate(CertificateType.TLS, caCert, keyPair, caCert.subject, sslKey.public) - val sslKeyStore = KeyStoreUtilities.loadOrCreateKeyStore(config.sslKeystore, keystorePassword) + val sslKeyStore = loadOrCreateKeyStore(config.sslKeystore, keystorePassword) sslKeyStore.addOrReplaceKey(CORDA_CLIENT_TLS, sslKey.private, privateKeyPassword.toCharArray(), arrayOf(sslCert.cert, *certificates)) sslKeyStore.save(config.sslKeystore, config.keyStorePassword) diff --git a/node/src/test/kotlin/net/corda/node/utilities/registration/NetworkisRegistrationHelperTest.kt b/node/src/test/kotlin/net/corda/node/utilities/registration/NetworkisRegistrationHelperTest.kt index 201fb206f4..c08e5bc72b 100644 --- a/node/src/test/kotlin/net/corda/node/utilities/registration/NetworkisRegistrationHelperTest.kt +++ b/node/src/test/kotlin/net/corda/node/utilities/registration/NetworkisRegistrationHelperTest.kt @@ -6,6 +6,7 @@ import com.nhaarman.mockito_kotlin.mock import net.corda.core.crypto.* import net.corda.core.exists import net.corda.core.toTypedArray +import net.corda.node.utilities.loadKeyStore import net.corda.testing.ALICE import net.corda.testing.getTestX509Name import net.corda.testing.testNodeConfiguration @@ -52,10 +53,9 @@ class NetworkRegistrationHelperTest { assertTrue(config.sslKeystore.exists()) assertTrue(config.trustStoreFile.exists()) - val nodeKeystore = KeyStoreUtilities.loadKeyStore(config.nodeKeystore, config.keyStorePassword) - val sslKeystore = KeyStoreUtilities.loadKeyStore(config.sslKeystore, config.keyStorePassword) - val trustStore = KeyStoreUtilities.loadKeyStore(config.trustStoreFile, config.trustStorePassword) - + val nodeKeystore = loadKeyStore(config.nodeKeystore, config.keyStorePassword) + val sslKeystore = loadKeyStore(config.sslKeystore, config.keyStorePassword) + val trustStore = loadKeyStore(config.trustStoreFile, config.trustStorePassword) nodeKeystore.run { assertTrue(containsAlias(X509Utilities.CORDA_CLIENT_CA))