Skip to content

Commit

Permalink
feat(RAMF): Implement payload unwrapping (#68)
Browse files Browse the repository at this point in the history
  • Loading branch information
gnarea authored Jul 17, 2020
1 parent a21b954 commit fa107fc
Show file tree
Hide file tree
Showing 19 changed files with 275 additions and 105 deletions.
6 changes: 5 additions & 1 deletion src/main/kotlin/tech/relaycorp/relaynet/messages/Cargo.kt
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -21,7 +22,7 @@ class Cargo(
creationDate: ZonedDateTime? = null,
ttl: Int? = null,
senderCertificateChain: Set<Certificate>? = null
) : RAMFMessage(
) : RAMFMessage<CargoMessageSet>(
SERIALIZER,
recipientAddress,
payload,
Expand All @@ -31,6 +32,9 @@ class Cargo(
ttl,
senderCertificateChain
) {
override fun deserializePayload(payloadPlaintext: ByteArray) =
CargoMessageSet.deserialize(payloadPlaintext)

companion object : RAMFMessageCompanion<Cargo> {
/**
* Deserialize cargo
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -21,7 +22,7 @@ class CargoCollectionAuthorization(
creationDate: ZonedDateTime? = null,
ttl: Int? = null,
senderCertificateChain: Set<Certificate>? = null
) : RAMFMessage(
) : RAMFMessage<EmptyPayloadPlaintext>(
SERIALIZER,
recipientAddress,
payload,
Expand All @@ -31,6 +32,9 @@ class CargoCollectionAuthorization(
ttl,
senderCertificateChain
) {
override fun deserializePayload(payloadPlaintext: ByteArray) =
EmptyPayloadPlaintext.deserialize(payloadPlaintext)

companion object : RAMFMessageCompanion<CargoCollectionAuthorization> {
/**
* Deserialize a CCA
Expand Down
7 changes: 6 additions & 1 deletion src/main/kotlin/tech/relaycorp/relaynet/messages/Parcel.kt
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -21,7 +22,7 @@ class Parcel(
creationDate: ZonedDateTime? = null,
ttl: Int? = null,
senderCertificateChain: Set<Certificate>? = null
) : RAMFMessage(
) : RAMFMessage<ServiceMessage>(
SERIALIZER,
recipientAddress,
payload,
Expand All @@ -31,6 +32,10 @@ class Parcel(
ttl,
senderCertificateChain
) {
override fun deserializePayload(payloadPlaintext: ByteArray): ServiceMessage {
TODO("Not yet implemented")
}

companion object : RAMFMessageCompanion<Parcel> {
/**
* Deserialize parcel
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package tech.relaycorp.relaynet.messages.payloads

import tech.relaycorp.relaynet.ramf.RAMFException

/**
* Empty payload plaintext.
*/
class EmptyPayloadPlaintext : PayloadPlaintext {
/**
* 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()
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package tech.relaycorp.relaynet.messages.payloads

class ServiceMessage : PayloadPlaintext {
override fun serialize(): ByteArray {
TODO("Not yet implemented")
}
}
23 changes: 22 additions & 1 deletion src/main/kotlin/tech/relaycorp/relaynet/ramf/RAMFMessage.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ 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
Expand Down Expand Up @@ -32,7 +36,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<Payload : PayloadPlaintext> internal constructor(
private val serializer: RAMFSerializer,
val recipientAddress: String,
val payload: ByteArray,
Expand Down Expand Up @@ -131,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)
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
Expand Up @@ -2,7 +2,7 @@ package tech.relaycorp.relaynet.ramf

import java.io.InputStream

internal interface RAMFMessageCompanion<Message : RAMFMessage> {
internal interface RAMFMessageCompanion<Message : RAMFMessage<*>> {
fun deserialize(serialization: ByteArray): Message
fun deserialize(serialization: InputStream): Message
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
4 changes: 4 additions & 0 deletions src/test/kotlin/tech/relaycorp/relaynet/CryptoUtils.kt
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -28,3 +29,6 @@ fun issueStubCertificate(
issuerCertificate = issuerCertificate
)
}

val KEY_PAIR = generateRSAKeyPair()
val CERTIFICATE = issueStubCertificate(KEY_PAIR.public, KEY_PAIR.private)
Original file line number Diff line number Diff line change
@@ -1,27 +1,24 @@
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.CERTIFICATE
import tech.relaycorp.relaynet.ramf.RAMFSerializationTestCase
import tech.relaycorp.relaynet.wrappers.x509.Certificate
import kotlin.test.Test

class CargoCollectionAuthorizationTest {
@TestFactory
fun makeConstructorTests() =
makeRAMFMessageConstructorTests(
::CargoCollectionAuthorization,
{ r: String, p: ByteArray, s: Certificate -> CargoCollectionAuthorization(r, p, s) },
0x44,
0x00
internal class CargoCollectionAuthorizationTest :
RAMFSerializationTestCase<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`() {
val cca = CargoCollectionAuthorization(
"https://gb.relaycorp.tech", "".toByteArray(), CERTIFICATE
)

@Nested
inner class Companion {
@TestFactory
fun makeDeserializationTests() = makeRAMFMessageCompanionTests(
CargoCollectionAuthorization.Companion,
::CargoCollectionAuthorization
)
cca.deserializePayload("".toByteArray())
}
}
38 changes: 22 additions & 16 deletions src/test/kotlin/tech/relaycorp/relaynet/messages/CargoTest.kt
Original file line number Diff line number Diff line change
@@ -1,23 +1,29 @@
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.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

class CargoTest {
@TestFactory
fun makeConstructorTests() = makeRAMFMessageConstructorTests(
::Cargo,
{ r: String, p: ByteArray, s: Certificate -> Cargo(r, p, s) },
0x43,
0x00
)
internal class CargoTest : RAMFSerializationTestCase<Cargo>(
::Cargo,
{ r: String, p: ByteArray, s: Certificate -> Cargo(r, p, s) },
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)

@Nested
inner class Companion {
@TestFactory
fun makeDeserializationTests() = makeRAMFMessageCompanionTests(Cargo.Companion, ::Cargo)
val payloadDeserialized = cargo.deserializePayload(cargoMessageSet.serialize())

assertEquals(
cargoMessageSet.messages.map { it.asList() },
payloadDeserialized.messages.map { it.asList() }
)
}
}
32 changes: 16 additions & 16 deletions src/test/kotlin/tech/relaycorp/relaynet/messages/ParcelTest.kt
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
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 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

class ParcelTest {
@TestFactory
fun makeConstructorTests() = makeRAMFMessageConstructorTests(
::Parcel,
{ r: String, p: ByteArray, s: Certificate -> Parcel(r, p, s) },
0x50,
0x00
)
internal class ParcelTest : RAMFSerializationTestCase<Parcel>(
::Parcel,
{ r: String, p: ByteArray, s: Certificate -> Parcel(r, p, s) },
0x50,
0x00,
Parcel.Companion
) {
@Test
fun `Payload deserialization should be delegated to ServiceMessage`() {
val parcel = Parcel("https://gb.relaycorp.tech", "".toByteArray(), CERTIFICATE)

@Nested
inner class Companion {
@TestFactory
fun makeDeserializationTests() = makeRAMFMessageCompanionTests(Parcel.Companion, ::Parcel)
// TODO
assertThrows<NotImplementedError> { parcel.deserializePayload("invalid".toByteArray()) }
}
}
Original file line number Diff line number Diff line change
@@ -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<RAMFException> {
EmptyPayloadPlaintext.deserialize("a".toByteArray())
}

assertEquals("Payload is not empty", exception.message)
}
}
}
Original file line number Diff line number Diff line change
@@ -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<NotImplementedError> { ServiceMessage().serialize() }
}
}
Loading

0 comments on commit fa107fc

Please sign in to comment.