extracting a dedicated media decrypter

This commit is contained in:
Adam Brown 2022-09-20 20:54:35 +01:00 committed by Adam Brown
parent b3d4f79d8c
commit 99c028ec01
3 changed files with 76 additions and 53 deletions

View File

@ -1,7 +1,6 @@
package app.dapk.st.messenger package app.dapk.st.messenger
import android.content.Context import android.content.Context
import android.util.Base64
import app.dapk.st.matrix.sync.RoomEvent import app.dapk.st.matrix.sync.RoomEvent
import coil.ImageLoader import coil.ImageLoader
import coil.decode.DataSource import coil.decode.DataSource
@ -14,15 +13,6 @@ import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import okhttp3.Response import okhttp3.Response
import okio.Buffer 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) : Fetcher.Factory<RoomEvent.Image> { class DecryptingFetcherFactory(private val context: Context) : Fetcher.Factory<RoomEvent.Image> {
override fun create(data: RoomEvent.Image, options: Options, imageLoader: ImageLoader): Fetcher { override fun create(data: RoomEvent.Image, options: Options, imageLoader: ImageLoader): Fetcher {
@ -34,6 +24,8 @@ 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) : Fetcher {
private val mediaDecrypter = MediaDecrypter()
override suspend fun fetch(): FetchResult { override suspend fun fetch(): FetchResult {
val response = http.newCall(Request.Builder().url(data.imageMeta.url).build()).execute() val response = http.newCall(Request.Builder().url(data.imageMeta.url).build()).execute()
val outputStream = when { val outputStream = when {
@ -44,32 +36,7 @@ class DecryptingFetcher(private val data: RoomEvent.Image, private val context:
} }
private fun handleEncrypted(response: Response, keys: RoomEvent.Image.ImageMeta.Keys): Buffer { private fun handleEncrypted(response: Response, keys: RoomEvent.Image.ImageMeta.Keys): Buffer {
val key = Base64.decode(keys.k.replace('-', '+').replace('_', '/'), Base64.DEFAULT) return response.body?.byteStream()?.let { mediaDecrypter.decrypt(it, keys) } ?: Buffer()
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 outputStream
} }
} }

View File

@ -0,0 +1,47 @@
package app.dapk.st.messenger
import android.util.Base64
import app.dapk.st.matrix.sync.RoomEvent
import okio.Buffer
import java.io.InputStream
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 MediaDecrypter {
fun decrypt(input: InputStream, 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()
input.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 outputStream
}
}

View File

@ -38,22 +38,31 @@ internal class SendMessageUseCase(
} }
is MessageService.Message.ImageMessage -> { is MessageService.Message.ImageMessage -> {
val imageContent = imageContentReader.read(message.content.uri) val request = when (message.sendEncrypted) {
val uri = httpClient.execute(uploadRequest(imageContent.content, imageContent.fileName, imageContent.mimeType)).contentUri true -> {
val request = sendRequest( throw IllegalStateException()
roomId = message.roomId, }
eventType = EventType.ROOM_MESSAGE,
txId = message.localId, false -> {
content = MessageService.Message.Content.ImageContent( val imageContent = imageContentReader.read(message.content.uri)
url = uri, val uri = httpClient.execute(uploadRequest(imageContent.content, imageContent.fileName, imageContent.mimeType)).contentUri
filename = imageContent.fileName, sendRequest(
MessageService.Message.Content.ImageContent.Info( roomId = message.roomId,
height = imageContent.height, eventType = EventType.ROOM_MESSAGE,
width = imageContent.width, txId = message.localId,
size = imageContent.size content = MessageService.Message.Content.ImageContent(
url = uri,
filename = imageContent.fileName,
MessageService.Message.Content.ImageContent.Info(
height = imageContent.height,
width = imageContent.width,
size = imageContent.size
)
),
) )
), }
) }
httpClient.execute(request).eventId httpClient.execute(request).eventId
} }
} }