ENT-1439 Delegate SecureRandom to thread-local (#575)

while minimising CryptoUtils diff
This commit is contained in:
Andrzej Cichocki 2018-03-20 17:56:32 +00:00 committed by GitHub
parent 8a62af57e6
commit 96d976d555
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 126 additions and 9 deletions

View File

@ -14,6 +14,12 @@ import net.corda.core.crypto.CordaObjectIdentifier.COMPOSITE_KEY
import net.corda.core.crypto.CordaObjectIdentifier.COMPOSITE_SIGNATURE
import org.bouncycastle.asn1.ASN1ObjectIdentifier
import java.security.Provider
import io.netty.util.concurrent.FastThreadLocal
import net.corda.core.internal.VisibleForTesting
import java.security.SecureRandom
import java.security.SecureRandomSpi
internal const val CORDA_SECURE_RANDOM_ALGORITHM = "CordaPRNG"
class CordaSecurityProvider : Provider(PROVIDER_NAME, 0.1, "$PROVIDER_NAME security provider wrapper") {
companion object {
@ -27,6 +33,11 @@ class CordaSecurityProvider : Provider(PROVIDER_NAME, 0.1, "$PROVIDER_NAME secur
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)
// Assuming this Provider is the first SecureRandom Provider, this algorithm is the SecureRandom default:
putService(DelegatingSecureRandomService(this) {
// Unlike all the NativePRNG algorithms, this doesn't use a global lock:
object : SecureRandom(sun.security.provider.SecureRandom(), null) {}
})
}
}
@ -38,3 +49,26 @@ object CordaObjectIdentifier {
@JvmField
val COMPOSITE_SIGNATURE = ASN1ObjectIdentifier("2.25.30086077608615255153862931087626791003")
}
internal class DelegatingSecureRandomService internal constructor(
provider: CordaSecurityProvider, secureRandomFactory: () -> SecureRandom) : Provider.Service(
provider, type, CORDA_SECURE_RANDOM_ALGORITHM, DelegatingSecureRandomSpi::class.java.name, null, null) {
internal companion object {
internal val type = "SecureRandom"
}
internal val instance = DelegatingSecureRandomSpi(secureRandomFactory)
override fun newInstance(constructorParameter: Any?) = instance
}
internal class DelegatingSecureRandomSpi internal constructor(secureRandomFactory: () -> SecureRandom) : SecureRandomSpi() {
private val threadLocalSecureRandom = object : FastThreadLocal<SecureRandom>() {
override fun initialValue() = secureRandomFactory()
}
override fun engineSetSeed(seed: ByteArray) = threadLocalSecureRandom.get().setSeed(seed)
override fun engineNextBytes(bytes: ByteArray) = threadLocalSecureRandom.get().nextBytes(bytes)
override fun engineGenerateSeed(numBytes: Int): ByteArray? = threadLocalSecureRandom.get().generateSeed(numBytes)
@VisibleForTesting
internal fun currentThreadSecureRandom() = threadLocalSecureRandom.get()
}

View File

@ -1,15 +1,16 @@
package net.corda.core.crypto.internal
import net.corda.core.crypto.CORDA_SECURE_RANDOM_ALGORITHM
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.VisibleForTesting
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
@ -45,12 +46,10 @@ internal 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 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
@VisibleForTesting
internal val platformSecureRandom = when {
SgxSupport.isInsideEnclave -> DummySecureRandom
else -> SecureRandom.getInstance(CORDA_SECURE_RANDOM_ALGORITHM)
}
internal fun platformSecureRandomFactory() = platformSecureRandom // To minimise diff of CryptoUtils against open-source.

View File

@ -0,0 +1,84 @@
package net.corda.core.crypto
import net.corda.core.crypto.internal.cordaSecurityProvider
import net.corda.core.internal.concurrent.fork
import net.corda.core.internal.join
import net.corda.core.utilities.getOrThrow
import org.junit.Test
import java.security.SecureRandom
import java.util.concurrent.Executors
import kotlin.test.assertEquals
import kotlin.test.assertNotEquals
import kotlin.test.assertNotSame
import kotlin.test.assertSame
class SecureRandomTest {
private companion object {
private val getSpi = SecureRandom::class.java.getDeclaredMethod("getSecureRandomSpi").apply { isAccessible = true }
private fun SecureRandom.spi() = getSpi.invoke(this)
init {
newSecureRandom() // Ensure all globals installed before running tests.
}
}
@Test
fun `newSecureRandom returns a global that delegates to thread-local`() {
val sr = newSecureRandom()
assertSame(sr, newSecureRandom())
checkDelegatesToThreadLocal(sr)
}
@Test
fun `regular SecureRandom delegates to thread-local`() {
val sr = SecureRandom()
assertSame(sr.spi(), SecureRandom().spi())
checkDelegatesToThreadLocal(sr)
}
@Test(timeout = 1000)
fun `regular SecureRandom does not spend a lot of time seeding itself`() {
val bytes = ByteArray(1000)
repeat(10) {
val sr = SecureRandom()
repeat(100) {
sr.nextBytes(bytes)
}
}
}
@Test
fun `regular SecureRandom with seed delegates to thread-local`() {
val sr = SecureRandom(byteArrayOf(1, 2, 3))
assertSame(sr.spi(), SecureRandom(byteArrayOf(4, 5, 6)).spi())
checkDelegatesToThreadLocal(sr)
}
@Test
fun `SecureRandom#getInstance makes a SecureRandom that delegates to thread-local`() {
CORDA_SECURE_RANDOM_ALGORITHM.let {
val sr = SecureRandom.getInstance(it)
assertEquals(it, sr.algorithm)
assertSame(sr.spi(), SecureRandom.getInstance(it).spi())
checkDelegatesToThreadLocal(sr)
}
}
private fun checkDelegatesToThreadLocal(sr: SecureRandom) {
val spi = sr.spi() as DelegatingSecureRandomSpi
val fg = spi.currentThreadSecureRandom()
val e = Executors.newSingleThreadExecutor()
val bg = e.fork(spi::currentThreadSecureRandom).getOrThrow()
assertNotSame(fg, bg) // Background thread got a distinct instance.
// Each thread always gets the same instance:
assertSame(fg, spi.currentThreadSecureRandom())
assertSame(bg, e.fork(spi::currentThreadSecureRandom).getOrThrow())
e.join()
assertSame(fg.provider, bg.provider)
assertNotSame(cordaSecurityProvider, fg.provider)
assertEquals(fg.algorithm, bg.algorithm)
assertNotEquals(CORDA_SECURE_RANDOM_ALGORITHM, fg.algorithm)
assertSame(cordaSecurityProvider, sr.provider)
assertEquals(CORDA_SECURE_RANDOM_ALGORITHM, sr.algorithm)
}
}