SecurityToken: add support for T=1 TPDU level of exchange
This commit is contained in:
@@ -1,145 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2016 Nikita Mikhailov <nikita.s.mikhailov@gmail.com>
|
|
||||||
*
|
|
||||||
* 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 <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
package org.sufficientlysecure.keychain.securitytoken.t1;
|
|
||||||
|
|
||||||
public class RBlock extends Frame {
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,169 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016 Nikita Mikhailov <nikita.s.mikhailov@gmail.com>
|
||||||
|
*
|
||||||
|
* 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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016 Nikita Mikhailov <nikita.s.mikhailov@gmail.com>
|
||||||
|
*
|
||||||
|
* 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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.sufficientlysecure.keychain.securitytoken.usb;
|
||||||
|
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
|
||||||
|
public interface CcidTransportProtocol {
|
||||||
|
byte[] transceive(@NonNull byte[] apdu) throws UsbTransportException;
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016 Nikita Mikhailov <nikita.s.mikhailov@gmail.com>
|
||||||
|
*
|
||||||
|
* 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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,9 +15,8 @@
|
|||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
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.UsbConstants;
|
||||||
import android.hardware.usb.UsbDevice;
|
import android.hardware.usb.UsbDevice;
|
||||||
import android.hardware.usb.UsbDeviceConnection;
|
import android.hardware.usb.UsbDeviceConnection;
|
||||||
@@ -28,10 +27,9 @@ import android.support.annotation.NonNull;
|
|||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.util.Pair;
|
import android.util.Pair;
|
||||||
|
|
||||||
import org.bouncycastle.util.Arrays;
|
|
||||||
import org.bouncycastle.util.encoders.Hex;
|
|
||||||
import org.sufficientlysecure.keychain.Constants;
|
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 org.sufficientlysecure.keychain.util.Log;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -45,7 +43,15 @@ import java.nio.ByteOrder;
|
|||||||
*/
|
*/
|
||||||
public class UsbTransport implements Transport {
|
public class UsbTransport implements Transport {
|
||||||
private static final int USB_CLASS_SMARTCARD = 11;
|
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 UsbManager mUsbManager;
|
||||||
private final UsbDevice mUsbDevice;
|
private final UsbDevice mUsbDevice;
|
||||||
@@ -53,34 +59,14 @@ public class UsbTransport implements Transport {
|
|||||||
private UsbEndpoint mBulkIn;
|
private UsbEndpoint mBulkIn;
|
||||||
private UsbEndpoint mBulkOut;
|
private UsbEndpoint mBulkOut;
|
||||||
private UsbDeviceConnection mConnection;
|
private UsbDeviceConnection mConnection;
|
||||||
private byte mCounter;
|
private CcidTransceiver mTransceiver;
|
||||||
|
private CcidTransportProtocol mProtocol;
|
||||||
|
|
||||||
public UsbTransport(UsbDevice usbDevice, UsbManager usbManager) {
|
public UsbTransport(UsbDevice usbDevice, UsbManager usbManager) {
|
||||||
mUsbDevice = usbDevice;
|
mUsbDevice = usbDevice;
|
||||||
mUsbManager = usbManager;
|
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
|
* Get first class 11 (Chip/Smartcard) interface of the device
|
||||||
*
|
*
|
||||||
@@ -162,7 +148,6 @@ public class UsbTransport implements Transport {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void connect() throws IOException {
|
public void connect() throws IOException {
|
||||||
mCounter = 0;
|
|
||||||
mUsbInterface = getSmartCardInterface(mUsbDevice);
|
mUsbInterface = getSmartCardInterface(mUsbDevice);
|
||||||
if (mUsbInterface == null) {
|
if (mUsbInterface == null) {
|
||||||
// Shouldn't happen as we whitelist only class 11 devices
|
// 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");
|
throw new UsbTransportException("USB error - failed to claim interface");
|
||||||
}
|
}
|
||||||
|
|
||||||
setIccPower(true);
|
mTransceiver = new CcidTransceiver(mConnection, mBulkIn, mBulkOut);
|
||||||
try {
|
|
||||||
Thread.sleep(1000);
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
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="
|
if ((dwFeatures & MASK_TPDU) != 0) {
|
||||||
+ Hex.toHexString(atrPacket));
|
mProtocol = new T1TpduProtocol(mTransceiver);
|
||||||
|
} else if (((dwFeatures & MASK_SHORT_APDU) != 0) || ((dwFeatures & MASK_EXTENDED_APDU) != 0)) {
|
||||||
new T1TPDUProtocol(this).pps();
|
mProtocol = new T1ShortApduProtocol(mTransceiver);
|
||||||
|
} else {
|
||||||
|
throw new UsbTransportException("Character level exchange is not supported");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -208,100 +209,7 @@ public class UsbTransport implements Transport {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public byte[] transceive(byte[] data) throws UsbTransportException {
|
public byte[] transceive(byte[] data) throws UsbTransportException {
|
||||||
final T1TPDUProtocol t1TPDUProtocol = new T1TPDUProtocol(this);
|
return mProtocol.transceive(data);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.sufficientlysecure.keychain.securitytoken;
|
package org.sufficientlysecure.keychain.securitytoken.usb;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016 Nikita Mikhailov <nikita.s.mikhailov@gmail.com>
|
||||||
|
*
|
||||||
|
* 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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,9 +15,9 @@
|
|||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
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 {
|
public enum FrameType {
|
||||||
I_BLOCK(0b00000000, 0b10000000, 6, true), // Information
|
I_BLOCK(0b00000000, 0b10000000, 6, true), // Information
|
||||||
@@ -0,0 +1,148 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016 Nikita Mikhailov <nikita.s.mikhailov@gmail.com>
|
||||||
|
*
|
||||||
|
* 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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016 Nikita Mikhailov <nikita.s.mikhailov@gmail.com>
|
||||||
|
*
|
||||||
|
* 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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016 Nikita Mikhailov <nikita.s.mikhailov@gmail.com>
|
||||||
|
*
|
||||||
|
* 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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016 Nikita Mikhailov <nikita.s.mikhailov@gmail.com>
|
||||||
|
*
|
||||||
|
* 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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016 Nikita Mikhailov <nikita.s.mikhailov@gmail.com>
|
||||||
|
*
|
||||||
|
* 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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,21 +15,10 @@
|
|||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.sufficientlysecure.keychain.securitytoken.t1;
|
package org.sufficientlysecure.keychain.securitytoken.usb.tpdu.block;
|
||||||
|
|
||||||
public enum ChecksumType {
|
public class SBlock extends Block {
|
||||||
LRC, CRC;
|
public SBlock(Block baseBlock) {
|
||||||
|
super(baseBlock);
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -47,7 +47,7 @@ import org.sufficientlysecure.keychain.securitytoken.NfcTransport;
|
|||||||
import org.sufficientlysecure.keychain.securitytoken.SecurityTokenHelper;
|
import org.sufficientlysecure.keychain.securitytoken.SecurityTokenHelper;
|
||||||
import org.sufficientlysecure.keychain.securitytoken.Transport;
|
import org.sufficientlysecure.keychain.securitytoken.Transport;
|
||||||
import org.sufficientlysecure.keychain.util.UsbConnectionDispatcher;
|
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.CreateKeyActivity;
|
||||||
import org.sufficientlysecure.keychain.ui.PassphraseDialogActivity;
|
import org.sufficientlysecure.keychain.ui.PassphraseDialogActivity;
|
||||||
import org.sufficientlysecure.keychain.ui.ViewKeyActivity;
|
import org.sufficientlysecure.keychain.ui.ViewKeyActivity;
|
||||||
|
|||||||
Reference in New Issue
Block a user