diff --git a/app/src/main/java/eu/faircode/email/FragmentCompose.java b/app/src/main/java/eu/faircode/email/FragmentCompose.java index 702e06bb8b..333e92b4aa 100644 --- a/app/src/main/java/eu/faircode/email/FragmentCompose.java +++ b/app/src/main/java/eu/faircode/email/FragmentCompose.java @@ -52,6 +52,8 @@ import android.os.Handler; import android.os.OperationCanceledException; import android.provider.ContactsContract; import android.provider.MediaStore; +import android.security.KeyChain; +import android.security.KeyChainAliasCallback; import android.text.Html; import android.text.SpannableStringBuilder; import android.text.Spanned; @@ -97,6 +99,17 @@ import com.google.android.material.bottomnavigation.BottomNavigationView; import com.google.android.material.bottomnavigation.LabelVisibilityMode; import com.google.android.material.snackbar.Snackbar; +import org.bouncycastle.cert.jcajce.JcaCertStore; +import org.bouncycastle.cms.CMSProcessableFile; +import org.bouncycastle.cms.CMSSignedData; +import org.bouncycastle.cms.CMSSignedDataGenerator; +import org.bouncycastle.cms.CMSTypedData; +import org.bouncycastle.cms.jcajce.JcaSignerInfoGeneratorBuilder; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.operator.ContentSigner; +import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; +import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder; +import org.bouncycastle.util.Store; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import org.jsoup.nodes.TextNode; @@ -114,6 +127,8 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.UnknownHostException; +import java.security.PrivateKey; +import java.security.cert.X509Certificate; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; @@ -1203,56 +1218,81 @@ public class FragmentCompose extends FragmentBase { onAction(R.id.action_check, extras); } - private void onEncrypt(EntityMessage draft) { - if (pgpService.isBound()) - try { - List
recipients = new ArrayList<>(); - if (draft.to != null) - recipients.addAll(Arrays.asList(draft.to)); - if (draft.cc != null) - recipients.addAll(Arrays.asList(draft.cc)); - if (draft.bcc != null) - recipients.addAll(Arrays.asList(draft.bcc)); - - if (recipients.size() == 0) - throw new IllegalArgumentException(getString(R.string.title_to_missing)); - - pgpUserIds = new String[recipients.size()]; - for (int i = 0; i < recipients.size(); i++) { - InternetAddress recipient = (InternetAddress) recipients.get(i); - pgpUserIds[i] = recipient.getAddress().toLowerCase(Locale.ROOT); - } + private void onEncrypt(final EntityMessage draft) { + if (EntityMessage.SMIME_SIGNONLY.equals(draft.encrypt) || + EntityMessage.SMIME_SIGNENCRYPT.equals(draft.encrypt)) { + Handler handler = new Handler(); + KeyChain.choosePrivateKeyAlias(getActivity(), new KeyChainAliasCallback() { + @Override + public void alias(@Nullable String alias) { + Log.i("Selected key alias=" + alias); + if (alias != null) { + handler.post(new Runnable() { + @Override + public void run() { + try { + onSmime(draft, alias); + } catch (Throwable ex) { + Log.e(ex); + } + } + }); + } + } + }, + null, null, null, -1, null); - Intent intent; - if (EntityMessage.PGP_SIGNONLY.equals(draft.encrypt)) { - intent = new Intent(OpenPgpApi.ACTION_GET_SIGN_KEY_ID); - intent.putExtra(BuildConfig.APPLICATION_ID, working); - } else if (EntityMessage.PGP_SIGNENCRYPT.equals(draft.encrypt)) { - intent = new Intent(OpenPgpApi.ACTION_GET_KEY_IDS); - intent.putExtra(OpenPgpApi.EXTRA_USER_IDS, pgpUserIds); - intent.putExtra(BuildConfig.APPLICATION_ID, working); - } else - throw new IllegalArgumentException("Invalid encrypt=" + draft.encrypt); + } else { + if (pgpService.isBound()) + try { + List
recipients = new ArrayList<>(); + if (draft.to != null) + recipients.addAll(Arrays.asList(draft.to)); + if (draft.cc != null) + recipients.addAll(Arrays.asList(draft.cc)); + if (draft.bcc != null) + recipients.addAll(Arrays.asList(draft.bcc)); + + if (recipients.size() == 0) + throw new IllegalArgumentException(getString(R.string.title_to_missing)); + + pgpUserIds = new String[recipients.size()]; + for (int i = 0; i < recipients.size(); i++) { + InternetAddress recipient = (InternetAddress) recipients.get(i); + pgpUserIds[i] = recipient.getAddress().toLowerCase(Locale.ROOT); + } - onPgp(intent); - } catch (Throwable ex) { - if (ex instanceof IllegalArgumentException) - Snackbar.make(view, ex.getMessage(), Snackbar.LENGTH_LONG).show(); - else { - Log.e(ex); - Helper.unexpectedError(getParentFragmentManager(), ex); + Intent intent; + if (EntityMessage.PGP_SIGNONLY.equals(draft.encrypt)) { + intent = new Intent(OpenPgpApi.ACTION_GET_SIGN_KEY_ID); + intent.putExtra(BuildConfig.APPLICATION_ID, working); + } else if (EntityMessage.PGP_SIGNENCRYPT.equals(draft.encrypt)) { + intent = new Intent(OpenPgpApi.ACTION_GET_KEY_IDS); + intent.putExtra(OpenPgpApi.EXTRA_USER_IDS, pgpUserIds); + intent.putExtra(BuildConfig.APPLICATION_ID, working); + } else + throw new IllegalArgumentException("Invalid encrypt=" + draft.encrypt); + + onPgp(intent); + } catch (Throwable ex) { + if (ex instanceof IllegalArgumentException) + Snackbar.make(view, ex.getMessage(), Snackbar.LENGTH_LONG).show(); + else { + Log.e(ex); + Helper.unexpectedError(getParentFragmentManager(), ex); + } } + else { + Snackbar snackbar = Snackbar.make(view, R.string.title_no_openpgp, Snackbar.LENGTH_LONG); + if (Helper.getIntentOpenKeychain().resolveActivity(getContext().getPackageManager()) != null) + snackbar.setAction(R.string.title_fix, new View.OnClickListener() { + @Override + public void onClick(View v) { + startActivity(Helper.getIntentOpenKeychain()); + } + }); + snackbar.show(); } - else { - Snackbar snackbar = Snackbar.make(view, R.string.title_no_openpgp, Snackbar.LENGTH_LONG); - if (Helper.getIntentOpenKeychain().resolveActivity(getContext().getPackageManager()) != null) - snackbar.setAction(R.string.title_fix, new View.OnClickListener() { - @Override - public void onClick(View v) { - startActivity(Helper.getIntentOpenKeychain()); - } - }); - snackbar.show(); } } @@ -1518,8 +1558,8 @@ public class FragmentCompose extends FragmentBase { throw new IllegalArgumentException(getString(R.string.title_from_missing)); // Create files - File input = new File(context.getCacheDir(), "pgp_input." + id); - File output = new File(context.getCacheDir(), "pgp_output." + id); + File input = new File(context.getCacheDir(), "pgp_input." + draft.id); + File output = new File(context.getCacheDir(), "pgp_output." + draft.id); // Serializing messages is NOT reproducible if ((EntityMessage.PGP_SIGNONLY.equals(draft.encrypt) && @@ -1527,7 +1567,7 @@ public class FragmentCompose extends FragmentBase { (EntityMessage.PGP_SIGNENCRYPT.equals(draft.encrypt) && OpenPgpApi.ACTION_GET_KEY_IDS.equals(data.getAction()))) { // Get/clean attachments - List attachments = db.attachment().getAttachments(id); + List attachments = db.attachment().getAttachments(draft.id); for (EntityAttachment attachment : new ArrayList<>(attachments)) if (attachment.encryption != null) { db.attachment().deleteAttachment(attachment.id); @@ -1610,7 +1650,7 @@ public class FragmentCompose extends FragmentBase { db.beginTransaction(); String name; - String type = "application/octet-stream"; + ContentType ct = new ContentType("application/octet-stream"); int encryption; if (OpenPgpApi.ACTION_GET_KEY.equals(data.getAction())) { name = "keydata.asc"; @@ -1618,8 +1658,8 @@ public class FragmentCompose extends FragmentBase { } else if (OpenPgpApi.ACTION_DETACHED_SIGN.equals(data.getAction())) { name = "signature.asc"; encryption = EntityAttachment.PGP_SIGNATURE; - type = "application/pgp-signature; micalg=\"" + - result.getStringExtra(OpenPgpApi.RESULT_SIGNATURE_MICALG) + "\""; + ct = new ContentType("application/pgp-signature"); + ct.setParameter("micalg", result.getStringExtra(OpenPgpApi.RESULT_SIGNATURE_MICALG)); } else if (OpenPgpApi.ACTION_SIGN_AND_ENCRYPT.equals(data.getAction())) { name = "encrypted.asc"; encryption = EntityAttachment.PGP_MESSAGE; @@ -1627,10 +1667,10 @@ public class FragmentCompose extends FragmentBase { throw new IllegalStateException(data.getAction()); EntityAttachment attachment = new EntityAttachment(); - attachment.message = id; - attachment.sequence = db.attachment().getAttachmentSequence(id) + 1; + attachment.message = draft.id; + attachment.sequence = db.attachment().getAttachmentSequence(draft.id) + 1; attachment.name = name; - attachment.type = type; + attachment.type = ct.toString(); attachment.disposition = Part.INLINE; attachment.encryption = encryption; attachment.id = db.attachment().insertAttachment(attachment); @@ -1666,7 +1706,7 @@ public class FragmentCompose extends FragmentBase { Intent intent = new Intent(OpenPgpApi.ACTION_GET_KEY); intent.putExtra(OpenPgpApi.EXTRA_KEY_ID, pgpKeyIds[0]); intent.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true); - intent.putExtra(BuildConfig.APPLICATION_ID, id); + intent.putExtra(BuildConfig.APPLICATION_ID, draft.id); return intent; } } @@ -1678,7 +1718,7 @@ public class FragmentCompose extends FragmentBase { Intent intent = new Intent(OpenPgpApi.ACTION_DETACHED_SIGN); intent.putExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID, pgpSignKeyId); intent.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true); - intent.putExtra(BuildConfig.APPLICATION_ID, id); + intent.putExtra(BuildConfig.APPLICATION_ID, draft.id); return intent; } else { if (identity.sign_key != null) { @@ -1687,13 +1727,13 @@ public class FragmentCompose extends FragmentBase { intent.putExtra(OpenPgpApi.EXTRA_KEY_IDS, pgpKeyIds); intent.putExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID, identity.sign_key); intent.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true); - intent.putExtra(BuildConfig.APPLICATION_ID, id); + intent.putExtra(BuildConfig.APPLICATION_ID, draft.id); return intent; } else { // Get sign key Intent intent = new Intent(OpenPgpApi.ACTION_GET_SIGN_KEY_ID); intent.putExtra(OpenPgpApi.EXTRA_USER_IDS, pgpUserIds); - intent.putExtra(BuildConfig.APPLICATION_ID, id); + intent.putExtra(BuildConfig.APPLICATION_ID, draft.id); return intent; } } @@ -1706,7 +1746,7 @@ public class FragmentCompose extends FragmentBase { Intent intent = new Intent(OpenPgpApi.ACTION_GET_KEY); intent.putExtra(OpenPgpApi.EXTRA_KEY_ID, pgpSignKeyId); intent.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true); - intent.putExtra(BuildConfig.APPLICATION_ID, id); + intent.putExtra(BuildConfig.APPLICATION_ID, draft.id); return intent; } else if (EntityMessage.PGP_SIGNENCRYPT.equals(draft.encrypt)) { // Encrypt message @@ -1714,14 +1754,14 @@ public class FragmentCompose extends FragmentBase { intent.putExtra(OpenPgpApi.EXTRA_KEY_IDS, pgpKeyIds); intent.putExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID, pgpSignKeyId); intent.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true); - intent.putExtra(BuildConfig.APPLICATION_ID, id); + intent.putExtra(BuildConfig.APPLICATION_ID, draft.id); return intent; } else throw new IllegalArgumentException("Invalid encrypt=" + draft.encrypt); } else if (OpenPgpApi.ACTION_DETACHED_SIGN.equals(data.getAction())) { EntityAttachment attachment = new EntityAttachment(); - attachment.message = id; - attachment.sequence = db.attachment().getAttachmentSequence(id) + 1; + attachment.message = draft.id; + attachment.sequence = db.attachment().getAttachmentSequence(draft.id) + 1; attachment.name = "content.asc"; attachment.type = "text/plain"; attachment.disposition = Part.INLINE; @@ -1794,7 +1834,150 @@ public class FragmentCompose extends FragmentBase { } else Helper.unexpectedError(getParentFragmentManager(), ex); } - }.execute(this, args, "compose:encrypt"); + }.execute(this, args, "compose:pgp"); + } + + private void onSmime(EntityMessage draft, String alias) { + Bundle args = new Bundle(); + args.putLong("id", draft.id); + args.putInt("type", draft.encrypt); + args.putString("alias", alias); + + new SimpleTask() { + @Override + protected Void onExecute(Context context, Bundle args) throws Throwable { + long id = args.getLong("id"); + int type = args.getInt("type"); + String alias = args.getString("alias"); + + if (!EntityMessage.SMIME_SIGNONLY.equals(type)) + throw new UnsupportedOperationException("Not yet supported"); + + if (alias == null) + throw new IllegalArgumentException("Key alias missing"); + + // Get key + PrivateKey privkey = KeyChain.getPrivateKey(context, alias); + if (privkey == null) + throw new IllegalArgumentException("Private key missing"); + X509Certificate[] chain = KeyChain.getCertificateChain(context, alias); + if (chain == null || chain.length == 0) + throw new IllegalArgumentException("Certificate missing"); + + DB db = DB.getInstance(context); + + // Get data + EntityMessage draft = db.message().getMessage(id); + if (draft == null) + throw new MessageRemovedException("S/MIME"); + EntityIdentity identity = db.identity().getIdentity(draft.identity); + if (identity == null) + throw new IllegalArgumentException(getString(R.string.title_from_missing)); + + // Get/clean attachments + List attachments = db.attachment().getAttachments(id); + for (EntityAttachment attachment : new ArrayList<>(attachments)) + if (attachment.encryption != null) { + db.attachment().deleteAttachment(attachment.id); + attachments.remove(attachment); + } + + // Build message + Properties props = MessageHelper.getSessionProperties(); + Session isession = Session.getInstance(props, null); + MimeMessage imessage = new MimeMessage(isession); + MessageHelper.build(context, draft, attachments, identity, imessage); + imessage.saveChanges(); + BodyPart bpContent = new MimeBodyPart() { + @Override + public void setContent(Object content, String type) throws MessagingException { + super.setContent(content, type); + + // https://javaee.github.io/javamail/FAQ#howencode + updateHeaders(); + if (content instanceof Multipart) { + try { + MessageHelper.overrideContentTransferEncoding((Multipart) content); + } catch (IOException ex) { + Log.e(ex); + } + } else + setHeader("Content-Transfer-Encoding", "base64"); + } + }; + bpContent.setContent(imessage.getContent(), imessage.getContentType()); + + // Build content + EntityAttachment cattachment = new EntityAttachment(); + cattachment.message = draft.id; + cattachment.sequence = db.attachment().getAttachmentSequence(draft.id) + 1; + cattachment.name = "content.asc"; + cattachment.type = "text/plain"; + cattachment.disposition = Part.INLINE; + cattachment.encryption = EntityAttachment.SMIME_CONTENT; + cattachment.id = db.attachment().insertAttachment(cattachment); + + File content = cattachment.getFile(context); + try (OutputStream os = new FileOutputStream(content)) { + bpContent.writeTo(os); + } + + db.attachment().setDownloaded(cattachment.id, content.length()); + + // Build signature + CMSTypedData cmsData = new CMSProcessableFile(content); + List certList = new ArrayList<>(); + certList.add(chain[0]); + Store certs = new JcaCertStore(certList); + + CMSSignedDataGenerator cmsGenerator = new CMSSignedDataGenerator(); + ContentSigner contentSigner = new JcaContentSignerBuilder("SHA256withRSA").build(privkey); + cmsGenerator.addSignerInfoGenerator( + new JcaSignerInfoGeneratorBuilder( + new JcaDigestCalculatorProviderBuilder() + .setProvider(new BouncyCastleProvider()).build()) + .build(contentSigner, chain[0])); + cmsGenerator.addCertificates(certs); + + CMSSignedData cms = cmsGenerator.generate(cmsData, true); + byte[] signedMessage = cms.getEncoded(); + + ContentType ct = new ContentType("application/pkcs7-signature"); + ct.setParameter("micalg", "sha-256"); + + EntityAttachment sattachment = new EntityAttachment(); + sattachment.message = draft.id; + sattachment.sequence = db.attachment().getAttachmentSequence(draft.id) + 1; + sattachment.name = "smime.p7s"; + sattachment.type = ct.toString(); + sattachment.disposition = Part.INLINE; + sattachment.encryption = EntityAttachment.SMIME_SIGNATURE; + sattachment.id = db.attachment().insertAttachment(sattachment); + + File file = sattachment.getFile(context); + try (OutputStream os = new FileOutputStream(file)) { + os.write(signedMessage); + } + + db.attachment().setDownloaded(sattachment.id, file.length()); + + return null; + } + + @Override + protected void onExecuted(Bundle args, Void result) { + onAction(R.id.action_send); + } + + @Override + protected void onException(Bundle args, Throwable ex) { + if (ex instanceof IllegalArgumentException) { + Log.i(ex); + Snackbar.make(view, ex.getMessage(), Snackbar.LENGTH_LONG).show(); + } else + Helper.unexpectedError(getParentFragmentManager(), ex); + } + }.execute(this, args, "compose:s/mimem"); } private void onContactGroupSelected(Bundle args) { diff --git a/app/src/main/java/eu/faircode/email/FragmentMessages.java b/app/src/main/java/eu/faircode/email/FragmentMessages.java index 0536ce6006..3f570f8d4e 100644 --- a/app/src/main/java/eu/faircode/email/FragmentMessages.java +++ b/app/src/main/java/eu/faircode/email/FragmentMessages.java @@ -4392,11 +4392,12 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences. return null; } else { + // Get alias String alias = args.getString("alias"); if (alias == null) throw new IllegalArgumentException("Key alias missing"); - // Check private key + // Get private key PrivateKey privkey = KeyChain.getPrivateKey(context, alias); if (privkey == null) throw new IllegalArgumentException("Private key missing"); diff --git a/app/src/main/java/eu/faircode/email/MessageHelper.java b/app/src/main/java/eu/faircode/email/MessageHelper.java index 3024c39f76..a1442869c2 100644 --- a/app/src/main/java/eu/faircode/email/MessageHelper.java +++ b/app/src/main/java/eu/faircode/email/MessageHelper.java @@ -78,6 +78,7 @@ import javax.mail.internet.MimeBodyPart; import javax.mail.internet.MimeMessage; import javax.mail.internet.MimeMultipart; import javax.mail.internet.MimeUtility; +import javax.mail.internet.ParameterList; import javax.mail.internet.ParseException; import biweekly.Biweekly; @@ -248,48 +249,56 @@ public class MessageHelper { // https://tools.ietf.org/html/rfc3156 for (final EntityAttachment attachment : attachments) if (EntityAttachment.PGP_SIGNATURE.equals(attachment.encryption)) { - Log.i("Sending signed message"); + Log.i("Sending PGP signed message"); for (final EntityAttachment content : attachments) if (EntityAttachment.PGP_CONTENT.equals(content.encryption)) { BodyPart bpContent = new MimeBodyPart(new FileInputStream(content.getFile(context))); - // Build signature final ContentType cts = new ContentType(attachment.type); + String micalg = cts.getParameter("micalg"); + ParameterList params = cts.getParameterList(); + params.remove("micalg"); + cts.setParameterList(params); + + // Build signature BodyPart bpSignature = new MimeBodyPart(); bpSignature.setFileName(attachment.name); FileDataSource dsSignature = new FileDataSource(attachment.getFile(context)); dsSignature.setFileTypeMap(new FileTypeMap() { @Override public String getContentType(File file) { - return cts.getBaseType(); + return cts.toString(); } @Override public String getContentType(String filename) { - return cts.getBaseType(); + return cts.toString(); } }); bpSignature.setDataHandler(new DataHandler(dsSignature)); bpSignature.setDisposition(Part.INLINE); // Build message - Multipart multipart = new MimeMultipart("signed;" + - " micalg=\"" + cts.getParameter("micalg") + "\";" + - " protocol=\"application/pgp-signature\""); + ContentType ct = new ContentType("multipart/signed"); + ct.setParameter("micalg", micalg); + ct.setParameter("protocol", "application/pgp-signature"); + String ctx = ct.toString(); + int slash = ctx.indexOf("/"); + Multipart multipart = new MimeMultipart(ctx.substring(slash + 1)); multipart.addBodyPart(bpContent); multipart.addBodyPart(bpSignature); imessage.setContent(multipart); return imessage; } - throw new IllegalStateException("Content not found"); + throw new IllegalStateException("PGP content not found"); } else if (EntityAttachment.PGP_MESSAGE.equals(attachment.encryption)) { - Log.i("Sending encrypted message"); + Log.i("Sending PGP encrypted message"); // Build header BodyPart bpHeader = new MimeBodyPart(); - bpHeader.setContent("Version: 1\r\n", "application/pgp-encrypted"); + bpHeader.setContent("", "application/pgp-encrypted"); // Build content BodyPart bpContent = new MimeBodyPart(); @@ -310,13 +319,64 @@ public class MessageHelper { bpContent.setDisposition(Part.INLINE); // Build message - Multipart multipart = new MimeMultipart("encrypted; protocol=\"application/pgp-encrypted\""); + ContentType ct = new ContentType("multipart/encrypted"); + ct.setParameter("protocol", "application/pgp-encrypted"); + String ctx = ct.toString(); + int slash = ctx.indexOf("/"); + Multipart multipart = new MimeMultipart(ctx.substring(slash + 1)); multipart.addBodyPart(bpHeader); multipart.addBodyPart(bpContent); imessage.setContent(multipart); return imessage; - } + } else if (EntityAttachment.SMIME_SIGNATURE.equals(attachment.encryption)) { + Log.i("Sending S/MIME signed message"); + + for (final EntityAttachment content : attachments) + if (EntityAttachment.SMIME_CONTENT.equals(content.encryption)) { + BodyPart bpContent = new MimeBodyPart(new FileInputStream(content.getFile(context))); + + final ContentType cts = new ContentType(attachment.type); + String micalg = cts.getParameter("micalg"); + ParameterList params = cts.getParameterList(); + params.remove("micalg"); + cts.setParameterList(params); + + // Build signature + BodyPart bpSignature = new MimeBodyPart(); + bpSignature.setFileName(attachment.name); + FileDataSource dsSignature = new FileDataSource(attachment.getFile(context)); + dsSignature.setFileTypeMap(new FileTypeMap() { + @Override + public String getContentType(File file) { + return cts.toString(); + } + + @Override + public String getContentType(String filename) { + return cts.toString(); + } + }); + bpSignature.setDataHandler(new DataHandler(dsSignature)); + bpSignature.setDisposition(Part.INLINE); + + // Build message + ContentType ct = new ContentType("multipart/signed"); + ct.setParameter("micalg", micalg); + ct.setParameter("protocol", "application/pkcs7-signature"); + ct.setParameter("smime-type", "signed-data"); + String ctx = ct.toString(); + int slash = ctx.indexOf("/"); + Multipart multipart = new MimeMultipart(ctx.substring(slash + 1)); + multipart.addBodyPart(bpContent); + multipart.addBodyPart(bpSignature); + imessage.setContent(multipart); + + return imessage; + } + throw new IllegalStateException("S/MIME content not found"); + } else if (EntityAttachment.SMIME_MESSAGE.equals(attachment.encryption)) + throw new UnsupportedOperationException(); build(context, message, attachments, identity, imessage); diff --git a/app/src/main/res/values-bg-rBG/strings.xml b/app/src/main/res/values-bg-rBG/strings.xml index 203ecc9f5b..c96cff9fea 100644 --- a/app/src/main/res/values-bg-rBG/strings.xml +++ b/app/src/main/res/values-bg-rBG/strings.xml @@ -960,5 +960,7 @@ Няма PGP само подписване PGP подписване+криптиране + S/MIME sign-only + S/MIME sign+encrypt diff --git a/app/src/main/res/values-cs-rCZ/strings.xml b/app/src/main/res/values-cs-rCZ/strings.xml index 202fee5553..dd04eb8aa9 100644 --- a/app/src/main/res/values-cs-rCZ/strings.xml +++ b/app/src/main/res/values-cs-rCZ/strings.xml @@ -981,5 +981,7 @@ Žádný Pouze PGP podpis PGP podpis+šifrovat + S/MIME sign-only + S/MIME sign+encrypt diff --git a/app/src/main/res/values-da-rDK/strings.xml b/app/src/main/res/values-da-rDK/strings.xml index b90f9dda3f..5204ef57cf 100644 --- a/app/src/main/res/values-da-rDK/strings.xml +++ b/app/src/main/res/values-da-rDK/strings.xml @@ -959,5 +959,7 @@ Ingen Kun PGP-signering PGP-signering + kryptering + S/MIME sign-only + S/MIME sign+encrypt diff --git a/app/src/main/res/values-de-rDE/strings.xml b/app/src/main/res/values-de-rDE/strings.xml index 0b9374f4e7..cd9848e148 100644 --- a/app/src/main/res/values-de-rDE/strings.xml +++ b/app/src/main/res/values-de-rDE/strings.xml @@ -959,5 +959,7 @@ Konten und Identitäten (Aliase) können bei Bedarf auch manuell eingerichtet we Keine Verschlüsselung PGP nur unterschreiben PGP unterschreiben + verschlüsseln + S/MIME sign-only + S/MIME sign+encrypt diff --git a/app/src/main/res/values-es-rES/strings.xml b/app/src/main/res/values-es-rES/strings.xml index 7a55dd13c3..4eb7557522 100644 --- a/app/src/main/res/values-es-rES/strings.xml +++ b/app/src/main/res/values-es-rES/strings.xml @@ -961,5 +961,7 @@ Ninguno Sólo firmar con PGP Firmar y cifrar con PGP + S/MIME sign-only + S/MIME sign+encrypt diff --git a/app/src/main/res/values-eu-rES/strings.xml b/app/src/main/res/values-eu-rES/strings.xml index 1aff7718e7..070226b717 100644 --- a/app/src/main/res/values-eu-rES/strings.xml +++ b/app/src/main/res/values-eu-rES/strings.xml @@ -961,5 +961,7 @@ Bat ere ez PGP sinadura soilik PGP sinadura eta enkriptatzea + S/MIME sign-only + S/MIME sign+encrypt diff --git a/app/src/main/res/values-fi-rFI/strings.xml b/app/src/main/res/values-fi-rFI/strings.xml index 60db712a4c..9427ddf3f0 100644 --- a/app/src/main/res/values-fi-rFI/strings.xml +++ b/app/src/main/res/values-fi-rFI/strings.xml @@ -961,5 +961,7 @@ Ei mitään PGP vain allekirjoita PGP allekirjoita+salaa + S/MIME sign-only + S/MIME sign+encrypt diff --git a/app/src/main/res/values-fr-rFR/strings.xml b/app/src/main/res/values-fr-rFR/strings.xml index a7e3853e7c..b7dfa3cf5e 100644 --- a/app/src/main/res/values-fr-rFR/strings.xml +++ b/app/src/main/res/values-fr-rFR/strings.xml @@ -961,5 +961,7 @@ Aucun Signer PGP uniquement Signer & chiffrer PGP + S/MIME sign-only + S/MIME sign+encrypt diff --git a/app/src/main/res/values-it-rIT/strings.xml b/app/src/main/res/values-it-rIT/strings.xml index 0aefa04bd1..a623572f2c 100644 --- a/app/src/main/res/values-it-rIT/strings.xml +++ b/app/src/main/res/values-it-rIT/strings.xml @@ -960,5 +960,7 @@ Nulla PGP solo firma PGP firma e crittografia + S/MIME sign-only + S/MIME sign+encrypt diff --git a/app/src/main/res/values-nl-rNL/strings.xml b/app/src/main/res/values-nl-rNL/strings.xml index 69309bb6e8..74c86dedeb 100644 --- a/app/src/main/res/values-nl-rNL/strings.xml +++ b/app/src/main/res/values-nl-rNL/strings.xml @@ -958,5 +958,7 @@ Geen PGP teken-alleen PGP teken+versleutel + S/MIME teken-alleen + S/MIME teken+versleutel diff --git a/app/src/main/res/values-nn-rNO/strings.xml b/app/src/main/res/values-nn-rNO/strings.xml index 374a70c4cc..9e7de4170b 100644 --- a/app/src/main/res/values-nn-rNO/strings.xml +++ b/app/src/main/res/values-nn-rNO/strings.xml @@ -961,5 +961,7 @@ Ingen Bare PGP-signering PGP signer+krypter + S/MIME sign-only + S/MIME sign+encrypt diff --git a/app/src/main/res/values-no-rNO/strings.xml b/app/src/main/res/values-no-rNO/strings.xml index 374a70c4cc..9e7de4170b 100644 --- a/app/src/main/res/values-no-rNO/strings.xml +++ b/app/src/main/res/values-no-rNO/strings.xml @@ -961,5 +961,7 @@ Ingen Bare PGP-signering PGP signer+krypter + S/MIME sign-only + S/MIME sign+encrypt diff --git a/app/src/main/res/values-ro-rRO/strings.xml b/app/src/main/res/values-ro-rRO/strings.xml index b2ceaaf84f..0a2f0b4a8d 100644 --- a/app/src/main/res/values-ro-rRO/strings.xml +++ b/app/src/main/res/values-ro-rRO/strings.xml @@ -970,5 +970,7 @@ Fără Doar semnare PGP Semnare+criptare PGP + S/MIME sign-only + S/MIME sign+encrypt diff --git a/app/src/main/res/values-ru-rRU/strings.xml b/app/src/main/res/values-ru-rRU/strings.xml index 3c1df79db7..fa924932e7 100644 --- a/app/src/main/res/values-ru-rRU/strings.xml +++ b/app/src/main/res/values-ru-rRU/strings.xml @@ -983,5 +983,7 @@ Ничего Только подпись PGP Подпись+шифрование PGP + S/MIME sign-only + S/MIME sign+encrypt diff --git a/app/src/main/res/values-tr-rTR/strings.xml b/app/src/main/res/values-tr-rTR/strings.xml index b551746f30..cf53a2a40c 100644 --- a/app/src/main/res/values-tr-rTR/strings.xml +++ b/app/src/main/res/values-tr-rTR/strings.xml @@ -961,5 +961,7 @@ Hiçbiri PGP sadece imzala PGP imzala ve şifrele + S/MIME sign-only + S/MIME sign+encrypt diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 05d6647a84..76f903d69a 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -948,5 +948,7 @@ FairEmail会显示一条消息来提醒您这一点,这条消息在您购买 仅PGP签名 PGP 签名+加密 + S/MIME sign-only + S/MIME sign+encrypt diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e35cc036fd..1cdf10e74b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1159,12 +1159,16 @@ None PGP sign-only PGP sign+encrypt + S/MIME sign-only + S/MIME sign+encrypt 0 2 1 + 4 + 3 17BA15C1AF55D925F98B99CEA4375D4CDF4C174B