Added userId to fetchOrganizationKeys request.

MAILAND-1527
This commit is contained in:
Tomasz Giszczak 2021-02-17 13:49:36 +01:00
parent d806a4fce2
commit ff38cd136a
10 changed files with 120 additions and 68 deletions

View File

@ -58,7 +58,8 @@ internal class CryptoTest {
//region One Address Key Setup
private val oneAddressKeyUserId = UserId("one_address_key_user")
private val oneAddressKeyMailboxPassword = "7NgO4d0h72zt4XuFLOUbg352vhrn.tu"
private val oneAddressKeyAddressId = "MMxTCP7DMErrWkOREQljdviitcZ1mDzjWcnzdg8wqObisClP25ILZ18vUsNgUVi-JD3O2EgmOuiEmx8C5qmofw=="
private val oneAddressKeyAddressId =
"MMxTCP7DMErrWkOREQljdviitcZ1mDzjWcnzdg8wqObisClP25ILZ18vUsNgUVi-JD3O2EgmOuiEmx8C5qmofw=="
private val oneAddressKeyUserKeyFingerprint = "20cf363b58ec99e722e53ec411c31e8e5e07f4d0"
private val armoredPrivateKey =
"""
@ -125,7 +126,7 @@ internal class CryptoTest {
dlEt7f8XNvX3HxQw9w==
=FW0u
-----END PGP PRIVATE KEY BLOCK-----
""".trimIndent()
""".trimIndent()
private val oneAddressKeyAddressKeys = listOf(
Keys(
"DvRZxrFRFUnjsL6MCOdGjyMZ9AoECd7kNXX9uKxmV-75K4iArEHijRPvd7Dhw43yBlCDxIsbNSW7cg2Uu5FUFg==",
@ -589,7 +590,7 @@ internal class CryptoTest {
a2j5Ak+MfeXknuaYqP6hs5BzulMTTJjM
=sm1p
-----END PGP PRIVATE KEY BLOCK-----
""".trimIndent(),
""".trimIndent(),
0,
1,
null,
@ -663,7 +664,7 @@ internal class CryptoTest {
b5BlAx31rUZ+NdCZyPnU+83opOdYrRjy
=kArG
-----END PGP PRIVATE KEY BLOCK-----
""".trimIndent(),
""".trimIndent(),
0,
0,
null,
@ -689,40 +690,40 @@ internal class CryptoTest {
private val userKeyMapper = UserKeyBridgeMapper()
private val oneKeyUserMock: ch.protonmail.android.domain.entity.user.User = mockk {
every { addresses } returns mockk {
every { findBy(AddressId(oneAddressKeyAddressId)) } answers { addresses[1] }
every { addresses } returns mapOf(
1 to mockk {
every { keys } returns mockk {
every { keys } returns oneAddressKeyAddressKeys.map(addressKeyMapper) { it.toNewModel() }
every { primaryKey } returns keys.first()
}
every { addresses } returns mockk {
every { findBy(AddressId(oneAddressKeyAddressId)) } answers { addresses[1] }
every { addresses } returns mapOf(
1 to mockk {
every { keys } returns mockk {
every { keys } returns oneAddressKeyAddressKeys.map(addressKeyMapper) { it.toNewModel() }
every { primaryKey } returns keys.first()
}
)
}
every { keys } returns mockk {
every { keys } returns oneAddressKeyUserKeys.map(userKeyMapper) { it.toNewModel() }
every { primaryKey } returns keys.first()
}
}
)
}
every { keys } returns mockk {
every { keys } returns oneAddressKeyUserKeys.map(userKeyMapper) { it.toNewModel() }
every { primaryKey } returns keys.first()
}
}
private val manyAddressKeysUserMock: ch.protonmail.android.domain.entity.user.User = mockk {
every { addresses } returns mockk {
every { findBy(AddressId(manyAddressKeysAddressId)) } answers { addresses[1] }
every { addresses } returns mapOf(
1 to mockk {
every { keys } returns mockk {
every { keys } returns manyAddressKeysAddressKeys.map(addressKeyMapper) { it.toNewModel() }
every { primaryKey } returns keys.first()
}
every { addresses } returns mockk {
every { findBy(AddressId(manyAddressKeysAddressId)) } answers { addresses[1] }
every { addresses } returns mapOf(
1 to mockk {
every { keys } returns mockk {
every { keys } returns manyAddressKeysAddressKeys.map(addressKeyMapper) { it.toNewModel() }
every { primaryKey } returns keys.first()
}
)
}
every { keys } returns mockk {
every { keys } returns manyAddressKeysUserKeys.map(userKeyMapper) { it.toNewModel() }
every { primaryKey } returns keys.first()
}
}
)
}
every { keys } returns mockk {
every { keys } returns manyAddressKeysUserKeys.map(userKeyMapper) { it.toNewModel() }
every { primaryKey } returns keys.first()
}
}
init {
mockkStatic(TextUtils::class)
@ -736,7 +737,11 @@ internal class CryptoTest {
every { keys } returns oneAddressKeyAddressKeys
}*/
every { userManagerMock.getUserBlocking(oneAddressKeyUserId) } returns oneKeyUserMock
every { userManagerMock.getUserPassphraseBlocking(oneAddressKeyUserId) } returns oneAddressKeyMailboxPassword.toByteArray()
every {
userManagerMock.getUserPassphraseBlocking(
oneAddressKeyUserId
)
} returns oneAddressKeyMailboxPassword.toByteArray()
// many address keys
/*
@ -745,7 +750,11 @@ internal class CryptoTest {
every { keys } returns manyAddressKeysAddressKeys
}*/
every { userManagerMock.getUserBlocking(manyAddressKeysUserId) } returns manyAddressKeysUserMock
every { userManagerMock.getUserPassphraseBlocking(manyAddressKeysUserId) } returns manyAddressKeysMailboxPassword.toByteArray()
every {
userManagerMock.getUserPassphraseBlocking(
manyAddressKeysUserId
)
} returns manyAddressKeysMailboxPassword.toByteArray()
// token and signature generation
every { userManagerMock.currentUserId } returns tokenAndSignatureUserId
@ -1609,7 +1618,7 @@ internal class CryptoTest {
every { mimeType } returns "text/rfc822-headers; protected-headers=\"v1\""
every { fileSize } returns 48
every { messageId } returns mockedMessageId
//every { headers } returns
// every { headers } returns
},
mockk {
every { attachmentId } returns "PGPAttachment/0-KKFldtlrtWQRGmOQR1V4QVsnqq7nuHw_nkmdDAd2xtIvibEqnV0IYVS3FfX-RT8BruIrL35HQ35rQkP6VbBg==/778b4f3c8e74a652aefbee588e67421a/1"
@ -1617,7 +1626,7 @@ internal class CryptoTest {
every { mimeType } returns "image/jpeg; name=\"elon.jpg\""
every { fileSize } returns 27723
every { messageId } returns mockedMessageId
//every { headers } returns
// every { headers } returns
}
)
@ -1652,8 +1661,12 @@ internal class CryptoTest {
@Test
fun check_key_passphrase() {
assertTrue(openPgp.checkPassphrase(oneAddressKeyAddressKeys[0].privateKey, oneAddressKeyMailboxPassword.toByteArray()))
assertFalse(openPgp.checkPassphrase(oneAddressKeyAddressKeys[0].privateKey, "incorrect key password".toByteArray()))
assertTrue(
openPgp.checkPassphrase(oneAddressKeyAddressKeys[0].privateKey, oneAddressKeyMailboxPassword.toByteArray())
)
assertFalse(
openPgp.checkPassphrase(oneAddressKeyAddressKeys[0].privateKey, "incorrect key password".toByteArray())
)
}
@Test
@ -1704,7 +1717,7 @@ internal class CryptoTest {
every { userManagerMock.getCurrentUserPassphrase() } returns passphrase
runBlocking {
val (token, signature) =
GenerateTokenAndSignature(userManagerMock, openPgpMock).invoke(null)
GenerateTokenAndSignature(userManagerMock, openPgpMock).invoke("")
val testMessage = newPGPMessageFromArmored(token)
val testKey = newKeyFromArmored(armoredPrivateKey)
val unlocked = testKey.unlock(passphrase)
@ -1714,7 +1727,9 @@ internal class CryptoTest {
assertEquals(randomTokenString, decryptedTokenString)
val armoredSignature = newPGPSignatureFromArmored(signature)
verificationKeyRing.verifyDetached(decryptedTokenPlainMessage, armoredSignature, com.proton.gopenpgp.crypto.Crypto.getUnixTime())
verificationKeyRing.verifyDetached(
decryptedTokenPlainMessage, armoredSignature, com.proton.gopenpgp.crypto.Crypto.getUnixTime()
)
}
}

View File

@ -32,10 +32,10 @@ import ch.protonmail.android.api.models.DeleteResponse
import ch.protonmail.android.api.models.DraftBody
import ch.protonmail.android.api.models.GetSubscriptionResponse
import ch.protonmail.android.api.models.IDList
import ch.protonmail.android.api.models.Keys
import ch.protonmail.android.api.models.LabelBody
import ch.protonmail.android.api.models.MailSettingsResponse
import ch.protonmail.android.api.models.MoveToFolderResponse
import ch.protonmail.android.api.models.OrganizationKeysResponse
import ch.protonmail.android.api.models.OrganizationResponse
import ch.protonmail.android.api.models.PaymentMethodsResponse
import ch.protonmail.android.api.models.PaymentsStatusResponse
@ -312,7 +312,8 @@ class ProtonMailApiManager @Inject constructor(var api: ProtonMailApi) :
override suspend fun fetchOrganization(userId: UserId): ApiResult<OrganizationResponse> =
api.fetchOrganization(userId)
override suspend fun fetchOrganizationKeys(): ApiResult<Keys> = api.fetchOrganizationKeys()
override suspend fun fetchOrganizationKeys(userId: UserId): ApiResult<OrganizationKeysResponse> =
api.fetchOrganizationKeys(userId)
override suspend fun fetchSubscription(): GetSubscriptionResponse = api.fetchSubscription()

View File

@ -0,0 +1,36 @@
/*
* Copyright (c) 2020 Proton Technologies AG
*
* This file is part of ProtonMail.
*
* ProtonMail is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ProtonMail is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with ProtonMail. If not, see https://www.gnu.org/licenses/.
*/
package ch.protonmail.android.api.models
import ch.protonmail.android.api.utils.Fields
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
data class OrganizationKeysResponse(
@SerialName(Fields.Response.CODE)
val code: Int,
@SerialName(Fields.Keys.KeyBody.PUBLIC_KEY)
val publicKey: String,
@SerialName(Fields.Keys.KeyBody.PRIVATE_KEY)
val privateKey: String,
)

View File

@ -26,7 +26,7 @@ import androidx.work.WorkManager
import androidx.work.WorkerParameters
import androidx.work.workDataOf
import ch.protonmail.android.api.ProtonMailApiManager
import ch.protonmail.android.api.models.Keys
import ch.protonmail.android.api.models.OrganizationKeysResponse
import ch.protonmail.android.core.UserManager
import ch.protonmail.android.domain.entity.user.Address
import ch.protonmail.android.domain.entity.user.AddressKey
@ -81,24 +81,25 @@ class AddressKeyActivationWorker @AssistedInject constructor(
// get orgKeys if existent -> This will work only if user is an organisation owner, otherwise
// backend will return 403 for all other users (e.g. just members of an organisation)
val orgKeysResult = if (context.app.organization != null) {
api.fetchOrganizationKeys()
api.fetchOrganizationKeys(UserId(user.name.s))
} else {
null
}
val orgKeys = if (orgKeysResult is ApiResult.Success) {
orgKeysResult.value
val errors = if (orgKeysResult is ApiResult.Success) {
val keysResponse = orgKeysResult.value
keysToActivate.map {
activateKey(it, user.keys, mailboxPassword, keysResponse, it in primaryKeys)
}.filterIsInstance<ActivationResult.Error>()
} else {
null
}
val errors = keysToActivate.map {
activateKey(it, user.keys, mailboxPassword, orgKeys, it in primaryKeys)
}.filterIsInstance<ActivationResult.Error>()
val failedCountMessage = "impossible to activate ${errors.size} keys out of ${keysToActivate.size}"
val failedCountMessage = "impossible to activate ${errors?.size} keys out of ${keysToActivate.size}"
return@withContext when {
errors.isEmpty() -> Result.success()
errors.isNullOrEmpty() -> Result.success()
errors.any { it is ActivationResult.Error.Network } -> {
Timber.w("Network error: $failedCountMessage, runAttemptCount: $runAttemptCount")
if (runAttemptCount < MAX_RETRY_COUNT) {
@ -118,7 +119,7 @@ class AddressKeyActivationWorker @AssistedInject constructor(
addressKey: AddressKey,
userKeys: UserKeys,
mailboxPassword: ByteArray,
orgKeys: Keys?,
orgKeysResponse: OrganizationKeysResponse,
isPrimary: Boolean
): ActivationResult {
return try {
@ -163,7 +164,7 @@ class AddressKeyActivationWorker @AssistedInject constructor(
} else {
Timber.v("Activate key for non-legacy user")
val generatedTokenAndSignature =
GenerateTokenAndSignature(userManager, openPgp).invoke(orgKeys?.toUserKey())
GenerateTokenAndSignature(userManager, openPgp).invoke(orgKeysResponse.privateKey)
val keyActivationBody = KeyActivationBody(
newPrivateKey,
signedKeyList,

View File

@ -18,7 +18,7 @@
*/
package ch.protonmail.android.api.segments.organization
import ch.protonmail.android.api.models.Keys
import ch.protonmail.android.api.models.OrganizationKeysResponse
import ch.protonmail.android.api.models.OrganizationResponse
import me.proton.core.domain.entity.UserId
import me.proton.core.network.data.ApiProvider
@ -31,8 +31,8 @@ class OrganizationApi(private val apiProvider: ApiProvider) : OrganizationApiSpe
fetchOrganization()
}
override suspend fun fetchOrganizationKeys(): ApiResult<Keys> =
apiProvider.get<OrganizationService>().invoke {
override suspend fun fetchOrganizationKeys(userId: UserId): ApiResult<OrganizationKeysResponse> =
apiProvider.get<OrganizationService>(userId).invoke {
fetchOrganizationsKeys()
}
}

View File

@ -18,7 +18,7 @@
*/
package ch.protonmail.android.api.segments.organization
import ch.protonmail.android.api.models.Keys
import ch.protonmail.android.api.models.OrganizationKeysResponse
import ch.protonmail.android.api.models.OrganizationResponse
import me.proton.core.domain.entity.UserId
import me.proton.core.network.domain.ApiResult
@ -27,6 +27,6 @@ interface OrganizationApiSpec {
suspend fun fetchOrganization(userId: UserId): ApiResult<OrganizationResponse>
suspend fun fetchOrganizationKeys(): ApiResult<Keys>
suspend fun fetchOrganizationKeys(userId: UserId): ApiResult<OrganizationKeysResponse>
}

View File

@ -18,7 +18,7 @@
*/
package ch.protonmail.android.api.segments.organization
import ch.protonmail.android.api.models.Keys
import ch.protonmail.android.api.models.OrganizationKeysResponse
import ch.protonmail.android.api.models.OrganizationResponse
import ch.protonmail.android.api.segments.RetrofitConstants.ACCEPT_HEADER_V1
import ch.protonmail.android.api.segments.RetrofitConstants.CONTENT_TYPE
@ -34,6 +34,6 @@ interface OrganizationService : BaseRetrofitApi {
@GET("organizations/keys")
@Headers(CONTENT_TYPE, ACCEPT_HEADER_V1)
suspend fun fetchOrganizationsKeys(): Keys
suspend fun fetchOrganizationsKeys(): OrganizationKeysResponse
}

View File

@ -361,6 +361,7 @@ object Fields {
object KeyBody {
const val FLAGS = "Flags"
const val PUBLIC_KEY = "PublicKey"
const val PRIVATE_KEY = "PrivateKey"
}
}

View File

@ -35,10 +35,11 @@ class GetOrganizationJob : ProtonMailBaseJob(
@Throws(Throwable::class)
override fun onRun() {
runBlocking {
val response = getApi().fetchOrganization(requireNotNull(userId))
val userId = requireNotNull(userId)
val response = getApi().fetchOrganization(userId)
val keysResponse = if (response is ApiResult.Success) {
Timber.v("fetching Organization Keys")
getApi().fetchOrganizationKeys()
getApi().fetchOrganizationKeys(userId)
} else {
Timber.i("Get Organization failure: $response")
null

View File

@ -20,7 +20,6 @@
package ch.protonmail.android.usecase.crypto
import ch.protonmail.android.core.UserManager
import ch.protonmail.android.domain.entity.user.UserKey
import ch.protonmail.android.utils.crypto.OpenPGP
import com.proton.gopenpgp.crypto.Crypto
import javax.inject.Inject
@ -29,7 +28,7 @@ class GenerateTokenAndSignature @Inject constructor (
private val userManager: UserManager,
private val openPgp: OpenPGP
) {
operator fun invoke(orgKeys: UserKey?): TokenAndSignature {
operator fun invoke(privateKey: String): TokenAndSignature {
val user = userManager.currentUser
val secret = openPgp.randomToken()
val tokenString = secret.joinToString("") { String.format("%02x", (it.toInt() and 0xff)) }
@ -39,10 +38,8 @@ class GenerateTokenAndSignature @Inject constructor (
val unlockedUserKey = Crypto.newKeyFromArmored(armoredPrivateKey).unlock(mailboxPassword)
val tokenKeyRing = Crypto.newKeyRing(unlockedUserKey)
if (orgKeys != null) {
val unlockedOrgKey = Crypto.newKeyFromArmored(orgKeys.privateKey.content.s).unlock(mailboxPassword)
tokenKeyRing.addKey(unlockedOrgKey)
}
val unlockedOrgKey = Crypto.newKeyFromArmored(privateKey).unlock(mailboxPassword)
tokenKeyRing.addKey(unlockedOrgKey)
val token = tokenKeyRing.encrypt(binMessage, null).armored
val signature = tokenKeyRing.signDetached(binMessage).armored