1
0
Fork 0
mirror of https://github.com/M66B/FairEmail.git synced 2025-02-27 08:23:24 +00:00

Just show who S/MIME signed

This commit is contained in:
M66B 2019-12-02 20:34:56 +01:00
parent e67bc5e370
commit ce34a95b92
2 changed files with 38 additions and 83 deletions

View file

@ -24,15 +24,12 @@ import android.app.Dialog;
import android.app.NotificationManager; import android.app.NotificationManager;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.content.BroadcastReceiver; import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.content.IntentSender; import android.content.IntentSender;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.res.AssetFileDescriptor;
import android.graphics.Canvas; import android.graphics.Canvas;
import android.graphics.Color; import android.graphics.Color;
import android.graphics.PorterDuff; import android.graphics.PorterDuff;
@ -113,10 +110,12 @@ import com.google.android.material.snackbar.Snackbar;
import com.sun.mail.util.FolderClosedIOException; import com.sun.mail.util.FolderClosedIOException;
import org.bouncycastle.cert.X509CertificateHolder; import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.cms.CMSEnvelopedData; import org.bouncycastle.cms.CMSEnvelopedData;
import org.bouncycastle.cms.CMSProcessable; import org.bouncycastle.cms.CMSProcessable;
import org.bouncycastle.cms.CMSProcessableFile; import org.bouncycastle.cms.CMSProcessableFile;
import org.bouncycastle.cms.CMSSignedData; import org.bouncycastle.cms.CMSSignedData;
import org.bouncycastle.cms.CMSVerifierCertificateNotValidException;
import org.bouncycastle.cms.KeyTransRecipientInformation; import org.bouncycastle.cms.KeyTransRecipientInformation;
import org.bouncycastle.cms.RecipientInformation; import org.bouncycastle.cms.RecipientInformation;
import org.bouncycastle.cms.SignerInformation; import org.bouncycastle.cms.SignerInformation;
@ -124,9 +123,8 @@ import org.bouncycastle.cms.SignerInformationStore;
import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder; import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder;
import org.bouncycastle.cms.jcajce.JceKeyTransEnvelopedRecipient; import org.bouncycastle.cms.jcajce.JceKeyTransEnvelopedRecipient;
import org.bouncycastle.cms.jcajce.JceKeyTransRecipient; import org.bouncycastle.cms.jcajce.JceKeyTransRecipient;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.util.Store; import org.bouncycastle.util.Store;
import org.bouncycastle.util.io.pem.PemObject;
import org.bouncycastle.util.io.pem.PemReader;
import org.jsoup.nodes.Document; import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element; import org.jsoup.nodes.Element;
import org.openintents.openpgp.OpenPgpError; import org.openintents.openpgp.OpenPgpError;
@ -134,7 +132,6 @@ import org.openintents.openpgp.OpenPgpSignatureResult;
import org.openintents.openpgp.util.OpenPgpApi; import org.openintents.openpgp.util.OpenPgpApi;
import org.openintents.openpgp.util.OpenPgpServiceConnection; import org.openintents.openpgp.util.OpenPgpServiceConnection;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
@ -142,12 +139,9 @@ import java.io.FileNotFoundException;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream; import java.io.OutputStream;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.security.PrivateKey; import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
import java.text.Collator; import java.text.Collator;
import java.text.DateFormat; import java.text.DateFormat;
@ -286,7 +280,6 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences.
private static final int REQUEST_SEARCH = 18; private static final int REQUEST_SEARCH = 18;
private static final int REQUEST_ACCOUNT = 19; private static final int REQUEST_ACCOUNT = 19;
private static final int REQUEST_EMPTY_FOLDER = 20; private static final int REQUEST_EMPTY_FOLDER = 20;
private static final int REQUEST_CERTIFICATE = 21;
static final String ACTION_STORE_RAW = BuildConfig.APPLICATION_ID + ".STORE_RAW"; static final String ACTION_STORE_RAW = BuildConfig.APPLICATION_ID + ".STORE_RAW";
static final String ACTION_DECRYPT = BuildConfig.APPLICATION_ID + ".DECRYPT"; static final String ACTION_DECRYPT = BuildConfig.APPLICATION_ID + ".DECRYPT";
@ -3923,39 +3916,31 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences.
boolean auto = intent.getBooleanExtra("auto", false); boolean auto = intent.getBooleanExtra("auto", false);
int type = intent.getIntExtra("type", EntityMessage.ENCRYPT_NONE); int type = intent.getIntExtra("type", EntityMessage.ENCRYPT_NONE);
if (EntityMessage.SMIME_SIGNONLY.equals(type)) { final Bundle args = new Bundle();
this.message = id; args.putLong("id", id);
args.putInt("type", type);
Intent open = new Intent(Intent.ACTION_OPEN_DOCUMENT);
open.addCategory(Intent.CATEGORY_OPENABLE);
open.setType("*/*");
Helper.openAdvanced(open);
PackageManager pm = getContext().getPackageManager();
if (open.resolveActivity(pm) == null)
Snackbar.make(view, R.string.title_no_saf, Snackbar.LENGTH_LONG).show();
else
startActivityForResult(Helper.getChooser(getContext(), open), REQUEST_CERTIFICATE);
} else if (EntityMessage.SMIME_SIGNENCRYPT.equals(type)) {
final Bundle args = new Bundle();
args.putLong("id", id);
args.putInt("type", type);
if (EntityMessage.SMIME_SIGNONLY.equals(type))
onSmime(args);
else if (EntityMessage.SMIME_SIGNENCRYPT.equals(type)) {
Handler handler = new Handler(); Handler handler = new Handler();
KeyChain.choosePrivateKeyAlias(getActivity(), new KeyChainAliasCallback() { KeyChain.choosePrivateKeyAlias(getActivity(), new KeyChainAliasCallback() {
@Override @Override
public void alias(@Nullable String alias) { public void alias(@Nullable String alias) {
Log.i("Selected key alias=" + alias); Log.i("Selected key alias=" + alias);
args.putString("alias", alias); if (alias != null) {
handler.post(new Runnable() { args.putString("alias", alias);
@Override handler.post(new Runnable() {
public void run() { @Override
try { public void run() {
onSmime(args); try {
} catch (Throwable ex) { onSmime(args);
Log.e(ex); } catch (Throwable ex) {
Log.e(ex);
}
} }
} });
}); }
} }
}, },
null, null, null, -1, null); null, null, null, -1, null);
@ -4077,14 +4062,6 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences.
if (resultCode == RESULT_OK) if (resultCode == RESULT_OK)
onEmptyFolder(data.getBundleExtra("args")); onEmptyFolder(data.getBundleExtra("args"));
break; break;
case REQUEST_CERTIFICATE:
Bundle args = new Bundle();
args.putLong("id", message);
args.putInt("type", EntityMessage.SMIME_SIGNONLY);
if (resultCode == RESULT_OK && data != null)
args.putParcelable("uri", data.getData());
onSmime(args);
break;
} }
} catch (Throwable ex) { } catch (Throwable ex) {
Log.e(ex); Log.e(ex);
@ -4382,36 +4359,15 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences.
} }
private void onSmime(Bundle args) { private void onSmime(Bundle args) {
new SimpleTask<Boolean>() { new SimpleTask<String>() {
@Override @Override
protected Boolean onExecute(Context context, Bundle args) throws Throwable { protected String onExecute(Context context, Bundle args) throws Throwable {
long id = args.getLong("id"); long id = args.getLong("id");
int type = args.getInt("type"); int type = args.getInt("type");
DB db = DB.getInstance(context); DB db = DB.getInstance(context);
if (EntityMessage.SMIME_SIGNONLY.equals(type)) { if (EntityMessage.SMIME_SIGNONLY.equals(type)) {
PublicKey pubkey = null;
if (args.containsKey("uri")) {
Uri uri = args.getParcelable("uri");
// Read certificate
PemObject pem;
ContentResolver resolver = context.getContentResolver();
AssetFileDescriptor descriptor = resolver.openTypedAssetFileDescriptor(uri, "*/*", null);
try (InputStream is = new BufferedInputStream(descriptor.createInputStream())) {
pem = new PemReader(new InputStreamReader(is)).readPemObject();
}
if (pem == null)
throw new IllegalArgumentException("Invalid certificate");
// Get public key
CertificateFactory factory = CertificateFactory.getInstance("X.509");
ByteArrayInputStream in = new ByteArrayInputStream(pem.getContent());
X509Certificate result = (X509Certificate) factory.generateCertificate(in);
pubkey = result.getPublicKey();
}
// Get content/signature // Get content/signature
File content = null; File content = null;
File signature = null; File signature = null;
@ -4441,20 +4397,20 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences.
Store store = signedData.getCertificates(); Store store = signedData.getCertificates();
SignerInformationStore signerInfos = signedData.getSignerInfos(); SignerInformationStore signerInfos = signedData.getSignerInfos();
for (SignerInformation signer : signerInfos.getSigners()) for (SignerInformation signer : signerInfos.getSigners())
for (Object cert : store.getMatches(signer.getSID())) { for (Object match : store.getMatches(signer.getSID())) {
X509CertificateHolder certHolder = (X509CertificateHolder) cert; X509CertificateHolder certHolder = (X509CertificateHolder) match;
if (signer.verify(new JcaSimpleSignerInfoVerifierBuilder().build(certHolder))) { X509Certificate cert = new JcaX509CertificateConverter()
// Check public key .setProvider(new BouncyCastleProvider())
if (certHolder.isValidOn(new Date()) && .getCertificate(certHolder);
pubkey != null && try {
signer.verify(new JcaSimpleSignerInfoVerifierBuilder().build(pubkey))) if (signer.verify(new JcaSimpleSignerInfoVerifierBuilder().build(cert)))
return true; return cert.getSubjectDN().getName();
else } catch (CMSVerifierCertificateNotValidException ex) {
return null; Log.w(ex);
} }
} }
return false; return null;
} else { } else {
String alias = args.getString("alias"); String alias = args.getString("alias");
if (alias == null) if (alias == null)
@ -4533,15 +4489,13 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences.
} }
@Override @Override
protected void onExecuted(Bundle args, Boolean result) { protected void onExecuted(Bundle args, String result) {
int type = args.getInt("type"); int type = args.getInt("type");
if (EntityMessage.SMIME_SIGNONLY.equals(type)) if (EntityMessage.SMIME_SIGNONLY.equals(type))
if (result == null) if (result == null)
Snackbar.make(view, R.string.title_signature_unconfirmed, Snackbar.LENGTH_LONG).show();
else if (result)
Snackbar.make(view, R.string.title_signature_valid, Snackbar.LENGTH_LONG).show();
else
Snackbar.make(view, R.string.title_signature_invalid, Snackbar.LENGTH_LONG).show(); Snackbar.make(view, R.string.title_signature_invalid, Snackbar.LENGTH_LONG).show();
else
Snackbar.make(view, getString(R.string.title_signature_signed_by, result), Snackbar.LENGTH_LONG).show();
} }
@Override @Override

View file

@ -705,6 +705,7 @@
<string name="title_signature_valid">Message signature valid</string> <string name="title_signature_valid">Message signature valid</string>
<string name="title_signature_unconfirmed">Message signature valid but not confirmed</string> <string name="title_signature_unconfirmed">Message signature valid but not confirmed</string>
<string name="title_signature_invalid">Message signature invalid</string> <string name="title_signature_invalid">Message signature invalid</string>
<string name="title_signature_signed_by">Message signature valid, signed by: %1$s</string>
<string name="title_search">Search</string> <string name="title_search">Search</string>
<string name="title_search_server">Search on server</string> <string name="title_search_server">Search on server</string>