Store notaries's identity composite key in keystore (#1036)

* * Store composite key in keystore from file for notaries's identity.
* Some refactoring.

* * Addressed PR issues

* * Remove unintended format changes

* * Fixed failing test due to getting keys from wrong keystore
This commit is contained in:
Patrick Kuo
2017-07-19 11:14:48 +01:00
committed by GitHub
parent 8a2074eeee
commit 264b9316e3
5 changed files with 114 additions and 88 deletions

View File

@ -22,7 +22,6 @@ import org.assertj.core.api.Assertions.assertThatExceptionOfType
import org.bouncycastle.asn1.x509.GeneralName import org.bouncycastle.asn1.x509.GeneralName
import org.bouncycastle.asn1.x509.GeneralSubtree import org.bouncycastle.asn1.x509.GeneralSubtree
import org.bouncycastle.asn1.x509.NameConstraints import org.bouncycastle.asn1.x509.NameConstraints
import org.bouncycastle.cert.path.CertPath
import org.junit.Test import org.junit.Test
import java.nio.file.Files import java.nio.file.Files
@ -118,7 +117,7 @@ class MQSecurityAsNodeTest : MQSecurityTest() {
X509Utilities.CORDA_CLIENT_CA, X509Utilities.CORDA_CLIENT_CA,
clientKey.private, clientKey.private,
keyPass, keyPass,
CertPath(arrayOf(clientCACert, intermediateCA.certificate, rootCACert))) arrayOf(clientCACert, intermediateCA.certificate, rootCACert))
clientCAKeystore.save(nodeKeystore, keyStorePassword) clientCAKeystore.save(nodeKeystore, keyStorePassword)
val tlsKeystore = loadOrCreateKeyStore(sslKeystore, keyStorePassword) val tlsKeystore = loadOrCreateKeyStore(sslKeystore, keyStorePassword)
@ -126,7 +125,7 @@ class MQSecurityAsNodeTest : MQSecurityTest() {
X509Utilities.CORDA_CLIENT_TLS, X509Utilities.CORDA_CLIENT_TLS,
tlsKey.private, tlsKey.private,
keyPass, keyPass,
CertPath(arrayOf(clientTLSCert, clientCACert, intermediateCA.certificate, rootCACert))) arrayOf(clientTLSCert, clientCACert, intermediateCA.certificate, rootCACert))
tlsKeystore.save(sslKeystore, keyStorePassword) tlsKeystore.save(sslKeystore, keyStorePassword)
} }
} }

View File

@ -8,9 +8,9 @@ import com.google.common.util.concurrent.MoreExecutors
import com.google.common.util.concurrent.SettableFuture import com.google.common.util.concurrent.SettableFuture
import io.github.lukehutch.fastclasspathscanner.FastClasspathScanner import io.github.lukehutch.fastclasspathscanner.FastClasspathScanner
import io.github.lukehutch.fastclasspathscanner.scanner.ScanResult import io.github.lukehutch.fastclasspathscanner.scanner.ScanResult
import net.corda.core.*
import net.corda.core.crypto.* import net.corda.core.crypto.*
import net.corda.core.crypto.composite.CompositeKey import net.corda.core.crypto.composite.CompositeKey
import net.corda.core.flatMap
import net.corda.core.flows.* import net.corda.core.flows.*
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.identity.PartyAndCertificate import net.corda.core.identity.PartyAndCertificate
@ -28,7 +28,10 @@ import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.debug import net.corda.core.utilities.debug
import net.corda.core.utilities.toNonEmptySet import net.corda.core.utilities.toNonEmptySet
import net.corda.flows.* import net.corda.flows.CashExitFlow
import net.corda.flows.CashIssueFlow
import net.corda.flows.CashPaymentFlow
import net.corda.flows.IssuerFlow
import net.corda.node.services.* import net.corda.node.services.*
import net.corda.node.services.api.* import net.corda.node.services.api.*
import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.config.NodeConfiguration
@ -69,15 +72,14 @@ import rx.Observable
import java.io.IOException import java.io.IOException
import java.lang.reflect.InvocationTargetException import java.lang.reflect.InvocationTargetException
import java.lang.reflect.Modifier.* import java.lang.reflect.Modifier.*
import java.math.BigInteger
import java.net.JarURLConnection import java.net.JarURLConnection
import java.net.URI import java.net.URI
import java.nio.file.Path import java.nio.file.Path
import java.nio.file.Paths import java.nio.file.Paths
import java.security.KeyPair import java.security.KeyPair
import java.security.KeyStore
import java.security.KeyStoreException import java.security.KeyStoreException
import java.security.cert.* import java.security.cert.CertificateFactory
import java.security.cert.X509Certificate
import java.time.Clock import java.time.Clock
import java.util.* import java.util.*
import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentHashMap
@ -709,63 +711,62 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
// the legal name is actually validated in some way. // the legal name is actually validated in some way.
// TODO: Integrate with Key management service? // TODO: Integrate with Key management service?
val certFactory = CertificateFactory.getInstance("X509")
val keyStore = KeyStoreWrapper(configuration.nodeKeystore, configuration.keyStorePassword) val keyStore = KeyStoreWrapper(configuration.nodeKeystore, configuration.keyStorePassword)
val privateKeyAlias = "$serviceId-private-key" val privateKeyAlias = "$serviceId-private-key"
val compositeKeyAlias = "$serviceId-composite-key"
if (!keyStore.containsAlias(privateKeyAlias)) {
val privKeyFile = configuration.baseDirectory / privateKeyAlias val privKeyFile = configuration.baseDirectory / privateKeyAlias
val pubIdentityFile = configuration.baseDirectory / "$serviceId-public" val pubIdentityFile = configuration.baseDirectory / "$serviceId-public"
val certificateAndKeyPair = keyStore.certificateAndKeyPair(privateKeyAlias) // TODO: Remove use of [ServiceIdentityGenerator.generateToDisk].
val identityCertPathAndKey: Pair<PartyAndCertificate, KeyPair> = if (certificateAndKeyPair != null) { // Get keys from key file.
val clientCertPath = keyStore.keyStore.getCertificateChain(X509Utilities.CORDA_CLIENT_CA) // TODO: this is here to smooth out the key storage transition, remove this migration in future release.
val (cert, keyPair) = certificateAndKeyPair if (privKeyFile.exists()) {
migrateKeysFromFile(keyStore, serviceName, pubIdentityFile, privKeyFile, privateKeyAlias, compositeKeyAlias)
} else {
log.info("$privateKeyAlias not found in keystore ${configuration.nodeKeystore}, generating fresh key!")
keyStore.saveNewKeyPair(serviceName, privateKeyAlias, generateKeyPair())
}
}
val (cert, keyPair) = keyStore.certificateAndKeyPair(privateKeyAlias)
// Get keys from keystore. // Get keys from keystore.
val loadedServiceName = cert.subject val loadedServiceName = cert.subject
if (loadedServiceName != serviceName) { if (loadedServiceName != serviceName)
throw ConfigurationException("The legal name in the config file doesn't match the stored identity keystore:" + throw ConfigurationException("The legal name in the config file doesn't match the stored identity keystore:$serviceName vs $loadedServiceName")
"$serviceName vs $loadedServiceName")
val certPath = CertificateFactory.getInstance("X509").generateCertPath(keyStore.getCertificateChain(privateKeyAlias).toList())
// Use composite key instead if exists
// TODO: Use configuration to indicate composite key should be used instead of public key for the identity.
val publicKey = if (keyStore.containsAlias(compositeKeyAlias)) {
Crypto.toSupportedPublicKey(keyStore.getCertificate(compositeKeyAlias).publicKey)
} else {
keyPair.public
} }
val certPath = certFactory.generateCertPath(listOf(cert.cert) + clientCertPath)
Pair(PartyAndCertificate(loadedServiceName, keyPair.public, cert, certPath), keyPair) partyKeys += keyPair
} else if (privKeyFile.exists()) { return Pair(PartyAndCertificate(loadedServiceName, publicKey, cert, certPath), keyPair)
// Get keys from key file. }
// TODO: this is here to smooth out the key storage transition, remove this in future release.
private fun migrateKeysFromFile(keyStore: KeyStoreWrapper, serviceName: X500Name,
pubIdentityFile: Path, privKeyFile: Path,
privateKeyAlias: String, compositeKeyAlias: String) {
log.info("Migrating $privateKeyAlias from file to keystore...")
val myIdentity = pubIdentityFile.readAll().deserialize<Party>()
// Check that the identity in the config file matches the identity file we have stored to disk. // Check that the identity in the config file matches the identity file we have stored to disk.
// This is just a sanity check. It shouldn't fail unless the admin has fiddled with the files and messed // This is just a sanity check. It shouldn't fail unless the admin has fiddled with the files and messed
// things up for us. // things up for us.
val myIdentity = pubIdentityFile.readAll().deserialize<Party>()
if (myIdentity.name != serviceName) if (myIdentity.name != serviceName)
throw ConfigurationException("The legal name in the config file doesn't match the stored identity file:" + throw ConfigurationException("The legal name in the config file doesn't match the stored identity file:$serviceName vs ${myIdentity.name}")
"$serviceName vs ${myIdentity.name}")
// Load the private key. // Load the private key.
val keyPair = privKeyFile.readAll().deserialize<KeyPair>() val keyPair = privKeyFile.readAll().deserialize<KeyPair>()
if (myIdentity.owningKey !is CompositeKey) { // TODO: Support case where owningKey is a composite key. keyStore.saveNewKeyPair(serviceName, privateKeyAlias, keyPair)
keyStore.save(serviceName, privateKeyAlias, keyPair) // Store composite key separately.
if (myIdentity.owningKey is CompositeKey) {
keyStore.savePublicKey(serviceName, compositeKeyAlias, myIdentity.owningKey)
} }
val dummyCaKey = entropyToKeyPair(BigInteger.valueOf(111)) log.info("Finish migrating $privateKeyAlias from file to keystore.")
val dummyCa = CertificateAndKeyPair(
X509Utilities.createSelfSignedCACertificate(X500Name("CN=Dummy CA,OU=Corda,O=R3 Ltd,L=London,C=GB"), dummyCaKey),
dummyCaKey)
val partyAndCertificate = getTestPartyAndCertificate(myIdentity, dummyCa)
// Sanity check the certificate and path
val validatorParameters = PKIXParameters(setOf(TrustAnchor(dummyCa.certificate.cert, null)))
val validator = CertPathValidator.getInstance("PKIX")
validatorParameters.isRevocationEnabled = false
validator.validate(partyAndCertificate.certPath, validatorParameters) as PKIXCertPathValidatorResult
Pair(partyAndCertificate, keyPair)
} else {
val clientCertPath = keyStore.keyStore.getCertificateChain(X509Utilities.CORDA_CLIENT_CA)
val clientCA = keyStore.certificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA)!!
// Create new keys and store in keystore.
log.info("Identity key not found, generating fresh key!")
val keyPair: KeyPair = generateKeyPair()
val cert = X509Utilities.createCertificate(CertificateType.IDENTITY, clientCA.certificate, clientCA.keyPair, serviceName, keyPair.public)
val certPath = certFactory.generateCertPath(listOf(cert.cert) + clientCertPath)
keyStore.save(serviceName, privateKeyAlias, keyPair)
require(certPath.certificates.isNotEmpty()) { "Certificate path cannot be empty" }
Pair(PartyAndCertificate(serviceName, keyPair.public, cert, certPath), keyPair)
}
partyKeys += identityCertPathAndKey.second
return identityCertPathAndKey
} }
private fun getTestPartyAndCertificate(party: Party, trustRoot: CertificateAndKeyPair): PartyAndCertificate { private fun getTestPartyAndCertificate(party: Party, trustRoot: CertificateAndKeyPair): PartyAndCertificate {
@ -801,10 +802,11 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
override val keyManagementService by lazy { makeKeyManagementService(identityService) } override val keyManagementService by lazy { makeKeyManagementService(identityService) }
override val schedulerService by lazy { NodeSchedulerService(this, unfinishedSchedules = busyNodeLatch) } override val schedulerService by lazy { NodeSchedulerService(this, unfinishedSchedules = busyNodeLatch) }
override val identityService by lazy { override val identityService by lazy {
val keyStoreWrapper = KeyStoreWrapper(configuration.trustStoreFile, configuration.trustStorePassword) val trustStore = KeyStoreWrapper(configuration.trustStoreFile, configuration.trustStorePassword)
val caKeyStore = KeyStoreWrapper(configuration.nodeKeystore, configuration.keyStorePassword)
makeIdentityService( makeIdentityService(
keyStoreWrapper.keyStore.getCertificate(X509Utilities.CORDA_ROOT_CA)!! as X509Certificate, trustStore.getX509Certificate(X509Utilities.CORDA_ROOT_CA).cert,
keyStoreWrapper.certificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA), caKeyStore.certificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA),
info.legalIdentityAndCert) info.legalIdentityAndCert)
} }
override val attachments: AttachmentStorage get() = this@AbstractNode.attachments override val attachments: AttachmentStorage get() = this@AbstractNode.attachments
@ -836,18 +838,3 @@ 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(loadKeyStore(storePath, storePassword), storePath, storePassword)
fun certificateAndKeyPair(alias: String): CertificateAndKeyPair? {
return if (keyStore.containsAlias(alias)) keyStore.getCertificateAndKeyPair(alias, storePassword) else null
}
fun save(serviceName: X500Name, privateKeyAlias: String, keyPair: KeyPair) {
val clientCA = keyStore.getCertificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA, storePassword)
val cert = X509Utilities.createCertificate(CertificateType.IDENTITY, clientCA.certificate, clientCA.keyPair, serviceName, keyPair.public).cert
keyStore.addOrReplaceKey(privateKeyAlias, keyPair.private, storePassword.toCharArray(), arrayOf(cert, *keyStore.getCertificateChain(X509Utilities.CORDA_CLIENT_CA)))
keyStore.save(storePath, storePassword)
}
}

View File

@ -98,7 +98,7 @@ fun createKeystoreForCordaNode(sslKeyStorePath: Path,
X509Utilities.CORDA_CLIENT_CA, X509Utilities.CORDA_CLIENT_CA,
clientKey.private, clientKey.private,
keyPass, keyPass,
org.bouncycastle.cert.path.CertPath(arrayOf(clientCACert, intermediateCACert, rootCACert))) arrayOf(clientCACert, intermediateCACert, rootCACert))
clientCAKeystore.save(clientCAKeystorePath, storePassword) clientCAKeystore.save(clientCAKeystorePath, storePassword)
val tlsKeystore = loadOrCreateKeyStore(sslKeyStorePath, storePassword) val tlsKeystore = loadOrCreateKeyStore(sslKeyStorePath, storePassword)
@ -106,6 +106,6 @@ fun createKeystoreForCordaNode(sslKeyStorePath: Path,
X509Utilities.CORDA_CLIENT_TLS, X509Utilities.CORDA_CLIENT_TLS,
tlsKey.private, tlsKey.private,
keyPass, keyPass,
org.bouncycastle.cert.path.CertPath(arrayOf(clientTLSCert, clientCACert, intermediateCACert, rootCACert))) arrayOf(clientTLSCert, clientCACert, intermediateCACert, rootCACert))
tlsKeystore.save(sslKeyStorePath, storePassword) tlsKeystore.save(sslKeyStorePath, storePassword)
} }

View File

@ -1,19 +1,19 @@
package net.corda.node.utilities package net.corda.node.utilities
import net.corda.core.crypto.CertificateAndKeyPair import net.corda.core.crypto.*
import net.corda.core.crypto.Crypto
import net.corda.core.crypto.cert
import net.corda.core.internal.exists import net.corda.core.internal.exists
import net.corda.core.internal.read import net.corda.core.internal.read
import net.corda.core.internal.write import net.corda.core.internal.write
import org.bouncycastle.asn1.x500.X500Name
import org.bouncycastle.cert.X509CertificateHolder import org.bouncycastle.cert.X509CertificateHolder
import org.bouncycastle.cert.path.CertPath
import java.io.IOException import java.io.IOException
import java.io.InputStream import java.io.InputStream
import java.io.OutputStream import java.io.OutputStream
import java.nio.file.Path import java.nio.file.Path
import java.security.* import java.security.*
import java.security.cert.CertPath
import java.security.cert.Certificate import java.security.cert.Certificate
import java.security.cert.CertificateFactory
val KEYSTORE_TYPE = "JKS" val KEYSTORE_TYPE = "JKS"
@ -77,8 +77,8 @@ fun loadKeyStore(input: InputStream, storePassword: String): KeyStore {
* but for SSL purposes this is recommended. * but for SSL purposes this is recommended.
* @param chain the sequence of certificates starting with the public key certificate for this key and extending to the root CA cert. * @param chain the sequence of certificates starting with the public key certificate for this key and extending to the root CA cert.
*/ */
fun KeyStore.addOrReplaceKey(alias: String, key: Key, password: CharArray, chain: CertPath) { fun KeyStore.addOrReplaceKey(alias: String, key: Key, password: CharArray, chain: Array<out X509CertificateHolder>) {
addOrReplaceKey(alias, key, password, chain.certificates.map { it.cert }.toTypedArray<Certificate>()) addOrReplaceKey(alias, key, password, chain.map { it.cert }.toTypedArray<Certificate>())
} }
/** /**
@ -89,7 +89,7 @@ fun KeyStore.addOrReplaceKey(alias: String, key: Key, password: CharArray, chain
* but for SSL purposes this is recommended. * but for SSL purposes this is recommended.
* @param chain the sequence of certificates starting with the public key certificate for this key and extending to the root CA cert. * @param chain the sequence of certificates starting with the public key certificate for this key and extending to the root CA cert.
*/ */
fun KeyStore.addOrReplaceKey(alias: String, key: Key, password: CharArray, chain: Array<Certificate>) { fun KeyStore.addOrReplaceKey(alias: String, key: Key, password: CharArray, chain: Array<out Certificate>) {
if (containsAlias(alias)) { if (containsAlias(alias)) {
this.deleteEntry(alias) this.deleteEntry(alias)
} }
@ -168,3 +168,43 @@ fun KeyStore.getSupportedKey(alias: String, keyPassword: String): PrivateKey {
val key = getKey(alias, keyPass) as PrivateKey val key = getKey(alias, keyPass) as PrivateKey
return Crypto.toSupportedPrivateKey(key) return Crypto.toSupportedPrivateKey(key)
} }
class KeyStoreWrapper(private val storePath: Path, private val storePassword: String) {
private val keyStore = storePath.read { loadKeyStore(it, storePassword) }
private fun createCertificate(serviceName: X500Name, pubKey: PublicKey): CertPath {
val clientCertPath = keyStore.getCertificateChain(X509Utilities.CORDA_CLIENT_CA)
// Assume key password = store password.
val clientCA = certificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA)
// Create new keys and store in keystore.
val cert = X509Utilities.createCertificate(CertificateType.IDENTITY, clientCA.certificate, clientCA.keyPair, serviceName, pubKey)
val certPath = CertificateFactory.getInstance("X509").generateCertPath(listOf(cert.cert) + clientCertPath)
require(certPath.certificates.isNotEmpty()) { "Certificate path cannot be empty" }
return certPath
}
fun saveNewKeyPair(serviceName: X500Name, privateKeyAlias: String, keyPair: KeyPair) {
val certPath = createCertificate(serviceName, keyPair.public)
// Assume key password = store password.
keyStore.addOrReplaceKey(privateKeyAlias, keyPair.private, storePassword.toCharArray(), certPath.certificates.toTypedArray())
keyStore.save(storePath, storePassword)
}
fun savePublicKey(serviceName: X500Name, pubKeyAlias: String, pubKey: PublicKey) {
val certPath = createCertificate(serviceName, pubKey)
// Assume key password = store password.
keyStore.addOrReplaceCertificate(pubKeyAlias, certPath.certificates.first())
keyStore.save(storePath, storePassword)
}
// Delegate methods to keystore. Sadly keystore doesn't have an interface.
fun containsAlias(alias: String) = keyStore.containsAlias(alias)
fun getX509Certificate(alias: String) = keyStore.getX509Certificate(alias)
fun getCertificateChain(alias: String): Array<out Certificate> = keyStore.getCertificateChain(alias)
fun getCertificate(alias: String): Certificate = keyStore.getCertificate(alias)
fun certificateAndKeyPair(alias: String) = keyStore.getCertificateAndKeyPair(alias, storePassword)
}

View File

@ -49,7 +49,7 @@ class NetworkRegistrationHelper(val config: NodeConfiguration, val certService:
val selfSignCert = X509Utilities.createSelfSignedCACertificate(config.myLegalName, keyPair) val selfSignCert = X509Utilities.createSelfSignedCACertificate(config.myLegalName, keyPair)
// Save to the key store. // Save to the key store.
caKeyStore.addOrReplaceKey(SELF_SIGNED_PRIVATE_KEY, keyPair.private, privateKeyPassword.toCharArray(), caKeyStore.addOrReplaceKey(SELF_SIGNED_PRIVATE_KEY, keyPair.private, privateKeyPassword.toCharArray(),
CertPath(arrayOf(selfSignCert))) arrayOf(selfSignCert))
caKeyStore.save(config.nodeKeystore, keystorePassword) caKeyStore.save(config.nodeKeystore, keystorePassword)
} }
val keyPair = caKeyStore.getKeyPair(SELF_SIGNED_PRIVATE_KEY, privateKeyPassword) val keyPair = caKeyStore.getKeyPair(SELF_SIGNED_PRIVATE_KEY, privateKeyPassword)