Moved KeyStoreUtilities out of core and into node

This commit is contained in:
Shams Asari 2017-07-13 13:59:45 +01:00
parent 5438e82e68
commit a49baddd4b
11 changed files with 182 additions and 163 deletions

View File

@ -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)
}

View File

@ -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

View File

@ -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<Certificate>())

View File

@ -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<Certificate>(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)
}
}

View File

@ -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,

View File

@ -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

View File

@ -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<String, Any?>) = ConfigFactory.parseMap(mapOf(*pairs))
operator fun Config.plus(overrides: Map<String, Any?>) = ConfigFactory.parseMap(overrides).withFallback(this)
fun configOf(vararg pairs: Pair<String, Any?>): Config = ConfigFactory.parseMap(mapOf(*pairs))
operator fun Config.plus(overrides: Map<String, Any?>): Config = ConfigFactory.parseMap(overrides).withFallback(this)
object ConfigHelper {
private val log = loggerFor<ConfigHelper>()
@ -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)
}

View File

@ -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

View File

@ -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)
}

View File

@ -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)

View File

@ -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))