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..bd2bd6f67bfb --- /dev/null +++ b/app/src/androidTest/java/com/owncloud/android/utils/EncryptionUtilsMetadataVerificationTests.kt @@ -0,0 +1,70 @@ +/* + * 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 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 + +class EncryptionUtilsMetadataVerificationTests { + + private val sut = EncryptionUtilsV2() + + @Test + fun testVerifyMetadataWhenGivenValidInputsShouldReturnTrue() { + 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----- + 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, message) + val result = sut.verifySignedData(signedData, certs) + assertTrue(result) + } +} 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..77ee70659aef --- /dev/null +++ b/app/src/main/java/com/nextcloud/utils/CmsSignatureVerifier.kt @@ -0,0 +1,18 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +package com.nextcloud.utils + +class CmsSignatureVerifier { + 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..6b158d389d56 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 @@ -668,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) @@ -978,7 +894,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) @@ -990,21 +906,53 @@ class EncryptionUtilsV2 { return CMSSignedData(cmsProcessableByteArray, contentInfo) } - @Suppress("TooGenericExceptionCaught") fun verifySignedData(data: CMSSignedData, certs: List): Boolean { - val signer = data.signerInfos.signers.first() as SignerInformation + 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.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 { - signer.verify(verifierBuilder.build(cert.publicKey)) + val verifier = verifierBuilder.build(cert.publicKey) + signer.verify(verifier) }.getOrElse { - Log_OC.e(TAG, "Exception verifySignedData: $it") + Log_OC.e(TAG, "Exception verifySignedDataViaBouncyCastle: $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 +1044,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 @@ + + + + + + + +