mirror of
https://github.com/corda/corda.git
synced 2024-12-19 04:57:58 +00:00
Store identity key to keystore with self sign cert (#645)
* Support signing and storing EdDSA key and certificate in java keystore.
This commit is contained in:
parent
1ecce79913
commit
af7ba082a4
@ -558,9 +558,10 @@ object Crypto {
|
||||
fun createCertificate(issuer: X500Name, issuerKeyPair: KeyPair,
|
||||
subject: X500Name, subjectPublicKey: PublicKey,
|
||||
keyUsage: KeyUsage, purposes: List<KeyPurposeId>,
|
||||
signatureScheme: SignatureScheme, validityWindow: Pair<Date, Date>,
|
||||
validityWindow: Pair<Date, Date>,
|
||||
pathLength: Int? = null, subjectAlternativeName: List<GeneralName>? = null): X509Certificate {
|
||||
|
||||
val signatureScheme = findSignatureScheme(issuerKeyPair.private)
|
||||
val provider = providerMap[signatureScheme.providerName]
|
||||
val serial = BigInteger.valueOf(random63BitValue())
|
||||
val keyPurposes = DERSequence(ASN1EncodableVector().apply { purposes.forEach { add(it) } })
|
||||
|
@ -125,7 +125,9 @@ fun KeyStore.getCertificateAndKey(alias: String, keyPassword: String): Certifica
|
||||
val keyPass = keyPassword.toCharArray()
|
||||
val key = getKey(alias, keyPass) as PrivateKey
|
||||
val cert = getCertificate(alias) as X509Certificate
|
||||
return CertificateAndKey(cert, KeyPair(cert.publicKey, key))
|
||||
// Using Crypto.decodePublicKey to convert X509Key to bouncy castle public key implementation.
|
||||
// Using Crypto.decodePrivateKey to convert sun provider key implementation to bouncy castle private key implementation.
|
||||
return CertificateAndKey(cert, KeyPair(Crypto.decodePublicKey(cert.publicKey.encoded), Crypto.decodePrivateKey(key.encoded)))
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -103,13 +103,17 @@ object X509Utilities {
|
||||
* Note the generated certificate tree is capped at max depth of 2 to be in line with commercially available certificates
|
||||
*/
|
||||
@JvmStatic
|
||||
fun createSelfSignedCACert(subject: X500Name, signatureScheme: SignatureScheme = DEFAULT_TLS_SIGNATURE_SCHEME, validityWindow: Pair<Int, Int> = DEFAULT_VALIDITY_WINDOW): CertificateAndKey {
|
||||
val keyPair = generateKeyPair(signatureScheme)
|
||||
fun createSelfSignedCACert(subject: X500Name, keyPair: KeyPair, validityWindow: Pair<Int, Int> = DEFAULT_VALIDITY_WINDOW): CertificateAndKey {
|
||||
val window = getCertificateValidityWindow(validityWindow.first, validityWindow.second)
|
||||
val cert = Crypto.createCertificate(subject, keyPair, subject, keyPair.public, CA_KEY_USAGE, CA_KEY_PURPOSES, signatureScheme, window, pathLength = 2)
|
||||
val cert = Crypto.createCertificate(subject, keyPair, subject, keyPair.public, CA_KEY_USAGE, CA_KEY_PURPOSES, window, pathLength = 2)
|
||||
return CertificateAndKey(cert, keyPair)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun createSelfSignedCACert(subject: X500Name, signatureScheme: SignatureScheme = DEFAULT_TLS_SIGNATURE_SCHEME,
|
||||
validityWindow: Pair<Int, Int> = DEFAULT_VALIDITY_WINDOW): CertificateAndKey
|
||||
= createSelfSignedCACert(subject, generateKeyPair(signatureScheme), validityWindow)
|
||||
|
||||
/**
|
||||
* Create a de novo root intermediate X509 v3 CA cert and KeyPair.
|
||||
* @param subject subject of the generated certificate.
|
||||
@ -124,7 +128,7 @@ object X509Utilities {
|
||||
val keyPair = generateKeyPair(signatureScheme)
|
||||
val issuer = X509CertificateHolder(ca.certificate.encoded).subject
|
||||
val window = getCertificateValidityWindow(validityWindow.first, validityWindow.second, ca.certificate.notBefore, ca.certificate.notAfter)
|
||||
val cert = Crypto.createCertificate(issuer, ca.keyPair, subject, keyPair.public, CA_KEY_USAGE, CA_KEY_PURPOSES, signatureScheme, window, pathLength = 1)
|
||||
val cert = Crypto.createCertificate(issuer, ca.keyPair, subject, keyPair.public, CA_KEY_USAGE, CA_KEY_PURPOSES, window, pathLength = 1)
|
||||
return CertificateAndKey(cert, keyPair)
|
||||
}
|
||||
|
||||
@ -145,7 +149,6 @@ object X509Utilities {
|
||||
ca: CertificateAndKey,
|
||||
subjectAlternativeNameDomains: List<String>,
|
||||
subjectAlternativeNameIps: List<String>,
|
||||
signatureScheme: SignatureScheme = DEFAULT_TLS_SIGNATURE_SCHEME,
|
||||
validityWindow: Pair<Int, Int> = DEFAULT_VALIDITY_WINDOW): X509Certificate {
|
||||
|
||||
val issuer = X509CertificateHolder(ca.certificate.encoded).subject
|
||||
@ -154,7 +157,7 @@ object X509Utilities {
|
||||
val ipAddresses = subjectAlternativeNameIps.filter {
|
||||
IPAddress.isValidIPv6WithNetmask(it) || IPAddress.isValidIPv6(it) || IPAddress.isValidIPv4WithNetmask(it) || IPAddress.isValidIPv4(it)
|
||||
}.map { GeneralName(GeneralName.iPAddress, it) }
|
||||
return Crypto.createCertificate(issuer, ca.keyPair, subject, publicKey, CLIENT_KEY_USAGE, CLIENT_KEY_PURPOSES, signatureScheme, window, subjectAlternativeName = dnsNames + ipAddresses)
|
||||
return Crypto.createCertificate(issuer, ca.keyPair, subject, publicKey, CLIENT_KEY_USAGE, CLIENT_KEY_PURPOSES, window, subjectAlternativeName = dnsNames + ipAddresses)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -208,7 +211,7 @@ object X509Utilities {
|
||||
|
||||
val serverKey = generateKeyPair(signatureScheme)
|
||||
val host = InetAddress.getLocalHost()
|
||||
val serverCert = createServerCert(commonName, serverKey.public, intermediateCA, listOf(host.hostName), listOf(host.hostAddress), signatureScheme)
|
||||
val serverCert = createServerCert(commonName, serverKey.public, intermediateCA, listOf(host.hostName), listOf(host.hostAddress))
|
||||
|
||||
val keyPass = keyPassword.toCharArray()
|
||||
val keyStore = KeyStoreUtilities.loadOrCreateKeyStore(keyStoreFilePath, storePassword)
|
||||
|
@ -8,9 +8,7 @@ import com.google.common.util.concurrent.SettableFuture
|
||||
import io.github.lukehutch.fastclasspathscanner.FastClasspathScanner
|
||||
import io.github.lukehutch.fastclasspathscanner.scanner.ClassInfo
|
||||
import net.corda.core.*
|
||||
import net.corda.core.crypto.KeyStoreUtilities
|
||||
import net.corda.core.crypto.X509Utilities
|
||||
import net.corda.core.crypto.replaceCommonName
|
||||
import net.corda.core.crypto.*
|
||||
import net.corda.core.flows.FlowInitiator
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.flows.InitiatingFlow
|
||||
@ -24,7 +22,6 @@ import net.corda.core.node.services.*
|
||||
import net.corda.core.node.services.NetworkMapCache.MapChange
|
||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||
import net.corda.core.serialization.deserialize
|
||||
import net.corda.core.serialization.serialize
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.utilities.debug
|
||||
import net.corda.flows.*
|
||||
@ -60,6 +57,7 @@ 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.bouncycastle.cert.X509CertificateHolder
|
||||
import org.jetbrains.exposed.sql.Database
|
||||
import org.slf4j.Logger
|
||||
import java.io.IOException
|
||||
@ -93,10 +91,6 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
|
||||
val advertisedServices: Set<ServiceInfo>,
|
||||
val platformClock: Clock,
|
||||
@VisibleForTesting val busyNodeLatch: ReusableLatch = ReusableLatch()) : SingletonSerializeAsToken() {
|
||||
companion object {
|
||||
val PRIVATE_KEY_FILE_NAME = "identity-private-key"
|
||||
val PUBLIC_IDENTITY_FILE_NAME = "identity-public"
|
||||
}
|
||||
|
||||
// TODO: Persist this, as well as whether the node is registered.
|
||||
/**
|
||||
@ -396,7 +390,7 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
|
||||
return advertisedServices.map {
|
||||
val serviceId = it.type.id
|
||||
val serviceName = it.name ?: configuration.myLegalName.replaceCommonName(serviceId)
|
||||
val identity = obtainKeyPair(configuration.baseDirectory, serviceId + "-private-key", serviceId + "-public", serviceName).first
|
||||
val identity = obtainKeyPair(serviceId, serviceName).first
|
||||
ServiceEntry(it, identity)
|
||||
}
|
||||
}
|
||||
@ -592,39 +586,56 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
|
||||
stateMachineRecordedTransactionMappingStorage: StateMachineRecordedTransactionMappingStorage) =
|
||||
StorageServiceImpl(attachments, transactionStorage, stateMachineRecordedTransactionMappingStorage)
|
||||
|
||||
protected fun obtainLegalIdentity(): Party = obtainKeyPair(configuration.baseDirectory, PRIVATE_KEY_FILE_NAME, PUBLIC_IDENTITY_FILE_NAME).first
|
||||
protected fun obtainLegalIdentityKey(): KeyPair = obtainKeyPair(configuration.baseDirectory, PRIVATE_KEY_FILE_NAME, PUBLIC_IDENTITY_FILE_NAME).second
|
||||
protected fun obtainLegalIdentity(): Party = obtainKeyPair().first
|
||||
protected fun obtainLegalIdentityKey(): KeyPair = obtainKeyPair().second
|
||||
|
||||
private fun obtainKeyPair(dir: Path, privateKeyFileName: String, publicKeyFileName: String, serviceName: X500Name? = null): Pair<Party, KeyPair> {
|
||||
private fun obtainKeyPair(serviceId: String = "identity", serviceName: X500Name = configuration.myLegalName): Pair<Party, KeyPair> {
|
||||
// Load the private identity key, creating it if necessary. The identity key is a long term well known key that
|
||||
// is distributed to other peers and we use it (or a key signed by it) when we need to do something
|
||||
// "permissioned". The identity file is what gets distributed and contains the node's legal name along with
|
||||
// the public key. Obviously in a real system this would need to be a certificate chain of some kind to ensure
|
||||
// the legal name is actually validated in some way.
|
||||
val privKeyFile = dir / privateKeyFileName
|
||||
val pubIdentityFile = dir / publicKeyFileName
|
||||
val identityPrincipal: X500Name = serviceName ?: configuration.myLegalName
|
||||
|
||||
val identityAndKey = if (!privKeyFile.exists()) {
|
||||
log.info("Identity key not found, generating fresh key!")
|
||||
val keyPair: KeyPair = generateKeyPair()
|
||||
keyPair.serialize().writeToFile(privKeyFile)
|
||||
val myIdentity = Party(identityPrincipal, keyPair.public)
|
||||
// We include the Party class with the file here to help catch mixups when admins provide files of the
|
||||
// wrong type by mistake.
|
||||
myIdentity.serialize().writeToFile(pubIdentityFile)
|
||||
Pair(myIdentity, keyPair)
|
||||
} else {
|
||||
// TODO: Integrate with Key management service?
|
||||
val keystore = KeyStoreUtilities.loadKeyStore(configuration.keyStoreFile, configuration.keyStorePassword)
|
||||
val privateKeyAlias = "$serviceId-private-key"
|
||||
val privKeyFile = configuration.baseDirectory / privateKeyAlias
|
||||
val pubIdentityFile = configuration.baseDirectory / "$serviceId-public"
|
||||
|
||||
val identityAndKey = if (configuration.keyStoreFile.exists() && keystore.containsAlias(privateKeyAlias)) {
|
||||
// Get keys from keystore.
|
||||
val (cert, keyPair) = keystore.getCertificateAndKey(privateKeyAlias, configuration.keyStorePassword)
|
||||
val loadedServiceName = X509CertificateHolder(cert.encoded).subject
|
||||
if (X509CertificateHolder(cert.encoded).subject != serviceName) {
|
||||
throw ConfigurationException("The legal name in the config file doesn't match the stored identity keystore:" +
|
||||
"$serviceName vs $loadedServiceName")
|
||||
}
|
||||
Pair(Party(loadedServiceName, keyPair.public), keyPair)
|
||||
} else if (privKeyFile.exists()) {
|
||||
// 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 != identityPrincipal)
|
||||
if (myIdentity.name != serviceName)
|
||||
throw ConfigurationException("The legal name in the config file doesn't match the stored identity file:" +
|
||||
"$identityPrincipal vs ${myIdentity.name}")
|
||||
"$serviceName vs ${myIdentity.name}")
|
||||
// Load the private key.
|
||||
val keyPair = privKeyFile.readAll().deserialize<KeyPair>()
|
||||
// TODO: Use a proper certificate chain.
|
||||
val selfSignCert = X509Utilities.createSelfSignedCACert(serviceName, keyPair)
|
||||
keystore.addOrReplaceKey(privateKeyAlias, keyPair.private, configuration.keyStorePassword.toCharArray(), arrayOf(selfSignCert.certificate))
|
||||
keystore.save(configuration.keyStoreFile, configuration.keyStorePassword)
|
||||
Pair(myIdentity, keyPair)
|
||||
} else {
|
||||
// Create new keys and store in keystore.
|
||||
log.info("Identity key not found, generating fresh key!")
|
||||
val keyPair: KeyPair = generateKeyPair()
|
||||
val selfSignCert = X509Utilities.createSelfSignedCACert(serviceName, keyPair)
|
||||
keystore.addOrReplaceKey(privateKeyAlias, selfSignCert.keyPair.private, configuration.keyStorePassword.toCharArray(), arrayOf(selfSignCert.certificate))
|
||||
keystore.save(configuration.keyStoreFile, configuration.keyStorePassword)
|
||||
Pair(Party(serviceName, selfSignCert.keyPair.public), selfSignCert.keyPair)
|
||||
}
|
||||
partyKeys += identityAndKey.second
|
||||
return identityAndKey
|
||||
|
Loading…
Reference in New Issue
Block a user