From c9c7feb5f7bebbe9b9ab151fd0c95d158254b641 Mon Sep 17 00:00:00 2001 From: Nikita Mikhailov Date: Sun, 8 May 2016 23:11:57 +0600 Subject: [PATCH] SecurityToken: add support for T=1 TPDU level of exchange --- .../keychain/securitytoken/t1/Frame.java | 145 --------------- .../keychain/securitytoken/t1/RBlock.java | 5 - .../securitytoken/t1/T1TPDUProtocol.java | 85 --------- .../securitytoken/usb/CcidTransceiver.java | 169 +++++++++++++++++ .../usb/CcidTransportProtocol.java | 24 +++ .../usb/T1ShortApduProtocol.java | 41 +++++ .../securitytoken/{ => usb}/UsbTransport.java | 172 ++++-------------- .../{ => usb}/UsbTransportException.java | 2 +- .../usb/tpdu/BlockChecksumType.java | 46 +++++ .../{t1 => usb/tpdu}/FrameType.java | 4 +- .../usb/tpdu/T1TpduProtocol.java | 148 +++++++++++++++ .../securitytoken/usb/tpdu/block/Block.java | 100 ++++++++++ .../usb/tpdu/block/BlockFactory.java | 51 ++++++ .../securitytoken/usb/tpdu/block/IBlock.java | 45 +++++ .../securitytoken/usb/tpdu/block/RBlock.java | 63 +++++++ .../tpdu/block/SBlock.java} | 19 +- .../ui/base/BaseSecurityTokenActivity.java | 2 +- 17 files changed, 735 insertions(+), 386 deletions(-) delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/t1/Frame.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/t1/RBlock.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/t1/T1TPDUProtocol.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/usb/CcidTransceiver.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/usb/CcidTransportProtocol.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/usb/T1ShortApduProtocol.java rename OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/{ => usb}/UsbTransport.java (58%) rename OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/{ => usb}/UsbTransportException.java (95%) create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/usb/tpdu/BlockChecksumType.java rename OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/{t1 => usb/tpdu}/FrameType.java (92%) create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/usb/tpdu/T1TpduProtocol.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/usb/tpdu/block/Block.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/usb/tpdu/block/BlockFactory.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/usb/tpdu/block/IBlock.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/usb/tpdu/block/RBlock.java rename OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/{t1/ChecksumType.java => usb/tpdu/block/SBlock.java} (64%) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/t1/Frame.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/t1/Frame.java deleted file mode 100644 index b91efdcae..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/t1/Frame.java +++ /dev/null @@ -1,145 +0,0 @@ -/* - * Copyright (C) 2016 Nikita Mikhailov - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package org.sufficientlysecure.keychain.securitytoken.t1; - -import android.support.annotation.NonNull; - -import org.bouncycastle.util.Arrays; -import org.sufficientlysecure.keychain.securitytoken.UsbTransportException; - -public class Frame { - private static final byte IBLOCK_PCB_M_BIT_MASK = 1 << 5; - private ChecksumType mChecksumType; - private FrameType mFrameType; - - // Prologue - private byte mNAD; - private byte mPCB; - private byte mLEN; - // Information field - private byte[] mAPDU; // 0..254 - // Epilogue - private byte[] mEDC; // 1..2 - - @NonNull - public static Frame newFrame(@NonNull ChecksumType checksumType, @NonNull FrameType frameType, - boolean hasMore, byte sequenceCounter, @NonNull byte[] apdu) throws UsbTransportException { - final Frame res = new Frame(); - - res.mNAD = 0; - res.mPCB = (byte) (frameType.getSequenceBit() == -1 ? 0 : ((1 & sequenceCounter) << frameType.getSequenceBit())); - if (hasMore && !frameType.isChainingSupported()) { - throw new UsbTransportException("Invalid arguments"); - } - if (hasMore) { - res.mPCB |= 1 << 5; - } - - if (apdu.length == 0) { - throw new UsbTransportException("APDU is too short"); - } - if (apdu.length > 254) { - throw new UsbTransportException("APDU is too long; should be split"); - } - res.mLEN = (byte) apdu.length; - res.mAPDU = apdu; - res.mChecksumType = checksumType; - res.mFrameType = frameType; - - return res; - } - - @NonNull - public static Frame fromData(@NonNull ChecksumType checksumProtocol, @NonNull byte[] data) - throws UsbTransportException { - - final Frame res = new Frame(); - res.mChecksumType = checksumProtocol; - - res.mNAD = data[0]; - res.mPCB = data[1]; - res.mLEN = data[2]; - - int epilogueOffset = 3 + res.mLEN; - res.mAPDU = Arrays.copyOfRange(data, 3, epilogueOffset); - res.mEDC = Arrays.copyOfRange(data, epilogueOffset, data.length); - - if (!Arrays.areEqual(res.mEDC, res.mChecksumType.computeChecksum(Arrays.concatenate( - new byte[]{res.mNAD, res.mPCB, res.mLEN}, - res.mAPDU)))) { - throw new UsbTransportException("T=1 CRC doesnt match"); - } - - return res; - } - - @NonNull - public byte[] getBytes() { - byte[] res = Arrays.concatenate(new byte[]{ - mNAD, mPCB, mLEN - }, mAPDU); - - return Arrays.concatenate(res, mChecksumType.computeChecksum(res)); - } - - @NonNull - public RError getRError() throws UsbTransportException { - if (mFrameType != FrameType.R_BLOCK) { - throw new UsbTransportException("getRerror called for non R block"); - } - return RError.from(mPCB & 0x3); - } - - public byte[] getAPDU() { - return mAPDU; - } - - @NonNull - public FrameType getBlockType() throws UsbTransportException { - return FrameType.fromPCB(mPCB); - } - - public boolean hasMore() { - return mFrameType.isChainingSupported() && (mPCB & IBLOCK_PCB_M_BIT_MASK) != 0; - } - - public enum RError { - NO_ERROR(0), EDC_ERROR(1), OTHER_ERROR(2); - - private int mLowBits; - - RError(int lowBits) { - mLowBits = lowBits; - } - - public int getLowBits() { - return mLowBits; - } - - @NonNull - public static RError from(int i) throws UsbTransportException { - for (final RError error : values()) { - if (error.mLowBits == (i & 0x3)) { - return error; - } - } - throw new UsbTransportException("Invalid R block error bits"); - } - } - -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/t1/RBlock.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/t1/RBlock.java deleted file mode 100644 index 842497585..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/t1/RBlock.java +++ /dev/null @@ -1,5 +0,0 @@ -package org.sufficientlysecure.keychain.securitytoken.t1; - -public class RBlock extends Frame { - -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/t1/T1TPDUProtocol.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/t1/T1TPDUProtocol.java deleted file mode 100644 index 899758252..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/t1/T1TPDUProtocol.java +++ /dev/null @@ -1,85 +0,0 @@ -package org.sufficientlysecure.keychain.securitytoken.t1; - -import android.os.Debug; -import android.support.annotation.NonNull; - -import org.bouncycastle.util.Arrays; -import org.bouncycastle.util.encoders.Hex; -import org.sufficientlysecure.keychain.Constants; -import org.sufficientlysecure.keychain.securitytoken.UsbTransport; -import org.sufficientlysecure.keychain.securitytoken.UsbTransportException; -import org.sufficientlysecure.keychain.util.Log; - -public class T1TPDUProtocol { - private final static int MAX_FRAME_LEN = 254; - private byte mCounter = 0; - private UsbTransport mTransport; - - public T1TPDUProtocol(final UsbTransport transport) { - mTransport = transport; - } - - - public void pps() throws UsbTransportException { - byte[] pps = new byte[]{ - (byte) 0xFF, - 1, - (byte) (0xFF ^ 1) - }; - - mTransport.sendXfrBlock(pps); - - byte[] ppsResponse = mTransport.receiveRaw(); - - Log.d(Constants.TAG, "PPS response " + Hex.toHexString(ppsResponse)); - } - - public byte[] transceive(@NonNull byte[] apdu) throws UsbTransportException { - int start = 0; - - Frame responseFrame = null; - while (apdu.length - start > 0) { - boolean hasMore = start + MAX_FRAME_LEN < apdu.length; - int len = Math.min(MAX_FRAME_LEN, apdu.length - start); - - final Frame frame = Frame.newFrame(ChecksumType.LRC, FrameType.I_BLOCK, hasMore, mCounter ^= 1, - Arrays.copyOfRange(apdu, start, start + len)); - - mTransport.sendXfrBlock(frame.getBytes()); - - // Receive R - byte[] response = mTransport.receiveRaw(); - responseFrame = Frame.fromData(ChecksumType.LRC, response); - - start += len; - - if (responseFrame.getBlockType() == FrameType.S_BLOCK) { - Log.d(Constants.TAG, "S block received " + Hex.toHexString(response)); - } else if (responseFrame.getBlockType() == FrameType.R_BLOCK) { - Log.d(Constants.TAG, "R block received " + Hex.toHexString(response)); - if (responseFrame.getRError() != Frame.RError.NO_ERROR) { - throw new UsbTransportException("R block reports error"); - } - } else { - // I block - if (start == apdu.length) { - throw new UsbTransportException("T1 frame response underflow"); - } - break; - } - } - - if (responseFrame == null || responseFrame.getBlockType() != FrameType.I_BLOCK) - throw new UsbTransportException("Invalid frame state"); - - byte[] responseApdu = responseFrame.getAPDU(); - - while (responseFrame.hasMore()) { - responseFrame = Frame.fromData(ChecksumType.LRC, mTransport.receive()); - responseApdu = Arrays.concatenate(responseApdu, responseFrame.getAPDU()); - } - - return responseApdu; - } - -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/usb/CcidTransceiver.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/usb/CcidTransceiver.java new file mode 100644 index 000000000..d43c373d7 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/usb/CcidTransceiver.java @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2016 Nikita Mikhailov + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.sufficientlysecure.keychain.securitytoken.usb; + +import android.hardware.usb.UsbDeviceConnection; +import android.hardware.usb.UsbEndpoint; +import android.os.SystemClock; +import android.support.annotation.NonNull; + +import org.bouncycastle.util.Arrays; +import org.bouncycastle.util.encoders.Hex; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +public class CcidTransceiver { + private static final int TIMEOUT = 20 * 1000; // 20s + + private byte mCounter; + private UsbDeviceConnection mConnection; + private UsbEndpoint mBulkIn; + private UsbEndpoint mBulkOut; + + public CcidTransceiver(final UsbDeviceConnection connection, final UsbEndpoint bulkIn, + final UsbEndpoint bulkOut) { + + mConnection = connection; + mBulkIn = bulkIn; + mBulkOut = bulkOut; + } + + public byte[] receiveRaw() throws UsbTransportException { + byte[] bytes; + do { + bytes = receive(); + } while (isDataBlockNotReady(bytes)); + + checkDataBlockResponse(bytes); + + return Arrays.copyOfRange(bytes, 10, bytes.length); + } + + /** + * Power of ICC + * Spec: 6.1.1 PC_to_RDR_IccPowerOn + * + * @throws UsbTransportException + */ + @NonNull + public byte[] iccPowerOn() throws UsbTransportException { + final byte[] iccPowerCommand = { + 0x62, + 0x00, 0x00, 0x00, 0x00, + 0x00, + mCounter++, + 0x00, + 0x00, 0x00 + }; + + sendRaw(iccPowerCommand); + + long startTime = System.currentTimeMillis(); + byte[] atr = null; + while (true) { + try { + atr = receiveRaw(); + break; + } catch (UsbTransportException e) { + // Try more startTime + if (System.currentTimeMillis() - startTime > TIMEOUT) { + break; + } + } + SystemClock.sleep(100); + } + + if (atr == null) { + throw new UsbTransportException("Couldn't power up Security Token"); + } + + return atr; + } + + /** + * Transmits XfrBlock + * 6.1.4 PC_to_RDR_XfrBlock + * @param payload payload to transmit + * @throws UsbTransportException + */ + public void sendXfrBlock(byte[] payload) throws UsbTransportException { + int l = payload.length; + byte[] data = Arrays.concatenate(new byte[]{ + 0x6f, + (byte) l, (byte) (l >> 8), (byte) (l >> 16), (byte) (l >> 24), + 0x00, + mCounter++, + 0x00, + 0x00, 0x00}, + payload); + + 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 UsbTransportException { + 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 UsbTransportException("USB error - failed to receive response " + res); + } + if (result == null) { + if (res < 10) { + throw new UsbTransportException("USB-CCID 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 UsbTransportException { + final int tr1 = mConnection.bulkTransfer(mBulkOut, data, data.length, TIMEOUT); + if (tr1 != data.length) { + throw new UsbTransportException("USB error - failed to transmit data " + tr1); + } + } + + private byte getStatus(byte[] bytes) { + return (byte) ((bytes[7] >> 6) & 0x03); + } + + private void checkDataBlockResponse(byte[] bytes) throws UsbTransportException { + final byte status = getStatus(bytes); + if (status != 0) { + throw new UsbTransportException("USB-CCID error - status " + status + " error code: " + Hex.toHexString(bytes, 8, 1)); + } + } + + private boolean isDataBlockNotReady(byte[] bytes) { + return getStatus(bytes) == 2; + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/usb/CcidTransportProtocol.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/usb/CcidTransportProtocol.java new file mode 100644 index 000000000..5cfc3586d --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/usb/CcidTransportProtocol.java @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2016 Nikita Mikhailov + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.sufficientlysecure.keychain.securitytoken.usb; + +import android.support.annotation.NonNull; + +public interface CcidTransportProtocol { + byte[] transceive(@NonNull byte[] apdu) throws UsbTransportException; +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/usb/T1ShortApduProtocol.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/usb/T1ShortApduProtocol.java new file mode 100644 index 000000000..883f11cef --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/usb/T1ShortApduProtocol.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2016 Nikita Mikhailov + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.sufficientlysecure.keychain.securitytoken.usb; + +import android.support.annotation.NonNull; + +import org.bouncycastle.util.encoders.Hex; +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.util.Log; + +public class T1ShortApduProtocol implements CcidTransportProtocol { + private CcidTransceiver mTransceiver; + + public T1ShortApduProtocol(CcidTransceiver transceiver) throws UsbTransportException { + mTransceiver = transceiver; + + byte[] atr = mTransceiver.iccPowerOn(); + Log.d(Constants.TAG, "Usb transport connected T1/Short APDU, ATR=" + Hex.toHexString(atr)); + } + + @Override + public byte[] transceive(@NonNull final byte[] apdu) throws UsbTransportException { + mTransceiver.sendXfrBlock(apdu); + return mTransceiver.receiveRaw(); + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/UsbTransport.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/usb/UsbTransport.java similarity index 58% rename from OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/UsbTransport.java rename to OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/usb/UsbTransport.java index 94460fc10..037a23352 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/UsbTransport.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/usb/UsbTransport.java @@ -15,9 +15,8 @@ * along with this program. If not, see . */ -package org.sufficientlysecure.keychain.securitytoken; +package org.sufficientlysecure.keychain.securitytoken.usb; -import android.hardware.usb.UsbConfiguration; import android.hardware.usb.UsbConstants; import android.hardware.usb.UsbDevice; import android.hardware.usb.UsbDeviceConnection; @@ -28,10 +27,9 @@ 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 org.sufficientlysecure.keychain.Constants; -import org.sufficientlysecure.keychain.securitytoken.t1.T1TPDUProtocol; +import org.sufficientlysecure.keychain.securitytoken.Transport; +import org.sufficientlysecure.keychain.securitytoken.usb.tpdu.T1TpduProtocol; import org.sufficientlysecure.keychain.util.Log; import java.io.IOException; @@ -45,7 +43,15 @@ import java.nio.ByteOrder; */ public class UsbTransport implements Transport { private static final int USB_CLASS_SMARTCARD = 11; - private static final int TIMEOUT = 20 * 1000; // 20s + private static final int PROTOCOLS_OFFSET = 6; + private static final int FEATURES_OFFSET = 40; + private static final int MASK_T1_PROTO = 2; + + // dwFeatures Masks + private static final int MASK_TPDU = 0x10000; + private static final int MASK_SHORT_APDU = 0x20000; + private static final int MASK_EXTENDED_APDU = 0x40000; + private final UsbManager mUsbManager; private final UsbDevice mUsbDevice; @@ -53,34 +59,14 @@ public class UsbTransport implements Transport { private UsbEndpoint mBulkIn; private UsbEndpoint mBulkOut; private UsbDeviceConnection mConnection; - private byte mCounter; + private CcidTransceiver mTransceiver; + private CcidTransportProtocol mProtocol; public UsbTransport(UsbDevice usbDevice, UsbManager usbManager) { mUsbDevice = usbDevice; mUsbManager = usbManager; } - - /** - * Manage ICC power, Yubikey requires to power on ICC - * Spec: 6.1.1 PC_to_RDR_IccPowerOn; 6.1.2 PC_to_RDR_IccPowerOff - * - * @param on true to turn ICC on, false to turn it off - * @throws UsbTransportException - */ - private void setIccPower(boolean on) throws UsbTransportException { - final byte[] iccPowerCommand = { - (byte) (on ? 0x62 : 0x63), - 0x00, 0x00, 0x00, 0x00, - 0x00, - mCounter++, - 0x00, - 0x00, 0x00 - }; - - sendRaw(iccPowerCommand); - } - /** * Get first class 11 (Chip/Smartcard) interface of the device * @@ -162,7 +148,6 @@ public class UsbTransport implements Transport { */ @Override public void connect() throws IOException { - mCounter = 0; mUsbInterface = getSmartCardInterface(mUsbDevice); if (mUsbInterface == null) { // Shouldn't happen as we whitelist only class 11 devices @@ -186,18 +171,34 @@ public class UsbTransport implements Transport { throw new UsbTransportException("USB error - failed to claim interface"); } - setIccPower(true); - try { - Thread.sleep(1000); - } catch (InterruptedException e) { + mTransceiver = new CcidTransceiver(mConnection, mBulkIn, mBulkOut); + + + configureProtocol(); + } + + private void configureProtocol() throws UsbTransportException { + byte[] desc = mConnection.getRawDescriptors(); + if (desc.length < FEATURES_OFFSET + 4) + throw new UsbTransportException("Can't access dwFeatures for protocol selection"); + + int dwProtocols = ByteBuffer.wrap(desc, FEATURES_OFFSET, 4).order(ByteOrder.LITTLE_ENDIAN) + .asIntBuffer().get(); + int dwFeatures = ByteBuffer.wrap(desc, FEATURES_OFFSET, 4).order(ByteOrder.LITTLE_ENDIAN) + .asIntBuffer().get(); + + if ((dwProtocols & MASK_T1_PROTO) == 0) { + throw new UsbTransportException("T=0 protocol is not supported"); } - byte[] atrPacket = receiveRaw(); - Log.d(Constants.TAG, "Usb transport connected, ATR=" - + Hex.toHexString(atrPacket)); - - new T1TPDUProtocol(this).pps(); + if ((dwFeatures & MASK_TPDU) != 0) { + mProtocol = new T1TpduProtocol(mTransceiver); + } else if (((dwFeatures & MASK_SHORT_APDU) != 0) || ((dwFeatures & MASK_EXTENDED_APDU) != 0)) { + mProtocol = new T1ShortApduProtocol(mTransceiver); + } else { + throw new UsbTransportException("Character level exchange is not supported"); + } } /** @@ -208,100 +209,7 @@ public class UsbTransport implements Transport { */ @Override public byte[] transceive(byte[] data) throws UsbTransportException { - final T1TPDUProtocol t1TPDUProtocol = new T1TPDUProtocol(this); - return t1TPDUProtocol.transceive(data); - /*sendXfrBlock(data); - byte[] bytes = receiveRaw(); - - // Discard header - return Arrays.copyOfRange(bytes, 10, bytes.length);*/ - } - - public byte[] receiveRaw() throws UsbTransportException { - byte[] bytes; - do { - bytes = receive(); - } while (isDataBlockNotReady(bytes)); - - checkDataBlockResponse(bytes); - - return Arrays.copyOfRange(bytes, 10, bytes.length); - } - - /** - * Transmits XfrBlock - * 6.1.4 PC_to_RDR_XfrBlock - * @param payload payload to transmit - * @throws UsbTransportException - */ - public void sendXfrBlock(byte[] payload) throws UsbTransportException { - int l = payload.length; - byte[] data = Arrays.concatenate(new byte[]{ - 0x6f, - (byte) l, (byte) (l >> 8), (byte) (l >> 16), (byte) (l >> 24), - 0x00, - mCounter++, - 0x00, - 0x00, 0x00}, - payload); - - 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 UsbTransportException { - byte[] buffer = new byte[mBulkIn.getMaxPacketSize()]; - byte[] result = null; - int readBytes = 0, totalBytes = 0; - - /*try { - Thread.sleep(1000); - } catch (InterruptedException e) { - - }*/ - do { - int res = mConnection.bulkTransfer(mBulkIn, buffer, buffer.length, TIMEOUT); - if (res < 0) { - throw new UsbTransportException("USB error - failed to receive response " + res); - } - if (result == null) { - if (res < 10) { - throw new UsbTransportException("USB-CCID 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 UsbTransportException { - final int tr1 = mConnection.bulkTransfer(mBulkOut, data, data.length, TIMEOUT); - if (tr1 != data.length) { - throw new UsbTransportException("USB error - failed to transmit data " + tr1); - } - } - - private byte getStatus(byte[] bytes) { - return (byte) ((bytes[7] >> 6) & 0x03); - } - - private void checkDataBlockResponse(byte[] bytes) throws UsbTransportException { - final byte status = getStatus(bytes); - if (status != 0) { - throw new UsbTransportException("USB-CCID error - status " + status + " error code: " + Hex.toHexString(bytes, 8, 1)); - } - } - - private boolean isDataBlockNotReady(byte[] bytes) { - return getStatus(bytes) == 2; + return mProtocol.transceive(data); } @Override diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/UsbTransportException.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/usb/UsbTransportException.java similarity index 95% rename from OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/UsbTransportException.java rename to OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/usb/UsbTransportException.java index 6d9212d9f..13e86c723 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/UsbTransportException.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/usb/UsbTransportException.java @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package org.sufficientlysecure.keychain.securitytoken; +package org.sufficientlysecure.keychain.securitytoken.usb; import java.io.IOException; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/usb/tpdu/BlockChecksumType.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/usb/tpdu/BlockChecksumType.java new file mode 100644 index 000000000..4af6364e3 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/usb/tpdu/BlockChecksumType.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2016 Nikita Mikhailov + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.sufficientlysecure.keychain.securitytoken.usb.tpdu; + +import org.sufficientlysecure.keychain.securitytoken.usb.UsbTransportException; + +public enum BlockChecksumType { + LRC(1), CRC(2); + + private int mLength; + + BlockChecksumType(int length) { + mLength = length; + } + + public byte[] computeChecksum(byte[] data, int offset, int len) throws UsbTransportException { + if (this == LRC) { + byte res = 0; + for (int i = offset; i < len; i++) { + res ^= data[i]; + } + return new byte[]{res}; + } else { + throw new UsbTransportException("CRC checksum is not implemented"); + } + } + + public int getLength() { + return mLength; + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/t1/FrameType.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/usb/tpdu/FrameType.java similarity index 92% rename from OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/t1/FrameType.java rename to OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/usb/tpdu/FrameType.java index 2a9fd60c6..03a8b109a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/t1/FrameType.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/usb/tpdu/FrameType.java @@ -15,9 +15,9 @@ * along with this program. If not, see . */ -package org.sufficientlysecure.keychain.securitytoken.t1; +package org.sufficientlysecure.keychain.securitytoken.usb.tpdu; -import org.sufficientlysecure.keychain.securitytoken.UsbTransportException; +import org.sufficientlysecure.keychain.securitytoken.usb.UsbTransportException; public enum FrameType { I_BLOCK(0b00000000, 0b10000000, 6, true), // Information diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/usb/tpdu/T1TpduProtocol.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/usb/tpdu/T1TpduProtocol.java new file mode 100644 index 000000000..59e651a3f --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/usb/tpdu/T1TpduProtocol.java @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2016 Nikita Mikhailov + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.sufficientlysecure.keychain.securitytoken.usb.tpdu; + +import android.support.annotation.NonNull; + +import org.bouncycastle.util.Arrays; +import org.bouncycastle.util.encoders.Hex; +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.securitytoken.usb.CcidTransceiver; +import org.sufficientlysecure.keychain.securitytoken.usb.UsbTransportException; +import org.sufficientlysecure.keychain.securitytoken.usb.CcidTransportProtocol; +import org.sufficientlysecure.keychain.securitytoken.usb.tpdu.block.Block; +import org.sufficientlysecure.keychain.securitytoken.usb.tpdu.block.IBlock; +import org.sufficientlysecure.keychain.securitytoken.usb.tpdu.block.RBlock; +import org.sufficientlysecure.keychain.securitytoken.usb.tpdu.block.SBlock; +import org.sufficientlysecure.keychain.util.Log; + +public class T1TpduProtocol implements CcidTransportProtocol { + private final static int MAX_FRAME_LEN = 254; + + private byte mCounter = 0; + private CcidTransceiver mTransceiver; + private BlockChecksumType mChecksumType; + + public T1TpduProtocol(final CcidTransceiver transceiver) throws UsbTransportException { + mTransceiver = transceiver; + + // Connect + byte[] atr = mTransceiver.iccPowerOn(); + Log.d(Constants.TAG, "Usb transport connected T1/TPDU, ATR=" + Hex.toHexString(atr)); + // TODO: set checksum from atr + mChecksumType = BlockChecksumType.LRC; + + // PPS all auto + pps(); + } + + protected void pps() throws UsbTransportException { + byte[] pps = new byte[]{(byte) 0xFF, 1, (byte) (0xFF ^ 1)}; + + mTransceiver.sendXfrBlock(pps); + + byte[] ppsResponse = mTransceiver.receiveRaw(); + + Log.d(Constants.TAG, "PPS response " + Hex.toHexString(ppsResponse)); + } + + public byte[] transceive(@NonNull byte[] apdu) throws UsbTransportException { + int start = 0; + + if (apdu.length == 0) { + throw new UsbTransportException("Cant transcive zero-length apdu(tpdu)"); + } + + Block responseBlock = null; + while (apdu.length - start > 0) { + boolean hasMore = start + MAX_FRAME_LEN < apdu.length; + int len = Math.min(MAX_FRAME_LEN, apdu.length - start); + + // Send next frame + Block block = newIBlock(mCounter++, hasMore, Arrays.copyOfRange(apdu, start, start + len)); + + mTransceiver.sendXfrBlock(block.getRawData()); + + // Receive I or R block + responseBlock = getBlockFromResponse(mTransceiver.receiveRaw()); + + start += len; + + if (responseBlock instanceof SBlock) { + Log.d(Constants.TAG, "S-Block received " + responseBlock.toString()); + // just ignore + } else if (responseBlock instanceof RBlock) { + Log.d(Constants.TAG, "R-Block received " + responseBlock.toString()); + if (((RBlock) responseBlock).getError() != RBlock.RError.NO_ERROR) { + throw new UsbTransportException("R-Block reports error " + + ((RBlock) responseBlock).getError()); + } + } else { // I block + if (start != apdu.length) { + throw new UsbTransportException("T1 frame response underflow"); + } + break; + } + } + + // Receive + if (responseBlock == null || !(responseBlock instanceof IBlock)) + throw new UsbTransportException("Invalid tpdu sequence state"); + + byte[] responseApdu = responseBlock.getApdu(); + + while (((IBlock) responseBlock).getChaining()) { + Block ackBlock = newRBlock(((IBlock) responseBlock).getSequence()); + mTransceiver.sendXfrBlock(ackBlock.getRawData()); + + responseBlock = getBlockFromResponse(mTransceiver.receive()); + + if (responseBlock instanceof IBlock) { + responseApdu = Arrays.concatenate(responseApdu, responseBlock.getApdu()); + } else { + Log.d(Constants.TAG, "Response block received " + responseBlock.toString()); + throw new UsbTransportException("Response: invalid state - invalid block received"); + } + } + + return responseApdu; + } + + // Factory methods + public Block getBlockFromResponse(byte[] data) throws UsbTransportException { + final Block baseBlock = new Block(mChecksumType, data); + + if ((baseBlock.getPcb() & 0b10000000) == 0b00000000) { + return new IBlock(baseBlock); + } else if ((baseBlock.getPcb() & 0b11000000) == 0b11000000) { + return new SBlock(baseBlock); + } else if ((baseBlock.getPcb() & 0b11000000) == 0b10000000) { + return new RBlock(baseBlock); + } + + throw new UsbTransportException("TPDU Unknown block type"); + } + + public IBlock newIBlock(byte sequence, boolean chaining, byte[] apdu) throws UsbTransportException { + return new IBlock(mChecksumType, (byte) 0, sequence, chaining, apdu); + } + + public RBlock newRBlock(byte sequence) throws UsbTransportException { + return new RBlock(mChecksumType, (byte) 0, sequence); + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/usb/tpdu/block/Block.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/usb/tpdu/block/Block.java new file mode 100644 index 000000000..bfebd1934 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/usb/tpdu/block/Block.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2016 Nikita Mikhailov + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.sufficientlysecure.keychain.securitytoken.usb.tpdu.block; + +import org.bouncycastle.util.Arrays; +import org.bouncycastle.util.encoders.Hex; +import org.sufficientlysecure.keychain.securitytoken.usb.UsbTransportException; +import org.sufficientlysecure.keychain.securitytoken.usb.tpdu.BlockChecksumType; + +public class Block { + protected static final int MAX_PAYLOAD_LEN = 254; + protected static final int OFFSET_NAD = 0; + protected static final int OFFSET_PCB = 1; + protected static final int OFFSET_LEN = 2; + protected static final int OFFSET_DATA = 3; + + protected byte[] mData; + protected BlockChecksumType mChecksumType; + + public Block(BlockChecksumType checksumType, byte[] data) throws UsbTransportException { + this.mChecksumType = checksumType; + this.mData = data; + + int checksumOffset = this.mData.length - mChecksumType.getLength(); + byte[] checksum = mChecksumType.computeChecksum(data, 0, checksumOffset); + if (!Arrays.areEqual(checksum, getEdc())) { + throw new UsbTransportException("TPDU CRC doesn't match"); + } + } + + protected Block(BlockChecksumType checksumType, byte nad, byte pcb, byte[] apdu) + throws UsbTransportException { + this.mChecksumType = checksumType; + if (apdu.length > MAX_PAYLOAD_LEN) { + throw new UsbTransportException("APDU is too long; should be split"); + } + this.mData = Arrays.concatenate( + new byte[]{nad, pcb, (byte) apdu.length}, + apdu, + new byte[mChecksumType.getLength()]); + + int checksumOffset = this.mData.length - mChecksumType.getLength(); + byte[] checksum = mChecksumType.computeChecksum(this.mData, 0, checksumOffset); + + System.arraycopy(checksum, 0, this.mData, checksumOffset, mChecksumType.getLength()); + } + + protected Block(Block baseBlock) { + this.mChecksumType = baseBlock.getChecksumType(); + this.mData = baseBlock.getRawData(); + } + + public byte getNad() { + return mData[OFFSET_NAD]; + } + + public byte getPcb() { + return mData[OFFSET_PCB]; + } + + public byte getLen() { + return mData[OFFSET_LEN]; + } + + public byte[] getEdc() { + return Arrays.copyOfRange(mData, mData.length - mChecksumType.getLength(), mChecksumType.getLength()); + } + + public BlockChecksumType getChecksumType() { + return mChecksumType; + } + + public byte[] getApdu() { + return Arrays.copyOfRange(mData, OFFSET_DATA, mData.length - mChecksumType.getLength()); + } + + public byte[] getRawData() { + return mData; + } + + @Override + public String toString() { + return Hex.toHexString(mData); + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/usb/tpdu/block/BlockFactory.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/usb/tpdu/block/BlockFactory.java new file mode 100644 index 000000000..c64f2315c --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/usb/tpdu/block/BlockFactory.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2016 Nikita Mikhailov + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.sufficientlysecure.keychain.securitytoken.usb.tpdu.block; + +import org.sufficientlysecure.keychain.securitytoken.usb.UsbTransportException; +import org.sufficientlysecure.keychain.securitytoken.usb.tpdu.BlockChecksumType; + +public class BlockFactory { + private BlockChecksumType mChecksumType = BlockChecksumType.LRC; + + public Block getBlockFromResponse(byte[] data) throws UsbTransportException { + final Block baseBlock = new Block(mChecksumType, data); + + if ((baseBlock.getPcb() & 0b10000000) == 0b00000000) { + return new IBlock(baseBlock); + } else if ((baseBlock.getPcb() & 0b11000000) == 0b11000000) { + return new SBlock(baseBlock); + } else if ((baseBlock.getPcb() & 0b11000000) == 0b10000000) { + return new RBlock(baseBlock); + } + + throw new UsbTransportException("TPDU Unknown block type"); + } + + public IBlock newIBlock(byte sequence, boolean chaining, byte[] apdu) throws UsbTransportException { + return new IBlock(mChecksumType, (byte) 0, sequence, chaining, apdu); + } + + public RBlock newRBlock(byte sequence) throws UsbTransportException { + return new RBlock(mChecksumType, (byte) 0, sequence); + } + + public void setChecksumType(final BlockChecksumType checksumType) { + mChecksumType = checksumType; + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/usb/tpdu/block/IBlock.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/usb/tpdu/block/IBlock.java new file mode 100644 index 000000000..e7e9f8b51 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/usb/tpdu/block/IBlock.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2016 Nikita Mikhailov + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.sufficientlysecure.keychain.securitytoken.usb.tpdu.block; + +import org.sufficientlysecure.keychain.securitytoken.usb.UsbTransportException; +import org.sufficientlysecure.keychain.securitytoken.usb.tpdu.BlockChecksumType; + +public class IBlock extends Block { + private static final byte BIT_SEQUENCE = 6; + private static final byte BIT_CHAINING = 5; + + public IBlock(final Block baseBlock) { + super(baseBlock); + } + + public IBlock(BlockChecksumType checksumType, byte nad, byte sequence, boolean chaining, + byte[] apdu) throws UsbTransportException { + super(checksumType, nad, + (byte) (((sequence & 1) << BIT_SEQUENCE) | (chaining ? 1 << BIT_CHAINING : 0)), + apdu); + } + + public byte getSequence() { + return (byte) ((getPcb() >> BIT_SEQUENCE) & 1); + } + + public boolean getChaining() { + return ((getPcb() >> BIT_CHAINING) & 1) != 0; + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/usb/tpdu/block/RBlock.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/usb/tpdu/block/RBlock.java new file mode 100644 index 000000000..f0262257e --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/usb/tpdu/block/RBlock.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2016 Nikita Mikhailov + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.sufficientlysecure.keychain.securitytoken.usb.tpdu.block; + +import android.support.annotation.NonNull; + +import org.sufficientlysecure.keychain.securitytoken.usb.UsbTransportException; +import org.sufficientlysecure.keychain.securitytoken.usb.tpdu.BlockChecksumType; + +public class RBlock extends Block { + private static final byte BIT_SEQUENCE = 5; + + public RBlock(Block baseBlock) throws UsbTransportException { + super(baseBlock); + if (getApdu().length != 0) { + throw new UsbTransportException("Data in R-block"); + } + } + + public RBlock(BlockChecksumType checksumType, byte nad, byte sequence) + throws UsbTransportException { + super(checksumType, nad, (byte) (0b10000000 | ((sequence & 1) << BIT_SEQUENCE)), new byte[0]); + } + + public RError getError() throws UsbTransportException { + return RError.from(getPcb()); + } + + public enum RError { + NO_ERROR(0), EDC_ERROR(1), OTHER_ERROR(2); + + private byte mLowBits; + + RError(int lowBits) { + mLowBits = (byte) lowBits; + } + + @NonNull + public static RError from(byte i) throws UsbTransportException { + for (final RError error : values()) { + if (error.mLowBits == (i & 0x3)) { + return error; + } + } + throw new UsbTransportException("Invalid R block error bits"); + } + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/t1/ChecksumType.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/usb/tpdu/block/SBlock.java similarity index 64% rename from OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/t1/ChecksumType.java rename to OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/usb/tpdu/block/SBlock.java index dbd8896fa..18a66f548 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/t1/ChecksumType.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/usb/tpdu/block/SBlock.java @@ -15,21 +15,10 @@ * along with this program. If not, see . */ -package org.sufficientlysecure.keychain.securitytoken.t1; +package org.sufficientlysecure.keychain.securitytoken.usb.tpdu.block; -public enum ChecksumType { - LRC, CRC; - - public byte[] computeChecksum(byte[] data) { - if (this == LRC) { - byte res = 0; - for (byte b : data) { - res ^= b; - } - return new byte[]{res}; - } else { - // Not needed for Nitrokey - return null; - } +public class SBlock extends Block { + public SBlock(Block baseBlock) { + super(baseBlock); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenActivity.java index 680613596..72216bd52 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenActivity.java @@ -47,7 +47,7 @@ import org.sufficientlysecure.keychain.securitytoken.NfcTransport; import org.sufficientlysecure.keychain.securitytoken.SecurityTokenHelper; import org.sufficientlysecure.keychain.securitytoken.Transport; import org.sufficientlysecure.keychain.util.UsbConnectionDispatcher; -import org.sufficientlysecure.keychain.securitytoken.UsbTransport; +import org.sufficientlysecure.keychain.securitytoken.usb.UsbTransport; import org.sufficientlysecure.keychain.ui.CreateKeyActivity; import org.sufficientlysecure.keychain.ui.PassphraseDialogActivity; import org.sufficientlysecure.keychain.ui.ViewKeyActivity;