Prepared signed-only messages

This commit is contained in:
M66B
2019-11-27 10:40:43 +01:00
parent fc18f916a8
commit cc05ff3937
18 changed files with 524 additions and 182 deletions

View File

@@ -131,13 +131,17 @@ import java.util.Properties;
import java.util.regex.Pattern;
import javax.mail.Address;
import javax.mail.BodyPart;
import javax.mail.MessageRemovedException;
import javax.mail.Multipart;
import javax.mail.Part;
import javax.mail.Session;
import javax.mail.internet.AddressException;
import javax.mail.internet.ContentType;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
import javax.mail.internet.ParseException;
import static android.app.Activity.RESULT_CANCELED;
@@ -188,7 +192,7 @@ public class FragmentCompose extends FragmentBase {
private boolean prefix_once = false;
private boolean monospaced = false;
private boolean encrypt = false;
private Integer encrypt = null;
private boolean media = true;
private boolean compact = false;
private int zoom = 0;
@@ -205,6 +209,8 @@ public class FragmentCompose extends FragmentBase {
private String[] pgpUserIds;
private long[] pgpKeyIds;
private long pgpSignKeyId;
private String pgpContent;
private String pgpContentType;
static final int REDUCED_IMAGE_SIZE = 1440; // pixels
static final int REDUCED_IMAGE_QUALITY = 90; // percent
@@ -930,14 +936,20 @@ 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);
ib.setImageResource(encrypt ? R.drawable.baseline_lock_24 : R.drawable.baseline_lock_open_24);
ib.setImageTintList(encrypt ? ColorStateList.valueOf(colorEncrypt) : null);
ib.setImageResource(EntityMessage.ENCRYPTION_SIGNENCRYPT.equals(encrypt)
? R.drawable.baseline_lock_24 : R.drawable.baseline_lock_open_24);
ib.setImageTintList(EntityMessage.ENCRYPTION_SIGNENCRYPT.equals(encrypt)
? ColorStateList.valueOf(colorEncrypt) : null);
menu.findItem(R.id.menu_media).setChecked(media);
menu.findItem(R.id.menu_compact).setChecked(compact);
bottom_navigation.getMenu().findItem(R.id.action_send)
.setTitle(encrypt ? R.string.title_encrypt : R.string.title_send);
if (EntityMessage.ENCRYPTION_SIGNONLY.equals(encrypt))
bottom_navigation.getMenu().findItem(R.id.action_send).setTitle(R.string.title_sign);
else if (EntityMessage.ENCRYPTION_SIGNENCRYPT.equals(encrypt))
bottom_navigation.getMenu().findItem(R.id.action_send).setTitle(R.string.title_encrypt);
else
bottom_navigation.getMenu().findItem(R.id.action_send).setTitle(R.string.title_send);
}
@Override
@@ -987,21 +999,25 @@ public class FragmentCompose extends FragmentBase {
}
private void onMenuEncrypt() {
encrypt = !encrypt;
encrypt = (EntityMessage.ENCRYPTION_SIGNENCRYPT.equals(encrypt)
? EntityMessage.ENCRYPTION_NONE : EntityMessage.ENCRYPTION_SIGNENCRYPT);
getActivity().invalidateOptionsMenu();
Bundle args = new Bundle();
args.putLong("id", working);
args.putBoolean("encrypt", encrypt);
args.putInt("encrypt", encrypt);
new SimpleTask<Void>() {
@Override
protected Void onExecute(Context context, Bundle args) {
long id = args.getLong("id");
boolean encrypt = args.getBoolean("encrypt");
int encrypt = args.getInt("encrypt");
DB db = DB.getInstance(context);
db.message().setMessageEncrypt(id, encrypt);
if (EntityMessage.ENCRYPTION_NONE.equals(encrypt))
db.message().setMessageEncrypt(id, null);
else
db.message().setMessageEncrypt(id, encrypt);
return null;
}
@@ -1200,9 +1216,17 @@ public class FragmentCompose extends FragmentBase {
pgpUserIds[i] = recipient.getAddress().toLowerCase(Locale.ROOT);
}
Intent intent = new Intent(OpenPgpApi.ACTION_GET_KEY_IDS);
intent.putExtra(OpenPgpApi.EXTRA_USER_IDS, pgpUserIds);
intent.putExtra(BuildConfig.APPLICATION_ID, working);
Intent intent;
if (EntityMessage.ENCRYPTION_SIGNONLY.equals(draft.encrypt)) {
intent = new Intent(OpenPgpApi.ACTION_GET_SIGN_KEY_ID);
intent.putExtra(BuildConfig.APPLICATION_ID, working);
} else if (EntityMessage.ENCRYPTION_SIGNENCRYPT.equals(draft.encrypt)) {
intent = new Intent(OpenPgpApi.ACTION_GET_KEY_IDS);
intent.putExtra(OpenPgpApi.EXTRA_USER_IDS, pgpUserIds);
intent.putExtra(BuildConfig.APPLICATION_ID, working);
} else
throw new IllegalArgumentException("Invalid encrypt=" + draft.encrypt);
onPgp(intent);
} catch (Throwable ex) {
if (ex instanceof IllegalArgumentException)
@@ -1479,36 +1503,70 @@ public class FragmentCompose extends FragmentBase {
DB db = DB.getInstance(context);
// Get data
EntityMessage message = db.message().getMessage(id);
if (message == null)
throw new MessageRemovedException();
EntityIdentity identity = db.identity().getIdentity(message.identity);
EntityMessage draft = db.message().getMessage(id);
if (draft == null)
throw new MessageRemovedException("PGP");
EntityIdentity identity = db.identity().getIdentity(draft.identity);
if (identity == null)
throw new IllegalArgumentException(getString(R.string.title_from_missing));
List<EntityAttachment> attachments = db.attachment().getAttachments(id);
for (EntityAttachment attachment : new ArrayList<>(attachments))
if (attachment.encryption != null) {
if (OpenPgpApi.ACTION_GET_KEY_IDS.equals(data.getAction()))
db.attachment().deleteAttachment(attachment.id);
attachments.remove(attachment);
}
// Create files
File input = new File(context.getCacheDir(), "input." + id);
File output = new File(context.getCacheDir(), "output." + id);
// Serializing messages is NOT reproducible
if (OpenPgpApi.ACTION_GET_KEY_IDS.equals(data.getAction())) {
if ((EntityMessage.ENCRYPTION_SIGNONLY.equals(draft.encrypt) &&
OpenPgpApi.ACTION_GET_SIGN_KEY_ID.equals(data.getAction())) ||
(EntityMessage.ENCRYPTION_SIGNENCRYPT.equals(draft.encrypt) &&
OpenPgpApi.ACTION_GET_KEY_IDS.equals(data.getAction()))) {
// Get attachments
List<EntityAttachment> attachments = db.attachment().getAttachments(id);
for (EntityAttachment attachment : new ArrayList<>(attachments))
if (attachment.encryption != null) {
db.attachment().deleteAttachment(attachment.id);
attachments.remove(attachment);
}
// Build message
Properties props = MessageHelper.getSessionProperties();
Session isession = Session.getInstance(props, null);
MimeMessage imessage = new MimeMessage(isession);
MessageHelper.build(context, message, attachments, identity, imessage);
MessageHelper.build(context, draft, attachments, identity, imessage);
// Serialize message
try (OutputStream out = new FileOutputStream(input)) {
imessage.writeTo(out);
if (OpenPgpApi.ACTION_GET_SIGN_KEY_ID.equals(data.getAction())) {
// Serialize content
imessage.saveChanges();
Object content = imessage.getContent();
if (content instanceof String) {
pgpContent = (String) content;
pgpContentType = imessage.getContentType();
// Build plain text part with headers
BodyPart plainPart = new MimeBodyPart();
plainPart.setContent(pgpContent, pgpContentType);
Multipart plainMultiPart = new MimeMultipart();
plainMultiPart.addBodyPart(plainPart);
MimeMessage m = new MimeMessage(isession);
m.setContent(plainMultiPart);
m.saveChanges();
try (OutputStream out = new FileOutputStream(input)) {
plainPart.writeTo(out);
}
} else if (content instanceof Multipart) {
pgpContent = null;
pgpContentType = ((MimeMultipart) content).getContentType();
try (OutputStream out = new FileOutputStream(input)) {
((MimeMultipart) content).writeTo(out);
}
} else
throw new ParseException(content.getClass().getName());
} else {
// Serialize message
try (OutputStream out = new FileOutputStream(input)) {
imessage.writeTo(out);
}
}
}
@@ -1533,6 +1591,7 @@ public class FragmentCompose extends FragmentBase {
db.beginTransaction();
String name;
String type = "application/octet-stream";
int encryption;
if (OpenPgpApi.ACTION_GET_KEY.equals(data.getAction())) {
name = "keydata.asc";
@@ -1543,6 +1602,8 @@ public class FragmentCompose extends FragmentBase {
} else if (OpenPgpApi.ACTION_DETACHED_SIGN.equals(data.getAction())) {
name = "signature.asc";
encryption = EntityAttachment.PGP_SIGNATURE;
type = "application/pgp-signature; micalg=\"" +
result.getStringExtra(OpenPgpApi.RESULT_SIGNATURE_MICALG) + "\"";
} else
throw new IllegalStateException(data.getAction());
@@ -1550,7 +1611,7 @@ public class FragmentCompose extends FragmentBase {
attachment.message = id;
attachment.sequence = db.attachment().getAttachmentSequence(id) + 1;
attachment.name = name;
attachment.type = "application/octet-stream";
attachment.type = type;
attachment.disposition = Part.INLINE;
attachment.encryption = encryption;
attachment.id = db.attachment().insertAttachment(attachment);
@@ -1593,43 +1654,79 @@ public class FragmentCompose extends FragmentBase {
if (OpenPgpApi.ACTION_GET_KEY.equals(data.getAction()) ||
(OpenPgpApi.ACTION_GET_KEY_IDS.equals(data.getAction()) && pgpKeyIds.length > 1)) {
if (identity.sign_key != null) {
// Encrypt message
Intent intent = new Intent(OpenPgpApi.ACTION_SIGN_AND_ENCRYPT);
intent.putExtra(OpenPgpApi.EXTRA_KEY_IDS, pgpKeyIds);
intent.putExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID, identity.sign_key);
if (EntityMessage.ENCRYPTION_SIGNONLY.equals(draft.encrypt)) {
// Sign message
Intent intent = new Intent(OpenPgpApi.ACTION_DETACHED_SIGN);
intent.putExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID, pgpSignKeyId);
intent.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
intent.putExtra(BuildConfig.APPLICATION_ID, id);
return intent;
} else {
// Get sign key
Intent intent = new Intent(OpenPgpApi.ACTION_GET_SIGN_KEY_ID);
intent.putExtra(OpenPgpApi.EXTRA_USER_IDS, pgpUserIds);
intent.putExtra(BuildConfig.APPLICATION_ID, id);
return intent;
if (identity.sign_key != null) {
// Encrypt message
Intent intent = new Intent(OpenPgpApi.ACTION_SIGN_AND_ENCRYPT);
intent.putExtra(OpenPgpApi.EXTRA_KEY_IDS, pgpKeyIds);
intent.putExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID, identity.sign_key);
intent.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
intent.putExtra(BuildConfig.APPLICATION_ID, id);
return intent;
} else {
// Get sign key
Intent intent = new Intent(OpenPgpApi.ACTION_GET_SIGN_KEY_ID);
intent.putExtra(OpenPgpApi.EXTRA_USER_IDS, pgpUserIds);
intent.putExtra(BuildConfig.APPLICATION_ID, id);
return intent;
}
}
} else if (OpenPgpApi.ACTION_GET_SIGN_KEY_ID.equals(data.getAction())) {
pgpSignKeyId = result.getLongExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID, -1);
db.identity().setIdentitySignKey(identity.id, pgpSignKeyId);
// Encrypt message
Intent intent = new Intent(OpenPgpApi.ACTION_SIGN_AND_ENCRYPT);
intent.putExtra(OpenPgpApi.EXTRA_KEY_IDS, pgpKeyIds);
intent.putExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID, pgpSignKeyId);
intent.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
intent.putExtra(BuildConfig.APPLICATION_ID, id);
return intent;
if (EntityMessage.ENCRYPTION_SIGNONLY.equals(draft.encrypt)) {
// Get sign key
Intent intent = new Intent(OpenPgpApi.ACTION_GET_KEY);
intent.putExtra(OpenPgpApi.EXTRA_KEY_ID, pgpSignKeyId);
intent.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
intent.putExtra(BuildConfig.APPLICATION_ID, id);
return intent;
} else if (EntityMessage.ENCRYPTION_SIGNENCRYPT.equals(draft.encrypt)) {
// Encrypt message
Intent intent = new Intent(OpenPgpApi.ACTION_SIGN_AND_ENCRYPT);
intent.putExtra(OpenPgpApi.EXTRA_KEY_IDS, pgpKeyIds);
intent.putExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID, pgpSignKeyId);
intent.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
intent.putExtra(BuildConfig.APPLICATION_ID, id);
return intent;
} else
throw new IllegalArgumentException("Invalid encrypt=" + draft.encrypt);
} else if (OpenPgpApi.ACTION_SIGN_AND_ENCRYPT.equals(data.getAction())) {
input.delete();
// Get signature
//Intent intent = new Intent(OpenPgpApi.ACTION_DETACHED_SIGN);
//intent.putExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID, pgpSignKeyId);
//intent.putExtra(BuildConfig.APPLICATION_ID, id);
// send message
return null;
} else if (OpenPgpApi.ACTION_DETACHED_SIGN.equals(data.getAction())) {
EntityAttachment attachment = new EntityAttachment();
attachment.message = id;
attachment.sequence = db.attachment().getAttachmentSequence(id) + 1;
attachment.name = "content.txt";
attachment.type = pgpContentType;
attachment.disposition = Part.INLINE;
attachment.encryption = EntityAttachment.PGP_CONTENT;
attachment.id = db.attachment().insertAttachment(attachment);
// Restore plain text without headers
ContentType ct = new ContentType(pgpContentType);
if (!"multipart".equals(ct.getPrimaryType()))
try (OutputStream out = new FileOutputStream(input)) {
out.write(pgpContent.getBytes());
}
File file = attachment.getFile(context);
input.renameTo(file);
db.attachment().setDownloaded(attachment.id, file.length());
// send message
return null;
} else
@@ -1845,7 +1942,7 @@ public class FragmentCompose extends FragmentBase {
}
private void onActionSend(EntityMessage draft) {
if (draft.encrypt != null && draft.encrypt)
if (draft.encrypt != null && draft.encrypt != 0)
onEncrypt(draft);
else
onAction(R.id.action_send);
@@ -2135,7 +2232,7 @@ public class FragmentCompose extends FragmentBase {
if (plain_only)
data.draft.plain_only = true;
if (encrypt_default)
data.draft.encrypt = true;
data.draft.encrypt = EntityMessage.ENCRYPTION_SIGNENCRYPT;
if (receipt_default)
data.draft.receipt_request = true;
@@ -2371,8 +2468,8 @@ public class FragmentCompose extends FragmentBase {
if (ref.plain_only != null && ref.plain_only)
data.draft.plain_only = true;
if (ref.encrypt != null && ref.encrypt)
data.draft.encrypt = true;
if (ref.encrypt != null && ref.encrypt != 0)
data.draft.encrypt = ref.encrypt;
if (answer > 0) {
EntityAnswer a = db.answer().getAnswer(answer);
@@ -2558,7 +2655,7 @@ public class FragmentCompose extends FragmentBase {
}
}
if (data.draft.encrypt == null || !data.draft.encrypt)
if (data.draft.encrypt == null || data.draft.encrypt == 0)
EntityOperation.queue(context, data.draft, EntityOperation.ADD);
} else {
if (data.draft.revision == null) {
@@ -2627,7 +2724,7 @@ public class FragmentCompose extends FragmentBase {
Log.i("Loaded draft id=" + data.draft.id + " action=" + action);
working = data.draft.id;
encrypt = (data.draft.encrypt != null && data.draft.encrypt);
encrypt = data.draft.encrypt;
getActivity().invalidateOptionsMenu();
// Show identities
@@ -2716,7 +2813,7 @@ public class FragmentCompose extends FragmentBase {
if (draft == null || draft.ui_hide)
finish();
else {
encrypt = (draft.encrypt != null && draft.encrypt);
encrypt = draft.encrypt;
getActivity().invalidateOptionsMenu();
Log.i("Draft content=" + draft.content);
@@ -2864,7 +2961,7 @@ public class FragmentCompose extends FragmentBase {
draft.ui_hide = ui_hide;
db.message().updateMessage(draft);
if (draft.content && (draft.encrypt == null || !draft.encrypt))
if (draft.content && (draft.encrypt == null || draft.encrypt == 0))
EntityOperation.queue(context, draft, EntityOperation.ADD);
}
@@ -3040,7 +3137,7 @@ public class FragmentCompose extends FragmentBase {
action == R.id.action_redo ||
action == R.id.action_check) {
if (BuildConfig.DEBUG || dirty)
if (draft.encrypt == null || !draft.encrypt)
if (draft.encrypt == null || draft.encrypt == 0)
EntityOperation.queue(context, draft, EntityOperation.ADD);
if (action == R.id.action_check) {
@@ -3602,6 +3699,7 @@ public class FragmentCompose extends FragmentBase {
int send_delayed = prefs.getInt("send_delayed", 0);
boolean send_dialog = prefs.getBoolean("send_dialog", true);
final int[] encryptValues = getResources().getIntArray(R.array.encryptValues);
final int[] sendDelayedValues = getResources().getIntArray(R.array.sendDelayedValues);
final String[] sendDelayedNames = getResources().getStringArray(R.array.sendDelayedNames);
@@ -3612,9 +3710,9 @@ public class FragmentCompose extends FragmentBase {
final TextView tvTo = dview.findViewById(R.id.tvTo);
final TextView tvVia = dview.findViewById(R.id.tvVia);
final CheckBox cbPlainOnly = dview.findViewById(R.id.cbPlainOnly);
final CheckBox cbEncrypt = dview.findViewById(R.id.cbEncrypt);
final CheckBox cbReceipt = dview.findViewById(R.id.cbReceipt);
final TextView tvReceipt = dview.findViewById(R.id.tvReceipt);
final Spinner spEncrypt = dview.findViewById(R.id.spEncrypt);
final Spinner spPriority = dview.findViewById(R.id.spPriority);
final TextView tvSendAt = dview.findViewById(R.id.tvSendAt);
final ImageButton ibSendAt = dview.findViewById(R.id.ibSendAt);
@@ -3627,6 +3725,8 @@ public class FragmentCompose extends FragmentBase {
tvTo.setText(null);
tvVia.setText(null);
tvReceipt.setVisibility(View.GONE);
spEncrypt.setTag(0);
spEncrypt.setSelection(0);
spPriority.setTag(1);
spPriority.setSelection(1);
tvSendAt.setText(null);
@@ -3671,33 +3771,6 @@ public class FragmentCompose extends FragmentBase {
}
});
cbEncrypt.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton compoundButton, boolean checked) {
Bundle args = new Bundle();
args.putLong("id", id);
args.putBoolean("encrypt", checked);
new SimpleTask<Void>() {
@Override
protected Void onExecute(Context context, Bundle args) {
long id = args.getLong("id");
boolean encrypt = args.getBoolean("encrypt");
DB db = DB.getInstance(context);
db.message().setMessageEncrypt(id, encrypt);
return null;
}
@Override
protected void onException(Bundle args, Throwable ex) {
Helper.unexpectedError(getParentFragmentManager(), ex);
}
}.execute(FragmentDialogSend.this, args, "compose:encrypt");
}
});
cbReceipt.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton compoundButton, boolean checked) {
@@ -3727,6 +3800,47 @@ public class FragmentCompose extends FragmentBase {
}
});
spEncrypt.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
int last = (int) spEncrypt.getTag();
if (last != position) {
spEncrypt.setTag(position);
setEncrypt(encryptValues[position]);
}
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
spEncrypt.setTag(0);
setEncrypt(encryptValues[0]);
}
private void setEncrypt(int encrypt) {
Bundle args = new Bundle();
args.putLong("id", id);
args.putInt("encrypt", encrypt);
new SimpleTask<Void>() {
@Override
protected Void onExecute(Context context, Bundle args) {
long id = args.getLong("id");
int encrypt = args.getInt("encrypt");
DB db = DB.getInstance(context);
db.message().setMessageEncrypt(id, encrypt);
return null;
}
@Override
protected void onException(Bundle args, Throwable ex) {
Helper.unexpectedError(getParentFragmentManager(), ex);
}
}.execute(FragmentDialogSend.this, args, "compose:encrypt");
}
});
spPriority.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
@@ -3797,13 +3911,20 @@ public class FragmentCompose extends FragmentBase {
tvVia.setText(draft.identityEmail);
cbPlainOnly.setChecked(draft.plain_only != null && draft.plain_only);
cbEncrypt.setChecked(draft.encrypt != null && draft.encrypt);
cbReceipt.setChecked(draft.receipt_request != null && draft.receipt_request);
cbPlainOnly.setVisibility(draft.receipt != null && draft.receipt ? View.GONE : View.VISIBLE);
cbEncrypt.setVisibility(draft.receipt != null && draft.receipt ? View.GONE : View.VISIBLE);
cbReceipt.setVisibility(draft.receipt != null && draft.receipt ? View.GONE : View.VISIBLE);
int encrypt = (draft.encrypt == null ? EntityMessage.ENCRYPTION_NONE : draft.encrypt);
for (int i = 0; i < encryptValues.length; i++)
if (encryptValues[i] == encrypt) {
spEncrypt.setTag(i);
spEncrypt.setSelection(i);
break;
}
spEncrypt.setVisibility(draft.receipt != null && draft.receipt ? View.GONE : View.VISIBLE);
int priority = (draft.priority == null ? 1 : draft.priority);
spPriority.setTag(priority);
spPriority.setSelection(priority);