From 0091807c2fa5b1e2400fcad874046662d323467f Mon Sep 17 00:00:00 2001 From: Shams Asari Date: Thu, 29 Feb 2024 14:46:27 +0000 Subject: [PATCH] ENT-11101: Fix all crypto issues introduced by Java 17 upgrade MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The various crypto tests that were previously ignored have been re-enabled. The abandoned i2p EdDSA library has been replaced with native support that was added in Java 15. Java 17 (via the `SunEC` provider) does not support the secp256k1 curve (one of the two ECDSA curves supported in Corda). This would not normally have been an issue as secp256k1 is already taken care of by Bouncy Castle. However, this only works if the `Crypto` API is used or if `”BC”` is explicitly specified as the provider (e.g. `Signature.getInstance(“SHA256withECDSA”, “BC”)`). If no provider is specified, which is what is more common, and actually what the Java docs recommend, then this doesn’t work as the `SunEC` provider is selected. To resolve this, a custom provider was created, installed just in front of `SunEC`, which “augments” `SunEC` by delegating to Bouncy Castle if keys or parameters for secp256k1 are encountered. `X509Utilities.createCertificate` now calls `X509Certificate.verify()` to verify the created certificate, rather than using the Bouncy Castle API. This is more representative of how certificates will be verified (e.g. during SSL handshake) and weeds out other issues (such as unsupported curve error for secp256k1). `BCCryptoService` has been renamed to `DefaultCryptoService` as it no longer explicitly uses Bouncy Castle but rather uses the installed security providers. This was done to fix a failing test. Further, `BCCryptoService` was already relying on the installed providers in some places. The hack to get Corda `SecureRandom` working was also resolved. Also, as an added bonus, tests which ignored `SPHINCS256_SHA256` have been reinstated. Note, there is a slightly inconsistency between how EdDSA and ECDSA keys are handled (and also RSA). For the later, Bouncy Castle is preferred, and methods such as `toSupportedKey*` will convert any JDK class to Bouncy Castle. For EdDSA the preference is the JDK (`SunEC`). However, this is simply a continuation of the previous preference of the i2p library over Bouncy Castle. --- .ci/api-current.txt | 5 - build.gradle | 2 - constants.properties | 3 +- core-tests/build.gradle | 1 - core/build.gradle | 15 -- .../core/crypto/CordaSecurityProvider.kt | 22 +- .../kotlin/net/corda/core/crypto/Crypto.kt | 159 ++++++--------- .../net/corda/core/crypto/CryptoUtils.kt | 13 +- .../net/corda/core/crypto/SignatureScheme.kt | 10 +- .../corda/core/crypto/internal/Curve25519.kt | 46 +++++ .../corda/core/crypto/internal/Instances.kt | 5 +- .../crypto/internal/PlatformSecureRandom.kt | 22 +- .../corda/core/crypto/internal/ProviderMap.kt | 55 +---- .../internal/Secp256k1SupportProvider.kt | 188 ++++++++++++++++++ .../corda/core/internal/StatePointerSearch.kt | 2 +- .../corda/core/internal/X509EdDSAEngine.kt | 59 ------ .../net/corda/core/utilities/SgxSupport.kt | 8 - .../core/internal/X509EdDSAEngineTest.java | 135 ------------- .../net/corda/core/crypto/CryptoUtilsTest.kt | 52 +++-- .../net/corda/core/crypto/EdDSATests.kt | 52 +++-- detekt-baseline.xml | 4 - node-api-tests/build.gradle | 1 - .../internal/crypto/X509UtilitiesTest.kt | 38 ++-- node-api/build.gradle | 1 - .../internal/config/CertificateStore.kt | 2 +- .../internal/crypto/ContentSignerBuilder.kt | 7 +- .../nodeapi/internal/crypto/X509Utilities.kt | 30 +-- ...yptoService.kt => DefaultCryptoService.kt} | 47 +++-- .../kryo/DefaultKryoCustomizer.kt | 25 +-- .../internal/serialization/kryo/Kryo.kt | 8 +- .../nodeapi/internal/SignedNodeInfoTest.kt | 6 +- .../crypto/ContentSignerBuilderTest.kt | 4 +- ...eTests.kt => DefaultCryptoServiceTests.kt} | 61 +++--- .../internal/serialization/kryo/KryoTests.kt | 7 +- node/build.gradle | 2 - node/capsule/build.gradle | 2 +- .../src/main/resources/node-jvm-args.txt | 5 + .../customcheckpointserializer/TestCorDapp.kt | 14 -- .../net/corda/node/internal/AbstractNode.kt | 4 +- .../corda/node/internal/KeyStoreHandler.kt | 6 +- .../node/services/config/ConfigUtilities.kt | 4 +- .../node/services/config/NodeConfiguration.kt | 2 +- .../registration/NetworkRegistrationHelper.kt | 8 +- .../node/internal/KeyStoreHandlerTest.kt | 10 +- .../testing/node/internal/DriverDSLImpl.kt | 3 +- 45 files changed, 507 insertions(+), 648 deletions(-) create mode 100644 core/src/main/kotlin/net/corda/core/crypto/internal/Curve25519.kt create mode 100644 core/src/main/kotlin/net/corda/core/crypto/internal/Secp256k1SupportProvider.kt delete mode 100644 core/src/main/kotlin/net/corda/core/internal/X509EdDSAEngine.kt delete mode 100644 core/src/main/kotlin/net/corda/core/utilities/SgxSupport.kt delete mode 100644 core/src/test/java/net/corda/core/internal/X509EdDSAEngineTest.java rename node-api/src/main/kotlin/net/corda/nodeapi/internal/cryptoservice/{bouncycastle/BCCryptoService.kt => DefaultCryptoService.kt} (87%) rename node-api/src/test/kotlin/net/corda/nodeapi/internal/cryptoservice/{bouncycastle/BCCryptoServiceTests.kt => DefaultCryptoServiceTests.kt} (83%) diff --git a/.ci/api-current.txt b/.ci/api-current.txt index 72eea8da0a..d4f9301255 100644 --- a/.ci/api-current.txt +++ b/.ci/api-current.txt @@ -8290,11 +8290,6 @@ public static final class net.corda.core.utilities.ProgressTracker$UNSTARTED ext public interface net.corda.core.utilities.PropertyDelegate public abstract T getValue(Object, kotlin.reflect.KProperty) ## -public final class net.corda.core.utilities.SgxSupport extends java.lang.Object - public static final boolean isInsideEnclave() - @NotNull - public static final net.corda.core.utilities.SgxSupport INSTANCE -## public final class net.corda.core.utilities.ThreadDumpUtilsKt extends java.lang.Object @NotNull public static final String asString(management.ThreadInfo, int) diff --git a/build.gradle b/build.gradle index 94ce6dbe9b..34fe923b28 100644 --- a/build.gradle +++ b/build.gradle @@ -90,7 +90,6 @@ buildscript { ext.h2_version = constants.getProperty("h2Version") ext.rxjava_version = constants.getProperty("rxjavaVersion") ext.dokka_version = constants.getProperty("dokkaVersion") - ext.eddsa_version = constants.getProperty("eddsaVersion") ext.dependency_checker_version = constants.getProperty("dependencyCheckerVersion") ext.commons_collections_version = constants.getProperty("commonsCollectionsVersion") ext.beanutils_version = constants.getProperty("beanutilsVersion") @@ -178,7 +177,6 @@ buildscript { classpath "com.guardsquare:proguard-gradle:$proguard_version" classpath 'com.github.ben-manes:gradle-versions-plugin:0.15.0' classpath "org.jetbrains.dokka:dokka-base:$dokka_version" - classpath "net.i2p.crypto:eddsa:$eddsa_version" // Needed for ServiceIdentityGenerator in the build environment. classpath "org.owasp:dependency-check-gradle:$dependency_checker_version" classpath "org.jfrog.buildinfo:build-info-extractor-gradle:$artifactory_plugin_version" // Capsule gradle plugin forked and maintained locally to support Gradle 5.x diff --git a/constants.properties b/constants.properties index 3880d9f178..efd2246e17 100644 --- a/constants.properties +++ b/constants.properties @@ -21,7 +21,7 @@ guavaVersion=28.0-jre quasarVersion=0.9.0_r3 dockerJavaVersion=3.2.5 proguardVersion=7.3.1 -// bouncy castle version must not be changed on a patch release. Needs a full release test cycle to flush out any issues. +# Bouncy Castle version must not be changed on a patch release. Needs a full release test cycle to flush out any issues. bouncycastleVersion=1.75 classgraphVersion=4.8.135 disruptorVersion=3.4.2 @@ -79,7 +79,6 @@ hibernateVersion=5.6.14.Final h2Version=2.2.224 rxjavaVersion=1.3.8 dokkaVersion=1.8.20 -eddsaVersion=0.3.0 dependencyCheckerVersion=5.2.0 commonsCollectionsVersion=4.3 beanutilsVersion=1.9.4 diff --git a/core-tests/build.gradle b/core-tests/build.gradle index 80c9b5c6ab..16347f88e2 100644 --- a/core-tests/build.gradle +++ b/core-tests/build.gradle @@ -184,7 +184,6 @@ quasar { "io.github.classgraph**", "io.netty*", "liquibase**", - "net.i2p.crypto.**", "nonapi.io.github.classgraph.**", "org.apiguardian.**", "org.bouncycastle**", diff --git a/core/build.gradle b/core/build.gradle index 427179cb67..b1c5e1d7dd 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -36,8 +36,6 @@ dependencies { // For caches rather than guava implementation "com.github.ben-manes.caffeine:caffeine:$caffeine_version" implementation "org.apache.commons:commons-lang3:$commons_lang3_version" - // Java ed25519 implementation. See https://github.com/str4d/ed25519-java/ - implementation "net.i2p.crypto:eddsa:$eddsa_version" // Bouncy castle support needed for X509 certificate manipulation implementation "org.bouncycastle:bcprov-jdk18on:${bouncycastle_version}" // required to use @Type annotation @@ -93,19 +91,7 @@ processTestResources { } } -compileTestJava { - options.compilerArgs += [ - '--add-exports', 'java.base/sun.security.util=ALL-UNNAMED', - '--add-exports', 'java.base/sun.security.x509=ALL-UNNAMED' - ] -} - test { - // TODO This obscures whether any Corda client APIs need these JVM flags as well (which they shouldn't do) - jvmArgs += [ - '--add-exports', 'java.base/sun.security.util=ALL-UNNAMED', - '--add-exports', 'java.base/sun.security.x509=ALL-UNNAMED' - ] maxParallelForks = (System.env.CORDA_CORE_TESTING_FORKS == null) ? 1 : "$System.env.CORDA_CORE_TESTING_FORKS".toInteger() } @@ -129,7 +115,6 @@ quasar { "io.github.classgraph**", "io.netty*", "liquibase**", - "net.i2p.crypto.**", "nonapi.io.github.classgraph.**", "org.apiguardian.**", "org.bouncycastle**", diff --git a/core/src/main/kotlin/net/corda/core/crypto/CordaSecurityProvider.kt b/core/src/main/kotlin/net/corda/core/crypto/CordaSecurityProvider.kt index 0be1edfd55..70bf257a3f 100644 --- a/core/src/main/kotlin/net/corda/core/crypto/CordaSecurityProvider.kt +++ b/core/src/main/kotlin/net/corda/core/crypto/CordaSecurityProvider.kt @@ -5,11 +5,10 @@ import net.corda.core.crypto.CordaObjectIdentifier.COMPOSITE_SIGNATURE import net.corda.core.crypto.internal.PlatformSecureRandomService import org.bouncycastle.asn1.ASN1ObjectIdentifier import java.security.Provider -import java.util.* +import java.util.Optional import java.util.concurrent.ConcurrentHashMap -@Suppress("DEPRECATION") // JDK11: should replace with Provider(String name, double version, String info) (since 9) -class CordaSecurityProvider : Provider(PROVIDER_NAME, 0.1, "$PROVIDER_NAME security provider wrapper") { +class CordaSecurityProvider : Provider(PROVIDER_NAME, "0.2", "$PROVIDER_NAME security provider") { companion object { const val PROVIDER_NAME = "Corda" } @@ -17,21 +16,8 @@ class CordaSecurityProvider : Provider(PROVIDER_NAME, 0.1, "$PROVIDER_NAME secur private val services = ConcurrentHashMap, Optional>() init { - put("KeyFactory.${CompositeKey.KEY_ALGORITHM}", CompositeKeyFactory::class.java.name) - put("Alg.Alias.KeyFactory.$COMPOSITE_KEY", CompositeKey.KEY_ALGORITHM) - put("Alg.Alias.KeyFactory.OID.$COMPOSITE_KEY", CompositeKey.KEY_ALGORITHM) - put("Signature.${CompositeSignature.SIGNATURE_ALGORITHM}", CompositeSignature::class.java.name) - put("Alg.Alias.Signature.$COMPOSITE_SIGNATURE", CompositeSignature.SIGNATURE_ALGORITHM) - put("Alg.Alias.Signature.OID.$COMPOSITE_SIGNATURE", CompositeSignature.SIGNATURE_ALGORITHM) - putPlatformSecureRandomService() - - // JDK11+ - Hack to set Provider#legacyChanged to false, without this SecureRandom will not - // pickup our random implementation (even if our provider is the first provider in - // the chain). - super.getService("UNDEFINED", "UNDEFINED") - } - - private fun putPlatformSecureRandomService() { + putService(Service(this, "KeyFactory", CompositeKey.KEY_ALGORITHM, CompositeKeyFactory::class.java.name, listOf("$COMPOSITE_KEY", "OID.$COMPOSITE_KEY"), null)) + putService(Service(this, "Signature", CompositeSignature.SIGNATURE_ALGORITHM, CompositeSignature::class.java.name, listOf("$COMPOSITE_SIGNATURE", "OID.$COMPOSITE_SIGNATURE"), null)) putService(PlatformSecureRandomService(this)) } diff --git a/core/src/main/kotlin/net/corda/core/crypto/Crypto.kt b/core/src/main/kotlin/net/corda/core/crypto/Crypto.kt index 8018c68892..7fbdde3cd7 100644 --- a/core/src/main/kotlin/net/corda/core/crypto/Crypto.kt +++ b/core/src/main/kotlin/net/corda/core/crypto/Crypto.kt @@ -1,31 +1,26 @@ package net.corda.core.crypto import net.corda.core.CordaOID +import net.corda.core.crypto.Crypto.EDDSA_ED25519_SHA512 import net.corda.core.crypto.internal.AliasPrivateKey +import net.corda.core.crypto.internal.Curve25519.isOnCurve25519 import net.corda.core.crypto.internal.Instances.withSignature import net.corda.core.crypto.internal.PublicKeyCache import net.corda.core.crypto.internal.bouncyCastlePQCProvider import net.corda.core.crypto.internal.cordaBouncyCastleProvider import net.corda.core.crypto.internal.cordaSecurityProvider -import net.corda.core.crypto.internal.`id-Curve25519ph` import net.corda.core.crypto.internal.providerMap +import net.corda.core.crypto.internal.sunEcProvider import net.corda.core.internal.utilities.PrivateInterner import net.corda.core.serialization.serialize import net.corda.core.utilities.ByteSequence -import net.i2p.crypto.eddsa.EdDSAEngine -import net.i2p.crypto.eddsa.EdDSAPrivateKey -import net.i2p.crypto.eddsa.EdDSAPublicKey -import net.i2p.crypto.eddsa.math.GroupElement -import net.i2p.crypto.eddsa.spec.EdDSANamedCurveSpec -import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable -import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec -import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec import org.bouncycastle.asn1.ASN1Integer import org.bouncycastle.asn1.ASN1ObjectIdentifier import org.bouncycastle.asn1.DERNull import org.bouncycastle.asn1.DERUTF8String import org.bouncycastle.asn1.DLSequence import org.bouncycastle.asn1.bc.BCObjectIdentifiers +import org.bouncycastle.asn1.edec.EdECObjectIdentifiers import org.bouncycastle.asn1.nist.NISTObjectIdentifiers import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers import org.bouncycastle.asn1.pkcs.PrivateKeyInfo @@ -34,6 +29,9 @@ import org.bouncycastle.asn1.x509.AlgorithmIdentifier import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo import org.bouncycastle.asn1.x9.X9ObjectIdentifiers import org.bouncycastle.crypto.CryptoServicesRegistrar +import org.bouncycastle.crypto.params.Ed25519PrivateKeyParameters +import org.bouncycastle.crypto.util.PrivateKeyInfoFactory +import org.bouncycastle.crypto.util.SubjectPublicKeyInfoFactory import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey import org.bouncycastle.jcajce.provider.asymmetric.edec.BCEdDSAPrivateKey @@ -49,20 +47,24 @@ import org.bouncycastle.jce.spec.ECPublicKeySpec import org.bouncycastle.math.ec.ECConstants import org.bouncycastle.math.ec.FixedPointCombMultiplier import org.bouncycastle.math.ec.WNafUtil +import org.bouncycastle.math.ec.rfc8032.Ed25519 import org.bouncycastle.pqc.jcajce.provider.sphincs.BCSphincs256PrivateKey import org.bouncycastle.pqc.jcajce.provider.sphincs.BCSphincs256PublicKey import org.bouncycastle.pqc.jcajce.spec.SPHINCS256KeyGenParameterSpec import java.math.BigInteger import java.security.InvalidKeyException import java.security.Key -import java.security.KeyFactory import java.security.KeyPair import java.security.KeyPairGenerator import java.security.PrivateKey import java.security.Provider import java.security.PublicKey +import java.security.Signature import java.security.SignatureException +import java.security.interfaces.EdECPrivateKey +import java.security.interfaces.EdECPublicKey import java.security.spec.InvalidKeySpecException +import java.security.spec.NamedParameterSpec import java.security.spec.PKCS8EncodedKeySpec import java.security.spec.X509EncodedKeySpec import javax.crypto.Mac @@ -77,7 +79,7 @@ import javax.crypto.spec.SecretKeySpec *
  • RSA_SHA256 (RSA PKCS#1 using SHA256 as hash algorithm). *
  • ECDSA_SECP256K1_SHA256 (ECDSA using the secp256k1 Koblitz curve and SHA256 as hash algorithm). *
  • ECDSA_SECP256R1_SHA256 (ECDSA using the secp256r1 (NIST P-256) curve and SHA256 as hash algorithm). - *
  • EDDSA_ED25519_SHA512 (EdDSA using the ed255519 twisted Edwards curve and SHA512 as hash algorithm). + *
  • EDDSA_ED25519_SHA512 (EdDSA using the ed25519 twisted Edwards curve and SHA512 as hash algorithm). *
  • SPHINCS256_SHA512 (SPHINCS-256 hash-based signature scheme using SHA512 as hash algorithm). * */ @@ -95,7 +97,7 @@ object Crypto { listOf(AlgorithmIdentifier(PKCSObjectIdentifiers.rsaEncryption, null)), cordaBouncyCastleProvider.name, "RSA", - "SHA256WITHRSA", + "SHA256withRSA", null, 3072, "RSA_SHA256 signature scheme using SHA256 as hash algorithm." @@ -140,13 +142,12 @@ object Crypto { val EDDSA_ED25519_SHA512: SignatureScheme = SignatureScheme( 4, "EDDSA_ED25519_SHA512", - AlgorithmIdentifier(`id-Curve25519ph`, null), - emptyList(), // Both keys and the signature scheme use the same OID in i2p library. - // We added EdDSA to bouncy castle for certificate signing. - cordaBouncyCastleProvider.name, - "1.3.101.112", - EdDSAEngine.SIGNATURE_ALGORITHM, - EdDSANamedCurveTable.getByName("ED25519"), + AlgorithmIdentifier(EdECObjectIdentifiers.id_Ed25519, null), + emptyList(), // Both keys and the signature scheme use the same OID. + sunEcProvider.name, + "Ed25519", + "Ed25519", + NamedParameterSpec.ED25519, 256, "EdDSA signature scheme using the ed25519 twisted Edwards curve." ) @@ -164,11 +165,11 @@ object Crypto { val SPHINCS256_SHA256 = SignatureScheme( 5, "SPHINCS-256_SHA512", - AlgorithmIdentifier(BCObjectIdentifiers.sphincs256_with_SHA512, DLSequence(arrayOf(ASN1Integer(0), SHA512_256))), + AlgorithmIdentifier(BCObjectIdentifiers.sphincs256_with_SHA512, null), listOf(AlgorithmIdentifier(BCObjectIdentifiers.sphincs256, DLSequence(arrayOf(ASN1Integer(0), SHA512_256)))), bouncyCastlePQCProvider.name, "SPHINCS256", - "SHA512WITHSPHINCS256", + "SHA512withSPHINCS256", SPHINCS256KeyGenParameterSpec(SPHINCS256KeyGenParameterSpec.SHA512_256), 256, "SPHINCS-256 hash-based signature scheme. It provides 128bit security against post-quantum attackers " + @@ -244,8 +245,9 @@ object Crypto { @JvmStatic fun findSignatureScheme(algorithm: AlgorithmIdentifier): SignatureScheme { - return algorithmMap[normaliseAlgorithmIdentifier(algorithm)] - ?: throw IllegalArgumentException("Unrecognised algorithm: ${algorithm.algorithm.id}") + return requireNotNull(algorithmMap[normaliseAlgorithmIdentifier(algorithm)]) { + "Unrecognised algorithm identifier: ${algorithm.algorithm} ${algorithm.parameters}" + } } /** Find [SignatureScheme] by platform specific schemeNumberID. */ @@ -307,12 +309,11 @@ object Crypto { @JvmStatic fun decodePrivateKey(encodedKey: ByteArray): PrivateKey { val keyInfo = PrivateKeyInfo.getInstance(encodedKey) - if (keyInfo.privateKeyAlgorithm.algorithm == ASN1ObjectIdentifier(CordaOID.ALIAS_PRIVATE_KEY)) { - return convertIfBCEdDSAPrivateKey(decodeAliasPrivateKey(keyInfo)) + return if (keyInfo.privateKeyAlgorithm.algorithm == ASN1ObjectIdentifier(CordaOID.ALIAS_PRIVATE_KEY)) { + decodeAliasPrivateKey(keyInfo) + } else { + findSignatureScheme(keyInfo.privateKeyAlgorithm).keyFactory.generatePrivate(PKCS8EncodedKeySpec(encodedKey)) } - val signatureScheme = findSignatureScheme(keyInfo.privateKeyAlgorithm) - val keyFactory = keyFactory(signatureScheme) - return convertIfBCEdDSAPrivateKey(keyFactory.generatePrivate(PKCS8EncodedKeySpec(encodedKey))) } private fun decodeAliasPrivateKey(keyInfo: PrivateKeyInfo): PrivateKey { @@ -351,8 +352,7 @@ object Crypto { "Unsupported key/algorithm for schemeCodeName: ${signatureScheme.schemeCodeName}" } try { - val keyFactory = keyFactory(signatureScheme) - return convertIfBCEdDSAPrivateKey(keyFactory.generatePrivate(PKCS8EncodedKeySpec(encodedKey))) + return signatureScheme.keyFactory.generatePrivate(PKCS8EncodedKeySpec(encodedKey)) } catch (ikse: InvalidKeySpecException) { throw InvalidKeySpecException("This private key cannot be decoded, please ensure it is PKCS8 encoded and that " + "it corresponds to the input scheme's code name.", ikse) @@ -368,12 +368,11 @@ object Crypto { */ @JvmStatic fun decodePublicKey(encodedKey: ByteArray): PublicKey { - return PublicKeyCache.publicKeyForCachedBytes(ByteSequence.of(encodedKey)) ?: { + return PublicKeyCache.publicKeyForCachedBytes(ByteSequence.of(encodedKey)) ?: run { val subjectPublicKeyInfo = SubjectPublicKeyInfo.getInstance(encodedKey) val signatureScheme = findSignatureScheme(subjectPublicKeyInfo.algorithm) - val keyFactory = keyFactory(signatureScheme) - convertIfBCEdDSAPublicKey(keyFactory.generatePublic(X509EncodedKeySpec(encodedKey))) - }() + internPublicKey(signatureScheme.keyFactory.generatePublic(X509EncodedKeySpec(encodedKey))) + } } @JvmStatic @@ -412,8 +411,7 @@ object Crypto { "Unsupported key/algorithm for schemeCodeName: ${signatureScheme.schemeCodeName}" } try { - val keyFactory = keyFactory(signatureScheme) - return convertIfBCEdDSAPublicKey(keyFactory.generatePublic(X509EncodedKeySpec(encodedKey))) + return signatureScheme.keyFactory.generatePublic(X509EncodedKeySpec(encodedKey)) } catch (ikse: InvalidKeySpecException) { throw InvalidKeySpecException("This public key cannot be decoded, please ensure it is X509 encoded and " + "that it corresponds to the input scheme's code name.", ikse) @@ -471,12 +469,8 @@ object Crypto { return withSignature(signatureScheme) { signature -> // Note that deterministic signature schemes, such as EdDSA, original SPHINCS-256 and RSA PKCS#1, do not require // extra randomness, but we have to ensure that non-deterministic algorithms (i.e., ECDSA) use non-blocking - // SecureRandom implementation. Also, SPHINCS-256 implementation in BouncyCastle 1.60 fails with - // ClassCastException if we invoke initSign with a SecureRandom as an input. - // TODO Although we handle the above issue here, consider updating to BC 1.61+ which provides a fix. - if (signatureScheme == EDDSA_ED25519_SHA512 - || signatureScheme == SPHINCS256_SHA256 - || signatureScheme == RSA_SHA256) { + // SecureRandom implementation. + if (signatureScheme == EDDSA_ED25519_SHA512 || signatureScheme == SPHINCS256_SHA256 || signatureScheme == RSA_SHA256) { signature.initSign(privateKey) } else { // The rest of the algorithms will require a SecureRandom input (i.e., ECDSA or any new algorithm for which @@ -716,8 +710,7 @@ object Crypto { * This operation is currently supported for ECDSA secp256r1 (NIST P-256), ECDSA secp256k1 and EdDSA ed25519. * * Similarly to BIP32, the implemented algorithm uses an HMAC function based on SHA512 and it is actually - * an implementation the HKDF rfc - Step 1: Extract function, - * @see HKDF + * an implementation of the [HKDF rfc - Step 1: Extract function](https://tools.ietf.org/html/rfc5869), * which is practically a variation of the private-parent-key -> private-child-key hardened key generation of BIP32. * * Unlike BIP32, where both private and public keys are extended to prevent deterministically @@ -725,8 +718,8 @@ object Crypto { * without a chain-code and the generated key relies solely on the security of the private key. * * Although without a chain-code we lose the aforementioned property of not depending solely on the key, - * it should be mentioned that the cryptographic strength of the HMAC depends upon the size of the secret key. - * @see HMAC Security + * it should be mentioned that the cryptographic strength of the HMAC depends upon the size of the secret key + * (see [HMAC Security](https://en.wikipedia.org/wiki/Hash-based_message_authentication_code#Security)). * Thus, as long as the master key is kept secret and has enough entropy (~256 bits for EC-schemes), the system * is considered secure. * @@ -743,9 +736,9 @@ object Crypto { *
  • salt values should not be chosen by an attacker. *

    * - * Regarding the last requirement, according to Krawczyk's HKDF scheme: While there is no need to keep the salt secret, - * it is assumed that salt values are independent of the input keying material. - * @see Cryptographic Extraction and Key Derivation - The HKDF Scheme. + * Regarding the last requirement, according to Krawczyk's HKDF scheme: _While there is no need to keep the salt secret, + * it is assumed that salt values are independent of the input keying material_ + * (see [Cryptographic Extraction and Key Derivation - The HKDF Scheme](http://eprint.iacr.org/2010/264.pdf)). * * There are also protocols that require an authenticated nonce (e.g. when a DH derived key is used as a seed) and thus * we need to make sure that nonces come from legitimate parties rather than selected by an attacker. @@ -845,13 +838,7 @@ object Crypto { private fun deriveKeyPairEdDSA(privateKey: PrivateKey, seed: ByteArray): KeyPair { // Compute HMAC(privateKey, seed). val macBytes = deriveHMAC(privateKey, seed) - - // Calculate key pair. - val params = EDDSA_ED25519_SHA512.algSpec as EdDSANamedCurveSpec - val bytes = macBytes.copyOf(params.curve.field.getb() / 8) // Need to pad the entropy to the valid seed length. - val privateKeyD = EdDSAPrivateKeySpec(bytes, params) - val publicKeyD = EdDSAPublicKeySpec(privateKeyD.a, params) - return KeyPair(internPublicKey(EdDSAPublicKey(publicKeyD)), EdDSAPrivateKey(privateKeyD)) + return deriveEdDSAKeyPair(macBytes) } /** @@ -882,15 +869,20 @@ object Crypto { fun deriveKeyPairFromEntropy(entropy: BigInteger): KeyPair = deriveKeyPairFromEntropy(DEFAULT_SIGNATURE_SCHEME, entropy) // Custom key pair generator from entropy. - // The BigIntenger.toByteArray() uses the two's-complement representation. + // The BigInteger.toByteArray() uses the two's-complement representation. // The entropy is transformed to a byte array in big-endian byte-order and // only the first ed25519.field.getb() / 8 bytes are used. private fun deriveEdDSAKeyPairFromEntropy(entropy: BigInteger): KeyPair { - val params = EDDSA_ED25519_SHA512.algSpec as EdDSANamedCurveSpec - val bytes = entropy.toByteArray().copyOf(params.curve.field.getb() / 8) // Need to pad the entropy to the valid seed length. - val priv = EdDSAPrivateKeySpec(bytes, params) - val pub = EdDSAPublicKeySpec(priv.a, params) - return KeyPair(internPublicKey(EdDSAPublicKey(pub)), EdDSAPrivateKey(priv)) + return deriveEdDSAKeyPair(entropy.toByteArray().copyOf(Ed25519.PUBLIC_KEY_SIZE)) + } + + private fun deriveEdDSAKeyPair(bytes: ByteArray): KeyPair { + val privateKeyParams = Ed25519PrivateKeyParameters(bytes, 0) // This will copy the first 256 bits + val encodedPrivateKey = PrivateKeyInfoFactory.createPrivateKeyInfo(privateKeyParams).encoded + val privateKey = EDDSA_ED25519_SHA512.keyFactory.generatePrivate(PKCS8EncodedKeySpec(encodedPrivateKey)) + val encodedPublicKey = SubjectPublicKeyInfoFactory.createSubjectPublicKeyInfo(privateKeyParams.generatePublicKey()).encoded + val publicKey = EDDSA_ED25519_SHA512.keyFactory.generatePublic(X509EncodedKeySpec(encodedPublicKey)) + return KeyPair(internPublicKey(publicKey), privateKey) } // Custom key pair generator from an entropy required for various tests. It is similar to deriveKeyPairECDSA, @@ -925,7 +917,7 @@ object Crypto { val mac = Mac.getInstance("HmacSHA512", cordaBouncyCastleProvider) val keyData = when (privateKey) { is BCECPrivateKey -> privateKey.d.toByteArray() - is EdDSAPrivateKey -> privateKey.geta() + is EdECPrivateKey -> privateKey.bytes.get() else -> throw InvalidKeyException("Key type ${privateKey.algorithm} is not supported for deterministic key derivation") } val key = SecretKeySpec(keyData, "HmacSHA512") @@ -936,12 +928,12 @@ object Crypto { /** * Check if a point's coordinates are on the expected curve to avoid certain types of ECC attacks. * Point-at-infinity is not permitted as well. - * @see Small subgroup and invalid-curve attacks for a more descriptive explanation on such attacks. + * See [Small subgroup and invalid-curve attacks](https://safecurves.cr.yp.to/twist.html) for a more descriptive explanation on such attacks. * We use this function on [validatePublicKey], which is currently used for signature verification only. * Thus, as these attacks are mostly not relevant to signature verification, we should note that * we are doing it out of an abundance of caution and specifically to proactively protect developers * against using these points as part of a DH key agreement or for use cases as yet unimagined. - * This method currently applies to BouncyCastle's ECDSA (both R1 and K1 curves) and I2P's EdDSA (ed25519 curve). + * This method currently applies to BouncyCastle's ECDSA (both R1 and K1 curves) and JCA EdDSA (ed25519 curve). * @param publicKey a [PublicKey], usually used to validate a signer's public key in on the Curve. * @param signatureScheme a [SignatureScheme] object, retrieved from supported signature schemes, see [Crypto]. * @return true if the point lies on the curve or false if it doesn't. @@ -954,17 +946,11 @@ object Crypto { } return when (publicKey) { is BCECPublicKey -> publicKey.parameters == signatureScheme.algSpec && !publicKey.q.isInfinity && publicKey.q.isValid - is EdDSAPublicKey -> publicKey.params == signatureScheme.algSpec && !isEdDSAPointAtInfinity(publicKey) && publicKey.a.isOnCurve + is EdECPublicKey -> signatureScheme == EDDSA_ED25519_SHA512 && publicKey.params.name.equals("Ed25519", ignoreCase = true) && publicKey.point.isOnCurve25519 else -> throw IllegalArgumentException("Unsupported key type: ${publicKey::class}") } } - // Return true if EdDSA publicKey is point at infinity. - // For EdDSA a custom function is required as it is not supported by the I2P implementation. - private fun isEdDSAPointAtInfinity(publicKey: EdDSAPublicKey): Boolean { - return publicKey.a.toP3() == (EDDSA_ED25519_SHA512.algSpec as EdDSANamedCurveSpec).curve.getZero(GroupElement.Representation.P3) - } - /** Check if the requested [SignatureScheme] is supported by the system. */ @JvmStatic fun isSupportedSignatureScheme(signatureScheme: SignatureScheme): Boolean { @@ -981,7 +967,7 @@ object Crypto { // Check if a public key satisfies algorithm specs (for ECC: key should lie on the curve and not being point-at-infinity). private fun validatePublicKey(signatureScheme: SignatureScheme, key: PublicKey): Boolean { return when (key) { - is BCECPublicKey, is EdDSAPublicKey -> publicKeyOnCurve(signatureScheme, key) + is BCECPublicKey, is EdECPublicKey -> publicKeyOnCurve(signatureScheme, key) is BCRSAPublicKey -> key.modulus.bitLength() >= 2048 // Although the recommended RSA key size is 3072, we accept any key >= 2048bits. is BCSphincs256PublicKey -> true else -> throw IllegalArgumentException("Unsupported key type: ${key::class}") @@ -991,21 +977,6 @@ object Crypto { private val interner = PrivateInterner() private fun internPublicKey(key: PublicKey): PublicKey = PublicKeyCache.cachePublicKey(interner.intern(key)) - - private fun convertIfBCEdDSAPublicKey(key: PublicKey): PublicKey { - return internPublicKey(when (key) { - is BCEdDSAPublicKey -> EdDSAPublicKey(X509EncodedKeySpec(key.encoded)) - else -> key - }) - } - - private fun convertIfBCEdDSAPrivateKey(key: PrivateKey): PrivateKey { - return when (key) { - is BCEdDSAPrivateKey -> EdDSAPrivateKey(PKCS8EncodedKeySpec(key.encoded)) - else -> key - } - } - /** * Convert a public key to a supported implementation. * @param key a public key. @@ -1031,9 +1002,9 @@ object Crypto { is BCECPublicKey -> internPublicKey(key) is BCRSAPublicKey -> internPublicKey(key) is BCSphincs256PublicKey -> internPublicKey(key) - is EdDSAPublicKey -> internPublicKey(key) + is EdECPublicKey -> internPublicKey(key) is CompositeKey -> internPublicKey(key) - is BCEdDSAPublicKey -> convertIfBCEdDSAPublicKey(key) + is BCEdDSAPublicKey -> internPublicKey(key) else -> decodePublicKey(key.encoded) } } @@ -1052,8 +1023,8 @@ object Crypto { is BCECPrivateKey -> key is BCRSAPrivateKey -> key is BCSphincs256PrivateKey -> key - is EdDSAPrivateKey -> key - is BCEdDSAPrivateKey -> convertIfBCEdDSAPrivateKey(key) + is EdECPrivateKey -> key + is BCEdDSAPrivateKey -> key else -> decodePrivateKey(key.encoded) } } @@ -1095,8 +1066,4 @@ object Crypto { private fun setBouncyCastleRNG() { CryptoServicesRegistrar.setSecureRandom(newSecureRandom()) } - - private fun keyFactory(signatureScheme: SignatureScheme) = signatureScheme.getKeyFactory { - KeyFactory.getInstance(signatureScheme.algorithmName, providerMap[signatureScheme.providerName]) - } } diff --git a/core/src/main/kotlin/net/corda/core/crypto/CryptoUtils.kt b/core/src/main/kotlin/net/corda/core/crypto/CryptoUtils.kt index 3fc649f989..797123bf4d 100644 --- a/core/src/main/kotlin/net/corda/core/crypto/CryptoUtils.kt +++ b/core/src/main/kotlin/net/corda/core/crypto/CryptoUtils.kt @@ -3,7 +3,8 @@ package net.corda.core.crypto import net.corda.core.contracts.PrivacySalt -import net.corda.core.crypto.internal.platformSecureRandomFactory +import net.corda.core.crypto.internal.PlatformSecureRandomService +import net.corda.core.crypto.internal.cordaSecurityProvider import net.corda.core.serialization.SerializationDefaults import net.corda.core.serialization.serialize import net.corda.core.utilities.OpaqueBytes @@ -19,6 +20,7 @@ import java.security.PublicKey import java.security.SecureRandom import java.security.SecureRandomSpi import java.security.SignatureException +import kotlin.math.abs /** * Utility to simplify the act of signing a byte array. @@ -231,7 +233,12 @@ object DummySecureRandom : SecureRandom(DummySecureRandomSpi(), null) * which should never happen and suggests an unusual JVM or non-standard Java library. */ @Throws(NoSuchAlgorithmException::class) -fun newSecureRandom(): SecureRandom = platformSecureRandomFactory() +fun newSecureRandom(): SecureRandom = sharedSecureRandom + +// This is safe to share because of the underlying implementation of SecureRandomSpi +private val sharedSecureRandom: SecureRandom by lazy(LazyThreadSafetyMode.PUBLICATION) { + SecureRandom.getInstance(PlatformSecureRandomService.ALGORITHM, cordaSecurityProvider) +} /** * Returns a random positive non-zero long generated using a secure RNG. This function sacrifies a bit of entropy in order @@ -239,7 +246,7 @@ fun newSecureRandom(): SecureRandom = platformSecureRandomFactory() */ fun random63BitValue(): Long { while (true) { - val candidate = Math.abs(newSecureRandom().nextLong()) + val candidate = abs(newSecureRandom().nextLong()) // No need to check for -0L if (candidate != 0L && candidate != Long.MIN_VALUE) { return candidate diff --git a/core/src/main/kotlin/net/corda/core/crypto/SignatureScheme.kt b/core/src/main/kotlin/net/corda/core/crypto/SignatureScheme.kt index 27b3fc4750..885868873a 100644 --- a/core/src/main/kotlin/net/corda/core/crypto/SignatureScheme.kt +++ b/core/src/main/kotlin/net/corda/core/crypto/SignatureScheme.kt @@ -1,5 +1,6 @@ package net.corda.core.crypto +import net.corda.core.crypto.internal.providerMap import org.bouncycastle.asn1.x509.AlgorithmIdentifier import java.security.KeyFactory import java.security.Signature @@ -36,11 +37,6 @@ data class SignatureScheme( @Volatile private var memoizedKeyFactory: KeyFactory? = null - internal fun getKeyFactory(factoryFactory: () -> KeyFactory): KeyFactory { - return memoizedKeyFactory ?: run { - val newFactory = factoryFactory() - memoizedKeyFactory = newFactory - newFactory - } - } + internal val keyFactory: KeyFactory + get() = memoizedKeyFactory ?: KeyFactory.getInstance(algorithmName, providerMap[providerName]).also { memoizedKeyFactory = it } } diff --git a/core/src/main/kotlin/net/corda/core/crypto/internal/Curve25519.kt b/core/src/main/kotlin/net/corda/core/crypto/internal/Curve25519.kt new file mode 100644 index 0000000000..7b5a7dd9f8 --- /dev/null +++ b/core/src/main/kotlin/net/corda/core/crypto/internal/Curve25519.kt @@ -0,0 +1,46 @@ + +package net.corda.core.crypto.internal + +import java.math.BigInteger +import java.math.BigInteger.TWO +import java.security.spec.EdECPoint + +/** + * Parameters for Curve25519, as defined in https://www.rfc-editor.org/rfc/rfc7748#section-4.1. + */ +@Suppress("MagicNumber") +object Curve25519 { + val p = TWO.pow(255) - 19.toBigInteger() // 2^255 - 19 + val d = ModP(BigInteger("37095705934669439343138083508754565189542113879843219016388785533085940283555")) + + val EdECPoint.isOnCurve25519: Boolean + // https://www.rfc-editor.org/rfc/rfc8032.html#section-5.1.3 + get() { + if (y >= p) return false + val ySquared = ModP(y).pow(TWO) + val u = ySquared - 1 // y^2 - 1 (mod p) + val v = d * ySquared + 1 // dy^2 + 1 (mod p) + val x = (u / v).pow((p + 3.toBigInteger()).shiftRight(3)) // (u/v)^((p+3)/8) (mod p) + val vxSquared = v * x.pow(TWO) + return vxSquared == u || vxSquared == -u + } + + fun BigInteger.modP(): ModP = ModP(mod(p)) + + private fun BigInteger.additiveInverse(): BigInteger = p - this + + data class ModP(val value: BigInteger) : Comparable { + fun pow(exponent: BigInteger): ModP = ModP(value.modPow(exponent, p)) + + operator fun unaryMinus(): ModP = ModP(value.additiveInverse()) + operator fun plus(other: ModP): ModP = (this.value + other.value).modP() + operator fun plus(other: Int): ModP = (this.value + other.toBigInteger()).modP() + operator fun minus(other: ModP): ModP = (this.value + other.value.additiveInverse()).modP() + operator fun minus(other: Int): ModP = (this.value + other.toBigInteger().additiveInverse()).modP() + operator fun times(other: ModP): ModP = (this.value * other.value).modP() + operator fun div(other: ModP): ModP = (this.value * other.value.modInverse(p)).modP() + + override fun compareTo(other: ModP): Int = this.value.compareTo(other.value) + override fun toString(): String = "$value (mod Curve25519 p)" + } +} diff --git a/core/src/main/kotlin/net/corda/core/crypto/internal/Instances.kt b/core/src/main/kotlin/net/corda/core/crypto/internal/Instances.kt index fc7336f855..64b5feef78 100644 --- a/core/src/main/kotlin/net/corda/core/crypto/internal/Instances.kt +++ b/core/src/main/kotlin/net/corda/core/crypto/internal/Instances.kt @@ -26,9 +26,8 @@ object Instances { private val signatureFactory: SignatureFactory = CachingSignatureFactory() // The provider itself is a very bad key class as hashCode() is expensive and contended. So use name and version instead. - private data class SignatureKey(val algorithm: String, val providerName: String?, val providerVersion: Double?) { - constructor(algorithm: String, provider: Provider?) : this(algorithm, provider?.name, - @Suppress("DEPRECATION") provider?.version) // JDK11: should replace with getVersionStr() (since 9) + private data class SignatureKey(val algorithm: String, val providerName: String?, val providerVersion: String?) { + constructor(algorithm: String, provider: Provider?) : this(algorithm, provider?.name, provider?.versionStr) } private class CachingSignatureFactory : SignatureFactory { diff --git a/core/src/main/kotlin/net/corda/core/crypto/internal/PlatformSecureRandom.kt b/core/src/main/kotlin/net/corda/core/crypto/internal/PlatformSecureRandom.kt index 6e94948c3b..1570c55f82 100644 --- a/core/src/main/kotlin/net/corda/core/crypto/internal/PlatformSecureRandom.kt +++ b/core/src/main/kotlin/net/corda/core/crypto/internal/PlatformSecureRandom.kt @@ -2,8 +2,6 @@ package net.corda.core.crypto.internal import io.netty.util.concurrent.FastThreadLocal -import net.corda.core.crypto.DummySecureRandom -import net.corda.core.utilities.SgxSupport import net.corda.core.utilities.loggerFor import org.apache.commons.lang3.SystemUtils import java.io.DataInputStream @@ -16,21 +14,8 @@ import java.security.SecureRandom import java.security.SecureRandomSpi import kotlin.system.exitProcess -/** - * This has been migrated into a separate class so that it - * is easier to delete from the core-deterministic module. - */ -val platformSecureRandom: () -> SecureRandom = when { - SgxSupport.isInsideEnclave -> { - { DummySecureRandom } - } - else -> { - { sharedSecureRandom } - } -} - class PlatformSecureRandomService(provider: Provider) - : Provider.Service(provider, "SecureRandom", ALGORITHM, PlatformSecureRandomSpi::javaClass.name, null, null) { + : Provider.Service(provider, "SecureRandom", ALGORITHM, PlatformSecureRandomSpi::class.java.name, null, null) { companion object { const val ALGORITHM = "CordaPRNG" @@ -88,8 +73,3 @@ private class LinuxSecureRandomSpi : SecureRandomSpi() { override fun engineGenerateSeed(numBytes: Int): ByteArray = ByteArray(numBytes).apply { engineNextBytes(this) } } - -// This is safe to share because of the underlying implementation of SecureRandomSpi -private val sharedSecureRandom: SecureRandom by lazy(LazyThreadSafetyMode.PUBLICATION) { - SecureRandom.getInstance(PlatformSecureRandomService.ALGORITHM) -} diff --git a/core/src/main/kotlin/net/corda/core/crypto/internal/ProviderMap.kt b/core/src/main/kotlin/net/corda/core/crypto/internal/ProviderMap.kt index df19ab17b3..27bfcafb96 100644 --- a/core/src/main/kotlin/net/corda/core/crypto/internal/ProviderMap.kt +++ b/core/src/main/kotlin/net/corda/core/crypto/internal/ProviderMap.kt @@ -1,24 +1,17 @@ package net.corda.core.crypto.internal import net.corda.core.crypto.CordaSecurityProvider -import net.corda.core.crypto.Crypto.EDDSA_ED25519_SHA512 -import net.corda.core.crypto.Crypto.decodePrivateKey -import net.corda.core.crypto.Crypto.decodePublicKey -import net.corda.core.internal.X509EdDSAEngine -import net.i2p.crypto.eddsa.EdDSAEngine -import net.i2p.crypto.eddsa.EdDSASecurityProvider -import org.bouncycastle.asn1.ASN1ObjectIdentifier -import org.bouncycastle.asn1.pkcs.PrivateKeyInfo -import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo -import org.bouncycastle.jcajce.provider.asymmetric.ec.AlgorithmParametersSpi -import org.bouncycastle.jcajce.provider.util.AsymmetricKeyInfoConverter import org.bouncycastle.jce.provider.BouncyCastleProvider import org.bouncycastle.pqc.jcajce.provider.BouncyCastlePQCProvider import java.security.Provider -import java.security.SecureRandom import java.security.Security import java.util.Collections.unmodifiableMap +val sunEcProvider = checkNotNull(Security.getProvider("SunEC")).also { + // Insert Secp256k1SupportProvider just in-front of SunEC for adding back support for secp256k1 + Security.insertProviderAt(Secp256k1SupportProvider(), Security.getProviders().indexOf(it)) +} + val cordaSecurityProvider = CordaSecurityProvider().also { // Among the others, we should register [CordaSecurityProvider] as the first provider, to ensure that when invoking [SecureRandom()] // the [platformSecureRandom] is returned (which is registered in CordaSecurityProvider). @@ -29,40 +22,8 @@ val cordaSecurityProvider = CordaSecurityProvider().also { Security.insertProviderAt(it, 1) // The position is 1-based. } -// OID taken from https://tools.ietf.org/html/draft-ietf-curdle-pkix-00 -val `id-Curve25519ph` = ASN1ObjectIdentifier("1.3.101.112") -val cordaBouncyCastleProvider = BouncyCastleProvider().apply { - putAll(EdDSASecurityProvider()) - // Override the normal EdDSA engine with one which can handle X509 keys. - put("Signature.${EdDSAEngine.SIGNATURE_ALGORITHM}", X509EdDSAEngine::class.java.name) - put("Signature.Ed25519", X509EdDSAEngine::class.java.name) - addKeyInfoConverter(`id-Curve25519ph`, object : AsymmetricKeyInfoConverter { - override fun generatePublic(keyInfo: SubjectPublicKeyInfo) = decodePublicKey(EDDSA_ED25519_SHA512, keyInfo.encoded) - override fun generatePrivate(keyInfo: PrivateKeyInfo) = decodePrivateKey(EDDSA_ED25519_SHA512, keyInfo.encoded) - }) - // Required due to [X509CRL].verify() reported issues in network-services after BC 1.60 update. - put("AlgorithmParameters.SHA256WITHECDSA", AlgorithmParametersSpi::class.java.name) -}.also { - // This registration is needed for reading back EdDSA key from java keystore. - // TODO: Find a way to make JKS work with bouncy castle provider or implement our own provide so we don't have to register bouncy castle provider. +val cordaBouncyCastleProvider = BouncyCastleProvider().also { Security.addProvider(it) - - // Remove providers that class with bouncy castle - val bcProviders = it.keys - - // JDK 17: Add SunEC provider as lowest priority, as we use Bouncycastle for EDDSA - // and remove amy algorithms that conflict with Bouncycastle - val sunEC = Security.getProvider("SunEC") - if (sunEC != null) { - Security.removeProvider("SunEC") - Security.addProvider(sunEC) - - for(alg in sunEC.keys) { - if (bcProviders.contains(alg)) { - sunEC.remove(alg) - } - } - } } val bouncyCastlePQCProvider = BouncyCastlePQCProvider().apply { @@ -75,8 +36,6 @@ val bouncyCastlePQCProvider = BouncyCastlePQCProvider().apply { // i.e. if someone removes a Provider and then he/she adds a new one with the same name. // The val is immutable to avoid any harmful state changes. internal val providerMap: Map = unmodifiableMap( - listOf(cordaBouncyCastleProvider, cordaSecurityProvider, bouncyCastlePQCProvider) + listOf(sunEcProvider, cordaBouncyCastleProvider, cordaSecurityProvider, bouncyCastlePQCProvider) .associateByTo(LinkedHashMap(), Provider::getName) ) - -fun platformSecureRandomFactory(): SecureRandom = platformSecureRandom() // To minimise diff of CryptoUtils against open-source. diff --git a/core/src/main/kotlin/net/corda/core/crypto/internal/Secp256k1SupportProvider.kt b/core/src/main/kotlin/net/corda/core/crypto/internal/Secp256k1SupportProvider.kt new file mode 100644 index 0000000000..5f7f669b1b --- /dev/null +++ b/core/src/main/kotlin/net/corda/core/crypto/internal/Secp256k1SupportProvider.kt @@ -0,0 +1,188 @@ +@file:Suppress("MagicNumber") + +package net.corda.core.crypto.internal + +import org.bouncycastle.jce.provider.BouncyCastleProvider +import java.math.BigInteger +import java.math.BigInteger.ZERO +import java.security.AlgorithmParameters +import java.security.KeyPair +import java.security.KeyPairGeneratorSpi +import java.security.PrivateKey +import java.security.Provider +import java.security.PublicKey +import java.security.SecureRandom +import java.security.Signature +import java.security.SignatureSpi +import java.security.interfaces.ECPrivateKey +import java.security.interfaces.ECPublicKey +import java.security.spec.AlgorithmParameterSpec +import java.security.spec.ECFieldFp +import java.security.spec.ECParameterSpec +import java.security.spec.ECPoint +import java.security.spec.EllipticCurve +import java.security.spec.NamedParameterSpec + +/** + * Augment the SunEC provider with secp256k1 curve support by delegating to [BouncyCastleProvider] when secp256k1 keys or params are + * requested. Otherwise delegates to SunEC. + */ +class Secp256k1SupportProvider : Provider("Secp256k1Support", "1.0", "Augmenting SunEC with support for the secp256k1 curve via BC") { + init { + put("Signature.SHA256withECDSA", Secp256k1SupportSignatureSpi::class.java.name) + put("KeyPairGenerator.EC", Secp256k1SupportKeyPairGeneratorSpi::class.java.name) + put("AlgorithmParameters.EC", "sun.security.util.ECParameters") + put("KeyFactory.EC", "sun.security.ec.ECKeyFactory") + } + + class Secp256k1SupportSignatureSpi : SignatureSpi() { + private lateinit var sunEc: Signature + private lateinit var bc: Signature + private lateinit var selected: Signature + + override fun engineInitVerify(publicKey: PublicKey?) { + selectProvider((publicKey as? ECPublicKey)?.params) + selected.initVerify(publicKey) + } + + override fun engineInitSign(privateKey: PrivateKey?) { + selectProvider((privateKey as? ECPrivateKey)?.params) + selected.initSign(privateKey) + } + + override fun engineSetParameter(params: AlgorithmParameterSpec?) { + selectProvider(params) + // The BC implementation throws UnsupportedOperationException, so we just avoid calling it. + if (selected !== bc) { + selected.setParameter(params) + } + } + + private fun selectProvider(params: AlgorithmParameterSpec?) { + if (params.isSecp256k1) { + if (!::bc.isInitialized) { + bc = Signature.getInstance("SHA256withECDSA", cordaBouncyCastleProvider) + } + selected = bc + } else { + selectSunEc() + } + } + + private fun selectSunEc() { + if (!::sunEc.isInitialized) { + sunEc = Signature.getInstance("SHA256withECDSA", sunEcProvider) + } + selected = sunEc + } + + override fun engineUpdate(b: Byte) { + defaultToSunEc() + selected.update(b) + } + + override fun engineUpdate(b: ByteArray?, off: Int, len: Int) { + defaultToSunEc() + selected.update(b, off, len) + } + + override fun engineSign(): ByteArray { + defaultToSunEc() + return selected.sign() + } + + override fun engineVerify(sigBytes: ByteArray?): Boolean { + defaultToSunEc() + return selected.verify(sigBytes) + } + + override fun engineGetParameters(): AlgorithmParameters { + defaultToSunEc() + return selected.parameters + } + + @Deprecated("Deprecated in Java") + @Suppress("DEPRECATION") + override fun engineSetParameter(param: String?, value: Any?) { + defaultToSunEc() + selected.setParameter(param, value) + } + + @Deprecated("Deprecated in Java") + @Suppress("DEPRECATION") + override fun engineGetParameter(param: String?): Any { + defaultToSunEc() + return selected.getParameter(param) + } + + private fun defaultToSunEc() { + // Even though it's probably a bug to start using the Signature object without first calling one of the intialize methods, + // default it to SunEC provider anyway and let it deal with the issue. + if (!::selected.isInitialized) { + selectSunEc() + } + } + } + + class Secp256k1SupportKeyPairGeneratorSpi : KeyPairGeneratorSpi() { + // The methods in KeyPairGeneratorSpi are public, which allows us to directly call them. This is not the case with SignatureSpi (above). + private lateinit var sunEc: KeyPairGeneratorSpi + private lateinit var bc: KeyPairGeneratorSpi + private lateinit var selected: KeyPairGeneratorSpi + + override fun initialize(keysize: Int, random: SecureRandom?) { + selectSunEc() + selected.initialize(keysize, random) + } + + override fun initialize(params: AlgorithmParameterSpec?, random: SecureRandom?) { + if (params.isSecp256k1) { + if (!::bc.isInitialized) { + bc = org.bouncycastle.jcajce.provider.asymmetric.ec.KeyPairGeneratorSpi.EC() + } + selected = bc + } else { + selectSunEc() + } + selected.initialize(params, random) + } + + private fun selectSunEc() { + if (!::sunEc.isInitialized) { + sunEc = sunEcProvider.getService("KeyPairGenerator", "EC").newInstance(null) as KeyPairGeneratorSpi + } + selected = sunEc + } + + override fun generateKeyPair(): KeyPair { + if (!::selected.isInitialized) { + // In-case initialize wasn't first called, default to SunEC + selectSunEc() + } + return selected.generateKeyPair() + } + } +} + +/** + * Parameters for the secp256k1 curve + */ +private object Secp256k1 { + val n = BigInteger("fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141", 16) + val g = ECPoint( + BigInteger("79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", 16), + BigInteger("483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8", 16) + ) + val curve = EllipticCurve( + ECFieldFp(BigInteger("fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f", 16)), + ZERO, + 7.toBigInteger() + ) +} + +val AlgorithmParameterSpec?.isSecp256k1: Boolean + get() = when (this) { + is ECParameterSpec -> cofactor == 1 && order == Secp256k1.n && curve == Secp256k1.curve && generator == Secp256k1.g + is NamedParameterSpec -> name.equals("secp256k1", ignoreCase = true) + else -> false + } diff --git a/core/src/main/kotlin/net/corda/core/internal/StatePointerSearch.kt b/core/src/main/kotlin/net/corda/core/internal/StatePointerSearch.kt index e552172844..b17f50e0c8 100644 --- a/core/src/main/kotlin/net/corda/core/internal/StatePointerSearch.kt +++ b/core/src/main/kotlin/net/corda/core/internal/StatePointerSearch.kt @@ -13,7 +13,7 @@ import java.util.* class StatePointerSearch(val state: ContractState) { private companion object { // Classes in these packages should not be part of a search. - private val blackListedPackages = setOf("java.", "javax.", "org.bouncycastle.", "net.i2p.crypto.") + private val blackListedPackages = setOf("java.", "javax.", "org.bouncycastle.") } // Type required for traversal. diff --git a/core/src/main/kotlin/net/corda/core/internal/X509EdDSAEngine.kt b/core/src/main/kotlin/net/corda/core/internal/X509EdDSAEngine.kt deleted file mode 100644 index 94c4897da8..0000000000 --- a/core/src/main/kotlin/net/corda/core/internal/X509EdDSAEngine.kt +++ /dev/null @@ -1,59 +0,0 @@ -package net.corda.core.internal - -import net.corda.core.crypto.Crypto -import net.i2p.crypto.eddsa.EdDSAEngine -import net.i2p.crypto.eddsa.EdDSAPublicKey -import java.security.AlgorithmParameters -import java.security.InvalidKeyException -import java.security.MessageDigest -import java.security.PrivateKey -import java.security.PublicKey -import java.security.SecureRandom -import java.security.Signature -import java.security.spec.AlgorithmParameterSpec -import java.security.spec.X509EncodedKeySpec - -/** - * Wrapper around [EdDSAEngine] which can intelligently rewrite X509Keys to a [EdDSAPublicKey]. This is a temporary - * solution until this is integrated upstream and/or a custom certificate factory implemented to force the correct - * key type. Only intercepts public keys passed into [engineInitVerify], as there is no equivalent issue with private - * keys. - */ -class X509EdDSAEngine : Signature { - private val engine: EdDSAEngine - - constructor() : super(EdDSAEngine.SIGNATURE_ALGORITHM) { - engine = EdDSAEngine() - } - - constructor(digest: MessageDigest) : super(EdDSAEngine.SIGNATURE_ALGORITHM) { - engine = EdDSAEngine(digest) - } - - override fun engineInitSign(privateKey: PrivateKey) = engine.initSign(privateKey) - - override fun engineInitSign(privateKey: PrivateKey, random: SecureRandom) = engine.initSign(privateKey, random) - - override fun engineInitVerify(publicKey: PublicKey) { - val parsedKey = try { - publicKey as? EdDSAPublicKey ?: EdDSAPublicKey(X509EncodedKeySpec(Crypto.encodePublicKey(publicKey))) - } catch (e: Exception) { - throw (InvalidKeyException(e.message)) - } - engine.initVerify(parsedKey) - } - - override fun engineSign(): ByteArray = engine.sign() - override fun engineVerify(sigBytes: ByteArray): Boolean = engine.verify(sigBytes) - - override fun engineUpdate(b: Byte) = engine.update(b) - override fun engineUpdate(b: ByteArray, off: Int, len: Int) = engine.update(b, off, len) - - override fun engineGetParameters(): AlgorithmParameters = engine.parameters - override fun engineSetParameter(params: AlgorithmParameterSpec) = engine.setParameter(params) - @Suppress("DEPRECATION", "OverridingDeprecatedMember") - override fun engineGetParameter(param: String): Any = engine.getParameter(param) - - @Suppress("DEPRECATION", "OverridingDeprecatedMember") - override fun engineSetParameter(param: String, value: Any?) = engine.setParameter(param, value) -} diff --git a/core/src/main/kotlin/net/corda/core/utilities/SgxSupport.kt b/core/src/main/kotlin/net/corda/core/utilities/SgxSupport.kt deleted file mode 100644 index b4691cb1e0..0000000000 --- a/core/src/main/kotlin/net/corda/core/utilities/SgxSupport.kt +++ /dev/null @@ -1,8 +0,0 @@ -package net.corda.core.utilities - -object SgxSupport { - @JvmStatic - val isInsideEnclave: Boolean by lazy { - (System.getProperty("os.name") == "Linux") && (System.getProperty("java.vm.name") == "Avian (Corda)") - } -} diff --git a/core/src/test/java/net/corda/core/internal/X509EdDSAEngineTest.java b/core/src/test/java/net/corda/core/internal/X509EdDSAEngineTest.java deleted file mode 100644 index e353db8043..0000000000 --- a/core/src/test/java/net/corda/core/internal/X509EdDSAEngineTest.java +++ /dev/null @@ -1,135 +0,0 @@ -package net.corda.core.internal; - -import net.corda.core.crypto.Crypto; -import net.i2p.crypto.eddsa.EdDSAEngine; -import net.i2p.crypto.eddsa.EdDSAPublicKey; -import org.junit.Test; -import sun.security.util.BitArray; -import sun.security.util.ObjectIdentifier; -import sun.security.x509.AlgorithmId; -import sun.security.x509.X509Key; - -import java.io.IOException; -import java.math.BigInteger; -import java.security.InvalidKeyException; -import java.security.KeyPair; -import java.security.SignatureException; -import java.util.Random; - -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.junit.Assert.assertTrue; - -/** - * JDK11 upgrade: rewritten in Java to gain access to private internal JDK classes via module directives (not available to Kotlin compiler): - * import sun.security.util.BitArray; - * import sun.security.util.ObjectIdentifier; - * import sun.security.x509.AlgorithmId; - * import sun.security.x509.X509Key; - */ -public class X509EdDSAEngineTest { - private static final long SEED = 20170920L; - private static final int TEST_DATA_SIZE = 2000; - - // offset into an EdDSA header indicating where the key header and actual key start - // in the underlying byte array - private static final int KEY_HEADER_START = 9; - private static final int KEY_START = 12; - - private X509Key toX509Key(EdDSAPublicKey publicKey) throws IOException, InvalidKeyException { - byte[] internals = publicKey.getEncoded(); - - // key size in the header includes the count unused bits at the end of the key - // [keyHeaderStart + 2] but NOT the key header ID [keyHeaderStart] so the - // actual length of the key blob is size - 1 - int keySize = (internals[KEY_HEADER_START + 1]) - 1; - - byte[] key = new byte[keySize]; - System.arraycopy(internals, KEY_START, key, 0, keySize); - - // 1.3.101.102 is the EdDSA OID - return new TestX509Key(new AlgorithmId(ObjectIdentifier.of("1.3.101.112")), new BitArray(keySize * 8, key)); - } - - private static class TestX509Key extends X509Key { - TestX509Key(AlgorithmId algorithmId, BitArray key) throws InvalidKeyException { - this.algid = algorithmId; - this.setKey(key); - this.encode(); - } - } - - /** - * Put the X509EdDSA engine through basic tests to verify that the functions are hooked up correctly. - */ - @Test - public void SignAndVerify() throws InvalidKeyException, SignatureException { - X509EdDSAEngine engine = new X509EdDSAEngine(); - KeyPair keyPair = Crypto.deriveKeyPairFromEntropy(Crypto.EDDSA_ED25519_SHA512, BigInteger.valueOf(SEED)); - EdDSAPublicKey publicKey = (EdDSAPublicKey) keyPair.getPublic(); - byte[] randomBytes = new byte[TEST_DATA_SIZE]; - new Random(SEED).nextBytes(randomBytes); - engine.initSign(keyPair.getPrivate()); - engine.update(randomBytes[0]); - engine.update(randomBytes, 1, randomBytes.length - 1); - - // Now verify the signature - byte[] signature = engine.sign(); - - engine.initVerify(publicKey); - engine.update(randomBytes); - assertTrue(engine.verify(signature)); - } - - /** - * Verify that signing with an X509Key wrapped EdDSA key works. - */ - @Test - public void SignAndVerifyWithX509Key() throws InvalidKeyException, SignatureException, IOException { - X509EdDSAEngine engine = new X509EdDSAEngine(); - KeyPair keyPair = Crypto.deriveKeyPairFromEntropy(Crypto.EDDSA_ED25519_SHA512, BigInteger.valueOf(SEED + 1)); - X509Key publicKey = toX509Key((EdDSAPublicKey) keyPair.getPublic()); - byte[] randomBytes = new byte[TEST_DATA_SIZE]; - new Random(SEED + 1).nextBytes(randomBytes); - engine.initSign(keyPair.getPrivate()); - engine.update(randomBytes[0]); - engine.update(randomBytes, 1, randomBytes.length - 1); - - // Now verify the signature - byte[] signature = engine.sign(); - - engine.initVerify(publicKey); - engine.update(randomBytes); - assertTrue(engine.verify(signature)); - } - - /** - * Verify that signing with an X509Key wrapped EdDSA key succeeds when using the underlying EdDSAEngine. - */ - @Test - public void SignAndVerifyWithX509KeyAndOldEngineFails() throws InvalidKeyException, SignatureException, IOException { - X509EdDSAEngine engine = new X509EdDSAEngine(); - KeyPair keyPair = Crypto.deriveKeyPairFromEntropy(Crypto.EDDSA_ED25519_SHA512, BigInteger.valueOf(SEED + 1)); - X509Key publicKey = toX509Key((EdDSAPublicKey) keyPair.getPublic()); - byte[] randomBytes = new byte[TEST_DATA_SIZE]; - new Random(SEED + 1).nextBytes(randomBytes); - engine.initSign(keyPair.getPrivate()); - engine.update(randomBytes[0]); - engine.update(randomBytes, 1, randomBytes.length - 1); - - // Now verify the signature - byte[] signature = engine.sign(); - engine.initVerify(publicKey); - engine.update(randomBytes); - engine.verify(signature); - } - - /** Verify will fail if the input public key cannot be converted to EdDSA public key. */ - @Test - public void verifyWithNonSupportedKeyTypeFails() { - EdDSAEngine engine = new EdDSAEngine(); - KeyPair keyPair = Crypto.deriveKeyPairFromEntropy(Crypto.ECDSA_SECP256K1_SHA256, BigInteger.valueOf(SEED)); - assertThatExceptionOfType(InvalidKeyException.class).isThrownBy(() -> - engine.initVerify(keyPair.getPublic()) - ); - } -} diff --git a/core/src/test/kotlin/net/corda/core/crypto/CryptoUtilsTest.kt b/core/src/test/kotlin/net/corda/core/crypto/CryptoUtilsTest.kt index b011d029c6..d5c125b7bb 100644 --- a/core/src/test/kotlin/net/corda/core/crypto/CryptoUtilsTest.kt +++ b/core/src/test/kotlin/net/corda/core/crypto/CryptoUtilsTest.kt @@ -8,15 +8,10 @@ import net.corda.core.crypto.Crypto.RSA_SHA256 import net.corda.core.crypto.Crypto.SPHINCS256_SHA256 import net.corda.core.crypto.internal.PlatformSecureRandomService import net.corda.core.utilities.OpaqueBytes -import net.i2p.crypto.eddsa.EdDSAKey -import net.i2p.crypto.eddsa.EdDSAPrivateKey -import net.i2p.crypto.eddsa.EdDSAPublicKey -import net.i2p.crypto.eddsa.math.GroupElement -import net.i2p.crypto.eddsa.spec.EdDSANamedCurveSpec -import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable -import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec import org.apache.commons.lang3.ArrayUtils.EMPTY_BYTE_ARRAY +import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThatIllegalArgumentException +import org.assertj.core.api.Assertions.assertThatThrownBy import org.bouncycastle.asn1.pkcs.PrivateKeyInfo import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey @@ -24,15 +19,18 @@ import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey import org.bouncycastle.jce.ECNamedCurveTable import org.bouncycastle.jce.interfaces.ECKey import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec +import org.bouncycastle.math.ec.rfc8032.Ed25519 import org.bouncycastle.pqc.jcajce.provider.sphincs.BCSphincs256PrivateKey import org.bouncycastle.pqc.jcajce.provider.sphincs.BCSphincs256PublicKey import org.junit.Assert.assertNotEquals -import org.junit.Ignore import org.junit.Test import java.math.BigInteger import java.security.KeyPairGenerator import java.security.SecureRandom import java.security.Security +import java.security.interfaces.EdECPrivateKey +import java.security.interfaces.EdECPublicKey +import java.security.spec.NamedParameterSpec import java.util.Random import kotlin.test.assertEquals import kotlin.test.assertFalse @@ -132,11 +130,8 @@ class CryptoUtilsTest { // test on malformed signatures (even if they change for 1 bit) signedData[0] = signedData[0].inc() - try { + assertThatThrownBy { Crypto.doVerify(pubKey, signedData, testBytes) - fail() - } catch (e: Exception) { - // expected } } @@ -498,9 +493,9 @@ class CryptoUtilsTest { val (privEd, pubEd) = keyPairEd assertEquals(privEd.algorithm, "EdDSA") - assertEquals((privEd as EdDSAKey).params, EdDSANamedCurveTable.getByName("ED25519")) + assertEquals((privEd as EdECPrivateKey).params.name, NamedParameterSpec.ED25519.name) assertEquals(pubEd.algorithm, "EdDSA") - assertEquals((pubEd as EdDSAKey).params, EdDSANamedCurveTable.getByName("ED25519")) + assertEquals((pubEd as EdECPublicKey).params.name, NamedParameterSpec.ED25519.name) } @Test(timeout=300_000) @@ -659,18 +654,23 @@ class CryptoUtilsTest { @Test(timeout=300_000) fun `Check EdDSA public key on curve`() { - val keyPairEdDSA = Crypto.generateKeyPair(EDDSA_ED25519_SHA512) - val pubEdDSA = keyPairEdDSA.public - assertTrue(Crypto.publicKeyOnCurve(EDDSA_ED25519_SHA512, pubEdDSA)) - // Use R1 curve for check. - assertFalse(Crypto.publicKeyOnCurve(ECDSA_SECP256R1_SHA256, pubEdDSA)) - // Check for point at infinity. - val pubKeySpec = EdDSAPublicKeySpec((EDDSA_ED25519_SHA512.algSpec as EdDSANamedCurveSpec).curve.getZero(GroupElement.Representation.P3), EDDSA_ED25519_SHA512.algSpec as EdDSANamedCurveSpec) - assertFalse(Crypto.publicKeyOnCurve(EDDSA_ED25519_SHA512, EdDSAPublicKey(pubKeySpec))) + repeat(100) { + val keyPairEdDSA = Crypto.generateKeyPair(EDDSA_ED25519_SHA512) + val pubEdDSA = keyPairEdDSA.public + assertTrue(Crypto.publicKeyOnCurve(EDDSA_ED25519_SHA512, pubEdDSA)) + // Use R1 curve for check. + assertFalse(Crypto.publicKeyOnCurve(ECDSA_SECP256R1_SHA256, pubEdDSA)) + } + val invalidKey = run { + val bytes = ByteArray(Ed25519.PUBLIC_KEY_SIZE).also { it[0] = 2 } + val encoded = SubjectPublicKeyInfo(EDDSA_ED25519_SHA512.signatureOID, bytes).encoded + Crypto.decodePublicKey(encoded) + } + assertThat(invalidKey).isInstanceOf(EdECPublicKey::class.java) + assertThat(Crypto.publicKeyOnCurve(EDDSA_ED25519_SHA512, invalidKey)).isFalse() } @Test(timeout = 300_000) - @Ignore("TODO JDK17: Fixme") fun `Unsupported EC public key type on curve`() { val keyGen = KeyPairGenerator.getInstance("EC") // sun.security.ec.ECPublicKeyImpl keyGen.initialize(256, newSecureRandom()) @@ -772,10 +772,8 @@ class CryptoUtilsTest { // Check scheme. assertEquals(priv.algorithm, dpriv.algorithm) assertEquals(pub.algorithm, dpub.algorithm) - assertTrue(dpriv is EdDSAPrivateKey) - assertTrue(dpub is EdDSAPublicKey) - assertEquals((dpriv as EdDSAKey).params, EdDSANamedCurveTable.getByName("ED25519")) - assertEquals((dpub as EdDSAKey).params, EdDSANamedCurveTable.getByName("ED25519")) + assertEquals((dpriv as EdECPrivateKey).params.name, NamedParameterSpec.ED25519.name) + assertEquals((dpub as EdECPublicKey).params.name, NamedParameterSpec.ED25519.name) assertEquals(Crypto.findSignatureScheme(dpriv), EDDSA_ED25519_SHA512) assertEquals(Crypto.findSignatureScheme(dpub), EDDSA_ED25519_SHA512) diff --git a/core/src/test/kotlin/net/corda/core/crypto/EdDSATests.kt b/core/src/test/kotlin/net/corda/core/crypto/EdDSATests.kt index 6a30e5e2d6..666d2b7ce0 100644 --- a/core/src/test/kotlin/net/corda/core/crypto/EdDSATests.kt +++ b/core/src/test/kotlin/net/corda/core/crypto/EdDSATests.kt @@ -1,17 +1,16 @@ package net.corda.core.crypto +import net.corda.core.crypto.Crypto.EDDSA_ED25519_SHA512 +import net.corda.core.crypto.internal.Instances.withSignature import net.corda.core.utilities.hexToByteArray import net.corda.core.utilities.toHex -import net.i2p.crypto.eddsa.EdDSAPrivateKey -import net.i2p.crypto.eddsa.EdDSASecurityProvider -import net.i2p.crypto.eddsa.spec.EdDSANamedCurveSpec -import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec +import org.assertj.core.api.Assertions.assertThat +import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo import org.junit.Test import java.security.PrivateKey -import java.security.Signature -import java.util.Locale -import kotlin.test.assertEquals -import kotlin.test.assertNotEquals +import java.security.spec.EdECPrivateKeySpec +import java.security.spec.NamedParameterSpec +import java.security.spec.X509EncodedKeySpec /** * Testing PureEdDSA Ed25519 using test vectors from https://tools.ietf.org/html/rfc8032#section-7.1 @@ -19,8 +18,6 @@ import kotlin.test.assertNotEquals class EdDSATests { @Test(timeout=300_000) fun `PureEdDSA Ed25519 test vectors`() { - val edParams = Crypto.EDDSA_ED25519_SHA512.algSpec as EdDSANamedCurveSpec - // MESSAGE (length 0 bytes). val testVector1 = SignatureTestVector( "9d61b19deffd5a60ba844af492ec2cc4" + @@ -152,11 +149,24 @@ class EdDSATests { "3dca179c138ac17ad9bef1177331a704" ) + val keyFactory = EDDSA_ED25519_SHA512.keyFactory + val testVectors = listOf(testVector1, testVector2, testVector3, testVector1024, testVectorSHAabc) - testVectors.forEach { - val privateKey = EdDSAPrivateKey(EdDSAPrivateKeySpec(it.privateKeyHex.hexToByteArray(), edParams)) - assertEquals(it.signatureOutputHex, doSign(privateKey, it.messageToSignHex.hexToByteArray()).toHex() - .lowercase(Locale.getDefault())) + testVectors.forEach { testVector -> + val messageBytes = testVector.messageToSignHex.hexToByteArray() + val signatureBytes = testVector.signatureOutputHex.hexToByteArray() + // Check the private key produces the expected signature + val privateKey = keyFactory.generatePrivate(EdECPrivateKeySpec(NamedParameterSpec.ED25519, testVector.privateKeyHex.hexToByteArray())) + assertThat(doSign(privateKey, messageBytes)).isEqualTo(signatureBytes) + // Check the public key verifies the signature + val result = withSignature(EDDSA_ED25519_SHA512) { signature -> + val publicKeyInfo = SubjectPublicKeyInfo(EDDSA_ED25519_SHA512.signatureOID, testVector.publicKeyHex.hexToByteArray()) + val publicKey = keyFactory.generatePublic(X509EncodedKeySpec(publicKeyInfo.encoded)) + signature.initVerify(publicKey) + signature.update(messageBytes) + signature.verify(signatureBytes) + } + assertThat(result).isTrue() } // Test vector for the variant Ed25519ctx, expected to fail. @@ -172,9 +182,8 @@ class EdDSATests { "5a5ca2df6668346291c2043d4eb3e90d" ) - val privateKey = EdDSAPrivateKey(EdDSAPrivateKeySpec(testVectorEd25519ctx.privateKeyHex.hexToByteArray(), edParams)) - assertNotEquals(testVectorEd25519ctx.signatureOutputHex, doSign(privateKey, testVectorEd25519ctx.messageToSignHex.hexToByteArray()).toHex() - .lowercase(Locale.getDefault())) + val privateKey = keyFactory.generatePrivate(EdECPrivateKeySpec(NamedParameterSpec.ED25519, testVectorEd25519ctx.privateKeyHex.hexToByteArray())) + assertThat(doSign(privateKey, testVectorEd25519ctx.messageToSignHex.hexToByteArray()).toHex().lowercase()).isNotEqualTo(testVectorEd25519ctx.signatureOutputHex) } /** A test vector object for digital signature schemes. */ @@ -185,9 +194,10 @@ class EdDSATests { // Required to implement a custom doSign function, because Corda's Crypto.doSign does not allow empty messages (testVector1). private fun doSign(privateKey: PrivateKey, clearData: ByteArray): ByteArray { - val signature = Signature.getInstance(Crypto.EDDSA_ED25519_SHA512.signatureName, EdDSASecurityProvider()) - signature.initSign(privateKey) - signature.update(clearData) - return signature.sign() + return withSignature(EDDSA_ED25519_SHA512) { signature -> + signature.initSign(privateKey) + signature.update(clearData) + signature.sign() + } } } diff --git a/detekt-baseline.xml b/detekt-baseline.xml index 01a15a1059..d3fdd92468 100644 --- a/detekt-baseline.xml +++ b/detekt-baseline.xml @@ -1426,7 +1426,6 @@ TooManyFunctions:ActionExecutorImpl.kt$ActionExecutorImpl : ActionExecutor TooManyFunctions:AppendOnlyPersistentMap.kt$AppendOnlyPersistentMapBase<K, V, E, out EK> TooManyFunctions:ArtemisTcpTransport.kt$ArtemisTcpTransport$Companion - TooManyFunctions:BCCryptoService.kt$BCCryptoService : CryptoService TooManyFunctions:BFTSmart.kt$BFTSmart$Replica : DefaultRecoverable TooManyFunctions:BaseTransaction.kt$BaseTransaction : NamedByHash TooManyFunctions:ClassCarpenter.kt$ClassCarpenterImpl : ClassCarpenter @@ -1669,9 +1668,6 @@ WildcardImport:AttachmentsClassLoader.kt$import net.corda.core.serialization.* WildcardImport:AttachmentsClassLoaderStaticContractTests.kt$import net.corda.core.contracts.* WildcardImport:AutoOfferFlow.kt$import net.corda.core.flows.* - WildcardImport:BCCryptoService.kt$import java.security.* - WildcardImport:BCCryptoService.kt$import net.corda.nodeapi.internal.cryptoservice.* - WildcardImport:BCCryptoServiceTests.kt$import java.security.* WildcardImport:BFTNotaryServiceTests.kt$import net.corda.core.crypto.* WildcardImport:BFTNotaryServiceTests.kt$import net.corda.testing.node.internal.* WildcardImport:BFTSmart.kt$import net.corda.core.crypto.* diff --git a/node-api-tests/build.gradle b/node-api-tests/build.gradle index a13fc4d9d6..3ac3d4d775 100644 --- a/node-api-tests/build.gradle +++ b/node-api-tests/build.gradle @@ -15,7 +15,6 @@ dependencies { testImplementation "org.junit.jupiter:junit-jupiter-api:${junit_jupiter_version}" testImplementation "junit:junit:$junit_version" testImplementation "org.mockito.kotlin:mockito-kotlin:$mockito_kotlin_version" - testImplementation "net.i2p.crypto:eddsa:$eddsa_version" testImplementation "com.typesafe:config:$typesafe_config_version" testImplementation "io.dropwizard.metrics:metrics-core:$metrics_version" testImplementation "co.paralleluniverse:quasar-core:$quasar_version" diff --git a/node-api-tests/src/test/kotlin/net/corda/nodeapitests/internal/crypto/X509UtilitiesTest.kt b/node-api-tests/src/test/kotlin/net/corda/nodeapitests/internal/crypto/X509UtilitiesTest.kt index 356be174eb..b8d84467cd 100644 --- a/node-api-tests/src/test/kotlin/net/corda/nodeapitests/internal/crypto/X509UtilitiesTest.kt +++ b/node-api-tests/src/test/kotlin/net/corda/nodeapitests/internal/crypto/X509UtilitiesTest.kt @@ -1,6 +1,5 @@ package net.corda.nodeapitests.internal.crypto - import io.netty.handler.ssl.ClientAuth import io.netty.handler.ssl.SslContextBuilder import io.netty.handler.ssl.SslProvider @@ -52,7 +51,6 @@ import net.corda.testing.core.BOB_NAME import net.corda.testing.core.TestIdentity import net.corda.testing.driver.internal.incrementalPortAllocation import net.corda.testing.internal.createDevIntermediateCaCertPath -import net.i2p.crypto.eddsa.EdDSAPrivateKey import org.assertj.core.api.Assertions.assertThat import org.bouncycastle.asn1.x509.AuthorityKeyIdentifier import org.bouncycastle.asn1.x509.BasicConstraints @@ -60,9 +58,7 @@ import org.bouncycastle.asn1.x509.CRLDistPoint import org.bouncycastle.asn1.x509.Extension import org.bouncycastle.asn1.x509.KeyUsage import org.bouncycastle.asn1.x509.SubjectKeyIdentifier -import org.bouncycastle.jcajce.provider.asymmetric.edec.BCEdDSAPrivateKey import org.bouncycastle.pqc.jcajce.provider.sphincs.BCSphincs256PrivateKey -import org.junit.Ignore import org.junit.Rule import org.junit.Test import org.junit.rules.TemporaryFolder @@ -77,7 +73,8 @@ import java.security.KeyPair import java.security.PrivateKey import java.security.cert.CertPath import java.security.cert.X509Certificate -import java.util.* +import java.security.interfaces.EdECPrivateKey +import java.util.Date import javax.net.ssl.SSLContext import javax.net.ssl.SSLParameters import javax.net.ssl.SSLServerSocket @@ -93,7 +90,6 @@ import kotlin.test.assertNull import kotlin.test.assertTrue import kotlin.test.fail -@Ignore("TODO JDK17: Fixme") class X509UtilitiesTest { private companion object { val ALICE = TestIdentity(ALICE_NAME, 70).party @@ -122,9 +118,9 @@ class X509UtilitiesTest { val schemeToKeyTypes = listOf( // By default, JKS returns SUN EC key. - Triple(ECDSA_SECP256R1_SHA256,java.security.interfaces.ECPrivateKey::class.java, org.bouncycastle.jce.interfaces.ECPrivateKey::class.java), - Triple(ECDSA_SECP256K1_SHA256,java.security.interfaces.ECPrivateKey::class.java, org.bouncycastle.jce.interfaces.ECPrivateKey::class.java), - Triple(EDDSA_ED25519_SHA512, EdDSAPrivateKey::class.java, EdDSAPrivateKey::class.java), + Triple(ECDSA_SECP256R1_SHA256, java.security.interfaces.ECPrivateKey::class.java, org.bouncycastle.jce.interfaces.ECPrivateKey::class.java), + Triple(ECDSA_SECP256K1_SHA256, java.security.interfaces.ECPrivateKey::class.java, org.bouncycastle.jce.interfaces.ECPrivateKey::class.java), + Triple(EDDSA_ED25519_SHA512, EdECPrivateKey::class.java, EdECPrivateKey::class.java), // By default, JKS returns SUN RSA key. Triple(SPHINCS256_SHA256, BCSphincs256PrivateKey::class.java, BCSphincs256PrivateKey::class.java) ) @@ -136,8 +132,7 @@ class X509UtilitiesTest { @Test(timeout=300_000) fun `create valid self-signed CA certificate`() { - Crypto.supportedSignatureSchemes().filter { it != COMPOSITE_KEY - && ( it != SPHINCS256_SHA256)}.forEach { validSelfSignedCertificate(it) } + Crypto.supportedSignatureSchemes().filter { it != COMPOSITE_KEY }.forEach { validSelfSignedCertificate(it) } } private fun validSelfSignedCertificate(signatureScheme: SignatureScheme) { @@ -158,7 +153,7 @@ class X509UtilitiesTest { @Test(timeout=300_000) fun `load and save a PEM file certificate`() { - Crypto.supportedSignatureSchemes().filter { it != COMPOSITE_KEY }.forEach { loadSavePEMCert(it) } + Crypto.supportedSignatureSchemes().filter { it != COMPOSITE_KEY }.forEach(::loadSavePEMCert) } private fun loadSavePEMCert(signatureScheme: SignatureScheme) { @@ -172,8 +167,7 @@ class X509UtilitiesTest { @Test(timeout=300_000) fun `create valid server certificate chain`() { - certChainSchemeCombinations.filter{ it.first != SPHINCS256_SHA256 } - .forEach { createValidServerCertChain(it.first, it.second) } + certChainSchemeCombinations.forEach { createValidServerCertChain(it.first, it.second) } } private fun createValidServerCertChain(signatureSchemeRoot: SignatureScheme, signatureSchemeChild: SignatureScheme) { @@ -451,13 +445,11 @@ class X509UtilitiesTest { schemeToKeyTypes.forEach { getCorrectKeyFromKeystore(it.first, it.second, it.third) } } - private fun getCorrectKeyFromKeystore(signatureScheme: SignatureScheme, uncastedClass: Class, castedClass: Class) { + private fun getCorrectKeyFromKeystore(signatureScheme: SignatureScheme, rawClass: Class, supportedClass: Class) { val keyPair = generateKeyPair(signatureScheme) - val (keyFromKeystore, keyFromKeystoreCasted) = storeAndGetKeysFromKeystore(keyPair) - if (uncastedClass == EdDSAPrivateKey::class.java && keyFromKeystore !is BCEdDSAPrivateKey) { - assertThat(keyFromKeystore).isInstanceOf(uncastedClass) - } - assertThat(keyFromKeystoreCasted).isInstanceOf(castedClass) + val (rawKey, supportedKey) = storeAndGetKeysFromKeystore(keyPair) + assertThat(rawKey).isInstanceOf(rawClass) + assertThat(supportedKey).isInstanceOf(supportedClass) } private fun storeAndGetKeysFromKeystore(keyPair: KeyPair): Pair { @@ -466,9 +458,9 @@ class X509UtilitiesTest { val keyStore = loadOrCreateKeyStore(tempFile("testKeystore.jks"), "keystorepassword") keyStore.setKeyEntry("Key", keyPair.private, "keypassword".toCharArray(), arrayOf(selfSignCert)) - val keyFromKeystore = keyStore.getKey("Key", "keypassword".toCharArray()) - val keyFromKeystoreCasted = keyStore.getSupportedKey("Key", "keypassword") - return Pair(keyFromKeystore, keyFromKeystoreCasted) + val rawKey = keyStore.getKey("Key", "keypassword".toCharArray()) + val supportedKey = keyStore.getSupportedKey("Key", "keypassword") + return Pair(rawKey, supportedKey) } @Test(timeout=300_000) diff --git a/node-api/build.gradle b/node-api/build.gradle index 8b7dbdea93..79e04a203c 100644 --- a/node-api/build.gradle +++ b/node-api/build.gradle @@ -57,7 +57,6 @@ dependencies { implementation "io.reactivex:rxjava:$rxjava_version" implementation "javax.persistence:javax.persistence-api:2.2" implementation "org.hibernate:hibernate-core:$hibernate_version" - implementation "net.i2p.crypto:eddsa:$eddsa_version" implementation "co.paralleluniverse:quasar-osgi-annotations:$quasar_version" runtimeOnly 'com.mattbertolini:liquibase-slf4j:2.0.0' diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/config/CertificateStore.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/config/CertificateStore.kt index 92980beae1..b50e9d8fa3 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/config/CertificateStore.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/config/CertificateStore.kt @@ -78,7 +78,7 @@ interface CertificateStore : Iterable> { } fun setCertPathOnly(alias: String, certificates: List) { - // In case CryptoService and CertificateStore share the same KeyStore (i.e., when BCCryptoService is used), + // In case CryptoService and CertificateStore share the same KeyStore (i.e., when DefaultCryptoService is used), // extract the existing key from the Keystore and store it again along with the new certificate chain. // This is because KeyStores do not support updateKeyEntry and thus we cannot update the certificate chain // without overriding the key entry. diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/ContentSignerBuilder.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/ContentSignerBuilder.kt index bbee9e5d2a..a84fb0ad08 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/ContentSignerBuilder.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/ContentSignerBuilder.kt @@ -1,6 +1,5 @@ package net.corda.nodeapi.internal.crypto -import net.corda.core.crypto.Crypto.SPHINCS256_SHA256 import net.corda.core.crypto.SignatureScheme import net.corda.core.crypto.internal.Instances import org.bouncycastle.asn1.x509.AlgorithmIdentifier @@ -27,9 +26,7 @@ object ContentSignerBuilder { val sig = try { signatureInstance.apply { - // TODO special handling for Sphincs due to a known BouncyCastle's Sphincs bug we reported. - // It is fixed in BC 161b12, so consider updating the below if-statement after updating BouncyCastle. - if (random != null && signatureScheme != SPHINCS256_SHA256) { + if (random != null) { initSign(privateKey, random) } else { initSign(privateKey) @@ -48,7 +45,7 @@ object ContentSignerBuilder { private class SignatureOutputStream(private val sig: Signature, private val optimised: Boolean) : OutputStream() { private var alreadySigned = false - internal val signature: ByteArray by lazy { + val signature: ByteArray by lazy { try { alreadySigned = true sig.sign() diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/X509Utilities.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/X509Utilities.kt index c4d062dfff..c9e17b9773 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/X509Utilities.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/X509Utilities.kt @@ -69,7 +69,7 @@ import kotlin.io.path.reader import kotlin.io.path.writer object X509Utilities { - // Note that this default value only applies to BCCryptoService. Other implementations of CryptoService may have to use different + // Note that this default value only applies to DefaultCryptoService. Other implementations of CryptoService may have to use different // schemes (for instance `UtimacoCryptoService.DEFAULT_IDENTITY_SIGNATURE_SCHEME`). val DEFAULT_IDENTITY_SIGNATURE_SCHEME = Crypto.EDDSA_ED25519_SHA512 val DEFAULT_TLS_SIGNATURE_SCHEME = Crypto.ECDSA_SECP256R1_SHA256 @@ -303,10 +303,10 @@ object X509Utilities { crlDistPoint: String? = null, crlIssuer: X500Name? = null): X509Certificate { val builder = createPartialCertificate(certificateType, issuer, issuerPublicKey, subject, subjectPublicKey, validityWindow, nameConstraints, crlDistPoint, crlIssuer) - return builder.build(issuerSigner).run { - require(isValidOn(Date())){"Certificate is not valid at instant now"} - toJca() - } + val certificate = builder.build(issuerSigner).toJca() + certificate.checkValidity(Date()) + certificate.verify(issuerPublicKey) + return certificate } /** @@ -340,18 +340,22 @@ object X509Utilities { validityWindow, nameConstraints, crlDistPoint, - crlIssuer) - return builder.build(signer).run { - require(isValidOn(Date())){"Certificate is not valid at instant now"} - require(isSignatureValid(JcaContentVerifierProviderBuilder().build(issuerKeyPair.public))){"Invalid signature"} - toJca() - } + crlIssuer + ) + val certificate = builder.build(signer).toJca() + certificate.checkValidity(Date()) + certificate.verify(issuerKeyPair.public) + return certificate } /** * Create certificate signing request using provided information. */ - fun createCertificateSigningRequest(subject: X500Principal, email: String, publicKey: PublicKey, contentSigner: ContentSigner, certRole: CertRole = CertRole.NODE_CA): PKCS10CertificationRequest { + fun createCertificateSigningRequest(subject: X500Principal, + email: String, + publicKey: PublicKey, + contentSigner: ContentSigner, + certRole: CertRole = CertRole.NODE_CA): PKCS10CertificationRequest { return JcaPKCS10CertificationRequestBuilder(subject, publicKey) .addAttribute(BCStyle.E, DERUTF8String(email)) .addAttribute(ASN1ObjectIdentifier(CordaOID.X509_EXTENSION_CORDA_ROLE), certRole) @@ -410,7 +414,7 @@ object X509Utilities { } // Assuming cert type to role is 1:1 -val CertRole.certificateType: CertificateType get() = CertificateType.values().first { it.role == this } +val CertRole.certificateType: CertificateType get() = CertificateType.entries.first { it.role == this } /** * Convert a [X509Certificate] into BouncyCastle's [X509CertificateHolder]. diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/cryptoservice/bouncycastle/BCCryptoService.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/cryptoservice/DefaultCryptoService.kt similarity index 87% rename from node-api/src/main/kotlin/net/corda/nodeapi/internal/cryptoservice/bouncycastle/BCCryptoService.kt rename to node-api/src/main/kotlin/net/corda/nodeapi/internal/cryptoservice/DefaultCryptoService.kt index d57f750b96..f48cbe0fb1 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/cryptoservice/bouncycastle/BCCryptoService.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/cryptoservice/DefaultCryptoService.kt @@ -1,9 +1,8 @@ -package net.corda.nodeapi.internal.cryptoservice.bouncycastle +package net.corda.nodeapi.internal.cryptoservice import net.corda.core.crypto.Crypto import net.corda.core.crypto.SignatureScheme -import net.corda.core.crypto.internal.Instances.getSignatureInstance -import net.corda.core.crypto.internal.cordaBouncyCastleProvider +import net.corda.core.crypto.internal.Instances.withSignature import net.corda.core.crypto.newSecureRandom import net.corda.core.crypto.sha256 import net.corda.core.utilities.detailedLogger @@ -14,27 +13,30 @@ import net.corda.nodeapi.internal.crypto.ContentSignerBuilder import net.corda.nodeapi.internal.crypto.X509Utilities import net.corda.nodeapi.internal.crypto.loadOrCreateKeyStore import net.corda.nodeapi.internal.crypto.save -import net.corda.nodeapi.internal.cryptoservice.* -import net.corda.nodeapi.internal.cryptoservice.CryptoService -import net.corda.nodeapi.internal.cryptoservice.CryptoServiceException import org.bouncycastle.operator.ContentSigner import java.nio.file.Path -import java.security.* +import java.security.KeyPair +import java.security.KeyPairGenerator +import java.security.KeyStore +import java.security.PrivateKey +import java.security.Provider +import java.security.PublicKey +import java.security.Signature import java.security.spec.ECGenParameterSpec import javax.crypto.Cipher import javax.crypto.KeyGenerator import javax.security.auth.x500.X500Principal /** - * Basic implementation of a [CryptoService] that uses BouncyCastle for cryptographic operations + * Basic implementation of a [CryptoService] which uses Corda's [Provider]s for cryptographic operations * and a Java KeyStore in the form of [CertificateStore] to store private keys. - * This service reuses the [NodeConfiguration.signingCertificateStore] to store keys. * * The [wrappingKeyStorePath] must be provided in order to execute any wrapping operations (e.g. [createWrappingKey], [generateWrappedKeyPair]) */ -class BCCryptoService(private val legalName: X500Principal, - private val certificateStoreSupplier: CertificateStoreSupplier, - private val wrappingKeyStorePath: Path? = null) : CryptoService { +@Suppress("TooManyFunctions") +class DefaultCryptoService(private val legalName: X500Principal, + private val certificateStoreSupplier: CertificateStoreSupplier, + private val wrappingKeyStorePath: Path? = null) : CryptoService { private companion object { val detailedLogger = detailedLogger() @@ -97,7 +99,7 @@ class BCCryptoService(private val legalName: X500Principal, private fun signWithAlgorithm(alias: String, data: ByteArray, signAlgorithm: String): ByteArray { val privateKey = certificateStore.query { getPrivateKey(alias, certificateStore.entryPassword) } - val signature = Signature.getInstance(signAlgorithm, cordaBouncyCastleProvider) + val signature = Signature.getInstance(signAlgorithm) detailedLogger.trace { "CryptoService(action=signing_start;alias=$alias;algorithm=$signAlgorithm)" } signature.initSign(privateKey, newSecureRandom()) signature.update(data) @@ -126,7 +128,7 @@ class BCCryptoService(private val legalName: X500Principal, /** * If a node is running in [NodeConfiguration.devMode] and for backwards compatibility purposes, the same [KeyStore] - * is reused outside [BCCryptoService] to update certificate paths. [resyncKeystore] will sync [BCCryptoService]'s + * is reused outside [DefaultCryptoService] to update certificate paths. [resyncKeystore] will sync [DefaultCryptoService]'s * loaded [certificateStore] in memory with the contents of the corresponding [KeyStore] file. */ fun resyncKeystore() { @@ -178,7 +180,7 @@ class BCCryptoService(private val legalName: X500Principal, } val wrappingKey = wrappingKeyStore.getKey(masterKeyAlias, certificateStore.entryPassword.toCharArray()) - val cipher = Cipher.getInstance("AESWRAPPAD", cordaBouncyCastleProvider) + val cipher = Cipher.getInstance("AESWRAPPAD") cipher.init(Cipher.WRAP_MODE, wrappingKey) val keyPairGenerator = keyPairGeneratorFromScheme(childKeyScheme) @@ -199,22 +201,23 @@ class BCCryptoService(private val legalName: X500Principal, 1 -> "AESWRAPPAD" else -> "AES" } - val cipher = Cipher.getInstance(algorithm, cordaBouncyCastleProvider) + val cipher = Cipher.getInstance(algorithm) cipher.init(Cipher.UNWRAP_MODE, wrappingKey) val privateKey = cipher.unwrap(wrappedPrivateKey.keyMaterial, keyAlgorithmFromScheme(wrappedPrivateKey.signatureScheme), Cipher.PRIVATE_KEY) as PrivateKey - val signature = getSignatureInstance(wrappedPrivateKey.signatureScheme.signatureName, cordaBouncyCastleProvider) - signature.initSign(privateKey, newSecureRandom()) - signature.update(payloadToSign) - return signature.sign() + return withSignature(wrappedPrivateKey.signatureScheme) { signature -> + signature.initSign(privateKey, newSecureRandom()) + signature.update(payloadToSign) + signature.sign() + } } - override fun getWrappingMode(): WrappingMode? = WrappingMode.DEGRADED_WRAPPED + override fun getWrappingMode(): WrappingMode = WrappingMode.DEGRADED_WRAPPED private fun keyPairGeneratorFromScheme(scheme: SignatureScheme): KeyPairGenerator { val algorithm = keyAlgorithmFromScheme(scheme) - val keyPairGenerator = KeyPairGenerator.getInstance(algorithm, cordaBouncyCastleProvider) + val keyPairGenerator = KeyPairGenerator.getInstance(algorithm) when (scheme) { Crypto.ECDSA_SECP256R1_SHA256 -> keyPairGenerator.initialize(ECGenParameterSpec("secp256r1")) Crypto.ECDSA_SECP256K1_SHA256 -> keyPairGenerator.initialize(ECGenParameterSpec("secp256k1")) diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/DefaultKryoCustomizer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/DefaultKryoCustomizer.kt index 3f80ba5200..76597a3f32 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/DefaultKryoCustomizer.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/DefaultKryoCustomizer.kt @@ -20,7 +20,6 @@ import de.javakaffee.kryoserializers.guava.ImmutableSortedSetSerializer import net.corda.core.contracts.ContractAttachment import net.corda.core.contracts.ContractClassName import net.corda.core.contracts.PrivacySalt -import net.corda.core.crypto.CompositeKey import net.corda.core.crypto.SecureHash import net.corda.core.identity.PartyAndCertificate import net.corda.core.internal.AbstractAttachment @@ -40,14 +39,6 @@ import net.corda.core.utilities.toNonEmptySet import net.corda.serialization.internal.DefaultWhitelist import net.corda.serialization.internal.GeneratedAttachment import net.corda.serialization.internal.MutableClassWhitelist -import net.i2p.crypto.eddsa.EdDSAPrivateKey -import net.i2p.crypto.eddsa.EdDSAPublicKey -import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey -import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey -import org.bouncycastle.jcajce.provider.asymmetric.rsa.BCRSAPrivateCrtKey -import org.bouncycastle.jcajce.provider.asymmetric.rsa.BCRSAPublicKey -import org.bouncycastle.pqc.jcajce.provider.sphincs.BCSphincs256PrivateKey -import org.bouncycastle.pqc.jcajce.provider.sphincs.BCSphincs256PublicKey import org.objenesis.instantiator.ObjectInstantiator import org.objenesis.strategy.InstantiatorStrategy import org.objenesis.strategy.StdInstantiatorStrategy @@ -62,14 +53,13 @@ import java.security.PublicKey import java.security.cert.CertPath import java.security.cert.X509Certificate import java.util.* -import kotlin.collections.ArrayList object DefaultKryoCustomizer { private val serializationWhitelists: List by lazy { ServiceLoader.load(SerializationWhitelist::class.java, this.javaClass.classLoader).toList() + DefaultWhitelist } - fun customize(kryo: Kryo, publicKeySerializer: Serializer = PublicKeySerializer): Kryo { + fun customize(kryo: Kryo): Kryo { return kryo.apply { isRegistrationRequired = false references = true @@ -110,6 +100,8 @@ object DefaultKryoCustomizer { // Please add any new registrations to the end. addDefaultSerializer(LinkedHashMapIteratorSerializer.getIterator()::class.java.superclass, LinkedHashMapIteratorSerializer) + addDefaultSerializer(PublicKey::class.java, PublicKeySerializer) + addDefaultSerializer(PrivateKey::class.java, PrivateKeySerializer) register(LinkedHashMapEntrySerializer.getEntry()::class.java, LinkedHashMapEntrySerializer) register(LinkedListItrSerializer.getListItr()::class.java, LinkedListItrSerializer) register(Arrays.asList("").javaClass, ArraysAsListSerializer()) @@ -126,11 +118,6 @@ object DefaultKryoCustomizer { // InputStream subclasses whitelisting, required for attachments. register(BufferedInputStream::class.java, InputStreamSerializer) register(Class.forName("sun.net.www.protocol.jar.JarURLConnection\$JarURLInputStream"), InputStreamSerializer) - register(PublicKey::class.java, publicKeySerializer) - register(PrivateKey::class.java, PrivateKeySerializer) - register(EdDSAPublicKey::class.java, publicKeySerializer) - register(EdDSAPrivateKey::class.java, PrivateKeySerializer) - register(CompositeKey::class.java, publicKeySerializer) // Using a custom serializer for compactness // Exceptions. We don't bother sending the stack traces as the client will fill in its own anyway. register(Array::class, read = { _, _ -> emptyArray() }, write = { _, _, _ -> }) // This ensures a NonEmptySetSerializer is constructed with an initial value. @@ -139,12 +126,6 @@ object DefaultKryoCustomizer { register(Class::class.java, ClassSerializer) register(FileInputStream::class.java, InputStreamSerializer) register(CertPath::class.java, CertPathSerializer) - register(BCECPrivateKey::class.java, PrivateKeySerializer) - register(BCECPublicKey::class.java, publicKeySerializer) - register(BCRSAPrivateCrtKey::class.java, PrivateKeySerializer) - register(BCRSAPublicKey::class.java, publicKeySerializer) - register(BCSphincs256PrivateKey::class.java, PrivateKeySerializer) - register(BCSphincs256PublicKey::class.java, publicKeySerializer) register(NotaryChangeWireTransaction::class.java, NotaryChangeWireTransactionSerializer) register(PartyAndCertificate::class.java, PartyAndCertificateSerializer) diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/Kryo.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/Kryo.kt index b68b4a2e68..0ad5a7d037 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/Kryo.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/Kryo.kt @@ -28,7 +28,6 @@ import net.corda.core.transactions.NotaryChangeWireTransaction import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.WireTransaction import net.corda.core.utilities.OpaqueBytes -import net.corda.core.utilities.SgxSupport import net.corda.serialization.internal.serializationContextKey import org.slf4j.Logger import org.slf4j.LoggerFactory @@ -83,11 +82,8 @@ class ImmutableClassSerializer(val klass: KClass) : Serializer() init { // Verify that this class is immutable (all properties are final). - // We disable this check inside SGX as the reflection blows up. - if (!SgxSupport.isInsideEnclave) { - props.forEach { - require(it !is KMutableProperty<*>) { "$it mutable property of class: ${klass} is unsupported" } - } + props.forEach { + require(it !is KMutableProperty<*>) { "$it mutable property of class: $klass is unsupported" } } } diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/SignedNodeInfoTest.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/SignedNodeInfoTest.kt index 544a5d34ec..39fa3073ae 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/SignedNodeInfoTest.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/SignedNodeInfoTest.kt @@ -6,16 +6,15 @@ import net.corda.core.identity.CordaX500Name import net.corda.core.identity.PartyAndCertificate import net.corda.core.node.NodeInfo import net.corda.core.utilities.NetworkHostAndPort +import net.corda.coretesting.internal.TestNodeInfoBuilder +import net.corda.coretesting.internal.signWith import net.corda.nodeapi.internal.crypto.CertificateType import net.corda.nodeapi.internal.crypto.X509Utilities import net.corda.testing.core.ALICE_NAME import net.corda.testing.core.BOB_NAME import net.corda.testing.core.SerializationEnvironmentRule -import net.corda.coretesting.internal.TestNodeInfoBuilder -import net.corda.coretesting.internal.signWith import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThatThrownBy -import org.junit.Ignore import org.junit.Rule import org.junit.Test import java.security.KeyPair @@ -56,7 +55,6 @@ class SignedNodeInfoTest { } @Test(timeout=300_000) - @Ignore("TODO JDK17: Fixme") fun `verifying composite keys only`() { val aliceKeyPair = generateKeyPair() val bobKeyPair = generateKeyPair() diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/crypto/ContentSignerBuilderTest.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/crypto/ContentSignerBuilderTest.kt index 6920c78093..7b3a329a9b 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/crypto/ContentSignerBuilderTest.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/crypto/ContentSignerBuilderTest.kt @@ -28,6 +28,6 @@ class ContentSignerBuilderTest { .isThrownBy { ContentSignerBuilder.build(signatureScheme, issuerKeyPair.private, provider) } - .withMessage("Incorrect key type EC for signature scheme NONEwithEdDSA") + .withMessage("Incorrect key type EC for signature scheme Ed25519") } -} \ No newline at end of file +} diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/cryptoservice/bouncycastle/BCCryptoServiceTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/cryptoservice/DefaultCryptoServiceTests.kt similarity index 83% rename from node-api/src/test/kotlin/net/corda/nodeapi/internal/cryptoservice/bouncycastle/BCCryptoServiceTests.kt rename to node-api/src/test/kotlin/net/corda/nodeapi/internal/cryptoservice/DefaultCryptoServiceTests.kt index 5eafd10187..560f33a69a 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/cryptoservice/bouncycastle/BCCryptoServiceTests.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/cryptoservice/DefaultCryptoServiceTests.kt @@ -1,33 +1,32 @@ -package net.corda.nodeapi.internal.cryptoservice.bouncycastle +package net.corda.nodeapi.internal.cryptoservice import net.corda.core.crypto.Crypto import net.corda.core.crypto.SignatureScheme import net.corda.core.crypto.internal.cordaBouncyCastleProvider import net.corda.core.utilities.days +import net.corda.coretesting.internal.stubs.CertificateStoreStubs import net.corda.nodeapi.internal.config.CertificateStoreSupplier import net.corda.nodeapi.internal.crypto.CertificateType import net.corda.nodeapi.internal.crypto.X509Utilities -import net.corda.nodeapi.internal.cryptoservice.CryptoService -import net.corda.nodeapi.internal.cryptoservice.CryptoServiceException -import net.corda.nodeapi.internal.cryptoservice.WrappedPrivateKey -import net.corda.nodeapi.internal.cryptoservice.WrappingMode -import net.corda.testing.core.ALICE_NAME -import net.corda.coretesting.internal.stubs.CertificateStoreStubs import net.corda.nodeapi.internal.crypto.loadOrCreateKeyStore +import net.corda.testing.core.ALICE_NAME import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThatThrownBy import org.bouncycastle.jce.provider.BouncyCastleProvider import org.junit.Before -import org.junit.Ignore import org.junit.Rule import org.junit.Test import org.junit.rules.TemporaryFolder import java.io.FileOutputStream import java.nio.file.Path -import java.security.* +import java.security.KeyPair +import java.security.KeyPairGenerator +import java.security.KeyStore +import java.security.PublicKey +import java.security.Signature import java.security.spec.ECGenParameterSpec import java.time.Duration -import java.util.* +import java.util.UUID import javax.crypto.Cipher import javax.security.auth.x500.X500Principal import kotlin.io.path.div @@ -35,7 +34,7 @@ import kotlin.test.assertFailsWith import kotlin.test.assertFalse import kotlin.test.assertTrue -class BCCryptoServiceTests { +class DefaultCryptoServiceTests { companion object { val clearData = "data".toByteArray() } @@ -61,15 +60,13 @@ class BCCryptoServiceTests { } @Test(timeout=300_000) - @Ignore("TODO JDK17: Fixme") - fun `BCCryptoService generate key pair and sign both data and cert`() { - val cryptoService = BCCryptoService(ALICE_NAME.x500Principal, signingCertificateStore, wrappingKeyStorePath) + fun `cryptoService generate key pair and sign both data and cert`() { + val cryptoService = DefaultCryptoService(ALICE_NAME.x500Principal, signingCertificateStore, wrappingKeyStorePath) // Testing every supported scheme. - Crypto.supportedSignatureSchemes().filter { it != Crypto.COMPOSITE_KEY - && it.signatureName != "SHA512WITHSPHINCS256"}.forEach { generateKeyAndSignForScheme(cryptoService, it) } + Crypto.supportedSignatureSchemes().filter { it != Crypto.COMPOSITE_KEY }.forEach { generateKeyAndSignForScheme(cryptoService, it) } } - private fun generateKeyAndSignForScheme(cryptoService: BCCryptoService, signatureScheme: SignatureScheme) { + private fun generateKeyAndSignForScheme(cryptoService: DefaultCryptoService, signatureScheme: SignatureScheme) { val alias = "signature${signatureScheme.schemeNumberID}" val pubKey = cryptoService.generateKeyPair(alias, signatureScheme) assertTrue { cryptoService.containsKey(alias) } @@ -95,12 +92,10 @@ class BCCryptoServiceTests { } @Test(timeout=300_000) - @Ignore("TODO JDK17: Fixme") - fun `BCCryptoService generate key pair and sign with existing schemes`() { - val cryptoService = BCCryptoService(ALICE_NAME.x500Principal, signingCertificateStore, wrappingKeyStorePath) + fun `cryptoService generate key pair and sign with existing schemes`() { + val cryptoService = DefaultCryptoService(ALICE_NAME.x500Principal, signingCertificateStore, wrappingKeyStorePath) // Testing every supported scheme. - Crypto.supportedSignatureSchemes().filter { it != Crypto.COMPOSITE_KEY - && it.signatureName != "SHA512WITHSPHINCS256"}.forEach { + Crypto.supportedSignatureSchemes().filter { it != Crypto.COMPOSITE_KEY }.forEach { val alias = "signature${it.schemeNumberID}" val pubKey = cryptoService.generateKeyPair(alias, it) assertTrue { cryptoService.containsKey(alias) } @@ -110,9 +105,7 @@ class BCCryptoServiceTests { } @Test(timeout=300_000) - @Ignore("TODO JDK17: Fixme") - fun `BCCryptoService generate key pair and sign with passed signing algorithm`() { - + fun `cryptoService generate key pair and sign with passed signing algorithm`() { assertTrue{signAndVerify(signAlgo = "NONEwithRSA", alias = "myKeyAlias", keyTypeAlgo = "RSA")} assertTrue{signAndVerify(signAlgo = "MD2withRSA", alias = "myKeyAlias", keyTypeAlgo = "RSA")} assertTrue{signAndVerify(signAlgo = "MD5withRSA", alias = "myKeyAlias", keyTypeAlgo = "RSA")} @@ -132,7 +125,7 @@ class BCCryptoServiceTests { private fun signAndVerify(signAlgo: String, alias: String, keyTypeAlgo: String): Boolean { val keyPairGenerator = KeyPairGenerator.getInstance(keyTypeAlgo) val keyPair = keyPairGenerator.genKeyPair() - val cryptoService = BCCryptoService(ALICE_NAME.x500Principal, createKeystore(alias, keyPair), wrappingKeyStorePath) + val cryptoService = DefaultCryptoService(ALICE_NAME.x500Principal, createKeystore(alias, keyPair), wrappingKeyStorePath) assertTrue { cryptoService.containsKey(alias) } val signatureData = cryptoService.sign(alias, clearData, signAlgo) return verify(signAlgo, cryptoService.getPublicKey(alias), signatureData, clearData) @@ -175,7 +168,7 @@ class BCCryptoServiceTests { @Test(timeout=300_000) fun `When key does not exist getPublicKey, sign and getSigner should throw`() { val nonExistingAlias = "nonExistingAlias" - val cryptoService = BCCryptoService(ALICE_NAME.x500Principal, signingCertificateStore, wrappingKeyStorePath) + val cryptoService = DefaultCryptoService(ALICE_NAME.x500Principal, signingCertificateStore, wrappingKeyStorePath) assertFalse { cryptoService.containsKey(nonExistingAlias) } assertFailsWith { cryptoService.getPublicKey(nonExistingAlias) } assertFailsWith { cryptoService.sign(nonExistingAlias, clearData) } @@ -184,7 +177,7 @@ class BCCryptoServiceTests { @Test(timeout=300_000) fun `cryptoService supports degraded mode of wrapping`() { - val cryptoService = BCCryptoService(ALICE_NAME.x500Principal, signingCertificateStore, wrappingKeyStorePath) + val cryptoService = DefaultCryptoService(ALICE_NAME.x500Principal, signingCertificateStore, wrappingKeyStorePath) val supportedMode = cryptoService.getWrappingMode() assertThat(supportedMode).isEqualTo(WrappingMode.DEGRADED_WRAPPED) @@ -192,7 +185,7 @@ class BCCryptoServiceTests { @Test(timeout=300_000) fun `cryptoService does not fail when requested to create same wrapping key twice with failIfExists is false`() { - val cryptoService = BCCryptoService(ALICE_NAME.x500Principal, signingCertificateStore, wrappingKeyStorePath) + val cryptoService = DefaultCryptoService(ALICE_NAME.x500Principal, signingCertificateStore, wrappingKeyStorePath) val keyAlias = UUID.randomUUID().toString() cryptoService.createWrappingKey(keyAlias) @@ -201,7 +194,7 @@ class BCCryptoServiceTests { @Test(timeout=300_000) fun `cryptoService does fail when requested to create same wrapping key twice with failIfExists is true`() { - val cryptoService = BCCryptoService(ALICE_NAME.x500Principal, signingCertificateStore, wrappingKeyStorePath) + val cryptoService = DefaultCryptoService(ALICE_NAME.x500Principal, signingCertificateStore, wrappingKeyStorePath) val keyAlias = UUID.randomUUID().toString() cryptoService.createWrappingKey(keyAlias) @@ -213,7 +206,7 @@ class BCCryptoServiceTests { @Test(timeout=300_000) fun `cryptoService fails when asked to generate wrapped key pair or sign, but the master key specified does not exist`() { - val cryptoService = BCCryptoService(ALICE_NAME.x500Principal, signingCertificateStore, wrappingKeyStorePath) + val cryptoService = DefaultCryptoService(ALICE_NAME.x500Principal, signingCertificateStore, wrappingKeyStorePath) val wrappingKeyAlias = UUID.randomUUID().toString() @@ -230,7 +223,7 @@ class BCCryptoServiceTests { @Test(timeout=300_000) fun `cryptoService can generate wrapped key pair and sign with the private key successfully, using default algorithm`() { - val cryptoService = BCCryptoService(ALICE_NAME.x500Principal, signingCertificateStore, wrappingKeyStorePath) + val cryptoService = DefaultCryptoService(ALICE_NAME.x500Principal, signingCertificateStore, wrappingKeyStorePath) val wrappingKeyAlias = UUID.randomUUID().toString() cryptoService.createWrappingKey(wrappingKeyAlias) @@ -239,7 +232,7 @@ class BCCryptoServiceTests { @Test(timeout=300_000) fun `cryptoService can generate wrapped key pair and sign with the private key successfully`() { - val cryptoService = BCCryptoService(ALICE_NAME.x500Principal, signingCertificateStore, wrappingKeyStorePath) + val cryptoService = DefaultCryptoService(ALICE_NAME.x500Principal, signingCertificateStore, wrappingKeyStorePath) val wrappingKeyAlias = UUID.randomUUID().toString() cryptoService.createWrappingKey(wrappingKeyAlias) @@ -264,7 +257,7 @@ class BCCryptoServiceTests { @Test(timeout=300_000) fun `cryptoService can sign with previously encoded version of wrapped key`() { - val cryptoService = BCCryptoService(ALICE_NAME.x500Principal, signingCertificateStore, wrappingKeyStorePath) + val cryptoService = DefaultCryptoService(ALICE_NAME.x500Principal, signingCertificateStore, wrappingKeyStorePath) val wrappingKeyAlias = UUID.randomUUID().toString() cryptoService.createWrappingKey(wrappingKeyAlias) diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/kryo/KryoTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/kryo/KryoTests.kt index 0692abeae5..f58d46312c 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/kryo/KryoTests.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/kryo/KryoTests.kt @@ -139,7 +139,12 @@ class KryoTests(private val compression: CordaSerializationEncoding?) { @Test(timeout=300_000) fun `deserialised key pair functions the same as serialised one`() { - val keyPair = generateKeyPair() + // The default signature scheme, EDDSA_ED25519_SHA512, generates public keys which have a writeReplace method (EdDSAPublicKeyImpl). + // This is picked up by Quasar's custom ReplaceableObjectKryo, which will *always* use the writeReplace for serialisation, ignoring + // any Kryo serialisers that might have been configured. This thus means the deserialisation path does not go via + // Cryto.decodePublicKey, which interns the materialised PublicKey. To avoid all of this, and test the last assertion, we use + // ECDSA keys, whose implementation doesn't have a writeReplace method. + val keyPair = Crypto.generateKeyPair(Crypto.ECDSA_SECP256R1_SHA256) val bitsToSign: ByteArray = Ints.toByteArray(0x01234567) val wrongBits: ByteArray = Ints.toByteArray(0x76543210) val signature = keyPair.sign(bitsToSign) diff --git a/node/build.gradle b/node/build.gradle index 44c08ef17a..f904c1db21 100644 --- a/node/build.gradle +++ b/node/build.gradle @@ -223,7 +223,6 @@ dependencies { integrationTestImplementation "junit:junit:$junit_version" integrationTestImplementation "org.assertj:assertj-core:${assertj_version}" integrationTestImplementation "org.apache.qpid:qpid-jms-client:${protonj_version}" - integrationTestImplementation "net.i2p.crypto:eddsa:$eddsa_version" // used by FinalityFlowErrorHandlingTest slowIntegrationTestImplementation project(':testing:cordapps:cashobservers') @@ -276,7 +275,6 @@ quasar { "io.github.classgraph**", "io.netty*", "liquibase**", - "net.i2p.crypto.**", "nonapi.io.github.classgraph.**", "org.apiguardian.**", "org.bouncycastle**", diff --git a/node/capsule/build.gradle b/node/capsule/build.gradle index 3a2ac236c4..6258b55be3 100644 --- a/node/capsule/build.gradle +++ b/node/capsule/build.gradle @@ -65,7 +65,7 @@ tasks.register('buildCordaJAR', FatCapsule) { applicationVersion = corda_release_version applicationId = "net.corda.node.Corda" // See experimental/quasar-hook/README.md for how to generate. - def quasarExcludeExpression = "x(antlr**;bftsmart**;co.paralleluniverse**;com.codahale**;com.esotericsoftware**;com.fasterxml**;com.google**;com.ibm**;com.intellij**;com.jcabi**;org.mockito**;com.opengamma**;com.typesafe**;com.zaxxer**;de.javakaffee**;groovy**;groovyjarjarantlr**;groovyjarjarasm**;io.atomix**;io.github**;io.netty**;jdk**;kotlin**;net.bytebuddy**;net.i2p**;org.apache**;org.bouncycastle**;org.codehaus**;org.crsh**;org.dom4j**;org.fusesource**;org.h2**;org.hibernate**;org.jboss**;org.jcp**;org.joda**;org.objectweb**;org.objenesis**;org.slf4j**;org.w3c**;org.xml**;org.yaml**;reflectasm**;rx**;org.jolokia**;com.lmax**;picocli**;liquibase**;com.github.benmanes**;org.json**;org.postgresql**;nonapi.io.github.classgraph**;io.opentelemetry**)" + def quasarExcludeExpression = "x(antlr**;bftsmart**;co.paralleluniverse**;com.codahale**;com.esotericsoftware**;com.fasterxml**;com.google**;com.ibm**;com.intellij**;com.jcabi**;org.mockito**;com.opengamma**;com.typesafe**;com.zaxxer**;de.javakaffee**;groovy**;groovyjarjarantlr**;groovyjarjarasm**;io.atomix**;io.github**;io.netty**;jdk**;kotlin**;net.bytebuddy**;org.apache**;org.bouncycastle**;org.codehaus**;org.crsh**;org.dom4j**;org.fusesource**;org.h2**;org.hibernate**;org.jboss**;org.jcp**;org.joda**;org.objectweb**;org.objenesis**;org.slf4j**;org.w3c**;org.xml**;org.yaml**;reflectasm**;rx**;org.jolokia**;com.lmax**;picocli**;liquibase**;com.github.benmanes**;org.json**;org.postgresql**;nonapi.io.github.classgraph**;io.opentelemetry**)" def quasarClassLoaderExclusion = "l(net.corda.core.serialization.internal.**)" def quasarOptions = "m" javaAgents = quasar_classifier ? ["quasar-core-${quasar_version}-${quasar_classifier}.jar=${quasarOptions}${quasarExcludeExpression}${quasarClassLoaderExclusion}"] : ["quasar-core-${quasar_version}.jar=${quasarExcludeExpression}${quasarClassLoaderExclusion}"] diff --git a/node/capsule/src/main/resources/node-jvm-args.txt b/node/capsule/src/main/resources/node-jvm-args.txt index 21d6d9f829..d47e01d01a 100644 --- a/node/capsule/src/main/resources/node-jvm-args.txt +++ b/node/capsule/src/main/resources/node-jvm-args.txt @@ -3,7 +3,12 @@ --add-opens=java.base/java.nio=ALL-UNNAMED --add-opens=java.base/java.security=ALL-UNNAMED --add-opens=java.base/java.security.cert=ALL-UNNAMED +--add-opens=java.base/java.security.spec=ALL-UNNAMED --add-opens=java.base/java.time=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.base/java.util.concurrent=ALL-UNNAMED +--add-opens=java.base/sun.security.pkcs=ALL-UNNAMED +--add-opens=java.base/sun.security.util=ALL-UNNAMED +--add-opens=java.base/sun.security.x509=ALL-UNNAMED --add-opens=java.sql/java.sql=ALL-UNNAMED +--add-opens=jdk.crypto.ec/sun.security.ec.ed=ALL-UNNAMED diff --git a/node/src/integration-test/kotlin/net/corda/node/customcheckpointserializer/TestCorDapp.kt b/node/src/integration-test/kotlin/net/corda/node/customcheckpointserializer/TestCorDapp.kt index 1d3e929dde..ee94fc62d0 100644 --- a/node/src/integration-test/kotlin/net/corda/node/customcheckpointserializer/TestCorDapp.kt +++ b/node/src/integration-test/kotlin/net/corda/node/customcheckpointserializer/TestCorDapp.kt @@ -7,7 +7,6 @@ import net.corda.core.flows.StartableByRPC import net.corda.core.serialization.CheckpointCustomSerializer import net.corda.testing.node.internal.CustomCordapp import net.corda.testing.node.internal.enclosedCordapp -import net.i2p.crypto.eddsa.EdDSAPublicKey import org.assertj.core.api.Assertions import java.security.PublicKey import java.time.Duration @@ -198,17 +197,4 @@ class TestCorDapp { throw FlowException("Broken on purpose") } } - - @Suppress("unused") - class BrokenEdDSAPublicKeySerializer : - CheckpointCustomSerializer { - override fun toProxy(obj: EdDSAPublicKey): String { - throw FlowException("Broken on purpose") - } - - override fun fromProxy(proxy: String): EdDSAPublicKey { - throw FlowException("Broken on purpose") - } - } - } diff --git a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt index 57cbea71f5..8d12f27003 100644 --- a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt @@ -150,7 +150,7 @@ import net.corda.nodeapi.internal.SignedNodeInfo import net.corda.nodeapi.internal.cordapp.CordappLoader import net.corda.nodeapi.internal.cordapp.cordappSchemas import net.corda.nodeapi.internal.cryptoservice.CryptoService -import net.corda.nodeapi.internal.cryptoservice.bouncycastle.BCCryptoService +import net.corda.nodeapi.internal.cryptoservice.DefaultCryptoService import net.corda.nodeapi.internal.lifecycle.NodeLifecycleEvent import net.corda.nodeapi.internal.lifecycle.NodeLifecycleEventsDistributor import net.corda.nodeapi.internal.lifecycle.NodeServicesContext @@ -1061,7 +1061,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration, } protected open fun makeCryptoService(): CryptoService { - return BCCryptoService(configuration.myLegalName.x500Principal, configuration.signingCertificateStore) + return DefaultCryptoService(configuration.myLegalName.x500Principal, configuration.signingCertificateStore) } @VisibleForTesting diff --git a/node/src/main/kotlin/net/corda/node/internal/KeyStoreHandler.kt b/node/src/main/kotlin/net/corda/node/internal/KeyStoreHandler.kt index 6c4225cdc0..f5ff1aa2c3 100644 --- a/node/src/main/kotlin/net/corda/node/internal/KeyStoreHandler.kt +++ b/node/src/main/kotlin/net/corda/node/internal/KeyStoreHandler.kt @@ -16,7 +16,7 @@ import net.corda.nodeapi.internal.crypto.X509Utilities.DISTRIBUTED_NOTARY_KEY_AL import net.corda.nodeapi.internal.crypto.X509Utilities.NODE_IDENTITY_KEY_ALIAS import net.corda.nodeapi.internal.crypto.checkValidity import net.corda.nodeapi.internal.cryptoservice.CryptoService -import net.corda.nodeapi.internal.cryptoservice.bouncycastle.BCCryptoService +import net.corda.nodeapi.internal.cryptoservice.DefaultCryptoService import java.io.IOException import java.math.BigInteger import java.nio.file.NoSuchFileException @@ -54,8 +54,8 @@ class KeyStoreHandler(private val configuration: NodeConfiguration, private val if (configuration.devMode) { configuration.configureWithDevSSLCertificate(cryptoService, devModeKeyEntropy) // configureWithDevSSLCertificate is a devMode process that writes directly to keystore files, so - // we should re-synchronise BCCryptoService with the updated keystore file. - if (cryptoService is BCCryptoService) { + // we should re-synchronise DefaultCryptoService with the updated keystore file. + if (cryptoService is DefaultCryptoService) { cryptoService.resyncKeystore() } } diff --git a/node/src/main/kotlin/net/corda/node/services/config/ConfigUtilities.kt b/node/src/main/kotlin/net/corda/node/services/config/ConfigUtilities.kt index 9777d07a14..bd5c9c9999 100644 --- a/node/src/main/kotlin/net/corda/node/services/config/ConfigUtilities.kt +++ b/node/src/main/kotlin/net/corda/node/services/config/ConfigUtilities.kt @@ -17,7 +17,7 @@ import net.corda.nodeapi.internal.config.toProperties import net.corda.nodeapi.internal.crypto.X509KeyStore import net.corda.nodeapi.internal.crypto.X509Utilities import net.corda.nodeapi.internal.cryptoservice.CryptoService -import net.corda.nodeapi.internal.cryptoservice.bouncycastle.BCCryptoService +import net.corda.nodeapi.internal.cryptoservice.DefaultCryptoService import net.corda.nodeapi.internal.installDevNodeCaCertPath import net.corda.nodeapi.internal.loadDevCaTrustStore import net.corda.nodeapi.internal.registerDevP2pCertificates @@ -195,7 +195,7 @@ fun MutualSslConfiguration.configureDevKeyAndTrustStores(myLegalName: CordaX500N FileBasedCertificateStoreSupplier(keyStore.path, keyStore.storePassword, keyStore.entryPassword).get(true) .also { it.registerDevP2pCertificates(myLegalName) } when (cryptoService) { - is BCCryptoService, null -> { + is DefaultCryptoService, null -> { val signingKeyStore = FileBasedCertificateStoreSupplier(signingCertificateStore.path, signingCertificateStore.storePassword, signingCertificateStore.entryPassword).get(true) .also { it.installDevNodeCaCertPath(myLegalName) diff --git a/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt b/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt index a5cf742a9e..02d3695995 100644 --- a/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt +++ b/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt @@ -77,7 +77,7 @@ interface NodeConfiguration : ConfigurationWithOptionsContainer { val baseDirectory: Path val certificatesDirectory: Path // signingCertificateStore is used to store certificate chains. - // However, BCCryptoService is reusing this to store keys as well. + // However, DefaultCryptoService is reusing this to store keys as well. val signingCertificateStore: FileBasedCertificateStoreSupplier val p2pSslOptions: MutualSslConfiguration diff --git a/node/src/main/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelper.kt b/node/src/main/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelper.kt index b999b1183d..c35cc1fa9f 100644 --- a/node/src/main/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelper.kt +++ b/node/src/main/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelper.kt @@ -22,7 +22,7 @@ import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_ROOT_CA import net.corda.nodeapi.internal.crypto.X509Utilities.DEFAULT_VALIDITY_WINDOW import net.corda.nodeapi.internal.crypto.x509 import net.corda.nodeapi.internal.cryptoservice.CryptoService -import net.corda.nodeapi.internal.cryptoservice.bouncycastle.BCCryptoService +import net.corda.nodeapi.internal.cryptoservice.DefaultCryptoService import org.bouncycastle.asn1.x500.X500Name import org.bouncycastle.openssl.jcajce.JcaPEMWriter import org.bouncycastle.operator.ContentSigner @@ -98,7 +98,7 @@ open class NetworkRegistrationHelper( certificatesDirectory.safeSymbolicRead().createDirectories() // We need this in case cryptoService and certificateStore share the same KeyStore (for backwards compatibility purposes). // If we didn't, then an update to cryptoService wouldn't be reflected to certificateStore that is already loaded in memory. - val certStore: CertificateStore = if (cryptoService is BCCryptoService) cryptoService.certificateStore else certificateStore + val certStore: CertificateStore = if (cryptoService is DefaultCryptoService) cryptoService.certificateStore else certificateStore // SELF_SIGNED_PRIVATE_KEY is used as progress indicator. if (certStore.contains(nodeCaKeyAlias) && !certStore.contains(SELF_SIGNED_PRIVATE_KEY)) { @@ -169,7 +169,7 @@ open class NetworkRegistrationHelper( certificatesDirectory.safeSymbolicRead().createDirectories() // We need this in case cryptoService and certificateStore share the same KeyStore (for backwards compatibility purposes). // If we didn't, then an update to cryptoService wouldn't be reflected to certificateStore that is already loaded in memory. - val certStore: CertificateStore = if (cryptoService is BCCryptoService) cryptoService.certificateStore else certificateStore + val certStore: CertificateStore = if (cryptoService is DefaultCryptoService) cryptoService.certificateStore else certificateStore if (!certStore.contains(nodeCaKeyAlias)) { logProgress("Node CA key doesn't exist, program will now terminate...") @@ -374,7 +374,7 @@ class NodeRegistrationConfiguration( tlsCertCrlDistPoint = config.tlsCertCrlDistPoint, certificatesDirectory = config.certificatesDirectory, emailAddress = config.emailAddress, - cryptoService = BCCryptoService(config.myLegalName.x500Principal, config.signingCertificateStore), + cryptoService = DefaultCryptoService(config.myLegalName.x500Principal, config.signingCertificateStore), certificateStore = config.signingCertificateStore.get(true), notaryServiceConfig = config.notary?.let { // Validation of the presence of the notary service legal name is only done here and not in the top level configuration diff --git a/node/src/test/kotlin/net/corda/node/internal/KeyStoreHandlerTest.kt b/node/src/test/kotlin/net/corda/node/internal/KeyStoreHandlerTest.kt index 8e67f741ed..e6b39038a6 100644 --- a/node/src/test/kotlin/net/corda/node/internal/KeyStoreHandlerTest.kt +++ b/node/src/test/kotlin/net/corda/node/internal/KeyStoreHandlerTest.kt @@ -23,13 +23,12 @@ import net.corda.nodeapi.internal.crypto.X509Utilities.DISTRIBUTED_NOTARY_COMPOS import net.corda.nodeapi.internal.crypto.X509Utilities.DISTRIBUTED_NOTARY_KEY_ALIAS import net.corda.nodeapi.internal.crypto.X509Utilities.NODE_IDENTITY_KEY_ALIAS import net.corda.nodeapi.internal.cryptoservice.CryptoService -import net.corda.nodeapi.internal.cryptoservice.bouncycastle.BCCryptoService +import net.corda.nodeapi.internal.cryptoservice.DefaultCryptoService import net.corda.testing.core.ALICE_NAME import net.corda.testing.core.BOB_NAME import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThatThrownBy import org.junit.Before -import org.junit.Ignore import org.junit.Rule import org.junit.Test import org.junit.rules.TemporaryFolder @@ -37,7 +36,6 @@ import java.security.KeyPair import java.security.PublicKey import kotlin.io.path.div -@Ignore("TODO JDK17: Fixme") class KeyStoreHandlerTest { @Rule @JvmField @@ -49,7 +47,7 @@ class KeyStoreHandlerTest { private val keyStore get() = config.signingCertificateStore.get() - private lateinit var cryptoService: BCCryptoService + private lateinit var cryptoService: DefaultCryptoService private lateinit var keyStoreHandler: KeyStoreHandler @@ -66,7 +64,7 @@ class KeyStoreHandlerTest { doReturn(ALICE_NAME).whenever(it).myLegalName doReturn(null).whenever(it).notary } - cryptoService = BCCryptoService(ALICE_NAME.x500Principal, signingCertificateStore) + cryptoService = DefaultCryptoService(ALICE_NAME.x500Principal, signingCertificateStore) keyStoreHandler = KeyStoreHandler(config, cryptoService) } @@ -192,7 +190,7 @@ class KeyStoreHandlerTest { val devCertificateDir = tempFolder.root.toPath() / "certificates-dev" val signingCertificateStore = CertificateStoreStubs.Signing.withCertificatesDirectory(devCertificateDir) val p2pSslOptions = CertificateStoreStubs.P2P.withCertificatesDirectory(devCertificateDir) - val devCryptoService = BCCryptoService(config.myLegalName.x500Principal, signingCertificateStore) + val devCryptoService = DefaultCryptoService(config.myLegalName.x500Principal, signingCertificateStore) doReturn(true).whenever(config).devMode doReturn(signingCertificateStore).whenever(config).signingCertificateStore diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt index 6b982b920c..1110f14ea1 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt @@ -955,8 +955,7 @@ class DriverDSLImpl( val excludePackagePattern = "x(antlr**;bftsmart**;ch**;co.paralleluniverse**;com.codahale**;com.esotericsoftware**;" + "com.fasterxml**;com.google**;com.ibm**;com.intellij**;com.jcabi**;com.nhaarman**;com.opengamma**;" + "com.typesafe**;com.zaxxer**;de.javakaffee**;groovy**;groovyjarjarantlr**;groovyjarjarasm**;io.atomix**;" + - "io.github**;io.netty**;jdk**;joptsimple**;junit**;kotlin**;net.bytebuddy**;" + - "net.i2p**;org.apache**;" + + "io.github**;io.netty**;jdk**;joptsimple**;junit**;kotlin**;net.bytebuddy**;org.apache**;" + "org.assertj**;org.bouncycastle**;org.codehaus**;org.crsh**;org.dom4j**;org.fusesource**;org.h2**;" + "org.hamcrest**;org.hibernate**;org.jboss**;org.jcp**;org.joda**;org.junit**;org.mockito**;org.objectweb**;" + "org.objenesis**;org.slf4j**;org.w3c**;org.xml**;org.yaml**;reflectasm**;rx**;org.jolokia**;" +