S/MIME proof of concept

pull/168/head
M66B 6 years ago
parent 15e9d529c7
commit 0e4e96ef69

@ -332,6 +332,6 @@ dependencies {
// https://github.com/AzureAD/microsoft-authentication-library-for-android // https://github.com/AzureAD/microsoft-authentication-library-for-android
implementation "com.microsoft.identity.client:msal:$msal_version" implementation "com.microsoft.identity.client:msal:$msal_version"
// https://mvnrepository.com/artifact/org.bouncycastle/bcmail-jdk15on // https://mvnrepository.com/artifact/org.bouncycastle/bcpkix-jdk15on
//implementation "org.bouncycastle:bcmail-jdk15on:1.64" implementation "org.bouncycastle:bcpkix-jdk15on:1.64"
} }

@ -1572,7 +1572,8 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
ibVerify.setVisibility(EntityMessage.PGP_SIGNONLY.equals(message.encrypt) ibVerify.setVisibility(EntityMessage.PGP_SIGNONLY.equals(message.encrypt)
? View.VISIBLE : View.GONE); ? View.VISIBLE : View.GONE);
ibDecrypt.setVisibility(args.getBoolean("inline_encrypted") || ibDecrypt.setVisibility(args.getBoolean("inline_encrypted") ||
EntityMessage.PGP_SIGNENCRYPT.equals(message.encrypt) EntityMessage.PGP_SIGNENCRYPT.equals(message.encrypt) ||
EntityMessage.SMIME_SIGNENCRYPT.equals(message.encrypt)
? View.VISIBLE : View.GONE); ? View.VISIBLE : View.GONE);
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);

@ -48,6 +48,8 @@ import android.os.Parcelable;
import android.print.PrintAttributes; import android.print.PrintAttributes;
import android.print.PrintDocumentAdapter; import android.print.PrintDocumentAdapter;
import android.print.PrintManager; import android.print.PrintManager;
import android.security.KeyChain;
import android.security.KeyChainAliasCallback;
import android.text.TextUtils; import android.text.TextUtils;
import android.text.format.DateUtils; import android.text.format.DateUtils;
import android.util.LongSparseArray; import android.util.LongSparseArray;
@ -107,6 +109,11 @@ import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.snackbar.Snackbar; import com.google.android.material.snackbar.Snackbar;
import com.sun.mail.util.FolderClosedIOException; import com.sun.mail.util.FolderClosedIOException;
import org.bouncycastle.cms.CMSEnvelopedData;
import org.bouncycastle.cms.KeyTransRecipientInformation;
import org.bouncycastle.cms.RecipientInformation;
import org.bouncycastle.cms.jcajce.JceKeyTransEnvelopedRecipient;
import org.bouncycastle.cms.jcajce.JceKeyTransRecipient;
import org.jsoup.nodes.Document; import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element; import org.jsoup.nodes.Element;
import org.openintents.openpgp.OpenPgpError; import org.openintents.openpgp.OpenPgpError;
@ -123,6 +130,7 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.security.PrivateKey;
import java.text.Collator; import java.text.Collator;
import java.text.DateFormat; import java.text.DateFormat;
import java.text.NumberFormat; import java.text.NumberFormat;
@ -130,6 +138,7 @@ import java.text.SimpleDateFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Calendar; import java.util.Calendar;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.Date; import java.util.Date;
@ -242,7 +251,7 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences.
private static final int SWIPE_DISABLE_SELECT_DURATION = 1500; // milliseconds private static final int SWIPE_DISABLE_SELECT_DURATION = 1500; // milliseconds
private static final int REQUEST_RAW = 1; private static final int REQUEST_RAW = 1;
private static final int REQUEST_DECRYPT = 4; private static final int REQUEST_OPENPGP = 4;
static final int REQUEST_MESSAGE_DELETE = 5; static final int REQUEST_MESSAGE_DELETE = 5;
private static final int REQUEST_MESSAGES_DELETE = 6; private static final int REQUEST_MESSAGES_DELETE = 6;
static final int REQUEST_MESSAGE_JUNK = 7; static final int REQUEST_MESSAGE_JUNK = 7;
@ -3891,25 +3900,52 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences.
} }
private void onDecrypt(Intent intent) { private void onDecrypt(Intent intent) {
if (pgpService.isBound()) { long id = intent.getLongExtra("id", -1);
long id = intent.getLongExtra("id", -1); boolean auto = intent.getBooleanExtra("auto", false);
boolean auto = intent.getBooleanExtra("auto", false); int type = intent.getIntExtra("type", EntityMessage.ENCRYPT_NONE);
Intent data = new Intent(); if (EntityMessage.SMIME_SIGNENCRYPT.equals(type)) {
data.setAction(OpenPgpApi.ACTION_DECRYPT_VERIFY); final Bundle args = new Bundle();
data.putExtra(BuildConfig.APPLICATION_ID, id); args.putLong("id", id);
args.putInt("type", type);
Handler handler = new Handler();
KeyChain.choosePrivateKeyAlias(getActivity(), new KeyChainAliasCallback() {
@Override
public void alias(@Nullable String alias) {
Log.i("Selected key alias=" + alias);
onDecrypt(data, auto); args.putString("alias", alias);
handler.post(new Runnable() {
@Override
public void run() {
try {
onSmime(args);
} catch (Throwable ex) {
Log.e(ex);
}
}
});
}
},
null, null, null, -1, null);
} else { } else {
Snackbar snackbar = Snackbar.make(view, R.string.title_no_openpgp, Snackbar.LENGTH_LONG); if (pgpService.isBound()) {
if (Helper.getIntentOpenKeychain().resolveActivity(getContext().getPackageManager()) != null) Intent data = new Intent();
snackbar.setAction(R.string.title_fix, new View.OnClickListener() { data.setAction(OpenPgpApi.ACTION_DECRYPT_VERIFY);
@Override data.putExtra(BuildConfig.APPLICATION_ID, id);
public void onClick(View v) { onPgp(data, auto);
startActivity(Helper.getIntentOpenKeychain()); } else {
} Snackbar snackbar = Snackbar.make(view, R.string.title_no_openpgp, Snackbar.LENGTH_LONG);
}); if (Helper.getIntentOpenKeychain().resolveActivity(getContext().getPackageManager()) != null)
snackbar.show(); snackbar.setAction(R.string.title_fix, new View.OnClickListener() {
@Override
public void onClick(View v) {
startActivity(Helper.getIntentOpenKeychain());
}
});
snackbar.show();
}
} }
} }
@ -3923,9 +3959,9 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences.
if (resultCode == RESULT_OK && data != null) if (resultCode == RESULT_OK && data != null)
onSaveRaw(data); onSaveRaw(data);
break; break;
case REQUEST_DECRYPT: case REQUEST_OPENPGP:
if (resultCode == RESULT_OK && data != null) if (resultCode == RESULT_OK && data != null)
onDecrypt(data, false); onPgp(data, false);
break; break;
case REQUEST_MESSAGE_DELETE: case REQUEST_MESSAGE_DELETE:
if (resultCode == RESULT_OK && data != null) if (resultCode == RESULT_OK && data != null)
@ -4091,7 +4127,7 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences.
}.execute(this, args, "raw:save"); }.execute(this, args, "raw:save");
} }
private void onDecrypt(Intent data, boolean auto) { private void onPgp(Intent data, boolean auto) {
Bundle args = new Bundle(); Bundle args = new Bundle();
args.putParcelable("data", data); args.putParcelable("data", data);
args.putBoolean("auto", auto); args.putBoolean("auto", auto);
@ -4285,7 +4321,7 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences.
Log.i("Executing pi=" + pi); Log.i("Executing pi=" + pi);
startIntentSenderForResult( startIntentSenderForResult(
pi.getIntentSender(), pi.getIntentSender(),
REQUEST_DECRYPT, REQUEST_OPENPGP,
null, 0, 0, 0, null); null, 0, 0, 0, null);
} catch (IntentSender.SendIntentException ex) { } catch (IntentSender.SendIntentException ex) {
Helper.unexpectedError(getParentFragmentManager(), ex); Helper.unexpectedError(getParentFragmentManager(), ex);
@ -4300,7 +4336,69 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences.
} else } else
Helper.unexpectedError(getParentFragmentManager(), ex); Helper.unexpectedError(getParentFragmentManager(), ex);
} }
}.execute(this, args, "decrypt"); }.execute(this, args, "decrypt:pgp");
}
private void onSmime(Bundle args) {
new SimpleTask() {
@Override
protected Boolean onExecute(Context context, Bundle args) throws Throwable {
long id = args.getLong("id");
int type = args.getInt("type");
String alias = args.getString("alias");
if (alias == null)
throw new IllegalArgumentException("Key alias missing");
PrivateKey pk = KeyChain.getPrivateKey(context, alias);
if (pk == null)
throw new IllegalArgumentException("Private key missing");
DB db = DB.getInstance(context);
File input = null;
List<EntityAttachment> attachments = db.attachment().getAttachments(id);
for (EntityAttachment attachment : attachments)
if (EntityAttachment.SMIME_MESSAGE.equals(attachment.encryption)) {
input = attachment.getFile(context);
break;
}
if (input == null)
throw new IllegalArgumentException("Encrypted message missing");
FileInputStream fis = new FileInputStream(input);
CMSEnvelopedData envelopedData = new CMSEnvelopedData(fis);
Collection<RecipientInformation> recipients = envelopedData.getRecipientInfos().getRecipients();
KeyTransRecipientInformation recipientInfo = (KeyTransRecipientInformation) recipients.iterator().next();
JceKeyTransRecipient recipient = new JceKeyTransEnvelopedRecipient(pk);
byte[] result = recipientInfo.getContent(recipient);
File output = EntityMessage.getFile(context, id);
try (OutputStream os = new FileOutputStream(output)) {
os.write("<pre>\r\n".getBytes());
os.write(result);
os.write("</pre>\r\n".getBytes());
}
db.message().setMessageStored(id, new Date().getTime());
return true;
}
@Override
protected void onExecuted(Bundle args, Object data) {
int type = args.getInt("type");
}
@Override
protected void onException(Bundle args, Throwable ex) {
if (ex instanceof IllegalArgumentException)
Snackbar.make(view, ex.getMessage(), Snackbar.LENGTH_LONG).show();
else
Helper.unexpectedError(getParentFragmentManager(), ex);
}
}.execute(this, args, "decrypt:s/mime");
} }
private void onDelete(long id) { private void onDelete(long id) {

Loading…
Cancel
Save