OTG: Add support for persistent usb connection

No need to reinsert token on each operation
This commit is contained in:
Nikita Mikhailov
2016-04-08 00:41:53 +06:00
parent 3798249570
commit b5eb6468fe
8 changed files with 131 additions and 110 deletions

View File

@@ -28,4 +28,9 @@ public class NfcTransport implements Transport {
public boolean isConnected() {
return mIsoCard.isConnected();
}
@Override
public boolean allowPersistentConnection() {
return false;
}
}

View File

@@ -32,7 +32,32 @@ public class SmartcardDevice {
private boolean mPw3Validated;
private boolean mTagHandlingEnabled;
public SmartcardDevice() {
protected SmartcardDevice() {
}
public static SmartcardDevice getInstance() {
return LazyHolder.mSmartcardDevice;
}
// METHOD UPDATED [OK]
private String getHolderName(String name) {
try {
String slength;
int ilength;
name = name.substring(6);
slength = name.substring(0, 2);
ilength = Integer.parseInt(slength, 16) * 2;
name = name.substring(2, ilength + 2);
name = (new String(Hex.decode(name))).replace('<', ' ');
return name;
} catch (IndexOutOfBoundsException e) {
// try-catch for https://github.com/FluffyKaon/OpenPGP-Card
// Note: This should not happen, but happens with
// https://github.com/FluffyKaon/OpenPGP-Card, thus return an empty string for now!
Log.e(Constants.TAG, "Couldn't get holder name, returning empty string!", e);
return "";
}
}
private static String getHex(byte[] raw) {
@@ -541,15 +566,8 @@ public class SmartcardDevice {
return Hex.decode(signature);
}
private String getHolderName(String name) {
String slength;
int ilength;
name = name.substring(6);
slength = name.substring(0, 2);
ilength = Integer.parseInt(slength, 16) * 2;
name = name.substring(2, ilength + 2);
name = (new String(Hex.decode(name))).replace('<', ' ');
return (name);
public boolean isConnected() {
return mTransport != null && mTransport.isConnected();
}
/**
@@ -560,8 +578,8 @@ public class SmartcardDevice {
return getHex(mTransport.sendAndReceive(Hex.decode(apdu)));
}
public boolean isConnected() {
return mTransport.isConnected();
public Transport getTransport() {
return mTransport;
}
// NEW METHOD [OK]
@@ -702,6 +720,13 @@ public class SmartcardDevice {
public void setTransport(Transport mTransport) {
this.mTransport = mTransport;
}
public boolean allowPersistentConnection() {
return mTransport != null && mTransport.allowPersistentConnection();
}
private static class LazyHolder {
private static final SmartcardDevice mSmartcardDevice = new SmartcardDevice();
}
}

View File

@@ -8,4 +8,6 @@ public interface Transport {
void release();
boolean isConnected();
boolean allowPersistentConnection();
}

View File

@@ -51,4 +51,12 @@ public class UsbConnectionManager {
public void onStop() {
mActivity.unregisterReceiver(mUsbReceiver);
}
public void rescanDevices() {
final SmartcardDevice smartcardDevice = SmartcardDevice.getInstance();
if (smartcardDevice.isConnected()
&& (smartcardDevice.getTransport() instanceof UsbTransport)) {
mListener.usbDeviceDiscovered(((UsbTransport) smartcardDevice.getTransport()).getUsbDevice());
}
}
}

View File

@@ -12,6 +12,8 @@ import android.util.Pair;
import org.bouncycastle.util.Arrays;
import org.bouncycastle.util.encoders.Hex;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.util.Log;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
@@ -45,33 +47,7 @@ public class UsbTransport implements Transport {
// check result
powerOn();
setTimings();
}
private void setTimings() throws TransportIoException {
byte[] data = {
0x6C,
0x00, 0x00, 0x00, 0x00,
0x00,
mCounter++,
0x00, 0x00, 0x00
};
sendRaw(data);
data = receive();
data[0] = 0x61;
data[1] = 0x04;
data[2] = data[3] = data[4] = 0x00;
data[5] = 0x00;
data[6] = mCounter++;
data[7] = 0x00;
data[8] = data[9] = 0x00;
data[13] = 1;
sendRaw(data);
receive();
Log.d(Constants.TAG, "Usb transport connected");
}
private void powerOff() throws TransportIoException {
@@ -147,6 +123,11 @@ public class UsbTransport implements Transport {
return mUsbManager.getDeviceList().containsValue(mUsbDevice);
}
@Override
public boolean allowPersistentConnection() {
return true;
}
@Override
public byte[] sendAndReceive(byte[] data) throws TransportIoException {
send(data);
@@ -223,4 +204,8 @@ public class UsbTransport implements Transport {
private boolean isXfrBlockNotReady(byte[] bytes) {
return getStatus(bytes) == 2;
}
public UsbDevice getUsbDevice() {
return mUsbDevice;
}
}

View File

@@ -249,9 +249,9 @@ public class CreateSecurityTokenImportResetFragment
@Override
public void doNfcInBackground() throws IOException {
mTokenFingerprints = mCreateKeyActivity.mSmartcardDevice.getFingerprints();
mTokenAid = mCreateKeyActivity.mSmartcardDevice.getAid();
mTokenUserId = mCreateKeyActivity.mSmartcardDevice.getUserId();
mTokenFingerprints = mCreateKeyActivity.getSmartcardDevice().getFingerprints();
mTokenAid = mCreateKeyActivity.getSmartcardDevice().getAid();
mTokenUserId = mCreateKeyActivity.getSmartcardDevice().getUserId();
byte[] fp = new byte[20];
ByteBuffer.wrap(fp).put(mTokenFingerprints, 0, 20);

View File

@@ -140,6 +140,32 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity
if (mRequiredInput.mType != RequiredInputParcel.RequiredInputType.NFC_MOVE_KEY_TO_CARD
&& mRequiredInput.mType != RequiredInputParcel.RequiredInputType.NFC_RESET_CARD) {
obtainSecurityTokenPin(mRequiredInput);
checkPinAvailability();
} else {
// No need for pin, rescan USB devices
mUsbDispatcher.rescanDevices();
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (REQUEST_CODE_PIN == requestCode) {
checkPinAvailability();
}
}
private void checkPinAvailability() {
try {
Passphrase passphrase = PassphraseCacheService.getCachedPassphrase(this,
mRequiredInput.getMasterKeyId(), mRequiredInput.getSubKeyId());
if (passphrase != null) {
// Rescan USB devices
mUsbDispatcher.rescanDevices();
}
} catch (PassphraseCacheService.KeyNotFoundException e) {
throw new AssertionError(
"tried to find passphrase for non-existing key. this is a programming error!");
}
}
@@ -275,28 +301,33 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity
nfcGuideView.setCurrentStatus(NfcGuideView.NfcGuideViewStatus.DONE);
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
// check all 200ms if Security Token has been taken away
while (true) {
if (isNfcConnected()) {
try {
Thread.sleep(200);
} catch (InterruptedException ignored) {
if (mSmartcardDevice.allowPersistentConnection()) {
// Just close
finish();
} else {
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
// check all 200ms if Security Token has been taken away
while (true) {
if (isNfcConnected()) {
try {
Thread.sleep(200);
} catch (InterruptedException ignored) {
}
} else {
return null;
}
} else {
return null;
}
}
}
@Override
protected void onPostExecute(Void result) {
super.onPostExecute(result);
finish();
}
}.execute();
@Override
protected void onPostExecute(Void result) {
super.onPostExecute(result);
finish();
}
}.execute();
}
}
/**

View File

@@ -31,7 +31,6 @@ import android.nfc.TagLostException;
import android.os.AsyncTask;
import android.os.Bundle;
import org.bouncycastle.util.encoders.Hex;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
@@ -41,9 +40,9 @@ import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
import org.sufficientlysecure.keychain.smartcard.SmartcardDevice;
import org.sufficientlysecure.keychain.smartcard.NfcTransport;
import org.sufficientlysecure.keychain.smartcard.OnDiscoveredUsbDeviceListener;
import org.sufficientlysecure.keychain.smartcard.SmartcardDevice;
import org.sufficientlysecure.keychain.smartcard.UsbConnectionManager;
import org.sufficientlysecure.keychain.smartcard.UsbTransport;
import org.sufficientlysecure.keychain.ui.CreateKeyActivity;
@@ -72,7 +71,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity
private static final String FIDESMO_APP_PACKAGE = "com.fidesmo.sec.android";
public SmartcardDevice mSmartcardDevice = new SmartcardDevice();
protected SmartcardDevice mSmartcardDevice = SmartcardDevice.getInstance();
protected TagDispatcher mTagDispatcher;
protected UsbConnectionManager mUsbDispatcher;
private boolean mTagHandlingEnabled;
@@ -175,7 +174,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity
public void usbDeviceDiscovered(final UsbDevice device) {
// Actual NFC operations are executed in doInBackground to not block the UI thread
// Actual USB operations are executed in doInBackground to not block the UI thread
if (!mTagHandlingEnabled)
return;
new AsyncTask<Void, Void, IOException>() {
@@ -372,7 +371,6 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity
Log.d(Constants.TAG, "BaseNfcActivity.onPause");
mTagDispatcher.disableExclusiveNfc();
// mUsbDispatcher.stopListeningForDevices();
}
/**
@@ -453,10 +451,15 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity
}
protected void handleUsbDevice(UsbDevice device) throws IOException {
UsbManager usbManager = (UsbManager) getSystemService(USB_SERVICE);
mSmartcardDevice.setTransport(new UsbTransport(device, usbManager));
mSmartcardDevice.connectToDevice();
// Don't reconnect if device was already connected
if (!mSmartcardDevice.isConnected()
|| !(mSmartcardDevice.getTransport() instanceof UsbTransport)
|| !((UsbTransport) mSmartcardDevice.getTransport()).getUsbDevice().equals(device)) {
UsbManager usbManager = (UsbManager) getSystemService(USB_SERVICE);
mSmartcardDevice.setTransport(new UsbTransport(device, usbManager));
mSmartcardDevice.connectToDevice();
}
doNfcInBackground();
}
@@ -464,48 +467,6 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity
return mSmartcardDevice.isConnected();
}
/**
* Parses out the status word from a JavaCard response string.
*
* @param response A hex string with the response from the token
* @return A short indicating the SW1/SW2, or 0 if a status could not be determined.
*/
short parseCardStatus(String response) {
if (response.length() < 4) {
return 0; // invalid input
}
try {
return Short.parseShort(response.substring(response.length() - 4), 16);
} catch (NumberFormatException e) {
return 0;
}
}
public String getHolderName(String name) {
try {
String slength;
int ilength;
name = name.substring(6);
slength = name.substring(0, 2);
ilength = Integer.parseInt(slength, 16) * 2;
name = name.substring(2, ilength + 2);
name = (new String(Hex.decode(name))).replace('<', ' ');
return name;
} catch (IndexOutOfBoundsException e) {
// try-catch for https://github.com/FluffyKaon/OpenPGP-Card
// Note: This should not happen, but happens with
// https://github.com/FluffyKaon/OpenPGP-Card, thus return an empty string for now!
Log.e(Constants.TAG, "Couldn't get holder name, returning empty string!", e);
return "";
}
}
public static String getHex(byte[] raw) {
return new String(Hex.encode(raw));
}
public class IsoDepNotSupportedException extends IOException {
public IsoDepNotSupportedException(String detailMessage) {
@@ -575,4 +536,8 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity
super.onStart();
mUsbDispatcher.onStart();
}
public SmartcardDevice getSmartcardDevice() {
return mSmartcardDevice;
}
}