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 unwrapping #68

Merged
merged 7 commits into from
Jul 17, 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
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