Skip to content

Commit 08eb472

Browse files
authored
Address comments in #1924 (#1926)
Task/Issue URL: N/A Description Addressing all comments mentioned in #1924 Steps to test this PR N/A UI changes N/A
1 parent 5ceaa7e commit 08eb472

File tree

10 files changed

+142
-45
lines changed

10 files changed

+142
-45
lines changed

secure-storage/secure-storage-api/src/main/java/com/duckduckgo/securestorage/api/SecureStorage.kt

Lines changed: 74 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,31 +18,101 @@ package com.duckduckgo.securestorage.api
1818

1919
import kotlinx.coroutines.flow.Flow
2020

21+
/** Public API for the secure storage feature */
2122
interface SecureStorage {
23+
24+
/**
25+
* This method can be used to check if the secure storage has been instantiated properly.
26+
*
27+
* @return `true` if all dependencies of SecureStorage have been instantiated properly otherwise `false
28+
*/
2229
fun canAccessSecureStorage(): Boolean
2330

31+
/**
32+
* This method authenticates the user without having to pass a user password. This method is relevant
33+
* while the user password generation is still done programmatically within the secure storage. Once we support
34+
* password creation, this method will be deprecated.
35+
*
36+
* @return Result.Success if no issues with authentication is encountered otherwise Result.Error
37+
*/
2438
suspend fun authenticateUser(): Result
2539

40+
/**
41+
* This method authenticates the user using a provided [password].
42+
*
43+
* @return Result.Success if no issues with authentication is encountered otherwise Result.Error
44+
*/
2645
suspend fun authenticateUser(password: String): Result
2746

47+
/**
48+
* This method adds a raw plaintext [WebsiteLoginCredentials] into the [SecureStorage]. This requires the user to be authenticated
49+
* first see [authenticateUser].
50+
*
51+
* @throws UserNotAuthenticatedException if the user is not authenticated when calling this method
52+
*/
2853
@Throws(UserNotAuthenticatedException::class)
2954
suspend fun addWebsiteLoginCredential(websiteLoginCredentials: WebsiteLoginCredentials)
3055

31-
suspend fun getWebsiteLoginDetailsForDomain(domain: String): Flow<List<WebsiteLoginDetails>>
56+
/**
57+
* This method returns all [WebsiteLoginDetails] with the [domain] stored in the [SecureStorage].
58+
* This does not require the user to be authenticated since [WebsiteLoginDetails] doesn't contain any L2 data.
59+
*
60+
* @return Flow<List<WebsiteLoginDetails>> a flow emitting a List of plain text WebsiteLoginDetails stored in SecureStorage.
61+
*/
62+
suspend fun websiteLoginDetailsForDomain(domain: String): Flow<List<WebsiteLoginDetails>>
3263

33-
suspend fun getAllWebsiteLoginDetails(): Flow<List<WebsiteLoginDetails>>
64+
/**
65+
* This method returns all [WebsiteLoginDetails] stored in the [SecureStorage].
66+
* This does not require the user to be authenticated since [WebsiteLoginDetails] doesn't contain any L2 data.
67+
*
68+
* @return Flow<List<WebsiteLoginDetails>> a flow containing a List of plain text WebsiteLoginDetails stored in SecureStorage.
69+
*/
70+
suspend fun websiteLoginDetails(): Flow<List<WebsiteLoginDetails>>
3471

72+
/**
73+
* This method returns the [WebsiteLoginCredentials] with the [id] stored in the [SecureStorage].
74+
* This requires the user to be authenticated.
75+
*
76+
* @return [WebsiteLoginCredentials] containing the plaintext password
77+
* @throws [UserNotAuthenticatedException] if the user is not authenticated when calling this method
78+
*/
3579
@Throws(UserNotAuthenticatedException::class)
3680
suspend fun getWebsiteLoginCredentials(id: Int): WebsiteLoginCredentials
3781

82+
/**
83+
* This method returns the [WebsiteLoginCredentials] with the [domain] stored in the [SecureStorage].
84+
* This requires the user to be authenticated.
85+
*
86+
* @return Flow<List<WebsiteLoginCredentials>> a flow emitting a List of plain text WebsiteLoginCredentials stored in SecureStorage
87+
* containing the plaintext password
88+
* @throws [UserNotAuthenticatedException] if the user is not authenticated when calling this method
89+
*/
3890
@Throws(UserNotAuthenticatedException::class)
39-
suspend fun getWebsiteLoginCredentialsForDomain(domain: String): Flow<List<WebsiteLoginCredentials>>
91+
suspend fun websiteLoginCredentialsForDomain(domain: String): Flow<List<WebsiteLoginCredentials>>
4092

93+
/**
94+
* This method returns all the [WebsiteLoginCredentials] stored in the [SecureStorage].
95+
* This requires the user to be authenticated.
96+
*
97+
* @return Flow<List<WebsiteLoginCredentials>> a flow emitting a List of plain text WebsiteLoginCredentials stored in SecureStorage
98+
* containing the plaintext password
99+
* @throws [UserNotAuthenticatedException] if the user is not authenticated when calling this method
100+
*/
41101
@Throws(UserNotAuthenticatedException::class)
42-
suspend fun getAllWebsiteLoginCredentials(): Flow<List<WebsiteLoginCredentials>>
102+
suspend fun websiteLoginCredentials(): Flow<List<WebsiteLoginCredentials>>
43103

104+
/**
105+
* This method updates an existing [WebsiteLoginCredentials] in the [SecureStorage].
106+
* This requires the user to be authenticated.
107+
*
108+
* @throws [UserNotAuthenticatedException] if the user is not authenticated when calling this method
109+
*/
44110
@Throws(UserNotAuthenticatedException::class)
45111
suspend fun updateWebsiteLoginCredentials(websiteLoginCredentials: WebsiteLoginCredentials)
46112

113+
/**
114+
* This method removes an existing [WebsiteLoginCredentials] associated with an [id] from the [SecureStorage].
115+
* This does not require the user to be authenticated.
116+
*/
47117
suspend fun deleteWebsiteLoginCredentials(id: Int)
48118
}

secure-storage/secure-storage-api/src/main/java/com/duckduckgo/securestorage/api/SecureStorageModels.kt

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,42 @@
1616

1717
package com.duckduckgo.securestorage.api
1818

19+
/**
20+
* Public sealed class representing the result of secure storage user authentication.
21+
*/
1922
sealed class Result {
23+
/**
24+
* Data class representing a success in secure storage authentication.
25+
* [expiryInMillis] is the system time in millis to when the authentication is set to expire.
26+
*/
2027
data class Success(val expiryInMillis: Long) : Result()
28+
29+
/**
30+
* Data class representing any error in secure storage authentication.
31+
* [reason] provides more details on why the error occurred.
32+
*/
2133
data class Error(val reason: String) : Result()
2234
}
2335

36+
/**
37+
* Public data class that wraps all data related to a website login.
38+
*
39+
* [details] contains all l1 encrypted attributes
40+
* [password] plain text password. All succeeding l2 and above attributes should be added here directly.
41+
*/
2442
data class WebsiteLoginCredentials(
2543
val details: WebsiteLoginDetails,
2644
val password: String?
2745
)
2846

47+
/**
48+
* Public data class that wraps all data that should only be covered with l1 encryption.
49+
* All attributes is in plain text. Also, all should not require user authentication to be decrypted.
50+
*
51+
* [domain] url/name associated to a website login.
52+
* [username] used to populate the username fields in a login
53+
* [id] database id associated to the website login
54+
*/
2955
data class WebsiteLoginDetails(
3056
val domain: String?,
3157
val username: String?,

secure-storage/secure-storage-api/src/main/java/com/duckduckgo/securestorage/api/UserNotAuthenticatedException.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,8 @@ package com.duckduckgo.securestorage.api
1818

1919
import java.lang.RuntimeException
2020

21+
/**
22+
* Public data class exception that is thrown when a method that requires user authentication is accessed by a
23+
* non authenticated user.
24+
*/
2125
data class UserNotAuthenticatedException(override val message: String?) : RuntimeException()

secure-storage/secure-storage-impl/build.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,13 @@ dependencies {
2626
implementation project(path: ':appbuildconfig-api')
2727
implementation project(path: ':di')
2828
implementation project(path: ':secure-storage-api')
29-
api project(path: ':secure-storage-store')
29+
implementation project(path: ':secure-storage-store')
3030

3131
implementation AndroidX.room.ktx
3232
implementation Google.dagger
3333
implementation KotlinX.coroutines.core
3434

35-
api "net.zetetic:android-database-sqlcipher:_"
35+
implementation "net.zetetic:android-database-sqlcipher:_"
3636
}
3737

3838
android {

secure-storage/secure-storage-impl/src/main/java/com/duckduckgo/securestorage/impl/RealSecureStorage.kt

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,6 @@ import javax.inject.Inject
3232
class RealSecureStorage @Inject constructor(
3333
private val secureStorageRepository: SecureStorageRepository
3434
) : SecureStorage {
35-
companion object {
36-
private const val DEFAULT_EXPIRY_IN_MILLIS = 30 * 60 * 1000
37-
}
3835

3936
override fun canAccessSecureStorage(): Boolean = true
4037

@@ -53,15 +50,15 @@ class RealSecureStorage @Inject constructor(
5350
secureStorageRepository.addWebsiteLoginCredential(websiteLoginCredentials.toDataEntity())
5451
}
5552

56-
override suspend fun getWebsiteLoginDetailsForDomain(domain: String): Flow<List<WebsiteLoginDetails>> =
57-
secureStorageRepository.getWebsiteLoginCredentialsForDomain(domain).map { list ->
53+
override suspend fun websiteLoginDetailsForDomain(domain: String): Flow<List<WebsiteLoginDetails>> =
54+
secureStorageRepository.websiteLoginCredentialsForDomain(domain).map { list ->
5855
list.map {
5956
it.toDetails()
6057
}
6158
}
6259

63-
override suspend fun getAllWebsiteLoginDetails(): Flow<List<WebsiteLoginDetails>> =
64-
secureStorageRepository.getAllWebsiteLoginCredentials().map { list ->
60+
override suspend fun websiteLoginDetails(): Flow<List<WebsiteLoginDetails>> =
61+
secureStorageRepository.websiteLoginCredentials().map { list ->
6562
list.map {
6663
it.toDetails()
6764
}
@@ -70,17 +67,17 @@ class RealSecureStorage @Inject constructor(
7067
override suspend fun getWebsiteLoginCredentials(id: Int): WebsiteLoginCredentials =
7168
secureStorageRepository.getWebsiteLoginCredentialsForId(id).toCredentials()
7269

73-
override suspend fun getWebsiteLoginCredentialsForDomain(domain: String): Flow<List<WebsiteLoginCredentials>> =
70+
override suspend fun websiteLoginCredentialsForDomain(domain: String): Flow<List<WebsiteLoginCredentials>> =
7471
// TODO (karl) Integrate L2 encryption
75-
secureStorageRepository.getWebsiteLoginCredentialsForDomain(domain).map { list ->
72+
secureStorageRepository.websiteLoginCredentialsForDomain(domain).map { list ->
7673
list.map {
7774
it.toCredentials()
7875
}
7976
}
8077

81-
override suspend fun getAllWebsiteLoginCredentials(): Flow<List<WebsiteLoginCredentials>> =
78+
override suspend fun websiteLoginCredentials(): Flow<List<WebsiteLoginCredentials>> =
8279
// TODO (karl) Integrate L2 encryption
83-
secureStorageRepository.getAllWebsiteLoginCredentials().map { list ->
80+
secureStorageRepository.websiteLoginCredentials().map { list ->
8481
list.map {
8582
it.toCredentials()
8683
}
@@ -113,4 +110,8 @@ class RealSecureStorage @Inject constructor(
113110
username = username,
114111
id = id
115112
)
113+
114+
companion object {
115+
private const val DEFAULT_EXPIRY_IN_MILLIS = 30 * 60 * 1000
116+
}
116117
}

secure-storage/secure-storage-impl/src/main/java/com/duckduckgo/securestorage/impl/di/SecureStorageModule.kt

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -41,22 +41,17 @@ object SecureStorageModule {
4141
fun providesSecureStorageKeyStore(context: Context): SecureStorageKeyStore =
4242
RealSecureStorageKeyStore(context)
4343

44-
@Provides
45-
fun providesSupportFactory(keyManager: SecureStorageKeyManager): SupportFactory {
46-
return SupportFactory(keyManager.l1Key)
47-
}
48-
4944
@Provides
5045
@SingleInstanceIn(AppScope::class)
5146
fun providesSecureStorageDatabase(
5247
context: Context,
53-
factory: SupportFactory
48+
keyManager: SecureStorageKeyManager
5449
): SecureStorageDatabase {
5550
return Room.databaseBuilder(
5651
context,
5752
SecureStorageDatabase::class.java,
5853
"secure_storage_database_encrypted.db"
59-
).openHelperFactory(factory)
54+
).openHelperFactory(SupportFactory(keyManager.l1Key))
6055
.addMigrations(*ALL_MIGRATIONS)
6156
.enableMultiInstanceInvalidation()
6257
.fallbackToDestructiveMigration()

secure-storage/secure-storage-store/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ dependencies {
3636
implementation AndroidX.core.ktx
3737
implementation AndroidX.room.ktx
3838
implementation AndroidX.security.crypto
39+
implementation Square.okio
3940

4041
kapt AndroidX.room.compiler
4142
}

secure-storage/secure-storage-store/src/main/java/com/duckduckgo/securestorage/store/SecureStorageKeyStore.kt

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,11 @@ package com.duckduckgo.securestorage.store
1818

1919
import android.content.Context
2020
import android.content.SharedPreferences
21-
import android.util.Base64
2221
import androidx.core.content.edit
2322
import androidx.security.crypto.EncryptedSharedPreferences
2423
import androidx.security.crypto.MasterKey
24+
import okio.ByteString.Companion.decodeBase64
25+
import okio.ByteString.Companion.toByteString
2526
import java.lang.Exception
2627

2728
/**
@@ -54,12 +55,6 @@ interface SecureStorageKeyStore {
5455
class RealSecureStorageKeyStore constructor(
5556
private val context: Context
5657
) : SecureStorageKeyStore {
57-
companion object {
58-
const val FILENAME = "com.duckduckgo.securestorage.store"
59-
const val KEY_GENERATED_PASSWORD = "KEY_GENERATED_PASSWORD"
60-
const val KEY_L1KEY = "KEY_L1KEY"
61-
const val KEY_ENCRYPTED_L2KEY = "KEY_ENCRYPTED_L2KEY"
62-
}
6358

6459
private val encryptedPreferences: SharedPreferences? by lazy { encryptedPreferences() }
6560

@@ -102,19 +97,24 @@ class RealSecureStorageKeyStore constructor(
10297
key: String,
10398
value: ByteArray?
10499
) {
105-
Base64.encodeToString(value, Base64.DEFAULT).also {
106-
encryptedPreferences?.edit(commit = true) {
107-
if (value == null) remove(key)
108-
else putString(key, it)
109-
}
100+
encryptedPreferences?.edit(commit = true) {
101+
if (value == null) remove(key)
102+
else putString(key, value.toByteString().base64())
110103
}
111104
}
112105

113106
private fun getValue(key: String): ByteArray? {
114107
return encryptedPreferences?.getString(key, null)?.run {
115-
Base64.decode(this, Base64.DEFAULT)
108+
this.decodeBase64()?.toByteArray()
116109
}
117110
}
118111

119112
override fun canUseEncryption(): Boolean = encryptedPreferences != null
113+
114+
companion object {
115+
const val FILENAME = "com.duckduckgo.securestorage.store"
116+
const val KEY_GENERATED_PASSWORD = "KEY_GENERATED_PASSWORD"
117+
const val KEY_L1KEY = "KEY_L1KEY"
118+
const val KEY_ENCRYPTED_L2KEY = "KEY_ENCRYPTED_L2KEY"
119+
}
120120
}

secure-storage/secure-storage-store/src/main/java/com/duckduckgo/securestorage/store/SecureStorageRepository.kt

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,11 @@ interface SecureStorageRepository {
2828

2929
suspend fun getWebsiteLoginCredentials(id: Int): WebsiteLoginCredentialsEntity
3030

31-
suspend fun getWebsiteLoginCredentialsForDomain(domain: String): Flow<List<WebsiteLoginCredentialsEntity>>
31+
suspend fun websiteLoginCredentialsForDomain(domain: String): Flow<List<WebsiteLoginCredentialsEntity>>
3232

3333
suspend fun getWebsiteLoginCredentialsForId(id: Int): WebsiteLoginCredentialsEntity
3434

35-
suspend fun getAllWebsiteLoginCredentials(): Flow<List<WebsiteLoginCredentialsEntity>>
35+
suspend fun websiteLoginCredentials(): Flow<List<WebsiteLoginCredentialsEntity>>
3636

3737
suspend fun updateWebsiteLoginCredentials(websiteLoginCredentials: WebsiteLoginCredentialsEntity)
3838

@@ -49,11 +49,11 @@ class RealSecureStorageRepository constructor(
4949
override suspend fun getWebsiteLoginCredentials(id: Int): WebsiteLoginCredentialsEntity =
5050
websiteLoginCredentialsDao.getWebsiteLoginCredentialsById(id)
5151

52-
override suspend fun getWebsiteLoginCredentialsForDomain(domain: String): Flow<List<WebsiteLoginCredentialsEntity>> =
53-
websiteLoginCredentialsDao.getWebsiteLoginCredentialsByDomain(domain)
52+
override suspend fun websiteLoginCredentialsForDomain(domain: String): Flow<List<WebsiteLoginCredentialsEntity>> =
53+
websiteLoginCredentialsDao.websiteLoginCredentialsByDomain(domain)
5454

55-
override suspend fun getAllWebsiteLoginCredentials(): Flow<List<WebsiteLoginCredentialsEntity>> =
56-
websiteLoginCredentialsDao.getWebsiteLoginCredentials()
55+
override suspend fun websiteLoginCredentials(): Flow<List<WebsiteLoginCredentialsEntity>> =
56+
websiteLoginCredentialsDao.websiteLoginCredentials()
5757

5858
override suspend fun getWebsiteLoginCredentialsForId(id: Int): WebsiteLoginCredentialsEntity =
5959
websiteLoginCredentialsDao.getWebsiteLoginCredentialsById(id)

secure-storage/secure-storage-store/src/main/java/com/duckduckgo/securestorage/store/db/WebsiteLoginCredentialsDao.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,10 @@ interface WebsiteLoginCredentialsDao {
3434
fun update(loginCredentials: WebsiteLoginCredentialsEntity)
3535

3636
@Query("select * from website_login_credentials")
37-
fun getWebsiteLoginCredentials(): Flow<List<WebsiteLoginCredentialsEntity>>
37+
fun websiteLoginCredentials(): Flow<List<WebsiteLoginCredentialsEntity>>
3838

3939
@Query("select * from website_login_credentials where domain = :domain")
40-
fun getWebsiteLoginCredentialsByDomain(domain: String): Flow<List<WebsiteLoginCredentialsEntity>>
40+
fun websiteLoginCredentialsByDomain(domain: String): Flow<List<WebsiteLoginCredentialsEntity>>
4141

4242
@Query("select * from website_login_credentials where id = :id")
4343
fun getWebsiteLoginCredentialsById(id: Int): WebsiteLoginCredentialsEntity

0 commit comments

Comments
 (0)