123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098 |
- /*
- * Nextcloud - Android Client
- *
- * SPDX-FileCopyrightText: 2023 Tobias Kaminsky <tobias@kaminsky.me>
- * SPDX-FileCopyrightText: 2023 Nextcloud GmbH
- * SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
- */
- package com.owncloud.android.utils
- import android.accounts.AccountManager
- import android.content.Context
- import androidx.annotation.VisibleForTesting
- import com.google.gson.reflect.TypeToken
- import com.nextcloud.client.account.User
- import com.owncloud.android.MainApp
- import com.owncloud.android.R
- import com.owncloud.android.datamodel.ArbitraryDataProvider
- import com.owncloud.android.datamodel.ArbitraryDataProviderImpl
- import com.owncloud.android.datamodel.FileDataStorageManager
- import com.owncloud.android.datamodel.OCFile
- import com.owncloud.android.datamodel.e2e.v1.decrypted.DecryptedFolderMetadataFileV1
- import com.owncloud.android.datamodel.e2e.v1.encrypted.EncryptedFolderMetadataFileV1
- import com.owncloud.android.datamodel.e2e.v2.decrypted.DecryptedFile
- import com.owncloud.android.datamodel.e2e.v2.decrypted.DecryptedFolderMetadataFile
- import com.owncloud.android.datamodel.e2e.v2.decrypted.DecryptedMetadata
- import com.owncloud.android.datamodel.e2e.v2.decrypted.DecryptedUser
- import com.owncloud.android.datamodel.e2e.v2.encrypted.EncryptedFiledrop
- import com.owncloud.android.datamodel.e2e.v2.encrypted.EncryptedFolderMetadataFile
- import com.owncloud.android.datamodel.e2e.v2.encrypted.EncryptedMetadata
- import com.owncloud.android.datamodel.e2e.v2.encrypted.EncryptedUser
- import com.owncloud.android.lib.common.OwnCloudClient
- import com.owncloud.android.lib.common.accounts.AccountUtils
- import com.owncloud.android.lib.common.operations.RemoteOperationResult
- import com.owncloud.android.lib.common.utils.Log_OC
- import com.owncloud.android.lib.resources.e2ee.GetMetadataRemoteOperation
- import com.owncloud.android.lib.resources.e2ee.MetadataResponse
- import com.owncloud.android.lib.resources.e2ee.StoreMetadataV2RemoteOperation
- import com.owncloud.android.lib.resources.e2ee.UpdateMetadataV2RemoteOperation
- import com.owncloud.android.operations.UploadException
- import org.apache.commons.httpclient.HttpStatus
- import org.bouncycastle.asn1.ASN1Sequence
- import org.bouncycastle.asn1.cms.ContentInfo
- import org.bouncycastle.cert.jcajce.JcaCertStore
- import org.bouncycastle.cms.CMSProcessableByteArray
- import org.bouncycastle.cms.CMSSignedData
- import org.bouncycastle.cms.CMSSignedDataGenerator
- import org.bouncycastle.cms.SignerInformation
- import org.bouncycastle.cms.jcajce.JcaSignerInfoGeneratorBuilder
- import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder
- import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder
- import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder
- import java.io.BufferedReader
- import java.io.ByteArrayOutputStream
- import java.io.InputStream
- import java.io.InputStreamReader
- import java.math.BigInteger
- import java.security.MessageDigest
- import java.security.PrivateKey
- import java.security.cert.X509Certificate
- import java.util.zip.GZIPInputStream
- import java.util.zip.GZIPOutputStream
- @Suppress("TooManyFunctions", "LargeClass")
- class EncryptionUtilsV2 {
- @VisibleForTesting
- fun encryptMetadata(metadata: DecryptedMetadata, metadataKey: ByteArray): EncryptedMetadata {
- val json = EncryptionUtils.serializeJSON(metadata, true)
- val gzip = gZipCompress(json)
- return EncryptionUtils.encryptStringSymmetric(
- gzip,
- metadataKey,
- EncryptionUtils.ivDelimiter
- )
- }
- @VisibleForTesting
- fun decryptMetadata(metadata: EncryptedMetadata, metadataKey: ByteArray): DecryptedMetadata {
- val decrypted = EncryptionUtils.decryptStringSymmetric(
- metadata.ciphertext,
- metadataKey,
- metadata.authenticationTag,
- metadata.nonce
- )
- val json = gZipDecompress(decrypted)
- val decryptedMetadata = EncryptionUtils.deserializeJSON(json, object : TypeToken<DecryptedMetadata>() {})
- decryptedMetadata.metadataKey = metadataKey
- return decryptedMetadata
- }
- @Suppress("LongParameterList")
- fun encryptFolderMetadataFile(
- metadataFile: DecryptedFolderMetadataFile,
- userId: String,
- folder: OCFile,
- storageManager: FileDataStorageManager,
- client: OwnCloudClient,
- privateKey: String,
- user: User,
- context: Context,
- arbitraryDataProvider: ArbitraryDataProvider
- ): EncryptedFolderMetadataFile {
- val encryptedUsers: List<EncryptedUser>
- val encryptedMetadata: EncryptedMetadata
- if (metadataFile.users.isEmpty()) {
- // we are in a subfolder, re-use users array
- val key = retrieveTopMostMetadataKey(
- folder,
- storageManager,
- client,
- userId,
- privateKey,
- user,
- context,
- arbitraryDataProvider
- )
- // do not store metadata key
- metadataFile.metadata.metadataKey = ByteArray(0)
- metadataFile.metadata.keyChecksums.clear()
- encryptedUsers = emptyList()
- encryptedMetadata = encryptMetadata(metadataFile.metadata, key)
- } else {
- encryptedUsers = metadataFile.users.map {
- encryptUser(
- it,
- metadataFile.metadata.metadataKey
- )
- }
- encryptedMetadata = encryptMetadata(metadataFile.metadata, metadataFile.metadata.metadataKey)
- }
- return EncryptedFolderMetadataFile(
- encryptedMetadata,
- encryptedUsers,
- mutableMapOf()
- )
- // if (metadataFile.users.isEmpty()) {
- // // we are in a subfolder, re-use users array
- // retrieveTopMostMetadata(
- // ocFile,
- // storageManager,
- // client
- // )
- // } else {
- // val encryptedUsers = metadataFile.users.map {
- // encryptUser(it, metadataFile.metadata.metadataKey)
- // }
- //
- // return EncryptedFolderMetadataFile(
- // encryptedMetadata,
- // encryptedUsers,
- // emptyMap()
- // )
- // }
- }
- @Throws(IllegalStateException::class, UploadException::class, Throwable::class)
- @Suppress("LongParameterList", "LongMethod", "ThrowsCount")
- fun decryptFolderMetadataFile(
- metadataFile: EncryptedFolderMetadataFile,
- userId: String,
- privateKey: String,
- ocFile: OCFile,
- storageManager: FileDataStorageManager,
- client: OwnCloudClient,
- oldCounter: Long,
- signature: String,
- user: User,
- context: Context,
- arbitraryDataProvider: ArbitraryDataProvider
- ): DecryptedFolderMetadataFile {
- val parent =
- storageManager.getFileById(ocFile.parentId) ?: throw IllegalStateException("Cannot retrieve metadata")
- var filesDropCountBefore = 0
- var filesBefore = 0
- val decryptedFolderMetadataFile = if (parent.isEncrypted) {
- // we are in a subfolder, decrypt information is in top most encrypted folder
- val topMostMetadata = retrieveTopMostMetadata(
- ocFile,
- storageManager,
- client,
- userId,
- privateKey,
- user,
- context,
- arbitraryDataProvider
- )
- val decryptedMetadata = decryptMetadata(metadataFile.metadata, topMostMetadata.metadata.metadataKey)
- decryptedMetadata.metadataKey = topMostMetadata.metadata.metadataKey
- decryptedMetadata.keyChecksums.addAll(topMostMetadata.metadata.keyChecksums)
- DecryptedFolderMetadataFile(
- decryptedMetadata,
- // subfolder do not store user array
- mutableListOf(),
- mutableMapOf()
- )
- } else {
- // Top folder
- val encryptedUser = metadataFile.users.find { it.userId == userId }
- ?: throw IllegalStateException("Cannot find current user in metadata")
- val decryptedMetadataKey = decryptMetadataKey(encryptedUser, privateKey)
- val users = metadataFile.users.map { transformUser(it) }.toMutableList()
- val decryptedMetadata = decryptMetadata(
- metadataFile.metadata,
- decryptedMetadataKey
- )
- // only top folder can have files drop
- filesBefore = decryptedMetadata.files.size
- if (metadataFile.filedrop != null) {
- filesDropCountBefore = metadataFile.filedrop.size
- }
- val fileDrop = metadataFile.filedrop
- if (fileDrop != null) {
- for (entry in fileDrop) {
- val key = entry.key
- val encryptedFile = entry.value
- val decryptedFile = decryptFiledrop(
- encryptedFile,
- privateKey,
- arbitraryDataProvider,
- user
- )
- decryptedMetadata.files[key] = decryptedFile
- }
- }
- DecryptedFolderMetadataFile(
- decryptedMetadata,
- users,
- mutableMapOf()
- )
- }
- verifyMetadata(metadataFile, decryptedFolderMetadataFile, oldCounter, signature)
- val transferredFiledrop = filesDropCountBefore > 0 &&
- decryptedFolderMetadataFile.metadata.files.size == filesBefore + filesDropCountBefore
- if (transferredFiledrop) {
- // lock folder
- val token = EncryptionUtils.lockFolder(ocFile, client)
- serializeAndUploadMetadata(
- ocFile,
- decryptedFolderMetadataFile,
- token,
- client,
- true,
- context,
- user,
- storageManager
- )
- // unlock folder
- val unlockFolderResult: RemoteOperationResult<*> = EncryptionUtils.unlockFolder(ocFile, client, token)
- if (!unlockFolderResult.isSuccess) {
- Log_OC.e(TAG, unlockFolderResult.message)
- throw IllegalStateException()
- }
- }
- return decryptedFolderMetadataFile
- }
- @Throws(Throwable::class)
- fun decryptFiledrop(
- filedrop: EncryptedFiledrop,
- privateKey: String,
- arbitraryDataProvider: ArbitraryDataProvider,
- user: User
- ): DecryptedFile {
- // decrypt key
- val encryptedKey = EncryptionUtils.decryptStringAsymmetricAsBytes(
- filedrop.users[0].encryptedFiledropKey,
- privateKey
- )
- // decrypt encrypted blob with key
- val decryptedData = EncryptionUtils.decryptStringSymmetricAsString(
- filedrop.ciphertext,
- encryptedKey,
- EncryptionUtils.decodeStringToBase64Bytes(filedrop.nonce),
- EncryptionUtils.decodeStringToBase64Bytes(filedrop.authenticationTag),
- true,
- arbitraryDataProvider,
- user
- )
- return EncryptionUtils.deserializeJSON(
- decryptedData,
- object : TypeToken<DecryptedFile>() {}
- )
- }
- @Throws(IllegalStateException::class)
- @Suppress("ThrowsCount", "LongParameterList")
- fun retrieveTopMostMetadata(
- folder: OCFile,
- storageManager: FileDataStorageManager,
- client: OwnCloudClient,
- userId: String,
- privateKey: String,
- user: User,
- context: Context,
- arbitraryDataProvider: ArbitraryDataProvider
- ): DecryptedFolderMetadataFile {
- var topMost = folder
- var parent =
- storageManager.getFileById(topMost.parentId) ?: throw IllegalStateException("Cannot retrieve metadata")
- while (parent.isEncrypted) {
- topMost = parent
- parent =
- storageManager.getFileById(topMost.parentId) ?: throw IllegalStateException("Cannot retrieve metadata")
- }
- // parent is now top most encrypted folder
- val result = GetMetadataRemoteOperation(topMost.localId).execute(client)
- if (result.isSuccess) {
- val v2 = EncryptionUtils.deserializeJSON(
- result.resultData.metadata,
- object : TypeToken<EncryptedFolderMetadataFile>() {}
- )
- return decryptFolderMetadataFile(
- v2,
- userId,
- privateKey,
- topMost,
- storageManager,
- client,
- topMost.e2eCounter,
- result.resultData.signature,
- user,
- context,
- arbitraryDataProvider
- )
- } else {
- throw IllegalStateException("Cannot retrieve metadata")
- }
- }
- @Throws(IllegalStateException::class)
- @Suppress("ThrowsCount", "LongParameterList")
- fun retrieveTopMostMetadataKey(
- folder: OCFile,
- storageManager: FileDataStorageManager,
- client: OwnCloudClient,
- userId: String,
- privateKey: String,
- user: User,
- context: Context,
- arbitraryDataProvider: ArbitraryDataProvider
- ): ByteArray {
- return retrieveTopMostMetadata(
- folder,
- storageManager,
- client,
- userId,
- privateKey,
- user,
- context,
- arbitraryDataProvider
- )
- .metadata.metadataKey
- }
- @VisibleForTesting
- fun encryptUser(user: DecryptedUser, metadataKey: ByteArray): EncryptedUser {
- val encryptedKey = EncryptionUtils.encryptStringAsymmetricV2(
- metadataKey,
- user.certificate
- )
- return EncryptedUser(
- user.userId,
- user.certificate,
- encryptedKey
- )
- }
- @VisibleForTesting
- fun transformUser(user: EncryptedUser): DecryptedUser {
- return DecryptedUser(
- user.userId,
- user.certificate,
- user.encryptedMetadataKey
- )
- }
- @VisibleForTesting
- fun decryptMetadataKey(user: EncryptedUser, privateKey: String): ByteArray {
- return EncryptionUtils.decryptStringAsymmetricV2(
- user.encryptedMetadataKey,
- privateKey
- )
- }
- fun gZipCompress(string: String): ByteArray {
- val outputStream = ByteArrayOutputStream()
- GZIPOutputStream(outputStream).apply {
- write(string.toByteArray())
- flush()
- close()
- }
- return outputStream.toByteArray()
- }
- fun gZipDecompress(compressed: String): String {
- return gZipDecompress(compressed.byteInputStream())
- }
- fun gZipDecompress(compressed: ByteArray): String {
- return gZipDecompress(compressed.inputStream())
- }
- @VisibleForTesting
- fun gZipDecompress(inputStream: InputStream): String {
- val stringBuilder = StringBuilder()
- val inputStream = GZIPInputStream(inputStream)
- // val inputStream = compressed.inputStream()
- val bufferedReader = BufferedReader(InputStreamReader(inputStream))
- // val sb = java.lang.StringBuilder()
- // for (b in compressed) {
- // sb.append(String.format("%02X ", b))
- // }
- // val out = sb.toString()
- var line = bufferedReader.readLine()
- while (line != null) {
- stringBuilder.appendLine(line)
- line = bufferedReader.readLine()
- }
- return stringBuilder.toString()
- }
- fun addShareeToMetadata(
- metadataFile: DecryptedFolderMetadataFile,
- userId: String,
- cert: String,
- decryptedMetadataKey: String?
- ): DecryptedFolderMetadataFile {
- metadataFile.users.add(DecryptedUser(userId, cert, decryptedMetadataKey))
- metadataFile.metadata.metadataKey = EncryptionUtils.generateKey()
- metadataFile.metadata.keyChecksums.add(hashMetadataKey(metadataFile.metadata.metadataKey))
- return metadataFile
- }
- @Throws(RuntimeException::class)
- fun removeShareeFromMetadata(
- metadataFile: DecryptedFolderMetadataFile,
- userIdToRemove: String
- ): DecryptedFolderMetadataFile {
- val remove = metadataFile.users.remove(metadataFile.users.find { it.userId == userIdToRemove })
- if (!remove) {
- throw java.lang.RuntimeException("Removal of user $userIdToRemove failed!")
- }
- metadataFile.metadata.metadataKey = EncryptionUtils.generateKey()
- metadataFile.metadata.keyChecksums.add(hashMetadataKey(metadataFile.metadata.metadataKey))
- return metadataFile
- }
- @Suppress("LongParameterList")
- fun addFileToMetadata(
- encryptedFileName: String,
- ocFile: OCFile,
- initializationVector: ByteArray,
- authenticationTag: String,
- key: ByteArray,
- metadataFile: DecryptedFolderMetadataFile,
- fileDataStorageManager: FileDataStorageManager
- ): DecryptedFolderMetadataFile {
- val decryptedFile = DecryptedFile(
- ocFile.decryptedFileName,
- ocFile.mimeType,
- EncryptionUtils.encodeBytesToBase64String(initializationVector),
- authenticationTag,
- EncryptionUtils.encodeBytesToBase64String(key)
- )
- metadataFile.metadata.files[encryptedFileName] = decryptedFile
- metadataFile.metadata.counter++
- ocFile.setE2eCounter(metadataFile.metadata.counter)
- fileDataStorageManager.saveFile(ocFile)
- return metadataFile
- }
- fun addFolderToMetadata(
- encryptedFileName: String,
- fileName: String,
- metadataFile: DecryptedFolderMetadataFile,
- ocFile: OCFile,
- fileDataStorageManager: FileDataStorageManager
- ): DecryptedFolderMetadataFile {
- metadataFile.metadata.folders[encryptedFileName] = fileName
- metadataFile.metadata.counter++
- ocFile.setE2eCounter(metadataFile.metadata.counter)
- fileDataStorageManager.saveFile(ocFile)
- return metadataFile
- }
- fun removeFolderFromMetadata(
- encryptedFileName: String,
- metadataFile: DecryptedFolderMetadataFile
- ): DecryptedFolderMetadataFile {
- metadataFile.metadata.folders.remove(encryptedFileName)
- return metadataFile
- }
- @Throws(IllegalStateException::class)
- fun removeFileFromMetadata(fileName: String, metadata: DecryptedFolderMetadataFile) {
- metadata.metadata.files.remove(fileName)
- ?: throw IllegalStateException("File $fileName not found in metadata!")
- }
- @Throws(IllegalStateException::class)
- fun renameFile(key: String, newName: String, metadataFile: DecryptedFolderMetadataFile) {
- if (!metadataFile.metadata.files.containsKey(key)) {
- throw IllegalStateException("File with key $key not found in metadata!")
- }
- metadataFile.metadata.files[key]!!.filename = newName
- }
- @Throws(UploadException::class, IllegalStateException::class)
- fun retrieveMetadata(
- folder: OCFile,
- client: OwnCloudClient,
- user: User,
- context: Context
- ): Pair<Boolean, DecryptedFolderMetadataFile> {
- val getMetadataOperationResult = GetMetadataRemoteOperation(folder.localId).execute(client)
- return if (getMetadataOperationResult.isSuccess) {
- // decrypt metadata
- val metadataResponse = getMetadataOperationResult.resultData
- val metadata = parseAnyMetadata(
- metadataResponse,
- user,
- client,
- context,
- folder
- )
- Pair(true, metadata)
- } else if (getMetadataOperationResult.httpCode == HttpStatus.SC_NOT_FOUND) {
- // check parent folder
- val parentFolder = FileDataStorageManager(user, context.contentResolver).getFileById(folder.parentId)
- ?: throw IllegalStateException("Cannot retrieve metadata!")
- val metadata = if (parentFolder.isEncrypted) {
- // new metadata but without sharing part
- createDecryptedFolderMetadataFile()
- } else {
- // new metadata
- val arbitraryDataProvider: ArbitraryDataProvider = ArbitraryDataProviderImpl(context)
- val publicKey: String = arbitraryDataProvider.getValue(user.accountName, EncryptionUtils.PUBLIC_KEY)
- createDecryptedFolderMetadataFile().apply {
- users = mutableListOf(DecryptedUser(client.userId, publicKey, null))
- }
- }
- Pair(false, metadata)
- } else {
- // TODO error
- throw UploadException("something wrong")
- }
- }
- @Throws(IllegalStateException::class)
- @Suppress("TooGenericExceptionCaught")
- fun parseAnyMetadata(
- metadataResponse: MetadataResponse,
- user: User,
- client: OwnCloudClient,
- context: Context,
- folder: OCFile
- ): DecryptedFolderMetadataFile {
- val arbitraryDataProvider: ArbitraryDataProvider = ArbitraryDataProviderImpl(context)
- val privateKey: String = arbitraryDataProvider.getValue(user.accountName, EncryptionUtils.PRIVATE_KEY)
- val storageManager = FileDataStorageManager(user, context.contentResolver)
- val v2 = EncryptionUtils.deserializeJSON(
- metadataResponse.metadata,
- object : TypeToken<EncryptedFolderMetadataFile>() {}
- )
- val decryptedFolderMetadata = if (v2.version == "2.0" || v2.version == "2") {
- val userId = AccountManager.get(context).getUserData(
- user.toPlatformAccount(),
- AccountUtils.Constants.KEY_USER_ID
- )
- decryptFolderMetadataFile(
- v2,
- userId,
- privateKey,
- folder,
- storageManager,
- client,
- folder.e2eCounter,
- metadataResponse.signature,
- user,
- context,
- arbitraryDataProvider
- )
- } else {
- // try to deserialize v1
- val v1 = EncryptionUtils.deserializeJSON(
- metadataResponse.metadata,
- object : TypeToken<EncryptedFolderMetadataFileV1?>() {}
- )
- // decrypt
- try {
- // decrypt metadata
- val decryptedV1 = EncryptionUtils.decryptFolderMetaData(
- v1,
- privateKey,
- arbitraryDataProvider,
- user,
- folder.localId
- )
- val publicKey: String = arbitraryDataProvider.getValue(
- user.accountName,
- EncryptionUtils.PUBLIC_KEY
- )
- // migrate to v2
- migrateV1ToV2andUpload(
- decryptedV1,
- client.userIdPlain,
- publicKey,
- folder,
- storageManager,
- client,
- user,
- context
- )
- } catch (e: Exception) {
- // TODO do better
- throw IllegalStateException("Cannot decrypt metadata")
- }
- }
- // TODO verify metadata
- // if (!verifyMetadata(decryptedFolderMetadata)) {
- // throw IllegalStateException("Metadata is corrupt!")
- // }
- return decryptedFolderMetadata
- // handle filesDrops
- // TODO re-add
- // try {
- // int filesDropCountBefore = encryptedFolderMetadata.getFiledrop().size();
- // DecryptedFolderMetadataFile decryptedFolderMetadata = new EncryptionUtilsV2().decryptFolderMetadataFile(
- // encryptedFolderMetadata,
- // privateKey);
- //
- // boolean transferredFiledrop = filesDropCountBefore > 0 && decryptedFolderMetadata.getFiles().size() ==
- // encryptedFolderMetadata.getFiles().size() + filesDropCountBefore;
- //
- // if (transferredFiledrop) {
- // // lock folder, only if not already locked
- // String token;
- // if (existingLockToken == null) {
- // token = EncryptionUtils.lockFolder(folder, client);
- // } else {
- // token = existingLockToken;
- // }
- //
- // // upload metadata
- // EncryptedFolderMetadataFile encryptedFolderMetadataNew =
- // encryptFolderMetadata(decryptedFolderMetadata, privateKey);
- //
- // String serializedFolderMetadata = EncryptionUtils.serializeJSON(encryptedFolderMetadataNew);
- //
- // EncryptionUtils.uploadMetadata(folder,
- // serializedFolderMetadata,
- // token,
- // client,
- // true);
- //
- // // unlock folder, only if not previously locked
- // if (existingLockToken == null) {
- // RemoteOperationResult unlockFolderResult = EncryptionUtils.unlockFolder(folder, client, token);
- //
- // if (!unlockFolderResult.isSuccess()) {
- // Log_OC.e(TAG, unlockFolderResult.getMessage());
- //
- // return null;
- // }
- // }
- // }
- //
- // return decryptedFolderMetadata;
- // } catch (Exception e) {
- // Log_OC.e(TAG, e.getMessage());
- // return null;
- // }
- // TODO to check
- // try {
- // int filesDropCountBefore = 0;
- // if (encryptedFolderMetadata.getFiledrop() != null) {
- // filesDropCountBefore = encryptedFolderMetadata.getFiledrop().size();
- // }
- // DecryptedFolderMetadataFile decryptedFolderMetadata = EncryptionUtils.decryptFolderMetaData(
- // encryptedFolderMetadata,
- // privateKey,
- // arbitraryDataProvider,
- // user,
- // folder.getLocalId());
- //
- // boolean transferredFiledrop = filesDropCountBefore > 0 &&
- // decryptedFolderMetadata.getFiles().size() ==
- // encryptedFolderMetadata.getFiles().size() + filesDropCountBefore;
- //
- // if (transferredFiledrop) {
- // // lock folder
- // String token = EncryptionUtils.lockFolder(folder, client);
- //
- // // upload metadata
- // EncryptedFolderMetadata encryptedFolderMetadataNew =
- // encryptFolderMetadata(decryptedFolderMetadata,
- // publicKey,
- // arbitraryDataProvider,
- // user,
- // folder.getLocalId());
- //
- }
- @Throws(UploadException::class)
- @Suppress("LongParameterList")
- fun migrateV1ToV2andUpload(
- v1: DecryptedFolderMetadataFileV1,
- userId: String,
- cert: String,
- folder: OCFile,
- storageManager: FileDataStorageManager,
- client: OwnCloudClient,
- user: User,
- context: Context
- ): DecryptedFolderMetadataFile {
- val newMetadata = migrateV1ToV2(
- v1,
- userId,
- cert,
- folder,
- storageManager
- )
- // lock
- val token = EncryptionUtils.lockFolder(folder, client)
- // upload
- serializeAndUploadMetadata(
- folder,
- newMetadata,
- token,
- client,
- true,
- context,
- user,
- storageManager
- )
- // unlock
- EncryptionUtils.unlockFolder(folder, client, token)
- return newMetadata
- }
- @Throws(IllegalStateException::class)
- fun migrateV1ToV2(
- v1: DecryptedFolderMetadataFileV1,
- userId: String,
- cert: String,
- folder: OCFile,
- storageManager: FileDataStorageManager
- ): DecryptedFolderMetadataFile {
- // key
- val key = if (v1.metadata.metadataKeys != null && v1.metadata.metadataKeys.size > 1) {
- v1.metadata.metadataKeys[0]
- } else {
- v1.metadata.metadataKey
- }
- // create new metadata
- val metadataV2 = DecryptedMetadata(
- mutableListOf(),
- false,
- 0,
- v1
- .files
- .filter { it.value.encrypted.mimetype == MimeType.WEBDAV_FOLDER }
- .mapValues { it.value.encrypted.filename }
- .toMutableMap(),
- v1
- .files
- .filter { it.value.encrypted.mimetype != MimeType.WEBDAV_FOLDER }
- .mapValues { migrateDecryptedFileV1ToV2(it.value) }
- .toMutableMap(),
- EncryptionUtils.decodeStringToBase64Bytes(key) ?: throw IllegalStateException("Metadata key not found!")
- )
- // upon migration there can only be one user, as there is no sharing yet in place
- val users = if (storageManager.getFileById(folder.parentId)?.isEncrypted == false) {
- mutableListOf(DecryptedUser(userId, cert, null))
- } else {
- mutableListOf()
- }
- // TODO
- val filedrop = mutableMapOf<String, DecryptedFile>()
- val newMetadata = DecryptedFolderMetadataFile(metadataV2, users, filedrop)
- val metadataKey = EncryptionUtils.generateKey() ?: throw UploadException("Could not encrypt folder!")
- newMetadata.metadata.metadataKey = metadataKey
- newMetadata.metadata.keyChecksums.add(EncryptionUtilsV2().hashMetadataKey(metadataKey))
- return newMetadata
- }
- @VisibleForTesting
- fun migrateDecryptedFileV1ToV2(v1: com.owncloud.android.datamodel.e2e.v1.decrypted.DecryptedFile): DecryptedFile {
- return DecryptedFile(
- v1.encrypted.filename,
- v1.encrypted.mimetype,
- v1.initializationVector,
- v1.authenticationTag ?: "",
- v1.encrypted.key
- )
- }
- @Throws(UploadException::class)
- @Suppress("LongParameterList")
- fun serializeAndUploadMetadata(
- folder: OCFile,
- metadata: DecryptedFolderMetadataFile,
- token: String,
- client: OwnCloudClient,
- metadataExists: Boolean,
- context: Context,
- user: User,
- storageManager: FileDataStorageManager
- ) {
- serializeAndUploadMetadata(
- folder.remoteId,
- metadata,
- token,
- client,
- metadataExists,
- context,
- user,
- folder,
- storageManager
- )
- }
- @Throws(UploadException::class)
- @Suppress("LongParameterList")
- fun serializeAndUploadMetadata(
- remoteId: String,
- metadata: DecryptedFolderMetadataFile,
- token: String,
- client: OwnCloudClient,
- metadataExists: Boolean,
- context: Context,
- user: User,
- folder: OCFile,
- storageManager: FileDataStorageManager
- ) {
- val arbitraryDataProvider: ArbitraryDataProvider = ArbitraryDataProviderImpl(context)
- val privateKeyString: String = arbitraryDataProvider.getValue(user.accountName, EncryptionUtils.PRIVATE_KEY)
- val publicKeyString: String = arbitraryDataProvider.getValue(user.accountName, EncryptionUtils.PUBLIC_KEY)
- val encryptedFolderMetadata = encryptFolderMetadataFile(
- metadata,
- client.userId,
- folder,
- storageManager,
- client,
- privateKeyString,
- user,
- context,
- arbitraryDataProvider
- )
- val serializedFolderMetadata = EncryptionUtils.serializeJSON(encryptedFolderMetadata, true)
- val cert = EncryptionUtils.convertCertFromString(publicKeyString)
- val privateKey = EncryptionUtils.PEMtoPrivateKey(privateKeyString)
- val signature = getMessageSignature(cert, privateKey, encryptedFolderMetadata)
- val uploadMetadataOperationResult = if (metadataExists) {
- // update metadata
- UpdateMetadataV2RemoteOperation(
- remoteId,
- serializedFolderMetadata,
- token,
- signature
- )
- .execute(client)
- } else {
- // store metadata
- StoreMetadataV2RemoteOperation(
- remoteId,
- serializedFolderMetadata,
- token,
- signature
- )
- .execute(client)
- }
- if (!uploadMetadataOperationResult.isSuccess) {
- if (metadataExists) {
- throw UploadException("Updating metadata was not successful")
- } else {
- throw UploadException("Storing metadata was not successful")
- }
- }
- }
- @Suppress("ReturnCount")
- fun verifyMetadata(
- encryptedFolderMetadataFile: EncryptedFolderMetadataFile,
- decryptedFolderMetadataFile: DecryptedFolderMetadataFile,
- oldCounter: Long,
- signature: String
- ) {
- if (decryptedFolderMetadataFile.metadata.counter < oldCounter) {
- MainApp.showMessage(R.string.e2e_counter_too_old)
- return
- }
- val message = EncryptionUtils.serializeJSON(encryptedFolderMetadataFile, true)
- val certs = decryptedFolderMetadataFile.users.map { EncryptionUtils.convertCertFromString(it.certificate) }
- val signedData = getSignedData(signature, message)
- if (!verifySignedData(signedData, certs)) {
- MainApp.showMessage(R.string.e2e_signature_does_not_match)
- return
- }
- val hashedMetadataKey = hashMetadataKey(decryptedFolderMetadataFile.metadata.metadataKey)
- if (!decryptedFolderMetadataFile.metadata.keyChecksums.contains(hashedMetadataKey)) {
- MainApp.showMessage(R.string.e2e_hash_not_found)
- return
- }
- }
- fun getSignedData(base64encodedSignature: String, message: String): CMSSignedData {
- val signature = EncryptionUtils.decodeStringToBase64Bytes(base64encodedSignature)
- val asn1Signature = ASN1Sequence.fromByteArray(signature)
- val contentInfo = ContentInfo.getInstance(asn1Signature)
- val encodedMessage = EncryptionUtils.encodeStringToBase64String(message)
- val messageData = encodedMessage.toByteArray()
- val cmsProcessableByteArray = CMSProcessableByteArray(messageData)
- return CMSSignedData(cmsProcessableByteArray, contentInfo)
- }
- fun verifySignedData(data: CMSSignedData, certs: List<X509Certificate>): Boolean {
- val signer: SignerInformation = data.signerInfos.signers.iterator().next() as SignerInformation
- certs.forEach {
- try {
- if (signer.verify(JcaSimpleSignerInfoVerifierBuilder().build(it))) {
- return true
- }
- } catch (e: java.lang.Exception) {
- Log_OC.e(TAG, "Error caught at verifySignedData: $e")
- }
- }
- return false
- }
- private fun signMessage(cert: X509Certificate, key: PrivateKey, data: ByteArray): CMSSignedData {
- val content = CMSProcessableByteArray(data)
- val certs = JcaCertStore(listOf(cert))
- val sha1signer = JcaContentSignerBuilder("SHA256withRSA").build(key)
- val signGen = CMSSignedDataGenerator().apply {
- addSignerInfoGenerator(
- JcaSignerInfoGeneratorBuilder(JcaDigestCalculatorProviderBuilder().build()).build(
- sha1signer,
- cert
- )
- )
- addCertificates(certs)
- }
- return signGen.generate(
- content,
- false
- )
- }
- /**
- * Sign the data with key, embed the certificate associated within the CMSSignedData
- * detached data not possible, as to restore asn.1
- */
- private fun signMessage(
- cert: X509Certificate,
- key: PrivateKey,
- message: EncryptedFolderMetadataFile
- ): CMSSignedData {
- val json = EncryptionUtils.serializeJSON(message, true)
- val base64 = EncryptionUtils.encodeStringToBase64String(json)
- val data = base64.toByteArray()
- return signMessage(cert, key, data)
- }
- fun signMessage(cert: X509Certificate, key: PrivateKey, string: String): CMSSignedData {
- val base64 = EncryptionUtils.encodeStringToBase64String(string)
- val data = base64.toByteArray()
- return signMessage(cert, key, data)
- }
- fun extractSignedString(signedData: CMSSignedData): String {
- val ans = signedData.getEncoded("BER")
- return EncryptionUtils.encodeBytesToBase64String(ans)
- }
- fun createDecryptedFolderMetadataFile(): DecryptedFolderMetadataFile {
- val metadata = DecryptedMetadata().apply {
- keyChecksums.add(hashMetadataKey(metadataKey))
- }
- return DecryptedFolderMetadataFile(metadata)
- }
- /**
- * SHA-256 hash of metadata-key
- */
- @Suppress("MagicNumber")
- fun hashMetadataKey(metadataKey: ByteArray): String {
- val bytes = MessageDigest
- .getInstance("SHA-256")
- .digest(metadataKey)
- return BigInteger(1, bytes).toString(16).padStart(32, '0')
- }
- fun getMessageSignature(cert: String, privateKey: String, metadataFile: EncryptedFolderMetadataFile): String {
- return getMessageSignature(
- EncryptionUtils.convertCertFromString(cert),
- EncryptionUtils.PEMtoPrivateKey(privateKey),
- metadataFile
- )
- }
- private fun getMessageSignature(
- cert: X509Certificate,
- key: PrivateKey,
- message: EncryptedFolderMetadataFile
- ): String {
- val signedMessage = signMessage(cert, key, message)
- return extractSignedString(signedMessage)
- }
- fun getMessageSignature(cert: X509Certificate, key: PrivateKey, string: String): String {
- val signedMessage = signMessage(cert, key, string)
- return extractSignedString(signedMessage)
- }
- companion object {
- private val TAG = EncryptionUtils::class.java.simpleName
- }
- }
|