1
0
Fork 0

For #5074 - Sync Logins, Uses KeySharedPreferences for Passwords Encryption Key

master
Emily Kager 2019-11-04 14:02:16 -08:00 committed by Emily Kager
parent ee4e1c8f39
commit c43f96096e
10 changed files with 82 additions and 15 deletions

View File

@ -402,6 +402,7 @@ dependencies {
implementation Deps.mozilla_lib_crash
implementation Deps.mozilla_lib_push_firebase
implementation Deps.mozilla_lib_dataprotect
debugImplementation Deps.leakcanary
implementation Deps.androidx_legacy

View File

@ -39,5 +39,5 @@ object FeatureFlags {
/**
* Gives option in Settings to see logins and sync logins
*/
const val logins = false
val logins = Config.channel.isNightlyOrDebug
}

View File

@ -26,6 +26,7 @@ import mozilla.components.feature.push.PushConfig
import mozilla.components.feature.push.PushSubscriptionObserver
import mozilla.components.feature.push.PushType
import mozilla.components.lib.crash.CrashReporter
import mozilla.components.lib.dataprotect.SecureAbove22Preferences
import mozilla.components.service.fxa.DeviceConfig
import mozilla.components.service.fxa.ServerConfig
import mozilla.components.service.fxa.SyncConfig
@ -33,6 +34,7 @@ import mozilla.components.service.fxa.SyncEngine
import mozilla.components.service.fxa.manager.FxaAccountManager
import mozilla.components.service.fxa.manager.SCOPE_SYNC
import mozilla.components.service.fxa.sync.GlobalSyncableStoreProvider
import mozilla.components.service.sync.logins.SyncableLoginsStore
import mozilla.components.support.base.log.logger.Logger
import org.mozilla.fenix.Experiments
import org.mozilla.fenix.R
@ -53,7 +55,9 @@ class BackgroundServices(
private val context: Context,
crashReporter: CrashReporter,
historyStorage: PlacesHistoryStorage,
bookmarkStorage: PlacesBookmarksStorage
bookmarkStorage: PlacesBookmarksStorage,
passwordsStorage: SyncableLoginsStore,
secureAbove22Preferences: SecureAbove22Preferences
) {
// // A malformed string is causing crashes.
// This will be removed when the string is fixed. See #5552
@ -87,8 +91,9 @@ class BackgroundServices(
val syncConfig = if (context.isInExperiment(Experiments.asFeatureSyncDisabled)) {
null
} else {
// TODO Add Passwords Here Waiting On https://github.com/mozilla-mobile/android-components/issues/4741
SyncConfig(setOf(SyncEngine.History, SyncEngine.Bookmarks), syncPeriodInMinutes = 240L) // four hours
SyncConfig(
setOf(SyncEngine.History, SyncEngine.Bookmarks, SyncEngine.Passwords),
syncPeriodInMinutes = 240L) // four hours
}
private val pushService by lazy { FirebasePush() }
@ -96,11 +101,11 @@ class BackgroundServices(
val push by lazy { makePushConfig()?.let { makePush(it) } }
init {
// Make the "history", "bookmark", and "logins" stores accessible to workers spawned by the sync manager.
// Make the "history", "bookmark", and "passwords" stores accessible to workers spawned by the sync manager.
GlobalSyncableStoreProvider.configureStore(SyncEngine.History to historyStorage)
GlobalSyncableStoreProvider.configureStore(SyncEngine.Bookmarks to bookmarkStorage)
// TODO Add Passwords Here Waiting On https://github.com/mozilla-mobile/android-components/issues/4741
// GlobalSyncableStoreProvider.configureStore(SyncEngine.Passwords to loginsStorage)
GlobalSyncableStoreProvider.configureStore(SyncEngine.Passwords to passwordsStorage)
GlobalSyncableStoreProvider.configureKeyStorage(secureAbove22Preferences)
}
private val deviceEventObserver = object : DeviceEventsObserver {
@ -170,7 +175,11 @@ class BackgroundServices(
).also { accountManager ->
// TODO this needs to change once we have a SyncManager
context.settings().fxaHasSyncedItems = syncConfig?.supportedEngines?.isNotEmpty() ?: false
accountManager.registerForDeviceEvents(deviceEventObserver, ProcessLifecycleOwner.get(), false)
accountManager.registerForDeviceEvents(
deviceEventObserver,
ProcessLifecycleOwner.get(),
false
)
// Register a telemetry account observer to keep track of FxA auth metrics.
accountManager.register(telemetryAccountObserver)

View File

@ -15,7 +15,14 @@ import org.mozilla.fenix.utils.ClipboardHandler
@Mockable
class Components(private val context: Context) {
val backgroundServices by lazy {
BackgroundServices(context, analytics.crashReporter, core.historyStorage, core.bookmarksStorage)
BackgroundServices(
context,
analytics.crashReporter,
core.historyStorage,
core.bookmarksStorage,
core.passwordsStorage,
core.secureAbove22Preferences
)
}
val services by lazy { Services(context, backgroundServices.accountManager) }
val core by lazy { Core(context) }

View File

@ -7,6 +7,7 @@ package org.mozilla.fenix.components
import GeckoProvider
import android.content.Context
import android.content.res.Configuration
import io.sentry.Sentry
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
@ -34,6 +35,8 @@ import mozilla.components.feature.pwa.ManifestStorage
import mozilla.components.feature.pwa.WebAppShortcutManager
import mozilla.components.feature.session.HistoryDelegate
import mozilla.components.feature.webcompat.WebCompatFeature
import mozilla.components.lib.dataprotect.SecureAbove22Preferences
import mozilla.components.lib.dataprotect.generateEncryptionKey
import mozilla.components.service.sync.logins.AsyncLoginsStorageAdapter
import mozilla.components.service.sync.logins.SyncableLoginsStore
import org.mozilla.fenix.AppRequestInterceptor
@ -171,7 +174,7 @@ class Core(private val context: Context) {
val webAppManifestStorage by lazy { ManifestStorage(context) }
val loginsStorage by lazy {
val passwordsStorage by lazy {
SyncableLoginsStore(
AsyncLoginsStorageAdapter.forDatabase(
File(
@ -180,10 +183,29 @@ class Core(private val context: Context) {
).canonicalPath
)
) {
CompletableDeferred("very-insecure-key")
CompletableDeferred(passwordsEncryptionKey)
}
}
/**
* Shared Preferences that encrypt/decrypt using Android KeyStore and lib-dataprotect for 23+
* otherwise simply stored
*/
val secureAbove22Preferences by lazy {
SecureAbove22Preferences(context, KEY_STORAGE_NAME)
}
private val passwordsEncryptionKey =
secureAbove22Preferences.getString(PASSWORDS_KEY)
?: generateEncryptionKey(KEY_STRENGTH).also {
if (context.settings().passwordsEncryptionKeyGenerated) {
// We already had previously generated an encryption key, but we have lost it
Sentry.capture("Passwords encryption key for passwords storage was lost and we generated a new one")
}
context.settings().recordPasswordsEncryptionKeyGenerated()
secureAbove22Preferences.putString(PASSWORDS_KEY, it)
}
/**
* Constructs a [TrackingProtectionPolicy] based on current preferences.
*
@ -223,4 +245,10 @@ class Core(private val context: Context) {
else -> PreferredColorScheme.Light
}
}
companion object {
private const val KEY_STRENGTH = 256
private const val KEY_STORAGE_NAME = "core_prefs"
private const val PASSWORDS_KEY = "passwords"
}
}

View File

@ -80,7 +80,7 @@ class SavedLoginsFragment : Fragment() {
private fun loadAndMapLogins() {
lifecycleScope.launch(IO) {
val syncedLogins = async {
context!!.components.core.loginsStorage.withUnlocked {
context!!.components.core.passwordsStorage.withUnlocked {
it.list().await().map { item ->
SavedLoginsItem(
item.hostname,

View File

@ -213,6 +213,16 @@ class Settings private constructor(
fun shouldDeleteAnyDataOnQuit() =
DeleteBrowsingDataOnQuitType.values().any { getDeleteDataOnQuit(it) }
val passwordsEncryptionKeyGenerated by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_encryption_key_generated),
false
)
fun recordPasswordsEncryptionKeyGenerated() = preferences.edit().putBoolean(
appContext.getPreferenceKey(R.string.pref_key_encryption_key_generated),
true
).apply()
val themeSettingString: String
get() = when {
shouldFollowDeviceTheme -> appContext.getString(R.string.preference_follow_device_theme)

View File

@ -117,4 +117,6 @@
<string name="pref_key_testing_stage" translatable="false">pref_key_testing_stage</string>
<string name="pref_key_total_uri" translatable="false">pref_key_total_uri</string>
<string name="pref_key_encryption_key_generated" translatable="false">pref_key_encryption_key_generated</string>
</resources>

View File

@ -50,15 +50,24 @@ class BackgroundServicesTest {
val context = mockk<Context>(relaxed = true)
every { context.isInExperiment(eq(Experiments.asFeatureWebChannelsDisabled)) } returns false
assertEquals("urn:ietf:wg:oauth:2.0:oob:oauth-redirect-webchannel", FxaServer.redirectUrl(context))
assertEquals(
"urn:ietf:wg:oauth:2.0:oob:oauth-redirect-webchannel",
FxaServer.redirectUrl(context)
)
every { context.isInExperiment(eq(Experiments.asFeatureWebChannelsDisabled)) } returns true
assertEquals("https://accounts.firefox.com/oauth/success/a2270f727f45f648", FxaServer.redirectUrl(context))
assertEquals(
"https://accounts.firefox.com/oauth/success/a2270f727f45f648",
FxaServer.redirectUrl(context)
)
every { context.isInExperiment(eq(Experiments.asFeatureSyncDisabled)) } returns false
var backgroundServices = TestableBackgroundServices(context)
assertEquals(
SyncConfig(setOf(SyncEngine.History, SyncEngine.Bookmarks), syncPeriodInMinutes = 240L),
SyncConfig(
setOf(SyncEngine.History, SyncEngine.Bookmarks, SyncEngine.Passwords),
syncPeriodInMinutes = 240L
),
backgroundServices.syncConfig
)

View File

@ -150,6 +150,7 @@ object Deps {
const val mozilla_lib_crash = "org.mozilla.components:lib-crash:${Versions.mozilla_android_components}"
const val mozilla_lib_push_firebase = "org.mozilla.components:lib-push-firebase:${Versions.mozilla_android_components}"
const val mozilla_lib_dataprotect = "org.mozilla.components:lib-dataprotect:${Versions.mozilla_android_components}"
const val mozilla_ui_publicsuffixlist = "org.mozilla.components:lib-publicsuffixlist:${Versions.mozilla_android_components}"