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

[Auto] Release Candidate #157

Merged
merged 16 commits into from
Sep 22, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
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
119 changes: 66 additions & 53 deletions app/src/main/kotlin/app/dapk/st/graph/AppModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import android.content.Intent
import android.graphics.BitmapFactory
import android.net.Uri
import android.os.Build
import android.provider.OpenableColumns
import app.dapk.db.DapkDb
import app.dapk.st.BuildConfig
import app.dapk.st.SharedPreferencesDelegate
Expand All @@ -31,14 +32,9 @@ import app.dapk.st.matrix.crypto.cryptoService
import app.dapk.st.matrix.crypto.installCryptoService
import app.dapk.st.matrix.device.deviceService
import app.dapk.st.matrix.device.installEncryptionService
import app.dapk.st.matrix.device.internal.ApiMessage
import app.dapk.st.matrix.http.MatrixHttpClient
import app.dapk.st.matrix.http.ktor.KtorMatrixHttpClientFactory
import app.dapk.st.matrix.message.MessageEncrypter
import app.dapk.st.matrix.message.MessageService
import app.dapk.st.matrix.message.installMessageService
import app.dapk.st.matrix.message.*
import app.dapk.st.matrix.message.internal.ImageContentReader
import app.dapk.st.matrix.message.messageService
import app.dapk.st.matrix.push.installPushService
import app.dapk.st.matrix.push.pushService
import app.dapk.st.matrix.room.*
Expand All @@ -64,6 +60,7 @@ import app.dapk.st.work.TaskRunnerModule
import app.dapk.st.work.WorkModule
import com.squareup.sqldelight.android.AndroidSqliteDriver
import kotlinx.coroutines.Dispatchers
import java.io.InputStream
import java.time.Clock

internal class AppModule(context: Application, logger: MatrixLogger) {
Expand All @@ -80,6 +77,7 @@ internal class AppModule(context: Application, logger: MatrixLogger) {
private val database = DapkDb(driver)
private val clock = Clock.systemUTC()
val coroutineDispatchers = CoroutineDispatchers(Dispatchers.IO)
val base64 = AndroidBase64()

val storeModule = unsafeLazy {
StoreModule(
Expand All @@ -94,7 +92,7 @@ internal class AppModule(context: Application, logger: MatrixLogger) {
private val workModule = WorkModule(context)
private val imageLoaderModule = ImageLoaderModule(context)

private val matrixModules = MatrixModules(storeModule, trackingModule, workModule, logger, coroutineDispatchers, context.contentResolver, buildMeta)
private val matrixModules = MatrixModules(storeModule, trackingModule, workModule, logger, coroutineDispatchers, context.contentResolver, base64, buildMeta)
val domainModules = DomainModules(matrixModules, trackingModule.errorTracker, workModule, storeModule, context, coroutineDispatchers)

val coreAndroidModule = CoreAndroidModule(
Expand Down Expand Up @@ -139,6 +137,7 @@ internal class AppModule(context: Application, logger: MatrixLogger) {
deviceMeta,
coroutineDispatchers,
clock,
base64,
)
}

Expand All @@ -154,6 +153,7 @@ internal class FeatureModules internal constructor(
deviceMeta: DeviceMeta,
coroutineDispatchers: CoroutineDispatchers,
clock: Clock,
base64: Base64,
) {

val directoryModule by unsafeLazy {
Expand Down Expand Up @@ -181,7 +181,9 @@ internal class FeatureModules internal constructor(
matrixModules.room,
storeModule.value.credentialsStore(),
storeModule.value.roomStore(),
clock
clock,
context,
base64,
)
}
val homeModule by unsafeLazy { HomeModule(storeModule.value, matrixModules.profile, matrixModules.sync, buildMeta) }
Expand Down Expand Up @@ -232,6 +234,7 @@ internal class MatrixModules(
private val logger: MatrixLogger,
private val coroutineDispatchers: CoroutineDispatchers,
private val contentResolver: ContentResolver,
private val base64: Base64,
private val buildMeta: BuildMeta,
) {

Expand All @@ -249,7 +252,6 @@ internal class MatrixModules(
installAuthService(credentialsStore)
installEncryptionService(store.knownDevicesStore())

val base64 = AndroidBase64()
val olmAccountStore = OlmPersistenceWrapper(store.olmStore(), base64)
val singletonFlows = SingletonFlows(coroutineDispatchers)
val olm = OlmWrapper(
Expand All @@ -274,40 +276,47 @@ internal class MatrixModules(
coroutineDispatchers = coroutineDispatchers,
)
val imageContentReader = AndroidImageContentReader(contentResolver)
installMessageService(store.localEchoStore, BackgroundWorkAdapter(workModule.workScheduler()), imageContentReader) { serviceProvider ->
MessageEncrypter { message ->
val result = serviceProvider.cryptoService().encrypt(
roomId = when (message) {
is MessageService.Message.TextMessage -> message.roomId
is MessageService.Message.ImageMessage -> message.roomId
},
credentials = credentialsStore.credentials()!!,
when (message) {
is MessageService.Message.TextMessage -> JsonString(
MatrixHttpClient.jsonWithDefaults.encodeToString(
ApiMessage.TextMessage.serializer(),
ApiMessage.TextMessage(
ApiMessage.TextMessage.TextContent(
message.content.body,
message.content.type,
), message.roomId, type = EventType.ROOM_MESSAGE.value
)
)
)
installMessageService(
store.localEchoStore,
BackgroundWorkAdapter(workModule.workScheduler()),
imageContentReader,
messageEncrypter = {
val cryptoService = it.cryptoService()
MessageEncrypter { message ->
val result = cryptoService.encrypt(
roomId = message.roomId,
credentials = credentialsStore.credentials()!!,
messageJson = message.contents,
)

is MessageService.Message.ImageMessage -> TODO()
}
)

MessageEncrypter.EncryptedMessagePayload(
result.algorithmName,
result.senderKey,
result.cipherText,
result.sessionId,
result.deviceId,
)
}
}
MessageEncrypter.EncryptedMessagePayload(
result.algorithmName,
result.senderKey,
result.cipherText,
result.sessionId,
result.deviceId,
)
}
},
mediaEncrypter = {
val cryptoService = it.cryptoService()
MediaEncrypter { input ->
val result = cryptoService.encrypt(input)
MediaEncrypter.Result(
uri = result.uri,
contentLength = result.contentLength,
algorithm = result.algorithm,
ext = result.ext,
keyOperations = result.keyOperations,
kty = result.kty,
k = result.k,
iv = result.iv,
hashes = result.hashes,
v = result.v,
)
}
},
)

val overviewStore = store.overviewStore()
installRoomService(
Expand Down Expand Up @@ -475,23 +484,27 @@ internal class DomainModules(
}

internal class AndroidImageContentReader(private val contentResolver: ContentResolver) : ImageContentReader {
override fun read(uri: String): ImageContentReader.ImageContent {
override fun meta(uri: String): ImageContentReader.ImageContent {
val androidUri = Uri.parse(uri)
val fileStream = contentResolver.openInputStream(androidUri) ?: throw IllegalArgumentException("Could not process $uri")

val options = BitmapFactory.Options().apply { inJustDecodeBounds = true }
BitmapFactory.decodeStream(fileStream, null, options)

return contentResolver.openInputStream(androidUri)?.use { stream ->
val output = stream.readBytes()
ImageContentReader.ImageContent(
height = options.outHeight,
width = options.outWidth,
size = output.size.toLong(),
mimeType = options.outMimeType,
fileName = androidUri.lastPathSegment ?: "file",
content = output
)
val fileSize = contentResolver.query(androidUri, null, null, null, null)?.use { cursor ->
cursor.moveToFirst()
val columnIndex = cursor.getColumnIndex(OpenableColumns.SIZE)
cursor.getLong(columnIndex)
} ?: throw IllegalArgumentException("Could not process $uri")

return ImageContentReader.ImageContent(
height = options.outHeight,
width = options.outWidth,
size = fileSize,
mimeType = options.outMimeType,
fileName = androidUri.lastPathSegment ?: "file",
)
}

override fun inputStream(uri: String): InputStream = contentResolver.openInputStream(Uri.parse(uri))!!
}
4 changes: 2 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -135,8 +135,8 @@ ext.kotlinTest = { dependencies ->
dependencies.testImplementation 'io.mockk:mockk:1.12.8'
dependencies.testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.4'

dependencies.testImplementation 'org.junit.jupiter:junit-jupiter-api:5.9.0'
dependencies.testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.9.0'
dependencies.testImplementation 'org.junit.jupiter:junit-jupiter-api:5.9.1'
dependencies.testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.9.1'
}

ext.kotlinFixtures = { dependencies ->
Expand Down
4 changes: 4 additions & 0 deletions domains/firebase/crashlytics-noop/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,7 @@ plugins { id 'kotlin' }
dependencies {
implementation project(':core')
}


task generateReleaseSources {}
task compileReleaseSources {}
1 change: 1 addition & 0 deletions features/messenger/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ apply plugin: 'kotlin-parcelize'
dependencies {
implementation project(":matrix:services:sync")
implementation project(":matrix:services:message")
implementation project(":matrix:services:crypto")
implementation project(":matrix:services:room")
implementation project(":domains:android:compose-core")
implementation project(":domains:android:viewmodel")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
package app.dapk.st.messenger

import android.content.Context
import android.util.Base64
import app.dapk.st.core.Base64
import app.dapk.st.matrix.crypto.MediaDecrypter
import app.dapk.st.matrix.sync.RoomEvent
import coil.ImageLoader
import coil.decode.DataSource
Expand All @@ -14,25 +15,23 @@ import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import okio.Buffer
import java.security.MessageDigest
import javax.crypto.Cipher
import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec

private const val CRYPTO_BUFFER_SIZE = 32 * 1024
private const val CIPHER_ALGORITHM = "AES/CTR/NoPadding"
private const val SECRET_KEY_SPEC_ALGORITHM = "AES"
private const val MESSAGE_DIGEST_ALGORITHM = "SHA-256"
class DecryptingFetcherFactory(private val context: Context, base64: Base64) : Fetcher.Factory<RoomEvent.Image> {

private val mediaDecrypter = MediaDecrypter(base64)

class DecryptingFetcherFactory(private val context: Context) : Fetcher.Factory<RoomEvent.Image> {
override fun create(data: RoomEvent.Image, options: Options, imageLoader: ImageLoader): Fetcher {
return DecryptingFetcher(data, context)
return DecryptingFetcher(data, context, mediaDecrypter)
}
}

private val http = OkHttpClient()

class DecryptingFetcher(private val data: RoomEvent.Image, private val context: Context) : Fetcher {
class DecryptingFetcher(
private val data: RoomEvent.Image,
private val context: Context,
private val mediaDecrypter: MediaDecrypter,
) : Fetcher {

override suspend fun fetch(): FetchResult {
val response = http.newCall(Request.Builder().url(data.imageMeta.url).build()).execute()
Expand All @@ -44,32 +43,11 @@ class DecryptingFetcher(private val data: RoomEvent.Image, private val context:
}

private fun handleEncrypted(response: Response, keys: RoomEvent.Image.ImageMeta.Keys): Buffer {
val key = Base64.decode(keys.k.replace('-', '+').replace('_', '/'), Base64.DEFAULT)
val initVectorBytes = Base64.decode(keys.iv, Base64.DEFAULT)

val decryptCipher = Cipher.getInstance(CIPHER_ALGORITHM)
val secretKeySpec = SecretKeySpec(key, SECRET_KEY_SPEC_ALGORITHM)
val ivParameterSpec = IvParameterSpec(initVectorBytes)
decryptCipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec)

val messageDigest = MessageDigest.getInstance(MESSAGE_DIGEST_ALGORITHM)

var read: Int
val d = ByteArray(CRYPTO_BUFFER_SIZE)
var decodedBytes: ByteArray

val outputStream = Buffer()
response.body?.let {
it.byteStream().use {
read = it.read(d)
while (read != -1) {
messageDigest.update(d, 0, read)
decodedBytes = decryptCipher.update(d, 0, read)
outputStream.write(decodedBytes)
read = it.read(d)
}
return response.body?.byteStream()?.let { byteStream ->
Buffer().also { buffer ->
mediaDecrypter.decrypt(byteStream, keys.k, keys.iv).collect { buffer.write(it) }
}
}
return outputStream
} ?: Buffer()
}
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,25 @@ import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.os.Parcelable
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Surface
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.Modifier
import app.dapk.st.core.*
import app.dapk.st.design.components.SmallTalkTheme
import app.dapk.st.core.DapkActivity
import app.dapk.st.core.extensions.unsafeLazy
import app.dapk.st.core.module
import app.dapk.st.core.viewModel
import app.dapk.st.matrix.common.RoomId
import app.dapk.st.navigator.MessageAttachment
import kotlinx.parcelize.Parcelize

val LocalDecyptingFetcherFactory = staticCompositionLocalOf<DecryptingFetcherFactory> { throw IllegalAccessError() }

class MessengerActivity : DapkActivity() {

private val viewModel by viewModel { module<MessengerModule>().messengerViewModel() }
private val module by unsafeLazy { module<MessengerModule>() }
private val viewModel by viewModel { module.messengerViewModel() }

companion object {

Expand All @@ -44,11 +50,13 @@ class MessengerActivity : DapkActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val payload = readPayload<MessagerActivityPayload>()
log(AppLogTag.ERROR_NON_FATAL, payload)
val factory = module.decryptingFetcherFactory()
setContent {
Surface(Modifier.fillMaxSize()) {
Surface(Modifier.fillMaxSize()) {
CompositionLocalProvider(LocalDecyptingFetcherFactory provides factory) {
MessengerScreen(RoomId(payload.roomId), payload.attachments, viewModel, navigator)
}
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package app.dapk.st.messenger

import android.content.Context
import app.dapk.st.core.Base64
import app.dapk.st.core.ProvidableModule
import app.dapk.st.matrix.common.CredentialsStore
import app.dapk.st.matrix.message.MessageService
Expand All @@ -15,6 +17,8 @@ class MessengerModule(
private val credentialsStore: CredentialsStore,
private val roomStore: RoomStore,
private val clock: Clock,
private val context: Context,
private val base64: Base64,
) : ProvidableModule {

internal fun messengerViewModel(): MessengerViewModel {
Expand All @@ -25,4 +29,6 @@ class MessengerModule(
val mergeWithLocalEchosUseCase = MergeWithLocalEchosUseCaseImpl(LocalEchoMapper(MetaMapper()))
return TimelineUseCaseImpl(syncService, messageService, roomService, mergeWithLocalEchosUseCase)
}

internal fun decryptingFetcherFactory() = DecryptingFetcherFactory(context, base64)
}
Loading