mirror of
https://github.com/bitwarden/android.git
synced 2024-11-22 09:25:58 +03:00
Add EncryptedSharedPreferences and BaseEncryptedDiskSource (#543)
This commit is contained in:
parent
14d686af76
commit
2d4427a7cf
15 changed files with 148 additions and 8 deletions
|
@ -72,6 +72,11 @@ The following is a list of all third-party dependencies included as part of the
|
||||||
- Purpose: Display and capture images for barcode scanning.
|
- Purpose: Display and capture images for barcode scanning.
|
||||||
- License: Apache 2.0
|
- License: Apache 2.0
|
||||||
|
|
||||||
|
- **AndroidX Security**
|
||||||
|
- https://developer.android.com/jetpack/androidx/releases/security
|
||||||
|
- Purpose: Safely manage keys and encrypt files and sharedpreferences.
|
||||||
|
- License: Apache 2.0
|
||||||
|
|
||||||
- **Core SplashScreen**
|
- **Core SplashScreen**
|
||||||
- https://developer.android.com/jetpack/androidx/releases/core
|
- https://developer.android.com/jetpack/androidx/releases/core
|
||||||
- Purpose: Backwards compatible SplashScreen API implementation.
|
- Purpose: Backwards compatible SplashScreen API implementation.
|
||||||
|
|
|
@ -121,6 +121,7 @@ dependencies {
|
||||||
ksp(libs.androidx.room.compiler)
|
ksp(libs.androidx.room.compiler)
|
||||||
implementation(libs.androidx.room.ktx)
|
implementation(libs.androidx.room.ktx)
|
||||||
implementation(libs.androidx.room.runtime)
|
implementation(libs.androidx.room.runtime)
|
||||||
|
implementation(libs.androidx.security.crypto)
|
||||||
implementation(libs.androidx.splashscreen)
|
implementation(libs.androidx.splashscreen)
|
||||||
implementation(libs.bitwarden.sdk)
|
implementation(libs.bitwarden.sdk)
|
||||||
implementation(libs.bumptech.glide)
|
implementation(libs.bumptech.glide)
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name=".BitwardenApplication"
|
android:name=".BitwardenApplication"
|
||||||
android:allowBackup="true"
|
android:allowBackup="false"
|
||||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||||
android:fullBackupContent="@xml/backup_rules"
|
android:fullBackupContent="@xml/backup_rules"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
|
|
|
@ -2,8 +2,8 @@ package com.x8bit.bitwarden.data.auth.datasource.disk
|
||||||
|
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.UserStateJson
|
import com.x8bit.bitwarden.data.auth.datasource.disk.model.UserStateJson
|
||||||
import com.x8bit.bitwarden.data.platform.datasource.disk.BaseDiskSource
|
|
||||||
import com.x8bit.bitwarden.data.platform.datasource.disk.BaseDiskSource.Companion.BASE_KEY
|
import com.x8bit.bitwarden.data.platform.datasource.disk.BaseDiskSource.Companion.BASE_KEY
|
||||||
|
import com.x8bit.bitwarden.data.platform.datasource.disk.BaseEncryptedDiskSource
|
||||||
import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFlow
|
import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFlow
|
||||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson
|
import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
@ -26,9 +26,13 @@ private const val ORGANIZATION_KEYS_KEY = "$BASE_KEY:encOrgKeys"
|
||||||
*/
|
*/
|
||||||
@Suppress("TooManyFunctions")
|
@Suppress("TooManyFunctions")
|
||||||
class AuthDiskSourceImpl(
|
class AuthDiskSourceImpl(
|
||||||
|
encryptedSharedPreferences: SharedPreferences,
|
||||||
sharedPreferences: SharedPreferences,
|
sharedPreferences: SharedPreferences,
|
||||||
private val json: Json,
|
private val json: Json,
|
||||||
) : BaseDiskSource(sharedPreferences = sharedPreferences),
|
) : BaseEncryptedDiskSource(
|
||||||
|
encryptedSharedPreferences = encryptedSharedPreferences,
|
||||||
|
sharedPreferences = sharedPreferences,
|
||||||
|
),
|
||||||
AuthDiskSource {
|
AuthDiskSource {
|
||||||
private val mutableOrganizationsFlowMap =
|
private val mutableOrganizationsFlowMap =
|
||||||
mutableMapOf<String, MutableSharedFlow<List<SyncResponseJson.Profile.Organization>?>>()
|
mutableMapOf<String, MutableSharedFlow<List<SyncResponseJson.Profile.Organization>?>>()
|
||||||
|
|
|
@ -3,6 +3,8 @@ package com.x8bit.bitwarden.data.auth.datasource.disk.di
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource
|
import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource
|
||||||
import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSourceImpl
|
import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSourceImpl
|
||||||
|
import com.x8bit.bitwarden.data.platform.datasource.di.EncryptedPreferences
|
||||||
|
import com.x8bit.bitwarden.data.platform.datasource.di.UnencryptedPreferences
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
import dagger.hilt.InstallIn
|
import dagger.hilt.InstallIn
|
||||||
|
@ -20,10 +22,12 @@ object AuthDiskModule {
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideAuthDiskSource(
|
fun provideAuthDiskSource(
|
||||||
sharedPreferences: SharedPreferences,
|
@EncryptedPreferences encryptedSharedPreferences: SharedPreferences,
|
||||||
|
@UnencryptedPreferences sharedPreferences: SharedPreferences,
|
||||||
json: Json,
|
json: Json,
|
||||||
): AuthDiskSource =
|
): AuthDiskSource =
|
||||||
AuthDiskSourceImpl(
|
AuthDiskSourceImpl(
|
||||||
|
encryptedSharedPreferences = encryptedSharedPreferences,
|
||||||
sharedPreferences = sharedPreferences,
|
sharedPreferences = sharedPreferences,
|
||||||
json = json,
|
json = json,
|
||||||
)
|
)
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
package com.x8bit.bitwarden.data.platform.datasource.di
|
||||||
|
|
||||||
|
import android.content.SharedPreferences
|
||||||
|
import javax.inject.Qualifier
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to denote an instance of [SharedPreferences] that encrypts its data.
|
||||||
|
*/
|
||||||
|
@Qualifier
|
||||||
|
@Retention(AnnotationRetention.RUNTIME)
|
||||||
|
annotation class EncryptedPreferences
|
|
@ -3,6 +3,8 @@ package com.x8bit.bitwarden.data.platform.datasource.di
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
|
import androidx.security.crypto.EncryptedSharedPreferences
|
||||||
|
import androidx.security.crypto.MasterKey
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
import dagger.hilt.InstallIn
|
import dagger.hilt.InstallIn
|
||||||
|
@ -18,11 +20,29 @@ object PreferenceModule {
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideDefaultSharedPreferences(
|
@UnencryptedPreferences
|
||||||
|
fun provideUnencryptedSharedPreferences(
|
||||||
application: Application,
|
application: Application,
|
||||||
): SharedPreferences =
|
): SharedPreferences =
|
||||||
application.getSharedPreferences(
|
application.getSharedPreferences(
|
||||||
"${application.packageName}_preferences",
|
"${application.packageName}_preferences",
|
||||||
Context.MODE_PRIVATE,
|
Context.MODE_PRIVATE,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
@EncryptedPreferences
|
||||||
|
fun provideEncryptedSharedPreferences(
|
||||||
|
application: Application,
|
||||||
|
): SharedPreferences =
|
||||||
|
EncryptedSharedPreferences
|
||||||
|
.create(
|
||||||
|
application,
|
||||||
|
"${application.packageName}_encrypted_preferences",
|
||||||
|
MasterKey.Builder(application)
|
||||||
|
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
|
||||||
|
.build(),
|
||||||
|
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
|
||||||
|
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
package com.x8bit.bitwarden.data.platform.datasource.di
|
||||||
|
|
||||||
|
import android.content.SharedPreferences
|
||||||
|
import javax.inject.Qualifier
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to denote an instance of [SharedPreferences] that does not encrypt its data.
|
||||||
|
*/
|
||||||
|
@Qualifier
|
||||||
|
@Retention(AnnotationRetention.RUNTIME)
|
||||||
|
annotation class UnencryptedPreferences
|
|
@ -0,0 +1,31 @@
|
||||||
|
package com.x8bit.bitwarden.data.platform.datasource.disk
|
||||||
|
|
||||||
|
import android.content.SharedPreferences
|
||||||
|
import androidx.core.content.edit
|
||||||
|
import androidx.security.crypto.EncryptedSharedPreferences
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base class for simplifying interactions with [SharedPreferences] and
|
||||||
|
* [EncryptedSharedPreferences].
|
||||||
|
*/
|
||||||
|
@Suppress("UnnecessaryAbstractClass")
|
||||||
|
abstract class BaseEncryptedDiskSource(
|
||||||
|
sharedPreferences: SharedPreferences,
|
||||||
|
private val encryptedSharedPreferences: SharedPreferences,
|
||||||
|
) : BaseDiskSource(
|
||||||
|
sharedPreferences = sharedPreferences,
|
||||||
|
) {
|
||||||
|
protected fun getEncryptedString(
|
||||||
|
key: String,
|
||||||
|
default: String? = null,
|
||||||
|
): String? = encryptedSharedPreferences.getString(key, default)
|
||||||
|
|
||||||
|
protected fun putEncryptedString(
|
||||||
|
key: String,
|
||||||
|
value: String?,
|
||||||
|
): Unit = encryptedSharedPreferences.edit { putString(key, value) }
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val ENCRYPTED_BASE_KEY: String = "bwSecureStorage"
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
package com.x8bit.bitwarden.data.platform.datasource.disk.di
|
package com.x8bit.bitwarden.data.platform.datasource.disk.di
|
||||||
|
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
|
import com.x8bit.bitwarden.data.platform.datasource.di.UnencryptedPreferences
|
||||||
import com.x8bit.bitwarden.data.platform.datasource.disk.EnvironmentDiskSource
|
import com.x8bit.bitwarden.data.platform.datasource.disk.EnvironmentDiskSource
|
||||||
import com.x8bit.bitwarden.data.platform.datasource.disk.EnvironmentDiskSourceImpl
|
import com.x8bit.bitwarden.data.platform.datasource.disk.EnvironmentDiskSourceImpl
|
||||||
import com.x8bit.bitwarden.data.platform.datasource.disk.SettingsDiskSource
|
import com.x8bit.bitwarden.data.platform.datasource.disk.SettingsDiskSource
|
||||||
|
@ -22,7 +23,7 @@ object PlatformDiskModule {
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideEnvironmentDiskSource(
|
fun provideEnvironmentDiskSource(
|
||||||
sharedPreferences: SharedPreferences,
|
@UnencryptedPreferences sharedPreferences: SharedPreferences,
|
||||||
json: Json,
|
json: Json,
|
||||||
): EnvironmentDiskSource =
|
): EnvironmentDiskSource =
|
||||||
EnvironmentDiskSourceImpl(
|
EnvironmentDiskSourceImpl(
|
||||||
|
@ -33,7 +34,7 @@ object PlatformDiskModule {
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideSettingsDiskSource(
|
fun provideSettingsDiskSource(
|
||||||
sharedPreferences: SharedPreferences,
|
@UnencryptedPreferences sharedPreferences: SharedPreferences,
|
||||||
): SettingsDiskSource =
|
): SettingsDiskSource =
|
||||||
SettingsDiskSourceImpl(
|
SettingsDiskSourceImpl(
|
||||||
sharedPreferences = sharedPreferences,
|
sharedPreferences = sharedPreferences,
|
||||||
|
|
|
@ -3,6 +3,7 @@ package com.x8bit.bitwarden.data.tools.generator.datasource.disk.di
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import androidx.room.Room
|
import androidx.room.Room
|
||||||
|
import com.x8bit.bitwarden.data.platform.datasource.di.UnencryptedPreferences
|
||||||
import com.x8bit.bitwarden.data.tools.generator.datasource.disk.GeneratorDiskSource
|
import com.x8bit.bitwarden.data.tools.generator.datasource.disk.GeneratorDiskSource
|
||||||
import com.x8bit.bitwarden.data.tools.generator.datasource.disk.GeneratorDiskSourceImpl
|
import com.x8bit.bitwarden.data.tools.generator.datasource.disk.GeneratorDiskSourceImpl
|
||||||
import com.x8bit.bitwarden.data.tools.generator.datasource.disk.PasswordHistoryDiskSource
|
import com.x8bit.bitwarden.data.tools.generator.datasource.disk.PasswordHistoryDiskSource
|
||||||
|
@ -26,7 +27,7 @@ object GeneratorDiskModule {
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideGeneratorDiskSource(
|
fun provideGeneratorDiskSource(
|
||||||
sharedPreferences: SharedPreferences,
|
@UnencryptedPreferences sharedPreferences: SharedPreferences,
|
||||||
json: Json,
|
json: Json,
|
||||||
): GeneratorDiskSource =
|
): GeneratorDiskSource =
|
||||||
GeneratorDiskSourceImpl(
|
GeneratorDiskSourceImpl(
|
||||||
|
|
|
@ -1,3 +1,18 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<full-backup-content>
|
<full-backup-content>
|
||||||
|
<exclude
|
||||||
|
domain="sharedpref"
|
||||||
|
path="." />
|
||||||
|
<exclude
|
||||||
|
domain="file"
|
||||||
|
path="." />
|
||||||
|
<exclude
|
||||||
|
domain="database"
|
||||||
|
path="." />
|
||||||
|
<exclude
|
||||||
|
domain="external"
|
||||||
|
path="." />
|
||||||
|
<exclude
|
||||||
|
domain="root"
|
||||||
|
path="." />
|
||||||
</full-backup-content>
|
</full-backup-content>
|
||||||
|
|
|
@ -1,5 +1,37 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<data-extraction-rules>
|
<data-extraction-rules>
|
||||||
<cloud-backup>
|
<cloud-backup>
|
||||||
|
<exclude
|
||||||
|
domain="sharedpref"
|
||||||
|
path="." />
|
||||||
|
<exclude
|
||||||
|
domain="file"
|
||||||
|
path="." />
|
||||||
|
<exclude
|
||||||
|
domain="database"
|
||||||
|
path="." />
|
||||||
|
<exclude
|
||||||
|
domain="external"
|
||||||
|
path="." />
|
||||||
|
<exclude
|
||||||
|
domain="root"
|
||||||
|
path="." />
|
||||||
</cloud-backup>
|
</cloud-backup>
|
||||||
|
<device-transfer>
|
||||||
|
<exclude
|
||||||
|
domain="sharedpref"
|
||||||
|
path="." />
|
||||||
|
<exclude
|
||||||
|
domain="file"
|
||||||
|
path="." />
|
||||||
|
<exclude
|
||||||
|
domain="database"
|
||||||
|
path="." />
|
||||||
|
<exclude
|
||||||
|
domain="external"
|
||||||
|
path="." />
|
||||||
|
<exclude
|
||||||
|
domain="root"
|
||||||
|
path="." />
|
||||||
|
</device-transfer>
|
||||||
</data-extraction-rules>
|
</data-extraction-rules>
|
||||||
|
|
|
@ -20,11 +20,13 @@ import org.junit.jupiter.api.Assertions.assertNull
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
|
|
||||||
class AuthDiskSourceTest {
|
class AuthDiskSourceTest {
|
||||||
|
private val fakeEncryptedSharedPreferences = FakeSharedPreferences()
|
||||||
private val fakeSharedPreferences = FakeSharedPreferences()
|
private val fakeSharedPreferences = FakeSharedPreferences()
|
||||||
|
|
||||||
private val json = PlatformNetworkModule.providesJson()
|
private val json = PlatformNetworkModule.providesJson()
|
||||||
|
|
||||||
private val authDiskSource = AuthDiskSourceImpl(
|
private val authDiskSource = AuthDiskSourceImpl(
|
||||||
|
encryptedSharedPreferences = fakeEncryptedSharedPreferences,
|
||||||
sharedPreferences = fakeSharedPreferences,
|
sharedPreferences = fakeSharedPreferences,
|
||||||
json = json,
|
json = json,
|
||||||
)
|
)
|
||||||
|
|
|
@ -21,6 +21,7 @@ androidxHiltNavigationCompose = "1.1.0"
|
||||||
androidxLifecycle = "2.6.2"
|
androidxLifecycle = "2.6.2"
|
||||||
androidxNavigation = "2.7.6"
|
androidxNavigation = "2.7.6"
|
||||||
androidxRoom = "2.6.1"
|
androidxRoom = "2.6.1"
|
||||||
|
androidXSecurityCrypto = "1.1.0-alpha06"
|
||||||
androidxSplash = "1.1.0-alpha02"
|
androidxSplash = "1.1.0-alpha02"
|
||||||
# Once the app and SDK reach a critical point of completeness we should begin fixing the version
|
# Once the app and SDK reach a critical point of completeness we should begin fixing the version
|
||||||
# here (BIT-311).
|
# here (BIT-311).
|
||||||
|
@ -74,6 +75,7 @@ androidx-navigation-compose = { module = "androidx.navigation:navigation-compose
|
||||||
androidx-room-compiler = { module = "androidx.room:room-compiler", version.ref = "androidxRoom" }
|
androidx-room-compiler = { module = "androidx.room:room-compiler", version.ref = "androidxRoom" }
|
||||||
androidx-room-ktx = { module = "androidx.room:room-ktx", version.ref = "androidxRoom" }
|
androidx-room-ktx = { module = "androidx.room:room-ktx", version.ref = "androidxRoom" }
|
||||||
androidx-room-runtime = { module = "androidx.room:room-runtime", version.ref = "androidxRoom" }
|
androidx-room-runtime = { module = "androidx.room:room-runtime", version.ref = "androidxRoom" }
|
||||||
|
androidx-security-crypto = { module = "androidx.security:security-crypto", version.ref = "androidXSecurityCrypto" }
|
||||||
androidx-splashscreen = { module = "androidx.core:core-splashscreen", version.ref = "androidxSplash" }
|
androidx-splashscreen = { module = "androidx.core:core-splashscreen", version.ref = "androidxSplash" }
|
||||||
bitwarden-sdk = { module = "com.bitwarden:sdk-android", version.ref = "bitwardenSdk" }
|
bitwarden-sdk = { module = "com.bitwarden:sdk-android", version.ref = "bitwardenSdk" }
|
||||||
bumptech-glide = { module = "com.github.bumptech.glide:compose", version.ref = "glide" }
|
bumptech-glide = { module = "com.github.bumptech.glide:compose", version.ref = "glide" }
|
||||||
|
|
Loading…
Reference in a new issue