Skip to content

Commit

Permalink
feat(RAMF): Implement payload wrapping (#71)
Browse files Browse the repository at this point in the history
  • Loading branch information
gnarea authored Jul 19, 2020
1 parent fa107fc commit ab24ca5
Show file tree
Hide file tree
Showing 26 changed files with 330 additions and 140 deletions.
4 changes: 2 additions & 2 deletions src/main/kotlin/tech/relaycorp/relaynet/messages/Cargo.kt
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package tech.relaycorp.relaynet.messages

import tech.relaycorp.relaynet.messages.payloads.CargoMessageSet
import tech.relaycorp.relaynet.ramf.EncryptedRAMFMessage
import tech.relaycorp.relaynet.ramf.RAMFException
import tech.relaycorp.relaynet.ramf.RAMFMessage
import tech.relaycorp.relaynet.ramf.RAMFMessageCompanion
import tech.relaycorp.relaynet.ramf.RAMFSerializer
import tech.relaycorp.relaynet.wrappers.x509.Certificate
Expand All @@ -22,7 +22,7 @@ class Cargo(
creationDate: ZonedDateTime? = null,
ttl: Int? = null,
senderCertificateChain: Set<Certificate>? = null
) : RAMFMessage<CargoMessageSet>(
) : EncryptedRAMFMessage<CargoMessageSet>(
SERIALIZER,
recipientAddress,
payload,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package tech.relaycorp.relaynet.messages

import tech.relaycorp.relaynet.messages.payloads.EmptyPayloadPlaintext
import tech.relaycorp.relaynet.messages.payloads.EmptyPayload
import tech.relaycorp.relaynet.ramf.RAMFException
import tech.relaycorp.relaynet.ramf.RAMFMessage
import tech.relaycorp.relaynet.ramf.RAMFMessageCompanion
import tech.relaycorp.relaynet.ramf.RAMFSerializer
import tech.relaycorp.relaynet.ramf.UnencryptedRAMFMessage
import tech.relaycorp.relaynet.wrappers.x509.Certificate
import java.io.InputStream
import java.time.ZonedDateTime
Expand All @@ -22,7 +22,7 @@ class CargoCollectionAuthorization(
creationDate: ZonedDateTime? = null,
ttl: Int? = null,
senderCertificateChain: Set<Certificate>? = null
) : RAMFMessage<EmptyPayloadPlaintext>(
) : UnencryptedRAMFMessage<EmptyPayload>(
SERIALIZER,
recipientAddress,
payload,
Expand All @@ -32,8 +32,7 @@ class CargoCollectionAuthorization(
ttl,
senderCertificateChain
) {
override fun deserializePayload(payloadPlaintext: ByteArray) =
EmptyPayloadPlaintext.deserialize(payloadPlaintext)
override fun deserializePayload() = EmptyPayload.deserialize(payload)

companion object : RAMFMessageCompanion<CargoCollectionAuthorization> {
/**
Expand Down
4 changes: 2 additions & 2 deletions src/main/kotlin/tech/relaycorp/relaynet/messages/Parcel.kt
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package tech.relaycorp.relaynet.messages

import tech.relaycorp.relaynet.messages.payloads.ServiceMessage
import tech.relaycorp.relaynet.ramf.EncryptedRAMFMessage
import tech.relaycorp.relaynet.ramf.RAMFException
import tech.relaycorp.relaynet.ramf.RAMFMessage
import tech.relaycorp.relaynet.ramf.RAMFMessageCompanion
import tech.relaycorp.relaynet.ramf.RAMFSerializer
import tech.relaycorp.relaynet.wrappers.x509.Certificate
Expand All @@ -22,7 +22,7 @@ class Parcel(
creationDate: ZonedDateTime? = null,
ttl: Int? = null,
senderCertificateChain: Set<Certificate>? = null
) : RAMFMessage<ServiceMessage>(
) : EncryptedRAMFMessage<ServiceMessage>(
SERIALIZER,
recipientAddress,
payload,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ import tech.relaycorp.relaynet.wrappers.asn1.ASN1Utils
/**
* Cargo message set.
*/
class CargoMessageSet(val messages: Array<ByteArray>) : PayloadPlaintext {
class CargoMessageSet(val messages: Array<ByteArray>) : EncryptedPayload() {
/**
* Serialize cargo message set.
*/
override fun serialize(): ByteArray {
override fun serializePlaintext(): ByteArray {
val messagesVector = ASN1EncodableVector(messages.size)
messages.forEach { messagesVector.add(DEROctetString(it)) }
val sequence = DERSequence(messagesVector)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ import tech.relaycorp.relaynet.ramf.RAMFException
/**
* Empty payload plaintext.
*/
class EmptyPayloadPlaintext : PayloadPlaintext {
class EmptyPayload : UnencryptedPayload() {
/**
* Serialize empty payload plaintext.
*/
override fun serialize() = ByteArray(0)
override fun serializePlaintext() = ByteArray(0)

companion object {
/**
Expand All @@ -18,11 +18,11 @@ class EmptyPayloadPlaintext : PayloadPlaintext {
* @throws RAMFException if `serialization` is not empty
*/
@Throws(RAMFException::class)
fun deserialize(serialization: ByteArray): EmptyPayloadPlaintext {
fun deserialize(serialization: ByteArray): EmptyPayload {
if (serialization.isNotEmpty()) {
throw RAMFException("Payload is not empty")
}
return EmptyPayloadPlaintext()
return EmptyPayload()
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package tech.relaycorp.relaynet.messages.payloads

import tech.relaycorp.relaynet.SymmetricEncryption
import tech.relaycorp.relaynet.wrappers.cms.SessionlessEnvelopedData
import tech.relaycorp.relaynet.wrappers.x509.Certificate

abstract class EncryptedPayload : Payload {
fun encrypt(
recipientCertificate: Certificate,
symmetricEncryptionAlgorithm: SymmetricEncryption = SymmetricEncryption.AES_128
): ByteArray {
val envelopedData = SessionlessEnvelopedData.encrypt(
this.serializePlaintext(),
recipientCertificate,
symmetricEncryptionAlgorithm
)
return envelopedData.serialize()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@ package tech.relaycorp.relaynet.messages.payloads
/**
* RAMF payload in plaintext form.
*/
interface PayloadPlaintext {
fun serialize(): ByteArray
interface Payload {
fun serializePlaintext(): ByteArray
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package tech.relaycorp.relaynet.messages.payloads

class ServiceMessage : PayloadPlaintext {
override fun serialize(): ByteArray {
class ServiceMessage : EncryptedPayload() {
override fun serializePlaintext(): ByteArray {
TODO("Not yet implemented")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package tech.relaycorp.relaynet.messages.payloads

abstract class UnencryptedPayload : Payload
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package tech.relaycorp.relaynet.ramf

import tech.relaycorp.relaynet.messages.payloads.EncryptedPayload
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 java.security.PrivateKey
import java.time.ZonedDateTime

abstract class EncryptedRAMFMessage<P : EncryptedPayload> internal constructor(
serializer: RAMFSerializer,
recipientAddress: String,
payload: ByteArray,
senderCertificate: Certificate,
messageId: String?,
creationDate: ZonedDateTime?,
ttl: Int?,
senderCertificateChain: Set<Certificate>?
) : RAMFMessage<P>(
serializer,
recipientAddress,
payload,
senderCertificate,
messageId,
creationDate,
ttl,
senderCertificateChain
) {
/**
* 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): P {
val envelopedData = EnvelopedData.deserialize(payload) as SessionlessEnvelopedData
val plaintext = envelopedData.decrypt(privateKey)
return deserializePayload(plaintext)
}

@Throws(RAMFException::class)
internal abstract fun deserializePayload(payloadPlaintext: ByteArray): P
}
24 changes: 2 additions & 22 deletions src/main/kotlin/tech/relaycorp/relaynet/ramf/RAMFMessage.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +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.cms.EnvelopedData
import tech.relaycorp.relaynet.wrappers.cms.EnvelopedDataException
import tech.relaycorp.relaynet.wrappers.cms.SessionlessEnvelopedData
import tech.relaycorp.relaynet.messages.payloads.Payload
import tech.relaycorp.relaynet.wrappers.x509.Certificate
import tech.relaycorp.relaynet.wrappers.x509.CertificateException
import java.net.MalformedURLException
Expand Down Expand Up @@ -36,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<Payload : PayloadPlaintext> internal constructor(
abstract class RAMFMessage<P : Payload> internal constructor(
private val serializer: RAMFSerializer,
val recipientAddress: String,
val payload: ByteArray,
Expand Down Expand Up @@ -135,23 +132,6 @@ abstract class RAMFMessage<Payload : PayloadPlaintext> 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)
internal abstract fun deserializePayload(payloadPlaintext: ByteArray): Payload

private fun validateTiming() {
val now = ZonedDateTime.now(UTC)
if (now < creationDate) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package tech.relaycorp.relaynet.ramf

import tech.relaycorp.relaynet.messages.payloads.UnencryptedPayload
import tech.relaycorp.relaynet.wrappers.x509.Certificate
import java.time.ZonedDateTime

abstract class UnencryptedRAMFMessage<P : UnencryptedPayload> internal constructor(
serializer: RAMFSerializer,
recipientAddress: String,
payload: ByteArray,
senderCertificate: Certificate,
messageId: String?,
creationDate: ZonedDateTime?,
ttl: Int?,
senderCertificateChain: Set<Certificate>?
) : RAMFMessage<P>(
serializer,
recipientAddress,
payload,
senderCertificate,
messageId,
creationDate,
ttl,
senderCertificateChain
) {
@Throws(RAMFException::class)
abstract fun deserializePayload(): P
}
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
package tech.relaycorp.relaynet.messages

import tech.relaycorp.relaynet.CERTIFICATE
import tech.relaycorp.relaynet.ramf.RAMFSerializationTestCase
import tech.relaycorp.relaynet.ramf.RAMFSpecializationTestCase
import tech.relaycorp.relaynet.wrappers.x509.Certificate
import kotlin.test.Test

internal class CargoCollectionAuthorizationTest :
RAMFSerializationTestCase<CargoCollectionAuthorization>(
RAMFSpecializationTestCase<CargoCollectionAuthorization>(
::CargoCollectionAuthorization,
{ r: String, p: ByteArray, s: Certificate -> CargoCollectionAuthorization(r, p, s) },
0x44,
0x00,
CargoCollectionAuthorization.Companion
) {
@Test
fun `Payload deserialization should be delegated to EmptyPayloadPlaintext`() {
fun `Payload deserialization should be delegated to EmptyPayload`() {
val cca = CargoCollectionAuthorization(
"https://gb.relaycorp.tech", "".toByteArray(), CERTIFICATE
)

cca.deserializePayload("".toByteArray())
cca.deserializePayload()
}
}
6 changes: 3 additions & 3 deletions src/test/kotlin/tech/relaycorp/relaynet/messages/CargoTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ package tech.relaycorp.relaynet.messages

import tech.relaycorp.relaynet.CERTIFICATE
import tech.relaycorp.relaynet.messages.payloads.CargoMessageSet
import tech.relaycorp.relaynet.ramf.RAMFSerializationTestCase
import tech.relaycorp.relaynet.ramf.RAMFSpecializationTestCase
import tech.relaycorp.relaynet.wrappers.x509.Certificate
import kotlin.test.Test
import kotlin.test.assertEquals

internal class CargoTest : RAMFSerializationTestCase<Cargo>(
internal class CargoTest : RAMFSpecializationTestCase<Cargo>(
::Cargo,
{ r: String, p: ByteArray, s: Certificate -> Cargo(r, p, s) },
0x43,
Expand All @@ -19,7 +19,7 @@ internal class CargoTest : RAMFSerializationTestCase<Cargo>(
val cargoMessageSet = CargoMessageSet(arrayOf("msg1".toByteArray(), "msg2".toByteArray()))
val cargo = Cargo("https://gb.relaycorp.tech", "".toByteArray(), CERTIFICATE)

val payloadDeserialized = cargo.deserializePayload(cargoMessageSet.serialize())
val payloadDeserialized = cargo.deserializePayload(cargoMessageSet.serializePlaintext())

assertEquals(
cargoMessageSet.messages.map { it.asList() },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ 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.ramf.RAMFSpecializationTestCase
import tech.relaycorp.relaynet.wrappers.x509.Certificate
import kotlin.test.Test

internal class ParcelTest : RAMFSerializationTestCase<Parcel>(
internal class ParcelTest : RAMFSpecializationTestCase<Parcel>(
::Parcel,
{ r: String, p: ByteArray, s: Certificate -> Parcel(r, p, s) },
0x50,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ internal class CargoMessageSetTest {
fun `An empty array should be serialized as such`() {
val cargoMessageSet = CargoMessageSet(emptyArray())

val serialization = cargoMessageSet.serialize()
val serialization = cargoMessageSet.serializePlaintext()

val messages = deserializeDERSequence(serialization)
assertEquals(0, messages.size)
Expand All @@ -43,7 +43,7 @@ internal class CargoMessageSetTest {
val message = "the message".toByteArray()
val cargoMessageSet = CargoMessageSet(arrayOf(message))

val serialization = cargoMessageSet.serialize()
val serialization = cargoMessageSet.serializePlaintext()

val messages = deserializeDERSequence(serialization)
assertEquals(1, messages.size)
Expand All @@ -57,7 +57,7 @@ internal class CargoMessageSetTest {
val message2 = "message 1".toByteArray()
val cargoMessageSet = CargoMessageSet(arrayOf(message1, message2))

val serialization = cargoMessageSet.serialize()
val serialization = cargoMessageSet.serializePlaintext()

val messages = deserializeDERSequence(serialization)
assertEquals(2, messages.size)
Expand Down
Loading

0 comments on commit ab24ca5

Please sign in to comment.