mirror of
https://github.com/corda/corda.git
synced 2024-12-18 20:47:57 +00:00
Backport contract key rotation to 4.9.
This commit is contained in:
parent
282ee95188
commit
828066a646
@ -77,6 +77,9 @@ def patchCore = tasks.register('patchCore', Zip) {
|
||||
exclude 'net/corda/core/serialization/internal/AttachmentsHolderImpl.class'
|
||||
exclude 'net/corda/core/serialization/internal/CheckpointSerializationFactory*.class'
|
||||
exclude 'net/corda/core/internal/rules/*.class'
|
||||
exclude 'net/corda/core/contracts/CordaRotatedKeys.class'
|
||||
exclude 'net/corda/core/contracts/RotatedKeysKt.class'
|
||||
exclude 'net/corda/core/contracts/RotatedKeys.class'
|
||||
}
|
||||
|
||||
reproducibleFileOrder = true
|
||||
|
@ -1,18 +1,14 @@
|
||||
package net.corda.core.contracts
|
||||
|
||||
import net.corda.core.DeleteForDJVM
|
||||
import net.corda.core.KeepForDJVM
|
||||
import net.corda.core.crypto.CompositeKey
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.crypto.sha256
|
||||
import net.corda.core.internal.hash
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||
import java.security.PublicKey
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import java.util.concurrent.ConcurrentMap
|
||||
|
||||
@DeleteForDJVM
|
||||
object CordaRotatedKeys {
|
||||
val keys = RotatedKeys()
|
||||
}
|
||||
@ -47,8 +43,7 @@ private val CORDA_SIGNING_KEY_ROTATIONS = listOf(
|
||||
*
|
||||
*/
|
||||
@CordaSerializable
|
||||
@DeleteForDJVM
|
||||
data class RotatedKeys(val rotatedSigningKeys: List<List<SecureHash>> = emptyList()): SingletonSerializeAsToken() {
|
||||
data class RotatedKeys(val rotatedSigningKeys: List<List<SecureHash>> = emptyList()) {
|
||||
private val canBeTransitionedMap: ConcurrentMap<Pair<PublicKey, PublicKey>, Boolean> = ConcurrentHashMap()
|
||||
private val rotateMap: Map<SecureHash, SecureHash> = HashMap<SecureHash, SecureHash>().apply {
|
||||
(rotatedSigningKeys + CORDA_SIGNING_KEY_ROTATIONS).forEach { rotatedKeyList ->
|
||||
|
@ -1,6 +1,5 @@
|
||||
package net.corda.coretests.contracts
|
||||
|
||||
<
|
||||
import com.nhaarman.mockito_kotlin.doReturn
|
||||
import com.nhaarman.mockito_kotlin.mock
|
||||
import com.nhaarman.mockito_kotlin.whenever
|
||||
|
@ -1,18 +1,19 @@
|
||||
package net.corda.coretests.transactions
|
||||
|
||||
import com.codahale.metrics.MetricRegistry
|
||||
import com.nhaarman.mockito_kotlin.doReturn
|
||||
import com.nhaarman.mockito_kotlin.whenever
|
||||
import net.corda.core.contracts.TransactionVerificationException
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.internal.AttachmentTrustCalculator
|
||||
import net.corda.core.internal.hash
|
||||
import net.corda.core.internal.verification.NodeVerificationSupport
|
||||
import net.corda.core.node.NetworkParameters
|
||||
import net.corda.core.node.ServicesForResolution
|
||||
import net.corda.core.node.services.AttachmentId
|
||||
import net.corda.core.serialization.internal.AttachmentsClassLoader
|
||||
import net.corda.coretesting.internal.rigorousMock
|
||||
import net.corda.node.services.attachments.NodeAttachmentTrustCalculator
|
||||
import net.corda.node.services.persistence.NodeAttachmentService
|
||||
import net.corda.node.services.persistence.toInternal
|
||||
import net.corda.nodeapi.internal.persistence.CordaPersistence
|
||||
import net.corda.nodeapi.internal.persistence.DatabaseConfig
|
||||
import net.corda.testing.common.internal.testNetworkParameters
|
||||
@ -30,8 +31,6 @@ import net.corda.testing.node.MockServices
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.mockito.kotlin.doReturn
|
||||
import org.mockito.kotlin.whenever
|
||||
import java.net.URL
|
||||
import kotlin.test.assertFailsWith
|
||||
|
||||
@ -49,12 +48,11 @@ class AttachmentsClassLoaderWithStoragePersistenceTests {
|
||||
|
||||
private lateinit var database: CordaPersistence
|
||||
private lateinit var storage: NodeAttachmentService
|
||||
private lateinit var attachmentTrustCalculator: AttachmentTrustCalculator
|
||||
private lateinit var attachmentTrustCalculator2: AttachmentTrustCalculator
|
||||
private val networkParameters = testNetworkParameters()
|
||||
private val cacheFactory = TestingNamedCacheFactory(1)
|
||||
private val cacheFactory2 = TestingNamedCacheFactory()
|
||||
private val nodeVerificationSupport = rigorousMock<NodeVerificationSupport>().also {
|
||||
private val services = rigorousMock<ServicesForResolution>().also {
|
||||
doReturn(testNetworkParameters()).whenever(it).networkParameters
|
||||
}
|
||||
|
||||
@ -86,8 +84,7 @@ class AttachmentsClassLoaderWithStoragePersistenceTests {
|
||||
it.start()
|
||||
}
|
||||
}
|
||||
storage.nodeVerificationSupport = nodeVerificationSupport
|
||||
attachmentTrustCalculator = NodeAttachmentTrustCalculator(storage.toInternal(), cacheFactory)
|
||||
storage.servicesForResolution = services
|
||||
attachmentTrustCalculator2 = NodeAttachmentTrustCalculator(storage, database, cacheFactory2)
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
package net.corda.core.node.services
|
||||
package net.corda.core.contracts
|
||||
|
||||
import net.corda.core.KeepForDJVM
|
||||
import net.corda.core.crypto.CompositeKey
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.crypto.sha256
|
||||
@ -45,7 +44,6 @@ private val CORDA_SIGNING_KEY_ROTATIONS = listOf(
|
||||
*
|
||||
*/
|
||||
@CordaSerializable
|
||||
@KeepForDJVM
|
||||
data class RotatedKeys(val rotatedSigningKeys: List<List<SecureHash>> = emptyList()): SingletonSerializeAsToken() {
|
||||
private val canBeTransitionedMap: ConcurrentMap<Pair<PublicKey, PublicKey>, Boolean> = ConcurrentHashMap()
|
||||
private val rotateMap: Map<SecureHash, SecureHash> = HashMap<SecureHash, SecureHash>().apply {
|
||||
|
@ -3,6 +3,7 @@ package net.corda.core.internal
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.crypto.keys
|
||||
import net.corda.core.internal.cordapp.CordappImpl
|
||||
import net.corda.core.contracts.RotatedKeys
|
||||
import net.corda.core.utilities.loggerFor
|
||||
|
||||
/**
|
||||
|
@ -90,6 +90,7 @@ abstract class AbstractVerifier(
|
||||
/**
|
||||
* Because we create a separate [LedgerTransaction] onto which we need to perform verification, it becomes important we don't verify the
|
||||
* wrong object instance. This class helps
|
||||
*/
|
||||
private class Validator(private val ltx: LedgerTransaction, private val transactionClassLoader: ClassLoader, private val rotatedKeys: RotatedKeys) {
|
||||
private val inputStates: List<TransactionState<*>> = ltx.inputs.map(StateAndRef<ContractState>::state)
|
||||
private val allStates: List<TransactionState<*>> = inputStates + ltx.references.map(StateAndRef<ContractState>::state) + ltx.outputs
|
||||
@ -438,7 +439,7 @@ private class Validator(private val ltx: LedgerTransaction, private val transact
|
||||
private fun verifyConstraintUsingRotatedKeys(constraint: AttachmentConstraint, constraintAttachment: AttachmentWithContext, contract: ContractClassName ) {
|
||||
// constraint could be an input constraint so we manually have to rotate to updated constraint
|
||||
if (constraint is SignatureAttachmentConstraint && rotatedKeys.canBeTransitioned(constraint.key, constraintAttachment.signerKeys)) {
|
||||
val constraintWithRotatedKeys = SignatureAttachmentConstraint.create(CompositeKey.Builder().addKeys(constraintAttachment.signerKeys).build())
|
||||
val constraintWithRotatedKeys = SignatureAttachmentConstraint(CompositeKey.Builder().addKeys(constraintAttachment.signerKeys).build())
|
||||
if (!constraintWithRotatedKeys.isSatisfiedBy(constraintAttachment)) throw ContractConstraintRejection(ltx.id, contract)
|
||||
}
|
||||
else {
|
||||
|
@ -171,6 +171,12 @@ interface ServiceHub : ServicesForResolution {
|
||||
*/
|
||||
val transactionVerifierService: TransactionVerifierService
|
||||
|
||||
/**
|
||||
* INTERNAL. DO NOT USE.
|
||||
* @suppress
|
||||
*/
|
||||
val rotatedKeys: RotatedKeys
|
||||
|
||||
/**
|
||||
* A [Clock] representing the node's current time. This should be used in preference to directly accessing the
|
||||
* clock so the current time can be controlled during unit testing.
|
||||
|
@ -2,7 +2,6 @@ package net.corda.core.serialization.internal
|
||||
|
||||
import com.github.benmanes.caffeine.cache.Cache
|
||||
import com.github.benmanes.caffeine.cache.Caffeine
|
||||
import net.corda.core.DeleteForDJVM
|
||||
import net.corda.core.contracts.Attachment
|
||||
import net.corda.core.contracts.ContractAttachment
|
||||
import net.corda.core.contracts.CordaRotatedKeys
|
||||
@ -359,7 +358,7 @@ object AttachmentsClassLoaderBuilder {
|
||||
|
||||
val cache = if (attachmentsClassLoaderCache is AttachmentsClassLoaderForRotatedKeysOnlyImpl) fallBackCache else
|
||||
attachmentsClassLoaderCache ?: fallBackCache
|
||||
val cachedSerializationContext = cache.computeIfAbsent(AttachmentsClassLoaderKey(attachmentIds, params)), Function { key ->
|
||||
val cachedSerializationContext = cache.computeIfAbsent(AttachmentsClassLoaderKey(attachmentIds, params), Function { key ->
|
||||
// Create classloader and load serializers, whitelisted classes
|
||||
val transactionClassLoader = AttachmentsClassLoader(attachments, key.params, txId, isAttachmentTrusted, parent)
|
||||
val serializers = try {
|
||||
@ -539,7 +538,7 @@ class AttachmentsClassLoaderSimpleCacheImpl(cacheSize: Int, override val rotated
|
||||
}
|
||||
|
||||
class AttachmentsClassLoaderForRotatedKeysOnlyImpl(override val rotatedKeys: RotatedKeys = CordaRotatedKeys.keys) : AttachmentsClassLoaderCache {
|
||||
override fun computeIfAbsent(key: AttachmentsClassLoaderKey, mappingFunction: (AttachmentsClassLoaderKey) -> SerializationContext): SerializationContext {
|
||||
override fun computeIfAbsent(key: AttachmentsClassLoaderKey, mappingFunction: Function<in AttachmentsClassLoaderKey, out SerializationContext>): SerializationContext {
|
||||
throw NotImplementedError("AttachmentsClassLoaderForRotatedKeysOnlyImpl.computeIfAbsent should never be called. Should be replaced by the fallback cache")
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,9 @@ import net.corda.core.node.ServiceHub
|
||||
import net.corda.core.node.ServicesForResolution
|
||||
import net.corda.core.node.ZoneVersionTooLowException
|
||||
import net.corda.core.node.services.AttachmentId
|
||||
import net.corda.core.contracts.CordaRotatedKeys
|
||||
import net.corda.core.node.services.KeyManagementService
|
||||
import net.corda.core.contracts.RotatedKeys
|
||||
import net.corda.core.serialization.CustomSerializationScheme
|
||||
import net.corda.core.serialization.SerializationContext
|
||||
import net.corda.core.serialization.SerializationDefaults
|
||||
@ -524,11 +526,11 @@ open class TransactionBuilder(
|
||||
|
||||
// This is the logic to determine the constraint which will replace the AutomaticPlaceholderConstraint.
|
||||
val (defaultOutputConstraint, constraintAttachment) = selectDefaultOutputConstraintAndConstraintAttachment(contractClassName,
|
||||
inputStates, selectedAttachment.currentAttachment, serviceHub)
|
||||
inputStates, attachmentToUse, services)
|
||||
|
||||
// Sanity check that the selected attachment actually passes.
|
||||
require(defaultOutputConstraint.isSatisfiedBy(constraintAttachment)) {
|
||||
"Selected output constraint: $defaultOutputConstraint not satisfying $selectedAttachment"
|
||||
"Selected output constraint: $defaultOutputConstraint not satisfying $attachmentToUse"
|
||||
}
|
||||
|
||||
val resolvedOutputStates = outputStates.map {
|
||||
@ -538,7 +540,7 @@ open class TransactionBuilder(
|
||||
} else {
|
||||
// If the constraint on the output state is already set, and is not a valid transition or can't be transitioned, then fail early.
|
||||
inputStates?.forEach { input ->
|
||||
require(outputConstraint.canBeTransitionedFrom(input.constraint, selectedAttachment.currentAttachment, serviceHub.toVerifyingServiceHub().rotatedKeys)) {
|
||||
require(outputConstraint.canBeTransitionedFrom(input.constraint, attachmentToUse, getRotatedKeys(serviceHub))) {
|
||||
"Output state constraint $outputConstraint cannot be transitioned from ${input.constraint}"
|
||||
}
|
||||
}
|
||||
@ -550,6 +552,14 @@ open class TransactionBuilder(
|
||||
return Pair(selectedAttachmentId, resolvedOutputStates)
|
||||
}
|
||||
|
||||
private fun getRotatedKeys(services: ServiceHub?): RotatedKeys {
|
||||
return services?.rotatedKeys ?: CordaRotatedKeys.keys.also {
|
||||
log.warn("WARNING: You must pass in a ServiceHub reference to TransactionBuilder to resolve " +
|
||||
"rotated keys defined in configuration. If you are writing a unit test then pass in a " +
|
||||
"MockServices instance.")
|
||||
}
|
||||
}
|
||||
|
||||
private fun selectDefaultOutputConstraintAndConstraintAttachment( contractClassName: ContractClassName,
|
||||
inputStates: List<TransactionState<ContractState>>?,
|
||||
attachmentToUse: ContractAttachment,
|
||||
@ -564,7 +574,7 @@ open class TransactionBuilder(
|
||||
|
||||
if (!defaultOutputConstraint.isSatisfiedBy(constraintAttachment)) {
|
||||
// The defaultOutputConstraint is the input constraint by the attachment in use currently may have a rotated key
|
||||
if (defaultOutputConstraint is SignatureAttachmentConstraint && services.toVerifyingServiceHub().rotatedKeys.canBeTransitioned(defaultOutputConstraint.key, constraintAttachment.signerKeys)) {
|
||||
if (defaultOutputConstraint is SignatureAttachmentConstraint && (getRotatedKeys(serviceHub).canBeTransitioned(defaultOutputConstraint.key, constraintAttachment.signerKeys))) {
|
||||
return Pair(makeSignatureAttachmentConstraint(attachmentToUse.signerKeys), constraintAttachment)
|
||||
}
|
||||
}
|
||||
|
@ -6,15 +6,12 @@ import net.corda.core.KeepForDJVM
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.contracts.ComponentGroupEnum.COMMANDS_GROUP
|
||||
import net.corda.core.contracts.ComponentGroupEnum.OUTPUTS_GROUP
|
||||
import net.corda.core.contracts.ComponentGroupEnum.SIGNERS_GROUP
|
||||
import net.corda.core.contracts.ContractState
|
||||
import net.corda.core.contracts.PrivacySalt
|
||||
import net.corda.core.contracts.RotatedKeys
|
||||
import net.corda.core.contracts.StateRef
|
||||
import net.corda.core.contracts.TimeWindow
|
||||
import net.corda.core.contracts.TransactionResolutionException
|
||||
import net.corda.core.contracts.TransactionState
|
||||
import net.corda.core.contracts.TransactionVerificationException
|
||||
import net.corda.core.crypto.DigestService
|
||||
import net.corda.core.crypto.MerkleTree
|
||||
import net.corda.core.crypto.SecureHash
|
||||
|
@ -5,6 +5,7 @@ import net.corda.core.contracts.Attachment
|
||||
import net.corda.core.contracts.CommandData
|
||||
import net.corda.core.contracts.CommandWithParties
|
||||
import net.corda.core.contracts.ContractState
|
||||
import net.corda.core.contracts.CordaRotatedKeys
|
||||
import net.corda.core.contracts.PrivacySalt
|
||||
import net.corda.core.contracts.StateAndRef
|
||||
import net.corda.core.contracts.StateRef
|
||||
@ -54,7 +55,8 @@ class LtxSupplierFactory : Function<Array<out Any?>, Supplier<LedgerTransaction>
|
||||
privacySalt = txArgs[TX_PRIVACY_SALT] as PrivacySalt,
|
||||
networkParameters = networkParameters,
|
||||
references = referencesProvider.get(),
|
||||
digestService = txArgs[TX_DIGEST_SERVICE] as DigestService
|
||||
digestService = txArgs[TX_DIGEST_SERVICE] as DigestService,
|
||||
rotatedKeys = CordaRotatedKeys.keys
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -30,9 +30,8 @@ import org.apache.commons.io.FileUtils.deleteDirectory
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.mockito.kotlin.doReturn
|
||||
import org.mockito.kotlin.whenever
|
||||
import kotlin.io.path.div
|
||||
import com.nhaarman.mockito_kotlin.doReturn
|
||||
import com.nhaarman.mockito_kotlin.whenever
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class ContractWithRotatedKeyTest {
|
||||
@ -62,7 +61,7 @@ class ContractWithRotatedKeyTest {
|
||||
): TestStartedNode {
|
||||
node.internals.disableDBCloseOnStop()
|
||||
node.dispose()
|
||||
val cordappsDir = network.baseDirectory(node) / "cordapps"
|
||||
val cordappsDir = network.baseDirectory(node).resolve("cordapps")
|
||||
deleteDirectory(cordappsDir.toFile())
|
||||
return network.createNode(
|
||||
parameters.copy(legalName = node.internals.configuration.myLegalName, forcedID = node.internals.id),
|
||||
@ -75,8 +74,8 @@ class ContractWithRotatedKeyTest {
|
||||
val keyStoreDir1 = SelfCleaningDir()
|
||||
val keyStoreDir2 = SelfCleaningDir()
|
||||
|
||||
val packageOwnerKey1 = keyStoreDir1.path.generateKey(alias="1-testcordapp-rsa")
|
||||
val packageOwnerKey2 = keyStoreDir2.path.generateKey(alias="1-testcordapp-rsa")
|
||||
val packageOwnerKey1 = keyStoreDir1.path.generateKey(alias="alias1")
|
||||
val packageOwnerKey2 = keyStoreDir2.path.generateKey(alias="alias1")
|
||||
|
||||
val unsignedFinanceCorDapp1 = cordappWithPackages("net.corda.finance", "migration", "META-INF.services")
|
||||
val unsignedFinanceCorDapp2 = cordappWithPackages("net.corda.finance", "migration", "META-INF.services").copy(versionId = 2)
|
||||
|
@ -6,6 +6,7 @@ import io.github.classgraph.ScanResult
|
||||
import net.corda.common.logging.errorReporting.CordappErrors
|
||||
import net.corda.common.logging.errorReporting.ErrorCode
|
||||
import net.corda.core.CordaRuntimeException
|
||||
import net.corda.core.contracts.CordaRotatedKeys
|
||||
import net.corda.core.contracts.RotatedKeys
|
||||
import net.corda.core.cordapp.Cordapp
|
||||
import net.corda.core.crypto.SecureHash
|
||||
@ -90,9 +91,9 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths:
|
||||
* @param scanJars Uses the JAR URLs provided for classpath scanning and Cordapp detection.
|
||||
*/
|
||||
fun fromJarUrls(scanJars: List<URL>, versionInfo: VersionInfo = VersionInfo.UNKNOWN, extraCordapps: List<CordappImpl> = emptyList(),
|
||||
cordappsSignerKeyFingerprintBlacklist: List<SecureHash> = emptyList()): JarScanningCordappLoader {
|
||||
cordappsSignerKeyFingerprintBlacklist: List<SecureHash> = emptyList(), rotatedKeys: RotatedKeys = CordaRotatedKeys.keys): JarScanningCordappLoader {
|
||||
val paths = scanJars.map { it.restricted() }
|
||||
return JarScanningCordappLoader(paths, versionInfo, extraCordapps, cordappsSignerKeyFingerprintBlacklist)
|
||||
return JarScanningCordappLoader(paths, versionInfo, extraCordapps, cordappsSignerKeyFingerprintBlacklist, rotatedKeys)
|
||||
}
|
||||
|
||||
private fun URL.restricted(rootPackageName: String? = null) = RestrictedURL(this, rootPackageName)
|
||||
|
@ -10,7 +10,6 @@ import net.corda.core.flows.FlowException
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.internal.*
|
||||
import net.corda.core.internal.notary.NotaryService
|
||||
|
||||
import net.corda.core.node.ServiceHub
|
||||
import net.corda.core.node.ServicesForResolution
|
||||
import net.corda.core.node.services.AttachmentId
|
||||
|
Loading…
Reference in New Issue
Block a user