diff --git a/.ci/api-current.txt b/.ci/api-current.txt index 8770bf5465..8a98f07116 100644 --- a/.ci/api-current.txt +++ b/.ci/api-current.txt @@ -3579,6 +3579,9 @@ public static final class net.corda.client.jackson.StringToMethodCallParser$Unpa public (String) @org.jetbrains.annotations.NotNull public final String getMethodName() ## +public static interface net.corda.testing.node.InMemoryMessagingNetwork$LatencyCalculator + @org.jetbrains.annotations.NotNull public abstract java.time.Duration between(net.corda.core.messaging.SingleMessageRecipient, net.corda.core.messaging.SingleMessageRecipient) +## public final class net.corda.client.rpc.CordaRPCClient extends java.lang.Object public (net.corda.core.utilities.NetworkHostAndPort) public (net.corda.core.utilities.NetworkHostAndPort, net.corda.client.rpc.CordaRPCClientConfiguration) diff --git a/confidential-identities/src/main/kotlin/net/corda/confidential/SwapIdentitiesFlow.kt b/confidential-identities/src/main/kotlin/net/corda/confidential/SwapIdentitiesFlow.kt index 575c1f8d52..f98970c322 100644 --- a/confidential-identities/src/main/kotlin/net/corda/confidential/SwapIdentitiesFlow.kt +++ b/confidential-identities/src/main/kotlin/net/corda/confidential/SwapIdentitiesFlow.kt @@ -10,7 +10,6 @@ import net.corda.core.identity.AnonymousParty import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party import net.corda.core.identity.PartyAndCertificate -import net.corda.core.internal.toX509CertHolder import net.corda.core.node.services.IdentityService import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.SerializedBytes @@ -18,14 +17,8 @@ import net.corda.core.serialization.deserialize import net.corda.core.serialization.serialize import net.corda.core.utilities.ProgressTracker import net.corda.core.utilities.unwrap -import org.bouncycastle.asn1.DERSet -import org.bouncycastle.asn1.pkcs.CertificationRequestInfo -import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo -import java.io.ByteArrayOutputStream -import java.nio.charset.Charset import java.security.PublicKey import java.security.SignatureException -import java.security.cert.CertPath import java.util.* /** diff --git a/core/src/main/kotlin/net/corda/core/identity/CordaX500Name.kt b/core/src/main/kotlin/net/corda/core/identity/CordaX500Name.kt index ad315c065e..abe3d21fd4 100644 --- a/core/src/main/kotlin/net/corda/core/identity/CordaX500Name.kt +++ b/core/src/main/kotlin/net/corda/core/identity/CordaX500Name.kt @@ -80,8 +80,10 @@ data class CordaX500Name(val commonName: String?, const val MAX_LENGTH_STATE = 64 const val MAX_LENGTH_ORGANISATION_UNIT = 64 const val MAX_LENGTH_COMMON_NAME = 64 + private val supportedAttributes = setOf(BCStyle.O, BCStyle.C, BCStyle.L, BCStyle.CN, BCStyle.ST, BCStyle.OU) private val countryCodes: Set = ImmutableSet.copyOf(Locale.getISOCountries() + unspecifiedCountry) + @JvmStatic fun build(principal: X500Principal): CordaX500Name { val x500Name = X500Name.getInstance(principal.encoded) @@ -115,20 +117,12 @@ data class CordaX500Name(val commonName: String?, } @Transient - private var x500Cache: X500Name? = null + private var _x500Principal: X500Principal? = null - val x500Principal: X500Principal - get() { - if (x500Cache == null) { - x500Cache = this.x500Name - } - return X500Principal(x500Cache!!.encoded) - } - - override fun toString(): String { - if (x500Cache == null) { - x500Cache = this.x500Name - } - return x500Cache.toString() + /** Return the [X500Principal] equivalent of this name. */ + val x500Principal: X500Principal get() { + return _x500Principal ?: X500Principal(this.x500Name.encoded).also { _x500Principal = it } } + + override fun toString(): String = x500Principal.toString() } \ No newline at end of file diff --git a/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt b/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt index 42145a2f26..1ad9e16630 100644 --- a/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt +++ b/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt @@ -10,8 +10,9 @@ import net.corda.core.node.ServicesForResolution import net.corda.core.serialization.SerializationContext import net.corda.core.transactions.TransactionBuilder import net.corda.core.transactions.WireTransaction -import org.bouncycastle.cert.X509CertificateHolder -import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter +import org.bouncycastle.asn1.x500.X500Name +import org.bouncycastle.asn1.x500.X500NameBuilder +import org.bouncycastle.asn1.x500.style.BCStyle import org.slf4j.Logger import rx.Observable import rx.Observer @@ -26,8 +27,6 @@ import java.nio.charset.Charset import java.nio.charset.StandardCharsets.UTF_8 import java.nio.file.* import java.nio.file.attribute.FileAttribute -import java.security.cert.Certificate -import java.security.cert.X509Certificate import java.time.Duration import java.time.temporal.Temporal import java.util.* @@ -186,9 +185,6 @@ fun logElapsedTime(label: String, logger: Logger? = null, body: () -> T): T } } -fun Certificate.toX509CertHolder() = X509CertificateHolder(encoded) -val X509CertificateHolder.cert: X509Certificate get() = JcaX509CertificateConverter().getCertificate(this) - /** Convert a [ByteArrayOutputStream] to [InputStreamAndHash]. */ fun ByteArrayOutputStream.toInputStreamAndHash(): InputStreamAndHash { val bytes = toByteArray() @@ -320,6 +316,22 @@ fun ExecutorService.join() { } } +/** + * Return the underlying X.500 name from this Corda-safe X.500 name. These are guaranteed to have a consistent + * ordering, such that their `toString()` function returns the same value every time for the same [CordaX500Name]. + */ +val CordaX500Name.x500Name: X500Name + get() { + return X500NameBuilder(BCStyle.INSTANCE).apply { + addRDN(BCStyle.C, country) + state?.let { addRDN(BCStyle.ST, it) } + addRDN(BCStyle.L, locality) + addRDN(BCStyle.O, organisation) + organisationUnit?.let { addRDN(BCStyle.OU, it) } + commonName?.let { addRDN(BCStyle.CN, it) } + }.build() + } + @Suppress("unused") @VisibleForTesting val CordaX500Name.Companion.unspecifiedCountry diff --git a/core/src/main/kotlin/net/corda/core/internal/X500NameUtils.kt b/core/src/main/kotlin/net/corda/core/internal/X500NameUtils.kt deleted file mode 100644 index 7d57d7d522..0000000000 --- a/core/src/main/kotlin/net/corda/core/internal/X500NameUtils.kt +++ /dev/null @@ -1,34 +0,0 @@ -@file:JvmName("X500NameUtils") - -package net.corda.core.internal - -import net.corda.core.identity.CordaX500Name -import org.bouncycastle.asn1.ASN1ObjectIdentifier -import org.bouncycastle.asn1.x500.X500Name -import org.bouncycastle.asn1.x500.X500NameBuilder -import org.bouncycastle.asn1.x500.style.BCStyle - -val X500Name.commonName: String? get() = getRDNValueString(BCStyle.CN) -val X500Name.state: String? get() = getRDNValueString(BCStyle.ST) -val X500Name.organisation: String get() = getRDNValueString(BCStyle.O) ?: throw IllegalArgumentException("Malformed X500 name, organisation attribute (O) cannot be empty.") -val X500Name.locality: String get() = getRDNValueString(BCStyle.L) ?: throw IllegalArgumentException("Malformed X500 name, locality attribute (L) cannot be empty.") -val X500Name.country: String get() = getRDNValueString(BCStyle.C) ?: throw IllegalArgumentException("Malformed X500 name, country attribute (C) cannot be empty.") - -private fun X500Name.getRDNValueString(identifier: ASN1ObjectIdentifier): String? = getRDNs(identifier).firstOrNull()?.first?.value?.toString() - - -/** - * Return the underlying X.500 name from this Corda-safe X.500 name. These are guaranteed to have a consistent - * ordering, such that their `toString()` function returns the same value every time for the same [CordaX500Name]. - */ -val CordaX500Name.x500Name: X500Name - get() { - return X500NameBuilder(BCStyle.INSTANCE).apply { - addRDN(BCStyle.C, country) - state?.let { addRDN(BCStyle.ST, it) } - addRDN(BCStyle.L, locality) - addRDN(BCStyle.O, organisation) - organisationUnit?.let { addRDN(BCStyle.OU, it) } - commonName?.let { addRDN(BCStyle.CN, it) } - }.build() - } \ No newline at end of file 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 751037b1ee..61d0c3d2f6 100644 --- a/core/src/test/kotlin/net/corda/core/crypto/CompositeKeyTests.kt +++ b/core/src/test/kotlin/net/corda/core/crypto/CompositeKeyTests.kt @@ -1,8 +1,6 @@ package net.corda.core.crypto import net.corda.core.crypto.CompositeKey.NodeAndWeight -import net.corda.core.identity.CordaX500Name -import net.corda.core.internal.cert import net.corda.core.internal.declaredField import net.corda.core.internal.div import net.corda.core.serialization.serialize @@ -15,6 +13,7 @@ import org.junit.Rule import org.junit.Test import org.junit.rules.TemporaryFolder import java.security.PublicKey +import javax.security.auth.x500.X500Principal import kotlin.test.assertEquals import kotlin.test.assertFailsWith import kotlin.test.assertFalse @@ -334,7 +333,7 @@ class CompositeKeyTests { // Create self sign CA. val caKeyPair = Crypto.generateKeyPair() - val caName = CordaX500Name(commonName = "Test CA", organisation = "R3 Ltd", locality = "London", country = "GB") + val caName = X500Principal("CN=Test CA,O=R3 Ltd,L=London,C=GB") val ca = X509Utilities.createSelfSignedCACertificate(caName, caKeyPair) // Sign the composite key with the self sign CA. @@ -343,7 +342,7 @@ class CompositeKeyTests { // Store certificate to keystore. val keystorePath = tempFolder.root.toPath() / "keystore.jks" val keystore = loadOrCreateKeyStore(keystorePath, "password") - keystore.setCertificateEntry("CompositeKey", compositeKeyCert.cert) + keystore.setCertificateEntry("CompositeKey", compositeKeyCert) keystore.save(keystorePath, "password") // Load keystore from disk. diff --git a/core/src/test/kotlin/net/corda/core/crypto/X509NameConstraintsTest.kt b/core/src/test/kotlin/net/corda/core/crypto/X509NameConstraintsTest.kt index 707a32bba4..3e51ad7b9f 100644 --- a/core/src/test/kotlin/net/corda/core/crypto/X509NameConstraintsTest.kt +++ b/core/src/test/kotlin/net/corda/core/crypto/X509NameConstraintsTest.kt @@ -1,7 +1,6 @@ package net.corda.core.crypto import net.corda.core.identity.CordaX500Name -import net.corda.core.internal.cert import net.corda.nodeapi.internal.crypto.* import net.corda.testing.internal.createDevIntermediateCaCertPath import org.bouncycastle.asn1.x500.X500Name @@ -14,6 +13,7 @@ import java.security.KeyStore import java.security.cert.CertPathValidator import java.security.cert.CertPathValidatorException import java.security.cert.PKIXParameters +import javax.security.auth.x500.X500Principal import kotlin.test.assertFailsWith import kotlin.test.assertTrue @@ -26,17 +26,22 @@ class X509NameConstraintsTest { CertificateType.NODE_CA, intermediateCa.certificate, intermediateCa.keyPair, - CordaX500Name("Corda Client CA", "R3 Ltd", "London", "GB"), + CordaX500Name("Corda Client CA", "R3 Ltd", "London", "GB").x500Principal, nodeCaKeyPair.public, nameConstraints = nameConstraints) val keyPass = "password" val trustStore = KeyStore.getInstance(KEYSTORE_TYPE) trustStore.load(null, keyPass.toCharArray()) - trustStore.addOrReplaceCertificate(X509Utilities.CORDA_ROOT_CA, rootCa.certificate.cert) + trustStore.addOrReplaceCertificate(X509Utilities.CORDA_ROOT_CA, rootCa.certificate) val tlsKeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) - val tlsCert = X509Utilities.createCertificate(CertificateType.TLS, nodeCaCert, nodeCaKeyPair, subjectName, tlsKeyPair.public) + val tlsCert = X509Utilities.createCertificate( + CertificateType.TLS, + nodeCaCert, + nodeCaKeyPair, + X500Principal(subjectName.encoded), + tlsKeyPair.public) val keyStore = KeyStore.getInstance(KEYSTORE_TYPE) keyStore.load(null, keyPass.toCharArray()) diff --git a/core/src/test/kotlin/net/corda/core/identity/CordaX500NameTest.kt b/core/src/test/kotlin/net/corda/core/identity/CordaX500NameTest.kt index 766fa202a4..3cfc7c089b 100644 --- a/core/src/test/kotlin/net/corda/core/identity/CordaX500NameTest.kt +++ b/core/src/test/kotlin/net/corda/core/identity/CordaX500NameTest.kt @@ -7,30 +7,36 @@ import kotlin.test.assertNull class CordaX500NameTest { @Test - fun `parse service name with organisational unit`() { + fun `service name with organisational unit`() { val name = CordaX500Name.parse("O=Bank A, L=New York, C=US, OU=Org Unit, CN=Service Name") assertEquals("Service Name", name.commonName) assertEquals("Org Unit", name.organisationUnit) assertEquals("Bank A", name.organisation) assertEquals("New York", name.locality) + assertEquals(CordaX500Name.parse(name.toString()), name) + assertEquals(CordaX500Name.build(name.x500Principal), name) } @Test - fun `parse service name`() { + fun `service name`() { val name = CordaX500Name.parse("O=Bank A, L=New York, C=US, CN=Service Name") assertEquals("Service Name", name.commonName) assertNull(name.organisationUnit) assertEquals("Bank A", name.organisation) assertEquals("New York", name.locality) + assertEquals(CordaX500Name.parse(name.toString()), name) + assertEquals(CordaX500Name.build(name.x500Principal), name) } @Test - fun `parse legal entity name`() { + fun `legal entity name`() { val name = CordaX500Name.parse("O=Bank A, L=New York, C=US") assertNull(name.commonName) assertNull(name.organisationUnit) assertEquals("Bank A", name.organisation) assertEquals("New York", name.locality) + assertEquals(CordaX500Name.parse(name.toString()), name) + assertEquals(CordaX500Name.build(name.x500Principal), name) } @Test 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 f64ac5440a..ff8207db8d 100644 --- a/core/src/test/kotlin/net/corda/core/identity/PartyAndCertificateTest.kt +++ b/core/src/test/kotlin/net/corda/core/identity/PartyAndCertificateTest.kt @@ -1,14 +1,13 @@ package net.corda.core.identity import net.corda.core.crypto.entropyToKeyPair -import net.corda.core.internal.cert 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.testing.DEV_CA +import net.corda.testing.DEV_ROOT_CA import net.corda.testing.SerializationEnvironmentRule import net.corda.testing.getTestPartyAndCertificate import org.assertj.core.api.Assertions.assertThat @@ -25,8 +24,8 @@ class PartyAndCertificateTest { val testSerialization = SerializationEnvironmentRule() @Test - fun `should reject a path with no roles`() { - val path = X509CertificateFactory().generateCertPath(DEV_CA.certificate.cert) + fun `reject a path with no roles`() { + val path = X509CertificateFactory().generateCertPath(DEV_ROOT_CA.certificate) assertFailsWith { PartyAndCertificate(path) } } diff --git a/docs/source/hello-world-running.rst b/docs/source/hello-world-running.rst index 4ac394d26c..58e3271c71 100644 --- a/docs/source/hello-world-running.rst +++ b/docs/source/hello-world-running.rst @@ -103,7 +103,7 @@ Go to the terminal window displaying the CRaSH shell of PartyA. Typing ``help`` commands. .. note:: Local terminal shell is available only in a development mode. In production environment SSH server can be enabled. - More about SSH and how to connect can be found on :doc:`Shell` page. + More about SSH and how to connect can be found on the :doc:`shell` page. We want to create an IOU of 100 with PartyB. We start the ``IOUFlow`` by typing: diff --git a/docs/source/serialization.rst b/docs/source/serialization.rst index 70dcb44dee..b4f87b912a 100644 --- a/docs/source/serialization.rst +++ b/docs/source/serialization.rst @@ -192,9 +192,6 @@ The following 3rd party types are supported. org.apache.activemq.artemis.api.core.SimpleString - org.bouncycastle.asn1.x500.X500Name - org.bouncycastle.cert.X509CertificateHolder - Corda Types ``````````` 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 ef58a930b2..0cb8472f89 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 @@ -5,10 +5,8 @@ import net.corda.core.crypto.Crypto import net.corda.core.crypto.generateKeyPair import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party -import net.corda.core.internal.cert import net.corda.core.internal.createDirectories import net.corda.core.internal.div -import net.corda.core.internal.toX509CertHolder import net.corda.core.utilities.trace import net.corda.nodeapi.internal.config.NodeSSLConfiguration import net.corda.nodeapi.internal.crypto.* @@ -39,10 +37,10 @@ object DevIdentityGenerator { // TODO The passwords for the dev key stores are spread everywhere and should be constants in a single location val caKeyStore = loadKeyStore(javaClass.classLoader.getResourceAsStream("certificates/cordadevcakeys.jks"), "cordacadevpass") val intermediateCa = caKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_INTERMEDIATE_CA, "cordacadevkeypass") - val rootCert = caKeyStore.getCertificate(X509Utilities.CORDA_ROOT_CA) + val rootCert = caKeyStore.getX509Certificate(X509Utilities.CORDA_ROOT_CA) nodeSslConfig.certificatesDirectory.createDirectories() - nodeSslConfig.createDevKeyStores(rootCert.toX509CertHolder(), intermediateCa, legalName) + nodeSslConfig.createDevKeyStores(rootCert, intermediateCa, legalName) val keyStoreWrapper = KeyStoreWrapper(nodeSslConfig.nodeKeystore, nodeSslConfig.keyStorePassword) val identity = keyStoreWrapper.storeLegalIdentity(legalName, "$NODE_IDENTITY_ALIAS_PREFIX-private-key", Crypto.generateKeyPair()) @@ -62,16 +60,21 @@ object DevIdentityGenerator { keyPairs.zip(dirs) { keyPair, nodeDir -> val (serviceKeyCert, compositeKeyCert) = listOf(keyPair.public, compositeKey).map { publicKey -> - X509Utilities.createCertificate(CertificateType.SERVICE_IDENTITY, intermediateCa.certificate, intermediateCa.keyPair, notaryName, publicKey) + X509Utilities.createCertificate( + CertificateType.SERVICE_IDENTITY, + intermediateCa.certificate, + intermediateCa.keyPair, + notaryName.x500Principal, + publicKey) } val distServKeyStoreFile = (nodeDir / "certificates").createDirectories() / "distributedService.jks" val keystore = loadOrCreateKeyStore(distServKeyStoreFile, "cordacadevpass") - keystore.setCertificateEntry("$DISTRIBUTED_NOTARY_ALIAS_PREFIX-composite-key", compositeKeyCert.cert) + keystore.setCertificateEntry("$DISTRIBUTED_NOTARY_ALIAS_PREFIX-composite-key", compositeKeyCert) keystore.setKeyEntry( "$DISTRIBUTED_NOTARY_ALIAS_PREFIX-private-key", keyPair.private, "cordacadevkeypass".toCharArray(), - arrayOf(serviceKeyCert.cert, intermediateCa.certificate.cert, rootCert)) + arrayOf(serviceKeyCert, intermediateCa.certificate, rootCert)) keystore.save(distServKeyStoreFile, "cordacadevpass") } 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 03bd39e635..374a4cdf24 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 @@ -8,13 +8,13 @@ import net.corda.nodeapi.internal.crypto.* import org.bouncycastle.asn1.x509.GeneralName import org.bouncycastle.asn1.x509.GeneralSubtree import org.bouncycastle.asn1.x509.NameConstraints -import org.bouncycastle.cert.X509CertificateHolder +import java.security.cert.X509Certificate /** * 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: X509CertificateHolder, intermediateCa: CertificateAndKeyPair, legalName: CordaX500Name) { +fun SSLConfiguration.createDevKeyStores(rootCert: X509Certificate, intermediateCa: CertificateAndKeyPair, legalName: CordaX500Name) { val (nodeCaCert, nodeCaKeyPair) = createDevNodeCa(intermediateCa, legalName) loadOrCreateKeyStore(nodeKeystore, keyStorePassword).apply { @@ -27,7 +27,7 @@ fun SSLConfiguration.createDevKeyStores(rootCert: X509CertificateHolder, interme } val tlsKeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) - val tlsCert = X509Utilities.createCertificate(CertificateType.TLS, nodeCaCert, nodeCaKeyPair, legalName, tlsKeyPair.public) + val tlsCert = X509Utilities.createCertificate(CertificateType.TLS, nodeCaCert, nodeCaKeyPair, legalName.x500Principal, tlsKeyPair.public) loadOrCreateKeyStore(sslKeystore, keyStorePassword).apply { addOrReplaceKey( @@ -50,7 +50,7 @@ fun createDevNodeCa(intermediateCa: CertificateAndKeyPair, legalName: CordaX500N CertificateType.NODE_CA, intermediateCa.certificate, intermediateCa.keyPair, - legalName, + legalName.x500Principal, keyPair.public, nameConstraints = nameConstraints) return CertificateAndKeyPair(cert, keyPair) 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 b49493ded2..d060c756b0 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 @@ -3,8 +3,9 @@ package net.corda.nodeapi.internal.crypto import net.corda.core.crypto.Crypto -import net.corda.core.internal.* -import org.bouncycastle.cert.X509CertificateHolder +import net.corda.core.internal.exists +import net.corda.core.internal.read +import net.corda.core.internal.write import java.io.IOException import java.io.InputStream import java.io.OutputStream @@ -67,18 +68,6 @@ fun loadKeyStore(input: InputStream, storePassword: String): KeyStore { return keyStore } -/** - * Helper extension method to add, or overwrite any key data in store. - * @param alias name to record the private key and certificate chain under. - * @param key cryptographic key to store. - * @param password password for unlocking the key entry in the future. This does not have to be the same password as any keys stored, - * but for SSL purposes this is recommended. - * @param chain the sequence of certificates starting with the public key certificate for this key and extending to the root CA cert. - */ -fun KeyStore.addOrReplaceKey(alias: String, key: Key, password: CharArray, chain: Array) { - addOrReplaceKey(alias, key, password, chain.map { it.cert }.toTypedArray()) -} - /** * Helper extension method to add, or overwrite any key data in store. * @param alias name to record the private key and certificate chain under. @@ -132,9 +121,9 @@ fun KeyStore.getKeyPair(alias: String, keyPassword: String): KeyPair = getCertif * @param keyPassword The password for the PrivateKey (not the store access password). */ fun KeyStore.getCertificateAndKeyPair(alias: String, keyPassword: String): CertificateAndKeyPair { - val cert = getX509Certificate(alias).toX509CertHolder() - val publicKey = Crypto.toSupportedPublicKey(cert.subjectPublicKeyInfo) - return CertificateAndKeyPair(cert, KeyPair(publicKey, getSupportedKey(alias, keyPassword))) + val certificate = getX509Certificate(alias) + val publicKey = Crypto.toSupportedPublicKey(certificate.publicKey) + return CertificateAndKeyPair(certificate, KeyPair(publicKey, getSupportedKey(alias, keyPassword))) } /** 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 index 2504ce221b..f7cae5b441 100644 --- 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 @@ -2,7 +2,6 @@ package net.corda.nodeapi.internal.crypto import net.corda.core.identity.CordaX500Name import net.corda.core.identity.PartyAndCertificate -import net.corda.core.internal.cert import net.corda.core.internal.read import java.nio.file.Path import java.security.KeyPair @@ -15,8 +14,13 @@ class KeyStoreWrapper(private val storePath: Path, private val storePassword: St 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, keyPair.public) - val identityCertPath = X509CertificateFactory().generateCertPath(identityCert.cert, *nodeCaCertChain) + 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) 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 78b44a8786..fb2e512d38 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 @@ -4,12 +4,14 @@ import net.corda.core.CordaOID import net.corda.core.crypto.Crypto import net.corda.core.crypto.SignatureScheme 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.* -import org.bouncycastle.asn1.x500.X500Name import org.bouncycastle.asn1.x500.style.BCStyle import org.bouncycastle.asn1.x509.* import org.bouncycastle.asn1.x509.Extension @@ -34,6 +36,7 @@ import java.time.Duration import java.time.Instant import java.time.temporal.ChronoUnit import java.util.* +import javax.security.auth.x500.X500Principal object X509Utilities { val DEFAULT_IDENTITY_SIGNATURE_SCHEME = Crypto.EDDSA_ED25519_SHA512 @@ -74,7 +77,7 @@ object X509Utilities { * @param after duration to roll forward returned end date relative to current date. * @param parent if provided certificate whose validity should bound the date interval returned. */ - fun getCertificateValidityWindow(before: Duration, after: Duration, parent: X509CertificateHolder? = null): Pair { + fun getCertificateValidityWindow(before: Duration, after: Duration, parent: X509Certificate? = null): Pair { val startOfDayUTC = Instant.now().truncatedTo(ChronoUnit.DAYS) val notBefore = max(startOfDayUTC - before, parent?.notBefore) val notAfter = min(startOfDayUTC + after, parent?.notAfter) @@ -85,60 +88,11 @@ object X509Utilities { * Create a de novo root self-signed X509 v3 CA cert. */ @JvmStatic - fun createSelfSignedCACertificate(subject: CordaX500Name, + fun createSelfSignedCACertificate(subject: X500Principal, keyPair: KeyPair, - validityWindow: Pair = DEFAULT_VALIDITY_WINDOW): X509CertificateHolder { + validityWindow: Pair = DEFAULT_VALIDITY_WINDOW): X509Certificate { val window = getCertificateValidityWindow(validityWindow.first, validityWindow.second) - return createCertificate(CertificateType.ROOT_CA, subject.x500Name, keyPair, subject.x500Name, keyPair.public, window) - } - - /** - * Create a X509 v3 certificate for use as a CA or for TLS. This does not require a [CordaX500Name] because the - * constraints are inappropriate for TLS/CA usage, however as a result this is unsuitable for Corda identity - * certificate generation. - * - * @param issuerCertificate The Public certificate of the root CA above this used to sign it. - * @param issuerKeyPair The KeyPair of the root CA above this used to sign it. - * @param subject subject of the generated certificate. - * @param subjectPublicKey subject's public key. - * @param validityWindow The certificate's validity window. Default to [DEFAULT_VALIDITY_WINDOW] if not provided. - * @return A data class is returned containing the new intermediate CA Cert and its KeyPair for signing downstream certificates. - * Note the generated certificate tree is capped at max depth of 1 below this to be in line with commercially available certificates. - */ - @JvmStatic - fun createCertificate(certificateType: CertificateType, - issuerCertificate: X509CertificateHolder, - issuerKeyPair: KeyPair, - subject: CordaX500Name, - subjectPublicKey: PublicKey, - validityWindow: Pair = DEFAULT_VALIDITY_WINDOW, - nameConstraints: NameConstraints? = null): X509CertificateHolder { - return createCertificate(certificateType, issuerCertificate, issuerKeyPair, subject.x500Name, subjectPublicKey, validityWindow, nameConstraints) - } - - /** - * Create a X509 v3 certificate for use as a CA or for TLS. This does not require a [CordaX500Name] because the - * constraints are inappropriate for TLS/CA usage, however as a result this is unsuitable for Corda identity - * certificate generation. - * - * @param issuerCertificate The Public certificate of the root CA above this used to sign it. - * @param issuerKeyPair The KeyPair of the root CA above this used to sign it. - * @param subject subject of the generated certificate. - * @param subjectPublicKey subject's public key. - * @param validityWindow The certificate's validity window. Default to [DEFAULT_VALIDITY_WINDOW] if not provided. - * @return A data class is returned containing the new intermediate CA Cert and its KeyPair for signing downstream certificates. - * Note the generated certificate tree is capped at max depth of 1 below this to be in line with commercially available certificates. - */ - @JvmStatic - fun createCertificate(certificateType: CertificateType, - issuerCertificate: X509CertificateHolder, - issuerKeyPair: KeyPair, - subject: X500Name, - subjectPublicKey: PublicKey, - validityWindow: Pair = DEFAULT_VALIDITY_WINDOW, - nameConstraints: NameConstraints? = null): X509CertificateHolder { - val window = getCertificateValidityWindow(validityWindow.first, validityWindow.second, issuerCertificate) - return createCertificate(certificateType, issuerCertificate.subject, issuerKeyPair, subject, subjectPublicKey, window, nameConstraints) + return createCertificate(CertificateType.ROOT_CA, subject, keyPair, subject, keyPair.public, window) } @Throws(CertPathValidatorException::class) @@ -153,13 +107,13 @@ object X509Utilities { /** * Helper method to store a .pem/.cer format file copy of a certificate if required for import into a PC/Mac, or for inspection. - * @param x509Certificate certificate to save. + * @param certificate certificate to save. * @param file Target file. */ @JvmStatic - fun saveCertificateAsPEMFile(x509Certificate: X509Certificate, file: Path) { + fun saveCertificateAsPEMFile(certificate: X509Certificate, file: Path) { JcaPEMWriter(file.writer()).use { - it.writeObject(x509Certificate) + it.writeObject(certificate) } } @@ -172,9 +126,10 @@ object X509Utilities { fun loadCertificateFromPEMFile(file: Path): X509Certificate { return file.reader().use { val pemObject = PemReader(it).readPemObject() - val certHolder = X509CertificateHolder(pemObject.content) - certHolder.isValidOn(Date()) - certHolder.cert + X509CertificateHolder(pemObject.content).run { + isValidOn(Date()) + toJca() + } } } @@ -187,38 +142,18 @@ object X509Utilities { * @param validityWindow the time period the certificate is valid for. * @param nameConstraints any name constraints to impose on certificates signed by the generated certificate. */ - fun createCertificate(certificateType: CertificateType, - issuer: CordaX500Name, - subject: CordaX500Name, - subjectPublicKey: PublicKey, - validityWindow: Pair, - nameConstraints: NameConstraints? = null): X509v3CertificateBuilder { - return createCertificate(certificateType, issuer.x500Name, subject.x500Name, subjectPublicKey, validityWindow, nameConstraints) - } - - /** - * Build a partial X.509 certificate ready for signing. - * - * @param issuer name of the issuing entity. - * @param subject name of the certificate subject. - * @param subjectPublicKey public key of the certificate subject. - * @param validityWindow the time period the certificate is valid for. - * @param nameConstraints any name constraints to impose on certificates signed by the generated certificate. - */ - internal fun createCertificate(certificateType: CertificateType, - issuer: X500Name, - subject: X500Name, - subjectPublicKey: PublicKey, - validityWindow: Pair, - nameConstraints: NameConstraints? = null): X509v3CertificateBuilder { - + fun createPartialCertificate(certificateType: CertificateType, + issuer: X500Principal, + subject: X500Principal, + subjectPublicKey: PublicKey, + validityWindow: Pair, + nameConstraints: NameConstraints? = null): X509v3CertificateBuilder { val serial = BigInteger.valueOf(random63BitValue()) val keyPurposes = DERSequence(ASN1EncodableVector().apply { certificateType.purposes.forEach { add(it) } }) val subjectPublicKeyInfo = SubjectPublicKeyInfo.getInstance(ASN1Sequence.getInstance(subjectPublicKey.encoded)) val role = certificateType.role - val builder = JcaX509v3CertificateBuilder(issuer, serial, validityWindow.first, validityWindow.second, - subject, subjectPublicKey) + val builder = JcaX509v3CertificateBuilder(issuer, serial, validityWindow.first, validityWindow.second, subject, subjectPublicKey) .addExtension(Extension.subjectKeyIdentifier, false, BcX509ExtensionUtils().createSubjectKeyIdentifier(subjectPublicKeyInfo)) .addExtension(Extension.basicConstraints, certificateType.isCA, BasicConstraints(certificateType.isCA)) .addExtension(Extension.keyUsage, false, certificateType.keyUsage) @@ -234,6 +169,37 @@ object X509Utilities { return builder } + /** + * Create a X509 v3 certificate using the given issuer certificate and key pair. + * + * @param issuerCertificate The Public certificate of the root CA above this used to sign it. + * @param issuerKeyPair The KeyPair of the root CA above this used to sign it. + * @param subject subject of the generated certificate. + * @param subjectPublicKey subject's public key. + * @param validityWindow The certificate's validity window. Default to [DEFAULT_VALIDITY_WINDOW] if not provided. + * @return A data class is returned containing the new intermediate CA Cert and its KeyPair for signing downstream certificates. + * Note the generated certificate tree is capped at max depth of 1 below this to be in line with commercially available certificates. + */ + @JvmStatic + fun createCertificate(certificateType: CertificateType, + issuerCertificate: X509Certificate, + issuerKeyPair: KeyPair, + subject: X500Principal, + subjectPublicKey: PublicKey, + validityWindow: Pair = DEFAULT_VALIDITY_WINDOW, + nameConstraints: NameConstraints? = null): X509Certificate { + val window = getCertificateValidityWindow(validityWindow.first, validityWindow.second, issuerCertificate) + return createCertificate( + certificateType, + issuerCertificate.subjectX500Principal, + issuerKeyPair, + subject, + subjectPublicKey, + window, + nameConstraints + ) + } + /** * Build and sign an X.509 certificate with the given signer. * @@ -245,15 +211,16 @@ object X509Utilities { * @param nameConstraints any name constraints to impose on certificates signed by the generated certificate. */ fun createCertificate(certificateType: CertificateType, - issuer: X500Name, + issuer: X500Principal, issuerSigner: ContentSigner, - subject: CordaX500Name, + subject: X500Principal, subjectPublicKey: PublicKey, validityWindow: Pair, - nameConstraints: NameConstraints? = null): X509CertificateHolder { - val builder = createCertificate(certificateType, issuer, subject.x500Name, subjectPublicKey, validityWindow, nameConstraints) - return builder.build(issuerSigner).apply { + nameConstraints: NameConstraints? = null): X509Certificate { + val builder = createPartialCertificate(certificateType, issuer, subject, subjectPublicKey, validityWindow, nameConstraints) + return builder.build(issuerSigner).run { require(isValidOn(Date())) + toJca() } } @@ -268,39 +235,47 @@ object X509Utilities { * @param nameConstraints any name constraints to impose on certificates signed by the generated certificate. */ fun createCertificate(certificateType: CertificateType, - issuer: X500Name, + issuer: X500Principal, issuerKeyPair: KeyPair, - subject: X500Name, + subject: X500Principal, subjectPublicKey: PublicKey, validityWindow: Pair, - nameConstraints: NameConstraints? = null): X509CertificateHolder { + nameConstraints: NameConstraints? = null): X509Certificate { val signatureScheme = Crypto.findSignatureScheme(issuerKeyPair.private) val provider = Crypto.findProvider(signatureScheme.providerName) - val builder = createCertificate(certificateType, issuer, subject, subjectPublicKey, validityWindow, nameConstraints) - val signer = ContentSignerBuilder.build(signatureScheme, issuerKeyPair.private, provider) - return builder.build(signer).apply { + val builder = createPartialCertificate(certificateType, issuer, subject, subjectPublicKey, validityWindow, nameConstraints) + return builder.build(signer).run { require(isValidOn(Date())) require(isSignatureValid(JcaContentVerifierProviderBuilder().build(issuerKeyPair.public))) + toJca() } } /** * Create certificate signing request using provided information. */ - private fun createCertificateSigningRequest(subject: CordaX500Name, + private fun createCertificateSigningRequest(subject: X500Principal, email: String, keyPair: KeyPair, signatureScheme: SignatureScheme): PKCS10CertificationRequest { val signer = ContentSignerBuilder.build(signatureScheme, keyPair.private, Crypto.findProvider(signatureScheme.providerName)) - return JcaPKCS10CertificationRequestBuilder(subject.x500Name, keyPair.public).addAttribute(BCStyle.E, DERUTF8String(email)).build(signer) + return JcaPKCS10CertificationRequestBuilder(subject, keyPair.public).addAttribute(BCStyle.E, DERUTF8String(email)).build(signer) } - fun createCertificateSigningRequest(subject: CordaX500Name, email: String, keyPair: KeyPair): PKCS10CertificationRequest { + fun createCertificateSigningRequest(subject: X500Principal, email: String, keyPair: KeyPair): PKCS10CertificationRequest { return createCertificateSigningRequest(subject, email, keyPair, DEFAULT_TLS_SIGNATURE_SCHEME) } } +/** + * Convert a [X509Certificate] into Bouncycastle's [X509CertificateHolder]. + * + * NOTE: To avoid unnecessary copying use [X509Certificate] where possible. + */ +fun X509Certificate.toBc() = X509CertificateHolder(encoded) +fun X509CertificateHolder.toJca(): X509Certificate = X509CertificateFactory().generateCertificate(encoded.inputStream()) + /** * Wraps a [CertificateFactory] to remove boilerplate. It's unclear whether [CertificateFactory] is threadsafe so best * so assume this class is not. @@ -396,4 +371,4 @@ enum class CertificateType(val keyUsage: KeyUsage, vararg val purposes: KeyPurpo ) } -data class CertificateAndKeyPair(val certificate: X509CertificateHolder, val keyPair: KeyPair) +data class CertificateAndKeyPair(val certificate: X509Certificate, val keyPair: KeyPair) diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationHelper.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationHelper.kt index 8b677630d2..57903f546e 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationHelper.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationHelper.kt @@ -1,9 +1,9 @@ package net.corda.nodeapi.internal.serialization.amqp -import net.corda.core.serialization.ClassWhitelist -import net.corda.core.serialization.CordaSerializable import com.google.common.primitives.Primitives import com.google.common.reflect.TypeToken +import net.corda.core.serialization.ClassWhitelist +import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.SerializationContext import org.apache.qpid.proton.codec.Data import java.beans.IndexedPropertyDescriptor @@ -81,10 +81,17 @@ private fun propertiesForSerializationFromConstructor(kotlinConstructo val rc: MutableList = ArrayList(kotlinConstructor.parameters.size) for (param in kotlinConstructor.parameters) { val name = param.name ?: throw NotSerializableException("Constructor parameter of $clazz has no name.") + val matchingProperty = properties[name] ?: - throw NotSerializableException("No property matching constructor parameter named '$name' of '$clazz'. " + - "If using Java, check that you have the -parameters option specified in the Java compiler. " + - "Alternately, provide a proxy serializer (SerializationCustomSerializer) if recompiling isn't an option") + try { + clazz.getDeclaredField(param.name) + throw NotSerializableException("Property '$name' or it's getter is non public, this renders class '$clazz' unserializable") + } catch (e: NoSuchFieldException) { + throw NotSerializableException("No property matching constructor parameter named '$name' of '$clazz'. " + + "If using Java, check that you have the -parameters option specified in the Java compiler. " + + "Alternately, provide a proxy serializer (SerializationCustomSerializer) if recompiling isn't an option") + } + // Check that the method has a getter in java. val getter = matchingProperty.readMethod ?: throw NotSerializableException("Property has no getter method for $name of $clazz. " + "If using Java and the parameter name looks anonymous, check that you have the -parameters option specified in the Java compiler." + diff --git a/node-api/src/test/java/net/corda/nodeapi/internal/serialization/amqp/ErrorMessageTests.java b/node-api/src/test/java/net/corda/nodeapi/internal/serialization/amqp/ErrorMessageTests.java new file mode 100644 index 0000000000..2df95a808d --- /dev/null +++ b/node-api/src/test/java/net/corda/nodeapi/internal/serialization/amqp/ErrorMessageTests.java @@ -0,0 +1,40 @@ +package net.corda.nodeapi.internal.serialization.amqp; + +import net.corda.nodeapi.internal.serialization.AllWhitelist; +import org.assertj.core.api.Assertions; +import org.junit.Test; + +import java.io.NotSerializableException; + +public class ErrorMessageTests { + private String errMsg(String property, String testname) { + return "Property '" + + property + + "' or it's getter is non public, this renders class 'class " + + testname + + "$C' unserializable -> class " + + testname + + "$C"; + } + + static class C { + public Integer a; + + public C(Integer a) { + this.a = a; + } + + private Integer getA() { return this.a; } + } + + @Test + public void testJavaConstructorAnnotations() { + SerializerFactory factory1 = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader()); + SerializationOutput ser = new SerializationOutput(factory1); + + Assertions.assertThatThrownBy(() -> ser.serialize(new C(1))) + .isInstanceOf(NotSerializableException.class) + .hasMessage(errMsg("a", getClass().getName())); + } + +} 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 47c2c8b9ef..af38597bfe 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 @@ -4,10 +4,7 @@ import net.corda.core.crypto.Crypto import net.corda.core.crypto.Crypto.EDDSA_ED25519_SHA512 import net.corda.core.crypto.Crypto.generateKeyPair import net.corda.core.identity.CordaX500Name -import net.corda.core.internal.cert import net.corda.core.internal.div -import net.corda.core.internal.toTypedArray -import net.corda.core.internal.x500Name import net.corda.core.serialization.SerializationContext import net.corda.core.serialization.deserialize import net.corda.core.serialization.serialize @@ -23,12 +20,9 @@ import net.corda.testing.BOB_NAME import net.corda.testing.TestIdentity import net.corda.testing.internal.createDevIntermediateCaCertPath import org.assertj.core.api.Assertions.assertThat -import org.bouncycastle.asn1.x500.X500Name import org.bouncycastle.asn1.x509.BasicConstraints import org.bouncycastle.asn1.x509.Extension import org.bouncycastle.asn1.x509.KeyUsage -import org.bouncycastle.cert.X509CertificateHolder -import org.bouncycastle.operator.jcajce.JcaContentVerifierProviderBuilder import org.junit.Rule import org.junit.Test import org.junit.rules.TemporaryFolder @@ -42,18 +36,16 @@ import java.security.SecureRandom import java.security.cert.CertPath import java.security.cert.X509Certificate import java.util.* -import java.util.stream.Stream import javax.net.ssl.* +import javax.security.auth.x500.X500Principal import kotlin.concurrent.thread import kotlin.test.* class X509UtilitiesTest { private companion object { val ALICE = TestIdentity(ALICE_NAME, 70).party - val bob = TestIdentity(BOB_NAME, 80) + val BOB = TestIdentity(BOB_NAME, 80) val MEGA_CORP = TestIdentity(CordaX500Name("MegaCorp", "London", "GB")).party - val BOB get() = bob.party - val BOB_PUBKEY get() = bob.publicKey val CIPHER_SUITES = arrayOf( "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", @@ -63,27 +55,30 @@ class X509UtilitiesTest { @Rule @JvmField - val tempFolder: TemporaryFolder = TemporaryFolder() + val tempFolder = TemporaryFolder() @Test fun `create valid self-signed CA certificate`() { val caKey = generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) - val caCert = X509Utilities.createSelfSignedCACertificate(CordaX500Name(commonName = "Test Cert", organisation = "R3 Ltd", locality = "London", country = "GB"), caKey) - assertEquals(X500Name("CN=Test Cert,O=R3 Ltd,L=London,C=GB"), caCert.subject) // using our subject common name - assertEquals(caCert.issuer, caCert.subject) //self-signed - caCert.isValidOn(Date()) // throws on verification problems - caCert.isSignatureValid(JcaContentVerifierProviderBuilder().build(caKey.public)) // throws on verification problems - val basicConstraints = BasicConstraints.getInstance(caCert.getExtension(Extension.basicConstraints).parsedValue) - val keyUsage = KeyUsage.getInstance(caCert.getExtension(Extension.keyUsage).parsedValue) - assertFalse { keyUsage.hasUsages(5) } // Bit 5 == keyCertSign according to ASN.1 spec (see full comment on KeyUsage property) - assertNull(basicConstraints.pathLenConstraint) // No length constraint specified on this CA certificate + val subject = X500Principal("CN=Test Cert,O=R3 Ltd,L=London,C=GB") + val caCert = X509Utilities.createSelfSignedCACertificate(subject, caKey) + assertEquals(subject, caCert.subjectX500Principal) // using our subject common name + assertEquals(caCert.issuerX500Principal, caCert.subjectX500Principal) //self-signed + caCert.checkValidity(Date()) // throws on verification problems + caCert.verify(caKey.public) // throws on verification problems + caCert.toBc().run { + val basicConstraints = BasicConstraints.getInstance(getExtension(Extension.basicConstraints).parsedValue) + val keyUsage = KeyUsage.getInstance(getExtension(Extension.keyUsage).parsedValue) + assertFalse { keyUsage.hasUsages(5) } // Bit 5 == keyCertSign according to ASN.1 spec (see full comment on KeyUsage property) + assertNull(basicConstraints.pathLenConstraint) // No length constraint specified on this CA certificate + } } @Test fun `load and save a PEM file certificate`() { val tmpCertificateFile = tempFile("cacert.pem") val caKey = generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) - val caCert = X509Utilities.createSelfSignedCACertificate(CordaX500Name(commonName = "Test Cert", organisation = "R3 Ltd", locality = "London", country = "GB"), caKey).cert + val caCert = X509Utilities.createSelfSignedCACertificate(X500Principal("CN=Test Cert,O=R3 Ltd,L=London,C=GB"), caKey) X509Utilities.saveCertificateAsPEMFile(caCert, tmpCertificateFile) val readCertificate = X509Utilities.loadCertificateFromPEMFile(tmpCertificateFile) assertEquals(caCert, readCertificate) @@ -92,18 +87,20 @@ class X509UtilitiesTest { @Test fun `create valid server certificate chain`() { val caKey = generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) - val caCert = X509Utilities.createSelfSignedCACertificate(CordaX500Name(commonName = "Test CA Cert", organisation = "R3 Ltd", locality = "London", country = "GB"), caKey) - val subject = CordaX500Name(commonName = "Server Cert", organisation = "R3 Ltd", locality = "London", country = "GB") + val caCert = X509Utilities.createSelfSignedCACertificate(X500Principal("CN=Test CA Cert,O=R3 Ltd,L=London,C=GB"), caKey) + val subject = X500Principal("CN=Server Cert,O=R3 Ltd,L=London,C=GB") val keyPair = generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) val serverCert = X509Utilities.createCertificate(CertificateType.TLS, caCert, caKey, subject, keyPair.public) - assertEquals(X500Name("C=GB,L=London,O=R3 Ltd,CN=Server Cert"), serverCert.subject) // using our subject common name - assertEquals(caCert.issuer, serverCert.issuer) // Issued by our CA cert - serverCert.isValidOn(Date()) // throws on verification problems - serverCert.isSignatureValid(JcaContentVerifierProviderBuilder().build(caKey.public)) // throws on verification problems - val basicConstraints = BasicConstraints.getInstance(serverCert.getExtension(Extension.basicConstraints).parsedValue) - val keyUsage = KeyUsage.getInstance(serverCert.getExtension(Extension.keyUsage).parsedValue) - assertFalse { keyUsage.hasUsages(5) } // Bit 5 == keyCertSign according to ASN.1 spec (see full comment on KeyUsage property) - assertNull(basicConstraints.pathLenConstraint) // Non-CA certificate + assertEquals(subject, serverCert.subjectX500Principal) // using our subject common name + assertEquals(caCert.issuerX500Principal, serverCert.issuerX500Principal) // Issued by our CA cert + serverCert.checkValidity(Date()) // throws on verification problems + serverCert.verify(caKey.public) // throws on verification problems + serverCert.toBc().run { + val basicConstraints = BasicConstraints.getInstance(getExtension(Extension.basicConstraints).parsedValue) + val keyUsage = KeyUsage.getInstance(getExtension(Extension.keyUsage).parsedValue) + assertFalse { keyUsage.hasUsages(5) } // Bit 5 == keyCertSign according to ASN.1 spec (see full comment on KeyUsage property) + assertNull(basicConstraints.pathLenConstraint) // Non-CA certificate + } } @Test @@ -111,15 +108,14 @@ class X509UtilitiesTest { val tmpKeyStore = tempFile("keystore.jks") val keyPair = generateKeyPair(EDDSA_ED25519_SHA512) - val testName = CordaX500Name(commonName = "Test", organisation = "R3 Ltd", locality = "London", country = "GB") + val testName = X500Principal("CN=Test,O=R3 Ltd,L=London,C=GB") val selfSignCert = X509Utilities.createSelfSignedCACertificate(testName, keyPair) - assertTrue(Arrays.equals(selfSignCert.subjectPublicKeyInfo.encoded, keyPair.public.encoded)) + assertTrue(Arrays.equals(selfSignCert.publicKey.encoded, keyPair.public.encoded)) // Save the EdDSA private key with self sign cert in the keystore. val keyStore = loadOrCreateKeyStore(tmpKeyStore, "keystorepass") - keyStore.setKeyEntry("Key", keyPair.private, "password".toCharArray(), - Stream.of(selfSignCert).map { it.cert }.toTypedArray()) + keyStore.setKeyEntry("Key", keyPair.private, "password".toCharArray(), arrayOf(selfSignCert)) keyStore.save(tmpKeyStore, "keystorepass") // Load the keystore from file and make sure keys are intact. @@ -137,15 +133,14 @@ class X509UtilitiesTest { fun `signing EdDSA key with EcDSA certificate`() { val tmpKeyStore = tempFile("keystore.jks") val ecDSAKey = generateKeyPair(Crypto.ECDSA_SECP256R1_SHA256) - val testName = CordaX500Name(commonName = "Test", organisation = "R3 Ltd", locality = "London", country = "GB") + val testName = X500Principal("CN=Test,O=R3 Ltd,L=London,C=GB") val ecDSACert = X509Utilities.createSelfSignedCACertificate(testName, ecDSAKey) val edDSAKeypair = generateKeyPair(EDDSA_ED25519_SHA512) - val edDSACert = X509Utilities.createCertificate(CertificateType.TLS, ecDSACert, ecDSAKey, X500Name("CN=TestEdDSA"), edDSAKeypair.public) + val edDSACert = X509Utilities.createCertificate(CertificateType.TLS, ecDSACert, ecDSAKey, BOB.name.x500Principal, edDSAKeypair.public) // Save the EdDSA private key with cert chains. val keyStore = loadOrCreateKeyStore(tmpKeyStore, "keystorepass") - keyStore.setKeyEntry("Key", edDSAKeypair.private, "password".toCharArray(), - Stream.of(ecDSACert, edDSACert).map { it.cert }.toTypedArray()) + keyStore.setKeyEntry("Key", edDSAKeypair.private, "password".toCharArray(), arrayOf(ecDSACert, edDSACert)) keyStore.save(tmpKeyStore, "keystorepass") // Load the keystore from file and make sure keys are intact. @@ -179,23 +174,22 @@ class X509UtilitiesTest { val serverKeyStore = loadKeyStore(sslConfig.nodeKeystore, sslConfig.keyStorePassword) val (serverCert, serverKeyPair) = serverKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA, sslConfig.keyStorePassword) - serverCert.cert.checkValidity() - serverCert.cert.verify(intermediateCa.certificate.cert.publicKey) - assertThat(CordaX500Name.parse(serverCert.subject.toString())).isEqualTo(MEGA_CORP.name) + serverCert.checkValidity() + serverCert.verify(intermediateCa.certificate.publicKey) + assertThat(CordaX500Name.build(serverCert.subjectX500Principal)).isEqualTo(MEGA_CORP.name) // Load back SSL certificate val sslKeyStore = loadKeyStore(sslConfig.sslKeystore, sslConfig.keyStorePassword) val (sslCert) = sslKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_CLIENT_TLS, sslConfig.keyStorePassword) - sslCert.cert.checkValidity() - sslCert.cert.verify(serverCert.cert.publicKey) - assertThat(CordaX500Name.parse(sslCert.subject.toString())).isEqualTo(MEGA_CORP.name) + sslCert.checkValidity() + sslCert.verify(serverCert.publicKey) + assertThat(CordaX500Name.build(sslCert.subjectX500Principal)).isEqualTo(MEGA_CORP.name) // Now sign something with private key and verify against certificate public key val testData = "123456".toByteArray() val signature = Crypto.doSign(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME, serverKeyPair.private, testData) - val publicKey = Crypto.toSupportedPublicKey(serverCert.subjectPublicKeyInfo) - assertTrue { Crypto.isValid(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME, publicKey, signature, testData) } + assertTrue { Crypto.isValid(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME, serverCert.publicKey, signature, testData) } } @Test @@ -210,7 +204,7 @@ class X509UtilitiesTest { // Generate server cert and private key and populate another keystore suitable for SSL sslConfig.createDevKeyStores(rootCa.certificate, intermediateCa, MEGA_CORP.name) - sslConfig.createTrustStore(rootCa.certificate.cert) + sslConfig.createTrustStore(rootCa.certificate) val keyStore = loadKeyStore(sslConfig.sslKeystore, sslConfig.keyStorePassword) val trustStore = loadKeyStore(sslConfig.trustStoreFile, sslConfig.trustStorePassword) @@ -303,10 +297,10 @@ class X509UtilitiesTest { @Test fun `get correct private key type from Keystore`() { val keyPair = generateKeyPair(Crypto.ECDSA_SECP256R1_SHA256) - val testName = CordaX500Name(commonName = "Test", organisation = "R3 Ltd", locality = "London", country = "GB") + val testName = X500Principal("CN=Test,O=R3 Ltd,L=London,C=GB") val selfSignCert = X509Utilities.createSelfSignedCACertificate(testName, keyPair) val keyStore = loadOrCreateKeyStore(tempFile("testKeystore.jks"), "keystorepassword") - keyStore.setKeyEntry("Key", keyPair.private, "keypassword".toCharArray(), arrayOf(selfSignCert.cert)) + keyStore.setKeyEntry("Key", keyPair.private, "keypassword".toCharArray(), arrayOf(selfSignCert)) val keyFromKeystore = keyStore.getKey("Key", "keypassword".toCharArray()) val keyFromKeystoreCasted = keyStore.getSupportedKey("Key", "keypassword") @@ -316,7 +310,7 @@ class X509UtilitiesTest { } @Test - fun `serialize - deserialize X509CertififcateHolder`() { + fun `serialize - deserialize X509Certififcate`() { val factory = SerializationFactoryImpl().apply { registerScheme(KryoServerSerializationScheme()) } val context = SerializationContextImpl(KryoHeaderV0_1, javaClass.classLoader, @@ -324,9 +318,9 @@ class X509UtilitiesTest { emptyMap(), true, SerializationContext.UseCase.P2P) - val expected: X509CertificateHolder = X509Utilities.createSelfSignedCACertificate(ALICE.name, Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)) + val expected = X509Utilities.createSelfSignedCACertificate(ALICE.name.x500Principal, Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)) val serialized = expected.serialize(factory, context).bytes - val actual: X509CertificateHolder = serialized.deserialize(factory, context) + val actual = serialized.deserialize(factory, context) assertEquals(expected, actual) } @@ -340,9 +334,9 @@ class X509UtilitiesTest { true, SerializationContext.UseCase.P2P) 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.x500Name, BOB_PUBKEY) - val expected = X509CertificateFactory().generateCertPath(certificate.cert, rootCACert.cert) + val rootCACert = X509Utilities.createSelfSignedCACertificate(ALICE_NAME.x500Principal, rootCAKey) + val certificate = X509Utilities.createCertificate(CertificateType.TLS, rootCACert, rootCAKey, BOB_NAME.x500Principal, BOB.publicKey) + val expected = X509CertificateFactory().generateCertPath(certificate, rootCACert) val serialized = expected.serialize(factory, context).bytes val actual: CertPath = serialized.deserialize(factory, context) assertEquals(expected, actual) diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/ErrorMessagesTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/ErrorMessagesTests.kt new file mode 100644 index 0000000000..775e5c3405 --- /dev/null +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/ErrorMessagesTests.kt @@ -0,0 +1,69 @@ +package net.corda.nodeapi.internal.serialization.amqp + +import org.assertj.core.api.Assertions +import org.junit.Test +import java.io.NotSerializableException + +class ErrorMessagesTests { + companion object { + val VERBOSE get() = false + } + + private fun errMsg(property:String, testname: String) = + "Property '$property' or it's getter is non public, this renders class 'class $testname\$C' unserializable -> class $testname\$C" + + @Test + fun privateProperty() { + data class C(private val a: Int) + + val sf = testDefaultFactory() + + val testname = "${javaClass.name}\$${testName()}" + + Assertions.assertThatThrownBy { + TestSerializationOutput(VERBOSE, sf).serialize(C(1)) + }.isInstanceOf(NotSerializableException::class.java).hasMessage(errMsg("a", testname)) + } + + @Test + fun privateProperty2() { + data class C(val a: Int, private val b: Int) + + val sf = testDefaultFactory() + + val testname = "${javaClass.name}\$${testName()}" + + Assertions.assertThatThrownBy { + TestSerializationOutput(VERBOSE, sf).serialize(C(1, 2)) + }.isInstanceOf(NotSerializableException::class.java).hasMessage(errMsg("b", testname)) + } + + @Test + fun privateProperty3() { + // despite b being private, the getter we've added is public and thus allows for the serialisation + // of the object + data class C(val a: Int, private val b: Int) { + public fun getB() = b + } + + val sf = testDefaultFactory() + + val testname = "${javaClass.name}\$${testName()}" + + val bytes = TestSerializationOutput(VERBOSE, sf).serialize(C(1, 2)) + val c = DeserializationInput(sf).deserialize(bytes) + } + + @Test + fun protectedProperty() { + data class C(protected val a: Int) + + val sf = testDefaultFactory() + + val testname = "${javaClass.name}\$${testName()}" + + Assertions.assertThatThrownBy { + TestSerializationOutput(VERBOSE, sf).serialize(C(1)) + }.isInstanceOf(NotSerializableException::class.java).hasMessage(errMsg("a", testname)) + } +} \ No newline at end of file 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 c8bd587e37..dbe469e9cf 100644 --- a/node/src/integration-test/kotlin/net/corda/node/NodeKeystoreCheckTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/NodeKeystoreCheckTest.kt @@ -1,8 +1,6 @@ package net.corda.node import net.corda.core.crypto.Crypto -import net.corda.core.identity.CordaX500Name -import net.corda.core.internal.cert import net.corda.core.internal.div import net.corda.core.utilities.getOrThrow import net.corda.node.services.config.configureDevKeyAndTrustStores @@ -17,6 +15,10 @@ import org.junit.ClassRule import org.assertj.core.api.Assertions.assertThatThrownBy import org.junit.Test import java.nio.file.Path +import javax.security.auth.x500.X500Principal +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith +import kotlin.test.assertTrue class NodeKeystoreCheckTest : IntegrationTest() { companion object { @@ -59,10 +61,10 @@ class NodeKeystoreCheckTest : IntegrationTest() { // Self signed root val badRootKeyPair = Crypto.generateKeyPair() - val badRoot = X509Utilities.createSelfSignedCACertificate(CordaX500Name("Bad Root", "Lodnon", "GB"), badRootKeyPair) + 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, nodeCA.keyPair.public) - keystore.setKeyEntry(X509Utilities.CORDA_CLIENT_CA, nodeCA.keyPair.private, config.keyStorePassword.toCharArray(), arrayOf(badNodeCACert.cert, badRoot.cert)) + 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) assertThatThrownBy { 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 871c7de400..88c299dd41 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 @@ -59,10 +59,10 @@ class ProtonWrapperTests { amqpClient.start() val serverConnect = serverConnected.get() assertEquals(true, serverConnect.connected) - assertEquals(BOB_NAME, CordaX500Name.parse(serverConnect.remoteCert!!.subject.toString())) + assertEquals(BOB_NAME, CordaX500Name.build(serverConnect.remoteCert!!.subjectX500Principal)) val clientConnect = clientConnected.get() assertEquals(true, clientConnect.connected) - assertEquals(ALICE_NAME, CordaX500Name.parse(clientConnect.remoteCert!!.subject.toString())) + assertEquals(ALICE_NAME, CordaX500Name.build(clientConnect.remoteCert!!.subjectX500Principal)) val msg = amqpClient.createMessage("Test".toByteArray(), "p2p.inbound", ALICE_NAME.toString(), @@ -102,10 +102,10 @@ class ProtonWrapperTests { amqpClient.start() val serverConn1 = serverConnected.get() assertEquals(true, serverConn1.connected) - assertEquals(BOB_NAME, CordaX500Name.parse(serverConn1.remoteCert!!.subject.toString())) + assertEquals(BOB_NAME, CordaX500Name.build(serverConn1.remoteCert!!.subjectX500Principal)) val connState1 = clientConnected.next() assertEquals(true, connState1.connected) - assertEquals(ALICE_NAME, CordaX500Name.parse(connState1.remoteCert!!.subject.toString())) + assertEquals(ALICE_NAME, CordaX500Name.build(connState1.remoteCert!!.subjectX500Principal)) assertEquals(serverPort, connState1.remoteAddress.port) // Fail over @@ -116,10 +116,10 @@ class ProtonWrapperTests { assertEquals(serverPort, connState2.remoteAddress.port) val serverConn2 = serverConnected2.get() assertEquals(true, serverConn2.connected) - assertEquals(BOB_NAME, CordaX500Name.parse(serverConn2.remoteCert!!.subject.toString())) + assertEquals(BOB_NAME, CordaX500Name.build(serverConn2.remoteCert!!.subjectX500Principal)) val connState3 = clientConnected.next() assertEquals(true, connState3.connected) - assertEquals(ALICE_NAME, CordaX500Name.parse(connState3.remoteCert!!.subject.toString())) + assertEquals(ALICE_NAME, CordaX500Name.build(connState3.remoteCert!!.subjectX500Principal)) assertEquals(serverPort2, connState3.remoteAddress.port) // Fail back @@ -130,10 +130,10 @@ class ProtonWrapperTests { assertEquals(serverPort2, connState4.remoteAddress.port) val serverConn3 = serverConnected.get() assertEquals(true, serverConn3.connected) - assertEquals(BOB_NAME, CordaX500Name.parse(serverConn3.remoteCert!!.subject.toString())) + assertEquals(BOB_NAME, CordaX500Name.build(serverConn3.remoteCert!!.subjectX500Principal)) val connState5 = clientConnected.next() assertEquals(true, connState5.connected) - assertEquals(ALICE_NAME, CordaX500Name.parse(connState5.remoteCert!!.subject.toString())) + assertEquals(ALICE_NAME, CordaX500Name.build(connState5.remoteCert!!.subjectX500Principal)) assertEquals(serverPort, connState5.remoteAddress.port) } finally { amqpClient.close() @@ -149,7 +149,7 @@ class ProtonWrapperTests { val clientConnected = amqpClient.onConnection.toFuture() amqpClient.start() assertEquals(true, clientConnected.get().connected) - assertEquals(CHARLIE_NAME, CordaX500Name.parse(clientConnected.get().remoteCert!!.subject.toString())) + assertEquals(CHARLIE_NAME, CordaX500Name.build(clientConnected.get().remoteCert!!.subjectX500Principal)) val artemis = artemisClient.started!! val sendAddress = "p2p.inbound" artemis.session.createQueue(sendAddress, RoutingType.MULTICAST, "queue", true) @@ -180,13 +180,13 @@ class ProtonWrapperTests { amqpClient1.start() val connection1 = connectionEvents.next() assertEquals(true, connection1.connected) - val connection1ID = CordaX500Name.parse(connection1.remoteCert!!.subject.toString()) + val connection1ID = CordaX500Name.build(connection1.remoteCert!!.subjectX500Principal) assertEquals("client 0", connection1ID.organisationUnit) val source1 = connection1.remoteAddress amqpClient2.start() val connection2 = connectionEvents.next() assertEquals(true, connection2.connected) - val connection2ID = CordaX500Name.parse(connection2.remoteCert!!.subject.toString()) + val connection2ID = CordaX500Name.build(connection2.remoteCert!!.subjectX500Principal) assertEquals("client 1", connection2ID.organisationUnit) val source2 = connection2.remoteAddress // Stopping one shouldn't disconnect the other @@ -207,7 +207,7 @@ class ProtonWrapperTests { amqpClient1.start() val connection5 = connectionEvents.next() assertEquals(true, connection5.connected) - val connection5ID = CordaX500Name.parse(connection5.remoteCert!!.subject.toString()) + val connection5ID = CordaX500Name.build(connection5.remoteCert!!.subjectX500Principal) assertEquals("client 0", connection5ID.organisationUnit) assertEquals(true, amqpClient1.connected) assertEquals(false, amqpClient2.connected) @@ -252,7 +252,8 @@ class ProtonWrapperTests { val clientTruststore = loadKeyStore(clientConfig.trustStoreFile, clientConfig.trustStorePassword) val clientKeystore = loadKeyStore(clientConfig.sslKeystore, clientConfig.keyStorePassword) - val amqpClient = AMQPClient(listOf(NetworkHostAndPort("localhost", serverPort), + return AMQPClient( + listOf(NetworkHostAndPort("localhost", serverPort), NetworkHostAndPort("localhost", serverPort2), NetworkHostAndPort("localhost", artemisPort)), setOf(ALICE_NAME, CHARLIE_NAME), @@ -261,7 +262,6 @@ class ProtonWrapperTests { clientKeystore, clientConfig.keyStorePassword, clientTruststore, true) - return amqpClient } private fun createSharedThreadsClient(sharedEventGroup: EventLoopGroup, id: Int): AMQPClient { @@ -275,14 +275,14 @@ class ProtonWrapperTests { val clientTruststore = loadKeyStore(clientConfig.trustStoreFile, clientConfig.trustStorePassword) val clientKeystore = loadKeyStore(clientConfig.sslKeystore, clientConfig.keyStorePassword) - val amqpClient = AMQPClient(listOf(NetworkHostAndPort("localhost", serverPort)), + return AMQPClient( + listOf(NetworkHostAndPort("localhost", serverPort)), setOf(ALICE_NAME), PEER_USER, PEER_USER, clientKeystore, clientConfig.keyStorePassword, clientTruststore, true, sharedEventGroup) - return amqpClient } private fun createServer(port: Int, name: CordaX500Name = ALICE_NAME): AMQPServer { @@ -296,14 +296,13 @@ class ProtonWrapperTests { 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", port, PEER_USER, PEER_USER, serverKeystore, serverConfig.keyStorePassword, serverTruststore) - return amqpServer } - -} \ No newline at end of file +} diff --git a/node/src/integration-test/kotlin/net/corda/node/utilities/registration/NodeRegistrationTest.kt b/node/src/integration-test/kotlin/net/corda/node/utilities/registration/NodeRegistrationTest.kt index c3af62c480..8c6afec769 100644 --- a/node/src/integration-test/kotlin/net/corda/node/utilities/registration/NodeRegistrationTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/utilities/registration/NodeRegistrationTest.kt @@ -2,9 +2,7 @@ package net.corda.node.utilities.registration import net.corda.core.crypto.Crypto import net.corda.core.identity.CordaX500Name -import net.corda.core.internal.cert import net.corda.core.internal.concurrent.transpose -import net.corda.core.internal.toX509CertHolder import net.corda.core.messaging.startFlow import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.OpaqueBytes @@ -38,8 +36,10 @@ import java.security.KeyPair import java.security.cert.CertPath import java.security.cert.CertPathValidatorException import java.security.cert.Certificate +import java.security.cert.X509Certificate import java.util.zip.ZipEntry import java.util.zip.ZipOutputStream +import javax.security.auth.x500.X500Principal import javax.ws.rs.* import javax.ws.rs.core.MediaType import javax.ws.rs.core.Response @@ -59,14 +59,13 @@ class NodeRegistrationTest : IntegrationTest() { val testSerialization = SerializationEnvironmentRule(true) private val portAllocation = PortAllocation.Incremental(13000) - private val registrationHandler = RegistrationHandler(ROOT_CA) - + private val registrationHandler = RegistrationHandler(DEV_ROOT_CA) private lateinit var server: NetworkMapServer private lateinit var serverHostAndPort: NetworkHostAndPort @Before fun startServer() { - server = NetworkMapServer(1.minutes, portAllocation.nextHostAndPort(), ROOT_CA, "localhost", registrationHandler) + server = NetworkMapServer(1.minutes, portAllocation.nextHostAndPort(), DEV_ROOT_CA, "localhost", registrationHandler) serverHostAndPort = server.start() } @@ -80,7 +79,7 @@ class NodeRegistrationTest : IntegrationTest() { val compatibilityZone = CompatibilityZoneParams( URL("http://$serverHostAndPort"), publishNotaries = { server.networkParameters = testNetworkParameters(it) }, - rootCert = ROOT_CA.certificate.cert) + rootCert = DEV_ROOT_CA.certificate) internalDriver( portAllocation = portAllocation, compatibilityZone = compatibilityZone, @@ -116,12 +115,12 @@ class NodeRegistrationTest : IntegrationTest() { @Test fun `node registration wrong root cert`() { val someRootCert = X509Utilities.createSelfSignedCACertificate( - CordaX500Name("Integration Test Corda Node Root CA", "R3 Ltd", "London", "GB"), + X500Principal("CN=Integration Test Corda Node Root CA,O=R3 Ltd,L=London,C=GB"), Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)) val compatibilityZone = CompatibilityZoneParams( URL("http://$serverHostAndPort"), publishNotaries = { server.networkParameters = testNetworkParameters(it) }, - rootCert = someRootCert.cert) + rootCert = someRootCert) internalDriver( portAllocation = portAllocation, compatibilityZone = compatibilityZone, @@ -149,7 +148,7 @@ class RegistrationHandler(private val rootCertAndKeyPair: CertificateAndKeyPair) val (certPath, name) = createSignedClientCertificate( certificationRequest, rootCertAndKeyPair.keyPair, - arrayOf(rootCertAndKeyPair.certificate.cert)) + arrayOf(rootCertAndKeyPair.certificate)) require(!name.organisation.contains("\\s".toRegex())) { "Whitespace in the organisation name not supported" } certPaths[name.organisation] = certPath return Response.ok(name.organisation).build() @@ -183,12 +182,12 @@ class RegistrationHandler(private val rootCertAndKeyPair: CertificateAndKeyPair) val name = CordaX500Name.parse(request.subject.toString()) val nodeCaCert = X509Utilities.createCertificate( CertificateType.NODE_CA, - caCertPath.first().toX509CertHolder(), + caCertPath[0] as X509Certificate , caKeyPair, - name, + name.x500Principal, request.publicKey, nameConstraints = null) - val certPath = X509CertificateFactory().generateCertPath(nodeCaCert.cert, *caCertPath) + val certPath = X509CertificateFactory().generateCertPath(nodeCaCert, *caCertPath) return Pair(certPath, name) } } 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 f8da5302a4..8252173ca0 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 @@ -94,22 +94,34 @@ class MQSecurityAsNodeTest : MQSecurityTest() { javaClass.classLoader.getResourceAsStream("certificates/cordadevcakeys.jks"), "cordacadevpass") - val rootCACert = caKeyStore.getX509Certificate(X509Utilities.CORDA_ROOT_CA).toX509CertHolder() + val rootCACert = caKeyStore.getX509Certificate(X509Utilities.CORDA_ROOT_CA) val intermediateCA = caKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_INTERMEDIATE_CA, "cordacadevkeypass") - val clientKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) + val clientKeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) // Set name constrain to the legal name. val nameConstraints = NameConstraints(arrayOf(GeneralSubtree(GeneralName(GeneralName.directoryName, legalName.x500Name))), arrayOf()) - val clientCACert = X509Utilities.createCertificate(CertificateType.INTERMEDIATE_CA, intermediateCA.certificate, - intermediateCA.keyPair, legalName, clientKey.public, nameConstraints = nameConstraints) - val tlsKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) + val clientCACert = X509Utilities.createCertificate( + CertificateType.INTERMEDIATE_CA, + intermediateCA.certificate, + intermediateCA.keyPair, + legalName.x500Principal, + clientKeyPair.public, + nameConstraints = nameConstraints) + + val tlsKeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) // Using different x500 name in the TLS cert which is not allowed in the name constraints. - val clientTLSCert = X509Utilities.createCertificate(CertificateType.TLS, clientCACert, clientKey, CordaX500Name("MiniCorp", "London", "GB"), tlsKey.public) + val clientTLSCert = X509Utilities.createCertificate( + CertificateType.TLS, + clientCACert, + clientKeyPair, + CordaX500Name("MiniCorp", "London", "GB").x500Principal, + tlsKeyPair.public) + val keyPass = keyStorePassword.toCharArray() val clientCAKeystore = loadOrCreateKeyStore(nodeKeystore, keyStorePassword) clientCAKeystore.addOrReplaceKey( X509Utilities.CORDA_CLIENT_CA, - clientKey.private, + clientKeyPair.private, keyPass, arrayOf(clientCACert, intermediateCA.certificate, rootCACert)) clientCAKeystore.save(nodeKeystore, keyStorePassword) @@ -117,7 +129,7 @@ class MQSecurityAsNodeTest : MQSecurityTest() { val tlsKeystore = loadOrCreateKeyStore(sslKeystore, keyStorePassword) tlsKeystore.addOrReplaceKey( X509Utilities.CORDA_CLIENT_TLS, - tlsKey.private, + tlsKeyPair.private, keyPass, arrayOf(clientTLSCert, clientCACert, intermediateCA.certificate, rootCACert)) tlsKeystore.save(sslKeystore, keyStorePassword) 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 aaffb40be2..4ab8030d09 100644 --- a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt @@ -726,7 +726,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration, 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.cert) + val caCertificates = arrayOf(identityCert, clientCa.certificate) return PersistentIdentityService(trustRoot, *caCertificates) } @@ -784,7 +784,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration, listOf(certificate) + keyStore.getCertificateChain(privateKeyAlias).drop(1) } else { keyStore.getCertificateChain(privateKeyAlias).let { - check(it[0].toX509CertHolder() == x509Cert) { "Certificates from key store do not line up!" } + check(it[0] == x509Cert) { "Certificates from key store do not line up!" } it.asList() } } diff --git a/node/src/main/kotlin/net/corda/node/internal/protonwrapper/netty/AMQPChannelHandler.kt b/node/src/main/kotlin/net/corda/node/internal/protonwrapper/netty/AMQPChannelHandler.kt index 0f9e6cd607..f31f9fbe88 100644 --- a/node/src/main/kotlin/net/corda/node/internal/protonwrapper/netty/AMQPChannelHandler.kt +++ b/node/src/main/kotlin/net/corda/node/internal/protonwrapper/netty/AMQPChannelHandler.kt @@ -9,7 +9,6 @@ import io.netty.handler.ssl.SslHandler import io.netty.handler.ssl.SslHandshakeCompletionEvent import io.netty.util.ReferenceCountUtil import net.corda.core.identity.CordaX500Name -import net.corda.core.internal.toX509CertHolder import net.corda.core.utilities.debug import net.corda.node.internal.protonwrapper.engine.EventProcessor import net.corda.node.internal.protonwrapper.messages.ReceivedMessage @@ -19,9 +18,9 @@ import org.apache.qpid.proton.engine.ProtonJTransport import org.apache.qpid.proton.engine.Transport import org.apache.qpid.proton.engine.impl.ProtocolTracer import org.apache.qpid.proton.framing.TransportFrame -import org.bouncycastle.cert.X509CertificateHolder import org.slf4j.LoggerFactory import java.net.InetSocketAddress +import java.security.cert.X509Certificate /** * An instance of AMQPChannelHandler sits inside the netty pipeline and controls the socket level lifecycle. @@ -38,20 +37,20 @@ internal class AMQPChannelHandler(private val serverMode: Boolean, private val onReceive: (ReceivedMessage) -> Unit) : ChannelDuplexHandler() { private val log = LoggerFactory.getLogger(allowedRemoteLegalNames?.firstOrNull()?.toString() ?: "AMQPChannelHandler") private lateinit var remoteAddress: InetSocketAddress - private lateinit var localCert: X509CertificateHolder - private lateinit var remoteCert: X509CertificateHolder + private lateinit var localCert: X509Certificate + private lateinit var remoteCert: X509Certificate private var eventProcessor: EventProcessor? = null override fun channelActive(ctx: ChannelHandlerContext) { val ch = ctx.channel() remoteAddress = ch.remoteAddress() as InetSocketAddress val localAddress = ch.localAddress() as InetSocketAddress - log.info("New client connection ${ch.id()} from ${remoteAddress} to ${localAddress}") + log.info("New client connection ${ch.id()} from $remoteAddress to $localAddress") } private fun createAMQPEngine(ctx: ChannelHandlerContext) { val ch = ctx.channel() - eventProcessor = EventProcessor(ch, serverMode, localCert.subject.toString(), remoteCert.subject.toString(), userName, password) + eventProcessor = EventProcessor(ch, serverMode, localCert.subjectX500Principal.toString(), remoteCert.subjectX500Principal.toString(), userName, password) val connection = eventProcessor!!.connection val transport = connection.transport as ProtonJTransport if (trace) { @@ -71,7 +70,7 @@ internal class AMQPChannelHandler(private val serverMode: Boolean, override fun channelInactive(ctx: ChannelHandlerContext) { val ch = ctx.channel() - log.info("Closed client connection ${ch.id()} from ${remoteAddress} to ${ch.localAddress()}") + log.info("Closed client connection ${ch.id()} from $remoteAddress to ${ch.localAddress()}") onClose(Pair(ch as SocketChannel, ConnectionChange(remoteAddress, null, false))) eventProcessor?.close() ctx.fireChannelInactive() @@ -81,12 +80,12 @@ internal class AMQPChannelHandler(private val serverMode: Boolean, if (evt is SslHandshakeCompletionEvent) { if (evt.isSuccess) { val sslHandler = ctx.pipeline().get(SslHandler::class.java) - localCert = sslHandler.engine().session.localCertificates.first().toX509CertHolder() - remoteCert = sslHandler.engine().session.peerCertificates.first().toX509CertHolder() + localCert = sslHandler.engine().session.localCertificates[0] as X509Certificate + remoteCert = sslHandler.engine().session.peerCertificates[0] as X509Certificate try { - val remoteX500Name = CordaX500Name.parse(remoteCert.subject.toString()) + val remoteX500Name = CordaX500Name.build(remoteCert.subjectX500Principal) require(allowedRemoteLegalNames == null || remoteX500Name in allowedRemoteLegalNames) - log.info("handshake completed subject: ${remoteX500Name}") + log.info("handshake completed subject: $remoteX500Name") } catch (ex: IllegalArgumentException) { log.error("Invalid certificate subject", ex) ctx.close() @@ -124,7 +123,7 @@ internal class AMQPChannelHandler(private val serverMode: Boolean, require(inetAddress == remoteAddress) { "Message for incorrect endpoint" } - require(CordaX500Name.parse(msg.destinationLegalName) == CordaX500Name.parse(remoteCert.subject.toString())) { + require(CordaX500Name.parse(msg.destinationLegalName) == CordaX500Name.build(remoteCert.subjectX500Principal)) { "Message for incorrect legal identity" } log.debug { "channel write ${msg.applicationProperties["_AMQ_DUPL_ID"]}" } diff --git a/node/src/main/kotlin/net/corda/node/internal/protonwrapper/netty/ConnectionChange.kt b/node/src/main/kotlin/net/corda/node/internal/protonwrapper/netty/ConnectionChange.kt index a576a25d2b..f0d83a8cf5 100644 --- a/node/src/main/kotlin/net/corda/node/internal/protonwrapper/netty/ConnectionChange.kt +++ b/node/src/main/kotlin/net/corda/node/internal/protonwrapper/netty/ConnectionChange.kt @@ -1,6 +1,6 @@ package net.corda.node.internal.protonwrapper.netty -import org.bouncycastle.cert.X509CertificateHolder import java.net.InetSocketAddress +import java.security.cert.X509Certificate -data class ConnectionChange(val remoteAddress: InetSocketAddress, val remoteCert: X509CertificateHolder?, val connected: Boolean) \ No newline at end of file +data class ConnectionChange(val remoteAddress: InetSocketAddress, val remoteCert: X509Certificate?, val connected: Boolean) \ No newline at end of file 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 3e31a977ae..c3942b3fc9 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 @@ -11,7 +11,6 @@ import net.corda.core.identity.CordaX500Name import net.corda.core.internal.createDirectories import net.corda.core.internal.div import net.corda.core.internal.exists -import net.corda.core.internal.toX509CertHolder import net.corda.nodeapi.internal.config.SSLConfiguration import net.corda.nodeapi.internal.config.toProperties import net.corda.nodeapi.internal.createDevKeyStores @@ -73,7 +72,7 @@ fun SSLConfiguration.configureDevKeyAndTrustStores(myLegalName: CordaX500Name) { } if (!sslKeystore.exists() || !nodeKeystore.exists()) { val caKeyStore = loadKeyStore(javaClass.classLoader.getResourceAsStream("certificates/cordadevcakeys.jks"), "cordacadevpass") - val rootCert = caKeyStore.getX509Certificate(X509Utilities.CORDA_ROOT_CA).toX509CertHolder() + val rootCert = caKeyStore.getX509Certificate(X509Utilities.CORDA_ROOT_CA) val intermediateCa = caKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_INTERMEDIATE_CA, "cordacadevkeypass") createDevKeyStores(rootCert, intermediateCa, myLegalName) diff --git a/node/src/main/kotlin/net/corda/node/services/identity/InMemoryIdentityService.kt b/node/src/main/kotlin/net/corda/node/services/identity/InMemoryIdentityService.kt index 4654473ef1..4876533003 100644 --- a/node/src/main/kotlin/net/corda/node/services/identity/InMemoryIdentityService.kt +++ b/node/src/main/kotlin/net/corda/node/services/identity/InMemoryIdentityService.kt @@ -4,15 +4,12 @@ import net.corda.core.contracts.PartyAndReference import net.corda.core.crypto.toStringShort import net.corda.core.identity.* import net.corda.core.internal.CertRole -import net.corda.core.internal.cert -import net.corda.core.internal.toX509CertHolder import net.corda.core.node.services.UnknownAnonymousPartyException import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.core.utilities.contextLogger import net.corda.core.utilities.trace import net.corda.node.services.api.IdentityServiceInternal import net.corda.nodeapi.internal.crypto.X509CertificateFactory -import org.bouncycastle.cert.X509CertificateHolder import java.security.InvalidAlgorithmParameterException import java.security.PublicKey import java.security.cert.* @@ -27,7 +24,7 @@ import javax.annotation.concurrent.ThreadSafe // TODO There is duplicated logic between this and PersistentIdentityService @ThreadSafe class InMemoryIdentityService(identities: Array, - trustRoot: X509CertificateHolder) : SingletonSerializeAsToken(), IdentityServiceInternal { + override val trustRoot: X509Certificate) : SingletonSerializeAsToken(), IdentityServiceInternal { companion object { private val log = contextLogger() } @@ -35,14 +32,12 @@ class InMemoryIdentityService(identities: Array, /** * Certificate store for certificate authority and intermediary certificates. */ - override val caCertStore: CertStore - override val trustRoot = trustRoot.cert - override val trustAnchor: TrustAnchor = TrustAnchor(this.trustRoot, null) + override val caCertStore: CertStore = CertStore.getInstance("Collection", CollectionCertStoreParameters(setOf(trustRoot))) + override val trustAnchor: TrustAnchor = TrustAnchor(trustRoot, null) private val keyToParties = ConcurrentHashMap() private val principalToParties = ConcurrentHashMap() init { - caCertStore = CertStore.getInstance("Collection", CollectionCertStoreParameters(setOf(this.trustRoot))) keyToParties.putAll(identities.associateBy { it.owningKey }) principalToParties.putAll(identities.associateBy { it.name }) } @@ -57,7 +52,7 @@ class InMemoryIdentityService(identities: Array, log.warn("Certificate path :") identity.certPath.certificates.reversed().forEachIndexed { index, certificate -> val space = (0 until index).joinToString("") { " " } - log.warn("$space${certificate.toX509CertHolder().subject}") + log.warn("$space${(certificate as X509Certificate).subjectX500Principal}") } throw e } diff --git a/node/src/main/kotlin/net/corda/node/services/identity/PersistentIdentityService.kt b/node/src/main/kotlin/net/corda/node/services/identity/PersistentIdentityService.kt index 83e7b0b267..b36072fd81 100644 --- a/node/src/main/kotlin/net/corda/node/services/identity/PersistentIdentityService.kt +++ b/node/src/main/kotlin/net/corda/node/services/identity/PersistentIdentityService.kt @@ -5,8 +5,6 @@ import net.corda.core.crypto.SecureHash import net.corda.core.crypto.toStringShort import net.corda.core.identity.* import net.corda.core.internal.CertRole -import net.corda.core.internal.cert -import net.corda.core.internal.toX509CertHolder import net.corda.core.node.services.UnknownAnonymousPartyException import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.core.utilities.MAX_HASH_HEX_SIZE @@ -16,7 +14,6 @@ import net.corda.node.services.api.IdentityServiceInternal import net.corda.node.utilities.AppendOnlyPersistentMap import net.corda.nodeapi.internal.crypto.X509CertificateFactory import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX -import org.bouncycastle.cert.X509CertificateHolder import java.security.InvalidAlgorithmParameterException import java.security.PublicKey import java.security.cert.* @@ -30,7 +27,6 @@ import javax.persistence.Lob @ThreadSafe class PersistentIdentityService(override val trustRoot: X509Certificate, vararg caCertificates: X509Certificate) : SingletonSerializeAsToken(), IdentityServiceInternal { - constructor(trustRoot: X509CertificateHolder) : this(trustRoot.cert) companion object { private val log = contextLogger() @@ -121,7 +117,7 @@ class PersistentIdentityService(override val trustRoot: X509Certificate, log.warn(e.localizedMessage) log.warn("Path = ") identity.certPath.certificates.reversed().forEach { - log.warn(it.toX509CertHolder().subject.toString()) + log.warn((it as X509Certificate).subjectX500Principal.toString()) } throw e } 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 df99435dd3..dad6a7b2a7 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 @@ -3,8 +3,6 @@ package net.corda.node.services.keys import net.corda.core.crypto.Crypto import net.corda.core.identity.PartyAndCertificate import net.corda.core.internal.CertRole -import net.corda.core.internal.cert -import net.corda.core.internal.toX509CertHolder import net.corda.core.utilities.days import net.corda.node.services.api.IdentityServiceInternal import net.corda.nodeapi.internal.crypto.CertificateType @@ -36,11 +34,16 @@ fun freshCertificate(identityService: IdentityServiceInternal, revocationEnabled: Boolean = false): PartyAndCertificate { val issuerRole = CertRole.extract(issuer.certificate) require(issuerRole == CertRole.LEGAL_IDENTITY) { "Confidential identities can only be issued from well known identities, provided issuer ${issuer.name} has role $issuerRole" } - val issuerCert = issuer.certificate.toX509CertHolder() + val issuerCert = issuer.certificate val window = X509Utilities.getCertificateValidityWindow(Duration.ZERO, 3650.days, issuerCert) - val ourCertificate = X509Utilities.createCertificate(CertificateType.CONFIDENTIAL_LEGAL_IDENTITY, issuerCert.subject, - issuerSigner, issuer.name, subjectPublicKey, window) - val ourCertPath = X509CertificateFactory().generateCertPath(listOf(ourCertificate.cert) + issuer.certPath.certificates) + val ourCertificate = X509Utilities.createCertificate( + CertificateType.CONFIDENTIAL_LEGAL_IDENTITY, + issuerCert.subjectX500Principal, + issuerSigner, + issuer.name.x500Principal, + subjectPublicKey, + window) + val ourCertPath = X509CertificateFactory().generateCertPath(listOf(ourCertificate) + issuer.certPath.certificates) val anonymisedIdentity = PartyAndCertificate(ourCertPath) identityService.justVerifyAndRegisterIdentity(anonymisedIdentity) return anonymisedIdentity 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 af9735b89a..090eef335c 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 @@ -22,9 +22,9 @@ import java.security.cert.X509Certificate * needed. */ class NetworkRegistrationHelper(private val config: NodeConfiguration, private val certService: NetworkRegistrationService) { - companion object { + private companion object { val pollInterval = 10.seconds - val SELF_SIGNED_PRIVATE_KEY = "Self Signed Private Key" + const val SELF_SIGNED_PRIVATE_KEY = "Self Signed Private Key" } private val requestIdStore = config.certificatesDirectory / "certificate-request-id.txt" @@ -62,54 +62,81 @@ class NetworkRegistrationHelper(private val config: NodeConfiguration, private v */ fun buildKeystore() { config.certificatesDirectory.createDirectories() - val caKeyStore = loadOrCreateKeyStore(config.nodeKeystore, keystorePassword) - if (!caKeyStore.containsAlias(CORDA_CLIENT_CA)) { - // 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 (!caKeyStore.containsAlias(SELF_SIGNED_PRIVATE_KEY)) { - val keyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) - val selfSignCert = X509Utilities.createSelfSignedCACertificate(config.myLegalName, keyPair) - // Save to the key store. - caKeyStore.addOrReplaceKey(SELF_SIGNED_PRIVATE_KEY, keyPair.private, privateKeyPassword.toCharArray(), - arrayOf(selfSignCert)) - caKeyStore.save(config.nodeKeystore, keystorePassword) - } - val keyPair = caKeyStore.getKeyPair(SELF_SIGNED_PRIVATE_KEY, privateKeyPassword) - val requestId = submitOrResumeCertificateSigningRequest(keyPair) - - val certificates = try { - pollServerForCertificates(requestId) - } catch (certificateRequestException: CertificateRequestException) { - System.err.println(certificateRequestException.message) - System.err.println("Please make sure the details in configuration file are correct and try again.") - System.err.println("Corda node will now terminate.") - requestIdStore.deleteIfExists() - throw certificateRequestException - } - - println("Certificate signing request approved, storing private key with the certificate chain.") - // Save private key and certificate chain to the key store. - caKeyStore.addOrReplaceKey(CORDA_CLIENT_CA, keyPair.private, privateKeyPassword.toCharArray(), certificates) - caKeyStore.deleteEntry(SELF_SIGNED_PRIVATE_KEY) - caKeyStore.save(config.nodeKeystore, keystorePassword) - println("Node private key and certificate stored in ${config.nodeKeystore}.") - - println("Checking root of the certificate path is what we expect.") - X509Utilities.validateCertificateChain(rootCert, *certificates) - - println("Generating SSL certificate for node messaging service.") - val sslKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) - val caCert = caKeyStore.getX509Certificate(CORDA_CLIENT_CA).toX509CertHolder() - val sslCert = X509Utilities.createCertificate(CertificateType.TLS, caCert, keyPair, CordaX500Name.build(caCert.cert.subjectX500Principal), sslKey.public) - val sslKeyStore = loadOrCreateKeyStore(config.sslKeystore, keystorePassword) - sslKeyStore.addOrReplaceKey(CORDA_CLIENT_TLS, sslKey.private, privateKeyPassword.toCharArray(), arrayOf(sslCert.cert, *certificates)) - sslKeyStore.save(config.sslKeystore, config.keyStorePassword) - println("SSL private key and certificate stored in ${config.sslKeystore}.") - // All done, clean up temp files. - requestIdStore.deleteIfExists() - } else { + val nodeKeyStore = loadOrCreateKeyStore(config.nodeKeystore, keystorePassword) + if (nodeKeyStore.containsAlias(CORDA_CLIENT_CA)) { 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)) { + 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) + } + + val keyPair = nodeKeyStore.getKeyPair(SELF_SIGNED_PRIVATE_KEY, privateKeyPassword) + val requestId = submitOrResumeCertificateSigningRequest(keyPair) + + val certificates = try { + pollServerForCertificates(requestId) + } catch (certificateRequestException: CertificateRequestException) { + System.err.println(certificateRequestException.message) + System.err.println("Please make sure the details in configuration file are correct and try again.") + System.err.println("Corda node will now terminate.") + requestIdStore.deleteIfExists() + throw certificateRequestException + } + + val nodeCaCert = certificates[0] as X509Certificate + + val nodeCaSubject = try { + CordaX500Name.build(nodeCaCert.subjectX500Principal) + } catch (e: IllegalArgumentException) { + throw CertificateRequestException("Received node CA cert has invalid subject name: ${e.message}") + } + if (nodeCaSubject != config.myLegalName) { + throw CertificateRequestException("Subject of received node CA cert doesn't match with node legal name: $nodeCaSubject") + } + + val nodeCaCertRole = try { + CertRole.extract(nodeCaCert) + } catch (e: IllegalArgumentException) { + throw CertificateRequestException("Unable to extract cert role from received node CA cert: ${e.message}") + } + if (nodeCaCertRole != CertRole.NODE_CA) { + throw CertificateRequestException("Received node CA cert has invalid role: $nodeCaCertRole") + } + + println("Checking root of the certificate path is what we expect.") + X509Utilities.validateCertificateChain(rootCert, *certificates) + + 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) + 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) + println("SSL private key and certificate stored in ${config.sslKeystore}.") + + // All done, clean up temp files. + requestIdStore.deleteIfExists() } /** @@ -138,7 +165,7 @@ class NetworkRegistrationHelper(private val config: NodeConfiguration, private v private fun submitOrResumeCertificateSigningRequest(keyPair: KeyPair): String { // Retrieve request id from file if exists, else post a request to server. return if (!requestIdStore.exists()) { - val request = X509Utilities.createCertificateSigningRequest(config.myLegalName, config.emailAddress, keyPair) + val request = X509Utilities.createCertificateSigningRequest(config.myLegalName.x500Principal, config.emailAddress, keyPair) val writer = StringWriter() JcaPEMWriter(writer).use { it.writeObject(PemObject("CERTIFICATE REQUEST", request.encoded)) diff --git a/node/src/test/kotlin/net/corda/node/services/identity/InMemoryIdentityServiceTests.kt b/node/src/test/kotlin/net/corda/node/services/identity/InMemoryIdentityServiceTests.kt index 30717cdad4..9131143e9f 100644 --- a/node/src/test/kotlin/net/corda/node/services/identity/InMemoryIdentityServiceTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/identity/InMemoryIdentityServiceTests.kt @@ -6,8 +6,6 @@ import net.corda.core.identity.AnonymousParty import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party import net.corda.core.identity.PartyAndCertificate -import net.corda.core.internal.cert -import net.corda.core.internal.toX509CertHolder import net.corda.core.node.services.UnknownAnonymousPartyException import net.corda.nodeapi.internal.crypto.CertificateType import net.corda.nodeapi.internal.crypto.X509CertificateFactory @@ -32,7 +30,7 @@ class InMemoryIdentityServiceTests { val BOB get() = bob.party val BOB_IDENTITY get() = bob.identity val BOB_PUBKEY get() = bob.publicKey - fun createService(vararg identities: PartyAndCertificate) = InMemoryIdentityService(identities, DEV_TRUST_ROOT) + fun createService(vararg identities: PartyAndCertificate) = InMemoryIdentityService(identities, DEV_ROOT_CA.certificate) } @Rule @@ -100,11 +98,11 @@ class InMemoryIdentityServiceTests { @Test fun `assert unknown anonymous key is unrecognised`() { val rootKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) - val rootCert = X509Utilities.createSelfSignedCACertificate(ALICE.name, rootKey) + val rootCert = X509Utilities.createSelfSignedCACertificate(ALICE.name.x500Principal, rootKey) val txKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) val service = createService() // TODO: Generate certificate with an EdDSA key rather than ECDSA - val identity = Party(rootCert.cert) + val identity = Party(rootCert) val txIdentity = AnonymousParty(txKey.public) assertFailsWith { @@ -159,8 +157,8 @@ class InMemoryIdentityServiceTests { } assertFailsWith { - val owningKey = Crypto.decodePublicKey(DEV_CA.certificate.subjectPublicKeyInfo.encoded) - val subject = CordaX500Name.build(DEV_CA.certificate.cert.subjectX500Principal) + val owningKey = DEV_INTERMEDIATE_CA.certificate.publicKey + val subject = CordaX500Name.build(DEV_INTERMEDIATE_CA.certificate.subjectX500Principal) service.assertOwnership(Party(subject, owningKey), anonymousAlice.party.anonymise()) } } @@ -168,9 +166,14 @@ class InMemoryIdentityServiceTests { private fun createConfidentialIdentity(x500Name: CordaX500Name): Pair { val issuerKeyPair = generateKeyPair() val issuer = getTestPartyAndCertificate(x500Name, issuerKeyPair.public) - val txKey = Crypto.generateKeyPair() - val txCert = X509Utilities.createCertificate(CertificateType.CONFIDENTIAL_LEGAL_IDENTITY, issuer.certificate.toX509CertHolder(), issuerKeyPair, x500Name, txKey.public) - val txCertPath = X509CertificateFactory().generateCertPath(listOf(txCert.cert) + issuer.certPath.certificates) + val txKeyPair = Crypto.generateKeyPair() + val txCert = X509Utilities.createCertificate( + CertificateType.CONFIDENTIAL_LEGAL_IDENTITY, + issuer.certificate, + issuerKeyPair, + x500Name.x500Principal, + txKeyPair.public) + val txCertPath = X509CertificateFactory().generateCertPath(listOf(txCert) + issuer.certPath.certificates) return Pair(issuer, PartyAndCertificate(txCertPath)) } diff --git a/node/src/test/kotlin/net/corda/node/services/identity/PersistentIdentityServiceTests.kt b/node/src/test/kotlin/net/corda/node/services/identity/PersistentIdentityServiceTests.kt index 2553d30ef5..5d6cc41ce1 100644 --- a/node/src/test/kotlin/net/corda/node/services/identity/PersistentIdentityServiceTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/identity/PersistentIdentityServiceTests.kt @@ -6,8 +6,6 @@ import net.corda.core.identity.AnonymousParty import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party import net.corda.core.identity.PartyAndCertificate -import net.corda.core.internal.cert -import net.corda.core.internal.toX509CertHolder import net.corda.core.node.services.IdentityService import net.corda.core.node.services.UnknownAnonymousPartyException import net.corda.node.internal.configureDatabase @@ -50,7 +48,7 @@ class PersistentIdentityServiceTests { @Before fun setup() { - identityService = PersistentIdentityService(DEV_TRUST_ROOT) + identityService = PersistentIdentityService(DEV_ROOT_CA.certificate) database = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(), identityService) } @@ -143,9 +141,9 @@ class PersistentIdentityServiceTests { @Test fun `assert unknown anonymous key is unrecognised`() { val rootKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) - val rootCert = X509Utilities.createSelfSignedCACertificate(ALICE.name, rootKey) + val rootCert = X509Utilities.createSelfSignedCACertificate(ALICE.name.x500Principal, rootKey) val txKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_IDENTITY_SIGNATURE_SCHEME) - val identity = Party(rootCert.cert) + val identity = Party(rootCert) val txIdentity = AnonymousParty(txKey.public) assertFailsWith { @@ -219,9 +217,9 @@ class PersistentIdentityServiceTests { } assertFailsWith { - val owningKey = Crypto.decodePublicKey(DEV_CA.certificate.subjectPublicKeyInfo.encoded) + val owningKey = DEV_INTERMEDIATE_CA.certificate.publicKey database.transaction { - val subject = CordaX500Name.build(DEV_CA.certificate.cert.subjectX500Principal) + val subject = CordaX500Name.build(DEV_INTERMEDIATE_CA.certificate.subjectX500Principal) identityService.assertOwnership(Party(subject, owningKey), anonymousAlice.party.anonymise()) } } @@ -243,7 +241,7 @@ class PersistentIdentityServiceTests { // Create new identity service mounted onto same DB val newPersistentIdentityService = database.transaction { - PersistentIdentityService(DEV_TRUST_ROOT) + PersistentIdentityService(DEV_ROOT_CA.certificate) } database.transaction { @@ -266,8 +264,8 @@ class PersistentIdentityServiceTests { val issuerKeyPair = generateKeyPair() val issuer = getTestPartyAndCertificate(x500Name, issuerKeyPair.public) val txKey = Crypto.generateKeyPair() - val txCert = X509Utilities.createCertificate(CertificateType.CONFIDENTIAL_LEGAL_IDENTITY, issuer.certificate.toX509CertHolder(), issuerKeyPair, x500Name, txKey.public) - val txCertPath = X509CertificateFactory().generateCertPath(listOf(txCert.cert) + issuer.certPath.certificates) + val txCert = X509Utilities.createCertificate(CertificateType.CONFIDENTIAL_LEGAL_IDENTITY, issuer.certificate, issuerKeyPair, x500Name.x500Principal, txKey.public) + val txCertPath = X509CertificateFactory().generateCertPath(listOf(txCert) + issuer.certPath.certificates) return Pair(issuer, PartyAndCertificate(txCertPath)) } diff --git a/node/src/test/kotlin/net/corda/node/services/network/NetworkMapClientTest.kt b/node/src/test/kotlin/net/corda/node/services/network/NetworkMapClientTest.kt index c609d59f2c..9f342b2e0b 100644 --- a/node/src/test/kotlin/net/corda/node/services/network/NetworkMapClientTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/network/NetworkMapClientTest.kt @@ -2,12 +2,11 @@ package net.corda.node.services.network import net.corda.core.crypto.SecureHash import net.corda.core.crypto.sha256 -import net.corda.core.internal.cert import net.corda.core.serialization.serialize import net.corda.core.utilities.seconds import net.corda.testing.ALICE_NAME import net.corda.testing.BOB_NAME -import net.corda.testing.DEV_TRUST_ROOT +import net.corda.testing.DEV_ROOT_CA import net.corda.testing.SerializationEnvironmentRule import net.corda.testing.driver.PortAllocation import net.corda.testing.internal.createNodeInfoAndSigned @@ -35,7 +34,7 @@ class NetworkMapClientTest { fun setUp() { server = NetworkMapServer(cacheTimeout, PortAllocation.Incremental(10000).nextHostAndPort()) val hostAndPort = server.start() - networkMapClient = NetworkMapClient(URL("http://${hostAndPort.host}:${hostAndPort.port}"), DEV_TRUST_ROOT.cert) + networkMapClient = NetworkMapClient(URL("http://${hostAndPort.host}:${hostAndPort.port}"), DEV_ROOT_CA.certificate) } @After diff --git a/node/src/test/kotlin/net/corda/node/utilities/TLSAuthenticationTests.kt b/node/src/test/kotlin/net/corda/node/utilities/TLSAuthenticationTests.kt index 98ff559fe8..ef933d5ccc 100644 --- a/node/src/test/kotlin/net/corda/node/utilities/TLSAuthenticationTests.kt +++ b/node/src/test/kotlin/net/corda/node/utilities/TLSAuthenticationTests.kt @@ -18,6 +18,7 @@ import java.net.ServerSocket import java.nio.file.Path import java.security.KeyStore import javax.net.ssl.* +import javax.security.auth.x500.X500Principal import kotlin.concurrent.thread import kotlin.test.* @@ -51,9 +52,9 @@ class TLSAuthenticationTests { val tempFolder: TemporaryFolder = TemporaryFolder() // Root CA. - private val ROOT_X500 = CordaX500Name(commonName = "Root_CA_1", organisation = "R3CEV", locality = "London", country = "GB") + private val ROOT_X500 = X500Principal("CN=Root_CA_1,O=R3CEV,L=London,C=GB") // Intermediate CA. - private val INTERMEDIATE_X500 = CordaX500Name(commonName = "Intermediate_CA_1", organisation = "R3CEV", locality = "London", country = "GB") + private val INTERMEDIATE_X500 = X500Principal("CN=Intermediate_CA_1,O=R3CEV,L=London,C=GB") // TLS server (client1). private val CLIENT_1_X500 = CordaX500Name(commonName = "Client_1", organisation = "R3CEV", locality = "London", country = "GB") // TLS client (client2). @@ -274,7 +275,7 @@ class TLSAuthenticationTests { CertificateType.NODE_CA, intermediateCACert, intermediateCAKeyPair, - CLIENT_1_X500, + CLIENT_1_X500.x500Principal, client1CAKeyPair.public ) @@ -283,7 +284,7 @@ class TLSAuthenticationTests { CertificateType.TLS, client1CACert, client1CAKeyPair, - CLIENT_1_X500, + CLIENT_1_X500.x500Principal, client1TLSKeyPair.public ) @@ -301,7 +302,7 @@ class TLSAuthenticationTests { CertificateType.NODE_CA, intermediateCACert, intermediateCAKeyPair, - CLIENT_2_X500, + CLIENT_2_X500.x500Principal, client2CAKeyPair.public ) @@ -310,7 +311,7 @@ class TLSAuthenticationTests { CertificateType.TLS, client2CACert, client2CAKeyPair, - CLIENT_2_X500, + CLIENT_2_X500.x500Principal, client2TLSKeyPair.public ) @@ -323,8 +324,8 @@ class TLSAuthenticationTests { // client2TLSKeyStore.save(client2TLSKeyStorePath, PASSWORD) val trustStore = loadOrCreateKeyStore(trustStorePath, PASSWORD) - trustStore.addOrReplaceCertificate(X509Utilities.CORDA_ROOT_CA, rootCACert.cert) - trustStore.addOrReplaceCertificate(X509Utilities.CORDA_INTERMEDIATE_CA, intermediateCACert.cert) + trustStore.addOrReplaceCertificate(X509Utilities.CORDA_ROOT_CA, rootCACert) + trustStore.addOrReplaceCertificate(X509Utilities.CORDA_INTERMEDIATE_CA, intermediateCACert) // trustStore.save(trustStorePath, PASSWORD) val client1SSLContext = sslContext(client1TLSKeyStore, PASSWORD, trustStore) 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 13113fccf9..f56c82a6cf 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 @@ -9,21 +9,23 @@ import com.nhaarman.mockito_kotlin.whenever import net.corda.core.crypto.Crypto import net.corda.core.crypto.SecureHash import net.corda.core.identity.CordaX500Name -import net.corda.core.internal.cert 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.testing.ALICE_NAME -import net.corda.testing.internal.createDevNodeCaCertPath +import net.corda.testing.internal.createDevIntermediateCaCertPath import net.corda.testing.internal.rigorousMock -import org.assertj.core.api.Assertions.assertThat -import org.assertj.core.api.Assertions.assertThatThrownBy +import org.assertj.core.api.Assertions.* +import org.bouncycastle.asn1.x509.GeneralName +import org.bouncycastle.asn1.x509.GeneralSubtree +import org.bouncycastle.asn1.x509.NameConstraints import org.junit.After import org.junit.Before import org.junit.Test import java.security.cert.CertPathValidatorException -import java.security.cert.Certificate import java.security.cert.X509Certificate +import javax.security.auth.x500.X500Principal import kotlin.test.assertFalse import kotlin.test.assertTrue @@ -32,18 +34,10 @@ class NetworkRegistrationHelperTest { private val requestId = SecureHash.randomSHA256().toString() private val nodeLegalName = ALICE_NAME - private lateinit var rootCaCert: X509Certificate - private lateinit var intermediateCaCert: X509Certificate - private lateinit var nodeCaCert: X509Certificate private lateinit var config: NodeConfiguration @Before fun init() { - val (rootCa, intermediateCa, nodeCa) = createDevNodeCaCertPath(nodeLegalName) - this.rootCaCert = rootCa.certificate.cert - this.intermediateCaCert = intermediateCa.certificate.cert - this.nodeCaCert = nodeCa.certificate.cert - val baseDirectory = fs.getPath("/baseDir").createDirectories() abstract class AbstractNodeConfiguration : NodeConfiguration config = rigorousMock().also { @@ -66,9 +60,10 @@ class NetworkRegistrationHelperTest { assertThat(config.sslKeystore).doesNotExist() assertThat(config.trustStoreFile).doesNotExist() - saveTrustStoreWithRootCa(rootCaCert) + val nodeCaCertPath = createNodeCaCertPath() - createRegistrationHelper().buildKeystore() + saveTrustStoreWithRootCa(nodeCaCertPath.last()) + createRegistrationHelper(nodeCaCertPath).buildKeystore() val nodeKeystore = loadKeyStore(config.nodeKeystore, config.keyStorePassword) val sslKeystore = loadKeyStore(config.sslKeystore, config.keyStorePassword) @@ -79,8 +74,7 @@ class NetworkRegistrationHelperTest { assertFalse(containsAlias(X509Utilities.CORDA_INTERMEDIATE_CA)) assertFalse(containsAlias(X509Utilities.CORDA_ROOT_CA)) assertFalse(containsAlias(X509Utilities.CORDA_CLIENT_TLS)) - val nodeCaCertChain = getCertificateChain(X509Utilities.CORDA_CLIENT_CA) - assertThat(nodeCaCertChain).containsExactly(nodeCaCert, intermediateCaCert, rootCaCert) + assertThat(getCertificateChain(X509Utilities.CORDA_CLIENT_CA)).containsExactly(*nodeCaCertPath) } sslKeystore.run { @@ -92,40 +86,77 @@ class NetworkRegistrationHelperTest { 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(nodeCaCert, intermediateCaCert, rootCaCert) + assertThat(nodeTlsCertChain.drop(1)).containsExactly(*nodeCaCertPath) } trustStore.run { assertFalse(containsAlias(X509Utilities.CORDA_CLIENT_CA)) assertFalse(containsAlias(X509Utilities.CORDA_INTERMEDIATE_CA)) assertTrue(containsAlias(X509Utilities.CORDA_ROOT_CA)) - val trustStoreRootCaCert = getCertificate(X509Utilities.CORDA_ROOT_CA) - assertThat(trustStoreRootCaCert).isEqualTo(rootCaCert) + assertThat(getCertificate(X509Utilities.CORDA_ROOT_CA)).isEqualTo(nodeCaCertPath.last()) } } @Test fun `missing truststore`() { + val nodeCaCertPath = createNodeCaCertPath() assertThatThrownBy { - createRegistrationHelper() + createRegistrationHelper(nodeCaCertPath) }.hasMessageContaining("This file must contain the root CA cert of your compatibility zone. Please contact your CZ operator.") } + @Test + fun `node CA with incorrect cert role`() { + val nodeCaCertPath = createNodeCaCertPath(type = CertificateType.TLS) + saveTrustStoreWithRootCa(nodeCaCertPath.last()) + val registrationHelper = createRegistrationHelper(nodeCaCertPath) + assertThatExceptionOfType(CertificateRequestException::class.java) + .isThrownBy { registrationHelper.buildKeystore() } + .withMessageContaining(CertificateType.TLS.toString()) + } + + @Test + fun `node CA with incorrect subject`() { + val invalidName = CordaX500Name("Foo", "MU", "GB") + val nodeCaCertPath = createNodeCaCertPath(legalName = invalidName) + saveTrustStoreWithRootCa(nodeCaCertPath.last()) + val registrationHelper = createRegistrationHelper(nodeCaCertPath) + assertThatExceptionOfType(CertificateRequestException::class.java) + .isThrownBy { registrationHelper.buildKeystore() } + .withMessageContaining(invalidName.toString()) + } + @Test fun `wrong root cert in truststore`() { - val rootKeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) - val rootCert = X509Utilities.createSelfSignedCACertificate(CordaX500Name("Foo", "MU", "GB"), rootKeyPair) - saveTrustStoreWithRootCa(rootCert.cert) - val registrationHelper = createRegistrationHelper() + val wrongRootCert = X509Utilities.createSelfSignedCACertificate( + X500Principal("O=Foo,L=MU,C=GB"), + Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)) + saveTrustStoreWithRootCa(wrongRootCert) + val registrationHelper = createRegistrationHelper(createNodeCaCertPath()) assertThatThrownBy { registrationHelper.buildKeystore() }.isInstanceOf(CertPathValidatorException::class.java) } - private fun createRegistrationHelper(): NetworkRegistrationHelper { + private fun createNodeCaCertPath(type: CertificateType = CertificateType.NODE_CA, + legalName: CordaX500Name = nodeLegalName): Array { + val (rootCa, intermediateCa) = createDevIntermediateCaCertPath() + val keyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) + val nameConstraints = NameConstraints(arrayOf(GeneralSubtree(GeneralName(GeneralName.directoryName, legalName.x500Name))), arrayOf()) + val nodeCaCert = X509Utilities.createCertificate( + type, + intermediateCa.certificate, + intermediateCa.keyPair, + legalName.x500Principal, + keyPair.public, + nameConstraints = nameConstraints) + return arrayOf(nodeCaCert, intermediateCa.certificate, rootCa.certificate) + } + + private fun createRegistrationHelper(response: Array): NetworkRegistrationHelper { val certService = rigorousMock().also { doReturn(requestId).whenever(it).submitRequest(any()) - doReturn(arrayOf(nodeCaCert, intermediateCaCert, rootCaCert)).whenever(it).retrieveCertificates(eq(requestId)) + doReturn(response).whenever(it).retrieveCertificates(eq(requestId)) } return NetworkRegistrationHelper(config, certService) } diff --git a/samples/irs-demo/README.md b/samples/irs-demo/README.md index 12af545191..a22d7acaaf 100644 --- a/samples/irs-demo/README.md +++ b/samples/irs-demo/README.md @@ -9,11 +9,12 @@ webapp which provides REST API and web frontend. Application communicate using C To run from the command line in Unix: 1. Run ``./gradlew samples:irs-demo:cordapp:deployNodes`` to install configs and a command line tool under - ``samples/irs-demo/build`` + ``samples/irs-demo/cordapp/build`` 2. Run ``./gradlew samples:irs-demo:web:deployWebapps`` to install configs and tools for running webservers 3. Move to the ``samples/irs-demo/`` directory 4. Run ``./cordapp/build/nodes/runnodes`` to open up three new terminals with the three nodes (you may have to install xterm) -5. Run ``./web/build/webapps/runwebapps`` to open three more terminals for associated webserver +5. On Linux, run ``./web/build/webapps/runwebapps`` to open three more terminals for associated webservers. On macOS, + use the following command instead: ``osascript ./web/build/webapps/runwebapps.scpt`` To run from the command line in Windows: diff --git a/samples/simm-valuation-demo/README.md b/samples/simm-valuation-demo/README.md index 7931c876e0..8237872b33 100644 --- a/samples/simm-valuation-demo/README.md +++ b/samples/simm-valuation-demo/README.md @@ -1,8 +1,180 @@ -# SIMM Valuation Demo +# SIMM and Portfolio Demo - aka the Initial Margin Agreement Demo -See our [main documentation site](https://docs.corda.net/initial-margin-agreement.html) regarding the SIMM valuation and agreement on a distributed ledger. +## Background and SIMM Introduction + +This app is a demonstration of how Corda can be used for the real world requirement of initial margin calculation and +agreement; featuring the integration of complex and industry proven third party libraries into Corda nodes. + +SIMM is an acronym for "Standard Initial Margin Model". It is effectively the calculation of a "margin" that is paid +by one party to another when they agree a trade on certain types of transaction. + +The SIMM was introduced to standardise the calculation of how much margin counterparties charge each other on their +bilateral transactions. Before SIMM, each counterparty computed margins according to its own model and it was made it very + difficult to agree the exact margin with the counterparty that faces the same trade on the other side. + +To enact this, in September 2016, the ISDA committee - with full backing from various governing bodies - +[issued a ruling on what is known as the ISDA SIMM ™ model](http://www2.isda.org/news/isda-simm-deployed-today-new-industry-standard-for-calculating-initial-margin-widely-adopted-by-market-participants) +a way of fairly and consistently calculating this margin. Any parties wishing to trade a financial product that is +covered under this ruling would, independently, use this model and calculate their margin payment requirement, +agree it with their trading counterparty and then pay (or receive, depending on the results of this calculation) +this amount. In the case of disagreement that is not resolved in a timely fashion, this payment would increase +and so therefore it is in the parties' interest to reach agreement in as short as time frame as possible. + +To be more accurate, the SIMM calculation is not performed on just one trade - it is calculated on an aggregate of +intermediary values (which in this model are sensitivities to risk factors) from a portfolio of trades; therefore +the input to a SIMM is actually this data, not the individual trades themselves. + +Also note that implementations of the SIMM are actually protected and subject to license restrictions by ISDA +(this is due to the model itself being protected). We were fortunate enough to technically partner with +[OpenGamma](http://www.opengamma.com) who allowed us to demonstrate the SIMM process using their proprietary model. +In the source code released, we have replaced their analytics engine with very simple stub functions that allow +the process to run without actually calculating correct values, and can easily be swapped out in place for their real libraries. + +## What happens in the demo (notionally) +Preliminaries + - Ensure that there are a number of live trades with another party based on financial products that are covered under the + ISDA SIMM agreement (if none, then use the demo to enter some simple trades as described below). + +Initial Margin Agreement Process + - Agree that one will be performing the margining calculation against a portfolio of trades with another party, and agree the trades in that portfolio. In practice, one node will start the flow but it does not matter which node does. + - Individually (at the node level), identify the data (static, reference etc) one will need in order to be able to calculate the metrics on those trades + - Confirm with the other counterparty the dataset from the above set + - Calculate any intermediary steps and values needed for the margin calculation (ie sensitivities to risk factors) + - Agree on the results of these steps + - Calculate the initial margin + - Agree on the calculation of the above with the other party + - In practice, pay (or receive) this margin (omitted for the sake of complexity for this example) + +## Demo execution (step by step) + + +**Setting up the Corda infrastructure** + +To run from the command line in Unix: + +1. Deploy the nodes using ``./gradlew samples:simm-valuation-demo:deployNodes`` +2. Run the nodes using ``./samples/simm-valuation-demo/build/nodes/runnodes`` + +To run from the command line in Windows: + +1. Deploy the nodes using ``gradlew samples:simm-valuation-demo:deployNodes`` +2. Run the nodes using ``samples\simm-valuation-demo\build\nodes\runnodes`` + +**Getting Bank A's details** + +From the command line run + + curl http://localhost:10005/api/simmvaluationdemo/whoami + +The response should be something like + + { + "self" : { + "id" : "8Kqd4oWdx4KQGHGQW3FwXHQpjiv7cHaSsaAWMwRrK25bBJj792Z4rag7EtA", + "text" : "C=GB,L=London,O=Bank A" + }, + "counterparties" : [ + { + "id" : "8Kqd4oWdx4KQGHGL1DzULumUmZyyokeSGJDY1n5M6neUfAj2sjbf65wYwQM", + "text" : "C=JP,L=Tokyo,O=Bank C" + }, + { + "id" : "8Kqd4oWdx4KQGHGTBm34eCM2nrpcWKeM1ZG3DUYat3JTFUQTwB3Lv2WbPM8", + "text" : "C=US,L=New York,O=Bank B" + } + ] + } + +Now, if we ask the same question of Bank C we will see that it's id matches the id for Bank C as a counter +party to Bank A and Bank A will appear as a counter party + + curl -i -H "Content-Type: application/json" -X GET http://localhost:10011/api/simmvaluationdemo/whoami + +**Creating a trade with Bank C** + +In what follows, we assume we are Bank A (which is listening on port 10005) + +Notice the id field in the output of the ``whoami`` command. We are going to use the id assocatied +with Bank C, one of our counter parties, to create a trade. The general command for this is: + + curl -i -H "Content-Type: application/json" -X PUT -d <<>> http://localhost:10005/api/simmvaluationdemo/<<>>/trades + +where the representation of the trade is + + + { + "id" : "trade1", + "description" : "desc", + "tradeDate" : [ 2016, 6, 6 ], + "convention" : "EUR_FIXED_1Y_EURIBOR_3M", + "startDate" : [ 2016, 6, 6 ], + "endDate" : [ 2020, 1, 2 ], + "buySell" : "BUY", + "notional" : "1000", + "fixedRate" : "0.1" + } + +Continuing our example, the specific command we would run is + + curl -i -H "Content-Type: application/json" \ + -X PUT \ + -d '{"id":"trade1","description" : "desc","tradeDate" : [ 2016, 6, 6 ], "convention" : "EUR_FIXED_1Y_EURIBOR_3M", "startDate" : [ 2016, 6, 6 ], "endDate" : [ 2020, 1, 2 ], "buySell" : "BUY", "notional" : "1000", "fixedRate" : "0.1"}' \ + http://localhost:10005/api/simmvaluationdemo/8Kqd4oWdx4KQGHGL1DzULumUmZyyokeSGJDY1n5M6neUfAj2sjbf65wYwQM/trades + +With an expected response of + + HTTP/1.1 202 Accepted + Date: Thu, 28 Sep 2017 17:19:39 GMT + Content-Type: text/plain + Access-Control-Allow-Origin: * + Content-Length: 2 + Server: Jetty(9.3.9.v20160517) + +**Verifying trade completion** + +With the trade completed and stored by both parties, the complete list of trades with our couterparty can be seen with the following command + + curl -X GET http://localhost:10005/api/simmvaluationdemo/<<>>/trades + +The command for our example, using Bank A, would thus be + + curl -X GET http://localhost:10005/api/simmvaluationdemo/8Kqd4oWdx4KQGHGL1DzULumUmZyyokeSGJDY1n5M6neUfAj2sjbf65wYwQM/trades + +whilst a specific trade can be seen with + + + curl -X GET http://localhost:10005/api/simmvaluationdemo/<<>>/trades/<<>> + +If we look at the trade we created above, we assigned it the id "trade1", the complete command in this case would be + + curl -X GET http://localhost:10005/api/simmvaluationdemo/8Kqd4oWdx4KQGHGL1DzULumUmZyyokeSGJDY1n5M6neUfAj2sjbf65wYwQM/trades/trade1 + +**Generating a valuation** + + curl -i -H "Content-Type: application/json" \ + -X POST \ + -d <<>> + http://localhost:10005/api/simmvaluationdemo/<<>>/portfolio/valuations/calculate + +Again, the specific command to continue our example would be + + curl -i -H "Content-Type: application/json" \ + -X POST \ + -d '{"valuationDate":[2016,6,6]}' \ + http://localhost:10005/api/simmvaluationdemo/8Kqd4oWdx4KQGHGL1DzLumUmZyyokeSGJDY1n5M6neUfAj2sjbf65wYwQM/portfolio/valuations/calculate + +**Viewing a valuation** + +In the same way we can ask for specific instances of trades with a counter party, we can request details of valuations + + curl -i -H "Content-Type: application/json" -X GET http://localhost:10005/api/simmvaluationdemo/<<>>/portfolio/valuations + +The specific command for out Bank A example is + + curl -i -H "Content-Type: application/json" \ + -X GET http://localhost:10005/api/simmvaluationdemo/8Kqd4oWdx4KQGHGL1DzULumUmZyyokeSGJDY1n5M6neUfAj2sjbf65YwQM/portfolio/valuations ## SIMM Library Licensing diff --git a/testing/node-driver/build.gradle b/testing/node-driver/build.gradle index a13ec48018..fb75da9ab7 100644 --- a/testing/node-driver/build.gradle +++ b/testing/node-driver/build.gradle @@ -2,6 +2,7 @@ apply plugin: 'kotlin' apply plugin: 'kotlin-jpa' apply plugin: 'net.corda.plugins.quasar-utils' apply plugin: 'net.corda.plugins.publish-utils' +apply plugin: 'net.corda.plugins.api-scanner' apply plugin: 'com.jfrog.artifactory' //noinspection GroovyAssignabilityCheck diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt index e9808f518e..6c558bf529 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt @@ -3,6 +3,7 @@ package net.corda.testing.driver import net.corda.client.rpc.CordaRPCClient +import net.corda.core.DoNotImplement import net.corda.core.concurrent.CordaFuture import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party @@ -30,6 +31,7 @@ import java.util.concurrent.atomic.AtomicInteger */ data class NotaryHandle(val identity: Party, val validating: Boolean, val nodeHandles: CordaFuture>) +@DoNotImplement sealed class NodeHandle { abstract val nodeInfo: NodeInfo /** @@ -90,6 +92,7 @@ data class WebserverHandle( val process: Process ) +@DoNotImplement sealed class PortAllocation { abstract fun nextPort(): Int fun nextHostAndPort() = NetworkHostAndPort("localhost", nextPort()) diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/DriverDSL.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/DriverDSL.kt index d8d3d1b49a..a4908d5f53 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/DriverDSL.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/DriverDSL.kt @@ -1,5 +1,6 @@ package net.corda.testing.driver +import net.corda.core.DoNotImplement import net.corda.core.concurrent.CordaFuture import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party @@ -10,6 +11,7 @@ import net.corda.nodeapi.internal.config.User import net.corda.testing.node.NotarySpec import java.nio.file.Path +@DoNotImplement interface DriverDSL { /** Returns a list of [NotaryHandle]s matching the list of [NotarySpec]s passed into [driver]. */ val notaryHandles: List diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/InMemoryMessagingNetwork.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/InMemoryMessagingNetwork.kt index 4ccf3ab00a..3d0dd89a58 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/InMemoryMessagingNetwork.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/InMemoryMessagingNetwork.kt @@ -1,5 +1,6 @@ package net.corda.testing.node +import net.corda.core.DoNotImplement import net.corda.core.crypto.CompositeKey import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party @@ -123,7 +124,7 @@ class InMemoryMessagingNetwork internal constructor( } } - interface LatencyCalculator { + interface LatencyCalculator { // XXX: Used? fun between(sender: SingleMessageRecipient, receiver: SingleMessageRecipient): Duration } @@ -181,6 +182,7 @@ class InMemoryMessagingNetwork internal constructor( /** * Mock service loadbalancing */ + @DoNotImplement sealed class ServicePeerAllocationStrategy { abstract fun pickNext(service: ServiceHandle, pickFrom: List): A class Random(val random: SplittableRandom = SplittableRandom()) : ServicePeerAllocationStrategy() { diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt index 430e5ecc9b..289ca46330 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt @@ -4,6 +4,7 @@ import com.google.common.jimfs.Configuration.unix import com.google.common.jimfs.Jimfs import com.nhaarman.mockito_kotlin.doReturn import com.nhaarman.mockito_kotlin.whenever +import net.corda.core.DoNotImplement import net.corda.core.crypto.entropyToKeyPair import net.corda.core.crypto.random63BitValue import net.corda.core.identity.CordaX500Name @@ -492,6 +493,7 @@ fun StartedNode.setMessagingServiceSpy(messagingServiceSpy } private fun mockNodeConfiguration(): NodeConfiguration { + @DoNotImplement abstract class AbstractNodeConfiguration : NodeConfiguration return rigorousMock().also { doReturn("cordacadevpass").whenever(it).keyStorePassword diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt index cc4dfb2bef..651aadf123 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt @@ -23,6 +23,7 @@ import net.corda.core.transactions.SignedTransaction import net.corda.node.VersionInfo import net.corda.node.internal.configureDatabase import net.corda.node.internal.cordapp.CordappLoader +import net.corda.node.services.api.IdentityServiceInternal import net.corda.node.services.api.SchemaService import net.corda.node.services.api.VaultServiceInternal import net.corda.node.services.api.WritableTransactionStorage @@ -34,12 +35,12 @@ import net.corda.node.services.schema.HibernateObserver import net.corda.node.services.schema.NodeSchemaService import net.corda.node.services.transactions.InMemoryTransactionVerifierService import net.corda.node.services.vault.NodeVaultService -import net.corda.node.services.api.IdentityServiceInternal 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.persistence.TransactionIsolationLevel -import net.corda.testing.* +import net.corda.testing.DEV_ROOT_CA +import net.corda.testing.TestIdentity import net.corda.testing.database.DatabaseConstants import net.corda.testing.database.DatabaseConstants.DATA_SOURCE_CLASSNAME import net.corda.testing.database.DatabaseConstants.DATA_SOURCE_PASSWORD @@ -59,7 +60,7 @@ import java.sql.Connection import java.time.Clock import java.util.* -fun makeTestIdentityService(vararg identities: PartyAndCertificate) = InMemoryIdentityService(identities, DEV_TRUST_ROOT) +fun makeTestIdentityService(vararg identities: PartyAndCertificate) = InMemoryIdentityService(identities, DEV_ROOT_CA.certificate) /** * A singleton utility that only provides a mock identity, key and storage service. However, this is sufficient for * building chains of transactions and verifying them. It isn't sufficient for testing flows however. @@ -197,7 +198,7 @@ class MockKeyManagementService(val identityService: IdentityServiceInternal, override val keys: Set get() = keyStore.keys - val nextKeys = LinkedList() + private val nextKeys = LinkedList() override fun freshKey(): PublicKey { val k = nextKeys.poll() ?: generateKeyPair() @@ -259,11 +260,10 @@ open class MockTransactionStorage : WritableTransactionStorage, SingletonSeriali } fun createMockCordaService(serviceHub: MockServices, serviceConstructor: (AppServiceHub) -> T): T { - class MockAppServiceHubImpl(val serviceHub: MockServices, serviceConstructor: (AppServiceHub) -> T) : AppServiceHub, ServiceHub by serviceHub { - val serviceInstance: T + class MockAppServiceHubImpl(val serviceHub: MockServices, serviceConstructor: (AppServiceHub) -> T) : AppServiceHub, ServiceHub by serviceHub { + val serviceInstance: T = serviceConstructor(this) init { - serviceInstance = serviceConstructor(this) serviceHub.cordappServices.putInstance(serviceInstance.javaClass, serviceInstance) } diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/NotarySpec.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/NotarySpec.kt index b8988e50c7..b6533ebd98 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/NotarySpec.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/NotarySpec.kt @@ -1,5 +1,6 @@ package net.corda.testing.node +import net.corda.core.DoNotImplement import net.corda.core.identity.CordaX500Name import net.corda.node.services.config.VerifierType import net.corda.nodeapi.internal.config.User @@ -12,6 +13,7 @@ data class NotarySpec( val cluster: ClusterSpec? = null ) +@DoNotImplement sealed class ClusterSpec { abstract val clusterSize: Int diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/network/NetworkMapServer.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/network/NetworkMapServer.kt index 345eaf84b8..be1a11c293 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/network/NetworkMapServer.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/network/NetworkMapServer.kt @@ -2,8 +2,6 @@ package net.corda.testing.node.internal.network import net.corda.core.crypto.* import net.corda.core.identity.CordaX500Name -import net.corda.core.internal.cert -import net.corda.core.internal.toX509CertHolder import net.corda.core.node.NodeInfo import net.corda.core.serialization.deserialize import net.corda.core.serialization.serialize @@ -16,7 +14,7 @@ import net.corda.nodeapi.internal.network.DigitalSignatureWithCert import net.corda.nodeapi.internal.network.NetworkMap import net.corda.nodeapi.internal.network.NetworkParameters import net.corda.nodeapi.internal.network.SignedNetworkMap -import net.corda.testing.ROOT_CA +import net.corda.testing.DEV_ROOT_CA import org.eclipse.jetty.server.Server import org.eclipse.jetty.server.ServerConnector import org.eclipse.jetty.server.handler.HandlerCollection @@ -29,6 +27,7 @@ import java.io.InputStream import java.net.InetSocketAddress import java.time.Duration import java.time.Instant +import javax.security.auth.x500.X500Principal import javax.ws.rs.* import javax.ws.rs.core.MediaType import javax.ws.rs.core.Response @@ -36,7 +35,7 @@ import javax.ws.rs.core.Response.ok class NetworkMapServer(cacheTimeout: Duration, hostAndPort: NetworkHostAndPort, - rootCa: CertificateAndKeyPair = ROOT_CA, // Default to ROOT_CA for testing. + rootCa: CertificateAndKeyPair = DEV_ROOT_CA, private val myHostNameValue: String = "test.host.name", vararg additionalServices: Any) : Closeable { companion object { @@ -48,12 +47,12 @@ class NetworkMapServer(cacheTimeout: Duration, CertificateType.NETWORK_MAP, rootCAKeyAndCert.certificate, rootCAKeyAndCert.keyPair, - CordaX500Name("Corda Network Map", "R3 Ltd", "London","GB"), - networkMapKey.public).cert + X500Principal("CN=Corda Network Map,O=R3 Ltd,L=London,C=GB"), + networkMapKey.public) // Check that the certificate validates. Nodes will perform this check upon receiving a network map, // it's better to fail here than there. - X509Utilities.validateCertificateChain(rootCAKeyAndCert.certificate.cert, networkMapCert) - return CertificateAndKeyPair(networkMapCert.toX509CertHolder(), networkMapKey) + X509Utilities.validateCertificateChain(rootCAKeyAndCert.certificate, networkMapCert) + return CertificateAndKeyPair(networkMapCert, networkMapKey) } } @@ -130,7 +129,7 @@ class NetworkMapServer(cacheTimeout: Duration, val networkMap = NetworkMap(nodeInfoMap.keys.toList(), parametersHash) val serializedNetworkMap = networkMap.serialize() val signature = Crypto.doSign(networkMapKeyAndCert.keyPair.private, serializedNetworkMap.bytes) - val signedNetworkMap = SignedNetworkMap(networkMap.serialize(), DigitalSignatureWithCert(networkMapKeyAndCert.certificate.cert, signature)) + val signedNetworkMap = SignedNetworkMap(networkMap.serialize(), DigitalSignatureWithCert(networkMapKeyAndCert.certificate, signature)) return Response.ok(signedNetworkMap.serialize().bytes).header("Cache-Control", "max-age=${cacheTimeout.seconds}").build() } diff --git a/testing/test-common/build.gradle b/testing/test-common/build.gradle index df37173fc1..e68a9dba67 100644 --- a/testing/test-common/build.gradle +++ b/testing/test-common/build.gradle @@ -1,4 +1,5 @@ apply plugin: 'net.corda.plugins.publish-utils' +apply plugin: 'net.corda.plugins.api-scanner' apply plugin: 'com.jfrog.artifactory' dependencies { diff --git a/testing/test-utils/build.gradle b/testing/test-utils/build.gradle index 74a2ff763b..e927c12eab 100644 --- a/testing/test-utils/build.gradle +++ b/testing/test-utils/build.gradle @@ -2,6 +2,7 @@ apply plugin: 'kotlin' apply plugin: 'kotlin-jpa' apply plugin: 'net.corda.plugins.quasar-utils' apply plugin: 'net.corda.plugins.publish-utils' +apply plugin: 'net.corda.plugins.api-scanner' apply plugin: 'com.jfrog.artifactory' description 'Testing utilities for Corda' diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/CoreTestUtils.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/CoreTestUtils.kt index 362672ce89..e24496c50c 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/CoreTestUtils.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/CoreTestUtils.kt @@ -12,7 +12,6 @@ import net.corda.core.crypto.toStringShort import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party import net.corda.core.identity.PartyAndCertificate -import net.corda.core.internal.cert import net.corda.core.internal.unspecifiedCountry import net.corda.core.node.NodeInfo import net.corda.core.utilities.NetworkHostAndPort @@ -21,10 +20,13 @@ import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair import net.corda.nodeapi.internal.crypto.CertificateType import net.corda.nodeapi.internal.crypto.X509CertificateFactory import net.corda.nodeapi.internal.crypto.X509Utilities -import org.bouncycastle.cert.X509CertificateHolder +import org.bouncycastle.asn1.x509.GeneralName +import org.bouncycastle.asn1.x509.GeneralSubtree +import org.bouncycastle.asn1.x509.NameConstraints import java.math.BigInteger import java.security.KeyPair import java.security.PublicKey +import java.security.cert.X509Certificate import java.util.concurrent.atomic.AtomicInteger /** @@ -76,8 +78,8 @@ fun getFreeLocalPorts(hostName: String, numberToAlloc: Int): List S.genericExpectEvents( finishFuture.getOrThrow() } +@DoNotImplement sealed class ExpectCompose { internal class Single(val expect: Expect) : ExpectCompose() internal class Sequential(val sequence: List>) : ExpectCompose() diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/SerializationTestHelpers.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/SerializationTestHelpers.kt index 8aa5a0cc61..b76f571bc3 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/SerializationTestHelpers.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/SerializationTestHelpers.kt @@ -2,6 +2,7 @@ package net.corda.testing import com.nhaarman.mockito_kotlin.* import net.corda.client.rpc.internal.KryoClientSerializationScheme +import net.corda.core.DoNotImplement import net.corda.core.internal.staticField import net.corda.core.serialization.internal.* import net.corda.node.serialization.KryoServerSerializationScheme @@ -62,6 +63,7 @@ class SerializationEnvironmentRule(private val inheritable: Boolean = false) : T } } +@DoNotImplement interface GlobalSerializationEnvironment : SerializationEnvironment { /** Unset this environment. */ fun unset() diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/TestConstants.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/TestConstants.kt index 0f700d9938..406477c52b 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/TestConstants.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/TestConstants.kt @@ -6,12 +6,10 @@ import net.corda.core.contracts.Command import net.corda.core.contracts.TypeOnlyCommandData import net.corda.core.crypto.generateKeyPair import net.corda.core.identity.CordaX500Name -import net.corda.core.internal.toX509CertHolder import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair import net.corda.nodeapi.internal.crypto.X509Utilities import net.corda.nodeapi.internal.crypto.getCertificateAndKeyPair import net.corda.nodeapi.internal.crypto.loadKeyStore -import org.bouncycastle.cert.X509CertificateHolder import java.security.PublicKey import java.time.Instant @@ -34,22 +32,17 @@ val ALICE_NAME = CordaX500Name("Alice Corp", "Madrid", "ES") val BOB_NAME = CordaX500Name("Bob Plc", "Rome", "IT") @JvmField val CHARLIE_NAME = CordaX500Name("Charlie Ltd", "Athens", "GR") -val DEV_CA: CertificateAndKeyPair by lazy { +val DEV_INTERMEDIATE_CA: CertificateAndKeyPair by lazy { // TODO: Should be identity scheme val caKeyStore = loadKeyStore(ClassLoader.getSystemResourceAsStream("certificates/cordadevcakeys.jks"), "cordacadevpass") caKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_INTERMEDIATE_CA, "cordacadevkeypass") } -val ROOT_CA: CertificateAndKeyPair by lazy { +val DEV_ROOT_CA: CertificateAndKeyPair by lazy { // TODO: Should be identity scheme val caKeyStore = loadKeyStore(ClassLoader.getSystemResourceAsStream("certificates/cordadevcakeys.jks"), "cordacadevpass") caKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_ROOT_CA, "cordacadevkeypass") } -val DEV_TRUST_ROOT: X509CertificateHolder by lazy { - // TODO: Should be identity scheme - val caKeyStore = loadKeyStore(ClassLoader.getSystemResourceAsStream("certificates/cordadevcakeys.jks"), "cordacadevpass") - caKeyStore.getCertificateChain(X509Utilities.CORDA_INTERMEDIATE_CA).last().toX509CertHolder() -} fun dummyCommand(vararg signers: PublicKey = arrayOf(generateKeyPair().public)) = Command(DummyCommandData, signers.toList()) diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/contracts/DummyContract.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/contracts/DummyContract.kt index 28341e2e92..3355b1fdf0 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/contracts/DummyContract.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/contracts/DummyContract.kt @@ -1,5 +1,6 @@ package net.corda.testing.contracts +import net.corda.core.DoNotImplement import net.corda.core.contracts.* import net.corda.core.identity.AbstractParty import net.corda.core.identity.Party @@ -12,6 +13,7 @@ data class DummyContract(val blank: Any? = null) : Contract { val PROGRAM_ID = "net.corda.testing.contracts.DummyContract" + @DoNotImplement // State is effectively a sealed class. interface State : ContractState { val magicNumber: Int } diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/dsl/LedgerDSLInterpreter.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/dsl/LedgerDSLInterpreter.kt index 869643d2aa..89e9ecb202 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/dsl/LedgerDSLInterpreter.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/dsl/LedgerDSLInterpreter.kt @@ -1,5 +1,6 @@ package net.corda.testing.dsl +import net.corda.core.DoNotImplement import net.corda.core.contracts.ContractState import net.corda.core.contracts.StateAndRef import net.corda.core.contracts.TransactionState @@ -14,6 +15,7 @@ import java.io.InputStream * This interface defines output state lookup by label. It is split from the interpreter interfaces so that outputs may * be looked up both in ledger{..} and transaction{..} blocks. */ +@DoNotImplement interface OutputStateLookup { /** * Retrieves an output previously defined by [TransactionDSLInterpreter.output] with a label passed in. @@ -27,6 +29,7 @@ interface OutputStateLookup { /** * This interface asserts that the DSL at hand is capable of verifying its underlying construct(ledger/transaction). */ +@DoNotImplement interface Verifies { /** * Verifies the ledger/transaction, throws if the verification fails. @@ -83,6 +86,7 @@ interface Verifies { * * TODO (Kotlin 1.1): Use type synonyms to make the type params less unwieldy */ +@DoNotImplement interface LedgerDSLInterpreter : Verifies, OutputStateLookup { /** * Creates and adds a transaction to the ledger. diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/dsl/TestDSL.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/dsl/TestDSL.kt index bf48a07240..3a0e36e5f3 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/dsl/TestDSL.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/dsl/TestDSL.kt @@ -1,5 +1,6 @@ package net.corda.testing.dsl +import net.corda.core.DoNotImplement import net.corda.core.contracts.* import net.corda.core.cordapp.CordappProvider import net.corda.core.crypto.* @@ -50,6 +51,7 @@ import kotlin.collections.set * will have as the last line either an accept or a failure test. The name is deliberately long to help make sense of * the triggered diagnostic. */ +@DoNotImplement sealed class EnforceVerifyOrFail { internal object Token : EnforceVerifyOrFail() } diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/dsl/TransactionDSLInterpreter.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/dsl/TransactionDSLInterpreter.kt index d79260592f..f78678801a 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/dsl/TransactionDSLInterpreter.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/dsl/TransactionDSLInterpreter.kt @@ -1,5 +1,6 @@ package net.corda.testing.dsl +import net.corda.core.DoNotImplement import net.corda.core.contracts.AlwaysAcceptAttachmentConstraint import net.corda.core.contracts.Attachment import net.corda.core.contracts.AttachmentConstraint @@ -22,6 +23,7 @@ import java.time.Instant * @param The return type of [verifies]/[failsWith] and the like. It is generic so that we have control over whether * we want to enforce users to call these methods (see [EnforceVerifyOrFail]) or not. */ +@DoNotImplement interface TransactionDSLInterpreter : Verifies, OutputStateLookup { /** * A reference to the enclosing ledger{..}'s interpreter. diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/internal/InternalTestUtils.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/internal/InternalTestUtils.kt index 87a73491b5..ccf34284e7 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/internal/InternalTestUtils.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/internal/InternalTestUtils.kt @@ -15,6 +15,7 @@ import org.mockito.internal.stubbing.answers.ThrowsException import java.lang.reflect.Modifier import java.nio.file.Files import java.util.* +import javax.security.auth.x500.X500Principal @Suppress("unused") inline fun T.kryoSpecific(reason: String, function: () -> Unit) = if (!AMQP_ENABLED) { @@ -64,8 +65,8 @@ fun configureTestSSL(legalName: CordaX500Name): SSLConfiguration { } } -private val defaultRootCaName = CordaX500Name("Corda Root CA", "R3 Ltd", "London", "GB") -private val defaultIntermediateCaName = CordaX500Name("Corda Intermediate CA", "R3 Ltd", "London", "GB") +private val defaultRootCaName = X500Principal("CN=Corda Root CA,O=R3 Ltd,L=London,C=GB") +private val defaultIntermediateCaName = X500Principal("CN=Corda Intermediate CA,O=R3 Ltd,L=London,C=GB") /** * Returns a pair of [CertificateAndKeyPair]s, the first being the root CA and the second the intermediate CA. @@ -73,8 +74,8 @@ private val defaultIntermediateCaName = CordaX500Name("Corda Intermediate CA", " * @param intermediateCaName The subject name for the intermediate CA cert. */ fun createDevIntermediateCaCertPath( - rootCaName: CordaX500Name = defaultRootCaName, - intermediateCaName: CordaX500Name = defaultIntermediateCaName + rootCaName: X500Principal = defaultRootCaName, + intermediateCaName: X500Principal = defaultIntermediateCaName ): Pair { val rootKeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) val rootCert = X509Utilities.createSelfSignedCACertificate(rootCaName, rootKeyPair) @@ -87,7 +88,10 @@ fun createDevIntermediateCaCertPath( intermediateCaName, intermediateCaKeyPair.public) - return Pair(CertificateAndKeyPair(rootCert, rootKeyPair), CertificateAndKeyPair(intermediateCaCert, intermediateCaKeyPair)) + return Pair( + CertificateAndKeyPair(rootCert, rootKeyPair), + CertificateAndKeyPair(intermediateCaCert, intermediateCaKeyPair) + ) } /** @@ -97,8 +101,8 @@ fun createDevIntermediateCaCertPath( */ fun createDevNodeCaCertPath( legalName: CordaX500Name, - rootCaName: CordaX500Name = defaultRootCaName, - intermediateCaName: CordaX500Name = defaultIntermediateCaName + rootCaName: X500Principal = defaultRootCaName, + intermediateCaName: X500Principal = defaultIntermediateCaName ): Triple { val (rootCa, intermediateCa) = createDevIntermediateCaCertPath(rootCaName, intermediateCaName) val nodeCa = createDevNodeCa(intermediateCa, legalName)