Decrypt video file

This commit is contained in:
Benoit Marty
2019-07-08 17:07:21 +02:00
parent 1b82ed5abb
commit 12bd85e0a9
11 changed files with 280 additions and 49 deletions

View File

@ -26,12 +26,14 @@ import im.vector.matrix.android.api.session.events.model.Content
import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.internal.crypto.MXEventDecryptionResult
import im.vector.matrix.android.internal.crypto.NewSessionListener
import im.vector.matrix.android.internal.crypto.attachments.ElementToDecrypt
import im.vector.matrix.android.internal.crypto.model.ImportRoomKeysResult
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
import im.vector.matrix.android.internal.crypto.model.MXEncryptEventContentResult
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
import im.vector.matrix.android.internal.crypto.model.rest.DevicesListResponse
import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody
import java.io.File
interface CryptoService {
@ -103,6 +105,13 @@ interface CryptoService {
fun decryptEventAsync(event: Event, timeline: String, callback: MatrixCallback<MXEventDecryptionResult>)
/**
* Decrypt a file.
* Result will be a decrypted file, stored in the cache folder. id parameter will be used to create a sub folder to avoid name collision.
* You can pass the eventId
*/
fun decryptFile(id: String, filename: String, url: String, elementToDecrypt: ElementToDecrypt, callback: MatrixCallback<File>)
fun getEncryptionAlgorithm(roomId: String): String?
fun shouldEncryptForInvitedMembers(roomId: String): Boolean

View File

@ -48,6 +48,7 @@ import im.vector.matrix.android.internal.crypto.actions.SetDeviceVerificationAct
import im.vector.matrix.android.internal.crypto.algorithms.IMXEncrypting
import im.vector.matrix.android.internal.crypto.algorithms.megolm.MXMegolmEncryptionFactory
import im.vector.matrix.android.internal.crypto.algorithms.olm.MXOlmEncryptionFactory
import im.vector.matrix.android.internal.crypto.attachments.ElementToDecrypt
import im.vector.matrix.android.internal.crypto.keysbackup.KeysBackup
import im.vector.matrix.android.internal.crypto.model.ImportRoomKeysResult
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
@ -78,6 +79,7 @@ import im.vector.matrix.android.internal.util.fetchCopied
import kotlinx.coroutines.*
import org.matrix.olm.OlmManager
import timber.log.Timber
import java.io.File
import java.util.*
import java.util.concurrent.atomic.AtomicBoolean
import javax.inject.Inject
@ -112,6 +114,8 @@ internal class CryptoManager @Inject constructor(
private val keysBackup: KeysBackup,
//
private val objectSigner: ObjectSigner,
// File decryptor
private val fileDecryptor: FileDecryptor,
//
private val oneTimeKeysUploader: OneTimeKeysUploader,
//
@ -607,6 +611,10 @@ internal class CryptoManager @Inject constructor(
}
}
override fun decryptFile(id: String, filename: String, url: String, elementToDecrypt: ElementToDecrypt, callback: MatrixCallback<File>) {
fileDecryptor.decryptFile(id, filename, url, elementToDecrypt, callback)
}
/**
* Decrypt an event
*

View File

@ -0,0 +1,99 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.internal.crypto
import android.content.Context
import arrow.core.Try
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.auth.data.SessionParams
import im.vector.matrix.android.api.session.content.ContentUrlResolver
import im.vector.matrix.android.internal.crypto.attachments.ElementToDecrypt
import im.vector.matrix.android.internal.crypto.attachments.MXEncryptedAttachments
import im.vector.matrix.android.internal.extensions.foldToCallback
import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import im.vector.matrix.android.internal.util.md5
import im.vector.matrix.android.internal.util.writeToFile
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import okhttp3.OkHttpClient
import okhttp3.Request
import timber.log.Timber
import java.io.File
import java.io.IOException
import javax.inject.Inject
@SessionScope
internal class FileDecryptor @Inject constructor(private val context: Context,
private val sessionParams: SessionParams,
private val contentUrlResolver: ContentUrlResolver,
private val coroutineDispatchers: MatrixCoroutineDispatchers) {
val okHttpClient = OkHttpClient()
fun decryptFile(id: String,
fileName: String,
url: String,
elementToDecrypt: ElementToDecrypt,
callback: MatrixCallback<File>) {
GlobalScope.launch(coroutineDispatchers.main) {
withContext(coroutineDispatchers.io) {
Try {
// Create dir tree:
// <cache>/DF/<md5(userId)>/<md5(id)>/
val tmpFolderRoot = File(context.cacheDir, "DF")
val tmpFolderUser = File(tmpFolderRoot, sessionParams.credentials.userId.md5())
val tmpFolder = File(tmpFolderUser, id.md5())
if (!tmpFolder.exists()) {
tmpFolder.mkdirs()
}
File(tmpFolder, fileName)
}.map { destFile ->
if (!destFile.exists()) {
Try {
Timber.v("## decrypt file")
val resolvedUrl = contentUrlResolver.resolveFullSize(url) ?: throw IllegalArgumentException("url is null")
val request = Request.Builder()
.url(resolvedUrl)
.build()
val response = okHttpClient.newCall(request).execute()
val inputStream = response.body()?.byteStream()
Timber.v("Response size ${response.body()?.contentLength()} - Stream available: ${inputStream?.available()}")
if (!response.isSuccessful) {
throw IOException()
}
MXEncryptedAttachments.decryptAttachment(inputStream, elementToDecrypt) ?: throw IllegalStateException("Decryption error")
}
.map { inputStream ->
writeToFile(inputStream, destFile)
}
}
destFile
}
}
.foldToCallback(callback)
}
}
}

View File

@ -16,7 +16,6 @@
package im.vector.matrix.android.internal.crypto.attachments
import android.text.TextUtils
import android.util.Base64
import arrow.core.Try
import im.vector.matrix.android.internal.crypto.model.rest.EncryptedFileInfo
@ -198,7 +197,7 @@ object MXEncryptedAttachments {
val currentDigestValue = base64ToUnpaddedBase64(Base64.encodeToString(messageDigest.digest(), Base64.DEFAULT))
if (!TextUtils.equals(elementToDecrypt.sha256, currentDigestValue)) {
if (elementToDecrypt.sha256 != currentDigestValue) {
Timber.e("## decryptAttachment() : Digest value mismatch")
outStream.close()
return null

View File

@ -0,0 +1,37 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.internal.util
import androidx.annotation.WorkerThread
import okio.Okio
import java.io.File
import java.io.InputStream
/**
* Save an input stream to a file with Okio
*/
@WorkerThread
fun writeToFile(inputStream: InputStream, outputFile: File) {
val source = Okio.buffer(Okio.source(inputStream))
val sink = Okio.buffer(Okio.sink(outputFile))
source.use { input ->
sink.use { output ->
output.writeAll(input)
}
}
}