ENT-4595 harmonize core and serialization (#5792)

* Harmonize serialization/core and deterministic counterparts

* Fix test for changed private alias key behaviour

* Detekt errors

* roll back project.xml
This commit is contained in:
Christian Sailer 2019-12-09 14:17:48 +00:00 committed by Rick Parker
parent 87b39bf515
commit 14050826e9
37 changed files with 468 additions and 72 deletions

View File

@ -77,7 +77,7 @@ buildscript {
ext.djvm_version = constants.getProperty("djvmVersion")
ext.deterministic_rt_version = constants.getProperty('deterministicRtVersion')
ext.okhttp_version = '3.14.2'
ext.netty_version = '4.1.22.Final'
ext.netty_version = '4.1.29.Final'
ext.typesafe_config_version = constants.getProperty("typesafeConfigVersion")
ext.fileupload_version = '1.4'
// Legacy JUnit 4 version
@ -95,10 +95,10 @@ buildscript {
ext.jansi_version = '1.18'
ext.hibernate_version = '5.4.3.Final'
ext.h2_version = '1.4.199' // Update docs if renamed or removed.
ext.postgresql_version = '42.2.5'
ext.postgresql_version = '42.2.8'
ext.rxjava_version = '1.3.8'
ext.dokka_version = '0.9.17'
ext.eddsa_version = '0.2.0'
ext.eddsa_version = '0.3.0'
ext.dependency_checker_version = '5.2.0'
ext.commons_collections_version = '4.3'
ext.beanutils_version = '1.9.3'
@ -120,6 +120,7 @@ buildscript {
ext.class_graph_version = constants.getProperty('classgraphVersion')
ext.jcabi_manifests_version = '1.1'
ext.picocli_version = '3.9.6'
ext.commons_lang_version = '3.9'
ext.commons_io_version = '2.6'
ext.controlsfx_version = '8.40.15'
if (JavaVersion.current() == JavaVersion.VERSION_11) {

View File

@ -58,10 +58,13 @@ task patchCore(type: Zip, dependsOn: coreJarTask) {
from(compileKotlin)
from(processResources)
from(zipTree(originalJar)) {
exclude 'net/corda/core/crypto/DelegatingSecureRandomService*.class'
exclude 'net/corda/core/crypto/SHA256DigestSupplier.class'
exclude 'net/corda/core/internal/*ToggleField*.class'
exclude 'net/corda/core/serialization/*SerializationFactory*.class'
exclude 'net/corda/core/serialization/internal/CheckpointSerializationFactory*.class'
exclude 'net/corda/core/internal/rules/*.class'
exclude 'net/corda/core/utilities/SgxSupport*.class'
}
reproducibleFileOrder = true

View File

@ -0,0 +1,19 @@
package net.corda.core.crypto
import java.security.Provider
import java.security.SecureRandomSpi
@Suppress("unused")
class DelegatingSecureRandomService(provider: CordaSecurityProvider)
: Provider.Service(provider, "SecureRandom", "dummy-algorithm", UnsupportedSecureRandomSpi::javaClass.name, null, null) {
private val instance: SecureRandomSpi = UnsupportedSecureRandomSpi(algorithm)
override fun newInstance(param: Any?) = instance
private class UnsupportedSecureRandomSpi(private val algorithm: String) : SecureRandomSpi() {
override fun engineSetSeed(seed: ByteArray) = unsupported()
override fun engineNextBytes(bytes: ByteArray) = unsupported()
override fun engineGenerateSeed(numBytes: Int) = unsupported()
private fun unsupported(): Nothing = throw UnsupportedOperationException("$algorithm not supported")
}
}

View File

@ -0,0 +1,9 @@
package net.corda.core.crypto
import java.security.MessageDigest
import java.util.function.Supplier
@Suppress("unused")
private class SHA256DigestSupplier : Supplier<MessageDigest> {
override fun get(): MessageDigest = MessageDigest.getInstance("SHA-256")
}

View File

@ -0,0 +1,9 @@
package net.corda.core.utilities
import net.corda.core.KeepForDJVM
@KeepForDJVM
object SgxSupport {
@JvmStatic
val isInsideEnclave: Boolean = true
}

View File

@ -60,7 +60,7 @@ class FinalityFlowTests : WithFinality {
fun `allow use of the old API if the CorDapp target version is 3`() {
val oldBob = createBob(cordapps = listOf(tokenOldCordapp()))
val stx = aliceNode.issuesCashTo(oldBob)
val resultFuture = CordappResolver.withCordapp(targetPlatformVersion = 3) {
val resultFuture = CordappResolver.withTestCordapp(targetPlatformVersion = 3) {
@Suppress("DEPRECATION")
aliceNode.startFlowAndRunNetwork(FinalityFlow(stx)).resultFuture
}

View File

@ -59,7 +59,7 @@ dependencies {
// RxJava: observable streams of events.
compile "io.reactivex:rxjava:$rxjava_version"
compile "org.apache.commons:commons-lang3:3.9"
compile "org.apache.commons:commons-lang3:$commons_lang_version"
// Java ed25519 implementation. See https://github.com/str4d/ed25519-java/
compile "net.i2p.crypto:eddsa:$eddsa_version"
@ -74,6 +74,9 @@ dependencies {
// required to use @Type annotation
compile "org.hibernate:hibernate-core:$hibernate_version"
// FastThreadLocal
compile "io.netty:netty-common:$netty_version"
compile group: "io.github.classgraph", name: "classgraph", version: class_graph_version
testCompile "org.ow2.asm:asm:$asm_version"

View File

@ -7,6 +7,8 @@ import net.corda.core.crypto.CordaObjectIdentifier.COMPOSITE_SIGNATURE
import net.corda.core.crypto.internal.PlatformSecureRandomService
import org.bouncycastle.asn1.ASN1ObjectIdentifier
import java.security.Provider
import java.util.*
import java.util.concurrent.ConcurrentHashMap
@KeepForDJVM
@Suppress("DEPRECATION") // JDK11: should replace with Provider(String name, double version, String info) (since 9)
@ -29,6 +31,40 @@ class CordaSecurityProvider : Provider(PROVIDER_NAME, 0.1, "$PROVIDER_NAME secur
private fun putPlatformSecureRandomService() {
putService(PlatformSecureRandomService(this))
}
override fun getService(type: String, algorithm: String): Service? = serviceFactory(type, algorithm)
// Used to work around banning of ConcurrentHashMap in DJVM
@Suppress("TooGenericExceptionCaught")
private val serviceFactory: (String, String) -> Service? = try {
// Will throw UnsupportedOperationException in DJVM
makeCachingFactory()
} catch (e: Exception) {
makeFactory()
}
private fun superGetService(type: String, algorithm: String): Service? = super.getService(type, algorithm)
@StubOutForDJVM
private fun makeCachingFactory(): Function2<String, String, Service?> {
return object : Function2<String, String, Service?> {
private val services = ConcurrentHashMap<Pair<String, String>, Optional<Service>>()
override fun invoke(type: String, algorithm: String): Service? {
return services.getOrPut(Pair(type, algorithm)) {
Optional.ofNullable(superGetService(type, algorithm))
}.orElse(null)
}
}
}
private fun makeFactory(): Function2<String, String, Service?> {
return object : Function2<String, String, Service?> {
override fun invoke(type: String, algorithm: String): Service? {
return superGetService(type, algorithm)
}
}
}
}
@KeepForDJVM

View File

@ -1,9 +1,16 @@
package net.corda.core.crypto
import net.corda.core.CordaOID
import net.corda.core.DeleteForDJVM
import net.corda.core.KeepForDJVM
import net.corda.core.StubOutForDJVM
import net.corda.core.crypto.internal.*
import net.corda.core.crypto.internal.AliasPrivateKey
import net.corda.core.crypto.internal.Instances.withSignature
import net.corda.core.crypto.internal.`id-Curve25519ph`
import net.corda.core.crypto.internal.bouncyCastlePQCProvider
import net.corda.core.crypto.internal.cordaBouncyCastleProvider
import net.corda.core.crypto.internal.cordaSecurityProvider
import net.corda.core.crypto.internal.providerMap
import net.corda.core.serialization.serialize
import net.i2p.crypto.eddsa.EdDSAEngine
import net.i2p.crypto.eddsa.EdDSAPrivateKey
@ -14,7 +21,9 @@ import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable
import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec
import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec
import org.bouncycastle.asn1.ASN1Integer
import org.bouncycastle.asn1.ASN1ObjectIdentifier
import org.bouncycastle.asn1.DERNull
import org.bouncycastle.asn1.DERUTF8String
import org.bouncycastle.asn1.DLSequence
import org.bouncycastle.asn1.bc.BCObjectIdentifiers
import org.bouncycastle.asn1.nist.NISTObjectIdentifiers
@ -42,7 +51,15 @@ import org.bouncycastle.pqc.jcajce.provider.sphincs.BCSphincs256PrivateKey
import org.bouncycastle.pqc.jcajce.provider.sphincs.BCSphincs256PublicKey
import org.bouncycastle.pqc.jcajce.spec.SPHINCS256KeyGenParameterSpec
import java.math.BigInteger
import java.security.*
import java.security.InvalidKeyException
import java.security.Key
import java.security.KeyFactory
import java.security.KeyPair
import java.security.KeyPairGenerator
import java.security.PrivateKey
import java.security.Provider
import java.security.PublicKey
import java.security.SignatureException
import java.security.spec.InvalidKeySpecException
import java.security.spec.PKCS8EncodedKeySpec
import java.security.spec.X509EncodedKeySpec
@ -289,11 +306,21 @@ object Crypto {
@JvmStatic
fun decodePrivateKey(encodedKey: ByteArray): PrivateKey {
val keyInfo = PrivateKeyInfo.getInstance(encodedKey)
if (keyInfo.privateKeyAlgorithm.algorithm == ASN1ObjectIdentifier(CordaOID.ALIAS_PRIVATE_KEY)) {
return decodeAliasPrivateKey(keyInfo)
}
val signatureScheme = findSignatureScheme(keyInfo.privateKeyAlgorithm)
val keyFactory = keyFactory(signatureScheme)
return keyFactory.generatePrivate(PKCS8EncodedKeySpec(encodedKey))
}
private fun decodeAliasPrivateKey(keyInfo: PrivateKeyInfo): PrivateKey {
val encodable = keyInfo.parsePrivateKey() as DLSequence
val derutF8String = encodable.getObjectAt(0)
val alias = (derutF8String as DERUTF8String).string
return AliasPrivateKey(alias)
}
/**
* Decode a PKCS8 encoded key to its [PrivateKey] object based on the input scheme code name.
* This should be used when the type key is known, e.g. during deserialisation or with key caches or key managers.
@ -436,23 +463,24 @@ object Crypto {
"Unsupported key/algorithm for schemeCodeName: ${signatureScheme.schemeCodeName}"
}
require(clearData.isNotEmpty()) { "Signing of an empty array is not permitted!" }
val signature = Instances.getSignatureInstance(signatureScheme.signatureName, providerMap[signatureScheme.providerName])
// Note that deterministic signature schemes, such as EdDSA, original SPHINCS-256 and RSA PKCS#1, do not require
// extra randomness, but we have to ensure that non-deterministic algorithms (i.e., ECDSA) use non-blocking
// SecureRandom implementation. Also, SPHINCS-256 implementation in BouncyCastle 1.60 fails with
// ClassCastException if we invoke initSign with a SecureRandom as an input.
// TODO Although we handle the above issue here, consider updating to BC 1.61+ which provides a fix.
if (signatureScheme == EDDSA_ED25519_SHA512
|| signatureScheme == SPHINCS256_SHA256
|| signatureScheme == RSA_SHA256) {
signature.initSign(privateKey)
} else {
// The rest of the algorithms will require a SecureRandom input (i.e., ECDSA or any new algorithm for which
// we don't know if it's deterministic).
signature.initSign(privateKey, newSecureRandom())
return withSignature(signatureScheme) { signature ->
// Note that deterministic signature schemes, such as EdDSA, original SPHINCS-256 and RSA PKCS#1, do not require
// extra randomness, but we have to ensure that non-deterministic algorithms (i.e., ECDSA) use non-blocking
// SecureRandom implementation. Also, SPHINCS-256 implementation in BouncyCastle 1.60 fails with
// ClassCastException if we invoke initSign with a SecureRandom as an input.
// TODO Although we handle the above issue here, consider updating to BC 1.61+ which provides a fix.
if (signatureScheme == EDDSA_ED25519_SHA512
|| signatureScheme == SPHINCS256_SHA256
|| signatureScheme == RSA_SHA256) {
signature.initSign(privateKey)
} else {
// The rest of the algorithms will require a SecureRandom input (i.e., ECDSA or any new algorithm for which
// we don't know if it's deterministic).
signature.initSign(privateKey, newSecureRandom())
}
signature.update(clearData)
signature.sign()
}
signature.update(clearData)
return signature.sign()
}
/**
@ -640,10 +668,11 @@ object Crypto {
require(isSupportedSignatureScheme(signatureScheme)) {
"Unsupported key/algorithm for schemeCodeName: ${signatureScheme.schemeCodeName}"
}
val signature = Instances.getSignatureInstance(signatureScheme.signatureName, providerMap[signatureScheme.providerName])
signature.initVerify(publicKey)
signature.update(clearData)
return signature.verify(signatureData)
return withSignature(signatureScheme) { signature ->
signature.initVerify(publicKey)
signature.update(clearData)
signature.verify(signatureData)
}
}
/**

View File

@ -1,3 +1,4 @@
@file:Suppress("MatchingDeclarationName")
@file:KeepForDJVM
@file:JvmName("CryptoUtils")
@ -14,7 +15,14 @@ import net.corda.core.utilities.toBase58
import net.corda.core.utilities.toSHA256Bytes
import java.math.BigInteger
import java.nio.ByteBuffer
import java.security.*
import java.security.InvalidKeyException
import java.security.KeyPair
import java.security.NoSuchAlgorithmException
import java.security.PrivateKey
import java.security.PublicKey
import java.security.SecureRandom
import java.security.SecureRandomSpi
import java.security.SignatureException
/**
* Utility to simplify the act of signing a byte array.
@ -116,6 +124,7 @@ val PublicKey.keys: Set<PublicKey> get() = (this as? CompositeKey)?.leafKeys ?:
/** Return true if [otherKey] fulfils the requirements of this [PublicKey]. */
fun PublicKey.isFulfilledBy(otherKey: PublicKey): Boolean = isFulfilledBy(setOf(otherKey))
/** Return true if [otherKeys] fulfil the requirements of this [PublicKey]. */
fun PublicKey.isFulfilledBy(otherKeys: Iterable<PublicKey>): Boolean = (this as? CompositeKey)?.isFulfilledBy(otherKeys) ?: (this in otherKeys)
@ -137,6 +146,7 @@ fun Iterable<TransactionSignature>.byKeys() = map { it.by }.toSet()
// val (private, public) = keyPair
/* The [PrivateKey] of this [KeyPair]. */
operator fun KeyPair.component1(): PrivateKey = this.private
/* The [PublicKey] of this [KeyPair]. */
operator fun KeyPair.component2(): PublicKey = this.public
@ -190,6 +200,29 @@ fun KeyPair.verify(signatureData: ByteArray, clearData: ByteArray): Boolean = Cr
@Throws(NoSuchAlgorithmException::class)
fun secureRandomBytes(numOfBytes: Int): ByteArray = ByteArray(numOfBytes).apply { newSecureRandom().nextBytes(this) }
/**
* This is a hack added because during deserialisation when no-param constructors are called sometimes default values
* generate random numbers, which fail in SGX.
* TODO remove this once deserialisation is figured out.
*/
private class DummySecureRandomSpi : SecureRandomSpi() {
override fun engineSetSeed(bytes: ByteArray?) {
Exception("DummySecureRandomSpi.engineSetSeed called").printStackTrace(System.out)
}
override fun engineNextBytes(bytes: ByteArray?) {
Exception("DummySecureRandomSpi.engineNextBytes called").printStackTrace(System.out)
bytes?.fill(0)
}
override fun engineGenerateSeed(numberOfBytes: Int): ByteArray {
Exception("DummySecureRandomSpi.engineGenerateSeed called").printStackTrace(System.out)
return ByteArray(numberOfBytes)
}
}
object DummySecureRandom : SecureRandom(DummySecureRandomSpi(), null)
/**
* Get an instance of [SecureRandom] to avoid blocking, due to waiting for additional entropy, when possible.
* In this version, the NativePRNGNonBlocking is exclusively used on Linux OS to utilize dev/urandom because in high traffic
@ -254,3 +287,4 @@ fun <T : Any> serializedHash(x: T): SecureHash = x.serialize(context = Serializa
* @return SHA256(SHA256(privacySalt || groupIndex || internalIndex))
*/
fun computeNonce(privacySalt: PrivacySalt, groupIndex: Int, internalIndex: Int) = SecureHash.sha256Twice(privacySalt.bytes + ByteBuffer.allocate(8).putInt(groupIndex).putInt(internalIndex).array())

View File

@ -1,6 +1,7 @@
@file:KeepForDJVM
package net.corda.core.crypto
import io.netty.util.concurrent.FastThreadLocal
import net.corda.core.DeleteForDJVM
import net.corda.core.KeepForDJVM
import net.corda.core.serialization.CordaSerializable
@ -9,6 +10,7 @@ import net.corda.core.utilities.parseAsHex
import net.corda.core.utilities.toHexString
import java.nio.ByteBuffer
import java.security.MessageDigest
import java.util.function.Supplier
/**
* Container for a cryptographically secure hash value.
@ -69,12 +71,14 @@ sealed class SecureHash(bytes: ByteArray) : OpaqueBytes(bytes) {
} ?: throw IllegalArgumentException("Provided string is null")
}
private val sha256MessageDigest = SHA256DigestSupplier()
/**
* Computes the SHA-256 hash value of the [ByteArray].
* @param bytes The [ByteArray] to hash.
*/
@JvmStatic
fun sha256(bytes: ByteArray) = SHA256(MessageDigest.getInstance("SHA-256").digest(bytes))
fun sha256(bytes: ByteArray) = SHA256(sha256MessageDigest.get().digest(bytes))
/**
* Computes the SHA-256 hash of the [ByteArray], and then computes the SHA-256 hash of the hash.
@ -139,4 +143,17 @@ fun ByteArray.sha256(): SecureHash.SHA256 = SecureHash.sha256(this)
*/
fun OpaqueBytes.sha256(): SecureHash.SHA256 = SecureHash.sha256(this.bytes)
/**
* Hide the [FastThreadLocal] class behind a [Supplier] interface
* so that we can remove it for core-deterministic.
*/
private class SHA256DigestSupplier : Supplier<MessageDigest> {
private val threadLocalSha256MessageDigest = LocalSHA256Digest()
override fun get(): MessageDigest = threadLocalSha256MessageDigest.get()
}
// Declaring this as "object : FastThreadLocal<>" would have
// created an extra public class in the API definition.
private class LocalSHA256Digest : FastThreadLocal<MessageDigest>() {
override fun initialValue(): MessageDigest = MessageDigest.getInstance("SHA-256")
}

View File

@ -1,12 +1,73 @@
package net.corda.core.crypto.internal
import net.corda.core.DeleteForDJVM
import net.corda.core.StubOutForDJVM
import net.corda.core.crypto.SignatureScheme
import net.corda.core.internal.LazyPool
import java.security.Provider
import java.security.Signature
import java.util.concurrent.ConcurrentHashMap
/**
* This is a collection of crypto related getInstance methods that tend to be quite inefficient and we want to be able to
* optimise them en masse.
*/
object Instances {
fun getSignatureInstance(algorithm: String, provider: Provider?) = Signature.getInstance(algorithm, provider)
}
fun <A> withSignature(signatureScheme: SignatureScheme, func: (signature: Signature) -> A): A {
val signature = getSignatureInstance(signatureScheme.signatureName, providerMap[signatureScheme.providerName])
try {
return func(signature)
} finally {
releaseSignatureInstance(signature)
}
}
fun getSignatureInstance(algorithm: String, provider: Provider?) = signatureFactory.borrow(algorithm, provider)
fun releaseSignatureInstance(sig: Signature) = signatureFactory.release(sig)
// Used to work around banning of ConcurrentHashMap in DJVM
private val signatureFactory: SignatureFactory = try {
makeCachingFactory()
} catch (e: UnsupportedOperationException) {
// Thrown by DJVM for method stubbed out below.
makeFactory()
}
// The provider itself is a very bad key class as hashCode() is expensive and contended. So use name and version instead.
private data class SignatureKey(val algorithm: String, val providerName: String?, val providerVersion: Double?) {
constructor(algorithm: String, provider: Provider?) : this(algorithm, provider?.name,
@Suppress("DEPRECATION") provider?.version) // JDK11: should replace with getVersionStr() (since 9)
}
@StubOutForDJVM
private fun makeCachingFactory(): SignatureFactory {
return CachingSignatureFactory()
}
@DeleteForDJVM
private class CachingSignatureFactory : SignatureFactory {
private val signatureInstances = ConcurrentHashMap<SignatureKey, LazyPool<Signature>>()
override fun borrow(algorithm: String, provider: Provider?): Signature {
return signatureInstances.getOrPut(SignatureKey(algorithm, provider)) {
LazyPool(newInstance = { Signature.getInstance(algorithm, provider) })
}.borrow()
}
override fun release(sig: Signature): Unit =
signatureInstances[SignatureKey(sig.algorithm, sig.provider)]?.release(sig)!!
}
private fun makeFactory(): SignatureFactory {
return object : SignatureFactory {
override fun borrow(algorithm: String, provider: Provider?): Signature {
return Signature.getInstance(algorithm, provider)
}
}
}
}
interface SignatureFactory {
fun borrow(algorithm: String, provider: Provider?): Signature
fun release(sig: Signature) {}
}

View File

@ -2,9 +2,17 @@
@file:DeleteForDJVM
package net.corda.core.crypto.internal
import io.netty.util.concurrent.FastThreadLocal
import net.corda.core.DeleteForDJVM
import net.corda.core.crypto.newSecureRandom
import net.corda.core.crypto.DummySecureRandom
import net.corda.core.utilities.SgxSupport
import net.corda.core.utilities.loggerFor
import org.apache.commons.lang3.SystemUtils
import java.io.DataInputStream
import java.io.File
import java.io.FileInputStream
import java.io.IOException
import java.io.InputStream
import java.security.Provider
import java.security.SecureRandom
import java.security.SecureRandomSpi
@ -13,11 +21,13 @@ import java.security.SecureRandomSpi
* This has been migrated into a separate class so that it
* is easier to delete from the core-deterministic module.
*/
val platformSecureRandom: () -> SecureRandom = when {
SystemUtils.IS_OS_LINUX -> {
{ SecureRandom.getInstance("NativePRNGNonBlocking") }
internal val platformSecureRandom: () -> SecureRandom = when {
SgxSupport.isInsideEnclave -> {
{ DummySecureRandom }
}
else -> {
{ sharedSecureRandom }
}
else -> SecureRandom::getInstanceStrong
}
@DeleteForDJVM
@ -26,17 +36,65 @@ class PlatformSecureRandomService(provider: Provider)
companion object {
const val algorithm = "CordaPRNG"
private val logger = loggerFor<PlatformSecureRandomService>()
}
private val instance: SecureRandomSpi = if (SystemUtils.IS_OS_LINUX) tryAndUseLinuxSecureRandomSpi() else PlatformSecureRandomSpi()
@Suppress("TooGenericExceptionCaught", "TooGenericExceptionThrown")
private fun tryAndUseLinuxSecureRandomSpi(): SecureRandomSpi = try {
LinuxSecureRandomSpi()
} catch (e: Exception) {
logger.error("Unable to initialise LinuxSecureRandomSpi. The exception logged with this message might assist with diagnosis." +
" The process will now exit.", e)
System.exit(1)
throw RuntimeException("Never reached, but calms the compiler.")
}
private val instance: SecureRandomSpi = PlatformSecureRandomSpi()
override fun newInstance(constructorParameter: Any?) = instance
}
@DeleteForDJVM
private class PlatformSecureRandomSpi : SecureRandomSpi() {
private val secureRandom: SecureRandom = newSecureRandom()
private val threadLocalSecureRandom = object : FastThreadLocal<SecureRandom>() {
override fun initialValue() = SecureRandom.getInstanceStrong()
}
private val secureRandom: SecureRandom get() = 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)
}
@DeleteForDJVM
@Suppress("TooGenericExceptionCaught", "TooGenericExceptionThrown")
private class LinuxSecureRandomSpi : SecureRandomSpi() {
private fun openURandom(): InputStream {
try {
val file = File("/dev/urandom")
val stream = FileInputStream(file)
if (stream.read() == -1)
throw RuntimeException("/dev/urandom not readable?")
return stream
} catch (e: Exception) {
throw RuntimeException(e)
}
}
private var urandom = DataInputStream(openURandom())
override fun engineSetSeed(seed: ByteArray) {}
override fun engineNextBytes(bytes: ByteArray) = try {
urandom.readFully(bytes)
} catch (e: IOException) {
throw RuntimeException(e)
}
override fun engineGenerateSeed(numBytes: Int): ByteArray = ByteArray(numBytes).apply { engineNextBytes(this) }
}
// This is safe to share because of the underlying implementation of SecureRandomSpi
private val sharedSecureRandom: SecureRandom by lazy(LazyThreadSafetyMode.PUBLICATION) {
SecureRandom.getInstance(PlatformSecureRandomService.algorithm)
}

View File

@ -26,13 +26,19 @@ abstract class BackpressureAwareTimedFlow<ResultType> : FlowLogic<ResultType>(),
val unwrapped = wrappedResult.fromUntrustedWorld
when (unwrapped) {
is WaitTimeUpdate -> {
logger.info("Counterparty [${session.counterparty}] is busy - TimedFlow $runId has been asked to wait for an additional ${unwrapped.waitTime} seconds for completion.")
stateMachine.updateTimedFlowTimeout(unwrapped.waitTime.seconds)
applyWaitTimeUpdate(session, unwrapped)
}
is ReceiveType -> @Suppress("UNCHECKED_CAST") // The compiler doesn't understand it's checked in the line above
return wrappedResult as UntrustworthyData<ReceiveType>
else -> throw throw IllegalArgumentException("We were expecting a ${ReceiveType::class.java.name} or WaitTimeUpdate but we instead got a ${unwrapped.javaClass.name} ($unwrapped)")
else -> throw throw IllegalArgumentException("We were expecting a ${ReceiveType::class.java.name} or WaitTimeUpdate but " +
"we instead got a ${unwrapped.javaClass.name} ($unwrapped)")
}
}
}
open fun applyWaitTimeUpdate(session: FlowSession, update: WaitTimeUpdate) {
logger.info("Counterparty [${session.counterparty}] is busy - TimedFlow $runId has been asked to wait for an additional " +
"${update.waitTime} for completion.")
stateMachine.updateTimedFlowTimeout(update.waitTime.seconds)
}
}

View File

@ -2,10 +2,10 @@
package net.corda.core.internal
import java.util.concurrent.locks.ReentrantLock
import io.github.classgraph.ClassGraph
import io.github.classgraph.ScanResult
import net.corda.core.DeleteForDJVM
import java.util.concurrent.locks.ReentrantLock
import kotlin.concurrent.withLock
private val pooledScanMutex = ReentrantLock()

View File

@ -43,4 +43,5 @@ interface FlowStateMachine<FLOWRETURN> {
val context: InvocationContext
val ourIdentity: Party
val ourSenderUUID: String?
val creationTime: Long
}

View File

@ -27,6 +27,7 @@ import java.lang.reflect.Modifier
import java.math.BigDecimal
import java.net.HttpURLConnection
import java.net.HttpURLConnection.HTTP_OK
import java.net.Proxy
import java.net.URI
import java.net.URL
import java.nio.ByteBuffer
@ -426,15 +427,17 @@ val DEFAULT_HTTP_CONNECT_TIMEOUT = 30.seconds.toMillis()
val DEFAULT_HTTP_READ_TIMEOUT = 30.seconds.toMillis()
@DeleteForDJVM
fun URL.openHttpConnection(): HttpURLConnection = openConnection().also {
fun URL.openHttpConnection(proxy: Proxy? = null): HttpURLConnection = (
if (proxy == null) openConnection()
else openConnection(proxy)).also {
// The default values are 0 which means infinite timeout.
it.connectTimeout = DEFAULT_HTTP_CONNECT_TIMEOUT.toInt()
it.readTimeout = DEFAULT_HTTP_READ_TIMEOUT.toInt()
} as HttpURLConnection
@DeleteForDJVM
fun URL.post(serializedData: OpaqueBytes, vararg properties: Pair<String, String>): ByteArray {
return openHttpConnection().run {
fun URL.post(serializedData: OpaqueBytes, vararg properties: Pair<String, String>, proxy: Proxy? = null): ByteArray {
return openHttpConnection(proxy).run {
doOutput = true
requestMethod = "POST"
properties.forEach { (key, value) -> setRequestProperty(key, value) }

View File

@ -15,19 +15,52 @@ class LifeCycle<S : Enum<S>>(initial: S) {
private val lock = ReentrantReadWriteLock()
private var state = initial
/** Assert that the lifecycle in the [requiredState]. */
fun requireState(requiredState: S) {
requireState({ "Required state to be $requiredState, was $it" }) { it == requiredState }
/**
* Assert that the lifecycle in the [requiredState]. Optionally runs [block], for the duration of which the
* lifecycle is guaranteed to stay in [requiredState].
*/
fun <A> requireState(
requiredState: S,
block: () -> A
): A {
return requireState(
errorMessage = { "Required state to be $requiredState, was $it" },
predicate = { it == requiredState },
block = block
)
}
fun requireState(requiredState: S) = requireState(requiredState) {}
fun <A> requireState(
requiredState: S,
throwable: Throwable,
block: () -> A
): A {
return lock.readLock().withLock {
if (requiredState != state) {
throw throwable
}
block()
}
}
/** Assert something about the current state atomically. */
fun <A> requireState(
errorMessage: (S) -> String,
predicate: (S) -> Boolean,
block: () -> A
): A {
return lock.readLock().withLock {
require(predicate(state)) { errorMessage(state) }
block()
}
}
fun requireState(
errorMessage: (S) -> String = { "Predicate failed on state $it" },
predicate: (S) -> Boolean
) {
lock.readLock().withLock {
require(predicate(state)) { errorMessage(state) }
}
requireState(errorMessage, predicate) {}
}
/** Transition the state from [from] to [to]. */

View File

@ -327,6 +327,7 @@ abstract class Verifier(val ltx: LedgerTransaction, protected val transactionCla
*
* @throws TransactionVerificationException if the constraints fail to verify
*/
@Suppress("NestedBlockDepth", "MagicNumber")
private fun verifyConstraints(contractAttachmentsByContract: Map<ContractClassName, ContractAttachment>) {
// For each contract/constraint pair check that the relevant attachment is valid.
allStates.map { it.contract to it.constraint }.toSet().forEach { (contract, constraint) ->

View File

@ -35,7 +35,8 @@ data class CordappImpl(
val notaryService: Class<out NotaryService>? = null,
/** Indicates whether the CorDapp is loaded from external sources, or generated on node startup (virtual). */
val isLoaded: Boolean = true,
private val explicitCordappClasses: List<String> = emptyList()
private val explicitCordappClasses: List<String> = emptyList(),
val isVirtual: Boolean = false
) : Cordapp {
override val name: String = jarName(jarPath)

View File

@ -88,20 +88,41 @@ object CordappResolver {
*/
val currentTargetVersion: Int get() = currentCordapp?.targetPlatformVersion ?: 1
// A list of extra CorDapps added to the current CorDapps list for testing purposes.
private var extraCordappsForTesting = listOf<Cordapp>()
/**
* Return all the CorDapps that were involved in the call stack at the point the provided exception was generated.
*
* This is provided to allow splitting the cost of generating the exception and retrieving the CorDapps involved.
*/
fun cordappsFromException(exception: Exception): List<Cordapp> {
val apps = exception.stackTrace
.mapNotNull { cordappClasses[it.className] }
.flatten()
.distinct()
return (apps + extraCordappsForTesting)
}
/**
* Temporarily apply a fake CorDapp with the given parameters. For use in testing.
*/
@Synchronized
@VisibleForTesting
fun <T> withCordapp(minimumPlatformVersion: Int = 1, targetPlatformVersion: Int = PLATFORM_VERSION, block: () -> T): T {
fun <T> withTestCordapp(minimumPlatformVersion: Int = 1,
targetPlatformVersion: Int = PLATFORM_VERSION,
extraApps: List<CordappImpl> = listOf(),
block: () -> T): T {
val currentResolver = cordappResolver
cordappResolver = {
CordappImpl.TEST_INSTANCE.copy(minimumPlatformVersion = minimumPlatformVersion, targetPlatformVersion = targetPlatformVersion)
}
extraCordappsForTesting = listOf(cordappResolver()!!) + extraApps
try {
return block()
} finally {
cordappResolver = currentResolver
extraCordappsForTesting = listOf()
}
}

View File

@ -0,0 +1,8 @@
package net.corda.core.internal.utilities
import java.util.concurrent.TimeUnit
import kotlin.system.measureNanoTime
fun measureMilliAndNanoTime(block: () -> Unit): Double {
return measureNanoTime(block).toDouble() / TimeUnit.MILLISECONDS.toNanos(1).toDouble()
}

View File

@ -5,7 +5,10 @@ import net.corda.core.DoNotImplement
import net.corda.core.contracts.*
import net.corda.core.cordapp.CordappContext
import net.corda.core.cordapp.CordappProvider
import net.corda.core.crypto.*
import net.corda.core.crypto.Crypto
import net.corda.core.crypto.SignableData
import net.corda.core.crypto.SignatureMetadata
import net.corda.core.crypto.TransactionSignature
import net.corda.core.flows.ContractUpgradeFlow
import net.corda.core.node.services.*
import net.corda.core.serialization.SerializeAsToken

View File

@ -7,7 +7,6 @@ import net.corda.core.crypto.DigitalSignature
import net.corda.core.crypto.SignableData
import net.corda.core.crypto.TransactionSignature
import net.corda.core.identity.PartyAndCertificate
import net.corda.core.node.ServiceHub
import java.security.KeyPair
import java.security.PrivateKey
import java.security.PublicKey

View File

@ -9,7 +9,6 @@ import net.corda.core.node.services.vault.CollectionOperator.*
import net.corda.core.node.services.vault.ColumnPredicate.*
import net.corda.core.node.services.vault.EqualityComparisonOperator.*
import net.corda.core.node.services.vault.LikenessOperator.*
import net.corda.core.schemas.PersistentState
import net.corda.core.schemas.StatePersistable
import net.corda.core.serialization.CordaSerializable
import java.lang.reflect.Field

View File

@ -22,7 +22,8 @@ import net.corda.core.utilities.contextLogger
import java.security.PublicKey
import java.time.Duration
import java.time.Instant
import java.util.*
import java.util.ArrayDeque
import java.util.UUID
import java.util.regex.Pattern
import kotlin.collections.ArrayList
import kotlin.collections.component1

View File

@ -38,6 +38,9 @@ infix fun Long.exactAdd(b: Long): Long = Math.addExact(this, b)
*/
inline fun <reified T : Any> loggerFor(): Logger = LoggerFactory.getLogger(T::class.java)
/** Returns the logger used for detailed logging. */
fun detailedLogger(): Logger = LoggerFactory.getLogger("DetailedInfo")
/** When called from a companion object, returns the logger for the enclosing class. */
fun Any.contextLogger(): Logger = LoggerFactory.getLogger(javaClass.enclosingClass)

View File

@ -0,0 +1,8 @@
package net.corda.core.utilities
object SgxSupport {
@JvmStatic
val isInsideEnclave: Boolean by lazy {
(System.getProperty("os.name") == "Linux") && (System.getProperty("java.vm.name") == "Avian (Corda)")
}
}

View File

@ -0,0 +1,11 @@
package net.corda.core.crypto
import org.junit.Test
import kotlin.test.assertEquals
class SecureHashTest {
@Test
fun `sha256 does not retain state between same-thread invocations`() {
assertEquals(SecureHash.sha256("abc"), SecureHash.sha256("abc"))
}
}

View File

@ -0,0 +1,17 @@
package net.corda.core.crypto
import org.junit.Test
import java.security.SecureRandom
class SecureRandomTest {
@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)
}
}
}
}

View File

@ -28,7 +28,7 @@ class CordappResolverTest {
assertEquals(defaultTargetVersion, CordappResolver.currentTargetVersion)
val expectedTargetVersion = 555
CordappResolver.withCordapp(targetPlatformVersion = expectedTargetVersion) {
CordappResolver.withTestCordapp(targetPlatformVersion = expectedTargetVersion) {
val actualTargetVersion = CordappResolver.currentTargetVersion
assertEquals(expectedTargetVersion, actualTargetVersion)
}

View File

@ -4,8 +4,8 @@ import net.corda.core.contracts.StateRef
import net.corda.core.crypto.SecureHash
import net.corda.core.internal.declaredField
import org.assertj.core.api.Assertions.catchThrowable
import org.junit.Assert
import org.junit.Assert.assertSame
import org.junit.Assert.assertTrue
import org.junit.Test
import java.nio.ByteBuffer
import java.nio.ReadOnlyBufferException
@ -52,10 +52,10 @@ class ByteArraysTest {
val privacySalt = net.corda.core.contracts.PrivacySalt()
val privacySaltAsHexString = privacySalt.bytes.toHexString()
Assert.assertTrue(privacySaltAsHexString.matches(HEX_REGEX))
assertTrue(privacySaltAsHexString.matches(HEX_REGEX))
val stateRef = StateRef(SecureHash.randomSHA256(), 0)
val txhashAsHexString = stateRef.txhash.bytes.toHexString()
Assert.assertTrue(txhashAsHexString.matches(HEX_REGEX))
assertTrue(txhashAsHexString.matches(HEX_REGEX))
}
}

View File

@ -2,7 +2,6 @@ package net.corda.nodeapi.internal.crypto
import net.corda.core.crypto.internal.AliasPrivateKey
import net.corda.testing.internal.stubs.CertificateStoreStubs
import org.assertj.core.api.Assertions.assertThatIllegalArgumentException
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
@ -20,15 +19,18 @@ class AliasPrivateKeyTest {
val alias = "01234567890"
val aliasPrivateKey = AliasPrivateKey(alias)
val certificatesDirectory = tempFolder.root.toPath()
val signingCertStore = CertificateStoreStubs.Signing.withCertificatesDirectory(certificatesDirectory, "keystorepass").get(createNew = true)
signingCertStore.query { setPrivateKey(alias, aliasPrivateKey, listOf(NOT_YET_REGISTERED_MARKER_KEYS_AND_CERTS.ECDSAR1_CERT), "entrypassword") }
val signingCertStore = CertificateStoreStubs.Signing.withCertificatesDirectory(
certificatesDirectory,
"keystorepass").get(createNew = true)
signingCertStore.query {
setPrivateKey(alias, aliasPrivateKey, listOf(NOT_YET_REGISTERED_MARKER_KEYS_AND_CERTS.ECDSAR1_CERT), "entrypassword")
}
// We can retrieve the certificate.
assertTrue { signingCertStore.contains(alias) }
// We can retrieve the certificate.
assertEquals(NOT_YET_REGISTERED_MARKER_KEYS_AND_CERTS.ECDSAR1_CERT, signingCertStore[alias])
// Although we can store an AliasPrivateKey, we cannot retrieve it. But, it's fine as we use certStore for storing/handling certs only.
assertThatIllegalArgumentException().isThrownBy {
signingCertStore.query { getPrivateKey(alias, "entrypassword") }
}.withMessage("Unrecognised algorithm: 1.3.6.1.4.1.50530.1.2")
// Although we can store an AliasPrivateKey, we cannot retrieve it. But, it's fine as we use certStore for storing/handling certs
// only.
assertEquals(aliasPrivateKey, signingCertStore.query { getPrivateKey(alias, "entrypassword") })
}
}

View File

@ -45,7 +45,8 @@ class TransientReference<out A>(@Transient val value: A)
class FlowStateMachineImpl<R>(override val id: StateMachineRunId,
override val logic: FlowLogic<R>,
scheduler: FiberScheduler
scheduler: FiberScheduler,
override val creationTime: Long = System.currentTimeMillis()
) : Fiber<Unit>(id.toString(), scheduler), FlowStateMachine<R>, FlowFiber {
companion object {
/**

View File

@ -82,7 +82,7 @@ class FinalityHandlerTest {
}
private fun TestStartedNode.finaliseWithOldApi(stx: SignedTransaction): CordaFuture<SignedTransaction> {
return CordappResolver.withCordapp(targetPlatformVersion = 3) {
return CordappResolver.withTestCordapp(targetPlatformVersion = 3) {
@Suppress("DEPRECATION")
services.startFlow(FinalityFlow(stx)).resultFuture.apply {
mockNet.runNetwork()

View File

@ -897,7 +897,7 @@ class NodeVaultServiceTest {
fun List<StateAndRef<DummyState>>.getNumbers() = map { it.state.data.magicNumber }.toSet()
CordappResolver.withCordapp(targetPlatformVersion = 3) {
CordappResolver.withTestCordapp(targetPlatformVersion = 3) {
services.recordTransactions(StatesToRecord.ONLY_RELEVANT, listOf(createTx(1, megaCorp.party)))
services.recordTransactions(StatesToRecord.ONLY_RELEVANT, listOf(createTx(2, miniCorp.party)))
services.recordTransactions(StatesToRecord.ONLY_RELEVANT, listOf(createTx(3, miniCorp.party, megaCorp.party)))

View File

@ -4,7 +4,6 @@ import com.google.common.reflect.TypeToken
import net.corda.core.KeepForDJVM
import net.corda.core.internal.isPublic
import net.corda.core.serialization.SerializableCalculatedProperty
import net.corda.core.utilities.contextLogger
import net.corda.serialization.internal.amqp.MethodClassifier.*
import java.lang.reflect.Field
import java.lang.reflect.Method