aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--OpenKeychain/src/main/AndroidManifest.xml5
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/OnDiscoveredUsbDeviceListener.java7
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/UsbConnectionManager.java135
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java96
-rw-r--r--OpenKeychain/src/main/res/xml/usb_device_filter.xml4
5 files changed, 219 insertions, 28 deletions
diff --git a/OpenKeychain/src/main/AndroidManifest.xml b/OpenKeychain/src/main/AndroidManifest.xml
index 74bf936b4..f485e964c 100644
--- a/OpenKeychain/src/main/AndroidManifest.xml
+++ b/OpenKeychain/src/main/AndroidManifest.xml
@@ -43,6 +43,11 @@
android:name="android.hardware.screen.portrait"
android:required="false" />
+ <!-- For OTG tokens -->
+ <uses-feature
+ android:name="android.hardware.usb.host"
+ android:required="false" />
+
<!-- TemporaryStorageProvider should be writable by OpenKeychain only, thus signature-level permission -->
<permission
android:name="${applicationId}.WRITE_TEMPORARY_STORAGE"
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/OnDiscoveredUsbDeviceListener.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/OnDiscoveredUsbDeviceListener.java
new file mode 100644
index 000000000..6104985be
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/OnDiscoveredUsbDeviceListener.java
@@ -0,0 +1,7 @@
+package org.sufficientlysecure.keychain.javacard;
+
+import android.hardware.usb.UsbDevice;
+
+public interface OnDiscoveredUsbDeviceListener {
+ void usbDeviceDiscovered(UsbDevice usbDevice);
+} \ No newline at end of file
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/UsbConnectionManager.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/UsbConnectionManager.java
new file mode 100644
index 000000000..d140c771c
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/UsbConnectionManager.java
@@ -0,0 +1,135 @@
+package org.sufficientlysecure.keychain.javacard;
+
+import android.app.Activity;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.hardware.usb.UsbDevice;
+import android.hardware.usb.UsbManager;
+
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.util.Log;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Semaphore;
+
+public class UsbConnectionManager {
+ private static final String LOG_TAG = UsbConnectionManager.class.getName();
+ private static final String ACTION_USB_PERMISSION = Constants.PACKAGE_NAME + ".USB_PERMITSSION";
+
+ private Activity mActivity;
+ private OnDiscoveredUsbDeviceListener mListener;
+ private final Semaphore mRunning = new Semaphore(1);
+ private final Set<UsbDevice> mProcessedDevices = Collections.newSetFromMap(new ConcurrentHashMap<UsbDevice, Boolean>());
+
+ /**
+ * Receives broadcast when a supported USB device is attached, detached or
+ * when a permission to communicate to the device has been granted.
+ */
+ private final BroadcastReceiver mUsbReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ UsbDevice usbDevice = (UsbDevice) intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
+ String deviceName = usbDevice.getDeviceName();
+
+ if (ACTION_USB_PERMISSION.equals(action)) {
+ boolean permission = intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED,
+ false);
+ Log.d(LOG_TAG, "ACTION_USB_PERMISSION: " + permission + " Device: " + deviceName);
+
+ interceptIntent(intent);
+
+ context.unregisterReceiver(mUsbReceiver);
+ }
+ }
+ };
+
+ private final Thread mWatchThread = new Thread() {
+ @Override
+ public void run() {
+ final UsbManager usbManager = (UsbManager) mActivity.getSystemService(Context.USB_SERVICE);
+
+ while (true) {
+ mRunning.acquireUninterruptibly();
+ mRunning.release();
+
+ //
+
+ final UsbDevice device = getDevice(usbManager);
+ if (device != null && !mProcessedDevices.contains(device)) {
+ mProcessedDevices.add(device);
+
+ final Intent intent = new Intent(ACTION_USB_PERMISSION);
+
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(ACTION_USB_PERMISSION);
+ mActivity.registerReceiver(mUsbReceiver, filter);
+
+ usbManager.requestPermission(device, PendingIntent.getBroadcast(mActivity, 0, intent, 0));
+ }
+
+ try {
+ sleep(1000);
+ } catch (InterruptedException ignored) {
+ }
+ }
+ }
+ };
+
+ public UsbConnectionManager(final Activity activity, final OnDiscoveredUsbDeviceListener listener) {
+ this.mActivity = activity;
+ this.mListener = listener;
+ mRunning.acquireUninterruptibly();
+ mWatchThread.start();
+ }
+
+ public void startListeningForDevices() {
+ mRunning.release();
+ }
+
+ public void stopListeningForDevices() {
+ mRunning.acquireUninterruptibly();
+ }
+
+ public void interceptIntent(final Intent intent) {
+ if (intent == null || intent.getAction() == null) return;
+ switch (intent.getAction()) {
+ case UsbManager.ACTION_USB_DEVICE_ATTACHED: {
+ final UsbManager usbManager = (UsbManager) mActivity.getSystemService(Context.USB_SERVICE);
+ final UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
+ Intent usbI = new Intent(mActivity, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ usbI.setAction(ACTION_USB_PERMISSION);
+ usbI.putExtra(UsbManager.EXTRA_DEVICE, device);
+ PendingIntent pi = PendingIntent.getActivity(mActivity, 0, usbI, PendingIntent.FLAG_CANCEL_CURRENT);
+ usbManager.requestPermission(device, pi);
+ break;
+ }
+ case ACTION_USB_PERMISSION: {
+ UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
+ if (device != null)
+ mListener.usbDeviceDiscovered(device);
+ break;
+ }
+ default:
+ break;
+ }
+ }
+
+ private static UsbDevice getDevice(UsbManager manager) {
+ HashMap<String, UsbDevice> deviceList = manager.getDeviceList();
+ for (UsbDevice device : deviceList.values()) {
+ Log.d(LOG_TAG, device.getDeviceName() + " " + device.getDeviceId());
+ if (device.getVendorId() == 0x1050 && device.getProductId() == 0x0112) {
+ Log.d(LOG_TAG, device.getDeviceName() + " OK");
+ return device;
+ }
+ }
+ return null;
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java
index 94d640768..573123daf 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java
@@ -20,42 +20,32 @@
package org.sufficientlysecure.keychain.ui.base;
-import java.io.IOException;
-import java.math.BigInteger;
-import java.nio.ByteBuffer;
-import java.security.interfaces.RSAPrivateCrtKey;
-
import android.app.Activity;
+import android.app.PendingIntent;
import android.content.Intent;
import android.content.pm.PackageManager;
+import android.hardware.usb.UsbDevice;
+import android.hardware.usb.UsbManager;
import android.nfc.NfcAdapter;
import android.nfc.Tag;
import android.nfc.TagLostException;
import android.os.AsyncTask;
import android.os.Bundle;
-import nordpol.Apdu;
-import nordpol.IsoCard;
-import nordpol.android.TagDispatcher;
-import nordpol.android.AndroidCard;
-import nordpol.android.OnDiscoveredTagListener;
-
-import org.bouncycastle.bcpg.HashAlgorithmTags;
-import org.bouncycastle.util.Arrays;
import org.bouncycastle.util.encoders.Hex;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.javacard.BaseJavacardDevice;
import org.sufficientlysecure.keychain.javacard.JavacardDevice;
import org.sufficientlysecure.keychain.javacard.NfcTransport;
-import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey;
-import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
+import org.sufficientlysecure.keychain.javacard.OnDiscoveredUsbDeviceListener;
+import org.sufficientlysecure.keychain.javacard.UsbConnectionManager;
+import org.sufficientlysecure.keychain.javacard.UsbTransport;
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
-import org.sufficientlysecure.keychain.service.PassphraseCacheService.KeyNotFoundException;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
import org.sufficientlysecure.keychain.ui.CreateKeyActivity;
@@ -66,27 +56,27 @@ import org.sufficientlysecure.keychain.ui.dialog.FidesmoPgpInstallDialog;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.ui.util.Notify;
import org.sufficientlysecure.keychain.ui.util.Notify.Style;
-import org.sufficientlysecure.keychain.util.Iso7816TLV;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.Passphrase;
-public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implements OnDiscoveredTagListener {
+import java.io.IOException;
+
+import nordpol.IsoCard;
+import nordpol.android.AndroidCard;
+import nordpol.android.OnDiscoveredTagListener;
+import nordpol.android.TagDispatcher;
+
+public abstract class BaseSecurityTokenNfcActivity extends BaseActivity
+ implements OnDiscoveredTagListener, OnDiscoveredUsbDeviceListener {
public static final int REQUEST_CODE_PIN = 1;
public static final String EXTRA_TAG_HANDLING_ENABLED = "tag_handling_enabled";
private static final String FIDESMO_APP_PACKAGE = "com.fidesmo.sec.android";
- //protected Passphrase mPin;
- //protected Passphrase mAdminPin;
- //protected boolean mPw1ValidForMultipleSignatures;
- //protected boolean mPw1ValidatedForSignature;
- //protected boolean mPw1ValidatedForDecrypt; // Mode 82 does other things; consider renaming?
- //protected boolean mPw3Validated;
-
public JavacardDevice mJavacardDevice;
protected TagDispatcher mTagDispatcher;
-// private IsoCard mIsoCard;
+ protected UsbConnectionManager mUsbDispatcher;
private boolean mTagHandlingEnabled;
private byte[] mNfcFingerprints;
@@ -185,6 +175,43 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen
}.execute();
}
+
+ public void usbDeviceDiscovered(final UsbDevice device) {
+ // Actual NFC operations are executed in doInBackground to not block the UI thread
+ if(!mTagHandlingEnabled)
+ return;
+ new AsyncTask<Void, Void, IOException>() {
+ @Override
+ protected void onPreExecute() {
+ super.onPreExecute();
+ onNfcPreExecute();
+ }
+
+ @Override
+ protected IOException doInBackground(Void... params) {
+ try {
+ handleUsbDevice(device);
+ } catch (IOException e) {
+ return e;
+ }
+
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(IOException exception) {
+ super.onPostExecute(exception);
+
+ if (exception != null) {
+ handleNfcError(exception);
+ return;
+ }
+
+ onNfcPostExecute();
+ }
+ }.execute();
+ }
+
protected void pauseTagHandling() {
mTagHandlingEnabled = false;
}
@@ -198,6 +225,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen
super.onCreate(savedInstanceState);
mTagDispatcher = TagDispatcher.get(this, this, false, false, true, false);
+ mUsbDispatcher = new UsbConnectionManager(this, this);
// Check whether we're recreating a previously destroyed instance
if (savedInstanceState != null) {
@@ -228,7 +256,9 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen
*/
@Override
public void onNewIntent(final Intent intent) {
- mTagDispatcher.interceptIntent(intent);
+ if (!mTagDispatcher.interceptIntent(intent)) {
+ mUsbDispatcher.interceptIntent(intent);
+ }
}
private void handleNfcError(IOException e) {
@@ -346,6 +376,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen
Log.d(Constants.TAG, "BaseNfcActivity.onPause");
mTagDispatcher.disableExclusiveNfc();
+ mUsbDispatcher.stopListeningForDevices();
}
/**
@@ -356,6 +387,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen
super.onResume();
Log.d(Constants.TAG, "BaseNfcActivity.onResume");
mTagDispatcher.enableExclusiveNfc();
+ mUsbDispatcher.startListeningForDevices();
}
protected void obtainSecurityTokenPin(RequiredInputParcel requiredInput) {
@@ -372,7 +404,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen
intent.putExtra(PassphraseDialogActivity.EXTRA_REQUIRED_INPUT,
RequiredInputParcel.createRequiredPassphrase(requiredInput));
startActivityForResult(intent, REQUEST_CODE_PIN);
- } catch (KeyNotFoundException e) {
+ } catch (PassphraseCacheService.KeyNotFoundException e) {
throw new AssertionError(
"tried to find passphrase for non-existing key. this is a programming error!");
}
@@ -425,6 +457,14 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen
doNfcInBackground();
}
+ protected void handleUsbDevice(UsbDevice device) throws IOException {
+ UsbManager usbManager = (UsbManager) getSystemService(USB_SERVICE);
+ mJavacardDevice = new BaseJavacardDevice(new UsbTransport(device, usbManager));
+ mJavacardDevice.connectToDevice();
+
+ doNfcInBackground();
+ }
+
public boolean isNfcConnected() {
return mJavacardDevice.isConnected();
}
diff --git a/OpenKeychain/src/main/res/xml/usb_device_filter.xml b/OpenKeychain/src/main/res/xml/usb_device_filter.xml
new file mode 100644
index 000000000..cc0599009
--- /dev/null
+++ b/OpenKeychain/src/main/res/xml/usb_device_filter.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+ <usb-device class="11"/>
+</resources> \ No newline at end of file