mirror of
https://github.com/corda/corda.git
synced 2025-06-19 07:38:22 +00:00
ENT-2610: Separate passwords for store and for private keys in Corda OS. (#4090)
* ENT-2610: Separate passwords for store and for private keys in Corda OS. When it comes to KeyStores there are *2* passwords: 1 for the keyStore as a whole and separately there is one private keys within this keyStore. Unfortunately, those 2 passwords have to be the same due to Artemis limitation, for more details please see: `org.apache.activemq.artemis.core.remoting.impl.ssl.SSLSupport.loadKeyManagerFactory` where it is calling `KeyManagerFactory.init()` with store password. Before change in this PR, throughout our codebase there are multiple places where we assume that storePassword is the same as keyPassword, even in the classes that have nothing to do with Artemis. This is of course less than ideal as TLS communication may be used not only for Artemis connectivity (e.g. Bridge/Float interaction in Ent) and it is unfair to impose same passwords constraint on that communication channel. Therefore this PR is removing this limitation and properly separating storePassword from keyPassword. Linked Jira(https://r3-cev.atlassian.net/browse/ENT-2610) has for more background info. Suggest to start review from `net.corda.core.crypto.X509NameConstraintsTest` to get an idea about the nature of the changes made. * ENT-2610: Address PR input from @kchalkias * ENT-2610: Address PR input from @kchalkias, s/privateKeyPassword/entryPassword/ * ENT-2610: Address PR input from @kchalkias, s/keyPassword/entryPassword/ In the implementation of `CertificateStoreSupplier`
This commit is contained in:
@ -30,9 +30,9 @@ object DevIdentityGenerator {
|
||||
/** Install a node key store for the given node directory using the given legal name. */
|
||||
fun installKeyStoreWithNodeIdentity(nodeDir: Path, legalName: CordaX500Name): Party {
|
||||
val certificatesDirectory = nodeDir / "certificates"
|
||||
val signingCertStore = FileBasedCertificateStoreSupplier(certificatesDirectory / "nodekeystore.jks", "cordacadevpass")
|
||||
val p2pKeyStore = FileBasedCertificateStoreSupplier(certificatesDirectory / "sslkeystore.jks", "cordacadevpass")
|
||||
val p2pTrustStore = FileBasedCertificateStoreSupplier(certificatesDirectory / "truststore.jks", "trustpass")
|
||||
val signingCertStore = FileBasedCertificateStoreSupplier(certificatesDirectory / "nodekeystore.jks", DEV_CA_KEY_STORE_PASS, DEV_CA_KEY_STORE_PASS)
|
||||
val p2pKeyStore = FileBasedCertificateStoreSupplier(certificatesDirectory / "sslkeystore.jks", DEV_CA_KEY_STORE_PASS, DEV_CA_KEY_STORE_PASS)
|
||||
val p2pTrustStore = FileBasedCertificateStoreSupplier(certificatesDirectory / "truststore.jks", DEV_CA_TRUST_STORE_PASS, DEV_CA_TRUST_STORE_PRIVATE_KEY_PASS)
|
||||
val p2pSslConfig = SslConfiguration.mutual(p2pKeyStore, p2pTrustStore)
|
||||
|
||||
certificatesDirectory.createDirectories()
|
||||
@ -77,13 +77,16 @@ object DevIdentityGenerator {
|
||||
publicKey)
|
||||
}
|
||||
val distServKeyStoreFile = (nodeDir / "certificates").createDirectories() / "distributedService.jks"
|
||||
X509KeyStore.fromFile(distServKeyStoreFile, "cordacadevpass", createNew = true).update {
|
||||
X509KeyStore.fromFile(distServKeyStoreFile, DEV_CA_KEY_STORE_PASS, createNew = true).update {
|
||||
setCertificate("$DISTRIBUTED_NOTARY_ALIAS_PREFIX-composite-key", compositeKeyCert)
|
||||
setPrivateKey(
|
||||
"$DISTRIBUTED_NOTARY_ALIAS_PREFIX-private-key",
|
||||
keyPair.private,
|
||||
listOf(serviceKeyCert, DEV_INTERMEDIATE_CA.certificate, DEV_ROOT_CA.certificate),
|
||||
"cordacadevkeypass")
|
||||
DEV_CA_KEY_STORE_PASS // Unfortunately we have to use the same password for private key due to Artemis limitation, for more details please see:
|
||||
// org.apache.activemq.artemis.core.remoting.impl.ssl.SSLSupport.loadKeyManagerFactory
|
||||
// where it is calling `KeyManagerFactory.init()` with store password
|
||||
/*DEV_CA_PRIVATE_KEY_PASS*/)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -27,7 +27,8 @@ fun CertificateStore.registerDevSigningCertificates(legalName: CordaX500Name,
|
||||
devNodeCa: CertificateAndKeyPair = createDevNodeCa(intermediateCa, legalName)) {
|
||||
|
||||
update {
|
||||
setPrivateKey(X509Utilities.CORDA_CLIENT_CA, devNodeCa.keyPair.private, listOf(devNodeCa.certificate, intermediateCa.certificate, rootCert))
|
||||
setPrivateKey(X509Utilities.CORDA_CLIENT_CA, devNodeCa.keyPair.private, listOf(devNodeCa.certificate, intermediateCa.certificate, rootCert),
|
||||
this@registerDevSigningCertificates.entryPassword)
|
||||
}
|
||||
}
|
||||
|
||||
@ -39,7 +40,8 @@ fun CertificateStore.registerDevP2pCertificates(legalName: CordaX500Name,
|
||||
update {
|
||||
val tlsKeyPair = generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
|
||||
val tlsCert = X509Utilities.createCertificate(CertificateType.TLS, devNodeCa.certificate, devNodeCa.keyPair, legalName.x500Principal, tlsKeyPair.public)
|
||||
setPrivateKey(X509Utilities.CORDA_CLIENT_TLS, tlsKeyPair.private, listOf(tlsCert, devNodeCa.certificate, intermediateCa.certificate, rootCert))
|
||||
setPrivateKey(X509Utilities.CORDA_CLIENT_TLS, tlsKeyPair.private, listOf(tlsCert, devNodeCa.certificate, intermediateCa.certificate, rootCert),
|
||||
this@registerDevP2pCertificates.entryPassword)
|
||||
}
|
||||
}
|
||||
|
||||
@ -47,15 +49,14 @@ fun CertificateStore.storeLegalIdentity(alias: String, keyPair: KeyPair = Crypto
|
||||
val identityCertPath = query {
|
||||
val nodeCaCertPath = getCertificateChain(X509Utilities.CORDA_CLIENT_CA)
|
||||
// Assume key password = store password.
|
||||
val nodeCaCertAndKeyPair = getCertificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA)
|
||||
val nodeCaCertAndKeyPair = getCertificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA, this@storeLegalIdentity.entryPassword)
|
||||
// Create new keys and store in keystore.
|
||||
val identityCert = X509Utilities.createCertificate(CertificateType.LEGAL_IDENTITY, nodeCaCertAndKeyPair.certificate, nodeCaCertAndKeyPair.keyPair, nodeCaCertAndKeyPair.certificate.subjectX500Principal, keyPair.public)
|
||||
// TODO: X509Utilities.validateCertificateChain()
|
||||
// Assume key password = store password.
|
||||
listOf(identityCert) + nodeCaCertPath
|
||||
}
|
||||
update {
|
||||
setPrivateKey(alias, keyPair.private, identityCertPath)
|
||||
setPrivateKey(alias, keyPair.private, identityCertPath, this@storeLegalIdentity.entryPassword)
|
||||
}
|
||||
return PartyAndCertificate(X509Utilities.buildCertPath(identityCertPath))
|
||||
}
|
||||
@ -96,6 +97,7 @@ const val DEV_CA_KEY_STORE_FILE: String = "cordadevcakeys.jks"
|
||||
const val DEV_CA_KEY_STORE_PASS: String = "cordacadevpass"
|
||||
const val DEV_CA_TRUST_STORE_FILE: String = "cordatruststore.jks"
|
||||
const val DEV_CA_TRUST_STORE_PASS: String = "trustpass"
|
||||
const val DEV_CA_TRUST_STORE_PRIVATE_KEY_PASS: String = "trustpasskeypass"
|
||||
|
||||
// We need a class so that we can get hold of the class loader
|
||||
internal object DevCaHelper {
|
||||
@ -104,6 +106,8 @@ internal object DevCaHelper {
|
||||
}
|
||||
}
|
||||
|
||||
fun loadDevCaKeyStore(classLoader: ClassLoader = DevCaHelper::class.java.classLoader): CertificateStore = CertificateStore.fromResource("certificates/$DEV_CA_KEY_STORE_FILE", DEV_CA_KEY_STORE_PASS, classLoader)
|
||||
fun loadDevCaKeyStore(classLoader: ClassLoader = DevCaHelper::class.java.classLoader): CertificateStore = CertificateStore.fromResource(
|
||||
"certificates/$DEV_CA_KEY_STORE_FILE", DEV_CA_KEY_STORE_PASS, DEV_CA_PRIVATE_KEY_PASS, classLoader)
|
||||
|
||||
fun loadDevCaTrustStore(classLoader: ClassLoader = DevCaHelper::class.java.classLoader): CertificateStore = CertificateStore.fromResource("certificates/$DEV_CA_TRUST_STORE_FILE", DEV_CA_TRUST_STORE_PASS, classLoader)
|
||||
fun loadDevCaTrustStore(classLoader: ClassLoader = DevCaHelper::class.java.classLoader): CertificateStore = CertificateStore.fromResource(
|
||||
"certificates/$DEV_CA_TRUST_STORE_FILE", DEV_CA_TRUST_STORE_PASS, DEV_CA_TRUST_STORE_PRIVATE_KEY_PASS, classLoader)
|
||||
|
@ -14,17 +14,18 @@ interface CertificateStore : Iterable<Pair<String, X509Certificate>> {
|
||||
|
||||
companion object {
|
||||
|
||||
fun of(store: X509KeyStore, password: String): CertificateStore = DelegatingCertificateStore(store, password)
|
||||
fun of(store: X509KeyStore, password: String, entryPassword: String): CertificateStore = DelegatingCertificateStore(store, password, entryPassword)
|
||||
|
||||
fun fromFile(storePath: Path, password: String, createNew: Boolean): CertificateStore = DelegatingCertificateStore(X509KeyStore.fromFile(storePath, password, createNew), password)
|
||||
fun fromFile(storePath: Path, password: String, entryPassword: String, createNew: Boolean): CertificateStore = DelegatingCertificateStore(X509KeyStore.fromFile(storePath, password, createNew), password, entryPassword)
|
||||
|
||||
fun fromInputStream(stream: InputStream, password: String): CertificateStore = DelegatingCertificateStore(X509KeyStore.fromInputStream(stream, password), password)
|
||||
fun fromInputStream(stream: InputStream, password: String, entryPassword: String): CertificateStore = DelegatingCertificateStore(X509KeyStore.fromInputStream(stream, password), password, entryPassword)
|
||||
|
||||
fun fromResource(storeResourceName: String, password: String, classLoader: ClassLoader = Thread.currentThread().contextClassLoader): CertificateStore = fromInputStream(classLoader.getResourceAsStream(storeResourceName), password)
|
||||
fun fromResource(storeResourceName: String, password: String, entryPassword: String, classLoader: ClassLoader = Thread.currentThread().contextClassLoader): CertificateStore = fromInputStream(classLoader.getResourceAsStream(storeResourceName), password, entryPassword)
|
||||
}
|
||||
|
||||
val value: X509KeyStore
|
||||
val password: String
|
||||
val entryPassword: String
|
||||
|
||||
fun writeTo(stream: OutputStream) = value.internal.store(stream, password.toCharArray())
|
||||
|
||||
@ -79,4 +80,4 @@ interface CertificateStore : Iterable<Pair<String, X509Certificate>> {
|
||||
}
|
||||
}
|
||||
|
||||
private class DelegatingCertificateStore(override val value: X509KeyStore, override val password: String) : CertificateStore
|
||||
private class DelegatingCertificateStore(override val value: X509KeyStore, override val password: String, override val entryPassword: String) : CertificateStore
|
@ -18,7 +18,7 @@ interface CertificateStoreSupplier {
|
||||
}
|
||||
|
||||
// TODO replace reference to FileBasedCertificateStoreSupplier with CertificateStoreSupplier, after coming up with a way of passing certificate stores to Artemis.
|
||||
class FileBasedCertificateStoreSupplier(val path: Path, val password: String) : CertificateStoreSupplier {
|
||||
class FileBasedCertificateStoreSupplier(val path: Path, val storePassword: String, val entryPassword: String) : CertificateStoreSupplier {
|
||||
|
||||
override fun get(createNew: Boolean) = CertificateStore.fromFile(path, password, createNew)
|
||||
override fun get(createNew: Boolean) = CertificateStore.fromFile(path, storePassword, entryPassword, createNew)
|
||||
}
|
@ -151,7 +151,7 @@ private fun Config.getSingleValue(path: String, type: KType, onUnknownKeys: (Set
|
||||
|
||||
private fun ConfigException.Missing.relative(path: String, nestedPath: String?): ConfigException.Missing {
|
||||
return when {
|
||||
nestedPath != null -> throw ConfigException.Missing("$nestedPath.$path")
|
||||
nestedPath != null -> throw ConfigException.Missing("$nestedPath.$path", this)
|
||||
else -> this
|
||||
}
|
||||
}
|
||||
|
@ -53,17 +53,17 @@ class X509KeyStore private constructor(val internal: KeyStore, private val store
|
||||
return uncheckedCast(certArray.asList())
|
||||
}
|
||||
|
||||
fun getCertificateAndKeyPair(alias: String, keyPassword: String = storePassword): CertificateAndKeyPair {
|
||||
fun getCertificateAndKeyPair(alias: String, keyPassword: String): CertificateAndKeyPair {
|
||||
val cert = getCertificate(alias)
|
||||
val publicKey = Crypto.toSupportedPublicKey(cert.publicKey)
|
||||
return CertificateAndKeyPair(cert, KeyPair(publicKey, getPrivateKey(alias, keyPassword)))
|
||||
}
|
||||
|
||||
fun getPrivateKey(alias: String, keyPassword: String = storePassword): PrivateKey {
|
||||
fun getPrivateKey(alias: String, keyPassword: String): PrivateKey {
|
||||
return internal.getSupportedKey(alias, keyPassword)
|
||||
}
|
||||
|
||||
fun setPrivateKey(alias: String, key: PrivateKey, certificates: List<X509Certificate>, keyPassword: String = storePassword) {
|
||||
fun setPrivateKey(alias: String, key: PrivateKey, certificates: List<X509Certificate>, keyPassword: String) {
|
||||
internal.setKeyEntry(alias, key, keyPassword.toCharArray(), certificates.toTypedArray())
|
||||
}
|
||||
|
||||
|
@ -159,7 +159,9 @@ internal fun initialiseTrustStoreAndEnableCrlChecking(trustStore: CertificateSto
|
||||
return CertPathTrustManagerParameters(pkixParams)
|
||||
}
|
||||
|
||||
fun KeyManagerFactory.init(keyStore: CertificateStore) = init(keyStore.value.internal, keyStore.password.toCharArray())
|
||||
// As per Javadoc in: https://docs.oracle.com/javase/8/docs/api/javax/net/ssl/KeyManagerFactory.html `init` method
|
||||
// 2nd parameter `password` - the password for recovering keys in the KeyStore
|
||||
fun KeyManagerFactory.init(keyStore: CertificateStore) = init(keyStore.value.internal, keyStore.entryPassword.toCharArray())
|
||||
|
||||
fun TrustManagerFactory.init(trustStore: CertificateStore) = init(trustStore.value.internal)
|
||||
|
||||
|
@ -235,8 +235,9 @@ class X509UtilitiesTest {
|
||||
signingCertStore.get(createNew = true).also { it.registerDevSigningCertificates(MEGA_CORP.name, rootCa.certificate, intermediateCa, nodeCa) }
|
||||
p2pSslConfig.keyStore.get(createNew = true).also { it.registerDevP2pCertificates(MEGA_CORP.name, rootCa.certificate, intermediateCa, nodeCa) }
|
||||
// Load back server certificate
|
||||
val serverKeyStore = signingCertStore.get().value
|
||||
val (serverCert, serverKeyPair) = serverKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA)
|
||||
val certStore = signingCertStore.get()
|
||||
val serverKeyStore = certStore.value
|
||||
val (serverCert, serverKeyPair) = serverKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA, certStore.entryPassword)
|
||||
|
||||
serverCert.checkValidity()
|
||||
serverCert.verify(intermediateCa.certificate.publicKey)
|
||||
@ -244,7 +245,7 @@ class X509UtilitiesTest {
|
||||
|
||||
// Load back SSL certificate
|
||||
val sslKeyStoreReloaded = p2pSslConfig.keyStore.get()
|
||||
val (sslCert) = sslKeyStoreReloaded.query { getCertificateAndKeyPair(X509Utilities.CORDA_CLIENT_TLS, p2pSslConfig.keyStore.password) }
|
||||
val (sslCert) = sslKeyStoreReloaded.query { getCertificateAndKeyPair(X509Utilities.CORDA_CLIENT_TLS, sslKeyStoreReloaded.entryPassword) }
|
||||
|
||||
sslCert.checkValidity()
|
||||
sslCert.verify(serverCert.publicKey)
|
||||
|
@ -20,8 +20,10 @@ class SSLHelperTest {
|
||||
val keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm())
|
||||
val trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
|
||||
|
||||
keyManagerFactory.init(CertificateStore.fromFile(sslConfig.keyStore.path, sslConfig.keyStore.password, false))
|
||||
trustManagerFactory.init(initialiseTrustStoreAndEnableCrlChecking(CertificateStore.fromFile(sslConfig.trustStore.path, sslConfig.trustStore.password, false), false))
|
||||
val keyStore = sslConfig.keyStore
|
||||
keyManagerFactory.init(CertificateStore.fromFile(keyStore.path, keyStore.storePassword, keyStore.entryPassword, false))
|
||||
val trustStore = sslConfig.trustStore
|
||||
trustManagerFactory.init(initialiseTrustStoreAndEnableCrlChecking(CertificateStore.fromFile(trustStore.path, trustStore.storePassword, trustStore.entryPassword, false), false))
|
||||
|
||||
val sslHandler = createClientSslHelper(NetworkHostAndPort("localhost", 1234), setOf(legalName), keyManagerFactory, trustManagerFactory)
|
||||
val legalNameHash = SecureHash.sha256(legalName.toString()).toString().take(32).toLowerCase()
|
||||
|
Reference in New Issue
Block a user