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:
Patrick Kuo 2017-05-11 16:53:44 +01:00 committed by GitHub
parent 1ecce79913
commit af7ba082a4
4 changed files with 53 additions and 36 deletions

View File

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

View File

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

View File

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

View File

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