KeyStoreCrypto fallback if Android KeyStore is not properly working.

This commit is contained in:
Neil Marietta 2021-06-14 11:54:37 +02:00
parent 6136a429f7
commit ae9280d50b
77 changed files with 414 additions and 180 deletions

View File

@ -1,3 +1,45 @@
## Crypto Version [1.15.2]
Sep 20, 2021
### Dependencies
- Auth 1.15.2.
- Account 1.15.2.
- Crypto 1.15.2.
- Human-Verification 1.15.2.
- Key 1.15.2.
- Network 1.15.2.
- Util Kotlin 1.15.2.
- User 1.15.2.
- User-Settings 1.15.2.
### Api Changes
- Logger is no more injected. Instead Core use a static ```CoreLogger```. You now have to set the Logger instance, on Application create:
```
override fun onCreate() {
super.onCreate()
CoreLogger.set(CoreExampleLogger())
```
- There is also a new KeyStoreCrypto LogTag object you must be aware:
```
object LogTag {
/** Tag for KeyStore initialization check failure. */
const val KEYSTORE_INIT = "core.crypto.common.keystore.init"
/** Tag for KeyStore encrypt failure. */
const val KEYSTORE_ENCRYPT = "core.crypto.common.keystore.encrypt"
/** Tag for KeyStore decrypt failure. */
const val KEYSTORE_DECRYPT = "core.crypto.common.keystore.decrypt"
}
```
### New Features
- KeyStoreCrypto fallback if Android KeyStore is not properly working.
## Presentation [1.15.1]
Sep 21, 2021

View File

@ -24,7 +24,7 @@ plugins {
kotlin("android")
}
libVersion = Version(1, 15, 0)
libVersion = Version(1, 15, 2)
android()

View File

@ -23,7 +23,7 @@ plugins {
kotlin("android")
}
libVersion = Version(1, 15, 0)
libVersion = Version(1, 15, 2)
android()

View File

@ -23,7 +23,7 @@ import androidx.room.ForeignKey
import androidx.room.Index
import me.proton.core.crypto.common.keystore.EncryptedString
import me.proton.core.crypto.common.keystore.KeyStoreCrypto
import me.proton.core.crypto.common.keystore.decryptWith
import me.proton.core.crypto.common.keystore.decryptOrElse
import me.proton.core.data.room.db.CommonConverters
import me.proton.core.domain.entity.Product
import me.proton.core.domain.entity.UserId
@ -55,8 +55,10 @@ data class SessionEntity(
) {
fun toSession(keyStoreCrypto: KeyStoreCrypto): Session = Session(
sessionId = sessionId,
accessToken = accessToken.decryptWith(keyStoreCrypto),
refreshToken = refreshToken.decryptWith(keyStoreCrypto),
// Fall back to invalid tokens to force delete session on decryption failure.
// See RefreshTokenHandler and sessionListener.onSessionForceLogout.
accessToken = requireNotNull(accessToken.decryptOrElse(keyStoreCrypto) { "invalid" }),
refreshToken = requireNotNull(refreshToken.decryptOrElse(keyStoreCrypto) { "invalid" }),
scopes = CommonConverters.fromStringToListOfString(scopes).orEmpty()
)
}

View File

@ -19,8 +19,8 @@
package me.proton.core.account.data.extension
import me.proton.core.account.data.entity.SessionEntity
import me.proton.core.crypto.common.keystore.encrypt
import me.proton.core.crypto.common.keystore.KeyStoreCrypto
import me.proton.core.crypto.common.keystore.encryptWith
import me.proton.core.data.room.db.CommonConverters
import me.proton.core.domain.entity.Product
import me.proton.core.domain.entity.UserId
@ -33,8 +33,8 @@ fun Session.toSessionEntity(
): SessionEntity = SessionEntity(
userId = userId,
sessionId = sessionId,
accessToken = accessToken.encryptWith(keyStoreCrypto),
refreshToken = refreshToken.encryptWith(keyStoreCrypto),
accessToken = accessToken.encrypt(keyStoreCrypto),
refreshToken = refreshToken.encrypt(keyStoreCrypto),
scopes = CommonConverters.fromListOfStringToString(scopes).orEmpty(),
product = product
)

View File

@ -38,7 +38,7 @@ import me.proton.core.account.domain.entity.SessionDetails
import me.proton.core.account.domain.entity.SessionState
import me.proton.core.account.domain.repository.AccountRepository
import me.proton.core.crypto.common.keystore.KeyStoreCrypto
import me.proton.core.crypto.common.keystore.encryptWith
import me.proton.core.crypto.common.keystore.encrypt
import me.proton.core.data.room.db.CommonConverters
import me.proton.core.domain.entity.Product
import me.proton.core.domain.entity.UserId
@ -215,8 +215,8 @@ class AccountRepositoryImpl(
override suspend fun updateSessionToken(sessionId: SessionId, accessToken: String, refreshToken: String) =
sessionDao.updateToken(
sessionId,
accessToken.encryptWith(keyStoreCrypto),
refreshToken.encryptWith(keyStoreCrypto)
accessToken.encrypt(keyStoreCrypto),
refreshToken.encrypt(keyStoreCrypto)
)
override fun getPrimaryUserId(): Flow<UserId?> =

View File

@ -67,6 +67,7 @@ class AccountRepositoryImplTest {
private lateinit var metadataDao: AccountMetadataDao
private val simpleCrypto = object : KeyStoreCrypto {
override fun isUsingKeyStore(): Boolean = false
override fun encrypt(value: String): EncryptedString = value
override fun encrypt(value: PlainByteArray): EncryptedByteArray = EncryptedByteArray(value.array)
override fun decrypt(value: EncryptedString): String = value

View File

@ -23,7 +23,7 @@ plugins {
kotlin("jvm")
}
libVersion = Version(1, 15, 0)
libVersion = Version(1, 15, 2)
dependencies {

View File

@ -23,7 +23,7 @@ plugins {
kotlin("android")
}
libVersion = Version(1, 15, 0)
libVersion = Version(1, 15, 2)
android()

View File

@ -24,7 +24,7 @@ plugins {
kotlin("plugin.serialization")
}
libVersion = Version(1, 15, 0)
libVersion = Version(1, 15, 2)
android()

View File

@ -24,7 +24,7 @@ plugins {
kotlin("jvm")
}
libVersion = Version(1, 15, 0)
libVersion = Version(1, 15, 2)
dependencies {

View File

@ -24,7 +24,7 @@ import me.proton.core.auth.domain.entity.SessionInfo
import me.proton.core.auth.domain.repository.AuthRepository
import me.proton.core.crypto.common.keystore.EncryptedString
import me.proton.core.crypto.common.keystore.KeyStoreCrypto
import me.proton.core.crypto.common.keystore.decryptWith
import me.proton.core.crypto.common.keystore.decrypt
import me.proton.core.crypto.common.keystore.use
import me.proton.core.crypto.common.srp.SrpCrypto
import me.proton.core.crypto.common.srp.SrpProofs
@ -47,7 +47,7 @@ class PerformLogin @Inject constructor(
username = username,
clientSecret = clientSecret
)
password.decryptWith(keyStoreCrypto).toByteArray().use {
password.decrypt(keyStoreCrypto).toByteArray().use {
val clientProofs: SrpProofs = srpCrypto.generateSrpProofs(
username = username,
password = it.array,

View File

@ -22,7 +22,7 @@ import me.proton.core.account.domain.entity.AccountType
import me.proton.core.auth.domain.repository.AuthRepository
import me.proton.core.crypto.common.keystore.EncryptedString
import me.proton.core.crypto.common.keystore.KeyStoreCrypto
import me.proton.core.crypto.common.keystore.decryptWith
import me.proton.core.crypto.common.keystore.decrypt
import me.proton.core.crypto.common.keystore.use
import me.proton.core.crypto.common.srp.SrpCrypto
import me.proton.core.domain.entity.UserId
@ -74,7 +74,7 @@ class SetupPrimaryKeys @Inject constructor(
}
val modulus = authRepository.randomModulus()
password.decryptWith(keyStoreCrypto).toByteArray().use { decryptedPassword ->
password.decrypt(keyStoreCrypto).toByteArray().use { decryptedPassword ->
val auth = srpCrypto.calculatePasswordVerifier(
username = email,
password = decryptedPassword.array,

View File

@ -20,7 +20,7 @@ package me.proton.core.auth.domain.usecase
import me.proton.core.crypto.common.keystore.EncryptedString
import me.proton.core.crypto.common.keystore.KeyStoreCrypto
import me.proton.core.crypto.common.keystore.decryptWith
import me.proton.core.crypto.common.keystore.decrypt
import me.proton.core.crypto.common.keystore.use
import me.proton.core.domain.entity.UserId
import me.proton.core.user.domain.UserManager
@ -42,7 +42,7 @@ class UnlockUserPrimaryKey @Inject constructor(
suspend operator fun invoke(
userId: UserId,
password: EncryptedString
): UserManager.UnlockResult = password.decryptWith(keyStoreCrypto).toByteArray().use {
): UserManager.UnlockResult = password.decrypt(keyStoreCrypto).toByteArray().use {
userManager.unlockWithPassword(userId, it)
}
}

View File

@ -21,7 +21,7 @@ package me.proton.core.auth.domain.usecase.signup
import me.proton.core.auth.domain.repository.AuthRepository
import me.proton.core.crypto.common.keystore.EncryptedString
import me.proton.core.crypto.common.keystore.KeyStoreCrypto
import me.proton.core.crypto.common.keystore.decryptWith
import me.proton.core.crypto.common.keystore.decrypt
import me.proton.core.crypto.common.keystore.use
import me.proton.core.crypto.common.srp.SrpCrypto
import me.proton.core.user.domain.entity.CreateUserType
@ -44,7 +44,7 @@ class PerformCreateExternalEmailUser @Inject constructor(
require(email.isNotBlank()) { "Email must not be empty." }
val modulus = authRepository.randomModulus()
password.decryptWith(keyStoreCrypto).toByteArray().use { decryptedPassword ->
password.decrypt(keyStoreCrypto).toByteArray().use { decryptedPassword ->
val auth = srpCrypto.calculatePasswordVerifier(
username = email,
password = decryptedPassword.array,

View File

@ -21,7 +21,7 @@ package me.proton.core.auth.domain.usecase.signup
import me.proton.core.auth.domain.repository.AuthRepository
import me.proton.core.crypto.common.keystore.EncryptedString
import me.proton.core.crypto.common.keystore.KeyStoreCrypto
import me.proton.core.crypto.common.keystore.decryptWith
import me.proton.core.crypto.common.keystore.decrypt
import me.proton.core.crypto.common.keystore.use
import me.proton.core.crypto.common.srp.SrpCrypto
import me.proton.core.user.domain.entity.CreateUserType
@ -51,7 +51,7 @@ class PerformCreateUser @Inject constructor(
) { "Recovery Email and Phone could not be set together" }
val modulus = authRepository.randomModulus()
password.decryptWith(keyStoreCrypto).toByteArray().use { decryptedPassword ->
password.decrypt(keyStoreCrypto).toByteArray().use { decryptedPassword ->
val auth = srpCrypto.calculatePasswordVerifier(
username = username,
password = decryptedPassword.array,

View File

@ -28,7 +28,7 @@ plugins {
id("dagger.hilt.android.plugin")
}
libVersion = Version(1, 15, 0)
libVersion = Version(1, 15, 2)
android(useDataBinding = true)

View File

@ -46,7 +46,7 @@ import me.proton.core.auth.domain.usecase.UnlockUserPrimaryKey
import me.proton.core.auth.presentation.entity.signup.SubscriptionDetails
import me.proton.core.crypto.common.keystore.EncryptedString
import me.proton.core.crypto.common.keystore.KeyStoreCrypto
import me.proton.core.crypto.common.keystore.encryptWith
import me.proton.core.crypto.common.keystore.encrypt
import me.proton.core.domain.entity.UserId
import me.proton.core.humanverification.domain.HumanVerificationManager
import me.proton.core.humanverification.presentation.HumanVerificationOrchestrator
@ -112,7 +112,7 @@ internal class LoginViewModel @Inject constructor(
) = flow<State> {
emit(State.Processing)
val encryptedPassword = password.encryptWith(keyStoreCrypto)
val encryptedPassword = password.encrypt(keyStoreCrypto)
val sessionInfo = performLogin.invoke(username, encryptedPassword)
val userId = sessionInfo.userId

View File

@ -32,7 +32,7 @@ import me.proton.core.auth.domain.AccountWorkflowHandler
import me.proton.core.auth.domain.usecase.UnlockUserPrimaryKey
import me.proton.core.crypto.common.keystore.EncryptedString
import me.proton.core.crypto.common.keystore.KeyStoreCrypto
import me.proton.core.crypto.common.keystore.encryptWith
import me.proton.core.crypto.common.keystore.encrypt
import me.proton.core.domain.entity.UserId
import me.proton.core.presentation.viewmodel.ProtonViewModel
import me.proton.core.user.domain.UserManager
@ -72,7 +72,7 @@ class TwoPassModeViewModel @Inject constructor(
) = flow {
emit(State.Processing)
val encryptedPassword = password.encryptWith(keyStoreCrypto)
val encryptedPassword = password.encrypt(keyStoreCrypto)
val state = unlockUserPrimaryKey(userId, encryptedPassword)
emit(state)

View File

@ -37,8 +37,8 @@ import me.proton.core.auth.presentation.entity.signup.SubscriptionDetails
import me.proton.core.auth.presentation.viewmodel.AuthViewModel
import me.proton.core.crypto.common.keystore.EncryptedString
import me.proton.core.crypto.common.keystore.KeyStoreCrypto
import me.proton.core.crypto.common.keystore.decryptWith
import me.proton.core.crypto.common.keystore.encryptWith
import me.proton.core.crypto.common.keystore.decrypt
import me.proton.core.crypto.common.keystore.encrypt
import me.proton.core.humanverification.domain.HumanVerificationManager
import me.proton.core.humanverification.presentation.HumanVerificationOrchestrator
import me.proton.core.humanverification.presentation.onHumanVerificationFailed
@ -82,9 +82,9 @@ internal class SignupViewModel @Inject constructor(
var externalEmail: String? = null
var password: String
get() = _password.decryptWith(keyStoreCrypto)
get() = _password.decrypt(keyStoreCrypto)
set(value) {
_password = value.encryptWith(keyStoreCrypto)
_password = value.encrypt(keyStoreCrypto)
}
override val recoveryEmailAddress: String?

View File

@ -22,6 +22,7 @@ import android.app.Application
import android.util.Log
import androidx.appcompat.app.AppCompatDelegate.setCompatVectorFromResourcesEnabled
import dagger.hilt.android.HiltAndroidApp
import me.proton.core.util.kotlin.CoreLogger
import timber.log.Timber
import timber.log.Timber.DebugTree
@ -44,6 +45,8 @@ class CoreExampleApp : Application() {
override fun onCreate() {
super.onCreate()
CoreLogger.set(CoreExampleLogger())
if (BuildConfig.DEBUG) {
Timber.plant(DebugTree())
} else {

View File

@ -22,13 +22,11 @@ import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import me.proton.android.core.coreexample.CoreExampleLogger
import me.proton.android.core.coreexample.api.CoreExampleRepository
import me.proton.core.account.domain.entity.AccountType
import me.proton.core.auth.domain.ClientSecret
import me.proton.core.domain.entity.Product
import me.proton.core.network.data.ApiProvider
import me.proton.core.util.kotlin.Logger
import javax.inject.Singleton
@Module
@ -49,11 +47,6 @@ object ApplicationModule {
@ClientSecret
fun provideClientSecret(): String = ""
@Provides
@Singleton
fun provideLogger(): Logger =
CoreExampleLogger()
@Provides
@Singleton
fun provideCoreExampleRepository(apiProvider: ApiProvider): CoreExampleRepository =

View File

@ -46,7 +46,6 @@ import me.proton.core.network.domain.humanverification.HumanVerificationProvider
import me.proton.core.network.domain.server.ServerTimeListener
import me.proton.core.network.domain.session.SessionListener
import me.proton.core.network.domain.session.SessionProvider
import me.proton.core.util.kotlin.Logger
import okhttp3.Cache
import java.io.File
import javax.inject.Singleton
@ -86,7 +85,6 @@ class NetworkModule {
@Singleton
fun provideApiFactory(
@ApplicationContext context: Context,
logger: Logger,
apiClient: ApiClient,
clientIdProvider: ClientIdProvider,
serverTimeListener: ServerTimeListener,
@ -102,7 +100,6 @@ class NetworkModule {
apiClient,
clientIdProvider,
serverTimeListener,
logger,
networkManager,
networkPrefs,
sessionProvider,

View File

@ -41,6 +41,7 @@ import me.proton.core.accountmanager.presentation.onAccountCreateAddressFailed
import me.proton.core.accountmanager.presentation.onAccountCreateAddressNeeded
import me.proton.core.accountmanager.presentation.onAccountTwoPassModeFailed
import me.proton.core.accountmanager.presentation.onAccountTwoPassModeNeeded
import me.proton.core.accountmanager.presentation.onSessionForceLogout
import me.proton.core.accountmanager.presentation.onSessionSecondFactorNeeded
import me.proton.core.auth.presentation.AuthOrchestrator
import me.proton.core.domain.entity.Product
@ -49,11 +50,13 @@ import me.proton.core.humanverification.domain.HumanVerificationManager
import me.proton.core.humanverification.presentation.HumanVerificationOrchestrator
import me.proton.core.humanverification.presentation.observe
import me.proton.core.humanverification.presentation.onHumanVerificationNeeded
import me.proton.core.user.domain.UserManager
import javax.inject.Inject
@HiltViewModel
class AccountViewModel @Inject constructor(
private val accountManager: AccountManager,
private val userManager: UserManager,
private val humanVerificationManager: HumanVerificationManager,
private var authOrchestrator: AuthOrchestrator,
private var humanVerificationOrchestrator: HumanVerificationOrchestrator
@ -84,6 +87,7 @@ class AccountViewModel @Inject constructor(
with(authOrchestrator) {
accountManager.observe(context.lifecycle, minActiveState = Lifecycle.State.CREATED)
.onSessionForceLogout { userManager.lock(it.userId) }
.onSessionSecondFactorNeeded { startSecondFactorWorkflow(it) }
.onAccountTwoPassModeNeeded { startTwoPassModeWorkflow(it) }
.onAccountCreateAddressNeeded { startChooseAddressWorkflow(it) }

View File

@ -24,7 +24,7 @@ plugins {
kotlin("android")
}
libVersion = Version(1, 15, 0)
libVersion = Version(1, 15, 2)
android()

View File

@ -23,9 +23,12 @@ import android.security.keystore.KeyProperties
import android.util.Base64
import me.proton.core.crypto.common.keystore.EncryptedByteArray
import me.proton.core.crypto.common.keystore.EncryptedString
import me.proton.core.crypto.common.keystore.PlainByteArray
import me.proton.core.crypto.common.keystore.KeyStoreCrypto
import me.proton.core.crypto.common.keystore.LogTag
import me.proton.core.crypto.common.keystore.PlainByteArray
import me.proton.core.crypto.common.keystore.use
import me.proton.core.util.kotlin.CoreLogger
import java.security.Key
import java.security.KeyStore
import javax.crypto.Cipher
import javax.crypto.KeyGenerator
@ -67,37 +70,67 @@ class AndroidKeyStoreCrypto private constructor(
)
it.generateKey()
}
}.let { keyStoreKey ->
// Check if encrypt/decrypt is properly working (CP-1500).
runCatching {
val message = "message"
val encrypted = encrypt(message, keyStoreKey)
val decrypted = decrypt(encrypted, keyStoreKey)
check(message == decrypted)
keyStoreKey
}.getOrElse {
CoreLogger.e(LogTag.KEYSTORE_INIT, it)
null
}
}
}
override fun encrypt(value: PlainByteArray): EncryptedByteArray {
private fun encrypt(value: PlainByteArray, key: Key): EncryptedByteArray {
val cipher = Cipher.getInstance(cipherTransformation)
cipher.init(Cipher.ENCRYPT_MODE, secretKey)
cipher.init(Cipher.ENCRYPT_MODE, key)
val cipherByteArray = cipher.doFinal(value.array)
return EncryptedByteArray(cipher.iv + cipherByteArray)
}
override fun decrypt(value: EncryptedByteArray): PlainByteArray {
private fun decrypt(value: EncryptedByteArray, key: Key): PlainByteArray {
val cipher = Cipher.getInstance(cipherTransformation)
val iv = value.array.copyOf(cipherIvBytes)
val cipherByteArray = value.array.copyOfRange(cipherIvBytes, value.array.size)
cipher.init(Cipher.DECRYPT_MODE, secretKey, GCMParameterSpec(cipherGCMTagBits, iv))
cipher.init(Cipher.DECRYPT_MODE, key, GCMParameterSpec(cipherGCMTagBits, iv))
return PlainByteArray(cipher.doFinal(cipherByteArray))
}
override fun encrypt(value: String): EncryptedString {
private fun encrypt(value: String, key: Key): EncryptedString {
return value.encodeToByteArray().use {
Base64.encodeToString(encrypt(it).array, Base64.NO_WRAP)
Base64.encodeToString(encrypt(it, key).array, Base64.NO_WRAP)
}
}
override fun decrypt(value: EncryptedString): String {
private fun decrypt(value: EncryptedString, key: Key): String {
val encryptedByteArray = Base64.decode(value, Base64.NO_WRAP)
return decrypt(EncryptedByteArray(encryptedByteArray)).use {
return decrypt(EncryptedByteArray(encryptedByteArray), key).use {
it.array.decodeToString()
}
}
override fun isUsingKeyStore(): Boolean = secretKey != null
override fun encrypt(value: PlainByteArray): EncryptedByteArray {
return secretKey?.let { encrypt(value, it) } ?: EncryptedByteArray(value.array.copyOf())
}
override fun decrypt(value: EncryptedByteArray): PlainByteArray {
return secretKey?.let { decrypt(value, it) } ?: PlainByteArray(value.array.copyOf())
}
override fun encrypt(value: String): EncryptedString {
return secretKey?.let { encrypt(value, it) } ?: value
}
override fun decrypt(value: EncryptedString): String {
return secretKey?.let { decrypt(value, it) } ?: value
}
companion object {
private const val DEFAULT_MASTER_KEY_ALIAS = "_me_proton_core_data_crypto_master_key_"

View File

@ -23,7 +23,7 @@ plugins {
kotlin("android")
}
libVersion = Version(1, 15, 0)
libVersion = Version(1, 15, 2)
android()

View File

@ -23,7 +23,7 @@ plugins {
kotlin("jvm")
}
libVersion = Version(1, 15, 0)
libVersion = Version(1, 15, 2)
dependencies {
implementation(

View File

@ -32,9 +32,29 @@ data class EncryptedByteArray(val array: ByteArray) {
/**
* Decrypt an [EncryptedByteArray] using a [KeyStoreCrypto].
*/
fun EncryptedByteArray.decryptWith(keyStoreCrypto: KeyStoreCrypto) = keyStoreCrypto.decrypt(this)
fun EncryptedByteArray.decrypt(crypto: KeyStoreCrypto) = crypto.decrypt(this)
/**
* Encrypt a [PlainByteArray] using a [KeyStoreCrypto].
*/
fun PlainByteArray.encryptWith(keyStoreCrypto: KeyStoreCrypto) = keyStoreCrypto.encrypt(this)
fun PlainByteArray.encrypt(crypto: KeyStoreCrypto) = crypto.encrypt(this)
/**
* Returns decrypted value, or the result of [onFailure] function on decryption failure.
*
* @see [EncryptedByteArray.decrypt]
*/
fun EncryptedByteArray.decryptOrElse(
crypto: KeyStoreCrypto,
onFailure: (Throwable) -> PlainByteArray?
) = crypto.decryptOrElse(this, onFailure)
/**
* Returns encrypted value, or the result of [onFailure] function on encryption failure.
*
* @see [PlainByteArray.encrypt]
*/
fun PlainByteArray.encryptOrElse(
crypto: KeyStoreCrypto,
onFailure: (Throwable) -> EncryptedByteArray?
) = crypto.encryptOrElse(this, onFailure)

View File

@ -26,9 +26,29 @@ typealias EncryptedString = String
/**
* Decrypt an [EncryptedString] using a [KeyStoreCrypto].
*/
fun EncryptedString.decryptWith(crypto: KeyStoreCrypto) = crypto.decrypt(this)
fun EncryptedString.decrypt(crypto: KeyStoreCrypto) = crypto.decrypt(this)
/**
* Encrypt a [String] using a [KeyStoreCrypto].
*/
fun String.encryptWith(crypto: KeyStoreCrypto) = crypto.encrypt(this)
fun String.encrypt(crypto: KeyStoreCrypto) = crypto.encrypt(this)
/**
* Returns decrypted value, or the result of onFailure function on decryption failure.
*
* @see [EncryptedString.decryptWith]
*/
fun EncryptedString.decryptOrElse(
crypto: KeyStoreCrypto,
onFailure: (Throwable) -> String?
) = crypto.decryptOrElse(this, onFailure)
/**
* Returns encrypted value, or the result of [onFailure] function on encryption failure.
*
* @see [String.encrypt]
*/
fun String.encryptOrElse(
crypto: KeyStoreCrypto,
onFailure: (Throwable) -> EncryptedString?
) = crypto.encryptOrElse(this, onFailure)

View File

@ -18,11 +18,18 @@
package me.proton.core.crypto.common.keystore
import me.proton.core.util.kotlin.CoreLogger
/**
* KeyStore Cryptographic interface providing [encrypt] function on [String] and [PlainByteArray],
* and a [decrypt] function on [EncryptedString] and [EncryptedByteArray].
*/
interface KeyStoreCrypto {
/**
* Returns whether System Keystore is being used providing secure key, or false otherwise.
*/
fun isUsingKeyStore(): Boolean
/**
* Encrypt a [String] [value] and return an [EncryptedString].
*/
@ -43,3 +50,74 @@ interface KeyStoreCrypto {
*/
fun decrypt(value: EncryptedByteArray): PlainByteArray
}
/**
* Returns encrypted value, or the result of [onFailure] function on encryption failure.
*
* @see [KeyStoreCrypto.encrypt]
*/
fun KeyStoreCrypto.encryptOrElse(
value: String,
onFailure: (Throwable) -> EncryptedString?
): EncryptedString? = runCatching {
encrypt(value)
}.getOrElse {
CoreLogger.e(LogTag.KEYSTORE_ENCRYPT, it)
onFailure(it)
}
/**
* Returns encrypted value, or the result of [onFailure] function on encryption failure.
*
* @see [KeyStoreCrypto.encrypt]
*/
fun KeyStoreCrypto.encryptOrElse(
value: PlainByteArray,
onFailure: (Throwable) -> EncryptedByteArray?
): EncryptedByteArray? = runCatching {
encrypt(value)
}.getOrElse {
CoreLogger.e(LogTag.KEYSTORE_ENCRYPT, it)
onFailure(it)
}
/**
* Returns decrypted value, or the result of [onFailure] function on decryption failure.
*
* @see [KeyStoreCrypto.decrypt]
*/
fun KeyStoreCrypto.decryptOrElse(
value: EncryptedString,
onFailure: (Throwable) -> String?
): String? = runCatching {
decrypt(value)
}.getOrElse {
CoreLogger.e(LogTag.KEYSTORE_DECRYPT, it)
onFailure(it)
}
/**
* Returns decrypted value, or the result of [onFailure] function on decryption failure.
*
* @see [KeyStoreCrypto.decrypt]
*/
fun KeyStoreCrypto.decryptOrElse(
value: EncryptedByteArray,
onFailure: (Throwable) -> PlainByteArray?
): PlainByteArray? = runCatching {
decrypt(value)
}.getOrElse {
CoreLogger.e(LogTag.KEYSTORE_DECRYPT, it)
onFailure(it)
}
object LogTag {
/** Tag for KeyStore initialization check failure. */
const val KEYSTORE_INIT = "core.crypto.common.keystore.init"
/** Tag for KeyStore encrypt failure. */
const val KEYSTORE_ENCRYPT = "core.crypto.common.keystore.encrypt"
/** Tag for KeyStore decrypt failure. */
const val KEYSTORE_DECRYPT = "core.crypto.common.keystore.decrypt"
}

View File

@ -23,7 +23,7 @@ plugins {
kotlin("android")
}
libVersion = Version(1, 15, 0)
libVersion = Version(1, 15, 2)
android()

View File

@ -26,7 +26,7 @@ plugins {
kotlin("kapt")
}
libVersion = Version(1, 15, 0)
libVersion = Version(1, 15, 2)
android(minSdk = 23)

View File

@ -21,13 +21,13 @@ package me.proton.core.humanverification.data.entity
import androidx.room.Entity
import me.proton.core.crypto.common.keystore.EncryptedString
import me.proton.core.crypto.common.keystore.KeyStoreCrypto
import me.proton.core.crypto.common.keystore.decryptWith
import me.proton.core.network.domain.humanverification.HumanVerificationDetails
import me.proton.core.network.domain.humanverification.HumanVerificationState
import me.proton.core.network.domain.humanverification.VerificationMethod
import me.proton.core.crypto.common.keystore.decryptOrElse
import me.proton.core.network.domain.client.ClientId
import me.proton.core.network.domain.client.ClientIdType
import me.proton.core.network.domain.client.CookieSessionId
import me.proton.core.network.domain.humanverification.HumanVerificationDetails
import me.proton.core.network.domain.humanverification.HumanVerificationState
import me.proton.core.network.domain.humanverification.VerificationMethod
import me.proton.core.network.domain.session.SessionId
@Entity(
@ -50,7 +50,9 @@ data class HumanVerificationEntity(
verificationMethods = verificationMethods.map { VerificationMethod.getByValue(it) },
captchaVerificationToken = captchaVerificationToken,
state = state,
tokenType = humanHeaderTokenType?.decryptWith(keyStoreCrypto),
tokenCode = humanHeaderTokenCode?.decryptWith(keyStoreCrypto)
// Fall back to an invalid captcha to force delete token on decryption failure.
// See HumanVerificationInvalidHandler and HumanVerificationListener.onHumanVerificationInvalid.
tokenType = humanHeaderTokenType?.decryptOrElse(keyStoreCrypto) { "captcha" },
tokenCode = humanHeaderTokenCode?.decryptOrElse(keyStoreCrypto) { "invalid" }
)
}

View File

@ -26,7 +26,7 @@ import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onSubscription
import me.proton.core.crypto.common.keystore.KeyStoreCrypto
import me.proton.core.crypto.common.keystore.encryptWith
import me.proton.core.crypto.common.keystore.encrypt
import me.proton.core.humanverification.data.db.HumanVerificationDatabase
import me.proton.core.humanverification.data.entity.HumanVerificationEntity
import me.proton.core.humanverification.domain.repository.HumanVerificationRepository
@ -67,8 +67,8 @@ class HumanVerificationRepositoryImpl(
verificationMethods = details.verificationMethods.map { method -> method.value },
captchaVerificationToken = details.captchaVerificationToken,
state = details.state,
humanHeaderTokenType = details.tokenType?.encryptWith(keyStoreCrypto),
humanHeaderTokenCode = details.tokenCode?.encryptWith(keyStoreCrypto)
humanHeaderTokenType = details.tokenType?.encrypt(keyStoreCrypto),
humanHeaderTokenCode = details.tokenCode?.encrypt(keyStoreCrypto)
)
)
}
@ -85,8 +85,8 @@ class HumanVerificationRepositoryImpl(
humanVerificationDetailsDao.updateStateAndToken(
clientId.id,
state,
tokenType?.encryptWith(keyStoreCrypto),
tokenCode?.encryptWith(keyStoreCrypto)
tokenType?.encrypt(keyStoreCrypto),
tokenCode?.encrypt(keyStoreCrypto)
)
}
getHumanVerificationDetails(clientId)?.let { tryEmitStateChanged(it) }

View File

@ -60,6 +60,7 @@ class HumanVerificationRepositoryImplTest {
private val clientId = ClientId.AccountSession(session1.sessionId)
private val simpleCrypto = object : KeyStoreCrypto {
override fun isUsingKeyStore(): Boolean = false
override fun encrypt(value: String): EncryptedString = "encrypted-$value"
override fun encrypt(value: PlainByteArray): EncryptedByteArray = EncryptedByteArray(value.array)
override fun decrypt(value: EncryptedString): String = "decrypted-$value"

View File

@ -23,7 +23,7 @@ plugins {
kotlin("jvm")
}
libVersion = Version(1, 15, 0)
libVersion = Version(1, 15, 2)
dependencies {

View File

@ -27,7 +27,7 @@ plugins {
id("dagger.hilt.android.plugin")
}
libVersion = Version(1, 15, 0)
libVersion = Version(1, 15, 2)
android(useDataBinding = true)

View File

@ -23,7 +23,7 @@ plugins {
kotlin("android")
}
libVersion = Version(1, 15, 0)
libVersion = Version(1, 15, 2)
android()

View File

@ -25,7 +25,7 @@ plugins {
kotlin("plugin.serialization")
}
libVersion = Version(1, 15, 0)
libVersion = Version(1, 15, 2)
android()

View File

@ -21,7 +21,7 @@ package me.proton.core.key.domain
import me.proton.core.crypto.common.context.CryptoContext
import me.proton.core.crypto.common.pgp.Armored
import me.proton.core.crypto.common.keystore.PlainByteArray
import me.proton.core.crypto.common.keystore.encryptWith
import me.proton.core.crypto.common.keystore.encrypt
import me.proton.core.key.domain.entity.key.KeyId
import me.proton.core.key.domain.entity.key.PrivateKey
import me.proton.core.key.domain.entity.keyholder.KeyHolder
@ -39,7 +39,7 @@ class TestKeyHolder(
key = privateKeyArmored,
isPrimary = isPrimary,
// Encrypt passphrase as it should be stored in PrivateKey.
passphrase = PlainByteArray(privateKeyPassphrase).encryptWith(context.keyStoreCrypto)
passphrase = PlainByteArray(privateKeyPassphrase).encrypt(context.keyStoreCrypto)
)
}

View File

@ -23,7 +23,7 @@ plugins {
kotlin("jvm")
}
libVersion = Version(1, 15, 0)
libVersion = Version(1, 15, 2)
dependencies {
implementation(

View File

@ -19,8 +19,8 @@
package me.proton.core.key.domain
import me.proton.core.crypto.common.context.CryptoContext
import me.proton.core.crypto.common.keystore.decryptWith
import me.proton.core.crypto.common.keystore.encryptWith
import me.proton.core.crypto.common.keystore.decrypt
import me.proton.core.crypto.common.keystore.encrypt
import me.proton.core.crypto.common.keystore.use
import me.proton.core.crypto.common.pgp.Armored
import me.proton.core.crypto.common.pgp.DecryptedData
@ -668,7 +668,7 @@ fun KeyHolderContext.decryptAndVerifyNestedKey(
privateKey = nestedPrivateKey.privateKey.copy(
passphrase = decryptDataOrNull(nestedPrivateKey.passphrase)
?.takeIf { verifyKeyRing.verifyData(context, it, nestedPrivateKey.passphraseSignature) }
?.let { plain -> plain.use { it.encryptWith(context.keyStoreCrypto) } }
?.let { plain -> plain.use { it.encrypt(context.keyStoreCrypto) } }
)
)
}
@ -705,7 +705,7 @@ fun KeyHolderContext.encryptAndSignNestedKey(
encryptKeyRing: PublicKeyRing = publicKeyRing
): NestedPrivateKey {
checkNotNull(nestedPrivateKey.privateKey.passphrase) { "Cannot encrypt without passphrase." }
return nestedPrivateKey.privateKey.passphrase.decryptWith(context.keyStoreCrypto).use { passphrase ->
return nestedPrivateKey.privateKey.passphrase.decrypt(context.keyStoreCrypto).use { passphrase ->
nestedPrivateKey.copy(
privateKey = nestedPrivateKey.privateKey.copy(passphrase = null),
passphrase = encryptKeyRing.encryptData(context, passphrase.array),

View File

@ -19,7 +19,7 @@
package me.proton.core.key.domain
import me.proton.core.crypto.common.context.CryptoContext
import me.proton.core.crypto.common.keystore.decryptWith
import me.proton.core.crypto.common.keystore.decrypt
import me.proton.core.crypto.common.pgp.EncryptedMessage
import me.proton.core.crypto.common.pgp.KeyPacket
import me.proton.core.crypto.common.pgp.SessionKey
@ -181,7 +181,7 @@ fun PrivateKey.signData(context: CryptoContext, data: ByteArray): Signature =
* @see [UnlockedPrivateKey.lock]
*/
fun PrivateKey.unlock(context: CryptoContext): UnlockedPrivateKey =
requireNotNull(passphrase).decryptWith(context.keyStoreCrypto).use { decrypted ->
requireNotNull(passphrase).decrypt(context.keyStoreCrypto).use { decrypted ->
context.pgpCrypto.unlock(key, decrypted.array).let {
UnlockedPrivateKey(it, isPrimary)
}
@ -196,7 +196,7 @@ fun PrivateKey.unlock(context: CryptoContext): UnlockedPrivateKey =
* @see [UnlockedPrivateKey.lock]
*/
fun PrivateKey.unlockOrNull(context: CryptoContext): UnlockedPrivateKey? =
passphrase?.decryptWith(context.keyStoreCrypto)?.use { decrypted ->
passphrase?.decrypt(context.keyStoreCrypto)?.use { decrypted ->
context.pgpCrypto.unlockOrNull(key, decrypted.array)?.let {
UnlockedPrivateKey(it, isPrimary)
}

View File

@ -20,7 +20,7 @@ package me.proton.core.key.domain
import me.proton.core.crypto.common.context.CryptoContext
import me.proton.core.crypto.common.keystore.EncryptedByteArray
import me.proton.core.crypto.common.keystore.decryptWith
import me.proton.core.crypto.common.keystore.decrypt
import me.proton.core.crypto.common.pgp.EncryptedMessage
import me.proton.core.crypto.common.pgp.KeyPacket
import me.proton.core.crypto.common.pgp.SessionKey
@ -211,16 +211,15 @@ fun UnlockedPrivateKey.lock(
isActive: Boolean = true,
canEncrypt: Boolean = true,
canVerify: Boolean = true,
): PrivateKey =
passphrase.decryptWith(context.keyStoreCrypto).use { decrypted ->
context.pgpCrypto.lock(unlockedKey.value, decrypted.array).let {
PrivateKey(
key = it,
isPrimary = isPrimary,
isActive = isActive,
canEncrypt = canEncrypt,
canVerify = canVerify,
passphrase = passphrase
)
}
): PrivateKey = passphrase.decrypt(context.keyStoreCrypto).use { decrypted ->
context.pgpCrypto.lock(unlockedKey.value, decrypted.array).let {
PrivateKey(
key = it,
isPrimary = isPrimary,
isActive = isActive,
canEncrypt = canEncrypt,
canVerify = canVerify,
passphrase = passphrase
)
}
}

View File

@ -19,7 +19,7 @@
package me.proton.core.key.domain.entity.key
import me.proton.core.crypto.common.context.CryptoContext
import me.proton.core.crypto.common.keystore.encryptWith
import me.proton.core.crypto.common.keystore.encrypt
import me.proton.core.crypto.common.keystore.use
import me.proton.core.crypto.common.pgp.Armored
import me.proton.core.crypto.common.pgp.EncryptedMessage
@ -65,7 +65,7 @@ data class NestedPrivateKey(
domain = domain,
passphrase = passphrase.array
)
val encryptedPassphrase = passphrase.encryptWith(context.keyStoreCrypto)
val encryptedPassphrase = passphrase.encrypt(context.keyStoreCrypto)
val keyHolderPrivateKey = PrivateKey(
key = privateKey,
isPrimary = true,

View File

@ -19,7 +19,7 @@
package me.proton.core.key.domain.extension
import me.proton.core.crypto.common.context.CryptoContext
import me.proton.core.crypto.common.keystore.decryptWith
import me.proton.core.crypto.common.keystore.decrypt
import me.proton.core.crypto.common.pgp.Armored
import me.proton.core.crypto.common.pgp.updatePrivateKeyPassphraseOrNull
import me.proton.core.key.domain.entity.key.Key
@ -29,7 +29,7 @@ fun KeyHolderPrivateKey.updatePrivateKeyPassphraseOrNull(
cryptoContext: CryptoContext,
newPassphrase: ByteArray
): Key? {
val passphrase = privateKey.passphrase?.decryptWith(cryptoContext.keyStoreCrypto)?.array ?: return null
val passphrase = privateKey.passphrase?.decrypt(cryptoContext.keyStoreCrypto)?.array ?: return null
return cryptoContext.pgpCrypto.updatePrivateKeyPassphraseOrNull(
privateKey = privateKey.key,
passphrase = passphrase,

View File

@ -52,6 +52,9 @@ class TestCryptoContext : CryptoContext {
// Use the defaultKey to encrypt/decrypt.
override val keyStoreCrypto: KeyStoreCrypto = object : KeyStoreCrypto {
override fun isUsingKeyStore(): Boolean = false
override fun encrypt(value: String): EncryptedString =
value.toByteArray().encrypt(defaultKey).fromByteArray()

View File

@ -19,7 +19,7 @@
package me.proton.core.key.domain
import me.proton.core.crypto.common.keystore.PlainByteArray
import me.proton.core.crypto.common.keystore.encryptWith
import me.proton.core.crypto.common.keystore.encrypt
import me.proton.core.key.domain.entity.key.KeyId
import me.proton.core.key.domain.entity.key.PrivateKey
import me.proton.core.key.domain.entity.keyholder.KeyHolder
@ -46,7 +46,7 @@ class TestKeyHolder(
isPrimary = isPrimary,
isActive = isActive,
// Encrypt passphrase as it should be stored in PrivateKey.
passphrase = passphrase?.let { PlainByteArray(passphrase).encryptWith(context.keyStoreCrypto) }
passphrase = passphrase?.let { PlainByteArray(passphrase).encrypt(context.keyStoreCrypto) }
).also { key ->
// Workaround: Remember unlockedKey to be able to decrypt messages encrypted with PublicKey.
val publicKey = context.pgpCrypto.getPublicKey(key.key)

View File

@ -23,7 +23,7 @@ plugins {
kotlin("android")
}
libVersion = Version(1, 15, 0)
libVersion = Version(1, 15, 2)
android()

View File

@ -25,7 +25,7 @@ plugins {
kotlin("plugin.serialization")
}
libVersion = Version(1, 15, 0)
libVersion = Version(1, 15, 2)
android()

View File

@ -46,7 +46,6 @@ import me.proton.core.network.domain.server.ServerTimeListener
import me.proton.core.network.domain.session.SessionId
import me.proton.core.network.domain.session.SessionListener
import me.proton.core.network.domain.session.SessionProvider
import me.proton.core.util.kotlin.Logger
import me.proton.core.util.kotlin.ProtonCoreConfig
import okhttp3.Cache
import okhttp3.JavaNetCookieJar
@ -71,7 +70,6 @@ class ApiManagerFactory(
private val apiClient: ApiClient,
private val clientIdProvider: ClientIdProvider,
private val serverTimeListener: ServerTimeListener,
private val logger: Logger,
private val networkManager: NetworkManager,
private val prefs: NetworkPrefs,
private val sessionProvider: SessionProvider,
@ -117,7 +115,7 @@ class ApiManagerFactory(
private val dohProvider by lazy {
val dohServices = Constants.DOH_PROVIDERS_URLS.map { serviceUrl ->
DnsOverHttpsProviderRFC8484(baseOkHttpClient, serviceUrl, apiClient, networkManager, logger)
DnsOverHttpsProviderRFC8484(baseOkHttpClient, serviceUrl, apiClient, networkManager)
}
DohProvider(baseUrl, apiClient, dohServices, mainScope, prefs, ::javaMonoClockMs)
}
@ -169,7 +167,6 @@ class ApiManagerFactory(
apiClient,
clientIdProvider,
serverTimeListener,
logger,
sessionId,
sessionProvider,
humanVerificationProvider,
@ -199,7 +196,6 @@ class ApiManagerFactory(
apiClient,
clientIdProvider,
serverTimeListener,
logger,
sessionId,
sessionProvider,
humanVerificationProvider,

View File

@ -20,7 +20,7 @@ package me.proton.core.network.data
import kotlinx.serialization.SerializationException
import me.proton.core.network.domain.ApiResult
import me.proton.core.network.domain.NetworkManager
import me.proton.core.util.kotlin.Logger
import me.proton.core.util.kotlin.CoreLogger
import okhttp3.Response
import retrofit2.HttpException
import java.io.IOException
@ -32,7 +32,6 @@ import javax.net.ssl.SSLPeerUnverifiedException
internal suspend fun <Api, T> safeApiCall(
networkManager: NetworkManager,
logger: Logger,
api: Api,
block: suspend (Api) -> T
): ApiResult<T> {
@ -58,7 +57,7 @@ internal suspend fun <Api, T> safeApiCall(
ApiResult.Error.Connection(networkManager.isConnectedToNetwork(), e)
}
if (result is ApiResult.Error) {
result.cause?.let { logger.e(LogTag.DEFAULT, it) }
result.cause?.let { CoreLogger.e(LogTag.DEFAULT, it) }
}
return result
}

View File

@ -19,17 +19,17 @@ package me.proton.core.network.data
import android.os.SystemClock
import me.proton.core.network.domain.ApiClient
import me.proton.core.util.kotlin.Logger
import me.proton.core.util.kotlin.CoreLogger
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
internal fun OkHttpClient.Builder.initLogging(client: ApiClient, logger: Logger): OkHttpClient.Builder {
internal fun OkHttpClient.Builder.initLogging(client: ApiClient): OkHttpClient.Builder {
if (client.enableDebugLogging) {
// HttpLoggingInterceptor generate log messages and forward them into provided Logger.
addInterceptor(
HttpLoggingInterceptor(
logger = object : HttpLoggingInterceptor.Logger {
override fun log(message: String) = logger.d(LogTag.DEFAULT, message)
override fun log(message: String) = CoreLogger.d(LogTag.DEFAULT, message)
}
).apply { level = HttpLoggingInterceptor.Level.BODY }
)
@ -38,7 +38,7 @@ internal fun OkHttpClient.Builder.initLogging(client: ApiClient, logger: Logger)
addInterceptor { chain ->
val request = chain.request()
val auth = request.header("Authorization").formatToken(client)
logger.log(
CoreLogger.log(
LogTag.API_CALL,
with(request) { "--> $method $url (auth $auth)" },
)
@ -46,7 +46,7 @@ internal fun OkHttpClient.Builder.initLogging(client: ApiClient, logger: Logger)
val startMs = SystemClock.elapsedRealtime()
val response = chain.proceed(request)
val durationMs = SystemClock.elapsedRealtime() - startMs
logger.log(
CoreLogger.log(
LogTag.API_CALL,
with(response) { "<-- $code $message ${request.method} ${request.url} (${durationMs}ms)" }
)

View File

@ -35,7 +35,7 @@ import me.proton.core.network.domain.server.ServerTimeListener
import me.proton.core.network.domain.session.Session
import me.proton.core.network.domain.session.SessionId
import me.proton.core.network.domain.session.SessionProvider
import me.proton.core.util.kotlin.Logger
import me.proton.core.util.kotlin.CoreLogger
import me.proton.core.util.kotlin.takeIfNotBlank
import okhttp3.Interceptor
import okhttp3.OkHttpClient
@ -65,7 +65,6 @@ internal class ProtonApiBackend<Api : BaseRetrofitApi>(
private val client: ApiClient,
private val clientIdProvider: ClientIdProvider,
serverTimeListener: ServerTimeListener,
private val logger: Logger,
private val sessionId: SessionId?,
private val sessionProvider: SessionProvider,
private val humanVerificationProvider: HumanVerificationProvider,
@ -85,7 +84,7 @@ internal class ProtonApiBackend<Api : BaseRetrofitApi>(
val chain = handleTimeoutTag(orgChain)
chain.proceed(prepareHeaders(chain.request()).build())
}
.initLogging(client, logger)
.initLogging(client)
.addInterceptor(ServerErrorInterceptor())
.addInterceptor(TooManyRequestInterceptor(sessionId, wallClockMs))
.addNetworkInterceptor(ServerTimeInterceptor(serverTimeListener))
@ -147,7 +146,7 @@ internal class ProtonApiBackend<Api : BaseRetrofitApi>(
invokeInternal(call.block)
private suspend fun <T> invokeInternal(block: suspend Api.() -> T): ApiResult<T> =
safeApiCall(networkManager, logger, api, block)
safeApiCall(networkManager, api, block)
override suspend fun refreshSession(session: Session): ApiResult<Session> {
val result = invokeInternal {
@ -155,8 +154,7 @@ internal class ProtonApiBackend<Api : BaseRetrofitApi>(
}
return when (result) {
is ApiResult.Success -> {
logger.log(LogTag.REFRESH_TOKEN, "new access token: ${result.value.accessToken.formatToken(client)}")
logger.log(LogTag.REFRESH_TOKEN, "new refresh token: ${result.value.refreshToken.formatToken(client)}")
CoreLogger.log(LogTag.REFRESH_TOKEN, "Access & refresh tokens refreshed.")
ApiResult.Success(
session.refreshWith(
accessToken = result.value.accessToken,

View File

@ -24,7 +24,6 @@ import me.proton.core.network.domain.ApiClient
import me.proton.core.network.domain.ApiResult
import me.proton.core.network.domain.DohService
import me.proton.core.network.domain.NetworkManager
import me.proton.core.util.kotlin.Logger
import okhttp3.OkHttpClient
import okhttp3.ResponseBody
import org.apache.commons.codec.binary.Base32
@ -43,8 +42,7 @@ class DnsOverHttpsProviderRFC8484(
baseOkHttpClient: OkHttpClient,
private val baseUrl: String,
client: ApiClient,
private val networkManager: NetworkManager,
private val logger: Logger
private val networkManager: NetworkManager
) : DohService {
private val api: DnsOverHttpsRetrofitApi
@ -68,7 +66,7 @@ class DnsOverHttpsProviderRFC8484(
.connectTimeout(TIMEOUT_S, TimeUnit.SECONDS)
.writeTimeout(TIMEOUT_S, TimeUnit.SECONDS)
.readTimeout(TIMEOUT_S, TimeUnit.SECONDS)
.initLogging(client, logger)
.initLogging(client)
val okClient = httpClientBuilder.build()
api = Retrofit.Builder()
@ -87,10 +85,12 @@ class DnsOverHttpsProviderRFC8484(
.setRecursionDesired(true)
.setQuestion(question)
.build()
val queryMessageBase64 = Base64.encodeToString(queryMessage.toArray(),
Base64.URL_SAFE or Base64.NO_PADDING or Base64.NO_WRAP)
val queryMessageBase64 = Base64.encodeToString(
queryMessage.toArray(),
Base64.URL_SAFE or Base64.NO_PADDING or Base64.NO_WRAP
)
val response = safeApiCall(networkManager, logger, api) {
val response = safeApiCall(networkManager, api) {
api.getServers(baseUrl.removeSuffix("/"), queryMessageBase64)
}
if (response is ApiResult.Success) {

View File

@ -31,7 +31,6 @@ import kotlinx.coroutines.test.TestCoroutineDispatcher
import kotlinx.coroutines.test.runBlockingTest
import me.proton.core.network.data.util.MockApiClient
import me.proton.core.network.data.util.MockClientId
import me.proton.core.network.data.util.MockLogger
import me.proton.core.network.data.util.MockNetworkManager
import me.proton.core.network.data.util.MockNetworkPrefs
import me.proton.core.network.data.util.MockSession
@ -133,7 +132,6 @@ internal class ApiManagerTests {
apiClient,
clientIdProvider,
serverTimeListener,
MockLogger(),
networkManager,
prefs,
sessionProvider,

View File

@ -24,7 +24,6 @@ import io.mockk.impl.annotations.MockK
import kotlinx.coroutines.runBlocking
import me.proton.core.network.data.doh.DnsOverHttpsProviderRFC8484
import me.proton.core.network.data.util.MockApiClient
import me.proton.core.network.data.util.MockLogger
import me.proton.core.network.domain.NetworkManager
import okhttp3.OkHttpClient
import okhttp3.mockwebserver.MockResponse
@ -70,8 +69,7 @@ internal class DohProviderTests {
okHttpClient,
webServer.url("/").toString(),
client,
networkManager,
MockLogger()
networkManager
)
}

View File

@ -158,7 +158,6 @@ internal class HumanVerificationTests {
client,
clientIdProvider,
serverTimeListener,
logger,
networkManager,
prefs,
sessionProvider,
@ -184,7 +183,6 @@ internal class HumanVerificationTests {
client,
clientIdProvider,
serverTimeListener,
logger,
sessionId,
sessionProvider,
humanVerificationProvider,

View File

@ -107,7 +107,6 @@ internal class ProtonApiBackendTests {
@BeforeTest
fun before() {
MockKAnnotations.init(this)
logger = MockLogger()
client = MockApiClient()
prefs = MockNetworkPrefs()
@ -123,7 +122,6 @@ internal class ProtonApiBackendTests {
client,
clientIdProvider,
serverTimeListener,
logger,
networkManager,
prefs,
sessionProvider,
@ -163,7 +161,6 @@ internal class ProtonApiBackendTests {
client,
clientIdProvider,
serverTimeListener,
logger,
session.sessionId,
sessionProvider,
humanVerificationProvider,

View File

@ -24,7 +24,7 @@ plugins {
kotlin("plugin.serialization")
}
libVersion = Version(1, 15, 0)
libVersion = Version(1, 15, 2)
dependencies {

View File

@ -23,7 +23,7 @@ plugins {
kotlin("android")
}
libVersion = Version(1, 15, 1)
libVersion = Version(1, 15, 2)
android()

View File

@ -24,7 +24,7 @@ plugins {
kotlin("plugin.serialization")
}
libVersion = Version(1, 15, 1)
libVersion = Version(1, 15, 2)
android()

View File

@ -23,7 +23,7 @@ plugins {
kotlin("jvm")
}
libVersion = Version(1, 15, 1)
libVersion = Version(1, 15, 2)
dependencies {

View File

@ -23,7 +23,7 @@ import me.proton.core.auth.domain.ClientSecret
import me.proton.core.auth.domain.repository.AuthRepository
import me.proton.core.crypto.common.context.CryptoContext
import me.proton.core.crypto.common.keystore.EncryptedString
import me.proton.core.crypto.common.keystore.decryptWith
import me.proton.core.crypto.common.keystore.decrypt
import me.proton.core.crypto.common.keystore.use
import me.proton.core.crypto.common.srp.SrpProofs
import me.proton.core.domain.entity.UserId
@ -57,8 +57,8 @@ class PerformUpdateLoginPassword @Inject constructor(
)
val modulus = authRepository.randomModulus()
password.decryptWith(keyStore).toByteArray().use { decryptedPassword ->
newPassword.decryptWith(keyStore).toByteArray().use { decryptedNewPassword ->
password.decrypt(keyStore).toByteArray().use { decryptedPassword ->
newPassword.decrypt(keyStore).toByteArray().use { decryptedNewPassword ->
val clientProofs: SrpProofs = srp.generateSrpProofs(
username = username,
password = decryptedPassword.array,

View File

@ -23,7 +23,7 @@ import me.proton.core.auth.domain.ClientSecret
import me.proton.core.auth.domain.repository.AuthRepository
import me.proton.core.crypto.common.keystore.EncryptedString
import me.proton.core.crypto.common.keystore.KeyStoreCrypto
import me.proton.core.crypto.common.keystore.decryptWith
import me.proton.core.crypto.common.keystore.decrypt
import me.proton.core.crypto.common.keystore.use
import me.proton.core.crypto.common.srp.SrpCrypto
import me.proton.core.crypto.common.srp.SrpProofs
@ -50,7 +50,7 @@ class PerformUpdateRecoveryEmail @Inject constructor(
username = username,
clientSecret = clientSecret
)
password.decryptWith(keyStoreCrypto).toByteArray().use { decryptedPassword ->
password.decrypt(keyStoreCrypto).toByteArray().use { decryptedPassword ->
val clientProofs: SrpProofs = srpCrypto.generateSrpProofs(
username = username,
password = decryptedPassword.array,

View File

@ -22,7 +22,7 @@ import me.proton.core.auth.domain.ClientSecret
import me.proton.core.auth.domain.repository.AuthRepository
import me.proton.core.crypto.common.context.CryptoContext
import me.proton.core.crypto.common.keystore.EncryptedString
import me.proton.core.crypto.common.keystore.decryptWith
import me.proton.core.crypto.common.keystore.decrypt
import me.proton.core.crypto.common.keystore.use
import me.proton.core.crypto.common.srp.SrpProofs
import me.proton.core.domain.entity.UserId
@ -61,8 +61,8 @@ class PerformUpdateUserPassword @Inject constructor(
organizationRepository.getOrganizationKeys(userId)
} else null
loginPassword.decryptWith(keyStore).toByteArray().use { decryptedLoginPassword ->
newPassword.decryptWith(keyStore).toByteArray().use { decryptedNewPassword ->
loginPassword.decrypt(keyStore).toByteArray().use { decryptedLoginPassword ->
newPassword.decrypt(keyStore).toByteArray().use { decryptedNewPassword ->
val clientProofs: SrpProofs = srp.generateSrpProofs(
username = username,
password = decryptedLoginPassword.array,

View File

@ -27,7 +27,7 @@ plugins {
id("dagger.hilt.android.plugin")
}
libVersion = Version(1, 15, 1)
libVersion = Version(1, 15, 2)
android(useDataBinding = true)

View File

@ -27,7 +27,7 @@ import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import me.proton.core.crypto.common.keystore.KeyStoreCrypto
import me.proton.core.crypto.common.keystore.encryptWith
import me.proton.core.crypto.common.keystore.encrypt
import me.proton.core.domain.entity.UserId
import me.proton.core.presentation.viewmodel.ProtonViewModel
import me.proton.core.usersettings.domain.entity.UserSettings
@ -95,8 +95,8 @@ class PasswordManagementViewModel @Inject constructor(
return@flow
}
emit(State.UpdatingLoginPassword)
val encryptedPassword = password.encryptWith(keyStoreCrypto)
val encryptedNewPassword = newPassword.encryptWith(keyStoreCrypto)
val encryptedPassword = password.encrypt(keyStoreCrypto)
val encryptedNewPassword = newPassword.encrypt(keyStoreCrypto)
val result = performUpdateLoginPassword(
userId = userId,
@ -121,8 +121,8 @@ class PasswordManagementViewModel @Inject constructor(
secondFactorCode: String = ""
) = flow {
emit(if (twoPasswordMode == true) State.UpdatingMailboxPassword else State.UpdatingSinglePassModePassword)
val encryptedLoginPassword = loginPassword.encryptWith(keyStoreCrypto)
val encryptedNewMailboxPassword = newMailboxPassword.encryptWith(keyStoreCrypto)
val encryptedLoginPassword = loginPassword.encrypt(keyStoreCrypto)
val encryptedNewMailboxPassword = newMailboxPassword.encrypt(keyStoreCrypto)
val result = performUpdateUserPassword.invoke(
twoPasswordMode = twoPasswordMode!!,
userId = userId,

View File

@ -27,7 +27,7 @@ import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import me.proton.core.crypto.common.keystore.KeyStoreCrypto
import me.proton.core.crypto.common.keystore.encryptWith
import me.proton.core.crypto.common.keystore.encrypt
import me.proton.core.domain.entity.UserId
import me.proton.core.presentation.viewmodel.ProtonViewModel
import me.proton.core.user.domain.repository.UserRepository
@ -84,7 +84,7 @@ class UpdateRecoveryEmailViewModel @Inject constructor(
secondFactorCode: String
) = flow {
emit(State.UpdatingCurrent)
val encryptedPassword = password.encryptWith(keyStoreCrypto)
val encryptedPassword = password.encrypt(keyStoreCrypto)
val user = userRepository.getUser(userId)
val username = requireNotNull(user.name ?: user.email)

View File

@ -23,7 +23,7 @@ plugins {
kotlin("android")
}
libVersion = Version(1, 15, 0)
libVersion = Version(1, 15, 2)
android()

View File

@ -25,7 +25,7 @@ plugins {
kotlin("plugin.serialization")
}
libVersion = Version(1, 15, 0)
libVersion = Version(1, 15, 2)
android()

View File

@ -20,8 +20,8 @@ package me.proton.core.user.data
import me.proton.core.crypto.common.context.CryptoContext
import me.proton.core.crypto.common.keystore.EncryptedByteArray
import me.proton.core.crypto.common.keystore.decryptWith
import me.proton.core.crypto.common.keystore.encryptWith
import me.proton.core.crypto.common.keystore.decrypt
import me.proton.core.crypto.common.keystore.encrypt
import me.proton.core.crypto.common.keystore.use
import me.proton.core.crypto.common.pgp.Armored
import me.proton.core.domain.entity.UserId
@ -86,7 +86,7 @@ class UserAddressKeySecretProvider(
// New address key format -> user keys encrypt token + signature -> address passphrase.
cryptoContext.pgpCrypto.generateNewToken().use { passphrase ->
UserAddressKeySecret(
passphrase = passphrase.encryptWith(keyStoreCrypto),
passphrase = passphrase.encrypt(keyStoreCrypto),
token = userPrivateKey.encryptData(cryptoContext, passphrase.array),
signature = userPrivateKey.signData(cryptoContext, passphrase.array)
)
@ -102,7 +102,7 @@ class UserAddressKeySecretProvider(
isPrimary: Boolean
): UserAddressKey {
val secret = generateUserAddressKeySecret(userPrivateKey, generateOldFormat)
secret.passphrase.decryptWith(keyStoreCrypto).use { decryptedPassphrase ->
secret.passphrase.decrypt(keyStoreCrypto).use { decryptedPassphrase ->
val email = userAddress.emailSplit
val privateKey = PrivateKey(
key = cryptoContext.pgpCrypto.generateNewPrivateKey(

View File

@ -24,8 +24,8 @@ import me.proton.core.crypto.common.context.CryptoContext
import me.proton.core.crypto.common.keystore.EncryptedByteArray
import me.proton.core.crypto.common.keystore.EncryptedString
import me.proton.core.crypto.common.keystore.PlainByteArray
import me.proton.core.crypto.common.keystore.decryptWith
import me.proton.core.crypto.common.keystore.encryptWith
import me.proton.core.crypto.common.keystore.decrypt
import me.proton.core.crypto.common.keystore.encrypt
import me.proton.core.crypto.common.keystore.use
import me.proton.core.crypto.common.pgp.Armored
import me.proton.core.crypto.common.srp.Auth
@ -103,7 +103,7 @@ class UserManagerImpl(
?: return UnlockResult.Error.NoKeySaltsForPrimaryKey
val passphrase = pgp.getPassphrase(password.array, primaryKeySalt).use {
it.encryptWith(keyStore)
it.encrypt(keyStore)
}
return unlockWithPassphrase(userId, passphrase, refresh = false)
}
@ -139,7 +139,7 @@ class UserManagerImpl(
auth: Auth?,
orgPrivateKey: Armored?
): Boolean {
newPassword.decryptWith(keyStore).toByteArray().use { decryptedNewPassword ->
newPassword.decrypt(keyStore).toByteArray().use { decryptedNewPassword ->
val keySalt = pgp.generateNewKeySalt()
pgp.getPassphrase(decryptedNewPassword.array, keySalt).use { newPassphrase ->
val addresses = userAddressRepository.getAddresses(userId, refresh = true)
@ -157,7 +157,7 @@ class UserManagerImpl(
// Update organization key if provided.
val updatedOrgPrivateKey = orgPrivateKey?.let { key ->
val encryptedPassphrase = requireNotNull(passphraseRepository.getPassphrase(userId))
encryptedPassphrase.decryptWith(keyStore).use {
encryptedPassphrase.decrypt(keyStore).use {
key.updatePrivateKeyPassphrase(cryptoContext, it.array, newPassphrase.array)
}
}
@ -177,7 +177,7 @@ class UserManagerImpl(
)
// We know we can unlock the key with this passphrase as we just generated from it.
passphraseRepository.setPassphrase(userId, newPassphrase.encryptWith(keyStore))
passphraseRepository.setPassphrase(userId, newPassphrase.encrypt(keyStore))
// Refresh User and Addresses.
userAddressRepository.getAddresses(userId, refresh = true)
@ -202,7 +202,7 @@ class UserManagerImpl(
domain = domain,
passphrase = passphrase.array
)
val encryptedPassphrase = passphrase.encryptWith(keyStore)
val encryptedPassphrase = passphrase.encrypt(keyStore)
val userPrivateKey = PrivateKey(
key = privateKey,
isPrimary = true,

View File

@ -23,7 +23,7 @@ plugins {
kotlin("jvm")
}
libVersion = Version(1, 15, 0)
libVersion = Version(1, 15, 2)
dependencies {
implementation(

View File

@ -24,7 +24,7 @@ plugins {
kotlin("plugin.serialization")
}
libVersion = Version(1, 15, 0)
libVersion = Version(1, 15, 2)
dependencies {

View File

@ -17,6 +17,7 @@
*/
package me.proton.core.util.kotlin
import me.proton.core.util.kotlin.CoreLogger.set
import org.jetbrains.annotations.NonNls
/**
@ -58,4 +59,55 @@ interface Logger {
}
/** Type for all tags used in conjunction with [Logger.log]. */
inline class LoggerLogTag(val name: String)
@JvmInline
value class LoggerLogTag(val name: String)
/**
* Main object/singleton any Core module is using to log.
*
* Call [set] to set your own [Logger].
*/
object CoreLogger : Logger {
private var logger: Logger? = null
fun set(logger: Logger) {
this.logger = logger
}
override fun e(tag: String, e: Throwable) {
logger?.e(tag, e)
}
override fun e(tag: String, e: Throwable, message: String) {
logger?.e(tag, e, message)
}
override fun i(tag: String, message: String) {
logger?.i(tag, message)
}
override fun i(tag: String, e: Throwable, message: String) {
logger?.i(tag, e, message)
}
override fun d(tag: String, message: String) {
logger?.d(tag, message)
}
override fun d(tag: String, e: Throwable, message: String) {
logger?.d(tag, e, message)
}
override fun v(tag: String, message: String) {
logger?.v(tag, message)
}
override fun v(tag: String, e: Throwable, message: String) {
logger?.v(tag, e, message)
}
override fun log(tag: LoggerLogTag, message: String) {
logger?.log(tag, message)
}
}