mirror of
https://github.com/corda/corda.git
synced 2025-06-18 07:08:15 +00:00
Replace certificate path generation
Use the certificate factory directly to build paths rather than assembling them via an interim API call. After reducing the complexity of the utility API, it's replacing two lines of code, at which point it seems better to make the behaviour clearer rather than having a function hide what's actually going on.
This commit is contained in:
@ -10,7 +10,6 @@ import org.bouncycastle.cert.X509CertificateHolder
|
|||||||
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter
|
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter
|
||||||
import org.bouncycastle.openssl.jcajce.JcaPEMWriter
|
import org.bouncycastle.openssl.jcajce.JcaPEMWriter
|
||||||
import org.bouncycastle.util.io.pem.PemReader
|
import org.bouncycastle.util.io.pem.PemReader
|
||||||
import java.io.ByteArrayInputStream
|
|
||||||
import java.io.FileReader
|
import java.io.FileReader
|
||||||
import java.io.FileWriter
|
import java.io.FileWriter
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
@ -129,22 +128,6 @@ object X509Utilities {
|
|||||||
return Crypto.createCertificate(certificateType, issuerCertificate.subject, issuerKeyPair, subject, subjectPublicKey, window, nameConstraints)
|
return Crypto.createCertificate(certificateType, issuerCertificate.subject, issuerKeyPair, subject, subjectPublicKey, window, nameConstraints)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Build a certificate path from a trusted root certificate to a target certificate. This will always return a path
|
|
||||||
* directly from the target to the root.
|
|
||||||
*
|
|
||||||
* @param trustedRoot trusted root certificate that will be the start of the path.
|
|
||||||
* @param certificates certificates in the path.
|
|
||||||
* @param revocationEnabled whether revocation of certificates in the path should be checked.
|
|
||||||
*/
|
|
||||||
fun createCertificatePath(trustedRoot: X509CertificateHolder, vararg certificates: X509CertificateHolder, revocationEnabled: Boolean): CertPath {
|
|
||||||
val certFactory = CertificateFactory.getInstance("X509")
|
|
||||||
val trustedRootX509 = certFactory.generateCertificate(ByteArrayInputStream(trustedRoot.encoded)) as X509Certificate
|
|
||||||
val params = PKIXParameters(setOf(TrustAnchor(trustedRootX509, null)))
|
|
||||||
params.isRevocationEnabled = revocationEnabled
|
|
||||||
return certFactory.generateCertPath(certificates.map { certFactory.generateCertificate(ByteArrayInputStream(it.encoded)) }.toList())
|
|
||||||
}
|
|
||||||
|
|
||||||
fun validateCertificateChain(trustedRoot: X509CertificateHolder, vararg certificates: Certificate) {
|
fun validateCertificateChain(trustedRoot: X509CertificateHolder, vararg certificates: Certificate) {
|
||||||
require(certificates.isNotEmpty()) { "Certificate path must contain at least one certificate" }
|
require(certificates.isNotEmpty()) { "Certificate path must contain at least one certificate" }
|
||||||
val certFactory = CertificateFactory.getInstance("X509")
|
val certFactory = CertificateFactory.getInstance("X509")
|
||||||
|
@ -9,6 +9,7 @@ import org.bouncycastle.asn1.x500.X500Name
|
|||||||
import java.math.BigInteger
|
import java.math.BigInteger
|
||||||
import java.security.KeyPair
|
import java.security.KeyPair
|
||||||
import java.security.PublicKey
|
import java.security.PublicKey
|
||||||
|
import java.security.cert.CertificateFactory
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
|
|
||||||
// A dummy time at which we will be pretending test transactions are created.
|
// A dummy time at which we will be pretending test transactions are created.
|
||||||
@ -69,8 +70,9 @@ val DUMMY_CA: CertificateAndKeyPair by lazy {
|
|||||||
/**
|
/**
|
||||||
* Build a test party with a nonsense certificate authority for testing purposes.
|
* Build a test party with a nonsense certificate authority for testing purposes.
|
||||||
*/
|
*/
|
||||||
fun getTestPartyAndCertificate(name: X500Name, publicKey: PublicKey, ca: CertificateAndKeyPair = DUMMY_CA): PartyAndCertificate {
|
fun getTestPartyAndCertificate(name: X500Name, publicKey: PublicKey, trustRoot: CertificateAndKeyPair = DUMMY_CA): PartyAndCertificate {
|
||||||
val cert = X509Utilities.createCertificate(CertificateType.IDENTITY, ca.certificate, ca.keyPair, name, publicKey)
|
val certFactory = CertificateFactory.getInstance("X509")
|
||||||
val certPath = X509Utilities.createCertificatePath(ca.certificate, cert, revocationEnabled = false)
|
val certHolder = X509Utilities.createCertificate(CertificateType.IDENTITY, trustRoot.certificate, trustRoot.keyPair, name, publicKey)
|
||||||
return PartyAndCertificate(name, publicKey, cert, certPath)
|
val certPath = certFactory.generateCertPath(listOf(certHolder.cert, trustRoot.certificate.cert))
|
||||||
|
return PartyAndCertificate(name, publicKey, certHolder, certPath)
|
||||||
}
|
}
|
||||||
|
@ -16,8 +16,7 @@ import org.junit.Test
|
|||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import java.io.ByteArrayInputStream
|
import java.io.ByteArrayInputStream
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.security.cert.CertPath
|
import java.security.cert.*
|
||||||
import java.security.cert.X509Certificate
|
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
@ -152,10 +151,11 @@ class KryoTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `serialize - deserialize X509CertPath`() {
|
fun `serialize - deserialize X509CertPath`() {
|
||||||
|
val certFactory = CertificateFactory.getInstance("X509")
|
||||||
val rootCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
|
val rootCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
|
||||||
val rootCACert = X509Utilities.createSelfSignedCACertificate(ALICE.name, rootCAKey)
|
val rootCACert = X509Utilities.createSelfSignedCACertificate(ALICE.name, rootCAKey)
|
||||||
val certificate = X509Utilities.createCertificate(CertificateType.TLS, rootCACert, rootCAKey, BOB.name, BOB_PUBKEY)
|
val certificate = X509Utilities.createCertificate(CertificateType.TLS, rootCACert, rootCAKey, BOB.name, BOB_PUBKEY)
|
||||||
val expected = X509Utilities.createCertificatePath(rootCACert, certificate, revocationEnabled = false)
|
val expected = certFactory.generateCertPath(listOf(certificate.cert, rootCACert.cert))
|
||||||
val serialized = expected.serialize(kryo).bytes
|
val serialized = expected.serialize(kryo).bytes
|
||||||
val actual: CertPath = serialized.deserialize(kryo)
|
val actual: CertPath = serialized.deserialize(kryo)
|
||||||
assertEquals(expected, actual)
|
assertEquals(expected, actual)
|
||||||
|
@ -69,8 +69,9 @@ import java.nio.file.FileAlreadyExistsException
|
|||||||
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.X509Certificate
|
import java.security.cert.*
|
||||||
import java.time.Clock
|
import java.time.Clock
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
@ -214,7 +215,8 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
|
|||||||
|
|
||||||
// Do all of this in a database transaction so anything that might need a connection has one.
|
// Do all of this in a database transaction so anything that might need a connection has one.
|
||||||
initialiseDatabasePersistence {
|
initialiseDatabasePersistence {
|
||||||
val tokenizableServices = makeServices()
|
val keyStoreWrapper = KeyStoreWrapper(configuration.trustStoreFile, configuration.trustStorePassword)
|
||||||
|
val tokenizableServices = makeServices(keyStoreWrapper)
|
||||||
|
|
||||||
smm = StateMachineManager(services,
|
smm = StateMachineManager(services,
|
||||||
checkpointStorage,
|
checkpointStorage,
|
||||||
@ -437,7 +439,8 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
|
|||||||
* Builds node internal, advertised, and plugin services.
|
* Builds node internal, advertised, and plugin services.
|
||||||
* Returns a list of tokenizable services to be added to the serialisation context.
|
* Returns a list of tokenizable services to be added to the serialisation context.
|
||||||
*/
|
*/
|
||||||
private fun makeServices(): MutableList<Any> {
|
private fun makeServices(keyStoreWrapper: KeyStoreWrapper): MutableList<Any> {
|
||||||
|
val keyStore = keyStoreWrapper.keyStore
|
||||||
val storageServices = initialiseStorageService(configuration.baseDirectory)
|
val storageServices = initialiseStorageService(configuration.baseDirectory)
|
||||||
storage = storageServices.first
|
storage = storageServices.first
|
||||||
checkpointStorage = storageServices.second
|
checkpointStorage = storageServices.second
|
||||||
@ -752,20 +755,22 @@ 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 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)
|
val certificateAndKeyPair = keyStore.certificateAndKeyPair(privateKeyAlias)
|
||||||
val identityCertPathAndKey: Pair<PartyAndCertificate, KeyPair> = if (certificateAndKeyPair != null) {
|
val identityCertPathAndKey: Pair<PartyAndCertificate, KeyPair> = if (certificateAndKeyPair != null) {
|
||||||
|
val clientCertPath = keyStore.keyStore.getCertificateChain(X509Utilities.CORDA_CLIENT_CA)
|
||||||
val (cert, keyPair) = certificateAndKeyPair
|
val (cert, keyPair) = certificateAndKeyPair
|
||||||
// Get keys from keystore.
|
// Get keys from keystore.
|
||||||
val loadedServiceName = X509CertificateHolder(cert.encoded).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 = X509Utilities.createCertificatePath(cert, cert, revocationEnabled = false)
|
val certPath = certFactory.generateCertPath(listOf(cert.cert) + clientCertPath)
|
||||||
Pair(PartyAndCertificate(loadedServiceName, keyPair.public, cert, certPath), keyPair)
|
Pair(PartyAndCertificate(loadedServiceName, keyPair.public, cert, certPath), keyPair)
|
||||||
} else if (privKeyFile.exists()) {
|
} else if (privKeyFile.exists()) {
|
||||||
// Get keys from key file.
|
// Get keys from key file.
|
||||||
@ -784,12 +789,13 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
|
|||||||
}
|
}
|
||||||
Pair(myIdentity, keyPair)
|
Pair(myIdentity, keyPair)
|
||||||
} else {
|
} else {
|
||||||
|
val clientCertPath = keyStore.keyStore.getCertificateChain(X509Utilities.CORDA_CLIENT_CA)
|
||||||
val clientCA = keyStore.certificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA)!!
|
val clientCA = keyStore.certificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA)!!
|
||||||
// Create new keys and store in keystore.
|
// Create new keys and store in keystore.
|
||||||
log.info("Identity key not found, generating fresh key!")
|
log.info("Identity key not found, generating fresh key!")
|
||||||
val keyPair: KeyPair = generateKeyPair()
|
val keyPair: KeyPair = generateKeyPair()
|
||||||
val cert = X509Utilities.createCertificate(CertificateType.IDENTITY, clientCA.certificate, clientCA.keyPair, serviceName, keyPair.public)
|
val cert = X509Utilities.createCertificate(CertificateType.IDENTITY, clientCA.certificate, clientCA.keyPair, serviceName, keyPair.public)
|
||||||
val certPath = X509Utilities.createCertificatePath(cert, cert, revocationEnabled = false)
|
val certPath = certFactory.generateCertPath(listOf(cert.cert) + clientCertPath)
|
||||||
keyStore.save(serviceName, privateKeyAlias, keyPair)
|
keyStore.save(serviceName, privateKeyAlias, keyPair)
|
||||||
require(certPath.certificates.isNotEmpty()) { "Certificate path cannot be empty" }
|
require(certPath.certificates.isNotEmpty()) { "Certificate path cannot be empty" }
|
||||||
Pair(PartyAndCertificate(serviceName, keyPair.public, cert, certPath), keyPair)
|
Pair(PartyAndCertificate(serviceName, keyPair.public, cert, certPath), keyPair)
|
||||||
@ -814,8 +820,8 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class KeyStoreWrapper(private val storePath: Path, private val storePassword: String) {
|
private class KeyStoreWrapper(val keyStore: KeyStore, val storePath: Path, private val storePassword: String) {
|
||||||
private val keyStore = KeyStoreUtilities.loadKeyStore(storePath, storePassword)
|
constructor(storePath: Path, storePassword: String) : this(KeyStoreUtilities.loadKeyStore(storePath, storePassword), storePath, storePassword)
|
||||||
|
|
||||||
fun certificateAndKeyPair(alias: String): CertificateAndKeyPair? {
|
fun certificateAndKeyPair(alias: String): CertificateAndKeyPair? {
|
||||||
return if (keyStore.containsAlias(alias)) keyStore.getCertificateAndKeyPair(alias, storePassword) else null
|
return if (keyStore.containsAlias(alias)) keyStore.getCertificateAndKeyPair(alias, storePassword) else null
|
||||||
|
@ -1,9 +1,6 @@
|
|||||||
package net.corda.node.services.keys
|
package net.corda.node.services.keys
|
||||||
|
|
||||||
import net.corda.core.crypto.CertificateType
|
import net.corda.core.crypto.*
|
||||||
import net.corda.core.crypto.ContentSignerBuilder
|
|
||||||
import net.corda.core.crypto.Crypto
|
|
||||||
import net.corda.core.crypto.X509Utilities
|
|
||||||
import net.corda.core.identity.AnonymousParty
|
import net.corda.core.identity.AnonymousParty
|
||||||
import net.corda.core.identity.PartyAndCertificate
|
import net.corda.core.identity.PartyAndCertificate
|
||||||
import net.corda.core.node.services.IdentityService
|
import net.corda.core.node.services.IdentityService
|
||||||
@ -13,6 +10,7 @@ import java.security.KeyPair
|
|||||||
import java.security.PublicKey
|
import java.security.PublicKey
|
||||||
import java.security.Security
|
import java.security.Security
|
||||||
import java.security.cert.CertPath
|
import java.security.cert.CertPath
|
||||||
|
import java.security.cert.CertificateFactory
|
||||||
import java.security.cert.X509Certificate
|
import java.security.cert.X509Certificate
|
||||||
import java.time.Duration
|
import java.time.Duration
|
||||||
import java.util.*
|
import java.util.*
|
||||||
@ -21,10 +19,10 @@ import java.util.*
|
|||||||
* Generates a new random [KeyPair], adds it to the internal key storage, then generates a corresponding
|
* Generates a new random [KeyPair], adds it to the internal key storage, then generates a corresponding
|
||||||
* [X509Certificate] and adds it to the identity service.
|
* [X509Certificate] and adds it to the identity service.
|
||||||
*
|
*
|
||||||
* @param subjectPublicKey public key of new identity.
|
|
||||||
* @param issuerSigner a content signer for the issuer.
|
|
||||||
* @param identityService issuer service to use when registering the certificate.
|
* @param identityService issuer service to use when registering the certificate.
|
||||||
|
* @param subjectPublicKey public key of new identity.
|
||||||
* @param issuer issuer to generate a key and certificate for. Must be an identity this node has the private key for.
|
* @param issuer issuer to generate a key and certificate for. Must be an identity this node has the private key for.
|
||||||
|
* @param issuerSigner a content signer for the issuer.
|
||||||
* @param revocationEnabled whether to check revocation status of certificates in the certificate path.
|
* @param revocationEnabled whether to check revocation status of certificates in the certificate path.
|
||||||
* @return X.509 certificate and path to the trust root.
|
* @return X.509 certificate and path to the trust root.
|
||||||
*/
|
*/
|
||||||
@ -36,10 +34,8 @@ fun freshCertificate(identityService: IdentityService,
|
|||||||
val issuerCertificate = issuer.certificate
|
val issuerCertificate = issuer.certificate
|
||||||
val window = X509Utilities.getCertificateValidityWindow(Duration.ZERO, Duration.ofDays(10 * 365), issuerCertificate)
|
val window = X509Utilities.getCertificateValidityWindow(Duration.ZERO, Duration.ofDays(10 * 365), issuerCertificate)
|
||||||
val ourCertificate = Crypto.createCertificate(CertificateType.IDENTITY, issuerCertificate.subject, issuerSigner, issuer.name, subjectPublicKey, window)
|
val ourCertificate = Crypto.createCertificate(CertificateType.IDENTITY, issuerCertificate.subject, issuerSigner, issuer.name, subjectPublicKey, window)
|
||||||
val actualPublicKey = Crypto.toSupportedPublicKey(ourCertificate.subjectPublicKeyInfo)
|
val certFactory = CertificateFactory.getInstance("X509")
|
||||||
require(subjectPublicKey == actualPublicKey)
|
val ourCertPath = certFactory.generateCertPath(listOf(ourCertificate.cert) + issuer.certPath.certificates)
|
||||||
val ourCertPath = X509Utilities.createCertificatePath(issuerCertificate, ourCertificate, revocationEnabled = revocationEnabled)
|
|
||||||
require(Arrays.equals(ourCertificate.subjectPublicKeyInfo.encoded, subjectPublicKey.encoded))
|
|
||||||
identityService.registerAnonymousIdentity(AnonymousParty(subjectPublicKey),
|
identityService.registerAnonymousIdentity(AnonymousParty(subjectPublicKey),
|
||||||
issuer.party,
|
issuer.party,
|
||||||
ourCertPath)
|
ourCertPath)
|
||||||
|
@ -34,9 +34,10 @@ object ServiceIdentityGenerator {
|
|||||||
log.trace { "Generating a group identity \"serviceName\" for nodes: ${dirs.joinToString()}" }
|
log.trace { "Generating a group identity \"serviceName\" for nodes: ${dirs.joinToString()}" }
|
||||||
val keyPairs = (1..dirs.size).map { generateKeyPair() }
|
val keyPairs = (1..dirs.size).map { generateKeyPair() }
|
||||||
val notaryKey = CompositeKey.Builder().addKeys(keyPairs.map { it.public }).build(threshold)
|
val notaryKey = CompositeKey.Builder().addKeys(keyPairs.map { it.public }).build(threshold)
|
||||||
val notaryCert = X509Utilities.createCertificate(CertificateType.INTERMEDIATE_CA, serviceCa.certificate,
|
val certFactory = CertificateFactory.getInstance("X509")
|
||||||
|
val notaryCert = X509Utilities.createCertificate(CertificateType.IDENTITY, serviceCa.certificate,
|
||||||
serviceCa.keyPair, serviceName, notaryKey)
|
serviceCa.keyPair, serviceName, notaryKey)
|
||||||
val notaryCertPath = X509Utilities.createCertificatePath(serviceCa.certificate, notaryCert, revocationEnabled = false)
|
val notaryCertPath = certFactory.generateCertPath(listOf(notaryCert.cert, serviceCa.certificate.cert))
|
||||||
val notaryParty = PartyAndCertificate(serviceName, notaryKey, notaryCert, notaryCertPath)
|
val notaryParty = PartyAndCertificate(serviceName, notaryKey, notaryCert, notaryCertPath)
|
||||||
val notaryPartyBytes = notaryParty.serialize()
|
val notaryPartyBytes = notaryParty.serialize()
|
||||||
val privateKeyFile = "$serviceId-private-key"
|
val privateKeyFile = "$serviceId-private-key"
|
||||||
|
Reference in New Issue
Block a user