From 65f7d92a7f84e30cd44b6fe43cd1554229e7cd04 Mon Sep 17 00:00:00 2001 From: Frank Morgner Date: Sat, 14 Mar 2015 15:14:02 +0100 Subject: [PATCH] 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 --- .../app/src/main/AndroidManifest.xml | 2 + .../vsmartcard/acardemulator/Settings.java | 2 - .../acardemulator/SimulatorService.java | 141 +++++++++++++++++- .../app/src/main/res/xml/aid_list.xml | 5 + pcsc-relay/src/Makefile.am | 2 +- pcsc-relay/src/pcsc-relay.c | 26 +++- pcsc-relay/src/pcsc-relay.ggo.in | 2 +- pcsc-relay/src/pcsc-relay.h | 1 + pcsc-relay/src/vicc.c | 138 +++++++++++++++++ 9 files changed, 305 insertions(+), 14 deletions(-) create mode 100644 pcsc-relay/src/vicc.c diff --git a/ACardEmulator/app/src/main/AndroidManifest.xml b/ACardEmulator/app/src/main/AndroidManifest.xml index d295d18..afbaaf6 100644 --- a/ACardEmulator/app/src/main/AndroidManifest.xml +++ b/ACardEmulator/app/src/main/AndroidManifest.xml @@ -2,6 +2,8 @@ + + diff --git a/ACardEmulator/app/src/main/java/com/vsmartcard/acardemulator/Settings.java b/ACardEmulator/app/src/main/java/com/vsmartcard/acardemulator/Settings.java index 759d127..ab50e7a 100644 --- a/ACardEmulator/app/src/main/java/com/vsmartcard/acardemulator/Settings.java +++ b/ACardEmulator/app/src/main/java/com/vsmartcard/acardemulator/Settings.java @@ -27,8 +27,6 @@ import android.preference.PreferenceManager; import com.licel.jcardsim.base.Simulator; -import net.pwendland.javacard.pki.isoapplet.FileNotFoundException; - import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; diff --git a/ACardEmulator/app/src/main/java/com/vsmartcard/acardemulator/SimulatorService.java b/ACardEmulator/app/src/main/java/com/vsmartcard/acardemulator/SimulatorService.java index 3338514..3fb1fb0 100644 --- a/ACardEmulator/app/src/main/java/com/vsmartcard/acardemulator/SimulatorService.java +++ b/ACardEmulator/app/src/main/java/com/vsmartcard/acardemulator/SimulatorService.java @@ -25,7 +25,9 @@ import android.content.Intent; import android.content.IntentFilter; import android.nfc.cardemulation.HostApduService; import android.os.Bundle; +import android.os.StrictMode; import android.support.v4.content.LocalBroadcastManager; +import android.util.Log; import com.licel.jcardsim.samples.HelloWorldApplet; import com.licel.jcardsim.smartcardio.CardSimulator; @@ -33,6 +35,12 @@ import com.licel.jcardsim.utils.AIDUtil; 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 pkgYkneoOath.YkneoOath; @@ -44,6 +52,13 @@ public class SimulatorService extends HostApduService { public static final String EXTRA_ERROR = "MSG_ERROR"; public static final String EXTRA_DESELECT = "MSG_DESELECT"; 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; @@ -111,8 +126,25 @@ public class SimulatorService extends HostApduService { public void onCreate () { super.onCreate(); - if (simulator == null) - createSimulator(); + 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) + createSimulator(); + } } @Override @@ -123,10 +155,16 @@ public class SimulatorService extends HostApduService { i.putExtra(EXTRA_CAPDU, Util.byteArrayToHexString(capdu)); try { - rapdu = simulator.transmitCommand(capdu); - i.putExtra(EXTRA_RAPDU, Util.byteArrayToHexString(rapdu)); + if (useVPCD) { + rapdu = transmit(capdu); + } else { + rapdu = simulator.transmitCommand(capdu); + } + if (rapdu != null) + i.putExtra(EXTRA_RAPDU, Util.byteArrayToHexString(rapdu)); } catch (Exception e) { e.printStackTrace(); + vpcdDisconnect(); extra_error += "Internal error"; } @@ -142,16 +180,111 @@ public class SimulatorService extends HostApduService { public void onDeactivated(int reason) { Intent i = new Intent(TAG); + Log.d("", "End transaction"); switch (reason) { case DEACTIVATION_LINK_LOSS: i.putExtra(EXTRA_DESELECT, "link lost"); + if (useVPCD) + try { + sendPowerOff(); + } catch (IOException e) { + e.printStackTrace(); + vpcdDisconnect(); + } break; case DEACTIVATION_DESELECTED: i.putExtra(EXTRA_DESELECT, "deactivated"); + if (useVPCD) + try { + sendReset(); + } catch (IOException e) { + e.printStackTrace(); + vpcdDisconnect(); + } break; } + if (useVPCD) { + //vpcdDisconnect(); + } 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; + } + } + } } diff --git a/ACardEmulator/app/src/main/res/xml/aid_list.xml b/ACardEmulator/app/src/main/res/xml/aid_list.xml index 8409b23..e1bff42 100644 --- a/ACardEmulator/app/src/main/res/xml/aid_list.xml +++ b/ACardEmulator/app/src/main/res/xml/aid_list.xml @@ -19,4 +19,9 @@ + + + + + diff --git a/pcsc-relay/src/Makefile.am b/pcsc-relay/src/Makefile.am index afc594c..05454ff 100644 --- a/pcsc-relay/src/Makefile.am +++ b/pcsc-relay/src/Makefile.am @@ -29,7 +29,7 @@ $(abs_builddir)/pcsc-relay.1: bin_PROGRAMS = pcsc-relay 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_CFLAGS = $(PCSC_CFLAGS) $(LIBNFC_CFLAGS) diff --git a/pcsc-relay/src/pcsc-relay.c b/pcsc-relay/src/pcsc-relay.c index 10649d4..a8a708d 100644 --- a/pcsc-relay/src/pcsc-relay.c +++ b/pcsc-relay/src/pcsc-relay.c @@ -32,7 +32,13 @@ #include "pcsc-relay.h" #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 int verbose = 0; @@ -133,7 +139,7 @@ int main (int argc, char **argv) unsigned char *buf = NULL; size_t buflen; - unsigned char outputBuffer[MAX_BUFFER_SIZE]; + unsigned char outputBuffer[MAX_EXT_BUFFER_SIZE]; size_t outputLength; struct gengetopt_args_info args_info; @@ -205,8 +211,12 @@ int main (int argc, char **argv) while(1) { /* get C-APDU */ - if (!rfdriver->receive_capdu(rfdriver_data, &buf, &buflen)) - goto err; + if (!rfdriver->receive_capdu(rfdriver_data, &buf, &buflen)) { + do { + INFO("Trying to recover by reconnecting to emulator\n"); + sleep(10); + } while (!rfdriver->connect(&rfdriver_data)); + } if (!buflen || !buf) continue; @@ -223,8 +233,12 @@ int main (int argc, char **argv) /* send R-APDU */ hexdump("R-APDU:\n", outputBuffer, outputLength); - if (!rfdriver->send_rapdu(rfdriver_data, outputBuffer, outputLength)) - goto err; + if (!rfdriver->send_rapdu(rfdriver_data, outputBuffer, outputLength)) { + do { + INFO("Trying to recover by reconnecting to emulator\n"); + sleep(10); + } while (!rfdriver->connect(&rfdriver_data)); + } } diff --git a/pcsc-relay/src/pcsc-relay.ggo.in b/pcsc-relay/src/pcsc-relay.ggo.in index 169ecb5..aace70f 100644 --- a/pcsc-relay/src/pcsc-relay.ggo.in +++ b/pcsc-relay/src/pcsc-relay.ggo.in @@ -3,7 +3,7 @@ description "Using an contact-less interface (currently OpenPICC or libnfc) @PAC option "emulator" e "Contact-less emulator backend" - values="libnfc","openpicc" default="libnfc" + values="libnfc","vpcd","openpicc" default="libnfc" enum optional option "connector" c diff --git a/pcsc-relay/src/pcsc-relay.h b/pcsc-relay/src/pcsc-relay.h index a6e9618..394645f 100644 --- a/pcsc-relay/src/pcsc-relay.h +++ b/pcsc-relay/src/pcsc-relay.h @@ -44,6 +44,7 @@ extern int verbose; extern struct rf_driver driver_openpicc; extern struct rf_driver driver_libnfc; +extern struct rf_driver driver_vicc; struct sc_driver { int (*connect) (driver_data_t **driver_data); diff --git a/pcsc-relay/src/vicc.c b/pcsc-relay/src/vicc.c new file mode 100644 index 0000000..3cd921e --- /dev/null +++ b/pcsc-relay/src/vicc.c @@ -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 . + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "pcsc-relay.h" +#include "vpcd.h" +#include +#include +#include + + + +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, +};