diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/node/NodeRepository.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/node/NodeRepository.kt index e0ffab2a11..e82d90a31a 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/node/NodeRepository.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/node/NodeRepository.kt @@ -10,7 +10,6 @@ import android.os.AsyncTask import androidx.lifecycle.MutableLiveData import com.flowcrypt.email.api.retrofit.LoadingState import com.flowcrypt.email.api.retrofit.request.node.DecryptKeyRequest -import com.flowcrypt.email.api.retrofit.request.node.GenerateKeyRequest import com.flowcrypt.email.api.retrofit.request.node.NodeRequest import com.flowcrypt.email.api.retrofit.request.node.NodeRequestWrapper import com.flowcrypt.email.api.retrofit.request.node.ParseDecryptMsgRequest @@ -19,12 +18,10 @@ import com.flowcrypt.email.api.retrofit.request.node.ZxcvbnStrengthBarRequest import com.flowcrypt.email.api.retrofit.response.base.Result import com.flowcrypt.email.api.retrofit.response.node.BaseNodeResponse import com.flowcrypt.email.api.retrofit.response.node.DecryptKeyResult -import com.flowcrypt.email.api.retrofit.response.node.GenerateKeyResult import com.flowcrypt.email.api.retrofit.response.node.NodeResponseWrapper import com.flowcrypt.email.api.retrofit.response.node.ParseDecryptedMsgResult import com.flowcrypt.email.api.retrofit.response.node.ParseKeysResult import com.flowcrypt.email.api.retrofit.response.node.ZxcvbnStrengthBarResult -import com.flowcrypt.email.model.PgpContact import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext @@ -64,12 +61,6 @@ class NodeRepository : PgpApiRepository { load(requestCode, liveData, request) } - override suspend fun createPrivateKey(context: Context, passphrase: String, pgpContacts: List): Result = - withContext(Dispatchers.IO) { - val apiService = NodeRetrofitHelper.getRetrofit()!!.create(NodeService::class.java) - getResult(call = { apiService.generateKeySuspend(GenerateKeyRequest(passphrase, pgpContacts)) }) - } - override suspend fun zxcvbnStrengthBar(context: Context, guesses: Double): Result = withContext(Dispatchers.IO) { val apiService = NodeRetrofitHelper.getRetrofit()!!.create(NodeService::class.java) diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/node/NodeService.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/node/NodeService.kt index 88adc1e8b8..aa6848c529 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/node/NodeService.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/node/NodeService.kt @@ -11,7 +11,6 @@ import com.flowcrypt.email.api.retrofit.request.node.DecryptKeyRequest import com.flowcrypt.email.api.retrofit.request.node.EncryptFileRequest import com.flowcrypt.email.api.retrofit.request.node.EncryptKeyRequest import com.flowcrypt.email.api.retrofit.request.node.EncryptMsgRequest -import com.flowcrypt.email.api.retrofit.request.node.GenerateKeyRequest import com.flowcrypt.email.api.retrofit.request.node.GmailBackupSearchRequest import com.flowcrypt.email.api.retrofit.request.node.ParseDecryptMsgRequest import com.flowcrypt.email.api.retrofit.request.node.ParseKeysRequest @@ -23,7 +22,6 @@ import com.flowcrypt.email.api.retrofit.response.node.DecryptedFileResult import com.flowcrypt.email.api.retrofit.response.node.EncryptKeyResult import com.flowcrypt.email.api.retrofit.response.node.EncryptedFileResult import com.flowcrypt.email.api.retrofit.response.node.EncryptedMsgResult -import com.flowcrypt.email.api.retrofit.response.node.GenerateKeyResult import com.flowcrypt.email.api.retrofit.response.node.GmailBackupSearchResult import com.flowcrypt.email.api.retrofit.response.node.ParseDecryptedMsgResult import com.flowcrypt.email.api.retrofit.response.node.ParseKeysResult @@ -87,12 +85,6 @@ interface NodeService { @Streaming fun decryptFile(@Body request: DecryptFileRequest): Call - @POST("/") - fun generateKey(@Body request: GenerateKeyRequest): Call - - @POST("/") - suspend fun generateKeySuspend(@Body request: GenerateKeyRequest): Response - @POST("/") fun zxcvbnStrengthBar(@Body request: ZxcvbnStrengthBarRequest): Call diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/node/PgpApiRepository.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/node/PgpApiRepository.kt index acfc563c3f..0bfb547966 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/node/PgpApiRepository.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/node/PgpApiRepository.kt @@ -14,12 +14,10 @@ import com.flowcrypt.email.api.retrofit.request.node.ZxcvbnStrengthBarRequest import com.flowcrypt.email.api.retrofit.response.base.Result import com.flowcrypt.email.api.retrofit.response.model.node.NodeKeyDetails import com.flowcrypt.email.api.retrofit.response.node.DecryptKeyResult -import com.flowcrypt.email.api.retrofit.response.node.GenerateKeyResult import com.flowcrypt.email.api.retrofit.response.node.NodeResponseWrapper import com.flowcrypt.email.api.retrofit.response.node.ParseDecryptedMsgResult import com.flowcrypt.email.api.retrofit.response.node.ParseKeysResult import com.flowcrypt.email.api.retrofit.response.node.ZxcvbnStrengthBarResult -import com.flowcrypt.email.model.PgpContact /** * It's an entry point of all requests to work with PGP actions. @@ -68,16 +66,6 @@ interface PgpApiRepository : BaseApiRepository { suspend fun decryptKey(context: Context, armoredKey: String, passphrases: List): Result - /** - * Generate a private key using the given parameters. - * - * @param passphrase The given passphrase. - * @param pgpContacts A list of contacts. - * @return A result with an instance of [GenerateKeyResult] - */ - suspend fun createPrivateKey(context: Context, passphrase: String, pgpContacts: List): Result - - /** * Check the passphrase strong value. * diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/request/node/GenerateKeyRequest.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/request/node/GenerateKeyRequest.kt deleted file mode 100644 index f43b468e16..0000000000 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/request/node/GenerateKeyRequest.kt +++ /dev/null @@ -1,54 +0,0 @@ -/* - * © 2016-present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com - * Contributors: DenBond7 - */ - -package com.flowcrypt.email.api.retrofit.request.node - -import com.flowcrypt.email.api.retrofit.node.NodeService -import com.flowcrypt.email.model.PgpContact -import com.google.gson.annotations.Expose -import retrofit2.Response -import java.util.* - -/** - * Using this class we can create a request to create a new key with the given parameters. - * - * @author Denis Bondarenko - * Date: 4/1/19 - * Time: 9:44 AM - * E-mail: DenBond7@gmail.com - */ -class GenerateKeyRequest(@Expose val passphrase: String, - pgpContacts: List) : BaseNodeRequest() { - - @Expose - private val variant: String - - @Expose - private val userIds: MutableList - - override val endpoint: String = "generateKey" - - init { - this.variant = KEY_VARIANT_CURVE25519 // default, not yet configurable - this.userIds = ArrayList() - for ((email, name) in pgpContacts) { - userIds.add(UserId(email, name ?: email)) - // todo - fix https://github.com/FlowCrypt/flowcrypt-android/issues/591 - } - } - - override fun getResponse(nodeService: NodeService): Response<*> { - return nodeService.generateKey(this).execute() - } - - private class UserId internal constructor(@Expose val email: String, - @Expose val name: String) - - companion object { - private const val KEY_VARIANT_CURVE25519 = "curve25519" - private const val KEY_VARIANT_RSA2048 = "rsa2048" - private const val KEY_VARIANT_RSA4096 = "rsa4096" - } -} diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/response/node/GenerateKeyResult.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/response/node/GenerateKeyResult.kt deleted file mode 100644 index 7a1bc85b66..0000000000 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/response/node/GenerateKeyResult.kt +++ /dev/null @@ -1,53 +0,0 @@ -/* - * © 2016-present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com - * Contributors: DenBond7 - */ - -package com.flowcrypt.email.api.retrofit.response.node - -import android.os.Parcel -import android.os.Parcelable -import com.flowcrypt.email.api.retrofit.response.base.ApiError -import com.flowcrypt.email.api.retrofit.response.model.node.NodeKeyDetails -import com.google.gson.annotations.Expose -import com.google.gson.annotations.SerializedName -import java.io.BufferedInputStream - -/** - * It's a result for "generateKey" requests. - * - * @author Denis Bondarenko - * Date: 4/1/19 - * Time: 9:44 AM - * E-mail: DenBond7@gmail.com - */ -data class GenerateKeyResult constructor(@Expose val key: NodeKeyDetails?, - @SerializedName("error") - @Expose override val apiError: ApiError?) : BaseNodeResponse { - override fun handleRawData(bufferedInputStream: BufferedInputStream) { - - } - - constructor(source: Parcel) : this( - source.readParcelable(NodeKeyDetails::class.java.classLoader), - source.readParcelable(ApiError::class.java.classLoader) - ) - - override fun describeContents(): Int { - return 0 - } - - override fun writeToParcel(dest: Parcel, flags: Int) = - with(dest) { - writeParcelable(key, flags) - writeParcelable(apiError, flags) - } - - companion object { - @JvmField - val CREATOR: Parcelable.Creator = object : Parcelable.Creator { - override fun createFromParcel(source: Parcel): GenerateKeyResult = GenerateKeyResult(source) - override fun newArray(size: Int): Array = arrayOfNulls(size) - } - } -} diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/extensions/KeyRingInfoExt.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/extensions/KeyRingInfoExt.kt new file mode 100644 index 0000000000..f469671805 --- /dev/null +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/extensions/KeyRingInfoExt.kt @@ -0,0 +1,35 @@ +/* + * © 2016-present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com + * Contributors: DenBond7 + */ + +package com.flowcrypt.email.extensions + +import org.pgpainless.algorithm.SymmetricKeyAlgorithm +import org.pgpainless.key.info.KeyRingInfo + +/** + * @author Denis Bondarenko + * Date: 3/11/21 + * Time: 10:33 AM + * E-mail: DenBond7@gmail.com + */ + +/** + * Return true when every secret key on the key ring is encrypted. + * If there is at least one unencrypted secret key on the ring, return false. + * If the ring is a [PGPPublicKeyRing], return false. + * + * @return true if all secret keys are encrypted. + */ +fun KeyRingInfo.isFullyEncrypted(): Boolean { + if (isSecretKey) { + for (secretKey in secretKeys) { + if (secretKey.keyEncryptionAlgorithm == SymmetricKeyAlgorithm.NULL.algorithmId) { + return false + } + } + + return true + } else return false +} \ No newline at end of file diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/extensions/PGPKeyRingExt.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/extensions/PGPKeyRingExt.kt new file mode 100644 index 0000000000..4d9f715d56 --- /dev/null +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/extensions/PGPKeyRingExt.kt @@ -0,0 +1,74 @@ +/* + * © 2016-present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com + * Contributors: DenBond7 + */ + +package com.flowcrypt.email.extensions + +import com.flowcrypt.email.api.retrofit.response.model.node.Algo +import com.flowcrypt.email.api.retrofit.response.model.node.KeyId +import com.flowcrypt.email.api.retrofit.response.model.node.NodeKeyDetails +import com.flowcrypt.email.security.pgp.PgpArmorUtils +import org.bouncycastle.openpgp.PGPKeyRing +import org.bouncycastle.openpgp.PGPSecretKeyRing +import org.pgpainless.algorithm.PublicKeyAlgorithm +import org.pgpainless.key.OpenPgpV4Fingerprint +import org.pgpainless.key.generation.type.eddsa.EdDSACurve +import org.pgpainless.key.info.KeyInfo +import org.pgpainless.key.info.KeyRingInfo +import org.pgpainless.key.util.KeyRingUtils +import java.util.concurrent.TimeUnit + +/** + * @author Denis Bondarenko + * Date: 3/11/21 + * Time: 10:08 AM + * E-mail: DenBond7@gmail.com + */ +fun PGPKeyRing.toNodeKeyDetails(): NodeKeyDetails { + val keyRingInfo = KeyRingInfo(this) + + val algo = Algo( + algorithm = keyRingInfo.algorithm.name, + algorithmId = keyRingInfo.algorithm.algorithmId, + bits = if (keyRingInfo.publicKey.bitStrength != -1) keyRingInfo.publicKey.bitStrength else 0, + curve = when (keyRingInfo.algorithm) { + PublicKeyAlgorithm.ECDSA, PublicKeyAlgorithm.ECDH -> KeyInfo.getCurveName(publicKey) + PublicKeyAlgorithm.EDDSA -> EdDSACurve._Ed25519.getName() // for EDDSA KeyInfo.getCurveName(publicKey) return null + else -> null + } + ) + + val ids = publicKeys.iterator().asSequence().toList() + .map { + val fingerprint = OpenPgpV4Fingerprint(it) + KeyId( + fingerprint = fingerprint.toString(), + longId = fingerprint.takeLast(16).toString(), + shortId = fingerprint.takeLast(8).toString(), + keywords = ""//skipped as deprecated + ) + } + + val privateKey = if (keyRingInfo.isSecretKey) PgpArmorUtils.toAsciiArmoredString(this) else null + val publicKey = if (keyRingInfo.isSecretKey) { + PgpArmorUtils.toAsciiArmoredString(KeyRingUtils.publicKeyRingFrom(this as PGPSecretKeyRing?)) + } else { + PgpArmorUtils.toAsciiArmoredString(this) + } + + return NodeKeyDetails( + isFullyDecrypted = keyRingInfo.isFullyDecrypted, + isFullyEncrypted = keyRingInfo.isFullyEncrypted(), + privateKey = privateKey, + publicKey = publicKey, + users = keyRingInfo.userIds, + ids = ids, + created = TimeUnit.SECONDS.convert(keyRingInfo.creationDate.time, TimeUnit.MILLISECONDS), + lastModified = TimeUnit.SECONDS.convert(keyRingInfo.lastModified.time, TimeUnit.MILLISECONDS), + expiration = TimeUnit.SECONDS.convert(keyRingInfo.expirationDate?.time + ?: 0, TimeUnit.MILLISECONDS), + algo = algo, + passphrase = null, + errorMsg = null) +} diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/PrivateKeysViewModel.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/PrivateKeysViewModel.kt index 9be0725793..a64e84f540 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/PrivateKeysViewModel.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/PrivateKeysViewModel.kt @@ -33,6 +33,7 @@ import com.flowcrypt.email.api.retrofit.response.model.node.NodeKeyDetails import com.flowcrypt.email.api.retrofit.response.node.ParseKeysResult import com.flowcrypt.email.database.entity.AccountEntity import com.flowcrypt.email.database.entity.ActionQueueEntity +import com.flowcrypt.email.extensions.toNodeKeyDetails import com.flowcrypt.email.model.KeyDetails import com.flowcrypt.email.model.KeyImportModel import com.flowcrypt.email.model.PgpContact @@ -51,6 +52,8 @@ import com.google.android.gms.common.util.CollectionUtils import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import org.pgpainless.PGPainless +import org.pgpainless.key.util.UserId import java.util.* /** @@ -261,8 +264,9 @@ class PrivateKeysViewModel(application: Application) : BaseNodeApiViewModel(appl createPrivateKeyLiveData.value = Result.loading() var nodeKeyDetails: NodeKeyDetails? = null try { - nodeKeyDetails = genPrivateKeyViaNode(passphrase, accountEntity) - requireNotNull(nodeKeyDetails) + nodeKeyDetails = PGPainless.generateKeyRing().simpleEcKeyRing( + UserId.nameAndEmail(accountEntity.displayName + ?: accountEntity.email, accountEntity.email), passphrase).toNodeKeyDetails() val existedAccount = roomDatabase.accountDao().getAccountSuspend(accountEntity.email.toLowerCase(Locale.US)) if (existedAccount == null) { @@ -307,28 +311,6 @@ class PrivateKeysViewModel(application: Application) : BaseNodeApiViewModel(appl } } - private suspend fun genPrivateKeyViaNode(passphrase: String, accountEntity: AccountEntity): NodeKeyDetails? { - val generateKeyResult = nodeRepository.createPrivateKey(getApplication(), passphrase, genContacts(accountEntity)) - when (generateKeyResult.status) { - Result.Status.SUCCESS -> { - return generateKeyResult.data?.key - } - - Result.Status.EXCEPTION -> { - generateKeyResult.exception?.let { exception -> throw exception } - } - - Result.Status.ERROR -> { - generateKeyResult.data?.apiError?.let { apiError -> throw ApiException(apiError) } - } - - else -> { - // all looks well - } - } - return null - } - private suspend fun savePrivateKeyToDatabase(accountEntity: AccountEntity, nodeKeyDetails: NodeKeyDetails, passphrase: String) { val keyEntity = nodeKeyDetails.toKeyEntity(accountEntity).copy( source = KeyDetails.Type.NEW.toPrivateKeySourceTypeString(), diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/security/pgp/PgpArmorUtils.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/security/pgp/PgpArmorUtils.kt new file mode 100644 index 0000000000..489ff4bf2c --- /dev/null +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/security/pgp/PgpArmorUtils.kt @@ -0,0 +1,38 @@ +/* + * © 2016-present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com + * Contributors: DenBond7 + */ + +package com.flowcrypt.email.security.pgp + +import com.flowcrypt.email.BuildConfig +import org.bouncycastle.bcpg.ArmoredOutputStream +import org.bouncycastle.openpgp.PGPKeyRing +import java.io.ByteArrayOutputStream +import java.io.IOException + +/** + * @author Denis Bondarenko + * Date: 3/11/21 + * Time: 1:49 PM + * E-mail: DenBond7@gmail.com + */ +object PgpArmorUtils { + private const val HEADER_NAME_COMMENT = "Comment" + + @Throws(IOException::class) + fun toAsciiArmoredString(pgpKeyRing: PGPKeyRing): String { + ByteArrayOutputStream().use { out -> + ArmoredOutputStream(out).use { armoredOut -> + addHeaders(armoredOut) + pgpKeyRing.encode(armoredOut) + } + return out.toString() + } + } + + private fun addHeaders(armoredOutputStream: ArmoredOutputStream) { + armoredOutputStream.setHeader(ArmoredOutputStream.VERSION_HDR, "FlowCrypt ${BuildConfig.VERSION_NAME} Gmail Encryption") + armoredOutputStream.setHeader(HEADER_NAME_COMMENT, "Seamlessly send and receive encrypted email") + } +} \ No newline at end of file