Ignore leading data before IccPowerOn operation in CcidTransceiver

This commit is contained in:
Vincent Breitmoser
2017-10-08 01:05:43 +02:00
parent 7a24f4c237
commit 31da4c450d

View File

@@ -17,20 +17,23 @@
package org.sufficientlysecure.keychain.securitytoken.usb; package org.sufficientlysecure.keychain.securitytoken.usb;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import android.hardware.usb.UsbDeviceConnection; import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbEndpoint; import android.hardware.usb.UsbEndpoint;
import android.os.SystemClock; import android.os.SystemClock;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.annotation.WorkerThread;
import android.util.Log; import android.util.Log;
import com.google.auto.value.AutoValue; import com.google.auto.value.AutoValue;
import org.bouncycastle.util.Arrays; import org.bouncycastle.util.Arrays;
import org.bouncycastle.util.encoders.Hex;
import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
public class CcidTransceiver { public class CcidTransceiver {
private static final int CCID_HEADER_LENGTH = 10; private static final int CCID_HEADER_LENGTH = 10;
@@ -45,18 +48,24 @@ public class CcidTransceiver {
private static final int ICC_STATUS_SUCCESS = 0; private static final int ICC_STATUS_SUCCESS = 0;
private static final int TIMEOUT = 20 * 1000; // 20s private static final int DEVICE_COMMUNICATE_TIMEOUT_MILLIS = 5000;
private static final int DEVICE_SKIP_TIMEOUT_MILLIS = 100;
private final UsbDeviceConnection usbConnection; private final UsbDeviceConnection usbConnection;
private final UsbEndpoint usbBulkIn; private final UsbEndpoint usbBulkIn;
private final UsbEndpoint usbBulkOut; private final UsbEndpoint usbBulkOut;
private final byte[] inputBuffer;
private byte currentSequenceNumber; private byte currentSequenceNumber;
CcidTransceiver(UsbDeviceConnection connection, UsbEndpoint bulkIn, UsbEndpoint bulkOut) { CcidTransceiver(UsbDeviceConnection connection, UsbEndpoint bulkIn, UsbEndpoint bulkOut) {
usbConnection = connection; usbConnection = connection;
usbBulkIn = bulkIn; usbBulkIn = bulkIn;
usbBulkOut = bulkOut; usbBulkOut = bulkOut;
inputBuffer = new byte[usbBulkIn.getMaxPacketSize()];
} }
/** /**
@@ -64,7 +73,12 @@ public class CcidTransceiver {
* Spec: 6.1.1 PC_to_RDR_IccPowerOn * Spec: 6.1.1 PC_to_RDR_IccPowerOn
*/ */
@NonNull @NonNull
public CcidDataBlock iccPowerOn() throws UsbTransportException { @WorkerThread
public synchronized CcidDataBlock iccPowerOn() throws UsbTransportException {
long startTime = SystemClock.elapsedRealtime();
skipAvailableInput();
byte sequenceNumber = currentSequenceNumber++; byte sequenceNumber = currentSequenceNumber++;
final byte[] iccPowerCommand = { final byte[] iccPowerCommand = {
MESSAGE_TYPE_PC_TO_RDR_ICC_POWER_ON, MESSAGE_TYPE_PC_TO_RDR_ICC_POWER_ON,
@@ -76,22 +90,12 @@ public class CcidTransceiver {
}; };
sendRaw(iccPowerCommand, 0, iccPowerCommand.length); sendRaw(iccPowerCommand, 0, iccPowerCommand.length);
CcidDataBlock response = receiveDataBlock(sequenceNumber);
long startTime = System.currentTimeMillis(); long elapsedTime = SystemClock.elapsedRealtime() - startTime;
while (true) { Log.d(Constants.TAG, "USB IccPowerOn call took " + elapsedTime + "ms");
try {
return receiveDataBlock(sequenceNumber);
} catch (Exception e) {
Log.e(Constants.TAG, "Error waiting for device power on", e);
// Try more startTime
if (System.currentTimeMillis() - startTime > TIMEOUT) {
break;
}
}
SystemClock.sleep(100);
}
throw new UsbTransportException("Couldn't power up Security Token"); return response;
} }
/** /**
@@ -100,7 +104,10 @@ public class CcidTransceiver {
* *
* @param payload payload to transmit * @param payload payload to transmit
*/ */
public CcidDataBlock sendXfrBlock(byte[] payload) throws UsbTransportException { @WorkerThread
public synchronized CcidDataBlock sendXfrBlock(byte[] payload) throws UsbTransportException {
long startTime = SystemClock.elapsedRealtime();
int l = payload.length; int l = payload.length;
byte sequenceNumber = currentSequenceNumber++; byte sequenceNumber = currentSequenceNumber++;
byte[] headerData = { byte[] headerData = {
@@ -120,7 +127,24 @@ public class CcidTransceiver {
sentBytes += bytesToSend; sentBytes += bytesToSend;
} }
return receiveDataBlock(sequenceNumber); CcidDataBlock ccidDataBlock = receiveDataBlock(sequenceNumber);
long elapsedTime = SystemClock.elapsedRealtime() - startTime;
Log.d(Constants.TAG, "USB XferBlock call took " + elapsedTime + "ms");
return ccidDataBlock;
}
private void skipAvailableInput() {
int ignoredBytes;
do {
ignoredBytes = usbConnection.bulkTransfer(
usbBulkIn, inputBuffer, inputBuffer.length, DEVICE_SKIP_TIMEOUT_MILLIS);
if (ignoredBytes > 0) {
Log.e(Constants.TAG,
"Skipped " + ignoredBytes + " bytes: " + Hex.toHexString(inputBuffer, 0, ignoredBytes));
}
} while (ignoredBytes > 0);
} }
private CcidDataBlock receiveDataBlock(byte expectedSequenceNumber) throws UsbTransportException { private CcidDataBlock receiveDataBlock(byte expectedSequenceNumber) throws UsbTransportException {
@@ -137,17 +161,21 @@ public class CcidTransceiver {
} }
private CcidDataBlock receiveDataBlockImmediate(byte expectedSequenceNumber) throws UsbTransportException { private CcidDataBlock receiveDataBlockImmediate(byte expectedSequenceNumber) throws UsbTransportException {
byte[] buffer = new byte[usbBulkIn.getMaxPacketSize()]; int readBytes = usbConnection.bulkTransfer(usbBulkIn, inputBuffer, inputBuffer.length, DEVICE_COMMUNICATE_TIMEOUT_MILLIS);
int readBytes = usbConnection.bulkTransfer(usbBulkIn, buffer, buffer.length, TIMEOUT);
if (readBytes < CCID_HEADER_LENGTH) { if (readBytes < CCID_HEADER_LENGTH) {
throw new UsbTransportException("USB-CCID error - failed to receive CCID header"); throw new UsbTransportException("USB-CCID error - failed to receive CCID header");
} }
if (buffer[0] != (byte) MESSAGE_TYPE_RDR_TO_PC_DATA_BLOCK) { if (inputBuffer[0] != (byte) MESSAGE_TYPE_RDR_TO_PC_DATA_BLOCK) {
throw new UsbTransportException("USB-CCID error - bad CCID header type " + buffer[0]); if (expectedSequenceNumber != inputBuffer[6]) {
throw new UsbTransportException("USB-CCID error - bad CCID header, type " + inputBuffer[0] + " (expected " +
MESSAGE_TYPE_RDR_TO_PC_DATA_BLOCK + "), sequence number " + inputBuffer[6] + " (expected " +
expectedSequenceNumber + ")");
}
throw new UsbTransportException("USB-CCID error - bad CCID header type " + inputBuffer[0]);
} }
CcidDataBlock result = CcidDataBlock.parseHeaderFromBytes(buffer); CcidDataBlock result = CcidDataBlock.parseHeaderFromBytes(inputBuffer);
if (expectedSequenceNumber != result.getSeq()) { if (expectedSequenceNumber != result.getSeq()) {
throw new UsbTransportException("USB-CCID error - expected sequence number " + throw new UsbTransportException("USB-CCID error - expected sequence number " +
@@ -156,14 +184,14 @@ public class CcidTransceiver {
byte[] dataBuffer = new byte[result.getDataLength()]; byte[] dataBuffer = new byte[result.getDataLength()];
int bufferedBytes = readBytes - CCID_HEADER_LENGTH; int bufferedBytes = readBytes - CCID_HEADER_LENGTH;
System.arraycopy(buffer, CCID_HEADER_LENGTH, dataBuffer, 0, bufferedBytes); System.arraycopy(inputBuffer, CCID_HEADER_LENGTH, dataBuffer, 0, bufferedBytes);
while (bufferedBytes < dataBuffer.length) { while (bufferedBytes < dataBuffer.length) {
readBytes = usbConnection.bulkTransfer(usbBulkIn, buffer, buffer.length, TIMEOUT); readBytes = usbConnection.bulkTransfer(usbBulkIn, inputBuffer, inputBuffer.length, DEVICE_COMMUNICATE_TIMEOUT_MILLIS);
if (readBytes < 0) { if (readBytes < 0) {
throw new UsbTransportException("USB error - failed reading response data! Header: " + result); throw new UsbTransportException("USB error - failed reading response data! Header: " + result);
} }
System.arraycopy(buffer, 0, dataBuffer, bufferedBytes, readBytes); System.arraycopy(inputBuffer, 0, dataBuffer, bufferedBytes, readBytes);
bufferedBytes += readBytes; bufferedBytes += readBytes;
} }
@@ -173,12 +201,11 @@ public class CcidTransceiver {
private void sendRaw(byte[] data, int offset, int length) throws UsbTransportException { private void sendRaw(byte[] data, int offset, int length) throws UsbTransportException {
int tr1; int tr1;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR2) { if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR2) {
tr1 = usbConnection.bulkTransfer(usbBulkOut, data, offset, length, TIMEOUT); tr1 = usbConnection.bulkTransfer(usbBulkOut, data, offset, length, DEVICE_COMMUNICATE_TIMEOUT_MILLIS);
} else { } else {
byte[] dataToSend = Arrays.copyOfRange(data, offset, offset+length); byte[] dataToSend = Arrays.copyOfRange(data, offset, offset+length);
tr1 = usbConnection.bulkTransfer(usbBulkOut, dataToSend, dataToSend.length, TIMEOUT); tr1 = usbConnection.bulkTransfer(usbBulkOut, dataToSend, dataToSend.length, DEVICE_COMMUNICATE_TIMEOUT_MILLIS);
} }
if (tr1 != length) { if (tr1 != length) {