diff --git a/app/src/main/java/com/idormy/sms/forwarder/fragment/senders/EmailFragment.kt b/app/src/main/java/com/idormy/sms/forwarder/fragment/senders/EmailFragment.kt index ca566723..6e05563a 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/fragment/senders/EmailFragment.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/fragment/senders/EmailFragment.kt @@ -24,6 +24,7 @@ import com.idormy.sms.forwarder.database.viewmodel.SenderViewModel import com.idormy.sms.forwarder.databinding.FragmentSendersEmailBinding import com.idormy.sms.forwarder.entity.MsgInfo import com.idormy.sms.forwarder.entity.setting.EmailSetting +import com.idormy.sms.forwarder.utils.Base64 import com.idormy.sms.forwarder.utils.CommonUtils import com.idormy.sms.forwarder.utils.EVENT_TOAST_ERROR import com.idormy.sms.forwarder.utils.KEY_SENDER_CLONE @@ -51,6 +52,7 @@ import io.reactivex.disposables.Disposable import io.reactivex.schedulers.Schedulers import org.pgpainless.PGPainless import org.pgpainless.key.info.KeyRingInfo +import java.io.ByteArrayInputStream import java.io.File import java.io.FileInputStream import java.security.KeyStore @@ -377,8 +379,14 @@ class EmailFragment : BaseFragment(), View.OnClick cert.first.isNotEmpty() && cert.second.isNotEmpty() -> { try { // 判断是否有效的PKCS12私钥证书 + val fileInputStream = if (cert.first.startsWith("/")) { + FileInputStream(cert.first) + } else { + val decodedBytes = Base64.decode(cert.first) + ByteArrayInputStream(decodedBytes) + } val keyStore = KeyStore.getInstance("PKCS12") - keyStore.load(FileInputStream(cert.first), cert.second.toCharArray()) + keyStore.load(fileInputStream, cert.second.toCharArray()) val alias = keyStore.aliases().nextElement() val recipientPublicKey = keyStore.getCertificate(alias) as X509Certificate Log.d(TAG, "PKCS12 Certificate: $recipientPublicKey") @@ -391,8 +399,13 @@ class EmailFragment : BaseFragment(), View.OnClick cert.first.isNotEmpty() && cert.second.isEmpty() -> { try { // 判断是否有效的X.509公钥证书 + val fileInputStream = if (cert.first.startsWith("/")) { + FileInputStream(cert.first) + } else { + val decodedBytes = Base64.decode(cert.first) + ByteArrayInputStream(decodedBytes) + } val certFactory = CertificateFactory.getInstance("X.509") - val fileInputStream = FileInputStream(cert.first) val recipientPublicKey = certFactory.generateCertificate(fileInputStream) as X509Certificate Log.d(TAG, "X.509 Certificate: $recipientPublicKey") } catch (e: Exception) { @@ -408,7 +421,12 @@ class EmailFragment : BaseFragment(), View.OnClick cert.first.isNotEmpty() && cert.second.isNotEmpty() -> { try { //从私钥证书文件提取公钥 - val recipientPrivateKeyStream = FileInputStream(cert.first) + val recipientPrivateKeyStream = if (cert.first.startsWith("/")) { + FileInputStream(cert.first) + } else { + val decodedBytes = Base64.decode(cert.first) + ByteArrayInputStream(decodedBytes) + } val recipientPGPSecretKeyRing = PGPainless.readKeyRing().secretKeyRing(recipientPrivateKeyStream) val recipientPGPPublicKeyRing = PGPainless.extractCertificate(recipientPGPSecretKeyRing!!) val keyInfo = KeyRingInfo(recipientPGPPublicKeyRing) @@ -422,7 +440,12 @@ class EmailFragment : BaseFragment(), View.OnClick cert.first.isNotEmpty() && cert.second.isEmpty() -> { try { //从证书文件提取公钥 - val recipientPublicKeyStream = FileInputStream(cert.first) + val recipientPublicKeyStream = if (cert.first.startsWith("/")) { + FileInputStream(cert.first) + } else { + val decodedBytes = Base64.decode(cert.first) + ByteArrayInputStream(decodedBytes) + } val recipientPGPPublicKeyRing = PGPainless.readKeyRing().publicKeyRing(recipientPublicKeyStream) val keyInfo = KeyRingInfo(recipientPGPPublicKeyRing!!) Log.d(TAG, "recipientPGPPublicKeyRing: $keyInfo") @@ -448,7 +471,12 @@ class EmailFragment : BaseFragment(), View.OnClick val keystore = binding!!.etSenderKeystore.text.toString().trim() val password = binding!!.etSenderPassword.text.toString().trim() if (keystore.isNotEmpty()) { - val senderPrivateKeyStream = FileInputStream(keystore) + val senderPrivateKeyStream = if (keystore.startsWith("/")) { + FileInputStream(keystore) + } else { + val decodedBytes = Base64.decode(keystore) + ByteArrayInputStream(decodedBytes) + } if (senderPrivateKeyStream.available() <= 0) { throw Exception(getString(R.string.invalid_sender_keystore)) } @@ -567,12 +595,12 @@ class EmailFragment : BaseFragment(), View.OnClick return } MaterialDialog.Builder(requireContext()) - .title(getString(R.string.keystore_path)) + .title(getString(R.string.keystore_base64)) .content(String.format(getString(R.string.root_directory), downloadPath)) .items(fileList) .itemsCallbackSingleChoice(0) { _: MaterialDialog?, _: View?, _: Int, text: CharSequence -> val webPath = "$downloadPath/$text" - etKeyStore.setText(webPath) + etKeyStore.setText(convertCertToBase64String(webPath)) true // allow selection } .positiveText(R.string.select) @@ -614,6 +642,12 @@ class EmailFragment : BaseFragment(), View.OnClick return supportedExtensions.any { it.equals(file.extension, ignoreCase = true) } } + private fun convertCertToBase64String(pfxFilePath: String): String { + val pfxInputStream = FileInputStream(pfxFilePath) + val pfxBytes = pfxInputStream.readBytes() + return Base64.encode(pfxBytes) + } + override fun onDestroyView() { if (mCountDownHelper != null) mCountDownHelper!!.recycle() super.onDestroyView() diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/sender/EmailUtils.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/sender/EmailUtils.kt index 5eb9b3b6..67275562 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/utils/sender/EmailUtils.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/sender/EmailUtils.kt @@ -4,6 +4,7 @@ import com.idormy.sms.forwarder.R import com.idormy.sms.forwarder.database.entity.Rule import com.idormy.sms.forwarder.entity.MsgInfo import com.idormy.sms.forwarder.entity.setting.EmailSetting +import com.idormy.sms.forwarder.utils.Base64 import com.idormy.sms.forwarder.utils.Log import com.idormy.sms.forwarder.utils.SendUtils import com.idormy.sms.forwarder.utils.SettingUtils @@ -16,6 +17,7 @@ import org.bouncycastle.openpgp.PGPPublicKeyRing import org.bouncycastle.openpgp.PGPSecretKeyRing import org.pgpainless.PGPainless import org.pgpainless.key.info.KeyRingInfo +import java.io.ByteArrayInputStream import java.io.FileInputStream import java.security.KeyStore import java.security.PrivateKey @@ -166,8 +168,13 @@ class EmailUtils { var senderPGPSecretKeyPassword = "" if (!setting.keystore.isNullOrEmpty() && !setting.password.isNullOrEmpty()) { - val keystoreStream = FileInputStream(setting.keystore) try { + val keystoreStream = if (setting.keystore!!.startsWith("/")) { + FileInputStream(setting.keystore) + } else { + val decodedBytes = Base64.decode(setting.keystore!!) + ByteArrayInputStream(decodedBytes) + } when (setting.encryptionProtocol) { "S/MIME" -> { val keystorePassword = setting.password.toString() @@ -206,15 +213,21 @@ class EmailUtils { //逐一发送加密邮件 val recipientsWithoutCert = mutableListOf() setting.recipients.forEach { (email, cert) -> - val keystorePath = cert.first + val keystoreBase64 = cert.first val keystorePassword = cert.second var recipientX509Cert: X509Certificate? = null var recipientPGPPublicKeyRing: PGPPublicKeyRing? = null try { when { //从私钥证书文件提取公钥 - keystorePath.isNotEmpty() && keystorePassword.isNotEmpty() -> { - val keystoreStream = FileInputStream(keystorePath) + keystoreBase64.isNotEmpty() && keystorePassword.isNotEmpty() -> { + val keystoreStream = if (keystoreBase64.startsWith("/")) { + FileInputStream(keystoreBase64) + } else { + val decodedBytes = Base64.decode(keystoreBase64) + ByteArrayInputStream(decodedBytes) + } + when (setting.encryptionProtocol) { "S/MIME" -> { val keyStore = KeyStore.getInstance("PKCS12") @@ -235,12 +248,18 @@ class EmailUtils { } //从证书文件提取公钥 - keystorePath.isNotEmpty() && keystorePassword.isEmpty() -> { - val keystoreStream = FileInputStream(keystorePath) + keystoreBase64.isNotEmpty() && keystorePassword.isEmpty() -> { + val keystoreStream = if (keystoreBase64.startsWith("/")) { + FileInputStream(keystoreBase64) + } else { + val decodedBytes = Base64.decode(keystoreBase64) + ByteArrayInputStream(decodedBytes) + } + when (setting.encryptionProtocol) { "S/MIME" -> { val certFactory = CertificateFactory.getInstance("X.509") - recipientX509Cert = certFactory.generateCertificate(FileInputStream(keystorePath)) as X509Certificate + recipientX509Cert = certFactory.generateCertificate(FileInputStream(keystoreBase64)) as X509Certificate } "OpenPGP" -> { diff --git a/app/src/main/res/layout/fragment_senders_email.xml b/app/src/main/res/layout/fragment_senders_email.xml index bbe0f5e0..9baefbd0 100644 --- a/app/src/main/res/layout/fragment_senders_email.xml +++ b/app/src/main/res/layout/fragment_senders_email.xml @@ -368,7 +368,7 @@ @@ -378,10 +378,13 @@ android:layout_height="wrap_content" android:layout_marginStart="5dp" android:layout_weight="1" - android:hint="@string/keystore_path_tips" + android:hint="@string/keystore_base64_tips" android:importantForAutofill="no" - android:singleLine="true" - android:textSize="@dimen/text_size_small" + android:inputType="textMultiLine" + android:maxLines="5" + android:minLines="2" + android:scrollbars="vertical" + android:textSize="@dimen/text_size_mini" app:met_clearButton="true" /> @@ -73,10 +73,13 @@ android:layout_height="wrap_content" android:layout_marginStart="5dp" android:layout_weight="1" - android:hint="@string/keystore_path_tips" + android:hint="@string/keystore_base64_tips" android:importantForAutofill="no" - android:singleLine="true" - android:textSize="@dimen/text_size_small" + android:inputType="textMultiLine" + android:maxLines="5" + android:minLines="2" + android:scrollbars="vertical" + android:textSize="@dimen/text_size_mini" app:met_clearButton="true" /> Sender OpenPGP Cert. (Opt.) Invalid Sender Signing Private Key Recipient - Cert. Path - Opt., Copy keystore to the Download dir + Specified Cert. + Opt., Copy keystore to the Download dir Cert. Pwd. Import password for `Private key` Email Title diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 87766514..bdeae4d4 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -236,8 +236,8 @@ 发件人OpenPGP签名私钥(可选) 发件人签名私钥无效 收件人邮箱 - 证书路径 - 可选,下载证书文件到 Download 目录 + 指定证书 + 可选,下载证书文件到 Download 目录 证书密码 `私钥证书`对应的导入密钥 邮件主题 diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 32d2749c..b419035f 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -236,8 +236,8 @@ 發件人OpenPGP簽名私鑰(可選) 發件人簽名私鑰無效 收件人郵箱 - 證書路徑 - 可選,下載證書文件到 Download 目錄 + 指定證書 + 可選,下載證書文件到 Download 目錄 證書密碼 「私鑰證書」相對應的導入密碼 郵件主題 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ef601db0..c9e6d42b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -262,8 +262,8 @@ 发件人OpenPGP签名私钥(可选) 发件人签名私钥无效 收件人邮箱 - 证书路径 - 可选,下载证书文件到 Download 目录 + 指定证书 + 可选,下载证书文件到 Download 目录 证书密码 `私钥证书`对应的导入密钥 邮件主题