Keychain 6.0.4 no longer exports the legacy org.openintents.openpgp.api.OpenPgpService action, so the OpenPgpServiceConnection from openpgp-api.jar bound nothing and every encrypt/decrypt died with a NullPointerException in OpenPgpApi.executeApi. Bind our own ServiceConnection against org.openintents.openpgp.IOpenPgpService (falling back to the legacy action for older Keychain releases), and queue API calls until the service is actually connected instead of racing the async bind. Also fix the //go:generate recipe: it built one jar containing both PgpConnect and Permissions, which collides with Permissions.jar when gogio dexes every *.jar in the repo root.
373 lines
12 KiB
Java
373 lines
12 KiB
Java
package st.wow.git.passgo;
|
|
|
|
import java.lang.Runnable;
|
|
import java.lang.String;
|
|
import java.util.concurrent.BlockingQueue;
|
|
import java.util.concurrent.LinkedBlockingQueue;
|
|
import android.os.Handler;
|
|
import android.os.IBinder;
|
|
import java.io.InputStream;
|
|
import java.io.ByteArrayInputStream;
|
|
import java.io.ByteArrayOutputStream;
|
|
import java.io.UnsupportedEncodingException;
|
|
import org.openintents.openpgp.IOpenPgpService2;
|
|
import org.openintents.openpgp.OpenPgpError;
|
|
import org.openintents.openpgp.OpenPgpSignatureResult;
|
|
import org.openintents.openpgp.util.OpenPgpApi;
|
|
import android.content.ComponentName;
|
|
import android.content.ServiceConnection;
|
|
import android.content.Context;
|
|
import android.content.ClipboardManager;
|
|
import android.content.ClipData;
|
|
import android.content.Intent;
|
|
import android.util.Log;
|
|
import android.app.Activity;
|
|
import android.app.Fragment;
|
|
import android.app.FragmentManager;
|
|
import android.app.FragmentTransaction;
|
|
import android.app.PendingIntent;
|
|
import android.content.IntentSender;
|
|
import android.content.IntentSender.OnFinished;
|
|
import android.content.IntentSender.SendIntentException;
|
|
import android.os.Bundle;
|
|
import android.Manifest;
|
|
import android.content.pm.PackageManager;
|
|
import android.content.Context;
|
|
import android.view.View;
|
|
|
|
public class PgpConnect extends Fragment {
|
|
Context ctx;
|
|
Handler handler;
|
|
ClipboardManager cb;
|
|
IOpenPgpService2 mService;
|
|
final int PERMISSIONS_REQUEST = 1;
|
|
|
|
// Keychain >= 11 (and the standalone "Keychain" 5.x/6.x releases) expose
|
|
// the OpenPGP API service under org.openintents.openpgp.IOpenPgpService;
|
|
// older releases used org.openintents.openpgp.api.OpenPgpService (the
|
|
// action hardcoded in OpenPgpServiceConnection). Try the new action
|
|
// first and fall back to the legacy one.
|
|
static final String[] SERVICE_ACTIONS = {
|
|
"org.openintents.openpgp.IOpenPgpService",
|
|
"org.openintents.openpgp.api.OpenPgpService",
|
|
};
|
|
|
|
final ServiceConnection mConnection = new ServiceConnection() {
|
|
@Override public void onServiceConnected(ComponentName name, IBinder service) {
|
|
mService = IOpenPgpService2.Stub.asInterface(service);
|
|
Log.d("gio", "OpenPGP service connected: " + name);
|
|
}
|
|
@Override public void onServiceDisconnected(ComponentName name) {
|
|
mService = null;
|
|
Log.d("gio", "OpenPGP service disconnected");
|
|
}
|
|
};
|
|
|
|
private void bindOpenPgp() {
|
|
Intent bindIntent = null;
|
|
for (String action : SERVICE_ACTIONS) {
|
|
Intent i = new Intent(action);
|
|
i.setPackage("org.sufficientlysecure.keychain");
|
|
if (ctx.getPackageManager().resolveService(i, 0) != null) {
|
|
bindIntent = i;
|
|
break;
|
|
}
|
|
}
|
|
if (bindIntent == null) {
|
|
Log.e("gio", "No OpenPGP service found in org.sufficientlysecure.keychain");
|
|
return;
|
|
}
|
|
Log.d("gio", "Binding OpenPGP service: " + bindIntent.getAction());
|
|
ctx.bindService(bindIntent, mConnection, Context.BIND_AUTO_CREATE);
|
|
}
|
|
|
|
// executeApi needs a live service; the bind is asynchronous, so if the
|
|
// service is not connected yet, retry on the main looper (up to ~10 s)
|
|
// instead of calling into a null service.
|
|
private void runWhenConnected(final int chint, final Runnable task, final int attempt) {
|
|
if (mService != null) {
|
|
task.run();
|
|
return;
|
|
}
|
|
if (attempt > 33) {
|
|
Log.e("gio", "OpenPGP service never connected");
|
|
stringResult(chint, null);
|
|
return;
|
|
}
|
|
handler.postDelayed(new Runnable() {
|
|
public void run() {
|
|
runWhenConnected(chint, task, attempt + 1);
|
|
}
|
|
}, 300);
|
|
}
|
|
|
|
public PgpConnect(View view) {
|
|
Log.d("gio", "PgpConnect()");
|
|
this.ctx = view.getContext();
|
|
this.handler = new Handler(this.ctx.getMainLooper());
|
|
PgpConnect inst = this;
|
|
handler.post(new Runnable() {
|
|
public void run() {
|
|
Activity act = (Activity)ctx;
|
|
FragmentTransaction ft = act.getFragmentManager().beginTransaction();
|
|
ft.add(inst, "PgpConnect");
|
|
ft.commitNow();
|
|
}
|
|
});
|
|
}
|
|
|
|
@Override public void onAttach(Context ctx) {
|
|
super.onAttach(ctx);
|
|
Log.d("gio", "onAttach()");
|
|
if (ctx instanceof Activity) {
|
|
Log.d("gio", "It's an Activity!");
|
|
}
|
|
bindOpenPgp();
|
|
cb = (ClipboardManager) ctx.getSystemService(Context.CLIPBOARD_SERVICE);
|
|
if (ctx.checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED || ctx.checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
|
|
requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSIONS_REQUEST);
|
|
}
|
|
System.loadLibrary("gio");
|
|
installComplete(this);
|
|
}
|
|
|
|
//For debugging:
|
|
/*private void printExtras(Intent data) {
|
|
Bundle bundle = data.getExtras();
|
|
if (bundle != null) {
|
|
for (String k : bundle.keySet()) {
|
|
Log.d("gio", "extra:" + k);
|
|
}
|
|
}
|
|
}*/
|
|
|
|
@Override public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
|
Log.d("gio", "onActivityResult(" + requestCode + "): " + resultCode);
|
|
super.onActivityResult(requestCode, resultCode, data);
|
|
if (resultCode != Activity.RESULT_OK) {
|
|
Log.d("gio", "onActivityResult: not OK");
|
|
stringResult(requestCode, null);
|
|
return;
|
|
}
|
|
switch (data.getAction()) {
|
|
case OpenPgpApi.ACTION_DECRYPT_VERIFY: {
|
|
Log.d("gio", "action decrypt");
|
|
_decrypt(data, requestCode);
|
|
break;
|
|
}
|
|
case OpenPgpApi.ACTION_ENCRYPT: {
|
|
Log.d("gio", "action encrypt");
|
|
_encrypt(data, requestCode);
|
|
break;
|
|
}
|
|
case OpenPgpApi.ACTION_GET_KEY_IDS:
|
|
Log.d("gio", "action getid");
|
|
String[] ids = data.getStringArrayExtra("user_ids");
|
|
|
|
if (ids != null && ids.length > 0) {
|
|
Log.d("gio", "got some IDs");
|
|
stringResult(requestCode, ids[0]);
|
|
} else {
|
|
Log.d("gio", "no ids");
|
|
stringResult(requestCode, null);
|
|
}
|
|
break;
|
|
default: {
|
|
Log.d("gio", "some other action");
|
|
}
|
|
}
|
|
}
|
|
|
|
public void Clip(byte []dat) {
|
|
ClipData clip = ClipData.newPlainText("PgpConnect", new String(dat));
|
|
cb.setPrimaryClip(clip);
|
|
}
|
|
|
|
public void GetId(int chint) {
|
|
if (handler == null) {
|
|
stringResult(chint, null);
|
|
return;
|
|
}
|
|
Log.d("gio", "GetId()");
|
|
handler.post(new Runnable() {
|
|
public void run() {
|
|
Intent data = new Intent();
|
|
data.setAction(OpenPgpApi.ACTION_GET_KEY_IDS);
|
|
//data.setAction(OpenPgpApi.ACTION_ENCRYPT);
|
|
data.putExtra(OpenPgpApi.EXTRA_USER_IDS, new String[]{" "});
|
|
_getid(data, chint);
|
|
}
|
|
});
|
|
}
|
|
|
|
public void Decrypt(byte []dat, int chint) {
|
|
if (handler == null) {
|
|
stringResult(chint, null);
|
|
return;
|
|
}
|
|
handler.post(new Runnable() {
|
|
public void run() {
|
|
Intent data = new Intent();
|
|
data.setAction(OpenPgpApi.ACTION_DECRYPT_VERIFY);
|
|
data.putExtra("DATA", dat);
|
|
_decrypt(data, chint);
|
|
}
|
|
});
|
|
}
|
|
|
|
private void _getid(Intent data, int chint) {
|
|
Log.d("gio","_getid");
|
|
runWhenConnected(chint, new Runnable() {
|
|
public void run() { doGetid(data, chint); }
|
|
}, 0);
|
|
}
|
|
|
|
private void doGetid(Intent data, int chint) {
|
|
InputStream is = null;
|
|
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
|
OpenPgpApi api = new OpenPgpApi(this.ctx, mService);
|
|
Intent result = api.executeApi(data, is, os);
|
|
switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR)) {
|
|
case OpenPgpApi.RESULT_CODE_SUCCESS: {
|
|
Log.d("gio","_getid: success");
|
|
try {
|
|
String ret = os.toString("UTF-8");
|
|
//Log.d(OpenPgpApi.TAG, "output: " + ret);
|
|
stringResult(chint, ret);
|
|
} catch (UnsupportedEncodingException e) {
|
|
Log.e("gio", "UnsupportedEncodingException", e);
|
|
stringResult(chint, null);
|
|
}
|
|
break;
|
|
}
|
|
case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED: {
|
|
Log.d("gio","_getid: interaction required");
|
|
PendingIntent pi = result.getParcelableExtra(OpenPgpApi.RESULT_INTENT);
|
|
try {
|
|
startIntentSenderForResult(pi.getIntentSender(), chint, null, 0, 0, 0, null);
|
|
} catch (IntentSender.SendIntentException e) {
|
|
Log.e("gio", "SendIntentException", e);
|
|
stringResult(chint, null);
|
|
}
|
|
break;
|
|
}
|
|
case OpenPgpApi.RESULT_CODE_ERROR: {
|
|
Log.d("gio","_getid: error");
|
|
OpenPgpError error = result.getParcelableExtra(OpenPgpApi.RESULT_ERROR);
|
|
stringResult(chint, null);
|
|
}
|
|
}
|
|
}
|
|
private void _decrypt(Intent data, int chint) {
|
|
Log.d("gio","_decrypt");
|
|
runWhenConnected(chint, new Runnable() {
|
|
public void run() { doDecrypt(data, chint); }
|
|
}, 0);
|
|
}
|
|
|
|
private void doDecrypt(Intent data, int chint) {
|
|
byte []dat = data.getByteArrayExtra("DATA");
|
|
InputStream is = new ByteArrayInputStream(dat);
|
|
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
|
OpenPgpApi api = new OpenPgpApi(this.ctx, mService);
|
|
Intent result = api.executeApi(data, is, os);
|
|
switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR)) {
|
|
case OpenPgpApi.RESULT_CODE_SUCCESS: {
|
|
try {
|
|
String ret = os.toString("UTF-8");
|
|
//Log.d(OpenPgpApi.TAG, "output: " + ret);
|
|
stringResult(chint, ret);
|
|
} catch (UnsupportedEncodingException e) {
|
|
Log.e("gio", "UnsupportedEncodingException", e);
|
|
stringResult(chint, null);
|
|
}
|
|
break;
|
|
}
|
|
case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED: {
|
|
PendingIntent pi = result.getParcelableExtra(OpenPgpApi.RESULT_INTENT);
|
|
try {
|
|
startIntentSenderForResult(pi.getIntentSender(), chint, null, 0, 0, 0, null);
|
|
} catch (IntentSender.SendIntentException e) {
|
|
Log.e("gio", "SendIntentException", e);
|
|
stringResult(chint, null);
|
|
}
|
|
break;
|
|
}
|
|
case OpenPgpApi.RESULT_CODE_ERROR: {
|
|
OpenPgpError error = result.getParcelableExtra(OpenPgpApi.RESULT_ERROR);
|
|
stringResult(chint, null);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void Encrypt(byte[] id, byte[] dat, int chint) {
|
|
if (handler == null) {
|
|
stringResult(chint, null);
|
|
return;
|
|
}
|
|
handler.post(new Runnable() {
|
|
public void run() {
|
|
Intent data = new Intent();
|
|
data.setAction(OpenPgpApi.ACTION_ENCRYPT);
|
|
data.putExtra(OpenPgpApi.EXTRA_USER_IDS, new String[]{new String(id)});
|
|
data.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
|
|
data.putExtra("DATA", dat);
|
|
_encrypt(data, chint);
|
|
}
|
|
});
|
|
}
|
|
|
|
private void _encrypt(Intent data, int chint) {
|
|
Log.d("gio","_encrypt");
|
|
runWhenConnected(chint, new Runnable() {
|
|
public void run() { doEncrypt(data, chint); }
|
|
}, 0);
|
|
}
|
|
|
|
private void doEncrypt(Intent data, int chint) {
|
|
byte []dat = data.getByteArrayExtra("DATA");
|
|
InputStream is = new ByteArrayInputStream(dat);
|
|
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
|
OpenPgpApi api = new OpenPgpApi(this.ctx, mService);
|
|
Intent result = api.executeApi(data, is, os);
|
|
switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR)) {
|
|
case OpenPgpApi.RESULT_CODE_SUCCESS: {
|
|
try {
|
|
String ret = os.toString("UTF-8");
|
|
//Log.d(OpenPgpApi.TAG, "output: " + ret);
|
|
stringResult(chint, ret);
|
|
} catch (UnsupportedEncodingException e) {
|
|
Log.e("gio", "UnsupportedEncodingException", e);
|
|
stringResult(chint, null);
|
|
}
|
|
|
|
if (result.hasExtra(OpenPgpApi.RESULT_SIGNATURE)) {
|
|
OpenPgpSignatureResult sigResult
|
|
= result.getParcelableExtra(OpenPgpApi.RESULT_SIGNATURE);
|
|
}
|
|
break;
|
|
}
|
|
case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED: {
|
|
PendingIntent pi = result.getParcelableExtra(OpenPgpApi.RESULT_INTENT);
|
|
try {
|
|
IntentSender sender = pi.getIntentSender();
|
|
//Log.d("PgpConnect", "IntentSender:" + sender.toString());
|
|
startIntentSenderForResult(sender, chint, null, 0, 0, 0, null);
|
|
} catch (IntentSender.SendIntentException e) {
|
|
Log.e("gio", "SendIntentException", e);
|
|
stringResult(chint, null);
|
|
}
|
|
break;
|
|
}
|
|
case OpenPgpApi.RESULT_CODE_ERROR: {
|
|
OpenPgpError error = result.getParcelableExtra(OpenPgpApi.RESULT_ERROR);
|
|
stringResult(chint, null);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static private native void installComplete(PgpConnect p);
|
|
static private native void stringResult(int requestCode, String result);
|
|
}
|