[CORDA-2225] Use platform's non-blocking PRNG when invoking SecureRandom() (#4234)

This commit is contained in:
Konstantinos Chalkias 2018-11-14 18:32:22 +00:00 committed by GitHub
parent e1e5d13941
commit b8327ddf98
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 67 additions and 33 deletions

View File

@ -4,6 +4,7 @@ import net.corda.core.KeepForDJVM
import net.corda.core.StubOutForDJVM import net.corda.core.StubOutForDJVM
import net.corda.core.crypto.CordaObjectIdentifier.COMPOSITE_KEY import net.corda.core.crypto.CordaObjectIdentifier.COMPOSITE_KEY
import net.corda.core.crypto.CordaObjectIdentifier.COMPOSITE_SIGNATURE import net.corda.core.crypto.CordaObjectIdentifier.COMPOSITE_SIGNATURE
import net.corda.core.crypto.internal.PlatformSecureRandomService
import org.bouncycastle.asn1.ASN1ObjectIdentifier import org.bouncycastle.asn1.ASN1ObjectIdentifier
import java.security.Provider 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("Signature.${CompositeSignature.SIGNATURE_ALGORITHM}", CompositeSignature::class.java.name)
put("Alg.Alias.Signature.$COMPOSITE_SIGNATURE", CompositeSignature.SIGNATURE_ALGORITHM) put("Alg.Alias.Signature.$COMPOSITE_SIGNATURE", CompositeSignature.SIGNATURE_ALGORITHM)
put("Alg.Alias.Signature.OID.$COMPOSITE_SIGNATURE", CompositeSignature.SIGNATURE_ALGORITHM) put("Alg.Alias.Signature.OID.$COMPOSITE_SIGNATURE", CompositeSignature.SIGNATURE_ALGORITHM)
putPlatformSecureRandomService()
}
@StubOutForDJVM
private fun putPlatformSecureRandomService() {
putService(PlatformSecureRandomService(this))
} }
} }

View File

@ -3,16 +3,40 @@
package net.corda.core.crypto.internal package net.corda.core.crypto.internal
import net.corda.core.DeleteForDJVM import net.corda.core.DeleteForDJVM
import net.corda.core.crypto.newSecureRandom
import org.apache.commons.lang.SystemUtils import org.apache.commons.lang.SystemUtils
import java.security.Provider
import java.security.SecureRandom import java.security.SecureRandom
import java.security.SecureRandomSpi
/** /**
* This has been migrated into a separate class so that it * This has been migrated into a separate class so that it
* is easier to delete from the core-deterministic module. * is easier to delete from the core-deterministic module.
*/ */
internal val platformSecureRandom: () -> SecureRandom = when { val platformSecureRandom: () -> SecureRandom = when {
SystemUtils.IS_OS_LINUX -> { SystemUtils.IS_OS_LINUX -> {
{ SecureRandom.getInstance("NativePRNGNonBlocking") } { SecureRandom.getInstance("NativePRNGNonBlocking") }
} }
else -> SecureRandom::getInstanceStrong 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)
}

View File

@ -18,12 +18,18 @@ import org.bouncycastle.pqc.jcajce.provider.BouncyCastlePQCProvider
import java.security.SecureRandom import java.security.SecureRandom
import java.security.Security 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. Security.insertProviderAt(it, 1) // The position is 1-based.
} }
// OID taken from https://tools.ietf.org/html/draft-ietf-curdle-pkix-00 // OID taken from https://tools.ietf.org/html/draft-ietf-curdle-pkix-00
internal val `id-Curve25519ph` = ASN1ObjectIdentifier("1.3.101.112") val `id-Curve25519ph` = ASN1ObjectIdentifier("1.3.101.112")
internal val cordaBouncyCastleProvider = BouncyCastleProvider().apply { val cordaBouncyCastleProvider = BouncyCastleProvider().apply {
putAll(EdDSASecurityProvider()) putAll(EdDSASecurityProvider())
// Override the normal EdDSA engine with one which can handle X509 keys. // Override the normal EdDSA engine with one which can handle X509 keys.
put("Signature.${EdDSAEngine.SIGNATURE_ALGORITHM}", X509EdDSAEngine::class.java.name) 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. // 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) Security.addProvider(it)
} }
internal val bouncyCastlePQCProvider = BouncyCastlePQCProvider().apply { val bouncyCastlePQCProvider = BouncyCastlePQCProvider().apply {
require(name == "BCPQC") // The constant it comes from is not final. require(name == "BCPQC") // The constant it comes from is not final.
}.also { }.also {
Security.addProvider(it) Security.addProvider(it)
@ -47,7 +53,7 @@ internal val bouncyCastlePQCProvider = BouncyCastlePQCProvider().apply {
// that could cause unexpected and suspicious behaviour. // 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. // 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. // 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 @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.

View File

@ -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.EDDSA_ED25519_SHA512
import net.corda.core.crypto.Crypto.RSA_SHA256 import net.corda.core.crypto.Crypto.RSA_SHA256
import net.corda.core.crypto.Crypto.SPHINCS256_SHA256 import net.corda.core.crypto.Crypto.SPHINCS256_SHA256
import net.corda.core.crypto.internal.PlatformSecureRandomService
import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.OpaqueBytes
import net.i2p.crypto.eddsa.EdDSAKey import net.i2p.crypto.eddsa.EdDSAKey
import net.i2p.crypto.eddsa.EdDSAPrivateKey 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 net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec
import org.apache.commons.lang.ArrayUtils.EMPTY_BYTE_ARRAY import org.apache.commons.lang.ArrayUtils.EMPTY_BYTE_ARRAY
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo import org.bouncycastle.asn1.pkcs.PrivateKeyInfo
import org.bouncycastle.asn1.x500.X500Name
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo 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.BCECPrivateKey
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey
import org.bouncycastle.jce.ECNamedCurveTable 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.Assert.assertNotEquals
import org.junit.Test import org.junit.Test
import java.math.BigInteger import java.math.BigInteger
import java.security.KeyPair
import java.security.KeyPairGenerator import java.security.KeyPairGenerator
import java.security.cert.X509Certificate import java.security.SecureRandom
import java.security.Security
import java.util.* import java.util.*
import kotlin.test.* import kotlin.test.*
@ -934,26 +932,25 @@ class CryptoUtilsTest {
this.outputStream.close() this.outputStream.close()
} }
private fun createCert(signer: ContentSigner, keyPair: KeyPair): X509Certificate { @Test
val dname = X500Name("CN=TestEntity") fun `test default SecureRandom uses platformSecureRandom`() {
val startDate = Calendar.getInstance().let { cal -> // Note than in Corda, [CordaSecurityProvider] is registered as the first provider.
cal.time = Date()
cal.add(Calendar.HOUR, -1) // Remove [CordaSecurityProvider] in case it is already registered.
cal.time Security.removeProvider(CordaSecurityProvider.PROVIDER_NAME)
} // Try after removing CordaSecurityProvider.
val endDate = Calendar.getInstance().let { cal -> val secureRandomNotRegisteredCordaProvider = SecureRandom()
cal.time = startDate assertNotEquals(PlatformSecureRandomService.algorithm, secureRandomNotRegisteredCordaProvider.algorithm)
cal.add(Calendar.YEAR, 1)
cal.time // Now register CordaSecurityProvider as last Provider.
} Security.addProvider(CordaSecurityProvider())
val certificate = JcaX509v3CertificateBuilder( val secureRandomRegisteredLastCordaProvider = SecureRandom()
dname, assertNotEquals(PlatformSecureRandomService.algorithm, secureRandomRegisteredLastCordaProvider.algorithm)
BigInteger.TEN,
startDate, // Remove Corda Provider again and add it as the first Provider entry.
endDate, Security.removeProvider(CordaSecurityProvider.PROVIDER_NAME)
dname, Security.insertProviderAt(CordaSecurityProvider(), 1) // This is base-1.
keyPair.public val secureRandomRegisteredFirstCordaProvider = SecureRandom()
).build(signer) assertEquals(PlatformSecureRandomService.algorithm, secureRandomRegisteredFirstCordaProvider.algorithm)
return JcaX509CertificateConverter().getCertificate(certificate)
} }
} }