diff --git a/core/src/main/kotlin/net/corda/core/crypto/X509Utilities.kt b/core/src/main/kotlin/net/corda/core/crypto/X509Utilities.kt index 50bf8a3f8d..836d5a8b37 100644 --- a/core/src/main/kotlin/net/corda/core/crypto/X509Utilities.kt +++ b/core/src/main/kotlin/net/corda/core/crypto/X509Utilities.kt @@ -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") diff --git a/core/src/main/kotlin/net/corda/core/utilities/TestConstants.kt b/core/src/main/kotlin/net/corda/core/utilities/TestConstants.kt index 384fdcab4f..42ff6bf326 100644 --- a/core/src/main/kotlin/net/corda/core/utilities/TestConstants.kt +++ b/core/src/main/kotlin/net/corda/core/utilities/TestConstants.kt @@ -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) } diff --git a/core/src/test/kotlin/net/corda/core/serialization/KryoTests.kt b/core/src/test/kotlin/net/corda/core/serialization/KryoTests.kt index 6250b85108..0852846808 100644 --- a/core/src/test/kotlin/net/corda/core/serialization/KryoTests.kt +++ b/core/src/test/kotlin/net/corda/core/serialization/KryoTests.kt @@ -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) diff --git a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt index eb9e260f66..dac13f9159 100644 --- a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt @@ -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 { + private fun makeServices(keyStoreWrapper: KeyStoreWrapper): MutableList { + 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 = 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 diff --git a/node/src/main/kotlin/net/corda/node/services/keys/KMSUtils.kt b/node/src/main/kotlin/net/corda/node/services/keys/KMSUtils.kt index 35f29829be..7f4dff1c5e 100644 --- a/node/src/main/kotlin/net/corda/node/services/keys/KMSUtils.kt +++ b/node/src/main/kotlin/net/corda/node/services/keys/KMSUtils.kt @@ -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) diff --git a/node/src/main/kotlin/net/corda/node/utilities/ServiceIdentityGenerator.kt b/node/src/main/kotlin/net/corda/node/utilities/ServiceIdentityGenerator.kt index 3c684075f7..e9bf4f8266 100644 --- a/node/src/main/kotlin/net/corda/node/utilities/ServiceIdentityGenerator.kt +++ b/node/src/main/kotlin/net/corda/node/utilities/ServiceIdentityGenerator.kt @@ -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"