diff --git a/.classpath b/.classpath deleted file mode 100644 index 6c635c0..0000000 --- a/.classpath +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/.gitignore b/.gitignore index ff2422f..37c3137 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,114 @@ -/assets -/bin -/gen + +# Created by https://www.gitignore.io/api/androidstudio + +### AndroidStudio ### +# Covers files to be ignored for android development using Android Studio. + +# Built application files +*.apk +*.ap_ + +# Files for the ART/Dalvik VM +*.dex + +# Java class files +*.class + +# Generated files +bin/ +gen/ +out/ + +# Gradle files +.gradle +.gradle/ +build/ + +# Signing files +.signing/ + +# Local configuration file (sdk path, etc) +local.properties + +# Proguard folder generated by Eclipse +proguard/ + +# Log Files +*.log + +# Android Studio +/*/build/ +/*/local.properties +/*/out +/*/*/build +/*/*/production +captures/ +.navigation/ +*.ipr +*~ +*.swp + +# Android Patch +gen-external-apklibs + +# External native build folder generated in Android Studio 2.2 and later +.externalNativeBuild + +# NDK +obj/ + +# IntelliJ IDEA +*.iml +*.iws +/out/ + +# https://stackoverflow.com/questions/26336709/android-studio-should-the-entire-idea-directory-be-in-git-ignore +.idea + +# Keystore files +*.jks + +# OS-specific files +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + +# Legacy Eclipse project files +.classpath +.project + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.ear + +# virtual machine crash logs (Reference: http://www.java.com/en/download/help/error_hotspot.xml) +hs_err_pid* + +## Plugin-specific files: + + +# JIRA plugin +atlassian-ide-plugin.xml + + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +### AndroidStudio Patch ### +# Google Services plugin +google-services.json + +!/gradle/wrapper/gradle-wrapper.jar + +# End of https://www.gitignore.io/api/androidstudio diff --git a/.project b/.project deleted file mode 100644 index e39595e..0000000 --- a/.project +++ /dev/null @@ -1,33 +0,0 @@ - - - HelloSmartcard - - - - - - com.android.ide.eclipse.adt.ResourceManagerBuilder - - - - - com.android.ide.eclipse.adt.PreCompilerBuilder - - - - - org.eclipse.jdt.core.javabuilder - - - - - com.android.ide.eclipse.adt.ApkBuilder - - - - - - com.android.ide.eclipse.adt.AndroidNature - org.eclipse.jdt.core.javanature - - diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..5b39783 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,21 @@ +apply plugin: 'com.android.application' +android { + compileSdkVersion 'Giesecke Devrient GmbH:Open Mobile API:21' + buildToolsVersion '27.0.3' + + defaultConfig { + applicationId "com.gieseckedevrient.android.hellosmartcard" + minSdkVersion 4 + targetSdkVersion 15 + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt' + } + } +} + +dependencies { +} \ No newline at end of file diff --git a/app/release/output.json b/app/release/output.json new file mode 100644 index 0000000..b651919 --- /dev/null +++ b/app/release/output.json @@ -0,0 +1 @@ +[{"outputType":{"type":"APK"},"apkInfo":{"type":"MAIN","splits":[],"versionCode":1,"versionName":"1.0.1-SNAPSHOT","enabled":true,"outputFile":"app-release.apk","fullName":"release","baseName":"release"},"path":"app-release.apk","properties":{}}] \ No newline at end of file diff --git a/AndroidManifest.xml b/app/src/main/AndroidManifest.xml similarity index 100% rename from AndroidManifest.xml rename to app/src/main/AndroidManifest.xml diff --git a/app/src/main/java/com/gieseckedevrient/android/hellosmartcard/Eduroam.java b/app/src/main/java/com/gieseckedevrient/android/hellosmartcard/Eduroam.java new file mode 100644 index 0000000..485de38 --- /dev/null +++ b/app/src/main/java/com/gieseckedevrient/android/hellosmartcard/Eduroam.java @@ -0,0 +1,55 @@ +package com.gieseckedevrient.android.hellosmartcard; + +import android.util.Log; + +import nl.mansoft.openmobileapi.util.CommandApdu; +import nl.mansoft.openmobileapi.util.ResponseApdu; + +import java.io.IOException; + +public class Eduroam { + private final String TAG = Eduroam.class.getSimpleName(); + public final static int OFFSET_USER = 0x00; + public final static int OFFSET_PASSWORD = 0x20; + private SmartcardIO mSmartcardIO; + + public Eduroam(SmartcardIO smartcardIO) { + mSmartcardIO = smartcardIO; + } + + public ResponseApdu selectEduroam() throws IOException { + CommandApdu c = new CommandApdu((byte)0x00, (byte)0xA4, (byte)0x00, (byte)0x00, new byte[] { 0x10, 0x00 }); + return mSmartcardIO.runAPDU(c); + } + + public ResponseApdu readEduroam() throws IOException { + ResponseApdu result = selectEduroam(); + if (result.isSuccess()) { + Log.d(TAG, "reading eduroam"); + CommandApdu c = new CommandApdu((byte)0x00, (byte)0xB0, (byte)0x00, (byte)0x00); + result = mSmartcardIO.runAPDU(c); + } + return result; + } + + public boolean updateEduroam(byte[] data) throws IOException { + boolean result = false; + if (selectEduroam() != null) { + Log.d(TAG, "updating eduroam"); + CommandApdu c = new CommandApdu((byte)0x00, (byte)0xD6, (byte)0x00, (byte)0x00, data); + result = mSmartcardIO.runAPDU(c) != null; + } + return result; + } + + public static String readStringFromByteArray(byte[] barr, int offset) { + String result = ""; + byte b; + int i = offset; + while ((b = barr[i++]) != (byte) 0xFF) { + result += (char) b; + } + return result; + } + +} diff --git a/app/src/main/java/com/gieseckedevrient/android/hellosmartcard/MainActivity.java b/app/src/main/java/com/gieseckedevrient/android/hellosmartcard/MainActivity.java new file mode 100644 index 0000000..23b7ae5 --- /dev/null +++ b/app/src/main/java/com/gieseckedevrient/android/hellosmartcard/MainActivity.java @@ -0,0 +1,241 @@ +package com.gieseckedevrient.android.hellosmartcard; + +import android.app.Activity; +import android.os.Bundle; +import android.util.Log; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup.LayoutParams; +import android.widget.Button; +import android.widget.LinearLayout; +import android.widget.Toast; + +import org.simalliance.openmobileapi.SEService; +import nl.mansoft.openmobileapi.util.CommandApdu; +import nl.mansoft.openmobileapi.util.ResponseApdu; + +import java.io.IOException; + + +public class MainActivity extends Activity implements SEService.CallBack { + final String TAG = MainActivity.class.getSimpleName(); + public final static byte[] AID_HELLOAPPLET = { + (byte) 0xD2, 0x76, 0x00, 0x01, 0x18, 0x00, 0x02, + (byte) 0xFF, 0x49, 0x50, 0x25, (byte) 0x89, + (byte) 0xC0, 0x01, (byte) 0x9B, 0x01 + }; + public final static byte[] AID_JOOSTAPPLET = { (byte) 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x01 }; + public final static byte[] AID_3GPP = { (byte) 0xA0, 0x00, 0x00, 0x00, (byte) 0x87 }; + public final static byte[] AID_ISOAPPLET = { (byte) 0xF2, (byte) 0x76, (byte) 0xA2, (byte) 0x88, (byte) 0xBC, (byte) 0xFB, (byte) 0xA6, (byte) 0x9D, (byte) 0x34, (byte) 0xF3, (byte) 0x10, (byte) 0x01 }; + + private Button hello; + private Button joost; + private Button eduroam; + private Button telecom; + + private SmartcardIO mSmartcardIO; + + void loge(String message) { + Log.e(TAG, message); + Toast.makeText(this, message, Toast.LENGTH_LONG).show(); + } + + private void logd(String message) { + Log.d(TAG, message); + Toast.makeText(this, message, Toast.LENGTH_LONG).show(); + } + + interface ResponseCallback { + void responseCallback(byte response[]); + } + + class ShowText implements ResponseCallback { + @Override + public void responseCallback(byte response[]) { + String hello = new String(response); + logd(hello); + } + } + + private class MyOnClickListener implements OnClickListener { + final String LOG_TAG = MyOnClickListener.class.getSimpleName(); + private byte[] mAid; + private CommandApdu mCommandApdu[]; + private SmartcardIO mSmartcardIO; + private ResponseCallback mResponseCallback; + + + public MyOnClickListener(SmartcardIO smartcardIO, byte[] aid, CommandApdu commandApdu[], ResponseCallback responseCallback) throws IOException { + mSmartcardIO = smartcardIO; + mAid = aid; + mCommandApdu = commandApdu; + mResponseCallback = responseCallback; + } + + @Override + public void onClick(View view) { + try { + mSmartcardIO.openChannel(mAid); + int length = mCommandApdu.length; + for (int i = 0; i < length; i++) { + CommandApdu commandApdu = mCommandApdu[i]; + ResponseApdu response = mSmartcardIO.runAPDU(commandApdu); + if (response.isSuccess()) { + byte data[] = response.getData(); + Log.d(LOG_TAG, "response: " + SmartcardIO.hex(data)); + if (mResponseCallback != null) { + mResponseCallback.responseCallback(data); + } + } else { + Log.d(LOG_TAG, "response: null"); + } + } + } catch (Exception e) { + loge(e.getMessage()); + } + } + } + + private class EduroamOnClickListener implements OnClickListener { + final String TAG = EduroamOnClickListener.class.getSimpleName(); + private SmartcardIO mSmartcardIO; + + public EduroamOnClickListener(SmartcardIO smartcardIO) throws IOException { + mSmartcardIO = smartcardIO; + } + + public void showEduroam() throws Exception { + mSmartcardIO.openChannel(AID_ISOAPPLET); + Eduroam eduroam = new Eduroam(mSmartcardIO); + ResponseApdu responseApdu = eduroam.readEduroam(); + if (responseApdu.isSuccess()) { + byte data[] = responseApdu.getData(); + logd("user: " + Eduroam.readStringFromByteArray(data, Eduroam.OFFSET_USER)); + logd("password: " + Eduroam.readStringFromByteArray(data, Eduroam.OFFSET_PASSWORD)); + } else { + loge("No credentials found on SIM card"); + } + mSmartcardIO.closeChannel(); + } + + @Override + public void onClick(View view) { + try { + showEduroam(); + } catch (Exception e) { + loge(e.getMessage()); + } + } + } + + private class TelecomOnClickListener implements OnClickListener { + final String TAG = EduroamOnClickListener.class.getSimpleName(); + private SmartcardIO mSmartcardIO; + + public TelecomOnClickListener(SmartcardIO smartcardIO) throws IOException { + mSmartcardIO = smartcardIO; + } + + public void showTelecom() throws Exception { + mSmartcardIO.openChannel(AID_3GPP); + // select EXT1 + Telecom telecom = new Telecom(mSmartcardIO); + logd("user: " + telecom.readData(Telecom.EF_EXT1, Telecom.RECORD_USER)); + logd("password: " + telecom.readData(Telecom.EF_EXT1, Telecom.RECORD_PASSWORD)); + mSmartcardIO.closeChannel(); + } + + @Override + public void onClick(View view) { + try { + showTelecom(); + } catch (Exception e) { + loge(e.getMessage()); + } + } + } + + @Override + public void onCreate(Bundle savedInstanceState) { + + try { + super.onCreate(savedInstanceState); + + mSmartcardIO = new SmartcardIO(); + mSmartcardIO.setup(this, this); + LinearLayout layout = new LinearLayout(this); + layout.setLayoutParams(new LayoutParams( + LayoutParams.WRAP_CONTENT, + LayoutParams.WRAP_CONTENT)); + + hello = new Button(this); + hello.setLayoutParams(new LayoutParams( + LayoutParams.WRAP_CONTENT, + LayoutParams.WRAP_CONTENT)); + + hello.setEnabled(false); + hello.setText("Click Me"); + hello.setOnClickListener(new MyOnClickListener( + mSmartcardIO, + AID_HELLOAPPLET, + new CommandApdu[] { new CommandApdu((byte) 0x90, (byte) 0x10, (byte) 0x00, (byte) 0x00,(byte) 0x00 ) }, + new ShowText() + )); + layout.addView(hello); + joost = new Button(this); + joost.setLayoutParams(new LayoutParams( + LayoutParams.WRAP_CONTENT, + LayoutParams.WRAP_CONTENT)); + joost.setEnabled(false); + joost.setText("Joost"); + joost.setOnClickListener(new MyOnClickListener( + mSmartcardIO, + AID_JOOSTAPPLET, + new CommandApdu[] { new CommandApdu((byte) 0x80, (byte) 0x00, (byte) 0x00, (byte) 0x00,(byte) 0x04 ) }, + new ShowText() + )); + layout.addView(joost); + eduroam = new Button(this); + eduroam.setLayoutParams(new LayoutParams( + LayoutParams.WRAP_CONTENT, + LayoutParams.WRAP_CONTENT)); + + eduroam.setEnabled(false); + eduroam.setText("eduroam"); + eduroam.setOnClickListener(new EduroamOnClickListener(mSmartcardIO)); + layout.addView(eduroam); + telecom = new Button(this); + telecom.setLayoutParams(new LayoutParams( + LayoutParams.WRAP_CONTENT, + LayoutParams.WRAP_CONTENT)); + + telecom.setEnabled(false); + telecom.setText("telecom"); + telecom.setOnClickListener(new TelecomOnClickListener(mSmartcardIO)); + layout.addView(telecom); + setContentView(layout); + } catch (IOException e) { + e.printStackTrace(); + } + } + + @Override + protected void onDestroy() { + mSmartcardIO.teardown(); + super.onDestroy(); + } + + @Override + public void serviceConnected(SEService seService) { + try { + mSmartcardIO.setSession(); + hello.setEnabled(true); + joost.setEnabled(true); + eduroam.setEnabled(true); + telecom.setEnabled(true); + } catch (IOException e) { + loge(e.getMessage()); + } + } + +} diff --git a/app/src/main/java/com/gieseckedevrient/android/hellosmartcard/SmartcardIO.java b/app/src/main/java/com/gieseckedevrient/android/hellosmartcard/SmartcardIO.java new file mode 100644 index 0000000..d8775c9 --- /dev/null +++ b/app/src/main/java/com/gieseckedevrient/android/hellosmartcard/SmartcardIO.java @@ -0,0 +1,99 @@ +package com.gieseckedevrient.android.hellosmartcard; + +import android.content.Context; +import android.util.Log; +import android.widget.Toast; + +import org.simalliance.openmobileapi.Channel; +import org.simalliance.openmobileapi.Reader; +import org.simalliance.openmobileapi.SEService; +import org.simalliance.openmobileapi.Session; +import nl.mansoft.openmobileapi.util.CommandApdu; +import nl.mansoft.openmobileapi.util.ResponseApdu; + +import java.io.IOException; + +public class SmartcardIO { + private final String TAG = SmartcardIO.class.getSimpleName(); + private Session session; + private Channel cardChannel; + private SEService mSeService; + private Context mContext; + + private void loge(String message) { + Log.e(TAG, message); + Toast.makeText(mContext, message, Toast.LENGTH_LONG).show(); + } + + private void logd(String message) { + Log.d(TAG, message); + Toast.makeText(mContext, message, Toast.LENGTH_LONG).show(); + } + + public ResponseApdu runAPDU(CommandApdu commandApdu) throws IOException { + byte cmdApdu[] = commandApdu.toByteArray(); + ResponseApdu responseApdu = new ResponseApdu(cardChannel.transmit(cmdApdu)); + + if (!responseApdu.isSuccess()) { + Log.e(TAG,"ERROR: status: " + String.format("%04X", responseApdu.getSwValue())); + } + return responseApdu; + } + + public void setup(Context context, SEService.CallBack callBack) throws IOException { + mContext = context; + mSeService = new SEService(context, callBack); + } + + public void teardown() { + Reader[] readers = mSeService.getReaders(); + closeChannel(); + if (readers.length < 1) { + Log.e(TAG, "No readers found"); + } else { + readers[0].closeSessions(); + } + if (mSeService != null && mSeService.isConnected()) { + mSeService.shutdown(); + } + } + + public void closeChannel() { + if (cardChannel != null && !cardChannel.isClosed()) { + cardChannel.close(); + } + } + public void openChannel(byte aid[]) throws Exception { + closeChannel(); + cardChannel = session.openLogicalChannel(aid); + } + + public static String hex2(int hex) { + return String.format("%02X", hex & 0xff); + } + + public static String hex(byte[] barr) { + String result; + if (barr == null) { + result = "null"; + } else { + result = ""; + for (byte b : barr) { + result += " " + hex2(b); + } + } + return result; + } + + public void setSession() throws IOException { + Log.d(TAG, "serviceConnected()"); + Log.d(TAG, "Retrieve available readers..."); + Reader[] readers = mSeService.getReaders(); + if (readers.length < 1) { + loge("No readers found"); + } else { + Log.d(TAG, "Create Session from the first reader..."); + session = readers[0].openSession(); + } + } +} diff --git a/app/src/main/java/com/gieseckedevrient/android/hellosmartcard/Telecom.java b/app/src/main/java/com/gieseckedevrient/android/hellosmartcard/Telecom.java new file mode 100644 index 0000000..b64bf2a --- /dev/null +++ b/app/src/main/java/com/gieseckedevrient/android/hellosmartcard/Telecom.java @@ -0,0 +1,93 @@ +package com.gieseckedevrient.android.hellosmartcard; + +import android.util.Log; + +import nl.mansoft.openmobileapi.util.CommandApdu; +import nl.mansoft.openmobileapi.util.ResponseApdu; + +import java.io.IOException; + +public class Telecom { + private final String TAG = Telecom.class.getSimpleName(); + private SmartcardIO mSmartcardIO; + public final static int EXT_RECORD_SIZE = 13; + public final static int EF_EXT1 = 0x6F4A; + public final static int EF_EXT2 = 0x6F4B; + public final static int RECORD_USER = 1; + public final static int RECORD_PASSWORD = 3; + + public Telecom(SmartcardIO smartcardIO) { + mSmartcardIO = smartcardIO; + } + + public ResponseApdu readRecord(int record) throws IOException { + CommandApdu c = new CommandApdu((byte)0x00, (byte)0xB2, (byte)record, (byte)0x04); + return mSmartcardIO.runAPDU(c); + } + + public void readRecords() throws IOException { + int record = 1; + ResponseApdu responseApdu; + while ((responseApdu = readRecord(record++)).isSuccess()) { + Log.d(TAG, SmartcardIO.hex(responseApdu.getData())); + } + } + public static byte hi(int x) { + return (byte) (x >> 8); + } + + public static byte lo(int x) { + return (byte) (x & 0xff); + } + + public ResponseApdu selectTelecom(int fid) throws IOException { + CommandApdu c = new CommandApdu((byte)0x00, (byte)0xA4, (byte)0x08, (byte)0x04, new byte[] { 0x7f, 0x10, hi(fid), lo(fid) }); + return mSmartcardIO.runAPDU(c); + } + + public byte[] readTelecomRecord(int fid, int record) throws IOException { + byte result[] = null; + if (selectTelecom(fid) != null) { + Log.d(TAG, String.format("reading telecom %04X", fid)); + ResponseApdu responseApdu = readRecord(record); + if (responseApdu.isSuccess()) { + result = responseApdu.getData(); + } + } + return result; + } + + public void readTelecomRecords(int fid) throws IOException { + if (selectTelecom(fid) != null) { + Log.d(TAG, String.format("reading telecom %04X", fid)); + readRecords(); + } + } + public void updateRecord(int record, byte[] data) throws IOException { + CommandApdu c = new CommandApdu((byte)0x00, (byte)0xdc, (byte)record, (byte)0x04, data); + mSmartcardIO.runAPDU(c); + } + + public void writeTelecom(int fid, int record, byte[] data) throws IOException { + if (selectTelecom(fid) != null) { + Log.d(TAG, String.format("write telecom %04X, record %d", fid, record)); + updateRecord(record, data); + } + } + + public String readData(int ext, int recordnr) throws IOException { + String result = ""; + byte record[]; + while ((record = readTelecomRecord(ext, recordnr++)) != null) { + for (int i = 1; i < EXT_RECORD_SIZE; i++) { + int val = (record[i] & 0xFF); + if (val == 0xFF) { + return result; + } + result += String.valueOf((char) val); + } + } + return result; + } + +} diff --git a/app/src/main/java/nl/mansoft/openmobileapi/util/CommandApdu.java b/app/src/main/java/nl/mansoft/openmobileapi/util/CommandApdu.java new file mode 100644 index 0000000..b735900 --- /dev/null +++ b/app/src/main/java/nl/mansoft/openmobileapi/util/CommandApdu.java @@ -0,0 +1,476 @@ +/* + * Copyright 2013 Giesecke & Devrient GmbH. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package nl.mansoft.openmobileapi.util; + +/** + * This object represents a command APDU as specified by ISO/IEC 7816. + * + * @author Giesecke & Devrient + * + */ +public class CommandApdu { + + /** + * CLA field of command APDU. + */ + private byte mCla; + + /** + * INS field of command APDU. + */ + private byte mIns; + + /** + * P1 field of command APDU. + */ + private byte mP1; + + /** + * P2 field of command APDU. + */ + private byte mP2; + + /** + * DATA field of command APDU. + */ + private byte[] mData; + + /** + * Le field of command APDU. + */ + private Integer mLe; + + /** + * Override default constructor. + */ + private CommandApdu() { + } + + /** + * Creates a case 1 Command APDU. + * + * @param cla Value of CLA field. + * @param ins Value of INS field. + * @param p1 Value of P1 field. + * @param p2 Value of P2 field. + * + * @throws IllegalArgumentException if CLA or INS are invalid. + */ + public CommandApdu(byte cla, byte ins, byte p1, byte p2) + throws IllegalArgumentException { + setCla(cla); + setIns(ins); + setP1(p1); + setP2(p2); + } + + /** + * Creates a case 2 Command APDU. + * + * @param cla Value of CLA field. + * @param ins Value of INS field. + * @param p1 Value of P1 field. + * @param p2 Value of P2 field. + * @param le Value of Le field. + * + * @throws IllegalArgumentException if CLA, INS or Le are invalid. + */ + public CommandApdu(byte cla, byte ins, byte p1, byte p2, int le) + throws IllegalArgumentException { + setCla(cla); + setIns(ins); + setP1(p1); + setP2(p2); + setLe(le); + } + + /** + * Creates case 3 Command APDU. + * + * @param cla Value of CLA field. + * @param ins Value of INS field. + * @param p1 Value of P1 field. + * @param p2 Value of P2 field. + * @param data Value of DATA field. + * + * @throws IllegalArgumentException if CLA, INS or Data are invalid. + */ + public CommandApdu(byte cla, byte ins, byte p1, byte p2, byte[] data) + throws IllegalArgumentException { + setCla(cla); + setIns(ins); + setP1(p1); + setP2(p2); + setData(data); + } + + /** + * Creates a case 4 Command APDU. + * + * @param cla Value of CLA field. + * @param ins Value of INS field. + * @param p1 Value of P1 field. + * @param p2 Value of P2 field. + * @param data Value of DATA field. + * @param le Value of Le field. + * + * @throws IllegalArgumentException if CLA, INS, Data or Le are invalid. + */ + public CommandApdu( + byte cla, byte ins, byte p1, byte p2, byte[] data, int le) + throws IllegalArgumentException { + setCla(cla); + setIns(ins); + setP1(p1); + setP2(p2); + setData(data); + setLe(le); + } + + /** + * Creates a command APDU object from a byte Array. + * + * @param cmdApduAsByteArray Command APDU as byte array. + * + * @throws IllegalArgumentException If cmdApduAsByteArray does not + * contain a valid APDU. + */ + public CommandApdu(byte[] cmdApduAsByteArray) + throws IllegalArgumentException { + if (cmdApduAsByteArray.length < ISO7816.CMD_APDU_LENGTH_CASE1) { + throw new IllegalArgumentException("Invalid length for command (" + + cmdApduAsByteArray.length + ")."); + } + + setCla(cmdApduAsByteArray[ISO7816.OFFSET_CLA]); + setIns(cmdApduAsByteArray[ISO7816.OFFSET_INS]); + setP1(cmdApduAsByteArray[ISO7816.OFFSET_P1]); + setP2(cmdApduAsByteArray[ISO7816.OFFSET_P2]); + + if (cmdApduAsByteArray.length == ISO7816.CMD_APDU_LENGTH_CASE1) { + // Case 1 APDU -- Nothing left to be done + } else if (cmdApduAsByteArray.length == ISO7816.CMD_APDU_LENGTH_CASE2) { + // Case 2 APDU + setLe((int) 0x0FF & cmdApduAsByteArray[ISO7816.OFFSET_P3]); + } else if (cmdApduAsByteArray[ISO7816.OFFSET_P3] != (byte) 0x00) { + // Case 3 or Case 4 APDU + + // Get Lc and check that it's not 0 + int lc = ((int) 0x0FF & cmdApduAsByteArray[ISO7816.OFFSET_P3]); + if (lc == 0) { + throw new IllegalArgumentException( + "Lc can't be 0"); + } + + if (cmdApduAsByteArray.length + == ISO7816.CMD_APDU_LENGTH_CASE3_WITHOUT_DATA + lc) { + // Case 3 APDU -- nothing to be done here + } else if (cmdApduAsByteArray.length + == ISO7816.CMD_APDU_LENGTH_CASE4_WITHOUT_DATA + lc) { + // Case 4 APDU -- get Le: + setLe((int) 0x0FF + & cmdApduAsByteArray[cmdApduAsByteArray.length - 1]); + } else { + // Lc has a wrong value! + throw new IllegalArgumentException( + "Unexpected value of Lc (" + lc + ")"); + } + + // Store the data + mData = new byte[lc]; + System.arraycopy( + cmdApduAsByteArray, + ISO7816.OFFSET_DATA, + mData, + 0, + lc); + } else if (cmdApduAsByteArray.length + == ISO7816.CMD_APDU_LENGTH_CASE2_EXTENDED) { + // Case 2 extended APDU + setLe((((int) 0x0FF + & cmdApduAsByteArray[ISO7816.OFFSET_DATA]) << 8) + + ((int) 0x0FF + & cmdApduAsByteArray[ISO7816.OFFSET_DATA + 1])); + } else { + // Case 3 or Case 4 APDU + + if (cmdApduAsByteArray.length <= ISO7816.OFFSET_DATA_EXTENDED) { + throw new IllegalArgumentException( + "Unexpected value of Lc or Le" + cmdApduAsByteArray.length); + } + // Get Lc and check that it's not 0 + int lc = (((int) 0x0FF + & cmdApduAsByteArray[ISO7816.OFFSET_DATA]) << 8) + + ((int) 0x0FF + & cmdApduAsByteArray[ISO7816.OFFSET_DATA + 1]); + if (lc == 0) { + throw new IllegalArgumentException( + "Lc can't be 0"); + } + + if (cmdApduAsByteArray.length + == ISO7816.CMD_APDU_LENGTH_CASE3_WITHOUT_DATA_EXTENDED + + lc) { + // Case 3 APDU -- nothing to be done here + } else if (cmdApduAsByteArray.length + == ISO7816.CMD_APDU_LENGTH_CASE4_WITHOUT_DATA_EXTENDED + + lc) { + // Case 4 APDU -- get Le: + setLe((((int) 0x0FF + & cmdApduAsByteArray[cmdApduAsByteArray.length - 2]) + << 8) + + ((int) 0x0FF + & cmdApduAsByteArray[cmdApduAsByteArray.length - 1])); + } else { + // Lc has a wrong value! + throw new IllegalArgumentException( + "Unexpected value of Lc (" + lc + ")--- 9 -" + cmdApduAsByteArray.length); + } + + // Store the data + mData = new byte[lc]; + System.arraycopy( + cmdApduAsByteArray, + ISO7816.OFFSET_DATA_EXTENDED, + mData, + 0, + lc); + } + } + + /** + * Returns this Command APDU as a byte array. + * + * @return Command APDU as byte array. + */ + public byte[] toByteArray() { + byte[] array; + if (!isExtendedLength()) { + if (mData == null && mLe == null) { + // APDU Case 1 + array = new byte[ISO7816.CMD_APDU_LENGTH_CASE1]; + array[ISO7816.OFFSET_CLA] = mCla; + array[ISO7816.OFFSET_INS] = mIns; + array[ISO7816.OFFSET_P1] = mP1; + array[ISO7816.OFFSET_P2] = mP2; + } else if (mData == null && mLe != null) { + // APDU Case 2 + array = new byte[ISO7816.CMD_APDU_LENGTH_CASE2]; + array[ISO7816.OFFSET_CLA] = mCla; + array[ISO7816.OFFSET_INS] = mIns; + array[ISO7816.OFFSET_P1] = mP1; + array[ISO7816.OFFSET_P2] = mP2; + array[ISO7816.OFFSET_P3] = (byte) (mLe & 0x0FF); + } else if (mData != null && mLe == null) { + // APDU Case 3 + array = new byte[ISO7816.CMD_APDU_LENGTH_CASE3_WITHOUT_DATA + + mData.length]; + array[ISO7816.OFFSET_CLA] = mCla; + array[ISO7816.OFFSET_INS] = mIns; + array[ISO7816.OFFSET_P1] = mP1; + array[ISO7816.OFFSET_P2] = mP2; + array[ISO7816.OFFSET_P3] = (byte) (mData.length & 0x0FF); + System.arraycopy( + mData, 0, array, ISO7816.OFFSET_DATA, mData.length); + } else { + // APDU Case 4 + array = new byte[ISO7816.CMD_APDU_LENGTH_CASE4_WITHOUT_DATA + + mData.length]; + array[ISO7816.OFFSET_CLA] = mCla; + array[ISO7816.OFFSET_INS] = mIns; + array[ISO7816.OFFSET_P1] = mP1; + array[ISO7816.OFFSET_P2] = mP2; + array[ISO7816.OFFSET_P3] = (byte) (mData.length & 0x0FF); + System.arraycopy( + mData, 0, array, ISO7816.OFFSET_DATA, mData.length); + array[array.length - 1] = (byte) (mLe & 0x0FF); + } + + } else { + if (mData == null && mLe != null) { + // APDU Case 2 + array = new byte[ISO7816.CMD_APDU_LENGTH_CASE2_EXTENDED]; + array[ISO7816.OFFSET_CLA] = mCla; + array[ISO7816.OFFSET_INS] = mIns; + array[ISO7816.OFFSET_P1] = mP1; + array[ISO7816.OFFSET_P2] = mP2; + array[ISO7816.OFFSET_P3] = (byte) 0x00; + array[ISO7816.OFFSET_DATA] = (byte) ((mLe >> 8) & 0x0FF); + array[ISO7816.OFFSET_DATA + 1] = (byte) (mLe & 0x0FF); + } else if (mData != null && mLe == null) { + // APDU Case 3 + array = new byte[ISO7816. + CMD_APDU_LENGTH_CASE3_WITHOUT_DATA_EXTENDED + + mData.length]; + array[ISO7816.OFFSET_CLA] = mCla; + array[ISO7816.OFFSET_INS] = mIns; + array[ISO7816.OFFSET_P1] = mP1; + array[ISO7816.OFFSET_P2] = mP2; + array[ISO7816.OFFSET_P3] = (byte) 0x00; + array[ISO7816.OFFSET_DATA] = (byte) ((mData.length >> 8) + & 0x0FF); + array[ISO7816.OFFSET_DATA + 1] = (byte) (mData.length & 0x0FF); + System.arraycopy( + mData, + 0, + array, + ISO7816.OFFSET_DATA_EXTENDED, + mData.length); + } else { + // APDU Case 4 + array = new byte[ISO7816. + CMD_APDU_LENGTH_CASE4_WITHOUT_DATA_EXTENDED + + mData.length]; + array[ISO7816.OFFSET_CLA] = mCla; + array[ISO7816.OFFSET_INS] = mIns; + array[ISO7816.OFFSET_P1] = mP1; + array[ISO7816.OFFSET_P2] = mP2; + array[ISO7816.OFFSET_P3] = (byte) 0x00; + array[ISO7816.OFFSET_DATA] = (byte) ((mData.length >> 8) + & 0x0FF); + array[ISO7816.OFFSET_DATA + 1] = (byte) (mData.length & 0x0FF); + System.arraycopy( + mData, + 0, + array, + ISO7816.OFFSET_DATA_EXTENDED, + mData.length); + array[array.length - 2] = (byte) ((mLe >> 8) & 0x0FF); + array[array.length - 1] = (byte) (mLe & 0x0FF); + } + } + return array; + } + + public CommandApdu cloneWithLe(int le) { + if (mData == null) { + // Original APDU was Case 1 or Case 2 + return new CommandApdu(mCla, mIns, mP1, mP2, (byte) le); + } else { + // Original APDU was case 3 or 4 + return new CommandApdu(mCla, mIns, mP1, mP2, mData, (byte) le); + } + } + + /** + * Private method - Set CLA byte and check if it is a valid value or not. + * + * @param cla Value of CLA field. + * + * @throws IllegalArgumentException if CLA is invalid. + */ + private void setCla(byte cla) throws IllegalArgumentException { + if (cla == (byte) 0xFF) { + // cla has a wrong value! + throw new IllegalArgumentException( + "Invalid value of CLA (" + Integer.toHexString(cla) + ")"); + } + mCla = cla; + } + + /** + * Private method - Set INS byte and check if it is a valid value or not. + * + * @param ins Value of INS field. + * + * @throws IllegalArgumentException if INS is invalid. + */ + private void setIns(byte ins) throws IllegalArgumentException { + if ((ins & 0x0F0) == 0x60 || ((ins & 0x0F0) == 0x90)) { + // ins has a wrong value! + throw new IllegalArgumentException( + "Invalid value of INS (" + Integer.toHexString(ins) + "). " + + "0x6X and 0x9X are not valid values"); + } + mIns = ins; + } + + /** + * Private method - Set p1 byte. + * + * @param p1 Value of P1 field. + */ + private void setP1(byte p1) { + mP1 = p1; + } + + /** + * Private method - Set p2 byte. + * + * @param p2 Value of P2 field. + */ + private void setP2(byte p2) { + mP2 = p2; + } + + /** + * Private method Set Data. + * + * @param data Value of APDU data. + * @throws IllegalArgumentException if Data is null, 0 or is too long. + */ + private void setData(byte[] data) throws IllegalArgumentException { + if (data == null) { + throw new IllegalArgumentException( + "Data must not be null."); + } + + if (data.length > ISO7816.MAX_COMMAND_DATA_LENGTH) { + throw new IllegalArgumentException( + "Data too long."); + } + + if (data.length == 0) { + throw new IllegalArgumentException( + "Data must not be empty."); + } + + mData = new byte[data.length]; + System.arraycopy(data, 0, mData, 0, data.length); + } + + /** + * Private method - Set LE byte. + * + * @param le Value of LE field. + * + * @throws IllegalArgumentException if Le is invalid. + */ + private void setLe(int le) throws IllegalArgumentException { + + if (le < 0 || le > ISO7816.MAX_RESPONSE_DATA_LENGTH) { + throw new IllegalArgumentException( + "Invalid value for le parameter (" + le + ")."); + } + mLe = le; + } + + /** + * Check if the Apdu is extended. + * + * @return true if Apdu is extended or false otherwise. + */ + public boolean isExtendedLength() { + if ((mLe != null && mLe > ISO7816.MAX_RESPONSE_DATA_LENGTH_NO_EXTENDED) + || (mData != null + && mData.length + > ISO7816.MAX_COMMAND_DATA_LENGTH_NO_EXTENDED)) { + return true; + } + return false; + } +} diff --git a/app/src/main/java/nl/mansoft/openmobileapi/util/ISO7816.java b/app/src/main/java/nl/mansoft/openmobileapi/util/ISO7816.java new file mode 100644 index 0000000..6fe2f0e --- /dev/null +++ b/app/src/main/java/nl/mansoft/openmobileapi/util/ISO7816.java @@ -0,0 +1,585 @@ +/* + * Copyright (C) 2015, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* + * Contributed by: Giesecke & Devrient GmbH. + */ + +package nl.mansoft.openmobileapi.util; + +/** + * This object represents ISO/IEC 7816. + * + * @author Giesecke & Devrient + * + */ +public class ISO7816 { + /** + * Maximum command APDU data length. + */ + public static final int MAX_COMMAND_DATA_LENGTH = 65535; + + /** + * Maximum response APDU data length. + */ + public static final int MAX_RESPONSE_DATA_LENGTH = 65536; + + /** + * Maximum command APDU data length. + */ + public static final int MAX_COMMAND_DATA_LENGTH_NO_EXTENDED = 255; + + /** + * Maximum response APDU data length. + */ + public static final int MAX_RESPONSE_DATA_LENGTH_NO_EXTENDED = 256; + + /** + * Length of an APDU Case 1. + */ + public static final int CMD_APDU_LENGTH_CASE1 = 4; + + /** + * Length of an APDU Case 2. + */ + public static final int CMD_APDU_LENGTH_CASE2 = 5; + + /** + * Length of an APDU Case 2 extended case. + */ + public static final int CMD_APDU_LENGTH_CASE2_EXTENDED = 7; + + /** + * Length of an APDU Case 3 without taking data into account. + */ + public static final int CMD_APDU_LENGTH_CASE3_WITHOUT_DATA = 5; + + /** + * Length of an APDU Case 3 extended without taking data into account. + */ + public static final int CMD_APDU_LENGTH_CASE3_WITHOUT_DATA_EXTENDED = 7; + + /** + * Length of an APDU Case 4 without taking data into account. + */ + public static final int CMD_APDU_LENGTH_CASE4_WITHOUT_DATA = 6; + + /** + * Length of an APDU Case 4 extended without taking data into account. + */ + public static final int CMD_APDU_LENGTH_CASE4_WITHOUT_DATA_EXTENDED = 9; + + /** + * Length of an status word. + */ + public static final int RESP_APDU_LENGTH_SW = 2; + + // Offsets in Command APDU. + + /** + * CLA offset in Command APDU. + */ + public static final int OFFSET_CLA = 0; + /** + * INS offset in Command APDU. + */ + public static final int OFFSET_INS = 1; + /** + * P1 offset in Command APDU. + */ + public static final int OFFSET_P1 = 2; + /** + * P2 offset in Command APDU. + */ + public static final int OFFSET_P2 = 3; + /** + * P3 offset in Command APDU. + */ + public static final int OFFSET_P3 = 4; + /** + * Data offset in Command APDU. + */ + public static final int OFFSET_DATA = 5; + + /** + * Data offset in Command APDU. + */ + public static final int OFFSET_DATA_EXTENDED = 7; + + // CLA values. + + /** + * Interindustry class value. + */ + public static final byte CLA_INTERINDUSTRY = (byte) 0x00; + /** + * Proprietary class value. + */ + public static final byte CLA_PROPRIETARY = (byte) 0x80; + + // Ins values. + + /** + * ISO EXTERNAL AUTHENTICATE DATA instruction value. + */ + public static final byte INS_EXTERNAL_AUTHENTICATE = (byte) 0x82; + /** + * ISO APPEND RECORD instruction value. + */ + public static final byte INS_APPEND_RECORD = (byte) 0xE2; + /** + * ISO CHANGE REFERENCE DATA instruction value. + */ + public static final byte INS_CHANGE_REF_DATA = (byte) 0x24; + /** + * ISO ENABLE VERIFICAITION REQUIREMENT instruction value. + */ + public static final byte INS_ENABLE_VERIF_REQ = (byte) 0x28; + /** + * ISO ENVELOPE instruction value (0xC3). + */ + public static final byte INS_ENVELOPE_C3 = (byte) 0xC3; + /** + * ISO ENVELOPE instruction value (0xC2). + */ + public static final byte INS_ENVELOPE_C2 = (byte) 0xC2; + /** + * ISO ERASE BINARY instruction value (0x0E). + */ + public static final byte INS_ERASE_BINARY_0E = (byte) 0x0E; + /** + * ISO ERASE BINARY instruction value (0x0F). + */ + public static final byte INS_ERASE_BINARY_0F = (byte) 0x0F; + /** + * ISO ERASE RECORD instruction value. + */ + public static final byte INS_ERASE_RECORD = (byte) 0x0C; + /** + * ISO GENERAL AUTHENTICATE instruction value (0x86). + */ + public static final byte INS_GENERAL_AUTHENTICATE_86 = (byte) 0x86; + /** + * ISO GENERAL AUTHENTICATE instruction value (0x87). + */ + public static final byte INS_GENERAL_AUTHENTICATE_87 = (byte) 0x87; + /** + * ISO GENERATE ASYMMETRIC KEY PAIR instruction value. + */ + public static final byte INS_GENERATE_ASYMMETRIC_KEY_PAIR = (byte) 0x46; + /** + * ISO GET CHALLENGE instruction value. + */ + public static final byte INS_GET_CHALLENGE = (byte) 0x84; + /** + * ISO GET DATA instruction value (0xCA). + */ + public static final byte INS_GET_DATA_CA = (byte) 0xCA; + /** + * ISO GET DATA instruction value (0xCB). + */ + public static final byte INS_GET_DATA_CB = (byte) 0xCB; + /** + * ISO GET RESPONSE instruction value. + */ + public static final byte INS_GET_RESPONSE = (byte) 0xC0; + /** + * ISO INTERNAL AUTHENTICATE instruction value. + */ + public static final byte INS_INTERNAL_AUTHENTICATE = (byte) 0x88; + /** + * ISO MANAGE CHANNEL instruction value. + */ + public static final byte INS_MANAGE_CHANNEL = (byte) 0x70; + /** + * ISO MANAGE SECURITY ENVIRONMENT instruction value. + */ + public static final byte INS_MANAGE_SECURITY_ENVIRONMENT = (byte) 0x22; + /** + * ISO PUT DATA instruction value (0xDA). + */ + public static final byte INS_PUT_DATA_DA = (byte) 0xDA; + /** + * ISO PUT DATA instruction value (0xDB). + */ + public static final byte INS_PUT_DATA_DB = (byte) 0xDB; + /** + * ISO DISABLE VERIFICATION REQUIREMENT instruction value. + */ + public static final byte INS_DISABLE_VERIF_REQ = (byte) 0x26; + /** + * ISO READ BINARY instruction value (0xB0). + */ + public static final byte INS_READ_BINARY_B0 = (byte) 0xB0; + /** + * ISO READ BINARY instruction value (0xB1). + */ + public static final byte INS_READ_BINARY_B1 = (byte) 0xB1; + /** + * ISO READ RECORD instruction value (0xB2). + */ + public static final byte INS_READ_RECORD_B2 = (byte) 0xB2; + /** + * ISO READ RECORD instruction value (0xB3). + */ + public static final byte INS_READ_RECORD_B3 = (byte) 0xB3; + /** + * ISO RESET RETRY COUNTER instruction value. + */ + public static final byte INS_RESET_RETRY_CTR = (byte) 0x2C; + /** + * ISO SEARCH BINARY instruction value (0xA0). + */ + public static final byte INS_SEARCH_BINARY_A0 = (byte) 0xA0; + /** + * ISO SEARCH BINARY instruction value (0xA1). + */ + public static final byte INS_SEARCH_BINARY_A1 = (byte) 0xA1; + /** + * ISO SEARCH RECORD instruction value. + */ + public static final byte INS_SEARCH_RECORD = (byte) 0xA2; + /** + * ISO SELECT instruction value. + */ + public static final byte INS_SELECT = (byte) 0xA4; + /** + * ISO UPDATE BINARY instruction value (0xD6). + */ + public static final byte INS_UPDATE_BINARY_D6 = (byte) 0xD6; + /** + * ISO UPDATE BINARY instruction value (0xD7). + */ + public static final byte INS_UPDATE_BINARY_D7 = (byte) 0xD7; + /** + * ISO UPDATE RECORD instruction value (0xDC). + */ + public static final byte INS_UPDATE_RECORD_DC = (byte) 0xDC; + /** + * ISO UPDATE RECORD instruction value (0xDD). + */ + public static final byte INS_UPDATE_RECORD_DD = (byte) 0xDD; + /** + * ISO VERIFY instruction value (0x20). + */ + public static final byte INS_VERIFY_20 = (byte) 0x20; + /** + * ISO VERIFY instruction value (0x21). + */ + public static final byte INS_VERIFY_21 = (byte) 0x21; + /** + * ISO WRITE BINARY instruction value (0xD0). + */ + public static final byte INS_WRITE_BINARY_D0 = (byte) 0xD0; + /** + * ISO WRITE BINARY instruction value (0xD1). + */ + public static final byte INS_WRITE_BINARY_D1 = (byte) 0xD1; + /** + * ISO WRITE RECORD instruction value. + */ + public static final byte INS_WRITE_RECORD = (byte) 0xD2; + + // SW Values + + // 9000 - Normal processing - No further qualification. + /** + * No further qualification. + */ + public static final int SW_NO_FURTHER_QUALIFICATION = 0x9000; + + // 61XX - Normal processing - SW2 encodes the number of data bytes still + // available. + /** + * SW2 encodes the number of data bytes still available (minimum value). + */ + public static final int SW_NORMAL_PROCESSING_MIN = 0x6100; + /** + * SW2 encodes the number of data bytes still available (maximum value). + */ + public static final int SW_NORMAL_PROCESSING_MAX = 0x61FF; + + // 62XX - Warning processing - State of non-volatile memory is unchanged + // (further qualification in SW2). + /** + * Warning given. + */ + public static final byte SW1_62 = (byte) 0x62; + /** + * No information given. + */ + public static final int SW_62_NO_INFO = 0x6200; + /** + * Triggering by the card (minimum value). + */ + public static final int SW_62_TRIGGERING_CARD_MIN = 0x6202; + /** + * Triggering by the card (maximum value). + */ + public static final int SW_62_TRIGGERING_CARD_MAX = 0x6280; + /** + * Part of returned data may be corrupted. + */ + public static final int SW_DATA_CORRUPTED = 0x6281; + /** + * End of file or record reached before reading Ne bytes. + */ + public static final int SW_UNEXPECTED_EOF = 0x6282; + /** + * Selected file deactivated. + */ + public static final int SW_FILE_DEACTIVATED = 0x6283; + /** + * File control information not formatted according to 5.3.3. + */ + public static final int SW_WRONG_FILE_CONTROL_FORMAT = 0x6284; + /** + * Selected file in termination state. + */ + public static final int SW_FILE_STATE_TERMINATION = 0x6285; + /** + * No input data available from a sensor on the card. + */ + public static final int SW_NO_INPUT_DATA_AVAILABLE = 0x6286; + + // 63XX - Warning processing - State of non-volatile memory has + // changed (further qualification in SW2). + /** + * Warning given. + */ + public static final byte SW1_63 = (byte) 0x63; + /** + * No information given. + */ + public static final int SW_63_NO_INFO = 0x6300; + /** + * File filled up by the last write. + */ + public static final int SW_FILE_FILLED_UP = 0x6381; + /** + * Counter from 0 to 15 encoded by X (exact meaning depending on the + * command). Minimum value. + */ + public static final int SW_CTR_MIN = 0x63C0; + /** + * Counter from 0 to 15 encoded by X (exact meaning depending on the + * command). Maximum value. + */ + public static final int SW_CTR_MAX = 0x63CF; + + // 64XX - Execution error - State of non-volatile memory is + // unchanged (further qualification in SW2) + /** + * Execution error. + */ + public static final int SW_EXEC_ERROR = 0x6400; + /** + * Immediate response required by the card. + */ + public static final int SW_IMMEDIATE_RESPONSE_REQUIRED = 0x6401; + /** + * Triggering by the card (minimum value). + */ + public static final int SW_64_TRIGGERING_CARD_MIN = 0x6402; + /** + * Triggering by the card (maximum value). + */ + public static final int SW_64_TRIGGERING_CARD_MAX = 0x6480; + + // 65XX - Execution error - State of non-volatile memory has changed + // (further qualification in SW2) + /** + * No information given. + */ + public static final int SW_65_NO_INFO = 0x6500; + /** + * Memory failure. + */ + public static final int SW_MEMORY_FAILURE = 0x6581; + + // 66XX - Execution error - Security-related issues + /** + * Security issue (minimum value). + */ + public static final int SW_SECURITY_ISSUE_MIN = 0x6600; + /** + * Security issue (maximum value). + */ + public static final int SW_SECURITY_ISSUE_MAX = 0x66FF; + + // 6700 - Checking error - Wrong length, no further indication + /** + * Wrong length, no further indication. + */ + public static final int SW_WRONG_LENGTH = 0x6700; + + // 68XX - Checking error - Functions in CLA not supported + // (further qualification in SW2) + /** + * No information given. + */ + public static final int SW_68_NO_INFO = 0x6800; + /** + * Logical channel not supported. + */ + public static final int SW_LOGICAL_CHANNEL_NOT_SUPPORTED = 0x6881; + /** + * Secure messaging not supported. + */ + public static final int SW_SECURE_MESSAGING_NOT_SUPPORTED = 0x6882; + /** + * Last command of the chain expected. + */ + public static final int SW_LAST_COMMAND_EXPECTED = 0x6883; + /** + * Command chaining not supported. + */ + public static final int SW_COMMAND_CHAINING_NOT_SUPPORTED = 0x6884; + + // 69XX - Checking error - Command not allowed (further + // qualification in SW2). + /** + * No information given. + */ + public static final int SW_69_NO_INFO = 0x6900; + /** + * Command incompatible with file structure. + */ + public static final int SW_COMMAND_INCOMPATIBLE = 0x6981; + /** + * Security status not satisfied. + */ + public static final int SW_SECURITY_STATUS_NOT_SATISFIED = 0x6982; + /** + * Authentication method blocked. + */ + public static final int SW_AUTH_METHOD_BLOCKED = 0x6983; + /** + * Reference data not usable. + */ + public static final int SW_REF_DATA_NOT_USABLE = 0x6984; + /** + * Conditions of use not satisfied. + */ + public static final int SW_CONDITIONS_NOT_SATISFIED = 0x6985; + /** + * Command not allowed (no current EF). + */ + public static final int SW_COMMAND_NOT_ALLOWED = 0x6986; + /** + * Expected secure messaging data objects missing. + */ + public static final int SW_SM_OBJECT_MISSING = 0x6987; + /** + * Incorrect secure messaging data objects. + */ + public static final int SW_SM_INCORRECT_OBJECT = 0x6988; + + // 6AXX - Checking error - Wrong parameters P1-P2 (further + // qualification in SW2) + /** + * No information given. + */ + public static final int SW_6A_NO_INFO = 0x6A00; + /** + * Incorrect parameters in the command data field. + */ + public static final int SW_WRONG_DATA = 0x6A80; + /** + * Function not supported. + */ + public static final int SW_FUNC_NOT_SUPPORTED = 0x6A81; + /** + * File or application not found. + */ + public static final int SW_FILE_OR_APP_NOT_FOUND = 0x6A82; + /** + * Record not found. + */ + public static final int SW_RECORD_NOT_FOUND = 0x6A83; + /** + * Not enough memory space in the file. + */ + public static final int SW_NOT_ENOUGH_MEMORY = 0x6A84; + /** + * Nc inconsistent with TLV structure. + */ + public static final int SW_WRONG_NC_TLV = 0x6A85; + /** + * Incorrect parameters P1-P2. + */ + public static final int SW_INCORRECT_P1P2 = 0x6A86; + /** + * Nc inconsistent with parameters P1-P2. + */ + public static final int SW_WRONG_NC_P1P2 = 0x6A87; + /** + * Referenced data or reference data not found (exact meaning depending on + * the command). + */ + public static final int SW_REF_NOT_FOUND = 0x6A88; + /** + * File already exists. + */ + public static final int SW_FILE_ALREADY_EXISTS = 0x6A89; + /** + * DF name already exists. + */ + public static final int SW_DF_NAME_ALREADY_EXISTS = 0x6A8A; + + // 6B00 - Checking error - Wrong parameters P1-P2 + /** + * Wrong parameters P1-P2. + */ + public static final int SW_WRONG_PARAMETERS_P1P2 = 0x6B00; + + // 6CXX - Checking error - Wrong Le field; SW2 encodes the exact + // number of available data bytes. + /** + * Wrong Le field; SW2 encodes the exact number of available data bytes + * (minimum value). + */ + public static final int SW_WRONG_LE_MIN = 0x6C00; + /** + * Wrong Le field; SW2 encodes the exact number of available data bytes + * (maximum value). + */ + public static final int SW_WRONG_LE_MAX = 0x6CFF; + + // 6D00 - Checking error - Instruction code not supported or + // invalid. + /** + * Instruction code not supported or invalid. + */ + public static final int SW_INS_NOT_SUPPORTED = 0x6D00; + + // 6E00 - Checking error - Class not supported. + /** + * Class not supported. + */ + public static final int SW_CLA_NOT_SUPPORTED = 0x6E00; + + // 6F00 - Checking error - No precise diagnostic + /** + * No precise diagnostic. + */ + public static final int SW_NO_PRECISE_DIAGNOSTIC = 0x6F00; + + /** + * This class cannot be instantiated. + */ + private ISO7816() { + } +} diff --git a/app/src/main/java/nl/mansoft/openmobileapi/util/ResponseApdu.java b/app/src/main/java/nl/mansoft/openmobileapi/util/ResponseApdu.java new file mode 100644 index 0000000..e1bab86 --- /dev/null +++ b/app/src/main/java/nl/mansoft/openmobileapi/util/ResponseApdu.java @@ -0,0 +1,125 @@ +/* + * Copyright 2013 Giesecke & Devrient GmbH. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package nl.mansoft.openmobileapi.util; + +/** + * This object represents a response APDU as specified by ISO/IEC 7816. + * + * @author Giesecke & Devrient + * + */ +public class ResponseApdu { + + /** + * DATA field of response APDU. + */ + private byte[] mData; + + /** + * STATUS WORD field of response APDU. + */ + private byte[] mSw; + + /** + * Creates a response APDU. + * + * @param response The response APDU as a byte array. + */ + public ResponseApdu(byte[] response) { + if (response == null) { + throw new IllegalArgumentException("Response must not be null."); + } + if (response.length < ISO7816.RESP_APDU_LENGTH_SW + || response.length > ISO7816.MAX_RESPONSE_DATA_LENGTH + + ISO7816.RESP_APDU_LENGTH_SW) { + throw new IllegalArgumentException( + "Invalid response length (" + response.length + ")."); + } + if (response.length > ISO7816.RESP_APDU_LENGTH_SW) { + mData = new byte[response.length - ISO7816.RESP_APDU_LENGTH_SW]; + System.arraycopy(response, 0, mData, 0, mData.length); + } + mSw = new byte[ISO7816.RESP_APDU_LENGTH_SW]; + System.arraycopy(response, response.length + - ISO7816.RESP_APDU_LENGTH_SW, mSw, 0, + ISO7816.RESP_APDU_LENGTH_SW); + } + + /** + * Returns the DATA field of the response if present, null otherwise. + * + * @return The DATA field of the response if present, null otherwise. + */ + public byte[] getData() { + return mData; + } + + /** + * Returns the STATUS WORD field of the response. + * + * @return The STATUS WORD field of the response. + */ + public byte[] getSw() { + return mSw; + } + + /** + * Returns the STATUS WORD field as int value. + * + * @return The STATUS WORD field as int value. + */ + public int getSwValue() { + return ((mSw[0] & 0x0FF) << 8) + (mSw[1] & 0x0FF); + } + + /** + * Returns true if the SW = 90 00, false otherwise. + * + * @return true if the SW = 90 00, false otherwise.. + */ + public boolean isSuccess() { + return getSwValue() == ISO7816.SW_NO_FURTHER_QUALIFICATION; + } + + /** + * Returns true if the SW = 62 XX or SW = 63 XX, false otherwise. + * + * @return true if the SW = 62 XX or SW = 63 XX, false otherwise. + */ + public boolean isWarning() { + return mSw[0] == ISO7816.SW1_62 || mSw[0] == ISO7816.SW1_63; + } + + /** + * Returns the first byte of STATUS WORD field as int value. + * + * @return The first byte of STATUS WORD field as int value. + */ + public int getSw1Value() { + return (int) 0x0FF & mSw[0]; + } + + /** + * Returns the second byte of STATUS WORD field as int value. + * + * @return The second byte of STATUS WORD field as int value. + */ + public int getSw2Value() { + return (int) 0x0FF & mSw[1]; + } +} + diff --git a/res/drawable-hdpi/icon.png b/app/src/main/res/drawable-hdpi/icon.png similarity index 100% rename from res/drawable-hdpi/icon.png rename to app/src/main/res/drawable-hdpi/icon.png diff --git a/res/drawable-ldpi/icon.png b/app/src/main/res/drawable-ldpi/icon.png similarity index 100% rename from res/drawable-ldpi/icon.png rename to app/src/main/res/drawable-ldpi/icon.png diff --git a/res/drawable-mdpi/icon.png b/app/src/main/res/drawable-mdpi/icon.png similarity index 100% rename from res/drawable-mdpi/icon.png rename to app/src/main/res/drawable-mdpi/icon.png diff --git a/res/layout/main.xml b/app/src/main/res/layout/main.xml similarity index 100% rename from res/layout/main.xml rename to app/src/main/res/layout/main.xml diff --git a/res/values/strings.xml b/app/src/main/res/values/strings.xml similarity index 100% rename from res/values/strings.xml rename to app/src/main/res/values/strings.xml diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..6c5cbc3 --- /dev/null +++ b/build.gradle @@ -0,0 +1,17 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. +buildscript { + repositories { + jcenter() + google() + } + dependencies { + classpath 'com.android.tools.build:gradle:3.1.3' + } +} + +allprojects { + repositories { + jcenter() + google() + } +} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..13372ae Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..d08c250 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Tue Apr 10 19:04:30 CEST 2018 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip diff --git a/gradlew b/gradlew new file mode 100644 index 0000000..9d82f78 --- /dev/null +++ b/gradlew @@ -0,0 +1,160 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..8a0b282 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/project.properties b/project.properties deleted file mode 100644 index 7320fca..0000000 --- a/project.properties +++ /dev/null @@ -1,16 +0,0 @@ -# This file is automatically generated by Android Tools. -# Do not modify this file -- YOUR CHANGES WILL BE ERASED! -# -# This file must be checked in Version Control Systems. -# -# To customize properties used by the Ant build system edit -# "ant.properties", and override values to adapt the script to your -# project structure. -# -# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): -#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt - -# Indicates whether an apk should be generated for each density. -split.density=false -# Project target. -target=Giesecke & Devrient GmbH:Open Mobile API:15 diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..e7b4def --- /dev/null +++ b/settings.gradle @@ -0,0 +1 @@ +include ':app' diff --git a/src/com/gieseckedevrient/android/hellosmartcard/MainActivity.java b/src/com/gieseckedevrient/android/hellosmartcard/MainActivity.java deleted file mode 100644 index 3a3f210..0000000 --- a/src/com/gieseckedevrient/android/hellosmartcard/MainActivity.java +++ /dev/null @@ -1,101 +0,0 @@ -package com.gieseckedevrient.android.hellosmartcard; - -import android.app.Activity; -import android.os.Bundle; -import android.util.Log; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.ViewGroup.LayoutParams; -import android.widget.Button; -import android.widget.LinearLayout; -import android.widget.Toast; -import org.simalliance.openmobileapi.*; - - -public class MainActivity extends Activity implements SEService.CallBack { - - final String LOG_TAG = "HelloSmartcard"; - - private SEService seService; - - private Button button; - - - @Override - public void onCreate(Bundle savedInstanceState) { - - super.onCreate(savedInstanceState); - - LinearLayout layout = new LinearLayout(this); - layout.setLayoutParams(new LayoutParams( - LayoutParams.WRAP_CONTENT, - LayoutParams.WRAP_CONTENT)); - - button = new Button(this); - button.setLayoutParams(new LayoutParams( - LayoutParams.WRAP_CONTENT, - LayoutParams.WRAP_CONTENT)); - - button.setText("Click Me"); - button.setEnabled(false); - button.setOnClickListener(new OnClickListener() { - public void onClick(View v) { - try { - Log.i(LOG_TAG, "Retrieve available readers..."); - Reader[] readers = seService.getReaders(); - if (readers.length < 1) - return; - - Log.i(LOG_TAG, "Create Session from the first reader..."); - Session session = readers[0].openSession(); - - Log.i(LOG_TAG, "Create logical channel within the session..."); - Channel channel = session.openLogicalChannel(new byte[] { - (byte) 0xD2, 0x76, 0x00, 0x01, 0x18, 0x00, 0x02, - (byte) 0xFF, 0x49, 0x50, 0x25, (byte) 0x89, - (byte) 0xC0, 0x01, (byte) 0x9B, 0x01 }); - - Log.i(LOG_TAG, "Send HelloWorld APDU command"); - byte[] respApdu = channel.transmit(new byte[] { - (byte) 0x90, 0x10, 0x00, 0x00, 0x00 }); - - channel.close(); - - // Parse response APDU and show text but remove SW1 SW2 first - byte[] helloStr = new byte[respApdu.length - 2]; - System.arraycopy(respApdu, 0, helloStr, 0, respApdu.length - 2); - Toast.makeText(MainActivity.this, new String(helloStr), Toast.LENGTH_LONG).show(); - } catch (Exception e) { - Log.e(LOG_TAG, "Error occured:", e); - return; - } - } - }); - - layout.addView(button); - setContentView(layout); - - - try { - Log.i(LOG_TAG, "creating SEService object"); - seService = new SEService(this, this); - } catch (SecurityException e) { - Log.e(LOG_TAG, "Binding not allowed, uses-permission org.simalliance.openmobileapi.SMARTCARD?"); - } catch (Exception e) { - Log.e(LOG_TAG, "Exception: " + e.getMessage()); - } - } - - @Override - protected void onDestroy() { - if (seService != null && seService.isConnected()) { - seService.shutdown(); - } - super.onDestroy(); - } - - public void serviceConnected(SEService service) { - Log.i(LOG_TAG, "seviceConnected()"); - button.setEnabled(true); - } -}