S/MIME sign

pull/168/head
M66B 5 years ago
parent e8f710d03f
commit 7cab6d8a21

@ -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<Address> 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<Address> 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<EntityAttachment> attachments = db.attachment().getAttachments(id);
List<EntityAttachment> 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<Void>() {
@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<EntityAttachment> 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<X509Certificate> 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) {

@ -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");

@ -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);

@ -960,5 +960,7 @@
<item>Няма</item>
<item>PGP само подписване</item>
<item>PGP подписване+криптиране</item>
<item>S/MIME sign-only</item>
<item>S/MIME sign+encrypt</item>
</string-array>
</resources>

@ -981,5 +981,7 @@
<item>Žádný</item>
<item>Pouze PGP podpis</item>
<item>PGP podpis+šifrovat</item>
<item>S/MIME sign-only</item>
<item>S/MIME sign+encrypt</item>
</string-array>
</resources>

@ -959,5 +959,7 @@
<item>Ingen</item>
<item>Kun PGP-signering</item>
<item>PGP-signering + kryptering</item>
<item>S/MIME sign-only</item>
<item>S/MIME sign+encrypt</item>
</string-array>
</resources>

@ -959,5 +959,7 @@ Konten und Identitäten (Aliase) können bei Bedarf auch manuell eingerichtet we
<item>Keine Verschlüsselung</item>
<item>PGP nur unterschreiben</item>
<item>PGP unterschreiben + verschlüsseln</item>
<item>S/MIME sign-only</item>
<item>S/MIME sign+encrypt</item>
</string-array>
</resources>

@ -961,5 +961,7 @@
<item>Ninguno</item>
<item>Sólo firmar con PGP</item>
<item>Firmar y cifrar con PGP</item>
<item>S/MIME sign-only</item>
<item>S/MIME sign+encrypt</item>
</string-array>
</resources>

@ -961,5 +961,7 @@
<item>Bat ere ez</item>
<item>PGP sinadura soilik</item>
<item>PGP sinadura eta enkriptatzea</item>
<item>S/MIME sign-only</item>
<item>S/MIME sign+encrypt</item>
</string-array>
</resources>

@ -961,5 +961,7 @@
<item>Ei mitään</item>
<item>PGP vain allekirjoita</item>
<item>PGP allekirjoita+salaa</item>
<item>S/MIME sign-only</item>
<item>S/MIME sign+encrypt</item>
</string-array>
</resources>

@ -961,5 +961,7 @@
<item>Aucun</item>
<item>Signer PGP uniquement</item>
<item>Signer &amp; chiffrer PGP</item>
<item>S/MIME sign-only</item>
<item>S/MIME sign+encrypt</item>
</string-array>
</resources>

@ -960,5 +960,7 @@
<item>Nulla</item>
<item>PGP solo firma</item>
<item>PGP firma e crittografia</item>
<item>S/MIME sign-only</item>
<item>S/MIME sign+encrypt</item>
</string-array>
</resources>

@ -958,5 +958,7 @@
<item>Geen</item>
<item>PGP teken-alleen</item>
<item>PGP teken+versleutel</item>
<item>S/MIME teken-alleen</item>
<item>S/MIME teken+versleutel</item>
</string-array>
</resources>

@ -961,5 +961,7 @@
<item>Ingen</item>
<item>Bare PGP-signering</item>
<item>PGP signer+krypter</item>
<item>S/MIME sign-only</item>
<item>S/MIME sign+encrypt</item>
</string-array>
</resources>

@ -961,5 +961,7 @@
<item>Ingen</item>
<item>Bare PGP-signering</item>
<item>PGP signer+krypter</item>
<item>S/MIME sign-only</item>
<item>S/MIME sign+encrypt</item>
</string-array>
</resources>

@ -970,5 +970,7 @@
<item>Fără</item>
<item>Doar semnare PGP</item>
<item>Semnare+criptare PGP</item>
<item>S/MIME sign-only</item>
<item>S/MIME sign+encrypt</item>
</string-array>
</resources>

@ -983,5 +983,7 @@
<item>Ничего</item>
<item>Только подпись PGP</item>
<item>Подпись+шифрование PGP</item>
<item>S/MIME sign-only</item>
<item>S/MIME sign+encrypt</item>
</string-array>
</resources>

@ -961,5 +961,7 @@
<item>Hiçbiri</item>
<item>PGP sadece imzala</item>
<item>PGP imzala ve şifrele</item>
<item>S/MIME sign-only</item>
<item>S/MIME sign+encrypt</item>
</string-array>
</resources>

@ -948,5 +948,7 @@ FairEmail会显示一条消息来提醒您这一点这条消息在您购买
<item></item>
<item>仅PGP签名</item>
<item>PGP 签名+加密</item>
<item>S/MIME sign-only</item>
<item>S/MIME sign+encrypt</item>
</string-array>
</resources>

@ -1159,12 +1159,16 @@
<item>None</item>
<item>PGP sign-only</item>
<item>PGP sign+encrypt</item>
<item>S/MIME sign-only</item>
<item>S/MIME sign+encrypt</item>
</string-array>
<integer-array name="encryptValues" translatable="false">
<item>0</item>
<item>2</item>
<item>1</item>
<item>4</item>
<item>3</item>
</integer-array>
<string name="fingerprint" translatable="false">17BA15C1AF55D925F98B99CEA4375D4CDF4C174B</string>

Loading…
Cancel
Save