├── .classpath
├── .gitignore
├── .project
├── AndroidManifest.xml
├── LICENSE
├── README.md
├── proguard-project.txt
├── project.properties
├── res
├── drawable-hdpi
│ ├── ic_action_search.png
│ └── ic_launcher.png
├── drawable-mdpi
│ ├── ic_action_search.png
│ └── ic_launcher.png
├── drawable-xhdpi
│ ├── ic_action_search.png
│ └── ic_launcher.png
├── layout
│ └── activity_main.xml
├── values-v11
│ └── styles.xml
├── values-v14
│ └── styles.xml
└── values
│ ├── strings.xml
│ └── styles.xml
└── src
└── org
└── nick
└── nfc
└── seaccess
├── CPLC.java
├── GPCommands.java
├── Hex.java
├── KeyInfo.java
├── MainActivity.java
├── MifareManagerCommands.java
├── PPSE.java
├── SECardResponse.java
├── SEConnection.java
├── SEEMVSession.java
├── SEReceiver.java
├── SETerminal.java
├── SETerminalProvider.java
├── SecurityDomainFCI.java
├── WalletControllerCommands.java
└── WalletControllerFCI.java
/.classpath:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # built application files
2 | *.apk
3 | *.ap_
4 |
5 | # files for the dex VM
6 | *.dex
7 |
8 | # Java class files
9 | *.class
10 |
11 | # generated files
12 | bin/
13 | gen/
14 |
15 | # Local configuration file (sdk path, etc)
16 | local.properties
17 |
--------------------------------------------------------------------------------
/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | android-se-access
4 |
5 |
6 |
7 |
8 |
9 | com.android.ide.eclipse.adt.ResourceManagerBuilder
10 |
11 |
12 |
13 |
14 | com.android.ide.eclipse.adt.PreCompilerBuilder
15 |
16 |
17 |
18 |
19 | org.eclipse.jdt.core.javabuilder
20 |
21 |
22 |
23 |
24 | com.android.ide.eclipse.adt.ApkBuilder
25 |
26 |
27 |
28 |
29 |
30 | com.android.ide.eclipse.adt.AndroidNature
31 | org.eclipse.jdt.core.javanature
32 |
33 |
34 |
--------------------------------------------------------------------------------
/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
5 |
6 |
9 |
10 |
11 |
12 |
16 |
17 |
20 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2012 Nikolay Elenkov
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Android Secure Element (SE) access sample app
2 | ==============================================
3 |
4 | Shows how to access the secure element on Android 4.0.4 and 4.1.
5 | Requires a rooted device to install and run. More details and
6 | background information in related blog posts:
7 |
8 | * http://nelenkov.blogspot.com/2012/08/accessing-embedded-secure-element-in.html
9 | * http://nelenkov.blogspot.com/2012/08/android-secure-element-execution.html
10 | * http://nelenkov.blogspot.com/2012/08/exploring-google-wallet-using-secure.html
11 |
12 | **WARNING**
13 |
14 | While this program doesn't try to modify the SE and doesn't contain any
15 | 'dangerous' SE commands, using and/or modifying it may lock (brick) the secure
16 | element on your phone. Make sure you know what you are doing and use at your
17 | own risk!
18 |
19 | Building
20 | --------
21 |
22 | 1. Get the source code for the Java EMV Reader library from http://code.google.com/p/javaemvreader/ and build it.
23 | 2. Drop the resulting jar file in `lib/`.
24 | 3. Import the project in Eclipse and build it.
25 |
26 | Installation and running
27 | ------------------------
28 |
29 | 1. Add the signing certificate to `/etc/nfcee_access.xml` on your device.
30 | This requires root access to remount `/system` as rw. See first blog post
31 | for details on file format.
32 | 2. Sign and install the APK.
33 | 3. Run from launcher or Eclipse.
34 | 4. (Optional) Install Google Wallet to test EMV functionality.
35 |
36 |
37 | Read linked blog posts for more details.
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/proguard-project.txt:
--------------------------------------------------------------------------------
1 | # To enable ProGuard in your project, edit project.properties
2 | # to define the proguard.config property as described in that file.
3 | #
4 | # Add project specific ProGuard rules here.
5 | # By default, the flags in this file are appended to flags specified
6 | # in ${sdk.dir}/tools/proguard/proguard-android.txt
7 | # You can edit the include path and order by changing the ProGuard
8 | # include property in project.properties.
9 | #
10 | # For more details, see
11 | # http://developer.android.com/guide/developing/tools/proguard.html
12 |
13 | # Add any project specific keep options here:
14 |
15 | # If your project uses WebView with JS, uncomment the following
16 | # and specify the fully qualified class name to the JavaScript interface
17 | # class:
18 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
19 | # public *;
20 | #}
21 |
--------------------------------------------------------------------------------
/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 edit
7 | # "ant.properties", and override values to adapt the script to your
8 | # project structure.
9 | #
10 | # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
11 | #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
12 |
13 | # Project target.
14 | target=android-16
15 |
--------------------------------------------------------------------------------
/res/drawable-hdpi/ic_action_search.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nelenkov/android-se-access/d28f28dac1a6d8ba0b55b94003e061308a60b3ac/res/drawable-hdpi/ic_action_search.png
--------------------------------------------------------------------------------
/res/drawable-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nelenkov/android-se-access/d28f28dac1a6d8ba0b55b94003e061308a60b3ac/res/drawable-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/res/drawable-mdpi/ic_action_search.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nelenkov/android-se-access/d28f28dac1a6d8ba0b55b94003e061308a60b3ac/res/drawable-mdpi/ic_action_search.png
--------------------------------------------------------------------------------
/res/drawable-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nelenkov/android-se-access/d28f28dac1a6d8ba0b55b94003e061308a60b3ac/res/drawable-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/res/drawable-xhdpi/ic_action_search.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nelenkov/android-se-access/d28f28dac1a6d8ba0b55b94003e061308a60b3ac/res/drawable-xhdpi/ic_action_search.png
--------------------------------------------------------------------------------
/res/drawable-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nelenkov/android-se-access/d28f28dac1a6d8ba0b55b94003e061308a60b3ac/res/drawable-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
17 |
18 |
23 |
24 |
30 |
31 |
37 |
38 |
43 |
44 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/res/values-v11/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/res/values-v14/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | SE Access
4 | Make sure nfcee_access.xml is modified and use buttons to get SE/Wallet info.
5 | @string/app_name
6 |
7 |
--------------------------------------------------------------------------------
/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/org/nick/nfc/seaccess/CPLC.java:
--------------------------------------------------------------------------------
1 | package org.nick.nfc.seaccess;
2 |
3 | import java.io.ByteArrayInputStream;
4 | import java.util.Arrays;
5 | import java.util.LinkedHashMap;
6 | import java.util.Map;
7 |
8 | import sasc.emv.EMVUtil;
9 | import sasc.iso7816.BERTLV;
10 | import sasc.iso7816.Tag;
11 | import sasc.iso7816.TagImpl;
12 | import sasc.iso7816.TagValueType;
13 |
14 | public class CPLC {
15 |
16 | public static final Tag CPLC_TAG = new TagImpl("9f7f", TagValueType.BINARY,
17 | "Card Production Life Cycle Data",
18 | "Card Production Life Cycle Data");
19 |
20 | private static Map FIELD_NAMES_LENGTHS = new LinkedHashMap();
21 |
22 | private Map fields = new LinkedHashMap();
23 |
24 | static {
25 | FIELD_NAMES_LENGTHS.put("IC Fabricator", 2);
26 | FIELD_NAMES_LENGTHS.put("IC Type", 2);
27 | FIELD_NAMES_LENGTHS.put("Operating System Provider Identifier", 2);
28 | FIELD_NAMES_LENGTHS.put("Operating System Release Date", 2);
29 | FIELD_NAMES_LENGTHS.put("Operating System Release Level", 2);
30 | FIELD_NAMES_LENGTHS.put("IC Fabrication Date", 2);
31 | FIELD_NAMES_LENGTHS.put("IC Serial Number", 4);
32 | FIELD_NAMES_LENGTHS.put("IC Batch Identifier", 2);
33 | FIELD_NAMES_LENGTHS.put("IC ModuleFabricator", 2);
34 | FIELD_NAMES_LENGTHS.put("IC ModulePackaging Date", 2);
35 | FIELD_NAMES_LENGTHS.put("ICC Manufacturer", 2);
36 | FIELD_NAMES_LENGTHS.put("IC Embedding Date", 2);
37 | FIELD_NAMES_LENGTHS.put("Prepersonalizer Identifier", 2);
38 | FIELD_NAMES_LENGTHS.put("Prepersonalization Date", 2);
39 | FIELD_NAMES_LENGTHS.put("Prepersonalization Equipment", 4);
40 | FIELD_NAMES_LENGTHS.put("Personalizer Identifier", 2);
41 | FIELD_NAMES_LENGTHS.put("Personalization Date", 2);
42 | FIELD_NAMES_LENGTHS.put("Personalization Equipment", 4);
43 | }
44 |
45 |
46 | private CPLC() {
47 | }
48 |
49 | public static CPLC parse(byte[] raw) {
50 | CPLC result = new CPLC();
51 | BERTLV tlv = EMVUtil.getNextTLV(new ByteArrayInputStream(raw));
52 | if (!tlv.getTag().equals(CPLC_TAG)) {
53 | throw new IllegalArgumentException("Not a valid CPLC. Found tag: "
54 | + tlv.getTag());
55 | }
56 |
57 | int idx = 0;
58 | byte[] cplc = tlv.getValueBytes();
59 | for (String fieldName : FIELD_NAMES_LENGTHS.keySet()) {
60 | int length = FIELD_NAMES_LENGTHS.get(fieldName);
61 | byte[] value = Arrays.copyOfRange(cplc, idx, idx + length);
62 | idx += length;
63 | result.fields.put(fieldName, Hex.toHex(value));
64 | }
65 |
66 | return result;
67 | }
68 |
69 | @Override
70 | public String toString() {
71 | StringBuilder buff = new StringBuilder();
72 | buff.append("CPLC");
73 | buff.append("\n");
74 | for (String key : fields.keySet()) {
75 | buff.append(String.format(" %s: %s", key, fields.get(key)));
76 | buff.append("\n");
77 | }
78 |
79 | return buff.toString();
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/src/org/nick/nfc/seaccess/GPCommands.java:
--------------------------------------------------------------------------------
1 | package org.nick.nfc.seaccess;
2 |
3 | public class GPCommands {
4 |
5 | public static final byte[] EMPTY_SELECT = { (byte) 0x00, (byte) 0xa4,
6 | (byte) 0x04, (byte) 0x00, (byte) 0x00 };
7 |
8 | public static final byte[] GET_ISSUER_ID_COMMAND = { (byte) 0x80,
9 | (byte) 0xCA, (byte) 0x00, 0x42, 0x00 };
10 |
11 | public static final byte[] GET_CARD_DATA = { (byte) 0x80, (byte) 0xCA,
12 | (byte) 0x00, 0x66, 0x00 };
13 |
14 | public static final byte[] GET_KEY_INFORMATION_TEMPLATE_COMMAND = {
15 | (byte) 0x80, (byte) 0xCA, (byte) 0x00, (byte) 0xe0, 0x00 };
16 |
17 | public static final byte[] GET_KEY_VERSION_SEQUENCE_COUNTER_COMMAND = {
18 | (byte) 0x80, (byte) 0xCA, (byte) 0x00, (byte) 0xc1, 0x00 };
19 |
20 | public static final byte[] SELECT_CARD_MANAGER_COMMAND = { (byte) 0x00,
21 | (byte) 0xA4, (byte) 0x04, (byte) 0x00, (byte) 0x08, (byte) 0xA0,
22 | (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x03, (byte) 0x00,
23 | (byte) 0x00, (byte) 0x00, (byte) 0x00 };
24 |
25 | public static final byte[] GET_CPLC_COMMAND = { (byte) 0x80, (byte) 0xCA,
26 | (byte) 0x9F, (byte) 0x7F, 0x00 };
27 |
28 | private GPCommands() {
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/org/nick/nfc/seaccess/Hex.java:
--------------------------------------------------------------------------------
1 | package org.nick.nfc.seaccess;
2 |
3 | public class Hex {
4 |
5 | private Hex() {
6 | }
7 |
8 | public static String toHex(byte[] bytes) {
9 | StringBuffer buff = new StringBuffer();
10 | for (byte b : bytes) {
11 | buff.append(String.format("%02X", b));
12 | }
13 |
14 | return buff.toString();
15 | }
16 |
17 | public static byte[] fromHex(String digits) {
18 | digits = digits.replace(" ", "");
19 | final int bytes = digits.length() / 2;
20 | if (2 * bytes != digits.length()) {
21 | throw new IllegalArgumentException(
22 | "Hex string must have an even number of digits");
23 | }
24 |
25 | byte[] result = new byte[bytes];
26 | for (int i = 0; i < digits.length(); i += 2) {
27 | result[i / 2] = (byte) Integer.parseInt(digits.substring(i, i + 2),
28 | 16);
29 | }
30 | return result;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/org/nick/nfc/seaccess/KeyInfo.java:
--------------------------------------------------------------------------------
1 | package org.nick.nfc.seaccess;
2 |
3 | import java.io.ByteArrayInputStream;
4 | import java.util.ArrayList;
5 | import java.util.List;
6 |
7 | import sasc.emv.EMVUtil;
8 | import sasc.iso7816.BERTLV;
9 | import sasc.iso7816.Tag;
10 | import sasc.iso7816.TagImpl;
11 | import sasc.iso7816.TagValueType;
12 |
13 | public class KeyInfo {
14 |
15 | public static final Tag KEY_INFORMATION_TEMPLATE = new TagImpl("E0",
16 | TagValueType.BINARY, "Key information template",
17 | "Key information template");
18 |
19 | public static final Tag KEY_INFORMATION_DATA = new TagImpl("C0",
20 | TagValueType.BINARY, "Key information data", "Key information data");
21 |
22 | private static final int ID_IDX = 0;
23 | private static final int VERSION_IDX = 1;
24 | private static final int KEY_TYPE_IDX = 2;
25 | private static final int LENGTH_IDX = 3;
26 |
27 |
28 | private byte id;
29 | private byte version;
30 | private byte keyType;
31 | // bytes
32 | private byte length;
33 |
34 | public KeyInfo(byte id, byte version, byte keyType, byte length) {
35 | this.id = id;
36 | this.version = version;
37 | this.keyType = keyType;
38 | this.length = length;
39 | }
40 |
41 | public static KeyInfo createFromRawData(byte[] raw) {
42 | return new KeyInfo(raw[ID_IDX], raw[VERSION_IDX], raw[KEY_TYPE_IDX],
43 | raw[LENGTH_IDX]);
44 | }
45 |
46 | public byte getId() {
47 | return id;
48 | }
49 |
50 | public byte getVersion() {
51 | return version;
52 | }
53 |
54 | public byte getKeyType() {
55 | return keyType;
56 | }
57 |
58 | public String getKeyTypeAsString() {
59 | return keyTypeAsString(keyType);
60 | }
61 |
62 | public byte getLength() {
63 | return length;
64 | }
65 |
66 | public static String keyTypeAsString(byte keyType) {
67 | switch (keyType) {
68 | case (byte) 0x80:
69 | return "DES (EBC/CBC)";
70 | case (byte) 0xA0:
71 | return "RSA Pub - e (clear)";
72 | case (byte) 0xA1:
73 | return "RSA Pub - N (clear)";
74 | case (byte) 0xA2:
75 | return "RSA Priv - N";
76 | case (byte) 0xA3:
77 | return "RSA Priv - d";
78 | case (byte) 0xA4:
79 | return "RSA Priv - CRT P";
80 | case (byte) 0xA5:
81 | return "RSA Priv - CRT Q";
82 | case (byte) 0xA6:
83 | return "RSA Priv - CRT PQ";
84 | case (byte) 0xA7:
85 | return "RSA Priv - CRT DP1";
86 | case (byte) 0xA8:
87 | return "RSA Priv - CRT DQ1";
88 | case (byte) 0xFF:
89 | return "N/A";
90 | default:
91 | // 'A9'-'FE' RFU (asymmetric algorithms)
92 | // '81'-'9F' RFU (symmetric algorithms) or anything else
93 | return "RFU or unknown";
94 | }
95 | }
96 |
97 | @Override
98 | public String toString() {
99 | return String.format("ID: %d, version: %s, type: %s, length: %d bits",
100 | id, version, getKeyTypeAsString(), length * 8);
101 | }
102 |
103 | public static List parse(byte[] raw) {
104 | List result = new ArrayList();
105 | BERTLV tlv = EMVUtil.getNextTLV(new ByteArrayInputStream(raw));
106 |
107 | if (tlv.getTag().equals(KEY_INFORMATION_TEMPLATE)) {
108 | ByteArrayInputStream templateStream = tlv.getValueStream();
109 |
110 | while (templateStream.available() >= 2) {
111 | tlv = EMVUtil.getNextTLV(templateStream);
112 | if (tlv.getTag().equals(KEY_INFORMATION_DATA)) {
113 | byte[] keyInfoData = tlv.getValueBytes();
114 | KeyInfo ki = KeyInfo.createFromRawData(keyInfoData);
115 | result.add(ki);
116 | }
117 | }
118 | } else {
119 | throw new IllegalArgumentException("Invalid key information data");
120 | }
121 |
122 | return result;
123 | }
124 |
125 |
126 | }
127 |
--------------------------------------------------------------------------------
/src/org/nick/nfc/seaccess/MainActivity.java:
--------------------------------------------------------------------------------
1 | package org.nick.nfc.seaccess;
2 |
3 | import static org.nick.nfc.seaccess.Hex.fromHex;
4 |
5 | import java.util.Collection;
6 | import java.util.List;
7 |
8 | import sasc.emv.EMVApplication;
9 | import sasc.emv.EMVCard;
10 | import sasc.emv.EMVUtil;
11 | import sasc.emv.SW;
12 | import sasc.iso7816.AID;
13 | import sasc.iso7816.BERTLV;
14 | import sasc.terminal.CardConnection;
15 | import sasc.terminal.CardResponse;
16 | import sasc.terminal.Terminal;
17 | import sasc.terminal.TerminalException;
18 | import sasc.util.Util;
19 | import android.app.Activity;
20 | import android.os.AsyncTask;
21 | import android.os.Bundle;
22 | import android.util.Log;
23 | import android.view.View;
24 | import android.view.View.OnClickListener;
25 | import android.view.Window;
26 | import android.widget.Button;
27 | import android.widget.TextView;
28 | import android.widget.Toast;
29 |
30 | public class MainActivity extends Activity implements OnClickListener {
31 |
32 | private static final String TAG = MainActivity.class.getSimpleName();
33 |
34 | private Button gpInfoButton;
35 | private Button emvInfoButton;
36 | private Button walletInfoButton;
37 |
38 | private TextView infoText;
39 |
40 | private Terminal terminal;
41 | private CardConnection seConn;
42 |
43 | @Override
44 | public void onCreate(Bundle savedInstanceState) {
45 | requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
46 |
47 | super.onCreate(savedInstanceState);
48 | setContentView(R.layout.activity_main);
49 |
50 | setProgressBarIndeterminateVisibility(false);
51 |
52 | gpInfoButton = (Button) findViewById(R.id.gp_info_button);
53 | gpInfoButton.setOnClickListener(this);
54 |
55 | emvInfoButton = (Button) findViewById(R.id.emv_info_button);
56 | emvInfoButton.setOnClickListener(this);
57 |
58 | walletInfoButton = (Button) findViewById(R.id.wallet_info_button);
59 | walletInfoButton.setOnClickListener(this);
60 |
61 | infoText = (TextView) findViewById(R.id.info_text);
62 |
63 | terminal = new SETerminal(getApplication());
64 | }
65 |
66 | @Override
67 | public void onDestroy() {
68 | super.onDestroy();
69 |
70 | closeSeSilently();
71 | }
72 |
73 | private void closeSeSilently() {
74 | if (seConn != null) {
75 | try {
76 | seConn.disconnect(false);
77 | } catch (TerminalException e) {
78 | Log.w(TAG, "Eror closing SE: " + e.getMessage(), e);
79 | }
80 | }
81 | }
82 |
83 | @Override
84 | public void onStart() {
85 | super.onStart();
86 |
87 | try {
88 | seConn = terminal.connect();
89 | } catch (TerminalException e) {
90 | String message = "Failed to open SE: " + e.getMessage();
91 | Log.w(TAG, message, e);
92 | Toast.makeText(this, message, Toast.LENGTH_LONG).show();
93 | seConn = null;
94 | finish();
95 | }
96 | }
97 |
98 | @Override
99 | public void onStop() {
100 | super.onStop();
101 |
102 | closeSeSilently();
103 | seConn = null;
104 | }
105 |
106 | @Override
107 | public void onClick(View v) {
108 | try {
109 | if (v.getId() == R.id.gp_info_button) {
110 | new AsyncTask() {
111 |
112 | Exception error;
113 |
114 | @Override
115 | protected void onPreExecute() {
116 | setProgressBarIndeterminateVisibility(true);
117 | toggleButtons(false);
118 | }
119 |
120 | @Override
121 | protected Object[] doInBackground(Void... arg0) {
122 | try {
123 | CardResponse response = transmit(
124 | GPCommands.EMPTY_SELECT, "EMPTY_SELECT");
125 | if (response.getSW() != SW.SUCCESS.getSW()) {
126 | // do something
127 | return null;
128 | }
129 |
130 | SecurityDomainFCI sdFci = SecurityDomainFCI
131 | .parse(response.getData());
132 | Log.d(TAG, "SD FCI: " + sdFci.toString());
133 |
134 | response = transmit(
135 | GPCommands.GET_ISSUER_ID_COMMAND,
136 | "GET_ISSUER_ID_COMMAND");
137 |
138 | response = transmit(fromHex("00CA004500"),
139 | "GET_CARD_IMAGE_NUMBER");
140 |
141 | response = transmit(GPCommands.GET_CARD_DATA,
142 | "GET_CARD_DATA");
143 |
144 | List keys = null;
145 | response = transmit(
146 | GPCommands.GET_KEY_INFORMATION_TEMPLATE_COMMAND,
147 | "GET_KEY_INFORMATION_TEMPLATE_COMMAND");
148 | if (response.getSW() == SW.SUCCESS.getSW()) {
149 | keys = KeyInfo.parse(response.getData());
150 | for (KeyInfo key : keys) {
151 | Log.d(TAG, "Key: " + key);
152 | }
153 | }
154 |
155 | response = transmit(
156 | GPCommands.GET_KEY_VERSION_SEQUENCE_COUNTER_COMMAND,
157 | "GET_KEY_VERSION_SEQUENCE_COUNTER_COMMAND");
158 |
159 | response = transmit(GPCommands.GET_CPLC_COMMAND,
160 | "GET_CPLC_COMMAND");
161 | CPLC cplc = null;
162 | if (response.getSW() == SW.SUCCESS.getSW()) {
163 | cplc = CPLC.parse(response.getData());
164 | }
165 | Log.d(TAG, "CPLC: " + cplc);
166 |
167 | return new Object[] { sdFci, keys, cplc };
168 | } catch (Exception e) {
169 | Log.e(TAG, "Error:" + e.getMessage(), e);
170 | return null;
171 | }
172 | }
173 |
174 | @SuppressWarnings("unchecked")
175 | @Override
176 | protected void onPostExecute(Object[] data) {
177 | setProgressBarIndeterminateVisibility(false);
178 | toggleButtons(true);
179 |
180 | if (data == null && error != null) {
181 | Toast.makeText(MainActivity.this,
182 | "Error: " + error.getMessage(),
183 | Toast.LENGTH_LONG).show();
184 |
185 | return;
186 | }
187 |
188 | if (data == null) {
189 | infoText.setText("Error selecting CardManager. Does the SE work?");
190 | return;
191 | }
192 |
193 | infoText.setText(getGpInfoDisplayString(
194 | (SecurityDomainFCI) data[0],
195 | (List) data[1], (CPLC) data[2]));
196 | }
197 | }.execute();
198 | } else if (v.getId() == R.id.emv_info_button) {
199 | new AsyncTask() {
200 |
201 | Exception error;
202 |
203 | @Override
204 | protected void onPreExecute() {
205 | setProgressBarIndeterminateVisibility(true);
206 | toggleButtons(false);
207 | }
208 |
209 | @Override
210 | protected EMVCard doInBackground(Void... arg0) {
211 | try {
212 | SEEMVSession emvSession = SEEMVSession
213 | .startSession(getApplication(), seConn);
214 | EMVCard emvCard = emvSession.initCard();
215 | Log.d(TAG, "card: " + emvCard);
216 | if (emvCard != null) {
217 | Collection apps = emvCard
218 | .getApplications();
219 | for (EMVApplication app : apps) {
220 | Log.d(TAG, "EMV app: " + app);
221 | // always fails with 0x6999
222 | // emvSession.selectApplication(app);
223 | }
224 | }
225 |
226 | return emvCard;
227 | } catch (Exception e) {
228 | Log.e(TAG, "Error: " + e.getMessage(), e);
229 | error = e;
230 | return null;
231 | }
232 | }
233 |
234 | @Override
235 | protected void onPostExecute(EMVCard emvCard) {
236 | setProgressBarIndeterminateVisibility(false);
237 | toggleButtons(true);
238 |
239 | if (emvCard == null && error != null) {
240 | Toast.makeText(MainActivity.this,
241 | "Error: " + error.getMessage(),
242 | Toast.LENGTH_LONG).show();
243 |
244 | return;
245 | }
246 |
247 | infoText.setText(getEmvCardDisplayString(emvCard));
248 | }
249 | }.execute();
250 |
251 | } else if (v.getId() == R.id.wallet_info_button) {
252 | new AsyncTask() {
253 |
254 | Exception error;
255 |
256 | @Override
257 | protected void onPreExecute() {
258 | setProgressBarIndeterminateVisibility(true);
259 | toggleButtons(false);
260 | }
261 |
262 | @Override
263 | protected Object[] doInBackground(Void... arg0) {
264 | try {
265 | CardResponse response = transmit(
266 | WalletControllerCommands.SELECT_WALLET_CONTROLLER_COMMAND,
267 | "SELECT_WALLET_CONTROLLER_COMMAND");
268 |
269 | WalletControllerFCI wcFci = null;
270 | if (response.getSW() == SW.SUCCESS.getSW()) {
271 | wcFci = WalletControllerFCI.parse(response
272 | .getData());
273 | Log.d(TAG,
274 | "Wallet controller: "
275 | + wcFci.toString());
276 | } else {
277 | Log.d(TAG, "Wallet controller applet not found");
278 | }
279 |
280 | AID mmAid = null;
281 | response = transmit(
282 | MifareManagerCommands.SELECT_MIFARE_MANAGER_COMMAND,
283 | "SELECT_MIFARE_MANAGER_COMMAND");
284 | if (response.getSW() == SW.SUCCESS.getSW()) {
285 | mmAid = MifareManagerCommands.MIFARE_MANAGER_AID;
286 | } else {
287 | Log.d(TAG,
288 | "Mifare manager applet not found. SW: "
289 | + Integer.toHexString(response
290 | .getSW()));
291 | }
292 |
293 | return new Object[] { wcFci, mmAid };
294 | } catch (Exception e) {
295 | Log.e(TAG, "Error: " + e.getMessage(), e);
296 | return null;
297 | }
298 | }
299 |
300 | @Override
301 | protected void onPostExecute(Object[] data) {
302 | setProgressBarIndeterminateVisibility(false);
303 | toggleButtons(true);
304 |
305 | if (data == null && error != null) {
306 | Toast.makeText(MainActivity.this,
307 | "Error: " + error.getMessage(),
308 | Toast.LENGTH_LONG).show();
309 |
310 | return;
311 | }
312 |
313 | infoText.setText(getWalletDisplayString(
314 | (WalletControllerFCI) data[0], (AID) data[1]));
315 | }
316 | }.execute();
317 | }
318 | } catch (Exception e) {
319 | Log.e(TAG, "Error: " + e.getMessage());
320 | Toast.makeText(this, "Error: " + e.getMessage(), Toast.LENGTH_LONG)
321 | .show();
322 | }
323 | }
324 |
325 | private CardResponse transmit(byte[] command, String description)
326 | throws TerminalException {
327 | Log.d(TAG, description);
328 | CardResponse response = seConn.transmit(command);
329 | EMVUtil.printResponse(response, true);
330 |
331 | return response;
332 | }
333 |
334 | private CharSequence getGpInfoDisplayString(SecurityDomainFCI sdFci,
335 | List keys, CPLC cplc) {
336 | StringBuilder buff = new StringBuilder();
337 | buff.append(sdFci.toString());
338 | buff.append("\n\n");
339 | if (keys != null) {
340 | buff.append("Security domain keys:\n");
341 | for (KeyInfo key : keys) {
342 | buff.append(Util.getSpaces(2) + key.toString() + "\n");
343 | }
344 | }
345 | buff.append("\n");
346 |
347 | buff.append("Card Production Life Cyle Data");
348 | buff.append("\n");
349 | buff.append(cplc.toString());
350 |
351 | return buff.toString();
352 | }
353 |
354 | private void toggleButtons(boolean enable) {
355 | gpInfoButton.setEnabled(enable);
356 | emvInfoButton.setEnabled(enable);
357 | walletInfoButton.setEnabled(enable);
358 | }
359 |
360 | private String getWalletDisplayString(WalletControllerFCI wcFci, AID mmAid) {
361 | StringBuilder buff = new StringBuilder();
362 | buff.append("Wallet applets: ");
363 | buff.append("\n\n");
364 | buff.append(wcFci == null ? "Wallet controller: not installed" : wcFci
365 | .toString());
366 | buff.append("\n\n");
367 | buff.append("MIFARE manager applet");
368 | buff.append("\n");
369 | buff.append(mmAid == null ? "Mifare manager: not installed" : mmAid
370 | .toString());
371 |
372 | return buff.toString();
373 | }
374 |
375 | private String getEmvCardDisplayString(EMVCard card) {
376 | StringBuilder buff = new StringBuilder();
377 | int indent = 2;
378 |
379 | if (card.getApplications().isEmpty()) {
380 | buff.append("Google Wallet not installed or locked. Install and unlock Wallet and try again.");
381 | buff.append("\n");
382 | // PPSE in fact
383 | if (card.getPSE() != null) {
384 | buff.append("\n");
385 | buff.append("PPSE: ");
386 | buff.append("\n");
387 | buff.append(Util.getSpaces(indent) + card.getPSE().toString());
388 |
389 | return buff.toString();
390 | }
391 | }
392 |
393 | buff.append((Util.getSpaces(indent) + "EMV applications on SE"));
394 | buff.append("\n\n");
395 | buff.append(Util.getSpaces(indent + 2 * indent) + "Applications ("
396 | + card.getApplications().size() + " found):");
397 | buff.append("\n");
398 | for (EMVApplication app : card.getApplications()) {
399 | buff.append(Util.getSpaces(indent + 3 * indent) + app.toString());
400 | }
401 |
402 | if (card.getMasterFile() != null) {
403 | buff.append(Util.getSpaces(indent + indent) + "MF: "
404 | + card.getMasterFile());
405 | }
406 |
407 | buff.append("------------------------------------------------------");
408 | buff.append("Extra info (if any)");
409 | buff.append(Util.getSpaces(indent) + "ATR: " + card.getATR());
410 | buff.append(Util.getSpaces(indent + indent) + "Interface Type: "
411 | + card.getType());
412 | buff.append("\n");
413 |
414 |
415 | if (!card.getUnhandledRecords().isEmpty()) {
416 | buff.append(Util.getSpaces(indent + indent)
417 | + "UNHANDLED GLOBAL RECORDS ("
418 | + card.getUnhandledRecords().size() + " found):");
419 |
420 | for (BERTLV tlv : card.getUnhandledRecords()) {
421 | buff.append(Util.getSpaces(indent + 2 * indent) + tlv.getTag()
422 | + " " + tlv);
423 | }
424 | }
425 | buff.append("\n");
426 |
427 | return buff.toString();
428 | }
429 |
430 | }
431 |
--------------------------------------------------------------------------------
/src/org/nick/nfc/seaccess/MifareManagerCommands.java:
--------------------------------------------------------------------------------
1 | package org.nick.nfc.seaccess;
2 |
3 | import sasc.iso7816.AID;
4 |
5 | public class MifareManagerCommands {
6 |
7 | public static final AID MIFARE_MANAGER_AID = new AID("A0000004763030");
8 |
9 | public static final byte[] SELECT_MIFARE_MANAGER_COMMAND = { (byte) 0x00,
10 | (byte) 0xA4, (byte) 0x04, (byte) 0x00, (byte) 0x07, (byte) 0xA0,
11 | 0x00, 0x00, 0x04, 0x76, 0x30, 0x30, 0x00 };
12 |
13 | }
14 |
--------------------------------------------------------------------------------
/src/org/nick/nfc/seaccess/PPSE.java:
--------------------------------------------------------------------------------
1 | package org.nick.nfc.seaccess;
2 |
3 | import java.io.ByteArrayInputStream;
4 |
5 | import sasc.emv.ApplicationPriorityIndicator;
6 | import sasc.emv.DDF;
7 | import sasc.emv.EMVApplication;
8 | import sasc.emv.EMVCard;
9 | import sasc.emv.EMVTags;
10 | import sasc.emv.EMVUtil;
11 | import sasc.iso7816.AID;
12 | import sasc.iso7816.BERTLV;
13 | import sasc.util.Util;
14 |
15 | public class PPSE {
16 |
17 | // mostly extracted from javaemvreader/EMVUtil.parseFCIDDF()
18 | public static DDF parse(byte[] data, EMVCard card) {
19 | DDF ddf = new DDF();
20 | BERTLV tlv = EMVUtil.getNextTLV(new ByteArrayInputStream(data));
21 |
22 | if (tlv.getTag().equals(EMVTags.FCI_TEMPLATE)) {
23 | ByteArrayInputStream templateStream = tlv.getValueStream();
24 |
25 | while (templateStream.available() >= 2) {
26 | tlv = EMVUtil.getNextTLV(templateStream);
27 | if (tlv.getTag().equals(EMVTags.DEDICATED_FILE_NAME)) {
28 | ddf.setName(tlv.getValueBytes());
29 | } else if (tlv.getTag()
30 | .equals(EMVTags.FCI_PROPRIETARY_TEMPLATE)) {
31 | ByteArrayInputStream bis2 = new ByteArrayInputStream(
32 | tlv.getValueBytes());
33 | int totalLen = bis2.available();
34 | int templateLen = tlv.getLength();
35 | while (bis2.available() > (totalLen - templateLen)) {
36 | tlv = EMVUtil.getNextTLV(bis2);
37 |
38 | if (tlv.getTag().equals(
39 | EMVTags.FCI_ISSUER_DISCRETIONARY_DATA)) {
40 | ByteArrayInputStream discrStream = new ByteArrayInputStream(
41 | tlv.getValueBytes());
42 | int total3Len = discrStream.available();
43 | int template3Len = tlv.getLength();
44 | while (discrStream.available() > (total3Len - template3Len)) {
45 | tlv = EMVUtil.getNextTLV(discrStream);
46 |
47 | if (tlv.getTag().equals(
48 | EMVTags.APPLICATION_TEMPLATE)) {
49 | ByteArrayInputStream appTemplateStream = new ByteArrayInputStream(
50 | tlv.getValueBytes());
51 | int appTemplateTotalLen = appTemplateStream
52 | .available();
53 | int template4Len = tlv.getLength();
54 | EMVApplication app = new EMVApplication();
55 | while (appTemplateStream.available() > (appTemplateTotalLen - template4Len)) {
56 | tlv = EMVUtil
57 | .getNextTLV(appTemplateStream);
58 |
59 | if (tlv.getTag().equals(
60 | EMVTags.AID_CARD)) {
61 | app.setAID(new AID(tlv
62 | .getValueBytes()));
63 | } else if (tlv.getTag().equals(
64 | EMVTags.APPLICATION_LABEL)) {
65 | // Use only safe print chars, just
66 | // in case
67 | String label = Util
68 | .getSafePrintChars(tlv
69 | .getValueBytes());
70 | app.setLabel(label);
71 | } else if (tlv
72 | .getTag()
73 | .equals(EMVTags.APPLICATION_PRIORITY_INDICATOR)) {
74 | ApplicationPriorityIndicator api = new ApplicationPriorityIndicator(
75 | tlv.getValueBytes()[0]);
76 | app.setApplicationPriorityIndicator(api);
77 | } else {
78 | card.addUnhandledRecord(tlv);
79 | }
80 | card.addApplication(app);
81 | }
82 | } else {
83 | card.addUnhandledRecord(tlv);
84 | }
85 | }
86 | } else {
87 | card.addUnhandledRecord(tlv);
88 | }
89 | }
90 | } else {
91 | card.addUnhandledRecord(tlv);
92 | }
93 | }
94 | } else {
95 | card.addUnhandledRecord(tlv);
96 | }
97 |
98 | return ddf;
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/src/org/nick/nfc/seaccess/SECardResponse.java:
--------------------------------------------------------------------------------
1 | package org.nick.nfc.seaccess;
2 |
3 | import static org.nick.nfc.seaccess.Hex.toHex;
4 |
5 | import java.util.Arrays;
6 |
7 | import sasc.terminal.CardResponse;
8 |
9 | public class SECardResponse implements CardResponse {
10 |
11 | private byte[] data;
12 | private byte sw1;
13 | private byte sw2;
14 | private short sw;
15 |
16 | public SECardResponse(byte[] raw) {
17 | this.data = getData(raw);
18 | this.sw = getStatus(raw);
19 | this.sw1 = raw[raw.length - 2];
20 | this.sw2 = raw[raw.length - 1];
21 | }
22 |
23 | public SECardResponse(byte[] data, byte sw1, byte sw2, short sw) {
24 | this.data = data.clone();
25 | this.sw1 = sw1;
26 | this.sw2 = sw2;
27 | this.sw = sw;
28 | }
29 |
30 | @Override
31 | public byte[] getData() {
32 | return data == null ? new byte[0] : data.clone();
33 | }
34 |
35 | @Override
36 | public byte getSW1() {
37 | return sw1;
38 | }
39 |
40 | @Override
41 | public byte getSW2() {
42 | return sw2;
43 | }
44 |
45 | @Override
46 | public short getSW() {
47 | return sw;
48 | }
49 |
50 | @Override
51 | public String toString() {
52 | String swStr = String.format("%02X", sw);
53 | if (data != null) {
54 | return String.format("<-- %s %s", toHex(data), swStr);
55 | }
56 |
57 | return String.format("<-- %s", swStr);
58 | }
59 |
60 | public static byte[] getData(byte[] responseApdu) {
61 | if (responseApdu.length <= 2) {
62 | return null;
63 | }
64 |
65 | return Arrays.copyOf(responseApdu, responseApdu.length - 2);
66 | }
67 |
68 | public static short getStatus(byte[] responseApdu) {
69 | int len = responseApdu.length;
70 | return (short) ((responseApdu[len - 2] << 8) | (0xff & responseApdu[len - 1]));
71 | }
72 |
73 | }
74 |
--------------------------------------------------------------------------------
/src/org/nick/nfc/seaccess/SEConnection.java:
--------------------------------------------------------------------------------
1 | package org.nick.nfc.seaccess;
2 |
3 | import static org.nick.nfc.seaccess.Hex.toHex;
4 |
5 | import java.lang.reflect.InvocationTargetException;
6 | import java.lang.reflect.Method;
7 | import java.util.Arrays;
8 |
9 | import sasc.terminal.CardConnection;
10 | import sasc.terminal.CardResponse;
11 | import sasc.terminal.Terminal;
12 | import sasc.terminal.TerminalException;
13 | import android.util.Log;
14 |
15 | public class SEConnection implements CardConnection {
16 |
17 | private static final String TAG = SEConnection.class.getSimpleName();
18 |
19 | private Object se;
20 | private Method openMethod;
21 | private Method closeMethod;
22 | private Method transceiveMethod;
23 |
24 | private SETerminal terminal;
25 |
26 | public SEConnection(SETerminal terminal) {
27 | this.terminal = terminal;
28 | try {
29 | Class> nfcExtrasClazz = Class
30 | .forName("com.android.nfc_extras.NfcAdapterExtras");
31 | Method getMethod = nfcExtrasClazz.getMethod("get",
32 | Class.forName("android.nfc.NfcAdapter"));
33 | Object nfcExtras = getMethod.invoke(nfcExtrasClazz,
34 | terminal.getDefaultAdapter());
35 | // public NfcExecutionEnvironment getEmbeddedExecutionEnvironment()
36 | Method getEEMethod = nfcExtras.getClass().getMethod(
37 | "getEmbeddedExecutionEnvironment", (Class>[]) null);
38 | se = getEEMethod.invoke(nfcExtras, (Object[]) null);
39 | // public byte[] transceive(byte[] in) throws IOException {
40 | Class> seClazz = se.getClass();
41 | openMethod = seClazz.getMethod("open", (Class>[]) null);
42 | transceiveMethod = se.getClass().getMethod("transceive",
43 | new Class>[] { byte[].class });
44 | closeMethod = seClazz.getMethod("close", (Class>[]) null);
45 | openMethod.invoke(se, (Object[]) null);
46 | } catch (InvocationTargetException e) {
47 | Log.e(TAG, "Error: " + e.getCause().getMessage(), e);
48 | throw new RuntimeException(e);
49 | } catch (Exception e) {
50 | Log.e(TAG, "Error: " + e.getMessage(), e);
51 | throw new RuntimeException(e);
52 | }
53 | }
54 |
55 | @Override
56 | public boolean disconnect(boolean arg0) throws TerminalException {
57 | try {
58 | closeMethod.invoke(se, (Object[]) null);
59 |
60 | return true;
61 | } catch (InvocationTargetException e) {
62 | Log.e(TAG, "Error: " + e.getCause().getMessage());
63 | return false;
64 | } catch (Exception e) {
65 | Log.e(TAG, "Error: " + e.getMessage());
66 | return false;
67 | }
68 | }
69 |
70 | @Override
71 | public byte[] getATR() {
72 | // dummy
73 | return new byte[10];
74 | }
75 |
76 | @Override
77 | public String getConnectionInfo() {
78 | return "Android emedded SE";
79 | }
80 |
81 | @Override
82 | public String getProtocol() {
83 | // dummy
84 | return "T1";
85 | }
86 |
87 | @Override
88 | public Terminal getTerminal() {
89 | return terminal;
90 | }
91 |
92 | @Override
93 | public void resetCard() throws TerminalException {
94 | // TODO Auto-generated method stub
95 | }
96 |
97 | @Override
98 | public CardResponse transmit(byte[] command) throws TerminalException {
99 | byte[] response = sendApdu(command);
100 |
101 | return new SECardResponse(response);
102 | }
103 |
104 | private byte[] sendApdu(byte[] commandApdu) {
105 | try {
106 | Log.d(TAG, String.format("--> %s", toHex(commandApdu)));
107 | Object response = transceiveMethod.invoke(se, commandApdu);
108 | byte[] responseApdu = (byte[]) response;
109 | short status = SECardResponse.getStatus(responseApdu);
110 | String statusStr = String.format("%02X", status);
111 | if (responseApdu.length > 2) {
112 | Log.d(TAG, String.format("<-- %s %s", toHex(Arrays.copyOf(
113 | responseApdu, responseApdu.length - 2)), statusStr));
114 | } else {
115 | Log.d(TAG, String.format("<-- %s", statusStr));
116 | }
117 |
118 | return responseApdu;
119 | } catch (InvocationTargetException e) {
120 | Log.e(TAG, "Error: " + e.getCause().getMessage(), e);
121 | throw new RuntimeException(e);
122 | } catch (Exception e) {
123 | Log.e(TAG, "Error: " + e.getMessage(), e);
124 | throw new RuntimeException(e);
125 | }
126 | }
127 |
128 | }
129 |
--------------------------------------------------------------------------------
/src/org/nick/nfc/seaccess/SEEMVSession.java:
--------------------------------------------------------------------------------
1 | package org.nick.nfc.seaccess;
2 |
3 | import static org.nick.nfc.seaccess.Hex.fromHex;
4 | import sasc.emv.ApplicationDefinitionFile;
5 | import sasc.emv.DDF;
6 | import sasc.emv.EMVAPDUCommands;
7 | import sasc.emv.EMVApplication;
8 | import sasc.emv.EMVCard;
9 | import sasc.emv.EMVUtil;
10 | import sasc.emv.SW;
11 | import sasc.iso7816.AID;
12 | import sasc.iso7816.SmartCardException;
13 | import sasc.terminal.CardConnection;
14 | import sasc.terminal.CardResponse;
15 | import sasc.terminal.TerminalException;
16 | import sasc.util.Util;
17 | import android.app.Application;
18 | import android.util.Log;
19 |
20 | // mostly copied from javaemvreader/EMVSession.java with some modifications
21 | public class SEEMVSession {
22 |
23 | private static final String TAG = SEEMVSession.class.getSimpleName();
24 |
25 | private EMVCard card = null;
26 | private CardConnection seConn;
27 |
28 | private boolean cardInitalized = false;
29 |
30 | public static SEEMVSession startSession(Application appCtx,
31 | CardConnection seConn) {
32 | if (seConn == null) {
33 | throw new IllegalArgumentException(
34 | "Needs initialized SE connection.");
35 | }
36 |
37 | return new SEEMVSession(seConn);
38 | }
39 |
40 | private SEEMVSession(CardConnection seConn) {
41 | this.seConn = seConn;
42 | }
43 |
44 | public EMVCard getCurrentCard() {
45 | return card;
46 | }
47 |
48 | public EMVCard initCard() throws TerminalException {
49 | card = new EMVCard(new sasc.iso7816.ATR(seConn.getATR()));
50 |
51 | // ATR file
52 | String command = "00 A4 00 00 02 2F01";
53 | CardResponse response = seConn.transmit(fromHex(command));
54 | if (response.getSW() == SW.SUCCESS.getSW()) {
55 | card = new EMVCard(new sasc.iso7816.ATR(response.getData()));
56 | } else {
57 | Log.d(TAG, "ATR file not found. Will use dummy. Response: "
58 | + response);
59 | card = new EMVCard(
60 | new sasc.iso7816.ATR(
61 | Hex.fromHex("3B 8A 80 01 00 31 C1 73 C8 40 00 00 90 00 90")));
62 | }
63 |
64 | // try to select the PPSE (Proximity Payment System Environment)
65 | // 2PAY.SYS.DDF01
66 | Log.d(TAG, "SELECT FILE 2PAY.SYS.DDF01 to get the PPSE directory");
67 | command = EMVAPDUCommands.selectPPSE();
68 | CardResponse selectPPSEdirResponse = EMVUtil.sendCmd(seConn, command);
69 | short sw = selectPPSEdirResponse.getSW();
70 | if (sw == SW.SUCCESS.getSW()) {
71 | Log.d(TAG, "***************************************************");
72 | // PPSE is available
73 | DDF ppse = PPSE.parse(selectPPSEdirResponse.getData(), card);
74 | Log.d(TAG, "Name: " + new String(ppse.getName()));
75 | Log.d(TAG, "PPSE DDF: " + ppse.toString());
76 | card.setType(EMVCard.Type.CONTACTLESS);
77 | card.setPSE(ppse);
78 |
79 | // loopback command test
80 | response = EMVUtil.sendCmd(seConn, "80ee00000301020300");
81 | Log.d(TAG, "loopback response: " + response.toString());
82 | }
83 |
84 | // Still no applications?
85 | if (card.getApplications().isEmpty()) {
86 | Log.d(TAG,
87 | "No PSE '2PAY.SYS.DDF01' or application(s) found. Might not be an EMV card. Is Wallet locked?");
88 | }
89 |
90 | cardInitalized = true;
91 | return card;
92 | }
93 |
94 | public void selectApplication(EMVApplication app) throws TerminalException {
95 | if (app == null) {
96 | throw new IllegalArgumentException("Parameter 'app' cannot be null");
97 | }
98 |
99 | if (!cardInitalized) {
100 | throw new SmartCardException(
101 | "Card not initialized. Call initCard() first");
102 | }
103 |
104 | EMVApplication currentSelectedApp = card.getSelectedApplication();
105 | if (currentSelectedApp != null
106 | && app.getAID().equals(currentSelectedApp.getAID())) {
107 | throw new SmartCardException("Application already selected. AID: "
108 | + app.getAID());
109 | }
110 |
111 | AID aid = app.getAID();
112 | Log.d(TAG, "Select application by AID: " + aid);
113 | String command = EMVAPDUCommands.selectByDFName(aid.getAIDBytes());
114 | CardResponse selectAppResponse = EMVUtil.sendCmd(seConn, command);
115 |
116 | if (selectAppResponse.getSW() == SW.SELECTED_FILE_INVALIDATED.getSW()) {
117 | // App blocked
118 | Log.i(TAG, "Application BLOCKED");
119 | // TODO abort execution if app blocked?
120 | throw new SmartCardException("EMVApplication "
121 | + Util.byteArrayToHexString(aid.getAIDBytes()) + " blocked");
122 | }
123 |
124 | if (selectAppResponse.getSW() != SW.SUCCESS.getSW()) {
125 | Log.e(TAG, "Can't select app. Card response: " + selectAppResponse);
126 | return;
127 | }
128 |
129 | ApplicationDefinitionFile adf = EMVUtil.parseFCIADF(
130 | selectAppResponse.getData(), app);
131 | Log.d(TAG, "ADF: " + adf);
132 |
133 | getCurrentCard().setSelectedApplication(app);
134 | }
135 |
136 | }
137 |
--------------------------------------------------------------------------------
/src/org/nick/nfc/seaccess/SEReceiver.java:
--------------------------------------------------------------------------------
1 | package org.nick.nfc.seaccess;
2 |
3 | import android.content.BroadcastReceiver;
4 | import android.content.Context;
5 | import android.content.Intent;
6 | import android.util.Log;
7 |
8 | public class SEReceiver extends BroadcastReceiver {
9 |
10 | private static final String TAG = SEReceiver.class.getSimpleName();
11 |
12 | public static final String ACTION_AID_SELECTED = "com.android.nfc_extras.action.AID_SELECTED";
13 | public static final String EXTRA_AID = "com.android.nfc_extras.extra.AID";
14 |
15 | public static final String ACTION_APDU_RECEIVED = "com.android.nfc_extras.action.APDU_RECEIVED";
16 | public static final String EXTRA_APDU_BYTES = "com.android.nfc_extras.extra.APDU_BYTES";
17 |
18 | public static final String ACTION_EMV_CARD_REMOVAL = "com.android.nfc_extras.action.EMV_CARD_REMOVAL";
19 |
20 | public static final String ACTION_MIFARE_ACCESS_DETECTED = "com.android.nfc_extras.action.MIFARE_ACCESS_DETECTED";
21 | public static final String EXTRA_MIFARE_BLOCK = "com.android.nfc_extras.extra.MIFARE_BLOCK";
22 |
23 |
24 | @Override
25 | public void onReceive(Context ctx, Intent intent) {
26 | String action = intent.getAction();
27 | Log.d(TAG, "Received: " + action);
28 | if (ACTION_AID_SELECTED.equals(action)) {
29 | byte[] aid = intent.getByteArrayExtra(EXTRA_AID);
30 | Log.d(TAG, "AID: " + Hex.toHex(aid));
31 | } else if (ACTION_APDU_RECEIVED.equals(action)) {
32 | byte[] apdu = intent.getByteArrayExtra(EXTRA_APDU_BYTES);
33 | Log.d(TAG, "APDU: " + Hex.toHex(apdu));
34 | } else if (ACTION_MIFARE_ACCESS_DETECTED.equals(action)) {
35 | byte[] block = intent.getByteArrayExtra(EXTRA_MIFARE_BLOCK);
36 | Log.d(TAG, "Mifare block: " + Hex.toHex(block));
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/org/nick/nfc/seaccess/SETerminal.java:
--------------------------------------------------------------------------------
1 | package org.nick.nfc.seaccess;
2 |
3 | import sasc.terminal.CardConnection;
4 | import sasc.terminal.Terminal;
5 | import sasc.terminal.TerminalException;
6 | import android.app.Application;
7 | import android.nfc.NfcAdapter;
8 |
9 | public class SETerminal implements Terminal {
10 |
11 | private Application appCtx;
12 | private NfcAdapter defaultAdapter;
13 |
14 | public SETerminal(Application appCtx) {
15 | this.appCtx = appCtx;
16 | }
17 |
18 | @Override
19 | public CardConnection connect() throws TerminalException {
20 | defaultAdapter = NfcAdapter.getDefaultAdapter(appCtx);
21 |
22 | return new SEConnection(this);
23 | }
24 |
25 | @Override
26 | public String getTerminalInfo() {
27 | return "Android Embeded Secure Element";
28 | }
29 |
30 | public NfcAdapter getDefaultAdapter() {
31 | return defaultAdapter;
32 | }
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/src/org/nick/nfc/seaccess/SETerminalProvider.java:
--------------------------------------------------------------------------------
1 | package org.nick.nfc.seaccess;
2 |
3 | import java.util.Arrays;
4 | import java.util.Collections;
5 | import java.util.List;
6 |
7 | import sasc.terminal.CardConnection;
8 | import sasc.terminal.Terminal;
9 | import sasc.terminal.TerminalException;
10 | import sasc.terminal.TerminalProvider;
11 | import android.app.Application;
12 |
13 | public class SETerminalProvider implements TerminalProvider {
14 |
15 | private SETerminal terminal;
16 |
17 | public SETerminalProvider(Application appCtx) {
18 | terminal = new SETerminal(appCtx);
19 | }
20 |
21 | @Override
22 | public CardConnection connectAnyTerminal() throws TerminalException {
23 | return terminal.connect();
24 | }
25 |
26 | @Override
27 | public CardConnection connectTerminal(String terminalName)
28 | throws TerminalException {
29 | return connectAnyTerminal();
30 | }
31 |
32 | @Override
33 | public CardConnection connectTerminal(int terminalNum)
34 | throws TerminalException {
35 | return connectAnyTerminal();
36 | }
37 |
38 | @Override
39 | public String getProviderInfo() {
40 | return "Android embedded secure element";
41 | }
42 |
43 | @Override
44 | public List listTerminals() throws TerminalException {
45 | return Collections.unmodifiableList(Arrays
46 | .asList(new Terminal[] { terminal }));
47 | }
48 |
49 | }
50 |
--------------------------------------------------------------------------------
/src/org/nick/nfc/seaccess/SecurityDomainFCI.java:
--------------------------------------------------------------------------------
1 | package org.nick.nfc.seaccess;
2 |
3 | import java.io.ByteArrayInputStream;
4 | import java.util.Arrays;
5 |
6 | import sasc.emv.EMVTags;
7 | import sasc.emv.EMVUtil;
8 | import sasc.iso7816.AID;
9 | import sasc.iso7816.BERTLV;
10 | import sasc.iso7816.Tag;
11 | import sasc.iso7816.TagImpl;
12 | import sasc.iso7816.TagValueType;
13 | import sasc.util.Util;
14 |
15 | public class SecurityDomainFCI {
16 |
17 | public static final Tag SECURITY_DOMAIN_MANAGEMENT_DATA = new TagImpl("73",
18 | TagValueType.BINARY, "Security domain management data",
19 | "Security domain management data");
20 | public static final Tag APPLICATION_PRODUCTION_LIFECYCLE_DATA = new TagImpl(
21 | "9f6e", TagValueType.BINARY,
22 | "Application production life cycle data",
23 | "Application production life cycle data");
24 | public static final Tag DATA_FIELD_MAX_LENGTH = new TagImpl("9f65",
25 | TagValueType.BINARY,
26 | "Maximum length of data field in command message",
27 | "Maximum length of data field in command message");
28 |
29 | public static final Tag OID = new TagImpl("06", TagValueType.BINARY,
30 | "Universal OID tag", "Universal OID tag");
31 | public static final Tag CARD_MANAGEMENT_TYPE_AND_VERSION_OID = new TagImpl(
32 | "60", TagValueType.BINARY, "Card management type and version",
33 | "Card management type and version");
34 | public static final Tag CARD_IDENTIFICATION_SCHEME_OID = new TagImpl("63",
35 | TagValueType.BINARY, "Card identification scheme",
36 | "Card identification scheme");
37 | public static final Tag SECURE_CHANNEL_OID = new TagImpl("64",
38 | TagValueType.BINARY,
39 | "Secure channel version and implementation options",
40 | "Secure channel version and implementation options");
41 |
42 | public static final Tag CARD_CONFIGURATION_DETAILS = new TagImpl("65",
43 | TagValueType.BINARY, "Card configuration details",
44 | "Card configuration details");
45 | public static final Tag CARD_CHIP_DETAILS = new TagImpl("66",
46 | TagValueType.BINARY, "Card/chip details", "Card/chip details");
47 |
48 | private static String GLOBAL_PLATFORM_OID = "2A864886FC6B";
49 |
50 |
51 | private AID securityManagerAid;
52 | private int dataFieldMaxLength;
53 | private String applicationProductionLifecycleData;
54 | private String tagAllocationAuthorityOID;
55 | private String cardManagementTypeAndVersion;
56 | private String cardIdentificationScheme;
57 | private String gpVersion;
58 | private String secureChannelVersion;
59 | private String cardConfigurationDetails;
60 | private String cardChipDetails;
61 |
62 | private SecurityDomainFCI() {
63 | }
64 |
65 | public static SecurityDomainFCI parse(byte[] raw) {
66 | SecurityDomainFCI result = new SecurityDomainFCI();
67 |
68 | BERTLV tlv = EMVUtil.getNextTLV(new ByteArrayInputStream(raw));
69 | if (tlv.getTag().equals(EMVTags.FCI_TEMPLATE)) {
70 | ByteArrayInputStream templateStream = tlv.getValueStream();
71 | while (templateStream.available() >= 2) {
72 | tlv = EMVUtil.getNextTLV(templateStream);
73 | if (tlv.getTag().equals(EMVTags.DEDICATED_FILE_NAME)) {
74 | result.securityManagerAid = new AID(tlv.getValueBytes());
75 | } else if (tlv.getTag()
76 | .equals(EMVTags.FCI_PROPRIETARY_TEMPLATE)) {
77 | ByteArrayInputStream fciBis = new ByteArrayInputStream(
78 | tlv.getValueBytes());
79 | int totalLen = fciBis.available();
80 | int templateLen = tlv.getLength();
81 | while (fciBis.available() > (totalLen - templateLen)) {
82 | tlv = EMVUtil.getNextTLV(fciBis);
83 | if (tlv.getTag()
84 | .equals(SECURITY_DOMAIN_MANAGEMENT_DATA)) {
85 | ByteArrayInputStream sdmBis = new ByteArrayInputStream(
86 | tlv.getValueBytes());
87 | int diff = sdmBis.available() - tlv.getLength();
88 | while (sdmBis.available() > diff) {
89 | tlv = EMVUtil.getNextTLV(sdmBis);
90 | if (tlv.getTag().equals(OID)) {
91 | result.tagAllocationAuthorityOID = Hex
92 | .toHex(tlv.getValueBytes());
93 | result.tagAllocationAuthorityOID = result.tagAllocationAuthorityOID
94 | .replace(GLOBAL_PLATFORM_OID,
95 | "globalPlatform ");
96 | } else if (tlv.getTag().equals(
97 | CARD_MANAGEMENT_TYPE_AND_VERSION_OID)) {
98 | ByteArrayInputStream cmBis = new ByteArrayInputStream(
99 | tlv.getValueBytes());
100 | tlv = EMVUtil.getNextTLV(cmBis);
101 | if (tlv.getTag().equals(OID)) {
102 | result.cardManagementTypeAndVersion = Hex
103 | .toHex(tlv.getValueBytes());
104 | result.cardManagementTypeAndVersion = result.cardManagementTypeAndVersion
105 | .replace(GLOBAL_PLATFORM_OID,
106 | "globalPlatform ");
107 | //2A864886FC6B 02 v == {globalPlatform 2 v}
108 | int prefixLength = 7;
109 | int valueLength = tlv.getValueBytes().length;
110 | byte[] version = Arrays.copyOfRange(
111 | tlv.getValueBytes(),
112 | prefixLength, valueLength);
113 | StringBuilder buff = new StringBuilder();
114 | for (int i = 0; i < version.length; i++) {
115 | buff.append(0xff & version[i]);
116 | if (i != version.length - 1) {
117 | buff.append(".");
118 | }
119 | }
120 | result.gpVersion = buff.toString();
121 | }
122 | } else if (tlv.getTag().equals(
123 | CARD_IDENTIFICATION_SCHEME_OID)) {
124 | ByteArrayInputStream cisBis = new ByteArrayInputStream(
125 | tlv.getValueBytes());
126 | tlv = EMVUtil.getNextTLV(cisBis);
127 | if (tlv.getTag().equals(OID)) {
128 | result.cardIdentificationScheme = Hex
129 | .toHex(tlv.getValueBytes());
130 | result.cardIdentificationScheme = result.cardIdentificationScheme
131 | .replace(GLOBAL_PLATFORM_OID,
132 | "globalPlatform ");
133 | }
134 | } else if (tlv.getTag().equals(
135 | SECURE_CHANNEL_OID)) {
136 | int len = tlv.getValueBytes().length;
137 | result.secureChannelVersion = String
138 | .format("SC%02d (options: %02X)",
139 | tlv.getValueBytes()[len - 2],
140 | tlv.getValueBytes()[len - 1]);
141 | } else if (tlv.getTag().equals(
142 | CARD_CONFIGURATION_DETAILS)) {
143 | result.cardConfigurationDetails = Hex
144 | .toHex(tlv.getValueBytes());
145 | } else if (tlv.getTag().equals(
146 | CARD_CHIP_DETAILS)) {
147 | result.cardChipDetails = Hex.toHex(tlv
148 | .getValueBytes());
149 | }
150 | }
151 | } else if (tlv.getTag().equals(
152 | APPLICATION_PRODUCTION_LIFECYCLE_DATA)) {
153 | result.applicationProductionLifecycleData = Hex.toHex(tlv.getValueBytes());
154 | } else if (tlv.getTag().equals(DATA_FIELD_MAX_LENGTH)) {
155 | // check length?
156 | if (tlv.getValueBytes().length == 1) {
157 | result.dataFieldMaxLength = 0xff & tlv
158 | .getValueBytes()[0];
159 | } else if (tlv.getValueBytes().length == 2) {
160 | result.dataFieldMaxLength = Util.byte2Short(
161 | tlv.getValueBytes()[0],
162 | tlv.getValueBytes()[1]);
163 | }
164 | }
165 | }
166 | }
167 | }
168 | } else {
169 | throw new IllegalArgumentException(
170 | "Invalid Security Domain FCI format");
171 | }
172 |
173 | return result;
174 | }
175 |
176 | public AID getSecurityManagerAid() {
177 | return securityManagerAid;
178 | }
179 |
180 | public int getDataFieldMaxLength() {
181 | return dataFieldMaxLength;
182 | }
183 |
184 | public String getApplicationProductionLifecycleData() {
185 | return applicationProductionLifecycleData;
186 | }
187 |
188 | public String getTagAllocationAuthorityOID() {
189 | return tagAllocationAuthorityOID;
190 | }
191 |
192 | public String getCardManagementTypeAndVersion() {
193 | return cardManagementTypeAndVersion;
194 | }
195 |
196 | public String getCardIdentificationScheme() {
197 | return cardIdentificationScheme;
198 | }
199 |
200 | public String getGpVersion() {
201 | return gpVersion;
202 | }
203 |
204 | public String getSecureChannelVersion() {
205 | return secureChannelVersion;
206 | }
207 |
208 | public String getCardConfigurationDetails() {
209 | return cardConfigurationDetails;
210 | }
211 |
212 | public String getCardChipDetails() {
213 | return cardChipDetails;
214 | }
215 |
216 | @Override
217 | public String toString() {
218 | StringBuilder buff = new StringBuilder();
219 | buff.append("Security Domain FCI");
220 | buff.append("\n");
221 | buff.append(" AID: " + securityManagerAid.toString());
222 | buff.append("\n");
223 | buff.append(" Data field max length: " + dataFieldMaxLength);
224 | buff.append("\n");
225 | buff.append(" Application prod. life cycle data: "
226 | + applicationProductionLifecycleData);
227 | buff.append("\n");
228 | buff.append(" Tag allocation authority (OID): "
229 | + tagAllocationAuthorityOID);
230 | buff.append("\n");
231 | buff.append(" Card management type and version (OID): "
232 | + cardManagementTypeAndVersion);
233 | buff.append("\n");
234 | buff.append(" Card identification scheme (OID): "
235 | + cardIdentificationScheme);
236 | buff.append("\n");
237 | buff.append(" Global Platform version: " + gpVersion);
238 | buff.append("\n");
239 | buff.append(" Secure channel version: " + secureChannelVersion);
240 | buff.append("\n");
241 | buff.append(" Card config details: " + cardConfigurationDetails);
242 | buff.append("\n");
243 | buff.append(" Card/chip details: " + cardChipDetails);
244 |
245 | return buff.toString();
246 | }
247 |
248 |
249 | }
250 |
--------------------------------------------------------------------------------
/src/org/nick/nfc/seaccess/WalletControllerCommands.java:
--------------------------------------------------------------------------------
1 | package org.nick.nfc.seaccess;
2 |
3 | import sasc.iso7816.AID;
4 |
5 | public class WalletControllerCommands {
6 |
7 | public static final AID WALLET_CONTROLLER_AID = new AID(
8 | "A0 00 00 04 76 20 10");
9 |
10 | public static final byte[] SELECT_WALLET_CONTROLLER_COMMAND = {
11 | (byte) 0x00, (byte) 0xA4, (byte) 0x04, (byte) 0x00, (byte) 0x07,
12 | (byte) 0xA0, (byte) 0x00, (byte) 0x00, (byte) 0x04, (byte) 0x76,
13 | (byte) 0x20, (byte) 0x10, (byte) 0x00 };
14 |
15 | private WalletControllerCommands() {
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/org/nick/nfc/seaccess/WalletControllerFCI.java:
--------------------------------------------------------------------------------
1 | package org.nick.nfc.seaccess;
2 |
3 | import java.io.ByteArrayInputStream;
4 |
5 | import sasc.emv.EMVTags;
6 | import sasc.emv.EMVUtil;
7 | import sasc.iso7816.AID;
8 | import sasc.iso7816.BERTLV;
9 | import sasc.util.Util;
10 |
11 | public class WalletControllerFCI {
12 |
13 | private AID aid;
14 | private short version;
15 |
16 | private WalletControllerFCI() {
17 | }
18 |
19 | public static WalletControllerFCI parse(byte[] raw) {
20 | WalletControllerFCI result = new WalletControllerFCI();
21 |
22 | BERTLV tlv = EMVUtil.getNextTLV(new ByteArrayInputStream(raw));
23 | if (tlv.getTag().equals(EMVTags.FCI_TEMPLATE)) {
24 | ByteArrayInputStream templateStream = tlv.getValueStream();
25 | while (templateStream.available() >= 2) {
26 | tlv = EMVUtil.getNextTLV(templateStream);
27 | if (tlv.getTag().equals(EMVTags.DEDICATED_FILE_NAME)) {
28 | result.aid = new AID(tlv.getValueBytes());
29 | } else if (tlv.getTag()
30 | .equals(EMVTags.FCI_PROPRIETARY_TEMPLATE)) {
31 | ByteArrayInputStream fciBis = new ByteArrayInputStream(
32 | tlv.getValueBytes());
33 | int totalLen = fciBis.available();
34 | int templateLen = tlv.getLength();
35 | while (fciBis.available() > (totalLen - templateLen)) {
36 | tlv = EMVUtil.getNextTLV(fciBis);
37 | if (tlv.getTag().equals(
38 | EMVTags.RESPONSE_MESSAGE_TEMPLATE_1)) {
39 | int len = tlv.getValueBytes().length;
40 | result.version = Util.byte2Short(
41 | tlv.getValueBytes()[len - 2],
42 | tlv.getValueBytes()[len - 1]);
43 | }
44 | }
45 | }
46 | }
47 | } else {
48 | throw new IllegalArgumentException("Invalid Wallet FCI response");
49 | }
50 |
51 | return result;
52 | }
53 |
54 | public AID getAid() {
55 | return aid;
56 | }
57 |
58 | public short getVersion() {
59 | return version;
60 | }
61 |
62 | @Override
63 | public String toString() {
64 | StringBuilder buff = new StringBuilder();
65 | buff.append("Wallet controller applet");
66 | buff.append("\n");
67 | buff.append("AID: " + aid.toString());
68 | buff.append("version: v" + version);
69 |
70 | return buff.toString();
71 | }
72 |
73 |
74 | }
75 |
--------------------------------------------------------------------------------