Added certificate import

pull/168/head
M66B 6 years ago
parent 2b3e119445
commit a4e5403f9d

@ -86,7 +86,7 @@ Related questions:
* ~~Unified starred messages view~~ (there is already a special search for this)
* ~~Notification move action~~
* Search for settings: low priority
* S/MIME: waiting for sponsoring
* S/MIME support
Anything on this list is in random order and *might* be added in the near future.
@ -591,6 +591,12 @@ Signed-only messages are supported, encrypted-only messages are not supported.
For S/MIME support, please see the [planned features](#user-content-planned-features).
Extract a public key from a S/MIME certificate:
```
openssl pkcs12 -in filename.pfx -clcerts -nokeys -out cert.pem
```
Please see [this comment](https://forum.xda-developers.com/showpost.php?p=79444379&postcount=5609)
about [these vulnerabilities](https://amp.thehackernews.com/thn/2019/04/email-signature-spoofing.html).

@ -20,6 +20,7 @@ package eu.faircode.email;
*/
import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@ -43,7 +44,7 @@ public class AdapterCertificate extends RecyclerView.Adapter<AdapterCertificate.
private List<EntityCertificate> items = new ArrayList<>();
public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener, View.OnLongClickListener {
private View view;
private TextView tvEmail;
private TextView tvSubject;
@ -66,8 +67,40 @@ public class AdapterCertificate extends RecyclerView.Adapter<AdapterCertificate.
intf.onSelected(certificate);
}
@Override
public boolean onLongClick(View v) {
int pos = getAdapterPosition();
if (pos == RecyclerView.NO_POSITION)
return false;
EntityCertificate certificate = items.get(pos);
Bundle args = new Bundle();
args.putLong("id", certificate.id);
new SimpleTask<Void>() {
@Override
protected Void onExecute(Context context, Bundle args) throws Throwable {
long id = args.getLong("id");
DB db = DB.getInstance(context);
db.certificate().deleteCertificate(id);
return null;
}
@Override
protected void onException(Bundle args, Throwable ex) {
// TODO: report error
}
}.execute(context, owner, args, "certificate:delete");
return true;
}
private void wire() {
view.setOnClickListener(this);
view.setOnLongClickListener(this);
}
private void unwire() {

@ -47,4 +47,7 @@ public interface DaoCertificate {
@Insert
long insertCertificate(EntityCertificate certificate);
@Query("DELETE FROM certificate WHERE id = :id")
void deleteCertificate(long id);
}

@ -71,6 +71,7 @@ import android.view.ViewGroup;
import android.view.inputmethod.InputMethodManager;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.EditText;
@ -119,6 +120,8 @@ import org.bouncycastle.operator.OutputEncryptor;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
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.Element;
import org.jsoup.nodes.TextNode;
@ -135,6 +138,7 @@ import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.UnknownHostException;
import java.security.PrivateKey;
@ -169,6 +173,7 @@ import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
import javax.mail.internet.ParseException;
import javax.mail.util.ByteArrayDataSource;
import javax.security.auth.x500.X500Principal;
import static android.app.Activity.RESULT_CANCELED;
import static android.app.Activity.RESULT_OK;
@ -255,7 +260,7 @@ public class FragmentCompose extends FragmentBase {
private static final int REQUEST_LINK = 12;
private static final int REQUEST_DISCARD = 13;
private static final int REQUEST_SEND = 14;
private static final int REQUEST_SELECT_CERTIFICATE = 15;
private static final int REQUEST_CERTIFICATE = 15;
@Override
public void onCreate(Bundle savedInstanceState) {
@ -1248,15 +1253,20 @@ public class FragmentCompose extends FragmentBase {
@Override
public void run() {
try {
String email = null;
if (draft.to != null && draft.to.length == 1)
email = ((InternetAddress) draft.to[0]).getAddress();
Bundle args = new Bundle();
args.putLong("id", draft.id);
args.putInt("type", draft.encrypt);
args.putString("email", email);
args.putString("alias", alias);
if (EntityMessage.SMIME_SIGNENCRYPT.equals(draft.encrypt)) {
FragmentDialogCertificate fragment = new FragmentDialogCertificate();
fragment.setArguments(args);
fragment.setTargetFragment(FragmentCompose.this, REQUEST_SELECT_CERTIFICATE);
fragment.setTargetFragment(FragmentCompose.this, REQUEST_CERTIFICATE);
fragment.show(getParentFragmentManager(), "compose:certificate");
} else
onSmime(args);
@ -1379,7 +1389,7 @@ public class FragmentCompose extends FragmentBase {
if (resultCode == RESULT_OK)
onActionSend();
break;
case REQUEST_SELECT_CERTIFICATE:
case REQUEST_CERTIFICATE:
if (resultCode == RESULT_OK && data != null)
onSmime(data.getBundleExtra("args"));
break;
@ -3938,11 +3948,16 @@ public class FragmentCompose extends FragmentBase {
}
public static class FragmentDialogCertificate extends FragmentDialogBase {
private String email;
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
email = getArguments().getString("email");
View dview = LayoutInflater.from(getContext()).inflate(R.layout.dialog_certificate, null);
final RecyclerView rvCertificate = dview.findViewById(R.id.rvCertificate);
final Button btnImport = dview.findViewById(R.id.btnImport);
final ProgressBar pbWait = dview.findViewById(R.id.pbWait);
final Dialog dialog = new AlertDialog.Builder(getContext())
@ -3964,6 +3979,22 @@ public class FragmentCompose extends FragmentBase {
});
rvCertificate.setAdapter(adapter);
btnImport.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("*/*");
Helper.openAdvanced(intent);
PackageManager pm = getContext().getPackageManager();
if (intent.resolveActivity(pm) == null)
ToastEx.makeText(getContext(), R.string.title_no_saf, Toast.LENGTH_LONG).show();
else
startActivityForResult(Helper.getChooser(getContext(), intent), 1);
}
});
btnImport.setEnabled(email != null);
rvCertificate.setVisibility(View.GONE);
pbWait.setVisibility(View.VISIBLE);
@ -3979,6 +4010,54 @@ public class FragmentCompose extends FragmentBase {
return dialog;
}
@Override
public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
if (resultCode == RESULT_OK && data != null) {
Uri uri = data.getData();
if (uri != null) {
Bundle args = new Bundle();
args.putParcelable("uri", uri);
new SimpleTask<Void>() {
@Override
protected Void onExecute(Context context, Bundle args) throws Throwable {
Uri uri = args.getParcelable("uri");
PemObject pem;
try (InputStream is = context.getContentResolver().openInputStream(uri)) {
pem = new PemReader(new InputStreamReader(is)).readPemObject();
}
ByteArrayInputStream bis = new ByteArrayInputStream(pem.getContent());
CertificateFactory fact = CertificateFactory.getInstance("X.509");
X509Certificate cert = (X509Certificate) fact.generateCertificate(bis);
String fingerprint = Helper.sha256(cert.getEncoded());
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.email = email;
record.subject = cert.getSubjectX500Principal().getName(X500Principal.RFC2253);
record.setEncoded(cert.getEncoded());
record.id = db.certificate().insertCertificate(record);
}
// TODO: report exists
return null;
}
@Override
protected void onException(Bundle args, Throwable ex) {
Helper.unexpectedError(getParentFragmentManager(), ex);
}
}.execute(this, args, "compose:cert");
}
}
}
}
public static class FragmentDialogSend extends FragmentDialogBase {

@ -2,19 +2,31 @@
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content">
android:layout_height="wrap_content"
android:padding="12dp">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvCertificate"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:padding="12dp"
android:scrollbarStyle="outsideOverlay"
android:scrollbars="vertical"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/btnImport"
style="?android:attr/buttonStyleSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:minWidth="0dp"
android:minHeight="0dp"
android:text="@string/title_import_certificate"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/rvCertificate" />
<eu.faircode.email.ContentLoadingProgressBar
android:id="@+id/pbWait"
style="@style/Base.Widget.AppCompat.ProgressBar"

@ -28,7 +28,6 @@
android:layout_height="wrap_content"
android:text="CN=marcel@faircode.eu"
android:textAppearance="@android:style/TextAppearance.Small"
android:textIsSelectable="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tvEmail" />

@ -673,7 +673,8 @@
<string name="title_insert_template">Insert template</string>
<string name="title_edit_plain_text">Edit as plain text</string>
<string name="title_edit_formatted_text">Edit as reformatted text</string>
<string name="title_select_certificate">Select certificate</string>
<string name="title_select_certificate">Select public key</string>
<string name="title_import_certificate">Import</string>
<string name="title_send_plain_text">Plain text only</string>
<string name="title_send_receipt">Request delivery/read receipt</string>
<string name="title_send_receipt_remark">Most providers and email clients ignore receipt requests</string>

Loading…
Cancel
Save