diff --git a/native/android/app/CMakeLists.txt b/native/android/app/CMakeLists.txt --- a/native/android/app/CMakeLists.txt +++ b/native/android/app/CMakeLists.txt @@ -118,6 +118,7 @@ "./src/cpp/StaffUtilsJNIHelper.cpp" "./src/cpp/AESCrypto.cpp" "./src/cpp/CommServicesAuthMetadataEmitter.cpp" + "./src/cpp/CommMMKV.cpp" ) list(APPEND GENERATED_NATIVE_CODE diff --git a/native/android/app/build.gradle b/native/android/app/build.gradle --- a/native/android/app/build.gradle +++ b/native/android/app/build.gradle @@ -652,6 +652,7 @@ } dependencies { + implementation 'com.tencent:mmkv:1.3.3' implementation fileTree(dir: "libs", include: ["*.jar"]) implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.6.10" //noinspection GradleDynamicVersion diff --git a/native/android/app/src/cpp/CommMMKV.cpp b/native/android/app/src/cpp/CommMMKV.cpp new file mode 100644 --- /dev/null +++ b/native/android/app/src/cpp/CommMMKV.cpp @@ -0,0 +1,68 @@ +#include "jniHelpers.h" +#include +#include + +using namespace facebook::jni; + +class CommMMKVJavaClass : public JavaClass { +public: + static auto constexpr kJavaDescriptor = "Lapp/comm/android/fbjni/CommMMKV;"; + + static void initialize() { + static const auto cls = javaClassStatic(); + static auto method = cls->getStaticMethod("initialize"); + method(cls); + } + + static void clearSensitiveData() { + static const auto cls = javaClassStatic(); + static auto method = cls->getStaticMethod("clearSensitiveData"); + method(cls); + } + + static bool setString(std::string key, std::string value) { + static const auto cls = javaClassStatic(); + static auto method = + cls->getStaticMethod("setString"); + return method(cls, key, value); + } + + static std::optional getString(std::string key) { + static const auto cls = javaClassStatic(); + static auto method = + cls->getStaticMethod("getString"); + const auto result = method(cls, key); + if (result) { + return result->toStdString(); + } + return std::nullopt; + } +}; + +namespace comm { + +void CommMMKV::initialize() { + NativeAndroidAccessProvider::runTask( + []() { CommMMKVJavaClass::initialize(); }); +} + +void CommMMKV::clearSensitiveData() { + NativeAndroidAccessProvider::runTask( + []() { CommMMKVJavaClass::clearSensitiveData(); }); +} + +bool CommMMKV::setString(std::string key, std::string value) { + bool result; + NativeAndroidAccessProvider::runTask( + [&]() { result = CommMMKVJavaClass::setString(key, value); }); + return result; +} + +std::optional CommMMKV::getString(std::string key) { + std::optional result; + NativeAndroidAccessProvider::runTask( + [&]() { result = CommMMKVJavaClass::getString(key); }); + return result; +} + +} // namespace comm diff --git a/native/android/app/src/main/java/app/comm/android/MainApplication.java b/native/android/app/src/main/java/app/comm/android/MainApplication.java --- a/native/android/app/src/main/java/app/comm/android/MainApplication.java +++ b/native/android/app/src/main/java/app/comm/android/MainApplication.java @@ -6,6 +6,7 @@ import androidx.annotation.NonNull; import androidx.multidex.MultiDexApplication; import app.comm.android.commservices.CommServicesPackage; +import app.comm.android.fbjni.CommMMKV; import app.comm.android.fbjni.CommSecureStore; import app.comm.android.fbjni.DatabaseInitializer; import app.comm.android.fbjni.GlobalDBSingleton; @@ -91,6 +92,7 @@ SoLoader.init(this, /* native exopackage */ false); this.initializeDatabase(); + CommMMKV.initialize(); ApplicationLifecycleDispatcher.onApplicationCreate(this); try { Field field = CursorWindow.class.getDeclaredField("sCursorWindowSize"); diff --git a/native/android/app/src/main/java/app/comm/android/fbjni/CommMMKV.java b/native/android/app/src/main/java/app/comm/android/fbjni/CommMMKV.java new file mode 100644 --- /dev/null +++ b/native/android/app/src/main/java/app/comm/android/fbjni/CommMMKV.java @@ -0,0 +1,98 @@ +package app.comm.android.fbjni; + +import app.comm.android.MainApplication; +import app.comm.android.fbjni.CommSecureStore; +import app.comm.android.fbjni.PlatformSpecificTools; +import com.tencent.mmkv.MMKV; +import java.util.Base64; + +public class CommMMKV { + private static final int MMKV_ENCRYPTION_KEY_SIZE = 16; + private static final int MMKV_ID_SIZE = 8; + + private static final String SECURE_STORE_MMKV_ENCRYPTION_KEY_ID = + "comm.mmkvEncryptionKey"; + private static final String SECURE_STORE_MMKV_IDENTIFIER_KEY_ID = + "comm.mmkvID"; + + private static String mmkvEncryptionKey; + private static String mmkvIdentifier; + + private static MMKV getMMKVInstance(String mmkvID, String encryptionKey) { + MMKV mmkv = + MMKV.mmkvWithID(mmkvID, MMKV.SINGLE_PROCESS_MODE, encryptionKey); + if (mmkv == null) { + throw new RuntimeException("Failed to instantiate MMKV object."); + } + return mmkv; + } + + private static void assignInitializationData() { + byte[] encryptionKeyBytes = PlatformSpecificTools.generateSecureRandomBytes( + MMKV_ENCRYPTION_KEY_SIZE); + byte[] identifierBytes = + PlatformSpecificTools.generateSecureRandomBytes(MMKV_ID_SIZE); + String encryptionKey = Base64.getEncoder() + .encodeToString(encryptionKeyBytes) + .substring(0, MMKV_ENCRYPTION_KEY_SIZE); + String identifier = Base64.getEncoder() + .encodeToString(identifierBytes) + .substring(0, MMKV_ID_SIZE); + CommSecureStore.set(SECURE_STORE_MMKV_ENCRYPTION_KEY_ID, encryptionKey); + CommSecureStore.set(SECURE_STORE_MMKV_IDENTIFIER_KEY_ID, identifier); + mmkvEncryptionKey = encryptionKey; + mmkvIdentifier = identifier; + } + + public static void initialize() { + if (mmkvEncryptionKey != null && mmkvIdentifier != null) { + return; + } + + synchronized (CommMMKV.class) { + if (mmkvEncryptionKey != null && mmkvIdentifier != null) { + return; + } + + String encryptionKey = + CommSecureStore.get(SECURE_STORE_MMKV_ENCRYPTION_KEY_ID); + String identifier = + CommSecureStore.get(SECURE_STORE_MMKV_IDENTIFIER_KEY_ID); + + if (encryptionKey == null || identifier == null) { + assignInitializationData(); + } else { + mmkvEncryptionKey = encryptionKey; + mmkvIdentifier = identifier; + } + + MMKV.initialize(MainApplication.getMainApplicationContext()); + getMMKVInstance(mmkvIdentifier, mmkvEncryptionKey); + } + } + + public static void clearSensitiveData() { + initialize(); + synchronized (mmkvEncryptionKey) { + getMMKVInstance(mmkvIdentifier, mmkvEncryptionKey).clearAll(); + boolean storageRemoved = MMKV.removeStorage(mmkvIdentifier); + if (!storageRemoved) { + throw new RuntimeException("Failed to remove MMKV storage."); + } + assignInitializationData(); + MMKV.initialize(MainApplication.getMainApplicationContext()); + getMMKVInstance(mmkvIdentifier, mmkvEncryptionKey); + } + } + + public static boolean setString(String key, String value) { + initialize(); + return getMMKVInstance(mmkvIdentifier, mmkvEncryptionKey) + .encode(key, value); + } + + public static String getString(String key) { + initialize(); + return getMMKVInstance(mmkvIdentifier, mmkvEncryptionKey).decodeString(key); + } +} diff --git a/native/cpp/CommonCpp/DatabaseManagers/DatabaseManager.cpp b/native/cpp/CommonCpp/DatabaseManagers/DatabaseManager.cpp --- a/native/cpp/CommonCpp/DatabaseManagers/DatabaseManager.cpp +++ b/native/cpp/CommonCpp/DatabaseManagers/DatabaseManager.cpp @@ -1,8 +1,6 @@ #include "DatabaseManager.h" #include "../Notifications/BackgroundDataStorage/NotificationsCryptoModule.h" -#ifdef __APPLE__ #include "../Tools/CommMMKV.h" -#endif #include "../Tools/CommSecureStore.h" #include "Logger.h" #include "PlatformSpecificTools.h" @@ -39,9 +37,7 @@ CommSecureStore::set(CommSecureStore::commServicesAccessToken, ""); SQLiteQueryExecutor::clearSensitiveData(); PlatformSpecificTools::removeBackupDirectory(); -#ifdef __APPLE__ CommMMKV::clearSensitiveData(); -#endif NotificationsCryptoModule::clearSensitiveData(); DatabaseManager::setDatabaseStatusAsWorkable(); }