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:
Ross Nicoll 2017-06-20 16:17:57 +01:00
parent 09ce52a33f
commit d54f66ccb0
6 changed files with 32 additions and 44 deletions

View File

@ -10,7 +10,6 @@ import org.bouncycastle.cert.X509CertificateHolder
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter
import org.bouncycastle.openssl.jcajce.JcaPEMWriter
import org.bouncycastle.util.io.pem.PemReader
import java.io.ByteArrayInputStream
import java.io.FileReader
import java.io.FileWriter
import java.io.InputStream
@ -129,22 +128,6 @@ object X509Utilities {
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) {
require(certificates.isNotEmpty()) { "Certificate path must contain at least one certificate" }
val certFactory = CertificateFactory.getInstance("X509")

View File

@ -9,6 +9,7 @@ import org.bouncycastle.asn1.x500.X500Name
import java.math.BigInteger
import java.security.KeyPair
import java.security.PublicKey
import java.security.cert.CertificateFactory
import java.time.Instant
// 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.
*/
fun getTestPartyAndCertificate(name: X500Name, publicKey: PublicKey, ca: CertificateAndKeyPair = DUMMY_CA): PartyAndCertificate {
val cert = X509Utilities.createCertificate(CertificateType.IDENTITY, ca.certificate, ca.keyPair, name, publicKey)
val certPath = X509Utilities.createCertificatePath(ca.certificate, cert, revocationEnabled = false)
return PartyAndCertificate(name, publicKey, cert, certPath)
fun getTestPartyAndCertificate(name: X500Name, publicKey: PublicKey, trustRoot: CertificateAndKeyPair = DUMMY_CA): PartyAndCertificate {
val certFactory = CertificateFactory.getInstance("X509")
val certHolder = X509Utilities.createCertificate(CertificateType.IDENTITY, trustRoot.certificate, trustRoot.keyPair, name, publicKey)
val certPath = certFactory.generateCertPath(listOf(certHolder.cert, trustRoot.certificate.cert))
return PartyAndCertificate(name, publicKey, certHolder, certPath)
}

View File

@ -16,8 +16,7 @@ import org.junit.Test
import org.slf4j.LoggerFactory
import java.io.ByteArrayInputStream
import java.io.InputStream
import java.security.cert.CertPath
import java.security.cert.X509Certificate
import java.security.cert.*
import java.time.Instant
import java.util.*
import kotlin.test.assertEquals
@ -152,10 +151,11 @@ class KryoTests {
@Test
fun `serialize - deserialize X509CertPath`() {
val certFactory = CertificateFactory.getInstance("X509")
val rootCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
val rootCACert = X509Utilities.createSelfSignedCACertificate(ALICE.name, rootCAKey)
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 actual: CertPath = serialized.deserialize(kryo)
assertEquals(expected, actual)

View File

@ -69,8 +69,9 @@ import java.nio.file.FileAlreadyExistsException
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.X509Certificate
import java.security.cert.*
import java.time.Clock
import java.util.*
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.
initialiseDatabasePersistence {
val tokenizableServices = makeServices()
val keyStoreWrapper = KeyStoreWrapper(configuration.trustStoreFile, configuration.trustStorePassword)
val tokenizableServices = makeServices(keyStoreWrapper)
smm = StateMachineManager(services,
checkpointStorage,
@ -437,7 +439,8 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
* Builds node internal, advertised, and plugin services.
* 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)
storage = storageServices.first
checkpointStorage = storageServices.second
@ -752,20 +755,22 @@ 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 = X509CertificateHolder(cert.encoded).subject
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 = X509Utilities.createCertificatePath(cert, cert, revocationEnabled = false)
val certPath = certFactory.generateCertPath(listOf(cert.cert) + clientCertPath)
Pair(PartyAndCertificate(loadedServiceName, keyPair.public, cert, certPath), keyPair)
} else if (privKeyFile.exists()) {
// Get keys from key file.
@ -784,12 +789,13 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
}
Pair(myIdentity, 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 = X509Utilities.createCertificatePath(cert, cert, revocationEnabled = false)
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)
@ -814,8 +820,8 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
}
}
private class KeyStoreWrapper(private val storePath: Path, private val storePassword: String) {
private val keyStore = KeyStoreUtilities.loadKeyStore(storePath, storePassword)
private class KeyStoreWrapper(val keyStore: KeyStore, val storePath: Path, private val storePassword: String) {
constructor(storePath: Path, storePassword: String) : this(KeyStoreUtilities.loadKeyStore(storePath, storePassword), storePath, storePassword)
fun certificateAndKeyPair(alias: String): CertificateAndKeyPair? {
return if (keyStore.containsAlias(alias)) keyStore.getCertificateAndKeyPair(alias, storePassword) else null

View File

@ -1,9 +1,6 @@
package net.corda.node.services.keys
import net.corda.core.crypto.CertificateType
import net.corda.core.crypto.ContentSignerBuilder
import net.corda.core.crypto.Crypto
import net.corda.core.crypto.X509Utilities
import net.corda.core.crypto.*
import net.corda.core.identity.AnonymousParty
import net.corda.core.identity.PartyAndCertificate
import net.corda.core.node.services.IdentityService
@ -13,6 +10,7 @@ import java.security.KeyPair
import java.security.PublicKey
import java.security.Security
import java.security.cert.CertPath
import java.security.cert.CertificateFactory
import java.security.cert.X509Certificate
import java.time.Duration
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
* [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 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 issuerSigner a content signer for the issuer.
* @param revocationEnabled whether to check revocation status of certificates in the certificate path.
* @return X.509 certificate and path to the trust root.
*/
@ -36,10 +34,8 @@ fun freshCertificate(identityService: IdentityService,
val issuerCertificate = issuer.certificate
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 actualPublicKey = Crypto.toSupportedPublicKey(ourCertificate.subjectPublicKeyInfo)
require(subjectPublicKey == actualPublicKey)
val ourCertPath = X509Utilities.createCertificatePath(issuerCertificate, ourCertificate, revocationEnabled = revocationEnabled)
require(Arrays.equals(ourCertificate.subjectPublicKeyInfo.encoded, subjectPublicKey.encoded))
val certFactory = CertificateFactory.getInstance("X509")
val ourCertPath = certFactory.generateCertPath(listOf(ourCertificate.cert) + issuer.certPath.certificates)
identityService.registerAnonymousIdentity(AnonymousParty(subjectPublicKey),
issuer.party,
ourCertPath)

View File

@ -34,9 +34,10 @@ object ServiceIdentityGenerator {
log.trace { "Generating a group identity \"serviceName\" for nodes: ${dirs.joinToString()}" }
val keyPairs = (1..dirs.size).map { generateKeyPair() }
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)
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 notaryPartyBytes = notaryParty.serialize()
val privateKeyFile = "$serviceId-private-key"