add initial USB connector
This commit is contained in:
parent
067204c7db
commit
527efc7a4e
|
@ -24,7 +24,7 @@ public class NFCCardManager extends Thread implements NfcAdapter.ReaderCallback
|
||||||
private int loopSleepMS;
|
private int loopSleepMS;
|
||||||
|
|
||||||
static {
|
static {
|
||||||
Crypto.addSpongyCastleProvider();
|
Crypto.addBouncyCastleProvider();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -3,6 +3,7 @@ apply plugin: 'maven'
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compile project(':lib')
|
compile project(':lib')
|
||||||
|
compile 'org.hid4java:hid4java:0.5.0'
|
||||||
}
|
}
|
||||||
|
|
||||||
task sourcesJar(type: Jar, dependsOn: classes) {
|
task sourcesJar(type: Jar, dependsOn: classes) {
|
||||||
|
|
|
@ -0,0 +1,177 @@
|
||||||
|
package im.status.keycard.desktop;
|
||||||
|
|
||||||
|
import im.status.keycard.io.APDUCommand;
|
||||||
|
import im.status.keycard.io.APDUResponse;
|
||||||
|
import im.status.keycard.io.CardChannel;
|
||||||
|
import org.hid4java.HidDevice;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
public class LedgerUSBChannel implements CardChannel {
|
||||||
|
private static final int HID_BUFFER_SIZE = 64;
|
||||||
|
private static final int LEDGER_DEFAULT_CHANNEL = 1;
|
||||||
|
private static final int TAG_APDU = 0x05;
|
||||||
|
|
||||||
|
private HidDevice hidDevice;
|
||||||
|
|
||||||
|
public LedgerUSBChannel(HidDevice hidDevice) {
|
||||||
|
this.hidDevice = hidDevice;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public APDUResponse send(APDUCommand cmd) throws IOException {
|
||||||
|
ByteArrayOutputStream response = new ByteArrayOutputStream();
|
||||||
|
|
||||||
|
int offset = 0;
|
||||||
|
|
||||||
|
byte[] command = wrapCommandAPDU(cmd.serialize());
|
||||||
|
|
||||||
|
byte[] chunk = new byte[HID_BUFFER_SIZE];
|
||||||
|
while(offset != command.length) {
|
||||||
|
int blockSize = (command.length - offset > HID_BUFFER_SIZE ? HID_BUFFER_SIZE : command.length - offset);
|
||||||
|
System.arraycopy(command, offset, chunk, 0, blockSize);
|
||||||
|
|
||||||
|
if (hidDevice.write(command, blockSize, (byte) 0x00) < 0) {
|
||||||
|
throw new IOException("Write failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
offset += blockSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] responseData = null;
|
||||||
|
|
||||||
|
while ((responseData = unwrapResponseAPDU(response.toByteArray())) == null) {
|
||||||
|
if (hidDevice.read(chunk, 500) < 0) {
|
||||||
|
throw new IOException("Read failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
response.write(chunk, 0, HID_BUFFER_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new APDUResponse(responseData);
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] unwrapResponseAPDU(byte[] data) throws IOException {
|
||||||
|
ByteArrayOutputStream response = new ByteArrayOutputStream();
|
||||||
|
int offset = 0;
|
||||||
|
int responseLength;
|
||||||
|
int sequenceIdx = 0;
|
||||||
|
|
||||||
|
if ((data == null) || (data.length < 7 + 5)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data[offset++] != (LEDGER_DEFAULT_CHANNEL >> 8)) {
|
||||||
|
throw new IOException("Invalid channel");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data[offset++] != (LEDGER_DEFAULT_CHANNEL & 0xff)) {
|
||||||
|
throw new IOException("Invalid channel");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data[offset++] != TAG_APDU) {
|
||||||
|
throw new IOException("Invalid tag");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data[offset++] != 0x00) {
|
||||||
|
throw new IOException("Invalid sequence");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data[offset++] != 0x00) {
|
||||||
|
throw new IOException("Invalid sequence");
|
||||||
|
}
|
||||||
|
|
||||||
|
responseLength = ((data[offset++] & 0xff) << 8);
|
||||||
|
responseLength |= (data[offset++] & 0xff);
|
||||||
|
|
||||||
|
if (data.length < 7 + responseLength) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
int blockSize = (responseLength > HID_BUFFER_SIZE - 7 ? HID_BUFFER_SIZE - 7 : responseLength);
|
||||||
|
response.write(data, offset, blockSize);
|
||||||
|
offset += blockSize;
|
||||||
|
|
||||||
|
while (response.size() != responseLength) {
|
||||||
|
sequenceIdx++;
|
||||||
|
|
||||||
|
if (offset == data.length) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data[offset++] != (LEDGER_DEFAULT_CHANNEL >> 8)) {
|
||||||
|
throw new IOException("Invalid channel");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data[offset++] != (LEDGER_DEFAULT_CHANNEL & 0xff)) {
|
||||||
|
throw new IOException("Invalid channel");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data[offset++] != TAG_APDU) {
|
||||||
|
throw new IOException("Invalid tag");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data[offset++] != (sequenceIdx >> 8)) {
|
||||||
|
throw new IOException("Invalid sequence");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data[offset++] != (sequenceIdx & 0xff)) {
|
||||||
|
throw new IOException("Invalid sequence");
|
||||||
|
}
|
||||||
|
|
||||||
|
blockSize = (responseLength - response.size() > HID_BUFFER_SIZE - 5 ? HID_BUFFER_SIZE - 5 : responseLength - response.size());
|
||||||
|
if (blockSize > data.length - offset) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
response.write(data, offset, blockSize);
|
||||||
|
offset += blockSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.toByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] wrapCommandAPDU(byte[] command) {
|
||||||
|
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
||||||
|
|
||||||
|
int sequenceIdx = 0;
|
||||||
|
int offset = 0;
|
||||||
|
output.write(LEDGER_DEFAULT_CHANNEL >> 8);
|
||||||
|
output.write(LEDGER_DEFAULT_CHANNEL);
|
||||||
|
output.write(TAG_APDU);
|
||||||
|
output.write(sequenceIdx >> 8);
|
||||||
|
output.write(sequenceIdx);
|
||||||
|
sequenceIdx++;
|
||||||
|
output.write(command.length >> 8);
|
||||||
|
output.write(command.length);
|
||||||
|
int blockSize = (command.length > HID_BUFFER_SIZE - 7 ? HID_BUFFER_SIZE - 7 : command.length);
|
||||||
|
output.write(command, offset, blockSize);
|
||||||
|
offset += blockSize;
|
||||||
|
|
||||||
|
while (offset != command.length) {
|
||||||
|
output.write(LEDGER_DEFAULT_CHANNEL >> 8);
|
||||||
|
output.write(LEDGER_DEFAULT_CHANNEL);
|
||||||
|
output.write(TAG_APDU);
|
||||||
|
output.write(sequenceIdx >> 8);
|
||||||
|
output.write(sequenceIdx);
|
||||||
|
sequenceIdx++;
|
||||||
|
blockSize = (command.length - offset > HID_BUFFER_SIZE - 5 ? HID_BUFFER_SIZE - 5 : command.length - offset);
|
||||||
|
output.write(command, offset, blockSize);
|
||||||
|
offset += blockSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((output.size() % HID_BUFFER_SIZE) != 0) {
|
||||||
|
byte[] padding = new byte[HID_BUFFER_SIZE - (output.size() % HID_BUFFER_SIZE)];
|
||||||
|
output.write(padding, 0, padding.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
return output.toByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isConnected() {
|
||||||
|
return hidDevice.isOpen();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,69 @@
|
||||||
|
package im.status.keycard.desktop;
|
||||||
|
|
||||||
|
import im.status.keycard.globalplatform.Crypto;
|
||||||
|
import im.status.keycard.io.CardListener;
|
||||||
|
import org.hid4java.*;
|
||||||
|
import org.hid4java.event.HidServicesEvent;
|
||||||
|
|
||||||
|
public class LedgerUSBManager implements HidServicesListener {
|
||||||
|
static {
|
||||||
|
Crypto.addBouncyCastleProvider();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final int VID = 0x2c97;
|
||||||
|
private static final int PID = 0x0001;
|
||||||
|
|
||||||
|
private HidServices hidServices;
|
||||||
|
private CardListener listener;
|
||||||
|
|
||||||
|
public LedgerUSBManager(CardListener listener) {
|
||||||
|
this.listener = listener;
|
||||||
|
|
||||||
|
HidServicesSpecification hidServicesSpecification = new HidServicesSpecification();
|
||||||
|
hidServicesSpecification.setAutoShutdown(true);
|
||||||
|
hidServicesSpecification.setScanInterval(500);
|
||||||
|
hidServicesSpecification.setPauseInterval(5000);
|
||||||
|
hidServicesSpecification.setScanMode(ScanMode.SCAN_AT_FIXED_INTERVAL_WITH_PAUSE_AFTER_WRITE);
|
||||||
|
|
||||||
|
hidServices = HidManager.getHidServices(hidServicesSpecification);
|
||||||
|
hidServices.addHidServicesListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void start() {
|
||||||
|
hidServices.start();
|
||||||
|
|
||||||
|
HidDevice hidDevice = hidServices.getHidDevice(VID, PID, null);
|
||||||
|
|
||||||
|
if (hidDevice != null) {
|
||||||
|
listener.onConnected(new LedgerUSBChannel(hidDevice));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stop() {
|
||||||
|
hidServices.shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void hidDeviceAttached(HidServicesEvent event) {
|
||||||
|
HidDevice hidDevice = event.getHidDevice();
|
||||||
|
|
||||||
|
if (hidDevice.isVidPidSerial(VID, PID, null)) {
|
||||||
|
listener.onConnected(new LedgerUSBChannel(hidDevice));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void hidDeviceDetached(HidServicesEvent event) {
|
||||||
|
hidFailure(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void hidFailure(HidServicesEvent event) {
|
||||||
|
HidDevice hidDevice = event.getHidDevice();
|
||||||
|
|
||||||
|
if (hidDevice.isVidPidSerial(VID, PID, null)) {
|
||||||
|
listener.onDisconnected();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,7 +15,7 @@ import java.io.IOException;
|
||||||
*/
|
*/
|
||||||
public class PCSCCardChannel implements CardChannel {
|
public class PCSCCardChannel implements CardChannel {
|
||||||
static {
|
static {
|
||||||
Crypto.addSpongyCastleProvider();
|
Crypto.addBouncyCastleProvider();
|
||||||
}
|
}
|
||||||
|
|
||||||
private javax.smartcardio.CardChannel cardChannel;
|
private javax.smartcardio.CardChannel cardChannel;
|
||||||
|
|
|
@ -21,13 +21,13 @@ public class Crypto {
|
||||||
public static long PIN_BOUND = 999999L;
|
public static long PIN_BOUND = 999999L;
|
||||||
public static long PUK_BOUND = 999999999999L;
|
public static long PUK_BOUND = 999999999999L;
|
||||||
|
|
||||||
private static boolean spongyCastleLoaded = false;
|
private static boolean bouncyCastleLoaded = false;
|
||||||
|
|
||||||
public static void addSpongyCastleProvider() {
|
public static void addBouncyCastleProvider() {
|
||||||
if (!spongyCastleLoaded) {
|
if (!bouncyCastleLoaded) {
|
||||||
Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME);
|
Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME);
|
||||||
Security.addProvider(new BouncyCastleProvider());
|
Security.addProvider(new BouncyCastleProvider());
|
||||||
spongyCastleLoaded = true;
|
bouncyCastleLoaded = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue