diff --git a/node-api-tests/src/test/kotlin/net/corda/nodeapitests/internal/crypto/X509UtilitiesTest.kt b/node-api-tests/src/test/kotlin/net/corda/nodeapitests/internal/crypto/X509UtilitiesTest.kt index 61f792a27a..7f57441d93 100644 --- a/node-api-tests/src/test/kotlin/net/corda/nodeapitests/internal/crypto/X509UtilitiesTest.kt +++ b/node-api-tests/src/test/kotlin/net/corda/nodeapitests/internal/crypto/X509UtilitiesTest.kt @@ -43,6 +43,7 @@ import net.corda.coretesting.internal.NettyTestServer import net.corda.testing.internal.createDevIntermediateCaCertPath import net.corda.coretesting.internal.stubs.CertificateStoreStubs import net.corda.nodeapi.internal.crypto.CertificateType +import net.corda.nodeapi.internal.crypto.X509CertificateFactory import net.corda.nodeapi.internal.crypto.X509Utilities import net.corda.nodeapi.internal.crypto.checkValidity import net.corda.nodeapi.internal.crypto.getSupportedKey @@ -50,6 +51,7 @@ import net.corda.nodeapi.internal.crypto.loadOrCreateKeyStore import net.corda.nodeapi.internal.crypto.save import net.corda.nodeapi.internal.crypto.toBc import net.corda.nodeapi.internal.crypto.x509 +import net.corda.nodeapi.internal.crypto.x509Certificates import net.corda.testing.internal.IS_OPENJ9 import net.i2p.crypto.eddsa.EdDSAPrivateKey import org.assertj.core.api.Assertions.assertThat @@ -570,4 +572,16 @@ class X509UtilitiesTest { cert.checkValidity({ "Error text" }, { }, Date.from(today.toInstant() + 51.days)) } } + + @Test(timeout = 300_000) + fun `check certificate serial number`() { + val keyPair = generateKeyPair() + val subject = X500Principal("CN=Test,O=R3 Ltd,L=London,C=GB") + val cert = X509Utilities.createSelfSignedCACertificate(subject, keyPair) + assertTrue(cert.serialNumber.signum() > 0) + assertEquals(127, cert.serialNumber.bitLength()) + val serialized = X509Utilities.buildCertPath(cert).encoded + val deserialized = X509CertificateFactory().delegate.generateCertPath(serialized.inputStream()).x509Certificates.first() + assertEquals(cert.serialNumber, deserialized.serialNumber) + } } 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 138cb23d37..841d105891 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 @@ -2,7 +2,7 @@ package net.corda.nodeapi.internal.crypto import net.corda.core.CordaOID import net.corda.core.crypto.Crypto -import net.corda.core.crypto.random63BitValue +import net.corda.core.crypto.newSecureRandom import net.corda.core.internal.* import net.corda.core.utilities.days import net.corda.core.utilities.millis @@ -35,6 +35,8 @@ import java.time.Instant import java.time.temporal.ChronoUnit import java.util.* import javax.security.auth.x500.X500Principal +import kotlin.experimental.and +import kotlin.experimental.or object X509Utilities { // Note that this default value only applies to BCCryptoService. Other implementations of CryptoService may have to use different @@ -58,6 +60,7 @@ object X509Utilities { const val TLS_CERTIFICATE_DAYS_TO_EXPIRY_WARNING_THRESHOLD = 30 private const val KEY_ALIAS_REGEX = "[a-z0-9-]+" private const val KEY_ALIAS_MAX_LENGTH = 100 + private const val CERTIFICATE_SERIAL_NUMBER_LENGTH = 16 /** * Checks if the provided key alias does not exceed maximum length and @@ -184,7 +187,7 @@ object X509Utilities { nameConstraints: NameConstraints? = null, crlDistPoint: String? = null, crlIssuer: X500Name? = null): X509v3CertificateBuilder { - val serial = BigInteger.valueOf(random63BitValue()) + val serial = generateCertificateSerialNumber() val keyPurposes = DERSequence(ASN1EncodableVector().apply { certificateType.purposes.forEach { add(it) } }) val subjectPublicKeyInfo = SubjectPublicKeyInfo.getInstance(ASN1Sequence.getInstance(subjectPublicKey.encoded)) val role = certificateType.role @@ -364,6 +367,15 @@ object X509Utilities { builder.addExtension(Extension.cRLDistributionPoints, false, CRLDistPoint(arrayOf(distPoint))) } } + + @Suppress("MagicNumber") + private fun generateCertificateSerialNumber(): BigInteger { + val bytes = ByteArray(CERTIFICATE_SERIAL_NUMBER_LENGTH) + newSecureRandom().nextBytes(bytes) + // Set highest byte to 01xxxxxx to ensure positive sign and constant bit length. + bytes[0] = bytes[0].and(0x3F).or(0x40) + return BigInteger(bytes) + } } // Assuming cert type to role is 1:1 diff --git a/node/src/integration-test/kotlin/net/corda/node/AuthDBTests.kt b/node/src/integration-test/kotlin/net/corda/node/AuthDBTests.kt index e2795b83b3..a30210999d 100644 --- a/node/src/integration-test/kotlin/net/corda/node/AuthDBTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/AuthDBTests.kt @@ -23,9 +23,9 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.Parameterized +import java.sql.Connection import java.sql.Statement import java.util.* -import javax.sql.DataSource import kotlin.test.assertFailsWith /* @@ -34,7 +34,7 @@ import kotlin.test.assertFailsWith */ @RunWith(Parameterized::class) class AuthDBTests : NodeBasedTest(cordappPackages = CORDAPPS) { - private lateinit var node: NodeWithInfo + private var node: NodeWithInfo? = null private lateinit var client: CordaRPCClient private lateinit var db: UsersDB @@ -97,8 +97,9 @@ class AuthDBTests : NodeBasedTest(cordappPackages = CORDAPPS) { ) ) - node = startNode(ALICE_NAME, rpcUsers = emptyList(), configOverrides = securityConfig) - client = CordaRPCClient(node.node.configuration.rpcOptions.address) + node = startNode(ALICE_NAME, rpcUsers = emptyList(), configOverrides = securityConfig).also { node -> + client = CordaRPCClient(node.node.configuration.rpcOptions.address) + } } @Test(timeout=300_000) @@ -219,6 +220,7 @@ class AuthDBTests : NodeBasedTest(cordappPackages = CORDAPPS) { @After fun tearDown() { + node?.node?.stop() db.close() } @@ -232,7 +234,7 @@ private data class RoleAndPermissions(val role: String, val permissions: List = emptyList(), roleAndPermissions: List = emptyList()) : AutoCloseable { - val jdbcUrl = "jdbc:h2:mem:$name;DB_CLOSE_DELAY=-1" + val jdbcUrl = "jdbc:h2:mem:$name" companion object { const val DB_CREATE_SCHEMA = """ @@ -273,36 +275,34 @@ private class UsersDB(name: String, users: List = emptyList(), rol } } - private val dataSource: DataSource + private val connection: Connection private inline fun session(statement: (Statement) -> Unit) { - dataSource.connection.use { - it.autoCommit = false - it.createStatement().use(statement) - it.commit() - } + connection.createStatement().use(statement) + connection.commit() } init { - dataSource = DataSourceFactory.createDataSource(Properties().apply { + require(users.map { it.username }.toSet().size == users.size) { + "Duplicate username in input" + } + connection = DataSourceFactory.createDataSource(Properties().apply { put("dataSourceClassName", "org.h2.jdbcx.JdbcDataSource") put("dataSource.url", jdbcUrl) }, false) + .connection + .apply { + autoCommit = false + } session { it.execute(DB_CREATE_SCHEMA) } - require(users.map { it.username }.toSet().size == users.size) { - "Duplicate username in input" - } users.forEach { insert(it) } roleAndPermissions.forEach { insert(it) } } override fun close() { - dataSource.connection.use { - it.createStatement().use { - it.execute("DROP ALL OBJECTS") - } - } + // Close the connection, at which point the database will shut down + connection.close() } } diff --git a/node/src/integration-test/kotlin/net/corda/node/services/statemachine/LargeTransactionsTest.kt b/node/src/integration-test/kotlin/net/corda/node/services/statemachine/LargeTransactionsTest.kt index 9e27f15d76..c212d10b98 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/statemachine/LargeTransactionsTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/statemachine/LargeTransactionsTest.kt @@ -22,6 +22,7 @@ import net.corda.testing.driver.driver import net.corda.testing.node.User import net.corda.testing.node.internal.DUMMY_CONTRACTS_CORDAPP import net.corda.testing.node.internal.enclosedCordapp +import org.junit.Ignore import org.junit.Test import kotlin.test.assertEquals @@ -29,6 +30,7 @@ import kotlin.test.assertEquals * Check that we can add lots of large attachments to a transaction and that it works OK, e.g. does not hit the * transaction size limit (which should only consider the hashes). */ +@Ignore("ENT-5679: This test triggers OOM errors") class LargeTransactionsTest { private companion object { val BOB = TestIdentity(BOB_NAME, 80).party diff --git a/node/src/integration-test/kotlin/net/corda/node/services/vault/VaultObserverExceptionTest.kt b/node/src/integration-test/kotlin/net/corda/node/services/vault/VaultObserverExceptionTest.kt index 48a2315803..198b9d3ab6 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/vault/VaultObserverExceptionTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/vault/VaultObserverExceptionTest.kt @@ -52,6 +52,7 @@ import kotlin.test.assertTrue class VaultObserverExceptionTest { companion object { + val waitForFlowDuration = 45.seconds val log = contextLogger() private fun testCordapps() = listOf( @@ -99,7 +100,7 @@ class VaultObserverExceptionTest { "Syntax Error in Custom SQL", CreateStateFlow.errorTargetsToNum(CreateStateFlow.ErrorTarget.ServiceSqlSyntaxError) ).returnValue.then { testControlFuture.complete(false) } - val foundExpectedException = testControlFuture.getOrThrow(30.seconds) + val foundExpectedException = testControlFuture.getOrThrow(waitForFlowDuration) Assert.assertTrue(foundExpectedException) } @@ -133,7 +134,7 @@ class VaultObserverExceptionTest { "Syntax Error in Custom SQL", CreateStateFlow.errorTargetsToNum(CreateStateFlow.ErrorTarget.ServiceSqlSyntaxError) ).returnValue.then { testControlFuture.complete(false) } - val foundExpectedException = testControlFuture.getOrThrow(30.seconds) + val foundExpectedException = testControlFuture.getOrThrow(waitForFlowDuration) Assert.assertTrue(foundExpectedException) } @@ -224,7 +225,7 @@ class VaultObserverExceptionTest { assertFailsWith("PersistenceException") { aliceNode.rpc.startFlow(CreateStateFlow::Initiator, "EntityManager", errorTargetsToNum( CreateStateFlow.ErrorTarget.TxInvalidState)) - .returnValue.getOrThrow(30.seconds) + .returnValue.getOrThrow(waitForFlowDuration) } } Assert.assertTrue("Flow has not been to hospital", counter > 0) @@ -260,7 +261,7 @@ class VaultObserverExceptionTest { CreateStateFlow.ErrorTarget.TxInvalidState, CreateStateFlow.ErrorTarget.FlowSwallowErrors)) val flowResult = flowHandle.returnValue - assertFailsWith("PersistenceException") { flowResult.getOrThrow(30.seconds) } + assertFailsWith("PersistenceException") { flowResult.getOrThrow(waitForFlowDuration) } Assert.assertTrue("Flow has not been to hospital", counter > 0) } } @@ -291,7 +292,7 @@ class VaultObserverExceptionTest { log.info("Flow has finished") testControlFuture.set(false) } - Assert.assertTrue("Flow has not been kept in hospital", testControlFuture.getOrThrow(30.seconds)) + Assert.assertTrue("Flow has not been kept in hospital", testControlFuture.getOrThrow(waitForFlowDuration)) } } @@ -310,7 +311,7 @@ class VaultObserverExceptionTest { CreateStateFlow.ErrorTarget.ServiceSqlSyntaxError, CreateStateFlow.ErrorTarget.ServiceSwallowErrors)) val flowResult = flowHandle.returnValue - flowResult.getOrThrow(30.seconds) + flowResult.getOrThrow(waitForFlowDuration) } } @@ -411,7 +412,7 @@ class VaultObserverExceptionTest { testControlFuture.complete(true) } startNode(providedName = ALICE_NAME, rpcUsers = listOf(aliceUser), startInSameProcess = true).getOrThrow() - assert(testControlFuture.getOrThrow(30.seconds)) + assert(testControlFuture.getOrThrow(waitForFlowDuration)) } else { throw IllegalStateException("Out of process node is still up and running!") } @@ -464,7 +465,7 @@ class VaultObserverExceptionTest { CreateStateFlow::Initiator, "AllGood", errorTargetsToNum(CreateStateFlow.ErrorTarget.ServiceSqlSyntaxErrorOnConsumed) - ).returnValue.getOrThrow(30.seconds) + ).returnValue.getOrThrow(waitForFlowDuration) println("Created new state") @@ -558,7 +559,7 @@ class VaultObserverExceptionTest { "AllGood", // should be a hospital exception errorTargetsToNum(CreateStateFlow.ErrorTarget.ServiceSqlSyntaxErrorOnConsumed) - ).returnValue.getOrThrow(30.seconds) + ).returnValue.getOrThrow(waitForFlowDuration) val flowHandle = aliceNode.rpc.startFlow( SendStateFlow::PassErroneousOwnableState, @@ -642,7 +643,7 @@ class VaultObserverExceptionTest { CreateStateFlow::Initiator, "AllGood", errorTargetsToNum(CreateStateFlow.ErrorTarget.NoError) - ).returnValue.getOrThrow(30.seconds) + ).returnValue.getOrThrow(waitForFlowDuration) aliceNode.rpc.startFlow( SendStateFlow::PassErroneousOwnableState, @@ -722,7 +723,7 @@ class VaultObserverExceptionTest { CreateStateFlow::Initiator, "AllGood", errorTargetsToNum(CreateStateFlow.ErrorTarget.ServiceSqlSyntaxErrorOnConsumed) - ).returnValue.getOrThrow(30.seconds) + ).returnValue.getOrThrow(waitForFlowDuration) val flowHandle = aliceNode.rpc.startFlow( SendStateFlow::PassErroneousOwnableState, @@ -777,7 +778,7 @@ class VaultObserverExceptionTest { "Flow ${SubscribingRawUpdatesFlow::class.java.name} tried to access VaultService.rawUpdates " + "- Rx.Observables should only be accessed outside the context of a flow " ) { - flowHandle.returnValue.getOrThrow(30.seconds) + flowHandle.returnValue.getOrThrow(waitForFlowDuration) } } }