diff --git a/.ci/api-current.txt b/.ci/api-current.txt index e30e90cdcf..d3363ad577 100644 --- a/.ci/api-current.txt +++ b/.ci/api-current.txt @@ -429,7 +429,7 @@ public static final class net.corda.core.contracts.AmountTransfer$Companion exte public interface net.corda.core.contracts.Attachment extends net.corda.core.contracts.NamedByHash public void extractFile(String, java.io.OutputStream) @NotNull - public abstract java.util.List getSigners() + public abstract java.util.List getSigners() public abstract int getSize() @NotNull public abstract java.io.InputStream open() @@ -540,7 +540,7 @@ public final class net.corda.core.contracts.ContractAttachment extends java.lang @NotNull public net.corda.core.crypto.SecureHash getId() @NotNull - public java.util.List getSigners() + public java.util.List getSigners() public int getSize() @Nullable public final String getUploader() diff --git a/core-deterministic/testing/common/src/main/kotlin/net/corda/deterministic/common/MockContractAttachment.kt b/core-deterministic/testing/common/src/main/kotlin/net/corda/deterministic/common/MockContractAttachment.kt index 1526825683..a4b3b8a21e 100644 --- a/core-deterministic/testing/common/src/main/kotlin/net/corda/deterministic/common/MockContractAttachment.kt +++ b/core-deterministic/testing/common/src/main/kotlin/net/corda/deterministic/common/MockContractAttachment.kt @@ -3,13 +3,13 @@ package net.corda.deterministic.common import net.corda.core.contracts.Attachment import net.corda.core.contracts.ContractClassName import net.corda.core.crypto.SecureHash -import net.corda.core.identity.Party import net.corda.core.serialization.CordaSerializable import java.io.ByteArrayInputStream import java.io.InputStream +import java.security.PublicKey @CordaSerializable -class MockContractAttachment(override val id: SecureHash = SecureHash.zeroHash, val contract: ContractClassName, override val signers: List = ArrayList()) : Attachment { +class MockContractAttachment(override val id: SecureHash = SecureHash.zeroHash, val contract: ContractClassName, override val signers: List = emptyList()) : Attachment { override fun open(): InputStream = ByteArrayInputStream(id.bytes) override val size = id.size } diff --git a/core-deterministic/testing/src/test/kotlin/net/corda/deterministic/contracts/AttachmentTest.kt b/core-deterministic/testing/src/test/kotlin/net/corda/deterministic/contracts/AttachmentTest.kt index 21ad6a3e45..01f7751905 100644 --- a/core-deterministic/testing/src/test/kotlin/net/corda/deterministic/contracts/AttachmentTest.kt +++ b/core-deterministic/testing/src/test/kotlin/net/corda/deterministic/contracts/AttachmentTest.kt @@ -42,8 +42,8 @@ class AttachmentTest { attachment = object : Attachment { override val id: SecureHash get() = SecureHash.allOnesHash - override val signers: List - get() = listOf(ALICE) + override val signers: List + get() = listOf(ALICE_KEY) override val size: Int get() = jarData.size diff --git a/core/src/main/kotlin/net/corda/core/contracts/Attachment.kt b/core/src/main/kotlin/net/corda/core/contracts/Attachment.kt index 8cf0ed5839..d17d053e1f 100644 --- a/core/src/main/kotlin/net/corda/core/contracts/Attachment.kt +++ b/core/src/main/kotlin/net/corda/core/contracts/Attachment.kt @@ -7,6 +7,7 @@ import net.corda.core.serialization.CordaSerializable import java.io.FileNotFoundException import java.io.InputStream import java.io.OutputStream +import java.security.PublicKey import java.util.jar.JarInputStream /** @@ -51,10 +52,10 @@ interface Attachment : NamedByHash { fun extractFile(path: String, outputTo: OutputStream) = openAsJAR().use { it.extractFile(path, outputTo) } /** - * The parties that have correctly signed the whole attachment. + * The keys that have correctly signed the whole attachment. * Can be empty, for example non-contract attachments won't be necessarily be signed. */ - val signers: List + val signers: List /** * Attachment size in bytes. diff --git a/core/src/main/kotlin/net/corda/core/contracts/AttachmentConstraint.kt b/core/src/main/kotlin/net/corda/core/contracts/AttachmentConstraint.kt index b4fbe24ef4..76ffc8a866 100644 --- a/core/src/main/kotlin/net/corda/core/contracts/AttachmentConstraint.kt +++ b/core/src/main/kotlin/net/corda/core/contracts/AttachmentConstraint.kt @@ -82,5 +82,5 @@ data class SignatureAttachmentConstraint( val key: PublicKey ) : AttachmentConstraint { override fun isSatisfiedBy(attachment: Attachment): Boolean = - key.isFulfilledBy(attachment.signers.map { it.owningKey }) + key.isFulfilledBy(attachment.signers.map { it }) } \ No newline at end of file diff --git a/core/src/main/kotlin/net/corda/core/internal/AbstractAttachment.kt b/core/src/main/kotlin/net/corda/core/internal/AbstractAttachment.kt index 3c72e5d0ef..c509abdd4f 100644 --- a/core/src/main/kotlin/net/corda/core/internal/AbstractAttachment.kt +++ b/core/src/main/kotlin/net/corda/core/internal/AbstractAttachment.kt @@ -1,4 +1,5 @@ @file:KeepForDJVM + package net.corda.core.internal import net.corda.core.DeleteForDJVM @@ -11,6 +12,7 @@ import java.io.FileNotFoundException import java.io.IOException import java.io.InputStream import java.io.OutputStream +import java.security.PublicKey import java.util.jar.JarInputStream const val DEPLOYED_CORDAPP_UPLOADER = "app" @@ -40,8 +42,8 @@ abstract class AbstractAttachment(dataLoader: () -> ByteArray) : Attachment { override val size: Int get() = attachmentData.size override fun open(): InputStream = attachmentData.inputStream() - override val signers by lazy { - openAsJAR().use(JarSignatureCollector::collectSigningParties) + override val signers: List by lazy { + openAsJAR().use(JarSignatureCollector::collectSigners) } override fun equals(other: Any?) = other === this || other is Attachment && other.id == this.id diff --git a/core/src/main/kotlin/net/corda/core/internal/JarSignatureCollector.kt b/core/src/main/kotlin/net/corda/core/internal/JarSignatureCollector.kt index 70d8c84873..ff8970461a 100644 --- a/core/src/main/kotlin/net/corda/core/internal/JarSignatureCollector.kt +++ b/core/src/main/kotlin/net/corda/core/internal/JarSignatureCollector.kt @@ -2,6 +2,7 @@ package net.corda.core.internal import net.corda.core.identity.Party import java.security.CodeSigner +import java.security.PublicKey import java.security.cert.X509Certificate import java.util.jar.JarEntry import java.util.jar.JarInputStream @@ -20,7 +21,7 @@ object JarSignatureCollector { * @param jar The open [JarInputStream] to collect signing parties from. * @throws InvalidJarSignersException If the signer sets for any two signable items are different from each other. */ - fun collectSigningParties(jar: JarInputStream): List { + fun collectSigners(jar: JarInputStream): List { val signerSets = jar.fileSignerSets if (signerSets.isEmpty()) return emptyList() @@ -28,14 +29,14 @@ object JarSignatureCollector { for ((otherFile, otherSignerSet) in signerSets.subList(1, signerSets.size)) { if (otherSignerSet != firstSignerSet) throw InvalidJarSignersException( """ - Mismatch between signers ${firstSignerSet.toPartiesOrderedByName()} for file $firstFile - and signers ${otherSignerSet.toPartiesOrderedByName()} for file ${otherFile}. + Mismatch between signers ${firstSignerSet.toOrderedPublicKeys()} for file $firstFile + and signers ${otherSignerSet.toOrderedPublicKeys()} for file ${otherFile}. See https://docs.corda.net/design/data-model-upgrades/signature-constraints.html for details of the constraints applied to attachment signatures. """.trimIndent().replace('\n', ' ')) } - return firstSignerSet.toPartiesOrderedByName() + return firstSignerSet.toOrderedPublicKeys() } private val JarInputStream.fileSignerSets: List>> get() = @@ -56,9 +57,9 @@ object JarSignatureCollector { private fun Sequence.toFileSignerSet(): Sequence>> = map { entry -> entry.name to (entry.codeSigners?.toSet() ?: emptySet()) } - private fun Set.toPartiesOrderedByName(): List = map { - Party(it.signerCertPath.certificates[0] as X509Certificate) - }.sortedBy { it.name.toString() } // Sorted for determinism. + private fun Set.toOrderedPublicKeys(): List = map { + (it.signerCertPath.certificates[0] as X509Certificate).publicKey + }.sortedBy { it.hash} // Sorted for determinism. private val JarInputStream.entries get(): Sequence = generateSequence(nextJarEntry) { nextJarEntry } } 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 bcfd8f3bca..7de546016a 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/TransactionBuilder.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/TransactionBuilder.kt @@ -163,8 +163,8 @@ open class TransactionBuilder @JvmOverloads constructor( } } - private fun makeSignatureAttachmentConstraint(attachmentSigners: List) = - SignatureAttachmentConstraint(CompositeKey.Builder().addKeys(attachmentSigners.map { it.owningKey }).build()) + private fun makeSignatureAttachmentConstraint(attachmentSigners: List) = + SignatureAttachmentConstraint(CompositeKey.Builder().addKeys(attachmentSigners.map { it }).build()) private fun useWhitelistedByZoneAttachmentConstraint(contractClassName: ContractClassName, networkParameters: NetworkParameters) = contractClassName in networkParameters.whitelistedContractImplementations.keys diff --git a/core/src/test/kotlin/net/corda/core/internal/JarSignatureCollectorTest.kt b/core/src/test/kotlin/net/corda/core/internal/JarSignatureCollectorTest.kt index b8620082d8..95f0b187e6 100644 --- a/core/src/test/kotlin/net/corda/core/internal/JarSignatureCollectorTest.kt +++ b/core/src/test/kotlin/net/corda/core/internal/JarSignatureCollectorTest.kt @@ -2,6 +2,7 @@ package net.corda.core.internal import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party +import net.corda.nodeapi.internal.crypto.loadKeyStore import net.corda.testing.core.ALICE_NAME import net.corda.testing.core.BOB_NAME import org.assertj.core.api.Assertions.assertThat @@ -13,6 +14,7 @@ import java.io.FileInputStream import java.nio.file.Files import java.nio.file.Path import java.nio.file.Paths +import java.security.PublicKey import java.util.jar.JarInputStream import kotlin.test.assertEquals import kotlin.test.assertFailsWith @@ -60,7 +62,7 @@ class JarSignatureCollectorTest { } } - private val List.names get() = map { it.name } + private val List.keys get() = map { it.owningKey } @After fun tearDown() { @@ -92,33 +94,33 @@ class JarSignatureCollectorTest { @Test fun `one signer`() { createJar("_signable1", "_signable2") - signAsAlice() - assertEquals(listOf(ALICE_NAME), getJarSigners().names) // We only reused ALICE's distinguished name, so the keys will be different. + val key = signAsAlice() + assertEquals(listOf(key), getJarSigners()) (dir / "my-dir").createDirectory() updateJar("my-dir") - assertEquals(listOf(ALICE_NAME), getJarSigners().names) // Unsigned directory is irrelevant. + assertEquals(listOf(key), getJarSigners()) // Unsigned directory is irrelevant. } @Test fun `two signers`() { createJar("_signable1", "_signable2") - signAsAlice() - signAsBob() + val key1 = signAsAlice() + val key2 = signAsBob() - assertEquals(listOf(ALICE_NAME, BOB_NAME), getJarSigners().names) + assertEquals(setOf(key1, key2), getJarSigners().toSet()) } @Test fun `all files must be signed by the same set of signers`() { createJar("_signable1") - signAsAlice() - assertEquals(listOf(ALICE_NAME), getJarSigners().names) + val key1 = signAsAlice() + assertEquals(listOf(key1), getJarSigners()) updateJar("_signable2") signAsBob() assertFailsWith( - """ + """ Mismatch between signers [O=Alice Corp, L=Madrid, C=ES, O=Bob Plc, L=Rome, C=IT] for file _signable1 and signers [O=Bob Plc, L=Rome, C=IT] for file _signable2. See https://docs.corda.net/design/data-model-upgrades/signature-constraints.html for details of the @@ -131,8 +133,8 @@ class JarSignatureCollectorTest { fun `bad signature is caught even if the party would not qualify as a signer`() { (dir / "volatile").writeLines(listOf("volatile")) createJar("volatile") - signAsAlice() - assertEquals(listOf(ALICE_NAME), getJarSigners().names) + val key1 = signAsAlice() + assertEquals(listOf(key1), getJarSigners()) (dir / "volatile").writeLines(listOf("garbage")) updateJar("volatile", "_signable1") // ALICE's signature on volatile is now bad. @@ -148,14 +150,17 @@ class JarSignatureCollectorTest { private fun updateJar(vararg contents: String) = execute(*(arrayOf("jar", "uvf", FILENAME) + contents)) - private fun signJar(alias: String, password: String) = - execute("jarsigner", "-keystore", "_teststore", "-storepass", "storepass", "-keypass", password, FILENAME, alias) + private fun signJar(alias: String, password: String): PublicKey { + execute("jarsigner", "-keystore", "_teststore", "-storepass", "storepass", "-keypass", password, FILENAME, alias) + val ks = loadKeyStore(dir.resolve("_teststore"), "storepass") + return ks.getCertificate(alias).publicKey + } private fun signAsAlice() = signJar(ALICE, ALICE_PASS) private fun signAsBob() = signJar(BOB, BOB_PASS) private fun getJarSigners() = - JarInputStream(FileInputStream((dir / FILENAME).toFile())).use(JarSignatureCollector::collectSigningParties) + JarInputStream(FileInputStream((dir / FILENAME).toFile())).use(JarSignatureCollector::collectSigners) //endregion } diff --git a/core/src/test/kotlin/net/corda/core/transactions/TransactionBuilderTest.kt b/core/src/test/kotlin/net/corda/core/transactions/TransactionBuilderTest.kt index f779c85678..aa1d6240d0 100644 --- a/core/src/test/kotlin/net/corda/core/transactions/TransactionBuilderTest.kt +++ b/core/src/test/kotlin/net/corda/core/transactions/TransactionBuilderTest.kt @@ -23,6 +23,7 @@ import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Rule import org.junit.Test +import java.security.PublicKey class TransactionBuilderTest { @Rule @@ -119,12 +120,12 @@ class TransactionBuilderTest { private val unsignedAttachment = object : AbstractAttachment({ byteArrayOf() }) { override val id: SecureHash get() = throw UnsupportedOperationException() - override val signers: List get() = emptyList() + override val signers: List get() = emptyList() } private fun signedAttachment(vararg parties: Party) = object : AbstractAttachment({ byteArrayOf() }) { override val id: SecureHash get() = throw UnsupportedOperationException() - override val signers: List get() = parties.toList() + override val signers: List get() = parties.map { it.owningKey } } }