Skip to content

Commit

Permalink
chore: Parcel Sync instrumented tests (#148)
Browse files Browse the repository at this point in the history
  • Loading branch information
sdsantos authored Oct 1, 2020
1 parent fd3dcc7 commit 54bc905
Show file tree
Hide file tree
Showing 13 changed files with 248 additions and 33 deletions.
2 changes: 1 addition & 1 deletion app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ dependencies {
implementation 'org.conscrypt:conscrypt-android:2.5.1'

// Local and Internet-based Parcel Delivery Connections (PDCs)
implementation 'tech.relaycorp:poweb:1.5.2'
implementation 'tech.relaycorp:poweb:1.5.4'
implementation "io.ktor:ktor-server-core:$ktorVersion"
implementation "io.ktor:ktor-server-netty:$ktorVersion"
implementation "io.ktor:ktor-websockets:$ktorVersion"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package tech.relaycorp.gateway.background.endpoint

import android.content.Context
import android.content.Intent
import androidx.test.core.app.ApplicationProvider.getApplicationContext
import androidx.test.rule.ServiceTestRule
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import tech.relaycorp.gateway.data.disk.SensitiveStore
import tech.relaycorp.gateway.data.model.RecipientLocation
import tech.relaycorp.gateway.domain.StoreParcel
import tech.relaycorp.gateway.pdc.local.PDCServer
import tech.relaycorp.gateway.test.AppTestProvider
import tech.relaycorp.gateway.test.factory.ParcelFactory
import tech.relaycorp.poweb.PoWebClient
import tech.relaycorp.poweb.ServerConnectionException
import tech.relaycorp.relaynet.bindings.pdc.NonceSigner
import tech.relaycorp.relaynet.bindings.pdc.StreamingMode
import tech.relaycorp.relaynet.messages.Parcel
import tech.relaycorp.relaynet.testing.CertificationPath
import tech.relaycorp.relaynet.testing.KeyPairSet
import tech.relaycorp.relaynet.wrappers.x509.Certificate
import javax.inject.Inject

class GatewaySyncServiceParcelCollectionTest {

@get:Rule
val serviceRule = ServiceTestRule()

@Inject
lateinit var sensitiveStore: SensitiveStore

@Inject
lateinit var storeParcel: StoreParcel

@Before
fun setUp() {
AppTestProvider.component.inject(this)
serviceRule.bindService(
Intent(
getApplicationContext<Context>(),
GatewaySyncService::class.java
)
)
}

@Test
fun parcelCollection_receiveParcel() = runBlocking {
setGatewayCertificate(CertificationPath.PRIVATE_GW)
val parcel = ParcelFactory.buildSerialized()
val storeResult = storeParcel.store(parcel, RecipientLocation.LocalEndpoint)
assertTrue(storeResult is StoreParcel.Result.Success)

val parcelCollection =
PoWebClient.initLocal(PDCServer.PORT)
.collectParcels(
arrayOf(
NonceSigner(
CertificationPath.PRIVATE_ENDPOINT,
KeyPairSet.PRIVATE_ENDPOINT.private
)
),
StreamingMode.CloseUponCompletion
)
.first()

assertEquals(
Parcel.deserialize(parcel).id,
Parcel.deserialize(parcelCollection.parcelSerialized).id
)
}

@Test(expected = ServerConnectionException::class)
fun parcelCollection_invalidHandshake() = runBlocking {
setGatewayCertificate(CertificationPath.PRIVATE_GW)
val parcel = ParcelFactory.buildSerialized()
val storeResult = storeParcel.store(parcel, RecipientLocation.LocalEndpoint)
assertTrue(storeResult is StoreParcel.Result.Success)

PoWebClient.initLocal(PDCServer.PORT)
.collectParcels(
arrayOf(
NonceSigner(
CertificationPath.PRIVATE_ENDPOINT,
KeyPairSet.PUBLIC_GW.private // Invalid key to trigger invalid handshake
)
),
StreamingMode.CloseUponCompletion
)
.collect()
}

private suspend fun setGatewayCertificate(cert: Certificate) {
sensitiveStore.store("local_gateway.certificate", cert.serialize())
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package tech.relaycorp.gateway.background.endpoint

import android.content.Context
import android.content.Intent
import androidx.test.core.app.ApplicationProvider.getApplicationContext
import androidx.test.rule.ServiceTestRule
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import tech.relaycorp.gateway.data.database.StoredParcelDao
import tech.relaycorp.gateway.data.disk.SensitiveStore
import tech.relaycorp.gateway.data.model.MessageAddress
import tech.relaycorp.gateway.data.model.RecipientLocation
import tech.relaycorp.gateway.pdc.local.PDCServer
import tech.relaycorp.gateway.test.AppTestProvider
import tech.relaycorp.poweb.PoWebClient
import tech.relaycorp.poweb.RejectedParcelException
import tech.relaycorp.relaynet.messages.Parcel
import tech.relaycorp.relaynet.testing.CertificationPath
import tech.relaycorp.relaynet.testing.KeyPairSet
import tech.relaycorp.relaynet.wrappers.x509.Certificate
import javax.inject.Inject

class GatewaySyncServiceParcelDeliveryTest {

@get:Rule
val serviceRule = ServiceTestRule()

@Inject
lateinit var sensitiveStore: SensitiveStore

@Inject
lateinit var storedParcelDao: StoredParcelDao

@Before
fun setUp() {
AppTestProvider.component.inject(this)
serviceRule.bindService(
Intent(
getApplicationContext<Context>(),
GatewaySyncService::class.java
)
)
}

@Test
fun parcelDelivery_validParcel() = runBlocking {
setGatewayCertificate(CertificationPath.PRIVATE_GW)
val recipient = "https://example.org"

val parcel = Parcel(
recipient,
ByteArray(0),
CertificationPath.PRIVATE_ENDPOINT,
senderCertificateChain = setOf(CertificationPath.PRIVATE_GW)
).serialize(KeyPairSet.PRIVATE_ENDPOINT.private)

PoWebClient.initLocal(PDCServer.PORT).deliverParcel(parcel)

val storedParcels = storedParcelDao.listForRecipients(
listOf(MessageAddress.of(recipient)),
RecipientLocation.ExternalGateway
).first()
assertEquals(1, storedParcels.size)
}

@Test(expected = RejectedParcelException::class)
fun parcelDelivery_invalidParcel() = runBlocking {
val parcel = Parcel(
"https://example.org",
ByteArray(0),
CertificationPath.PUBLIC_GW // Wrong certificate to make this parcel invalid
).serialize(KeyPairSet.PUBLIC_GW.private)

PoWebClient.initLocal(PDCServer.PORT).deliverParcel(parcel)
}

private suspend fun setGatewayCertificate(cert: Certificate) {
sensitiveStore.store("local_gateway.certificate", cert.serialize())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,21 @@ class SensitiveStoreTest {
)
}
}

@Test
fun storeAndUpdate() {
runBlocking {
val store = SensitiveStore(InstrumentationRegistry.getInstrumentation().targetContext)
val message1 = "1"
val message2 = "2"
val fileName = "${folder.name}/file"
store.store(fileName, message1.toByteArray(Charset.defaultCharset()))
store.store(fileName, message2.toByteArray(Charset.defaultCharset()))
val readData = store.read(fileName)
assertEquals(
message2,
readData!!.toString(Charset.defaultCharset())
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package tech.relaycorp.gateway.test
import dagger.Component
import tech.relaycorp.gateway.AppModule
import tech.relaycorp.gateway.background.endpoint.EndpointPreRegistrationServiceTest
import tech.relaycorp.gateway.background.endpoint.GatewaySyncServiceParcelCollectionTest
import tech.relaycorp.gateway.background.endpoint.GatewaySyncServiceParcelDeliveryTest
import tech.relaycorp.gateway.common.di.AppComponent
import tech.relaycorp.gateway.data.DataModule
import javax.inject.Singleton
Expand All @@ -15,4 +17,6 @@ interface AppTestComponent : AppComponent {
// Tests

fun inject(test: EndpointPreRegistrationServiceTest)
fun inject(test: GatewaySyncServiceParcelDeliveryTest)
fun inject(test: GatewaySyncServiceParcelCollectionTest)
}
1 change: 1 addition & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.Gateway"
android:networkSecurityConfig="@xml/network_security_config"
tools:ignore="GoogleAppIndexingWarning,UnusedAttribute">

<activity
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,34 +4,37 @@ import android.app.Service
import android.content.Intent
import android.os.Binder
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import tech.relaycorp.gateway.background.component
import tech.relaycorp.gateway.common.Logging.logger
import tech.relaycorp.gateway.pdc.local.PDCServer
import javax.inject.Inject

class GatewaySyncService : Service() {

private val scope get() = CoroutineScope(Dispatchers.IO + SupervisorJob())
private val scope = CoroutineScope(SupervisorJob())

@Inject
lateinit var pdcServer: PDCServer

override fun onBind(intent: Intent?): Binder {
component.inject(this)
logger.info("GatewaySyncService onBind")
scope.launch {
pdcServer.start()
// Wait for the server to start
runBlocking {
withContext(scope.coroutineContext) {
pdcServer.start()
}
}
return Binder()
}

override fun onUnbind(intent: Intent?): Boolean {
scope.launch {
pdcServer.stop()
runBlocking {
withContext(scope.coroutineContext) {
pdcServer.stop()
}
}
return true
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ class SensitiveStore

suspend fun store(location: String, data: ByteArray) {
withContext(Dispatchers.IO) {
buildFile(location).delete()
buildEncryptedFile(location)
.openFileOutput()
.use { it.write(data) }
Expand All @@ -41,10 +42,12 @@ class SensitiveStore
}
}

private fun buildFile(location: String) = File(context.filesDir, location)

private fun buildEncryptedFile(location: String) =
EncryptedFile.Builder(
context,
File(context.filesDir, location),
buildFile(location),
masterKey,
EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB
).build()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ class PDCServer

suspend fun start() {
withContext(Dispatchers.IO) {
server.start(true)
server.start(false)
}
}

Expand All @@ -48,7 +48,7 @@ class PDCServer
}

companion object {
private const val PORT = 13276
const val PORT = 13276
private val CALL_DEADLINE = 5.seconds
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package tech.relaycorp.gateway.pdc.local.routes

import io.ktor.application.call
import io.ktor.http.HttpStatusCode
import io.ktor.http.content.TextContent
import io.ktor.request.contentType
import io.ktor.request.receive
import io.ktor.response.respond
Expand All @@ -13,7 +12,6 @@ import tech.relaycorp.gateway.data.model.RecipientLocation
import tech.relaycorp.gateway.domain.StoreParcel
import tech.relaycorp.gateway.pdc.local.utils.ContentType
import javax.inject.Inject
import io.ktor.http.ContentType as KtorContentType

class ParcelDeliveryRoute
@Inject constructor(private val storeParcel: StoreParcel) : PDCServerRoute {
Expand All @@ -29,21 +27,17 @@ class ParcelDeliveryRoute

val storeResult =
storeParcel.store(call.receive<ByteArray>(), RecipientLocation.ExternalGateway)
call.respond(
when (storeResult) {
is StoreParcel.Result.MalformedParcel -> TextContent(
"Parcel is malformed",
KtorContentType.Text.Plain,
HttpStatusCode.BadRequest
)
is StoreParcel.Result.InvalidParcel -> TextContent(
"Parcel is invalid",
KtorContentType.Text.Plain,
HttpStatusCode.Forbidden
)
else -> HttpStatusCode.Accepted
}
)
when (storeResult) {
is StoreParcel.Result.MalformedParcel -> call.respondText(
"Parcel is malformed",
status = HttpStatusCode.BadRequest
)
is StoreParcel.Result.InvalidParcel -> call.respondText(
"Parcel is invalid",
status = HttpStatusCode.Forbidden
)
else -> call.respond(HttpStatusCode.Accepted)
}
}
}
}
7 changes: 7 additions & 0 deletions app/src/main/res/xml/network_security_config.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<!-- Allow local cleartext networking for instrumented tests of the PDC Server -->
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="false">127.0.0.1</domain>
</domain-config>
</network-security-config>
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ object StoredParcelFactory {
messageId = MessageId(Random().nextInt().toString()),
recipientLocation = recipientLocations[Random().nextInt(recipientLocations.size)],
creationTimeUtc = nowInUtc(),
expirationTimeUtc = nowInUtc(),
expirationTimeUtc = nowInUtc().plusDays(1),
storagePath = "",
size = StorageSizeFactory.build()
)
Expand Down
Loading

0 comments on commit 54bc905

Please sign in to comment.