├── .gitattribute
├── .gitignore
├── AndroidManifest.xml
├── proguard.cfg
├── project.properties
├── res
├── drawable-hdpi
│ └── ic_launcher.png
├── drawable-ldpi
│ └── ic_launcher.png
├── drawable-mdpi
│ └── ic_launcher.png
├── layout
│ ├── proxy.xml
│ ├── relay.xml
│ ├── save_dialog.xml
│ ├── save_tab.xml
│ └── selector.xml
├── menu
│ ├── data_context_menu.xml
│ ├── menu.xml
│ └── saved_context_menu.xml
├── values
│ └── strings.xml
└── xml
│ └── preferences.xml
└── src
└── org
└── eleetas
└── nfc
└── nfcproxy
├── DBHelper.java
├── ExportDialogFragment.java
├── NFCPrefs.java
├── NFCProxyActivity.java
├── NFCRelayActivity.java
├── NFCVars.java
├── SaveDialogFragment.java
├── SelectorActivity.java
├── SettingsActivity.java
├── SettingsActivityCompat.java
└── utils
├── BasicTagTechnologyWrapper.java
├── CryptoHelper.java
├── IOUtils.java
├── LogHelper.java
├── TagHelper.java
└── TextHelper.java
/.gitattribute:
--------------------------------------------------------------------------------
1 | # Set default behaviour, in case users don't have core.autocrlf set.
2 | * text=auto
3 |
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | bin/
2 | gen/
3 | /.classpath
4 | /.project
5 |
--------------------------------------------------------------------------------
/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
19 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/proguard.cfg:
--------------------------------------------------------------------------------
1 | -optimizationpasses 5
2 | -dontusemixedcaseclassnames
3 | -dontskipnonpubliclibraryclasses
4 | -dontpreverify
5 | -verbose
6 | -optimizations !code/simplification/arithmetic,!field/*,!class/merging/*
7 |
8 | -keep public class * extends android.app.Activity
9 | -keep public class * extends android.app.Application
10 | -keep public class * extends android.app.Service
11 | -keep public class * extends android.content.BroadcastReceiver
12 | -keep public class * extends android.content.ContentProvider
13 | -keep public class * extends android.app.backup.BackupAgentHelper
14 | -keep public class * extends android.preference.Preference
15 | -keep public class com.android.vending.licensing.ILicensingService
16 |
17 | -keepclasseswithmembernames class * {
18 | native ;
19 | }
20 |
21 | -keepclasseswithmembers class * {
22 | public (android.content.Context, android.util.AttributeSet);
23 | }
24 |
25 | -keepclasseswithmembers class * {
26 | public (android.content.Context, android.util.AttributeSet, int);
27 | }
28 |
29 | -keepclassmembers class * extends android.app.Activity {
30 | public void *(android.view.View);
31 | }
32 |
33 | -keepclassmembers enum * {
34 | public static **[] values();
35 | public static ** valueOf(java.lang.String);
36 | }
37 |
38 | -keep class * implements android.os.Parcelable {
39 | public static final android.os.Parcelable$Creator *;
40 | }
41 |
--------------------------------------------------------------------------------
/project.properties:
--------------------------------------------------------------------------------
1 | # This file is automatically generated by Android Tools.
2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED!
3 | #
4 | # This file must be checked in Version Control Systems.
5 | #
6 | # To customize properties used by the Ant build system use,
7 | # "ant.properties", and override values to adapt the script to your
8 | # project structure.
9 |
10 | # Project target.
11 | target=android-15
12 |
--------------------------------------------------------------------------------
/res/drawable-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nfcproxy/NFCProxy/8beea18bddc0d31e530a892457296a77efd7a435/res/drawable-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/res/drawable-ldpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nfcproxy/NFCProxy/8beea18bddc0d31e530a892457296a77efd7a435/res/drawable-ldpi/ic_launcher.png
--------------------------------------------------------------------------------
/res/drawable-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nfcproxy/NFCProxy/8beea18bddc0d31e530a892457296a77efd7a435/res/drawable-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/res/layout/proxy.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
12 |
13 |
18 |
19 |
23 |
24 |
25 |
29 |
30 |
34 |
35 |
38 |
39 |
40 |
46 |
47 |
48 |
49 |
50 |
55 |
56 |
64 |
65 |
66 |
70 |
71 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
--------------------------------------------------------------------------------
/res/layout/relay.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
12 |
13 |
17 |
18 |
22 |
23 |
24 |
28 |
29 |
34 |
35 |
42 |
43 |
104 |
105 |
106 |
107 |
108 |
--------------------------------------------------------------------------------
/res/layout/save_dialog.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
11 |
12 |
17 |
18 |
23 |
24 |
25 |
30 |
31 |
36 |
37 |
--------------------------------------------------------------------------------
/res/layout/save_tab.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
--------------------------------------------------------------------------------
/res/layout/selector.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
13 |
14 |
18 |
19 |
24 |
25 |
--------------------------------------------------------------------------------
/res/menu/data_context_menu.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/res/menu/menu.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/res/menu/saved_context_menu.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | NFCProxy
4 | Bad crypto?
5 | Bad password
6 | Cancel
7 | Config
8 | Connected to NFCRelay
9 | Connecting to NFCRelay
10 | Connection to NFCRelay failed
11 | Connection to NFCRelay timed out
12 | connection is funny...ha
13 | "Crypto error. Are both sides encrypting?"
14 | Data
15 | Debug Logging
16 | aaa.bbb.ccc.ddd
17 | 54321Password
18 | 9999
19 | Delete
20 | Prevents the screen from automatically shutting off.
21 | Enable debug logging (account numbers will be logged)
22 | Enable WiFi
23 | Encrypt communications
24 | Encrypt network traffic.
25 | equal
26 | Error getting id. Checking encryption settings.
27 | Export to file
28 | Export Filename
29 | Finished reading tag
30 | index to responses out of bounds
31 | IOException...socket error?
32 | IOException. Error writing to socket?
33 | IP
34 | The IP address or hostname of the relay
35 | Always keep screen on
36 | Lost connection tag?
37 | Lost PCD
38 | Lost tag
39 | 0x70 [Masked]
40 | Min password length is 8
41 | Mismatched protocol. Are both ends encrypting?
42 | Runs in relay mode if checked. Proxy mode if not checked.
43 | Name
44 | NDEF tag discovered
45 | NFCProxy
46 | NFCProxy connected, but NFCRelay has no tag
47 | NFCRelay
48 | NFCRelay not ready
49 | No data yet
50 | Not connected to PCD
51 | not equal
52 | OK
53 | P
54 | Password
55 | For encrypting traffic. NOTE: Stored in plaintext.
56 | Password not saved
57 | PCD
58 | PCD support not available.\nSwitching to Relay mode
59 | PCD support not available.\nUnpredictable behavior ahead
60 | Port
61 | Proxy mode
62 | Proxy mode not supported.\nSwitching to Relay mode
63 | Reader
64 | Relay lost tag
65 | Relay mode
66 | Relay connection settings\n
67 | Replay PCD
68 | Replaying PCD transactions on next scan
69 | Replay Tag
70 | Replaying tag transactions on next scan
71 | PCD type B not currently supported. Please report (really) reader model to dev team!
72 | Return to proxy mode
73 | Save
74 | Save PCD requests
75 | Save tag responses
76 | Save to database
77 | Saved
78 | Settings
79 | Socket error
80 | something happened
81 | Status
82 | Switching back to proxy mode
83 | T
84 | TAG
85 | TAG discovered
86 | TECH discovered
87 | Time
88 | Transaction Complete!
89 | Type: PCD\n
90 | Type: TAG\n
91 | Unexpected response
92 | Unexpected response. Are both sides encrypting?
93 | Unknown command
94 | Unknown host. Use settings to set relay IP address
95 | Unsupported tag type
96 | Waiting for tag...
97 | (Warning: Stored in plaintext)
98 |
--------------------------------------------------------------------------------
/res/xml/preferences.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/src/org/eleetas/nfc/nfcproxy/DBHelper.java:
--------------------------------------------------------------------------------
1 | package org.eleetas.nfc.nfcproxy;
2 |
3 | import java.io.ByteArrayOutputStream;
4 | import java.io.IOException;
5 | import java.io.ObjectOutputStream;
6 |
7 | import android.content.Context;
8 | import android.database.Cursor;
9 | import android.database.sqlite.SQLiteDatabase;
10 | import android.database.sqlite.SQLiteOpenHelper;
11 | import android.database.sqlite.SQLiteStatement;
12 |
13 | public class DBHelper extends SQLiteOpenHelper {
14 |
15 | private static final int DATABASE_VERSION = 1;
16 | private static final String DATABASE_NAME = "NFCProxyDB";
17 | private static final String CREATE_REPLAYS_TABLE = "CREATE TABLE replays(_id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT UNIQUE, transactions BLOB, type INTEGER, built_in INTEGER DEFAULT 0)";
18 | public static final int REPLAY_TAG = 0;
19 | public static final int REPLAY_PCD = 1;
20 |
21 | DBHelper(Context context){
22 | super(context, DATABASE_NAME, null, DATABASE_VERSION);
23 | }
24 |
25 | @Override
26 | public void onCreate(SQLiteDatabase db) {
27 | db.execSQL(CREATE_REPLAYS_TABLE);
28 | //built in transactions
29 | byte[][] vivoPayVisa = new byte[][] {{0x00, (byte)0xa4, 0x04, 0x00, 0x0e, 0x32, 0x50, 0x41, 0x59, 0x2e, 0x53, 0x59, 0x53, 0x2e, 0x44, 0x44, 0x46, 0x30, 0x31, 0x00}, {0x00, (byte)0xa4, 0x04, 0x00, 0x07, (byte)0xa0, 0x00, 0x00, 0x00, 0x03, 0x10, 0x10, 0x00}, {(byte)0x80, (byte)0xa8, 0x00, 0x00, 0x04, (byte)0x83, 0x02, (byte)0x80, 0x00, 0x00}, {0x00, (byte)0xb2, 0x01, 0x0c, 0x00}};
30 | byte[][] vivoPayMasterCard = new byte[][] {{0x00, (byte)0xa4, 0x04, 0x00, 0x0e, 0x32, 0x50, 0x41, 0x59, 0x2e, 0x53, 0x59, 0x53, 0x2e, 0x44, 0x44, 0x46, 0x30, 0x31, 0x00}, {0x00, (byte)0xa4, 0x04, 0x00, 0x07, (byte)0xa0, 0x00, 0x00, 0x00, 0x04, 0x10, 0x10, 0x00}, {(byte)0x80, (byte)0xa8, 0x00, 0x00, 0x02, (byte)0x83, 0x00, 0x00}, {0x00, (byte)0xb2, 0x01, 0x0c, 0x00}, {(byte)0x80, 0x2a, (byte)0x8e, (byte)0x80, 0x04, 0x00, 0x00, 0x00, 0x29, 0x00}};
31 | byte[][] vivoPayDiscover = new byte[][] {{0x00, (byte)0xa4, 0x04, 0x00, 0x0e, 0x32, 0x50, 0x41, 0x59, 0x2e, 0x53, 0x59, 0x53, 0x2e, 0x44, 0x44, 0x46, 0x30, 0x31, 0x00}, {0x00, (byte)0xa4, 0x04, 0x00, 0x07, (byte)0xa0, 0x00, 0x00, 0x03, 0x24, 0x10, 0x10, 0x00}, {(byte)0x80, (byte)0xa8, 0x00, 0x00, 0x03, (byte)0x83, 0x01, 0x63, 0x00}, {0x00, (byte)0xb2, 0x01, 0x0c, 0x00}};
32 | byte[][] vivoPayAmex = new byte[][] {{0x00, (byte)0xa4, 0x04, 0x00, 0x0e, 0x32, 0x50, 0x41, 0x59, 0x2e, 0x53, 0x59, 0x53, 0x2e, 0x44, 0x44, 0x46, 0x30, 0x31, 0x00}, {0x00, (byte)0xa4, 0x04, 0x00, 0x06, (byte)0xa0, 0x00, 0x00, 0x00, 0x25, 0x01, 0x00}, {(byte)0x80, (byte)0xa8, 0x00, 0x00, 0x02, (byte)0x83, 0x00, 0x00}, {0x00, (byte)0xb2, 0x01, 0x0c, 0x00}, {0x00, (byte)0xb2, 0x02, 0x0c, 0x00}, {0x00, (byte)0xb2, 0x03, 0x0c, 0x00}, {(byte)0x80, (byte)0xca, (byte)0x9f, 0x36, 0x00}, {(byte)0x80, (byte)0xae, (byte)0x80, 0x00, 0x04, 0x00, 0x00, 0x09, 0x03, 0x00}};
33 | saveTransactions(db, "VivoPay 4000 - Visa", vivoPayVisa, REPLAY_PCD, 1);
34 | saveTransactions(db, "VivoPay 4000 - MasterCard", vivoPayMasterCard, REPLAY_PCD, 1);
35 | saveTransactions(db, "VivoPay 4000 - Discover", vivoPayDiscover, REPLAY_PCD, 1);
36 | saveTransactions(db, "VivoPay 4000 - American Express", vivoPayAmex, REPLAY_PCD, 1);
37 | }
38 |
39 | @Override
40 | public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
41 | // TODO Auto-generated method stub
42 |
43 | }
44 |
45 | private long saveTransactions(SQLiteDatabase db,String name, byte[][] transactions, int type, int builtIn) {
46 | ByteArrayOutputStream baos = new ByteArrayOutputStream();
47 | ObjectOutputStream oos;
48 | try {
49 | oos = new ObjectOutputStream(baos);
50 | oos.writeObject(transactions);
51 | } catch (IOException e) {
52 | e.printStackTrace();
53 | }
54 |
55 | byte[] tBytes = baos.toByteArray();
56 | SQLiteStatement stmt = db.compileStatement("INSERT OR IGNORE INTO replays (name, transactions , type, built_in) values (?, ?, ?, ?)");
57 | stmt.bindString(1, name);
58 | stmt.bindBlob(2, tBytes);
59 | stmt.bindLong(3, type);
60 | stmt.bindLong(4, builtIn);
61 | return stmt.executeInsert();
62 | }
63 |
64 | public long saveTransactions(String name, byte[][] transactions, int type) {
65 | SQLiteDatabase wDB = getWritableDatabase();
66 | return saveTransactions(wDB, name, transactions, type, 0);
67 | }
68 |
69 | public Cursor getReplays() {
70 | SQLiteDatabase rDB = getReadableDatabase();
71 | return rDB.query("replays", null, null, null, null, null, null);
72 | }
73 |
74 | public int deleteReplay(String name) {
75 | SQLiteDatabase wDB = getWritableDatabase();
76 | SQLiteStatement stmt = wDB.compileStatement("DELETE FROM replays WHERE name = ? AND built_in = 0");
77 | stmt.bindString(1, name);
78 | return stmt.executeUpdateDelete();
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/src/org/eleetas/nfc/nfcproxy/ExportDialogFragment.java:
--------------------------------------------------------------------------------
1 | package org.eleetas.nfc.nfcproxy;
2 |
3 | import android.app.AlertDialog;
4 | import android.app.Dialog;
5 | import android.app.DialogFragment;
6 | import android.content.DialogInterface;
7 | import android.os.Bundle;
8 | import android.text.InputType;
9 | import android.widget.EditText;
10 |
11 | public class ExportDialogFragment extends DialogFragment {
12 | static ExportDialogFragment newInstance() {
13 | return new ExportDialogFragment();
14 | }
15 |
16 | /* (non-Javadoc)
17 | * @see android.app.DialogFragment#onCreateDialog(android.os.Bundle)
18 | */
19 | @Override
20 | public Dialog onCreateDialog(Bundle savedInstanceState) {
21 |
22 | final EditText input = new EditText(getActivity());
23 | input.setInputType(InputType.TYPE_CLASS_TEXT);
24 |
25 | return new AlertDialog.Builder(getActivity())
26 | .setTitle(R.string.export_title)
27 | .setMessage(R.string.warning_plaintext)
28 | .setView(input)
29 | .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener () {
30 |
31 | public void onClick(DialogInterface dialog, int which) {
32 | String filename = input.getText().toString();
33 | ((NFCProxyActivity)getActivity()).exportRun(filename);
34 | }
35 | })
36 | .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener () {
37 | public void onClick(DialogInterface dialog, int which) {
38 | }
39 | }).create();
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/org/eleetas/nfc/nfcproxy/NFCPrefs.java:
--------------------------------------------------------------------------------
1 | package org.eleetas.nfc.nfcproxy;
2 |
3 | import java.net.Inet4Address;
4 | import java.net.InetAddress;
5 | import java.net.NetworkInterface;
6 | import java.net.SocketException;
7 | import java.util.Enumeration;
8 |
9 | import org.eleetas.nfc.nfcproxy.NFCVars;
10 | import org.eleetas.nfc.nfcproxy.utils.CryptoHelper;
11 | import org.eleetas.nfc.nfcproxy.utils.LogHelper;
12 |
13 | import android.content.Context;
14 | import android.content.SharedPreferences;
15 | import android.graphics.Color;
16 | import android.os.Bundle;
17 | import android.preference.EditTextPreference;
18 | import android.preference.Preference;
19 | import android.preference.PreferenceFragment;
20 | import android.preference.Preference.OnPreferenceChangeListener;
21 | import android.preference.PreferenceManager;
22 | import android.text.SpannableString;
23 | import android.text.style.ForegroundColorSpan;
24 | import android.view.LayoutInflater;
25 | import android.view.View;
26 | import android.view.ViewGroup;
27 | import android.widget.Toast;
28 |
29 | public class NFCPrefs extends PreferenceFragment {
30 |
31 | /* (non-Javadoc)
32 | * @see android.preference.PreferenceFragment#onCreate(android.os.Bundle)
33 | */
34 | @Override
35 | public void onCreate(Bundle savedInstanceState) {
36 | super.onCreate(savedInstanceState);
37 |
38 | PreferenceManager.setDefaultValues(getActivity(), NFCVars.PREFERENCES, Context.MODE_PRIVATE, R.xml.preferences, true);
39 | PreferenceManager pMan = getPreferenceManager();
40 | pMan.setSharedPreferencesName(NFCVars.PREFERENCES);
41 | final SharedPreferences prefs = pMan.getSharedPreferences();
42 |
43 | //need to call this AFTER setting PreferenceManger stuff.
44 | addPreferencesFromResource(R.xml.preferences);
45 |
46 | final EditTextPreference password = (EditTextPreference) findPreference("passwordPref");
47 | password.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
48 |
49 | @Override
50 | public boolean onPreferenceChange(Preference preference, Object newValue) {
51 | if (newValue.toString().length() < 8) {
52 | password.getEditText().setError(NFCPrefs.this.getActivity().getString(R.string.min_password_length));
53 | SpannableString msg = new SpannableString(NFCPrefs.this.getActivity().getString(R.string.min_password_length));
54 | msg.setSpan(new ForegroundColorSpan(Color.RED) , 0, msg.length(), 0);
55 | password.setSummary(msg);
56 | Toast.makeText(NFCPrefs.this.getActivity().getBaseContext(), NFCPrefs.this.getActivity().getString(R.string.password_not_saved), Toast.LENGTH_LONG).show();
57 | return false;
58 | }
59 | password.getEditText().setError("");
60 | password.setSummary(getString(R.string.password_desc));
61 |
62 | //TODO: Not sure that salting is necessary since we're generating a key from the password and sending the salt in the clear
63 | prefs.edit().putString("saltPref", CryptoHelper.generateSalt()).commit();
64 | return true;
65 | }
66 |
67 | });
68 | }
69 |
70 | /* (non-Javadoc)
71 | * @see android.preference.PreferenceFragment#onCreateView(android.view.LayoutInflater, android.view.ViewGroup, android.os.Bundle)
72 | */
73 | @Override
74 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
75 |
76 | PreferenceManager.setDefaultValues(getActivity(), NFCVars.PREFERENCES, Context.MODE_PRIVATE, R.xml.preferences, true);
77 | PreferenceManager pMan = getPreferenceManager();
78 | pMan.setSharedPreferencesName(NFCVars.PREFERENCES);
79 | SharedPreferences prefs = pMan.getSharedPreferences();
80 | EditTextPreference ip = (EditTextPreference) findPreference("ipPref");
81 | if (prefs.getBoolean("relayPref", true)) {
82 | ip.setEnabled(false);
83 | String ipAddr = "";
84 | try {
85 | //assume IP is on wlan0 interface
86 | NetworkInterface net = NetworkInterface.getByName("wlan0");
87 | if (net != null) {
88 | for (Enumeration enumIpAddr = net.getInetAddresses(); enumIpAddr.hasMoreElements();) {
89 | InetAddress inetAddress = enumIpAddr.nextElement();
90 | if (inetAddress instanceof Inet4Address) {
91 | ipAddr = inetAddress.getHostAddress().toString();
92 | break;
93 | }
94 | }
95 | }
96 | } catch (SocketException e) {
97 | LogHelper.log(getActivity(), "Error getting local IPs: " + e.toString());
98 | }
99 |
100 | if (ipAddr.length() == 0) {
101 | ip.setSummary(getString(R.string.enable_wifi));
102 | }
103 | else {
104 | ip.setSummary(ipAddr);
105 | }
106 | }
107 | else {
108 | ip.setEnabled(true);
109 | ip.setSummary(getString(R.string.ip_desc));
110 | }
111 |
112 | return super.onCreateView(inflater, container, savedInstanceState);
113 | }
114 |
115 |
116 | }
117 |
--------------------------------------------------------------------------------
/src/org/eleetas/nfc/nfcproxy/NFCProxyActivity.java:
--------------------------------------------------------------------------------
1 | package org.eleetas.nfc.nfcproxy;
2 |
3 | import org.eleetas.nfc.nfcproxy.NFCVars;
4 | import org.eleetas.nfc.nfcproxy.utils.CryptoHelper;
5 | import org.eleetas.nfc.nfcproxy.utils.IOUtils;
6 | import org.eleetas.nfc.nfcproxy.utils.TagHelper;
7 | import org.eleetas.nfc.nfcproxy.utils.TextHelper;
8 | import org.eleetas.nfc.nfcproxy.utils.LogHelper;
9 | import org.eleetas.nfc.nfcproxy.utils.BasicTagTechnologyWrapper;
10 |
11 | import java.io.BufferedInputStream;
12 | import java.io.BufferedOutputStream;
13 | import java.io.ByteArrayInputStream;
14 | import java.io.File;
15 | import java.io.FileWriter;
16 | import java.io.IOException;
17 | import java.io.ObjectInputStream;
18 | import java.io.PrintWriter;
19 | import java.io.StreamCorruptedException;
20 | import java.io.StringWriter;
21 | import java.io.UnsupportedEncodingException;
22 | import java.lang.reflect.InvocationTargetException;
23 | import java.lang.reflect.Method;
24 | import java.net.ConnectException;
25 | import java.net.InetSocketAddress;
26 | import java.net.Socket;
27 | import java.net.SocketException;
28 | import java.net.SocketTimeoutException;
29 | import java.net.UnknownHostException;
30 | import java.security.spec.KeySpec;
31 | import java.util.ArrayList;
32 | import java.util.Arrays;
33 |
34 | import javax.crypto.SecretKey;
35 | import javax.crypto.SecretKeyFactory;
36 | import javax.crypto.spec.PBEKeySpec;
37 | import javax.crypto.spec.SecretKeySpec;
38 |
39 | import android.annotation.SuppressLint;
40 | import android.app.Activity;
41 | import android.app.PendingIntent;
42 | import android.content.Context;
43 | import android.content.Intent;
44 | import android.content.IntentFilter;
45 | import android.content.SharedPreferences;
46 | import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
47 | import android.database.Cursor;
48 | import android.graphics.Color;
49 | import android.nfc.NfcAdapter;
50 | import android.nfc.Tag;
51 | import android.nfc.TagLostException;
52 | import android.nfc.tech.IsoDep;
53 | import android.os.AsyncTask;
54 | import android.os.Build;
55 | import android.os.Bundle;
56 | import android.os.Environment;
57 | import android.os.Parcelable;
58 | import android.os.PowerManager;
59 | import android.os.PowerManager.WakeLock;
60 | import android.text.SpannableString;
61 | import android.text.TextUtils;
62 | import android.text.style.UnderlineSpan;
63 | import android.util.Base64;
64 | import android.view.ActionMode;
65 | import android.view.LayoutInflater;
66 | import android.view.Menu;
67 | import android.view.MenuInflater;
68 | import android.view.MenuItem;
69 | import android.view.View;
70 | import android.view.View.OnClickListener;
71 | import android.view.ViewGroup;
72 | import android.view.ViewGroup.LayoutParams;
73 | import android.widget.CursorAdapter;
74 | import android.widget.ListView;
75 | import android.widget.ScrollView;
76 | import android.widget.TabHost;
77 | import android.widget.TableLayout;
78 | import android.widget.TableRow;
79 | import android.widget.TextView;
80 | import android.widget.Toast;
81 |
82 | @SuppressLint("NewApi")
83 | public class NFCProxyActivity extends Activity {
84 |
85 | private static final int PROXY_MODE = 0;
86 | private static final int REPLAY_PCD_MODE = 1;
87 | private static final int REPLAY_TAG_MODE = 2;
88 | private final int CONNECT_TIMEOUT = 5000;
89 | private final String DEFAULT_SALT = "kAD/gd6tvu8=";
90 |
91 |
92 | private ScrollView mStatusTab;
93 | private TextView mStatusView;
94 | private TextView mDataView;
95 | private ScrollView mDataTab;
96 | private TableLayout mDataTable;
97 | private TabHost mTabHost;
98 | private ListView mSavedList;
99 | private Menu mOptionsMenu;
100 | private ActionMode mActionMode;
101 |
102 | private InetSocketAddress mSockAddr;
103 | private DBHelper mDBHelper;
104 | private SecretKey mSecret = null;
105 | private String mSalt = null;
106 |
107 | private View mSelectedSaveView;
108 | private int mSelectedId = 0;
109 | private Bundle mSessions = new Bundle();
110 | private Bundle mReplaySession;
111 |
112 | private WakeLock mWakeLock;
113 | private int mMode = PROXY_MODE;
114 |
115 | private boolean mDebugLogging = false;
116 | private int mServerPort;
117 | private String mServerIP;
118 | private boolean mEncrypt = true;
119 | private boolean mMask = false;
120 |
121 | private ActionMode.Callback mTransactionsActionModeCallback = new ActionMode.Callback() {
122 |
123 | @Override
124 | public boolean onCreateActionMode(ActionMode mode, Menu menu) {
125 | MenuInflater inflater = mode.getMenuInflater();
126 | inflater.inflate(R.menu.data_context_menu, menu);
127 | return true;
128 | }
129 |
130 | @Override
131 | public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
132 | return false; // Return false if nothing is done
133 | }
134 |
135 | @Override
136 | public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
137 | mReplaySession = mSessions.getBundle(String.valueOf(mSelectedId));
138 | switch (item.getItemId()) {
139 | case R.id.replayPcd:
140 | enablePCDReplay();
141 | mode.finish();
142 | return true;
143 | case R.id.replayTag:
144 | enableTagReplay();
145 | mode.finish();
146 | return true;
147 | case R.id.delete:
148 | deleteRun();
149 | mode.finish();
150 | return true;
151 | case R.id.save:
152 | SaveDialogFragment.newInstance().show(getFragmentManager(), "savedialog");
153 | mode.finish();
154 | return true;
155 | case R.id.export:
156 | ExportDialogFragment.newInstance().show(getFragmentManager(), "exportdialog");
157 | mode.finish();
158 | return true;
159 | default:
160 | return false;
161 | }
162 | }
163 |
164 | @Override
165 | public void onDestroyActionMode(ActionMode mode) {
166 | mActionMode = null;
167 | }
168 | };
169 |
170 | private ActionMode.Callback mSavedActionModeCallback = new ActionMode.Callback() {
171 |
172 | @Override
173 | public boolean onCreateActionMode(ActionMode mode, Menu menu) {
174 | MenuInflater inflater = mode.getMenuInflater();
175 | inflater.inflate(R.menu.saved_context_menu, menu);
176 | return true;
177 | }
178 |
179 | @Override
180 | public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
181 | return false; // Return false if nothing is done
182 | }
183 |
184 | @Override
185 | public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
186 | switch (item.getItemId()) {
187 | case R.id.deleteSaved:
188 | deleteSaved();
189 | mode.finish();
190 | return true;
191 | default:
192 | return false;
193 | }
194 | }
195 |
196 | @Override
197 | public void onDestroyActionMode(ActionMode mode) {
198 | mActionMode = null;
199 | }
200 | };
201 |
202 | @Override
203 | public boolean onCreateOptionsMenu(Menu menu) {
204 | MenuInflater inflater = getMenuInflater();
205 | inflater.inflate(R.menu.menu, menu);
206 | mOptionsMenu = menu;
207 | if (mMode == PROXY_MODE) {
208 | mOptionsMenu.getItem(0).setVisible(false);
209 | mOptionsMenu.getItem(1).setVisible(false);
210 | mOptionsMenu.getItem(2).setVisible(false);
211 | }
212 | else if (mMode == REPLAY_PCD_MODE) {
213 | mOptionsMenu.getItem(0).setVisible(false);
214 | mOptionsMenu.getItem(1).setVisible(true);
215 | mOptionsMenu.getItem(2).setVisible(true);
216 | }
217 | else if (mMode == REPLAY_TAG_MODE) {
218 | mOptionsMenu.getItem(0).setVisible(true);
219 | mOptionsMenu.getItem(1).setVisible(false);
220 | mOptionsMenu.getItem(2).setVisible(true);
221 | }
222 | return true;
223 | }
224 |
225 | @Override
226 | public boolean onOptionsItemSelected(MenuItem item) {
227 | switch(item.getItemId()) {
228 | case R.id.settingsButton:
229 | if (Build.VERSION.SDK_INT 0) {
509 | //TODO: ...might be race condition here XXX
510 | mSessions.putBundle(String.valueOf(mSessions.size()), session);
511 | addLineBreak(mSessions.size() - 1);
512 | mDataTab.fullScroll(ScrollView.FOCUS_DOWN);
513 | mDataView = null;
514 | }
515 | }
516 | });
517 | }
518 |
519 | private void updateStatus(CharSequence msg) {
520 | mStatusView.append(TextUtils.concat(msg, "\n"));
521 | mStatusTab.post(new Runnable() {
522 | @Override
523 | public void run() {
524 | mStatusTab.fullScroll(ScrollView.FOCUS_DOWN);
525 | }
526 | });
527 | }
528 |
529 | private void updateData(CharSequence msg) {
530 | mDataView.append(TextUtils.concat(msg, "\n"));
531 | mDataTab.post(new Runnable() {
532 | @Override
533 | public void run() {
534 | mDataTab.fullScroll(ScrollView.FOCUS_DOWN);
535 | }
536 | });
537 | }
538 |
539 | private void enablePCDReplay() {
540 | mMode = REPLAY_PCD_MODE;
541 | updateStatus(getString(R.string.waiting));
542 | mOptionsMenu.getItem(0).setVisible(false);
543 | mOptionsMenu.getItem(1).setVisible(true);
544 | mOptionsMenu.getItem(2).setVisible(true);
545 | mTabHost.setCurrentTab(0); //Data tab
546 | Toast.makeText(this, getString(R.string.replay_pcd_on_next), Toast.LENGTH_LONG).show();
547 | }
548 |
549 | private void enableTagReplay() {
550 | mMode = REPLAY_TAG_MODE;
551 | updateStatus("Waiting for PCD");
552 | mOptionsMenu.getItem(0).setVisible(true);
553 | mOptionsMenu.getItem(1).setVisible(false);
554 | mOptionsMenu.getItem(2).setVisible(true);
555 | mTabHost.setCurrentTab(0); //Data tab
556 | Toast.makeText(this, getString(R.string.replay_tag_on_next), Toast.LENGTH_LONG).show();
557 | }
558 |
559 | //TODO: do in separate thread
560 | private void doReplayPCD(Tag tag, Bundle pcdRequests, Bundle tagTransactions) {
561 | Bundle responses = new Bundle();
562 | BasicTagTechnologyWrapper tagTech = null;
563 | try {
564 | //TODO:add support for more tag types
565 | Class[] supportedTags = new Class[] { IsoDep.class };
566 | String[] tech = tag.getTechList();
567 | for (String s: tech) {
568 |
569 | for(Class c: supportedTags) {
570 | if (s.equals(c.getName())) {
571 | try {
572 | tagTech = new BasicTagTechnologyWrapper(tag, c.getName());
573 | break;
574 | } catch (IllegalArgumentException e) {
575 | log(e);
576 | } catch (ClassNotFoundException e) {
577 | log(e);
578 | } catch (NoSuchMethodException e) {
579 | log(e);
580 | } catch (IllegalAccessException e) {
581 | log(e);
582 | } catch (InvocationTargetException e) {
583 | log(e);
584 | }
585 | }
586 | }
587 | }
588 | if (tagTech != null) {
589 | tagTech.connect();
590 | boolean connected = tagTech.isConnected();
591 | log("isConnected: " + connected);
592 | if (!connected) return;
593 |
594 | //first store ID
595 | responses.putByteArray(String.valueOf(0), tag.getId());
596 | String tagStr = getString(R.string.tag) + ": ";
597 | String pcdStr = getString(R.string.pcd) + ": ";
598 | SpannableString msg = new SpannableString(tagStr + TextHelper.byteArrayToHexString(tag.getId()));
599 | msg.setSpan(new UnderlineSpan(), 0, 4, 0);
600 | updateData(msg);
601 | boolean foundCC = false;
602 | for(int i=0; i < pcdRequests.size(); i++) {
603 | if (foundCC) {
604 | updateData(""); //print newline. this will probably cause formatting problems later
605 | }
606 | byte[] tmp = pcdRequests.getByteArray(String.valueOf(i));
607 | msg = new SpannableString(pcdStr + TextHelper.byteArrayToHexString(tmp));
608 | msg.setSpan(new UnderlineSpan(), 0, 4, 0);
609 | updateData(msg);
610 | byte[] reply = tagTech.transceive(tmp);
611 |
612 | responses.putByteArray(String.valueOf(i+1), reply);
613 | if (mMask && reply != null && reply[0] == 0x70) {
614 | msg = new SpannableString(tagStr + getString(R.string.masked));
615 | }
616 | else {
617 | msg = new SpannableString(tagStr + TextHelper.byteArrayToHexString(reply));
618 | }
619 | msg.setSpan(new UnderlineSpan(), 0, 4, 0);
620 | updateData(msg);
621 |
622 | if (tagTransactions != null) {
623 | if (i + 1 < tagTransactions.size() ) {
624 | if (Arrays.equals(reply, tagTransactions.getByteArray(String.valueOf(i + 1)))) {
625 | log(getString(R.string.equal));
626 | updateStatus(getString(R.string.equal));
627 | }
628 | else {
629 | log(getString(R.string.not_equal));
630 | log("org: " + TextHelper.byteArrayToHexString(tagTransactions.getByteArray(String.valueOf(i + 1))));
631 | log("new : " + TextHelper.byteArrayToHexString(reply));
632 | updateStatus(getString(R.string.not_equal));
633 | updateStatus("org: " + TextHelper.byteArrayToHexString(tagTransactions.getByteArray(String.valueOf(i + 1))));
634 | updateStatus("new : " + TextHelper.byteArrayToHexString(reply));
635 | }
636 | }
637 | else {
638 | log("index to responses out of bounds");
639 | updateStatus(getString(R.string.index_out_bounds));
640 | }
641 | }
642 |
643 | if (reply != null && reply[0] == 0x70) {
644 | updateData("\n" + TagHelper.parseCC(reply, pcdRequests.getByteArray(String.valueOf(i - 1)), mMask));
645 | foundCC = true;
646 | if (i == pcdRequests.size() - 1) {
647 | log(getString(R.string.finished_reading));
648 | updateStatus(getString(R.string.finished_reading));
649 | }
650 | }
651 | else if (reply != null && reply.length > 3 && reply[0] == 0x77 && reply[2] == (byte)0x9f) {
652 | updateData("\n" + TagHelper.parseCryptogram(reply, tmp)); //previous pcdRequest
653 | log(getString(R.string.finished_reading));
654 | updateStatus(getString(R.string.finished_reading));
655 | }
656 |
657 | }
658 |
659 | }
660 | else {
661 | log(getString(R.string.unsupported_tag));
662 | updateStatus(getString(R.string.unsupported_tag));
663 | }
664 | } catch (IllegalStateException e) {
665 | log(e);
666 | updateStatus(e.toString());
667 | } catch (IOException e) {
668 | log(e);
669 | updateStatus(e.toString());
670 | }
671 | finally {
672 | storeTransactionsAndBreak(pcdRequests, responses);
673 | if (tagTech != null) {
674 | try {
675 | tagTech.close();
676 | } catch (IOException e) {
677 | log(e);
678 | }
679 | }
680 | }
681 |
682 | //log(getString(R.string.lost_connection));
683 | //updateStatus(getString(R.string.lost_connection));
684 | }
685 |
686 | //TODO: do in separate thread
687 | private void doReplayTag(Tag tag, Bundle tagTransactions, Bundle pcdRequests) {
688 | Bundle requests = new Bundle();
689 |
690 | try {
691 | //TODO:PCD hack. Add support for PCD B
692 | Class cls = Class.forName(NFCVars.ISO_PCDA_CLASS);
693 | Method meth = cls.getMethod("get", new Class[]{Tag.class});
694 | Object ipcd = meth.invoke(null, tag);
695 | meth = cls.getMethod("connect", null);
696 | meth.invoke(ipcd, null);
697 | meth = cls.getMethod("isConnected", null);
698 | boolean connected = (Boolean) meth.invoke(ipcd, null);
699 | log("isConnected: " + connected);
700 | if (!connected) {
701 | log("Not connected to PCD");
702 | //updateStatus("Not connected to PCD");
703 | //return;
704 | }
705 | else {
706 | meth = cls.getMethod("transceive", new Class[]{byte[].class});
707 | String tagStr = getString(R.string.tag) + ": ";
708 | String pcdStr = getString(R.string.pcd) + ": ";
709 |
710 | for (int i=0; i < tagTransactions.size(); i++) {
711 | byte []tmp = tagTransactions.getByteArray(String.valueOf(i));
712 | SpannableString msg;
713 | if (mMask && tmp != null && tmp[0] == 0x70) {
714 | msg = new SpannableString(tagStr + getString(R.string.masked));
715 | }
716 | else {
717 | msg = new SpannableString(tagStr + TextHelper.byteArrayToHexString(tmp));
718 | }
719 |
720 | msg.setSpan(new UnderlineSpan(), 0, 4, 0);
721 | updateData(msg);
722 | byte[] reply = (byte[]) meth.invoke(ipcd, tmp);
723 |
724 | requests.putByteArray(String.valueOf(i), reply);
725 | msg = new SpannableString(pcdStr + TextHelper.byteArrayToHexString(reply));
726 | msg.setSpan(new UnderlineSpan(), 0, 4, 0);
727 | updateData(msg);
728 |
729 | if (pcdRequests != null) {
730 | if (i < pcdRequests.size() ) {
731 | if (Arrays.equals(reply, pcdRequests.getByteArray(String.valueOf(i)))) {
732 | log(getString(R.string.equal));
733 | updateStatus(getString(R.string.equal));
734 | }
735 | else {
736 | log(getString(R.string.not_equal));
737 | log("org: " + TextHelper.byteArrayToHexString(pcdRequests.getByteArray(String.valueOf(i))));
738 | log("new : " + TextHelper.byteArrayToHexString(reply));
739 | updateStatus(getString(R.string.equal));
740 | updateStatus("org: " + TextHelper.byteArrayToHexString(pcdRequests.getByteArray(String.valueOf(i))));
741 | updateStatus("new : " + TextHelper.byteArrayToHexString(reply));
742 |
743 | //TODO:
744 | //attempt to find a matching response if it exists. This probably doesn't work.
745 | //this will also break replay mode unless new sequences are added to tagTransactions
746 | for (int k = 0; k < pcdRequests.size(); k++ ) {
747 | if (k == i) continue;
748 | if (Arrays.equals(reply, pcdRequests.getByteArray(String.valueOf(k)))) {
749 | i = k;
750 | i = k - 1;
751 | log("found matching response. replay of this run is probably broken.");
752 | break;
753 | }
754 | }
755 | }
756 | }
757 | else {
758 | log("index to requests out of bounds");
759 | updateStatus("index to requests out of bounds");
760 | }
761 | }
762 | }
763 | }
764 | }
765 | catch (InvocationTargetException e) {
766 | Throwable cause = e.getCause();
767 | if (cause instanceof IOException && cause.getMessage() != null && cause.getMessage().equals("Transceive failed")) {
768 | log("transaction complete");
769 | updateStatus("transaction complete");
770 | return;
771 | }
772 | else {
773 | log(e);
774 | }
775 | }
776 | catch (Exception e) { //TODO:
777 | log(e);
778 | updateStatus(e.toString());
779 | }
780 | finally {
781 | storeTransactionsAndBreak(requests, tagTransactions);
782 | }
783 | log("Lost connection to PCD?");
784 | updateStatus("Lost connection to PCD?");
785 | }
786 |
787 | private class ProxyTask extends AsyncTask {
788 |
789 | /* (non-Javadoc)
790 | * @see android.os.AsyncTask#onPostExecute(java.lang.Object)
791 | */
792 | @Override
793 | protected void onPostExecute(Void result) {
794 | super.onPostExecute(result);
795 | }
796 |
797 | /* (non-Javadoc)
798 | * @see android.os.AsyncTask#onCancelled(java.lang.Object)
799 | */
800 | @Override
801 | protected void onCancelled(Void result) {
802 | // TODO Auto-generated method stub
803 | super.onCancelled(result);
804 | }
805 |
806 | @Override
807 | protected Void doInBackground(Tag... params) {
808 | log("doInBackground start");
809 | Socket clientSocket = null;
810 | BufferedOutputStream clientOS = null;
811 | BufferedInputStream clientIS = null;
812 | long startTime = System.currentTimeMillis();
813 |
814 | try {
815 |
816 | log(getString(R.string.connecting_to_relay));
817 | updateStatusUI(getString(R.string.connecting_to_relay));
818 |
819 | mSockAddr = new InetSocketAddress(mServerIP, mServerPort);
820 | clientSocket = new Socket();
821 | clientSocket.connect(mSockAddr, CONNECT_TIMEOUT);
822 | clientOS = new BufferedOutputStream (clientSocket.getOutputStream());
823 | clientIS = new BufferedInputStream(clientSocket.getInputStream());
824 | log(getString(R.string.connected_to_relay));
825 | updateStatusUI(getString(R.string.connected_to_relay));
826 |
827 | Bundle requests = new Bundle();
828 | Bundle responses = new Bundle();
829 | try {
830 | log("sending ready");
831 | IOUtils.sendSocket((NFCVars.READY + "\n").getBytes("UTF-8"), clientOS, null, false);
832 | String line = IOUtils.readLine(clientIS);
833 | log("command: " + line);
834 | if (line.equals(NFCVars.NOT_READY)) {
835 | updateStatusUI(getString(R.string.nfcrelay_not_ready));
836 | log(getString(R.string.nfcrelay_not_ready));
837 | return null;
838 | } else if (!line.equals(NFCVars.OPTIONS)) {
839 | updateStatusUI(getString(R.string.unknown_command));
840 | log(getString(R.string.unknown_command));
841 | return null;
842 | }
843 | if (mEncrypt) {
844 |
845 | IOUtils.sendSocket((NFCVars.ENCRYPT + "\n").getBytes("UTF-8"), clientOS, null, false);
846 | IOUtils.sendSocket(Base64.decode(mSalt, Base64.DEFAULT), clientOS, null, false);
847 |
848 | if (mSecret == null) {
849 | mSecret = generateSecretKey();
850 | }
851 | byte[] verify = IOUtils.readSocket(clientIS, mSecret, mEncrypt);
852 | if (verify == null) {
853 | updateStatusUI(getString(R.string.unexpected_response_encrypting));
854 | log(getString(R.string.unexpected_response_encrypting));
855 | log(TextHelper.byteArrayToHexString(verify));
856 | return null;
857 | }
858 | else if (!new String(verify, "UTF-8").equals(NFCVars.VERIFY)) {
859 | updateStatusUI(getString(R.string.bad_password));
860 | log(getString(R.string.bad_password));
861 | log(TextHelper.byteArrayToHexString(verify));
862 | IOUtils.sendSocket(NFCVars.BAD_PASSWORD.getBytes("UTF-8"), clientOS, mSecret, mEncrypt);
863 | return null;
864 | }
865 | IOUtils.sendSocket(NFCVars.OK.getBytes("UTF-8"), clientOS, mSecret, mEncrypt);
866 |
867 | }
868 | else {
869 | IOUtils.sendSocket((NFCVars.CLEAR + "\n").getBytes("UTF-8"), clientOS, null, false);
870 | }
871 |
872 | log("getting id");
873 | byte[] id = IOUtils.readSocket(clientIS, mSecret, mEncrypt);
874 | if (id == null) {
875 | updateStatusUI(getString(R.string.error_getting_id));
876 | log(getString(R.string.error_getting_id));
877 | return null;
878 | }
879 | log("response: " + TextHelper.byteArrayToHexString(id));
880 | log(new String(id));
881 |
882 | byte[] pcdRequest = null;
883 | byte[] cardResponse = null;
884 | String tagStr = getString(R.string.tag) + ": ";
885 | String pcdStr = getString(R.string.pcd) + ": ";
886 | try {
887 | //TODO:PCD hack. Add support for PCD B
888 | Class cls = Class.forName(NFCVars.ISO_PCDA_CLASS);
889 | /*
890 | methods
891 | 05-14 16:49:03.229: D/NFCProxy(3642): close
892 | 05-14 16:49:03.229: D/NFCProxy(3642): connect
893 | 05-14 16:49:03.229: D/NFCProxy(3642): equals
894 | 05-14 16:49:03.229: D/NFCProxy(3642): get
895 | 05-14 16:49:03.229: D/NFCProxy(3642): getClass
896 | 05-14 16:49:03.229: D/NFCProxy(3642): getMaxTransceiveLength
897 | 05-14 16:49:03.229: D/NFCProxy(3642): getTag
898 | 05-14 16:49:03.229: D/NFCProxy(3642): hashCode
899 | 05-14 16:49:03.229: D/NFCProxy(3642): isConnected
900 | 05-14 16:49:03.229: D/NFCProxy(3642): notify
901 | 05-14 16:49:03.232: D/NFCProxy(3642): notifyAll
902 | 05-14 16:49:03.232: D/NFCProxy(3642): reconnect
903 | 05-14 16:49:03.232: D/NFCProxy(3642): toString
904 | 05-14 16:49:03.232: D/NFCProxy(3642): transceive
905 | 05-14 16:49:03.232: D/NFCProxy(3642): wait
906 | 05-14 16:49:03.232: D/NFCProxy(3642): wait
907 | 05-14 16:49:03.232: D/NFCProxy(3642): wait
908 |
909 | https://github.com/CyanogenMod/android_frameworks_base/blob/ics/core/java/android/nfc/tech/IsoPcdA.java
910 | */
911 | Method meth = cls.getMethod("get", new Class[]{Tag.class});
912 | Object ipcd = meth.invoke(null, params[0]);
913 | meth = cls.getMethod("connect", null);
914 | meth.invoke(ipcd, null);
915 | meth = cls.getMethod("isConnected", null);
916 | boolean connected = (Boolean) meth.invoke(ipcd, null);
917 | log("isConnected: " + connected);
918 | if (!connected) {
919 | log(getString(R.string.not_connected_to_pcd));
920 | updateStatusUI(getString(R.string.not_connected_to_pcd));
921 | return null;
922 | }
923 |
924 | meth = cls.getMethod("transceive", new Class[]{byte[].class}); //TODO: check against getMaxTransceiveLength()
925 |
926 | pcdRequest = (byte[])meth.invoke(ipcd, id);
927 |
928 | SpannableString msg = new SpannableString(tagStr + TextHelper.byteArrayToHexString(id));
929 | msg.setSpan(new UnderlineSpan(), 0, 4, 0);
930 | responses.putByteArray(String.valueOf(responses.size()), id);
931 | updateDataUI(msg);
932 | log("sent id to pcd: " + TextHelper.byteArrayToHexString(id));
933 |
934 | msg = new SpannableString(pcdStr + TextHelper.byteArrayToHexString(pcdRequest));
935 | msg.setSpan(new UnderlineSpan(), 0, 4, 0);
936 | requests.putByteArray(String.valueOf(requests.size()), pcdRequest);
937 | updateDataUI(msg);
938 | log("response from PCD: " + TextHelper.byteArrayToHexString(pcdRequest));
939 | log(new String(pcdRequest));
940 | do {
941 |
942 | IOUtils.sendSocket(pcdRequest, clientOS, mSecret, mEncrypt);
943 | log("sent response to relay/card");
944 |
945 | cardResponse = IOUtils.readSocket(clientIS, mSecret, mEncrypt);
946 | if (cardResponse != null) {
947 |
948 | if (new String(cardResponse, "UTF-8").equals("Relay lost tag") ) {
949 | updateStatusUI(getString(R.string.relay_lost_tag));
950 | log(getString(R.string.relay_lost_tag));
951 | break;
952 | }
953 | }
954 | else {
955 | updateStatusUI(getString(R.string.bad_crypto));
956 | log(getString(R.string.bad_crypto));
957 | break;
958 | }
959 |
960 | log("relay/card response: " + TextHelper.byteArrayToHexString(cardResponse));
961 | log(new String(cardResponse));
962 |
963 |
964 | log("sending card response to PCD");
965 | if (mMask && cardResponse[0] == 0x70) {
966 | msg = new SpannableString(tagStr + getString(R.string.masked));
967 | }
968 | else {
969 | msg = new SpannableString(tagStr + TextHelper.byteArrayToHexString(cardResponse));
970 | }
971 |
972 | msg.setSpan(new UnderlineSpan(), 0, 4, 0);
973 | responses.putByteArray(String.valueOf(responses.size()), cardResponse);
974 | updateDataUI(msg);
975 | if (cardResponse[0] == 0x70 || cardResponse[0] == 0x77) {
976 | try {
977 | pcdRequest = (byte[])meth.invoke(ipcd, cardResponse);
978 | } catch (InvocationTargetException e) {
979 | if (e.getCause() instanceof IOException && e.getCause().getMessage().equals("Transceive failed")) {
980 | //update UI only after sending cardResponse to PCD
981 | if (cardResponse[0] == 0x70) {
982 | updateDataUI("\n" + TagHelper.parseCC(cardResponse, requests.getByteArray(String.valueOf(requests.size() - 2)), mMask));
983 | }
984 | else if (cardResponse.length > 3 && cardResponse[0] == 0x77 && cardResponse[2] == (byte)0x9f) {
985 | updateDataUI("\n" + TagHelper.parseCryptogram(cardResponse, pcdRequest)); //previous pcdRequest
986 | }
987 | updateDataUI(getString(R.string.time) + ": " + (System.currentTimeMillis() - startTime));
988 | log(getString(R.string.transaction_complete));
989 | updateStatusUI(getString(R.string.transaction_complete));
990 | break;
991 | }
992 | throw e;
993 | }
994 | if (cardResponse[0] == 0x70) {
995 | updateDataUI("\n" + TagHelper.parseCC(cardResponse, requests.getByteArray(String.valueOf(requests.size() - 2)), mMask) + "\n");
996 | }
997 | }
998 | else {
999 | pcdRequest = (byte[])meth.invoke(ipcd, cardResponse);
1000 | }
1001 | requests.putByteArray(String.valueOf(requests.size()), pcdRequest);
1002 | msg = new SpannableString(pcdStr + TextHelper.byteArrayToHexString(pcdRequest));
1003 | msg.setSpan(new UnderlineSpan(), 0, 4, 0);
1004 | updateDataUI(msg);
1005 |
1006 | log("response from PCD: " + TextHelper.byteArrayToHexString(pcdRequest));
1007 |
1008 | } while (pcdRequest != null);
1009 | }
1010 | catch(ClassNotFoundException e) {
1011 | log(e);
1012 | updateStatusUI("ClassNotFoundException");
1013 | }
1014 | catch(NoSuchMethodException e) {
1015 | log(e);
1016 | updateStatusUI("NoSuchMethodException");
1017 | }
1018 | catch (InvocationTargetException e) {
1019 |
1020 | if (e instanceof InvocationTargetException) {
1021 | if (((InvocationTargetException) e).getCause() instanceof TagLostException) {
1022 | log(getString(R.string.lost_pcd));
1023 | updateStatusUI(getString(R.string.lost_pcd));
1024 | }
1025 | }
1026 | else {
1027 | log(e);
1028 | updateStatusUI("InvocationTargetException");
1029 | }
1030 | }
1031 | catch(IllegalAccessException e) {
1032 | log(e);
1033 | updateStatusUI("IllegalAccessException");
1034 | } catch (IOException e) {
1035 | log(e);
1036 | updateStatusUI(getString(R.string.ioexception_error_writing_socket));
1037 | }
1038 | finally {
1039 | try {
1040 | //TODO:PCD hack. Add support for PCD B
1041 | Class cls = Class.forName(NFCVars.ISO_PCDA_CLASS);
1042 | Method meth = cls.getMethod("get", new Class[]{Tag.class});
1043 | Object ipcd = meth.invoke(null, params[0]);
1044 | meth = cls.getMethod("close", null);
1045 | meth.invoke(ipcd, null);
1046 | }
1047 | catch(ClassNotFoundException e) {
1048 | log(e);
1049 | updateStatusUI("ClassNotFoundException");
1050 | }
1051 | catch(NoSuchMethodException e) {
1052 | log(e);
1053 | updateStatusUI("NoSuchMethodException");
1054 | }
1055 | catch (InvocationTargetException e) {
1056 | log(e);
1057 | updateStatusUI("InvocationTargetException");
1058 | }
1059 | catch(IllegalAccessException e) {
1060 | log(e);
1061 | updateStatusUI("IllegalAccessException");
1062 | }
1063 | }
1064 | }
1065 | catch (UnsupportedEncodingException e) {
1066 | log(e);
1067 | updateStatusUI("UnsupportedEncodingException");
1068 | }
1069 |
1070 | if (mDataView == null) {
1071 | log("mDataView null"); //??? happens on quick reads? activity is recreated with
1072 | }
1073 | else {
1074 | //Finish
1075 | storeTransactionsAndBreak(requests, responses);
1076 | }
1077 | }
1078 | catch (SocketTimeoutException e) {
1079 | log(e);
1080 | updateStatusUI(getString(R.string.connection_to_relay_timed_out));
1081 | }
1082 | catch (ConnectException e) {
1083 | log(getString(R.string.connection_to_relay_failed));
1084 | updateStatusUI(getString(R.string.connection_to_relay_failed));
1085 | }
1086 | catch (SocketException e) {
1087 | log(e);
1088 | updateStatusUI(getString(R.string.socket_error) + " " + e.getLocalizedMessage());
1089 | }
1090 | catch (UnknownHostException e) {
1091 | updateStatusUI(getString(R.string.unknown_host));
1092 | }
1093 | catch (IOException e) {
1094 | log(e);
1095 | updateStatusUI("IOException: " + e.getLocalizedMessage());
1096 | }
1097 | catch (final Exception e)
1098 | {
1099 | StringWriter sw = new StringWriter();
1100 | e.printStackTrace(new PrintWriter(sw));
1101 | log(getString(R.string.something_happened) + ": " + e.toString() + " " + sw.toString());
1102 | updateStatusUI(getString(R.string.something_happened) + ": " + e.toString() + " " + sw.toString());
1103 | }
1104 | finally {
1105 | try
1106 | {
1107 | log ("Closing connection to NFCRelay...");
1108 | if (clientSocket != null)
1109 | clientSocket.close();
1110 | }
1111 | catch (IOException e)
1112 | {
1113 | log("error closing socket: " + e);
1114 | }
1115 | log("doInBackground end");
1116 | }
1117 | return null;
1118 | }
1119 |
1120 | private void updateStatusUI(final CharSequence msg) {
1121 | mStatusView.post(new Runnable() {
1122 | @Override
1123 | public void run() {
1124 | mStatusView.append(TextUtils.concat( msg, "\n"));
1125 | mStatusTab.post(new Runnable() {
1126 | @Override
1127 | public void run() {
1128 | mStatusTab.fullScroll(ScrollView.FOCUS_DOWN);
1129 | }
1130 | });
1131 | }
1132 | });
1133 | }
1134 |
1135 | private void updateDataUI(final CharSequence msg) {
1136 | mDataView.post(new Runnable() {
1137 | @Override
1138 | public void run() {
1139 | mDataView.append(TextUtils.concat(msg, "\n"));
1140 | mDataTab.fullScroll(ScrollView.FOCUS_DOWN);
1141 | }
1142 | });
1143 | }
1144 | }
1145 |
1146 | private SecretKey generateSecretKey() throws IOException {
1147 | try {
1148 | SharedPreferences prefs = getSharedPreferences(NFCVars.PREFERENCES, MODE_PRIVATE);
1149 | SecretKeyFactory f;
1150 | f = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
1151 | byte[] salt = Base64.decode(mSalt, Base64.DEFAULT);
1152 | KeySpec ks = new PBEKeySpec(prefs.getString("passwordPref", getString(R.string.default_password)).toCharArray(), salt, 2000, 256);
1153 | SecretKey tmp = f.generateSecret(ks);
1154 | return new SecretKeySpec(tmp.getEncoded(), "AES");
1155 | } catch (Exception e) {
1156 | log(e);
1157 | throw new IOException(e);
1158 | }
1159 | }
1160 |
1161 | protected void saveRun(String name, int type) {
1162 | Bundle transactions = null;
1163 | if (type == DBHelper.REPLAY_PCD) {
1164 | transactions = mReplaySession.getBundle("requests");
1165 | }
1166 | else { //if (type == DBHelper.REPLAY_TAG) {
1167 | transactions = mReplaySession.getBundle("responses");
1168 | }
1169 | byte[][] data = new byte[transactions.size()][];
1170 | for (int i = 0; i < transactions.size(); i ++) {
1171 | data[i] = transactions.getByteArray(String.valueOf(i));
1172 | }
1173 | long inserted = mDBHelper.saveTransactions(name, data, type );
1174 |
1175 | if (inserted != -1) {
1176 | //TODO: should be done in new thread
1177 | ((CursorAdapter)mSavedList.getAdapter()).swapCursor(mDBHelper.getReplays());
1178 | Toast.makeText(this, "Saved", Toast.LENGTH_LONG).show();
1179 | }
1180 | else {
1181 | Toast.makeText(this, "Not saved. Duplicate name.", Toast.LENGTH_LONG).show();
1182 | }
1183 | }
1184 |
1185 | private void deleteSaved() {
1186 | String name = ((Bundle)mSelectedSaveView.getTag()).getString("name");
1187 | int num = mDBHelper.deleteReplay(name);
1188 | if (num != 1) {
1189 | Toast.makeText(this, "Error deleting replay", Toast.LENGTH_SHORT).show();
1190 | }
1191 | //TODO: should be done in new thread
1192 | ((CursorAdapter)mSavedList.getAdapter()).swapCursor(mDBHelper.getReplays());
1193 | }
1194 |
1195 | protected void exportRun(String filename) {
1196 | try {
1197 | String state = Environment.getExternalStorageState();
1198 | if (Environment.MEDIA_MOUNTED.equals(state)) {
1199 | File dir = Environment.getExternalStorageDirectory();
1200 | String dirPath = dir.getAbsolutePath();
1201 | File exportPath = new File(dirPath + File.separator + NFCVars.STORAGE_PATH);
1202 | if (!exportPath.exists()) {
1203 | if (!exportPath.mkdir()) {
1204 | Toast.makeText(this, "Error creating storage directory", Toast.LENGTH_LONG).show();
1205 | return;
1206 | }
1207 | }
1208 | //let user store where ever they want
1209 | File exportFile = new File(exportPath + File.separator + filename);
1210 | //TODO: make sure filename is valid filename. also warn if file exists.
1211 | FileWriter writer = new FileWriter(exportFile);
1212 |
1213 | Bundle session = mSessions.getBundle(String.valueOf(mSelectedId));
1214 | Bundle requests = session.getBundle("requests");
1215 | Bundle responses = session.getBundle("responses");
1216 |
1217 | StringBuilder sbRequests = new StringBuilder("byte[][] pcdRequests = new byte[][] {");
1218 | StringBuilder sbResponses = new StringBuilder("byte[][] tagResponses = new byte[][] {");
1219 | for(int i = 0; i < requests.size(); i ++) {
1220 | byte req[] = requests.getByteArray(String.valueOf(i));
1221 | sbRequests.append("{").append(TextHelper.byteArrayToHexString(req, "0x", ", ", true)).append("}");
1222 | if (i +1 != requests.size()) {
1223 | sbRequests.append(", ");
1224 | }
1225 | }
1226 | sbRequests.append("};\n");
1227 | for(int i = 0; i < responses.size(); i ++) {
1228 | byte resp[] = responses.getByteArray(String.valueOf(i));
1229 | sbResponses.append("{").append(TextHelper.byteArrayToHexString(resp, "0x", ", ", true)).append("}");
1230 | if (i +1 != responses.size()) {
1231 | sbResponses.append(", ");
1232 | }
1233 | }
1234 | sbResponses.append("};\n");
1235 | writer.write(sbRequests.toString());
1236 | writer.write(sbResponses.toString());
1237 | writer.close();
1238 | Toast.makeText(this, "Saved to:\n" + exportFile.getAbsolutePath(), Toast.LENGTH_LONG).show();
1239 | return;
1240 |
1241 | } else {
1242 | Toast.makeText(this, "Error writing to external storage", Toast.LENGTH_LONG).show();
1243 | }
1244 | } catch (IOException e) {
1245 | log(e);
1246 | }
1247 | }
1248 |
1249 | private void deleteRun() {
1250 | View v = mDataTable.findViewById(mSelectedId);
1251 | TableRow row = (TableRow)v.getParent();
1252 | row.setVisibility(View.GONE);
1253 | TableRow line = (TableRow)mDataTable.findViewWithTag(mSelectedId);
1254 | line.setVisibility(View.GONE);
1255 | Toast.makeText(this, "Deleted", Toast.LENGTH_LONG).show();
1256 | //don't re-adjust IDs
1257 | }
1258 |
1259 | private View.OnLongClickListener getTransactionsTextViewLongClickListener() {
1260 | return new View.OnLongClickListener() {
1261 |
1262 | @Override
1263 | public boolean onLongClick(View view) {
1264 | if (mActionMode != null) {
1265 | return false;
1266 | }
1267 | view.setSelected(true);
1268 | mSelectedId = view.getId();
1269 | log("selectedID: " + mSelectedId);
1270 | mActionMode = NFCProxyActivity.this.startActionMode(mTransactionsActionModeCallback);
1271 | return true;
1272 | }
1273 | };
1274 | }
1275 |
1276 | private View.OnLongClickListener getSavedTextViewLongClickListener() {
1277 | return new View.OnLongClickListener() {
1278 |
1279 | @Override
1280 | public boolean onLongClick(View view) {
1281 | if (mActionMode != null) {
1282 | return false;
1283 | }
1284 |
1285 | view.setSelected(true);
1286 | mSelectedSaveView = view;
1287 | mActionMode = NFCProxyActivity.this.startActionMode(mSavedActionModeCallback);
1288 | return true;
1289 | }
1290 | };
1291 | }
1292 |
1293 | private void cutSessionAt(int id) {
1294 |
1295 | int size = mSessions.size();
1296 | for(int i = id; i < size; i++) {
1297 | if (i == id) {
1298 | mSessions.remove(String.valueOf(id));
1299 | }
1300 | else {
1301 | Bundle b = mSessions.getBundle(String.valueOf(i));
1302 | mSessions.remove(String.valueOf(i));
1303 | mSessions.putBundle(String.valueOf(i - 1), b); //i will always be > 0
1304 | }
1305 | }
1306 | }
1307 |
1308 | /* (non-Javadoc)
1309 | * @see android.app.Activity#onSaveInstanceState(android.os.Bundle)
1310 | */
1311 | @Override
1312 | protected void onSaveInstanceState(Bundle outState) {
1313 | super.onSaveInstanceState(outState);
1314 |
1315 | ArrayList rows = new ArrayList();
1316 | for (int i = 0; i < mDataTable.getChildCount(); i++) {
1317 | TableRow tr = (TableRow)mDataTable.getChildAt(i);
1318 | if (tr.getVisibility() == View.GONE) {
1319 | cutSessionAt(i);
1320 | continue;
1321 | }
1322 | TextView tv = (TextView)(tr).getChildAt(0);
1323 | if (tv.getText().length() > 0 ) {
1324 | rows.add(tv.getText());
1325 | }
1326 |
1327 | }
1328 | outState.putCharSequenceArray("rows", rows.toArray(new CharSequence[rows.size()])); //TODO: this is not encrypted
1329 | outState.putInt("tab", mTabHost.getCurrentTab());
1330 | outState.putBundle("sessions", mSessions); //TODO: this is not encrypted
1331 | outState.putBundle("replaySession", mReplaySession); //TODO: this is not encrypted
1332 | outState.putInt("mode", mMode);
1333 | }
1334 |
1335 | /* (non-Javadoc)
1336 | * @see android.app.Activity#onRestoreInstanceState(android.os.Bundle)
1337 | */
1338 | @Override
1339 | protected void onRestoreInstanceState(Bundle savedInstanceState) {
1340 | super.onRestoreInstanceState(savedInstanceState);
1341 | CharSequence[] rows = savedInstanceState.getCharSequenceArray("rows");
1342 | if (rows != null) {
1343 | for(int i = 0 ; i < rows.length; i++) {
1344 | TableRow row = new TableRow(this);
1345 | row.setLayoutParams(new TableRow.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
1346 | TextView tv = new TextView(this);
1347 | tv.setFreezesText(true);
1348 | tv.setText(rows[i]);
1349 | tv.setOnLongClickListener(getTransactionsTextViewLongClickListener());
1350 | tv.setId(i);
1351 | row.addView(tv);
1352 | mDataTable.addView(row);
1353 |
1354 | addLineBreak(i);
1355 | }
1356 | }
1357 | mTabHost.setCurrentTab(savedInstanceState.getInt("tab"));
1358 | mSessions = savedInstanceState.getBundle("sessions");
1359 | mReplaySession = savedInstanceState.getBundle("replaySession");
1360 | mMode = savedInstanceState.getInt("mode");
1361 | if (mOptionsMenu != null && mMode == PROXY_MODE) {
1362 | mOptionsMenu.getItem(0).setVisible(false);
1363 | mOptionsMenu.getItem(1).setVisible(false);
1364 | mOptionsMenu.getItem(2).setVisible(false);
1365 | }
1366 | else if (mOptionsMenu != null && mMode == REPLAY_PCD_MODE) {
1367 | mOptionsMenu.getItem(0).setVisible(false);
1368 | mOptionsMenu.getItem(1).setVisible(true);
1369 | mOptionsMenu.getItem(2).setVisible(true);
1370 | }
1371 | else if (mOptionsMenu != null && mMode == REPLAY_TAG_MODE) {
1372 | mOptionsMenu.getItem(0).setVisible(true);
1373 | mOptionsMenu.getItem(1).setVisible(false);
1374 | mOptionsMenu.getItem(2).setVisible(true);
1375 | }
1376 | }
1377 | /* (non-Javadoc)
1378 | * @see android.app.Activity#onDestroy()
1379 | */
1380 | @Override
1381 | protected void onDestroy() {
1382 | super.onDestroy();
1383 | if (mDBHelper != null) {
1384 | mDBHelper.close();
1385 | }
1386 | }
1387 |
1388 | private void log(Object msg) {
1389 | if (mDebugLogging) {
1390 | LogHelper.log(this, msg);
1391 | }
1392 | }
1393 | }
1394 |
--------------------------------------------------------------------------------
/src/org/eleetas/nfc/nfcproxy/NFCRelayActivity.java:
--------------------------------------------------------------------------------
1 | package org.eleetas.nfc.nfcproxy;
2 |
3 | import java.io.BufferedInputStream;
4 | import java.io.BufferedOutputStream;
5 | import java.io.IOException;
6 | import java.lang.reflect.InvocationTargetException;
7 | import java.net.InetAddress;
8 | import java.net.Inet4Address;
9 | import java.net.NetworkInterface;
10 | import java.net.ServerSocket;
11 | import java.net.Socket;
12 | import java.net.SocketException;
13 | import java.security.spec.KeySpec;
14 | import java.util.Enumeration;
15 |
16 | import javax.crypto.IllegalBlockSizeException;
17 | import javax.crypto.SecretKey;
18 | import javax.crypto.SecretKeyFactory;
19 | import javax.crypto.spec.PBEKeySpec;
20 | import javax.crypto.spec.SecretKeySpec;
21 |
22 | import org.eleetas.nfc.nfcproxy.NFCVars;
23 | import org.eleetas.nfc.nfcproxy.utils.IOUtils;
24 | import org.eleetas.nfc.nfcproxy.utils.LogHelper;
25 | import org.eleetas.nfc.nfcproxy.utils.TextHelper;
26 | import org.eleetas.nfc.nfcproxy.utils.BasicTagTechnologyWrapper;
27 |
28 | import android.app.Activity;
29 | import android.content.Context;
30 | import android.content.Intent;
31 | import android.content.SharedPreferences;
32 | import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
33 | import android.nfc.NdefMessage;
34 | import android.nfc.NfcAdapter;
35 | import android.nfc.Tag;
36 | import android.os.Build;
37 | import android.os.Bundle;
38 | import android.os.Parcelable;
39 | import android.os.PowerManager;
40 | import android.os.PowerManager.WakeLock;
41 | import android.text.TextUtils;
42 | import android.view.Menu;
43 | import android.view.MenuInflater;
44 | import android.view.MenuItem;
45 | import android.widget.ScrollView;
46 | import android.widget.TabHost;
47 | import android.widget.TextView;
48 | import android.widget.Toast;
49 |
50 | public class NFCRelayActivity extends Activity {
51 |
52 | private static BasicTagTechnologyWrapper mTagTech = null;
53 | private TabHost mTabHost;
54 | private TextView mStatusView;
55 | private ScrollView mStatusTab;
56 |
57 | private static ServerSocket mServerSocket = null;
58 | private SecretKey mSecret = null;
59 |
60 | private WakeLock mWakeLock;
61 |
62 | private boolean mDebugLogging = false;
63 | private int mPort = NFCVars.DEFAULT_PORT;
64 | private boolean mEncrypt = true;
65 |
66 | @Override
67 | public boolean onCreateOptionsMenu(Menu menu) {
68 | MenuInflater inflater = getMenuInflater();
69 | inflater.inflate(R.menu.menu, menu);
70 | return true;
71 | }
72 |
73 | @Override
74 | public boolean onOptionsItemSelected(MenuItem item) {
75 | switch(item.getItemId()) {
76 | case R.id.settingsButton:
77 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
78 | startActivity(new Intent(this, SettingsActivityCompat.class));
79 | }
80 | else {
81 | startActivity(new Intent(this, SettingsActivity.class));
82 | }
83 | return true;
84 | default:
85 | return false;
86 |
87 | }
88 | }
89 |
90 | @Override
91 | public void onCreate(Bundle savedInstanceState) {
92 | super.onCreate(savedInstanceState);
93 | setContentView(R.layout.relay);
94 |
95 | mTabHost = (TabHost) findViewById(android.R.id.tabhost);
96 | mTabHost.setup();
97 | mTabHost.addTab(mTabHost.newTabSpec("status_tab").setContent(R.id.statusTab).setIndicator(getString(R.string.status)));
98 | //mTabHost.addTab(mTabHost.newTabSpec("config_tab").setContent(R.id.configTab).setIndicator(getString(R.string.config)));
99 | mStatusView = (TextView) findViewById(R.id.statusView);
100 | mStatusTab = (ScrollView) findViewById(R.id.statusTab);
101 |
102 | if (mServerSocket == null || (mServerSocket != null && mServerSocket.isBound() == false)) {
103 | log("Starting Server...");
104 | Thread th = new Thread(new ServerThread());
105 | th.start();
106 | }
107 |
108 | mStatusView.setText(getString(R.string.waiting) + "\n");
109 |
110 | String ipAddr = "";
111 | try {
112 | //assume IP is on wlan0 interface
113 | NetworkInterface net = NetworkInterface.getByName("wlan0");
114 | if (net != null) {
115 | for (Enumeration enumIpAddr = net.getInetAddresses(); enumIpAddr.hasMoreElements();) {
116 | InetAddress inetAddress = enumIpAddr.nextElement();
117 | if (inetAddress instanceof Inet4Address) {
118 | ipAddr = inetAddress.getHostAddress().toString();
119 | break;
120 | }
121 | }
122 | }
123 | } catch (SocketException e) {
124 | log("Error getting local IPs: " + e.toString());
125 | }
126 | if (ipAddr.length() == 0) {
127 | updateUIandScroll(getString(R.string.enable_wifi));
128 | }
129 | else {
130 | updateUIandScroll(ipAddr);
131 | }
132 | }
133 |
134 | public void updateUIandScroll(CharSequence msg) {
135 | mStatusView.append(TextUtils.concat(msg, "\n"));
136 |
137 | //use post so that recent update from setText/append takes effect first (at least a better chance of updating)
138 | mStatusTab.post(new Runnable(){
139 | @Override
140 | public void run() {
141 | mStatusTab.fullScroll(ScrollView.FOCUS_DOWN);
142 | }
143 | });
144 | }
145 |
146 | @Override
147 | public void onResume() {
148 | super.onResume();
149 |
150 | Intent intent = getIntent();
151 |
152 | SharedPreferences prefs = getSharedPreferences(NFCVars.PREFERENCES, Context.MODE_PRIVATE);
153 | if (!prefs.getBoolean("relayPref", false)) {
154 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
155 | prefs.edit().putBoolean("relayPref", true).commit();
156 | Toast.makeText(this, getString(R.string.proxy_mode_unsupported), Toast.LENGTH_LONG).show();
157 | }
158 | else {
159 | Intent forwardIntent = new Intent(intent);
160 | forwardIntent.setClass(this, NFCProxyActivity.class);
161 | startActivity(forwardIntent);
162 | finish();
163 | }
164 | }
165 |
166 | if (prefs.getBoolean("screenPref", true)) {
167 | PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
168 | mWakeLock = pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK, getString(R.string.app_name));
169 | mWakeLock.acquire();
170 | }
171 |
172 | mPort = prefs.getInt("portPref", NFCVars.DEFAULT_PORT);
173 | mEncrypt = prefs.getBoolean("encryptPref", true);
174 | mDebugLogging = prefs.getBoolean("debugLogPref", false);
175 |
176 | String text = "";
177 |
178 | if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(intent.getAction())) {
179 | Toast.makeText(this, getString(R.string.ndef_discovered), Toast.LENGTH_SHORT).show();
180 | //TODO
181 | }
182 | else if (NfcAdapter.ACTION_TECH_DISCOVERED.equals(intent.getAction())) {
183 | Toast.makeText(this, getString(R.string.tech_discovered), Toast.LENGTH_SHORT).show();
184 | //TODO
185 | }
186 | else if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(intent.getAction())) {
187 | Toast.makeText(this, getString(R.string.tag_discovered), Toast.LENGTH_SHORT).show();
188 | text = getTagInfo(intent);
189 | }
190 | updateUIandScroll(text);
191 | }
192 |
193 | /* (non-Javadoc)
194 | * @see android.app.Activity#onPause()
195 | */
196 | @Override
197 | protected void onPause() {
198 | super.onPause();
199 |
200 | if (mWakeLock != null) {
201 | mWakeLock.release();
202 | }
203 | }
204 |
205 | private String getTagInfo(Intent intent)
206 | {
207 | Tag extraTag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG); //required
208 | Parcelable[] extraNdefMsg = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES); //optional
209 | byte[] extraID = intent.getByteArrayExtra(NfcAdapter.EXTRA_ID); //optional
210 |
211 | extraTag.getId();
212 |
213 | String text = ""; //TODO: make this StringBuilder
214 | //text = extraTag.toString();
215 |
216 | String[] techList = extraTag.getTechList();
217 | if (techList.length > 0 ) {
218 | text += "TechList: ";
219 | for (String s: techList) {
220 | text += s + ", ";
221 | }
222 | //for now, just choose the first tech in the list
223 | String tech = techList[0];
224 |
225 | try {
226 | mTagTech = new BasicTagTechnologyWrapper(extraTag, tech);
227 | } catch (NoSuchMethodException e) {
228 | mTagTech = null;
229 | log("Unsupported tag type: " + e.toString());
230 | } catch (IllegalArgumentException e) {
231 | mTagTech = null;
232 | log("Unsupported tag type: " + e.toString());
233 | } catch (ClassNotFoundException e) {
234 | mTagTech = null;
235 | log("Unsupported tag type: " + e.toString());
236 | } catch (IllegalAccessException e) {
237 | mTagTech = null;
238 | log("Unsupported tag type: " + e.toString());
239 | } catch (InvocationTargetException e) {
240 | mTagTech = null;
241 | log("Unsupported tag type: " + e.toString());
242 | }
243 |
244 | if (mTagTech != null) {
245 |
246 | }
247 | }
248 |
249 | text += "\nNDEF Messages: ";
250 | if (extraNdefMsg != null) {
251 | NdefMessage[] msgs = new NdefMessage[extraNdefMsg.length];
252 | for (int i = 0; i < extraNdefMsg.length; i++) {
253 | msgs[i] = (NdefMessage) extraNdefMsg[i];
254 | text += msgs[i].toString() + ", ";
255 | }
256 | }
257 | else
258 | text += "null";
259 |
260 | text += "\nExtra ID: ";
261 | if (extraID != null) {
262 | text += TextHelper.byteArrayToHexString(extraID);
263 | }
264 | else
265 | text += "null";
266 |
267 | text += "\nUID: " + TextHelper.byteArrayToHexString(extraTag.getId()) + "\n";
268 | return text;
269 |
270 | }
271 |
272 | /* (non-Javadoc)
273 | * @see android.app.Activity#onNewIntent(android.content.Intent)
274 | */
275 | @Override
276 | protected void onNewIntent(Intent intent) {
277 | super.onNewIntent(intent);
278 | setIntent(intent);
279 | }
280 |
281 | public class ServerThread implements Runnable
282 | {
283 | private void updateUI(final CharSequence msg) {
284 | mStatusView.post(new Runnable() {
285 |
286 | @Override
287 | public void run() {
288 | mStatusView.append(TextUtils.concat(msg, "\n"));
289 | }
290 | });
291 | mStatusTab.post(new Runnable() {
292 |
293 | @Override
294 | public void run() {
295 | mStatusTab.fullScroll(ScrollView.FOCUS_DOWN);
296 | }
297 | });
298 |
299 | }
300 | public void run() {
301 | try {
302 | mServerSocket = new ServerSocket(mPort);
303 | }
304 | catch(final IOException e) {
305 | log(e);
306 | updateUI(e.toString());
307 | return;
308 | }
309 |
310 | Socket clientSocket = null;
311 | byte[] salt = new byte[8];
312 | log("Listening...");
313 | while (true) {
314 | log("Waiting for connection...");
315 | try {
316 |
317 | clientSocket = mServerSocket.accept();
318 | log("Connected.");
319 |
320 | if (mTagTech != null && (clientSocket != null && clientSocket.isConnected())) {
321 |
322 | BufferedInputStream is = new BufferedInputStream(clientSocket.getInputStream());
323 | BufferedOutputStream os = new BufferedOutputStream(clientSocket.getOutputStream());
324 |
325 | String line = null;
326 | line = IOUtils.readLine(is);
327 | log("command: " + line);
328 | if (line.equals(NFCVars.READY)) {
329 |
330 | IOUtils.sendSocket((NFCVars.OPTIONS + "\n").getBytes("UTF-8"), os, null, false);
331 |
332 | line = IOUtils.readLine(is);
333 |
334 | if (line.equals(NFCVars.ENCRYPT)) {
335 | //TODO: move to IOUtils
336 | int n = is.read(salt, 0, 8);
337 | //lazy
338 | if (n != 8) {
339 | log("meh...expected 8 bytes. got: " + n);
340 | updateUI(getString(R.string.connection_funny));
341 | if (clientSocket != null) {
342 | clientSocket.close();
343 | continue;
344 | }
345 | }
346 |
347 | if (mEncrypt) {
348 | //Have to do this every time...TODO: notify ends upon password change?
349 | mSecret = generateSecretKey(salt);
350 | }
351 |
352 | IOUtils.sendSocket(NFCVars.VERIFY.getBytes("UTF-8"), os, mSecret, mEncrypt);
353 |
354 | byte[] ok = IOUtils.readSocket(is, mSecret, mEncrypt);
355 | if (ok == null) {
356 | updateUI(getString(R.string.mismatched_protocol));
357 | log(getString(R.string.mismatched_protocol));
358 | continue;
359 | }
360 | String response = new String(ok, "UTF-8");
361 | if (!response.equals(NFCVars.OK)) {
362 | if (response.equals(NFCVars.BAD_PASSWORD)) {
363 | log(getString(R.string.bad_password));
364 | updateUI(getString(R.string.bad_password));
365 | }
366 | log(getString(R.string.unexpected_response));
367 | updateUI(getString(R.string.unexpected_response));
368 | log(TextHelper.byteArrayToHexString(ok));
369 | continue;
370 | }
371 | }
372 | else if (!line.equals(NFCVars.CLEAR)){
373 | log(getString(R.string.unexpected_response));
374 | updateUI(getString(R.string.unexpected_response));
375 | continue;
376 | }
377 | log("clear!");
378 | }
379 | else continue; //unsupported
380 |
381 |
382 | try {
383 | if (!mTagTech.isConnected()) {
384 | mTagTech.connect();
385 | }
386 | ////////////////////////////////////
387 | //Start sending tag data
388 |
389 | //From IsoPcdA doc
390 | //@param data - on the first call to transceive after PCD activation, the data sent to the method will be ignored
391 | IOUtils.sendSocket(mTagTech.getTag().getId(), os, mSecret, mEncrypt);
392 |
393 | byte[] response = null;
394 | do {
395 | response = IOUtils.readSocket(is, mSecret, mEncrypt);
396 | if (response == null)
397 | {
398 | log("no response from PCD");
399 | break;
400 | }
401 | log("response from PCD: " + TextHelper.byteArrayToHexString(response));
402 |
403 | log("sending response to card");
404 | response = mTagTech.transceive(response);
405 | log("response from card: " + TextHelper.byteArrayToHexString(response));
406 |
407 | log("sending card response to PCD");
408 |
409 | IOUtils.sendSocket(response, os, mSecret, mEncrypt);
410 | log("wrote: " + new String(response));
411 | } while (response != null);
412 | }
413 | catch (final IllegalStateException e) {
414 | log(e);
415 | updateUI(getString(R.string.lost_tag));
416 | if (mTagTech != null) {
417 | try {
418 | mTagTech.close();
419 | } catch (IOException e2) {
420 | log(e);
421 | }
422 | finally {
423 | mTagTech = null;
424 | }
425 | log("mTagTech closed1");
426 | }
427 | if (clientSocket != null) {
428 | try {
429 | if (!clientSocket.isClosed()) {
430 | //clientSocket.getOutputStream().write("Relay lost tag".getBytes("UTF-8"));
431 | IOUtils.sendSocket("Relay lost tag".getBytes("UTF-8"), clientSocket.getOutputStream(), mSecret, mEncrypt);
432 | }
433 | } catch (IOException e3) {
434 | log(e3);
435 | }
436 | }
437 | }
438 | catch (final IOException e) {
439 | log(e);
440 |
441 | if (e.getCause() instanceof IllegalBlockSizeException) {
442 | updateUI(getString(R.string.crypto_error));
443 | log(getString(R.string.crypto_error));
444 | }
445 | else {
446 | updateUI(getString(R.string.lost_tag));
447 | if (mTagTech != null) {
448 | try {
449 | mTagTech.close();
450 | } catch (IOException e2) {
451 | log(e);
452 | }
453 | finally {
454 | mTagTech = null;
455 | }
456 | log("mTagTech closed2");
457 | }
458 | if (clientSocket != null) {
459 | try {
460 | //if (e.getMessage().contains("Transceive failed") && !clientSocket.isClosed()) {
461 | if (!clientSocket.isClosed()) {
462 | //clientSocket.getOutputStream().write("Relay lost tag".getBytes("UTF-8"));
463 | IOUtils.sendSocket("Relay lost tag".getBytes("UTF-8"), clientSocket.getOutputStream(), mSecret, mEncrypt);
464 | }
465 | } catch (IOException e2) {
466 | log(e2);
467 | }
468 | }
469 | }
470 | }
471 | log("done reading...");
472 | }
473 | else {
474 | if (mTagTech == null) {
475 | updateUI(getString(R.string.nfcproxy_connected_no_tag));
476 | log("Closed connection to NFCRelay. mTagTech null");
477 | if (clientSocket != null) {
478 | //OutputStream os = clientSocket.getOutputStream();
479 | //os.write("NOT READY".getBytes("UTF-8"));
480 | //os.flush();
481 | //sendEncrypted("NOT READY".getBytes("UTF-8"), clientSocket.getOutputStream(), salt);
482 | IOUtils.sendSocket("NOT READY\n".getBytes("UTF-8"), clientSocket.getOutputStream(), null, false);
483 | try {
484 | clientSocket.close();
485 | } catch (IOException e2) {
486 | log(e2);
487 | }
488 | }
489 | }
490 | else {
491 | log("clientSocket null?");
492 | }
493 | }
494 | }
495 | catch(IOException e) {
496 | log(e);
497 | updateUI(getString(R.string.ioexception));
498 |
499 | }
500 | finally {
501 | if (clientSocket != null) {
502 | try {
503 | clientSocket.close();
504 | log("Closed client connection");
505 | } catch (IOException e2) {
506 | log(e2);
507 | }
508 |
509 | }
510 | }
511 | }//while
512 | }
513 | }
514 | private SecretKey generateSecretKey(byte[] salt) throws IOException {
515 | try {
516 | SharedPreferences prefs = getSharedPreferences(NFCVars.PREFERENCES, Context.MODE_PRIVATE);
517 | SecretKeyFactory f;
518 | f = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
519 | KeySpec ks = new PBEKeySpec(prefs.getString("passwordPref", getString(R.string.default_password)).toCharArray(), salt, 2000, 256);
520 | SecretKey tmp = f.generateSecret(ks);
521 | return new SecretKeySpec(tmp.getEncoded(), "AES");
522 | } catch (Exception e) {
523 | log(e);
524 | throw new IOException(e);
525 | }
526 | }
527 |
528 | private void log(Object msg) {
529 | if (mDebugLogging) {
530 | LogHelper.log(this, msg);
531 | }
532 | }
533 |
534 | /* (non-Javadoc)
535 | * @see android.app.Activity#onSaveInstanceState(android.os.Bundle)
536 | */
537 | @Override
538 | protected void onSaveInstanceState(Bundle outState) {
539 | super.onSaveInstanceState(outState);
540 | outState.putInt("tab", mTabHost.getCurrentTab());
541 | }
542 |
543 | /* (non-Javadoc)
544 | * @see android.app.Activity#onRestoreInstanceState(android.os.Bundle)
545 | */
546 | @Override
547 | protected void onRestoreInstanceState(Bundle savedInstanceState) {
548 | super.onRestoreInstanceState(savedInstanceState);
549 | mTabHost.setCurrentTab(savedInstanceState.getInt("tab"));
550 | }
551 | }
552 |
--------------------------------------------------------------------------------
/src/org/eleetas/nfc/nfcproxy/NFCVars.java:
--------------------------------------------------------------------------------
1 | package org.eleetas.nfc.nfcproxy;
2 |
3 | public class NFCVars {
4 |
5 | public static final String PREFERENCES = "NFCPrefs";
6 | public static final String STORAGE_PATH = "NFCProxy";
7 | public static final String READY = "READY";
8 | public static final String OPTIONS = "OPTIONS";
9 | public static final String NOT_READY = "NOT READY";
10 | public static final String ENCRYPT = "ENCRYPT";
11 | public static final String CLEAR = "CLEAR";
12 |
13 |
14 | public static final String VERIFY = "VERIFY ME";
15 | public static final String VERIFIED = "VERIFIED";
16 | public static final String BAD_PASSWORD = "BAD PASSWORD";
17 | public static final String OK = "OK";
18 |
19 | public static final String SAVE_PCD = "pcd";
20 | public static final String SAVE_TAG = "tag";
21 |
22 | public static final String ISO_PCDA_CLASS = "android.nfc.tech.IsoPcdA";
23 | public static final String ISO_PCDB_CLASS = "android.nfc.tech.IsoPcdB";
24 | public static final int DEFAULT_PORT = 9999;
25 | }
26 |
27 |
--------------------------------------------------------------------------------
/src/org/eleetas/nfc/nfcproxy/SaveDialogFragment.java:
--------------------------------------------------------------------------------
1 | package org.eleetas.nfc.nfcproxy;
2 |
3 | import android.app.AlertDialog;
4 | import android.app.Dialog;
5 | import android.app.DialogFragment;
6 | import android.content.DialogInterface;
7 | import android.content.DialogInterface.OnShowListener;
8 | import android.os.Bundle;
9 | import android.text.Editable;
10 | import android.text.TextWatcher;
11 | import android.view.LayoutInflater;
12 | import android.view.View;
13 | import android.widget.EditText;
14 | import android.widget.RadioGroup;
15 | import android.widget.RadioGroup.OnCheckedChangeListener;
16 |
17 | public class SaveDialogFragment extends DialogFragment {
18 | static SaveDialogFragment newInstance() {
19 | return new SaveDialogFragment();
20 | }
21 |
22 | private boolean hasText = false;
23 | private int selectedButton = -1;
24 | private AlertDialog mAlertDialog;
25 |
26 | /* (non-Javadoc)
27 | * @see android.app.DialogFragment#onCreateDialog(android.os.Bundle)
28 | */
29 | @Override
30 | public Dialog onCreateDialog(Bundle savedInstanceState) {
31 |
32 |
33 | final View view = LayoutInflater.from(getActivity()).inflate(R.layout.save_dialog, null, false);
34 | final RadioGroup rg = (RadioGroup) view.findViewById(R.id.saveRadioGroup);
35 | final EditText nameBox = (EditText) view.findViewById(R.id.saveNameEditText);
36 |
37 | mAlertDialog = new AlertDialog.Builder(getActivity())
38 | .setTitle(R.string.save_to_db)
39 | .setMessage(R.string.warning_plaintext)
40 | .setView(view)
41 | .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener () {
42 |
43 | public void onClick(DialogInterface dialog, int which) {
44 | int id = rg.getCheckedRadioButtonId();
45 | String name = nameBox.getText().toString();
46 | if (id == R.id.pcdButton) {
47 | ((NFCProxyActivity)getActivity()).saveRun(name, DBHelper.REPLAY_PCD);
48 | } else if (id == R.id.tagButton) {
49 | ((NFCProxyActivity)getActivity()).saveRun(name, DBHelper.REPLAY_TAG);
50 | }
51 | }
52 | })
53 | .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener () {
54 | public void onClick(DialogInterface dialog, int which) {
55 | }
56 | }).create();
57 |
58 | mAlertDialog.setOnShowListener(new OnShowListener() {
59 |
60 | @Override
61 | public void onShow(DialogInterface dialog) {
62 | mAlertDialog.getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(false);
63 |
64 | }
65 |
66 | });
67 | rg.setOnCheckedChangeListener(new OnCheckedChangeListener() {
68 |
69 | @Override
70 | public void onCheckedChanged(RadioGroup group, int checkedId) {
71 | selectedButton = checkedId;
72 | if (hasText) {
73 | mAlertDialog.getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(true);
74 | }
75 |
76 |
77 | }
78 |
79 | });
80 |
81 | nameBox.addTextChangedListener(new TextWatcher() {
82 |
83 | @Override
84 | public void beforeTextChanged(CharSequence s, int start, int count,
85 | int after) {
86 | // TODO Auto-generated method stub
87 |
88 | }
89 |
90 | @Override
91 | public void onTextChanged(CharSequence s, int start, int before,
92 | int count) {
93 | // TODO Auto-generated method stub
94 |
95 | }
96 |
97 | @Override
98 | public void afterTextChanged(Editable s) {
99 | if (s.length() > 0 ) {
100 | hasText = true;
101 | if (selectedButton != -1) {
102 | mAlertDialog.getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(true);
103 | }
104 | }
105 | else {
106 | hasText = false;
107 | mAlertDialog.getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(false);
108 | }
109 | }
110 |
111 | });
112 |
113 | return mAlertDialog;
114 | }
115 |
116 | void disableOK() {
117 | if (mAlertDialog != null) {
118 | mAlertDialog.getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(false);
119 | }
120 | }
121 |
122 | }
123 |
--------------------------------------------------------------------------------
/src/org/eleetas/nfc/nfcproxy/SelectorActivity.java:
--------------------------------------------------------------------------------
1 | package org.eleetas.nfc.nfcproxy;
2 |
3 | import org.eleetas.nfc.nfcproxy.NFCVars;
4 |
5 | import android.app.Activity;
6 | import android.content.Context;
7 | import android.content.Intent;
8 | import android.content.SharedPreferences;
9 | import android.os.Build;
10 | import android.view.View;
11 | import android.view.View.OnClickListener;
12 | import android.widget.Button;
13 | import android.widget.Toast;
14 |
15 | public class SelectorActivity extends Activity {
16 |
17 | /* (non-Javadoc)
18 | * @see android.app.Activity#onResume()
19 | */
20 | @Override
21 | protected void onResume() {
22 | super.onResume();
23 |
24 | SharedPreferences prefs = getSharedPreferences(NFCVars.PREFERENCES, Context.MODE_PRIVATE);
25 | if (prefs.contains("relayPref")) {
26 | if (prefs.getBoolean("relayPref", false)) {
27 | Intent intent = new Intent(getIntent());
28 | intent.setClass(this, NFCRelayActivity.class);
29 | startActivity(intent);
30 | finish();
31 | }
32 | else {
33 |
34 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
35 | prefs.edit().putBoolean("relayPref", true).commit();
36 | Toast.makeText(this, "Proxy mode not supported.\nSwitching to Relay mode", Toast.LENGTH_LONG).show();
37 | }
38 |
39 | Intent intent = new Intent(getIntent());
40 | intent.setClass(this, NFCProxyActivity.class);
41 | startActivity(intent);
42 | finish();
43 | }
44 | }
45 | setContentView(R.layout.selector);
46 |
47 | Button proxyButton = (Button) findViewById(R.id.proxyModeButton);
48 | Button relayButton = (Button) findViewById(R.id.relayModeButton);
49 |
50 | proxyButton.setOnClickListener(new OnClickListener() {
51 |
52 | @Override
53 | public void onClick(View v) {
54 | SharedPreferences prefs = getSharedPreferences(NFCVars.PREFERENCES, Context.MODE_PRIVATE);
55 | prefs.edit().putBoolean("relayPref", false).commit();
56 | Intent intent = new Intent(getIntent());
57 | intent.setClass(SelectorActivity.this, NFCProxyActivity.class);
58 | startActivity(intent);
59 | }
60 | });
61 |
62 | relayButton.setOnClickListener(new OnClickListener() {
63 |
64 | @Override
65 | public void onClick(View v) {
66 | SharedPreferences prefs = getSharedPreferences(NFCVars.PREFERENCES, Context.MODE_PRIVATE);
67 | prefs.edit().putBoolean("relayPref", true).commit();
68 | Intent intent = new Intent(getIntent());
69 | intent.setClass(SelectorActivity.this, NFCRelayActivity.class);
70 | startActivity(intent);
71 | }
72 | });
73 |
74 | }
75 |
76 | }
77 |
--------------------------------------------------------------------------------
/src/org/eleetas/nfc/nfcproxy/SettingsActivity.java:
--------------------------------------------------------------------------------
1 | package org.eleetas.nfc.nfcproxy;
2 |
3 | import android.app.Activity;
4 | import android.os.Bundle;
5 |
6 | public class SettingsActivity extends Activity {
7 |
8 |
9 | /* (non-Javadoc)
10 | * @see android.app.Activity#onCreate(android.os.Bundle)
11 | */
12 | @Override
13 | protected void onCreate(Bundle savedInstanceState) {
14 | super.onCreate(savedInstanceState);
15 |
16 | getFragmentManager().beginTransaction().replace(android.R.id.content, new NFCPrefs()).commit();
17 | }
18 |
19 | }
20 |
21 |
--------------------------------------------------------------------------------
/src/org/eleetas/nfc/nfcproxy/SettingsActivityCompat.java:
--------------------------------------------------------------------------------
1 | package org.eleetas.nfc.nfcproxy;
2 |
3 | import java.net.Inet4Address;
4 | import java.net.InetAddress;
5 | import java.net.NetworkInterface;
6 | import java.net.SocketException;
7 | import java.util.Enumeration;
8 |
9 | import org.eleetas.nfc.nfcproxy.utils.CryptoHelper;
10 | import org.eleetas.nfc.nfcproxy.utils.LogHelper;
11 |
12 | import android.content.Context;
13 | import android.content.SharedPreferences;
14 | import android.graphics.Color;
15 | import android.os.Bundle;
16 | import android.preference.EditTextPreference;
17 | import android.preference.Preference;
18 | import android.preference.PreferenceActivity;
19 | import android.preference.PreferenceManager;
20 | import android.preference.Preference.OnPreferenceChangeListener;
21 | import android.text.SpannableString;
22 | import android.text.style.ForegroundColorSpan;
23 | import android.widget.Toast;
24 |
25 | public class SettingsActivityCompat extends PreferenceActivity {
26 |
27 | /* (non-Javadoc)
28 | * @see android.preference.PreferenceActivity#onCreate(android.os.Bundle)
29 | */
30 | @Override
31 | protected void onCreate(Bundle savedInstanceState) {
32 | super.onCreate(savedInstanceState);
33 |
34 | PreferenceManager.setDefaultValues(this, NFCVars.PREFERENCES, Context.MODE_PRIVATE, R.xml.preferences, true);
35 | PreferenceManager pMan = getPreferenceManager();
36 | pMan.setSharedPreferencesName(NFCVars.PREFERENCES);
37 | final SharedPreferences prefs = pMan.getSharedPreferences();
38 |
39 | addPreferencesFromResource(R.xml.preferences);
40 |
41 | final EditTextPreference password = (EditTextPreference) findPreference("passwordPref");
42 | password.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
43 |
44 | @Override
45 | public boolean onPreferenceChange(Preference preference, Object newValue) {
46 | if (newValue.toString().length() < 8) {
47 | password.getEditText().setError("Min password length is 8");
48 | SpannableString msg = new SpannableString("Min password length is 8");
49 | msg.setSpan(new ForegroundColorSpan(Color.RED) , 0, msg.length(), 0);
50 | password.setSummary(msg);
51 | Toast.makeText(SettingsActivityCompat.this.getBaseContext(), "Password not saved", Toast.LENGTH_LONG).show();
52 | return false;
53 | }
54 | password.getEditText().setError("");
55 | password.setSummary(getString(R.string.password_desc));
56 |
57 | //TODO: Not sure that salting is necessary since we're generating a key from the password and sending the salt in the clear
58 | prefs.edit().putString("saltPref", CryptoHelper.generateSalt()).commit();
59 | return true;
60 | }
61 |
62 | });
63 |
64 | }
65 |
66 | /* (non-Javadoc)
67 | * @see android.app.Activity#onResume()
68 | */
69 | @Override
70 | protected void onResume() {
71 | super.onResume();
72 |
73 | PreferenceManager.setDefaultValues(this, NFCVars.PREFERENCES, Context.MODE_PRIVATE, R.xml.preferences, true);
74 | PreferenceManager pMan = getPreferenceManager();
75 | pMan.setSharedPreferencesName(NFCVars.PREFERENCES);
76 | SharedPreferences prefs = pMan.getSharedPreferences();
77 | EditTextPreference ip = (EditTextPreference) findPreference("ipPref");
78 | if (prefs.getBoolean("relayPref", true)) {
79 | ip.setEnabled(false);
80 | String ipAddr = "";
81 | try {
82 | //assume IP is on wlan0 interface
83 | NetworkInterface net = NetworkInterface.getByName("wlan0");
84 | if (net != null) {
85 | for (Enumeration enumIpAddr = net.getInetAddresses(); enumIpAddr.hasMoreElements();) {
86 | InetAddress inetAddress = enumIpAddr.nextElement();
87 | if (inetAddress instanceof Inet4Address) {
88 | ipAddr = inetAddress.getHostAddress().toString();
89 | break;
90 | }
91 | }
92 | }
93 | } catch (SocketException e) {
94 | LogHelper.log(this, "Error getting local IPs: " + e.toString());
95 | }
96 |
97 | if (ipAddr.length() == 0) {
98 | ip.setSummary(getString(R.string.enable_wifi));
99 | }
100 | else {
101 | ip.setSummary(ipAddr);
102 | }
103 | }
104 | else {
105 | ip.setEnabled(true);
106 | ip.setSummary(getString(R.string.ip_desc));
107 | }
108 | }
109 |
110 |
111 |
112 | }
113 |
--------------------------------------------------------------------------------
/src/org/eleetas/nfc/nfcproxy/utils/BasicTagTechnologyWrapper.java:
--------------------------------------------------------------------------------
1 | package org.eleetas.nfc.nfcproxy.utils;
2 |
3 | import java.io.IOException;
4 | import java.lang.reflect.InvocationTargetException;
5 | import java.lang.reflect.Method;
6 |
7 | import android.nfc.Tag;
8 | import android.nfc.tech.TagTechnology;
9 |
10 |
11 | //TODO: HACK since BasicTagTechnology is not currently visible from SDK. primiarly want the transceive() method (otherwise we could just use TagTechnology)
12 | //TODO: Just access BasicTagTechnolgy directly but modifying platform library android.jar. (use library with hidden classes from framework.jar)
13 | public class BasicTagTechnologyWrapper implements TagTechnology {
14 |
15 | // Method get;
16 | Method transceive;
17 | Method isConnected;
18 | Method connect;
19 | //Method getMaxTransceiveLength;
20 | Method close;
21 | Tag mTag;
22 | Object mTagTech;
23 |
24 | public BasicTagTechnologyWrapper(Tag tag, String tech) throws ClassNotFoundException, NoSuchMethodException, IllegalArgumentException, IllegalAccessException, InvocationTargetException {
25 | Class cls = Class.forName(tech);
26 | Method get = cls.getMethod("get", Tag.class);
27 | mTagTech = get.invoke(null, tag);
28 | transceive = cls.getMethod("transceive", byte[].class);
29 | isConnected = cls.getMethod("isConnected");
30 | connect = cls.getMethod("connect");
31 | //getMaxTransceiveLength = cls.getMethod("getMaxTransceiveLength");
32 | close = cls.getMethod("close");
33 | mTag = tag;
34 | }
35 |
36 | @Override
37 | public boolean isConnected() {
38 | Boolean ret = false;
39 | try {
40 | ret = (Boolean) isConnected.invoke(mTagTech);
41 | } catch (IllegalArgumentException e) {
42 | // TODO Auto-generated catch block
43 | e.printStackTrace();
44 | } catch (IllegalAccessException e) {
45 | // TODO Auto-generated catch block
46 | e.printStackTrace();
47 | } catch (InvocationTargetException e) {
48 | if (e.getTargetException() instanceof RuntimeException) {
49 | throw (RuntimeException) e.getTargetException();
50 | }
51 | e.printStackTrace();
52 | }
53 | return ret;
54 | }
55 |
56 | @Override
57 | public void connect() throws IOException {
58 | try {
59 | connect.invoke(mTagTech);
60 | } catch (IllegalArgumentException e) {
61 | // TODO Auto-generated catch block
62 | e.printStackTrace();
63 | } catch (IllegalAccessException e) {
64 | // TODO Auto-generated catch block
65 | e.printStackTrace();
66 | } catch (InvocationTargetException e) {
67 | if (e.getTargetException() instanceof RuntimeException) {
68 | throw (RuntimeException) e.getTargetException();
69 | }
70 | else if (e.getTargetException() instanceof IOException) {
71 | throw (IOException) e.getTargetException();
72 | }
73 | e.printStackTrace();
74 | }
75 | }
76 |
77 | @Override
78 | public void close() throws IOException {
79 | try {
80 | close.invoke(mTagTech);
81 | } catch (IllegalArgumentException e) {
82 | // TODO Auto-generated catch block
83 | e.printStackTrace();
84 | } catch (IllegalAccessException e) {
85 | // TODO Auto-generated catch block
86 | e.printStackTrace();
87 | } catch (InvocationTargetException e) {
88 | if (e.getTargetException() instanceof RuntimeException) {
89 | throw (RuntimeException) e.getTargetException();
90 | }
91 | else if (e.getTargetException() instanceof IOException) {
92 | throw (IOException) e.getTargetException();
93 | }
94 | e.printStackTrace();
95 | }
96 | }
97 |
98 | /*
99 | public int getMaxTransceiveLength() {
100 | try {
101 | return (Integer)getMaxTransceiveLength.invoke(mTagTech);
102 | } catch (IllegalArgumentException e) {
103 | // TODO Auto-generated catch block
104 | e.printStackTrace();
105 | } catch (IllegalAccessException e) {
106 | // TODO Auto-generated catch block
107 | e.printStackTrace();
108 | } catch (InvocationTargetException e) {
109 | if (e.getTargetException() instanceof RuntimeException) {
110 | throw (RuntimeException) e.getTargetException();
111 | }
112 | e.printStackTrace();
113 | }
114 | return 0;
115 | }
116 | */
117 | public byte[] transceive(byte[] data) throws IOException {
118 | try {
119 | return (byte[])transceive.invoke(mTagTech, data);
120 | } catch (IllegalArgumentException e) {
121 | // TODO Auto-generated catch block
122 | e.printStackTrace();
123 | } catch (IllegalAccessException e) {
124 | // TODO Auto-generated catch block
125 | e.printStackTrace();
126 | } catch (InvocationTargetException e) {
127 | if (e.getTargetException() instanceof RuntimeException) {
128 | throw (RuntimeException) e.getTargetException();
129 | }
130 | else if (e.getTargetException() instanceof IOException) {
131 | throw (IOException) e.getTargetException();
132 | }
133 | e.printStackTrace();
134 | System.err.println(e);
135 | System.err.println(e.getTargetException());
136 | }
137 | throw new IOException("transceive failed");
138 | }
139 |
140 | @Override
141 | public Tag getTag() {
142 | return mTag;
143 | }
144 | /*
145 | @Override
146 | public void reconnect() throws IOException {
147 | // TODO Auto-generated method stub
148 |
149 | }
150 | */
151 | }
152 |
--------------------------------------------------------------------------------
/src/org/eleetas/nfc/nfcproxy/utils/CryptoHelper.java:
--------------------------------------------------------------------------------
1 | package org.eleetas.nfc.nfcproxy.utils;
2 |
3 | import java.security.SecureRandom;
4 |
5 | import android.util.Base64;
6 |
7 | public class CryptoHelper {
8 |
9 | public static String generateSalt() {
10 | SecureRandom sr = new SecureRandom();
11 | byte[] salt = new byte[8];
12 | sr.nextBytes(salt);
13 | return Base64.encodeToString(salt, Base64.DEFAULT);
14 |
15 | }
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/src/org/eleetas/nfc/nfcproxy/utils/IOUtils.java:
--------------------------------------------------------------------------------
1 | package org.eleetas.nfc.nfcproxy.utils;
2 |
3 | import java.io.IOException;
4 | import java.io.InputStream;
5 | import java.io.OutputStream;
6 | import java.security.AlgorithmParameters;
7 | import java.util.Arrays;
8 |
9 | import javax.crypto.Cipher;
10 | import javax.crypto.SecretKey;
11 | import javax.crypto.spec.IvParameterSpec;
12 |
13 | public class IOUtils {
14 | public static byte[] readSocket(InputStream is, SecretKey secret, boolean encrypt) throws IOException {
15 | if (!encrypt) {
16 | return readUnencrypted(is);
17 | }
18 | else {
19 | if (secret == null) {
20 | return null;
21 | }
22 |
23 | try{
24 |
25 | byte[] iv = new byte[16];
26 | byte[] buffer = new byte[1024];
27 | int num = is.read(buffer);
28 | if (num < 0 || num <= iv.length) {
29 | return null;
30 | }
31 | byte[] cipherText = new byte[num - iv.length];
32 | System.arraycopy(buffer, 0, iv, 0, iv.length);
33 | System.arraycopy(buffer, iv.length, cipherText, 0, cipherText.length);
34 | Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
35 | cipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(iv));
36 | return cipher.doFinal(cipherText);
37 | } catch (Exception e) {
38 | e.printStackTrace();
39 | throw new IOException(e);
40 | }
41 | }
42 | }
43 |
44 | public static String readLine(InputStream is) {
45 | if(is == null) { return null;}
46 |
47 | byte[] c = new byte[1];
48 | byte[] str = new byte[1024];
49 | int i = 0;
50 | try {
51 | while ( is.read(c) != -1 && c[0] != 10 && i < 1024) {
52 | str[i] = c[0];
53 | i++;
54 | }
55 | if (c[0] == 10)
56 | return new String(str, 0, i, "UTF-8");
57 | else
58 | return null;
59 | }
60 | catch (IOException e) {
61 | e.printStackTrace();
62 | }
63 | return null;
64 | }
65 |
66 | private static byte[] readUnencrypted(InputStream is) {
67 | if (is == null ) { return null; }
68 | byte[] buf = new byte[1024];
69 | try {
70 | int numBytes = is.read(buf);
71 | if (numBytes != -1) {
72 | return Arrays.copyOf(buf, numBytes);
73 | }
74 | else {
75 | return null;
76 | }
77 | }
78 | catch (IOException e) {
79 | e.printStackTrace();
80 | }
81 | return null;
82 | }
83 |
84 | private static void sendUnencrypted(byte[] data, OutputStream os) throws IOException {
85 | os.write(data);
86 | os.flush();
87 | }
88 |
89 | public static void sendSocket(byte[] data, OutputStream os, SecretKey secret, boolean encrypt) throws IOException {
90 | if (!encrypt) {
91 | sendUnencrypted(data, os);
92 | }
93 | else
94 | {
95 | if (secret == null) {
96 | return;
97 | }
98 | try {
99 | Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
100 | cipher.init(Cipher.ENCRYPT_MODE, secret);
101 | AlgorithmParameters params = cipher.getParameters();
102 | byte[] iv = params.getParameterSpec(IvParameterSpec.class).getIV(); //16 bytes
103 | byte[] cipherText = cipher.doFinal(data);
104 | //for each piece of data send: iv + ciphertext
105 | byte[] send = new byte[iv.length + cipherText.length];
106 | System.arraycopy(iv, 0, send, 0, iv.length);
107 | System.arraycopy(cipherText, 0, send, iv.length, cipherText.length);
108 | os.write(send);
109 | os.flush();
110 |
111 | } catch (Exception e) {
112 | e.printStackTrace();
113 | throw new IOException(e);
114 | }
115 | }
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/src/org/eleetas/nfc/nfcproxy/utils/LogHelper.java:
--------------------------------------------------------------------------------
1 | package org.eleetas.nfc.nfcproxy.utils;
2 |
3 | import java.io.PrintWriter;
4 | import java.io.StringWriter;
5 |
6 | import org.eleetas.nfc.nfcproxy.R;
7 |
8 | import android.content.Context;
9 | import android.util.Log;
10 |
11 | public class LogHelper {
12 | public static void log(Context c, Object msg)
13 | {
14 | if (msg instanceof Exception) {
15 | Exception e = (Exception)msg;
16 | StringWriter sw = new StringWriter();
17 | e.printStackTrace(new PrintWriter(sw));
18 | Log.d(c.getString(R.string.app_name), e.toString() + "\n" + sw.toString());
19 | }
20 | else {
21 | Log.d(c.getString(R.string.app_name), String.valueOf(msg));
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/org/eleetas/nfc/nfcproxy/utils/TagHelper.java:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nfcproxy/NFCProxy/8beea18bddc0d31e530a892457296a77efd7a435/src/org/eleetas/nfc/nfcproxy/utils/TagHelper.java
--------------------------------------------------------------------------------
/src/org/eleetas/nfc/nfcproxy/utils/TextHelper.java:
--------------------------------------------------------------------------------
1 | package org.eleetas.nfc.nfcproxy.utils;
2 |
3 | public class TextHelper {
4 | public static String byteArrayToHexString(byte[] b) {
5 | return byteArrayToHexString(b, "0x", " ", false);
6 | }
7 |
8 | public static String byteArrayToHexString(byte[] b, String hexPrefix, String hexSuffix, boolean cast) {
9 | if (b == null) return null;
10 | StringBuffer sb = new StringBuffer(b.length * 2);
11 | for (int i = 0; i < b.length; i++) {
12 | int v = b[i] & 0xff;
13 | if (cast && v > 0x7f) {
14 | sb.append("(byte)");
15 | }
16 | sb.append(hexPrefix);
17 | if (v < 16) {
18 | sb.append('0');
19 | }
20 | sb.append(Integer.toHexString(v));
21 | if (i + 1 != b.length) {
22 | sb.append(hexSuffix);
23 | }
24 | }
25 | return sb.toString();
26 | }
27 | }
28 |
--------------------------------------------------------------------------------