Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(RAMF): Implement payload wrapping #71

Merged
merged 4 commits into from
Jul 19, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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