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:
Viktor Kolomeyko
2018-10-22 07:11:27 +01:00
committed by GitHub
parent dd60ae27f2
commit 88f368134f
21 changed files with 134 additions and 297 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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