use ACardEmulator as nfc backend for pcsc-relay
This feature is experimental and disabled by default At ACardEmulator's compile time it is required: - to add the correct application identifier to aid_list.xml - enable the connection to pcsc-relay by setting useVPCD
This commit is contained in:
@@ -2,6 +2,8 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="com.vsmartcard.acardemulator" >
|
package="com.vsmartcard.acardemulator" >
|
||||||
|
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
<uses-permission android:name="android.permission.NFC" />
|
<uses-permission android:name="android.permission.NFC" />
|
||||||
<uses-feature android:name="android.hardware.nfc" android:required="true" />
|
<uses-feature android:name="android.hardware.nfc" android:required="true" />
|
||||||
|
|
||||||
|
|||||||
@@ -27,8 +27,6 @@ import android.preference.PreferenceManager;
|
|||||||
|
|
||||||
import com.licel.jcardsim.base.Simulator;
|
import com.licel.jcardsim.base.Simulator;
|
||||||
|
|
||||||
import net.pwendland.javacard.pki.isoapplet.FileNotFoundException;
|
|
||||||
|
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|||||||
@@ -25,7 +25,9 @@ import android.content.Intent;
|
|||||||
import android.content.IntentFilter;
|
import android.content.IntentFilter;
|
||||||
import android.nfc.cardemulation.HostApduService;
|
import android.nfc.cardemulation.HostApduService;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.os.StrictMode;
|
||||||
import android.support.v4.content.LocalBroadcastManager;
|
import android.support.v4.content.LocalBroadcastManager;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
import com.licel.jcardsim.samples.HelloWorldApplet;
|
import com.licel.jcardsim.samples.HelloWorldApplet;
|
||||||
import com.licel.jcardsim.smartcardio.CardSimulator;
|
import com.licel.jcardsim.smartcardio.CardSimulator;
|
||||||
@@ -33,6 +35,12 @@ import com.licel.jcardsim.utils.AIDUtil;
|
|||||||
|
|
||||||
import net.pwendland.javacard.pki.isoapplet.IsoApplet;
|
import net.pwendland.javacard.pki.isoapplet.IsoApplet;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.Socket;
|
||||||
|
|
||||||
import openpgpcard.OpenPGPApplet;
|
import openpgpcard.OpenPGPApplet;
|
||||||
import pkgYkneoOath.YkneoOath;
|
import pkgYkneoOath.YkneoOath;
|
||||||
|
|
||||||
@@ -44,6 +52,13 @@ public class SimulatorService extends HostApduService {
|
|||||||
public static final String EXTRA_ERROR = "MSG_ERROR";
|
public static final String EXTRA_ERROR = "MSG_ERROR";
|
||||||
public static final String EXTRA_DESELECT = "MSG_DESELECT";
|
public static final String EXTRA_DESELECT = "MSG_DESELECT";
|
||||||
public static final String EXTRA_INSTALL = "MSG_INSTALL";
|
public static final String EXTRA_INSTALL = "MSG_INSTALL";
|
||||||
|
public static final int DEFAULT_PORT = 35963;
|
||||||
|
private int port = DEFAULT_PORT;
|
||||||
|
private static final String hostname = "192.168.42.158";
|
||||||
|
private static Socket socket;
|
||||||
|
private static InputStream inputStream;
|
||||||
|
private static OutputStream outputStream;
|
||||||
|
private static final boolean useVPCD = false;
|
||||||
|
|
||||||
private static CardSimulator simulator = null;
|
private static CardSimulator simulator = null;
|
||||||
|
|
||||||
@@ -111,9 +126,26 @@ public class SimulatorService extends HostApduService {
|
|||||||
public void onCreate () {
|
public void onCreate () {
|
||||||
super.onCreate();
|
super.onCreate();
|
||||||
|
|
||||||
|
Log.d("", "Begin transaction");
|
||||||
|
if (useVPCD) {
|
||||||
|
if (socket == null) {
|
||||||
|
try {
|
||||||
|
if (android.os.Build.VERSION.SDK_INT > 9) {
|
||||||
|
StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
|
||||||
|
StrictMode.setThreadPolicy(policy);
|
||||||
|
}
|
||||||
|
vpcdConnect();
|
||||||
|
sendPowerOn();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
vpcdDisconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
if (simulator == null)
|
if (simulator == null)
|
||||||
createSimulator();
|
createSimulator();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public byte[] processCommandApdu(byte[] capdu, Bundle extras) {
|
public byte[] processCommandApdu(byte[] capdu, Bundle extras) {
|
||||||
@@ -123,10 +155,16 @@ public class SimulatorService extends HostApduService {
|
|||||||
|
|
||||||
i.putExtra(EXTRA_CAPDU, Util.byteArrayToHexString(capdu));
|
i.putExtra(EXTRA_CAPDU, Util.byteArrayToHexString(capdu));
|
||||||
try {
|
try {
|
||||||
|
if (useVPCD) {
|
||||||
|
rapdu = transmit(capdu);
|
||||||
|
} else {
|
||||||
rapdu = simulator.transmitCommand(capdu);
|
rapdu = simulator.transmitCommand(capdu);
|
||||||
|
}
|
||||||
|
if (rapdu != null)
|
||||||
i.putExtra(EXTRA_RAPDU, Util.byteArrayToHexString(rapdu));
|
i.putExtra(EXTRA_RAPDU, Util.byteArrayToHexString(rapdu));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
|
vpcdDisconnect();
|
||||||
extra_error += "Internal error";
|
extra_error += "Internal error";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -142,16 +180,111 @@ public class SimulatorService extends HostApduService {
|
|||||||
public void onDeactivated(int reason) {
|
public void onDeactivated(int reason) {
|
||||||
Intent i = new Intent(TAG);
|
Intent i = new Intent(TAG);
|
||||||
|
|
||||||
|
Log.d("", "End transaction");
|
||||||
switch (reason) {
|
switch (reason) {
|
||||||
case DEACTIVATION_LINK_LOSS:
|
case DEACTIVATION_LINK_LOSS:
|
||||||
i.putExtra(EXTRA_DESELECT, "link lost");
|
i.putExtra(EXTRA_DESELECT, "link lost");
|
||||||
|
if (useVPCD)
|
||||||
|
try {
|
||||||
|
sendPowerOff();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
vpcdDisconnect();
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case DEACTIVATION_DESELECTED:
|
case DEACTIVATION_DESELECTED:
|
||||||
i.putExtra(EXTRA_DESELECT, "deactivated");
|
i.putExtra(EXTRA_DESELECT, "deactivated");
|
||||||
|
if (useVPCD)
|
||||||
|
try {
|
||||||
|
sendReset();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
vpcdDisconnect();
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
if (useVPCD) {
|
||||||
|
//vpcdDisconnect();
|
||||||
|
}
|
||||||
|
|
||||||
LocalBroadcastManager.getInstance(this).sendBroadcast(i);
|
LocalBroadcastManager.getInstance(this).sendBroadcast(i);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private byte[] receiveFromVPCD() throws IOException {
|
||||||
|
/* convert length from network byte order.
|
||||||
|
Note that Java always uses network byte order internally. */
|
||||||
|
int length1 = inputStream.read();
|
||||||
|
int length2 = inputStream.read();
|
||||||
|
if (length1 == -1 || length2 == -1) {
|
||||||
|
// EOF
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
int length = (length1 << 8) + length2;
|
||||||
|
|
||||||
|
byte[] data = new byte[length];
|
||||||
|
|
||||||
|
int offset = 0;
|
||||||
|
while (length > 0) {
|
||||||
|
int read = inputStream.read(data, offset, length);
|
||||||
|
if (read == -1) {
|
||||||
|
// EOF
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
offset += read;
|
||||||
|
length -= read;
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] transmit(byte[] capdu) throws IOException {
|
||||||
|
sendToVPCD(capdu);
|
||||||
|
return receiveFromVPCD();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendPowerOff() throws IOException {
|
||||||
|
Log.d("", "Power Off");
|
||||||
|
sendToVPCD(new byte[] {0x00});
|
||||||
|
}
|
||||||
|
private void sendPowerOn() throws IOException {
|
||||||
|
Log.d("", "Power On");
|
||||||
|
sendToVPCD(new byte[] {0x01});
|
||||||
|
}
|
||||||
|
private void sendReset() throws IOException {
|
||||||
|
Log.d("", "Reset");
|
||||||
|
sendToVPCD(new byte[] {0x02});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendToVPCD(byte[] data) throws IOException {
|
||||||
|
/* convert length to network byte order.
|
||||||
|
Note that Java always uses network byte order internally. */
|
||||||
|
byte[] length = new byte[2];
|
||||||
|
length[0] = (byte) (data.length >> 8);
|
||||||
|
length[1] = (byte) (data.length & 0xff);
|
||||||
|
outputStream.write(length);
|
||||||
|
|
||||||
|
outputStream.write(data, 0, data.length);
|
||||||
|
|
||||||
|
outputStream.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void vpcdConnect() throws IOException {
|
||||||
|
Log.d("", "Connecting to " + hostname + ":" + Integer.toString(port));
|
||||||
|
socket = new Socket(InetAddress.getByName(hostname), port);
|
||||||
|
outputStream = socket.getOutputStream();
|
||||||
|
inputStream = socket.getInputStream();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void vpcdDisconnect() {
|
||||||
|
Log.d("", "Disconnecting");
|
||||||
|
if (socket != null) {
|
||||||
|
try {
|
||||||
|
socket.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
// ignore
|
||||||
|
} finally {
|
||||||
|
socket = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,4 +19,9 @@
|
|||||||
<aid-group android:description="@string/applet_isoapplet" android:category="other">
|
<aid-group android:description="@string/applet_isoapplet" android:category="other">
|
||||||
<aid-filter android:name="@string/aid_isoapplet"/>
|
<aid-filter android:name="@string/aid_isoapplet"/>
|
||||||
</aid-group>
|
</aid-group>
|
||||||
|
|
||||||
|
<aid-group android:description="@string/applet_paccess" android:category="other">
|
||||||
|
<aid-filter android:name="@string/aid_paccess"/>
|
||||||
|
</aid-group>
|
||||||
|
|
||||||
</host-apdu-service>
|
</host-apdu-service>
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ $(abs_builddir)/pcsc-relay.1:
|
|||||||
bin_PROGRAMS = pcsc-relay
|
bin_PROGRAMS = pcsc-relay
|
||||||
bin_SCRIPTS = picc.py
|
bin_SCRIPTS = picc.py
|
||||||
|
|
||||||
pcsc_relay_SOURCES = pcsc-relay.c pcsc.c vpcd.c vpcd-driver.c opicc.c lnfc.c lock.c $(BUILT_SOURCES)
|
pcsc_relay_SOURCES = pcsc-relay.c pcsc.c vpcd.c vpcd-driver.c opicc.c lnfc.c vicc.c lock.c $(BUILT_SOURCES)
|
||||||
pcsc_relay_LDADD = $(PCSC_LIBS) $(LIBNFC_LIBS)
|
pcsc_relay_LDADD = $(PCSC_LIBS) $(LIBNFC_LIBS)
|
||||||
pcsc_relay_CFLAGS = $(PCSC_CFLAGS) $(LIBNFC_CFLAGS)
|
pcsc_relay_CFLAGS = $(PCSC_CFLAGS) $(LIBNFC_CFLAGS)
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,13 @@
|
|||||||
#include "pcsc-relay.h"
|
#include "pcsc-relay.h"
|
||||||
|
|
||||||
#ifndef MAX_BUFFER_SIZE
|
#ifndef MAX_BUFFER_SIZE
|
||||||
#define MAX_BUFFER_SIZE 264 /**< Maximum Tx/Rx Buffer for short APDU */
|
/** Maximum Tx/Rx Buffer for short APDU */
|
||||||
|
#define MAX_BUFFER_SIZE 261
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef MAX_EXT_BUFFER_SIZE
|
||||||
|
/** Maximum Tx/Rx Buffer for extended APDU */
|
||||||
|
#define MAX_EXT_BUFFER_SIZE 65538
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
int verbose = 0;
|
int verbose = 0;
|
||||||
@@ -133,7 +139,7 @@ int main (int argc, char **argv)
|
|||||||
unsigned char *buf = NULL;
|
unsigned char *buf = NULL;
|
||||||
size_t buflen;
|
size_t buflen;
|
||||||
|
|
||||||
unsigned char outputBuffer[MAX_BUFFER_SIZE];
|
unsigned char outputBuffer[MAX_EXT_BUFFER_SIZE];
|
||||||
size_t outputLength;
|
size_t outputLength;
|
||||||
|
|
||||||
struct gengetopt_args_info args_info;
|
struct gengetopt_args_info args_info;
|
||||||
@@ -205,8 +211,12 @@ int main (int argc, char **argv)
|
|||||||
|
|
||||||
while(1) {
|
while(1) {
|
||||||
/* get C-APDU */
|
/* get C-APDU */
|
||||||
if (!rfdriver->receive_capdu(rfdriver_data, &buf, &buflen))
|
if (!rfdriver->receive_capdu(rfdriver_data, &buf, &buflen)) {
|
||||||
goto err;
|
do {
|
||||||
|
INFO("Trying to recover by reconnecting to emulator\n");
|
||||||
|
sleep(10);
|
||||||
|
} while (!rfdriver->connect(&rfdriver_data));
|
||||||
|
}
|
||||||
if (!buflen || !buf)
|
if (!buflen || !buf)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
@@ -223,8 +233,12 @@ int main (int argc, char **argv)
|
|||||||
/* send R-APDU */
|
/* send R-APDU */
|
||||||
hexdump("R-APDU:\n", outputBuffer, outputLength);
|
hexdump("R-APDU:\n", outputBuffer, outputLength);
|
||||||
|
|
||||||
if (!rfdriver->send_rapdu(rfdriver_data, outputBuffer, outputLength))
|
if (!rfdriver->send_rapdu(rfdriver_data, outputBuffer, outputLength)) {
|
||||||
goto err;
|
do {
|
||||||
|
INFO("Trying to recover by reconnecting to emulator\n");
|
||||||
|
sleep(10);
|
||||||
|
} while (!rfdriver->connect(&rfdriver_data));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ description "Using an contact-less interface (currently OpenPICC or libnfc) @PAC
|
|||||||
|
|
||||||
option "emulator" e
|
option "emulator" e
|
||||||
"Contact-less emulator backend"
|
"Contact-less emulator backend"
|
||||||
values="libnfc","openpicc" default="libnfc"
|
values="libnfc","vpcd","openpicc" default="libnfc"
|
||||||
enum
|
enum
|
||||||
optional
|
optional
|
||||||
option "connector" c
|
option "connector" c
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ extern int verbose;
|
|||||||
|
|
||||||
extern struct rf_driver driver_openpicc;
|
extern struct rf_driver driver_openpicc;
|
||||||
extern struct rf_driver driver_libnfc;
|
extern struct rf_driver driver_libnfc;
|
||||||
|
extern struct rf_driver driver_vicc;
|
||||||
|
|
||||||
struct sc_driver {
|
struct sc_driver {
|
||||||
int (*connect) (driver_data_t **driver_data);
|
int (*connect) (driver_data_t **driver_data);
|
||||||
|
|||||||
138
pcsc-relay/src/vicc.c
Normal file
138
pcsc-relay/src/vicc.c
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2012 Frank Morgner
|
||||||
|
*
|
||||||
|
* This file is part of pcsc-relay.
|
||||||
|
*
|
||||||
|
* pcsc-relay 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.
|
||||||
|
*
|
||||||
|
* pcsc-relay 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
|
||||||
|
* pcsc-relay. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
#include "config.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "pcsc-relay.h"
|
||||||
|
#include "vpcd.h"
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
static int _vicc_connect(driver_data_t **driver_data)
|
||||||
|
{
|
||||||
|
struct vicc_ctx *ctx;
|
||||||
|
const long secs = 300;
|
||||||
|
|
||||||
|
if (!driver_data)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
|
||||||
|
ctx = vicc_init(NULL, vpcdport);
|
||||||
|
if (!ctx) {
|
||||||
|
RELAY_ERROR("Could not initialize connection to VPCD\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
*driver_data = ctx;
|
||||||
|
|
||||||
|
INFO("Waiting for VPCD on port %hu for %ld seconds\n",
|
||||||
|
(unsigned short) vpcdport, secs);
|
||||||
|
if (vicc_connect(ctx, secs, 0))
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int vicc_disconnect(driver_data_t *driver_data)
|
||||||
|
{
|
||||||
|
struct vicc_ctx *ctx = driver_data;
|
||||||
|
|
||||||
|
vicc_eject(ctx);
|
||||||
|
if (vicc_exit(ctx) != 0) {
|
||||||
|
RELAY_ERROR("Could not close connection to virtual ICC\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int vicc_receive_capdu(driver_data_t *driver_data,
|
||||||
|
unsigned char **capdu, size_t *len)
|
||||||
|
{
|
||||||
|
struct vicc_ctx *ctx = driver_data;
|
||||||
|
|
||||||
|
int r = 0;
|
||||||
|
ssize_t size;
|
||||||
|
const unsigned char atr[] = {0x3B, 0x80, 0x80, 0x01, 0x01};
|
||||||
|
|
||||||
|
if (!len)
|
||||||
|
goto err;
|
||||||
|
|
||||||
|
do {
|
||||||
|
size = vicc_transmit(ctx, 0, NULL, capdu);
|
||||||
|
|
||||||
|
if (size < 0) {
|
||||||
|
RELAY_ERROR("could not receive request\n");
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (size == 1) {
|
||||||
|
switch (*capdu[0]) {
|
||||||
|
case 0x00:
|
||||||
|
case 0x01:
|
||||||
|
case 0x02:
|
||||||
|
// ignore reset, power on, power off
|
||||||
|
break;
|
||||||
|
case 0x03:
|
||||||
|
if (vicc_transmit(ctx, sizeof atr, atr, NULL) < 0) {
|
||||||
|
RELAY_ERROR("could not send ATR\n");
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
RELAY_ERROR("Unknown request: 0x%0X\n", *capdu[0]);
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// finaly we got the capdu
|
||||||
|
*len = size;
|
||||||
|
r = 1;
|
||||||
|
}
|
||||||
|
} while (!r);
|
||||||
|
|
||||||
|
err:
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int vicc_send_rapdu(driver_data_t *driver_data,
|
||||||
|
const unsigned char *rapdu, size_t len)
|
||||||
|
{
|
||||||
|
struct vicc_ctx *ctx = driver_data;
|
||||||
|
|
||||||
|
if (!ctx || !rapdu)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (vicc_transmit(ctx, len, rapdu, NULL) < 0) {
|
||||||
|
RELAY_ERROR("could not send R-APDU\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
struct rf_driver driver_vicc = {
|
||||||
|
.connect = _vicc_connect,
|
||||||
|
.disconnect = vicc_disconnect,
|
||||||
|
.receive_capdu = vicc_receive_capdu,
|
||||||
|
.send_rapdu = vicc_send_rapdu,
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user