From 5e18b15775f4c6d9c563d61a71143320620e968e Mon Sep 17 00:00:00 2001 From: Nikita Mikhailov Date: Wed, 6 Apr 2016 22:49:52 +0600 Subject: OTG: Rename 'javacard' package, methods, remove JavacardInterface --- .../keychain/javacard/CardException.java | 17 - .../keychain/javacard/KeyType.java | 48 -- .../keychain/javacard/NfcTransport.java | 31 - .../javacard/OnDiscoveredUsbDeviceListener.java | 7 - .../keychain/javacard/PinException.java | 7 - .../keychain/javacard/PinType.java | 16 - .../keychain/javacard/Transport.java | 11 - .../keychain/javacard/TransportIoException.java | 20 - .../keychain/javacard/UsbConnectionManager.java | 150 ----- .../keychain/javacard/UsbTransport.java | 226 ------- .../keychain/smartcard/CardException.java | 17 + .../keychain/smartcard/KeyType.java | 48 ++ .../keychain/smartcard/NfcTransport.java | 31 + .../smartcard/OnDiscoveredUsbDeviceListener.java | 7 + .../keychain/smartcard/PinException.java | 7 + .../keychain/smartcard/PinType.java | 16 + .../keychain/smartcard/SmartcardDevice.java | 707 +++++++++++++++++++++ .../keychain/smartcard/Transport.java | 11 + .../keychain/smartcard/TransportIoException.java | 20 + .../keychain/smartcard/UsbConnectionManager.java | 148 +++++ .../keychain/smartcard/UsbTransport.java | 226 +++++++ .../keychain/ui/CreateKeyActivity.java | 6 +- .../ui/CreateSecurityTokenImportResetFragment.java | 7 +- .../ui/SecurityTokenOperationActivity.java | 36 +- .../keychain/ui/ViewKeyActivity.java | 6 +- .../ui/base/BaseSecurityTokenNfcActivity.java | 53 +- 26 files changed, 1291 insertions(+), 588 deletions(-) delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/CardException.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/KeyType.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/NfcTransport.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/OnDiscoveredUsbDeviceListener.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/PinException.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/PinType.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/Transport.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/TransportIoException.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/UsbConnectionManager.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/UsbTransport.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/CardException.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/KeyType.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/NfcTransport.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/OnDiscoveredUsbDeviceListener.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/PinException.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/PinType.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/SmartcardDevice.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/Transport.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/TransportIoException.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbConnectionManager.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbTransport.java diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/CardException.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/CardException.java deleted file mode 100644 index 3e9e9f2ca..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/CardException.java +++ /dev/null @@ -1,17 +0,0 @@ -package org.sufficientlysecure.keychain.javacard; - -import java.io.IOException; - -public class CardException extends IOException { - private short mResponseCode; - - public CardException(String detailMessage, short responseCode) { - super(detailMessage); - mResponseCode = responseCode; - } - - public short getResponseCode() { - return mResponseCode; - } - -} \ No newline at end of file diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/KeyType.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/KeyType.java deleted file mode 100644 index e0190f8bd..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/KeyType.java +++ /dev/null @@ -1,48 +0,0 @@ -package org.sufficientlysecure.keychain.javacard; - -import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey; - -public enum KeyType { - SIGN(0, 0xB6, 0xCE, 0xC7), - ENCRYPT(1, 0xB8, 0xCF, 0xC8), - AUTH(2, 0xA4, 0xD0, 0xC9),; - - private final int mIdx; - private final int mSlot; - private final int mTimestampObjectId; - private final int mFingerprintObjectId; - - KeyType(final int idx, final int slot, final int timestampObjectId, final int fingerprintObjectId) { - this.mIdx = idx; - this.mSlot = slot; - this.mTimestampObjectId = timestampObjectId; - this.mFingerprintObjectId = fingerprintObjectId; - } - - public static KeyType from(final CanonicalizedSecretKey key) { - if (key.canSign() || key.canCertify()) { - return SIGN; - } else if (key.canEncrypt()) { - return ENCRYPT; - } else if (key.canAuthenticate()) { - return AUTH; - } - return null; - } - - public int getIdx() { - return mIdx; - } - - public int getmSlot() { - return mSlot; - } - - public int getTimestampObjectId() { - return mTimestampObjectId; - } - - public int getmFingerprintObjectId() { - return mFingerprintObjectId; - } -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/NfcTransport.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/NfcTransport.java deleted file mode 100644 index 421b28aa8..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/NfcTransport.java +++ /dev/null @@ -1,31 +0,0 @@ -package org.sufficientlysecure.keychain.javacard; - -import java.io.IOException; - -import nordpol.IsoCard; - -public class NfcTransport implements Transport { - // timeout is set to 100 seconds to avoid cancellation during calculation - private static final int TIMEOUT = 100 * 1000; - private final IsoCard mIsoCard; - - public NfcTransport(final IsoCard isoDep) throws IOException { - this.mIsoCard = isoDep; - mIsoCard.setTimeout(TIMEOUT); - mIsoCard.connect(); - } - - @Override - public byte[] sendAndReceive(final byte[] data) throws TransportIoException, IOException { - return mIsoCard.transceive(data); - } - - @Override - public void release() { - } - - @Override - public boolean isConnected() { - return mIsoCard.isConnected(); - } -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/OnDiscoveredUsbDeviceListener.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/OnDiscoveredUsbDeviceListener.java deleted file mode 100644 index 6104985be..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/OnDiscoveredUsbDeviceListener.java +++ /dev/null @@ -1,7 +0,0 @@ -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/PinException.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/PinException.java deleted file mode 100644 index 84a34f116..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/PinException.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.sufficientlysecure.keychain.javacard; - -public class PinException extends CardException { - public PinException(final String detailMessage, final short responseCode) { - super(detailMessage, responseCode); - } -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/PinType.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/PinType.java deleted file mode 100644 index b6787a9e1..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/PinType.java +++ /dev/null @@ -1,16 +0,0 @@ -package org.sufficientlysecure.keychain.javacard; - -public enum PinType { - BASIC(0x81), - ADMIN(0x83),; - - private final int mMode; - - PinType(final int mode) { - this.mMode = mode; - } - - public int getmMode() { - return mMode; - } -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/Transport.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/Transport.java deleted file mode 100644 index 2d7dd3309..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/Transport.java +++ /dev/null @@ -1,11 +0,0 @@ -package org.sufficientlysecure.keychain.javacard; - -import java.io.IOException; - -public interface Transport { - byte[] sendAndReceive(byte[] data) throws IOException; - - void release(); - - boolean isConnected(); -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/TransportIoException.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/TransportIoException.java deleted file mode 100644 index 2dfb7df94..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/TransportIoException.java +++ /dev/null @@ -1,20 +0,0 @@ -package org.sufficientlysecure.keychain.javacard; - -import java.io.IOException; - -public class TransportIoException extends IOException { - public TransportIoException() { - } - - public TransportIoException(final String detailMessage) { - super(detailMessage); - } - - public TransportIoException(final String message, final Throwable cause) { - super(message, cause); - } - - public TransportIoException(final Throwable cause) { - super(cause); - } -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/UsbConnectionManager.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/UsbConnectionManager.java deleted file mode 100644 index 6b049159a..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/UsbConnectionManager.java +++ /dev/null @@ -1,150 +0,0 @@ -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 android.os.Handler; -import android.os.Looper; - -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; -import java.util.concurrent.atomic.AtomicBoolean; - -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 final Semaphore mRunning = new Semaphore(1); - private final Set mProcessedDevices = Collections.newSetFromMap(new ConcurrentHashMap()); - private final AtomicBoolean mStopped = new AtomicBoolean(false); - private Activity mActivity; - private final Thread mWatchThread = new Thread() { - @Override - public void run() { - final UsbManager usbManager = (UsbManager) mActivity.getSystemService(Context.USB_SERVICE); - - while (!mStopped.get()) { - try { - mRunning.acquire(); - } catch (InterruptedException e) { - } - mRunning.release(); - if (mStopped.get()) return; - - // - 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); - - Log.d(LOG_TAG, "Requesting permission for " + device.getDeviceName()); - usbManager.requestPermission(device, PendingIntent.getBroadcast(mActivity, 0, intent, 0)); - } - - try { - sleep(1000); - } catch (InterruptedException ignored) { - } - } - } - }; - private OnDiscoveredUsbDeviceListener mListener; - /** - * 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); - - if (permission) { - interceptIntent(intent); - } - - context.unregisterReceiver(mUsbReceiver); - } - } - }; - - public UsbConnectionManager(final Activity activity, final OnDiscoveredUsbDeviceListener listener) { - this.mActivity = activity; - this.mListener = listener; - mRunning.acquireUninterruptibly(); - mWatchThread.start(); - } - - private static UsbDevice getDevice(UsbManager manager) { - HashMap deviceList = manager.getDeviceList(); - for (UsbDevice device : deviceList.values()) { - if (device.getVendorId() == 0x1050 && (device.getProductId() == 0x0112 || device.getProductId() == 0x0115)) { - return device; - } - } - return null; - } - - 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; - } - } - - public void onDestroy() { - mStopped.set(true); - mRunning.release(); - try { - mActivity.unregisterReceiver(mUsbReceiver); - } catch (IllegalArgumentException ignore) { - } - mActivity = null; - } -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/UsbTransport.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/UsbTransport.java deleted file mode 100644 index 07697f11e..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/UsbTransport.java +++ /dev/null @@ -1,226 +0,0 @@ -package org.sufficientlysecure.keychain.javacard; - -import android.hardware.usb.UsbConstants; -import android.hardware.usb.UsbDevice; -import android.hardware.usb.UsbDeviceConnection; -import android.hardware.usb.UsbEndpoint; -import android.hardware.usb.UsbInterface; -import android.hardware.usb.UsbManager; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.util.Pair; - -import org.bouncycastle.util.Arrays; -import org.bouncycastle.util.encoders.Hex; - -import java.nio.ByteBuffer; -import java.nio.ByteOrder; - -public class UsbTransport implements Transport { - private static final int CLASS_SMARTCARD = 11; - private static final int TIMEOUT = 20 * 1000; // 2 s - - private final UsbManager mUsbManager; - private final UsbDevice mUsbDevice; - private final UsbInterface mUsbInterface; - private final UsbEndpoint mBulkIn; - private final UsbEndpoint mBulkOut; - private final UsbDeviceConnection mConnection; - private byte mCounter = 0; - - public UsbTransport(final UsbDevice usbDevice, final UsbManager usbManager) throws TransportIoException { - mUsbDevice = usbDevice; - mUsbManager = usbManager; - - mUsbInterface = getSmartCardInterface(mUsbDevice); - // throw if mUsbInterface == null - final Pair ioEndpoints = getIoEndpoints(mUsbInterface); - mBulkIn = ioEndpoints.first; - mBulkOut = ioEndpoints.second; - // throw if any endpoint is null - - mConnection = mUsbManager.openDevice(mUsbDevice); - // throw if connection is null - mConnection.claimInterface(mUsbInterface, true); - // 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(); - } - - private void powerOff() throws TransportIoException { - final byte[] iccPowerOff = { - 0x63, - 0x00, 0x00, 0x00, 0x00, - 0x00, - mCounter++, - 0x00, - 0x00, 0x00 - }; - sendRaw(iccPowerOff); - receive(); - } - - void powerOn() throws TransportIoException { - final byte[] iccPowerOn = { - 0x62, - 0x00, 0x00, 0x00, 0x00, - 0x00, - mCounter++, - 0x00, - 0x00, 0x00 - }; - sendRaw(iccPowerOn); - receive(); - } - - /** - * Get first class 11 (Chip/Smartcard) interface for the device - * - * @param device {@link UsbDevice} which will be searched - * @return {@link UsbInterface} of smartcard or null if it doesn't exist - */ - @Nullable - private static UsbInterface getSmartCardInterface(final UsbDevice device) { - for (int i = 0; i < device.getInterfaceCount(); i++) { - final UsbInterface anInterface = device.getInterface(i); - if (anInterface.getInterfaceClass() == CLASS_SMARTCARD) { - return anInterface; - } - } - return null; - } - - @NonNull - private static Pair getIoEndpoints(final UsbInterface usbInterface) { - UsbEndpoint bulkIn = null, bulkOut = null; - for (int i = 0; i < usbInterface.getEndpointCount(); i++) { - final UsbEndpoint endpoint = usbInterface.getEndpoint(i); - if (endpoint.getType() != UsbConstants.USB_ENDPOINT_XFER_BULK) { - continue; - } - - if (endpoint.getDirection() == UsbConstants.USB_DIR_IN) { - bulkIn = endpoint; - } else if (endpoint.getDirection() == UsbConstants.USB_DIR_OUT) { - bulkOut = endpoint; - } - } - return new Pair<>(bulkIn, bulkOut); - } - - @Override - public void release() { - mConnection.releaseInterface(mUsbInterface); - mConnection.close(); - } - - @Override - public boolean isConnected() { - // TODO: redo - return mUsbManager.getDeviceList().containsValue(mUsbDevice); - } - - @Override - public byte[] sendAndReceive(byte[] data) throws TransportIoException { - send(data); - byte[] bytes; - do { - bytes = receive(); - } while (isXfrBlockNotReady(bytes)); - - checkXfrBlockResult(bytes); - return Arrays.copyOfRange(bytes, 10, bytes.length); - } - - public void send(byte[] d) throws TransportIoException { - int l = d.length; - byte[] data = Arrays.concatenate(new byte[]{ - 0x6f, - (byte) l, (byte) (l >> 8), (byte) (l >> 16), (byte) (l >> 24), - 0x00, - mCounter++, - 0x00, - 0x00, 0x00}, - d); - - int send = 0; - while (send < data.length) { - final int len = Math.min(mBulkIn.getMaxPacketSize(), data.length - send); - sendRaw(Arrays.copyOfRange(data, send, send + len)); - send += len; - } - } - - public byte[] receive() throws TransportIoException { - byte[] buffer = new byte[mBulkIn.getMaxPacketSize()]; - byte[] result = null; - int readBytes = 0, totalBytes = 0; - - do { - int res = mConnection.bulkTransfer(mBulkIn, buffer, buffer.length, TIMEOUT); - if (res < 0) { - throw new TransportIoException("USB error, failed to receive response " + res); - } - if (result == null) { - if (res < 10) { - throw new TransportIoException("USB error, failed to receive ccid header"); - } - totalBytes = ByteBuffer.wrap(buffer, 1, 4).order(ByteOrder.LITTLE_ENDIAN).asIntBuffer().get() + 10; - result = new byte[totalBytes]; - } - System.arraycopy(buffer, 0, result, readBytes, res); - readBytes += res; - } while (readBytes < totalBytes); - - return result; - } - - private void sendRaw(final byte[] data) throws TransportIoException { - final int tr1 = mConnection.bulkTransfer(mBulkOut, data, data.length, TIMEOUT); - if (tr1 != data.length) { - throw new TransportIoException("USB error, failed to send data " + tr1); - } - } - - private byte getStatus(byte[] bytes) { - return (byte) ((bytes[7] >> 6) & 0x03); - } - - private void checkXfrBlockResult(byte[] bytes) throws TransportIoException { - final byte status = getStatus(bytes); - if (status != 0) { - throw new TransportIoException("CCID error, status " + status + " error code: " + Hex.toHexString(bytes, 8, 1)); - } - } - - private boolean isXfrBlockNotReady(byte[] bytes) { - return getStatus(bytes) == 2; - } -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/CardException.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/CardException.java new file mode 100644 index 000000000..9ea67f711 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/CardException.java @@ -0,0 +1,17 @@ +package org.sufficientlysecure.keychain.smartcard; + +import java.io.IOException; + +public class CardException extends IOException { + private short mResponseCode; + + public CardException(String detailMessage, short responseCode) { + super(detailMessage); + mResponseCode = responseCode; + } + + public short getResponseCode() { + return mResponseCode; + } + +} \ No newline at end of file diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/KeyType.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/KeyType.java new file mode 100644 index 000000000..625e1e669 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/KeyType.java @@ -0,0 +1,48 @@ +package org.sufficientlysecure.keychain.smartcard; + +import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey; + +public enum KeyType { + SIGN(0, 0xB6, 0xCE, 0xC7), + ENCRYPT(1, 0xB8, 0xCF, 0xC8), + AUTH(2, 0xA4, 0xD0, 0xC9),; + + private final int mIdx; + private final int mSlot; + private final int mTimestampObjectId; + private final int mFingerprintObjectId; + + KeyType(final int idx, final int slot, final int timestampObjectId, final int fingerprintObjectId) { + this.mIdx = idx; + this.mSlot = slot; + this.mTimestampObjectId = timestampObjectId; + this.mFingerprintObjectId = fingerprintObjectId; + } + + public static KeyType from(final CanonicalizedSecretKey key) { + if (key.canSign() || key.canCertify()) { + return SIGN; + } else if (key.canEncrypt()) { + return ENCRYPT; + } else if (key.canAuthenticate()) { + return AUTH; + } + return null; + } + + public int getIdx() { + return mIdx; + } + + public int getmSlot() { + return mSlot; + } + + public int getTimestampObjectId() { + return mTimestampObjectId; + } + + public int getmFingerprintObjectId() { + return mFingerprintObjectId; + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/NfcTransport.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/NfcTransport.java new file mode 100644 index 000000000..557c6f37d --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/NfcTransport.java @@ -0,0 +1,31 @@ +package org.sufficientlysecure.keychain.smartcard; + +import java.io.IOException; + +import nordpol.IsoCard; + +public class NfcTransport implements Transport { + // timeout is set to 100 seconds to avoid cancellation during calculation + private static final int TIMEOUT = 100 * 1000; + private final IsoCard mIsoCard; + + public NfcTransport(final IsoCard isoDep) throws IOException { + this.mIsoCard = isoDep; + mIsoCard.setTimeout(TIMEOUT); + mIsoCard.connect(); + } + + @Override + public byte[] sendAndReceive(final byte[] data) throws TransportIoException, IOException { + return mIsoCard.transceive(data); + } + + @Override + public void release() { + } + + @Override + public boolean isConnected() { + return mIsoCard.isConnected(); + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/OnDiscoveredUsbDeviceListener.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/OnDiscoveredUsbDeviceListener.java new file mode 100644 index 000000000..46b503b42 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/OnDiscoveredUsbDeviceListener.java @@ -0,0 +1,7 @@ +package org.sufficientlysecure.keychain.smartcard; + +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/smartcard/PinException.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/PinException.java new file mode 100644 index 000000000..58a7a31c9 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/PinException.java @@ -0,0 +1,7 @@ +package org.sufficientlysecure.keychain.smartcard; + +public class PinException extends CardException { + public PinException(final String detailMessage, final short responseCode) { + super(detailMessage, responseCode); + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/PinType.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/PinType.java new file mode 100644 index 000000000..7601edcf3 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/PinType.java @@ -0,0 +1,16 @@ +package org.sufficientlysecure.keychain.smartcard; + +public enum PinType { + BASIC(0x81), + ADMIN(0x83),; + + private final int mMode; + + PinType(final int mode) { + this.mMode = mode; + } + + public int getmMode() { + return mMode; + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/SmartcardDevice.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/SmartcardDevice.java new file mode 100644 index 000000000..cffc49555 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/SmartcardDevice.java @@ -0,0 +1,707 @@ +package org.sufficientlysecure.keychain.smartcard; + +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.pgp.CanonicalizedSecretKey; +import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; +import org.sufficientlysecure.keychain.util.Iso7816TLV; +import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.util.Passphrase; + +import java.io.IOException; +import java.math.BigInteger; +import java.nio.ByteBuffer; +import java.security.interfaces.RSAPrivateCrtKey; + +import nordpol.Apdu; + +public class SmartcardDevice { + // Fidesmo constants + private static final String FIDESMO_APPS_AID_PREFIX = "A000000617"; + + private static final byte[] BLANK_FINGERPRINT = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + private Transport mTransport; + + private Passphrase mPin; + private Passphrase mAdminPin; + private boolean mPw1ValidForMultipleSignatures; + private boolean mPw1ValidatedForSignature; + private boolean mPw1ValidatedForDecrypt; // Mode 82 does other things; consider renaming? + private boolean mPw3Validated; + private boolean mTagHandlingEnabled; + + public SmartcardDevice() { + } + + private static String getHex(byte[] raw) { + return new String(Hex.encode(raw)); + } + + public Passphrase getPin() { + return mPin; + } + + public void setPin(final Passphrase pin) { + this.mPin = pin; + } + + public Passphrase getAdminPin() { + return mAdminPin; + } + + public void setAdminPin(final Passphrase adminPin) { + this.mAdminPin = adminPin; + } + + public void changeKey(CanonicalizedSecretKey secretKey, Passphrase passphrase) throws IOException { + long keyGenerationTimestamp = secretKey.getCreationTime().getTime() / 1000; + byte[] timestampBytes = ByteBuffer.allocate(4).putInt((int) keyGenerationTimestamp).array(); + KeyType keyType = KeyType.from(secretKey); + + if (keyType == null) { + throw new IOException("Inappropriate key flags for smart card key."); + } + + // Slot is empty, or contains this key already. PUT KEY operation is safe + boolean canPutKey = !containsKey(keyType) + || keyMatchesFingerPrint(keyType, secretKey.getFingerprint()); + if (!canPutKey) { + throw new IOException(String.format("Key slot occupied; card must be reset to put new %s key.", + keyType.toString())); + } + + putKey(keyType.getmSlot(), secretKey, passphrase); + putData(keyType.getmFingerprintObjectId(), secretKey.getFingerprint()); + putData(keyType.getTimestampObjectId(), timestampBytes); + } + + public boolean containsKey(KeyType keyType) throws IOException { + return keyMatchesFingerPrint(keyType, BLANK_FINGERPRINT); + } + + public boolean keyMatchesFingerPrint(KeyType keyType, byte[] fingerprint) throws IOException { + return java.util.Arrays.equals(getMasterKeyFingerprint(keyType.getIdx()), fingerprint); + } + + // METHOD UPDATED OK + public void connectToDevice() throws IOException { + // SW1/2 0x9000 is the generic "ok" response, which we expect most of the time. + // See specification, page 51 + String accepted = "9000"; + + // Command APDU (page 51) for SELECT FILE command (page 29) + String opening = + "00" // CLA + + "A4" // INS + + "04" // P1 + + "00" // P2 + + "06" // Lc (number of bytes) + + "D27600012401" // Data (6 bytes) + + "00"; // Le + String response = communicate(opening); // activate connection + if (!response.endsWith(accepted)) { + throw new CardException("Initialization failed!", parseCardStatus(response)); + } + + byte[] pwStatusBytes = getPwStatusBytes(); + mPw1ValidForMultipleSignatures = (pwStatusBytes[0] == 1); + mPw1ValidatedForSignature = false; + mPw1ValidatedForDecrypt = false; + mPw3Validated = false; + } + + /** + * Parses out the status word from a JavaCard response string. + * + * @param response A hex string with the response from the card + * @return A short indicating the SW1/SW2, or 0 if a status could not be determined. + */ + private 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; + } + } + + /** + * Modifies the user's PW1 or PW3. Before sending, the new PIN will be validated for + * conformance to the token's requirements for key length. + * + * @param pw For PW1, this is 0x81. For PW3 (Admin PIN), mode is 0x83. + * @param newPin The new PW1 or PW3. + */ + // METHOD UPDATED[OK] + public void modifyPin(int pw, byte[] newPin) throws IOException { + final int MAX_PW1_LENGTH_INDEX = 1; + final int MAX_PW3_LENGTH_INDEX = 3; + + byte[] pwStatusBytes = getPwStatusBytes(); + + if (pw == 0x81) { + if (newPin.length < 6 || newPin.length > pwStatusBytes[MAX_PW1_LENGTH_INDEX]) { + throw new IOException("Invalid PIN length"); + } + } else if (pw == 0x83) { + if (newPin.length < 8 || newPin.length > pwStatusBytes[MAX_PW3_LENGTH_INDEX]) { + throw new IOException("Invalid PIN length"); + } + } else { + throw new IOException("Invalid PW index for modify PIN operation"); + } + + byte[] pin; + if (pw == 0x83) { + pin = mAdminPin.toStringUnsafe().getBytes(); + } else { + pin = mPin.toStringUnsafe().getBytes(); + } + + // Command APDU for CHANGE REFERENCE DATA command (page 32) + String changeReferenceDataApdu = "00" // CLA + + "24" // INS + + "00" // P1 + + String.format("%02x", pw) // P2 + + String.format("%02x", pin.length + newPin.length) // Lc + + getHex(pin) + + getHex(newPin); + String response = communicate(changeReferenceDataApdu); // change PIN + if (!response.equals("9000")) { + throw new CardException("Failed to change PIN", parseCardStatus(response)); + } + } + + /** + * Call DECIPHER command + * + * @param encryptedSessionKey the encoded session key + * @return the decoded session key + */ + // METHOD UPDATED [OK] + public byte[] decryptSessionKey(byte[] encryptedSessionKey) throws IOException { + if (!mPw1ValidatedForDecrypt) { + verifyPin(0x82); // (Verify PW1 with mode 82 for decryption) + } + + String firstApdu = "102a8086fe"; + String secondApdu = "002a808603"; + String le = "00"; + + byte[] one = new byte[254]; + // leave out first byte: + System.arraycopy(encryptedSessionKey, 1, one, 0, one.length); + + byte[] two = new byte[encryptedSessionKey.length - 1 - one.length]; + for (int i = 0; i < two.length; i++) { + two[i] = encryptedSessionKey[i + one.length + 1]; + } + + communicate(firstApdu + getHex(one)); + String second = communicate(secondApdu + getHex(two) + le); + + String decryptedSessionKey = getDataField(second); + + return Hex.decode(decryptedSessionKey); + } + + /** + * Verifies the user's PW1 or PW3 with the appropriate mode. + * + * @param mode For PW1, this is 0x81 for signing, 0x82 for everything else. + * For PW3 (Admin PIN), mode is 0x83. + */ + // METHOD UPDATED [OK] + private void verifyPin(int mode) throws IOException { + if (mPin != null || mode == 0x83) { + + byte[] pin; + if (mode == 0x83) { + pin = mAdminPin.toStringUnsafe().getBytes(); + } else { + pin = mPin.toStringUnsafe().getBytes(); + } + + // SW1/2 0x9000 is the generic "ok" response, which we expect most of the time. + // See specification, page 51 + String accepted = "9000"; + String response = tryPin(mode, pin); // login + if (!response.equals(accepted)) { + throw new CardException("Bad PIN!", parseCardStatus(response)); + } + + if (mode == 0x81) { + mPw1ValidatedForSignature = true; + } else if (mode == 0x82) { + mPw1ValidatedForDecrypt = true; + } else if (mode == 0x83) { + mPw3Validated = true; + } + } + } + + /** + * Stores a data object on the token. Automatically validates the proper PIN for the operation. + * Supported for all data objects < 255 bytes in length. Only the cardholder certificate + * (0x7F21) can exceed this length. + * + * @param dataObject The data object to be stored. + * @param data The data to store in the object + */ + // METHOD UPDATED [OK] + public void putData(int dataObject, byte[] data) throws IOException { + if (data.length > 254) { + throw new IOException("Cannot PUT DATA with length > 254"); + } + if (dataObject == 0x0101 || dataObject == 0x0103) { + if (!mPw1ValidatedForDecrypt) { + verifyPin(0x82); // (Verify PW1 for non-signing operations) + } + } else if (!mPw3Validated) { + verifyPin(0x83); // (Verify PW3) + } + + String putDataApdu = "00" // CLA + + "DA" // INS + + String.format("%02x", (dataObject & 0xFF00) >> 8) // P1 + + String.format("%02x", dataObject & 0xFF) // P2 + + String.format("%02x", data.length) // Lc + + getHex(data); + + String response = communicate(putDataApdu); // put data + if (!response.equals("9000")) { + throw new CardException("Failed to put data.", parseCardStatus(response)); + } + } + + + /** + * Puts a key on the token in the given slot. + * + * @param slot The slot on the token where the key should be stored: + * 0xB6: Signature Key + * 0xB8: Decipherment Key + * 0xA4: Authentication Key + */ + // METHOD UPDATED [OK] + public void putKey(int slot, CanonicalizedSecretKey secretKey, Passphrase passphrase) + throws IOException { + if (slot != 0xB6 && slot != 0xB8 && slot != 0xA4) { + throw new IOException("Invalid key slot"); + } + + RSAPrivateCrtKey crtSecretKey; + try { + secretKey.unlock(passphrase); + crtSecretKey = secretKey.getCrtSecretKey(); + } catch (PgpGeneralException e) { + throw new IOException(e.getMessage()); + } + + // Shouldn't happen; the UI should block the user from getting an incompatible key this far. + if (crtSecretKey.getModulus().bitLength() > 2048) { + throw new IOException("Key too large to export to Security Token."); + } + + // Should happen only rarely; all GnuPG keys since 2006 use public exponent 65537. + if (!crtSecretKey.getPublicExponent().equals(new BigInteger("65537"))) { + throw new IOException("Invalid public exponent for smart Security Token."); + } + + if (!mPw3Validated) { + verifyPin(0x83); // (Verify PW3 with mode 83) + } + + byte[] header = Hex.decode( + "4D82" + "03A2" // Extended header list 4D82, length of 930 bytes. (page 23) + + String.format("%02x", slot) + "00" // CRT to indicate targeted key, no length + + "7F48" + "15" // Private key template 0x7F48, length 21 (decimal, 0x15 hex) + + "9103" // Public modulus, length 3 + + "928180" // Prime P, length 128 + + "938180" // Prime Q, length 128 + + "948180" // Coefficient (1/q mod p), length 128 + + "958180" // Prime exponent P (d mod (p - 1)), length 128 + + "968180" // Prime exponent Q (d mod (1 - 1)), length 128 + + "97820100" // Modulus, length 256, last item in private key template + + "5F48" + "820383");// DO 5F48; 899 bytes of concatenated key data will follow + byte[] dataToSend = new byte[934]; + byte[] currentKeyObject; + int offset = 0; + + System.arraycopy(header, 0, dataToSend, offset, header.length); + offset += header.length; + currentKeyObject = crtSecretKey.getPublicExponent().toByteArray(); + System.arraycopy(currentKeyObject, 0, dataToSend, offset, 3); + offset += 3; + // NOTE: For a 2048-bit key, these lengths are fixed. However, bigint includes a leading 0 + // in the array to represent sign, so we take care to set the offset to 1 if necessary. + currentKeyObject = crtSecretKey.getPrimeP().toByteArray(); + System.arraycopy(currentKeyObject, currentKeyObject.length - 128, dataToSend, offset, 128); + Arrays.fill(currentKeyObject, (byte) 0); + offset += 128; + currentKeyObject = crtSecretKey.getPrimeQ().toByteArray(); + System.arraycopy(currentKeyObject, currentKeyObject.length - 128, dataToSend, offset, 128); + Arrays.fill(currentKeyObject, (byte) 0); + offset += 128; + currentKeyObject = crtSecretKey.getCrtCoefficient().toByteArray(); + System.arraycopy(currentKeyObject, currentKeyObject.length - 128, dataToSend, offset, 128); + Arrays.fill(currentKeyObject, (byte) 0); + offset += 128; + currentKeyObject = crtSecretKey.getPrimeExponentP().toByteArray(); + System.arraycopy(currentKeyObject, currentKeyObject.length - 128, dataToSend, offset, 128); + Arrays.fill(currentKeyObject, (byte) 0); + offset += 128; + currentKeyObject = crtSecretKey.getPrimeExponentQ().toByteArray(); + System.arraycopy(currentKeyObject, currentKeyObject.length - 128, dataToSend, offset, 128); + Arrays.fill(currentKeyObject, (byte) 0); + offset += 128; + currentKeyObject = crtSecretKey.getModulus().toByteArray(); + System.arraycopy(currentKeyObject, currentKeyObject.length - 256, dataToSend, offset, 256); + + String putKeyCommand = "10DB3FFF"; + String lastPutKeyCommand = "00DB3FFF"; + + // Now we're ready to communicate with the token. + offset = 0; + String response; + while (offset < dataToSend.length) { + int dataRemaining = dataToSend.length - offset; + if (dataRemaining > 254) { + response = communicate( + putKeyCommand + "FE" + Hex.toHexString(dataToSend, offset, 254) + ); + offset += 254; + } else { + int length = dataToSend.length - offset; + response = communicate( + lastPutKeyCommand + String.format("%02x", length) + + Hex.toHexString(dataToSend, offset, length)); + offset += length; + } + + if (!response.endsWith("9000")) { + throw new CardException("Key export to Security Token failed", parseCardStatus(response)); + } + } + + // Clear array with secret data before we return. + Arrays.fill(dataToSend, (byte) 0); + } + + /** + * Return fingerprints of all keys from application specific data stored + * on tag, or null if data not available. + * + * @return The fingerprints of all subkeys in a contiguous byte array. + */ + // METHOD UPDATED [OK] + public byte[] getFingerprints() throws IOException { + String data = "00CA006E00"; + byte[] buf = mTransport.sendAndReceive(Hex.decode(data)); + + Iso7816TLV tlv = Iso7816TLV.readSingle(buf, true); + Log.d(Constants.TAG, "nfcGetFingerprints() Iso7816TLV tlv data:\n" + tlv.prettyPrint()); + + Iso7816TLV fptlv = Iso7816TLV.findRecursive(tlv, 0xc5); + if (fptlv == null) { + return null; + } + return fptlv.mV; + } + + /** + * Return the PW Status Bytes from the token. This is a simple DO; no TLV decoding needed. + * + * @return Seven bytes in fixed format, plus 0x9000 status word at the end. + */ + // METHOD UPDATED [OK] + private byte[] getPwStatusBytes() throws IOException { + String data = "00CA00C400"; + return mTransport.sendAndReceive(Hex.decode(data)); + } + + // METHOD UPDATED [OK] + public byte[] getAid() throws IOException { + String info = "00CA004F00"; + return mTransport.sendAndReceive(Hex.decode(info)); + } + + // METHOD UPDATED [OK] + public String getUserId() throws IOException { + String info = "00CA006500"; + return getHolderName(communicate(info)); + } + + /** + * Call COMPUTE DIGITAL SIGNATURE command and returns the MPI value + * + * @param hash the hash for signing + * @return a big integer representing the MPI for the given hash + */ + // METHOD UPDATED [OK] + public byte[] calculateSignature(byte[] hash, int hashAlgo) throws IOException { + if (!mPw1ValidatedForSignature) { + verifyPin(0x81); // (Verify PW1 with mode 81 for signing) + } + + // dsi, including Lc + String dsi; + + Log.i(Constants.TAG, "Hash: " + hashAlgo); + switch (hashAlgo) { + case HashAlgorithmTags.SHA1: + if (hash.length != 20) { + throw new IOException("Bad hash length (" + hash.length + ", expected 10!"); + } + dsi = "23" // Lc + + "3021" // Tag/Length of Sequence, the 0x21 includes all following 33 bytes + + "3009" // Tag/Length of Sequence, the 0x09 are the following header bytes + + "0605" + "2B0E03021A" // OID of SHA1 + + "0500" // TLV coding of ZERO + + "0414" + getHex(hash); // 0x14 are 20 hash bytes + break; + case HashAlgorithmTags.RIPEMD160: + if (hash.length != 20) { + throw new IOException("Bad hash length (" + hash.length + ", expected 20!"); + } + dsi = "233021300906052B2403020105000414" + getHex(hash); + break; + case HashAlgorithmTags.SHA224: + if (hash.length != 28) { + throw new IOException("Bad hash length (" + hash.length + ", expected 28!"); + } + dsi = "2F302D300D06096086480165030402040500041C" + getHex(hash); + break; + case HashAlgorithmTags.SHA256: + if (hash.length != 32) { + throw new IOException("Bad hash length (" + hash.length + ", expected 32!"); + } + dsi = "333031300D060960864801650304020105000420" + getHex(hash); + break; + case HashAlgorithmTags.SHA384: + if (hash.length != 48) { + throw new IOException("Bad hash length (" + hash.length + ", expected 48!"); + } + dsi = "433041300D060960864801650304020205000430" + getHex(hash); + break; + case HashAlgorithmTags.SHA512: + if (hash.length != 64) { + throw new IOException("Bad hash length (" + hash.length + ", expected 64!"); + } + dsi = "533051300D060960864801650304020305000440" + getHex(hash); + break; + default: + throw new IOException("Not supported hash algo!"); + } + + // Command APDU for PERFORM SECURITY OPERATION: COMPUTE DIGITAL SIGNATURE (page 37) + String apdu = + "002A9E9A" // CLA, INS, P1, P2 + + dsi // digital signature input + + "00"; // Le + + String response = communicate(apdu); + + if (response.length() < 4) { + throw new CardException("Bad response", (short) 0); + } + // split up response into signature and status + String status = response.substring(response.length() - 4); + String signature = response.substring(0, response.length() - 4); + + // while we are getting 0x61 status codes, retrieve more data + while (status.substring(0, 2).equals("61")) { + Log.d(Constants.TAG, "requesting more data, status " + status); + // Send GET RESPONSE command + response = communicate("00C00000" + status.substring(2)); + status = response.substring(response.length() - 4); + signature += response.substring(0, response.length() - 4); + } + + Log.d(Constants.TAG, "final response:" + status); + + if (!mPw1ValidForMultipleSignatures) { + mPw1ValidatedForSignature = false; + } + + if (!"9000".equals(status)) { + throw new CardException("Bad NFC response code: " + status, parseCardStatus(response)); + } + + // Make sure the signature we received is actually the expected number of bytes long! + if (signature.length() != 256 && signature.length() != 512) { + throw new IOException("Bad signature length! Expected 128 or 256 bytes, got " + signature.length() / 2); + } + + 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); + } + + /** + * Transceive data via NFC encoded as Hex + */ + // METHOD UPDATED [OK] + private String communicate(String apdu) throws IOException, TransportIoException { + return getHex(mTransport.sendAndReceive(Hex.decode(apdu))); + } + + public boolean isConnected() { + return mTransport.isConnected(); + } + + // NEW METHOD [OK] + public boolean isFidesmoToken() { + if (isConnected()) { // Check if we can still talk to the card + try { + // By trying to select any apps that have the Fidesmo AID prefix we can + // see if it is a Fidesmo device or not + byte[] mSelectResponse = mTransport.sendAndReceive(Apdu.select(FIDESMO_APPS_AID_PREFIX)); + // Compare the status returned by our select with the OK status code + return Apdu.hasStatus(mSelectResponse, Apdu.OK_APDU); + } catch (IOException e) { + Log.e(Constants.TAG, "Card communication failed!", e); + } + } + return false; + } + + /** + * Generates a key on the card in the given slot. If the slot is 0xB6 (the signature key), + * this command also has the effect of resetting the digital signature counter. + * NOTE: This does not set the key fingerprint data object! After calling this command, you + * must construct a public key packet using the returned public key data objects, compute the + * key fingerprint, and store it on the card using: putData(0xC8, key.getFingerprint()) + * + * @param slot The slot on the card where the key should be generated: + * 0xB6: Signature Key + * 0xB8: Decipherment Key + * 0xA4: Authentication Key + * @return the public key data objects, in TLV format. For RSA this will be the public modulus + * (0x81) and exponent (0x82). These may come out of order; proper TLV parsing is required. + */ + // NEW METHOD [OK] + public byte[] generateKey(int slot) throws IOException { + if (slot != 0xB6 && slot != 0xB8 && slot != 0xA4) { + throw new IOException("Invalid key slot"); + } + + if (!mPw3Validated) { + verifyPin(0x83); // (Verify PW3 with mode 83) + } + + String generateKeyApdu = "0047800002" + String.format("%02x", slot) + "0000"; + String getResponseApdu = "00C00000"; + + String first = communicate(generateKeyApdu); + String second = communicate(getResponseApdu); + + if (!second.endsWith("9000")) { + throw new IOException("On-card key generation failed"); + } + + String publicKeyData = getDataField(first) + getDataField(second); + + Log.d(Constants.TAG, "Public Key Data Objects: " + publicKeyData); + + return Hex.decode(publicKeyData); + } + + // NEW METHOD [OK][OK] + private String getDataField(String output) { + return output.substring(0, output.length() - 4); + } + + // NEW METHOD [OK] + private String tryPin(int mode, byte[] pin) throws IOException { + // Command APDU for VERIFY command (page 32) + String login = + "00" // CLA + + "20" // INS + + "00" // P1 + + String.format("%02x", mode) // P2 + + String.format("%02x", pin.length) // Lc + + Hex.toHexString(pin); + + return communicate(login); + } + + /** + * Resets security token, which deletes all keys and data objects. + * This works by entering a wrong PIN and then Admin PIN 4 times respectively. + * Afterwards, the token is reactivated. + */ + // NEW METHOD [OK] + public void resetAndWipeToken() throws IOException { + String accepted = "9000"; + + // try wrong PIN 4 times until counter goes to C0 + byte[] pin = "XXXXXX".getBytes(); + for (int i = 0; i <= 4; i++) { + String response = tryPin(0x81, pin); + if (response.equals(accepted)) { // Should NOT accept! + throw new CardException("Should never happen, XXXXXX has been accepted!", parseCardStatus(response)); + } + } + + // try wrong Admin PIN 4 times until counter goes to C0 + byte[] adminPin = "XXXXXXXX".getBytes(); + for (int i = 0; i <= 4; i++) { + String response = tryPin(0x83, adminPin); + if (response.equals(accepted)) { // Should NOT accept! + throw new CardException("Should never happen, XXXXXXXX has been accepted", parseCardStatus(response)); + } + } + + // reactivate token! + String reactivate1 = "00" + "e6" + "00" + "00"; + String reactivate2 = "00" + "44" + "00" + "00"; + String response1 = communicate(reactivate1); + String response2 = communicate(reactivate2); + if (!response1.equals(accepted) || !response2.equals(accepted)) { + throw new CardException("Reactivating failed!", parseCardStatus(response1)); + } + + } + + /** + * Return the fingerprint from application specific data stored on tag, or + * null if it doesn't exist. + * + * @param idx Index of the key to return the fingerprint from. + * @return The fingerprint of the requested key, or null if not found. + */ + public byte[] getMasterKeyFingerprint(int idx) throws IOException { + byte[] data = getFingerprints(); + if (data == null) { + return null; + } + + // return the master key fingerprint + ByteBuffer fpbuf = ByteBuffer.wrap(data); + byte[] fp = new byte[20]; + fpbuf.position(idx * 20); + fpbuf.get(fp, 0, 20); + + return fp; + } + + public void setTransport(Transport mTransport) { + this.mTransport = mTransport; + + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/Transport.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/Transport.java new file mode 100644 index 000000000..e01d7da16 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/Transport.java @@ -0,0 +1,11 @@ +package org.sufficientlysecure.keychain.smartcard; + +import java.io.IOException; + +public interface Transport { + byte[] sendAndReceive(byte[] data) throws IOException; + + void release(); + + boolean isConnected(); +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/TransportIoException.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/TransportIoException.java new file mode 100644 index 000000000..544dd4045 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/TransportIoException.java @@ -0,0 +1,20 @@ +package org.sufficientlysecure.keychain.smartcard; + +import java.io.IOException; + +public class TransportIoException extends IOException { + public TransportIoException() { + } + + public TransportIoException(final String detailMessage) { + super(detailMessage); + } + + public TransportIoException(final String message, final Throwable cause) { + super(message, cause); + } + + public TransportIoException(final Throwable cause) { + super(cause); + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbConnectionManager.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbConnectionManager.java new file mode 100644 index 000000000..c98d5d43f --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbConnectionManager.java @@ -0,0 +1,148 @@ +package org.sufficientlysecure.keychain.smartcard; + +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; +import java.util.concurrent.atomic.AtomicBoolean; + +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 final Semaphore mRunning = new Semaphore(1); + private final Set mProcessedDevices = Collections.newSetFromMap(new ConcurrentHashMap()); + private final AtomicBoolean mStopped = new AtomicBoolean(false); + private Activity mActivity; + private final Thread mWatchThread = new Thread() { + @Override + public void run() { + final UsbManager usbManager = (UsbManager) mActivity.getSystemService(Context.USB_SERVICE); + + while (!mStopped.get()) { + try { + mRunning.acquire(); + } catch (InterruptedException e) { + } + mRunning.release(); + if (mStopped.get()) return; + + // + 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); + + Log.d(LOG_TAG, "Requesting permission for " + device.getDeviceName()); + usbManager.requestPermission(device, PendingIntent.getBroadcast(mActivity, 0, intent, 0)); + } + + try { + sleep(1000); + } catch (InterruptedException ignored) { + } + } + } + }; + private OnDiscoveredUsbDeviceListener mListener; + /** + * 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); + + if (permission) { + interceptIntent(intent); + } + + context.unregisterReceiver(mUsbReceiver); + } + } + }; + + public UsbConnectionManager(final Activity activity, final OnDiscoveredUsbDeviceListener listener) { + this.mActivity = activity; + this.mListener = listener; + mRunning.acquireUninterruptibly(); + mWatchThread.start(); + } + + private static UsbDevice getDevice(UsbManager manager) { + HashMap deviceList = manager.getDeviceList(); + for (UsbDevice device : deviceList.values()) { + if (device.getVendorId() == 0x1050 && (device.getProductId() == 0x0112 || device.getProductId() == 0x0115)) { + return device; + } + } + return null; + } + + 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; + } + } + + public void onDestroy() { + mStopped.set(true); + mRunning.release(); + try { + mActivity.unregisterReceiver(mUsbReceiver); + } catch (IllegalArgumentException ignore) { + } + mActivity = null; + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbTransport.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbTransport.java new file mode 100644 index 000000000..08f296c25 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbTransport.java @@ -0,0 +1,226 @@ +package org.sufficientlysecure.keychain.smartcard; + +import android.hardware.usb.UsbConstants; +import android.hardware.usb.UsbDevice; +import android.hardware.usb.UsbDeviceConnection; +import android.hardware.usb.UsbEndpoint; +import android.hardware.usb.UsbInterface; +import android.hardware.usb.UsbManager; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.util.Pair; + +import org.bouncycastle.util.Arrays; +import org.bouncycastle.util.encoders.Hex; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +public class UsbTransport implements Transport { + private static final int CLASS_SMARTCARD = 11; + private static final int TIMEOUT = 20 * 1000; // 2 s + + private final UsbManager mUsbManager; + private final UsbDevice mUsbDevice; + private final UsbInterface mUsbInterface; + private final UsbEndpoint mBulkIn; + private final UsbEndpoint mBulkOut; + private final UsbDeviceConnection mConnection; + private byte mCounter = 0; + + public UsbTransport(final UsbDevice usbDevice, final UsbManager usbManager) throws TransportIoException { + mUsbDevice = usbDevice; + mUsbManager = usbManager; + + mUsbInterface = getSmartCardInterface(mUsbDevice); + // throw if mUsbInterface == null + final Pair ioEndpoints = getIoEndpoints(mUsbInterface); + mBulkIn = ioEndpoints.first; + mBulkOut = ioEndpoints.second; + // throw if any endpoint is null + + mConnection = mUsbManager.openDevice(mUsbDevice); + // throw if connection is null + mConnection.claimInterface(mUsbInterface, true); + // 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(); + } + + private void powerOff() throws TransportIoException { + final byte[] iccPowerOff = { + 0x63, + 0x00, 0x00, 0x00, 0x00, + 0x00, + mCounter++, + 0x00, + 0x00, 0x00 + }; + sendRaw(iccPowerOff); + receive(); + } + + void powerOn() throws TransportIoException { + final byte[] iccPowerOn = { + 0x62, + 0x00, 0x00, 0x00, 0x00, + 0x00, + mCounter++, + 0x00, + 0x00, 0x00 + }; + sendRaw(iccPowerOn); + receive(); + } + + /** + * Get first class 11 (Chip/Smartcard) interface for the device + * + * @param device {@link UsbDevice} which will be searched + * @return {@link UsbInterface} of smartcard or null if it doesn't exist + */ + @Nullable + private static UsbInterface getSmartCardInterface(final UsbDevice device) { + for (int i = 0; i < device.getInterfaceCount(); i++) { + final UsbInterface anInterface = device.getInterface(i); + if (anInterface.getInterfaceClass() == CLASS_SMARTCARD) { + return anInterface; + } + } + return null; + } + + @NonNull + private static Pair getIoEndpoints(final UsbInterface usbInterface) { + UsbEndpoint bulkIn = null, bulkOut = null; + for (int i = 0; i < usbInterface.getEndpointCount(); i++) { + final UsbEndpoint endpoint = usbInterface.getEndpoint(i); + if (endpoint.getType() != UsbConstants.USB_ENDPOINT_XFER_BULK) { + continue; + } + + if (endpoint.getDirection() == UsbConstants.USB_DIR_IN) { + bulkIn = endpoint; + } else if (endpoint.getDirection() == UsbConstants.USB_DIR_OUT) { + bulkOut = endpoint; + } + } + return new Pair<>(bulkIn, bulkOut); + } + + @Override + public void release() { + mConnection.releaseInterface(mUsbInterface); + mConnection.close(); + } + + @Override + public boolean isConnected() { + // TODO: redo + return mUsbManager.getDeviceList().containsValue(mUsbDevice); + } + + @Override + public byte[] sendAndReceive(byte[] data) throws TransportIoException { + send(data); + byte[] bytes; + do { + bytes = receive(); + } while (isXfrBlockNotReady(bytes)); + + checkXfrBlockResult(bytes); + return Arrays.copyOfRange(bytes, 10, bytes.length); + } + + public void send(byte[] d) throws TransportIoException { + int l = d.length; + byte[] data = Arrays.concatenate(new byte[]{ + 0x6f, + (byte) l, (byte) (l >> 8), (byte) (l >> 16), (byte) (l >> 24), + 0x00, + mCounter++, + 0x00, + 0x00, 0x00}, + d); + + int send = 0; + while (send < data.length) { + final int len = Math.min(mBulkIn.getMaxPacketSize(), data.length - send); + sendRaw(Arrays.copyOfRange(data, send, send + len)); + send += len; + } + } + + public byte[] receive() throws TransportIoException { + byte[] buffer = new byte[mBulkIn.getMaxPacketSize()]; + byte[] result = null; + int readBytes = 0, totalBytes = 0; + + do { + int res = mConnection.bulkTransfer(mBulkIn, buffer, buffer.length, TIMEOUT); + if (res < 0) { + throw new TransportIoException("USB error, failed to receive response " + res); + } + if (result == null) { + if (res < 10) { + throw new TransportIoException("USB error, failed to receive ccid header"); + } + totalBytes = ByteBuffer.wrap(buffer, 1, 4).order(ByteOrder.LITTLE_ENDIAN).asIntBuffer().get() + 10; + result = new byte[totalBytes]; + } + System.arraycopy(buffer, 0, result, readBytes, res); + readBytes += res; + } while (readBytes < totalBytes); + + return result; + } + + private void sendRaw(final byte[] data) throws TransportIoException { + final int tr1 = mConnection.bulkTransfer(mBulkOut, data, data.length, TIMEOUT); + if (tr1 != data.length) { + throw new TransportIoException("USB error, failed to send data " + tr1); + } + } + + private byte getStatus(byte[] bytes) { + return (byte) ((bytes[7] >> 6) & 0x03); + } + + private void checkXfrBlockResult(byte[] bytes) throws TransportIoException { + final byte status = getStatus(bytes); + if (status != 0) { + throw new TransportIoException("CCID error, status " + status + " error code: " + Hex.toHexString(bytes, 8, 1)); + } + } + + private boolean isXfrBlockNotReady(byte[] bytes) { + return getStatus(bytes) == 2; + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java index b3f60ba41..268dbad02 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java @@ -149,9 +149,9 @@ public class CreateKeyActivity extends BaseSecurityTokenNfcActivity { return; } - mScannedFingerprints = mJavacardDevice.getFingerprints(); - mNfcAid = mJavacardDevice.getAid(); - mNfcUserId = mJavacardDevice.getUserId(); + mScannedFingerprints = mSmartcardDevice.getFingerprints(); + mNfcAid = mSmartcardDevice.getAid(); + mNfcUserId = mSmartcardDevice.getUserId(); } @Override diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenImportResetFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenImportResetFragment.java index 401db0b98..a0e93ed85 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenImportResetFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenImportResetFragment.java @@ -25,7 +25,6 @@ import java.util.ArrayList; import android.app.Activity; import android.content.Intent; import android.os.Bundle; -import android.os.Parcelable; import android.support.v4.app.Fragment; import android.view.LayoutInflater; import android.view.View; @@ -250,9 +249,9 @@ public class CreateSecurityTokenImportResetFragment @Override public void doNfcInBackground() throws IOException { - mTokenFingerprints = mCreateKeyActivity.mJavacardDevice.getFingerprints(); - mTokenAid = mCreateKeyActivity.mJavacardDevice.getAid(); - mTokenUserId = mCreateKeyActivity.mJavacardDevice.getUserId(); + mTokenFingerprints = mCreateKeyActivity.mSmartcardDevice.getFingerprints(); + mTokenAid = mCreateKeyActivity.mSmartcardDevice.getAid(); + mTokenUserId = mCreateKeyActivity.mSmartcardDevice.getUserId(); byte[] fp = new byte[20]; ByteBuffer.wrap(fp).put(mTokenFingerprints, 0, 20); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java index 7e1474eb7..c68936577 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java @@ -162,7 +162,7 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity case NFC_DECRYPT: { for (int i = 0; i < mRequiredInput.mInputData.length; i++) { byte[] encryptedSessionKey = mRequiredInput.mInputData[i]; - byte[] decryptedSessionKey = mJavacardDevice.decryptSessionKey(encryptedSessionKey); + byte[] decryptedSessionKey = mSmartcardDevice.decryptSessionKey(encryptedSessionKey); mInputParcel.addCryptoData(encryptedSessionKey, decryptedSessionKey); } break; @@ -173,15 +173,15 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity for (int i = 0; i < mRequiredInput.mInputData.length; i++) { byte[] hash = mRequiredInput.mInputData[i]; int algo = mRequiredInput.mSignAlgos[i]; - byte[] signedHash = mJavacardDevice.calculateSignature(hash, algo); + byte[] signedHash = mSmartcardDevice.calculateSignature(hash, algo); mInputParcel.addCryptoData(hash, signedHash); } break; } case NFC_MOVE_KEY_TO_CARD: { // TODO: assume PIN and Admin PIN to be default for this operation - mJavacardDevice.setPin(new Passphrase("123456")); - mJavacardDevice.setAdminPin(new Passphrase("12345678")); + mSmartcardDevice.setPin(new Passphrase("123456")); + mSmartcardDevice.setAdminPin(new Passphrase("12345678")); ProviderHelper providerHelper = new ProviderHelper(this); CanonicalizedSecretKeyRing secretKeyRing; @@ -206,7 +206,7 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity long keyGenerationTimestampMillis = key.getCreationTime().getTime(); long keyGenerationTimestamp = keyGenerationTimestampMillis / 1000; byte[] timestampBytes = ByteBuffer.allocate(4).putInt((int) keyGenerationTimestamp).array(); - byte[] tokenSerialNumber = Arrays.copyOf(mJavacardDevice.getAid(), 16); + byte[] tokenSerialNumber = Arrays.copyOf(mSmartcardDevice.getAid(), 16); Passphrase passphrase; try { @@ -218,25 +218,25 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity if (key.canSign() || key.canCertify()) { if (shouldPutKey(key.getFingerprint(), 0)) { - mJavacardDevice.putKey(0xB6, key, passphrase); - mJavacardDevice.putData(0xCE, timestampBytes); - mJavacardDevice.putData(0xC7, key.getFingerprint()); + mSmartcardDevice.putKey(0xB6, key, passphrase); + mSmartcardDevice.putData(0xCE, timestampBytes); + mSmartcardDevice.putData(0xC7, key.getFingerprint()); } else { throw new IOException("Key slot occupied; token must be reset to put new signature key."); } } else if (key.canEncrypt()) { if (shouldPutKey(key.getFingerprint(), 1)) { - mJavacardDevice.putKey(0xB8, key, passphrase); - mJavacardDevice.putData(0xCF, timestampBytes); - mJavacardDevice.putData(0xC8, key.getFingerprint()); + mSmartcardDevice.putKey(0xB8, key, passphrase); + mSmartcardDevice.putData(0xCF, timestampBytes); + mSmartcardDevice.putData(0xC8, key.getFingerprint()); } else { throw new IOException("Key slot occupied; token must be reset to put new decryption key."); } } else if (key.canAuthenticate()) { if (shouldPutKey(key.getFingerprint(), 2)) { - mJavacardDevice.putKey(0xA4, key, passphrase); - mJavacardDevice.putData(0xD0, timestampBytes); - mJavacardDevice.putData(0xC9, key.getFingerprint()); + mSmartcardDevice.putKey(0xA4, key, passphrase); + mSmartcardDevice.putData(0xD0, timestampBytes); + mSmartcardDevice.putData(0xC9, key.getFingerprint()); } else { throw new IOException("Key slot occupied; token must be reset to put new authentication key."); } @@ -249,13 +249,13 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity } // change PINs afterwards - mJavacardDevice.modifyPin(0x81, newPin); - mJavacardDevice.modifyPin(0x83, newAdminPin); + mSmartcardDevice.modifyPin(0x81, newPin); + mSmartcardDevice.modifyPin(0x83, newAdminPin); break; } case NFC_RESET_CARD: { - mJavacardDevice.resetAndWipeToken(); + mSmartcardDevice.resetAndWipeToken(); break; } @@ -330,7 +330,7 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity } private boolean shouldPutKey(byte[] fingerprint, int idx) throws IOException { - byte[] tokenFingerprint = mJavacardDevice.getMasterKeyFingerprint(idx); + byte[] tokenFingerprint = mSmartcardDevice.getMasterKeyFingerprint(idx); // Note: special case: This should not happen, but happens with // https://github.com/FluffyKaon/OpenPGP-Card, thus for now assume true diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java index 7d6ba5c8f..8ed2db9b7 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java @@ -649,9 +649,9 @@ public class ViewKeyActivity extends BaseSecurityTokenNfcActivity implements @Override protected void doNfcInBackground() throws IOException { - mNfcFingerprints = mJavacardDevice.getFingerprints(); - mNfcUserId = mJavacardDevice.getUserId(); - mNfcAid = mJavacardDevice.getAid(); + mNfcFingerprints = mSmartcardDevice.getFingerprints(); + mNfcUserId = mSmartcardDevice.getUserId(); + mNfcAid = mSmartcardDevice.getAid(); } @Override 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 e3c331b0b..8dde54a1f 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 @@ -21,7 +21,6 @@ package org.sufficientlysecure.keychain.ui.base; import android.app.Activity; -import android.app.PendingIntent; import android.content.Intent; import android.content.pm.PackageManager; import android.hardware.usb.UsbDevice; @@ -35,12 +34,6 @@ import android.os.Bundle; 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.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; @@ -48,6 +41,11 @@ 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.UsbConnectionManager; +import org.sufficientlysecure.keychain.smartcard.UsbTransport; import org.sufficientlysecure.keychain.ui.CreateKeyActivity; import org.sufficientlysecure.keychain.ui.PassphraseDialogActivity; import org.sufficientlysecure.keychain.ui.ViewKeyActivity; @@ -74,7 +72,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity private static final String FIDESMO_APP_PACKAGE = "com.fidesmo.sec.android"; - public JavacardDevice mJavacardDevice = new BaseJavacardDevice(); + public SmartcardDevice mSmartcardDevice = new SmartcardDevice(); protected TagDispatcher mTagDispatcher; protected UsbConnectionManager mUsbDispatcher; private boolean mTagHandlingEnabled; @@ -93,9 +91,9 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity * Override to implement NFC operations (background thread) */ protected void doNfcInBackground() throws IOException { - mNfcFingerprints = mJavacardDevice.getFingerprints(); - mNfcUserId = mJavacardDevice.getUserId(); - mNfcAid = mJavacardDevice.getAid(); + mNfcFingerprints = mSmartcardDevice.getFingerprints(); + mNfcUserId = mSmartcardDevice.getUserId(); + mNfcAid = mSmartcardDevice.getAid(); } /** @@ -141,7 +139,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity public void tagDiscovered(final Tag tag) { // Actual NFC operations are executed in doInBackground to not block the UI thread - if(!mTagHandlingEnabled) + if (!mTagHandlingEnabled) return; new AsyncTask() { @Override @@ -178,7 +176,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 - if(!mTagHandlingEnabled) + if (!mTagHandlingEnabled) return; new AsyncTask() { @Override @@ -347,7 +345,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity } // 6A82 app not installed on security token! case 0x6A82: { - if (mJavacardDevice.isFidesmoToken()) { + if (mSmartcardDevice.isFidesmoToken()) { // Check if the Fidesmo app is installed if (isAndroidAppInstalled(FIDESMO_APP_PACKAGE)) { promptFidesmoPgpInstall(); @@ -396,7 +394,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity Passphrase passphrase = PassphraseCacheService.getCachedPassphrase(this, requiredInput.getMasterKeyId(), requiredInput.getSubKeyId()); if (passphrase != null) { - mJavacardDevice.setPin(passphrase); + mSmartcardDevice.setPin(passphrase); return; } @@ -421,7 +419,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity return; } CryptoInputParcel input = data.getParcelableExtra(PassphraseDialogActivity.RESULT_CRYPTO_INPUT); - mJavacardDevice.setPin(input.getPassphrase()); + mSmartcardDevice.setPin(input.getPassphrase()); break; } default: @@ -429,19 +427,19 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity } } - /** Handle NFC communication and return a result. - * + /** + * Handle NFC communication and return a result. + *

* This method is called by onNewIntent above upon discovery of an NFC tag. * It handles initialization and login to the application, subsequently * calls either nfcCalculateSignature() or nfcDecryptSessionKey(), then * finishes the activity with an appropriate result. - * + *

* On general communication, see also * http://www.cardwerk.com/smartcards/smartcard_standard_ISO7816-4_annex-a.aspx - * + *

* References to pages are generally related to the OpenPGP Application * on ISO SmartCard Systems specification. - * */ protected void handleTagDiscovered(Tag tag) throws IOException { @@ -451,22 +449,22 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity throw new IsoDepNotSupportedException("Tag does not support ISO-DEP (ISO 14443-4)"); } - mJavacardDevice.setTransport(new NfcTransport(isoCard)); - mJavacardDevice.connectToDevice(); + mSmartcardDevice.setTransport(new NfcTransport(isoCard)); + mSmartcardDevice.connectToDevice(); doNfcInBackground(); } protected void handleUsbDevice(UsbDevice device) throws IOException { UsbManager usbManager = (UsbManager) getSystemService(USB_SERVICE); - mJavacardDevice.setTransport(new UsbTransport(device, usbManager)); - mJavacardDevice.connectToDevice(); + mSmartcardDevice.setTransport(new UsbTransport(device, usbManager)); + mSmartcardDevice.connectToDevice(); doNfcInBackground(); } public boolean isNfcConnected() { - return mJavacardDevice.isConnected(); + return mSmartcardDevice.isConnected(); } /** @@ -535,7 +533,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity /** * Ask user if she wants to install PGP onto her Fidesmo token - */ + */ private void promptFidesmoPgpInstall() { FidesmoPgpInstallDialog fidesmoPgpInstallDialog = new FidesmoPgpInstallDialog(); fidesmoPgpInstallDialog.show(getSupportFragmentManager(), "fidesmoPgpInstallDialog"); @@ -552,6 +550,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity /** * Use the package manager to detect if an application is installed on the phone + * * @param uri an URI identifying the application's package * @return 'true' if the app is installed */ -- cgit v1.2.3