mirror of
https://github.com/corda/corda.git
synced 2025-06-18 23:28:21 +00:00
CORDA-540: Add verification to ensure that private keys can only be serialized with specific contexts (#1800)
This commit is contained in:
@ -30,6 +30,9 @@ import kotlin.collections.LinkedHashSet
|
|||||||
* we can often end up pulling in a lot of objects that do not make sense to put in a checkpoint.
|
* we can often end up pulling in a lot of objects that do not make sense to put in a checkpoint.
|
||||||
* Thus, by blacklisting classes/interfaces we don't expect to be serialised, we can better handle/monitor the aforementioned behaviour.
|
* Thus, by blacklisting classes/interfaces we don't expect to be serialised, we can better handle/monitor the aforementioned behaviour.
|
||||||
* Inheritance works for blacklisted items, but one can specifically exclude classes from blacklisting as well.
|
* Inheritance works for blacklisted items, but one can specifically exclude classes from blacklisting as well.
|
||||||
|
* Note: Custom serializer registration trumps white/black lists. So if a given type has a custom serializer and has its name
|
||||||
|
* in the blacklist - it will still be serialized as specified by custom serializer.
|
||||||
|
* For more details, see [net.corda.nodeapi.internal.serialization.CordaClassResolver.getRegistration]
|
||||||
*/
|
*/
|
||||||
object AllButBlacklisted : ClassWhitelist {
|
object AllButBlacklisted : ClassWhitelist {
|
||||||
|
|
||||||
|
@ -10,30 +10,23 @@ import com.esotericsoftware.kryo.util.MapReferenceResolver
|
|||||||
import net.corda.core.concurrent.CordaFuture
|
import net.corda.core.concurrent.CordaFuture
|
||||||
import net.corda.core.contracts.PrivacySalt
|
import net.corda.core.contracts.PrivacySalt
|
||||||
import net.corda.core.contracts.StateRef
|
import net.corda.core.contracts.StateRef
|
||||||
import net.corda.core.crypto.CompositeKey
|
|
||||||
import net.corda.core.crypto.Crypto
|
import net.corda.core.crypto.Crypto
|
||||||
import net.corda.core.crypto.TransactionSignature
|
import net.corda.core.crypto.TransactionSignature
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.internal.uncheckedCast
|
import net.corda.core.internal.uncheckedCast
|
||||||
import net.corda.core.serialization.SerializationContext
|
import net.corda.core.serialization.SerializationContext
|
||||||
|
import net.corda.core.serialization.SerializationContext.UseCase.*
|
||||||
import net.corda.core.serialization.SerializeAsTokenContext
|
import net.corda.core.serialization.SerializeAsTokenContext
|
||||||
import net.corda.core.serialization.SerializedBytes
|
import net.corda.core.serialization.SerializedBytes
|
||||||
import net.corda.core.toFuture
|
import net.corda.core.toFuture
|
||||||
import net.corda.core.toObservable
|
import net.corda.core.toObservable
|
||||||
import net.corda.core.transactions.*
|
import net.corda.core.transactions.*
|
||||||
import net.i2p.crypto.eddsa.EdDSAPrivateKey
|
|
||||||
import net.i2p.crypto.eddsa.EdDSAPublicKey
|
|
||||||
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveSpec
|
|
||||||
import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec
|
|
||||||
import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec
|
|
||||||
import org.bouncycastle.asn1.ASN1InputStream
|
import org.bouncycastle.asn1.ASN1InputStream
|
||||||
import org.bouncycastle.asn1.x500.X500Name
|
import org.bouncycastle.asn1.x500.X500Name
|
||||||
import org.bouncycastle.cert.X509CertificateHolder
|
import org.bouncycastle.cert.X509CertificateHolder
|
||||||
import org.slf4j.Logger
|
import org.slf4j.Logger
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import sun.security.ec.ECPublicKeyImpl
|
|
||||||
import sun.security.util.DerValue
|
|
||||||
import java.io.ByteArrayInputStream
|
import java.io.ByteArrayInputStream
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.lang.reflect.InvocationTargetException
|
import java.lang.reflect.InvocationTargetException
|
||||||
@ -285,9 +278,16 @@ object SignedTransactionSerializer : Serializer<SignedTransaction>() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sealed class UseCaseSerializer<T>(private val allowedUseCases: EnumSet<SerializationContext.UseCase>) : Serializer<T>() {
|
||||||
|
protected fun checkUseCase() {
|
||||||
|
checkUseCase(allowedUseCases)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@ThreadSafe
|
@ThreadSafe
|
||||||
object PrivateKeySerializer : Serializer<PrivateKey>() {
|
object PrivateKeySerializer : UseCaseSerializer<PrivateKey>(EnumSet.of(Storage, Checkpoint)) {
|
||||||
override fun write(kryo: Kryo, output: Output, obj: PrivateKey) {
|
override fun write(kryo: Kryo, output: Output, obj: PrivateKey) {
|
||||||
|
checkUseCase()
|
||||||
output.writeBytesWithLength(obj.encoded)
|
output.writeBytesWithLength(obj.encoded)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,12 @@
|
|||||||
|
package net.corda.nodeapi.internal.serialization
|
||||||
|
|
||||||
|
import net.corda.core.serialization.SerializationContext
|
||||||
|
import net.corda.core.serialization.SerializationFactory
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
internal fun checkUseCase(allowedUseCases: EnumSet<SerializationContext.UseCase>) {
|
||||||
|
val currentContext: SerializationContext = SerializationFactory.currentFactory?.currentContext ?: throw IllegalStateException("Current context is not set")
|
||||||
|
if(!allowedUseCases.contains(currentContext.useCase)) {
|
||||||
|
throw IllegalStateException("UseCase '${currentContext.useCase}' is not within '$allowedUseCases'")
|
||||||
|
}
|
||||||
|
}
|
@ -43,7 +43,7 @@ class KryoTests : TestDependencyInjectionBase() {
|
|||||||
AllWhitelist,
|
AllWhitelist,
|
||||||
emptyMap(),
|
emptyMap(),
|
||||||
true,
|
true,
|
||||||
SerializationContext.UseCase.P2P)
|
SerializationContext.UseCase.Storage)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -0,0 +1,42 @@
|
|||||||
|
package net.corda.nodeapi.internal.serialization
|
||||||
|
|
||||||
|
import net.corda.core.crypto.Crypto
|
||||||
|
import net.corda.core.serialization.SerializationContext.UseCase.*
|
||||||
|
import net.corda.core.serialization.SerializationDefaults
|
||||||
|
import net.corda.core.serialization.serialize
|
||||||
|
import net.corda.testing.TestDependencyInjectionBase
|
||||||
|
import org.assertj.core.api.Assertions.assertThatThrownBy
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.junit.runners.Parameterized
|
||||||
|
import java.security.PrivateKey
|
||||||
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
|
@RunWith(Parameterized::class)
|
||||||
|
class PrivateKeySerializationTest(private val privateKey: PrivateKey, private val testName: String) : TestDependencyInjectionBase() {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@JvmStatic
|
||||||
|
@Parameterized.Parameters(name = "{1}")
|
||||||
|
fun data(): Collection<Array<Any>> {
|
||||||
|
val privateKeys: List<PrivateKey> = Crypto.supportedSignatureSchemes().filterNot { Crypto.COMPOSITE_KEY === it }
|
||||||
|
.map { Crypto.generateKeyPair(it).private }
|
||||||
|
|
||||||
|
return privateKeys.map { arrayOf<Any>(it, PrivateKeySerializationTest::class.java.simpleName + "-" + it.javaClass.simpleName) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `passed with expected UseCases`() {
|
||||||
|
assertTrue { privateKey.serialize(context = SerializationDefaults.STORAGE_CONTEXT).bytes.isNotEmpty() }
|
||||||
|
assertTrue { privateKey.serialize(context = SerializationDefaults.CHECKPOINT_CONTEXT).bytes.isNotEmpty() }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `failed with wrong UseCase`() {
|
||||||
|
assertThatThrownBy { privateKey.serialize(context = SerializationDefaults.P2P_CONTEXT) }
|
||||||
|
.isInstanceOf(IllegalStateException::class.java)
|
||||||
|
.hasMessageContaining("UseCase '${P2P}' is not within")
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user