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 1b0a8e47a1..4ef594062f 100644 --- a/core/src/main/kotlin/net/corda/core/crypto/CordaSecurityProvider.kt +++ b/core/src/main/kotlin/net/corda/core/crypto/CordaSecurityProvider.kt @@ -4,6 +4,7 @@ import net.corda.core.KeepForDJVM import net.corda.core.StubOutForDJVM import net.corda.core.crypto.CordaObjectIdentifier.COMPOSITE_KEY import net.corda.core.crypto.CordaObjectIdentifier.COMPOSITE_SIGNATURE +import net.corda.core.crypto.internal.PlatformSecureRandomService import org.bouncycastle.asn1.ASN1ObjectIdentifier import java.security.Provider @@ -18,6 +19,12 @@ class CordaSecurityProvider : Provider(PROVIDER_NAME, 0.1, "$PROVIDER_NAME secur 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() + } + + @StubOutForDJVM + private fun putPlatformSecureRandomService() { + putService(PlatformSecureRandomService(this)) } } 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 755569df0d..1544707f6e 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 @@ -3,16 +3,40 @@ package net.corda.core.crypto.internal import net.corda.core.DeleteForDJVM +import net.corda.core.crypto.newSecureRandom import org.apache.commons.lang.SystemUtils +import java.security.Provider import java.security.SecureRandom +import java.security.SecureRandomSpi /** * This has been migrated into a separate class so that it * is easier to delete from the core-deterministic module. */ -internal val platformSecureRandom: () -> SecureRandom = when { +val platformSecureRandom: () -> SecureRandom = when { SystemUtils.IS_OS_LINUX -> { { SecureRandom.getInstance("NativePRNGNonBlocking") } } else -> SecureRandom::getInstanceStrong } + +@DeleteForDJVM +class PlatformSecureRandomService(provider: Provider) + : Provider.Service(provider, "SecureRandom", algorithm, PlatformSecureRandomSpi::javaClass.name, null, null) { + + companion object { + const val algorithm = "CordaPRNG" + } + + private val instance: SecureRandomSpi = PlatformSecureRandomSpi() + override fun newInstance(constructorParameter: Any?) = instance +} + +@DeleteForDJVM +private class PlatformSecureRandomSpi : SecureRandomSpi() { + private val secureRandom: SecureRandom = newSecureRandom() + + override fun engineSetSeed(seed: ByteArray) = secureRandom.setSeed(seed) + override fun engineNextBytes(bytes: ByteArray) = secureRandom.nextBytes(bytes) + override fun engineGenerateSeed(numBytes: Int): ByteArray = secureRandom.generateSeed(numBytes) +} 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 03c4a01d72..bd45e7490b 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 @@ -18,12 +18,18 @@ import org.bouncycastle.pqc.jcajce.provider.BouncyCastlePQCProvider import java.security.SecureRandom import java.security.Security -internal val cordaSecurityProvider = CordaSecurityProvider().also { +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). + // Note that internally, [SecureRandom()] will look through all registered providers. + // Then it returns the first PRNG algorithm of the first provider that has registered a SecureRandom + // implementation (in our case [CordaSecurityProvider]), or null if none of the registered providers supplies + // a SecureRandom implementation. 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 { +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) @@ -38,7 +44,7 @@ internal val cordaBouncyCastleProvider = BouncyCastleProvider().apply { // 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 { +val bouncyCastlePQCProvider = BouncyCastlePQCProvider().apply { require(name == "BCPQC") // The constant it comes from is not final. }.also { Security.addProvider(it) @@ -47,7 +53,7 @@ internal val bouncyCastlePQCProvider = BouncyCastlePQCProvider().apply { // 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() +val providerMap = listOf(cordaBouncyCastleProvider, cordaSecurityProvider, bouncyCastlePQCProvider).map { it.name to it }.toMap() @DeleteForDJVM -internal fun platformSecureRandomFactory(): SecureRandom = platformSecureRandom() // To minimise diff of CryptoUtils against open-source. +fun platformSecureRandomFactory(): SecureRandom = platformSecureRandom() // To minimise diff of CryptoUtils against open-source. 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 cd2ed18a79..5f03ea9894 100644 --- a/core/src/test/kotlin/net/corda/core/crypto/CryptoUtilsTest.kt +++ b/core/src/test/kotlin/net/corda/core/crypto/CryptoUtilsTest.kt @@ -6,6 +6,7 @@ import net.corda.core.crypto.Crypto.ECDSA_SECP256R1_SHA256 import net.corda.core.crypto.Crypto.EDDSA_ED25519_SHA512 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 @@ -16,10 +17,7 @@ import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec import org.apache.commons.lang.ArrayUtils.EMPTY_BYTE_ARRAY import org.bouncycastle.asn1.pkcs.PrivateKeyInfo -import org.bouncycastle.asn1.x500.X500Name import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo -import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter -import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey import org.bouncycastle.jce.ECNamedCurveTable @@ -31,9 +29,9 @@ import org.bouncycastle.pqc.jcajce.provider.sphincs.BCSphincs256PublicKey import org.junit.Assert.assertNotEquals import org.junit.Test import java.math.BigInteger -import java.security.KeyPair import java.security.KeyPairGenerator -import java.security.cert.X509Certificate +import java.security.SecureRandom +import java.security.Security import java.util.* import kotlin.test.* @@ -934,26 +932,25 @@ class CryptoUtilsTest { this.outputStream.close() } - private fun createCert(signer: ContentSigner, keyPair: KeyPair): X509Certificate { - val dname = X500Name("CN=TestEntity") - val startDate = Calendar.getInstance().let { cal -> - cal.time = Date() - cal.add(Calendar.HOUR, -1) - cal.time - } - val endDate = Calendar.getInstance().let { cal -> - cal.time = startDate - cal.add(Calendar.YEAR, 1) - cal.time - } - val certificate = JcaX509v3CertificateBuilder( - dname, - BigInteger.TEN, - startDate, - endDate, - dname, - keyPair.public - ).build(signer) - return JcaX509CertificateConverter().getCertificate(certificate) + @Test + fun `test default SecureRandom uses platformSecureRandom`() { + // Note than in Corda, [CordaSecurityProvider] is registered as the first provider. + + // Remove [CordaSecurityProvider] in case it is already registered. + Security.removeProvider(CordaSecurityProvider.PROVIDER_NAME) + // Try after removing CordaSecurityProvider. + val secureRandomNotRegisteredCordaProvider = SecureRandom() + assertNotEquals(PlatformSecureRandomService.algorithm, secureRandomNotRegisteredCordaProvider.algorithm) + + // Now register CordaSecurityProvider as last Provider. + Security.addProvider(CordaSecurityProvider()) + val secureRandomRegisteredLastCordaProvider = SecureRandom() + assertNotEquals(PlatformSecureRandomService.algorithm, secureRandomRegisteredLastCordaProvider.algorithm) + + // Remove Corda Provider again and add it as the first Provider entry. + Security.removeProvider(CordaSecurityProvider.PROVIDER_NAME) + Security.insertProviderAt(CordaSecurityProvider(), 1) // This is base-1. + val secureRandomRegisteredFirstCordaProvider = SecureRandom() + assertEquals(PlatformSecureRandomService.algorithm, secureRandomRegisteredFirstCordaProvider.algorithm) } }