From 6ebc6e9b16575a7afbb781b0d93a8f04b3affba5 Mon Sep 17 00:00:00 2001 From: Chris Rankin Date: Tue, 26 May 2020 15:46:29 +0100 Subject: [PATCH] CORDA-3750: Reimplement Corda's Crypto object for use inside the sandbox. (#6193) * CORDA-3750: Use hand-written sandbox Crypto object that delegates to the node. * CORDA-3750: Add integration test for deterministic CashIssueAndPayment flow. * Tidy up generics for Array instances. * Upgrade to DJVM 1.1-RC04. --- constants.properties | 2 +- node/build.gradle | 5 + .../kotlin/net/corda/node/djvm/LtxFactory.kt | 8 +- .../DeterministicCashIssueAndPaymentTest.kt | 68 +++++ .../java/sandbox/java/lang/CharSequence.java | 8 + .../java/sandbox/java/lang/Comparable.java | 8 + .../main/java/sandbox/java/lang/Number.java | 8 + .../java/sandbox/java/math/BigInteger.java | 8 + .../main/java/sandbox/java/security/Key.java | 13 + .../java/sandbox/java/security/KeyPair.java | 8 + .../sandbox/java/security/PrivateKey.java | 8 + .../java/sandbox/java/security/PublicKey.java | 8 + .../security/spec/AlgorithmParameterSpec.java | 8 + .../java/sandbox/java/util/ArrayList.java | 16 ++ .../src/main/java/sandbox/java/util/List.java | 9 + .../sandbox/net/corda/core/crypto/DJVM.java | 62 +++++ .../net/corda/core/crypto/DJVMPublicKey.java | 61 ++++ .../org/bouncycastle/asn1/ASN1Encodable.java | 9 + .../org/bouncycastle/asn1/ASN1Object.java | 16 ++ .../asn1/x509/AlgorithmIdentifier.java | 14 + .../asn1/x509/SubjectPublicKeyInfo.java | 10 + .../corda/node/internal/djvm/Serializer.kt | 2 +- .../DeterministicVerifierFactoryService.kt | 15 +- .../sandbox/net/corda/core/crypto/Crypto.kt | 261 ++++++++++++++++++ .../net/corda/core/crypto/SecureHash.kt | 8 + .../net/corda/core/crypto/SignatureScheme.kt | 26 ++ .../corda/core/crypto/TransactionSignature.kt | 7 + .../corda/core/crypto/internal/ProviderMap.kt | 8 + .../serializers/SandboxPublicKeySerializer.kt | 3 +- .../djvm/DeserializePublicKeyTest.kt | 20 +- 30 files changed, 694 insertions(+), 13 deletions(-) create mode 100644 node/src/integration-test/kotlin/net/corda/node/services/DeterministicCashIssueAndPaymentTest.kt create mode 100644 node/src/main/java/sandbox/java/lang/CharSequence.java create mode 100644 node/src/main/java/sandbox/java/lang/Comparable.java create mode 100644 node/src/main/java/sandbox/java/lang/Number.java create mode 100644 node/src/main/java/sandbox/java/math/BigInteger.java create mode 100644 node/src/main/java/sandbox/java/security/Key.java create mode 100644 node/src/main/java/sandbox/java/security/KeyPair.java create mode 100644 node/src/main/java/sandbox/java/security/PrivateKey.java create mode 100644 node/src/main/java/sandbox/java/security/PublicKey.java create mode 100644 node/src/main/java/sandbox/java/security/spec/AlgorithmParameterSpec.java create mode 100644 node/src/main/java/sandbox/java/util/ArrayList.java create mode 100644 node/src/main/java/sandbox/java/util/List.java create mode 100644 node/src/main/java/sandbox/net/corda/core/crypto/DJVM.java create mode 100644 node/src/main/java/sandbox/net/corda/core/crypto/DJVMPublicKey.java create mode 100644 node/src/main/java/sandbox/org/bouncycastle/asn1/ASN1Encodable.java create mode 100644 node/src/main/java/sandbox/org/bouncycastle/asn1/ASN1Object.java create mode 100644 node/src/main/java/sandbox/org/bouncycastle/asn1/x509/AlgorithmIdentifier.java create mode 100644 node/src/main/java/sandbox/org/bouncycastle/asn1/x509/SubjectPublicKeyInfo.java create mode 100644 node/src/main/kotlin/sandbox/net/corda/core/crypto/Crypto.kt create mode 100644 node/src/main/kotlin/sandbox/net/corda/core/crypto/SecureHash.kt create mode 100644 node/src/main/kotlin/sandbox/net/corda/core/crypto/SignatureScheme.kt create mode 100644 node/src/main/kotlin/sandbox/net/corda/core/crypto/TransactionSignature.kt create mode 100644 node/src/main/kotlin/sandbox/net/corda/core/crypto/internal/ProviderMap.kt diff --git a/constants.properties b/constants.properties index 6865651962..131f2ae5f8 100644 --- a/constants.properties +++ b/constants.properties @@ -30,7 +30,7 @@ snakeYamlVersion=1.19 caffeineVersion=2.7.0 metricsVersion=4.1.0 metricsNewRelicVersion=1.1.1 -djvmVersion=1.1-RC03 +djvmVersion=1.1-RC04 deterministicRtVersion=1.0-RC02 openSourceBranch=https://github.com/corda/corda/blob/release/os/4.4 openSourceSamplesBranch=https://github.com/corda/samples/blob/release-V4 diff --git a/node/build.gradle b/node/build.gradle index ae98903907..58f7a5498e 100644 --- a/node/build.gradle +++ b/node/build.gradle @@ -304,6 +304,11 @@ quasar { jar { baseName 'corda-node' + exclude 'sandbox/java/**' + exclude 'sandbox/org/**' + exclude 'sandbox/net/corda/core/crypto/SecureHash.class' + exclude 'sandbox/net/corda/core/crypto/SignatureScheme.class' + exclude 'sandbox/net/corda/core/crypto/TransactionSignature.class' manifest { attributes('Corda-Deterministic-Runtime': configurations.jdkRt.singleFile.name) attributes('Corda-Deterministic-Classpath': configurations.deterministic.collect { it.name }.join(' ')) diff --git a/node/djvm/src/main/kotlin/net/corda/node/djvm/LtxFactory.kt b/node/djvm/src/main/kotlin/net/corda/node/djvm/LtxFactory.kt index dc3affd9e2..31c1b2aaa8 100644 --- a/node/djvm/src/main/kotlin/net/corda/node/djvm/LtxFactory.kt +++ b/node/djvm/src/main/kotlin/net/corda/node/djvm/LtxFactory.kt @@ -27,12 +27,12 @@ private const val TX_PRIVACY_SALT = 7 private const val TX_NETWORK_PARAMETERS = 8 private const val TX_REFERENCES = 9 -class LtxFactory : Function, LedgerTransaction> { +class LtxFactory : Function, LedgerTransaction> { @Suppress("unchecked_cast") - override fun apply(txArgs: Array): LedgerTransaction { + override fun apply(txArgs: Array): LedgerTransaction { return LedgerTransaction.createForSandbox( - inputs = (txArgs[TX_INPUTS] as Array>).map { it.toStateAndRef() }, + inputs = (txArgs[TX_INPUTS] as Array>).map { it.toStateAndRef() }, outputs = (txArgs[TX_OUTPUTS] as? List>) ?: emptyList(), commands = (txArgs[TX_COMMANDS] as? List>) ?: emptyList(), attachments = (txArgs[TX_ATTACHMENTS] as? List) ?: emptyList(), @@ -41,7 +41,7 @@ class LtxFactory : Function, LedgerTransaction> { timeWindow = txArgs[TX_TIME_WINDOW] as? TimeWindow, privacySalt = txArgs[TX_PRIVACY_SALT] as PrivacySalt, networkParameters = txArgs[TX_NETWORK_PARAMETERS] as NetworkParameters, - references = (txArgs[TX_REFERENCES] as Array>).map { it.toStateAndRef() } + references = (txArgs[TX_REFERENCES] as Array>).map { it.toStateAndRef() } ) } diff --git a/node/src/integration-test/kotlin/net/corda/node/services/DeterministicCashIssueAndPaymentTest.kt b/node/src/integration-test/kotlin/net/corda/node/services/DeterministicCashIssueAndPaymentTest.kt new file mode 100644 index 0000000000..0de1960375 --- /dev/null +++ b/node/src/integration-test/kotlin/net/corda/node/services/DeterministicCashIssueAndPaymentTest.kt @@ -0,0 +1,68 @@ +package net.corda.node.services + +import net.corda.core.messaging.startFlow +import net.corda.core.utilities.OpaqueBytes +import net.corda.core.utilities.getOrThrow +import net.corda.core.utilities.loggerFor +import net.corda.finance.DOLLARS +import net.corda.finance.flows.CashIssueAndPaymentFlow +import net.corda.node.DeterministicSourcesRule +import net.corda.testing.core.ALICE_NAME +import net.corda.testing.core.DUMMY_NOTARY_NAME +import net.corda.testing.core.singleIdentity +import net.corda.testing.driver.DriverParameters +import net.corda.testing.driver.driver +import net.corda.testing.driver.internal.incrementalPortAllocation +import net.corda.testing.node.NotarySpec +import net.corda.testing.node.internal.findCordapp +import org.junit.ClassRule +import org.junit.Test +import org.junit.jupiter.api.assertDoesNotThrow + +@Suppress("FunctionName") +class DeterministicCashIssueAndPaymentTest { + companion object { + val logger = loggerFor() + + @ClassRule + @JvmField + val djvmSources = DeterministicSourcesRule() + + @JvmField + val CASH_AMOUNT = 500.DOLLARS + + fun parametersFor(djvmSources: DeterministicSourcesRule): DriverParameters { + return DriverParameters( + portAllocation = incrementalPortAllocation(), + startNodesInProcess = false, + notarySpecs = listOf(NotarySpec(DUMMY_NOTARY_NAME, validating = true)), + cordappsForAllNodes = listOf( + findCordapp("net.corda.finance.contracts"), + findCordapp("net.corda.finance.workflows") + ), + djvmBootstrapSource = djvmSources.bootstrap, + djvmCordaSource = djvmSources.corda + ) + } + } + + @Test(timeout = 300_000) + fun `test DJVM can issue cash`() { + val reference = OpaqueBytes.of(0x01) + driver(parametersFor(djvmSources)) { + val alice = startNode(providedName = ALICE_NAME).getOrThrow() + val aliceParty = alice.nodeInfo.singleIdentity() + val notaryParty = notaryHandles.single().identity + val txId = assertDoesNotThrow { + alice.rpc.startFlow(::CashIssueAndPaymentFlow, + CASH_AMOUNT, + reference, + aliceParty, + false, + notaryParty + ).returnValue.getOrThrow() + } + logger.info("TX-ID: {}", txId) + } + } +} diff --git a/node/src/main/java/sandbox/java/lang/CharSequence.java b/node/src/main/java/sandbox/java/lang/CharSequence.java new file mode 100644 index 0000000000..9b62762a11 --- /dev/null +++ b/node/src/main/java/sandbox/java/lang/CharSequence.java @@ -0,0 +1,8 @@ +package sandbox.java.lang; + +/** + * This is a dummy class that implements just enough of {@link java.lang.CharSequence} + * to allow us to compile {@link sandbox.net.corda.core.crypto.Crypto}. + */ +public interface CharSequence extends java.lang.CharSequence { +} diff --git a/node/src/main/java/sandbox/java/lang/Comparable.java b/node/src/main/java/sandbox/java/lang/Comparable.java new file mode 100644 index 0000000000..5ca4f4871c --- /dev/null +++ b/node/src/main/java/sandbox/java/lang/Comparable.java @@ -0,0 +1,8 @@ +package sandbox.java.lang; + +/** + * This is a dummy class that implements just enough of {@link java.lang.Comparable} + * to allow us to compile {@link sandbox.net.corda.core.crypto.Crypto}. + */ +public interface Comparable extends java.lang.Comparable { +} diff --git a/node/src/main/java/sandbox/java/lang/Number.java b/node/src/main/java/sandbox/java/lang/Number.java new file mode 100644 index 0000000000..a98d60dbd6 --- /dev/null +++ b/node/src/main/java/sandbox/java/lang/Number.java @@ -0,0 +1,8 @@ +package sandbox.java.lang; + +/** + * This is a dummy class that implements just enough of {@link java.lang.Number} + * to allow us to compile {@link sandbox.net.corda.core.crypto.Crypto}. + */ +public class Number extends Object { +} diff --git a/node/src/main/java/sandbox/java/math/BigInteger.java b/node/src/main/java/sandbox/java/math/BigInteger.java new file mode 100644 index 0000000000..d58328fd3c --- /dev/null +++ b/node/src/main/java/sandbox/java/math/BigInteger.java @@ -0,0 +1,8 @@ +package sandbox.java.math; + +/** + * This is a dummy class that implements just enough of {@link java.math.BigInteger} + * to allow us to compile {@link sandbox.net.corda.core.crypto.Crypto}. + */ +public class BigInteger extends sandbox.java.lang.Number { +} diff --git a/node/src/main/java/sandbox/java/security/Key.java b/node/src/main/java/sandbox/java/security/Key.java new file mode 100644 index 0000000000..9c5c952852 --- /dev/null +++ b/node/src/main/java/sandbox/java/security/Key.java @@ -0,0 +1,13 @@ +package sandbox.java.security; + +import sandbox.java.lang.String; + +/** + * This is a dummy class that implements just enough of {@link java.security.Key} + * to allow us to compile {@link sandbox.net.corda.core.crypto.Crypto}. + */ +public interface Key { + String getAlgorithm(); + String getFormat(); + byte[] getEncoded(); +} diff --git a/node/src/main/java/sandbox/java/security/KeyPair.java b/node/src/main/java/sandbox/java/security/KeyPair.java new file mode 100644 index 0000000000..653e27de8e --- /dev/null +++ b/node/src/main/java/sandbox/java/security/KeyPair.java @@ -0,0 +1,8 @@ +package sandbox.java.security; + +/** + * This is a dummy class that implements just enough of {@link java.security.KeyPair} + * to allow us to compile {@link sandbox.net.corda.core.crypto.Crypto}. + */ +public final class KeyPair extends sandbox.java.lang.Object implements java.io.Serializable { +} \ No newline at end of file diff --git a/node/src/main/java/sandbox/java/security/PrivateKey.java b/node/src/main/java/sandbox/java/security/PrivateKey.java new file mode 100644 index 0000000000..a314aa6234 --- /dev/null +++ b/node/src/main/java/sandbox/java/security/PrivateKey.java @@ -0,0 +1,8 @@ +package sandbox.java.security; + +/** + * This is a dummy class that implements just enough of {@link java.security.PrivateKey} + * to allow us to compile {@link sandbox.net.corda.core.crypto.Crypto}. + */ +public interface PrivateKey extends Key { +} diff --git a/node/src/main/java/sandbox/java/security/PublicKey.java b/node/src/main/java/sandbox/java/security/PublicKey.java new file mode 100644 index 0000000000..451f33141a --- /dev/null +++ b/node/src/main/java/sandbox/java/security/PublicKey.java @@ -0,0 +1,8 @@ +package sandbox.java.security; + +/** + * This is a dummy class that implements just enough of {@link java.security.PublicKey} + * to allow us to compile {@link sandbox.net.corda.core.crypto.Crypto}. + */ +public interface PublicKey extends Key { +} diff --git a/node/src/main/java/sandbox/java/security/spec/AlgorithmParameterSpec.java b/node/src/main/java/sandbox/java/security/spec/AlgorithmParameterSpec.java new file mode 100644 index 0000000000..7943cf4612 --- /dev/null +++ b/node/src/main/java/sandbox/java/security/spec/AlgorithmParameterSpec.java @@ -0,0 +1,8 @@ +package sandbox.java.security.spec; + +/** + * This is a dummy class that implements just enough of {@link java.security.spec.AlgorithmParameterSpec} + * to allow us to compile {@link sandbox.net.corda.core.crypto.Crypto}. + */ +public interface AlgorithmParameterSpec { +} diff --git a/node/src/main/java/sandbox/java/util/ArrayList.java b/node/src/main/java/sandbox/java/util/ArrayList.java new file mode 100644 index 0000000000..7eb5df9f09 --- /dev/null +++ b/node/src/main/java/sandbox/java/util/ArrayList.java @@ -0,0 +1,16 @@ +package sandbox.java.util; + +/** + * This is a dummy class that implements just enough of {@link java.util.ArrayList} + * to allow us to compile {@link sandbox.net.corda.core.crypto.Crypto}. + */ +@SuppressWarnings("unused") +public class ArrayList extends sandbox.java.lang.Object implements List { + public ArrayList(int size) { + } + + @Override + public boolean add(T item) { + throw new UnsupportedOperationException("Dummy class - not implemented"); + } +} diff --git a/node/src/main/java/sandbox/java/util/List.java b/node/src/main/java/sandbox/java/util/List.java new file mode 100644 index 0000000000..0f7dbfe22c --- /dev/null +++ b/node/src/main/java/sandbox/java/util/List.java @@ -0,0 +1,9 @@ +package sandbox.java.util; + +/** + * This is a dummy class that implements just enough of {@link java.util.List} + * to allow us to compile {@link sandbox.net.corda.core.crypto.Crypto}. + */ +public interface List { + boolean add(T item); +} diff --git a/node/src/main/java/sandbox/net/corda/core/crypto/DJVM.java b/node/src/main/java/sandbox/net/corda/core/crypto/DJVM.java new file mode 100644 index 0000000000..8004d44666 --- /dev/null +++ b/node/src/main/java/sandbox/net/corda/core/crypto/DJVM.java @@ -0,0 +1,62 @@ +package sandbox.net.corda.core.crypto; + +import org.jetbrains.annotations.NotNull; +import sandbox.java.lang.Integer; +import sandbox.java.lang.String; +import sandbox.java.util.ArrayList; +import sandbox.java.util.List; +import sandbox.org.bouncycastle.asn1.x509.AlgorithmIdentifier; + +import java.io.IOException; + +/** + * Helper class for {@link sandbox.net.corda.core.crypto.Crypto}. + * Deliberately package-private. + */ +final class DJVM { + private DJVM() {} + + @NotNull + static SignatureScheme toDJVM(@NotNull net.corda.core.crypto.SignatureScheme scheme) { + // The AlgorithmParameterSpec is deliberately left as null + // because it is computationally expensive to generate these + // objects inside the sandbox every time. + return new SignatureScheme( + scheme.getSchemeNumberID(), + String.toDJVM(scheme.getSchemeCodeName()), + toDJVM(scheme.getSignatureOID()), + toDJVM(scheme.getAlternativeOIDs()), + String.toDJVM(scheme.getProviderName()), + String.toDJVM(scheme.getAlgorithmName()), + String.toDJVM(scheme.getSignatureName()), + null, + Integer.toDJVM(scheme.getKeySize()), + String.toDJVM(scheme.getDesc()) + ); + } + + static org.bouncycastle.asn1.x509.AlgorithmIdentifier fromDJVM(@NotNull AlgorithmIdentifier oid) { + try { + return org.bouncycastle.asn1.x509.AlgorithmIdentifier.getInstance(oid.getEncoded()); + } catch (IOException e) { + throw sandbox.java.lang.DJVM.toRuleViolationError(e); + } + } + + static AlgorithmIdentifier toDJVM(@NotNull org.bouncycastle.asn1.x509.AlgorithmIdentifier oid) { + try { + return AlgorithmIdentifier.getInstance(oid.getEncoded()); + } catch (IOException e) { + throw sandbox.java.lang.DJVM.toRuleViolationError(e); + } + } + + @NotNull + static List toDJVM(@NotNull java.util.List list) { + ArrayList djvmList = new ArrayList<>(list.size()); + for (org.bouncycastle.asn1.x509.AlgorithmIdentifier oid : list) { + djvmList.add(toDJVM(oid)); + } + return djvmList; + } +} diff --git a/node/src/main/java/sandbox/net/corda/core/crypto/DJVMPublicKey.java b/node/src/main/java/sandbox/net/corda/core/crypto/DJVMPublicKey.java new file mode 100644 index 0000000000..199acc39ce --- /dev/null +++ b/node/src/main/java/sandbox/net/corda/core/crypto/DJVMPublicKey.java @@ -0,0 +1,61 @@ +package sandbox.net.corda.core.crypto; + +import org.jetbrains.annotations.NotNull; +import sandbox.java.lang.Object; +import sandbox.java.lang.String; +import sandbox.java.security.PublicKey; + +/** + * We shall delegate as much cryptography as possible to Corda's + * underlying {@link net.corda.core.crypto.Crypto} object and its + * {@link java.security.Provider} classes. This wrapper only needs + * to implement {@link #equals} and {@link #hashCode}. + */ +final class DJVMPublicKey extends Object implements PublicKey { + private final java.security.PublicKey underlying; + private final String algorithm; + private final String format; + private final int hashCode; + + DJVMPublicKey(@NotNull java.security.PublicKey underlying) { + this.underlying = underlying; + this.algorithm = String.toDJVM(underlying.getAlgorithm()); + this.format = String.toDJVM(underlying.getFormat()); + this.hashCode = underlying.hashCode(); + } + + java.security.PublicKey getUnderlying() { + return underlying; + } + + @Override + public boolean equals(java.lang.Object other) { + if (this == other) { + return true; + } else if (!(other instanceof DJVMPublicKey)) { + return false; + } else { + return underlying.equals(((DJVMPublicKey) other).underlying); + } + } + + @Override + public int hashCode() { + return hashCode; + } + + @Override + public String getAlgorithm() { + return algorithm; + } + + @Override + public String getFormat() { + return format; + } + + @Override + public byte[] getEncoded() { + return underlying.getEncoded(); + } +} diff --git a/node/src/main/java/sandbox/org/bouncycastle/asn1/ASN1Encodable.java b/node/src/main/java/sandbox/org/bouncycastle/asn1/ASN1Encodable.java new file mode 100644 index 0000000000..e75ac2e6b4 --- /dev/null +++ b/node/src/main/java/sandbox/org/bouncycastle/asn1/ASN1Encodable.java @@ -0,0 +1,9 @@ +package sandbox.org.bouncycastle.asn1; + +/** + * This is a dummy class that implements just enough of {@link org.bouncycastle.asn1.ASN1Encodable} + * to allow us to compile {@link sandbox.net.corda.core.crypto.Crypto}. + */ +@SuppressWarnings("WeakerAccess") +public interface ASN1Encodable { +} diff --git a/node/src/main/java/sandbox/org/bouncycastle/asn1/ASN1Object.java b/node/src/main/java/sandbox/org/bouncycastle/asn1/ASN1Object.java new file mode 100644 index 0000000000..d71661cc71 --- /dev/null +++ b/node/src/main/java/sandbox/org/bouncycastle/asn1/ASN1Object.java @@ -0,0 +1,16 @@ +package sandbox.org.bouncycastle.asn1; + +import sandbox.java.lang.Object; + +import java.io.IOException; + +/** + * This is a dummy class that implements just enough of {@link org.bouncycastle.asn1.ASN1Object} + * to allow us to compile {@link sandbox.net.corda.core.crypto.Crypto}. + */ +@SuppressWarnings("RedundantThrows") +public class ASN1Object extends Object implements ASN1Encodable { + public byte[] getEncoded() throws IOException { + throw new UnsupportedOperationException("Dummy class - not implemented"); + } +} diff --git a/node/src/main/java/sandbox/org/bouncycastle/asn1/x509/AlgorithmIdentifier.java b/node/src/main/java/sandbox/org/bouncycastle/asn1/x509/AlgorithmIdentifier.java new file mode 100644 index 0000000000..144b5f9260 --- /dev/null +++ b/node/src/main/java/sandbox/org/bouncycastle/asn1/x509/AlgorithmIdentifier.java @@ -0,0 +1,14 @@ +package sandbox.org.bouncycastle.asn1.x509; + +import sandbox.org.bouncycastle.asn1.ASN1Object; + +/** + * This is a dummy class that implements just enough of {@link org.bouncycastle.asn1.x509.AlgorithmIdentifier} + * to allow us to compile {@link sandbox.net.corda.core.crypto.Crypto}. + */ +@SuppressWarnings("unused") +public class AlgorithmIdentifier extends ASN1Object { + public static AlgorithmIdentifier getInstance(Object obj) { + throw new UnsupportedOperationException("Dummy class - not implemented"); + } +} diff --git a/node/src/main/java/sandbox/org/bouncycastle/asn1/x509/SubjectPublicKeyInfo.java b/node/src/main/java/sandbox/org/bouncycastle/asn1/x509/SubjectPublicKeyInfo.java new file mode 100644 index 0000000000..a84fb60772 --- /dev/null +++ b/node/src/main/java/sandbox/org/bouncycastle/asn1/x509/SubjectPublicKeyInfo.java @@ -0,0 +1,10 @@ +package sandbox.org.bouncycastle.asn1.x509; + +import sandbox.org.bouncycastle.asn1.ASN1Object; + +/** + * This is a dummy class that implements just enough of {@link org.bouncycastle.asn1.x509.SubjectPublicKeyInfo} + * to allow us to compile {@link sandbox.net.corda.core.crypto.Crypto}. + */ +public class SubjectPublicKeyInfo extends ASN1Object { +} diff --git a/node/src/main/kotlin/net/corda/node/internal/djvm/Serializer.kt b/node/src/main/kotlin/net/corda/node/internal/djvm/Serializer.kt index 1262ae8710..40a5522a28 100644 --- a/node/src/main/kotlin/net/corda/node/internal/djvm/Serializer.kt +++ b/node/src/main/kotlin/net/corda/node/internal/djvm/Serializer.kt @@ -31,7 +31,7 @@ class Serializer( * [net.corda.node.djvm.LtxFactory] to be transformed finally to * a list of [net.corda.core.contracts.StateAndRef] objects, */ - fun deserialize(stateRefs: List): Array> { + fun deserialize(stateRefs: List): Array> { return stateRefs.map { arrayOf(deserialize(it.serializedState), deserialize(it.ref.serialize())) }.toTypedArray() diff --git a/node/src/main/kotlin/net/corda/node/services/transactions/DeterministicVerifierFactoryService.kt b/node/src/main/kotlin/net/corda/node/services/transactions/DeterministicVerifierFactoryService.kt index 3485e3af71..d514335c92 100644 --- a/node/src/main/kotlin/net/corda/node/services/transactions/DeterministicVerifierFactoryService.kt +++ b/node/src/main/kotlin/net/corda/node/services/transactions/DeterministicVerifierFactoryService.kt @@ -47,7 +47,20 @@ class DeterministicVerifierFactoryService( ConstructorForDeserialization::class.java, DeprecatedConstructorForDeserialization::class.java ), - bootstrapSource = bootstrapSource + bootstrapSource = bootstrapSource, + overrideClasses = setOf( + /** + * These classes are all duplicated into the sandbox + * without the DJVM modifying their byte-code first. + * The goal is to delegate cryptographic operations + * out to the Node rather than perform them inside + * the sandbox, because this is MUCH FASTER. + */ + sandbox.net.corda.core.crypto.Crypto::class.java.name, + "sandbox.net.corda.core.crypto.DJVM", + "sandbox.net.corda.core.crypto.DJVMPublicKey", + "sandbox.net.corda.core.crypto.internal.ProviderMapKt" + ) ) baseSandboxConfiguration = SandboxConfiguration.createFor( diff --git a/node/src/main/kotlin/sandbox/net/corda/core/crypto/Crypto.kt b/node/src/main/kotlin/sandbox/net/corda/core/crypto/Crypto.kt new file mode 100644 index 0000000000..b51a586c93 --- /dev/null +++ b/node/src/main/kotlin/sandbox/net/corda/core/crypto/Crypto.kt @@ -0,0 +1,261 @@ +package sandbox.net.corda.core.crypto + +import sandbox.net.corda.core.crypto.DJVM.fromDJVM +import sandbox.net.corda.core.crypto.DJVM.toDJVM +import sandbox.java.lang.String +import sandbox.java.lang.doCatch +import sandbox.java.math.BigInteger +import sandbox.java.security.KeyPair +import sandbox.java.security.PrivateKey +import sandbox.java.security.PublicKey +import sandbox.java.util.ArrayList +import sandbox.java.util.List +import sandbox.java.lang.Object +import sandbox.org.bouncycastle.asn1.x509.AlgorithmIdentifier +import sandbox.org.bouncycastle.asn1.x509.SubjectPublicKeyInfo +import java.security.GeneralSecurityException +import java.security.SignatureException +import java.security.spec.InvalidKeySpecException + +/** + * This is a hand-written "drop-in" replacement for the version of + * [net.corda.core.crypto.Crypto] found inside core-deterministic. + * This class is used in the DJVM sandbox instead of transforming + * the byte-code for [net.corda.core.crypto.Crypto], with the goal + * of not needing to instantiate some EXPENSIVE elliptic curve + * cryptography classes inside every single sandbox. + * + * The downside is that this class MUST manually be kept consistent + * with the DJVM's byte-code rewriting rules. + */ +@Suppress("unused", "unused_parameter", "TooManyFunctions") +object Crypto : Object() { + @JvmField + val RSA_SHA256: SignatureScheme = toDJVM(net.corda.core.crypto.Crypto.RSA_SHA256) + + @JvmField + val ECDSA_SECP256K1_SHA256: SignatureScheme = toDJVM(net.corda.core.crypto.Crypto.ECDSA_SECP256K1_SHA256) + + @JvmField + val ECDSA_SECP256R1_SHA256: SignatureScheme = toDJVM(net.corda.core.crypto.Crypto.ECDSA_SECP256R1_SHA256) + + @JvmField + val EDDSA_ED25519_SHA512: SignatureScheme = toDJVM(net.corda.core.crypto.Crypto.EDDSA_ED25519_SHA512) + + @JvmField + val SPHINCS256_SHA256: SignatureScheme = toDJVM(net.corda.core.crypto.Crypto.SPHINCS256_SHA256) + + @JvmField + val COMPOSITE_KEY: SignatureScheme = toDJVM(net.corda.core.crypto.Crypto.COMPOSITE_KEY) + + @JvmField + val DEFAULT_SIGNATURE_SCHEME = EDDSA_ED25519_SHA512 + + /** + * We can use the unsandboxed versions of [Map] and [List] here + * because the [underlyingSchemes] and [djvmSchemes] fields are + * private and not exposed to the rest of the sandbox. + */ + private val underlyingSchemes: Map + = net.corda.core.crypto.Crypto.supportedSignatureSchemes() + .associateBy(net.corda.core.crypto.SignatureScheme::schemeCodeName) + private val djvmSchemes: Map = listOf( + RSA_SHA256, + ECDSA_SECP256K1_SHA256, + ECDSA_SECP256R1_SHA256, + EDDSA_ED25519_SHA512, + SPHINCS256_SHA256, + COMPOSITE_KEY + ).associateByTo(LinkedHashMap(), SignatureScheme::schemeCodeName) + + private fun findUnderlyingSignatureScheme(signatureScheme: SignatureScheme): net.corda.core.crypto.SignatureScheme { + return net.corda.core.crypto.Crypto.findSignatureScheme(String.fromDJVM(signatureScheme.schemeCodeName)) + } + + private fun PublicKey.toUnderlyingKey(): java.security.PublicKey { + return (this as? DJVMPublicKey ?: throw sandbox.java.lang.fail("Unsupported key ${this::class.java.name}")).underlying + } + + @JvmStatic + fun supportedSignatureSchemes(): List { + val schemes = ArrayList(djvmSchemes.size) + for (scheme in djvmSchemes.values) { + schemes.add(scheme) + } + return schemes + } + + @JvmStatic + fun isSupportedSignatureScheme(signatureScheme: SignatureScheme): Boolean { + return String.fromDJVM(signatureScheme.schemeCodeName) in underlyingSchemes + } + + @JvmStatic + fun findSignatureScheme(schemeCodeName: String): SignatureScheme { + return djvmSchemes[schemeCodeName] + ?: throw IllegalArgumentException("Unsupported key/algorithm for schemeCodeName: $schemeCodeName") + } + + @JvmStatic + fun findSignatureScheme(schemeNumberID: Int): SignatureScheme { + val underlyingScheme = net.corda.core.crypto.Crypto.findSignatureScheme(schemeNumberID) + return findSignatureScheme(String.toDJVM(underlyingScheme.schemeCodeName)) + } + + @JvmStatic + fun findSignatureScheme(key: PublicKey): SignatureScheme { + val underlyingScheme = net.corda.core.crypto.Crypto.findSignatureScheme(key.toUnderlyingKey()) + return findSignatureScheme(String.toDJVM(underlyingScheme.schemeCodeName)) + } + + @JvmStatic + fun findSignatureScheme(algorithm: AlgorithmIdentifier): SignatureScheme { + val underlyingScheme = net.corda.core.crypto.Crypto.findSignatureScheme(fromDJVM(algorithm)) + return findSignatureScheme(String.toDJVM(underlyingScheme.schemeCodeName)) + } + + @JvmStatic + fun findSignatureScheme(key: PrivateKey): SignatureScheme { + throw sandbox.java.lang.failApi("Crypto.findSignatureScheme(PrivateKey)") + } + + @JvmStatic + fun decodePrivateKey(signatureScheme: SignatureScheme, encodedKey: ByteArray): PrivateKey { + throw sandbox.java.lang.failApi("Crypto.decodePrivateKey(SignatureScheme, byte[])") + } + + @JvmStatic + fun decodePublicKey(encodedKey: ByteArray): PublicKey { + val underlying = try { + net.corda.core.crypto.Crypto.decodePublicKey(encodedKey) + } catch (e: InvalidKeySpecException) { + throw sandbox.java.lang.fromDJVM(doCatch(e)) + } + return DJVMPublicKey(underlying) + } + + @JvmStatic + fun decodePublicKey(schemeCodeName: String, encodedKey: ByteArray): PublicKey { + val underlying = try { + net.corda.core.crypto.Crypto.decodePublicKey(String.fromDJVM(schemeCodeName), encodedKey) + } catch (e: InvalidKeySpecException) { + throw sandbox.java.lang.fromDJVM(doCatch(e)) + } + return DJVMPublicKey(underlying) + } + + @JvmStatic + fun decodePublicKey(signatureScheme: SignatureScheme, encodedKey: ByteArray): PublicKey { + return decodePublicKey(signatureScheme.schemeCodeName, encodedKey) + } + + @JvmStatic + fun deriveKeyPair(signatureScheme: SignatureScheme, privateKey: PrivateKey, seed: ByteArray): KeyPair { + throw sandbox.java.lang.failApi("Crypto.deriveKeyPair(SignatureScheme, PrivateKey, byte[])") + } + + @JvmStatic + fun deriveKeyPair(privateKey: PrivateKey, seed: ByteArray): KeyPair { + throw sandbox.java.lang.failApi("Crypto.deriveKeyPair(PrivateKey, byte[])") + } + + @JvmStatic + fun deriveKeyPairFromEntropy(signatureScheme: SignatureScheme, entropy: BigInteger): KeyPair { + throw sandbox.java.lang.failApi("Crypto.deriveKeyPairFromEntropy(SignatureScheme, BigInteger)") + } + + @JvmStatic + fun deriveKeyPairFromEntropy(entropy: BigInteger): KeyPair { + throw sandbox.java.lang.failApi("Crypto.deriveKeyPairFromEntropy(BigInteger)") + } + + @JvmStatic + fun doVerify(schemeCodeName: String, publicKey: PublicKey, signatureData: ByteArray, clearData: ByteArray): Boolean { + val underlyingKey = publicKey.toUnderlyingKey() + return try { + net.corda.core.crypto.Crypto.doVerify(String.fromDJVM(schemeCodeName), underlyingKey, signatureData, clearData) + } catch (e: GeneralSecurityException) { + throw sandbox.java.lang.fromDJVM(doCatch(e)) + } + } + + @JvmStatic + fun doVerify(publicKey: PublicKey, signatureData: ByteArray, clearData: ByteArray): Boolean { + val underlyingKey = publicKey.toUnderlyingKey() + return try { + net.corda.core.crypto.Crypto.doVerify(underlyingKey, signatureData, clearData) + } catch (e: GeneralSecurityException) { + throw sandbox.java.lang.fromDJVM(doCatch(e)) + } + } + + @JvmStatic + fun doVerify(signatureScheme: SignatureScheme, publicKey: PublicKey, signatureData: ByteArray, clearData: ByteArray): Boolean { + val underlyingScheme = findUnderlyingSignatureScheme(signatureScheme) + val underlyingKey = publicKey.toUnderlyingKey() + return try { + net.corda.core.crypto.Crypto.doVerify(underlyingScheme, underlyingKey, signatureData, clearData) + } catch (e: GeneralSecurityException) { + throw sandbox.java.lang.fromDJVM(doCatch(e)) + } + } + + @JvmStatic + fun doVerify(txId: SecureHash, transactionSignature: TransactionSignature): Boolean { + throw sandbox.java.lang.failApi("Crypto.doVerify(SecureHash, TransactionSignature)") + } + + @JvmStatic + fun isValid(txId: SecureHash, transactionSignature: TransactionSignature): Boolean { + throw sandbox.java.lang.failApi("Crypto.isValid(SecureHash, TransactionSignature)") + } + + @JvmStatic + fun isValid(publicKey: PublicKey, signatureData: ByteArray, clearData: ByteArray): Boolean { + val underlyingKey = publicKey.toUnderlyingKey() + return try { + net.corda.core.crypto.Crypto.isValid(underlyingKey, signatureData, clearData) + } catch (e: SignatureException) { + throw sandbox.java.lang.fromDJVM(doCatch(e)) + } + } + + @JvmStatic + fun isValid(signatureScheme: SignatureScheme, publicKey: PublicKey, signatureData: ByteArray, clearData: ByteArray): Boolean { + val underlyingScheme = findUnderlyingSignatureScheme(signatureScheme) + val underlyingKey = publicKey.toUnderlyingKey() + return try { + net.corda.core.crypto.Crypto.isValid(underlyingScheme, underlyingKey, signatureData, clearData) + } catch (e: SignatureException) { + throw sandbox.java.lang.fromDJVM(doCatch(e)) + } + } + + @JvmStatic + fun publicKeyOnCurve(signatureScheme: SignatureScheme, publicKey: PublicKey): Boolean { + val underlyingScheme = findUnderlyingSignatureScheme(signatureScheme) + val underlyingKey = publicKey.toUnderlyingKey() + return net.corda.core.crypto.Crypto.publicKeyOnCurve(underlyingScheme, underlyingKey) + } + + @JvmStatic + fun validatePublicKey(key: PublicKey): Boolean { + return net.corda.core.crypto.Crypto.validatePublicKey(key.toUnderlyingKey()) + } + + @JvmStatic + fun toSupportedPublicKey(key: SubjectPublicKeyInfo): PublicKey { + return decodePublicKey(key.encoded) + } + + @JvmStatic + fun toSupportedPublicKey(key: PublicKey): PublicKey { + val underlyingKey = key.toUnderlyingKey() + val supportedKey = net.corda.core.crypto.Crypto.toSupportedPublicKey(underlyingKey) + return if (supportedKey === underlyingKey) { + key + } else { + DJVMPublicKey(supportedKey) + } + } +} diff --git a/node/src/main/kotlin/sandbox/net/corda/core/crypto/SecureHash.kt b/node/src/main/kotlin/sandbox/net/corda/core/crypto/SecureHash.kt new file mode 100644 index 0000000000..ac8f733511 --- /dev/null +++ b/node/src/main/kotlin/sandbox/net/corda/core/crypto/SecureHash.kt @@ -0,0 +1,8 @@ +package sandbox.net.corda.core.crypto + +/** + * This is a dummy class that implements just enough of [net.corda.core.crypto.SecureHash] + * to allow us to compile [sandbox.net.corda.core.crypto.Crypto]. + */ +@Suppress("unused_parameter") +sealed class SecureHash(bytes: ByteArray) : sandbox.java.lang.Object() diff --git a/node/src/main/kotlin/sandbox/net/corda/core/crypto/SignatureScheme.kt b/node/src/main/kotlin/sandbox/net/corda/core/crypto/SignatureScheme.kt new file mode 100644 index 0000000000..dd315252df --- /dev/null +++ b/node/src/main/kotlin/sandbox/net/corda/core/crypto/SignatureScheme.kt @@ -0,0 +1,26 @@ +package sandbox.net.corda.core.crypto + +import sandbox.java.lang.String +import sandbox.java.lang.Integer +import sandbox.java.lang.Object +import sandbox.java.security.spec.AlgorithmParameterSpec +import sandbox.java.util.List +import sandbox.org.bouncycastle.asn1.x509.AlgorithmIdentifier + +/** + * This is a dummy class that implements just enough of [net.corda.core.crypto.SignatureScheme] + * to allow us to compile [sandbox.net.corda.core.crypto.Crypto]. + */ +@Suppress("unused") +class SignatureScheme( + val schemeNumberID: Int, + val schemeCodeName: String, + val signatureOID: AlgorithmIdentifier, + val alternativeOIDs: List, + val providerName: String, + val algorithmName: String, + val signatureName: String, + val algSpec: AlgorithmParameterSpec?, + val keySize: Integer?, + val desc: String +) : Object() diff --git a/node/src/main/kotlin/sandbox/net/corda/core/crypto/TransactionSignature.kt b/node/src/main/kotlin/sandbox/net/corda/core/crypto/TransactionSignature.kt new file mode 100644 index 0000000000..2d3c3e8b90 --- /dev/null +++ b/node/src/main/kotlin/sandbox/net/corda/core/crypto/TransactionSignature.kt @@ -0,0 +1,7 @@ +package sandbox.net.corda.core.crypto + +/** + * This is a dummy class that implements just enough of [net.corda.core.crypto.TransactionSignature] + * to allow us to compile [sandbox.net.corda.core.crypto.Crypto]. + */ +class TransactionSignature : sandbox.java.lang.Object() diff --git a/node/src/main/kotlin/sandbox/net/corda/core/crypto/internal/ProviderMap.kt b/node/src/main/kotlin/sandbox/net/corda/core/crypto/internal/ProviderMap.kt new file mode 100644 index 0000000000..5a0ebb4065 --- /dev/null +++ b/node/src/main/kotlin/sandbox/net/corda/core/crypto/internal/ProviderMap.kt @@ -0,0 +1,8 @@ +package sandbox.net.corda.core.crypto.internal + +/** + * THIS FILE IS DELIBERATELY EMPTY, APART FROM A SINGLE DUMMY VALUE. + * KOTLIN WILL NOT CREATE A CLASS IF THIS FILE IS COMPLETELY EMPTY. + */ +@Suppress("unused") +private const val DUMMY_VALUE = 0 diff --git a/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxPublicKeySerializer.kt b/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxPublicKeySerializer.kt index ec528667ee..6a22e05da6 100644 --- a/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxPublicKeySerializer.kt +++ b/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxPublicKeySerializer.kt @@ -19,8 +19,7 @@ class SandboxPublicKeySerializer( taskFactory: Function>, out Function> ) : CustomSerializer.Implements(classLoader.toSandboxAnyClass(PublicKey::class.java)) { @Suppress("unchecked_cast") - private val decoder: Function - = taskFactory.apply(PublicKeyDecoder::class.java) as Function + private val decoder = taskFactory.apply(PublicKeyDecoder::class.java) as Function override val schemaForDocumentation: Schema = Schema(emptyList()) diff --git a/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializePublicKeyTest.kt b/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializePublicKeyTest.kt index d952ff10d6..dc982a8569 100644 --- a/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializePublicKeyTest.kt +++ b/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializePublicKeyTest.kt @@ -8,6 +8,7 @@ import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.internal._contextSerializationEnv import net.corda.core.serialization.serialize import net.corda.serialization.djvm.SandboxType.KOTLIN +import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith @@ -69,13 +70,18 @@ class DeserializePublicKeyTest : TestBase(KOTLIN) { sandbox { _contextSerializationEnv.set(createSandboxSerializationEnv(classLoader)) - val sandboxKey = data.deserializeFor(classLoader) + val sandboxData = data.deserializeFor(classLoader) - val taskFactory = classLoader.createRawTaskFactory() - val showPublicKey = taskFactory.compose(classLoader.createSandboxFunction()).apply(ShowPublicKey::class.java) - val result = showPublicKey.apply(sandboxKey) ?: fail("Result cannot be null") + val taskFactory = classLoader.createRawTaskFactory().compose(classLoader.createSandboxFunction()) + val showPublicKey = taskFactory.apply(ShowPublicKey::class.java) + val result = showPublicKey.apply(sandboxData) ?: fail("Result cannot be null") assertEquals(ShowPublicKey().apply(compositeData), result.toString()) + + val sandboxKey = taskFactory.apply(GetPublicKey::class.java) + .apply(sandboxData) ?: fail("PublicKey cannot be null") + assertThat(sandboxKey::class.java.name) + .isEqualTo("sandbox." + CompositeKey::class.java.name) } } @@ -86,6 +92,12 @@ class DeserializePublicKeyTest : TestBase(KOTLIN) { } } } + + class GetPublicKey : Function { + override fun apply(data: PublicKeyData): PublicKey { + return data.key + } + } } @CordaSerializable