From 6e369e75a505a4b7c2f14ff8720cbd0f8e48a0ea Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Mon, 27 Apr 2026 14:46:18 +0200 Subject: [PATCH 1/5] fix(end-to-encryption): verify metadata Signed-off-by: alperozturk96 # Conflicts: # app/.gitignore # app/build.gradle.kts # ndk.env --- .../android/utils/EncryptionUtilsV2IT.kt | 35 +++++- app/src/main/cpp/CMakeLists.txt | 23 ++++ app/src/main/cpp/cms_verifier.cpp | 100 ++++++++++++++++++ .../nextcloud/utils/CmsSignatureVerifier.kt | 34 ++++++ .../android/utils/EncryptionUtilsV2.kt | 31 +++--- gradle/libs.versions.toml | 2 + gradle/verification-metadata.xml | 16 +++ 7 files changed, 224 insertions(+), 17 deletions(-) create mode 100644 app/src/main/cpp/CMakeLists.txt create mode 100644 app/src/main/cpp/cms_verifier.cpp create mode 100644 app/src/main/java/com/nextcloud/utils/CmsSignatureVerifier.kt diff --git a/app/src/androidTest/java/com/owncloud/android/utils/EncryptionUtilsV2IT.kt b/app/src/androidTest/java/com/owncloud/android/utils/EncryptionUtilsV2IT.kt index 3ab05fdeaf0e..ce7773b11e29 100644 --- a/app/src/androidTest/java/com/owncloud/android/utils/EncryptionUtilsV2IT.kt +++ b/app/src/androidTest/java/com/owncloud/android/utils/EncryptionUtilsV2IT.kt @@ -22,6 +22,9 @@ 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.EncryptedFiledropUser 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.utils.Log_OC import com.owncloud.android.operations.RefreshFolderOperation import com.owncloud.android.util.EncryptionTestIT import junit.framework.TestCase.assertEquals @@ -758,8 +761,6 @@ class EncryptionUtilsV2IT : EncryptionIT() { certificateEnc2, certificateT1 ) - - assertTrue(encryptionUtilsV2.verifySignedData(signed, certs)) } @Throws(Throwable::class) @@ -780,8 +781,6 @@ class EncryptionUtilsV2IT : EncryptionIT() { EncryptionUtils.convertCertFromString(enc2Cert), certificate ) - - assertTrue(encryptionUtilsV2.verifySignedData(signed, certs)) } @Test @@ -916,4 +915,32 @@ class EncryptionUtilsV2IT : EncryptionIT() { val decryptedFile = EncryptionUtilsV2().decryptFiledrop(sut, privateKey, arbitraryDataProvider, user) assertEquals("test.txt", decryptedFile.filename) } + + @Test + fun getMessageSignature_withFixedInputs_producesVerifiableSignature() { + val encryptedMetadata = EncryptedMetadata( + ciphertext = "ZmluaXNoZWRGaWxlQ2lwaGVydGV4dEFBQUFBQUFBQUFBQUFBQUFBQUE=", + nonce = "bm9uY2VBQUFBQUFBQQ==", + authenticationTag = "YXV0aFRhZ0FBQUFBQQ==" + ) + + val encryptedUser = EncryptedUser( + userId = enc1UserId, + certificate = enc1Cert, + encryptedMetadataKey = "ZW5jcnlwdGVkS2V5QUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQQ==" + ) + + val encryptedFolderMetadataFile = EncryptedFolderMetadataFile( + metadata = encryptedMetadata, + users = listOf(encryptedUser), + filedrop = null + ) + + val cert = EncryptionUtils.convertCertFromString(enc1Cert) + val privateKey = EncryptionUtils.PEMtoPrivateKey(enc1PrivateKey) + + val res = encryptionUtilsV2.getMessageSignatureT(cert, privateKey, encryptedFolderMetadataFile) + Log_OC.d("TAG", res) + assertTrue("Signature must verify against the signing certificate", res == "") + } } diff --git a/app/src/main/cpp/CMakeLists.txt b/app/src/main/cpp/CMakeLists.txt new file mode 100644 index 000000000000..87888514b1d4 --- /dev/null +++ b/app/src/main/cpp/CMakeLists.txt @@ -0,0 +1,23 @@ +# Nextcloud - Android Client +# +# SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors +# SPDX-License-Identifier: AGPL-3.0-or-later + +cmake_minimum_required(VERSION 3.10.2) + +project(cms_verifier VERSION 1.0) + +find_package(openssl REQUIRED CONFIG) + +add_library( + cms_verifier + SHARED + cms_verifier.cpp +) + +target_link_libraries( + cms_verifier + openssl::crypto + android + log +) diff --git a/app/src/main/cpp/cms_verifier.cpp b/app/src/main/cpp/cms_verifier.cpp new file mode 100644 index 000000000000..f369212a02c3 --- /dev/null +++ b/app/src/main/cpp/cms_verifier.cpp @@ -0,0 +1,100 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#include +#include +#include +#include +#include +#include +#include + +#define LOG_TAG "CmsVerifier" +#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__) + +extern "C" JNIEXPORT jboolean JNICALL +Java_com_nextcloud_utils_CmsSignatureVerifier_verifySignedData( + JNIEnv* env, + jobject /* thiz */, + jbyteArray cmsDataArray, + jbyteArray messageDataArray, + jobjectArray certPemArray +) { + jsize cmsLen = env->GetArrayLength(cmsDataArray); + jbyte* cmsBytes = env->GetByteArrayElements(cmsDataArray, nullptr); + BIO* cmsBio = BIO_new_mem_buf(cmsBytes, static_cast(cmsLen)); + + jsize msgLen = env->GetArrayLength(messageDataArray); + jbyte* msgBytes = env->GetByteArrayElements(messageDataArray, nullptr); + BIO* dataBio = BIO_new_mem_buf(msgBytes, static_cast(msgLen)); + + CMS_ContentInfo* contentInfo = d2i_CMS_bio(cmsBio, nullptr); + + BIO_free(cmsBio); + env->ReleaseByteArrayElements(cmsDataArray, cmsBytes, JNI_ABORT); + + if (contentInfo == nullptr) { + LOGE("Failed to parse CMS content info"); + BIO_free(dataBio); + env->ReleaseByteArrayElements(messageDataArray, msgBytes, JNI_ABORT); + return JNI_FALSE; + } + + int verifyResult = CMS_verify( + contentInfo, + nullptr, + nullptr, + dataBio, + nullptr, + CMS_DETACHED | CMS_NO_SIGNER_CERT_VERIFY + ); + + BIO_free(dataBio); + env->ReleaseByteArrayElements(messageDataArray, msgBytes, JNI_ABORT); + + if (verifyResult != 1) { + LOGE("CMS_verify failed"); + CMS_ContentInfo_free(contentInfo); + return JNI_FALSE; + } + + STACK_OF(CMS_SignerInfo)* signerInfos = CMS_get0_SignerInfos(contentInfo); + int numSigners = sk_CMS_SignerInfo_num(signerInfos); + jsize numCerts = env->GetArrayLength(certPemArray); + jboolean matched = JNI_FALSE; + + for (jsize i = 0; i < numCerts && !matched; ++i) { + auto certPem = reinterpret_cast(env->GetObjectArrayElement(certPemArray, i)); + const char* pemChars = env->GetStringUTFChars(certPem, nullptr); + + BIO* certBio = BIO_new(BIO_s_mem()); + BIO_write(certBio, pemChars, static_cast(strlen(pemChars))); + X509* certX509 = PEM_read_bio_X509(certBio, nullptr, nullptr, nullptr); + + BIO_free(certBio); + env->ReleaseStringUTFChars(certPem, pemChars); + env->DeleteLocalRef(certPem); + + if (certX509 == nullptr) { + LOGE("Failed to parse PEM certificate at index %d", i); + continue; + } + + for (int j = 0; j < numSigners; ++j) { + CMS_SignerInfo* signerInfo = sk_CMS_SignerInfo_value(signerInfos, j); + if (CMS_SignerInfo_cert_cmp(signerInfo, certX509) == 0) { + matched = JNI_TRUE; + break; + } + } + + X509_free(certX509); + } + + CMS_ContentInfo_free(contentInfo); + return matched; +} diff --git a/app/src/main/java/com/nextcloud/utils/CmsSignatureVerifier.kt b/app/src/main/java/com/nextcloud/utils/CmsSignatureVerifier.kt new file mode 100644 index 000000000000..8d2c6b9ce42d --- /dev/null +++ b/app/src/main/java/com/nextcloud/utils/CmsSignatureVerifier.kt @@ -0,0 +1,34 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +package com.nextcloud.utils + +/** + * JNI bridge that delegates CMS signature verification to native OpenSSL. + * + * The native implementation mirrors the iOS `verifySignatureCMS` logic: + * - Parses the DER-encoded CMS structure + * - Verifies the detached signature without CA-chain validation (CMS_NO_SIGNER_CERT_VERIFY) + * - Matches the resulting signer identity against each supplied PEM certificate + * (equivalent to `CMS_SignerInfo_cert_cmp`) + */ +class CmsSignatureVerifier { + /** + * @param cmsData DER-encoded CMS ContentInfo (detached, without embedded content) + * @param messageData The raw content bytes that were signed + * @param certificates PEM-encoded X.509 certificates to match against the CMS signer + * @return `true` if the signature is cryptographically valid and the signer matches + * at least one of the supplied certificates + */ + external fun verifySignedData(cmsData: ByteArray, messageData: ByteArray, certificates: Array): Boolean + + companion object { + init { + System.loadLibrary("cms_verifier") + } + } +} diff --git a/app/src/main/java/com/owncloud/android/utils/EncryptionUtilsV2.kt b/app/src/main/java/com/owncloud/android/utils/EncryptionUtilsV2.kt index e87ef399d635..512d77660834 100644 --- a/app/src/main/java/com/owncloud/android/utils/EncryptionUtilsV2.kt +++ b/app/src/main/java/com/owncloud/android/utils/EncryptionUtilsV2.kt @@ -12,6 +12,7 @@ import android.content.Context import androidx.annotation.VisibleForTesting import com.google.gson.reflect.TypeToken import com.nextcloud.client.account.User +import com.nextcloud.utils.CmsSignatureVerifier import com.nextcloud.utils.autoRename.AutoRename import com.nextcloud.utils.e2ee.E2EVersionHelper import com.nextcloud.utils.extensions.showToast @@ -47,9 +48,7 @@ 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 @@ -990,21 +989,26 @@ class EncryptionUtilsV2 { return CMSSignedData(cmsProcessableByteArray, contentInfo) } - @Suppress("TooGenericExceptionCaught") fun verifySignedData(data: CMSSignedData, certs: List): Boolean { - val signer = data.signerInfos.signers.first() as SignerInformation - val verifierBuilder = JcaSimpleSignerInfoVerifierBuilder() - - return certs.any { cert -> - runCatching { - signer.verify(verifierBuilder.build(cert.publicKey)) - }.getOrElse { - Log_OC.e(TAG, "Exception verifySignedData: $it") - false - } + val cmsBytes = data.toASN1Structure().encoded + + val messageBytes = ByteArrayOutputStream().also { data.signedContent.write(it) }.toByteArray() + + val certificatesAsPEMs = certs.map { cert -> toPemString(cert) }.toTypedArray() + + return runCatching { + CmsSignatureVerifier().verifySignedData(cmsBytes, messageBytes, certificatesAsPEMs) + }.getOrElse { + Log_OC.e(TAG, "Exception verifySignedData: $it") + false } } + private fun toPemString(cert: X509Certificate): String { + val encoded = java.util.Base64.getMimeEncoder(PEM_LINE_LENGTH, "\n".toByteArray()).encodeToString(cert.encoded) + return "-----BEGIN CERTIFICATE-----\n$encoded\n-----END CERTIFICATE-----\n" + } + private fun signMessage(cert: X509Certificate, key: PrivateKey, data: ByteArray): CMSSignedData { val content = CMSProcessableByteArray(data) val certs = JcaCertStore(listOf(cert)) @@ -1096,5 +1100,6 @@ class EncryptionUtilsV2 { companion object { private val TAG = EncryptionUtils::class.java.simpleName + private const val PEM_LINE_LENGTH = 64 } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index cca111a669e8..3a7aba38f043 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -65,6 +65,7 @@ mockitoVersion = "5.22.0" mockkVersion = "1.14.9" nnioVersion = "0.3.1" objenesis = "3.5" +opensslVersion = "1.1.1q-beta-1" orchestratorVersion = "1.6.1" orgJbundleUtilOsgiWrappedOrgApacheHttpClientVersion = "4.1.2" osmdroidAndroidVersion = "6.1.20" @@ -100,6 +101,7 @@ document-scanning-android-sdk = { module = "com.github.Hazzatur:Document-Scannin fragment-ktx = { module = "androidx.fragment:fragment-ktx", version.ref = "fragmentKtxVersion" } exifinterface = { module = "androidx.exifinterface:exifinterface", version.ref = "exifinterfaceVersion" } material-icons-core = { module = "androidx.compose.material:material-icons-core", version.ref = "materialIconsCoreVersion" } +openssl = { module = "com.android.ndk.thirdparty:openssl", version.ref = "opensslVersion" } webkit = { module = "androidx.webkit:webkit", version.ref = "webkitVersion" } splashscreen = { module = "androidx.core:core-splashscreen", version.ref = "splash-screen" } sectioned-recyclerview = { module = "com.github.nextcloud-deps:sectioned-recyclerview", version.ref = "sectionedRecyclerviewVersion" } diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index c7804dce66d7..f612b96defcb 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -12365,6 +12365,14 @@ + + + + + + + + @@ -26253,6 +26261,14 @@ + + + + + + + + From 2c5637ea7327b4972430fa41a41eadf4a5591bf5 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Mon, 27 Apr 2026 15:03:06 +0200 Subject: [PATCH 2/5] add tests Signed-off-by: alperozturk96 --- ...ncryptionUtilsMetadataVerificationTests.kt | 63 +++++++++++++++++++ .../android/utils/EncryptionUtilsV2IT.kt | 31 --------- .../android/utils/EncryptionUtilsV2.kt | 2 +- 3 files changed, 64 insertions(+), 32 deletions(-) create mode 100644 app/src/androidTest/java/com/owncloud/android/utils/EncryptionUtilsMetadataVerificationTests.kt diff --git a/app/src/androidTest/java/com/owncloud/android/utils/EncryptionUtilsMetadataVerificationTests.kt b/app/src/androidTest/java/com/owncloud/android/utils/EncryptionUtilsMetadataVerificationTests.kt new file mode 100644 index 000000000000..3272e465ac6c --- /dev/null +++ b/app/src/androidTest/java/com/owncloud/android/utils/EncryptionUtilsMetadataVerificationTests.kt @@ -0,0 +1,63 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +package com.owncloud.android.utils + +import org.junit.Assert.assertTrue +import org.junit.Test + +class EncryptionUtilsMetadataVerificationTests { + + private val sut = EncryptionUtilsV2() + + @Test + fun testVerifyMetadataWhenGivenValidInputsShouldReturnTrue() { + val metadata = """ + { + "metadata" : { + "authenticationTag" : "xkVxj0NbQEXIEMlulYZJgg==", + "ciphertext" : "EOnzuyVn9R8qDUBY4yeuJbhQdkOHBMy3nyRGwY0y/+oWctV17XvE0RIbOhH7+smKV3orJKatu5fG6iIZN+HZUQASTCdQ0mdFVPJmdk20UH5nFZ/ilQIyyXAFhLHdYwWA/M7wKYoh5W9fDXNX9cZvHgjWPdT9Pq99PUv37atYxj7Je25GenbtxkVxj0NbQEXIEMlulYZJgg==", + "nonce" : "HzRiseUfoFJ5lqUi" + }, + "users" : [ { + "certificate" : "-----BEGIN CERTIFICATE-----\nMIIC7jCCAdagAwIBAgIBADANBgkqhkiG9w0BAQUFADAQMQ4wDAYDVQQDEwVhZG1p\nbjAeFw0yNjA0MjcwNzI5NDdaFw00NjA0MjIwNzI5NDdaMBAxDjAMBgNVBAMTBWFk\nbWluMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwE513/kdkIp+Z5pI\n7rq1UKV5LBiB6dl4Wh46nI3mhVacOA1dJJWIUxRkkrUNWJewe8eJ7QWmhSpeBauA\n06PrAOTd1ZA4gSUWKpsYJqKm5Nxjp+BUMK1nHGQCkNQWjRllhyKTJeG/9PPc0ZrJ\nh4V27bCXC9iX5l/ve35fp99VR4tQ947HWObe07EdPIEFNYfT/IurPwMySZ1WH1gy\nDTw7IxMXcPVg+GUlfBoSVgQ1UdCkvgHc9pE1LuFmyBguAzGLbXDfspUuTs85RLGX\nGYdv2vZU/R2kJEs3ePMtaGXw6DVSx82RkPFVaLDCdShX24yk6gLNEv9oTUXY40i3\nn8njRQIDAQABo1MwUTAdBgNVHQ4EFgQUDE381mprCEvSLaFeOwZRliBSJnwwHwYD\nVR0jBBgwFoAUDE381mprCEvSLaFeOwZRliBSJnwwDwYDVR0TAQH/BAUwAwEB/zAN\nBgkqhkiG9w0BAQUFAAOCAQEAgU0o8Qp5wn3vkcQLYao2heWKsbYYl8wqkztRVVKb\n+qMe2m/FOOMK1Rxv/anEVHHN+SnTc481fHd8z3w6II28LxJ5M+IxzoAFTj6gCv8+\nrL1R9kE91401d1+ulAiJR92ykOcB1h8bk5yoCZSRLIXwViCGUrbC3iu2NLWQDYk4\nvjxwqCSJOWUQh+qaYGCjB6mgkBMAnXGJCN2fV7sAR7N8Hy7Yh5jvuQOgY574FSoS\nuKCMGJZ6ecJlw+rB5pqanlLS9+HNnQ655/gTYgVBJClFClh4nwdPHtpyTySwgx1V\nr3VDvglfnZM+gD/D2d9nTLIlT3MZqhGOIkxKvpVVkdJKzg==\n-----END CERTIFICATE-----", + "encryptedMetadataKey" : "coawvmhMoAl3iL5okD7K4a4au0Jt0SqUXp6pHP8WD1YTOemFVPsz+ts7TD5kB7ha6Ja3tLdGMq76LP/d2/pbHUiKBd6rytUo6ioHsNmmlTGHAlk9VTDY9fcvtVgkNzy7qyXvsdsUn0gBQ18l526J/bt1uRlClYNKvaEnIh2l3B8X58pzNZqhAKNI7z7WRDbXOVskr4rnqWr2ExBeaZgFwo5nNi9yiqpckICb1S2qwuZJbItqZ8VR2bOG+WpCMwrgcE5UJ6ZvaKLREfmR+qoYYB1oyUuy78eA+sDa3rO5bSgs/9I/cli1b3lZ8JFfgHXRiUYUmBcxZOmUE2IfRSHFTA==", + "userId" : "admin" + } ], + "version" : "2.0" + } + """.trimIndent() + + val cert = """ + -----BEGIN CERTIFICATE----- + MIIC7jCCAdagAwIBAgIBADANBgkqhkiG9w0BAQUFADAQMQ4wDAYDVQQDEwVhZG1p + bjAeFw0yNjA0MjcwNzI5NDdaFw00NjA0MjIwNzI5NDdaMBAxDjAMBgNVBAMTBWFk + bWluMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwE513/kdkIp+Z5pI + 7rq1UKV5LBiB6dl4Wh46nI3mhVacOA1dJJWIUxRkkrUNWJewe8eJ7QWmhSpeBauA + 06PrAOTd1ZA4gSUWKpsYJqKm5Nxjp+BUMK1nHGQCkNQWjRllhyKTJeG/9PPc0ZrJ + h4V27bCXC9iX5l/ve35fp99VR4tQ947HWObe07EdPIEFNYfT/IurPwMySZ1WH1gy + DTw7IxMXcPVg+GUlfBoSVgQ1UdCkvgHc9pE1LuFmyBguAzGLbXDfspUuTs85RLGX + GYdv2vZU/R2kJEs3ePMtaGXw6DVSx82RkPFVaLDCdShX24yk6gLNEv9oTUXY40i3 + n8njRQIDAQABo1MwUTAdBgNVHQ4EFgQUDE381mprCEvSLaFeOwZRliBSJnwwHwYD + VR0jBBgwFoAUDE381mprCEvSLaFeOwZRliBSJnwwDwYDVR0TAQH/BAUwAwEB/zAN + BgkqhkiG9w0BAQUFAAOCAQEAgU0o8Qp5wn3vkcQLYao2heWKsbYYl8wqkztRVVKb + +qMe2m/FOOMK1Rxv/anEVHHN+SnTc481fHd8z3w6II28LxJ5M+IxzoAFTj6gCv8+ + rL1R9kE91401d1+ulAiJR92ykOcB1h8bk5yoCZSRLIXwViCGUrbC3iu2NLWQDYk4 + vjxwqCSJOWUQh+qaYGCjB6mgkBMAnXGJCN2fV7sAR7N8Hy7Yh5jvuQOgY574FSoS + uKCMGJZ6ecJlw+rB5pqanlLS9+HNnQ655/gTYgVBJClFClh4nwdPHtpyTySwgx1V + r3VDvglfnZM+gD/D2d9nTLIlT3MZqhGOIkxKvpVVkdJKzg== + -----END CERTIFICATE----- + """.trimIndent() + val certs = listOf(EncryptionUtils.convertCertFromString(cert)) + val signature = """ + MIIE1wYJKoZIhvcNAQcCoIIEyDCCBMQCAQExDzANBglghkgBZQMEAgEFADALBgkqhkiG9w0BBwGgggLyMIIC7jCCAdagAwIBAgIBADANBgkqhkiG9w0BAQUFADAQMQ4wDAYDVQQDEwVhZG1pbjAeFw0yNjA0MjcwNzI5NDdaFw00NjA0MjIwNzI5NDdaMBAxDjAMBgNVBAMTBWFkbWluMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwE513/kdkIp+Z5pI7rq1UKV5LBiB6dl4Wh46nI3mhVacOA1dJJWIUxRkkrUNWJewe8eJ7QWmhSpeBauA06PrAOTd1ZA4gSUWKpsYJqKm5Nxjp+BUMK1nHGQCkNQWjRllhyKTJeG/9PPc0ZrJh4V27bCXC9iX5l/ve35fp99VR4tQ947HWObe07EdPIEFNYfT/IurPwMySZ1WH1gyDTw7IxMXcPVg+GUlfBoSVgQ1UdCkvgHc9pE1LuFmyBguAzGLbXDfspUuTs85RLGXGYdv2vZU/R2kJEs3ePMtaGXw6DVSx82RkPFVaLDCdShX24yk6gLNEv9oTUXY40i3n8njRQIDAQABo1MwUTAdBgNVHQ4EFgQUDE381mprCEvSLaFeOwZRliBSJnwwHwYDVR0jBBgwFoAUDE381mprCEvSLaFeOwZRliBSJnwwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEAgU0o8Qp5wn3vkcQLYao2heWKsbYYl8wqkztRVVKb+qMe2m/FOOMK1Rxv/anEVHHN+SnTc481fHd8z3w6II28LxJ5M+IxzoAFTj6gCv8+rL1R9kE91401d1+ulAiJR92ykOcB1h8bk5yoCZSRLIXwViCGUrbC3iu2NLWQDYk4vjxwqCSJOWUQh+qaYGCjB6mgkBMAnXGJCN2fV7sAR7N8Hy7Yh5jvuQOgY574FSoSuKCMGJZ6ecJlw+rB5pqanlLS9+HNnQ655/gTYgVBJClFClh4nwdPHtpyTySwgx1Vr3VDvglfnZM+gD/D2d9nTLIlT3MZqhGOIkxKvpVVkdJKzjGCAakwggGlAgEAMBUwEDEOMAwGA1UEAxMFYWRtaW4CAQAwDQYJYIZIAWUDBAIBBQCgaTAYBgkqhkiG9w0BCQMxCwYJKoZIhvcNAQcBMC8GCSqGSIb3DQEJBDEiBCDJDYSA3+VA0KfGQbP7BQsnL/s24W/WIb99zb+4uQ8KLjAcBgkqhkiG9w0BCQUxDxcNMjYwNDI3MDczMDAyWjALBgkqhkiG9w0BAQsEggEAYgDB02/z+KaLvieL1hMMA9IZN8KKc4igvilBoS5W7isiArP8D/GIxghMZkrC0Tzqs+/VRlfFREUgf4aBd9GVzd86Qfrhcrzrdd8hoDQvOw/X3UGftqbgJQmOjZUDpI3TiupyQvOU/zqlIjOq5BiZN6RNti2BTcbNyjaTeVh6u1tcqVVSp/Z0keUb+CnJFtIk6WhFepJMWI0vN84OyegNsjzIMSU2WjiN3i0jmYc62MpxUN0ZzmNgdZ7y6exe1Sb8EYUYL83BehQUPKO5EwEjEwX+ScYziWK0atXZioZYI2XLejVbQm1/czPTlA3frywKyM1dnkiufzmRpB49QN4o3g== + """.trimIndent() + val signedData = sut.getSignedData(signature, metadata) + val result = sut.verifySignedData(signedData, certs) + assertTrue(result) + } +} diff --git a/app/src/androidTest/java/com/owncloud/android/utils/EncryptionUtilsV2IT.kt b/app/src/androidTest/java/com/owncloud/android/utils/EncryptionUtilsV2IT.kt index ce7773b11e29..cde408c765f0 100644 --- a/app/src/androidTest/java/com/owncloud/android/utils/EncryptionUtilsV2IT.kt +++ b/app/src/androidTest/java/com/owncloud/android/utils/EncryptionUtilsV2IT.kt @@ -22,9 +22,6 @@ 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.EncryptedFiledropUser 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.utils.Log_OC import com.owncloud.android.operations.RefreshFolderOperation import com.owncloud.android.util.EncryptionTestIT import junit.framework.TestCase.assertEquals @@ -915,32 +912,4 @@ class EncryptionUtilsV2IT : EncryptionIT() { val decryptedFile = EncryptionUtilsV2().decryptFiledrop(sut, privateKey, arbitraryDataProvider, user) assertEquals("test.txt", decryptedFile.filename) } - - @Test - fun getMessageSignature_withFixedInputs_producesVerifiableSignature() { - val encryptedMetadata = EncryptedMetadata( - ciphertext = "ZmluaXNoZWRGaWxlQ2lwaGVydGV4dEFBQUFBQUFBQUFBQUFBQUFBQUE=", - nonce = "bm9uY2VBQUFBQUFBQQ==", - authenticationTag = "YXV0aFRhZ0FBQUFBQQ==" - ) - - val encryptedUser = EncryptedUser( - userId = enc1UserId, - certificate = enc1Cert, - encryptedMetadataKey = "ZW5jcnlwdGVkS2V5QUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQQ==" - ) - - val encryptedFolderMetadataFile = EncryptedFolderMetadataFile( - metadata = encryptedMetadata, - users = listOf(encryptedUser), - filedrop = null - ) - - val cert = EncryptionUtils.convertCertFromString(enc1Cert) - val privateKey = EncryptionUtils.PEMtoPrivateKey(enc1PrivateKey) - - val res = encryptionUtilsV2.getMessageSignatureT(cert, privateKey, encryptedFolderMetadataFile) - Log_OC.d("TAG", res) - assertTrue("Signature must verify against the signing certificate", res == "") - } } diff --git a/app/src/main/java/com/owncloud/android/utils/EncryptionUtilsV2.kt b/app/src/main/java/com/owncloud/android/utils/EncryptionUtilsV2.kt index 512d77660834..733a3dbe5d12 100644 --- a/app/src/main/java/com/owncloud/android/utils/EncryptionUtilsV2.kt +++ b/app/src/main/java/com/owncloud/android/utils/EncryptionUtilsV2.kt @@ -977,7 +977,7 @@ class EncryptionUtilsV2 { return true } - private fun getSignedData(base64encodedSignature: String, message: String): CMSSignedData { + fun getSignedData(base64encodedSignature: String, message: String): CMSSignedData { val signature = EncryptionUtils.decodeStringToBase64Bytes(base64encodedSignature) val asn1Signature = ASN1Sequence.fromByteArray(signature) val contentInfo = ContentInfo.getInstance(asn1Signature) From 99cda0afb219316a447873e0ec628583b74bfa0d Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Mon, 27 Apr 2026 15:09:29 +0200 Subject: [PATCH 3/5] add tests Signed-off-by: alperozturk96 --- ...ncryptionUtilsMetadataVerificationTests.kt | 39 +++++++++++-------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/app/src/androidTest/java/com/owncloud/android/utils/EncryptionUtilsMetadataVerificationTests.kt b/app/src/androidTest/java/com/owncloud/android/utils/EncryptionUtilsMetadataVerificationTests.kt index 3272e465ac6c..bd2bd6f67bfb 100644 --- a/app/src/androidTest/java/com/owncloud/android/utils/EncryptionUtilsMetadataVerificationTests.kt +++ b/app/src/androidTest/java/com/owncloud/android/utils/EncryptionUtilsMetadataVerificationTests.kt @@ -7,6 +7,9 @@ package com.owncloud.android.utils +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 org.junit.Assert.assertTrue import org.junit.Test @@ -16,21 +19,25 @@ class EncryptionUtilsMetadataVerificationTests { @Test fun testVerifyMetadataWhenGivenValidInputsShouldReturnTrue() { - val metadata = """ - { - "metadata" : { - "authenticationTag" : "xkVxj0NbQEXIEMlulYZJgg==", - "ciphertext" : "EOnzuyVn9R8qDUBY4yeuJbhQdkOHBMy3nyRGwY0y/+oWctV17XvE0RIbOhH7+smKV3orJKatu5fG6iIZN+HZUQASTCdQ0mdFVPJmdk20UH5nFZ/ilQIyyXAFhLHdYwWA/M7wKYoh5W9fDXNX9cZvHgjWPdT9Pq99PUv37atYxj7Je25GenbtxkVxj0NbQEXIEMlulYZJgg==", - "nonce" : "HzRiseUfoFJ5lqUi" - }, - "users" : [ { - "certificate" : "-----BEGIN CERTIFICATE-----\nMIIC7jCCAdagAwIBAgIBADANBgkqhkiG9w0BAQUFADAQMQ4wDAYDVQQDEwVhZG1p\nbjAeFw0yNjA0MjcwNzI5NDdaFw00NjA0MjIwNzI5NDdaMBAxDjAMBgNVBAMTBWFk\nbWluMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwE513/kdkIp+Z5pI\n7rq1UKV5LBiB6dl4Wh46nI3mhVacOA1dJJWIUxRkkrUNWJewe8eJ7QWmhSpeBauA\n06PrAOTd1ZA4gSUWKpsYJqKm5Nxjp+BUMK1nHGQCkNQWjRllhyKTJeG/9PPc0ZrJ\nh4V27bCXC9iX5l/ve35fp99VR4tQ947HWObe07EdPIEFNYfT/IurPwMySZ1WH1gy\nDTw7IxMXcPVg+GUlfBoSVgQ1UdCkvgHc9pE1LuFmyBguAzGLbXDfspUuTs85RLGX\nGYdv2vZU/R2kJEs3ePMtaGXw6DVSx82RkPFVaLDCdShX24yk6gLNEv9oTUXY40i3\nn8njRQIDAQABo1MwUTAdBgNVHQ4EFgQUDE381mprCEvSLaFeOwZRliBSJnwwHwYD\nVR0jBBgwFoAUDE381mprCEvSLaFeOwZRliBSJnwwDwYDVR0TAQH/BAUwAwEB/zAN\nBgkqhkiG9w0BAQUFAAOCAQEAgU0o8Qp5wn3vkcQLYao2heWKsbYYl8wqkztRVVKb\n+qMe2m/FOOMK1Rxv/anEVHHN+SnTc481fHd8z3w6II28LxJ5M+IxzoAFTj6gCv8+\nrL1R9kE91401d1+ulAiJR92ykOcB1h8bk5yoCZSRLIXwViCGUrbC3iu2NLWQDYk4\nvjxwqCSJOWUQh+qaYGCjB6mgkBMAnXGJCN2fV7sAR7N8Hy7Yh5jvuQOgY574FSoS\nuKCMGJZ6ecJlw+rB5pqanlLS9+HNnQ655/gTYgVBJClFClh4nwdPHtpyTySwgx1V\nr3VDvglfnZM+gD/D2d9nTLIlT3MZqhGOIkxKvpVVkdJKzg==\n-----END CERTIFICATE-----", - "encryptedMetadataKey" : "coawvmhMoAl3iL5okD7K4a4au0Jt0SqUXp6pHP8WD1YTOemFVPsz+ts7TD5kB7ha6Ja3tLdGMq76LP/d2/pbHUiKBd6rytUo6ioHsNmmlTGHAlk9VTDY9fcvtVgkNzy7qyXvsdsUn0gBQ18l526J/bt1uRlClYNKvaEnIh2l3B8X58pzNZqhAKNI7z7WRDbXOVskr4rnqWr2ExBeaZgFwo5nNi9yiqpckICb1S2qwuZJbItqZ8VR2bOG+WpCMwrgcE5UJ6ZvaKLREfmR+qoYYB1oyUuy78eA+sDa3rO5bSgs/9I/cli1b3lZ8JFfgHXRiUYUmBcxZOmUE2IfRSHFTA==", - "userId" : "admin" - } ], - "version" : "2.0" - } - """.trimIndent() + val metadata = EncryptedFolderMetadataFile( + metadata = + EncryptedMetadata( + authenticationTag = "xkVxj0NbQEXIEMlulYZJgg==", + nonce = "HzRiseUfoFJ5lqUi", + ciphertext = "EOnzuyVn9R8qDUBY4yeuJbhQdkOHBMy3nyRGwY0y/+oWctV17XvE0RIbOhH7+smKV3orJKatu5fG6iIZN+HZUQASTCdQ0mdFVPJmdk20UH5nFZ/ilQIyyXAFhLHdYwWA/M7wKYoh5W9fDXNX9cZvHgjWPdT9Pq99PUv37atYxj7Je25GenbtxkVxj0NbQEXIEMlulYZJgg==" + ), + users = listOf( + EncryptedUser( + userId = "admin", + certificate = "-----BEGIN CERTIFICATE-----\nMIIC7jCCAdagAwIBAgIBADANBgkqhkiG9w0BAQUFADAQMQ4wDAYDVQQDEwVhZG1p\nbjAeFw0yNjA0MjcwNzI5NDdaFw00NjA0MjIwNzI5NDdaMBAxDjAMBgNVBAMTBWFk\nbWluMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwE513/kdkIp+Z5pI\n7rq1UKV5LBiB6dl4Wh46nI3mhVacOA1dJJWIUxRkkrUNWJewe8eJ7QWmhSpeBauA\n06PrAOTd1ZA4gSUWKpsYJqKm5Nxjp+BUMK1nHGQCkNQWjRllhyKTJeG/9PPc0ZrJ\nh4V27bCXC9iX5l/ve35fp99VR4tQ947HWObe07EdPIEFNYfT/IurPwMySZ1WH1gy\nDTw7IxMXcPVg+GUlfBoSVgQ1UdCkvgHc9pE1LuFmyBguAzGLbXDfspUuTs85RLGX\nGYdv2vZU/R2kJEs3ePMtaGXw6DVSx82RkPFVaLDCdShX24yk6gLNEv9oTUXY40i3\nn8njRQIDAQABo1MwUTAdBgNVHQ4EFgQUDE381mprCEvSLaFeOwZRliBSJnwwHwYD\nVR0jBBgwFoAUDE381mprCEvSLaFeOwZRliBSJnwwDwYDVR0TAQH/BAUwAwEB/zAN\nBgkqhkiG9w0BAQUFAAOCAQEAgU0o8Qp5wn3vkcQLYao2heWKsbYYl8wqkztRVVKb\n+qMe2m/FOOMK1Rxv/anEVHHN+SnTc481fHd8z3w6II28LxJ5M+IxzoAFTj6gCv8+\nrL1R9kE91401d1+ulAiJR92ykOcB1h8bk5yoCZSRLIXwViCGUrbC3iu2NLWQDYk4\nvjxwqCSJOWUQh+qaYGCjB6mgkBMAnXGJCN2fV7sAR7N8Hy7Yh5jvuQOgY574FSoS\nuKCMGJZ6ecJlw+rB5pqanlLS9+HNnQ655/gTYgVBJClFClh4nwdPHtpyTySwgx1V\nr3VDvglfnZM+gD/D2d9nTLIlT3MZqhGOIkxKvpVVkdJKzg==\n-----END CERTIFICATE-----", + encryptedMetadataKey = "coawvmhMoAl3iL5okD7K4a4au0Jt0SqUXp6pHP8WD1YTOemFVPsz+ts7TD5kB7ha6Ja3tLdGMq76LP/d2/pbHUiKBd6rytUo6ioHsNmmlTGHAlk9VTDY9fcvtVgkNzy7qyXvsdsUn0gBQ18l526J/bt1uRlClYNKvaEnIh2l3B8X58pzNZqhAKNI7z7WRDbXOVskr4rnqWr2ExBeaZgFwo5nNi9yiqpckICb1S2qwuZJbItqZ8VR2bOG+WpCMwrgcE5UJ6ZvaKLREfmR+qoYYB1oyUuy78eA+sDa3rO5bSgs/9I/cli1b3lZ8JFfgHXRiUYUmBcxZOmUE2IfRSHFTA==" + ) + ), + version = "2.0", + filedrop = mutableMapOf() + ) + val message = EncryptionUtils.serializeJSON(metadata, true) + val cert = """ -----BEGIN CERTIFICATE----- @@ -56,7 +63,7 @@ class EncryptionUtilsMetadataVerificationTests { val signature = """ MIIE1wYJKoZIhvcNAQcCoIIEyDCCBMQCAQExDzANBglghkgBZQMEAgEFADALBgkqhkiG9w0BBwGgggLyMIIC7jCCAdagAwIBAgIBADANBgkqhkiG9w0BAQUFADAQMQ4wDAYDVQQDEwVhZG1pbjAeFw0yNjA0MjcwNzI5NDdaFw00NjA0MjIwNzI5NDdaMBAxDjAMBgNVBAMTBWFkbWluMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwE513/kdkIp+Z5pI7rq1UKV5LBiB6dl4Wh46nI3mhVacOA1dJJWIUxRkkrUNWJewe8eJ7QWmhSpeBauA06PrAOTd1ZA4gSUWKpsYJqKm5Nxjp+BUMK1nHGQCkNQWjRllhyKTJeG/9PPc0ZrJh4V27bCXC9iX5l/ve35fp99VR4tQ947HWObe07EdPIEFNYfT/IurPwMySZ1WH1gyDTw7IxMXcPVg+GUlfBoSVgQ1UdCkvgHc9pE1LuFmyBguAzGLbXDfspUuTs85RLGXGYdv2vZU/R2kJEs3ePMtaGXw6DVSx82RkPFVaLDCdShX24yk6gLNEv9oTUXY40i3n8njRQIDAQABo1MwUTAdBgNVHQ4EFgQUDE381mprCEvSLaFeOwZRliBSJnwwHwYDVR0jBBgwFoAUDE381mprCEvSLaFeOwZRliBSJnwwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEAgU0o8Qp5wn3vkcQLYao2heWKsbYYl8wqkztRVVKb+qMe2m/FOOMK1Rxv/anEVHHN+SnTc481fHd8z3w6II28LxJ5M+IxzoAFTj6gCv8+rL1R9kE91401d1+ulAiJR92ykOcB1h8bk5yoCZSRLIXwViCGUrbC3iu2NLWQDYk4vjxwqCSJOWUQh+qaYGCjB6mgkBMAnXGJCN2fV7sAR7N8Hy7Yh5jvuQOgY574FSoSuKCMGJZ6ecJlw+rB5pqanlLS9+HNnQ655/gTYgVBJClFClh4nwdPHtpyTySwgx1Vr3VDvglfnZM+gD/D2d9nTLIlT3MZqhGOIkxKvpVVkdJKzjGCAakwggGlAgEAMBUwEDEOMAwGA1UEAxMFYWRtaW4CAQAwDQYJYIZIAWUDBAIBBQCgaTAYBgkqhkiG9w0BCQMxCwYJKoZIhvcNAQcBMC8GCSqGSIb3DQEJBDEiBCDJDYSA3+VA0KfGQbP7BQsnL/s24W/WIb99zb+4uQ8KLjAcBgkqhkiG9w0BCQUxDxcNMjYwNDI3MDczMDAyWjALBgkqhkiG9w0BAQsEggEAYgDB02/z+KaLvieL1hMMA9IZN8KKc4igvilBoS5W7isiArP8D/GIxghMZkrC0Tzqs+/VRlfFREUgf4aBd9GVzd86Qfrhcrzrdd8hoDQvOw/X3UGftqbgJQmOjZUDpI3TiupyQvOU/zqlIjOq5BiZN6RNti2BTcbNyjaTeVh6u1tcqVVSp/Z0keUb+CnJFtIk6WhFepJMWI0vN84OyegNsjzIMSU2WjiN3i0jmYc62MpxUN0ZzmNgdZ7y6exe1Sb8EYUYL83BehQUPKO5EwEjEwX+ScYziWK0atXZioZYI2XLejVbQm1/czPTlA3frywKyM1dnkiufzmRpB49QN4o3g== """.trimIndent() - val signedData = sut.getSignedData(signature, metadata) + val signedData = sut.getSignedData(signature, message) val result = sut.verifySignedData(signedData, certs) assertTrue(result) } From df617bba799a2d86c51e16191376bc9cf18f2c91 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Mon, 27 Apr 2026 15:12:56 +0200 Subject: [PATCH 4/5] add tests Signed-off-by: alperozturk96 --- .../android/utils/EncryptionUtilsV2IT.kt | 4 ++++ .../com/nextcloud/utils/CmsSignatureVerifier.kt | 16 ---------------- 2 files changed, 4 insertions(+), 16 deletions(-) diff --git a/app/src/androidTest/java/com/owncloud/android/utils/EncryptionUtilsV2IT.kt b/app/src/androidTest/java/com/owncloud/android/utils/EncryptionUtilsV2IT.kt index cde408c765f0..3ab05fdeaf0e 100644 --- a/app/src/androidTest/java/com/owncloud/android/utils/EncryptionUtilsV2IT.kt +++ b/app/src/androidTest/java/com/owncloud/android/utils/EncryptionUtilsV2IT.kt @@ -758,6 +758,8 @@ class EncryptionUtilsV2IT : EncryptionIT() { certificateEnc2, certificateT1 ) + + assertTrue(encryptionUtilsV2.verifySignedData(signed, certs)) } @Throws(Throwable::class) @@ -778,6 +780,8 @@ class EncryptionUtilsV2IT : EncryptionIT() { EncryptionUtils.convertCertFromString(enc2Cert), certificate ) + + assertTrue(encryptionUtilsV2.verifySignedData(signed, certs)) } @Test diff --git a/app/src/main/java/com/nextcloud/utils/CmsSignatureVerifier.kt b/app/src/main/java/com/nextcloud/utils/CmsSignatureVerifier.kt index 8d2c6b9ce42d..77ee70659aef 100644 --- a/app/src/main/java/com/nextcloud/utils/CmsSignatureVerifier.kt +++ b/app/src/main/java/com/nextcloud/utils/CmsSignatureVerifier.kt @@ -7,23 +7,7 @@ package com.nextcloud.utils -/** - * JNI bridge that delegates CMS signature verification to native OpenSSL. - * - * The native implementation mirrors the iOS `verifySignatureCMS` logic: - * - Parses the DER-encoded CMS structure - * - Verifies the detached signature without CA-chain validation (CMS_NO_SIGNER_CERT_VERIFY) - * - Matches the resulting signer identity against each supplied PEM certificate - * (equivalent to `CMS_SignerInfo_cert_cmp`) - */ class CmsSignatureVerifier { - /** - * @param cmsData DER-encoded CMS ContentInfo (detached, without embedded content) - * @param messageData The raw content bytes that were signed - * @param certificates PEM-encoded X.509 certificates to match against the CMS signer - * @return `true` if the signature is cryptographically valid and the signer matches - * at least one of the supplied certificates - */ external fun verifySignedData(cmsData: ByteArray, messageData: ByteArray, certificates: Array): Boolean companion object { From 8bccac7094a4086b6d4fe904ec9561fc7091df62 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Thu, 30 Apr 2026 15:16:48 +0200 Subject: [PATCH 5/5] add fallback Signed-off-by: alperozturk96 --- .../android/utils/EncryptionUtilsV2.kt | 118 +++++------------- 1 file changed, 31 insertions(+), 87 deletions(-) diff --git a/app/src/main/java/com/owncloud/android/utils/EncryptionUtilsV2.kt b/app/src/main/java/com/owncloud/android/utils/EncryptionUtilsV2.kt index 733a3dbe5d12..6b158d389d56 100644 --- a/app/src/main/java/com/owncloud/android/utils/EncryptionUtilsV2.kt +++ b/app/src/main/java/com/owncloud/android/utils/EncryptionUtilsV2.kt @@ -48,7 +48,9 @@ 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 @@ -667,97 +669,12 @@ class EncryptionUtilsV2 { } } - // TODO verify metadata - // if (!verifyMetadata(decryptedFolderMetadata)) { - // throw IllegalStateException("Metadata is corrupt!") - // } - // Auto rename if oc capability enabled for windows compatibility decryptedFolderMetadata.metadata.files.values.forEach { file -> file.filename = AutoRename.rename(file.filename, storageManager.getCapability(user)) } 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) @@ -999,8 +916,35 @@ class EncryptionUtilsV2 { return runCatching { CmsSignatureVerifier().verifySignedData(cmsBytes, messageBytes, certificatesAsPEMs) }.getOrElse { - Log_OC.e(TAG, "Exception verifySignedData: $it") - false + Log_OC.w(TAG, "Exception verifySignedData: $it, trying bouncy castle") + verifySignedDataViaBouncyCastle(data, certs) + } + } + + @Suppress("TooGenericExceptionCaught") + private fun verifySignedDataViaBouncyCastle(data: CMSSignedData, certs: List): Boolean { + val signers = data.signerInfos.signers + if (signers.isEmpty()) { + Log_OC.e(TAG, "signers are empty") + return false + } + + val signer: SignerInformation? = signers.first() + if (signer == null) { + Log_OC.e(TAG, "signer is null") + return false + } + + val verifierBuilder = JcaSimpleSignerInfoVerifierBuilder() + + return certs.any { cert -> + runCatching { + val verifier = verifierBuilder.build(cert.publicKey) + signer.verify(verifier) + }.getOrElse { + Log_OC.e(TAG, "Exception verifySignedDataViaBouncyCastle: $it") + false + } } }