From bec984cfb404418032e416e4e0082350ea7e6d2a Mon Sep 17 00:00:00 2001 From: Gus Narea Date: Thu, 16 Jul 2020 23:42:56 +0100 Subject: [PATCH 1/6] feat(RAMF): Implement payload unwrapping --- src/main/kotlin/tech/relaycorp/relaynet/messages/Cargo.kt | 7 ++++++- .../kotlin/tech/relaycorp/relaynet/ramf/RAMFMessage.kt | 5 ++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/tech/relaycorp/relaynet/messages/Cargo.kt b/src/main/kotlin/tech/relaycorp/relaynet/messages/Cargo.kt index 2049fcb1..6dea60ab 100644 --- a/src/main/kotlin/tech/relaycorp/relaynet/messages/Cargo.kt +++ b/src/main/kotlin/tech/relaycorp/relaynet/messages/Cargo.kt @@ -1,5 +1,6 @@ package tech.relaycorp.relaynet.messages +import tech.relaycorp.relaynet.messages.payloads.CargoMessageSet import tech.relaycorp.relaynet.ramf.RAMFException import tech.relaycorp.relaynet.ramf.RAMFMessage import tech.relaycorp.relaynet.ramf.RAMFMessageCompanion @@ -21,7 +22,7 @@ class Cargo( creationDate: ZonedDateTime? = null, ttl: Int? = null, senderCertificateChain: Set? = null -) : RAMFMessage( +) : RAMFMessage( SERIALIZER, recipientAddress, payload, @@ -31,6 +32,10 @@ class Cargo( ttl, senderCertificateChain ) { + override fun deserializePayload(payloadPlaintext: ByteArray): CargoMessageSet { + TODO("Not yet implemented") + } + companion object : RAMFMessageCompanion { /** * Deserialize cargo diff --git a/src/main/kotlin/tech/relaycorp/relaynet/ramf/RAMFMessage.kt b/src/main/kotlin/tech/relaycorp/relaynet/ramf/RAMFMessage.kt index 1e86ca9e..a2cce4b7 100644 --- a/src/main/kotlin/tech/relaycorp/relaynet/ramf/RAMFMessage.kt +++ b/src/main/kotlin/tech/relaycorp/relaynet/ramf/RAMFMessage.kt @@ -2,6 +2,7 @@ package tech.relaycorp.relaynet.ramf import tech.relaycorp.relaynet.HashingAlgorithm import tech.relaycorp.relaynet.dateToZonedDateTime +import tech.relaycorp.relaynet.messages.payloads.PayloadPlaintext import tech.relaycorp.relaynet.wrappers.x509.Certificate import tech.relaycorp.relaynet.wrappers.x509.CertificateException import java.net.MalformedURLException @@ -32,7 +33,7 @@ private val PRIVATE_ADDRESS_REGEX = "^0[a-f0-9]+$".toRegex() * @property payload The payload * @property senderCertificate The sender's Relaynet PKI certificate */ -abstract class RAMFMessage internal constructor( +abstract class RAMFMessage internal constructor( private val serializer: RAMFSerializer, val recipientAddress: String, val payload: ByteArray, @@ -113,6 +114,8 @@ abstract class RAMFMessage internal constructor( return this.serializer.serialize(this, senderPrivateKey, hashingAlgorithm) } + protected abstract fun deserializePayload(payloadPlaintext: ByteArray): Payload + /** * Validate the message. * From 0451fec9fed6e6fade6ef24e6a42d2405b524b0e Mon Sep 17 00:00:00 2001 From: Gus Narea Date: Fri, 17 Jul 2020 15:21:39 +0100 Subject: [PATCH 2/6] Implement unwrapping --- .../messages/CargoCollectionAuthorization.kt | 7 +++++- .../relaycorp/relaynet/messages/Parcel.kt | 7 +++++- .../payloads/EmptyPayloadPlaintext.kt | 7 ++++++ .../messages/payloads/ServiceMessage.kt | 7 ++++++ .../relaycorp/relaynet/ramf/RAMFMessage.kt | 22 +++++++++++++++++-- .../relaynet/ramf/RAMFMessageCompanion.kt | 2 +- .../relaycorp/relaynet/ramf/RAMFSerializer.kt | 4 ++-- .../wrappers/cms/EnvelopedDataException.kt | 2 +- .../relaynet/ramf/RAMFMessageTest.kt | 20 +++++++++++++++++ .../relaynet/ramf/RAMFMessageTesting.kt | 4 ++-- .../relaycorp/relaynet/ramf/StubPayload.kt | 7 ++++++ .../relaynet/ramf/StubRAMFMessage.kt | 6 ++++- 12 files changed, 84 insertions(+), 11 deletions(-) create mode 100644 src/main/kotlin/tech/relaycorp/relaynet/messages/payloads/EmptyPayloadPlaintext.kt create mode 100644 src/main/kotlin/tech/relaycorp/relaynet/messages/payloads/ServiceMessage.kt create mode 100644 src/test/kotlin/tech/relaycorp/relaynet/ramf/StubPayload.kt diff --git a/src/main/kotlin/tech/relaycorp/relaynet/messages/CargoCollectionAuthorization.kt b/src/main/kotlin/tech/relaycorp/relaynet/messages/CargoCollectionAuthorization.kt index f9638051..a61f2ab0 100644 --- a/src/main/kotlin/tech/relaycorp/relaynet/messages/CargoCollectionAuthorization.kt +++ b/src/main/kotlin/tech/relaycorp/relaynet/messages/CargoCollectionAuthorization.kt @@ -1,5 +1,6 @@ package tech.relaycorp.relaynet.messages +import tech.relaycorp.relaynet.messages.payloads.EmptyPayloadPlaintext import tech.relaycorp.relaynet.ramf.RAMFException import tech.relaycorp.relaynet.ramf.RAMFMessage import tech.relaycorp.relaynet.ramf.RAMFMessageCompanion @@ -21,7 +22,7 @@ class CargoCollectionAuthorization( creationDate: ZonedDateTime? = null, ttl: Int? = null, senderCertificateChain: Set? = null -) : RAMFMessage( +) : RAMFMessage( SERIALIZER, recipientAddress, payload, @@ -31,6 +32,10 @@ class CargoCollectionAuthorization( ttl, senderCertificateChain ) { + override fun deserializePayload(payloadPlaintext: ByteArray): EmptyPayloadPlaintext { + TODO("Not yet implemented") + } + companion object : RAMFMessageCompanion { /** * Deserialize a CCA diff --git a/src/main/kotlin/tech/relaycorp/relaynet/messages/Parcel.kt b/src/main/kotlin/tech/relaycorp/relaynet/messages/Parcel.kt index f28d9ec0..9f8052c5 100644 --- a/src/main/kotlin/tech/relaycorp/relaynet/messages/Parcel.kt +++ b/src/main/kotlin/tech/relaycorp/relaynet/messages/Parcel.kt @@ -1,5 +1,6 @@ package tech.relaycorp.relaynet.messages +import tech.relaycorp.relaynet.messages.payloads.ServiceMessage import tech.relaycorp.relaynet.ramf.RAMFException import tech.relaycorp.relaynet.ramf.RAMFMessage import tech.relaycorp.relaynet.ramf.RAMFMessageCompanion @@ -21,7 +22,7 @@ class Parcel( creationDate: ZonedDateTime? = null, ttl: Int? = null, senderCertificateChain: Set? = null -) : RAMFMessage( +) : RAMFMessage( SERIALIZER, recipientAddress, payload, @@ -31,6 +32,10 @@ class Parcel( ttl, senderCertificateChain ) { + override fun deserializePayload(payloadPlaintext: ByteArray): ServiceMessage { + TODO("Not yet implemented") + } + companion object : RAMFMessageCompanion { /** * Deserialize parcel diff --git a/src/main/kotlin/tech/relaycorp/relaynet/messages/payloads/EmptyPayloadPlaintext.kt b/src/main/kotlin/tech/relaycorp/relaynet/messages/payloads/EmptyPayloadPlaintext.kt new file mode 100644 index 00000000..b7b16a58 --- /dev/null +++ b/src/main/kotlin/tech/relaycorp/relaynet/messages/payloads/EmptyPayloadPlaintext.kt @@ -0,0 +1,7 @@ +package tech.relaycorp.relaynet.messages.payloads + +class EmptyPayloadPlaintext : PayloadPlaintext { + override fun serialize(): ByteArray { + TODO("Not yet implemented") + } +} diff --git a/src/main/kotlin/tech/relaycorp/relaynet/messages/payloads/ServiceMessage.kt b/src/main/kotlin/tech/relaycorp/relaynet/messages/payloads/ServiceMessage.kt new file mode 100644 index 00000000..cbde45dc --- /dev/null +++ b/src/main/kotlin/tech/relaycorp/relaynet/messages/payloads/ServiceMessage.kt @@ -0,0 +1,7 @@ +package tech.relaycorp.relaynet.messages.payloads + +class ServiceMessage : PayloadPlaintext { + override fun serialize(): ByteArray { + TODO("Not yet implemented") + } +} diff --git a/src/main/kotlin/tech/relaycorp/relaynet/ramf/RAMFMessage.kt b/src/main/kotlin/tech/relaycorp/relaynet/ramf/RAMFMessage.kt index a2cce4b7..2f2f44ee 100644 --- a/src/main/kotlin/tech/relaycorp/relaynet/ramf/RAMFMessage.kt +++ b/src/main/kotlin/tech/relaycorp/relaynet/ramf/RAMFMessage.kt @@ -3,6 +3,9 @@ package tech.relaycorp.relaynet.ramf import tech.relaycorp.relaynet.HashingAlgorithm import tech.relaycorp.relaynet.dateToZonedDateTime import tech.relaycorp.relaynet.messages.payloads.PayloadPlaintext +import tech.relaycorp.relaynet.wrappers.cms.EnvelopedData +import tech.relaycorp.relaynet.wrappers.cms.EnvelopedDataException +import tech.relaycorp.relaynet.wrappers.cms.SessionlessEnvelopedData import tech.relaycorp.relaynet.wrappers.x509.Certificate import tech.relaycorp.relaynet.wrappers.x509.CertificateException import java.net.MalformedURLException @@ -114,8 +117,6 @@ abstract class RAMFMessage internal constructor( return this.serializer.serialize(this, senderPrivateKey, hashingAlgorithm) } - protected abstract fun deserializePayload(payloadPlaintext: ByteArray): Payload - /** * Validate the message. * @@ -134,6 +135,23 @@ abstract class RAMFMessage internal constructor( validateRecipientAddress() } + /** + * Decrypt and deserialize payload. + * + * @throws EnvelopedDataException if the CMS EnvelopedData value is invalid or the + * `privateKey` is invalid. + * @throws RAMFException if the plaintext is invalid. + */ + @Throws(RAMFException::class, EnvelopedDataException::class) + fun unwrapPayload(privateKey: PrivateKey): Payload { + val envelopedData = EnvelopedData.deserialize(payload) as SessionlessEnvelopedData + val plaintext = envelopedData.decrypt(privateKey) + return deserializePayload(plaintext) + } + + @Throws(RAMFException::class) + protected abstract fun deserializePayload(payloadPlaintext: ByteArray): Payload + private fun validateTiming() { val now = ZonedDateTime.now(UTC) if (now < creationDate) { diff --git a/src/main/kotlin/tech/relaycorp/relaynet/ramf/RAMFMessageCompanion.kt b/src/main/kotlin/tech/relaycorp/relaynet/ramf/RAMFMessageCompanion.kt index f328699c..0c5944e9 100644 --- a/src/main/kotlin/tech/relaycorp/relaynet/ramf/RAMFMessageCompanion.kt +++ b/src/main/kotlin/tech/relaycorp/relaynet/ramf/RAMFMessageCompanion.kt @@ -2,7 +2,7 @@ package tech.relaycorp.relaynet.ramf import java.io.InputStream -internal interface RAMFMessageCompanion { +internal interface RAMFMessageCompanion> { fun deserialize(serialization: ByteArray): Message fun deserialize(serialization: InputStream): Message } diff --git a/src/main/kotlin/tech/relaycorp/relaynet/ramf/RAMFSerializer.kt b/src/main/kotlin/tech/relaycorp/relaynet/ramf/RAMFSerializer.kt index 01a49e84..ac010857 100644 --- a/src/main/kotlin/tech/relaycorp/relaynet/ramf/RAMFSerializer.kt +++ b/src/main/kotlin/tech/relaycorp/relaynet/ramf/RAMFSerializer.kt @@ -47,7 +47,7 @@ private data class FieldSet( internal class RAMFSerializer(val concreteMessageType: Byte, val concreteMessageVersion: Byte) { fun serialize( - message: RAMFMessage, + message: RAMFMessage<*>, signerPrivateKey: PrivateKey, hashingAlgorithm: HashingAlgorithm? = null ): ByteArray { @@ -71,7 +71,7 @@ internal class RAMFSerializer(val concreteMessageType: Byte, val concreteMessage } @Throws(IOException::class) - private fun serializeMessage(message: RAMFMessage): ByteArray { + private fun serializeMessage(message: RAMFMessage<*>): ByteArray { val reverseOS = ReverseByteArrayOutputStream(1000, true) var codeLength = 0 diff --git a/src/main/kotlin/tech/relaycorp/relaynet/wrappers/cms/EnvelopedDataException.kt b/src/main/kotlin/tech/relaycorp/relaynet/wrappers/cms/EnvelopedDataException.kt index d8a8fcfc..37fabd34 100644 --- a/src/main/kotlin/tech/relaycorp/relaynet/wrappers/cms/EnvelopedDataException.kt +++ b/src/main/kotlin/tech/relaycorp/relaynet/wrappers/cms/EnvelopedDataException.kt @@ -2,5 +2,5 @@ package tech.relaycorp.relaynet.wrappers.cms import tech.relaycorp.relaynet.RelaynetException -internal class EnvelopedDataException(message: String, cause: Throwable? = null) : +class EnvelopedDataException internal constructor(message: String, cause: Throwable? = null) : RelaynetException(message, cause) diff --git a/src/test/kotlin/tech/relaycorp/relaynet/ramf/RAMFMessageTest.kt b/src/test/kotlin/tech/relaycorp/relaynet/ramf/RAMFMessageTest.kt index 09dc3cdb..f425d417 100644 --- a/src/test/kotlin/tech/relaycorp/relaynet/ramf/RAMFMessageTest.kt +++ b/src/test/kotlin/tech/relaycorp/relaynet/ramf/RAMFMessageTest.kt @@ -5,6 +5,7 @@ import org.junit.jupiter.api.assertThrows import tech.relaycorp.relaynet.HashingAlgorithm import tech.relaycorp.relaynet.issueStubCertificate import tech.relaycorp.relaynet.wrappers.cms.HASHING_ALGORITHM_OIDS +import tech.relaycorp.relaynet.wrappers.cms.SessionlessEnvelopedData import tech.relaycorp.relaynet.wrappers.cms.parseCmsSignedData import tech.relaycorp.relaynet.wrappers.generateRSAKeyPair import tech.relaycorp.relaynet.wrappers.x509.Certificate @@ -464,6 +465,25 @@ class RAMFMessageTest { } } + @Nested + inner class UnwrapPayload { + @Test + fun `SessionlessEnvelopedData payload should be decrypted`() { + val payloadPlaintext = StubPayload("the payload") + val recipientKeyPair = generateRSAKeyPair() + val recipientCertificate = + issueStubCertificate(recipientKeyPair.public, recipientKeyPair.private) + val payloadCiphertext = + SessionlessEnvelopedData.encrypt(payloadPlaintext.serialize(), recipientCertificate) + val message = + StubRAMFMessage(recipientAddress, payloadCiphertext.serialize(), senderCertificate) + + val plaintextDeserialized = message.unwrapPayload(recipientKeyPair.private) + + assertEquals(payloadPlaintext.payload, plaintextDeserialized.payload) + } + } + @Nested inner class IsRecipientAddressPrivate { @Test diff --git a/src/test/kotlin/tech/relaycorp/relaynet/ramf/RAMFMessageTesting.kt b/src/test/kotlin/tech/relaycorp/relaynet/ramf/RAMFMessageTesting.kt index 71903a34..171c2770 100644 --- a/src/test/kotlin/tech/relaycorp/relaynet/ramf/RAMFMessageTesting.kt +++ b/src/test/kotlin/tech/relaycorp/relaynet/ramf/RAMFMessageTesting.kt @@ -14,7 +14,7 @@ private val senderCertificate = issueStubCertificate(keyPair.public, keyPair.pri typealias MinimalRAMFMessageConstructor = (String, ByteArray, Certificate) -> M -fun makeRAMFMessageConstructorTests( +fun > makeRAMFMessageConstructorTests( messageConstructor: RAMFMessageConstructor, requiredParamsConstructor: MinimalRAMFMessageConstructor, expectedConcreteMessageType: Byte, @@ -103,7 +103,7 @@ fun makeRAMFMessageConstructorTests( ) } -internal fun makeRAMFMessageCompanionTests( +internal fun > makeRAMFMessageCompanionTests( companion: RAMFMessageCompanion, messageConstructor: RAMFMessageConstructor ): Collection { diff --git a/src/test/kotlin/tech/relaycorp/relaynet/ramf/StubPayload.kt b/src/test/kotlin/tech/relaycorp/relaynet/ramf/StubPayload.kt new file mode 100644 index 00000000..a89bdbde --- /dev/null +++ b/src/test/kotlin/tech/relaycorp/relaynet/ramf/StubPayload.kt @@ -0,0 +1,7 @@ +package tech.relaycorp.relaynet.ramf + +import tech.relaycorp.relaynet.messages.payloads.PayloadPlaintext + +class StubPayload(val payload: String) : PayloadPlaintext { + override fun serialize() = payload.toByteArray() +} diff --git a/src/test/kotlin/tech/relaycorp/relaynet/ramf/StubRAMFMessage.kt b/src/test/kotlin/tech/relaycorp/relaynet/ramf/StubRAMFMessage.kt index 6f0bb708..6524088c 100644 --- a/src/test/kotlin/tech/relaycorp/relaynet/ramf/StubRAMFMessage.kt +++ b/src/test/kotlin/tech/relaycorp/relaynet/ramf/StubRAMFMessage.kt @@ -2,6 +2,7 @@ package tech.relaycorp.relaynet.ramf import tech.relaycorp.relaynet.wrappers.x509.Certificate import java.io.InputStream +import java.nio.charset.Charset import java.time.ZonedDateTime internal val STUB_SERIALIZER = RAMFSerializer(32, 0) @@ -14,7 +15,7 @@ internal class StubRAMFMessage( creationDate: ZonedDateTime? = null, ttl: Int? = null, senderCertificateChain: Set? = null -) : RAMFMessage( +) : RAMFMessage( STUB_SERIALIZER, recipientAddress, payload, @@ -24,6 +25,9 @@ internal class StubRAMFMessage( ttl, senderCertificateChain ) { + override fun deserializePayload(payloadPlaintext: ByteArray) = + StubPayload(payloadPlaintext.toString(Charset.forName("ASCII"))) + companion object : RAMFMessageCompanion { override fun deserialize(serialization: ByteArray) = STUB_SERIALIZER.deserialize(serialization, ::StubRAMFMessage) From 9e45cb680e2de0b40139077f227e620fff920aba Mon Sep 17 00:00:00 2001 From: Gus Narea Date: Fri, 17 Jul 2020 17:44:20 +0100 Subject: [PATCH 3/6] Refactor testing of RAMF messages --- .../CargoCollectionAuthorizationTest.kt | 32 ++--- .../relaycorp/relaynet/messages/CargoTest.kt | 27 ++--- .../relaycorp/relaynet/messages/ParcelTest.kt | 27 ++--- ...esting.kt => RAMFSerializationTestCase.kt} | 111 ++++++++++-------- 4 files changed, 88 insertions(+), 109 deletions(-) rename src/test/kotlin/tech/relaycorp/relaynet/ramf/{RAMFMessageTesting.kt => RAMFSerializationTestCase.kt} (59%) diff --git a/src/test/kotlin/tech/relaycorp/relaynet/messages/CargoCollectionAuthorizationTest.kt b/src/test/kotlin/tech/relaycorp/relaynet/messages/CargoCollectionAuthorizationTest.kt index a07de952..61db9a0c 100644 --- a/src/test/kotlin/tech/relaycorp/relaynet/messages/CargoCollectionAuthorizationTest.kt +++ b/src/test/kotlin/tech/relaycorp/relaynet/messages/CargoCollectionAuthorizationTest.kt @@ -1,27 +1,13 @@ package tech.relaycorp.relaynet.messages -import org.junit.jupiter.api.Nested -import org.junit.jupiter.api.TestFactory -import tech.relaycorp.relaynet.ramf.makeRAMFMessageCompanionTests -import tech.relaycorp.relaynet.ramf.makeRAMFMessageConstructorTests +import tech.relaycorp.relaynet.ramf.RAMFSerializationTestCase import tech.relaycorp.relaynet.wrappers.x509.Certificate -class CargoCollectionAuthorizationTest { - @TestFactory - fun makeConstructorTests() = - makeRAMFMessageConstructorTests( - ::CargoCollectionAuthorization, - { r: String, p: ByteArray, s: Certificate -> CargoCollectionAuthorization(r, p, s) }, - 0x44, - 0x00 - ) - - @Nested - inner class Companion { - @TestFactory - fun makeDeserializationTests() = makeRAMFMessageCompanionTests( - CargoCollectionAuthorization.Companion, - ::CargoCollectionAuthorization - ) - } -} +internal class CargoCollectionAuthorizationTest : + RAMFSerializationTestCase( + ::CargoCollectionAuthorization, + { r: String, p: ByteArray, s: Certificate -> CargoCollectionAuthorization(r, p, s) }, + 0x44, + 0x00, + CargoCollectionAuthorization.Companion + ) diff --git a/src/test/kotlin/tech/relaycorp/relaynet/messages/CargoTest.kt b/src/test/kotlin/tech/relaycorp/relaynet/messages/CargoTest.kt index 1697a424..05be8a3f 100644 --- a/src/test/kotlin/tech/relaycorp/relaynet/messages/CargoTest.kt +++ b/src/test/kotlin/tech/relaycorp/relaynet/messages/CargoTest.kt @@ -1,23 +1,12 @@ package tech.relaycorp.relaynet.messages -import org.junit.jupiter.api.Nested -import org.junit.jupiter.api.TestFactory -import tech.relaycorp.relaynet.ramf.makeRAMFMessageCompanionTests -import tech.relaycorp.relaynet.ramf.makeRAMFMessageConstructorTests +import tech.relaycorp.relaynet.ramf.RAMFSerializationTestCase import tech.relaycorp.relaynet.wrappers.x509.Certificate -class CargoTest { - @TestFactory - fun makeConstructorTests() = makeRAMFMessageConstructorTests( - ::Cargo, - { r: String, p: ByteArray, s: Certificate -> Cargo(r, p, s) }, - 0x43, - 0x00 - ) - - @Nested - inner class Companion { - @TestFactory - fun makeDeserializationTests() = makeRAMFMessageCompanionTests(Cargo.Companion, ::Cargo) - } -} +internal class CargoTest : RAMFSerializationTestCase( + ::Cargo, + { r: String, p: ByteArray, s: Certificate -> Cargo(r, p, s) }, + 0x43, + 0x00, + Cargo.Companion +) diff --git a/src/test/kotlin/tech/relaycorp/relaynet/messages/ParcelTest.kt b/src/test/kotlin/tech/relaycorp/relaynet/messages/ParcelTest.kt index a8888752..4215ade9 100644 --- a/src/test/kotlin/tech/relaycorp/relaynet/messages/ParcelTest.kt +++ b/src/test/kotlin/tech/relaycorp/relaynet/messages/ParcelTest.kt @@ -1,23 +1,12 @@ package tech.relaycorp.relaynet.messages -import org.junit.jupiter.api.Nested -import org.junit.jupiter.api.TestFactory -import tech.relaycorp.relaynet.ramf.makeRAMFMessageCompanionTests -import tech.relaycorp.relaynet.ramf.makeRAMFMessageConstructorTests +import tech.relaycorp.relaynet.ramf.RAMFSerializationTestCase import tech.relaycorp.relaynet.wrappers.x509.Certificate -class ParcelTest { - @TestFactory - fun makeConstructorTests() = makeRAMFMessageConstructorTests( - ::Parcel, - { r: String, p: ByteArray, s: Certificate -> Parcel(r, p, s) }, - 0x50, - 0x00 - ) - - @Nested - inner class Companion { - @TestFactory - fun makeDeserializationTests() = makeRAMFMessageCompanionTests(Parcel.Companion, ::Parcel) - } -} +internal class ParcelTest : RAMFSerializationTestCase( + ::Parcel, + { r: String, p: ByteArray, s: Certificate -> Parcel(r, p, s) }, + 0x50, + 0x00, + Parcel.Companion +) diff --git a/src/test/kotlin/tech/relaycorp/relaynet/ramf/RAMFMessageTesting.kt b/src/test/kotlin/tech/relaycorp/relaynet/ramf/RAMFSerializationTestCase.kt similarity index 59% rename from src/test/kotlin/tech/relaycorp/relaynet/ramf/RAMFMessageTesting.kt rename to src/test/kotlin/tech/relaycorp/relaynet/ramf/RAMFSerializationTestCase.kt index 171c2770..9e176455 100644 --- a/src/test/kotlin/tech/relaycorp/relaynet/ramf/RAMFMessageTesting.kt +++ b/src/test/kotlin/tech/relaycorp/relaynet/ramf/RAMFSerializationTestCase.kt @@ -1,46 +1,51 @@ package tech.relaycorp.relaynet.ramf -import org.junit.jupiter.api.DynamicTest +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.Test import tech.relaycorp.relaynet.issueStubCertificate import tech.relaycorp.relaynet.wrappers.generateRSAKeyPair import tech.relaycorp.relaynet.wrappers.x509.Certificate import java.time.ZonedDateTime import kotlin.test.assertEquals -private const val recipientAddress = "0deadbeef" -private val payload = "Payload".toByteArray() -private val keyPair = generateRSAKeyPair() -private val senderCertificate = issueStubCertificate(keyPair.public, keyPair.private) - typealias MinimalRAMFMessageConstructor = (String, ByteArray, Certificate) -> M -fun > makeRAMFMessageConstructorTests( - messageConstructor: RAMFMessageConstructor, +internal abstract class RAMFSerializationTestCase>( + private val messageConstructor: RAMFMessageConstructor, requiredParamsConstructor: MinimalRAMFMessageConstructor, - expectedConcreteMessageType: Byte, - expectedConcreteMessageVersion: Byte -): Collection { + private val expectedConcreteMessageType: Byte, + private val expectedConcreteMessageVersion: Byte, + private val companion: RAMFMessageCompanion +) { val simpleMessage = requiredParamsConstructor(recipientAddress, payload, senderCertificate) - return listOf( - DynamicTest.dynamicTest("Recipient address should be honored") { + @Nested + inner class Serialization { + @Test + fun `Recipient address should be honored`() { assertEquals(recipientAddress, simpleMessage.recipientAddress) - }, - DynamicTest.dynamicTest("Payload should be honored") { + } + + @Test + fun `Payload should be honored`() { assertEquals(payload, simpleMessage.payload) - }, - DynamicTest.dynamicTest("Sender certificate should be honored") { + } + + @Test + fun `Sender certificate should be honored`() { assertEquals(senderCertificate, simpleMessage.senderCertificate) - }, - DynamicTest.dynamicTest( - "Serializer should be configured to use specified message type and version" - ) { + } + + @Test + fun `Serializer should be configured to use specified message type and version`() { val messageSerialized = simpleMessage.serialize(keyPair.private) assertEquals(expectedConcreteMessageType, messageSerialized[8]) assertEquals(expectedConcreteMessageVersion, messageSerialized[9]) - }, - DynamicTest.dynamicTest("Message id should be honored if set") { + } + + @Test + fun `Message id should be honored if set`() { val messageId = "the-id" val message = messageConstructor( recipientAddress, @@ -53,8 +58,10 @@ fun > makeRAMFMessageConstructorTests( ) assertEquals(messageId, message.id) - }, - DynamicTest.dynamicTest("Creation time should be honored if set") { + } + + @Test + fun `Creation time should be honored if set`() { val creationDate = ZonedDateTime.now().minusMinutes(10) val message = messageConstructor( @@ -68,8 +75,10 @@ fun > makeRAMFMessageConstructorTests( ) assertEquals(creationDate, message.creationDate) - }, - DynamicTest.dynamicTest("TTL should be honored if set") { + } + + @Test + fun `TTL should be honored if set`() { val ttl = 42 val message = messageConstructor( @@ -83,8 +92,10 @@ fun > makeRAMFMessageConstructorTests( ) assertEquals(ttl, message.ttl) - }, - DynamicTest.dynamicTest("Sender certificate chain should be honored if set") { + } + + @Test + fun `Sender certificate chain should be honored if set`() { val senderCertificateChain = setOf( issueStubCertificate(keyPair.public, keyPair.private) ) @@ -100,27 +111,31 @@ fun > makeRAMFMessageConstructorTests( assertEquals(senderCertificateChain, message.senderCertificateChain) } - ) -} - -internal fun > makeRAMFMessageCompanionTests( - companion: RAMFMessageCompanion, - messageConstructor: RAMFMessageConstructor -): Collection { - val message = - messageConstructor(recipientAddress, payload, senderCertificate, null, null, null, null) - val messageSerialized = message.serialize(keyPair.private) - - return listOf( - DynamicTest.dynamicTest("Valid ByteArray should be deserialized") { + } + + @Nested + inner class Deserialization { + private val messageSerialized = simpleMessage.serialize(keyPair.private) + + @Test + fun `Valid ByteArray should be deserialized`() { val ccaDeserialized = companion.deserialize(messageSerialized) - assertEquals(message.id, ccaDeserialized.id) - }, - DynamicTest.dynamicTest("Valid InputStream should be deserialized") { + assertEquals(simpleMessage.id, ccaDeserialized.id) + } + + @Test + fun `Valid InputStream should be deserialized`() { val ccaDeserialized = companion.deserialize(messageSerialized.inputStream()) - assertEquals(message.id, ccaDeserialized.id) + assertEquals(simpleMessage.id, ccaDeserialized.id) } - ) -} + } + + companion object { + private const val recipientAddress = "0deadbeef" + private val payload = "Payload".toByteArray() + private val keyPair = generateRSAKeyPair() + private val senderCertificate = issueStubCertificate(keyPair.public, keyPair.private) + } +} \ No newline at end of file From 2ba2ae284a394f2564403163f1a7404f76ac9b7b Mon Sep 17 00:00:00 2001 From: Gus Narea Date: Fri, 17 Jul 2020 18:16:54 +0100 Subject: [PATCH 4/6] Implement payload deserialisation in cargoes --- .../tech/relaycorp/relaynet/messages/Cargo.kt | 5 ++--- .../relaycorp/relaynet/ramf/RAMFMessage.kt | 2 +- .../tech/relaycorp/relaynet/CryptoUtils.kt | 4 ++++ .../relaycorp/relaynet/messages/CargoTest.kt | 19 ++++++++++++++++++- .../ramf/RAMFSerializationTestCase.kt | 2 +- 5 files changed, 26 insertions(+), 6 deletions(-) diff --git a/src/main/kotlin/tech/relaycorp/relaynet/messages/Cargo.kt b/src/main/kotlin/tech/relaycorp/relaynet/messages/Cargo.kt index 6dea60ab..ad2eb8ac 100644 --- a/src/main/kotlin/tech/relaycorp/relaynet/messages/Cargo.kt +++ b/src/main/kotlin/tech/relaycorp/relaynet/messages/Cargo.kt @@ -32,9 +32,8 @@ class Cargo( ttl, senderCertificateChain ) { - override fun deserializePayload(payloadPlaintext: ByteArray): CargoMessageSet { - TODO("Not yet implemented") - } + override fun deserializePayload(payloadPlaintext: ByteArray) = + CargoMessageSet.deserialize(payloadPlaintext) companion object : RAMFMessageCompanion { /** diff --git a/src/main/kotlin/tech/relaycorp/relaynet/ramf/RAMFMessage.kt b/src/main/kotlin/tech/relaycorp/relaynet/ramf/RAMFMessage.kt index 2f2f44ee..f8330eaf 100644 --- a/src/main/kotlin/tech/relaycorp/relaynet/ramf/RAMFMessage.kt +++ b/src/main/kotlin/tech/relaycorp/relaynet/ramf/RAMFMessage.kt @@ -150,7 +150,7 @@ abstract class RAMFMessage internal constructor( } @Throws(RAMFException::class) - protected abstract fun deserializePayload(payloadPlaintext: ByteArray): Payload + internal abstract fun deserializePayload(payloadPlaintext: ByteArray): Payload private fun validateTiming() { val now = ZonedDateTime.now(UTC) diff --git a/src/test/kotlin/tech/relaycorp/relaynet/CryptoUtils.kt b/src/test/kotlin/tech/relaycorp/relaynet/CryptoUtils.kt index 398d3a9b..7b719d73 100644 --- a/src/test/kotlin/tech/relaycorp/relaynet/CryptoUtils.kt +++ b/src/test/kotlin/tech/relaycorp/relaynet/CryptoUtils.kt @@ -1,5 +1,6 @@ package tech.relaycorp.relaynet +import tech.relaycorp.relaynet.wrappers.generateRSAKeyPair import tech.relaycorp.relaynet.wrappers.x509.Certificate import java.security.MessageDigest import java.security.PrivateKey @@ -28,3 +29,6 @@ fun issueStubCertificate( issuerCertificate = issuerCertificate ) } + +val KEY_PAIR = generateRSAKeyPair() +val CERTIFICATE = issueStubCertificate(KEY_PAIR.public, KEY_PAIR.private) diff --git a/src/test/kotlin/tech/relaycorp/relaynet/messages/CargoTest.kt b/src/test/kotlin/tech/relaycorp/relaynet/messages/CargoTest.kt index 05be8a3f..ba6d4bfd 100644 --- a/src/test/kotlin/tech/relaycorp/relaynet/messages/CargoTest.kt +++ b/src/test/kotlin/tech/relaycorp/relaynet/messages/CargoTest.kt @@ -1,7 +1,11 @@ package tech.relaycorp.relaynet.messages +import org.junit.jupiter.api.Test +import tech.relaycorp.relaynet.CERTIFICATE +import tech.relaycorp.relaynet.messages.payloads.CargoMessageSet import tech.relaycorp.relaynet.ramf.RAMFSerializationTestCase import tech.relaycorp.relaynet.wrappers.x509.Certificate +import kotlin.test.assertEquals internal class CargoTest : RAMFSerializationTestCase( ::Cargo, @@ -9,4 +13,17 @@ internal class CargoTest : RAMFSerializationTestCase( 0x43, 0x00, Cargo.Companion -) +) { + @Test + fun `Payload deserialization should be delegated to CargoMessageSet`() { + val cargoMessageSet = CargoMessageSet(arrayOf("msg1".toByteArray(), "msg2".toByteArray())) + val cargo = Cargo("https://gb.relaycorp.tech", "".toByteArray(), CERTIFICATE) + + val payloadDeserialized = cargo.deserializePayload(cargoMessageSet.serialize()) + + assertEquals( + cargoMessageSet.messages.map { it.asList() }, + payloadDeserialized.messages.map { it.asList() } + ) + } +} diff --git a/src/test/kotlin/tech/relaycorp/relaynet/ramf/RAMFSerializationTestCase.kt b/src/test/kotlin/tech/relaycorp/relaynet/ramf/RAMFSerializationTestCase.kt index 9e176455..d88030d1 100644 --- a/src/test/kotlin/tech/relaycorp/relaynet/ramf/RAMFSerializationTestCase.kt +++ b/src/test/kotlin/tech/relaycorp/relaynet/ramf/RAMFSerializationTestCase.kt @@ -138,4 +138,4 @@ internal abstract class RAMFSerializationTestCase>( private val keyPair = generateRSAKeyPair() private val senderCertificate = issueStubCertificate(keyPair.public, keyPair.private) } -} \ No newline at end of file +} From b25219bee54136fc1e5f4581f5fc26646eaa2b1f Mon Sep 17 00:00:00 2001 From: Gus Narea Date: Fri, 17 Jul 2020 18:36:14 +0100 Subject: [PATCH 5/6] Implement EmptyPayloadPlaintext --- .../messages/CargoCollectionAuthorization.kt | 5 ++- .../payloads/EmptyPayloadPlaintext.kt | 25 +++++++++++-- .../CargoCollectionAuthorizationTest.kt | 13 ++++++- .../relaycorp/relaynet/messages/CargoTest.kt | 2 +- .../payloads/EmptyPayloadPlaintextTest.kt | 36 +++++++++++++++++++ 5 files changed, 74 insertions(+), 7 deletions(-) create mode 100644 src/test/kotlin/tech/relaycorp/relaynet/messages/payloads/EmptyPayloadPlaintextTest.kt diff --git a/src/main/kotlin/tech/relaycorp/relaynet/messages/CargoCollectionAuthorization.kt b/src/main/kotlin/tech/relaycorp/relaynet/messages/CargoCollectionAuthorization.kt index a61f2ab0..e64bac4d 100644 --- a/src/main/kotlin/tech/relaycorp/relaynet/messages/CargoCollectionAuthorization.kt +++ b/src/main/kotlin/tech/relaycorp/relaynet/messages/CargoCollectionAuthorization.kt @@ -32,9 +32,8 @@ class CargoCollectionAuthorization( ttl, senderCertificateChain ) { - override fun deserializePayload(payloadPlaintext: ByteArray): EmptyPayloadPlaintext { - TODO("Not yet implemented") - } + override fun deserializePayload(payloadPlaintext: ByteArray) = + EmptyPayloadPlaintext.deserialize(payloadPlaintext) companion object : RAMFMessageCompanion { /** diff --git a/src/main/kotlin/tech/relaycorp/relaynet/messages/payloads/EmptyPayloadPlaintext.kt b/src/main/kotlin/tech/relaycorp/relaynet/messages/payloads/EmptyPayloadPlaintext.kt index b7b16a58..92e29c5d 100644 --- a/src/main/kotlin/tech/relaycorp/relaynet/messages/payloads/EmptyPayloadPlaintext.kt +++ b/src/main/kotlin/tech/relaycorp/relaynet/messages/payloads/EmptyPayloadPlaintext.kt @@ -1,7 +1,28 @@ package tech.relaycorp.relaynet.messages.payloads +import tech.relaycorp.relaynet.ramf.RAMFException + +/** + * Empty payload plaintext. + */ class EmptyPayloadPlaintext : PayloadPlaintext { - override fun serialize(): ByteArray { - TODO("Not yet implemented") + /** + * Serialize empty payload plaintext. + */ + override fun serialize() = ByteArray(0) + + companion object { + /** + * Deserialize empty payload plaintext. + * + * @throws RAMFException if `serialization` is not empty + */ + @Throws(RAMFException::class) + fun deserialize(serialization: ByteArray): EmptyPayloadPlaintext { + if (serialization.isNotEmpty()) { + throw RAMFException("Payload is not empty") + } + return EmptyPayloadPlaintext() + } } } diff --git a/src/test/kotlin/tech/relaycorp/relaynet/messages/CargoCollectionAuthorizationTest.kt b/src/test/kotlin/tech/relaycorp/relaynet/messages/CargoCollectionAuthorizationTest.kt index 61db9a0c..2ca184d4 100644 --- a/src/test/kotlin/tech/relaycorp/relaynet/messages/CargoCollectionAuthorizationTest.kt +++ b/src/test/kotlin/tech/relaycorp/relaynet/messages/CargoCollectionAuthorizationTest.kt @@ -1,7 +1,9 @@ package tech.relaycorp.relaynet.messages +import tech.relaycorp.relaynet.CERTIFICATE import tech.relaycorp.relaynet.ramf.RAMFSerializationTestCase import tech.relaycorp.relaynet.wrappers.x509.Certificate +import kotlin.test.Test internal class CargoCollectionAuthorizationTest : RAMFSerializationTestCase( @@ -10,4 +12,13 @@ internal class CargoCollectionAuthorizationTest : 0x44, 0x00, CargoCollectionAuthorization.Companion - ) + ) { + @Test + fun `Payload deserialization should be delegated to CargoMessageSet`() { + val cca = CargoCollectionAuthorization( + "https://gb.relaycorp.tech", "".toByteArray(), CERTIFICATE + ) + + cca.deserializePayload("".toByteArray()) + } +} diff --git a/src/test/kotlin/tech/relaycorp/relaynet/messages/CargoTest.kt b/src/test/kotlin/tech/relaycorp/relaynet/messages/CargoTest.kt index ba6d4bfd..388ef0c9 100644 --- a/src/test/kotlin/tech/relaycorp/relaynet/messages/CargoTest.kt +++ b/src/test/kotlin/tech/relaycorp/relaynet/messages/CargoTest.kt @@ -1,10 +1,10 @@ package tech.relaycorp.relaynet.messages -import org.junit.jupiter.api.Test import tech.relaycorp.relaynet.CERTIFICATE import tech.relaycorp.relaynet.messages.payloads.CargoMessageSet import tech.relaycorp.relaynet.ramf.RAMFSerializationTestCase import tech.relaycorp.relaynet.wrappers.x509.Certificate +import kotlin.test.Test import kotlin.test.assertEquals internal class CargoTest : RAMFSerializationTestCase( diff --git a/src/test/kotlin/tech/relaycorp/relaynet/messages/payloads/EmptyPayloadPlaintextTest.kt b/src/test/kotlin/tech/relaycorp/relaynet/messages/payloads/EmptyPayloadPlaintextTest.kt new file mode 100644 index 00000000..b2d38e88 --- /dev/null +++ b/src/test/kotlin/tech/relaycorp/relaynet/messages/payloads/EmptyPayloadPlaintextTest.kt @@ -0,0 +1,36 @@ +package tech.relaycorp.relaynet.messages.payloads + +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.assertThrows +import tech.relaycorp.relaynet.ramf.RAMFException +import kotlin.test.Test +import kotlin.test.assertEquals + +internal class EmptyPayloadPlaintextTest { + @Nested + inner class Serialize { + @Test + fun `An empty ByteArray should be returned`() { + val payloadPlaintext = EmptyPayloadPlaintext() + + assertEquals(0, payloadPlaintext.serialize().size) + } + } + + @Nested + inner class Deserialize { + @Test + fun `An empty buffer should be accepted`() { + EmptyPayloadPlaintext.deserialize(ByteArray(0)) + } + + @Test + fun `An error should be thrown if buffer is not empty`() { + val exception = assertThrows { + EmptyPayloadPlaintext.deserialize("a".toByteArray()) + } + + assertEquals("Payload is not empty", exception.message) + } + } +} From 63ddb19551bc149d60c61ceaf8d3e9b19ffd8f78 Mon Sep 17 00:00:00 2001 From: Gus Narea Date: Fri, 17 Jul 2020 18:42:28 +0100 Subject: [PATCH 6/6] Implement ServiceMessage --- .../messages/CargoCollectionAuthorizationTest.kt | 2 +- .../tech/relaycorp/relaynet/messages/ParcelTest.kt | 13 ++++++++++++- .../messages/payloads/ServiceMessageTest.kt | 12 ++++++++++++ 3 files changed, 25 insertions(+), 2 deletions(-) create mode 100644 src/test/kotlin/tech/relaycorp/relaynet/messages/payloads/ServiceMessageTest.kt diff --git a/src/test/kotlin/tech/relaycorp/relaynet/messages/CargoCollectionAuthorizationTest.kt b/src/test/kotlin/tech/relaycorp/relaynet/messages/CargoCollectionAuthorizationTest.kt index 2ca184d4..2348f342 100644 --- a/src/test/kotlin/tech/relaycorp/relaynet/messages/CargoCollectionAuthorizationTest.kt +++ b/src/test/kotlin/tech/relaycorp/relaynet/messages/CargoCollectionAuthorizationTest.kt @@ -14,7 +14,7 @@ internal class CargoCollectionAuthorizationTest : CargoCollectionAuthorization.Companion ) { @Test - fun `Payload deserialization should be delegated to CargoMessageSet`() { + fun `Payload deserialization should be delegated to EmptyPayloadPlaintext`() { val cca = CargoCollectionAuthorization( "https://gb.relaycorp.tech", "".toByteArray(), CERTIFICATE ) diff --git a/src/test/kotlin/tech/relaycorp/relaynet/messages/ParcelTest.kt b/src/test/kotlin/tech/relaycorp/relaynet/messages/ParcelTest.kt index 4215ade9..a130c4fc 100644 --- a/src/test/kotlin/tech/relaycorp/relaynet/messages/ParcelTest.kt +++ b/src/test/kotlin/tech/relaycorp/relaynet/messages/ParcelTest.kt @@ -1,7 +1,10 @@ package tech.relaycorp.relaynet.messages +import org.junit.jupiter.api.assertThrows +import tech.relaycorp.relaynet.CERTIFICATE import tech.relaycorp.relaynet.ramf.RAMFSerializationTestCase import tech.relaycorp.relaynet.wrappers.x509.Certificate +import kotlin.test.Test internal class ParcelTest : RAMFSerializationTestCase( ::Parcel, @@ -9,4 +12,12 @@ internal class ParcelTest : RAMFSerializationTestCase( 0x50, 0x00, Parcel.Companion -) +) { + @Test + fun `Payload deserialization should be delegated to ServiceMessage`() { + val parcel = Parcel("https://gb.relaycorp.tech", "".toByteArray(), CERTIFICATE) + + // TODO + assertThrows { parcel.deserializePayload("invalid".toByteArray()) } + } +} diff --git a/src/test/kotlin/tech/relaycorp/relaynet/messages/payloads/ServiceMessageTest.kt b/src/test/kotlin/tech/relaycorp/relaynet/messages/payloads/ServiceMessageTest.kt new file mode 100644 index 00000000..88c69ebb --- /dev/null +++ b/src/test/kotlin/tech/relaycorp/relaynet/messages/payloads/ServiceMessageTest.kt @@ -0,0 +1,12 @@ +package tech.relaycorp.relaynet.messages.payloads + +import org.junit.jupiter.api.assertThrows +import kotlin.test.Test + +internal class ServiceMessageTest { + @Test + fun deserialize() { + // TODO + assertThrows { ServiceMessage().serialize() } + } +}