wip: t1 tpdu loe
This commit is contained in:
@@ -17,6 +17,7 @@
|
|||||||
|
|
||||||
package org.sufficientlysecure.keychain.securitytoken;
|
package org.sufficientlysecure.keychain.securitytoken;
|
||||||
|
|
||||||
|
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;
|
||||||
@@ -30,6 +31,7 @@ import android.util.Pair;
|
|||||||
import org.bouncycastle.util.Arrays;
|
import org.bouncycastle.util.Arrays;
|
||||||
import org.bouncycastle.util.encoders.Hex;
|
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.util.Log;
|
import org.sufficientlysecure.keychain.util.Log;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -77,11 +79,6 @@ public class UsbTransport implements Transport {
|
|||||||
};
|
};
|
||||||
|
|
||||||
sendRaw(iccPowerCommand);
|
sendRaw(iccPowerCommand);
|
||||||
byte[] bytes;
|
|
||||||
do {
|
|
||||||
bytes = receive();
|
|
||||||
} while (isDataBlockNotReady(bytes));
|
|
||||||
checkDataBlockResponse(bytes);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -190,7 +187,17 @@ public class UsbTransport implements Transport {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setIccPower(true);
|
setIccPower(true);
|
||||||
Log.d(Constants.TAG, "Usb transport connected");
|
try {
|
||||||
|
Thread.sleep(1000);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
|
||||||
|
}
|
||||||
|
byte[] atrPacket = receiveRaw();
|
||||||
|
|
||||||
|
Log.d(Constants.TAG, "Usb transport connected, ATR="
|
||||||
|
+ Hex.toHexString(atrPacket));
|
||||||
|
|
||||||
|
new T1TPDUProtocol(this).pps();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -201,14 +208,23 @@ public class UsbTransport implements Transport {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public byte[] transceive(byte[] data) throws UsbTransportException {
|
public byte[] transceive(byte[] data) throws UsbTransportException {
|
||||||
sendXfrBlock(data);
|
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;
|
byte[] bytes;
|
||||||
do {
|
do {
|
||||||
bytes = receive();
|
bytes = receive();
|
||||||
} while (isDataBlockNotReady(bytes));
|
} while (isDataBlockNotReady(bytes));
|
||||||
|
|
||||||
checkDataBlockResponse(bytes);
|
checkDataBlockResponse(bytes);
|
||||||
// Discard header
|
|
||||||
return Arrays.copyOfRange(bytes, 10, bytes.length);
|
return Arrays.copyOfRange(bytes, 10, bytes.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -218,7 +234,7 @@ public class UsbTransport implements Transport {
|
|||||||
* @param payload payload to transmit
|
* @param payload payload to transmit
|
||||||
* @throws UsbTransportException
|
* @throws UsbTransportException
|
||||||
*/
|
*/
|
||||||
private void sendXfrBlock(byte[] payload) throws UsbTransportException {
|
public void sendXfrBlock(byte[] payload) throws UsbTransportException {
|
||||||
int l = payload.length;
|
int l = payload.length;
|
||||||
byte[] data = Arrays.concatenate(new byte[]{
|
byte[] data = Arrays.concatenate(new byte[]{
|
||||||
0x6f,
|
0x6f,
|
||||||
@@ -237,11 +253,16 @@ public class UsbTransport implements Transport {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] receive() throws UsbTransportException {
|
public byte[] receive() throws UsbTransportException {
|
||||||
byte[] buffer = new byte[mBulkIn.getMaxPacketSize()];
|
byte[] buffer = new byte[mBulkIn.getMaxPacketSize()];
|
||||||
byte[] result = null;
|
byte[] result = null;
|
||||||
int readBytes = 0, totalBytes = 0;
|
int readBytes = 0, totalBytes = 0;
|
||||||
|
|
||||||
|
/*try {
|
||||||
|
Thread.sleep(1000);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
|
||||||
|
}*/
|
||||||
do {
|
do {
|
||||||
int res = mConnection.bulkTransfer(mBulkIn, buffer, buffer.length, TIMEOUT);
|
int res = mConnection.bulkTransfer(mBulkIn, buffer, buffer.length, TIMEOUT);
|
||||||
if (res < 0) {
|
if (res < 0) {
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,145 @@
|
|||||||
|
/*
|
||||||
|
* 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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
/*
|
||||||
|
* 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 org.sufficientlysecure.keychain.securitytoken.UsbTransportException;
|
||||||
|
|
||||||
|
public enum FrameType {
|
||||||
|
I_BLOCK(0b00000000, 0b10000000, 6, true), // Information
|
||||||
|
R_BLOCK(0b10000000, 0b11000000, 4, false), // Receipt ack
|
||||||
|
S_BLOCK(0b11000000, 0b11000000, -1, false); // System
|
||||||
|
|
||||||
|
private byte mValue;
|
||||||
|
private byte mMask;
|
||||||
|
private int mSequenceBit;
|
||||||
|
private boolean mChainingSupported;
|
||||||
|
|
||||||
|
FrameType(int value, int mask, int sequenceBit, boolean chaining) {
|
||||||
|
// Accept ints just to avoid cast in creation
|
||||||
|
this.mValue = (byte) value;
|
||||||
|
this.mMask = (byte) mask;
|
||||||
|
this.mSequenceBit = sequenceBit;
|
||||||
|
this.mChainingSupported = chaining;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static FrameType fromPCB(byte pcb) throws UsbTransportException {
|
||||||
|
for (final FrameType frameType : values()) {
|
||||||
|
if ((frameType.mMask & pcb) == frameType.mValue) {
|
||||||
|
return frameType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new UsbTransportException("Invalid PCB byte");
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getSequenceBit() {
|
||||||
|
return mSequenceBit;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isChainingSupported() {
|
||||||
|
return mChainingSupported;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package org.sufficientlysecure.keychain.securitytoken.t1;
|
||||||
|
|
||||||
|
public class RBlock extends Frame {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user