From 36a091dd6a584a9d2ed922ae1df802df2dec836d Mon Sep 17 00:00:00 2001 From: Ross Nicoll Date: Tue, 23 May 2017 12:05:22 +0100 Subject: [PATCH] Add support for X509Certificate and CertPath serialization --- .../net/corda/core/crypto/X509Utilities.kt | 6 ++-- .../serialization/DefaultKryoCustomizer.kt | 9 +++++ .../net/corda/core/serialization/Kryo.kt | 33 +++++++++++++++++++ .../net/corda/core/serialization/KryoTests.kt | 23 +++++++++++++ .../network/InMemoryIdentityServiceTests.kt | 4 +-- 5 files changed, 70 insertions(+), 5 deletions(-) diff --git a/core/src/main/kotlin/net/corda/core/crypto/X509Utilities.kt b/core/src/main/kotlin/net/corda/core/crypto/X509Utilities.kt index 1834bf04b5..73a7192afa 100644 --- a/core/src/main/kotlin/net/corda/core/crypto/X509Utilities.kt +++ b/core/src/main/kotlin/net/corda/core/crypto/X509Utilities.kt @@ -188,15 +188,15 @@ object X509Utilities { * @param revocationEnabled whether revocation of certificates in the path should be checked. */ fun createCertificatePath(rootCertAndKey: CertificateAndKeyPair, - targetCertAndKey: CertificateAndKeyPair, + targetCertAndKey: X509Certificate, revocationEnabled: Boolean): CertPathBuilderResult { - val intermediateCertificates = setOf(targetCertAndKey.certificate) + val intermediateCertificates = setOf(targetCertAndKey) val certStore = CertStore.getInstance("Collection", CollectionCertStoreParameters(intermediateCertificates)) val certPathFactory = CertPathBuilder.getInstance("PKIX") val trustAnchor = TrustAnchor(rootCertAndKey.certificate, null) val certPathParameters = try { PKIXBuilderParameters(setOf(trustAnchor), X509CertSelector().apply { - certificate = targetCertAndKey.certificate + certificate = targetCertAndKey }) } catch (ex: InvalidAlgorithmParameterException) { throw RuntimeException(ex) diff --git a/core/src/main/kotlin/net/corda/core/serialization/DefaultKryoCustomizer.kt b/core/src/main/kotlin/net/corda/core/serialization/DefaultKryoCustomizer.kt index b553a0f67f..825ce18a20 100644 --- a/core/src/main/kotlin/net/corda/core/serialization/DefaultKryoCustomizer.kt +++ b/core/src/main/kotlin/net/corda/core/serialization/DefaultKryoCustomizer.kt @@ -26,9 +26,12 @@ import org.bouncycastle.pqc.jcajce.provider.sphincs.BCSphincs256PrivateKey import org.bouncycastle.pqc.jcajce.provider.sphincs.BCSphincs256PublicKey import org.objenesis.strategy.StdInstantiatorStrategy import org.slf4j.Logger +import sun.security.provider.certpath.X509CertPath import java.io.BufferedInputStream import java.io.FileInputStream import java.io.InputStream +import java.security.cert.CertPath +import java.security.cert.X509Certificate import java.util.* object DefaultKryoCustomizer { @@ -97,6 +100,12 @@ object DefaultKryoCustomizer { // Note that return type should be specifically set to InputStream, otherwise it may not work, i.e. val aStream : InputStream = HashCheckingStream(...). addDefaultSerializer(InputStream::class.java, InputStreamSerializer) + register(CertPath::class.java, CertPathSerializer) + register(X509CertPath::class.java, CertPathSerializer) + // TODO: We shouldn't need to serialize raw certificates, and if we do then we need a cleaner solution + // than this mess. + val x509CertObjectClazz = Class.forName("org.bouncycastle.jcajce.provider.asymmetric.x509.X509CertificateObject") + register(x509CertObjectClazz, X509CertificateSerializer) register(X500Name::class.java, X500NameSerializer) register(BCECPrivateKey::class.java, PrivateKeySerializer) diff --git a/core/src/main/kotlin/net/corda/core/serialization/Kryo.kt b/core/src/main/kotlin/net/corda/core/serialization/Kryo.kt index 8eba57307d..788bc93ad8 100644 --- a/core/src/main/kotlin/net/corda/core/serialization/Kryo.kt +++ b/core/src/main/kotlin/net/corda/core/serialization/Kryo.kt @@ -30,6 +30,9 @@ import java.nio.file.Files import java.nio.file.Path import java.security.PrivateKey import java.security.PublicKey +import java.security.cert.CertPath +import java.security.cert.CertificateFactory +import java.security.cert.X509Certificate import java.security.spec.InvalidKeySpecException import java.time.Instant import java.util.* @@ -613,6 +616,36 @@ object X500NameSerializer : Serializer() { } } +/** + * For serialising an [CertPath] in an X.500 standard format. + */ +@ThreadSafe +object CertPathSerializer : Serializer() { + val factory = CertificateFactory.getInstance("X.509") + override fun read(kryo: Kryo, input: Input, type: Class): CertPath { + return factory.generateCertPath(input) + } + + override fun write(kryo: Kryo, output: Output, obj: CertPath) { + output.writeBytes(obj.encoded) + } +} + +/** + * For serialising an [CX509Certificate] in an X.500 standard format. + */ +@ThreadSafe +object X509CertificateSerializer : Serializer() { + val factory = CertificateFactory.getInstance("X.509") + override fun read(kryo: Kryo, input: Input, type: Class): X509Certificate { + return factory.generateCertificate(input) as X509Certificate + } + + override fun write(kryo: Kryo, output: Output, obj: X509Certificate) { + output.writeBytes(obj.encoded) + } +} + class KryoPoolWithContext(val baseKryoPool: KryoPool, val contextKey: Any, val context: Any) : KryoPool { override fun run(callback: KryoCallback): T { val kryo = borrow() diff --git a/core/src/test/kotlin/net/corda/core/serialization/KryoTests.kt b/core/src/test/kotlin/net/corda/core/serialization/KryoTests.kt index 5d30f4b17d..4309ba3521 100644 --- a/core/src/test/kotlin/net/corda/core/serialization/KryoTests.kt +++ b/core/src/test/kotlin/net/corda/core/serialization/KryoTests.kt @@ -3,8 +3,11 @@ package net.corda.core.serialization import com.esotericsoftware.kryo.Kryo import com.google.common.primitives.Ints import net.corda.core.crypto.* +import net.corda.core.utilities.ALICE +import net.corda.core.utilities.BOB import net.corda.node.services.messaging.Ack import net.corda.node.services.persistence.NodeAttachmentService +import net.corda.testing.BOB_PUBKEY import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThatThrownBy import org.junit.Before @@ -12,6 +15,8 @@ import org.junit.Test import org.slf4j.LoggerFactory import java.io.ByteArrayInputStream import java.io.InputStream +import java.security.cert.CertPath +import java.security.cert.X509Certificate import java.time.Instant import java.util.* import kotlin.test.assertEquals @@ -136,6 +141,24 @@ class KryoTests { assertEquals(-1, readRubbishStream.read()) } + @Test + fun `serialize - deserialize X509Certififcate`() { + val expected = X509Utilities.createSelfSignedCACert(ALICE.name).certificate + val serialized = expected.serialize(kryo).bytes + val actual: X509Certificate = serialized.deserialize(kryo) + assertEquals(expected, actual) + } + + @Test + fun `serialize - deserialize X509CertPath`() { + val rootCA = X509Utilities.createSelfSignedCACert(ALICE.name) + val certificate = X509Utilities.createTlsServerCert(BOB.name, BOB_PUBKEY, rootCA, emptyList(), emptyList()) + val expected = X509Utilities.createCertificatePath(rootCA, certificate, false).certPath + val serialized = expected.serialize(kryo).bytes + val actual: CertPath = serialized.deserialize(kryo) + assertEquals(expected, actual) + } + @CordaSerializable private data class Person(val name: String, val birthday: Instant?) diff --git a/node/src/test/kotlin/net/corda/node/services/network/InMemoryIdentityServiceTests.kt b/node/src/test/kotlin/net/corda/node/services/network/InMemoryIdentityServiceTests.kt index 5733b2b6f3..f6057b11e1 100644 --- a/node/src/test/kotlin/net/corda/node/services/network/InMemoryIdentityServiceTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/network/InMemoryIdentityServiceTests.kt @@ -87,10 +87,10 @@ class InMemoryIdentityServiceTests { fun `assert ownership`() { val aliceRootCertAndKey = X509Utilities.createSelfSignedCACert(ALICE.name) val aliceTxCertAndKey = X509Utilities.createIntermediateCert(ALICE.name, aliceRootCertAndKey) - val aliceCertPath = X509Utilities.createCertificatePath(aliceRootCertAndKey, aliceTxCertAndKey, false).certPath + val aliceCertPath = X509Utilities.createCertificatePath(aliceRootCertAndKey, aliceTxCertAndKey.certificate, false).certPath val bobRootCertAndKey = X509Utilities.createSelfSignedCACert(BOB.name) val bobTxCertAndKey = X509Utilities.createIntermediateCert(BOB.name, bobRootCertAndKey) - val bobCertPath = X509Utilities.createCertificatePath(bobRootCertAndKey, bobTxCertAndKey, false).certPath + val bobCertPath = X509Utilities.createCertificatePath(bobRootCertAndKey, bobTxCertAndKey.certificate, false).certPath val service = InMemoryIdentityService() val alice = Party(aliceRootCertAndKey) val anonymousAlice = AnonymousParty(aliceTxCertAndKey.keyPair.public)