mirror of
https://github.com/corda/corda.git
synced 2025-02-20 17:33:15 +00:00
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:
parent
8a2074eeee
commit
264b9316e3
@ -22,7 +22,6 @@ import org.assertj.core.api.Assertions.assertThatExceptionOfType
|
||||
import org.bouncycastle.asn1.x509.GeneralName
|
||||
import org.bouncycastle.asn1.x509.GeneralSubtree
|
||||
import org.bouncycastle.asn1.x509.NameConstraints
|
||||
import org.bouncycastle.cert.path.CertPath
|
||||
import org.junit.Test
|
||||
import java.nio.file.Files
|
||||
|
||||
@ -118,7 +117,7 @@ class MQSecurityAsNodeTest : MQSecurityTest() {
|
||||
X509Utilities.CORDA_CLIENT_CA,
|
||||
clientKey.private,
|
||||
keyPass,
|
||||
CertPath(arrayOf(clientCACert, intermediateCA.certificate, rootCACert)))
|
||||
arrayOf(clientCACert, intermediateCA.certificate, rootCACert))
|
||||
clientCAKeystore.save(nodeKeystore, keyStorePassword)
|
||||
|
||||
val tlsKeystore = loadOrCreateKeyStore(sslKeystore, keyStorePassword)
|
||||
@ -126,7 +125,7 @@ class MQSecurityAsNodeTest : MQSecurityTest() {
|
||||
X509Utilities.CORDA_CLIENT_TLS,
|
||||
tlsKey.private,
|
||||
keyPass,
|
||||
CertPath(arrayOf(clientTLSCert, clientCACert, intermediateCA.certificate, rootCACert)))
|
||||
arrayOf(clientTLSCert, clientCACert, intermediateCA.certificate, rootCACert))
|
||||
tlsKeystore.save(sslKeystore, keyStorePassword)
|
||||
}
|
||||
}
|
||||
|
@ -8,9 +8,9 @@ import com.google.common.util.concurrent.MoreExecutors
|
||||
import com.google.common.util.concurrent.SettableFuture
|
||||
import io.github.lukehutch.fastclasspathscanner.FastClasspathScanner
|
||||
import io.github.lukehutch.fastclasspathscanner.scanner.ScanResult
|
||||
import net.corda.core.*
|
||||
import net.corda.core.crypto.*
|
||||
import net.corda.core.crypto.composite.CompositeKey
|
||||
import net.corda.core.flatMap
|
||||
import net.corda.core.flows.*
|
||||
import net.corda.core.identity.Party
|
||||
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.debug
|
||||
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.api.*
|
||||
import net.corda.node.services.config.NodeConfiguration
|
||||
@ -69,15 +72,14 @@ import rx.Observable
|
||||
import java.io.IOException
|
||||
import java.lang.reflect.InvocationTargetException
|
||||
import java.lang.reflect.Modifier.*
|
||||
import java.math.BigInteger
|
||||
import java.net.JarURLConnection
|
||||
import java.net.URI
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
import java.security.KeyPair
|
||||
import java.security.KeyStore
|
||||
import java.security.KeyStoreException
|
||||
import java.security.cert.*
|
||||
import java.security.cert.CertificateFactory
|
||||
import java.security.cert.X509Certificate
|
||||
import java.time.Clock
|
||||
import java.util.*
|
||||
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.
|
||||
|
||||
// TODO: Integrate with Key management service?
|
||||
val certFactory = CertificateFactory.getInstance("X509")
|
||||
val keyStore = KeyStoreWrapper(configuration.nodeKeystore, configuration.keyStorePassword)
|
||||
val privateKeyAlias = "$serviceId-private-key"
|
||||
val privKeyFile = configuration.baseDirectory / privateKeyAlias
|
||||
val pubIdentityFile = configuration.baseDirectory / "$serviceId-public"
|
||||
val certificateAndKeyPair = keyStore.certificateAndKeyPair(privateKeyAlias)
|
||||
val identityCertPathAndKey: Pair<PartyAndCertificate, KeyPair> = if (certificateAndKeyPair != null) {
|
||||
val clientCertPath = keyStore.keyStore.getCertificateChain(X509Utilities.CORDA_CLIENT_CA)
|
||||
val (cert, keyPair) = certificateAndKeyPair
|
||||
// Get keys from keystore.
|
||||
val loadedServiceName = cert.subject
|
||||
if (loadedServiceName != serviceName) {
|
||||
throw ConfigurationException("The legal name in the config file doesn't match the stored identity keystore:" +
|
||||
"$serviceName vs $loadedServiceName")
|
||||
}
|
||||
val certPath = certFactory.generateCertPath(listOf(cert.cert) + clientCertPath)
|
||||
Pair(PartyAndCertificate(loadedServiceName, keyPair.public, cert, certPath), keyPair)
|
||||
} else if (privKeyFile.exists()) {
|
||||
val compositeKeyAlias = "$serviceId-composite-key"
|
||||
|
||||
if (!keyStore.containsAlias(privateKeyAlias)) {
|
||||
val privKeyFile = configuration.baseDirectory / privateKeyAlias
|
||||
val pubIdentityFile = configuration.baseDirectory / "$serviceId-public"
|
||||
// TODO: Remove use of [ServiceIdentityGenerator.generateToDisk].
|
||||
// Get keys from key file.
|
||||
// TODO: this is here to smooth out the key storage transition, remove this in future release.
|
||||
// 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
|
||||
// things up for us.
|
||||
val myIdentity = pubIdentityFile.readAll().deserialize<Party>()
|
||||
if (myIdentity.name != serviceName)
|
||||
throw ConfigurationException("The legal name in the config file doesn't match the stored identity file:" +
|
||||
"$serviceName vs ${myIdentity.name}")
|
||||
// Load the private key.
|
||||
val keyPair = privKeyFile.readAll().deserialize<KeyPair>()
|
||||
if (myIdentity.owningKey !is CompositeKey) { // TODO: Support case where owningKey is a composite key.
|
||||
keyStore.save(serviceName, privateKeyAlias, keyPair)
|
||||
// TODO: this is here to smooth out the key storage transition, remove this migration in future release.
|
||||
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 dummyCaKey = entropyToKeyPair(BigInteger.valueOf(111))
|
||||
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
|
||||
|
||||
val (cert, keyPair) = keyStore.certificateAndKeyPair(privateKeyAlias)
|
||||
|
||||
// Get keys from keystore.
|
||||
val loadedServiceName = cert.subject
|
||||
if (loadedServiceName != serviceName)
|
||||
throw ConfigurationException("The legal name in the config file doesn't match the stored identity keystore:$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
|
||||
}
|
||||
|
||||
partyKeys += keyPair
|
||||
return Pair(PartyAndCertificate(loadedServiceName, publicKey, cert, certPath), keyPair)
|
||||
}
|
||||
|
||||
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.
|
||||
// This is just a sanity check. It shouldn't fail unless the admin has fiddled with the files and messed
|
||||
// things up for us.
|
||||
if (myIdentity.name != serviceName)
|
||||
throw ConfigurationException("The legal name in the config file doesn't match the stored identity file:$serviceName vs ${myIdentity.name}")
|
||||
// Load the private key.
|
||||
val keyPair = privKeyFile.readAll().deserialize<KeyPair>()
|
||||
keyStore.saveNewKeyPair(serviceName, privateKeyAlias, keyPair)
|
||||
// Store composite key separately.
|
||||
if (myIdentity.owningKey is CompositeKey) {
|
||||
keyStore.savePublicKey(serviceName, compositeKeyAlias, myIdentity.owningKey)
|
||||
}
|
||||
log.info("Finish migrating $privateKeyAlias from file to keystore.")
|
||||
}
|
||||
|
||||
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 schedulerService by lazy { NodeSchedulerService(this, unfinishedSchedules = busyNodeLatch) }
|
||||
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(
|
||||
keyStoreWrapper.keyStore.getCertificate(X509Utilities.CORDA_ROOT_CA)!! as X509Certificate,
|
||||
keyStoreWrapper.certificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA),
|
||||
trustStore.getX509Certificate(X509Utilities.CORDA_ROOT_CA).cert,
|
||||
caKeyStore.certificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA),
|
||||
info.legalIdentityAndCert)
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
@ -98,7 +98,7 @@ fun createKeystoreForCordaNode(sslKeyStorePath: Path,
|
||||
X509Utilities.CORDA_CLIENT_CA,
|
||||
clientKey.private,
|
||||
keyPass,
|
||||
org.bouncycastle.cert.path.CertPath(arrayOf(clientCACert, intermediateCACert, rootCACert)))
|
||||
arrayOf(clientCACert, intermediateCACert, rootCACert))
|
||||
clientCAKeystore.save(clientCAKeystorePath, storePassword)
|
||||
|
||||
val tlsKeystore = loadOrCreateKeyStore(sslKeyStorePath, storePassword)
|
||||
@ -106,6 +106,6 @@ fun createKeystoreForCordaNode(sslKeyStorePath: Path,
|
||||
X509Utilities.CORDA_CLIENT_TLS,
|
||||
tlsKey.private,
|
||||
keyPass,
|
||||
org.bouncycastle.cert.path.CertPath(arrayOf(clientTLSCert, clientCACert, intermediateCACert, rootCACert)))
|
||||
arrayOf(clientTLSCert, clientCACert, intermediateCACert, rootCACert))
|
||||
tlsKeystore.save(sslKeyStorePath, storePassword)
|
||||
}
|
@ -1,19 +1,19 @@
|
||||
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.crypto.*
|
||||
import net.corda.core.internal.exists
|
||||
import net.corda.core.internal.read
|
||||
import net.corda.core.internal.write
|
||||
import org.bouncycastle.asn1.x500.X500Name
|
||||
import org.bouncycastle.cert.X509CertificateHolder
|
||||
import org.bouncycastle.cert.path.CertPath
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
import java.nio.file.Path
|
||||
import java.security.*
|
||||
import java.security.cert.CertPath
|
||||
import java.security.cert.Certificate
|
||||
import java.security.cert.CertificateFactory
|
||||
|
||||
val KEYSTORE_TYPE = "JKS"
|
||||
|
||||
@ -77,8 +77,8 @@ fun loadKeyStore(input: InputStream, storePassword: String): KeyStore {
|
||||
* 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.
|
||||
*/
|
||||
fun KeyStore.addOrReplaceKey(alias: String, key: Key, password: CharArray, chain: CertPath) {
|
||||
addOrReplaceKey(alias, key, password, chain.certificates.map { it.cert }.toTypedArray<Certificate>())
|
||||
fun KeyStore.addOrReplaceKey(alias: String, key: Key, password: CharArray, chain: Array<out X509CertificateHolder>) {
|
||||
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.
|
||||
* @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)) {
|
||||
this.deleteEntry(alias)
|
||||
}
|
||||
@ -168,3 +168,43 @@ fun KeyStore.getSupportedKey(alias: String, keyPassword: String): PrivateKey {
|
||||
val key = getKey(alias, keyPass) as PrivateKey
|
||||
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)
|
||||
}
|
||||
|
@ -49,7 +49,7 @@ class NetworkRegistrationHelper(val config: NodeConfiguration, val certService:
|
||||
val selfSignCert = X509Utilities.createSelfSignedCACertificate(config.myLegalName, keyPair)
|
||||
// Save to the key store.
|
||||
caKeyStore.addOrReplaceKey(SELF_SIGNED_PRIVATE_KEY, keyPair.private, privateKeyPassword.toCharArray(),
|
||||
CertPath(arrayOf(selfSignCert)))
|
||||
arrayOf(selfSignCert))
|
||||
caKeyStore.save(config.nodeKeystore, keystorePassword)
|
||||
}
|
||||
val keyPair = caKeyStore.getKeyPair(SELF_SIGNED_PRIVATE_KEY, privateKeyPassword)
|
||||
|
Loading…
x
Reference in New Issue
Block a user