From 61c7de22d6717bd3f11dd059bc1acc2c5a3febde Mon Sep 17 00:00:00 2001 From: Shams Asari Date: Wed, 24 Jan 2018 07:51:55 +0000 Subject: [PATCH] Replaced KeyStoreWrapper with X509KeyStore, which is still a wrapper but assumes only X509 certs and has better APIs (#2411) --- .../net/corda/core/internal/CertRole.kt | 43 ++++------- .../corda/core/crypto/CompositeKeyTests.kt | 14 ++-- .../core/identity/PartyAndCertificateTest.kt | 29 ++++--- .../nodeapi/internal/DevIdentityGenerator.kt | 26 +++---- .../nodeapi/internal/KeyStoreConfigHelpers.kt | 77 +++++++++---------- .../internal/config/SSLConfiguration.kt | 13 ++++ .../internal/crypto/KeyStoreUtilities.kt | 19 ++--- .../internal/crypto/KeyStoreWrapper.kt | 40 ---------- .../nodeapi/internal/crypto/X509KeyStore.kt | 74 ++++++++++++++++++ .../nodeapi/internal/crypto/X509Utilities.kt | 5 +- .../internal/crypto/X509UtilitiesTest.kt | 8 +- .../net/corda/node/NodeKeystoreCheckTest.kt | 20 ++--- .../net/corda/node/amqp/AMQPBridgeTest.kt | 13 ++-- .../net/corda/node/amqp/ProtonWrapperTests.kt | 13 ++-- .../messaging/MQSecurityAsNodeTest.kt | 30 ++++---- .../net/corda/node/internal/AbstractNode.kt | 56 +++++++------- .../node/services/config/ConfigUtilities.kt | 23 +++--- .../services/messaging/AMQPBridgeManager.kt | 5 +- .../messaging/ArtemisMessagingServer.kt | 6 +- .../services/messaging/CoreBridgeManager.kt | 3 +- .../services/messaging/RPCMessagingClient.kt | 4 +- .../HTTPNetworkRegistrationService.kt | 8 +- .../registration/NetworkRegistrationHelper.kt | 56 +++++++------- .../NetworkRegistrationService.kt | 4 +- .../NetworkRegistrationHelperTest.kt | 46 +++++------ .../testing/node/internal/DriverDSLImpl.kt | 12 +-- 26 files changed, 330 insertions(+), 317 deletions(-) delete mode 100644 node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/KeyStoreWrapper.kt create mode 100644 node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/X509KeyStore.kt diff --git a/core/src/main/kotlin/net/corda/core/internal/CertRole.kt b/core/src/main/kotlin/net/corda/core/internal/CertRole.kt index c2224b3c5a..f14633c883 100644 --- a/core/src/main/kotlin/net/corda/core/internal/CertRole.kt +++ b/core/src/main/kotlin/net/corda/core/internal/CertRole.kt @@ -1,6 +1,7 @@ package net.corda.core.internal import net.corda.core.CordaOID +import net.corda.core.utilities.NonEmptySet import org.bouncycastle.asn1.ASN1Encodable import org.bouncycastle.asn1.ASN1Integer import org.bouncycastle.asn1.ASN1Primitive @@ -23,40 +24,38 @@ import java.security.cert.X509Certificate // NOTE: The order of the entries in the enum MUST NOT be changed, as their ordinality is used as an identifier. Please // also note that IDs are numbered from 1 upwards, matching numbering of other enum types in ASN.1 specifications. // TODO: Link to the specification once it has a permanent URL -enum class CertRole(val validParents: Set, val isIdentity: Boolean, val isWellKnown: Boolean) : ASN1Encodable { +enum class CertRole(val validParents: NonEmptySet, val isIdentity: Boolean, val isWellKnown: Boolean) : ASN1Encodable { /** * Intermediate CA (Doorman service). */ - INTERMEDIATE_CA(setOf(null), false, false), + INTERMEDIATE_CA(NonEmptySet.of(null), false, false), /** Signing certificate for the network map. */ - NETWORK_MAP(setOf(null), false, false), + NETWORK_MAP(NonEmptySet.of(null), false, false), /** Well known (publicly visible) identity of a service (such as notary). */ - SERVICE_IDENTITY(setOf(INTERMEDIATE_CA), true, true), + SERVICE_IDENTITY(NonEmptySet.of(INTERMEDIATE_CA), true, true), /** Node level CA from which the TLS and well known identity certificates are issued. */ - NODE_CA(setOf(INTERMEDIATE_CA), false, false), + NODE_CA(NonEmptySet.of(INTERMEDIATE_CA), false, false), /** Transport layer security certificate for a node. */ - TLS(setOf(NODE_CA), false, false), + TLS(NonEmptySet.of(NODE_CA), false, false), /** Well known (publicly visible) identity of a legal entity. */ - LEGAL_IDENTITY(setOf(INTERMEDIATE_CA, NODE_CA), true, true), + LEGAL_IDENTITY(NonEmptySet.of(INTERMEDIATE_CA, NODE_CA), true, true), /** Confidential (limited visibility) identity of a legal entity. */ - CONFIDENTIAL_LEGAL_IDENTITY(setOf(LEGAL_IDENTITY), true, false); + CONFIDENTIAL_LEGAL_IDENTITY(NonEmptySet.of(LEGAL_IDENTITY), true, false); companion object { - private var cachedRoles: Array? = null + private val values by lazy(LazyThreadSafetyMode.NONE, CertRole::values) + /** * Get a role from its ASN.1 encoded form. * * @throws IllegalArgumentException if the encoded data is not a valid role. */ fun getInstance(id: ASN1Integer): CertRole { - if (cachedRoles == null) { - cachedRoles = CertRole.values() - } val idVal = id.value - require(idVal.compareTo(BigInteger.ZERO) > 0) { "Invalid role ID" } + require(idVal > BigInteger.ZERO) { "Invalid role ID" } return try { val ordinal = idVal.intValueExact() - 1 - cachedRoles!![ordinal] + values[ordinal] } catch (ex: ArithmeticException) { throw IllegalArgumentException("Invalid role ID") } catch (ex: ArrayIndexOutOfBoundsException) { @@ -77,14 +76,7 @@ enum class CertRole(val validParents: Set, val isIdentity: Boolean, v * @return the role if the extension is present, or null otherwise. * @throws IllegalArgumentException if the extension is present but is invalid. */ - fun extract(cert: Certificate): CertRole? { - val x509Cert = cert as? X509Certificate - return if (x509Cert != null) { - extract(x509Cert) - } else { - null - } - } + fun extract(cert: Certificate): CertRole? = (cert as? X509Certificate)?.let { extract(it) } /** * Get a role from a certificate. @@ -93,12 +85,9 @@ enum class CertRole(val validParents: Set, val isIdentity: Boolean, v * @throws IllegalArgumentException if the extension is present but is invalid. */ fun extract(cert: X509Certificate): CertRole? { - val extensionData: ByteArray? = cert.getExtensionValue(CordaOID.X509_EXTENSION_CORDA_ROLE) - return if (extensionData != null) { - val extensionString = DEROctetString.getInstance(extensionData) + return cert.getExtensionValue(CordaOID.X509_EXTENSION_CORDA_ROLE)?.let { + val extensionString = DEROctetString.getInstance(it) getInstance(extensionString.octets) - } else { - null } } } diff --git a/core/src/test/kotlin/net/corda/core/crypto/CompositeKeyTests.kt b/core/src/test/kotlin/net/corda/core/crypto/CompositeKeyTests.kt index 9e749e3451..4c730ddf4a 100644 --- a/core/src/test/kotlin/net/corda/core/crypto/CompositeKeyTests.kt +++ b/core/src/test/kotlin/net/corda/core/crypto/CompositeKeyTests.kt @@ -6,9 +6,13 @@ import net.corda.core.internal.div import net.corda.core.serialization.serialize import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.toBase58String -import net.corda.nodeapi.internal.crypto.* +import net.corda.nodeapi.internal.crypto.CertificateType +import net.corda.nodeapi.internal.crypto.X509KeyStore +import net.corda.nodeapi.internal.crypto.X509Utilities +import net.corda.nodeapi.internal.crypto.loadKeyStore import net.corda.testing.core.SerializationEnvironmentRule import net.corda.testing.internal.kryoSpecific +import org.assertj.core.api.Assertions.assertThat import org.junit.Rule import org.junit.Test import org.junit.rules.TemporaryFolder @@ -341,9 +345,9 @@ class CompositeKeyTests { // Store certificate to keystore. val keystorePath = tempFolder.root.toPath() / "keystore.jks" - val keystore = loadOrCreateKeyStore(keystorePath, "password") - keystore.setCertificateEntry("CompositeKey", compositeKeyCert) - keystore.save(keystorePath, "password") + X509KeyStore.fromFile(keystorePath, "password", createNew = true).update { + setCertificate("CompositeKey", compositeKeyCert) + } // Load keystore from disk. val keystore2 = loadKeyStore(keystorePath, "password") @@ -352,7 +356,7 @@ class CompositeKeyTests { val key = keystore2.getCertificate("CompositeKey").publicKey // Convert sun public key to Composite key. val compositeKey2 = Crypto.toSupportedPublicKey(key) - assertTrue { compositeKey2 is CompositeKey } + assertThat(compositeKey2).isInstanceOf(CompositeKey::class.java) // Run the same composite key test again. assertTrue { compositeKey2.isFulfilledBy(signatures.byKeys()) } diff --git a/core/src/test/kotlin/net/corda/core/identity/PartyAndCertificateTest.kt b/core/src/test/kotlin/net/corda/core/identity/PartyAndCertificateTest.kt index faca71eeba..39cdbbe8ba 100644 --- a/core/src/test/kotlin/net/corda/core/identity/PartyAndCertificateTest.kt +++ b/core/src/test/kotlin/net/corda/core/identity/PartyAndCertificateTest.kt @@ -1,21 +1,19 @@ package net.corda.core.identity +import com.google.common.jimfs.Configuration.unix +import com.google.common.jimfs.Jimfs import net.corda.core.crypto.entropyToKeyPair -import net.corda.core.internal.read import net.corda.core.serialization.deserialize import net.corda.core.serialization.serialize -import net.corda.nodeapi.internal.crypto.KEYSTORE_TYPE import net.corda.nodeapi.internal.crypto.X509CertificateFactory -import net.corda.nodeapi.internal.crypto.save +import net.corda.nodeapi.internal.crypto.X509KeyStore import net.corda.testing.core.DEV_ROOT_CA import net.corda.testing.core.SerializationEnvironmentRule import net.corda.testing.core.getTestPartyAndCertificate import org.assertj.core.api.Assertions.assertThat import org.junit.Rule import org.junit.Test -import java.io.File import java.math.BigInteger -import java.security.KeyStore import kotlin.test.assertFailsWith class PartyAndCertificateTest { @@ -46,17 +44,18 @@ class PartyAndCertificateTest { CordaX500Name(organisation = "Test Corp", locality = "Madrid", country = "ES"), entropyToKeyPair(BigInteger.valueOf(83)).public)) val original = identity.certificate + val alias = identity.name.toString() val storePassword = "test" - val keyStoreFilePath = File.createTempFile("serialization_test", "jks").toPath() - var keyStore = KeyStore.getInstance(KEYSTORE_TYPE) - keyStore.load(null, storePassword.toCharArray()) - keyStore.setCertificateEntry(identity.name.toString(), original) - keyStore.save(keyStoreFilePath, storePassword) + Jimfs.newFileSystem(unix()).use { + val keyStoreFile = it.getPath("/serialization_test.jks") - // Load the key store back in again - keyStore = KeyStore.getInstance(KEYSTORE_TYPE) - keyStoreFilePath.read { keyStore.load(it, storePassword.toCharArray()) } - val copy = keyStore.getCertificate(identity.name.toString()) - assertThat(copy).isEqualTo(original) // .isNotSameAs(original) + X509KeyStore.fromFile(keyStoreFile, storePassword, createNew = true).update { + setCertificate(alias, original) + } + + // Load the key store back in again + val copy = X509KeyStore.fromFile(keyStoreFile, storePassword).getCertificate(alias) + assertThat(copy).isEqualTo(original) + } } } diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/DevIdentityGenerator.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/DevIdentityGenerator.kt index 0965db3a42..cc0563bef3 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/DevIdentityGenerator.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/DevIdentityGenerator.kt @@ -1,7 +1,6 @@ package net.corda.nodeapi.internal import net.corda.core.crypto.CompositeKey -import net.corda.core.crypto.Crypto import net.corda.core.crypto.generateKeyPair import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party @@ -9,7 +8,9 @@ import net.corda.core.internal.createDirectories import net.corda.core.internal.div import net.corda.core.utilities.trace import net.corda.nodeapi.internal.config.NodeSSLConfiguration -import net.corda.nodeapi.internal.crypto.* +import net.corda.nodeapi.internal.crypto.CertificateType +import net.corda.nodeapi.internal.crypto.X509KeyStore +import net.corda.nodeapi.internal.crypto.X509Utilities import org.slf4j.LoggerFactory import java.nio.file.Path import java.security.KeyPair @@ -37,10 +38,9 @@ object DevIdentityGenerator { } nodeSslConfig.certificatesDirectory.createDirectories() - nodeSslConfig.createDevKeyStores(legalName) + val (nodeKeyStore) = nodeSslConfig.createDevKeyStores(legalName) - val keyStoreWrapper = KeyStoreWrapper(nodeSslConfig.nodeKeystore, nodeSslConfig.keyStorePassword) - val identity = keyStoreWrapper.storeLegalIdentity(legalName, "$NODE_IDENTITY_ALIAS_PREFIX-private-key", Crypto.generateKeyPair()) + val identity = nodeKeyStore.storeLegalIdentity("$NODE_IDENTITY_ALIAS_PREFIX-private-key") return identity.party } @@ -78,13 +78,13 @@ object DevIdentityGenerator { publicKey) } val distServKeyStoreFile = (nodeDir / "certificates").createDirectories() / "distributedService.jks" - val keystore = loadOrCreateKeyStore(distServKeyStoreFile, "cordacadevpass") - keystore.setCertificateEntry("$DISTRIBUTED_NOTARY_ALIAS_PREFIX-composite-key", compositeKeyCert) - keystore.setKeyEntry( - "$DISTRIBUTED_NOTARY_ALIAS_PREFIX-private-key", - keyPair.private, - "cordacadevkeypass".toCharArray(), - arrayOf(serviceKeyCert, DEV_INTERMEDIATE_CA.certificate, DEV_ROOT_CA.certificate)) - keystore.save(distServKeyStoreFile, "cordacadevpass") + X509KeyStore.fromFile(distServKeyStoreFile, "cordacadevpass", createNew = true).update { + setCertificate("$DISTRIBUTED_NOTARY_ALIAS_PREFIX-composite-key", compositeKeyCert) + setPrivateKey( + "$DISTRIBUTED_NOTARY_ALIAS_PREFIX-private-key", + keyPair.private, + listOf(serviceKeyCert, DEV_INTERMEDIATE_CA.certificate, DEV_ROOT_CA.certificate), + "cordacadevkeypass") + } } } diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/KeyStoreConfigHelpers.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/KeyStoreConfigHelpers.kt index bbbde7aec4..2ecc461771 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/KeyStoreConfigHelpers.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/KeyStoreConfigHelpers.kt @@ -1,7 +1,9 @@ package net.corda.nodeapi.internal +import net.corda.core.crypto.Crypto import net.corda.core.crypto.Crypto.generateKeyPair import net.corda.core.identity.CordaX500Name +import net.corda.core.identity.PartyAndCertificate import net.corda.core.internal.x500Name import net.corda.nodeapi.internal.config.SSLConfiguration import net.corda.nodeapi.internal.crypto.* @@ -20,50 +22,47 @@ import javax.security.auth.x500.X500Principal */ fun SSLConfiguration.createDevKeyStores(legalName: CordaX500Name, rootCert: X509Certificate = DEV_ROOT_CA.certificate, - intermediateCa: CertificateAndKeyPair = DEV_INTERMEDIATE_CA) { + intermediateCa: CertificateAndKeyPair = DEV_INTERMEDIATE_CA): Pair { val (nodeCaCert, nodeCaKeyPair) = createDevNodeCa(intermediateCa, legalName) - createDevKeyStores(rootCert, intermediateCa, nodeCaCert, nodeCaKeyPair, legalName) -} - -/** - * Create the node and SSL key stores needed by a node. The node key store will be populated with a node CA cert (using - * the given legal name), and the SSL key store will store the TLS cert which is a sub-cert of the node CA. - */ -fun SSLConfiguration.createDevKeyStores(rootCert: X509Certificate, intermediateCa: CertificateAndKeyPair, nodeCaCert: X509Certificate, nodeCaKeyPair: KeyPair, legalName: CordaX500Name) { - createNodeKeyStore(nodeCaCert, nodeCaKeyPair, intermediateCa, rootCert) - createSslKeyStore(nodeCaCert, nodeCaKeyPair, legalName, intermediateCa, rootCert) -} - -/** - * Create the SSL key store needed by a node. - */ -fun SSLConfiguration.createSslKeyStore(nodeCaCert: X509Certificate, nodeCaKeyPair: KeyPair, legalName: CordaX500Name, intermediateCa: CertificateAndKeyPair, rootCert: X509Certificate) { - val tlsKeyPair = generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) - val tlsCert = X509Utilities.createCertificate(CertificateType.TLS, nodeCaCert, nodeCaKeyPair, legalName.x500Principal, tlsKeyPair.public) - - loadOrCreateKeyStore(sslKeystore, keyStorePassword).apply { - addOrReplaceKey( - X509Utilities.CORDA_CLIENT_TLS, - tlsKeyPair.private, - keyStorePassword.toCharArray(), - arrayOf(tlsCert, nodeCaCert, intermediateCa.certificate, rootCert)) - save(sslKeystore, keyStorePassword) - } -} - -/** - * Create the node key store needed by a node. - */ -fun SSLConfiguration.createNodeKeyStore(nodeCaCert: X509Certificate, nodeCaKeyPair: KeyPair, intermediateCa: CertificateAndKeyPair, rootCert: X509Certificate) { - loadOrCreateKeyStore(nodeKeystore, keyStorePassword).apply { - addOrReplaceKey( + val nodeKeyStore = loadNodeKeyStore(createNew = true) + nodeKeyStore.update { + setPrivateKey( X509Utilities.CORDA_CLIENT_CA, nodeCaKeyPair.private, - keyStorePassword.toCharArray(), - arrayOf(nodeCaCert, intermediateCa.certificate, rootCert)) - save(nodeKeystore, keyStorePassword) + listOf(nodeCaCert, intermediateCa.certificate, rootCert)) } + + val sslKeyStore = loadSslKeyStore(createNew = true) + sslKeyStore.update { + val tlsKeyPair = generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) + val tlsCert = X509Utilities.createCertificate(CertificateType.TLS, nodeCaCert, nodeCaKeyPair, legalName.x500Principal, tlsKeyPair.public) + setPrivateKey( + X509Utilities.CORDA_CLIENT_TLS, + tlsKeyPair.private, + listOf(tlsCert, nodeCaCert, intermediateCa.certificate, rootCert)) + } + + return Pair(nodeKeyStore, sslKeyStore) +} + +fun X509KeyStore.storeLegalIdentity(alias: String, keyPair: KeyPair = Crypto.generateKeyPair()): PartyAndCertificate { + val nodeCaCertPath = getCertificateChain(X509Utilities.CORDA_CLIENT_CA) + // Assume key password = store password. + val nodeCaCertAndKeyPair = getCertificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA) + // Create new keys and store in keystore. + val identityCert = X509Utilities.createCertificate( + CertificateType.LEGAL_IDENTITY, + nodeCaCertAndKeyPair.certificate, + nodeCaCertAndKeyPair.keyPair, + nodeCaCertAndKeyPair.certificate.subjectX500Principal, + keyPair.public) + // TODO: X509Utilities.validateCertificateChain() + // Assume key password = store password. + val identityCertPath = listOf(identityCert) + nodeCaCertPath + setPrivateKey(alias, keyPair.private, identityCertPath) + save() + return PartyAndCertificate(X509CertificateFactory().generateCertPath(identityCertPath)) } fun createDevNetworkMapCa(rootCa: CertificateAndKeyPair = DEV_ROOT_CA): CertificateAndKeyPair { diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/config/SSLConfiguration.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/config/SSLConfiguration.kt index fb5424e19d..f44b190d5a 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/config/SSLConfiguration.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/config/SSLConfiguration.kt @@ -1,6 +1,7 @@ package net.corda.nodeapi.internal.config import net.corda.core.internal.div +import net.corda.nodeapi.internal.crypto.X509KeyStore import java.nio.file.Path interface SSLConfiguration { @@ -11,6 +12,18 @@ interface SSLConfiguration { // TODO This looks like it should be in NodeSSLConfiguration val nodeKeystore: Path get() = certificatesDirectory / "nodekeystore.jks" val trustStoreFile: Path get() = certificatesDirectory / "truststore.jks" + + fun loadTrustStore(createNew: Boolean = false): X509KeyStore { + return X509KeyStore.fromFile(trustStoreFile, trustStorePassword, createNew) + } + + fun loadNodeKeyStore(createNew: Boolean = false): X509KeyStore { + return X509KeyStore.fromFile(nodeKeystore, keyStorePassword, createNew) + } + + fun loadSslKeyStore(createNew: Boolean = false): X509KeyStore { + return X509KeyStore.fromFile(sslKeystore, keyStorePassword, createNew) + } } interface NodeSSLConfiguration : SSLConfiguration { diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/KeyStoreUtilities.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/KeyStoreUtilities.kt index d00f994d9c..981d16cf94 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/KeyStoreUtilities.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/KeyStoreUtilities.kt @@ -9,7 +9,6 @@ import net.corda.core.internal.read import net.corda.core.internal.write import java.io.IOException import java.io.InputStream -import java.io.OutputStream import java.nio.file.Path import java.security.* import java.security.cert.Certificate @@ -103,17 +102,11 @@ fun KeyStore.addOrReplaceCertificate(alias: String, cert: Certificate) { * @param storePassword password to access the store in future. This does not have to be the same password as any keys stored, * but for SSL purposes this is recommended. */ -fun KeyStore.save(keyStoreFilePath: Path, storePassword: String) = keyStoreFilePath.write { store(it, storePassword) } - -fun KeyStore.store(out: OutputStream, password: String) = store(out, password.toCharArray()) - -/** - * Extract public and private keys from a KeyStore file assuming storage alias is known. - * @param alias The name to lookup the Key and Certificate chain from. - * @param keyPassword Password to unlock the private key entries. - * @return The KeyPair found in the KeyStore under the specified alias. - */ -fun KeyStore.getKeyPair(alias: String, keyPassword: String): KeyPair = getCertificateAndKeyPair(alias, keyPassword).keyPair +fun KeyStore.save(keyStoreFilePath: Path, storePassword: String) { + keyStoreFilePath.write { + store(it, storePassword.toCharArray()) + } +} /** * Helper method to load a Certificate and KeyPair from their KeyStore. @@ -135,7 +128,7 @@ fun KeyStore.getCertificateAndKeyPair(alias: String, keyPassword: String): Certi */ fun KeyStore.getX509Certificate(alias: String): X509Certificate { val certificate = getCertificate(alias) ?: throw IllegalArgumentException("No certificate under alias \"$alias\".") - return certificate as? X509Certificate ?: throw IllegalArgumentException("Certificate under alias \"$alias\" is not an X.509 certificate.") + return certificate as? X509Certificate ?: throw IllegalStateException("Certificate under alias \"$alias\" is not an X.509 certificate: $certificate") } /** diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/KeyStoreWrapper.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/KeyStoreWrapper.kt deleted file mode 100644 index f7cae5b441..0000000000 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/KeyStoreWrapper.kt +++ /dev/null @@ -1,40 +0,0 @@ -package net.corda.nodeapi.internal.crypto - -import net.corda.core.identity.CordaX500Name -import net.corda.core.identity.PartyAndCertificate -import net.corda.core.internal.read -import java.nio.file.Path -import java.security.KeyPair -import java.security.cert.Certificate - -class KeyStoreWrapper(private val storePath: Path, private val storePassword: String) { - private val keyStore = storePath.read { loadKeyStore(it, storePassword) } - - // TODO This method seems misplaced in this class. - fun storeLegalIdentity(legalName: CordaX500Name, alias: String, keyPair: KeyPair): PartyAndCertificate { - val nodeCaCertChain = keyStore.getCertificateChain(X509Utilities.CORDA_CLIENT_CA) - val nodeCa = getCertificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA) - val identityCert = X509Utilities.createCertificate( - CertificateType.LEGAL_IDENTITY, - nodeCa.certificate, - nodeCa.keyPair, - legalName.x500Principal, - keyPair.public) - val identityCertPath = X509CertificateFactory().generateCertPath(identityCert, *nodeCaCertChain) - // Assume key password = store password. - keyStore.addOrReplaceKey(alias, keyPair.private, storePassword.toCharArray(), identityCertPath.certificates.toTypedArray()) - keyStore.save(storePath, storePassword) - return PartyAndCertificate(identityCertPath) - } - - // Delegate methods to keystore. Sadly keystore doesn't have an interface. - fun containsAlias(alias: String) = keyStore.containsAlias(alias) - - fun getX509Certificate(alias: String) = keyStore.getX509Certificate(alias) - - fun getCertificateChain(alias: String): Array = keyStore.getCertificateChain(alias) - - fun getCertificate(alias: String): Certificate = keyStore.getCertificate(alias) - - fun getCertificateAndKeyPair(alias: String): CertificateAndKeyPair = keyStore.getCertificateAndKeyPair(alias, storePassword) -} \ No newline at end of file diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/X509KeyStore.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/X509KeyStore.kt new file mode 100644 index 0000000000..53eb020224 --- /dev/null +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/X509KeyStore.kt @@ -0,0 +1,74 @@ +package net.corda.nodeapi.internal.crypto + +import net.corda.core.crypto.Crypto +import net.corda.core.internal.uncheckedCast +import java.nio.file.Path +import java.security.KeyPair +import java.security.KeyStore +import java.security.PrivateKey +import java.security.cert.X509Certificate + +/** + * Wrapper around a [KeyStore] object but only dealing with [X509Certificate]s and with a better API. + */ +class X509KeyStore private constructor(val internal: KeyStore, private val storePassword: String, private val keyStoreFile: Path? = null) { + /** Wrap an existing [KeyStore]. [save] is not supported. */ + constructor(internal: KeyStore, storePassword: String) : this(internal, storePassword, null) + + companion object { + /** + * Read a [KeyStore] from the given file. If the key store doesn't exist and [createNew] is true then a blank + * key store will be written out. Changes to the returned [X509KeyStore] can be persisted with [save]. + */ + fun fromFile(keyStoreFile: Path, storePassword: String, createNew: Boolean = false): X509KeyStore { + val internal: KeyStore = if (createNew) loadOrCreateKeyStore(keyStoreFile, storePassword) else loadKeyStore(keyStoreFile, storePassword) + return X509KeyStore(internal, storePassword, keyStoreFile) + } + } + + operator fun contains(alias: String): Boolean = internal.containsAlias(alias) + + fun aliases(): Iterator = internal.aliases().iterator() + + fun getCertificate(alias: String): X509Certificate = internal.getX509Certificate(alias) + + fun getCertificateChain(alias: String): List { + val certArray = requireNotNull(internal.getCertificateChain(alias)) { "No certificate chain under the alias $alias" } + check(certArray.all { it is X509Certificate }) { "Certificate chain under alias $alias is not X.509" } + return uncheckedCast(certArray.asList()) + } + + fun getCertificateAndKeyPair(alias: String, keyPassword: String = storePassword): CertificateAndKeyPair { + val cert = getCertificate(alias) + val publicKey = Crypto.toSupportedPublicKey(cert.publicKey) + return CertificateAndKeyPair(cert, KeyPair(publicKey, getPrivateKey(alias, keyPassword))) + } + + fun getPrivateKey(alias: String, keyPassword: String = storePassword): PrivateKey { + return internal.getSupportedKey(alias, keyPassword) + } + + fun setPrivateKey(alias: String, key: PrivateKey, certificates: List, keyPassword: String = storePassword) { + checkWritableToFile() + internal.setKeyEntry(alias, key, keyPassword.toCharArray(), certificates.toTypedArray()) + } + + fun setCertificate(alias: String, certificate: X509Certificate) { + checkWritableToFile() + internal.setCertificateEntry(alias, certificate) + } + + fun save() { + internal.save(checkWritableToFile(), storePassword) + } + + fun update(action: X509KeyStore.() -> Unit) { + checkWritableToFile() + action(this) + save() + } + + private fun checkWritableToFile(): Path { + return keyStoreFile ?: throw IllegalStateException("This key store cannot be written to") + } +} diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/X509Utilities.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/X509Utilities.kt index fb2e512d38..959fc83a21 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/X509Utilities.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/X509Utilities.kt @@ -7,8 +7,6 @@ import net.corda.core.crypto.random63BitValue import net.corda.core.internal.CertRole import net.corda.core.internal.reader import net.corda.core.internal.writer -import net.corda.core.identity.CordaX500Name -import net.corda.core.internal.* import net.corda.core.utilities.days import net.corda.core.utilities.millis import org.bouncycastle.asn1.* @@ -95,6 +93,7 @@ object X509Utilities { return createCertificate(CertificateType.ROOT_CA, subject, keyPair, subject, keyPair.public, window) } + // TODO Provide an overload which takes in a List or a CertPath @Throws(CertPathValidatorException::class) fun validateCertificateChain(trustedRoot: X509Certificate, vararg certificates: Certificate) { require(certificates.isNotEmpty()) { "Certificate path must contain at least one certificate" } @@ -287,10 +286,12 @@ class X509CertificateFactory { return delegate.generateCertificate(input) as X509Certificate } + // TODO X509Certificate fun generateCertPath(certificates: List): CertPath { return delegate.generateCertPath(certificates) } + // TODO X509Certificate fun generateCertPath(vararg certificates: Certificate): CertPath { return delegate.generateCertPath(certificates.asList()) } diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/crypto/X509UtilitiesTest.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/crypto/X509UtilitiesTest.kt index 512e0a096d..2f12970874 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/crypto/X509UtilitiesTest.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/crypto/X509UtilitiesTest.kt @@ -222,7 +222,8 @@ class X509UtilitiesTest { val clientSocketFactory = context.socketFactory val serverSocket = serverSocketFactory.createServerSocket(0) as SSLServerSocket // use 0 to get first free socket - val serverParams = SSLParameters(CIPHER_SUITES, arrayOf("TLSv1.2")) + val serverParams = SSLParameters(CIPHER_SUITES, + arrayOf("TLSv1.2")) serverParams.wantClientAuth = true serverParams.needClientAuth = true serverParams.endpointIdentificationAlgorithm = null // Reconfirm default no server name indication, use our own validator. @@ -230,7 +231,8 @@ class X509UtilitiesTest { serverSocket.useClientMode = false val clientSocket = clientSocketFactory.createSocket() as SSLSocket - val clientParams = SSLParameters(CIPHER_SUITES, arrayOf("TLSv1.2")) + val clientParams = SSLParameters(CIPHER_SUITES, + arrayOf("TLSv1.2")) clientParams.endpointIdentificationAlgorithm = null // Reconfirm default no server name indication, use our own validator. clientSocket.sslParameters = clientParams clientSocket.useClientMode = true @@ -267,7 +269,7 @@ class X509UtilitiesTest { val peerChain = clientSocket.session.peerCertificates val peerX500Principal = (peerChain[0] as X509Certificate).subjectX500Principal assertEquals(MEGA_CORP.name.x500Principal, peerX500Principal) - X509Utilities.validateCertificateChain(trustStore.getX509Certificate(X509Utilities.CORDA_ROOT_CA), *peerChain) + X509Utilities.validateCertificateChain(rootCa.certificate, *peerChain) val output = DataOutputStream(clientSocket.outputStream) output.writeUTF("Hello World") var timeout = 0 diff --git a/node/src/integration-test/kotlin/net/corda/node/NodeKeystoreCheckTest.kt b/node/src/integration-test/kotlin/net/corda/node/NodeKeystoreCheckTest.kt index f36d543cc2..af572b8296 100644 --- a/node/src/integration-test/kotlin/net/corda/node/NodeKeystoreCheckTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/NodeKeystoreCheckTest.kt @@ -5,7 +5,8 @@ import net.corda.core.internal.div import net.corda.core.utilities.getOrThrow import net.corda.node.services.config.configureDevKeyAndTrustStores import net.corda.nodeapi.internal.config.SSLConfiguration -import net.corda.nodeapi.internal.crypto.* +import net.corda.nodeapi.internal.crypto.CertificateType +import net.corda.nodeapi.internal.crypto.X509Utilities import net.corda.testing.core.ALICE_NAME import net.corda.testing.driver.driver import org.assertj.core.api.Assertions.assertThatThrownBy @@ -45,15 +46,14 @@ class NodeKeystoreCheckTest { node.stop() // Fiddle with node keystore. - val keystore = loadKeyStore(config.nodeKeystore, config.keyStorePassword) - - // Self signed root - val badRootKeyPair = Crypto.generateKeyPair() - val badRoot = X509Utilities.createSelfSignedCACertificate(X500Principal("O=Bad Root,L=Lodnon,C=GB"), badRootKeyPair) - val nodeCA = keystore.getCertificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA, config.keyStorePassword) - val badNodeCACert = X509Utilities.createCertificate(CertificateType.NODE_CA, badRoot, badRootKeyPair, ALICE_NAME.x500Principal, nodeCA.keyPair.public) - keystore.setKeyEntry(X509Utilities.CORDA_CLIENT_CA, nodeCA.keyPair.private, config.keyStorePassword.toCharArray(), arrayOf(badNodeCACert, badRoot)) - keystore.save(config.nodeKeystore, config.keyStorePassword) + config.loadNodeKeyStore().update { + // Self signed root + val badRootKeyPair = Crypto.generateKeyPair() + val badRoot = X509Utilities.createSelfSignedCACertificate(X500Principal("O=Bad Root,L=Lodnon,C=GB"), badRootKeyPair) + val nodeCA = getCertificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA) + val badNodeCACert = X509Utilities.createCertificate(CertificateType.NODE_CA, badRoot, badRootKeyPair, ALICE_NAME.x500Principal, nodeCA.keyPair.public) + setPrivateKey(X509Utilities.CORDA_CLIENT_CA, nodeCA.keyPair.private, listOf(badNodeCACert, badRoot)) + } assertThatThrownBy { startNode(providedName = ALICE_NAME, customOverrides = mapOf("devMode" to false)).getOrThrow() diff --git a/node/src/integration-test/kotlin/net/corda/node/amqp/AMQPBridgeTest.kt b/node/src/integration-test/kotlin/net/corda/node/amqp/AMQPBridgeTest.kt index dea8798d4a..484c84687a 100644 --- a/node/src/integration-test/kotlin/net/corda/node/amqp/AMQPBridgeTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/amqp/AMQPBridgeTest.kt @@ -15,7 +15,6 @@ import net.corda.node.services.config.* import net.corda.node.services.messaging.ArtemisMessagingClient import net.corda.node.services.messaging.ArtemisMessagingServer import net.corda.nodeapi.internal.ArtemisMessagingComponent -import net.corda.nodeapi.internal.crypto.loadKeyStore import net.corda.testing.core.* import net.corda.testing.internal.rigorousMock import org.apache.activemq.artemis.api.core.Message.HDR_DUPLICATE_DETECTION_ID @@ -229,16 +228,14 @@ class AMQPBridgeTest { } serverConfig.configureWithDevSSLCertificate() - val serverTruststore = loadKeyStore(serverConfig.trustStoreFile, serverConfig.trustStorePassword) - val serverKeystore = loadKeyStore(serverConfig.sslKeystore, serverConfig.keyStorePassword) - val amqpServer = AMQPServer("0.0.0.0", + return AMQPServer("0.0.0.0", amqpPort, ArtemisMessagingComponent.PEER_USER, ArtemisMessagingComponent.PEER_USER, - serverKeystore, + serverConfig.loadSslKeyStore().internal, serverConfig.keyStorePassword, - serverTruststore, - trace = true) - return amqpServer + serverConfig.loadTrustStore().internal, + trace = true + ) } } \ No newline at end of file diff --git a/node/src/integration-test/kotlin/net/corda/node/amqp/ProtonWrapperTests.kt b/node/src/integration-test/kotlin/net/corda/node/amqp/ProtonWrapperTests.kt index 5d198758c8..13206aad4d 100644 --- a/node/src/integration-test/kotlin/net/corda/node/amqp/ProtonWrapperTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/amqp/ProtonWrapperTests.kt @@ -21,7 +21,6 @@ import net.corda.node.services.messaging.ArtemisMessagingClient import net.corda.node.services.messaging.ArtemisMessagingServer import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.P2P_PREFIX import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.PEER_USER -import net.corda.nodeapi.internal.crypto.loadKeyStore import net.corda.testing.core.* import net.corda.testing.internal.rigorousMock import org.apache.activemq.artemis.api.core.RoutingType @@ -253,8 +252,8 @@ class ProtonWrapperTests { } clientConfig.configureWithDevSSLCertificate() - val clientTruststore = loadKeyStore(clientConfig.trustStoreFile, clientConfig.trustStorePassword) - val clientKeystore = loadKeyStore(clientConfig.sslKeystore, clientConfig.keyStorePassword) + val clientTruststore = clientConfig.loadTrustStore().internal + val clientKeystore = clientConfig.loadSslKeyStore().internal return AMQPClient( listOf(NetworkHostAndPort("localhost", serverPort), NetworkHostAndPort("localhost", serverPort2), @@ -276,8 +275,8 @@ class ProtonWrapperTests { } clientConfig.configureWithDevSSLCertificate() - val clientTruststore = loadKeyStore(clientConfig.trustStoreFile, clientConfig.trustStorePassword) - val clientKeystore = loadKeyStore(clientConfig.sslKeystore, clientConfig.keyStorePassword) + val clientTruststore = clientConfig.loadTrustStore().internal + val clientKeystore = clientConfig.loadSslKeyStore().internal return AMQPClient( listOf(NetworkHostAndPort("localhost", serverPort)), setOf(ALICE_NAME), @@ -297,8 +296,8 @@ class ProtonWrapperTests { } serverConfig.configureWithDevSSLCertificate() - val serverTruststore = loadKeyStore(serverConfig.trustStoreFile, serverConfig.trustStorePassword) - val serverKeystore = loadKeyStore(serverConfig.sslKeystore, serverConfig.keyStorePassword) + val serverTruststore = serverConfig.loadTrustStore().internal + val serverKeystore = serverConfig.loadSslKeyStore().internal return AMQPServer( "0.0.0.0", port, diff --git a/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityAsNodeTest.kt b/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityAsNodeTest.kt index bc6d9128d3..d24b4b1811 100644 --- a/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityAsNodeTest.kt +++ b/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityAsNodeTest.kt @@ -12,7 +12,8 @@ import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.PEER_USER import net.corda.nodeapi.internal.DEV_INTERMEDIATE_CA import net.corda.nodeapi.internal.DEV_ROOT_CA import net.corda.nodeapi.internal.config.SSLConfiguration -import net.corda.nodeapi.internal.crypto.* +import net.corda.nodeapi.internal.crypto.CertificateType +import net.corda.nodeapi.internal.crypto.X509Utilities import org.apache.activemq.artemis.api.config.ActiveMQDefaultConfiguration import org.apache.activemq.artemis.api.core.ActiveMQClusterSecurityException import org.apache.activemq.artemis.api.core.ActiveMQNotConnectedException @@ -115,22 +116,19 @@ class MQSecurityAsNodeTest : P2PMQSecurityTest() { CordaX500Name("MiniCorp", "London", "GB").x500Principal, tlsKeyPair.public) - val keyPass = keyStorePassword.toCharArray() - val clientCAKeystore = loadOrCreateKeyStore(nodeKeystore, keyStorePassword) - clientCAKeystore.addOrReplaceKey( - X509Utilities.CORDA_CLIENT_CA, - clientKeyPair.private, - keyPass, - arrayOf(clientCACert, DEV_INTERMEDIATE_CA.certificate, DEV_ROOT_CA.certificate)) - clientCAKeystore.save(nodeKeystore, keyStorePassword) + loadNodeKeyStore(createNew = true).update { + setPrivateKey( + X509Utilities.CORDA_CLIENT_CA, + clientKeyPair.private, + listOf(clientCACert, DEV_INTERMEDIATE_CA.certificate, DEV_ROOT_CA.certificate)) + } - val tlsKeystore = loadOrCreateKeyStore(sslKeystore, keyStorePassword) - tlsKeystore.addOrReplaceKey( - X509Utilities.CORDA_CLIENT_TLS, - tlsKeyPair.private, - keyPass, - arrayOf(clientTLSCert, clientCACert, DEV_INTERMEDIATE_CA.certificate, DEV_ROOT_CA.certificate)) - tlsKeystore.save(sslKeystore, keyStorePassword) + loadSslKeyStore(createNew = true).update { + setPrivateKey( + X509Utilities.CORDA_CLIENT_TLS, + tlsKeyPair.private, + listOf(clientTLSCert, clientCACert, DEV_INTERMEDIATE_CA.certificate, DEV_ROOT_CA.certificate)) + } } } 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 5affcadaf8..df4a449772 100644 --- a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt @@ -36,7 +36,10 @@ import net.corda.node.services.ContractUpgradeHandler import net.corda.node.services.FinalityHandler import net.corda.node.services.NotaryChangeHandler import net.corda.node.services.api.* -import net.corda.node.services.config.* +import net.corda.node.services.config.BFTSMaRtConfiguration +import net.corda.node.services.config.NodeConfiguration +import net.corda.node.services.config.NotaryConfig +import net.corda.node.services.config.configureWithDevSSLCertificate import net.corda.node.services.events.NodeSchedulerService import net.corda.node.services.events.ScheduledActivityObserver import net.corda.node.services.identity.PersistentIdentityService @@ -55,16 +58,15 @@ import net.corda.node.shell.InteractiveShell import net.corda.node.utilities.AffinityExecutor import net.corda.nodeapi.internal.DevIdentityGenerator import net.corda.nodeapi.internal.SignedNodeInfo -import net.corda.nodeapi.internal.crypto.KeyStoreWrapper import net.corda.nodeapi.internal.crypto.X509CertificateFactory import net.corda.nodeapi.internal.crypto.X509Utilities -import net.corda.nodeapi.internal.crypto.loadKeyStore import net.corda.nodeapi.internal.network.NETWORK_PARAMS_FILE_NAME import net.corda.nodeapi.internal.network.NetworkParameters import net.corda.nodeapi.internal.network.verifiedNetworkMapCert import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.DatabaseConfig import net.corda.nodeapi.internal.persistence.HibernateConfiguration +import net.corda.nodeapi.internal.storeLegalIdentity import org.apache.activemq.artemis.utils.ReusableLatch import org.hibernate.type.descriptor.java.JavaTypeDescriptorRegistry import org.slf4j.Logger @@ -140,7 +142,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration, private val _nodeReadyFuture = openFuture() protected var networkMapClient: NetworkMapClient? = null - lateinit var securityManager: RPCSecurityManager get + lateinit var securityManager: RPCSecurityManager /** Completes once the node has successfully registered with the network map service * or has loaded network map data from local database */ @@ -568,9 +570,9 @@ abstract class AbstractNode(val configuration: NodeConfiguration, private fun validateKeystore() { val containCorrectKeys = try { // This will throw IOException if key file not found or KeyStoreException if keystore password is incorrect. - val sslKeystore = loadKeyStore(configuration.sslKeystore, configuration.keyStorePassword) - val identitiesKeystore = loadKeyStore(configuration.nodeKeystore, configuration.keyStorePassword) - sslKeystore.containsAlias(X509Utilities.CORDA_CLIENT_TLS) && identitiesKeystore.containsAlias(X509Utilities.CORDA_CLIENT_CA) + val sslKeystore = configuration.loadSslKeyStore() + val identitiesKeystore = configuration.loadNodeKeyStore() + X509Utilities.CORDA_CLIENT_TLS in sslKeystore && X509Utilities.CORDA_CLIENT_CA in identitiesKeystore } catch (e: KeyStoreException) { log.warn("Certificate key store found but key store password does not match configuration.") false @@ -585,15 +587,12 @@ abstract class AbstractNode(val configuration: NodeConfiguration, } // Check all cert path chain to the trusted root - val sslKeystore = loadKeyStore(configuration.sslKeystore, configuration.keyStorePassword) - val identitiesKeystore = loadKeyStore(configuration.nodeKeystore, configuration.keyStorePassword) - val trustStore = loadKeyStore(configuration.trustStoreFile, configuration.trustStorePassword) - val sslRoot = sslKeystore.getCertificateChain(X509Utilities.CORDA_CLIENT_TLS).last() - val clientCARoot = identitiesKeystore.getCertificateChain(X509Utilities.CORDA_CLIENT_CA).last() - val trustRoot = trustStore.getCertificate(X509Utilities.CORDA_ROOT_CA) + val sslCertChainRoot = configuration.loadSslKeyStore().getCertificateChain(X509Utilities.CORDA_CLIENT_TLS).last() + val nodeCaCertChainRoot = configuration.loadNodeKeyStore().getCertificateChain(X509Utilities.CORDA_CLIENT_CA).last() + val trustRoot = configuration.loadTrustStore().getCertificate(X509Utilities.CORDA_ROOT_CA) - require(sslRoot == trustRoot) { "TLS certificate must chain to the trusted root." } - require(clientCARoot == trustRoot) { "Client CA certificate must chain to the trusted root." } + require(sslCertChainRoot == trustRoot) { "TLS certificate must chain to the trusted root." } + require(nodeCaCertChainRoot == trustRoot) { "Client CA certificate must chain to the trusted root." } } // Specific class so that MockNode can catch it. @@ -684,12 +683,9 @@ abstract class AbstractNode(val configuration: NodeConfiguration, } private fun makeIdentityService(identityCert: X509Certificate): PersistentIdentityService { - val trustStore = KeyStoreWrapper(configuration.trustStoreFile, configuration.trustStorePassword) - val caKeyStore = KeyStoreWrapper(configuration.nodeKeystore, configuration.keyStorePassword) - val trustRoot = trustStore.getX509Certificate(X509Utilities.CORDA_ROOT_CA) - val clientCa = caKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA) - val caCertificates = arrayOf(identityCert, clientCa.certificate) - return PersistentIdentityService(trustRoot, *caCertificates) + val trustRoot = configuration.loadTrustStore().getCertificate(X509Utilities.CORDA_ROOT_CA) + val nodeCa = configuration.loadNodeKeyStore().getCertificate(X509Utilities.CORDA_CLIENT_CA) + return PersistentIdentityService(trustRoot, identityCert, nodeCa) } protected abstract fun makeTransactionVerifierService(): TransactionVerifierService @@ -713,7 +709,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration, protected abstract fun startMessagingService(rpcOps: RPCOps) private fun obtainIdentity(notaryConfig: NotaryConfig?): Pair { - val keyStore = KeyStoreWrapper(configuration.nodeKeystore, configuration.keyStorePassword) + val keyStore = configuration.loadNodeKeyStore() val (id, singleName) = if (notaryConfig == null || !notaryConfig.isClusterConfig) { // Node's main identity or if it's a single node notary @@ -725,19 +721,20 @@ abstract class AbstractNode(val configuration: NodeConfiguration, // TODO: Integrate with Key management service? val privateKeyAlias = "$id-private-key" - if (!keyStore.containsAlias(privateKeyAlias)) { + if (privateKeyAlias !in keyStore) { singleName ?: throw IllegalArgumentException( - "Unable to find in the key store the identity of the distributed notary ($id) the node is part of") - // TODO: Remove use of [IdentityGenerator.generateToDisk]. + "Unable to find in the key store the identity of the distributed notary the node is part of") log.info("$privateKeyAlias not found in key store ${configuration.nodeKeystore}, generating fresh key!") - keyStore.storeLegalIdentity(singleName, privateKeyAlias, generateKeyPair()) + // TODO This check shouldn't be needed + check(singleName == configuration.myLegalName) + keyStore.storeLegalIdentity(privateKeyAlias, generateKeyPair()) } val (x509Cert, keyPair) = keyStore.getCertificateAndKeyPair(privateKeyAlias) // TODO: Use configuration to indicate composite key should be used instead of public key for the identity. val compositeKeyAlias = "$id-composite-key" - val certificates = if (keyStore.containsAlias(compositeKeyAlias)) { + val certificates = if (compositeKeyAlias in keyStore) { // Use composite key instead if it exists val certificate = keyStore.getCertificate(compositeKeyAlias) // We have to create the certificate chain for the composite key manually, this is because we don't have a keystore @@ -747,12 +744,11 @@ abstract class AbstractNode(val configuration: NodeConfiguration, } else { keyStore.getCertificateChain(privateKeyAlias).let { check(it[0] == x509Cert) { "Certificates from key store do not line up!" } - it.asList() + it } } - val nodeCert = certificates[0] as? X509Certificate ?: throw ConfigurationException("Node certificate must be an X.509 certificate") - val subject = CordaX500Name.build(nodeCert.subjectX500Principal) + val subject = CordaX500Name.build(certificates[0].subjectX500Principal) // TODO Include the name of the distributed notary, which the node is part of, in the notary config so that we // can cross-check the identity we get from the key store if (singleName != null && subject != singleName) { diff --git a/node/src/main/kotlin/net/corda/node/services/config/ConfigUtilities.kt b/node/src/main/kotlin/net/corda/node/services/config/ConfigUtilities.kt index be5428675a..0b2ba8360b 100644 --- a/node/src/main/kotlin/net/corda/node/services/config/ConfigUtilities.kt +++ b/node/src/main/kotlin/net/corda/node/services/config/ConfigUtilities.kt @@ -10,7 +10,9 @@ import net.corda.core.internal.div import net.corda.core.internal.exists import net.corda.nodeapi.internal.config.SSLConfiguration import net.corda.nodeapi.internal.createDevKeyStores -import net.corda.nodeapi.internal.crypto.* +import net.corda.nodeapi.internal.crypto.X509KeyStore +import net.corda.nodeapi.internal.crypto.loadKeyStore +import net.corda.nodeapi.internal.crypto.save import org.slf4j.LoggerFactory import java.nio.file.Path @@ -52,22 +54,21 @@ fun SSLConfiguration.configureDevKeyAndTrustStores(myLegalName: CordaX500Name) { loadKeyStore(javaClass.classLoader.getResourceAsStream("certificates/cordatruststore.jks"), "trustpass").save(trustStoreFile, trustStorePassword) } if (!sslKeystore.exists() || !nodeKeystore.exists()) { - createDevKeyStores(myLegalName) + val (nodeKeyStore) = createDevKeyStores(myLegalName) // Move distributed service composite key (generated by IdentityGenerator.generateToDisk) to keystore if exists. val distributedServiceKeystore = certificatesDirectory / "distributedService.jks" if (distributedServiceKeystore.exists()) { - val serviceKeystore = loadKeyStore(distributedServiceKeystore, "cordacadevpass") - val cordaNodeKeystore = loadKeyStore(nodeKeystore, keyStorePassword) - - serviceKeystore.aliases().iterator().forEach { - if (serviceKeystore.isKeyEntry(it)) { - cordaNodeKeystore.setKeyEntry(it, serviceKeystore.getKey(it, "cordacadevkeypass".toCharArray()), keyStorePassword.toCharArray(), serviceKeystore.getCertificateChain(it)) - } else { - cordaNodeKeystore.setCertificateEntry(it, serviceKeystore.getCertificate(it)) + val serviceKeystore = X509KeyStore.fromFile(distributedServiceKeystore, "cordacadevpass") + nodeKeyStore.update { + serviceKeystore.aliases().forEach { + if (serviceKeystore.internal.isKeyEntry(it)) { + setPrivateKey(it, serviceKeystore.getPrivateKey(it, "cordacadevkeypass"), serviceKeystore.getCertificateChain(it)) + } else { + setCertificate(it, serviceKeystore.getCertificate(it)) + } } } - cordaNodeKeystore.save(nodeKeystore, keyStorePassword) } } } diff --git a/node/src/main/kotlin/net/corda/node/services/messaging/AMQPBridgeManager.kt b/node/src/main/kotlin/net/corda/node/services/messaging/AMQPBridgeManager.kt index 93b1137a00..1a0417d1ba 100644 --- a/node/src/main/kotlin/net/corda/node/services/messaging/AMQPBridgeManager.kt +++ b/node/src/main/kotlin/net/corda/node/services/messaging/AMQPBridgeManager.kt @@ -14,7 +14,6 @@ import net.corda.nodeapi.internal.ArtemisMessagingComponent import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.NODE_USER import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.PEER_USER import net.corda.nodeapi.internal.ArtemisMessagingComponent.RemoteInboxAddress.Companion.translateLocalQueueToInboxAddress -import net.corda.nodeapi.internal.crypto.loadKeyStore import org.apache.activemq.artemis.api.core.SimpleString import org.apache.activemq.artemis.api.core.client.ActiveMQClient.DEFAULT_ACK_BATCH_SIZE import org.apache.activemq.artemis.api.core.client.ClientConsumer @@ -38,9 +37,9 @@ internal class AMQPBridgeManager(val config: NodeConfiguration, val p2pAddress: private val lock = ReentrantLock() private val bridgeNameToBridgeMap = mutableMapOf() private var sharedEventLoopGroup: EventLoopGroup? = null - private val keyStore = loadKeyStore(config.sslKeystore, config.keyStorePassword) + private val keyStore = config.loadSslKeyStore().internal private val keyStorePrivateKeyPassword: String = config.keyStorePassword - private val trustStore = loadKeyStore(config.trustStoreFile, config.trustStorePassword) + private val trustStore = config.loadTrustStore().internal private var artemis: ArtemisMessagingClient? = null companion object { diff --git a/node/src/main/kotlin/net/corda/node/services/messaging/ArtemisMessagingServer.kt b/node/src/main/kotlin/net/corda/node/services/messaging/ArtemisMessagingServer.kt index b568685c14..cb6609d2ee 100644 --- a/node/src/main/kotlin/net/corda/node/services/messaging/ArtemisMessagingServer.kt +++ b/node/src/main/kotlin/net/corda/node/services/messaging/ArtemisMessagingServer.kt @@ -34,6 +34,8 @@ import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.P2P_PREFIX import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.PEERS_PREFIX import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.PEER_USER import net.corda.nodeapi.internal.ArtemisMessagingComponent.NodeAddress +import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_CLIENT_TLS +import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_ROOT_CA import net.corda.nodeapi.internal.crypto.loadKeyStore import net.corda.nodeapi.internal.requireOnDefaultFileSystem import org.apache.activemq.artemis.api.core.SimpleString @@ -205,8 +207,8 @@ class ArtemisMessagingServer(private val config: NodeConfiguration, @Throws(IOException::class, KeyStoreException::class) private fun createArtemisSecurityManager(loginListener: LoginListener): ActiveMQJAASSecurityManager { - val keyStore = loadKeyStore(config.sslKeystore, config.keyStorePassword) - val trustStore = loadKeyStore(config.trustStoreFile, config.trustStorePassword) + val keyStore = config.loadSslKeyStore().internal + val trustStore = config.loadTrustStore().internal val defaultCertPolicies = mapOf( PEER_ROLE to CertificateChainCheckPolicy.RootMustMatch, diff --git a/node/src/main/kotlin/net/corda/node/services/messaging/CoreBridgeManager.kt b/node/src/main/kotlin/net/corda/node/services/messaging/CoreBridgeManager.kt index b821d13cff..74e4a711c6 100644 --- a/node/src/main/kotlin/net/corda/node/services/messaging/CoreBridgeManager.kt +++ b/node/src/main/kotlin/net/corda/node/services/messaging/CoreBridgeManager.kt @@ -23,6 +23,7 @@ import org.apache.activemq.artemis.core.server.ActiveMQServer import org.apache.activemq.artemis.core.server.cluster.Transformer import org.apache.activemq.artemis.spi.core.remoting.* import org.apache.activemq.artemis.utils.ConfigurationHelper +import java.security.cert.X509Certificate import java.time.Duration import java.util.concurrent.Executor import java.util.concurrent.ScheduledExecutorService @@ -161,7 +162,7 @@ class VerifyingNettyConnectorFactory : NettyConnectorFactory() { "Peer has wrong subject name in the certificate - expected $expectedLegalNames but got $peerCertificateName. This is either a fatal " + "misconfiguration by the remote peer or an SSL man-in-the-middle attack!" } - X509Utilities.validateCertificateChain(session.localCertificates.last() as java.security.cert.X509Certificate, *session.peerCertificates) + X509Utilities.validateCertificateChain(session.localCertificates.last() as X509Certificate, *session.peerCertificates) } catch (e: IllegalArgumentException) { connection.close() log.error(e.message) diff --git a/node/src/main/kotlin/net/corda/node/services/messaging/RPCMessagingClient.kt b/node/src/main/kotlin/net/corda/node/services/messaging/RPCMessagingClient.kt index 3c5f4127a1..874c030641 100644 --- a/node/src/main/kotlin/net/corda/node/services/messaging/RPCMessagingClient.kt +++ b/node/src/main/kotlin/net/corda/node/services/messaging/RPCMessagingClient.kt @@ -8,8 +8,6 @@ import net.corda.node.internal.security.RPCSecurityManager import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.NODE_USER import net.corda.nodeapi.internal.config.SSLConfiguration import net.corda.nodeapi.internal.crypto.X509Utilities -import net.corda.nodeapi.internal.crypto.getX509Certificate -import net.corda.nodeapi.internal.crypto.loadKeyStore import org.apache.activemq.artemis.api.core.management.ActiveMQServerControl class RPCMessagingClient(private val config: SSLConfiguration, serverAddress: NetworkHostAndPort, maxMessageSize: Int) : SingletonSerializeAsToken(), AutoCloseable { @@ -18,7 +16,7 @@ class RPCMessagingClient(private val config: SSLConfiguration, serverAddress: Ne fun start(rpcOps: RPCOps, securityManager: RPCSecurityManager) = synchronized(this) { val locator = artemis.start().sessionFactory.serverLocator - val myCert = loadKeyStore(config.sslKeystore, config.keyStorePassword).getX509Certificate(X509Utilities.CORDA_CLIENT_TLS) + val myCert = config.loadSslKeyStore().getCertificate(X509Utilities.CORDA_CLIENT_TLS) rpcServer = RPCServer(rpcOps, NODE_USER, NODE_USER, locator, securityManager, CordaX500Name.build(myCert.subjectX500Principal)) } diff --git a/node/src/main/kotlin/net/corda/node/utilities/registration/HTTPNetworkRegistrationService.kt b/node/src/main/kotlin/net/corda/node/utilities/registration/HTTPNetworkRegistrationService.kt index 49dde51b6d..201cab875d 100644 --- a/node/src/main/kotlin/net/corda/node/utilities/registration/HTTPNetworkRegistrationService.kt +++ b/node/src/main/kotlin/net/corda/node/utilities/registration/HTTPNetworkRegistrationService.kt @@ -9,7 +9,7 @@ import java.io.IOException import java.net.HttpURLConnection import java.net.HttpURLConnection.* import java.net.URL -import java.security.cert.Certificate +import java.security.cert.X509Certificate import java.util.* import java.util.zip.ZipInputStream @@ -22,19 +22,19 @@ class HTTPNetworkRegistrationService(compatibilityZoneURL: URL) : NetworkRegistr } @Throws(CertificateRequestException::class) - override fun retrieveCertificates(requestId: String): Array? { + override fun retrieveCertificates(requestId: String): List? { // Poll server to download the signed certificate once request has been approved. val conn = URL("$registrationURL/$requestId").openHttpConnection() conn.requestMethod = "GET" return when (conn.responseCode) { HTTP_OK -> ZipInputStream(conn.inputStream).use { - val certificates = ArrayList() + val certificates = ArrayList() val factory = X509CertificateFactory() while (it.nextEntry != null) { certificates += factory.generateCertificate(it) } - certificates.toTypedArray() + certificates } HTTP_NO_CONTENT -> null HTTP_UNAUTHORIZED -> throw CertificateRequestException("Certificate signing request has been rejected: ${conn.errorMessage}") diff --git a/node/src/main/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelper.kt b/node/src/main/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelper.kt index 090eef335c..ac49461b05 100644 --- a/node/src/main/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelper.kt +++ b/node/src/main/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelper.kt @@ -5,7 +5,8 @@ import net.corda.core.identity.CordaX500Name import net.corda.core.internal.* import net.corda.core.utilities.seconds import net.corda.node.services.config.NodeConfiguration -import net.corda.nodeapi.internal.crypto.* +import net.corda.nodeapi.internal.crypto.CertificateType +import net.corda.nodeapi.internal.crypto.X509Utilities import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_CLIENT_CA import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_CLIENT_TLS import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_ROOT_CA @@ -14,7 +15,6 @@ import org.bouncycastle.util.io.pem.PemObject import java.io.StringWriter import java.security.KeyPair import java.security.KeyStore -import java.security.cert.Certificate import java.security.cert.X509Certificate /** @@ -28,10 +28,8 @@ class NetworkRegistrationHelper(private val config: NodeConfiguration, private v } private val requestIdStore = config.certificatesDirectory / "certificate-request-id.txt" - private val keystorePassword = config.keyStorePassword // TODO: Use different password for private key. private val privateKeyPassword = config.keyStorePassword - private val trustStore: KeyStore private val rootCert: X509Certificate init { @@ -39,8 +37,7 @@ class NetworkRegistrationHelper(private val config: NodeConfiguration, private v "${config.trustStoreFile} does not exist. This file must contain the root CA cert of your compatibility zone. " + "Please contact your CZ operator." } - trustStore = loadKeyStore(config.trustStoreFile, config.trustStorePassword) - val rootCert = trustStore.getCertificate(CORDA_ROOT_CA) + val rootCert = config.loadTrustStore().internal.getCertificate(CORDA_ROOT_CA) require(rootCert != null) { "${config.trustStoreFile} does not contain a certificate with the key $CORDA_ROOT_CA." + "This file must contain the root CA cert of your compatibility zone. " + @@ -62,24 +59,23 @@ class NetworkRegistrationHelper(private val config: NodeConfiguration, private v */ fun buildKeystore() { config.certificatesDirectory.createDirectories() - val nodeKeyStore = loadOrCreateKeyStore(config.nodeKeystore, keystorePassword) - if (nodeKeyStore.containsAlias(CORDA_CLIENT_CA)) { + val nodeKeyStore = config.loadNodeKeyStore(createNew = true) + if (CORDA_CLIENT_CA in nodeKeyStore) { println("Certificate already exists, Corda node will now terminate...") return } // Create or load self signed keypair from the key store. // We use the self sign certificate to store the key temporarily in the keystore while waiting for the request approval. - if (!nodeKeyStore.containsAlias(SELF_SIGNED_PRIVATE_KEY)) { + if (SELF_SIGNED_PRIVATE_KEY !in nodeKeyStore) { val keyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) val selfSignCert = X509Utilities.createSelfSignedCACertificate(config.myLegalName.x500Principal, keyPair) // Save to the key store. - nodeKeyStore.addOrReplaceKey(SELF_SIGNED_PRIVATE_KEY, keyPair.private, privateKeyPassword.toCharArray(), - arrayOf(selfSignCert)) - nodeKeyStore.save(config.nodeKeystore, keystorePassword) + nodeKeyStore.setPrivateKey(SELF_SIGNED_PRIVATE_KEY, keyPair.private, listOf(selfSignCert), keyPassword = privateKeyPassword) + nodeKeyStore.save() } - val keyPair = nodeKeyStore.getKeyPair(SELF_SIGNED_PRIVATE_KEY, privateKeyPassword) + val keyPair = nodeKeyStore.getCertificateAndKeyPair(SELF_SIGNED_PRIVATE_KEY, privateKeyPassword).keyPair val requestId = submitOrResumeCertificateSigningRequest(keyPair) val certificates = try { @@ -92,7 +88,7 @@ class NetworkRegistrationHelper(private val config: NodeConfiguration, private v throw certificateRequestException } - val nodeCaCert = certificates[0] as X509Certificate + val nodeCaCert = certificates[0] val nodeCaSubject = try { CordaX500Name.build(nodeCaCert.subjectX500Principal) @@ -113,26 +109,26 @@ class NetworkRegistrationHelper(private val config: NodeConfiguration, private v } println("Checking root of the certificate path is what we expect.") - X509Utilities.validateCertificateChain(rootCert, *certificates) + X509Utilities.validateCertificateChain(rootCert, *certificates.toTypedArray()) println("Certificate signing request approved, storing private key with the certificate chain.") // Save private key and certificate chain to the key store. - nodeKeyStore.addOrReplaceKey(CORDA_CLIENT_CA, keyPair.private, privateKeyPassword.toCharArray(), certificates) - nodeKeyStore.deleteEntry(SELF_SIGNED_PRIVATE_KEY) - nodeKeyStore.save(config.nodeKeystore, keystorePassword) + nodeKeyStore.setPrivateKey(CORDA_CLIENT_CA, keyPair.private, certificates, keyPassword = privateKeyPassword) + nodeKeyStore.internal.deleteEntry(SELF_SIGNED_PRIVATE_KEY) + nodeKeyStore.save() println("Node private key and certificate stored in ${config.nodeKeystore}.") - println("Generating SSL certificate for node messaging service.") - val sslKeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) - val sslCert = X509Utilities.createCertificate( - CertificateType.TLS, - nodeCaCert, - keyPair, - config.myLegalName.x500Principal, - sslKeyPair.public) - val sslKeyStore = loadOrCreateKeyStore(config.sslKeystore, keystorePassword) - sslKeyStore.addOrReplaceKey(CORDA_CLIENT_TLS, sslKeyPair.private, privateKeyPassword.toCharArray(), arrayOf(sslCert, *certificates)) - sslKeyStore.save(config.sslKeystore, config.keyStorePassword) + config.loadSslKeyStore(createNew = true).update { + println("Generating SSL certificate for node messaging service.") + val sslKeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) + val sslCert = X509Utilities.createCertificate( + CertificateType.TLS, + nodeCaCert, + keyPair, + config.myLegalName.x500Principal, + sslKeyPair.public) + setPrivateKey(CORDA_CLIENT_TLS, sslKeyPair.private, listOf(sslCert) + certificates) + } println("SSL private key and certificate stored in ${config.sslKeystore}.") // All done, clean up temp files. @@ -145,7 +141,7 @@ class NetworkRegistrationHelper(private val config: NodeConfiguration, private v * @param requestId Certificate signing request ID. * @return Map of certificate chain. */ - private fun pollServerForCertificates(requestId: String): Array { + private fun pollServerForCertificates(requestId: String): List { println("Start polling server for certificate signing approval.") // Poll server to download the signed certificate once request has been approved. var certificates = certService.retrieveCertificates(requestId) diff --git a/node/src/main/kotlin/net/corda/node/utilities/registration/NetworkRegistrationService.kt b/node/src/main/kotlin/net/corda/node/utilities/registration/NetworkRegistrationService.kt index ffe8bece0f..beea4635d5 100644 --- a/node/src/main/kotlin/net/corda/node/utilities/registration/NetworkRegistrationService.kt +++ b/node/src/main/kotlin/net/corda/node/utilities/registration/NetworkRegistrationService.kt @@ -3,7 +3,7 @@ package net.corda.node.utilities.registration import net.corda.core.CordaException import net.corda.core.serialization.CordaSerializable import org.bouncycastle.pkcs.PKCS10CertificationRequest -import java.security.cert.Certificate +import java.security.cert.X509Certificate interface NetworkRegistrationService { /** Submits a CSR to the signing service and returns an opaque request ID. */ @@ -11,7 +11,7 @@ interface NetworkRegistrationService { /** Poll Certificate Signing Server for the request and returns a chain of certificates if request has been approved, null otherwise. */ @Throws(CertificateRequestException::class) - fun retrieveCertificates(requestId: String): Array? + fun retrieveCertificates(requestId: String): List? } @CordaSerializable diff --git a/node/src/test/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelperTest.kt b/node/src/test/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelperTest.kt index 4ce5df1eb4..b2788fcd25 100644 --- a/node/src/test/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelperTest.kt +++ b/node/src/test/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelperTest.kt @@ -12,7 +12,8 @@ import net.corda.core.identity.CordaX500Name import net.corda.core.internal.createDirectories import net.corda.core.internal.x500Name import net.corda.node.services.config.NodeConfiguration -import net.corda.nodeapi.internal.crypto.* +import net.corda.nodeapi.internal.crypto.CertificateType +import net.corda.nodeapi.internal.crypto.X509Utilities import net.corda.testing.core.ALICE_NAME import net.corda.testing.internal.createDevIntermediateCaCertPath import net.corda.testing.internal.rigorousMock @@ -27,7 +28,6 @@ import java.security.cert.CertPathValidatorException import java.security.cert.X509Certificate import javax.security.auth.x500.X500Principal import kotlin.test.assertFalse -import kotlin.test.assertTrue class NetworkRegistrationHelperTest { private val fs = Jimfs.newFileSystem(unix()) @@ -65,34 +65,31 @@ class NetworkRegistrationHelperTest { saveTrustStoreWithRootCa(nodeCaCertPath.last()) createRegistrationHelper(nodeCaCertPath).buildKeystore() - val nodeKeystore = loadKeyStore(config.nodeKeystore, config.keyStorePassword) - val sslKeystore = loadKeyStore(config.sslKeystore, config.keyStorePassword) - val trustStore = loadKeyStore(config.trustStoreFile, config.trustStorePassword) + val nodeKeystore = config.loadNodeKeyStore() + val sslKeystore = config.loadSslKeyStore() + val trustStore = config.loadTrustStore() nodeKeystore.run { - assertTrue(containsAlias(X509Utilities.CORDA_CLIENT_CA)) - assertFalse(containsAlias(X509Utilities.CORDA_INTERMEDIATE_CA)) - assertFalse(containsAlias(X509Utilities.CORDA_ROOT_CA)) - assertFalse(containsAlias(X509Utilities.CORDA_CLIENT_TLS)) - assertThat(getCertificateChain(X509Utilities.CORDA_CLIENT_CA)).containsExactly(*nodeCaCertPath) + assertFalse(contains(X509Utilities.CORDA_INTERMEDIATE_CA)) + assertFalse(contains(X509Utilities.CORDA_ROOT_CA)) + assertFalse(contains(X509Utilities.CORDA_CLIENT_TLS)) + assertThat(getCertificateChain(X509Utilities.CORDA_CLIENT_CA)).containsExactlyElementsOf(nodeCaCertPath) } sslKeystore.run { - assertFalse(containsAlias(X509Utilities.CORDA_CLIENT_CA)) - assertFalse(containsAlias(X509Utilities.CORDA_INTERMEDIATE_CA)) - assertFalse(containsAlias(X509Utilities.CORDA_ROOT_CA)) - assertTrue(containsAlias(X509Utilities.CORDA_CLIENT_TLS)) + assertFalse(contains(X509Utilities.CORDA_CLIENT_CA)) + assertFalse(contains(X509Utilities.CORDA_INTERMEDIATE_CA)) + assertFalse(contains(X509Utilities.CORDA_ROOT_CA)) val nodeTlsCertChain = getCertificateChain(X509Utilities.CORDA_CLIENT_TLS) assertThat(nodeTlsCertChain).hasSize(4) // The TLS cert has the same subject as the node CA cert - assertThat(CordaX500Name.build((nodeTlsCertChain[0] as X509Certificate).subjectX500Principal)).isEqualTo(nodeLegalName) - assertThat(nodeTlsCertChain.drop(1)).containsExactly(*nodeCaCertPath) + assertThat(CordaX500Name.build(nodeTlsCertChain[0].subjectX500Principal)).isEqualTo(nodeLegalName) + assertThat(nodeTlsCertChain.drop(1)).containsExactlyElementsOf(nodeCaCertPath) } trustStore.run { - assertFalse(containsAlias(X509Utilities.CORDA_CLIENT_CA)) - assertFalse(containsAlias(X509Utilities.CORDA_INTERMEDIATE_CA)) - assertTrue(containsAlias(X509Utilities.CORDA_ROOT_CA)) + assertFalse(contains(X509Utilities.CORDA_CLIENT_CA)) + assertFalse(contains(X509Utilities.CORDA_INTERMEDIATE_CA)) assertThat(getCertificate(X509Utilities.CORDA_ROOT_CA)).isEqualTo(nodeCaCertPath.last()) } } @@ -139,7 +136,7 @@ class NetworkRegistrationHelperTest { } private fun createNodeCaCertPath(type: CertificateType = CertificateType.NODE_CA, - legalName: CordaX500Name = nodeLegalName): Array { + legalName: CordaX500Name = nodeLegalName): List { val (rootCa, intermediateCa) = createDevIntermediateCaCertPath() val keyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) val nameConstraints = NameConstraints(arrayOf(GeneralSubtree(GeneralName(GeneralName.directoryName, legalName.x500Name))), arrayOf()) @@ -150,10 +147,10 @@ class NetworkRegistrationHelperTest { legalName.x500Principal, keyPair.public, nameConstraints = nameConstraints) - return arrayOf(nodeCaCert, intermediateCa.certificate, rootCa.certificate) + return listOf(nodeCaCert, intermediateCa.certificate, rootCa.certificate) } - private fun createRegistrationHelper(response: Array): NetworkRegistrationHelper { + private fun createRegistrationHelper(response: List): NetworkRegistrationHelper { val certService = rigorousMock().also { doReturn(requestId).whenever(it).submitRequest(any()) doReturn(response).whenever(it).retrieveCertificates(eq(requestId)) @@ -163,9 +160,8 @@ class NetworkRegistrationHelperTest { private fun saveTrustStoreWithRootCa(rootCert: X509Certificate) { config.certificatesDirectory.createDirectories() - loadOrCreateKeyStore(config.trustStoreFile, config.trustStorePassword).also { - it.addOrReplaceCertificate(X509Utilities.CORDA_ROOT_CA, rootCert) - it.save(config.trustStoreFile, config.trustStorePassword) + config.loadTrustStore(createNew = true).update { + setCertificate(X509Utilities.CORDA_ROOT_CA, rootCert) } } } diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt index bfb34c1f76..a2d00fa9b1 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt @@ -34,16 +34,14 @@ import net.corda.nodeapi.internal.addShutdownHook import net.corda.nodeapi.internal.config.parseAs import net.corda.nodeapi.internal.config.toConfig import net.corda.nodeapi.internal.crypto.X509Utilities -import net.corda.nodeapi.internal.crypto.addOrReplaceCertificate -import net.corda.nodeapi.internal.crypto.loadOrCreateKeyStore -import net.corda.nodeapi.internal.crypto.save import net.corda.nodeapi.internal.network.NetworkParametersCopier import net.corda.nodeapi.internal.network.NodeInfoFilesCopier import net.corda.nodeapi.internal.network.NotaryInfo +import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.core.ALICE_NAME import net.corda.testing.core.BOB_NAME import net.corda.testing.core.DUMMY_BANK_A_NAME -import net.corda.testing.common.internal.testNetworkParameters +import net.corda.testing.core.setGlobalSerialization import net.corda.testing.driver.* import net.corda.testing.node.ClusterSpec import net.corda.testing.node.MockServices.Companion.MOCK_VERSION_INFO @@ -51,7 +49,6 @@ import net.corda.testing.node.NotarySpec import net.corda.testing.node.User import net.corda.testing.node.internal.DriverDSLImpl.ClusterType.NON_VALIDATING_RAFT import net.corda.testing.node.internal.DriverDSLImpl.ClusterType.VALIDATING_RAFT -import net.corda.testing.core.setGlobalSerialization import okhttp3.OkHttpClient import okhttp3.Request import rx.Observable @@ -239,9 +236,8 @@ class DriverDSLImpl( )) config.corda.certificatesDirectory.createDirectories() - loadOrCreateKeyStore(config.corda.trustStoreFile, config.corda.trustStorePassword).apply { - addOrReplaceCertificate(X509Utilities.CORDA_ROOT_CA, rootCert) - save(config.corda.trustStoreFile, config.corda.trustStorePassword) + config.corda.loadTrustStore(createNew = true).update { + setCertificate(X509Utilities.CORDA_ROOT_CA, rootCert) } return if (startNodesInProcess) {