mirror of
https://github.com/corda/corda.git
synced 2025-06-13 04:38:19 +00:00
Merge branch 'master' into shams-master-merge-271117
This commit is contained in:
@ -139,8 +139,8 @@ class SSHServerTest {
|
||||
|
||||
val response = String(Streams.readAll(channel.inputStream))
|
||||
|
||||
//There are ANSI control characters involved, so we want to avoid direct byte to byte matching
|
||||
assertThat(response.lines()).filteredOn( { it.contains("✓") && it.contains("Done")}).hasSize(1)
|
||||
// There are ANSI control characters involved, so we want to avoid direct byte to byte matching.
|
||||
assertThat(response.lines()).filteredOn( { it.contains("Done")}).hasSize(1)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,11 +2,11 @@ package net.corda.services.messaging
|
||||
|
||||
import net.corda.core.crypto.Crypto
|
||||
import net.corda.core.internal.*
|
||||
import net.corda.node.utilities.*
|
||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.NODE_USER
|
||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.PEER_USER
|
||||
import net.corda.nodeapi.RPCApi
|
||||
import net.corda.nodeapi.config.SSLConfiguration
|
||||
import net.corda.nodeapi.internal.crypto.*
|
||||
import net.corda.testing.MEGA_CORP
|
||||
import net.corda.testing.MINI_CORP
|
||||
import net.corda.testing.messaging.SimpleMQClient
|
||||
|
@ -4,7 +4,7 @@ package net.corda.node.shell;
|
||||
|
||||
import net.corda.core.messaging.CordaRPCOps;
|
||||
import net.corda.node.utilities.ANSIProgressRenderer;
|
||||
import net.corda.node.utilities.CRaSHNSIProgressRenderer;
|
||||
import net.corda.node.utilities.CRaSHANSIProgressRenderer;
|
||||
import org.crsh.cli.*;
|
||||
import org.crsh.command.*;
|
||||
import org.crsh.text.*;
|
||||
@ -12,7 +12,6 @@ import org.crsh.text.ui.TableElement;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import static net.corda.node.services.messaging.RPCServerKt.CURRENT_RPC_CONTEXT;
|
||||
import static net.corda.node.shell.InteractiveShell.*;
|
||||
|
||||
@Man(
|
||||
@ -49,7 +48,7 @@ public class FlowShellCommand extends InteractiveShellCommand {
|
||||
return;
|
||||
}
|
||||
String inp = input == null ? "" : String.join(" ", input).trim();
|
||||
runFlowByNameFragment(name, inp, out, rpcOps, ansiProgressRenderer != null ? ansiProgressRenderer : new CRaSHNSIProgressRenderer(out) );
|
||||
runFlowByNameFragment(name, inp, out, rpcOps, ansiProgressRenderer != null ? ansiProgressRenderer : new CRaSHANSIProgressRenderer(out) );
|
||||
}
|
||||
|
||||
@Command
|
||||
|
@ -30,7 +30,7 @@ public class RunShellCommand extends InteractiveShellCommand {
|
||||
return null;
|
||||
}
|
||||
|
||||
return InteractiveShell.runRPCFromString(command, out, context);
|
||||
return InteractiveShell.runRPCFromString(command, out, context, ops());
|
||||
}
|
||||
|
||||
private void emitHelp(InvocationContext<Map> context, StringToMethodCallParser<CordaRPCOps> parser) {
|
||||
|
@ -3,7 +3,7 @@ package net.corda.node.shell;
|
||||
// A simple forwarder to the "flow start" command, for easier typing.
|
||||
|
||||
import net.corda.node.utilities.ANSIProgressRenderer;
|
||||
import net.corda.node.utilities.CRaSHNSIProgressRenderer;
|
||||
import net.corda.node.utilities.CRaSHANSIProgressRenderer;
|
||||
import org.crsh.cli.*;
|
||||
|
||||
import java.util.*;
|
||||
@ -14,6 +14,6 @@ public class StartShellCommand extends InteractiveShellCommand {
|
||||
public void main(@Usage("The class name of the flow to run, or an unambiguous substring") @Argument String name,
|
||||
@Usage("The data to pass as input") @Argument(unquote = false) List<String> input) {
|
||||
ANSIProgressRenderer ansiProgressRenderer = ansiProgressRenderer();
|
||||
FlowShellCommand.startFlow(name, input, out, ops(), ansiProgressRenderer != null ? ansiProgressRenderer : new CRaSHNSIProgressRenderer(out));
|
||||
FlowShellCommand.startFlow(name, input, out, ops(), ansiProgressRenderer != null ? ansiProgressRenderer : new CRaSHANSIProgressRenderer(out));
|
||||
}
|
||||
}
|
||||
|
@ -61,7 +61,10 @@ import net.corda.node.services.upgrade.ContractUpgradeServiceImpl
|
||||
import net.corda.node.services.vault.NodeVaultService
|
||||
import net.corda.node.services.vault.VaultSoftLockManager
|
||||
import net.corda.node.shell.InteractiveShell
|
||||
import net.corda.node.utilities.*
|
||||
import net.corda.node.utilities.AffinityExecutor
|
||||
import net.corda.node.utilities.CordaPersistence
|
||||
import net.corda.node.utilities.configureDatabase
|
||||
import net.corda.nodeapi.internal.crypto.*
|
||||
import org.apache.activemq.artemis.utils.ReusableLatch
|
||||
import org.slf4j.Logger
|
||||
import rx.Observable
|
||||
@ -70,7 +73,6 @@ import java.lang.reflect.InvocationTargetException
|
||||
import java.security.KeyPair
|
||||
import java.security.KeyStoreException
|
||||
import java.security.PublicKey
|
||||
import java.security.cert.CertificateFactory
|
||||
import java.security.cert.X509Certificate
|
||||
import java.sql.Connection
|
||||
import java.time.Clock
|
||||
@ -189,11 +191,13 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
|
||||
val (keyPairs, info) = initNodeInfo()
|
||||
readNetworkParameters()
|
||||
val schemaService = NodeSchemaService(cordappLoader)
|
||||
val identityService = makeIdentityService(info)
|
||||
// Do all of this in a database transaction so anything that might need a connection has one.
|
||||
val (startedImpl, schedulerService) = initialiseDatabasePersistence(schemaService) { database ->
|
||||
val (startedImpl, schedulerService) = initialiseDatabasePersistence(schemaService, identityService) { database ->
|
||||
identityService.loadIdentities(info.legalIdentitiesAndCerts)
|
||||
val transactionStorage = makeTransactionStorage(database)
|
||||
val stateLoader = StateLoaderImpl(transactionStorage)
|
||||
val nodeServices = makeServices(keyPairs, schemaService, transactionStorage, stateLoader, database, info)
|
||||
val nodeServices = makeServices(keyPairs, schemaService, transactionStorage, stateLoader, database, info, identityService)
|
||||
val notaryService = makeNotaryService(nodeServices, database)
|
||||
smm = makeStateMachineManager(database)
|
||||
val flowStarter = FlowStarterImpl(serverThread, smm)
|
||||
@ -502,12 +506,11 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
|
||||
* Builds node internal, advertised, and plugin services.
|
||||
* Returns a list of tokenizable services to be added to the serialisation context.
|
||||
*/
|
||||
private fun makeServices(keyPairs: Set<KeyPair>, schemaService: SchemaService, transactionStorage: WritableTransactionStorage, stateLoader: StateLoader, database: CordaPersistence, info: NodeInfo): MutableList<Any> {
|
||||
private fun makeServices(keyPairs: Set<KeyPair>, schemaService: SchemaService, transactionStorage: WritableTransactionStorage, stateLoader: StateLoader, database: CordaPersistence, info: NodeInfo, identityService: IdentityService): MutableList<Any> {
|
||||
checkpointStorage = DBCheckpointStorage()
|
||||
val metrics = MetricRegistry()
|
||||
attachments = NodeAttachmentService(metrics)
|
||||
val cordappProvider = CordappProviderImpl(cordappLoader, attachments)
|
||||
val identityService = makeIdentityService(info)
|
||||
val keyManagementService = makeKeyManagementService(identityService, keyPairs)
|
||||
_services = ServiceHubInternalImpl(
|
||||
identityService,
|
||||
@ -561,10 +564,10 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
|
||||
// Specific class so that MockNode can catch it.
|
||||
class DatabaseConfigurationException(msg: String) : CordaException(msg)
|
||||
|
||||
protected open fun <T> initialiseDatabasePersistence(schemaService: SchemaService, insideTransaction: (CordaPersistence) -> T): T {
|
||||
protected open fun <T> initialiseDatabasePersistence(schemaService: SchemaService, identityService: IdentityService, insideTransaction: (CordaPersistence) -> T): T {
|
||||
val props = configuration.dataSourceProperties
|
||||
if (props.isNotEmpty()) {
|
||||
val database = configureDatabase(props, configuration.database, { _services.identityService }, schemaService)
|
||||
val database = configureDatabase(props, configuration.database, identityService, schemaService)
|
||||
// Now log the vendor string as this will also cause a connection to be tested eagerly.
|
||||
database.transaction {
|
||||
log.info("Connected to ${database.dataSource.connection.metaData.databaseProductName} database.")
|
||||
@ -631,13 +634,13 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
|
||||
}
|
||||
}
|
||||
|
||||
private fun makeIdentityService(info: NodeInfo): IdentityService {
|
||||
private fun makeIdentityService(info: NodeInfo): PersistentIdentityService {
|
||||
val trustStore = KeyStoreWrapper(configuration.trustStoreFile, configuration.trustStorePassword)
|
||||
val caKeyStore = KeyStoreWrapper(configuration.nodeKeystore, configuration.keyStorePassword)
|
||||
val trustRoot = trustStore.getX509Certificate(X509Utilities.CORDA_ROOT_CA)
|
||||
val clientCa = caKeyStore.certificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA)
|
||||
val caCertificates = arrayOf(info.legalIdentitiesAndCerts[0].certificate, clientCa.certificate.cert)
|
||||
return PersistentIdentityService(info.legalIdentitiesAndCerts, trustRoot = trustRoot, caCertificates = *caCertificates)
|
||||
return PersistentIdentityService(trustRoot, *caCertificates)
|
||||
}
|
||||
|
||||
protected abstract fun makeTransactionVerifierService(): TransactionVerifierService
|
||||
@ -710,7 +713,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
|
||||
throw ConfigurationException("The name '$singleName' for $id doesn't match what's in the key store: $subject")
|
||||
}
|
||||
|
||||
val certPath = CertificateFactory.getInstance("X509").generateCertPath(certificates)
|
||||
val certPath = X509CertificateFactory().delegate.generateCertPath(certificates)
|
||||
return Pair(PartyAndCertificate(certPath), keyPair)
|
||||
}
|
||||
|
||||
|
@ -8,6 +8,7 @@ import net.corda.core.internal.uncheckedCast
|
||||
import net.corda.core.messaging.RPCOps
|
||||
import net.corda.core.node.NodeInfo
|
||||
import net.corda.core.node.ServiceHub
|
||||
import net.corda.core.node.services.IdentityService
|
||||
import net.corda.core.node.services.TransactionVerifierService
|
||||
import net.corda.core.utilities.NetworkHostAndPort
|
||||
import net.corda.core.serialization.internal.SerializationEnvironmentImpl
|
||||
@ -228,7 +229,7 @@ open class Node(configuration: NodeConfiguration,
|
||||
* This is not using the H2 "automatic mixed mode" directly but leans on many of the underpinnings. For more details
|
||||
* on H2 URLs and configuration see: http://www.h2database.com/html/features.html#database_url
|
||||
*/
|
||||
override fun <T> initialiseDatabasePersistence(schemaService: SchemaService, insideTransaction: (CordaPersistence) -> T): T {
|
||||
override fun <T> initialiseDatabasePersistence(schemaService: SchemaService, identityService: IdentityService, insideTransaction: (CordaPersistence) -> T): T {
|
||||
val databaseUrl = configuration.dataSourceProperties.getProperty("dataSource.url")
|
||||
val h2Prefix = "jdbc:h2:file:"
|
||||
if (databaseUrl != null && databaseUrl.startsWith(h2Prefix)) {
|
||||
@ -245,7 +246,7 @@ open class Node(configuration: NodeConfiguration,
|
||||
printBasicNodeInfo("Database connection url is", "jdbc:h2:$url/node")
|
||||
}
|
||||
}
|
||||
return super.initialiseDatabasePersistence(schemaService, insideTransaction)
|
||||
return super.initialiseDatabasePersistence(schemaService, identityService, insideTransaction)
|
||||
}
|
||||
|
||||
private val _startupComplete = openFuture<Unit>()
|
||||
|
@ -8,8 +8,8 @@ import net.corda.core.crypto.Crypto
|
||||
import net.corda.core.crypto.SignatureScheme
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.internal.*
|
||||
import net.corda.node.utilities.*
|
||||
import net.corda.nodeapi.config.SSLConfiguration
|
||||
import net.corda.nodeapi.internal.crypto.*
|
||||
import org.bouncycastle.asn1.x509.GeneralName
|
||||
import org.bouncycastle.asn1.x509.GeneralSubtree
|
||||
import org.bouncycastle.asn1.x509.NameConstraints
|
||||
|
@ -12,8 +12,6 @@ import java.net.URL
|
||||
import java.nio.file.Path
|
||||
import java.util.*
|
||||
|
||||
data class DevModeOptions(val disableCheckpointChecker: Boolean = false)
|
||||
|
||||
interface NodeConfiguration : NodeSSLConfiguration {
|
||||
// myLegalName should be only used in the initial network registration, we should use the name from the certificate instead of this.
|
||||
// TODO: Remove this so we don't accidentally use this identity in the code?
|
||||
@ -21,7 +19,7 @@ interface NodeConfiguration : NodeSSLConfiguration {
|
||||
val emailAddress: String
|
||||
val exportJMXto: String
|
||||
val dataSourceProperties: Properties
|
||||
val database: Properties?
|
||||
val database: DatabaseConfig
|
||||
val rpcUsers: List<User>
|
||||
val devMode: Boolean
|
||||
val devModeOptions: DevModeOptions?
|
||||
@ -42,6 +40,27 @@ interface NodeConfiguration : NodeSSLConfiguration {
|
||||
val sshd: SSHDConfiguration?
|
||||
}
|
||||
|
||||
data class DevModeOptions(val disableCheckpointChecker: Boolean = false)
|
||||
|
||||
data class DatabaseConfig(
|
||||
val initDatabase: Boolean = true,
|
||||
val serverNameTablePrefix: String = "",
|
||||
val transactionIsolationLevel: TransactionIsolationLevel = TransactionIsolationLevel.REPEATABLE_READ
|
||||
)
|
||||
|
||||
enum class TransactionIsolationLevel {
|
||||
NONE,
|
||||
READ_UNCOMMITTED,
|
||||
READ_COMMITTED,
|
||||
REPEATABLE_READ,
|
||||
SERIALIZABLE;
|
||||
|
||||
/**
|
||||
* The JDBC constant value of the same name but with prefixed with TRANSACTION_ defined in [java.sql.Connection].
|
||||
*/
|
||||
val jdbcValue: Int = java.sql.Connection::class.java.getField("TRANSACTION_$name").get(null) as Int
|
||||
}
|
||||
|
||||
fun NodeConfiguration.shouldCheckCheckpoints(): Boolean {
|
||||
return this.devMode && this.devModeOptions?.disableCheckpointChecker != true
|
||||
}
|
||||
@ -89,7 +108,7 @@ data class NodeConfigurationImpl(
|
||||
override val keyStorePassword: String,
|
||||
override val trustStorePassword: String,
|
||||
override val dataSourceProperties: Properties,
|
||||
override val database: Properties?,
|
||||
override val database: DatabaseConfig = DatabaseConfig(),
|
||||
override val compatibilityZoneURL: URL? = null,
|
||||
override val rpcUsers: List<User>,
|
||||
override val verifierType: VerifierType,
|
||||
|
@ -10,6 +10,7 @@ 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.nodeapi.internal.crypto.X509CertificateFactory
|
||||
import org.bouncycastle.cert.X509CertificateHolder
|
||||
import java.security.InvalidAlgorithmParameterException
|
||||
import java.security.PublicKey
|
||||
@ -63,7 +64,7 @@ class InMemoryIdentityService(identities: Iterable<PartyAndCertificate> = emptyS
|
||||
log.error("Certificate validation failed for ${identity.name} against trusted root ${trustAnchor.trustedCert.subjectX500Principal}.")
|
||||
log.error("Certificate path :")
|
||||
identity.certPath.certificates.reversed().forEachIndexed { index, certificate ->
|
||||
val space = (0 until index).map { " " }.joinToString("")
|
||||
val space = (0 until index).joinToString("") { " " }
|
||||
log.error("$space${certificate.toX509CertHolder().subject}")
|
||||
}
|
||||
throw e
|
||||
@ -78,8 +79,7 @@ class InMemoryIdentityService(identities: Iterable<PartyAndCertificate> = emptyS
|
||||
if (firstCertWithThisName != identity.certificate) {
|
||||
val certificates = identity.certPath.certificates
|
||||
val idx = certificates.lastIndexOf(firstCertWithThisName)
|
||||
val certFactory = CertificateFactory.getInstance("X509")
|
||||
val firstPath = certFactory.generateCertPath(certificates.slice(idx..certificates.size - 1))
|
||||
val firstPath = X509CertificateFactory().delegate.generateCertPath(certificates.slice(idx until certificates.size))
|
||||
verifyAndRegisterIdentity(PartyAndCertificate(firstPath))
|
||||
}
|
||||
|
||||
@ -104,7 +104,7 @@ class InMemoryIdentityService(identities: Iterable<PartyAndCertificate> = emptyS
|
||||
val candidate = partyFromKey(party.owningKey)
|
||||
// TODO: This should be done via the network map cache, which is the authoritative source of well known identities
|
||||
return if (candidate != null) {
|
||||
require(party.nameOrNull() == null || party.nameOrNull() == candidate.name) { "Candidate party ${candidate} does not match expected ${party}" }
|
||||
require(party.nameOrNull() == null || party.nameOrNull() == candidate.name) { "Candidate party $candidate does not match expected $party" }
|
||||
wellKnownPartyFromX500Name(candidate.name)
|
||||
} else {
|
||||
null
|
||||
|
@ -9,13 +9,13 @@ import net.corda.core.internal.toX509CertHolder
|
||||
import net.corda.core.node.services.IdentityService
|
||||
import net.corda.core.node.services.UnknownAnonymousPartyException
|
||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||
import net.corda.core.utilities.debug
|
||||
import net.corda.core.utilities.MAX_HASH_HEX_SIZE
|
||||
import net.corda.core.utilities.contextLogger
|
||||
import net.corda.core.utilities.debug
|
||||
import net.corda.node.utilities.AppendOnlyPersistentMap
|
||||
import net.corda.node.utilities.NODE_DATABASE_PREFIX
|
||||
import net.corda.nodeapi.internal.crypto.X509CertificateFactory
|
||||
import org.bouncycastle.cert.X509CertificateHolder
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.security.InvalidAlgorithmParameterException
|
||||
import java.security.PublicKey
|
||||
import java.security.cert.*
|
||||
@ -26,26 +26,21 @@ import javax.persistence.Id
|
||||
import javax.persistence.Lob
|
||||
|
||||
@ThreadSafe
|
||||
class PersistentIdentityService(identities: Iterable<PartyAndCertificate> = emptySet(),
|
||||
confidentialIdentities: Iterable<PartyAndCertificate> = emptySet(),
|
||||
override val trustRoot: X509Certificate,
|
||||
class PersistentIdentityService(override val trustRoot: X509Certificate,
|
||||
vararg caCertificates: X509Certificate) : SingletonSerializeAsToken(), IdentityService {
|
||||
constructor(wellKnownIdentities: Iterable<PartyAndCertificate> = emptySet(),
|
||||
confidentialIdentities: Iterable<PartyAndCertificate> = emptySet(),
|
||||
trustRoot: X509CertificateHolder) : this(wellKnownIdentities, confidentialIdentities, trustRoot.cert)
|
||||
constructor(trustRoot: X509CertificateHolder) : this(trustRoot.cert)
|
||||
|
||||
companion object {
|
||||
private val log = contextLogger()
|
||||
private val certFactory: CertificateFactory = CertificateFactory.getInstance("X.509")
|
||||
|
||||
fun createPKMap(): AppendOnlyPersistentMap<SecureHash, PartyAndCertificate, PersistentIdentity, String> {
|
||||
return AppendOnlyPersistentMap(
|
||||
toPersistentEntityKey = { it.toString() },
|
||||
fromPersistentEntity = {
|
||||
Pair(SecureHash.parse(it.publicKeyHash),
|
||||
PartyAndCertificate(ByteArrayInputStream(it.identity).use {
|
||||
certFactory.generateCertPath(it)
|
||||
}))
|
||||
Pair(
|
||||
SecureHash.parse(it.publicKeyHash),
|
||||
PartyAndCertificate(X509CertificateFactory().delegate.generateCertPath(it.identity.inputStream()))
|
||||
)
|
||||
},
|
||||
toPersistentEntity = { key: SecureHash, value: PartyAndCertificate ->
|
||||
PersistentIdentity(key.toString(), value.certPath.encoded)
|
||||
@ -101,6 +96,10 @@ class PersistentIdentityService(identities: Iterable<PartyAndCertificate> = empt
|
||||
init {
|
||||
val caCertificatesWithRoot: Set<X509Certificate> = caCertificates.toSet() + trustRoot
|
||||
caCertStore = CertStore.getInstance("Collection", CollectionCertStoreParameters(caCertificatesWithRoot))
|
||||
}
|
||||
|
||||
/** Requires a database transaction. */
|
||||
fun loadIdentities(identities: Iterable<PartyAndCertificate> = emptySet(), confidentialIdentities: Iterable<PartyAndCertificate> = emptySet()) {
|
||||
identities.forEach {
|
||||
val key = mapToKey(it)
|
||||
keyToParties.addWithDuplicatesAllowed(key, it, false)
|
||||
@ -135,8 +134,7 @@ class PersistentIdentityService(identities: Iterable<PartyAndCertificate> = empt
|
||||
if (firstCertWithThisName != identity.certificate) {
|
||||
val certificates = identity.certPath.certificates
|
||||
val idx = certificates.lastIndexOf(firstCertWithThisName)
|
||||
val certFactory = CertificateFactory.getInstance("X509")
|
||||
val firstPath = certFactory.generateCertPath(certificates.slice(idx..certificates.size - 1))
|
||||
val firstPath = X509CertificateFactory().delegate.generateCertPath(certificates.slice(idx until certificates.size))
|
||||
verifyAndRegisterIdentity(PartyAndCertificate(firstPath))
|
||||
}
|
||||
|
||||
|
@ -6,14 +6,14 @@ import net.corda.core.internal.cert
|
||||
import net.corda.core.internal.toX509CertHolder
|
||||
import net.corda.core.node.services.IdentityService
|
||||
import net.corda.core.utilities.days
|
||||
import net.corda.node.utilities.CertificateType
|
||||
import net.corda.node.utilities.ContentSignerBuilder
|
||||
import net.corda.node.utilities.X509Utilities
|
||||
import net.corda.nodeapi.internal.crypto.CertificateType
|
||||
import net.corda.nodeapi.internal.crypto.ContentSignerBuilder
|
||||
import net.corda.nodeapi.internal.crypto.X509CertificateFactory
|
||||
import net.corda.nodeapi.internal.crypto.X509Utilities
|
||||
import org.bouncycastle.operator.ContentSigner
|
||||
import java.security.KeyPair
|
||||
import java.security.PublicKey
|
||||
import java.security.Security
|
||||
import java.security.cert.CertificateFactory
|
||||
import java.security.cert.X509Certificate
|
||||
import java.time.Duration
|
||||
|
||||
@ -37,8 +37,7 @@ fun freshCertificate(identityService: IdentityService,
|
||||
val window = X509Utilities.getCertificateValidityWindow(Duration.ZERO, 3650.days, issuerCert)
|
||||
val ourCertificate = X509Utilities.createCertificate(CertificateType.IDENTITY, issuerCert.subject,
|
||||
issuerSigner, issuer.name, subjectPublicKey, window)
|
||||
val certFactory = CertificateFactory.getInstance("X509")
|
||||
val ourCertPath = certFactory.generateCertPath(listOf(ourCertificate.cert) + issuer.certPath.certificates)
|
||||
val ourCertPath = X509CertificateFactory().delegate.generateCertPath(listOf(ourCertificate.cert) + issuer.certPath.certificates)
|
||||
val anonymisedIdentity = PartyAndCertificate(ourCertPath)
|
||||
identityService.verifyAndRegisterIdentity(anonymisedIdentity)
|
||||
return anonymisedIdentity
|
||||
|
@ -20,10 +20,10 @@ import net.corda.node.services.messaging.NodeLoginModule.Companion.NODE_ROLE
|
||||
import net.corda.node.services.messaging.NodeLoginModule.Companion.PEER_ROLE
|
||||
import net.corda.node.services.messaging.NodeLoginModule.Companion.RPC_ROLE
|
||||
import net.corda.node.services.messaging.NodeLoginModule.Companion.VERIFIER_ROLE
|
||||
import net.corda.node.utilities.X509Utilities
|
||||
import net.corda.node.utilities.X509Utilities.CORDA_CLIENT_TLS
|
||||
import net.corda.node.utilities.X509Utilities.CORDA_ROOT_CA
|
||||
import net.corda.node.utilities.loadKeyStore
|
||||
import net.corda.nodeapi.internal.crypto.X509Utilities
|
||||
import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_CLIENT_TLS
|
||||
import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_ROOT_CA
|
||||
import net.corda.nodeapi.internal.crypto.loadKeyStore
|
||||
import net.corda.nodeapi.*
|
||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.INTERNAL_PREFIX
|
||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.NODE_USER
|
||||
|
@ -10,23 +10,23 @@ import net.corda.core.serialization.SerializationDefaults
|
||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||
import net.corda.core.serialization.deserialize
|
||||
import net.corda.core.serialization.serialize
|
||||
import net.corda.core.utilities.*
|
||||
import net.corda.core.utilities.NetworkHostAndPort
|
||||
import net.corda.core.utilities.contextLogger
|
||||
import net.corda.core.utilities.sequence
|
||||
import net.corda.core.utilities.trace
|
||||
import net.corda.node.VersionInfo
|
||||
import net.corda.node.services.config.NodeConfiguration
|
||||
import net.corda.node.services.statemachine.StateMachineManagerImpl
|
||||
import net.corda.node.utilities.*
|
||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent
|
||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent.*
|
||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.P2P_QUEUE
|
||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent.ArtemisAddress
|
||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent.NodeAddress
|
||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent.ServiceAddress
|
||||
import org.apache.activemq.artemis.api.core.ActiveMQObjectClosedException
|
||||
import org.apache.activemq.artemis.api.core.Message.*
|
||||
import org.apache.activemq.artemis.api.core.RoutingType
|
||||
import org.apache.activemq.artemis.api.core.SimpleString
|
||||
import org.apache.activemq.artemis.api.core.client.*
|
||||
import org.apache.activemq.artemis.api.core.client.ClientConsumer
|
||||
import org.apache.activemq.artemis.api.core.client.ClientMessage
|
||||
import java.security.PublicKey
|
||||
import java.time.Instant
|
||||
import java.util.*
|
||||
|
@ -5,14 +5,17 @@ import net.corda.core.messaging.RPCOps
|
||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||
import net.corda.core.utilities.NetworkHostAndPort
|
||||
import net.corda.node.services.RPCUserService
|
||||
import net.corda.node.utilities.*
|
||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.NODE_USER
|
||||
import net.corda.nodeapi.config.SSLConfiguration
|
||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.NODE_USER
|
||||
import net.corda.nodeapi.internal.crypto.X509Utilities
|
||||
import net.corda.nodeapi.internal.crypto.getX509Certificate
|
||||
import net.corda.nodeapi.internal.crypto.loadKeyStore
|
||||
import org.apache.activemq.artemis.api.core.management.ActiveMQServerControl
|
||||
|
||||
class RPCMessagingClient(private val config: SSLConfiguration, serverAddress: NetworkHostAndPort) : SingletonSerializeAsToken() {
|
||||
private val artemis = ArtemisMessagingClient(config, serverAddress)
|
||||
private var rpcServer: RPCServer? = null
|
||||
|
||||
fun start(rpcOps: RPCOps, userService: RPCUserService) = synchronized(this) {
|
||||
val locator = artemis.start().sessionFactory.serverLocator
|
||||
val myCert = loadKeyStore(config.sslKeystore, config.keyStorePassword).getX509Certificate(X509Utilities.CORDA_CLIENT_TLS)
|
||||
|
@ -9,13 +9,11 @@ import org.hibernate.type.descriptor.java.AbstractTypeDescriptor
|
||||
import org.hibernate.type.descriptor.java.ImmutableMutabilityPlan
|
||||
import org.hibernate.type.descriptor.java.MutabilityPlan
|
||||
|
||||
class AbstractPartyDescriptor(identitySvc: () -> IdentityService) : AbstractTypeDescriptor<AbstractParty>(AbstractParty::class.java) {
|
||||
class AbstractPartyDescriptor(private val identityService: IdentityService) : AbstractTypeDescriptor<AbstractParty>(AbstractParty::class.java) {
|
||||
companion object {
|
||||
private val log = contextLogger()
|
||||
}
|
||||
|
||||
private val identityService: IdentityService by lazy(identitySvc)
|
||||
|
||||
override fun fromString(dbData: String?): AbstractParty? {
|
||||
return if (dbData != null) {
|
||||
val party = identityService.wellKnownPartyFromX500Name(CordaX500Name.parse(dbData))
|
||||
|
@ -12,13 +12,11 @@ import javax.persistence.Converter
|
||||
* Completely anonymous parties are stored as null (to preserve privacy).
|
||||
*/
|
||||
@Converter(autoApply = true)
|
||||
class AbstractPartyToX500NameAsStringConverter(identitySvc: () -> IdentityService) : AttributeConverter<AbstractParty, String> {
|
||||
class AbstractPartyToX500NameAsStringConverter(private val identityService: IdentityService) : AttributeConverter<AbstractParty, String> {
|
||||
companion object {
|
||||
private val log = contextLogger()
|
||||
}
|
||||
|
||||
private val identityService: IdentityService by lazy(identitySvc)
|
||||
|
||||
override fun convertToDatabaseColumn(party: AbstractParty?): String? {
|
||||
if (party != null) {
|
||||
val partyName = identityService.wellKnownPartyFromAnonymous(party)?.toString()
|
||||
|
@ -6,8 +6,8 @@ import net.corda.core.schemas.MappedSchema
|
||||
import net.corda.core.utilities.contextLogger
|
||||
import net.corda.core.utilities.toHexString
|
||||
import net.corda.node.services.api.SchemaService
|
||||
import net.corda.node.services.config.DatabaseConfig
|
||||
import net.corda.node.utilities.DatabaseTransactionManager
|
||||
import net.corda.node.utilities.parserTransactionIsolationLevel
|
||||
import org.hibernate.SessionFactory
|
||||
import org.hibernate.boot.MetadataSources
|
||||
import org.hibernate.boot.model.naming.Identifier
|
||||
@ -23,10 +23,9 @@ import org.hibernate.type.descriptor.java.PrimitiveByteArrayTypeDescriptor
|
||||
import org.hibernate.type.descriptor.sql.BlobTypeDescriptor
|
||||
import org.hibernate.type.descriptor.sql.VarbinaryTypeDescriptor
|
||||
import java.sql.Connection
|
||||
import java.util.*
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
class HibernateConfiguration(val schemaService: SchemaService, private val databaseProperties: Properties, private val createIdentityService: () -> IdentityService) {
|
||||
class HibernateConfiguration(val schemaService: SchemaService, private val databaseConfig: DatabaseConfig, private val identityService: IdentityService) {
|
||||
companion object {
|
||||
private val logger = contextLogger()
|
||||
}
|
||||
@ -34,14 +33,13 @@ class HibernateConfiguration(val schemaService: SchemaService, private val datab
|
||||
// TODO: make this a guava cache or similar to limit ability for this to grow forever.
|
||||
private val sessionFactories = ConcurrentHashMap<Set<MappedSchema>, SessionFactory>()
|
||||
|
||||
private val transactionIsolationLevel = parserTransactionIsolationLevel(databaseProperties.getProperty("transactionIsolationLevel") ?: "")
|
||||
val sessionFactoryForRegisteredSchemas = schemaService.schemaOptions.keys.let {
|
||||
logger.info("Init HibernateConfiguration for schemas: $it")
|
||||
// Register the AbstractPartyDescriptor so Hibernate doesn't warn when encountering AbstractParty. Unfortunately
|
||||
// Hibernate warns about not being able to find a descriptor if we don't provide one, but won't use it by default
|
||||
// so we end up providing both descriptor and converter. We should re-examine this in later versions to see if
|
||||
// either Hibernate can be convinced to stop warning, use the descriptor by default, or something else.
|
||||
JavaTypeDescriptorRegistry.INSTANCE.addDescriptor(AbstractPartyDescriptor(createIdentityService))
|
||||
JavaTypeDescriptorRegistry.INSTANCE.addDescriptor(AbstractPartyDescriptor(identityService))
|
||||
sessionFactoryForSchemas(it)
|
||||
}
|
||||
|
||||
@ -56,16 +54,16 @@ class HibernateConfiguration(val schemaService: SchemaService, private val datab
|
||||
// necessarily remain and would likely be replaced by something like Liquibase. For now it is very convenient though.
|
||||
// TODO: replace auto schema generation as it isn't intended for production use, according to Hibernate docs.
|
||||
val config = Configuration(metadataSources).setProperty("hibernate.connection.provider_class", NodeDatabaseConnectionProvider::class.java.name)
|
||||
.setProperty("hibernate.hbm2ddl.auto", if (databaseProperties.getProperty("initDatabase", "true") == "true") "update" else "validate")
|
||||
.setProperty("hibernate.hbm2ddl.auto", if (databaseConfig.initDatabase) "update" else "validate")
|
||||
.setProperty("hibernate.format_sql", "true")
|
||||
.setProperty("hibernate.connection.isolation", transactionIsolationLevel.toString())
|
||||
.setProperty("hibernate.connection.isolation", databaseConfig.transactionIsolationLevel.jdbcValue.toString())
|
||||
|
||||
schemas.forEach { schema ->
|
||||
// TODO: require mechanism to set schemaOptions (databaseSchema, tablePrefix) which are not global to session
|
||||
schema.mappedTypes.forEach { config.addAnnotatedClass(it) }
|
||||
}
|
||||
|
||||
val sessionFactory = buildSessionFactory(config, metadataSources, databaseProperties.getProperty("serverNameTablePrefix", ""))
|
||||
val sessionFactory = buildSessionFactory(config, metadataSources, databaseConfig.serverNameTablePrefix)
|
||||
logger.info("Created session factory for schemas: $schemas")
|
||||
return sessionFactory
|
||||
}
|
||||
@ -80,7 +78,7 @@ class HibernateConfiguration(val schemaService: SchemaService, private val datab
|
||||
}
|
||||
})
|
||||
// register custom converters
|
||||
applyAttributeConverter(AbstractPartyToX500NameAsStringConverter(createIdentityService))
|
||||
applyAttributeConverter(AbstractPartyToX500NameAsStringConverter(identityService))
|
||||
// Register a tweaked version of `org.hibernate.type.MaterializedBlobType` that truncates logged messages.
|
||||
// to avoid OOM when large blobs might get logged.
|
||||
applyBasicType(CordaMaterializedBlobType, CordaMaterializedBlobType.name)
|
||||
@ -132,11 +130,13 @@ class HibernateConfiguration(val schemaService: SchemaService, private val datab
|
||||
private val LOG_SIZE_LIMIT = 1024
|
||||
|
||||
override fun extractLoggableRepresentation(value: ByteArray?): String {
|
||||
return if (value == null) super.extractLoggableRepresentation(value) else {
|
||||
return if (value == null) {
|
||||
super.extractLoggableRepresentation(value)
|
||||
} else {
|
||||
if (value.size <= LOG_SIZE_LIMIT) {
|
||||
return "[size=${value.size}, value=${value.toHexString()}]"
|
||||
"[size=${value.size}, value=${value.toHexString()}]"
|
||||
} else {
|
||||
return "[size=${value.size}, value=${value.copyOfRange(0, LOG_SIZE_LIMIT).toHexString()}...truncated...]"
|
||||
"[size=${value.size}, value=${value.copyOfRange(0, LOG_SIZE_LIMIT).toHexString()}...truncated...]"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -25,7 +25,7 @@ class CordaAuthenticationPlugin(val rpcOps:CordaRPCOps, val userService:RPCUserS
|
||||
|
||||
if (user != null && user.password == credential) {
|
||||
val actor = Actor(Actor.Id(username), userService.id, nodeLegalName)
|
||||
return CordaSSHAuthInfo(true, RPCOpsWithContext(rpcOps, InvocationContext.rpc(actor), RpcPermissions(user.permissions)))
|
||||
return CordaSSHAuthInfo(true, makeRPCOpsWithContext(rpcOps, InvocationContext.rpc(actor), RpcPermissions(user.permissions)))
|
||||
}
|
||||
|
||||
return AuthInfo.UNSUCCESSFUL;
|
||||
|
@ -101,10 +101,10 @@ object InteractiveShell {
|
||||
this.nodeLegalName = configuration.myLegalName
|
||||
this.database = database
|
||||
val dir = configuration.baseDirectory
|
||||
val runSshDeamon = configuration.sshd != null
|
||||
val runSshDaemon = configuration.sshd != null
|
||||
|
||||
val config = Properties()
|
||||
if (runSshDeamon) {
|
||||
if (runSshDaemon) {
|
||||
val sshKeysDir = dir / "sshkey"
|
||||
sshKeysDir.toFile().mkdirs()
|
||||
|
||||
@ -120,7 +120,7 @@ object InteractiveShell {
|
||||
ExternalResolver.INSTANCE.addCommand("start", "An alias for 'flow start'", StartShellCommand::class.java)
|
||||
shell = ShellLifecycle(dir).start(config)
|
||||
|
||||
if (runSshDeamon) {
|
||||
if (runSshDaemon) {
|
||||
Node.printBasicNodeInfo("SSH server listening on port", configuration.sshd!!.port.toString())
|
||||
}
|
||||
}
|
||||
@ -182,7 +182,7 @@ object InteractiveShell {
|
||||
context.refresh()
|
||||
this.config = config
|
||||
start(context)
|
||||
return context.getPlugin(ShellFactory::class.java).create(null, CordaSSHAuthInfo(false, RPCOpsWithContext(rpcOps, net.corda.core.context.InvocationContext.shell(), RpcPermissions.ALL), StdoutANSIProgressRenderer))
|
||||
return context.getPlugin(ShellFactory::class.java).create(null, CordaSSHAuthInfo(false, makeRPCOpsWithContext(rpcOps, net.corda.core.context.InvocationContext.shell(), RpcPermissions.ALL), StdoutANSIProgressRenderer))
|
||||
}
|
||||
}
|
||||
|
||||
@ -236,7 +236,7 @@ object InteractiveShell {
|
||||
try {
|
||||
// Show the progress tracker on the console until the flow completes or is interrupted with a
|
||||
// Ctrl-C keypress.
|
||||
val stateObservable = runFlowFromString({ clazz,args -> rpcOps.startTrackedFlowDynamic (clazz, *args) }, inputData, clazz)
|
||||
val stateObservable = runFlowFromString({ clazz, args -> rpcOps.startTrackedFlowDynamic(clazz, *args) }, inputData, clazz)
|
||||
|
||||
val latch = CountDownLatch(1)
|
||||
ansiProgressRenderer.render(stateObservable, { latch.countDown() })
|
||||
@ -247,7 +247,6 @@ object InteractiveShell {
|
||||
} catch (e: InterruptedException) {
|
||||
// TODO: When the flow framework allows us to kill flows mid-flight, do so here.
|
||||
}
|
||||
|
||||
} catch (e: NoApplicableConstructor) {
|
||||
output.println("No matching constructor found:", Color.red)
|
||||
e.errors.forEach { output.println("- $it", Color.red) }
|
||||
@ -326,7 +325,9 @@ object InteractiveShell {
|
||||
val (stateMachines, stateMachineUpdates) = proxy.stateMachinesFeed()
|
||||
val currentStateMachines = stateMachines.map { StateMachineUpdate.Added(it) }
|
||||
val subscriber = FlowWatchPrintingSubscriber(out)
|
||||
stateMachineUpdates.startWith(currentStateMachines).subscribe(subscriber)
|
||||
database.transaction {
|
||||
stateMachineUpdates.startWith(currentStateMachines).subscribe(subscriber)
|
||||
}
|
||||
var result: Any? = subscriber.future
|
||||
if (result is Future<*>) {
|
||||
if (!result.isDone) {
|
||||
@ -348,7 +349,7 @@ object InteractiveShell {
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun runRPCFromString(input: List<String>, out: RenderPrintWriter, context: InvocationContext<out Any>): Any? {
|
||||
fun runRPCFromString(input: List<String>, out: RenderPrintWriter, context: InvocationContext<out Any>, cordaRPCOps: CordaRPCOps): Any? {
|
||||
val parser = StringToMethodCallParser(CordaRPCOps::class.java, context.attributes["mapper"] as ObjectMapper)
|
||||
|
||||
val cmd = input.joinToString(" ").trim { it <= ' ' }
|
||||
@ -363,7 +364,7 @@ object InteractiveShell {
|
||||
var result: Any? = null
|
||||
try {
|
||||
InputStreamSerializer.invokeContext = context
|
||||
val call = database.transaction { parser.parse(context.attributes["ops"] as CordaRPCOps, cmd) }
|
||||
val call = database.transaction { parser.parse(cordaRPCOps, cmd) }
|
||||
result = call.call()
|
||||
if (result != null && result !is kotlin.Unit && result !is Void) {
|
||||
result = printAndFollowRPCResponse(result, out)
|
||||
|
@ -1,205 +1,45 @@
|
||||
package net.corda.node.shell
|
||||
|
||||
import net.corda.core.concurrent.CordaFuture
|
||||
import net.corda.core.context.InvocationContext
|
||||
import net.corda.core.contracts.ContractState
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.messaging.*
|
||||
import net.corda.core.node.NodeInfo
|
||||
import net.corda.core.node.services.AttachmentId
|
||||
import net.corda.core.node.services.NetworkMapCache
|
||||
import net.corda.core.node.services.Vault
|
||||
import net.corda.core.node.services.vault.*
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.node.services.messaging.CURRENT_RPC_CONTEXT
|
||||
import net.corda.node.services.messaging.RpcAuthContext
|
||||
import net.corda.node.services.messaging.RpcPermissions
|
||||
import java.io.InputStream
|
||||
import java.security.PublicKey
|
||||
import java.time.Instant
|
||||
import java.lang.reflect.InvocationTargetException
|
||||
import java.lang.reflect.Proxy
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import java.util.concurrent.Future
|
||||
|
||||
class RPCOpsWithContext(val cordaRPCOps: CordaRPCOps, val invocationContext:InvocationContext, val rpcPermissions: RpcPermissions) : CordaRPCOps {
|
||||
fun makeRPCOpsWithContext(cordaRPCOps: CordaRPCOps, invocationContext:InvocationContext, rpcPermissions: RpcPermissions) : CordaRPCOps {
|
||||
return Proxy.newProxyInstance(CordaRPCOps::class.java.classLoader, arrayOf(CordaRPCOps::class.java), { proxy, method, args ->
|
||||
RPCContextRunner(invocationContext, rpcPermissions) {
|
||||
try {
|
||||
method.invoke(cordaRPCOps, *(args ?: arrayOf()))
|
||||
} catch (e: InvocationTargetException) {
|
||||
// Unpack exception.
|
||||
throw e.targetException
|
||||
}
|
||||
}.get().getOrThrow()
|
||||
}) as CordaRPCOps
|
||||
}
|
||||
|
||||
|
||||
class RPCContextRunner<T>(val invocationContext:InvocationContext, val permissions:RpcPermissions, val block:() -> T) : Thread() {
|
||||
private var result: CompletableFuture<T> = CompletableFuture()
|
||||
override fun run() {
|
||||
CURRENT_RPC_CONTEXT.set(RpcAuthContext(invocationContext, permissions))
|
||||
try {
|
||||
result.complete(block())
|
||||
} catch (e:Throwable) {
|
||||
result.completeExceptionally(e)
|
||||
}
|
||||
private class RPCContextRunner<T>(val invocationContext:InvocationContext, val rpcPermissions: RpcPermissions, val block:() -> T) : Thread() {
|
||||
private var result: CompletableFuture<T> = CompletableFuture()
|
||||
override fun run() {
|
||||
CURRENT_RPC_CONTEXT.set(RpcAuthContext(invocationContext, rpcPermissions))
|
||||
try {
|
||||
result.complete(block())
|
||||
} catch (e:Throwable) {
|
||||
result.completeExceptionally(e)
|
||||
} finally {
|
||||
CURRENT_RPC_CONTEXT.remove()
|
||||
}
|
||||
|
||||
fun get(): Future<T> {
|
||||
start()
|
||||
join()
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
override fun uploadAttachmentWithMetadata(jar: InputStream, uploader: String, filename: String): SecureHash {
|
||||
return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.uploadAttachmentWithMetadata(jar, uploader, filename) }.get().getOrThrow()
|
||||
}
|
||||
|
||||
override fun queryAttachments(query: AttachmentQueryCriteria, sorting: AttachmentSort?): List<AttachmentId> {
|
||||
return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.queryAttachments(query, sorting) }.get().getOrThrow()
|
||||
}
|
||||
|
||||
override fun <T : ContractState> vaultTrackByWithSorting(contractStateType: Class<out T>, criteria: QueryCriteria, sorting: Sort): DataFeed<Vault.Page<T>, Vault.Update<T>> {
|
||||
return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.vaultTrackByWithSorting(contractStateType, criteria, sorting) }.get().getOrThrow()
|
||||
}
|
||||
|
||||
override fun <T : ContractState> vaultTrackByWithPagingSpec(contractStateType: Class<out T>, criteria: QueryCriteria, paging: PageSpecification): DataFeed<Vault.Page<T>, Vault.Update<T>> {
|
||||
return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.vaultTrackByWithPagingSpec(contractStateType, criteria, paging) }.get().getOrThrow()
|
||||
}
|
||||
|
||||
override fun <T : ContractState> vaultTrackByCriteria(contractStateType: Class<out T>, criteria: QueryCriteria): DataFeed<Vault.Page<T>, Vault.Update<T>> {
|
||||
return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.vaultTrackByCriteria(contractStateType, criteria) }.get().getOrThrow()
|
||||
}
|
||||
|
||||
override fun <T : ContractState> vaultTrack(contractStateType: Class<out T>): DataFeed<Vault.Page<T>, Vault.Update<T>> {
|
||||
return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.vaultTrack(contractStateType) }.get().getOrThrow()
|
||||
}
|
||||
|
||||
override fun <T : ContractState> vaultQueryByWithSorting(contractStateType: Class<out T>, criteria: QueryCriteria, sorting: Sort): Vault.Page<T> {
|
||||
return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.vaultQueryByWithSorting(contractStateType, criteria, sorting) }.get().getOrThrow()
|
||||
}
|
||||
|
||||
override fun <T : ContractState> vaultQueryByWithPagingSpec(contractStateType: Class<out T>, criteria: QueryCriteria, paging: PageSpecification): Vault.Page<T> {
|
||||
return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.vaultQueryByWithPagingSpec(contractStateType, criteria, paging) }.get().getOrThrow()
|
||||
}
|
||||
|
||||
override fun <T : ContractState> vaultQueryByCriteria(criteria: QueryCriteria, contractStateType: Class<out T>): Vault.Page<T> {
|
||||
return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.vaultQueryByCriteria(criteria, contractStateType) }.get().getOrThrow()
|
||||
}
|
||||
|
||||
override fun <T : ContractState> vaultQuery(contractStateType: Class<out T>): Vault.Page<T> {
|
||||
return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.vaultQuery(contractStateType) }.get().getOrThrow()
|
||||
}
|
||||
|
||||
override fun stateMachinesSnapshot(): List<StateMachineInfo> {
|
||||
return RPCContextRunner(invocationContext, rpcPermissions, cordaRPCOps::stateMachinesSnapshot).get().getOrThrow()
|
||||
}
|
||||
|
||||
override fun stateMachinesFeed(): DataFeed<List<StateMachineInfo>, StateMachineUpdate> {
|
||||
return RPCContextRunner(invocationContext, rpcPermissions, cordaRPCOps::stateMachinesFeed).get().getOrThrow()
|
||||
}
|
||||
|
||||
override fun <T : ContractState> vaultQueryBy(criteria: QueryCriteria, paging: PageSpecification, sorting: Sort, contractStateType: Class<out T>): Vault.Page<T> {
|
||||
return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.vaultQueryBy(criteria, paging, sorting, contractStateType) }.get().getOrThrow()
|
||||
}
|
||||
|
||||
override fun <T : ContractState> vaultTrackBy(criteria: QueryCriteria, paging: PageSpecification, sorting: Sort, contractStateType: Class<out T>): DataFeed<Vault.Page<T>, Vault.Update<T>> {
|
||||
return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.vaultTrackBy(criteria, paging, sorting, contractStateType) }.get().getOrThrow()
|
||||
}
|
||||
|
||||
override fun internalVerifiedTransactionsSnapshot(): List<SignedTransaction> {
|
||||
return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.internalVerifiedTransactionsSnapshot() }.get().getOrThrow()
|
||||
}
|
||||
|
||||
override fun internalVerifiedTransactionsFeed(): DataFeed<List<SignedTransaction>, SignedTransaction> {
|
||||
return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.internalVerifiedTransactionsFeed() }.get().getOrThrow()
|
||||
}
|
||||
|
||||
override fun stateMachineRecordedTransactionMappingSnapshot(): List<StateMachineTransactionMapping> {
|
||||
return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.stateMachineRecordedTransactionMappingSnapshot() }.get().getOrThrow()
|
||||
}
|
||||
|
||||
override fun stateMachineRecordedTransactionMappingFeed(): DataFeed<List<StateMachineTransactionMapping>, StateMachineTransactionMapping> {
|
||||
return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.stateMachineRecordedTransactionMappingFeed() }.get().getOrThrow()
|
||||
}
|
||||
|
||||
override fun networkMapSnapshot(): List<NodeInfo> {
|
||||
return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.networkMapSnapshot() }.get().getOrThrow()
|
||||
}
|
||||
|
||||
override fun networkMapFeed(): DataFeed<List<NodeInfo>, NetworkMapCache.MapChange> {
|
||||
return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.networkMapFeed() }.get().getOrThrow()
|
||||
}
|
||||
|
||||
override fun <T> startFlowDynamic(logicType: Class<out FlowLogic<T>>, vararg args: Any?): FlowHandle<T> {
|
||||
return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.startFlowDynamic(logicType, *args) }.get().getOrThrow()
|
||||
}
|
||||
|
||||
override fun <T> startTrackedFlowDynamic(logicType: Class<out FlowLogic<T>>, vararg args: Any?): FlowProgressHandle<T> {
|
||||
return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.startTrackedFlowDynamic(logicType, *args) }.get().getOrThrow()
|
||||
}
|
||||
|
||||
override fun nodeInfo(): NodeInfo {
|
||||
return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.nodeInfo() }.get().getOrThrow()
|
||||
}
|
||||
|
||||
override fun notaryIdentities(): List<Party> {
|
||||
return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.notaryIdentities() }.get().getOrThrow()
|
||||
}
|
||||
|
||||
override fun addVaultTransactionNote(txnId: SecureHash, txnNote: String) {
|
||||
return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.addVaultTransactionNote(txnId, txnNote) }.get().getOrThrow()
|
||||
}
|
||||
|
||||
override fun getVaultTransactionNotes(txnId: SecureHash): Iterable<String> {
|
||||
return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.getVaultTransactionNotes(txnId) }.get().getOrThrow()
|
||||
}
|
||||
|
||||
override fun attachmentExists(id: SecureHash): Boolean {
|
||||
return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.attachmentExists(id) }.get().getOrThrow()
|
||||
}
|
||||
|
||||
override fun openAttachment(id: SecureHash): InputStream {
|
||||
return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.openAttachment(id) }.get().getOrThrow()
|
||||
}
|
||||
|
||||
override fun uploadAttachment(jar: InputStream): SecureHash {
|
||||
return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.uploadAttachment(jar) }.get().getOrThrow()
|
||||
}
|
||||
|
||||
override fun currentNodeTime(): Instant {
|
||||
return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.currentNodeTime() }.get().getOrThrow()
|
||||
}
|
||||
|
||||
override fun waitUntilNetworkReady(): CordaFuture<Void?> {
|
||||
return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.waitUntilNetworkReady() }.get().getOrThrow()
|
||||
}
|
||||
|
||||
override fun wellKnownPartyFromAnonymous(party: AbstractParty): Party? {
|
||||
return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.wellKnownPartyFromAnonymous(party) }.get().getOrThrow()
|
||||
}
|
||||
|
||||
override fun partyFromKey(key: PublicKey): Party? {
|
||||
return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.partyFromKey(key) }.get().getOrThrow()
|
||||
}
|
||||
|
||||
override fun wellKnownPartyFromX500Name(x500Name: CordaX500Name): Party? {
|
||||
return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.wellKnownPartyFromX500Name(x500Name) }.get().getOrThrow()
|
||||
}
|
||||
|
||||
override fun notaryPartyFromX500Name(x500Name: CordaX500Name): Party? {
|
||||
return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.notaryPartyFromX500Name(x500Name) }.get().getOrThrow()
|
||||
}
|
||||
|
||||
override fun partiesFromName(query: String, exactMatch: Boolean): Set<Party> {
|
||||
return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.partiesFromName(query, exactMatch) }.get().getOrThrow()
|
||||
}
|
||||
|
||||
override fun registeredFlows(): List<String> {
|
||||
return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.registeredFlows() }.get().getOrThrow()
|
||||
}
|
||||
|
||||
override fun nodeInfoFromParty(party: AbstractParty): NodeInfo? {
|
||||
return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.nodeInfoFromParty(party) }.get().getOrThrow()
|
||||
}
|
||||
|
||||
override fun clearNetworkMapCache() {
|
||||
return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.clearNetworkMapCache() }.get().getOrThrow()
|
||||
fun get(): Future<T> {
|
||||
start()
|
||||
join()
|
||||
return result
|
||||
}
|
||||
}
|
@ -169,7 +169,7 @@ abstract class ANSIProgressRenderer {
|
||||
|
||||
}
|
||||
|
||||
class CRaSHNSIProgressRenderer(val renderPrintWriter:RenderPrintWriter) : ANSIProgressRenderer() {
|
||||
class CRaSHANSIProgressRenderer(val renderPrintWriter:RenderPrintWriter) : ANSIProgressRenderer() {
|
||||
|
||||
override fun printLine(line: String) {
|
||||
renderPrintWriter.println(line)
|
||||
@ -181,7 +181,7 @@ class CRaSHNSIProgressRenderer(val renderPrintWriter:RenderPrintWriter) : ANSIPr
|
||||
}
|
||||
|
||||
override fun setup() {
|
||||
//we assume SSH always use ansi
|
||||
// We assume SSH always use ANSI.
|
||||
usingANSI = true
|
||||
}
|
||||
|
||||
|
@ -1,40 +0,0 @@
|
||||
package net.corda.node.utilities
|
||||
|
||||
import net.corda.core.crypto.SignatureScheme
|
||||
import org.bouncycastle.asn1.x509.AlgorithmIdentifier
|
||||
import org.bouncycastle.operator.ContentSigner
|
||||
import java.io.OutputStream
|
||||
import java.security.PrivateKey
|
||||
import java.security.Provider
|
||||
import java.security.SecureRandom
|
||||
import java.security.Signature
|
||||
|
||||
/**
|
||||
* Provide extra OID look up for signature algorithm not supported by bouncy castle.
|
||||
* This builder will use bouncy castle's JcaContentSignerBuilder as fallback for unknown algorithm.
|
||||
*/
|
||||
object ContentSignerBuilder {
|
||||
fun build(signatureScheme: SignatureScheme, privateKey: PrivateKey, provider: Provider, random: SecureRandom? = null): ContentSigner {
|
||||
val sigAlgId = signatureScheme.signatureOID
|
||||
val sig = Signature.getInstance(signatureScheme.signatureName, provider).apply {
|
||||
if (random != null) {
|
||||
initSign(privateKey, random)
|
||||
} else {
|
||||
initSign(privateKey)
|
||||
}
|
||||
}
|
||||
return object : ContentSigner {
|
||||
private val stream = SignatureOutputStream(sig)
|
||||
override fun getAlgorithmIdentifier(): AlgorithmIdentifier = sigAlgId
|
||||
override fun getOutputStream(): OutputStream = stream
|
||||
override fun getSignature(): ByteArray = stream.signature
|
||||
}
|
||||
}
|
||||
|
||||
private class SignatureOutputStream(private val sig: Signature) : OutputStream() {
|
||||
internal val signature: ByteArray get() = sig.sign()
|
||||
override fun write(bytes: ByteArray, off: Int, len: Int) = sig.update(bytes, off, len)
|
||||
override fun write(bytes: ByteArray) = sig.update(bytes)
|
||||
override fun write(b: Int) = sig.update(b.toByte())
|
||||
}
|
||||
}
|
@ -4,6 +4,7 @@ import com.zaxxer.hikari.HikariConfig
|
||||
import com.zaxxer.hikari.HikariDataSource
|
||||
import net.corda.core.node.services.IdentityService
|
||||
import net.corda.node.services.api.SchemaService
|
||||
import net.corda.node.services.config.DatabaseConfig
|
||||
import net.corda.node.services.persistence.HibernateConfiguration
|
||||
import net.corda.node.services.schema.NodeSchemaService
|
||||
import rx.Observable
|
||||
@ -21,20 +22,23 @@ import java.util.concurrent.CopyOnWriteArrayList
|
||||
const val NODE_DATABASE_PREFIX = "node_"
|
||||
|
||||
//HikariDataSource implements Closeable which allows CordaPersistence to be Closeable
|
||||
class CordaPersistence(var dataSource: HikariDataSource, private val schemaService: SchemaService,
|
||||
private val createIdentityService: () -> IdentityService, databaseProperties: Properties) : Closeable {
|
||||
var transactionIsolationLevel = parserTransactionIsolationLevel(databaseProperties.getProperty("transactionIsolationLevel"))
|
||||
|
||||
class CordaPersistence(
|
||||
val dataSource: HikariDataSource,
|
||||
private val schemaService: SchemaService,
|
||||
private val identityService: IdentityService,
|
||||
databaseConfig: DatabaseConfig
|
||||
) : Closeable {
|
||||
val transactionIsolationLevel = databaseConfig.transactionIsolationLevel.jdbcValue
|
||||
val hibernateConfig: HibernateConfiguration by lazy {
|
||||
transaction {
|
||||
HibernateConfiguration(schemaService, databaseProperties, createIdentityService)
|
||||
HibernateConfiguration(schemaService, databaseConfig, identityService)
|
||||
}
|
||||
}
|
||||
val entityManagerFactory get() = hibernateConfig.sessionFactoryForRegisteredSchemas
|
||||
|
||||
companion object {
|
||||
fun connect(dataSource: HikariDataSource, schemaService: SchemaService, createIdentityService: () -> IdentityService, databaseProperties: Properties): CordaPersistence {
|
||||
return CordaPersistence(dataSource, schemaService, createIdentityService, databaseProperties).apply {
|
||||
fun connect(dataSource: HikariDataSource, schemaService: SchemaService, identityService: IdentityService, databaseConfig: DatabaseConfig): CordaPersistence {
|
||||
return CordaPersistence(dataSource, schemaService, identityService, databaseConfig).apply {
|
||||
DatabaseTransactionManager(this)
|
||||
}
|
||||
}
|
||||
@ -53,9 +57,7 @@ class CordaPersistence(var dataSource: HikariDataSource, private val schemaServi
|
||||
/**
|
||||
* Creates an instance of [DatabaseTransaction], with the transaction isolation level specified at the creation time.
|
||||
*/
|
||||
fun createTransaction(): DatabaseTransaction {
|
||||
return createTransaction(transactionIsolationLevel)
|
||||
}
|
||||
fun createTransaction(): DatabaseTransaction = createTransaction(transactionIsolationLevel)
|
||||
|
||||
fun createSession(): Connection {
|
||||
// We need to set the database for the current [Thread] or [Fiber] here as some tests share threads across databases.
|
||||
@ -78,9 +80,7 @@ class CordaPersistence(var dataSource: HikariDataSource, private val schemaServi
|
||||
* Executes given statement in the scope of transaction with the transaction level specified at the creation time.
|
||||
* @param statement to be executed in the scope of this transaction.
|
||||
*/
|
||||
fun <T> transaction(statement: DatabaseTransaction.() -> T): T {
|
||||
return transaction(transactionIsolationLevel, statement)
|
||||
}
|
||||
fun <T> transaction(statement: DatabaseTransaction.() -> T): T = transaction(transactionIsolationLevel, statement)
|
||||
|
||||
private fun <T> transaction(transactionIsolation: Int, repetitionAttempts: Int, statement: DatabaseTransaction.() -> T): T {
|
||||
val outer = DatabaseTransactionManager.currentOrNull()
|
||||
@ -120,11 +120,10 @@ class CordaPersistence(var dataSource: HikariDataSource, private val schemaServi
|
||||
}
|
||||
}
|
||||
|
||||
fun configureDatabase(dataSourceProperties: Properties, databaseProperties: Properties?, createIdentityService: () -> IdentityService, schemaService: SchemaService = NodeSchemaService(null)): CordaPersistence {
|
||||
fun configureDatabase(dataSourceProperties: Properties, databaseConfig: DatabaseConfig, identityService: IdentityService, schemaService: SchemaService = NodeSchemaService(null)): CordaPersistence {
|
||||
val config = HikariConfig(dataSourceProperties)
|
||||
val dataSource = HikariDataSource(config)
|
||||
val persistence = CordaPersistence.connect(dataSource, schemaService, createIdentityService, databaseProperties ?: Properties())
|
||||
|
||||
val persistence = CordaPersistence.connect(dataSource, schemaService, identityService, databaseConfig)
|
||||
// Check not in read-only mode.
|
||||
persistence.transaction {
|
||||
persistence.dataSource.connection.use {
|
||||
@ -217,15 +216,3 @@ fun <T : Any> rx.Observable<T>.wrapWithDatabaseTransaction(db: CordaPersistence?
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun parserTransactionIsolationLevel(property: String?): Int =
|
||||
when (property) {
|
||||
"none" -> Connection.TRANSACTION_NONE
|
||||
"readUncommitted" -> Connection.TRANSACTION_READ_UNCOMMITTED
|
||||
"readCommitted" -> Connection.TRANSACTION_READ_COMMITTED
|
||||
"repeatableRead" -> Connection.TRANSACTION_REPEATABLE_READ
|
||||
"serializable" -> Connection.TRANSACTION_SERIALIZABLE
|
||||
else -> {
|
||||
Connection.TRANSACTION_REPEATABLE_READ
|
||||
}
|
||||
}
|
||||
|
@ -1,212 +0,0 @@
|
||||
@file:JvmName("KeyStoreUtilities")
|
||||
|
||||
package net.corda.node.utilities
|
||||
|
||||
import net.corda.core.crypto.Crypto
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.internal.*
|
||||
import org.bouncycastle.cert.X509CertificateHolder
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
import java.nio.file.Path
|
||||
import java.security.*
|
||||
import java.security.cert.CertPath
|
||||
import java.security.cert.Certificate
|
||||
import java.security.cert.CertificateFactory
|
||||
import java.security.cert.X509Certificate
|
||||
|
||||
const val KEYSTORE_TYPE = "JKS"
|
||||
|
||||
/**
|
||||
* Helper method to either open an existing keystore for modification, or create a new blank keystore.
|
||||
* @param keyStoreFilePath location of KeyStore file.
|
||||
* @param storePassword password to open the store. This does not have to be the same password as any keys stored,
|
||||
* but for SSL purposes this is recommended.
|
||||
* @return returns the KeyStore opened/created.
|
||||
*/
|
||||
fun loadOrCreateKeyStore(keyStoreFilePath: Path, storePassword: String): KeyStore {
|
||||
val pass = storePassword.toCharArray()
|
||||
val keyStore = KeyStore.getInstance(KEYSTORE_TYPE)
|
||||
if (keyStoreFilePath.exists()) {
|
||||
keyStoreFilePath.read { keyStore.load(it, pass) }
|
||||
} else {
|
||||
keyStore.load(null, pass)
|
||||
keyStoreFilePath.write { keyStore.store(it, pass) }
|
||||
}
|
||||
return keyStore
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to open an existing keystore for modification/read.
|
||||
* @param keyStoreFilePath location of KeyStore file which must exist, or this will throw FileNotFoundException.
|
||||
* @param storePassword password to open the store. This does not have to be the same password as any keys stored,
|
||||
* but for SSL purposes this is recommended.
|
||||
* @return returns the KeyStore opened.
|
||||
* @throws IOException if there was an error reading the key store from the file.
|
||||
* @throws KeyStoreException if the password is incorrect or the key store is damaged.
|
||||
*/
|
||||
@Throws(KeyStoreException::class, IOException::class)
|
||||
fun loadKeyStore(keyStoreFilePath: Path, storePassword: String): KeyStore {
|
||||
return keyStoreFilePath.read { loadKeyStore(it, storePassword) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to open an existing keystore for modification/read.
|
||||
* @param input stream containing a KeyStore e.g. loaded from a resource file.
|
||||
* @param storePassword password to open the store. This does not have to be the same password as any keys stored,
|
||||
* but for SSL purposes this is recommended.
|
||||
* @return returns the KeyStore opened.
|
||||
* @throws IOException if there was an error reading the key store from the stream.
|
||||
* @throws KeyStoreException if the password is incorrect or the key store is damaged.
|
||||
*/
|
||||
@Throws(KeyStoreException::class, IOException::class)
|
||||
fun loadKeyStore(input: InputStream, storePassword: String): KeyStore {
|
||||
val pass = storePassword.toCharArray()
|
||||
val keyStore = KeyStore.getInstance(KEYSTORE_TYPE)
|
||||
input.use {
|
||||
keyStore.load(input, pass)
|
||||
}
|
||||
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<out X509CertificateHolder>) {
|
||||
addOrReplaceKey(alias, key, password, chain.map { it.cert }.toTypedArray<Certificate>())
|
||||
}
|
||||
|
||||
/**
|
||||
* 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<out Certificate>) {
|
||||
if (containsAlias(alias)) {
|
||||
this.deleteEntry(alias)
|
||||
}
|
||||
this.setKeyEntry(alias, key, password, chain)
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper extension method to add, or overwrite any public certificate data in store.
|
||||
* @param alias name to record the public certificate under.
|
||||
* @param cert certificate to store.
|
||||
*/
|
||||
fun KeyStore.addOrReplaceCertificate(alias: String, cert: Certificate) {
|
||||
if (containsAlias(alias)) {
|
||||
this.deleteEntry(alias)
|
||||
}
|
||||
this.setCertificateEntry(alias, cert)
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method save KeyStore to storage.
|
||||
* @param keyStoreFilePath the file location to save to.
|
||||
* @param storePassword password to access the store in future. This does not have to be the same password as any keys stored,
|
||||
* but for SSL purposes this is recommended.
|
||||
*/
|
||||
fun KeyStore.save(keyStoreFilePath: Path, storePassword: String) = keyStoreFilePath.write { store(it, storePassword) }
|
||||
|
||||
fun KeyStore.store(out: OutputStream, password: String) = store(out, password.toCharArray())
|
||||
|
||||
/**
|
||||
* Extract public and private keys from a KeyStore file assuming storage alias is known.
|
||||
* @param alias The name to lookup the Key and Certificate chain from.
|
||||
* @param keyPassword Password to unlock the private key entries.
|
||||
* @return The KeyPair found in the KeyStore under the specified alias.
|
||||
*/
|
||||
fun KeyStore.getKeyPair(alias: String, keyPassword: String): KeyPair = getCertificateAndKeyPair(alias, keyPassword).keyPair
|
||||
|
||||
/**
|
||||
* Helper method to load a Certificate and KeyPair from their KeyStore.
|
||||
* The access details should match those of the createCAKeyStoreAndTrustStore call used to manufacture the keys.
|
||||
* @param alias The name to search for the data. Typically if generated with the methods here this will be one of
|
||||
* CERT_PRIVATE_KEY_ALIAS, ROOT_CA_CERT_PRIVATE_KEY_ALIAS, INTERMEDIATE_CA_PRIVATE_KEY_ALIAS defined above.
|
||||
* @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)))
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract public X509 certificate from a KeyStore file assuming storage alias is known.
|
||||
* @param alias The name to lookup the Key and Certificate chain from.
|
||||
* @return The X509Certificate found in the KeyStore under the specified alias.
|
||||
*/
|
||||
fun KeyStore.getX509Certificate(alias: String): X509Certificate {
|
||||
val certificate = getCertificate(alias) ?: throw IllegalArgumentException("No certificate under alias \"$alias\".")
|
||||
return certificate as? X509Certificate ?: throw IllegalArgumentException("Certificate under alias \"$alias\" is not an X.509 certificate.")
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract a private key from a KeyStore file assuming storage alias is known.
|
||||
* By default, a JKS keystore returns PrivateKey implementations supported by the SUN provider.
|
||||
* For instance, if one imports a BouncyCastle ECC key, JKS will return a SUN ECC key implementation on getKey.
|
||||
* To convert to a supported implementation, an encode->decode method is applied to the keystore's returned object.
|
||||
* @param alias The name to lookup the Key.
|
||||
* @param keyPassword Password to unlock the private key entries.
|
||||
* @return the requested private key in supported type.
|
||||
* @throws KeyStoreException if the keystore has not been initialized.
|
||||
* @throws NoSuchAlgorithmException if the algorithm for recovering the key cannot be found (not supported from the Keystore provider).
|
||||
* @throws UnrecoverableKeyException if the key cannot be recovered (e.g., the given password is wrong).
|
||||
* @throws IllegalArgumentException on not supported scheme or if the given key specification
|
||||
* is inappropriate for a supported key factory to produce a private key.
|
||||
*/
|
||||
fun KeyStore.getSupportedKey(alias: String, keyPassword: String): PrivateKey {
|
||||
val keyPass = keyPassword.toCharArray()
|
||||
val key = getKey(alias, keyPass) as PrivateKey
|
||||
return Crypto.toSupportedPrivateKey(key)
|
||||
}
|
||||
|
||||
class KeyStoreWrapper(private val storePath: Path, private val storePassword: String) {
|
||||
private val keyStore = storePath.read { loadKeyStore(it, storePassword) }
|
||||
|
||||
private fun createCertificate(serviceName: CordaX500Name, pubKey: PublicKey): CertPath {
|
||||
val clientCertPath = keyStore.getCertificateChain(X509Utilities.CORDA_CLIENT_CA)
|
||||
// Assume key password = store password.
|
||||
val clientCA = certificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA)
|
||||
// Create new keys and store in keystore.
|
||||
val cert = X509Utilities.createCertificate(CertificateType.IDENTITY, clientCA.certificate, clientCA.keyPair, serviceName, pubKey)
|
||||
val certPath = CertificateFactory.getInstance("X509").generateCertPath(listOf(cert.cert) + clientCertPath)
|
||||
require(certPath.certificates.isNotEmpty()) { "Certificate path cannot be empty" }
|
||||
// TODO: X509Utilities.validateCertificateChain()
|
||||
return certPath
|
||||
}
|
||||
|
||||
fun signAndSaveNewKeyPair(serviceName: CordaX500Name, privateKeyAlias: String, keyPair: KeyPair) {
|
||||
val certPath = createCertificate(serviceName, keyPair.public)
|
||||
// Assume key password = store password.
|
||||
keyStore.addOrReplaceKey(privateKeyAlias, keyPair.private, storePassword.toCharArray(), certPath.certificates.toTypedArray())
|
||||
keyStore.save(storePath, storePassword)
|
||||
}
|
||||
|
||||
fun savePublicKey(serviceName: CordaX500Name, pubKeyAlias: String, pubKey: PublicKey) {
|
||||
val certPath = createCertificate(serviceName, pubKey)
|
||||
// Assume key password = store password.
|
||||
keyStore.addOrReplaceCertificate(pubKeyAlias, certPath.certificates.first())
|
||||
keyStore.save(storePath, storePassword)
|
||||
}
|
||||
|
||||
// Delegate methods to keystore. Sadly keystore doesn't have an interface.
|
||||
fun containsAlias(alias: String) = keyStore.containsAlias(alias)
|
||||
|
||||
fun getX509Certificate(alias: String) = keyStore.getX509Certificate(alias)
|
||||
|
||||
fun getCertificateChain(alias: String): Array<out Certificate> = keyStore.getCertificateChain(alias)
|
||||
|
||||
fun getCertificate(alias: String): Certificate = keyStore.getCertificate(alias)
|
||||
|
||||
fun certificateAndKeyPair(alias: String): CertificateAndKeyPair = keyStore.getCertificateAndKeyPair(alias, storePassword)
|
||||
}
|
@ -8,6 +8,7 @@ import net.corda.core.internal.cert
|
||||
import net.corda.core.internal.createDirectories
|
||||
import net.corda.core.internal.div
|
||||
import net.corda.core.utilities.trace
|
||||
import net.corda.nodeapi.internal.crypto.*
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.nio.file.Path
|
||||
|
||||
|
@ -1,312 +0,0 @@
|
||||
package net.corda.node.utilities
|
||||
|
||||
import net.corda.core.crypto.Crypto
|
||||
import net.corda.core.crypto.SignatureScheme
|
||||
import net.corda.core.crypto.random63BitValue
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.internal.x500Name
|
||||
import net.corda.core.utilities.days
|
||||
import net.corda.core.utilities.millis
|
||||
import org.bouncycastle.asn1.ASN1EncodableVector
|
||||
import org.bouncycastle.asn1.ASN1Sequence
|
||||
import org.bouncycastle.asn1.DERSequence
|
||||
import org.bouncycastle.asn1.DERUTF8String
|
||||
import org.bouncycastle.asn1.x500.X500Name
|
||||
import org.bouncycastle.asn1.x500.style.BCStyle
|
||||
import org.bouncycastle.asn1.x509.*
|
||||
import org.bouncycastle.asn1.x509.Extension
|
||||
import org.bouncycastle.cert.X509CertificateHolder
|
||||
import org.bouncycastle.cert.X509v3CertificateBuilder
|
||||
import org.bouncycastle.cert.bc.BcX509ExtensionUtils
|
||||
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder
|
||||
import org.bouncycastle.openssl.jcajce.JcaPEMWriter
|
||||
import org.bouncycastle.operator.ContentSigner
|
||||
import org.bouncycastle.operator.jcajce.JcaContentVerifierProviderBuilder
|
||||
import org.bouncycastle.pkcs.PKCS10CertificationRequest
|
||||
import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequestBuilder
|
||||
import org.bouncycastle.util.io.pem.PemReader
|
||||
import java.io.FileReader
|
||||
import java.io.FileWriter
|
||||
import java.io.InputStream
|
||||
import java.math.BigInteger
|
||||
import java.nio.file.Path
|
||||
import java.security.KeyPair
|
||||
import java.security.PublicKey
|
||||
import java.security.cert.*
|
||||
import java.security.cert.Certificate
|
||||
import java.time.Duration
|
||||
import java.time.Instant
|
||||
import java.time.temporal.ChronoUnit
|
||||
import java.util.*
|
||||
|
||||
object X509Utilities {
|
||||
val DEFAULT_IDENTITY_SIGNATURE_SCHEME = Crypto.EDDSA_ED25519_SHA512
|
||||
val DEFAULT_TLS_SIGNATURE_SCHEME = Crypto.ECDSA_SECP256R1_SHA256
|
||||
|
||||
// Aliases for private keys and certificates.
|
||||
const val CORDA_ROOT_CA = "cordarootca"
|
||||
const val CORDA_INTERMEDIATE_CA = "cordaintermediateca"
|
||||
const val CORDA_CLIENT_TLS = "cordaclienttls"
|
||||
const val CORDA_CLIENT_CA = "cordaclientca"
|
||||
|
||||
const val CORDA_CLIENT_CA_CN = "Corda Client CA Certificate"
|
||||
|
||||
private val DEFAULT_VALIDITY_WINDOW = Pair(0.millis, 3650.days)
|
||||
/**
|
||||
* Helper function to return the latest out of an instant and an optional date.
|
||||
*/
|
||||
private fun max(first: Instant, second: Date?): Date {
|
||||
return if (second != null && second.time > first.toEpochMilli())
|
||||
second
|
||||
else
|
||||
Date(first.toEpochMilli())
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to return the earliest out of an instant and an optional date.
|
||||
*/
|
||||
private fun min(first: Instant, second: Date?): Date {
|
||||
return if (second != null && second.time < first.toEpochMilli())
|
||||
second
|
||||
else
|
||||
Date(first.toEpochMilli())
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to get a notBefore and notAfter pair from current day bounded by parent certificate validity range.
|
||||
* @param before duration to roll back returned start date relative to current date.
|
||||
* @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<Date, Date> {
|
||||
val startOfDayUTC = Instant.now().truncatedTo(ChronoUnit.DAYS)
|
||||
val notBefore = max(startOfDayUTC - before, parent?.notBefore)
|
||||
val notAfter = min(startOfDayUTC + after, parent?.notAfter)
|
||||
return Pair(notBefore, notAfter)
|
||||
}
|
||||
|
||||
/*
|
||||
* Create a de novo root self-signed X509 v3 CA cert.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun createSelfSignedCACertificate(subject: CordaX500Name, keyPair: KeyPair, validityWindow: Pair<Duration, Duration> = DEFAULT_VALIDITY_WINDOW): X509CertificateHolder {
|
||||
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<Duration, Duration> = DEFAULT_VALIDITY_WINDOW,
|
||||
nameConstraints: NameConstraints? = null): X509CertificateHolder
|
||||
= 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<Duration, Duration> = 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)
|
||||
}
|
||||
|
||||
@Throws(CertPathValidatorException::class)
|
||||
fun validateCertificateChain(trustedRoot: X509Certificate, vararg certificates: Certificate) {
|
||||
require(certificates.isNotEmpty()) { "Certificate path must contain at least one certificate" }
|
||||
val certFactory = CertificateFactory.getInstance("X509")
|
||||
val params = PKIXParameters(setOf(TrustAnchor(trustedRoot, null)))
|
||||
params.isRevocationEnabled = false
|
||||
val certPath = certFactory.generateCertPath(certificates.toList())
|
||||
val pathValidator = CertPathValidator.getInstance("PKIX")
|
||||
pathValidator.validate(certPath, params)
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 filename Target filename.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun saveCertificateAsPEMFile(x509Certificate: X509CertificateHolder, filename: Path) {
|
||||
FileWriter(filename.toFile()).use {
|
||||
JcaPEMWriter(it).use {
|
||||
it.writeObject(x509Certificate)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to load back a .pem/.cer format file copy of a certificate.
|
||||
* @param filename Source filename.
|
||||
* @return The X509Certificate that was encoded in the file.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun loadCertificateFromPEMFile(filename: Path): X509CertificateHolder {
|
||||
val reader = PemReader(FileReader(filename.toFile()))
|
||||
val pemObject = reader.readPemObject()
|
||||
val cert = X509CertificateHolder(pemObject.content)
|
||||
return cert.apply {
|
||||
isValidOn(Date())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
fun createCertificate(certificateType: CertificateType,
|
||||
issuer: CordaX500Name,
|
||||
subject: CordaX500Name,
|
||||
subjectPublicKey: PublicKey,
|
||||
validityWindow: Pair<Date, Date>,
|
||||
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<Date, Date>,
|
||||
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 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)
|
||||
.addExtension(Extension.extendedKeyUsage, false, keyPurposes)
|
||||
|
||||
if (nameConstraints != null) {
|
||||
builder.addExtension(Extension.nameConstraints, true, nameConstraints)
|
||||
}
|
||||
return builder
|
||||
}
|
||||
|
||||
/**
|
||||
* Build and sign an X.509 certificate with the given signer.
|
||||
*
|
||||
* @param issuer name of the issuing entity.
|
||||
* @param issuerSigner content signer to sign the certificate with.
|
||||
* @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,
|
||||
issuerSigner: ContentSigner,
|
||||
subject: CordaX500Name,
|
||||
subjectPublicKey: PublicKey,
|
||||
validityWindow: Pair<Date, Date>,
|
||||
nameConstraints: NameConstraints? = null): X509CertificateHolder {
|
||||
val builder = createCertificate(certificateType, issuer, subject.x500Name, subjectPublicKey, validityWindow, nameConstraints)
|
||||
return builder.build(issuerSigner).apply {
|
||||
require(isValidOn(Date()))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build and sign an X.509 certificate with CA cert private key.
|
||||
*
|
||||
* @param issuer name of the issuing entity.
|
||||
* @param issuerKeyPair the public & private key to sign the certificate with.
|
||||
* @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, issuerKeyPair: KeyPair,
|
||||
subject: X500Name, subjectPublicKey: PublicKey,
|
||||
validityWindow: Pair<Date, Date>,
|
||||
nameConstraints: NameConstraints? = null): X509CertificateHolder {
|
||||
|
||||
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 {
|
||||
require(isValidOn(Date()))
|
||||
require(isSignatureValid(JcaContentVerifierProviderBuilder().build(issuerKeyPair.public)))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create certificate signing request using provided information.
|
||||
*/
|
||||
internal fun createCertificateSigningRequest(subject: CordaX500Name, 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)
|
||||
}
|
||||
|
||||
fun createCertificateSigningRequest(subject: CordaX500Name, email: String, keyPair: KeyPair) = createCertificateSigningRequest(subject, email, keyPair, DEFAULT_TLS_SIGNATURE_SCHEME)
|
||||
}
|
||||
|
||||
|
||||
class CertificateStream(val input: InputStream) {
|
||||
private val certificateFactory = CertificateFactory.getInstance("X.509")
|
||||
|
||||
fun nextCertificate(): X509Certificate = certificateFactory.generateCertificate(input) as X509Certificate
|
||||
}
|
||||
|
||||
enum class CertificateType(val keyUsage: KeyUsage, vararg val purposes: KeyPurposeId, val isCA: Boolean) {
|
||||
ROOT_CA(KeyUsage(KeyUsage.digitalSignature or KeyUsage.keyCertSign or KeyUsage.cRLSign), KeyPurposeId.id_kp_serverAuth, KeyPurposeId.id_kp_clientAuth, KeyPurposeId.anyExtendedKeyUsage, isCA = true),
|
||||
INTERMEDIATE_CA(KeyUsage(KeyUsage.digitalSignature or KeyUsage.keyCertSign or KeyUsage.cRLSign), KeyPurposeId.id_kp_serverAuth, KeyPurposeId.id_kp_clientAuth, KeyPurposeId.anyExtendedKeyUsage, isCA = true),
|
||||
CLIENT_CA(KeyUsage(KeyUsage.digitalSignature or KeyUsage.keyCertSign or KeyUsage.cRLSign), KeyPurposeId.id_kp_serverAuth, KeyPurposeId.id_kp_clientAuth, KeyPurposeId.anyExtendedKeyUsage, isCA = true),
|
||||
TLS(KeyUsage(KeyUsage.digitalSignature or KeyUsage.keyEncipherment or KeyUsage.keyAgreement), KeyPurposeId.id_kp_serverAuth, KeyPurposeId.id_kp_clientAuth, KeyPurposeId.anyExtendedKeyUsage, isCA = false),
|
||||
// TODO: Identity certs should have only limited depth (i.e. 1) CA signing capability, with tight name constraints
|
||||
IDENTITY(KeyUsage(KeyUsage.digitalSignature or KeyUsage.keyCertSign), KeyPurposeId.id_kp_serverAuth, KeyPurposeId.id_kp_clientAuth, KeyPurposeId.anyExtendedKeyUsage, isCA = true)
|
||||
}
|
||||
|
||||
data class CertificateAndKeyPair(val certificate: X509CertificateHolder, val keyPair: KeyPair)
|
@ -2,7 +2,7 @@ package net.corda.node.utilities.registration
|
||||
|
||||
import com.google.common.net.MediaType
|
||||
import net.corda.core.internal.openHttpConnection
|
||||
import net.corda.node.utilities.CertificateStream
|
||||
import net.corda.nodeapi.internal.crypto.X509CertificateFactory
|
||||
import org.apache.commons.io.IOUtils
|
||||
import org.bouncycastle.pkcs.PKCS10CertificationRequest
|
||||
import java.io.IOException
|
||||
@ -32,9 +32,9 @@ class HTTPNetworkRegistrationService(compatibilityZoneURL: URL) : NetworkRegistr
|
||||
return when (conn.responseCode) {
|
||||
HTTP_OK -> ZipInputStream(conn.inputStream).use {
|
||||
val certificates = ArrayList<Certificate>()
|
||||
val stream = CertificateStream(it)
|
||||
val factory = X509CertificateFactory()
|
||||
while (it.nextEntry != null) {
|
||||
certificates.add(stream.nextCertificate())
|
||||
certificates += factory.generateCertificate(it)
|
||||
}
|
||||
certificates.toTypedArray()
|
||||
}
|
||||
|
@ -5,10 +5,10 @@ import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.internal.*
|
||||
import net.corda.core.utilities.seconds
|
||||
import net.corda.node.services.config.NodeConfiguration
|
||||
import net.corda.node.utilities.*
|
||||
import net.corda.node.utilities.X509Utilities.CORDA_CLIENT_CA
|
||||
import net.corda.node.utilities.X509Utilities.CORDA_CLIENT_TLS
|
||||
import net.corda.node.utilities.X509Utilities.CORDA_ROOT_CA
|
||||
import net.corda.nodeapi.internal.crypto.*
|
||||
import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_CLIENT_CA
|
||||
import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_CLIENT_TLS
|
||||
import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_ROOT_CA
|
||||
import org.bouncycastle.openssl.jcajce.JcaPEMWriter
|
||||
import org.bouncycastle.util.io.pem.PemObject
|
||||
import java.io.StringWriter
|
||||
|
@ -10,7 +10,7 @@ dataSourceProperties = {
|
||||
"dataSource.password" = ""
|
||||
}
|
||||
database = {
|
||||
transactionIsolationLevel = "repeatableRead"
|
||||
transactionIsolationLevel = "REPEATABLE_READ"
|
||||
initDatabase = true
|
||||
}
|
||||
devMode = true
|
||||
|
@ -69,7 +69,7 @@ public class VaultQueryJavaTests {
|
||||
keys.add(getDUMMY_NOTARY_KEY());
|
||||
IdentityService identitySvc = makeTestIdentityService();
|
||||
@SuppressWarnings("unchecked")
|
||||
Pair<CordaPersistence, MockServices> databaseAndServices = makeTestDatabaseAndMockServices(keys, () -> identitySvc, cordappPackages);
|
||||
Pair<CordaPersistence, MockServices> databaseAndServices = makeTestDatabaseAndMockServices(keys, identitySvc, cordappPackages);
|
||||
issuerServices = new MockServices(cordappPackages, getDUMMY_CASH_ISSUER_NAME(), getDUMMY_CASH_ISSUER_KEY(), getBOC_KEY());
|
||||
database = databaseAndServices.getFirst();
|
||||
services = databaseAndServices.getSecond();
|
||||
|
@ -2,18 +2,15 @@ package net.corda.node
|
||||
|
||||
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory
|
||||
import net.corda.client.jackson.JacksonSupport
|
||||
import net.corda.core.concurrent.CordaFuture
|
||||
import net.corda.core.contracts.Amount
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.flows.StateMachineRunId
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.internal.FlowStateMachine
|
||||
import net.corda.core.internal.concurrent.openFuture
|
||||
import net.corda.core.internal.objectOrNewInstance
|
||||
import net.corda.core.messaging.FlowProgressHandle
|
||||
import net.corda.core.messaging.FlowProgressHandleImpl
|
||||
import net.corda.core.utilities.ProgressTracker
|
||||
import net.corda.node.services.config.DatabaseConfig
|
||||
import net.corda.node.services.identity.InMemoryIdentityService
|
||||
import net.corda.node.shell.InteractiveShell
|
||||
import net.corda.node.utilities.configureDatabase
|
||||
@ -21,7 +18,6 @@ import net.corda.testing.DEV_TRUST_ROOT
|
||||
import net.corda.testing.MEGA_CORP
|
||||
import net.corda.testing.MEGA_CORP_IDENTITY
|
||||
import net.corda.testing.node.MockServices
|
||||
import net.corda.testing.node.MockServices.Companion.makeTestIdentityService
|
||||
import net.corda.testing.rigorousMock
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
@ -33,7 +29,7 @@ import kotlin.test.assertEquals
|
||||
class InteractiveShellTest {
|
||||
@Before
|
||||
fun setup() {
|
||||
InteractiveShell.database = configureDatabase(MockServices.makeTestDataSourceProperties(), MockServices.makeTestDatabaseProperties(), ::makeTestIdentityService)
|
||||
InteractiveShell.database = configureDatabase(MockServices.makeTestDataSourceProperties(), DatabaseConfig(), rigorousMock())
|
||||
}
|
||||
|
||||
@After
|
||||
@ -96,6 +92,4 @@ class InteractiveShellTest {
|
||||
|
||||
@Test
|
||||
fun party() = check("party: \"${MEGA_CORP.name}\"", MEGA_CORP.name.toString())
|
||||
|
||||
class DummyFSM(override val logic: FlowA) : FlowStateMachine<Any?> by rigorousMock()
|
||||
}
|
||||
|
@ -3,10 +3,8 @@ package net.corda.node.services.config
|
||||
import net.corda.core.utilities.NetworkHostAndPort
|
||||
import net.corda.testing.ALICE
|
||||
import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties
|
||||
import net.corda.testing.node.MockServices.Companion.makeTestDatabaseProperties
|
||||
import org.assertj.core.api.Assertions.assertThatThrownBy
|
||||
import org.junit.Test
|
||||
import java.net.URL
|
||||
import java.nio.file.Paths
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertTrue
|
||||
@ -40,7 +38,7 @@ class NodeConfigurationImplTest {
|
||||
keyStorePassword = "cordacadevpass",
|
||||
trustStorePassword = "trustpass",
|
||||
dataSourceProperties = makeTestDataSourceProperties(ALICE.name.organisation),
|
||||
database = makeTestDatabaseProperties(),
|
||||
database = DatabaseConfig(),
|
||||
rpcUsers = emptyList(),
|
||||
verifierType = VerifierType.InMemory,
|
||||
useHTTPS = false,
|
||||
|
@ -23,6 +23,7 @@ import net.corda.node.internal.cordapp.CordappLoader
|
||||
import net.corda.node.internal.cordapp.CordappProviderImpl
|
||||
import net.corda.node.services.api.MonitoringService
|
||||
import net.corda.node.services.api.ServiceHubInternal
|
||||
import net.corda.node.services.config.DatabaseConfig
|
||||
import net.corda.node.services.identity.InMemoryIdentityService
|
||||
import net.corda.node.services.network.NetworkMapCacheImpl
|
||||
import net.corda.node.services.persistence.DBCheckpointStorage
|
||||
@ -37,10 +38,11 @@ import net.corda.testing.*
|
||||
import net.corda.testing.contracts.DummyContract
|
||||
import net.corda.testing.node.*
|
||||
import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties
|
||||
import net.corda.testing.node.MockServices.Companion.makeTestDatabaseProperties
|
||||
import net.corda.testing.node.MockServices.Companion.makeTestIdentityService
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.*
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import java.nio.file.Paths
|
||||
import java.security.PublicKey
|
||||
import java.time.Clock
|
||||
@ -91,8 +93,7 @@ class NodeSchedulerServiceTest : SingletonSerializeAsToken() {
|
||||
smmHasRemovedAllFlows = CountDownLatch(1)
|
||||
calls = 0
|
||||
val dataSourceProps = makeTestDataSourceProperties()
|
||||
val databaseProperties = makeTestDatabaseProperties()
|
||||
database = configureDatabase(dataSourceProps, databaseProperties, ::makeTestIdentityService)
|
||||
database = configureDatabase(dataSourceProps, DatabaseConfig(), rigorousMock())
|
||||
val identityService = InMemoryIdentityService(trustRoot = DEV_TRUST_ROOT)
|
||||
kms = MockKeyManagementService(identityService, ALICE_KEY)
|
||||
val configuration = testNodeConfiguration(Paths.get("."), CordaX500Name("Alice", "London", "GB"))
|
||||
|
@ -6,16 +6,15 @@ 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.core.internal.cert
|
||||
import net.corda.node.services.identity.InMemoryIdentityService
|
||||
import net.corda.node.utilities.CertificateAndKeyPair
|
||||
import net.corda.node.utilities.CertificateType
|
||||
import net.corda.node.utilities.X509Utilities
|
||||
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 net.corda.testing.*
|
||||
import org.junit.Test
|
||||
import java.security.cert.CertificateFactory
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFailsWith
|
||||
import kotlin.test.assertNull
|
||||
@ -156,12 +155,11 @@ class InMemoryIdentityServiceTests {
|
||||
}
|
||||
|
||||
private fun createParty(x500Name: CordaX500Name, ca: CertificateAndKeyPair): Pair<PartyAndCertificate, PartyAndCertificate> {
|
||||
val certFactory = CertificateFactory.getInstance("X509")
|
||||
val issuerKeyPair = generateKeyPair()
|
||||
val issuer = getTestPartyAndCertificate(x500Name, issuerKeyPair.public, ca)
|
||||
val txKey = Crypto.generateKeyPair()
|
||||
val txCert = X509Utilities.createCertificate(CertificateType.IDENTITY, issuer.certificate.toX509CertHolder(), issuerKeyPair, x500Name, txKey.public)
|
||||
val txCertPath = certFactory.generateCertPath(listOf(txCert.cert) + issuer.certPath.certificates)
|
||||
val txCertPath = X509CertificateFactory().delegate.generateCertPath(listOf(txCert.cert) + issuer.certPath.certificates)
|
||||
return Pair(issuer, PartyAndCertificate(txCertPath))
|
||||
}
|
||||
|
||||
|
@ -6,22 +6,20 @@ 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.core.internal.cert
|
||||
import net.corda.node.services.identity.PersistentIdentityService
|
||||
import net.corda.node.utilities.CertificateAndKeyPair
|
||||
import net.corda.node.utilities.CertificateType
|
||||
import net.corda.node.utilities.CordaPersistence
|
||||
import net.corda.node.utilities.X509Utilities
|
||||
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 net.corda.testing.*
|
||||
import net.corda.testing.node.MockServices
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import java.security.cert.CertificateFactory
|
||||
import javax.security.auth.x500.X500Principal
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFailsWith
|
||||
import kotlin.test.assertNull
|
||||
@ -30,14 +28,13 @@ import kotlin.test.assertNull
|
||||
* Tests for the in memory identity service.
|
||||
*/
|
||||
class PersistentIdentityServiceTests {
|
||||
|
||||
lateinit var database: CordaPersistence
|
||||
lateinit var services: MockServices
|
||||
lateinit var identityService: IdentityService
|
||||
private lateinit var database: CordaPersistence
|
||||
private lateinit var services: MockServices
|
||||
private lateinit var identityService: IdentityService
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
val databaseAndServices = MockServices.makeTestDatabaseAndMockServices(keys = emptyList(), createIdentityService = { PersistentIdentityService(trustRoot = DEV_TRUST_ROOT) })
|
||||
val databaseAndServices = MockServices.makeTestDatabaseAndMockServices(keys = emptyList(), identityService = PersistentIdentityService(DEV_TRUST_ROOT))
|
||||
database = databaseAndServices.first
|
||||
services = databaseAndServices.second
|
||||
identityService = services.identityService
|
||||
@ -236,7 +233,7 @@ class PersistentIdentityServiceTests {
|
||||
|
||||
// Create new identity service mounted onto same DB
|
||||
val newPersistentIdentityService = database.transaction {
|
||||
PersistentIdentityService(trustRoot = DEV_TRUST_ROOT)
|
||||
PersistentIdentityService(DEV_TRUST_ROOT)
|
||||
}
|
||||
|
||||
database.transaction {
|
||||
@ -256,12 +253,11 @@ class PersistentIdentityServiceTests {
|
||||
}
|
||||
|
||||
private fun createParty(x500Name: CordaX500Name, ca: CertificateAndKeyPair): Pair<PartyAndCertificate, PartyAndCertificate> {
|
||||
val certFactory = CertificateFactory.getInstance("X509")
|
||||
val issuerKeyPair = generateKeyPair()
|
||||
val issuer = getTestPartyAndCertificate(x500Name, issuerKeyPair.public, ca)
|
||||
val txKey = Crypto.generateKeyPair()
|
||||
val txCert = X509Utilities.createCertificate(CertificateType.IDENTITY, issuer.certificate.toX509CertHolder(), issuerKeyPair, x500Name, txKey.public)
|
||||
val txCertPath = certFactory.generateCertPath(listOf(txCert.cert) + issuer.certPath.certificates)
|
||||
val txCertPath = X509CertificateFactory().delegate.generateCertPath(listOf(txCert.cert) + issuer.certPath.certificates)
|
||||
return Pair(issuer, PartyAndCertificate(txCertPath))
|
||||
}
|
||||
|
||||
|
@ -7,6 +7,7 @@ import net.corda.core.internal.concurrent.openFuture
|
||||
import net.corda.core.utilities.NetworkHostAndPort
|
||||
import net.corda.node.services.RPCUserService
|
||||
import net.corda.node.services.RPCUserServiceImpl
|
||||
import net.corda.node.services.config.DatabaseConfig
|
||||
import net.corda.node.services.config.NodeConfiguration
|
||||
import net.corda.node.services.config.configureWithDevSSLCertificate
|
||||
import net.corda.node.services.network.NetworkMapCacheImpl
|
||||
@ -18,8 +19,6 @@ import net.corda.node.utilities.configureDatabase
|
||||
import net.corda.testing.*
|
||||
import net.corda.testing.node.MockServices.Companion.MOCK_VERSION_INFO
|
||||
import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties
|
||||
import net.corda.testing.node.MockServices.Companion.makeTestDatabaseProperties
|
||||
import net.corda.testing.node.MockServices.Companion.makeTestIdentityService
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.assertj.core.api.Assertions.assertThatThrownBy
|
||||
import org.junit.After
|
||||
@ -68,7 +67,7 @@ class ArtemisMessagingTests {
|
||||
baseDirectory = baseDirectory,
|
||||
myLegalName = ALICE.name)
|
||||
LogHelper.setLevel(PersistentUniquenessProvider::class)
|
||||
database = configureDatabase(makeTestDataSourceProperties(), makeTestDatabaseProperties(), ::makeTestIdentityService)
|
||||
database = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(), rigorousMock())
|
||||
networkMapRegistrationFuture = doneFuture(Unit)
|
||||
networkMapCache = NetworkMapCacheImpl(PersistentNetworkMapCache(database, emptyList()), rigorousMock())
|
||||
}
|
||||
|
@ -10,8 +10,8 @@ import net.corda.core.serialization.serialize
|
||||
import net.corda.core.utilities.NetworkHostAndPort
|
||||
import net.corda.core.utilities.seconds
|
||||
import net.corda.node.services.network.TestNodeInfoFactory.createNodeInfo
|
||||
import net.corda.node.utilities.CertificateType
|
||||
import net.corda.node.utilities.X509Utilities
|
||||
import net.corda.nodeapi.internal.crypto.CertificateType
|
||||
import net.corda.nodeapi.internal.crypto.X509Utilities
|
||||
import net.corda.testing.SerializationEnvironmentRule
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.bouncycastle.asn1.x500.X500Name
|
||||
|
@ -8,15 +8,14 @@ import net.corda.core.identity.PartyAndCertificate
|
||||
import net.corda.core.node.NodeInfo
|
||||
import net.corda.core.serialization.serialize
|
||||
import net.corda.core.utilities.NetworkHostAndPort
|
||||
import net.corda.node.utilities.CertificateType
|
||||
import net.corda.node.utilities.X509Utilities
|
||||
import net.corda.nodeapi.internal.crypto.CertificateType
|
||||
import net.corda.nodeapi.internal.crypto.X509CertificateFactory
|
||||
import net.corda.nodeapi.internal.crypto.X509Utilities
|
||||
import org.bouncycastle.asn1.x500.X500Name
|
||||
import org.bouncycastle.cert.X509CertificateHolder
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.security.KeyPair
|
||||
import java.security.cert.CertPath
|
||||
import java.security.cert.Certificate
|
||||
import java.security.cert.CertificateFactory
|
||||
import java.security.cert.X509Certificate
|
||||
|
||||
object TestNodeInfoFactory {
|
||||
@ -40,11 +39,11 @@ object TestNodeInfoFactory {
|
||||
}
|
||||
|
||||
private fun buildCertPath(vararg certificates: Certificate): CertPath {
|
||||
return CertificateFactory.getInstance("X509").generateCertPath(certificates.asList())
|
||||
return X509CertificateFactory().delegate.generateCertPath(certificates.asList())
|
||||
}
|
||||
|
||||
private fun X509CertificateHolder.toX509Certificate(): X509Certificate {
|
||||
return CertificateFactory.getInstance("X509").generateCertificate(ByteArrayInputStream(encoded)) as X509Certificate
|
||||
return X509CertificateFactory().generateCertificate(encoded.inputStream())
|
||||
}
|
||||
|
||||
}
|
@ -4,14 +4,14 @@ import com.google.common.primitives.Ints
|
||||
import net.corda.core.serialization.SerializedBytes
|
||||
import net.corda.node.services.api.Checkpoint
|
||||
import net.corda.node.services.api.CheckpointStorage
|
||||
import net.corda.node.services.config.DatabaseConfig
|
||||
import net.corda.node.services.transactions.PersistentUniquenessProvider
|
||||
import net.corda.node.utilities.CordaPersistence
|
||||
import net.corda.node.utilities.configureDatabase
|
||||
import net.corda.testing.LogHelper
|
||||
import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties
|
||||
import net.corda.testing.node.MockServices.Companion.makeTestDatabaseProperties
|
||||
import net.corda.testing.node.MockServices.Companion.makeTestIdentityService
|
||||
import net.corda.testing.SerializationEnvironmentRule
|
||||
import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties
|
||||
import net.corda.testing.rigorousMock
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
@ -31,13 +31,14 @@ class DBCheckpointStorageTests {
|
||||
@Rule
|
||||
@JvmField
|
||||
val testSerialization = SerializationEnvironmentRule()
|
||||
lateinit var checkpointStorage: DBCheckpointStorage
|
||||
lateinit var database: CordaPersistence
|
||||
|
||||
private lateinit var checkpointStorage: DBCheckpointStorage
|
||||
private lateinit var database: CordaPersistence
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
LogHelper.setLevel(PersistentUniquenessProvider::class)
|
||||
database = configureDatabase(makeTestDataSourceProperties(), makeTestDatabaseProperties(), ::makeTestIdentityService)
|
||||
database = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(), rigorousMock())
|
||||
newCheckpointStorage()
|
||||
}
|
||||
|
||||
|
@ -6,11 +6,11 @@ import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.crypto.SignatureMetadata
|
||||
import net.corda.core.crypto.TransactionSignature
|
||||
import net.corda.core.node.StatesToRecord
|
||||
import net.corda.core.node.services.VaultService
|
||||
import net.corda.core.toFuture
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.transactions.WireTransaction
|
||||
import net.corda.node.services.api.VaultServiceInternal
|
||||
import net.corda.node.services.config.DatabaseConfig
|
||||
import net.corda.node.services.schema.HibernateObserver
|
||||
import net.corda.node.services.transactions.PersistentUniquenessProvider
|
||||
import net.corda.node.services.vault.NodeVaultService
|
||||
@ -19,8 +19,6 @@ import net.corda.node.utilities.configureDatabase
|
||||
import net.corda.testing.*
|
||||
import net.corda.testing.node.MockServices
|
||||
import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties
|
||||
import net.corda.testing.node.MockServices.Companion.makeTestDatabaseProperties
|
||||
import net.corda.testing.node.MockServices.Companion.makeTestIdentityService
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
@ -33,16 +31,16 @@ class DBTransactionStorageTests {
|
||||
@Rule
|
||||
@JvmField
|
||||
val testSerialization = SerializationEnvironmentRule()
|
||||
lateinit var database: CordaPersistence
|
||||
lateinit var transactionStorage: DBTransactionStorage
|
||||
lateinit var services: MockServices
|
||||
val vault: VaultService get() = services.vaultService
|
||||
|
||||
private lateinit var database: CordaPersistence
|
||||
private lateinit var transactionStorage: DBTransactionStorage
|
||||
private lateinit var services: MockServices
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
LogHelper.setLevel(PersistentUniquenessProvider::class)
|
||||
val dataSourceProps = makeTestDataSourceProperties()
|
||||
database = configureDatabase(dataSourceProps, makeTestDatabaseProperties(), ::makeTestIdentityService)
|
||||
database = configureDatabase(dataSourceProps, DatabaseConfig(), rigorousMock())
|
||||
database.transaction {
|
||||
services = object : MockServices(BOB_KEY) {
|
||||
override val vaultService: VaultServiceInternal
|
||||
|
@ -25,6 +25,7 @@ import net.corda.finance.schemas.CashSchemaV1
|
||||
import net.corda.finance.schemas.SampleCashSchemaV2
|
||||
import net.corda.finance.schemas.SampleCashSchemaV3
|
||||
import net.corda.finance.utils.sumCash
|
||||
import net.corda.node.services.config.DatabaseConfig
|
||||
import net.corda.node.services.schema.HibernateObserver
|
||||
import net.corda.node.services.vault.VaultSchemaV1
|
||||
import net.corda.node.utilities.CordaPersistence
|
||||
@ -36,7 +37,6 @@ import net.corda.testing.contracts.fillWithSomeTestDeals
|
||||
import net.corda.testing.contracts.fillWithSomeTestLinearStates
|
||||
import net.corda.testing.node.MockServices
|
||||
import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties
|
||||
import net.corda.testing.node.MockServices.Companion.makeTestDatabaseProperties
|
||||
import net.corda.testing.node.MockServices.Companion.makeTestIdentityService
|
||||
import net.corda.testing.schemas.DummyLinearStateSchemaV1
|
||||
import net.corda.testing.schemas.DummyLinearStateSchemaV2
|
||||
@ -84,8 +84,7 @@ class HibernateConfigurationTest {
|
||||
issuerServices = MockServices(cordappPackages, DUMMY_CASH_ISSUER_NAME, DUMMY_CASH_ISSUER_KEY)
|
||||
notaryServices = MockServices(cordappPackages, DUMMY_NOTARY.name, DUMMY_NOTARY_KEY)
|
||||
val dataSourceProps = makeTestDataSourceProperties()
|
||||
val defaultDatabaseProperties = makeTestDatabaseProperties()
|
||||
database = configureDatabase(dataSourceProps, defaultDatabaseProperties, ::makeTestIdentityService)
|
||||
database = configureDatabase(dataSourceProps, DatabaseConfig(), makeTestIdentityService())
|
||||
database.transaction {
|
||||
hibernateConfig = database.hibernateConfig
|
||||
// `consumeCash` expects we can self-notarise transactions
|
||||
|
@ -13,13 +13,13 @@ import net.corda.core.node.services.vault.AttachmentQueryCriteria
|
||||
import net.corda.core.node.services.vault.AttachmentSort
|
||||
import net.corda.core.node.services.vault.Builder
|
||||
import net.corda.core.node.services.vault.Sort
|
||||
import net.corda.node.services.config.DatabaseConfig
|
||||
import net.corda.node.services.transactions.PersistentUniquenessProvider
|
||||
import net.corda.node.utilities.CordaPersistence
|
||||
import net.corda.node.utilities.configureDatabase
|
||||
import net.corda.testing.LogHelper
|
||||
import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties
|
||||
import net.corda.testing.node.MockServices.Companion.makeTestDatabaseProperties
|
||||
import net.corda.testing.node.MockServices.Companion.makeTestIdentityService
|
||||
import net.corda.testing.rigorousMock
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Ignore
|
||||
@ -36,15 +36,15 @@ import kotlin.test.assertNull
|
||||
|
||||
class NodeAttachmentStorageTest {
|
||||
// Use an in memory file system for testing attachment storage.
|
||||
lateinit var fs: FileSystem
|
||||
lateinit var database: CordaPersistence
|
||||
private lateinit var fs: FileSystem
|
||||
private lateinit var database: CordaPersistence
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
LogHelper.setLevel(PersistentUniquenessProvider::class)
|
||||
|
||||
val dataSourceProperties = makeTestDataSourceProperties()
|
||||
database = configureDatabase(dataSourceProperties, makeTestDatabaseProperties(), ::makeTestIdentityService)
|
||||
database = configureDatabase(dataSourceProperties, DatabaseConfig(), rigorousMock())
|
||||
fs = Jimfs.newFileSystem(Configuration.unix())
|
||||
}
|
||||
|
||||
@ -82,9 +82,9 @@ class NodeAttachmentStorageTest {
|
||||
|
||||
@Test
|
||||
fun `metadata can be used to search`() {
|
||||
val (jarA,hashA) = makeTestJar()
|
||||
val (jarB,hashB) = makeTestJar(listOf(Pair("file","content")))
|
||||
val (jarC,hashC) = makeTestJar(listOf(Pair("magic_file","magic_content_puff")))
|
||||
val (jarA, _) = makeTestJar()
|
||||
val (jarB, hashB) = makeTestJar(listOf(Pair("file","content")))
|
||||
val (jarC, hashC) = makeTestJar(listOf(Pair("magic_file","magic_content_puff")))
|
||||
|
||||
database.transaction {
|
||||
val storage = NodeAttachmentService(MetricRegistry())
|
||||
|
@ -11,23 +11,21 @@ import net.corda.core.schemas.MappedSchema
|
||||
import net.corda.core.schemas.PersistentState
|
||||
import net.corda.core.schemas.QueryableState
|
||||
import net.corda.node.services.api.SchemaService
|
||||
import net.corda.node.services.config.DatabaseConfig
|
||||
import net.corda.node.utilities.DatabaseTransactionManager
|
||||
import net.corda.node.utilities.configureDatabase
|
||||
import net.corda.testing.LogHelper
|
||||
import net.corda.testing.MEGA_CORP
|
||||
import net.corda.testing.contracts.DummyContract
|
||||
import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties
|
||||
import net.corda.testing.node.MockServices.Companion.makeTestDatabaseProperties
|
||||
import net.corda.testing.node.MockServices.Companion.makeTestIdentityService
|
||||
import net.corda.testing.rigorousMock
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import rx.subjects.PublishSubject
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
|
||||
class HibernateObserverTests {
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
LogHelper.setLevel(HibernateObserver::class)
|
||||
@ -51,9 +49,8 @@ class HibernateObserverTests {
|
||||
get() = throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
// This method does not use back quotes for a nice name since it seems to kill the kotlin compiler.
|
||||
@Test
|
||||
fun testChildObjectsArePersisted() {
|
||||
fun `test child objects are persisted`() {
|
||||
val testSchema = TestSchema
|
||||
val rawUpdatesPublisher = PublishSubject.create<Vault.Update<ContractState>>()
|
||||
val schemaService = object : SchemaService {
|
||||
@ -68,7 +65,7 @@ class HibernateObserverTests {
|
||||
return parent
|
||||
}
|
||||
}
|
||||
val database = configureDatabase(makeTestDataSourceProperties(), makeTestDatabaseProperties(), ::makeTestIdentityService, schemaService)
|
||||
val database = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(), rigorousMock(), schemaService)
|
||||
HibernateObserver.install(rawUpdatesPublisher, database.hibernateConfig)
|
||||
database.transaction {
|
||||
rawUpdatesPublisher.onNext(Vault.Update(emptySet(), setOf(StateAndRef(TransactionState(TestState(), DummyContract.PROGRAM_ID, MEGA_CORP), StateRef(SecureHash.sha256("dummy"), 0)))))
|
||||
|
@ -10,16 +10,18 @@ import net.corda.core.internal.concurrent.asCordaFuture
|
||||
import net.corda.core.internal.concurrent.transpose
|
||||
import net.corda.core.utilities.NetworkHostAndPort
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.node.services.config.DatabaseConfig
|
||||
import net.corda.node.utilities.CordaPersistence
|
||||
import net.corda.node.utilities.DatabaseTransaction
|
||||
import net.corda.node.utilities.configureDatabase
|
||||
import net.corda.testing.LogHelper
|
||||
import net.corda.testing.SerializationEnvironmentRule
|
||||
import net.corda.testing.freeLocalHostAndPort
|
||||
import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties
|
||||
import net.corda.testing.node.MockServices.Companion.makeTestDatabaseProperties
|
||||
import net.corda.testing.node.MockServices.Companion.makeTestIdentityService
|
||||
import org.junit.*
|
||||
import net.corda.testing.rigorousMock
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertTrue
|
||||
@ -30,8 +32,8 @@ class DistributedImmutableMapTests {
|
||||
@Rule
|
||||
@JvmField
|
||||
val testSerialization = SerializationEnvironmentRule(true)
|
||||
lateinit var cluster: List<Member>
|
||||
lateinit var transaction: DatabaseTransaction
|
||||
|
||||
private lateinit var cluster: List<Member>
|
||||
private val databases: MutableList<CordaPersistence> = mutableListOf()
|
||||
|
||||
@Before
|
||||
@ -86,7 +88,7 @@ class DistributedImmutableMapTests {
|
||||
private fun createReplica(myAddress: NetworkHostAndPort, clusterAddress: NetworkHostAndPort? = null): CompletableFuture<Member> {
|
||||
val storage = Storage.builder().withStorageLevel(StorageLevel.MEMORY).build()
|
||||
val address = Address(myAddress.host, myAddress.port)
|
||||
val database = configureDatabase(makeTestDataSourceProperties(), makeTestDatabaseProperties("serverNameTablePrefix", "PORT_${myAddress.port}_"), ::makeTestIdentityService)
|
||||
val database = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(serverNameTablePrefix = "PORT_${myAddress.port}_"), rigorousMock())
|
||||
databases.add(database)
|
||||
val stateMachineFactory = { DistributedImmutableMap(database, RaftUniquenessProvider.Companion::createMap) }
|
||||
|
||||
|
@ -2,12 +2,11 @@ package net.corda.node.services.transactions
|
||||
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.node.services.UniquenessException
|
||||
import net.corda.node.services.config.DatabaseConfig
|
||||
import net.corda.node.utilities.CordaPersistence
|
||||
import net.corda.node.utilities.configureDatabase
|
||||
import net.corda.testing.*
|
||||
import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties
|
||||
import net.corda.testing.node.MockServices.Companion.makeTestDatabaseProperties
|
||||
import net.corda.testing.node.MockServices.Companion.makeTestIdentityService
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
@ -19,15 +18,16 @@ class PersistentUniquenessProviderTests {
|
||||
@Rule
|
||||
@JvmField
|
||||
val testSerialization = SerializationEnvironmentRule()
|
||||
val identity = MEGA_CORP
|
||||
val txID = SecureHash.randomSHA256()
|
||||
|
||||
lateinit var database: CordaPersistence
|
||||
private val identity = MEGA_CORP
|
||||
private val txID = SecureHash.randomSHA256()
|
||||
|
||||
private lateinit var database: CordaPersistence
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
LogHelper.setLevel(PersistentUniquenessProvider::class)
|
||||
database = configureDatabase(makeTestDataSourceProperties(), makeTestDatabaseProperties(), ::makeTestIdentityService)
|
||||
database = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(), rigorousMock())
|
||||
}
|
||||
|
||||
@After
|
||||
|
@ -27,15 +27,14 @@ import net.corda.finance.schemas.CashSchemaV1
|
||||
import net.corda.finance.schemas.CashSchemaV1.PersistentCashState
|
||||
import net.corda.finance.schemas.CommercialPaperSchemaV1
|
||||
import net.corda.finance.schemas.SampleCashSchemaV3
|
||||
import net.corda.node.services.config.DatabaseConfig
|
||||
import net.corda.node.utilities.CordaPersistence
|
||||
import net.corda.node.utilities.configureDatabase
|
||||
import net.corda.testing.*
|
||||
import net.corda.testing.contracts.*
|
||||
import net.corda.testing.node.MockServices
|
||||
import net.corda.testing.node.MockServices.Companion.makeTestDatabaseAndMockServices
|
||||
import net.corda.testing.node.MockServices.Companion.makeTestDatabaseProperties
|
||||
import net.corda.testing.schemas.DummyLinearStateSchemaV1
|
||||
import org.assertj.core.api.Assertions
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.assertj.core.api.Assertions.assertThatThrownBy
|
||||
import org.junit.*
|
||||
@ -53,9 +52,16 @@ class VaultQueryTests {
|
||||
@Rule
|
||||
@JvmField
|
||||
val testSerialization = SerializationEnvironmentRule()
|
||||
private val cordappPackages = setOf(
|
||||
"net.corda.testing.contracts", "net.corda.finance.contracts",
|
||||
CashSchemaV1::class.packageName, CommercialPaperSchemaV1::class.packageName, DummyLinearStateSchemaV1::class.packageName).toMutableList()
|
||||
|
||||
@Rule
|
||||
@JvmField
|
||||
val expectedEx: ExpectedException = ExpectedException.none()
|
||||
|
||||
private val cordappPackages = mutableListOf(
|
||||
"net.corda.testing.contracts",
|
||||
"net.corda.finance.contracts",
|
||||
CashSchemaV1::class.packageName,
|
||||
DummyLinearStateSchemaV1::class.packageName)
|
||||
private lateinit var services: MockServices
|
||||
private lateinit var notaryServices: MockServices
|
||||
private val vaultService: VaultService get() = services.vaultService
|
||||
@ -93,7 +99,7 @@ class VaultQueryTests {
|
||||
@Ignore
|
||||
@Test
|
||||
fun createPersistentTestDb() {
|
||||
val database = configureDatabase(makePersistentDataSourceProperties(), makeTestDatabaseProperties(), { identitySvc })
|
||||
val database = configureDatabase(makePersistentDataSourceProperties(), DatabaseConfig(), identitySvc)
|
||||
setUpDb(database, 5000)
|
||||
|
||||
database.close()
|
||||
@ -129,9 +135,6 @@ class VaultQueryTests {
|
||||
return props
|
||||
}
|
||||
|
||||
@get:Rule
|
||||
val expectedEx = ExpectedException.none()!!
|
||||
|
||||
/**
|
||||
* Query API tests
|
||||
*/
|
||||
@ -1597,8 +1600,8 @@ class VaultQueryTests {
|
||||
|
||||
val result = vaultService.queryBy<CommercialPaper.State>(criteria1)
|
||||
|
||||
Assertions.assertThat(result.states).hasSize(1)
|
||||
Assertions.assertThat(result.statesMetadata).hasSize(1)
|
||||
assertThat(result.states).hasSize(1)
|
||||
assertThat(result.statesMetadata).hasSize(1)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1642,8 +1645,8 @@ class VaultQueryTests {
|
||||
|
||||
vaultService.queryBy<CommercialPaper.State>(criteria1.and(criteria3).and(criteria2))
|
||||
}
|
||||
Assertions.assertThat(result.states).hasSize(1)
|
||||
Assertions.assertThat(result.statesMetadata).hasSize(1)
|
||||
assertThat(result.states).hasSize(1)
|
||||
assertThat(result.statesMetadata).hasSize(1)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,9 +3,9 @@ package net.corda.node.utilities
|
||||
import com.google.common.util.concurrent.SettableFuture
|
||||
import net.corda.core.internal.bufferUntilSubscribed
|
||||
import net.corda.core.internal.tee
|
||||
import net.corda.node.services.config.DatabaseConfig
|
||||
import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties
|
||||
import net.corda.testing.node.MockServices.Companion.makeTestDatabaseProperties
|
||||
import net.corda.testing.node.MockServices.Companion.makeTestIdentityService
|
||||
import net.corda.testing.rigorousMock
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.After
|
||||
import org.junit.Test
|
||||
@ -18,10 +18,10 @@ class ObservablesTests {
|
||||
|
||||
private fun isInDatabaseTransaction(): Boolean = (DatabaseTransactionManager.currentOrNull() != null)
|
||||
|
||||
val toBeClosed = mutableListOf<Closeable>()
|
||||
private val toBeClosed = mutableListOf<Closeable>()
|
||||
|
||||
fun createDatabase(): CordaPersistence {
|
||||
val database = configureDatabase(makeTestDataSourceProperties(), makeTestDatabaseProperties(), ::makeTestIdentityService)
|
||||
private fun createDatabase(): CordaPersistence {
|
||||
val database = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(), rigorousMock())
|
||||
toBeClosed += database
|
||||
return database
|
||||
}
|
||||
|
@ -0,0 +1,395 @@
|
||||
package net.corda.node.utilities
|
||||
|
||||
import net.corda.core.crypto.Crypto
|
||||
import net.corda.core.crypto.SignatureScheme
|
||||
import net.corda.core.crypto.newSecureRandom
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.internal.*
|
||||
import net.corda.nodeapi.internal.crypto.*
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.TemporaryFolder
|
||||
import java.io.DataInputStream
|
||||
import java.io.DataOutputStream
|
||||
import java.io.IOException
|
||||
import java.net.InetAddress
|
||||
import java.net.InetSocketAddress
|
||||
import java.net.ServerSocket
|
||||
import java.nio.file.Path
|
||||
import java.security.KeyStore
|
||||
import javax.net.ssl.*
|
||||
import kotlin.concurrent.thread
|
||||
import kotlin.test.*
|
||||
|
||||
/**
|
||||
* Various tests for mixed-scheme mutual TLS authentication, such as:
|
||||
* Both TLS keys and CAs are using EC NIST P-256.
|
||||
* Both TLS keys and CAs are using RSA.
|
||||
* Server EC NIST P-256 - Client RSA.
|
||||
* Server RSA - Client EC NIST P-256.
|
||||
* Mixed CA and TLS keys.
|
||||
*
|
||||
* TLS/SSL protocols support a large number of cipher suites.
|
||||
* A cipher suite is a collection of symmetric and asymmetric encryption algorithms used by hosts to establish
|
||||
* a secure communication. Supported cipher suites can be classified based on encryption algorithm strength,
|
||||
* key length, key exchange and authentication mechanisms. Some cipher suites offer better level of security than others.
|
||||
*
|
||||
* Each TLS cipher suite has a unique name that is used to identify it and to describe the algorithmic contents of it.
|
||||
* Each segment in a cipher suite name stands for a different algorithm or protocol.
|
||||
* An example of a cipher suite name: TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
|
||||
* The meaning of this name is:
|
||||
* TLS defines the protocol that this cipher suite is for; it will usually be TLS.
|
||||
* ECDHE indicates the key exchange algorithm being used.
|
||||
* ECDSA indicates the authentication algorithm (signing the DH keys).
|
||||
* AES_128_GCM indicates the block cipher being used to encrypt the message stream.
|
||||
* SHA256 indicates the message authentication algorithm which is used to authenticate a message.
|
||||
*/
|
||||
class TLSAuthenticationTests {
|
||||
|
||||
@Rule
|
||||
@JvmField
|
||||
val tempFolder: TemporaryFolder = TemporaryFolder()
|
||||
|
||||
// Root CA.
|
||||
private val ROOT_X500 = CordaX500Name(commonName = "Root_CA_1", organisation = "R3CEV", locality = "London", country = "GB")
|
||||
// Intermediate CA.
|
||||
private val INTERMEDIATE_X500 = CordaX500Name(commonName = "Intermediate_CA_1", organisation = "R3CEV", locality = "London", country = "GB")
|
||||
// TLS server (client1).
|
||||
private val CLIENT_1_X500 = CordaX500Name(commonName = "Client_1", organisation = "R3CEV", locality = "London", country = "GB")
|
||||
// TLS client (client2).
|
||||
private val CLIENT_2_X500 = CordaX500Name(commonName = "Client_2", organisation = "R3CEV", locality = "London", country = "GB")
|
||||
// Password for keys and keystores.
|
||||
private val PASSWORD = "dummypassword"
|
||||
// Default supported TLS schemes for Corda nodes.
|
||||
private val CORDA_TLS_CIPHER_SUITES = arrayOf(
|
||||
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
|
||||
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
|
||||
"TLS_DHE_RSA_WITH_AES_128_GCM_SHA256"
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `All EC R1`() {
|
||||
val (serverSocketFactory, clientSocketFactory) = buildTLSFactories(
|
||||
rootCAScheme = Crypto.ECDSA_SECP256R1_SHA256,
|
||||
intermediateCAScheme = Crypto.ECDSA_SECP256R1_SHA256,
|
||||
client1CAScheme = Crypto.ECDSA_SECP256R1_SHA256,
|
||||
client1TLSScheme = Crypto.ECDSA_SECP256R1_SHA256,
|
||||
client2CAScheme = Crypto.ECDSA_SECP256R1_SHA256,
|
||||
client2TLSScheme = Crypto.ECDSA_SECP256R1_SHA256
|
||||
)
|
||||
|
||||
val (serverSocket, clientSocket) =
|
||||
buildTLSSockets(serverSocketFactory, clientSocketFactory, 0, 0)
|
||||
|
||||
testConnect(serverSocket, clientSocket, "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `All RSA`() {
|
||||
val (serverSocketFactory, clientSocketFactory) = buildTLSFactories(
|
||||
rootCAScheme = Crypto.RSA_SHA256,
|
||||
intermediateCAScheme = Crypto.RSA_SHA256,
|
||||
client1CAScheme = Crypto.RSA_SHA256,
|
||||
client1TLSScheme = Crypto.RSA_SHA256,
|
||||
client2CAScheme = Crypto.RSA_SHA256,
|
||||
client2TLSScheme = Crypto.RSA_SHA256
|
||||
)
|
||||
|
||||
val (serverSocket, clientSocket) =
|
||||
buildTLSSockets(serverSocketFactory, clientSocketFactory, 0, 0)
|
||||
|
||||
testConnect(serverSocket, clientSocket, "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256")
|
||||
}
|
||||
|
||||
// Server's public key type is the one selected if users use different key types (e.g RSA and EC R1).
|
||||
@Test
|
||||
fun `Server RSA - Client EC R1 - CAs all EC R1`() {
|
||||
val (serverSocketFactory, clientSocketFactory) = buildTLSFactories(
|
||||
rootCAScheme = Crypto.ECDSA_SECP256R1_SHA256,
|
||||
intermediateCAScheme = Crypto.ECDSA_SECP256R1_SHA256,
|
||||
client1CAScheme = Crypto.ECDSA_SECP256R1_SHA256,
|
||||
client1TLSScheme = Crypto.RSA_SHA256,
|
||||
client2CAScheme = Crypto.ECDSA_SECP256R1_SHA256,
|
||||
client2TLSScheme = Crypto.ECDSA_SECP256R1_SHA256
|
||||
)
|
||||
|
||||
val (serverSocket, clientSocket) =
|
||||
buildTLSSockets(serverSocketFactory, clientSocketFactory, 0, 0)
|
||||
testConnect(serverSocket, clientSocket, "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256") // Server's key type is selected.
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Server EC R1 - Client RSA - CAs all EC R1`() {
|
||||
val (serverSocketFactory, clientSocketFactory) = buildTLSFactories(
|
||||
rootCAScheme = Crypto.ECDSA_SECP256R1_SHA256,
|
||||
intermediateCAScheme = Crypto.ECDSA_SECP256R1_SHA256,
|
||||
client1CAScheme = Crypto.ECDSA_SECP256R1_SHA256,
|
||||
client1TLSScheme = Crypto.ECDSA_SECP256R1_SHA256,
|
||||
client2CAScheme = Crypto.ECDSA_SECP256R1_SHA256,
|
||||
client2TLSScheme = Crypto.RSA_SHA256
|
||||
)
|
||||
|
||||
val (serverSocket, clientSocket) = buildTLSSockets(serverSocketFactory, clientSocketFactory, 0, 0)
|
||||
testConnect(serverSocket, clientSocket, "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256") // Server's key type is selected.
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Server EC R1 - Client EC R1 - CAs all RSA`() {
|
||||
val (serverSocketFactory, clientSocketFactory) = buildTLSFactories(
|
||||
rootCAScheme = Crypto.RSA_SHA256,
|
||||
intermediateCAScheme = Crypto.RSA_SHA256,
|
||||
client1CAScheme = Crypto.RSA_SHA256,
|
||||
client1TLSScheme = Crypto.ECDSA_SECP256R1_SHA256,
|
||||
client2CAScheme = Crypto.RSA_SHA256,
|
||||
client2TLSScheme = Crypto.ECDSA_SECP256R1_SHA256
|
||||
)
|
||||
|
||||
val (serverSocket, clientSocket) = buildTLSSockets(serverSocketFactory, clientSocketFactory, 0, 0)
|
||||
testConnect(serverSocket, clientSocket, "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Server EC R1 - Client RSA - Mixed CAs`() {
|
||||
val (serverSocketFactory, clientSocketFactory) = buildTLSFactories(
|
||||
rootCAScheme = Crypto.ECDSA_SECP256R1_SHA256,
|
||||
intermediateCAScheme = Crypto.RSA_SHA256,
|
||||
client1CAScheme = Crypto.RSA_SHA256,
|
||||
client1TLSScheme = Crypto.ECDSA_SECP256R1_SHA256,
|
||||
client2CAScheme = Crypto.ECDSA_SECP256R1_SHA256,
|
||||
client2TLSScheme = Crypto.RSA_SHA256
|
||||
)
|
||||
|
||||
val (serverSocket, clientSocket) = buildTLSSockets(serverSocketFactory, clientSocketFactory, 0, 0)
|
||||
testConnect(serverSocket, clientSocket, "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `All RSA - avoid ECC for DH`() {
|
||||
val (serverSocketFactory, clientSocketFactory) = buildTLSFactories(
|
||||
rootCAScheme = Crypto.RSA_SHA256,
|
||||
intermediateCAScheme = Crypto.RSA_SHA256,
|
||||
client1CAScheme = Crypto.RSA_SHA256,
|
||||
client1TLSScheme = Crypto.RSA_SHA256,
|
||||
client2CAScheme = Crypto.RSA_SHA256,
|
||||
client2TLSScheme = Crypto.RSA_SHA256
|
||||
)
|
||||
|
||||
val (serverSocket, clientSocket) = buildTLSSockets(
|
||||
serverSocketFactory,
|
||||
clientSocketFactory,
|
||||
0,
|
||||
0,
|
||||
CORDA_TLS_CIPHER_SUITES,
|
||||
arrayOf("TLS_DHE_RSA_WITH_AES_128_GCM_SHA256")) // Second client accepts DHE only.
|
||||
testConnect(serverSocket, clientSocket, "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256")
|
||||
}
|
||||
|
||||
// According to RFC 5246 (TLS 1.2), section 7.4.1.2 ClientHello cipher_suites:
|
||||
// This is a list of the cryptographic options supported by the client, with the client's first preference first.
|
||||
//
|
||||
// However, the server is still free to ignore this order and pick what it thinks is best,
|
||||
// see https://security.stackexchange.com/questions/121608 for more information.
|
||||
@Test
|
||||
fun `TLS cipher suite order matters - client wins`() {
|
||||
val (serverSocketFactory, clientSocketFactory) = buildTLSFactories(
|
||||
rootCAScheme = Crypto.ECDSA_SECP256R1_SHA256,
|
||||
intermediateCAScheme = Crypto.ECDSA_SECP256R1_SHA256,
|
||||
client1CAScheme = Crypto.ECDSA_SECP256R1_SHA256,
|
||||
client1TLSScheme = Crypto.ECDSA_SECP256R1_SHA256,
|
||||
client2CAScheme = Crypto.ECDSA_SECP256R1_SHA256,
|
||||
client2TLSScheme = Crypto.ECDSA_SECP256R1_SHA256
|
||||
)
|
||||
|
||||
val (serverSocket, clientSocket) = buildTLSSockets(
|
||||
serverSocketFactory,
|
||||
clientSocketFactory,
|
||||
0,
|
||||
0,
|
||||
arrayOf("TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256"), // GCM then CBC.
|
||||
arrayOf("TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256")) // CBC then GCM.
|
||||
testConnect(serverSocket, clientSocket, "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256") // Client order wins.
|
||||
}
|
||||
|
||||
private fun tempFile(name: String): Path = tempFolder.root.toPath() / name
|
||||
|
||||
private fun buildTLSFactories(
|
||||
rootCAScheme: SignatureScheme,
|
||||
intermediateCAScheme: SignatureScheme,
|
||||
client1CAScheme: SignatureScheme,
|
||||
client1TLSScheme: SignatureScheme,
|
||||
client2CAScheme: SignatureScheme,
|
||||
client2TLSScheme: SignatureScheme
|
||||
): Pair<SSLServerSocketFactory, SSLSocketFactory> {
|
||||
|
||||
val trustStorePath = tempFile("cordaTrustStore.jks")
|
||||
val client1TLSKeyStorePath = tempFile("client1sslkeystore.jks")
|
||||
val client2TLSKeyStorePath = tempFile("client2sslkeystore.jks")
|
||||
|
||||
// ROOT CA key and cert.
|
||||
val rootCAKeyPair = Crypto.generateKeyPair(rootCAScheme)
|
||||
val rootCACert = X509Utilities.createSelfSignedCACertificate(ROOT_X500, rootCAKeyPair)
|
||||
|
||||
// Intermediate CA key and cert.
|
||||
val intermediateCAKeyPair = Crypto.generateKeyPair(intermediateCAScheme)
|
||||
val intermediateCACert = X509Utilities.createCertificate(
|
||||
CertificateType.INTERMEDIATE_CA,
|
||||
rootCACert,
|
||||
rootCAKeyPair,
|
||||
INTERMEDIATE_X500,
|
||||
intermediateCAKeyPair.public
|
||||
)
|
||||
|
||||
// Client 1 keys, certs and SSLKeyStore.
|
||||
val client1CAKeyPair = Crypto.generateKeyPair(client1CAScheme)
|
||||
val client1CACert = X509Utilities.createCertificate(
|
||||
CertificateType.CLIENT_CA,
|
||||
intermediateCACert,
|
||||
intermediateCAKeyPair,
|
||||
CLIENT_1_X500,
|
||||
client1CAKeyPair.public
|
||||
)
|
||||
|
||||
val client1TLSKeyPair = Crypto.generateKeyPair(client1TLSScheme)
|
||||
val client1TLSCert = X509Utilities.createCertificate(
|
||||
CertificateType.TLS,
|
||||
client1CACert,
|
||||
client1CAKeyPair,
|
||||
CLIENT_1_X500,
|
||||
client1TLSKeyPair.public
|
||||
)
|
||||
|
||||
val client1TLSKeyStore = loadOrCreateKeyStore(client1TLSKeyStorePath, PASSWORD)
|
||||
client1TLSKeyStore.addOrReplaceKey(
|
||||
X509Utilities.CORDA_CLIENT_TLS,
|
||||
client1TLSKeyPair.private,
|
||||
PASSWORD.toCharArray(),
|
||||
arrayOf(client1TLSCert, client1CACert, intermediateCACert, rootCACert))
|
||||
// client1TLSKeyStore.save(client1TLSKeyStorePath, PASSWORD)
|
||||
|
||||
// Client 2 keys, certs and SSLKeyStore.
|
||||
val client2CAKeyPair = Crypto.generateKeyPair(client2CAScheme)
|
||||
val client2CACert = X509Utilities.createCertificate(
|
||||
CertificateType.CLIENT_CA,
|
||||
intermediateCACert,
|
||||
intermediateCAKeyPair,
|
||||
CLIENT_2_X500,
|
||||
client2CAKeyPair.public
|
||||
)
|
||||
|
||||
val client2TLSKeyPair = Crypto.generateKeyPair(client2TLSScheme)
|
||||
val client2TLSCert = X509Utilities.createCertificate(
|
||||
CertificateType.TLS,
|
||||
client2CACert,
|
||||
client2CAKeyPair,
|
||||
CLIENT_2_X500,
|
||||
client2TLSKeyPair.public
|
||||
)
|
||||
|
||||
val client2TLSKeyStore = loadOrCreateKeyStore(client2TLSKeyStorePath, PASSWORD)
|
||||
client2TLSKeyStore.addOrReplaceKey(
|
||||
X509Utilities.CORDA_CLIENT_TLS,
|
||||
client2TLSKeyPair.private,
|
||||
PASSWORD.toCharArray(),
|
||||
arrayOf(client2TLSCert, client2CACert, intermediateCACert, rootCACert))
|
||||
// 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.save(trustStorePath, PASSWORD)
|
||||
|
||||
val client1SSLContext = sslContext(client1TLSKeyStore, PASSWORD, trustStore)
|
||||
val client2SSLContext = sslContext(client2TLSKeyStore, PASSWORD, trustStore)
|
||||
|
||||
val serverSocketFactory = client1SSLContext.serverSocketFactory
|
||||
val clientSocketFactory = client2SSLContext.socketFactory
|
||||
|
||||
return Pair(serverSocketFactory, clientSocketFactory)
|
||||
}
|
||||
|
||||
private fun buildTLSSockets(
|
||||
serverSocketFactory: SSLServerSocketFactory,
|
||||
clientSocketFactory: SSLSocketFactory,
|
||||
serverPort: Int = 0, // Use 0 to get first free socket.
|
||||
clientPort: Int = 0, // Use 0 to get first free socket.
|
||||
cipherSuitesServer: Array<String> = CORDA_TLS_CIPHER_SUITES,
|
||||
cipherSuitesClient: Array<String> = CORDA_TLS_CIPHER_SUITES
|
||||
): Pair<SSLServerSocket, SSLSocket> {
|
||||
val serverSocket = serverSocketFactory.createServerSocket(serverPort) as SSLServerSocket // use 0 to get first free socket.
|
||||
val serverParams = SSLParameters(cipherSuitesServer, arrayOf("TLSv1.2"))
|
||||
serverParams.needClientAuth = true // Note that needClientAuth is requiring client authentication Vs wantClientAuth, in which client authentication is optional).
|
||||
serverParams.endpointIdentificationAlgorithm = null // Reconfirm default no server name indication, use our own validator.
|
||||
serverSocket.sslParameters = serverParams
|
||||
serverSocket.useClientMode = false
|
||||
|
||||
val clientSocket = clientSocketFactory.createSocket() as SSLSocket
|
||||
val clientParams = SSLParameters(cipherSuitesClient, arrayOf("TLSv1.2"))
|
||||
clientParams.endpointIdentificationAlgorithm = null // Reconfirm default no server name indication, use our own validator.
|
||||
clientSocket.sslParameters = clientParams
|
||||
clientSocket.useClientMode = true
|
||||
// We need to specify this explicitly because by default the client binds to 'localhost' and we want it to bind
|
||||
// to whatever <hostname> resolves to(as that's what the server binds to). In particular on Debian <hostname>
|
||||
// resolves to 127.0.1.1 instead of the external address of the interface, so the TLS handshake fails.
|
||||
clientSocket.bind(InetSocketAddress(InetAddress.getLocalHost(), clientPort))
|
||||
return Pair(serverSocket, clientSocket)
|
||||
}
|
||||
|
||||
private fun testConnect(serverSocket: ServerSocket, clientSocket: SSLSocket, expectedCipherSuite: String) {
|
||||
val lock = Object()
|
||||
var done = false
|
||||
var serverError = false
|
||||
|
||||
val serverThread = thread {
|
||||
try {
|
||||
val sslServerSocket = serverSocket.accept()
|
||||
assertTrue(sslServerSocket.isConnected)
|
||||
val serverInput = DataInputStream(sslServerSocket.inputStream)
|
||||
val receivedString = serverInput.readUTF()
|
||||
assertEquals("Hello World", receivedString)
|
||||
synchronized(lock) {
|
||||
done = true
|
||||
lock.notifyAll()
|
||||
}
|
||||
sslServerSocket.close()
|
||||
} catch (ex: Throwable) {
|
||||
serverError = true
|
||||
}
|
||||
}
|
||||
|
||||
clientSocket.connect(InetSocketAddress(InetAddress.getLocalHost(), serverSocket.localPort))
|
||||
assertTrue(clientSocket.isConnected)
|
||||
assertEquals(expectedCipherSuite, clientSocket.session.cipherSuite)
|
||||
|
||||
// Timeout after 30 secs.
|
||||
val output = DataOutputStream(clientSocket.outputStream)
|
||||
output.writeUTF("Hello World")
|
||||
var timeout = 0
|
||||
synchronized(lock) {
|
||||
while (!done) {
|
||||
timeout++
|
||||
if (timeout > 30) throw IOException("Timed out waiting for server to complete")
|
||||
lock.wait(1000)
|
||||
}
|
||||
}
|
||||
|
||||
clientSocket.close()
|
||||
serverThread.join(1000)
|
||||
assertFalse { serverError }
|
||||
serverSocket.close()
|
||||
assertTrue(done)
|
||||
}
|
||||
|
||||
// Generate an SSLContext from a KeyStore and a TrustStore.
|
||||
private fun sslContext(sslKeyStore: KeyStore, sslKeyStorePassword: String, sslTrustStore: KeyStore) : SSLContext {
|
||||
val context = SSLContext.getInstance("TLS")
|
||||
val keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm())
|
||||
// Requires the KeyStore password as well.
|
||||
keyManagerFactory.init(sslKeyStore, sslKeyStorePassword.toCharArray())
|
||||
val keyManagers = keyManagerFactory.keyManagers
|
||||
val trustMgrFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
|
||||
// Password is not required for TrustStore.
|
||||
trustMgrFactory.init(sslTrustStore)
|
||||
val trustManagers = trustMgrFactory.trustManagers
|
||||
return context.apply { init(keyManagers, trustManagers, newSecureRandom()) }
|
||||
}
|
||||
}
|
@ -13,10 +13,11 @@ import net.corda.core.serialization.deserialize
|
||||
import net.corda.core.serialization.serialize
|
||||
import net.corda.node.serialization.KryoServerSerializationScheme
|
||||
import net.corda.node.services.config.createKeystoreForCordaNode
|
||||
import net.corda.nodeapi.internal.crypto.*
|
||||
import net.corda.nodeapi.internal.serialization.AllWhitelist
|
||||
import net.corda.nodeapi.internal.serialization.kryo.KryoHeaderV0_1
|
||||
import net.corda.nodeapi.internal.serialization.SerializationContextImpl
|
||||
import net.corda.nodeapi.internal.serialization.SerializationFactoryImpl
|
||||
import net.corda.nodeapi.internal.serialization.kryo.KryoHeaderV0_1
|
||||
import net.corda.testing.ALICE
|
||||
import net.corda.testing.BOB
|
||||
import net.corda.testing.BOB_PUBKEY
|
||||
@ -41,7 +42,6 @@ import java.security.PrivateKey
|
||||
import java.security.SecureRandom
|
||||
import java.security.cert.CertPath
|
||||
import java.security.cert.Certificate
|
||||
import java.security.cert.CertificateFactory
|
||||
import java.security.cert.X509Certificate
|
||||
import java.util.*
|
||||
import java.util.stream.Stream
|
||||
@ -49,7 +49,6 @@ import javax.net.ssl.*
|
||||
import kotlin.concurrent.thread
|
||||
import kotlin.test.*
|
||||
|
||||
|
||||
class X509UtilitiesTest {
|
||||
@Rule
|
||||
@JvmField
|
||||
@ -360,10 +359,16 @@ class X509UtilitiesTest {
|
||||
trustStorePassword: String
|
||||
): KeyStore {
|
||||
val rootCAKey = generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
|
||||
val rootCACert = X509Utilities.createSelfSignedCACertificate(CordaX500Name(commonName = "Corda Node Root CA", organisation = "R3CEV", locality = "London", country = "GB"), rootCAKey)
|
||||
val baseName = CordaX500Name(organisation = "R3CEV", locality = "London", country = "GB")
|
||||
val rootCACert = X509Utilities.createSelfSignedCACertificate(baseName.copy(commonName = "Corda Node Root CA"), rootCAKey)
|
||||
|
||||
val intermediateCAKeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
|
||||
val intermediateCACert = X509Utilities.createCertificate(CertificateType.INTERMEDIATE_CA, rootCACert, rootCAKey, CordaX500Name(commonName = "Corda Node Intermediate CA", organisation = "R3CEV", locality = "London", country = "GB"), intermediateCAKeyPair.public)
|
||||
val intermediateCACert = X509Utilities.createCertificate(
|
||||
CertificateType.INTERMEDIATE_CA,
|
||||
rootCACert,
|
||||
rootCAKey,
|
||||
baseName.copy(commonName = "Corda Node Intermediate CA"),
|
||||
intermediateCAKeyPair.public)
|
||||
|
||||
val keyPass = keyPassword.toCharArray()
|
||||
val keyStore = loadOrCreateKeyStore(keyStoreFilePath, storePassword)
|
||||
@ -426,11 +431,10 @@ class X509UtilitiesTest {
|
||||
emptyMap(),
|
||||
true,
|
||||
SerializationContext.UseCase.P2P)
|
||||
val certFactory = CertificateFactory.getInstance("X509")
|
||||
val rootCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
|
||||
val rootCACert = X509Utilities.createSelfSignedCACertificate(ALICE.name, rootCAKey)
|
||||
val certificate = X509Utilities.createCertificate(CertificateType.TLS, rootCACert, rootCAKey, BOB.name.x500Name, BOB_PUBKEY)
|
||||
val expected = certFactory.generateCertPath(listOf(certificate.cert, rootCACert.cert))
|
||||
val expected = X509CertificateFactory().delegate.generateCertPath(listOf(certificate.cert, rootCACert.cert))
|
||||
val serialized = expected.serialize(factory, context).bytes
|
||||
val actual: CertPath = serialized.deserialize(factory, context)
|
||||
assertEquals(expected, actual)
|
||||
|
@ -5,9 +5,9 @@ import net.corda.core.crypto.Crypto
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.internal.*
|
||||
import net.corda.node.utilities.X509Utilities
|
||||
import net.corda.node.utilities.getX509Certificate
|
||||
import net.corda.node.utilities.loadKeyStore
|
||||
import net.corda.nodeapi.internal.crypto.X509Utilities
|
||||
import net.corda.nodeapi.internal.crypto.getX509Certificate
|
||||
import net.corda.nodeapi.internal.crypto.loadKeyStore
|
||||
import net.corda.testing.ALICE
|
||||
import net.corda.testing.rigorousMock
|
||||
import net.corda.testing.testNodeConfiguration
|
||||
|
Reference in New Issue
Block a user