mirror of
https://github.com/M66B/FairEmail.git
synced 2026-04-10 19:13:03 +02:00
Encryption improvements
This commit is contained in:
@@ -74,12 +74,16 @@ 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;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
@@ -88,9 +92,13 @@ import java.net.HttpURLConnection;
|
||||
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;
|
||||
@@ -106,6 +114,7 @@ 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;
|
||||
@@ -127,6 +136,7 @@ public class ActivitySetup extends ActivityBase implements FragmentManager.OnBac
|
||||
static final int REQUEST_IMPORT_OAUTH = 5;
|
||||
static final int REQUEST_CHOOSE_ACCOUNT = 6;
|
||||
static final int REQUEST_DONE = 7;
|
||||
static final int REQUEST_IMPORT_CERTIFICATE = 7;
|
||||
|
||||
static final String ACTION_QUICK_GMAIL = BuildConfig.APPLICATION_ID + ".ACTION_QUICK_GMAIL";
|
||||
static final String ACTION_QUICK_OUTLOOK = BuildConfig.APPLICATION_ID + ".ACTION_QUICK_OUTLOOK";
|
||||
@@ -135,7 +145,9 @@ public class ActivitySetup extends ActivityBase implements FragmentManager.OnBac
|
||||
static final String ACTION_VIEW_IDENTITIES = BuildConfig.APPLICATION_ID + ".ACTION_VIEW_IDENTITIES";
|
||||
static final String ACTION_EDIT_ACCOUNT = BuildConfig.APPLICATION_ID + ".EDIT_ACCOUNT";
|
||||
static final String ACTION_EDIT_IDENTITY = BuildConfig.APPLICATION_ID + ".EDIT_IDENTITY";
|
||||
static final String ACTION_MANAGE_LOCAL_CONTACTS = BuildConfig.APPLICATION_ID + ".LOCAL_CONTACTS";
|
||||
static final String ACTION_MANAGE_LOCAL_CONTACTS = BuildConfig.APPLICATION_ID + ".MANAGE_LOCAL_CONTACTS";
|
||||
static final String ACTION_MANAGE_CERTIFICATES = BuildConfig.APPLICATION_ID + ".MANAGE_CERTIFICATES";
|
||||
static final String ACTION_IMPORT_CERTIFICATE = BuildConfig.APPLICATION_ID + ".IMPORT_CERTIFICATE";
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
@@ -303,6 +315,8 @@ public class ActivitySetup extends ActivityBase implements FragmentManager.OnBac
|
||||
iff.addAction(ACTION_EDIT_ACCOUNT);
|
||||
iff.addAction(ACTION_EDIT_IDENTITY);
|
||||
iff.addAction(ACTION_MANAGE_LOCAL_CONTACTS);
|
||||
iff.addAction(ACTION_MANAGE_CERTIFICATES);
|
||||
iff.addAction(ACTION_IMPORT_CERTIFICATE);
|
||||
lbm.registerReceiver(receiver, iff);
|
||||
}
|
||||
|
||||
@@ -366,6 +380,10 @@ public class ActivitySetup extends ActivityBase implements FragmentManager.OnBac
|
||||
case REQUEST_IMPORT_OAUTH:
|
||||
ServiceSynchronize.reload(this, "oauth");
|
||||
break;
|
||||
case REQUEST_IMPORT_CERTIFICATE:
|
||||
if (resultCode == RESULT_OK && data != null)
|
||||
handleImportCertificate(data);
|
||||
break;
|
||||
}
|
||||
} catch (Throwable ex) {
|
||||
Log.e(ex);
|
||||
@@ -989,6 +1007,63 @@ public class ActivitySetup extends ActivityBase implements FragmentManager.OnBac
|
||||
}.execute(this, args, "setup:import");
|
||||
}
|
||||
|
||||
private void handleImportCertificate(Intent data) {
|
||||
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 email = "?";
|
||||
try {
|
||||
Collection<List<?>> 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());
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onException(Bundle args, Throwable ex) {
|
||||
Helper.unexpectedError(getSupportFragmentManager(), ex);
|
||||
}
|
||||
}.execute(this, args, "setup:cert");
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
private JSONObject channelToJSON(NotificationChannel channel) throws JSONException {
|
||||
JSONObject jchannel = new JSONObject();
|
||||
@@ -1302,6 +1377,23 @@ public class ActivitySetup extends ActivityBase implements FragmentManager.OnBac
|
||||
fragmentTransaction.commit();
|
||||
}
|
||||
|
||||
private void onManageCertificates(Intent intent) {
|
||||
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
|
||||
fragmentTransaction.replace(R.id.content_frame, new FragmentCertificates()).addToBackStack("certificates");
|
||||
fragmentTransaction.commit();
|
||||
}
|
||||
|
||||
private void onImportCertificate(Intent intent) {
|
||||
Intent open = new Intent(Intent.ACTION_OPEN_DOCUMENT);
|
||||
open.addCategory(Intent.CATEGORY_OPENABLE);
|
||||
open.setType("*/*");
|
||||
Helper.openAdvanced(open);
|
||||
if (open.resolveActivity(getPackageManager()) == null)
|
||||
ToastEx.makeText(this, R.string.title_no_saf, Toast.LENGTH_LONG).show();
|
||||
else
|
||||
startActivityForResult(Helper.getChooser(this, open), REQUEST_IMPORT_CERTIFICATE);
|
||||
}
|
||||
|
||||
private static Intent getIntentExport() {
|
||||
Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
|
||||
intent.addCategory(Intent.CATEGORY_OPENABLE);
|
||||
@@ -1397,6 +1489,10 @@ public class ActivitySetup extends ActivityBase implements FragmentManager.OnBac
|
||||
onEditIdentity(intent);
|
||||
else if (ACTION_MANAGE_LOCAL_CONTACTS.equals(action))
|
||||
onManageLocalContacts(intent);
|
||||
else if (ACTION_MANAGE_CERTIFICATES.equals(action))
|
||||
onManageCertificates(intent);
|
||||
else if (ACTION_IMPORT_CERTIFICATE.equals(action))
|
||||
onImportCertificate(intent);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -23,11 +23,14 @@ import android.content.Context;
|
||||
import android.graphics.Typeface;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.widget.PopupMenu;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.lifecycle.LifecycleOwner;
|
||||
import androidx.recyclerview.widget.DiffUtil;
|
||||
@@ -52,6 +55,8 @@ public class AdapterCertificate extends RecyclerView.Adapter<AdapterCertificate.
|
||||
private TextView tvEmail;
|
||||
private TextView tvSubject;
|
||||
|
||||
private TwoStateOwner powner = new TwoStateOwner(owner, "CertificatePopup");
|
||||
|
||||
ViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
|
||||
@@ -76,38 +81,65 @@ public class AdapterCertificate extends RecyclerView.Adapter<AdapterCertificate.
|
||||
if (pos == RecyclerView.NO_POSITION)
|
||||
return false;
|
||||
|
||||
EntityCertificate certificate = items.get(pos);
|
||||
final EntityCertificate certificate = items.get(pos);
|
||||
|
||||
Bundle args = new Bundle();
|
||||
args.putLong("id", certificate.id);
|
||||
PopupMenuLifecycle popupMenu = new PopupMenuLifecycle(context, powner, view);
|
||||
|
||||
new SimpleTask<Void>() {
|
||||
popupMenu.getMenu().add(Menu.NONE, 0, 0, certificate.email).setEnabled(false);
|
||||
|
||||
popupMenu.getMenu().add(Menu.NONE, R.string.title_delete, 1, R.string.title_delete);
|
||||
|
||||
popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
|
||||
@Override
|
||||
protected Void onExecute(Context context, Bundle args) throws Throwable {
|
||||
long id = args.getLong("id");
|
||||
public boolean onMenuItemClick(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.string.title_delete:
|
||||
onActionDelete();
|
||||
return true;
|
||||
|
||||
DB db = DB.getInstance(context);
|
||||
db.certificate().deleteCertificate(id);
|
||||
|
||||
return null;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onException(Bundle args, Throwable ex) {
|
||||
// TODO: report error
|
||||
private void onActionDelete() {
|
||||
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");
|
||||
}
|
||||
}.execute(context, owner, args, "certificate:delete");
|
||||
});
|
||||
|
||||
popupMenu.show();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void wire() {
|
||||
view.setOnClickListener(this);
|
||||
if (intf != null)
|
||||
view.setOnClickListener(this);
|
||||
view.setOnLongClickListener(this);
|
||||
}
|
||||
|
||||
private void unwire() {
|
||||
view.setOnClickListener(null);
|
||||
if (intf != null)
|
||||
view.setOnClickListener(null);
|
||||
view.setOnLongClickListener(null);
|
||||
}
|
||||
|
||||
private void bindTo(EntityCertificate certificate) {
|
||||
@@ -221,6 +253,11 @@ public class AdapterCertificate extends RecyclerView.Adapter<AdapterCertificate.
|
||||
holder.wire();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewDetachedFromWindow(@NonNull ViewHolder holder) {
|
||||
holder.powner.recreate();
|
||||
}
|
||||
|
||||
interface ICertificate {
|
||||
void onSelected(EntityCertificate certificate);
|
||||
}
|
||||
|
||||
110
app/src/main/java/eu/faircode/email/FragmentCertificates.java
Normal file
110
app/src/main/java/eu/faircode/email/FragmentCertificates.java
Normal file
@@ -0,0 +1,110 @@
|
||||
package eu.faircode.email;
|
||||
|
||||
/*
|
||||
This file is part of FairEmail.
|
||||
|
||||
FairEmail is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
FairEmail is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with FairEmail. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2018-2019 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.constraintlayout.widget.Group;
|
||||
import androidx.lifecycle.Observer;
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||
import androidx.recyclerview.widget.DividerItemDecoration;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class FragmentCertificates extends FragmentBase {
|
||||
private RecyclerView rvCertificate;
|
||||
private ContentLoadingProgressBar pbWait;
|
||||
private Group grpReady;
|
||||
private FloatingActionButton fab;
|
||||
|
||||
private AdapterCertificate adapter;
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
setSubtitle(R.string.title_advanced_manage_certificates);
|
||||
setHasOptionsMenu(true);
|
||||
|
||||
View view = inflater.inflate(R.layout.fragment_certificates, container, false);
|
||||
|
||||
// Get controls
|
||||
rvCertificate = view.findViewById(R.id.rvCertificate);
|
||||
pbWait = view.findViewById(R.id.pbWait);
|
||||
grpReady = view.findViewById(R.id.grpReady);
|
||||
fab = view.findViewById(R.id.fab);
|
||||
|
||||
// Wire controls
|
||||
|
||||
rvCertificate.setHasFixedSize(false);
|
||||
LinearLayoutManager llm = new LinearLayoutManager(getContext());
|
||||
rvCertificate.setLayoutManager(llm);
|
||||
|
||||
DividerItemDecoration itemDecorator = new DividerItemDecoration(getContext(), llm.getOrientation());
|
||||
itemDecorator.setDrawable(getContext().getDrawable(R.drawable.divider));
|
||||
rvCertificate.addItemDecoration(itemDecorator);
|
||||
|
||||
adapter = new AdapterCertificate(this, null);
|
||||
rvCertificate.setAdapter(adapter);
|
||||
|
||||
fab.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(getContext());
|
||||
lbm.sendBroadcast(new Intent(ActivitySetup.ACTION_IMPORT_CERTIFICATE));
|
||||
}
|
||||
});
|
||||
|
||||
// Initialize
|
||||
grpReady.setVisibility(View.GONE);
|
||||
pbWait.setVisibility(View.VISIBLE);
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
|
||||
DB db = DB.getInstance(getContext());
|
||||
db.certificate().liveCertificates(null).observe(getViewLifecycleOwner(), new Observer<List<EntityCertificate>>() {
|
||||
@Override
|
||||
public void onChanged(List<EntityCertificate> certificates) {
|
||||
if (certificates == null)
|
||||
certificates = new ArrayList<>();
|
||||
|
||||
adapter.set(null, certificates);
|
||||
|
||||
pbWait.setVisibility(View.GONE);
|
||||
grpReady.setVisibility(View.VISIBLE);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -71,7 +71,6 @@ 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;
|
||||
@@ -94,6 +93,7 @@ import androidx.documentfile.provider.DocumentFile;
|
||||
import androidx.lifecycle.Lifecycle;
|
||||
import androidx.lifecycle.Observer;
|
||||
import androidx.preference.PreferenceManager;
|
||||
import androidx.recyclerview.widget.DividerItemDecoration;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
@@ -120,8 +120,6 @@ 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;
|
||||
@@ -138,7 +136,6 @@ 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;
|
||||
@@ -173,7 +170,6 @@ 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;
|
||||
@@ -966,10 +962,10 @@ public class FragmentCompose extends FragmentBase {
|
||||
int colorEncrypt = Helper.resolveColor(getContext(), R.attr.colorEncrypt);
|
||||
ImageButton ib = (ImageButton) menu.findItem(R.id.menu_encrypt).getActionView();
|
||||
ib.setEnabled(!busy);
|
||||
if (EntityMessage.PGP_SIGNONLY.equals(encrypt)) {
|
||||
if (EntityMessage.PGP_SIGNONLY.equals(encrypt) || EntityMessage.SMIME_SIGNONLY.equals(encrypt)) {
|
||||
ib.setImageResource(R.drawable.baseline_gesture_24);
|
||||
ib.setImageTintList(null);
|
||||
} else if (EntityMessage.PGP_SIGNENCRYPT.equals(encrypt)) {
|
||||
} else if (EntityMessage.PGP_SIGNENCRYPT.equals(encrypt) || EntityMessage.SMIME_SIGNENCRYPT.equals(encrypt)) {
|
||||
ib.setImageResource(R.drawable.baseline_lock_24);
|
||||
ib.setImageTintList(ColorStateList.valueOf(colorEncrypt));
|
||||
} else {
|
||||
@@ -1037,12 +1033,25 @@ public class FragmentCompose extends FragmentBase {
|
||||
}
|
||||
|
||||
private void onMenuEncrypt() {
|
||||
if (EntityMessage.PGP_SIGNENCRYPT.equals(encrypt))
|
||||
encrypt = EntityMessage.PGP_SIGNONLY;
|
||||
else if (EntityMessage.PGP_SIGNONLY.equals(encrypt))
|
||||
encrypt = EntityMessage.ENCRYPT_NONE;
|
||||
else
|
||||
encrypt = EntityMessage.PGP_SIGNENCRYPT;
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext());
|
||||
String encrypt_method = prefs.getString("default_encrypt_method", "pgp");
|
||||
|
||||
if ("pgp".equals(encrypt_method)) {
|
||||
if (EntityMessage.ENCRYPT_NONE.equals(encrypt) || encrypt == null)
|
||||
encrypt = EntityMessage.PGP_SIGNENCRYPT;
|
||||
else if (EntityMessage.PGP_SIGNENCRYPT.equals(encrypt))
|
||||
encrypt = EntityMessage.PGP_SIGNONLY;
|
||||
else
|
||||
encrypt = EntityMessage.ENCRYPT_NONE;
|
||||
} else {
|
||||
if (EntityMessage.ENCRYPT_NONE.equals(encrypt) || encrypt == null)
|
||||
encrypt = EntityMessage.SMIME_SIGNENCRYPT;
|
||||
else if (EntityMessage.SMIME_SIGNENCRYPT.equals(encrypt))
|
||||
encrypt = EntityMessage.SMIME_SIGNONLY;
|
||||
else
|
||||
encrypt = EntityMessage.ENCRYPT_NONE;
|
||||
}
|
||||
|
||||
getActivity().invalidateOptionsMenu();
|
||||
|
||||
Bundle args = new Bundle();
|
||||
@@ -2481,6 +2490,7 @@ public class FragmentCompose extends FragmentBase {
|
||||
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
boolean plain_only = prefs.getBoolean("plain_only", false);
|
||||
String encrypt_method = prefs.getString("default_encrypt_method", "pgp");
|
||||
boolean sign_default = prefs.getBoolean("sign_default", false);
|
||||
boolean encrypt_default = prefs.getBoolean("encrypt_default", false);
|
||||
boolean receipt_default = prefs.getBoolean("receipt_default", false);
|
||||
@@ -2517,9 +2527,15 @@ public class FragmentCompose extends FragmentBase {
|
||||
if (plain_only)
|
||||
data.draft.plain_only = true;
|
||||
if (encrypt_default)
|
||||
data.draft.encrypt = EntityMessage.PGP_SIGNENCRYPT;
|
||||
if ("s/mime".equals(encrypt_method))
|
||||
data.draft.encrypt = EntityMessage.SMIME_SIGNENCRYPT;
|
||||
else
|
||||
data.draft.encrypt = EntityMessage.PGP_SIGNENCRYPT;
|
||||
else if (sign_default)
|
||||
data.draft.encrypt = EntityMessage.PGP_SIGNONLY;
|
||||
if ("s/mime".equals(encrypt_method))
|
||||
data.draft.encrypt = EntityMessage.SMIME_SIGNONLY;
|
||||
else
|
||||
data.draft.encrypt = EntityMessage.PGP_SIGNONLY;
|
||||
if (receipt_default)
|
||||
data.draft.receipt_request = true;
|
||||
|
||||
@@ -3957,7 +3973,6 @@ public class FragmentCompose extends FragmentBase {
|
||||
|
||||
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())
|
||||
@@ -3969,6 +3984,12 @@ public class FragmentCompose extends FragmentBase {
|
||||
LinearLayoutManager llm = new LinearLayoutManager(getContext());
|
||||
rvCertificate.setLayoutManager(llm);
|
||||
|
||||
DividerItemDecoration itemDecorator = new DividerItemDecoration(getContext(), llm.getOrientation());
|
||||
Drawable divider = getContext().getDrawable(R.drawable.divider);
|
||||
divider.mutate().setTint(getContext().getResources().getColor(R.color.lightColorSeparator));
|
||||
itemDecorator.setDrawable(divider);
|
||||
rvCertificate.addItemDecoration(itemDecorator);
|
||||
|
||||
final AdapterCertificate adapter = new AdapterCertificate(this, new AdapterCertificate.ICertificate() {
|
||||
@Override
|
||||
public void onSelected(EntityCertificate certificate) {
|
||||
@@ -3979,22 +4000,6 @@ 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);
|
||||
|
||||
@@ -4010,54 +4015,6 @@ 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 {
|
||||
|
||||
@@ -51,6 +51,7 @@ import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.widget.SwitchCompat;
|
||||
import androidx.lifecycle.Lifecycle;
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
import org.openintents.openpgp.util.OpenPgpApi;
|
||||
@@ -62,6 +63,7 @@ import java.util.List;
|
||||
public class FragmentOptionsPrivacy extends FragmentBase implements SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
private SwitchCompat swDisableTracking;
|
||||
private SwitchCompat swDisplayHidden;
|
||||
private Spinner spEncryptMethod;
|
||||
private Spinner spOpenPgp;
|
||||
private SwitchCompat swSign;
|
||||
private SwitchCompat swEncrypt;
|
||||
@@ -70,6 +72,7 @@ public class FragmentOptionsPrivacy extends FragmentBase implements SharedPrefer
|
||||
private Button btnBiometrics;
|
||||
private Button btnPin;
|
||||
private Spinner spBiometricsTimeout;
|
||||
private Button btnManageCertificates;
|
||||
private Button btnImportKey;
|
||||
private TextView tvKeySize;
|
||||
|
||||
@@ -77,7 +80,7 @@ public class FragmentOptionsPrivacy extends FragmentBase implements SharedPrefer
|
||||
|
||||
private final static String[] RESET_OPTIONS = new String[]{
|
||||
"disable_tracking", "display_hidden",
|
||||
"openpgp_provider", "sign_default", "encrypt_default", "auto_decrypt",
|
||||
"default_encrypt_method", "openpgp_provider", "sign_default", "encrypt_default", "auto_decrypt",
|
||||
"secure",
|
||||
"biometrics", "pin", "biometrics_timeout"
|
||||
};
|
||||
@@ -94,6 +97,7 @@ public class FragmentOptionsPrivacy extends FragmentBase implements SharedPrefer
|
||||
|
||||
swDisableTracking = view.findViewById(R.id.swDisableTracking);
|
||||
swDisplayHidden = view.findViewById(R.id.swDisplayHidden);
|
||||
spEncryptMethod = view.findViewById(R.id.spEncryptMethod);
|
||||
spOpenPgp = view.findViewById(R.id.spOpenPgp);
|
||||
swSign = view.findViewById(R.id.swSign);
|
||||
swEncrypt = view.findViewById(R.id.swEncrypt);
|
||||
@@ -102,6 +106,7 @@ public class FragmentOptionsPrivacy extends FragmentBase implements SharedPrefer
|
||||
btnBiometrics = view.findViewById(R.id.btnBiometrics);
|
||||
btnPin = view.findViewById(R.id.btnPin);
|
||||
spBiometricsTimeout = view.findViewById(R.id.spBiometricsTimeout);
|
||||
btnManageCertificates = view.findViewById(R.id.btnManageCertificates);
|
||||
btnImportKey = view.findViewById(R.id.btnImportKey);
|
||||
tvKeySize = view.findViewById(R.id.tvKeySize);
|
||||
|
||||
@@ -136,6 +141,21 @@ public class FragmentOptionsPrivacy extends FragmentBase implements SharedPrefer
|
||||
}
|
||||
});
|
||||
|
||||
spEncryptMethod.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||
if (position == 1)
|
||||
prefs.edit().putString("default_encrypt_method", "s/mime").apply();
|
||||
else
|
||||
onNothingSelected(parent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNothingSelected(AdapterView<?> parent) {
|
||||
prefs.edit().remove("default_encrypt_method").apply();
|
||||
}
|
||||
});
|
||||
|
||||
spOpenPgp.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> adapterView, View view, int position, long id) {
|
||||
@@ -225,6 +245,14 @@ public class FragmentOptionsPrivacy extends FragmentBase implements SharedPrefer
|
||||
}
|
||||
});
|
||||
|
||||
btnManageCertificates.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(getContext());
|
||||
lbm.sendBroadcast(new Intent(ActivitySetup.ACTION_MANAGE_CERTIFICATES));
|
||||
}
|
||||
});
|
||||
|
||||
final Intent importKey = KeyChain.createInstallIntent();
|
||||
btnImportKey.setEnabled(importKey.resolveActivity(getContext().getPackageManager()) != null);
|
||||
btnImportKey.setOnClickListener(new View.OnClickListener() {
|
||||
@@ -236,7 +264,7 @@ public class FragmentOptionsPrivacy extends FragmentBase implements SharedPrefer
|
||||
|
||||
try {
|
||||
int maxKeySize = javax.crypto.Cipher.getMaxAllowedKeyLength("AES");
|
||||
tvKeySize.setText(getString(R.string.title_aes_key_size, maxKeySize));
|
||||
tvKeySize.setText(getString(R.string.title_advanced_aes_key_size, maxKeySize));
|
||||
} catch (NoSuchAlgorithmException ex) {
|
||||
tvKeySize.setText(Helper.formatThrowable(ex));
|
||||
}
|
||||
@@ -290,6 +318,10 @@ public class FragmentOptionsPrivacy extends FragmentBase implements SharedPrefer
|
||||
swDisableTracking.setChecked(prefs.getBoolean("disable_tracking", true));
|
||||
swDisplayHidden.setChecked(prefs.getBoolean("display_hidden", false));
|
||||
|
||||
String encrypt_method = prefs.getString("default_encrypt_method", "pgp");
|
||||
if ("s/mime".equals(encrypt_method))
|
||||
spEncryptMethod.setSelection(1);
|
||||
|
||||
String provider = prefs.getString("openpgp_provider", "org.sufficientlysecure.keychain");
|
||||
for (int pos = 0; pos < openPgpProvider.size(); pos++)
|
||||
if (provider.equals(openPgpProvider.get(pos))) {
|
||||
|
||||
Reference in New Issue
Block a user