diff --git a/confidential-identities/src/main/kotlin/net/corda/confidential/SwapIdentitiesFlow.kt b/confidential-identities/src/main/kotlin/net/corda/confidential/SwapIdentitiesFlow.kt index d4ecd77987..1771c2846f 100644 --- a/confidential-identities/src/main/kotlin/net/corda/confidential/SwapIdentitiesFlow.kt +++ b/confidential-identities/src/main/kotlin/net/corda/confidential/SwapIdentitiesFlow.kt @@ -76,7 +76,8 @@ class SwapIdentitiesFlow(private val otherParty: Party, val legalIdentityAnonymous = serviceHub.keyManagementService.freshKeyAndCert(ourIdentityAndCert, revocationEnabled) val serializedIdentity = SerializedBytes(legalIdentityAnonymous.serialize().bytes) - // Special case that if we're both parties, a single identity is generated + // Special case that if we're both parties, a single identity is generated. + // TODO: for increased privacy, we should create one anonymous key per output state. val identities = LinkedHashMap() if (serviceHub.myInfo.isLegalIdentity(otherParty)) { identities.put(otherParty, legalIdentityAnonymous.party.anonymise()) diff --git a/core/src/main/kotlin/net/corda/core/internal/CertRole.kt b/core/src/main/kotlin/net/corda/core/internal/CertRole.kt index 82f4a42deb..29d24b80e0 100644 --- a/core/src/main/kotlin/net/corda/core/internal/CertRole.kt +++ b/core/src/main/kotlin/net/corda/core/internal/CertRole.kt @@ -24,9 +24,7 @@ import java.security.cert.X509Certificate // also note that IDs are numbered from 1 upwards, matching numbering of other enum types in ASN.1 specifications. // TODO: Link to the specification once it has a permanent URL enum class CertRole(val validParents: NonEmptySet, val isIdentity: Boolean, val isWellKnown: Boolean) : ASN1Encodable { - /** - * Intermediate CA (Doorman service). - */ + /** Intermediate CA (Doorman service). */ INTERMEDIATE_CA(NonEmptySet.of(null), false, false), /** Signing certificate for the network map. */ NETWORK_MAP(NonEmptySet.of(null), false, false), @@ -37,6 +35,10 @@ enum class CertRole(val validParents: NonEmptySet, val isIdentity: Bo /** Transport layer security certificate for a node. */ TLS(NonEmptySet.of(NODE_CA), false, false), /** Well known (publicly visible) identity of a legal entity. */ + // TODO: at the moment, Legal Identity certs are issued by Node CA only. However, [INTERMEDIATE_CA] is also added + // as a valid parent of [LEGAL_IDENTITY] for backwards compatibility purposes (eg. if we decide TLS has its + // own Root CA and Intermediate CA directly issues Legal Identities; thus, there won't be a requirement for + // Node CA). Consider removing [INTERMEDIATE_CA] from [validParents] when the model is finalised. LEGAL_IDENTITY(NonEmptySet.of(INTERMEDIATE_CA, NODE_CA), true, true), /** Confidential (limited visibility) identity of a legal entity. */ CONFIDENTIAL_LEGAL_IDENTITY(NonEmptySet.of(LEGAL_IDENTITY), true, false); diff --git a/core/src/main/kotlin/net/corda/core/node/services/VaultService.kt b/core/src/main/kotlin/net/corda/core/node/services/VaultService.kt index 17586bfc69..b7606475f3 100644 --- a/core/src/main/kotlin/net/corda/core/node/services/VaultService.kt +++ b/core/src/main/kotlin/net/corda/core/node/services/VaultService.kt @@ -6,13 +6,14 @@ import net.corda.core.concurrent.CordaFuture import net.corda.core.contracts.* import net.corda.core.crypto.SecureHash import net.corda.core.flows.FlowException +import net.corda.core.flows.FlowLogic import net.corda.core.identity.AbstractParty import net.corda.core.messaging.DataFeed -import net.corda.core.node.services.vault.PageSpecification -import net.corda.core.node.services.vault.QueryCriteria -import net.corda.core.node.services.vault.Sort +import net.corda.core.node.services.Vault.StateStatus +import net.corda.core.node.services.vault.* import net.corda.core.serialization.CordaSerializable import net.corda.core.toFuture +import net.corda.core.transactions.LedgerTransaction import net.corda.core.utilities.NonEmptySet import rx.Observable import java.time.Instant @@ -110,15 +111,15 @@ class Vault(val states: Iterable>) { /** * Returned in queries [VaultService.queryBy] and [VaultService.trackBy]. * A Page contains: - * 1) a [List] of actual [StateAndRef] requested by the specified [QueryCriteria] to a maximum of [MAX_PAGE_SIZE] - * 2) a [List] of associated [Vault.StateMetadata], one per [StateAndRef] result - * 3) a total number of states that met the given [QueryCriteria] if a [PageSpecification] was provided - * (otherwise defaults to -1) - * 4) Status types used in this query: UNCONSUMED, CONSUMED, ALL - * 5) Other results as a [List] of any type (eg. aggregate function results with/without group by) + * 1) a [List] of actual [StateAndRef] requested by the specified [QueryCriteria] to a maximum of [MAX_PAGE_SIZE]. + * 2) a [List] of associated [Vault.StateMetadata], one per [StateAndRef] result. + * 3) a total number of states that met the given [QueryCriteria] if a [PageSpecification] was provided, + * otherwise it defaults to -1. + * 4) Status types used in this query: [StateStatus.UNCONSUMED], [StateStatus.CONSUMED], [StateStatus.ALL]. + * 5) Other results as a [List] of any type (eg. aggregate function results with/without group by). * * Note: currently otherResults are used only for Aggregate Functions (in which case, the states and statesMetadata - * results will be empty) + * results will be empty). */ @CordaSerializable data class Page(val states: List>, @@ -158,17 +159,18 @@ interface VaultService { /** * Prefer the use of [updates] unless you know why you want to use this instead. * - * Get a synchronous Observable of updates. When observations are pushed to the Observer, the Vault will already incorporate - * the update, and the database transaction associated with the update will still be open and current. If for some - * reason the processing crosses outside of the database transaction (for example, the update is pushed outside the current - * JVM or across to another [Thread] which is executing in a different database transaction) then the Vault may - * not incorporate the update due to racing with committing the current database transaction. + * Get a synchronous [Observable] of updates. When observations are pushed to the Observer, the [Vault] will already + * incorporate the update, and the database transaction associated with the update will still be open and current. + * If for some reason the processing crosses outside of the database transaction (for example, the update is pushed + * outside the current JVM or across to another [Thread], which is executing in a different database transaction), + * then the [Vault] may not incorporate the update due to racing with committing the current database transaction. */ val rawUpdates: Observable> /** - * Get a synchronous Observable of updates. When observations are pushed to the Observer, the Vault will already incorporate - * the update, and the database transaction associated with the update will have been committed and closed. + * Get a synchronous [Observable] of updates. When observations are pushed to the Observer, the [Vault] will + * already incorporate the update and the database transaction associated with the update will have been committed + * and closed. */ val updates: Observable> @@ -180,10 +182,10 @@ interface VaultService { } /** - * Add a note to an existing [LedgerTransaction] given by its unique [SecureHash] id + * Add a note to an existing [LedgerTransaction] given by its unique [SecureHash] id. * Multiple notes may be attached to the same [LedgerTransaction]. - * These are additively and immutably persisted within the node local vault database in a single textual field - * using a semi-colon separator + * These are additively and immutably persisted within the node local vault database in a single textual field. + * using a semi-colon separator. */ fun addNoteToTransaction(txnId: SecureHash, noteText: String) @@ -192,7 +194,7 @@ interface VaultService { // DOCEND VaultStatesQuery /** - * Soft locking is used to prevent multiple transactions trying to use the same output simultaneously. + * Soft locking is used to prevent multiple transactions trying to use the same states simultaneously. * Violation of a soft lock would result in a double spend being created and rejected by the notary. */ @@ -200,35 +202,35 @@ interface VaultService { /** * Reserve a set of [StateRef] for a given [UUID] unique identifier. - * Typically, the unique identifier will refer to a [FlowLogic.runId.uuid] associated with an in-flight flow. + * Typically, the unique identifier will refer to a [FlowLogic.runId]'s [UUID] associated with an in-flight flow. * In this case if the flow terminates the locks will automatically be freed, even if there is an error. - * However, the user can specify their own [UUID] and manage this manually, possibly across the lifetime of multiple flows, - * or from other thread contexts e.g. [CordaService] instances. + * However, the user can specify their own [UUID] and manage this manually, possibly across the lifetime of multiple + * flows, or from other thread contexts e.g. [CordaService] instances. * In the case of coin selection, soft locks are automatically taken upon gathering relevant unconsumed input refs. * - * @throws [StatesNotAvailableException] when not possible to softLock all of requested [StateRef] + * @throws [StatesNotAvailableException] when not possible to soft-lock all of requested [StateRef]. */ @Throws(StatesNotAvailableException::class) fun softLockReserve(lockId: UUID, stateRefs: NonEmptySet) /** * Release all or an explicitly specified set of [StateRef] for a given [UUID] unique identifier. - * A vault soft lock manager is automatically notified of a Flows that are terminated, such that any soft locked states - * may be released. - * In the case of coin selection, softLock are automatically released once previously gathered unconsumed input refs - * are consumed as part of cash spending. + * A [Vault] soft-lock manager is automatically notified from flows that are terminated, such that any soft locked + * states may be released. + * In the case of coin selection, soft-locks are automatically released once previously gathered unconsumed + * input refs are consumed as part of cash spending. */ fun softLockRelease(lockId: UUID, stateRefs: NonEmptySet? = null) // DOCEND SoftLockAPI /** * Helper function to determine spendable states and soft locking them. - * Currently performance will be worse than for the hand optimised version in `Cash.unconsumedCashStatesForSpending` + * Currently performance will be worse than for the hand optimised version in `Cash.unconsumedCashStatesForSpending`. * However, this is fully generic and can operate with custom [FungibleAsset] states. - * @param lockId The [FlowLogic.runId.uuid] of the current flow used to soft lock the states. + * @param lockId The [FlowLogic.runId]'s [UUID] of the current flow used to soft lock the states. * @param eligibleStatesQuery A custom query object that selects down to the appropriate subset of all states of the - * [contractStateType]. e.g. by selecting on account, issuer, etc. The query is internally augmented with the UNCONSUMED, - * soft lock and contract type requirements. + * [contractStateType]. e.g. by selecting on account, issuer, etc. The query is internally augmented with the + * [StateStatus.UNCONSUMED], soft lock and contract type requirements. * @param amount The required amount of the asset, but with the issuer stripped off. * It is assumed that compatible issuer states will be filtered out by the [eligibleStatesQuery]. * @param contractStateType class type of the result set. @@ -249,12 +251,12 @@ interface VaultService { * and returns a [Vault.Page] object containing the following: * 1. states as a List of (page number and size defined by [PageSpecification]) * 2. states metadata as a List of [Vault.StateMetadata] held in the Vault States table. - * 3. total number of results available if [PageSpecification] supplied (otherwise returns -1) - * 4. status types used in this query: UNCONSUMED, CONSUMED, ALL - * 5. other results (aggregate functions with/without using value groups) + * 3. total number of results available if [PageSpecification] supplied (otherwise returns -1). + * 4. status types used in this query: [StateStatus.UNCONSUMED], [StateStatus.CONSUMED], [StateStatus.ALL]. + * 5. other results (aggregate functions with/without using value groups). * * @throws VaultQueryException if the query cannot be executed for any reason - * (missing criteria or parsing error, paging errors, unsupported query, underlying database error) + * (missing criteria or parsing error, paging errors, unsupported query, underlying database error). * * Notes * If no [PageSpecification] is provided, a maximum of [DEFAULT_PAGE_SIZE] results will be returned. @@ -271,11 +273,11 @@ interface VaultService { /** * Generic vault query function which takes a [QueryCriteria] object to define filters, * optional [PageSpecification] and optional [Sort] modification criteria (default unsorted), - * and returns a [Vault.PageAndUpdates] object containing - * 1) a snapshot as a [Vault.Page] (described previously in [queryBy]) - * 2) an [Observable] of [Vault.Update] + * and returns a [DataFeed] object containing: + * 1) a snapshot as a [Vault.Page] (described previously in [queryBy]). + * 2) an [Observable] of [Vault.Update]. * - * @throws VaultQueryException if the query cannot be executed for any reason + * @throws VaultQueryException if the query cannot be executed for any reason. * * Notes: the snapshot part of the query adheres to the same behaviour as the [queryBy] function. * the [QueryCriteria] applies to both snapshot and deltas (streaming updates). @@ -287,8 +289,8 @@ interface VaultService { contractStateType: Class): DataFeed, Vault.Update> // DOCEND VaultQueryAPI - // Note: cannot apply @JvmOverloads to interfaces nor interface implementations - // Java Helpers + // Note: cannot apply @JvmOverloads to interfaces nor interface implementations. + // Java Helpers. fun queryBy(contractStateType: Class): Vault.Page { return _queryBy(QueryCriteria.VaultQueryCriteria(), PageSpecification(), Sort(emptySet()), contractStateType) } diff --git a/core/src/main/kotlin/net/corda/core/transactions/LedgerTransaction.kt b/core/src/main/kotlin/net/corda/core/transactions/LedgerTransaction.kt index 09b3b7c98c..4c2445676a 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/LedgerTransaction.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/LedgerTransaction.kt @@ -400,6 +400,6 @@ data class LedgerTransaction @JvmOverloads constructor( notary: Party?, timeWindow: TimeWindow?, privacySalt: PrivacySalt - ) = copy(inputs, outputs, commands, attachments, id, notary, timeWindow, privacySalt, null) + ) = copy(inputs = inputs, outputs = outputs, commands = commands, attachments = attachments, id = id, notary = notary, timeWindow = timeWindow, privacySalt = privacySalt, networkParameters = null) } diff --git a/core/src/main/kotlin/net/corda/core/transactions/MerkleTransaction.kt b/core/src/main/kotlin/net/corda/core/transactions/MerkleTransaction.kt index be7cdf0d7b..b61daf9963 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/MerkleTransaction.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/MerkleTransaction.kt @@ -85,18 +85,18 @@ abstract class TraversableTransaction(open val componentGroups: List>(it).deserialize() }) val commandDataList = deserialiseComponentGroup(ComponentGroupEnum.COMMANDS_GROUP, { SerializedBytes(it).deserialize(context = SerializationFactory.defaultFactory.defaultContext.withAttachmentsClassLoader(attachments)) }) val group = componentGroups.firstOrNull { it.groupIndex == ComponentGroupEnum.COMMANDS_GROUP.ordinal } - if (group is FilteredComponentGroup) { + return if (group is FilteredComponentGroup) { check(commandDataList.size <= signersList.size) { "Invalid Transaction. Less Signers (${signersList.size}) than CommandData (${commandDataList.size}) objects" } val componentHashes = group.components.mapIndexed { index, component -> componentHash(group.nonces[index], component) } val leafIndices = componentHashes.map { group.partialMerkleTree.leafIndex(it) } if (leafIndices.isNotEmpty()) check(leafIndices.max()!! < signersList.size) { "Invalid Transaction. A command with no corresponding signer detected" } - return commandDataList.mapIndexed { index, commandData -> Command(commandData, signersList[leafIndices[index]]) } + commandDataList.mapIndexed { index, commandData -> Command(commandData, signersList[leafIndices[index]]) } } else { // It is a WireTransaction // or a FilteredTransaction with no Commands (in which case group is null). check(commandDataList.size == signersList.size) { "Invalid Transaction. Sizes of CommandData (${commandDataList.size}) and Signers (${signersList.size}) do not match" } - return commandDataList.mapIndexed { index, commandData -> Command(commandData, signersList[index]) } + commandDataList.mapIndexed { index, commandData -> Command(commandData, signersList[index]) } } } } @@ -148,9 +148,9 @@ class FilteredTransaction internal constructor( // As all of the helper Map structures, like availableComponentNonces, availableComponentHashes // and groupsMerkleRoots, are computed lazily via componentGroups.forEach, there should always be // a match on Map.get ensuring it will never return null. - filteredSerialisedComponents.put(componentGroupIndex, mutableListOf(serialisedComponent)) - filteredComponentNonces.put(componentGroupIndex, mutableListOf(wtx.availableComponentNonces[componentGroupIndex]!![internalIndex])) - filteredComponentHashes.put(componentGroupIndex, mutableListOf(wtx.availableComponentHashes[componentGroupIndex]!![internalIndex])) + filteredSerialisedComponents[componentGroupIndex] = mutableListOf(serialisedComponent) + filteredComponentNonces[componentGroupIndex] = mutableListOf(wtx.availableComponentNonces[componentGroupIndex]!![internalIndex]) + filteredComponentHashes[componentGroupIndex] = mutableListOf(wtx.availableComponentHashes[componentGroupIndex]!![internalIndex]) } else { group.add(serialisedComponent) // If the group[componentGroupIndex] existed, then we guarantee that @@ -165,9 +165,9 @@ class FilteredTransaction internal constructor( val signersGroupIndex = ComponentGroupEnum.SIGNERS_GROUP.ordinal // There exist commands, thus the signers group is not empty. val signersGroupComponents = wtx.componentGroups.first { it.groupIndex == signersGroupIndex } - filteredSerialisedComponents.put(signersGroupIndex, signersGroupComponents.components.toMutableList()) - filteredComponentNonces.put(signersGroupIndex, wtx.availableComponentNonces[signersGroupIndex]!!.toMutableList()) - filteredComponentHashes.put(signersGroupIndex, wtx.availableComponentHashes[signersGroupIndex]!!.toMutableList()) + filteredSerialisedComponents[signersGroupIndex] = signersGroupComponents.components.toMutableList() + filteredComponentNonces[signersGroupIndex] = wtx.availableComponentNonces[signersGroupIndex]!!.toMutableList() + filteredComponentHashes[signersGroupIndex] = wtx.availableComponentHashes[signersGroupIndex]!!.toMutableList() } } } @@ -312,14 +312,14 @@ class FilteredTransaction internal constructor( .filter { signers -> publicKey in signers }.size } - inline private fun verificationCheck(value: Boolean, lazyMessage: () -> Any) { + private inline fun verificationCheck(value: Boolean, lazyMessage: () -> Any) { if (!value) { val message = lazyMessage() throw FilteredTransactionVerificationException(id, message.toString()) } } - inline private fun visibilityCheck(value: Boolean, lazyMessage: () -> Any) { + private inline fun visibilityCheck(value: Boolean, lazyMessage: () -> Any) { if (!value) { val message = lazyMessage() throw ComponentVisibilityException(id, message.toString()) diff --git a/core/src/main/kotlin/net/corda/core/transactions/TransactionBuilder.kt b/core/src/main/kotlin/net/corda/core/transactions/TransactionBuilder.kt index 3204e15580..12808fc299 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/TransactionBuilder.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/TransactionBuilder.kt @@ -102,7 +102,7 @@ open class TransactionBuilder( // with an explicit [AttachmentConstraint] val resolvedOutputs = outputs.map { state -> when { - state.constraint !is AutomaticHashConstraint -> state + state.constraint !== AutomaticHashConstraint -> state useWhitelistedByZoneAttachmentConstraint(state.contract, services.networkParameters) -> state.copy(constraint = WhitelistedByZoneAttachmentConstraint) else -> services.cordappProvider.getContractAttachmentID(state.contract)?.let { state.copy(constraint = HashAttachmentConstraint(it)) diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/ArtemisTcpTransport.kt b/node-api/src/main/kotlin/net/corda/nodeapi/ArtemisTcpTransport.kt index 34b5bf7784..b0330f667a 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/ArtemisTcpTransport.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/ArtemisTcpTransport.kt @@ -17,26 +17,37 @@ sealed class ConnectionDirection { ) : ConnectionDirection() } +/** Class to set Artemis TCP configuration options. */ class ArtemisTcpTransport { companion object { const val VERIFY_PEER_LEGAL_NAME = "corda.verifyPeerCommonName" - // Restrict enabled TLS cipher suites to: - // AES128 using Galois/Counter Mode (GCM) for the block cipher being used to encrypt the message stream. - // SHA256 as message authentication algorithm. - // ECDHE as key exchange algorithm. DHE is also supported if one wants to completely avoid the use of ECC for TLS. - // ECDSA and RSA for digital signatures. Our self-generated certificates all use ECDSA for handshakes, - // but we allow classical RSA certificates to work in case: - // a) we need to use keytool certificates in some demos, - // b) we use cloud providers or HSMs that do not support ECC. + /** + * Corda supported TLS schemes. + *

    + *
  • TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 + *
  • TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 + *
  • TLS_DHE_RSA_WITH_AES_128_GCM_SHA256 + *

+ * As shown above, current version restricts enabled TLS cipher suites to: + * AES128 using Galois/Counter Mode (GCM) for the block cipher being used to encrypt the message stream. + * SHA256 as message authentication algorithm. + * Ephemeral Diffie Hellman key exchange for advanced forward secrecy. ECDHE is preferred, but DHE is also + * supported in case one wants to completely avoid the use of ECC for TLS. + * ECDSA and RSA for digital signatures. Our self-generated certificates all use ECDSA for handshakes, + * but we allow classical RSA certificates to work in case one uses external tools or cloud providers or HSMs + * that do not support ECC certificates. + */ val CIPHER_SUITES = listOf( "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256" ) + /** Supported TLS versions, currently TLSv1.2 only. */ val TLS_VERSIONS = listOf("TLSv1.2") + /** Specify [TransportConfiguration] for TCP communication. */ fun tcpTransport( direction: ConnectionDirection, hostAndPort: NetworkHostAndPort, diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/network/NetworkMap.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/network/NetworkMap.kt index 31314ca833..2e652d9279 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/network/NetworkMap.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/network/NetworkMap.kt @@ -44,6 +44,10 @@ data class ParametersUpdate( val updateDeadline: Instant ) +/** Verify that a Network Map certificate is issued by Root CA and its [CertRole] is correct. */ +// TODO: Current implementation works under the assumption that there are no intermediate CAs between Root and +// Network Map. Consider a more flexible implementation without the above assumption. + fun SignedDataWithCert.verifiedNetworkMapCert(rootCert: X509Certificate): T { require(CertRole.extract(sig.by) == CertRole.NETWORK_MAP) { "Incorrect cert role: ${CertRole.extract(sig.by)}" } X509Utilities.validateCertificateChain(rootCert, sig.by, rootCert)