From 87482f28ae08a5a3f936573abf4458a358bfe7c1 Mon Sep 17 00:00:00 2001 From: M66B Date: Thu, 5 Dec 2019 11:38:11 +0100 Subject: [PATCH] Improved S/MIME signature handling --- .../java/eu/faircode/email/ActivitySetup.java | 22 +------ .../eu/faircode/email/FragmentMessages.java | 59 ++++++++----------- .../main/java/eu/faircode/email/Helper.java | 26 ++++++++ 3 files changed, 55 insertions(+), 52 deletions(-) diff --git a/app/src/main/java/eu/faircode/email/ActivitySetup.java b/app/src/main/java/eu/faircode/email/ActivitySetup.java index c1c841261f..e0ac70fe61 100644 --- a/app/src/main/java/eu/faircode/email/ActivitySetup.java +++ b/app/src/main/java/eu/faircode/email/ActivitySetup.java @@ -74,7 +74,6 @@ import com.microsoft.identity.client.IPublicClientApplication; import com.microsoft.identity.client.PublicClientApplication; import com.microsoft.identity.client.exception.MsalException; -import org.bouncycastle.asn1.x509.GeneralName; import org.bouncycastle.util.io.pem.PemObject; import org.bouncycastle.util.io.pem.PemReader; import org.json.JSONArray; @@ -93,12 +92,10 @@ import java.net.URL; import java.nio.charset.StandardCharsets; import java.security.SecureRandom; import java.security.cert.CertificateFactory; -import java.security.cert.CertificateParsingException; import java.security.cert.X509Certificate; import java.security.spec.KeySpec; import java.text.SimpleDateFormat; import java.util.ArrayList; -import java.util.Collection; import java.util.Date; import java.util.HashMap; import java.util.List; @@ -114,7 +111,6 @@ import javax.crypto.SecretKey; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.PBEKeySpec; -import javax.security.auth.x500.X500Principal; public class ActivitySetup extends ActivityBase implements FragmentManager.OnBackStackChangedListener { private View view; @@ -1027,28 +1023,16 @@ public class ActivitySetup extends ActivityBase implements FragmentManager.OnBac CertificateFactory fact = CertificateFactory.getInstance("X.509"); X509Certificate cert = (X509Certificate) fact.generateCertificate(bis); - String email = "?"; - try { - Collection> altNames = cert.getSubjectAlternativeNames(); - if (altNames != null) - for (List altName : altNames) - if (altName.get(0).equals(GeneralName.rfc822Name)) - email = (String) altName.get(1); - else - Log.i("Alt type=" + altName.get(0) + " data=" + altName.get(1)); - } catch (CertificateParsingException ex) { - Log.w(ex); - } - String fingerprint = Helper.sha256(cert.getEncoded()); + String email = Helper.getAltSubjectName(cert); DB db = DB.getInstance(context); EntityCertificate record = db.certificate().getCertificate(fingerprint, email); if (record == null) { record = new EntityCertificate(); - record.fingerprint = Helper.sha256(cert.getEncoded()); + record.fingerprint = fingerprint; record.email = email; - record.subject = cert.getSubjectX500Principal().getName(X500Principal.RFC2253); + record.subject = Helper.getSubject(cert); record.setEncoded(cert.getEncoded()); record.id = db.certificate().insertCertificate(record); } diff --git a/app/src/main/java/eu/faircode/email/FragmentMessages.java b/app/src/main/java/eu/faircode/email/FragmentMessages.java index 5b0b2ced2a..547c22b2ca 100644 --- a/app/src/main/java/eu/faircode/email/FragmentMessages.java +++ b/app/src/main/java/eu/faircode/email/FragmentMessages.java @@ -142,7 +142,6 @@ import java.io.InputStream; import java.io.OutputStream; import java.nio.charset.StandardCharsets; import java.security.PrivateKey; -import java.security.cert.CertificateEncodingException; import java.security.cert.X509Certificate; import java.text.Collator; import java.text.DateFormat; @@ -164,9 +163,7 @@ import java.util.Properties; import javax.mail.FolderClosedException; import javax.mail.Session; -import javax.mail.internet.InternetAddress; import javax.mail.internet.MimeMessage; -import javax.security.auth.x500.X500Principal; import static android.app.Activity.RESULT_OK; import static android.os.Process.THREAD_PRIORITY_BACKGROUND; @@ -4342,9 +4339,9 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences. } private void onSmime(Bundle args) { - new SimpleTask() { + new SimpleTask() { @Override - protected X509Certificate onExecute(Context context, Bundle args) throws Throwable { + protected Boolean onExecute(Context context, Bundle args) throws Throwable { long id = args.getLong("id"); int type = args.getInt("type"); @@ -4389,8 +4386,19 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences. X509Certificate cert = new JcaX509CertificateConverter() .getCertificate(certHolder); try { - if (signer.verify(new JcaSimpleSignerInfoVerifierBuilder().build(cert))) - return cert; + if (signer.verify(new JcaSimpleSignerInfoVerifierBuilder().build(cert))) { + String fingerprint = Helper.sha256(cert.getEncoded()); + String email = Helper.getAltSubjectName(cert); + EntityCertificate record = db.certificate().getCertificate(fingerprint, email); + + args.putString("fingerprint", fingerprint); + args.putString("email", email); + args.putString("subject", Helper.getSubject(cert)); + args.putByteArray("encoded", cert.getEncoded()); + args.putBoolean("known", record != null); + + return true; + } } catch (CMSVerifierCertificateNotValidException ex) { Log.w(ex); } @@ -4505,31 +4513,24 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences. } @Override - protected void onExecuted(Bundle args, X509Certificate cert) { + protected void onExecuted(final Bundle args, Boolean valid) { int type = args.getInt("type"); if (EntityMessage.SMIME_SIGNONLY.equals(type)) - if (cert == null) + if (valid == null || !valid) Snackbar.make(view, R.string.title_signature_invalid, Snackbar.LENGTH_LONG).show(); + else if (args.getBoolean("known")) + Snackbar.make(view, R.string.title_signature_valid, Snackbar.LENGTH_LONG).show(); else { - String name = cert.getSubjectX500Principal().getName(X500Principal.RFC2253); new AlertDialog.Builder(getContext()) .setTitle(R.string.title_signature_valid) - .setMessage(name) + .setMessage(args.getString("email") + ": " + args.getString("subject")) .setPositiveButton(R.string.title_signature_store, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { - try { - args.putByteArray("encoded", cert.getEncoded()); - } catch (CertificateEncodingException ex) { - Helper.unexpectedError(getParentFragmentManager(), ex); - return; - } - new SimpleTask() { @Override protected Void onExecute(Context context, Bundle args) throws Throwable { long id = args.getLong("id"); - byte[] encoded = args.getByteArray("encoded"); DB db = DB.getInstance(context); @@ -4537,20 +4538,12 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences. if (message == null) return null; - if (message.from != null && message.from.length == 1) { - String fingerprint = Helper.sha256(encoded); - String email = ((InternetAddress) message.from[0]).getAddress(); - EntityCertificate record = db.certificate().getCertificate(fingerprint, email); - if (record == null) { - record = new EntityCertificate(); - record.fingerprint = fingerprint; - record.email = email; - record.subject = cert.getSubjectX500Principal().getName(X500Principal.RFC2253); - record.setEncoded(encoded); - record.id = db.certificate().insertCertificate(record); - } else - Log.i("Certificate already stored"); - } + EntityCertificate record = new EntityCertificate(); + record.fingerprint = args.getString("fingerprint"); + record.email = args.getString("email"); + record.subject = args.getString("subject"); + record.setEncoded(args.getByteArray("encoded")); + record.id = db.certificate().insertCertificate(record); return null; } diff --git a/app/src/main/java/eu/faircode/email/Helper.java b/app/src/main/java/eu/faircode/email/Helper.java index 67b9a9b551..512c9b9c07 100644 --- a/app/src/main/java/eu/faircode/email/Helper.java +++ b/app/src/main/java/eu/faircode/email/Helper.java @@ -85,6 +85,8 @@ import com.sun.mail.iap.BadCommandException; import com.sun.mail.iap.ConnectionException; import com.sun.mail.util.FolderClosedIOException; +import org.bouncycastle.asn1.x509.GeneralName; + import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; @@ -96,10 +98,13 @@ import java.io.UnsupportedEncodingException; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateParsingException; +import java.security.cert.X509Certificate; import java.text.DateFormat; import java.text.DecimalFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; +import java.util.Collection; import java.util.Date; import java.util.List; import java.util.Locale; @@ -116,6 +121,7 @@ import java.util.concurrent.atomic.AtomicInteger; import javax.mail.FolderClosedException; import javax.mail.MessageRemovedException; import javax.mail.MessagingException; +import javax.security.auth.x500.X500Principal; import static android.os.Process.THREAD_PRIORITY_BACKGROUND; import static androidx.browser.customtabs.CustomTabsService.ACTION_CUSTOM_TABS_CONNECTION; @@ -948,6 +954,26 @@ public class Helper { prefs.edit().remove("last_authentication").apply(); } + static String getSubject(X509Certificate certificate) { + return certificate.getSubjectX500Principal().getName(X500Principal.RFC2253); + } + + static String getAltSubjectName(X509Certificate certificate) { + try { + Collection> altNames = certificate.getSubjectAlternativeNames(); + if (altNames != null) + for (List altName : altNames) + if (altName.get(0).equals(GeneralName.rfc822Name)) + return (String) altName.get(1); + else + Log.i("Alt type=" + altName.get(0) + " data=" + altName.get(1)); + } catch (CertificateParsingException ex) { + Log.w(ex); + } + + return "?"; + } + // Miscellaneous static List> chunkList(List list, int size) {