added remote smart card reader app

This commit is contained in:
Frank Morgner
2014-06-16 00:59:34 +02:00
parent 5b6172acde
commit 7869ff66cc
47 changed files with 2430 additions and 0 deletions

1
remote-reader/app/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/build

84
remote-reader/app/app.iml Normal file
View File

@@ -0,0 +1,84 @@
<?xml version="1.0" encoding="UTF-8"?>
<module external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$/.." external.system.id="GRADLE" external.system.module.group="remote-reader" external.system.module.version="unspecified" type="JAVA_MODULE" version="4">
<component name="FacetManager">
<facet type="android-gradle" name="Android-Gradle">
<configuration>
<option name="GRADLE_PROJECT_PATH" value=":app" />
</configuration>
</facet>
<facet type="android" name="Android">
<configuration>
<option name="SELECTED_BUILD_VARIANT" value="debug" />
<option name="ASSEMBLE_TASK_NAME" value="assembleDebug" />
<option name="COMPILE_JAVA_TASK_NAME" value="compileDebugJava" />
<option name="ASSEMBLE_TEST_TASK_NAME" value="assembleDebugTest" />
<option name="SOURCE_GEN_TASK_NAME" value="generateDebugSources" />
<option name="ALLOW_USER_CONFIGURATION" value="false" />
<option name="MANIFEST_FILE_RELATIVE_PATH" value="/src/main/AndroidManifest.xml" />
<option name="RES_FOLDER_RELATIVE_PATH" value="/src/main/res" />
<option name="RES_FOLDERS_RELATIVE_PATH" value="file://$MODULE_DIR$/src/main/res" />
<option name="ASSETS_FOLDER_RELATIVE_PATH" value="/src/main/assets" />
</configuration>
</facet>
</component>
<component name="NewModuleRootManager" inherit-compiler-output="false">
<output url="file://$MODULE_DIR$/build/intermediates/classes/debug" />
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/r/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/aidl/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/debug" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/r/test/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/aidl/test/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/test/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/test/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/test/debug" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/res" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/aidl" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/assets" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/jni" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/rs" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/res" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/main/aidl" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/assets" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/jni" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/rs" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/res" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/resources" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/aidl" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/assets" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/java" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/jni" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/rs" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/build/apk" />
<excludeFolder url="file://$MODULE_DIR$/build/assets" />
<excludeFolder url="file://$MODULE_DIR$/build/classes" />
<excludeFolder url="file://$MODULE_DIR$/build/dependency-cache" />
<excludeFolder url="file://$MODULE_DIR$/build/dex" />
<excludeFolder url="file://$MODULE_DIR$/build/exploded-aar" />
<excludeFolder url="file://$MODULE_DIR$/build/exploded-bundles" />
<excludeFolder url="file://$MODULE_DIR$/build/incremental" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates" />
<excludeFolder url="file://$MODULE_DIR$/build/libs" />
<excludeFolder url="file://$MODULE_DIR$/build/manifests" />
<excludeFolder url="file://$MODULE_DIR$/build/ndk" />
<excludeFolder url="file://$MODULE_DIR$/build/outputs" />
<excludeFolder url="file://$MODULE_DIR$/build/pre-dexed" />
<excludeFolder url="file://$MODULE_DIR$/build/res" />
<excludeFolder url="file://$MODULE_DIR$/build/rs" />
<excludeFolder url="file://$MODULE_DIR$/build/source" />
<excludeFolder url="file://$MODULE_DIR$/build/symbols" />
</content>
<orderEntry type="jdk" jdkName="Android API 19 Platform" jdkType="Android SDK" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" exported="" name="appcompat-v7-19.1.0" level="project" />
<orderEntry type="library" exported="" name="support-v4-19.1.0" level="project" />
</component>
</module>

View File

@@ -0,0 +1,24 @@
apply plugin: 'android'
android {
compileSdkVersion 19
buildToolsVersion "19.1.0"
defaultConfig {
minSdkVersion 10
targetSdkVersion 19
versionCode 1
versionName "1.0"
}
buildTypes {
release {
runProguard false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
}
}
}
dependencies {
compile 'com.android.support:appcompat-v7:+'
compile fileTree(dir: 'libs', include: ['*.jar'])
}

View File

@@ -0,0 +1,17 @@
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /home/fm/.local/opt/android-studio/sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the ProGuard
# include property in project.properties.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

View File

@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.vsmartcard.remotesmartcardreader.app" >
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.NFC" />
<uses-feature android:name="android.hardware.nfc" android:required="true" />
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name="com.vsmartcard.remotesmartcardreader.app.MainActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.nfc.action.TECH_DISCOVERED" />
</intent-filter>
<meta-data
android:name="android.nfc.action.TECH_DISCOVERED"
android:resource="@xml/nfc_tech_filter" />
</activity>
</application>
</manifest>

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

View File

@@ -0,0 +1,39 @@
package com.vsmartcard.remotesmartcardreader.app;
public class Hex {
private static final char[] BYTE2HEX=(
"000102030405060708090A0B0C0D0E0F"+
"101112131415161718191A1B1C1D1E1F"+
"202122232425262728292A2B2C2D2E2F"+
"303132333435363738393A3B3C3D3E3F"+
"404142434445464748494A4B4C4D4E4F"+
"505152535455565758595A5B5C5D5E5F"+
"606162636465666768696A6B6C6D6E6F"+
"707172737475767778797A7B7C7D7E7F"+
"808182838485868788898A8B8C8D8E8F"+
"909192939495969798999A9B9C9D9E9F"+
"A0A1A2A3A4A5A6A7A8A9AAABACADAEAF"+
"B0B1B2B3B4B5B6B7B8B9BABBBCBDBEBF"+
"C0C1C2C3C4C5C6C7C8C9CACBCCCDCECF"+
"D0D1D2D3D4D5D6D7D8D9DADBDCDDDEDF"+
"E0E1E2E3E4E5E6E7E8E9EAEBECEDEEEF"+
"F0F1F2F3F4F5F6F7F8F9FAFBFCFDFEFF").toCharArray();
public static String getHexString(byte[] bytes) {
if (bytes == null) {
return "";
}
final int len=bytes.length;
final char[] chars=new char[len<<1];
int hexIndex;
int idx=0;
int ofs=0;
while (ofs<len) {
hexIndex=(bytes[ofs++] & 0xFF)<<1;
chars[idx++]=BYTE2HEX[hexIndex++];
chars[idx++]=BYTE2HEX[hexIndex];
}
return new String(chars);
}
}

View File

@@ -0,0 +1,269 @@
/*
* Copyright (C) 2014 Frank Morgner
*
* This file is part of RemoteSmartCardReader.
*
* RemoteSmartCardReader 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.
*
* RemoteSmartCardReader 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
* RemoteSmartCardReader. If not, see <http://www.gnu.org/licenses/>.
*/
package com.vsmartcard.remotesmartcardreader.app;
import android.app.Activity;
import android.app.PendingIntent;
import android.content.Intent;
import android.nfc.NfcAdapter;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.Bundle;
import android.text.Editable;
import android.text.Layout;
import android.text.method.LinkMovementMethod;
import android.text.method.ScrollingMovementMethod;
import android.view.KeyEvent;
import android.view.View;
import android.view.inputmethod.EditorInfo;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.ProgressBar;
import android.widget.TextView;
import com.vsmartcard.remotesmartcardreader.app.screaders.DummyReader;
import com.vsmartcard.remotesmartcardreader.app.screaders.NFCReader;
import com.vsmartcard.remotesmartcardreader.app.screaders.SCReader;
import android.text.ClipboardManager;
public class MainActivity extends Activity {
class VPCDHandler extends Handler {
VPCDHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message message) {
switch (message.what) {
case MessageSender.MESSAGE_CONNECTED:
textViewVPCDStatus.append(getResources().getString(R.string.status_connected_to)+" "+message.obj+"\n");
spinner.setVisibility(View.INVISIBLE);
break;
case MessageSender.MESSAGE_DISCONNECTED:
textViewVPCDStatus.append(getResources().getString(R.string.status_disconnected)+"\n");
testing = false;
updateLabels();
break;
case MessageSender.MESSAGE_ATR:
textViewVPCDStatus.append(getResources().getString(R.string.status_atr)+": "+message.obj+"\n");
break;
case MessageSender.MESSAGE_ON:
textViewVPCDStatus.append(getResources().getString(R.string.status_on)+"\n");
break;
case MessageSender.MESSAGE_OFF:
textViewVPCDStatus.append(getResources().getString(R.string.status_off)+"\n");
break;
case MessageSender.MESSAGE_RESET:
textViewVPCDStatus.append(getResources().getString(R.string.status_reset)+"\n");
break;
case MessageSender.MESSAGE_CAPDU:
textViewVPCDStatus.append(getResources().getString(R.string.status_capdu)+": "+message.obj+"\n");
break;
case MessageSender.MESSAGE_RAPDU:
textViewVPCDStatus.append(getResources().getString(R.string.status_rapdu)+": "+message.obj+"\n");
break;
case MessageSender.MESSAGE_ERROR:
if (testing) {
textViewVPCDStatus.append(getResources().getString(R.string.status_error)+": "+message.obj+"\n");
testing = false;
vpcdDisconnect();
updateLabels();
}
break;
default:
textViewVPCDStatus.append(getResources().getString(R.string.status_unknown)+" "+Integer.toString(message.what)+"\n");
break;
}
Layout layout = textViewVPCDStatus.getLayout();
if (layout != null) {
int scrollAmount = layout.getLineTop(textViewVPCDStatus.getLineCount()) - textViewVPCDStatus.getHeight();
if (scrollAmount > 0)
textViewVPCDStatus.scrollTo(0, scrollAmount);
else
textViewVPCDStatus.scrollTo(0, 0);
}
}
}
private EditText editTextVPCDHost;
private EditText editTextVPCDPort;
private TextView textViewVPCDStatus;
private TextView textViewInfo;
private Button button;
private ProgressBar spinner;
private VPCDHandler handler;
private VPCDWorker vpcdTest;
private boolean testing;
private String hostname;
private String port;
private String savedLog;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textViewInfo = (TextView) findViewById(R.id.textViewInfo);
editTextVPCDHost = (EditText) findViewById(R.id.editTextHostname);
editTextVPCDPort = (EditText) findViewById(R.id.editTextPort);
textViewVPCDStatus = (TextView) findViewById(R.id.textViewLog);
textViewVPCDStatus.setMovementMethod(new ScrollingMovementMethod());
spinner = (ProgressBar) findViewById(R.id.progressBar);
button = (Button) findViewById(R.id.buttonDisConnect);
EditText.OnEditorActionListener vpcdWatcher = new EditText.OnEditorActionListener() {
@Override
public boolean onEditorAction(TextView textView, int i, KeyEvent keyEvent) {
if (i == EditorInfo.IME_ACTION_DONE) {
editTextOnEditorDone();
return true;
}
return false;
}
};
editTextVPCDHost.setOnEditorActionListener(vpcdWatcher);
editTextVPCDPort.setOnEditorActionListener(vpcdWatcher);
handler = new VPCDHandler(Looper.getMainLooper());
PendingIntent.getActivity(this, 0, new Intent(this, this.getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);
loadSettings();
updateLabels();
}
private static String saved_status_key = "textViewVPCDStatus";
@Override
public void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
textViewVPCDStatus.setText(savedInstanceState.getCharSequence(saved_status_key));
}
@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
savedInstanceState.putCharSequence(saved_status_key, textViewVPCDStatus.getText());
super.onSaveInstanceState(savedInstanceState);
}
@Override
public void onPause() {
super.onPause();
testing = false;
vpcdDisconnect();
}
public void buttonOnClickDisConnect(View view) {
if (testing) {
testing = false;
vpcdDisconnect();
} else {
saveSettings();
testing = true;
spinner.setVisibility(View.VISIBLE);
textViewVPCDStatus.append(getResources().getString(R.string.status_dummy_start)+"\n");
vpcdConnect(new DummyReader());
}
updateLabels();
}
private void editTextOnEditorDone() {
textViewVPCDStatus.append(getResources().getString(R.string.status_dummy_start)+"\n");
forceConnect(new DummyReader());
}
private void vpcdConnect(SCReader scReader) {
vpcdTest = new VPCDWorker(hostname, Integer.parseInt(port), scReader, handler);
new Thread(vpcdTest).start();
}
private void vpcdDisconnect() {
if (vpcdTest != null) {
vpcdTest.close();
vpcdTest = null;
}
}
private void updateLabels() {
if (testing) {
button.setText(getResources().getString(R.string.button_stop_testing));
} else {
button.setText(getResources().getString(R.string.button_test_configuration));
spinner.setVisibility(View.INVISIBLE);
}
}
private void loadSettings() {
Settings settings = new Settings(this);
hostname = settings.getVPCDHost();
port = settings.getVPCDPort();
editTextVPCDHost.setText(hostname);
editTextVPCDPort.setText(port);
}
private void saveSettings() {
Editable textHostname = editTextVPCDHost.getText();
Editable textPort = editTextVPCDPort.getText();
if (textHostname != null) {
hostname = textHostname.toString();
}
if (textPort != null) {
port = textPort.toString();
}
Settings settings = new Settings(this);
settings.setVPCDSettings(hostname, port);
}
private void forceConnect(SCReader scReader) {
if (testing) {
testing = false;
vpcdDisconnect();
}
saveSettings();
testing = true;
spinner.setVisibility(View.VISIBLE);
vpcdConnect(scReader);
updateLabels();
}
@Override
public void onResume() {
super.onResume();
// Check to see that the Activity started due to a discovered tag
if (NfcAdapter.ACTION_TECH_DISCOVERED.equals(getIntent().getAction())) {
NFCReader nfcReader = NFCReader.processIntent(getIntent());
if (nfcReader != null) {
textViewVPCDStatus.append(getResources().getString(R.string.status_tag_discovered)+"\n");
forceConnect(nfcReader);
} else {
super.onNewIntent(getIntent());
}
}
}
@Override
public void onNewIntent(Intent intent) {
// onResume gets called after this to handle the intent
setIntent(intent);
}
}

View File

@@ -0,0 +1,81 @@
/*
* Copyright (C) 2014 Frank Morgner
*
* This file is part of RemoteSmartCardReader.
*
* RemoteSmartCardReader 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.
*
* RemoteSmartCardReader 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
* RemoteSmartCardReader. If not, see <http://www.gnu.org/licenses/>.
*/
package com.vsmartcard.remotesmartcardreader.app;
import android.os.Handler;
import android.os.Message;
public class MessageSender {
public static final int MESSAGE_CONNECTED = 0;
public static final int MESSAGE_DISCONNECTED = 1;
public static final int MESSAGE_ERROR = 2;
public static final int MESSAGE_ATR = 3;
public static final int MESSAGE_ON = 4;
public static final int MESSAGE_OFF = 5;
public static final int MESSAGE_RESET = 6;
public static final int MESSAGE_CAPDU = 7;
public static final int MESSAGE_RAPDU = 8;
private Handler handler;
public MessageSender(Handler handler) {
this.handler = handler;
}
private void sendMessage(int messageCode, Object object) {
Message message = handler.obtainMessage(messageCode, object);
handler.sendMessage(message);
}
public void sendConnected(String details) {
sendMessage(MESSAGE_CONNECTED, details);
}
public void sendATR(String details) {
sendMessage(MESSAGE_ATR, details);
}
public void sendCAPDU(String details) {
sendMessage(MESSAGE_CAPDU, details);
}
public void sendDisconnected(String details) {
sendMessage(MESSAGE_DISCONNECTED, details);
}
public void sendError(String details) {
sendMessage(MESSAGE_ERROR, details);
}
public void sendOff() {
sendMessage(MESSAGE_OFF, "");
}
public void sendOn() {
sendMessage(MESSAGE_ON, "");
}
public void sendRAPDU(String details) {
sendMessage(MESSAGE_RAPDU, details);
}
public void sendReset() {
sendMessage(MESSAGE_RESET, "");
}
}

View File

@@ -0,0 +1,75 @@
/*
* Copyright (C) 2014 Frank Morgner
*
* This file is part of RemoteSmartCardReader.
*
* RemoteSmartCardReader 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.
*
* RemoteSmartCardReader 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
* RemoteSmartCardReader. If not, see <http://www.gnu.org/licenses/>.
*/
package com.vsmartcard.remotesmartcardreader.app;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.preference.PreferenceManager;
public class Settings {
private static final String KEY_VPCD_HOST = "hostname";
private static final String KEY_VPCD_PORT = "port";
private static final String DEFAULT_VPCD_HOST = "10.0.2.2";
private static final String DEFAULT_VPCD_PORT = "35963";
private final SharedPreferences settings;
public Settings (Context activity) {
settings = PreferenceManager.getDefaultSharedPreferences(activity);
}
private void setString(String key, String value) {
Editor editor = settings.edit();
editor.putString(key, value);
editor.commit();
}
private void setBoolean(String key, boolean value) {
Editor editor = settings.edit();
editor.putBoolean(key, value);
editor.commit();
}
public void setVPCDHost(String hostname) {
setString(KEY_VPCD_HOST, hostname);
}
public void setVPCDPort(String port) {
setString(KEY_VPCD_PORT, port);
}
public void setVPCDSettings(String hostname, String port) {
Editor editor = settings.edit();
editor.putString(KEY_VPCD_HOST, hostname);
editor.putString(KEY_VPCD_PORT, port);
editor.commit();
}
public String getVPCDHost() {
return settings.getString(KEY_VPCD_HOST, DEFAULT_VPCD_HOST);
}
public String getVPCDPort() {
return settings.getString(KEY_VPCD_PORT, DEFAULT_VPCD_PORT);
}
}

View File

@@ -0,0 +1,173 @@
/*
* Copyright (C) 2014 Frank Morgner
*
* This file is part of RemoteSmartCardReader.
*
* RemoteSmartCardReader 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.
*
* RemoteSmartCardReader 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
* RemoteSmartCardReader. If not, see <http://www.gnu.org/licenses/>.
*/
package com.vsmartcard.remotesmartcardreader.app;
import android.os.Handler;
import com.vsmartcard.remotesmartcardreader.app.screaders.SCReader;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
public class VPCDWorker implements Runnable, Closeable {
private int port;
private String hostname;
private SCReader reader;
private Socket socket;
private InputStream inputStream;
private OutputStream outputStream;
private boolean doRun;
private MessageSender messageSender;
public VPCDWorker(String hostname, int port, SCReader reader, Handler handler) {
this.hostname = hostname;
this.port = port;
this.reader = reader;
this.messageSender = new MessageSender(handler);
}
public void close() {
doRun = false;
try {
vpcdDisconnect();
} catch (IOException e) {
e.printStackTrace();
}
}
private static final int VPCD_CTRL_LEN = 1;
private static final byte VPCD_CTRL_OFF = 0;
private static final byte VPCD_CTRL_ON = 1;
private static final byte VPCD_CTRL_RESET = 2;
private static final byte VPCD_CTRL_ATR = 4;
@Override
public void run() {
android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_BACKGROUND);
doRun = true;
try {
vpcdConnect();
messageSender.sendConnected(hostname+":"+Integer.toString(port));
while (doRun) {
byte[] in = receiveFromVPCD();
if (in == null) {
doRun = false;
break;
}
byte[] out = null;
if (in.length == VPCD_CTRL_LEN) {
switch (in[0]) {
case VPCD_CTRL_OFF:
reader.powerOff();
messageSender.sendOff();
break;
case VPCD_CTRL_ON:
reader.powerOn();
messageSender.sendOn();
break;
case VPCD_CTRL_RESET:
reader.reset();
messageSender.sendReset();
break;
case VPCD_CTRL_ATR:
out = reader.getATR();
//messageSender.sendATR(Hex.getHexString(out));
break;
default:
throw new IOException("Unhandled command from VPCD.");
}
} else {
messageSender.sendCAPDU(Hex.getHexString(in));
out = reader.transmit(in);
messageSender.sendRAPDU(Hex.getHexString(out));
}
if (out != null) {
sendToVPCD(out);
}
}
} catch (Exception e) {
if (doRun) {
e.printStackTrace();
messageSender.sendError(e.getMessage());
} else {
messageSender.sendDisconnected("");
}
}
}
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 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 {
socket = new Socket(InetAddress.getByName(hostname), port);
outputStream = socket.getOutputStream();
inputStream = socket.getInputStream();
}
private void vpcdDisconnect() throws IOException {
if (socket != null) {
socket.close();
}
}
}

View File

@@ -0,0 +1,56 @@
/*
* Copyright (C) 2014 Frank Morgner
*
* This file is part of RemoteSmartCardReader.
*
* RemoteSmartCardReader 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.
*
* RemoteSmartCardReader 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
* RemoteSmartCardReader. If not, see <http://www.gnu.org/licenses/>.
*/
package com.vsmartcard.remotesmartcardreader.app.screaders;
public class DummyReader implements SCReader {
@Override
public void eject() {
}
@Override
public boolean present() {
return true;
}
@Override
public void powerOn() {
}
@Override
public void powerOff() {
}
@Override
public void reset() {
}
private static final byte[] dummyATR = {0x3B, 0x68, 0x00, (byte) 0xFF, 56, 43, 0x41, 0x52, 0x44, 0x6E, 0x73, 0x73};
@Override
public byte[] getATR() {
return dummyATR;
}
private static final byte[] dummyResponse = {0x6F, 0x00};
@Override
public byte[] transmit(byte[] apdu) {
return dummyResponse;
}
}

View File

@@ -0,0 +1,113 @@
/*
* Copyright (C) 2014 Frank Morgner
*
* This file is part of RemoteSmartCardReader.
*
* RemoteSmartCardReader 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.
*
* RemoteSmartCardReader 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
* RemoteSmartCardReader. If not, see <http://www.gnu.org/licenses/>.
*/
package com.vsmartcard.remotesmartcardreader.app.screaders;
import android.content.Intent;
import android.nfc.NfcAdapter;
import android.nfc.Tag;
import android.nfc.tech.IsoDep;
import android.util.Log;
import com.vsmartcard.remotesmartcardreader.app.Hex;
import java.io.IOException;
public class NFCReader implements SCReader {
private IsoDep card;
public NFCReader(IsoDep sc) {
this.card = sc;
}
@Override
public void eject() throws IOException {
card.close();
}
@Override
public boolean present() {
return card.isConnected();
}
private static int TIMEOUT = 10000;
@Override
public void powerOn() throws IOException {
card.connect();
card.setTimeout(TIMEOUT);
}
@Override
public void powerOff() throws IOException {
card.close();
}
private static byte[] SELECT_MF = {(byte) 0x00, (byte) 0xa4, (byte) 0x00, (byte) 0x0C};
@Override
public void reset() throws IOException {
byte[] response = card.transceive(SELECT_MF);
if (response.length == 2 && response[0] == 0x90 && response[1] == 0x00) {
Log.d(this.getClass().getName(), "Resetting the card by selecting the MF results in " + Hex.getHexString(response));
}
}
/* calculation based on https://code.google.com/p/ifdnfc/source/browse/src/atr.c */
@Override
public byte[] getATR() {
byte[] historicalBytes = card.getHistoricalBytes();
if (historicalBytes == null) {
historicalBytes = new byte[0];
}
/* copy historical bytes if available */
byte[] atr = new byte[4+historicalBytes.length+1];
atr[0] = (byte)0x3b;
atr[1] = (byte) (0x80+historicalBytes.length);
atr[2] = (byte) 0x80;
atr[3] = (byte)0x01;
System.arraycopy(historicalBytes, 0, atr, 4, historicalBytes.length);
/* calculate TCK */
byte tck = atr[1];
for (int idx = 2; idx < atr.length; idx++) {
tck ^= atr[idx];
}
atr[atr.length-1] = tck;
return atr;
}
@Override
public byte[] transmit(byte[] apdu) throws IOException {
return card.transceive(apdu);
}
public static NFCReader processIntent(Intent intent) {
NFCReader nfcReader = null;
if (intent.getExtras() != null) {
Tag tag = (Tag) intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
if (tag != null) {
nfcReader = new NFCReader(IsoDep.get(tag));
}
}
return nfcReader;
}
}

View File

@@ -0,0 +1,47 @@
/*
* Copyright (C) 2014 Frank Morgner
*
* This file is part of RemoteSmartCardReader.
*
* RemoteSmartCardReader 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.
*
* RemoteSmartCardReader 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
* RemoteSmartCardReader. If not, see <http://www.gnu.org/licenses/>.
*/
package com.vsmartcard.remotesmartcardreader.app.screaders;
import java.io.IOException;
public interface SCReader {
public void eject() throws IOException;
public boolean present();
public void powerOn() throws IOException;
public void powerOff() throws IOException;
public void reset() throws IOException;
/**
* Receive ATR from the smart card.
*
* @return ATR on success or null in case of an error
*/
public byte[] getATR();
/**
* Send an APDU to the smart card
*
* @param apdu Data to be sent
* @return response data or null in case of an error
*/
public byte[] transmit(byte[] apdu) throws IOException;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

View File

@@ -0,0 +1,73 @@
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin"
tools:context="com.vsmartcard.remotesmartcardreader.app.MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceSmall"
android:text="@string/vpcd_config_info"
android:id="@+id/textViewInfo"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
android:layout_toLeftOf="@+id/buttonDisConnect" />
<EditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/editTextHostname"
android:hint="@string/hint_vpcd_hostname"
android:inputType="textUri"
android:layout_below="@+id/textViewInfo"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_toLeftOf="@+id/editTextPort" />
<EditText
android:layout_width="100dp"
android:layout_height="wrap_content"
android:inputType="number"
android:ems="10"
android:id="@+id/editTextPort"
android:hint="@string/hint_vpcd_port"
android:layout_below="@+id/textViewInfo"
android:layout_toLeftOf="@+id/buttonDisConnect" />
<Button
android:layout_width="160dp"
android:layout_height="wrap_content"
android:id="@+id/buttonDisConnect"
android:onClick="buttonOnClickDisConnect"
android:layout_alignParentTop="true"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true" />
<ProgressBar
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/progressBar"
android:layout_below="@+id/buttonDisConnect"
android:layout_alignRight="@+id/buttonDisConnect"
android:layout_alignEnd="@+id/buttonDisConnect" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceSmall"
android:id="@+id/textViewLog"
android:typeface="monospace"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true"
android:layout_alignParentBottom="true"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="@+id/editTextHostname" />
</RelativeLayout>

View File

@@ -0,0 +1,75 @@
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin"
tools:context="com.vsmartcard.remotesmartcardreader.app.MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceSmall"
android:text="@string/vpcd_config_info"
android:id="@+id/textViewInfo"
android:layout_alignParentTop="true"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true" />
<EditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/editTextHostname"
android:layout_below="@+id/textViewInfo"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:hint="@string/hint_vpcd_hostname"
android:inputType="textUri"
android:layout_toLeftOf="@+id/editTextPort" />
<EditText
android:layout_width="80dp"
android:layout_height="wrap_content"
android:inputType="number"
android:ems="10"
android:id="@+id/editTextPort"
android:hint="@string/hint_vpcd_port"
android:layout_below="@+id/textViewInfo"
android:layout_alignRight="@+id/textViewInfo"
android:layout_alignEnd="@+id/textViewInfo" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/buttonDisConnect"
android:onClick="buttonOnClickDisConnect"
android:layout_below="@+id/editTextPort"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_toLeftOf="@+id/progressBar" />
<ProgressBar
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/progressBar"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true"
android:layout_alignTop="@+id/buttonDisConnect" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceSmall"
android:id="@+id/textViewLog"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:typeface="monospace"
android:textIsSelectable="true"
android:layout_below="@+id/buttonDisConnect" />
</RelativeLayout>

View File

@@ -0,0 +1,10 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:context="com.vsmartcard.remotesmartcardreader.app.MainActivity" >
<item android:id="@+id/action_settings"
android:title="@string/action_settings"
android:orderInCategory="100"
app:showAsAction="never" />
</menu>

View File

@@ -0,0 +1,6 @@
<resources>
<!-- Default screen margins, per the Android Design guidelines. -->
<dimen name="activity_horizontal_margin">16dp</dimen>
<dimen name="activity_vertical_margin">16dp</dimen>
</resources>

View File

@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Remote Smart Card Reader</string>
<string name="button_test_configuration">Connect</string>
<string name="button_stop_testing">Disconnect</string>
<string name="hint_vpcd_port">port</string>
<string name="hint_vpcd_hostname">VPCD hostname</string>
<string name="vpcd_config_info">Settings for <a href="http://frankmorgner.github.io/vsmartcard/virtualsmartcard/README.html">remote smart card reader driver (VPCD)</a> of the host computer.</string>
<string name="status_error">Error</string>
<string name="status_disconnected">Disconnected</string>
<string name="status_connected_to">Connected to</string>
<string name="status_atr">ATR</string>
<string name="status_capdu">C-APDU</string>
<string name="status_rapdu">R-APDU</string>
<string name="status_on">On</string>
<string name="status_off">Off</string>
<string name="status_reset">Reset</string>
<string name="status_unknown">Unknown Event</string>
<string name="status_tag_discovered">Contact-less card discovered.</string>
<string name="status_dummy_start">Using a dummy card to test the connection.</string>
<string name="menu_copy_to_clipboard">Copy</string>
<string name="action_settings">Settings</string>
<string name="verbose">Verbose logging</string>
<string name="error_tag_lost">Data transmission failed. Is the card still present?</string>
</resources>

View File

@@ -0,0 +1,8 @@
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
</style>
</resources>

View File

@@ -0,0 +1,6 @@
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<tech-list>
<!-- catches both, NfcA and NfcB -->
<tech>android.nfc.tech.IsoDep</tech>
</tech-list>
</resources>