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 fccf879749..ca8a077354 100644 --- a/core/src/main/kotlin/net/corda/core/crypto/CordaSecurityProvider.kt +++ b/core/src/main/kotlin/net/corda/core/crypto/CordaSecurityProvider.kt @@ -10,9 +10,9 @@ package net.corda.core.crypto +import net.corda.core.crypto.CordaObjectIdentifier.COMPOSITE_KEY +import net.corda.core.crypto.CordaObjectIdentifier.COMPOSITE_SIGNATURE import org.bouncycastle.asn1.ASN1ObjectIdentifier -import java.security.AccessController -import java.security.PrivilegedAction import java.security.Provider class CordaSecurityProvider : Provider(PROVIDER_NAME, 0.1, "$PROVIDER_NAME security provider wrapper") { @@ -21,20 +21,12 @@ class CordaSecurityProvider : Provider(PROVIDER_NAME, 0.1, "$PROVIDER_NAME secur } init { - AccessController.doPrivileged(PrivilegedAction { setup() }) - } - - private fun setup() { - put("KeyFactory.${CompositeKey.KEY_ALGORITHM}", "net.corda.core.crypto.CompositeKeyFactory") - put("Signature.${CompositeSignature.SIGNATURE_ALGORITHM}", "net.corda.core.crypto.CompositeSignature") - - val compositeKeyOID = CordaObjectIdentifier.COMPOSITE_KEY.id - put("Alg.Alias.KeyFactory.$compositeKeyOID", CompositeKey.KEY_ALGORITHM) - put("Alg.Alias.KeyFactory.OID.$compositeKeyOID", CompositeKey.KEY_ALGORITHM) - - val compositeSignatureOID = CordaObjectIdentifier.COMPOSITE_SIGNATURE.id - put("Alg.Alias.Signature.$compositeSignatureOID", CompositeSignature.SIGNATURE_ALGORITHM) - put("Alg.Alias.Signature.OID.$compositeSignatureOID", CompositeSignature.SIGNATURE_ALGORITHM) + put("KeyFactory.${CompositeKey.KEY_ALGORITHM}", CompositeKeyFactory::class.java.name) + put("Signature.${CompositeSignature.SIGNATURE_ALGORITHM}", CompositeSignature::class.java.name) + put("Alg.Alias.KeyFactory.$COMPOSITE_KEY", CompositeKey.KEY_ALGORITHM) + put("Alg.Alias.KeyFactory.OID.$COMPOSITE_KEY", CompositeKey.KEY_ALGORITHM) + put("Alg.Alias.Signature.$COMPOSITE_SIGNATURE", CompositeSignature.SIGNATURE_ALGORITHM) + put("Alg.Alias.Signature.OID.$COMPOSITE_SIGNATURE", CompositeSignature.SIGNATURE_ALGORITHM) } } 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 8c77fdb8a3..9f1d7401f2 100644 --- a/core/src/main/kotlin/net/corda/core/crypto/Crypto.kt +++ b/core/src/main/kotlin/net/corda/core/crypto/Crypto.kt @@ -10,19 +10,17 @@ package net.corda.core.crypto -import net.corda.core.internal.X509EdDSAEngine +import net.corda.core.crypto.internal.* import net.corda.core.serialization.serialize import net.i2p.crypto.eddsa.EdDSAEngine import net.i2p.crypto.eddsa.EdDSAPrivateKey import net.i2p.crypto.eddsa.EdDSAPublicKey -import net.i2p.crypto.eddsa.EdDSASecurityProvider 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.DLSequence import org.bouncycastle.asn1.bc.BCObjectIdentifiers @@ -37,7 +35,6 @@ import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey import org.bouncycastle.jcajce.provider.asymmetric.rsa.BCRSAPrivateKey import org.bouncycastle.jcajce.provider.asymmetric.rsa.BCRSAPublicKey -import org.bouncycastle.jcajce.provider.util.AsymmetricKeyInfoConverter import org.bouncycastle.jce.ECNamedCurveTable import org.bouncycastle.jce.provider.BouncyCastleProvider import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec @@ -47,7 +44,6 @@ 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.pqc.jcajce.provider.BouncyCastlePQCProvider import org.bouncycastle.pqc.jcajce.provider.sphincs.BCSphincs256PrivateKey import org.bouncycastle.pqc.jcajce.provider.sphincs.BCSphincs256PublicKey import org.bouncycastle.pqc.jcajce.spec.SPHINCS256KeyGenParameterSpec @@ -84,7 +80,7 @@ object Crypto { "RSA_SHA256", AlgorithmIdentifier(PKCSObjectIdentifiers.sha256WithRSAEncryption, null), listOf(AlgorithmIdentifier(PKCSObjectIdentifiers.rsaEncryption, null)), - BouncyCastleProvider.PROVIDER_NAME, + cordaBouncyCastleProvider.name, "RSA", "SHA256WITHRSA", null, @@ -99,7 +95,7 @@ object Crypto { "ECDSA_SECP256K1_SHA256", AlgorithmIdentifier(X9ObjectIdentifiers.ecdsa_with_SHA256, SECObjectIdentifiers.secp256k1), listOf(AlgorithmIdentifier(X9ObjectIdentifiers.id_ecPublicKey, SECObjectIdentifiers.secp256k1)), - BouncyCastleProvider.PROVIDER_NAME, + cordaBouncyCastleProvider.name, "ECDSA", "SHA256withECDSA", ECNamedCurveTable.getParameterSpec("secp256k1"), @@ -114,7 +110,7 @@ object Crypto { "ECDSA_SECP256R1_SHA256", AlgorithmIdentifier(X9ObjectIdentifiers.ecdsa_with_SHA256, SECObjectIdentifiers.secp256r1), listOf(AlgorithmIdentifier(X9ObjectIdentifiers.id_ecPublicKey, SECObjectIdentifiers.secp256r1)), - BouncyCastleProvider.PROVIDER_NAME, + cordaBouncyCastleProvider.name, "ECDSA", "SHA256withECDSA", ECNamedCurveTable.getParameterSpec("secp256r1"), @@ -128,14 +124,13 @@ object Crypto { * Not to be confused with the EdDSA variants, Ed25519ctx and Ed25519ph. */ @JvmField - val EDDSA_ED25519_SHA512 = SignatureScheme( + val EDDSA_ED25519_SHA512: SignatureScheme = SignatureScheme( 4, "EDDSA_ED25519_SHA512", - // OID taken from https://tools.ietf.org/html/draft-ietf-curdle-pkix-00 - AlgorithmIdentifier(ASN1ObjectIdentifier("1.3.101.112"), null), + 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. - BouncyCastleProvider.PROVIDER_NAME, + cordaBouncyCastleProvider.name, "1.3.101.112", EdDSAEngine.SIGNATURE_ALGORITHM, EdDSANamedCurveTable.getByName("ED25519"), @@ -158,7 +153,7 @@ object Crypto { "SPHINCS-256_SHA512", AlgorithmIdentifier(BCObjectIdentifiers.sphincs256_with_SHA512, DLSequence(arrayOf(ASN1Integer(0), SHA512_256))), listOf(AlgorithmIdentifier(BCObjectIdentifiers.sphincs256, DLSequence(arrayOf(ASN1Integer(0), SHA512_256)))), - "BCPQC", + bouncyCastlePQCProvider.name, "SPHINCS256", "SHA512WITHSPHINCS256", SPHINCS256KeyGenParameterSpec(SPHINCS256KeyGenParameterSpec.SHA512_256), @@ -175,7 +170,7 @@ object Crypto { "COMPOSITE", AlgorithmIdentifier(CordaObjectIdentifier.COMPOSITE_KEY), emptyList(), - CordaSecurityProvider.PROVIDER_NAME, + cordaSecurityProvider.name, CompositeKey.KEY_ALGORITHM, CompositeSignature.SIGNATURE_ALGORITHM, null, @@ -209,22 +204,6 @@ object Crypto { + signatureSchemeMap.values.map { Pair(it.signatureOID, it) }) .toMap() - // This map is required to defend against users that forcibly call Security.addProvider / Security.removeProvider - // that could cause unexpected and suspicious behaviour. - // i.e. if someone removes a Provider and then he/she adds a new one with the same name. - // The val is private to avoid any harmful state changes. - private val providerMap: Map = mapOf( - BouncyCastleProvider.PROVIDER_NAME to getBouncyCastleProvider(), - CordaSecurityProvider.PROVIDER_NAME to CordaSecurityProvider(), - "BCPQC" to BouncyCastlePQCProvider()) // Unfortunately, provider's name is not final in BouncyCastlePQCProvider, so we explicitly set it. - - private fun getBouncyCastleProvider() = BouncyCastleProvider().apply { - putAll(EdDSASecurityProvider()) - // Override the normal EdDSA engine with one which can handle X509 keys. - put("Signature.${EdDSAEngine.SIGNATURE_ALGORITHM}", X509EdDSAEngine::class.qualifiedName) - addKeyInfoConverter(EDDSA_ED25519_SHA512.signatureOID.algorithm, KeyInfoConverter(EDDSA_ED25519_SHA512)) - } - @JvmStatic fun supportedSignatureSchemes(): List = ArrayList(signatureSchemeMap.values) @@ -233,13 +212,6 @@ object Crypto { return providerMap[name] ?: throw IllegalArgumentException("Unrecognised provider: $name") } - init { - // 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. - Security.addProvider(getBouncyCastleProvider()) - Security.addProvider(CordaSecurityProvider()) - } - /** * Normalise an algorithm identifier by converting [DERNull] parameters into a Kotlin null value. */ @@ -886,7 +858,7 @@ object Crypto { // Compute the HMAC-SHA512 using a privateKey as the MAC_key and a seed ByteArray. private fun deriveHMAC(privateKey: PrivateKey, seed: ByteArray): ByteArray { // Compute hmac(privateKey, seed). - val mac = Mac.getInstance("HmacSHA512", providerMap[BouncyCastleProvider.PROVIDER_NAME]) + val mac = Mac.getInstance("HmacSHA512", cordaBouncyCastleProvider) val keyData = when (privateKey) { is BCECPrivateKey -> privateKey.d.toByteArray() is EdDSAPrivateKey -> privateKey.geta() @@ -897,16 +869,6 @@ object Crypto { return mac.doFinal(seed) } - private class KeyInfoConverter(val signatureScheme: SignatureScheme) : AsymmetricKeyInfoConverter { - override fun generatePublic(keyInfo: SubjectPublicKeyInfo?): PublicKey? { - return keyInfo?.let { decodePublicKey(signatureScheme, it.encoded) } - } - - override fun generatePrivate(keyInfo: PrivateKeyInfo?): PrivateKey? { - return keyInfo?.let { decodePrivateKey(signatureScheme, it.encoded) } - } - } - /** * 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. 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 763d585e1e..493a4c0b59 100644 --- a/core/src/main/kotlin/net/corda/core/crypto/CryptoUtils.kt +++ b/core/src/main/kotlin/net/corda/core/crypto/CryptoUtils.kt @@ -13,13 +13,13 @@ package net.corda.core.crypto import net.corda.core.contracts.PrivacySalt +import net.corda.core.crypto.internal.platformSecureRandomFactory import net.corda.core.serialization.SerializationDefaults import net.corda.core.serialization.serialize import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.toBase58 import net.corda.core.utilities.toSHA256Bytes import java.math.BigInteger -import net.corda.core.utilities.SgxSupport import java.nio.ByteBuffer import java.security.* @@ -203,14 +203,6 @@ private class DummySecureRandomSpi : SecureRandomSpi() { } object DummySecureRandom : SecureRandom(DummySecureRandomSpi(), null) -private val _newSecureRandom: () -> SecureRandom by lazy { - when { - SgxSupport.isInsideEnclave -> { { DummySecureRandom } } - System.getProperty("os.name") == "Linux" -> { { SecureRandom.getInstance("NativePRNGNonBlocking") } } - else -> { { SecureRandom.getInstanceStrong() } } - } -} - /** * Get an instance of [SecureRandom] to avoid blocking, due to waiting for additional entropy, when possible. * In this version, the NativePRNGNonBlocking is exclusively used on Linux OS to utilize dev/urandom because in high traffic @@ -230,7 +222,7 @@ private val _newSecureRandom: () -> SecureRandom by lazy { * which should never happen and suggests an unusual JVM or non-standard Java library. */ @Throws(NoSuchAlgorithmException::class) -fun newSecureRandom(): SecureRandom = _newSecureRandom() +fun newSecureRandom(): SecureRandom = platformSecureRandomFactory() /** * Returns a random positive non-zero long generated using a secure RNG. This function sacrifies a bit of entropy in order 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 new file mode 100644 index 0000000000..be3beb0066 --- /dev/null +++ b/core/src/main/kotlin/net/corda/core/crypto/internal/ProviderMap.kt @@ -0,0 +1,56 @@ +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.crypto.DummySecureRandom +import net.corda.core.internal.X509EdDSAEngine +import net.corda.core.utilities.SgxSupport +import net.i2p.crypto.eddsa.EdDSAEngine +import net.i2p.crypto.eddsa.EdDSASecurityProvider +import org.apache.commons.lang.SystemUtils +import org.bouncycastle.asn1.ASN1ObjectIdentifier +import org.bouncycastle.asn1.pkcs.PrivateKeyInfo +import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo +import org.bouncycastle.jcajce.provider.util.AsymmetricKeyInfoConverter +import org.bouncycastle.jce.provider.BouncyCastleProvider +import org.bouncycastle.pqc.jcajce.provider.BouncyCastlePQCProvider +import java.security.SecureRandom +import java.security.Security + +internal 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 +internal val `id-Curve25519ph` = ASN1ObjectIdentifier("1.3.101.112") +internal 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) + 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) + }) +}.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. + Security.addProvider(it) +} +internal val bouncyCastlePQCProvider = BouncyCastlePQCProvider().apply { + require(name == "BCPQC") // The constant it comes from is not final. +} +// This map is required to defend against users that forcibly call Security.addProvider / Security.removeProvider +// that could cause unexpected and suspicious behaviour. +// i.e. if someone removes a Provider and then he/she adds a new one with the same name. +// The val is private to avoid any harmful state changes. +internal val providerMap = listOf(cordaBouncyCastleProvider, cordaSecurityProvider, bouncyCastlePQCProvider).map { it.name to it }.toMap() +internal val platformSecureRandomFactory: () -> SecureRandom = when { + SgxSupport.isInsideEnclave -> { + { DummySecureRandom } + } + SystemUtils.IS_OS_LINUX -> { + { SecureRandom.getInstance("NativePRNGNonBlocking") } + } + else -> SecureRandom::getInstanceStrong +} diff --git a/node/src/main/kotlin/net/corda/node/Corda.kt b/node/src/main/kotlin/net/corda/node/Corda.kt index f8166cf62b..c710fd0197 100644 --- a/node/src/main/kotlin/net/corda/node/Corda.kt +++ b/node/src/main/kotlin/net/corda/node/Corda.kt @@ -13,10 +13,13 @@ package net.corda.node +import net.corda.core.crypto.CordaSecurityProvider +import net.corda.core.crypto.Crypto import kotlin.system.exitProcess import net.corda.node.internal.EnterpriseNode fun main(args: Array) { + Crypto.findProvider(CordaSecurityProvider.PROVIDER_NAME) // Install our SecureRandom before e.g. UUID asks for one. // Pass the arguments to the Node factory. In the Enterprise edition, this line is modified to point to a subclass. // It will exit the process in case of startup failure and is not intended to be used by embedders. If you want // to embed Node in your own container, instantiate it directly and set up the configuration objects yourself.