Merge remote-tracking branch 'open/master' into mike-merge-4d2d9b83

This commit is contained in:
Mike Hearn
2018-11-16 15:35:27 +00:00
17 changed files with 189 additions and 208 deletions

View File

@ -1,17 +1,12 @@
package net.corda.core.crypto
import io.netty.util.concurrent.FastThreadLocal
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.internal.VisibleForTesting
import net.corda.core.crypto.internal.PlatformSecureRandomService
import org.bouncycastle.asn1.ASN1ObjectIdentifier
import java.security.Provider
import java.security.SecureRandom
import java.security.SecureRandomSpi
internal const val CORDA_SECURE_RANDOM_ALGORITHM = "CordaPRNG"
@KeepForDJVM
class CordaSecurityProvider : Provider(PROVIDER_NAME, 0.1, "$PROVIDER_NAME security provider wrapper") {
@ -24,8 +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)
// Assuming this Provider is the first SecureRandom Provider, this algorithm is the SecureRandom default:
putService(DelegatingSecureRandomService(this))
putPlatformSecureRandomService()
}
@StubOutForDJVM
private fun putPlatformSecureRandomService() {
putService(PlatformSecureRandomService(this))
}
}
@ -50,28 +49,3 @@ object CordaObjectIdentifier {
@JvmField
val COMPOSITE_SIGNATURE = ASN1ObjectIdentifier("2.25.30086077608615255153862931087626791003")
}
// Unlike all the NativePRNG algorithms, this doesn't use a global lock:
private class SunSecureRandom : SecureRandom(sun.security.provider.SecureRandom(), null)
private class DelegatingSecureRandomService(provider: CordaSecurityProvider) : Provider.Service(
provider, type, CORDA_SECURE_RANDOM_ALGORITHM, DelegatingSecureRandomSpi::class.java.name, null, null) {
private companion object {
private const val type = "SecureRandom"
}
internal val instance = DelegatingSecureRandomSpi(::SunSecureRandom)
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

@ -2,17 +2,52 @@
@file:DeleteForDJVM
package net.corda.core.crypto.internal
import io.netty.util.concurrent.FastThreadLocal
import net.corda.core.DeleteForDJVM
import net.corda.core.crypto.CORDA_SECURE_RANDOM_ALGORITHM
import net.corda.core.crypto.DummySecureRandom
import net.corda.core.utilities.SgxSupport
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 = when {
SgxSupport.isInsideEnclave -> DummySecureRandom
else -> SecureRandom.getInstance(CORDA_SECURE_RANDOM_ALGORITHM)
internal val platformSecureRandom: () -> SecureRandom = when {
SgxSupport.isInsideEnclave -> { { DummySecureRandom } }
SystemUtils.IS_OS_LINUX -> {
{ SunSecureRandom() }
}
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 threadLocalSecureRandom = object : FastThreadLocal<SecureRandom>() {
override fun initialValue() = platformSecureRandom()
}
private val secureRandom: SecureRandom = threadLocalSecureRandom.get()
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)
}
// Enterprise performance tweak: Unlike all the NativePRNG algorithms, this doesn't use a global lock:
// TODO: This is using private Java API. Just replace this with an implementation that always reads /dev/urandom on Linux.
private class SunSecureRandom : SecureRandom(sun.security.provider.SecureRandom(), null)

View File

@ -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.

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.RSA_SHA256
import net.corda.core.crypto.Crypto.SPHINCS256_SHA256
import net.corda.core.crypto.internal.PlatformSecureRandomService
import net.corda.core.identity.CordaX500Name
import net.corda.core.utilities.OpaqueBytes
import net.corda.nodeapi.internal.DEV_INTERMEDIATE_CA
@ -21,10 +22,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
@ -36,9 +34,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.*
@ -948,26 +946,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)
}
}

View File

@ -1,41 +1,9 @@
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)
@ -46,39 +14,4 @@ class SecureRandomTest {
}
}
}
@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)
}
}