From 3ade2e002f972a0edd393b477e622aeccd583daf Mon Sep 17 00:00:00 2001 From: M66B Date: Fri, 6 Dec 2019 10:14:09 +0100 Subject: [PATCH] Export/import certificates --- .../java/eu/faircode/email/ActivitySetup.java | 25 ++++++- .../eu/faircode/email/DaoCertificate.java | 3 + .../eu/faircode/email/EntityCertificate.java | 75 ++++++++++++++++++- .../eu/faircode/email/FragmentCompose.java | 9 +-- .../eu/faircode/email/FragmentMessages.java | 29 +++---- .../main/java/eu/faircode/email/Helper.java | 33 -------- 6 files changed, 115 insertions(+), 59 deletions(-) diff --git a/app/src/main/java/eu/faircode/email/ActivitySetup.java b/app/src/main/java/eu/faircode/email/ActivitySetup.java index 01bb9abbac..4fb2f8e79f 100644 --- a/app/src/main/java/eu/faircode/email/ActivitySetup.java +++ b/app/src/main/java/eu/faircode/email/ActivitySetup.java @@ -549,6 +549,11 @@ public class ActivitySetup extends ActivityBase implements FragmentManager.OnBac for (EntityAnswer answer : db.answer().getAnswers(true)) janswers.put(answer.toJSON()); + // Certificates + JSONArray jcertificates = new JSONArray(); + for (EntityCertificate certificate : db.certificate().getCertificates()) + jcertificates.put(certificate.toJSON()); + // Settings SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); JSONArray jsettings = new JSONArray(); @@ -576,6 +581,7 @@ public class ActivitySetup extends ActivityBase implements FragmentManager.OnBac JSONObject jexport = new JSONObject(); jexport.put("accounts", jaccounts); jexport.put("answers", janswers); + jexport.put("certificates", jcertificates); jexport.put("settings", jsettings); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { @@ -880,6 +886,17 @@ public class ActivitySetup extends ActivityBase implements FragmentManager.OnBac db.account().updateAccount(account); } + JSONArray jcertificates = jimport.getJSONArray("certificates"); + for (int c = 0; c < jcertificates.length(); c++) { + JSONObject jcertificate = (JSONObject) jcertificates.get(c); + EntityCertificate certificate = EntityCertificate.fromJSON(jcertificate); + EntityCertificate record = db.certificate().getCertificate(certificate.fingerprint, certificate.email); + if (record == null) { + db.certificate().insertCertificate(certificate); + Log.i("Imported certificate=" + certificate.email); + } + } + // Settings SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); SharedPreferences.Editor editor = prefs.edit(); @@ -1044,9 +1061,9 @@ public class ActivitySetup extends ActivityBase implements FragmentManager.OnBac } } - String fingerprint = Helper.getFingerprint(cert); - List emails = Helper.getAltSubjectName(cert); - String subject = Helper.getSubject(cert); + String fingerprint = EntityCertificate.getFingerprint(cert); + List emails = EntityCertificate.getAltSubjectName(cert); + String subject = EntityCertificate.getSubject(cert); DB db = DB.getInstance(context); for (String email : emails) { @@ -1056,7 +1073,7 @@ public class ActivitySetup extends ActivityBase implements FragmentManager.OnBac record.fingerprint = fingerprint; record.email = email; record.subject = subject; - record.setEncoded(cert.getEncoded()); + record.setCertificate(cert); record.id = db.certificate().insertCertificate(record); } } diff --git a/app/src/main/java/eu/faircode/email/DaoCertificate.java b/app/src/main/java/eu/faircode/email/DaoCertificate.java index 632fc4fd1e..3b04d73f92 100644 --- a/app/src/main/java/eu/faircode/email/DaoCertificate.java +++ b/app/src/main/java/eu/faircode/email/DaoCertificate.java @@ -28,6 +28,9 @@ import java.util.List; @Dao public interface DaoCertificate { + @Query("SELECT * FROM certificate") + List getCertificates(); + @Query("SELECT * FROM certificate" + " ORDER BY email, subject") LiveData> liveCertificates(); diff --git a/app/src/main/java/eu/faircode/email/EntityCertificate.java b/app/src/main/java/eu/faircode/email/EntityCertificate.java index bfe7abde84..d37debaf24 100644 --- a/app/src/main/java/eu/faircode/email/EntityCertificate.java +++ b/app/src/main/java/eu/faircode/email/EntityCertificate.java @@ -26,8 +26,24 @@ import androidx.room.Entity; import androidx.room.Index; import androidx.room.PrimaryKey; +import org.bouncycastle.asn1.x509.GeneralName; +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.ByteArrayInputStream; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateEncodingException; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.CertificateParsingException; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; import java.util.Objects; +import javax.security.auth.x500.X500Principal; + @Entity( tableName = EntityCertificate.TABLE_NAME, foreignKeys = { @@ -50,14 +66,69 @@ public class EntityCertificate { @NonNull public String data; - void setEncoded(byte[] encoded) { + private void setEncoded(byte[] encoded) { this.data = Base64.encodeToString(encoded, Base64.NO_WRAP); } - byte[] getEncoded() { + private byte[] getEncoded() { return Base64.decode(this.data, Base64.NO_WRAP); } + void setCertificate(X509Certificate certificate) throws CertificateEncodingException { + setEncoded(certificate.getEncoded()); + } + + X509Certificate getCertificate() throws CertificateException { + return (X509Certificate) CertificateFactory.getInstance("X.509") + .generateCertificate(new ByteArrayInputStream(getEncoded())); + } + + static String getFingerprint(X509Certificate certificate) throws CertificateEncodingException, NoSuchAlgorithmException { + return Helper.sha256(certificate.getEncoded()); + } + + static String getSubject(X509Certificate certificate) { + return certificate.getSubjectX500Principal().getName(X500Principal.RFC2253); + } + + static List getAltSubjectName(X509Certificate certificate) { + List result = new ArrayList<>(); + try { + Collection> altNames = certificate.getSubjectAlternativeNames(); + if (altNames != null) + for (List altName : altNames) + if (altName.get(0).equals(GeneralName.rfc822Name)) + result.add((String) altName.get(1)); + else + Log.i("Alt type=" + altName.get(0) + " data=" + altName.get(1)); + } catch (CertificateParsingException ex) { + Log.w(ex); + } + + return result; + } + + public JSONObject toJSON() throws JSONException { + JSONObject json = new JSONObject(); + json.put("id", id); + json.put("email", email); + json.put("data", data); + return json; + } + + public static EntityCertificate fromJSON(JSONObject json) throws JSONException, CertificateException, NoSuchAlgorithmException { + EntityCertificate certificate = new EntityCertificate(); + certificate.id = json.getLong("id"); + certificate.email = json.getString("email"); + certificate.data = json.getString("data"); + + X509Certificate cert = certificate.getCertificate(); + certificate.fingerprint = getFingerprint(cert); + certificate.subject = getSubject(cert); + + return certificate; + } + @Override public boolean equals(Object obj) { if (obj instanceof EntityCertificate) { diff --git a/app/src/main/java/eu/faircode/email/FragmentCompose.java b/app/src/main/java/eu/faircode/email/FragmentCompose.java index 425891b0bd..5e3957bb64 100644 --- a/app/src/main/java/eu/faircode/email/FragmentCompose.java +++ b/app/src/main/java/eu/faircode/email/FragmentCompose.java @@ -126,7 +126,6 @@ import org.openintents.openpgp.util.OpenPgpApi; import org.openintents.openpgp.util.OpenPgpServiceConnection; import java.io.BufferedOutputStream; -import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; @@ -136,7 +135,6 @@ import java.io.InputStream; import java.io.OutputStream; import java.net.UnknownHostException; import java.security.PrivateKey; -import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.text.DateFormat; import java.text.SimpleDateFormat; @@ -2029,11 +2027,8 @@ public class FragmentCompose extends FragmentBase { List acertificates = db.certificate().getCertificateByEmail(email); if (acertificates == null || acertificates.size() == 0) throw new IllegalArgumentException(context.getString(R.string.title_certificate_missing, email), new IllegalStateException()); - for (EntityCertificate acertificate : acertificates) { - X509Certificate cert = (X509Certificate) CertificateFactory.getInstance("X.509") - .generateCertificate(new ByteArrayInputStream(acertificate.getEncoded())); - certs.add(cert); - } + for (EntityCertificate acertificate : acertificates) + certs.add(acertificate.getCertificate()); } // Build signature diff --git a/app/src/main/java/eu/faircode/email/FragmentMessages.java b/app/src/main/java/eu/faircode/email/FragmentMessages.java index b7d31c25e9..0beb2f3fda 100644 --- a/app/src/main/java/eu/faircode/email/FragmentMessages.java +++ b/app/src/main/java/eu/faircode/email/FragmentMessages.java @@ -4420,8 +4420,8 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences. try { if (signer.verify(new JcaSimpleSignerInfoVerifierBuilder().build(cert))) { boolean known = true; - String fingerprint = Helper.getFingerprint(cert); - List emails = Helper.getAltSubjectName(cert); + String fingerprint = EntityCertificate.getFingerprint(cert); + List emails = EntityCertificate.getAltSubjectName(cert); for (String email : emails) { EntityCertificate record = db.certificate().getCertificate(fingerprint, email); if (record == null) @@ -4558,7 +4558,7 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences. boolean known = args.getBoolean("known"); boolean match = false; - List emails = (cert == null ? Collections.emptyList() : Helper.getAltSubjectName(cert)); + List emails = (cert == null ? Collections.emptyList() : EntityCertificate.getAltSubjectName(cert)); for (String email : emails) if (Objects.equals(sender, email)) { match = true; @@ -4580,7 +4580,7 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences. tvSender.setText(sender); tvEmail.setText(TextUtils.join(",", emails)); tvEmailInvalid.setVisibility(match ? View.GONE : View.VISIBLE); - tvSubject.setText(Helper.getSubject(cert)); + tvSubject.setText(EntityCertificate.getSubject(cert)); AlertDialog.Builder builder = new AlertDialog.Builder(getContext()) .setView(dview) @@ -4607,16 +4607,19 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences. if (message == null) return null; - String fingerprint = Helper.getFingerprint(cert); - List emails = Helper.getAltSubjectName(cert); - String subject = Helper.getSubject(cert); + String fingerprint = EntityCertificate.getFingerprint(cert); + List emails = EntityCertificate.getAltSubjectName(cert); + String subject = EntityCertificate.getSubject(cert); for (String email : emails) { - EntityCertificate record = new EntityCertificate(); - record.fingerprint = fingerprint; - record.email = email; - record.subject = subject; - record.setEncoded(encoded); - record.id = db.certificate().insertCertificate(record); + EntityCertificate record = db.certificate().getCertificate(fingerprint, email); + if (record == null) { + record = new EntityCertificate(); + record.fingerprint = fingerprint; + record.email = email; + record.subject = subject; + record.setCertificate(cert); + 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 d47283813d..9d971d8e2f 100644 --- a/app/src/main/java/eu/faircode/email/Helper.java +++ b/app/src/main/java/eu/faircode/email/Helper.java @@ -84,8 +84,6 @@ import androidx.recyclerview.widget.RecyclerView; import com.google.android.material.bottomnavigation.BottomNavigationView; -import org.bouncycastle.asn1.x509.GeneralName; - import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; @@ -97,14 +95,10 @@ import java.io.UnsupportedEncodingException; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; -import java.security.cert.CertificateEncodingException; -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; @@ -118,8 +112,6 @@ import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; -import javax.security.auth.x500.X500Principal; - import static android.os.Process.THREAD_PRIORITY_BACKGROUND; import static androidx.browser.customtabs.CustomTabsService.ACTION_CUSTOM_TABS_CONNECTION; @@ -816,31 +808,6 @@ public class Helper { prefs.edit().remove("last_authentication").apply(); } - static String getFingerprint(X509Certificate certificate) throws CertificateEncodingException, NoSuchAlgorithmException { - return sha256(certificate.getEncoded()); - } - - static String getSubject(X509Certificate certificate) { - return certificate.getSubjectX500Principal().getName(X500Principal.RFC2253); - } - - static List getAltSubjectName(X509Certificate certificate) { - List result = new ArrayList<>(); - try { - Collection> altNames = certificate.getSubjectAlternativeNames(); - if (altNames != null) - for (List altName : altNames) - if (altName.get(0).equals(GeneralName.rfc822Name)) - result.add((String) altName.get(1)); - else - Log.i("Alt type=" + altName.get(0) + " data=" + altName.get(1)); - } catch (CertificateParsingException ex) { - Log.w(ex); - } - - return result; - } - static void selectKeyAlias(final Activity activity, final String email, final IKeyAlias intf) { final Context context = activity.getApplicationContext(); final Handler handler = new Handler();