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 net.corda.core.crypto.CordaObjectIdentifier.COMPOSITE_SIGNATURE
import org.bouncycastle.asn1.ASN1ObjectIdentifier import org.bouncycastle.asn1.ASN1ObjectIdentifier
import java.security.Provider 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") { class CordaSecurityProvider : Provider(PROVIDER_NAME, 0.1, "$PROVIDER_NAME security provider wrapper") {
companion object { 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.KeyFactory.OID.$COMPOSITE_KEY", CompositeKey.KEY_ALGORITHM)
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)
// 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 @JvmField
val COMPOSITE_SIGNATURE = ASN1ObjectIdentifier("2.25.30086077608615255153862931087626791003") 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 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.CordaSecurityProvider
import net.corda.core.crypto.Crypto.EDDSA_ED25519_SHA512 import net.corda.core.crypto.Crypto.EDDSA_ED25519_SHA512
import net.corda.core.crypto.Crypto.decodePrivateKey import net.corda.core.crypto.Crypto.decodePrivateKey
import net.corda.core.crypto.Crypto.decodePublicKey import net.corda.core.crypto.Crypto.decodePublicKey
import net.corda.core.crypto.DummySecureRandom import net.corda.core.crypto.DummySecureRandom
import net.corda.core.internal.VisibleForTesting
import net.corda.core.internal.X509EdDSAEngine import net.corda.core.internal.X509EdDSAEngine
import net.corda.core.utilities.SgxSupport import net.corda.core.utilities.SgxSupport
import net.i2p.crypto.eddsa.EdDSAEngine import net.i2p.crypto.eddsa.EdDSAEngine
import net.i2p.crypto.eddsa.EdDSASecurityProvider import net.i2p.crypto.eddsa.EdDSASecurityProvider
import org.apache.commons.lang.SystemUtils
import org.bouncycastle.asn1.ASN1ObjectIdentifier import org.bouncycastle.asn1.ASN1ObjectIdentifier
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo import org.bouncycastle.asn1.pkcs.PrivateKeyInfo
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo 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. // 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() internal val providerMap = listOf(cordaBouncyCastleProvider, cordaSecurityProvider, bouncyCastlePQCProvider).map { it.name to it }.toMap()
internal val platformSecureRandomFactory: () -> SecureRandom = when { @VisibleForTesting
SgxSupport.isInsideEnclave -> { internal val platformSecureRandom = when {
{ DummySecureRandom } SgxSupport.isInsideEnclave -> DummySecureRandom
} else -> SecureRandom.getInstance(CORDA_SECURE_RANDOM_ALGORITHM)
SystemUtils.IS_OS_LINUX -> {
{ SecureRandom.getInstance("NativePRNGNonBlocking") }
}
else -> SecureRandom::getInstanceStrong
} }
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)
}
}