Apply smartcard-api.patch.
Change-Id: Iebb74aa0613469967af8510e6e86db0493e319e4
diff --git a/Android.mk b/Android.mk
new file mode 100644
index 0000000..8e4f18c
--- /dev/null
+++ b/Android.mk
@@ -0,0 +1,17 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_AIDL_INCLUDES := packages/apps/SmartCardService/openmobileapi/src/org/simalliance/openmobileapi/service
+
+LOCAL_PACKAGE_NAME := SmartcardService
+LOCAL_CERTIFICATE := platform
+
+LOCAL_JAVA_LIBRARIES := core framework org.simalliance.openmobileapi
+
+LOCAL_PROGUARD_ENABLED := disabled
+
+include $(BUILD_PACKAGE)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
new file mode 100644
index 0000000..bc67321
--- /dev/null
+++ b/AndroidManifest.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="org.simalliance.openmobileapi.service"
+ android:sharedUserId="org.simalliance.uid.openmobileapi"
+ android:versionCode="5"
+ android:versionName="3.1.0">
+
+ <permission android:label="SmartcardServicePermission label"
+ android:protectionLevel="dangerous"
+ android:name="org.simalliance.openmobileapi.SMARTCARD">
+ </permission>
+
+ <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
+ <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
+ <uses-permission android:name="android.permission.NFC" />
+ <uses-permission android:name="com.android.nfc.permission.NFCEE_ADMIN" />
+
+ <application android:label="SmartcardService">
+
+ <uses-library android:name="org.simalliance.openmobileapi" android:required="true" />
+
+ <service android:enabled="true"
+ android:name="org.simalliance.openmobileapi.service.SmartcardService"
+ android:process=":remote"
+ android:permission="org.simalliance.openmobileapi.SMARTCARD">
+ <intent-filter>
+ <action android:name="org.simalliance.openmobileapi.service.ISmartcardService" />
+ </intent-filter>
+ </service>
+ <receiver android:name="org.simalliance.openmobileapi.service.SmartcardServiceBootCompletedBroadcastReceiver">
+ <intent-filter>
+ <action android:name="android.intent.action.BOOT_COMPLETED"/>
+ </intent-filter>
+ </receiver>
+ </application>
+</manifest>
diff --git a/CleanSpec.mk b/CleanSpec.mk
new file mode 100644
index 0000000..f9dd75a
--- /dev/null
+++ b/CleanSpec.mk
@@ -0,0 +1,5 @@
+$(call add-clean-step, rm -f $(PRODUCT_OUT)/system/app/SmartCardService.apk)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/APPS/SmartcardService_intermediates)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/ETC/org.simalliance.openmobileapi.xml_intermediates)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/JAVA_LIBRARIES/org.simalliance.openmobileapi_intermediates)
+
diff --git a/NOTICE b/NOTICE
new file mode 100644
index 0000000..00ef47d
--- /dev/null
+++ b/NOTICE
@@ -0,0 +1,191 @@
+
+
+ Copyright 2010 Giesecke & Devrient GmbH for security package
+ Copyright (c) 2005-2008, The Android Open Source Project for all other source files
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+
+ 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.
+
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
diff --git a/jni/Android.mk b/jni/Android.mk
new file mode 100644
index 0000000..5053e7d
--- /dev/null
+++ b/jni/Android.mk
@@ -0,0 +1 @@
+include $(call all-subdir-makefiles)
diff --git a/jni/assd/ASSDTerminal.cpp b/jni/assd/ASSDTerminal.cpp
new file mode 100644
index 0000000..698ad82
--- /dev/null
+++ b/jni/assd/ASSDTerminal.cpp
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2011, 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.
+ */
+
+#include "ASSDTerminal.h"
+
+#include <linux/ioctl.h>
+#include "assd.h"
+
+#include <stdlib.h>
+#include <fcntl.h>
+
+// #include <utils/Log.h>
+
+#define LOG_TAG "libassd"
+
+static int fd = -1;
+
+JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
+ return JNI_VERSION_1_6;
+}
+
+/*
+ * Class: android_smartcard_terminals_ASSDTerminal
+ * Method: Close
+ * Signature: (I)V
+ */
+JNIEXPORT void JNICALL Java_org_simalliance_openmobileapi_service_terminals_ASSDTerminal_Close
+(JNIEnv *env, jobject obj) {
+ if (fd >= 0)
+ close(fd);
+ fd = -1;
+}
+
+/*
+ * Class: android_smartcard_terminals_ASSDTerminal
+ * Method: Open
+ * Signature: (I)I
+ */
+JNIEXPORT jboolean JNICALL Java_org_simalliance_openmobileapi_service_terminals_ASSDTerminal_Open
+(JNIEnv *env, jobject obj) {
+ if (fd >= 0)
+ return false;
+
+ fd = open("/dev/assd", O_RDWR);
+ if (fd < 0)
+ return false;
+
+ if (ioctl(fd, ASSD_IOC_ENABLE)) {
+ close(fd);
+ fd = -1;
+ return false;
+ }
+
+ return true;
+}
+
+JNIEXPORT
+jboolean JNICALL Java_org_simalliance_openmobileapi_service_terminals_ASSDTerminal_IsPresent
+(JNIEnv *env, jobject obj) {
+ int result;
+ int f = fd;
+ if (fd < 0)
+ f = open("/dev/assd", O_RDWR);
+ if (f < 0)
+ return false;
+
+ result = ioctl(f, ASSD_IOC_PROBE);
+ if (fd < 0)
+ close(f);
+
+ if (result)
+ return false;
+
+ return true;
+}
+
+/*
+ * Class: android_smartcard_terminals_ASSDTerminal
+ * Method: Transmit
+ * Signature: (I[B)[B
+ */
+JNIEXPORT
+jbyteArray JNICALL Java_org_simalliance_openmobileapi_service_terminals_ASSDTerminal_Transmit
+(JNIEnv *env, jobject obj, jbyteArray jcommand) {
+ uint8_t* buf = NULL;
+ int resultLength;
+ jbyteArray result = NULL;
+ int commandLength = env->GetArrayLength(jcommand);
+ jbyte* command = env->GetByteArrayElements(jcommand, NULL);
+
+ if (command == NULL)
+ return NULL;
+ if ((fd < 0) || (commandLength < 1) || (commandLength > 510))
+ goto clean_and_return;
+
+ buf = (uint8_t*)malloc(512);
+ if (buf == NULL)
+ goto clean_and_return;
+
+ buf[0] = ((commandLength + 2) >> 8) & 0xff;
+ buf[1] = (commandLength + 2) & 0xff;
+ memcpy(&buf[2], command, commandLength);
+
+ if (ioctl(fd, ASSD_IOC_TRANSCEIVE, buf))
+ goto clean_and_return;
+
+ resultLength = ((buf[0] << 8) | buf[1]) - 2;
+ if ((resultLength < 1) || (resultLength > 510))
+ goto clean_and_return;
+
+ result = env->NewByteArray(resultLength);
+ if (result == NULL)
+ goto clean_and_return;
+
+ env->SetByteArrayRegion(result, 0, resultLength, (jbyte*)&buf[2]);
+
+clean_and_return:
+ if (buf != NULL)
+ free(buf);
+
+ env->ReleaseByteArrayElements(jcommand, command, JNI_ABORT);
+ return result;
+}
+
diff --git a/jni/assd/ASSDTerminal.h b/jni/assd/ASSDTerminal.h
new file mode 100644
index 0000000..f99d7c2
--- /dev/null
+++ b/jni/assd/ASSDTerminal.h
@@ -0,0 +1,45 @@
+/* DO NOT EDIT THIS FILE - it is machine generated */
+#include <jni.h>
+/* Header for class org_simalliance_openmobileapi_service_terminals_ASSDTerminal */
+
+#ifndef _Included_org_simalliance_openmobileapi_service_terminals_ASSDTerminal
+#define _Included_org_simalliance_openmobileapi_service_terminals_ASSDTerminal
+#ifdef __cplusplus
+extern "C" {
+#endif
+/*
+ * Class: org_simalliance_openmobileapi_service_terminals_ASSDTerminal
+ * Method: Close
+ * Signature: ()V
+ */
+JNIEXPORT void JNICALL Java_org_simalliance_openmobileapi_service_terminals_ASSDTerminal_Close
+ (JNIEnv *, jobject);
+
+/*
+ * Class: org_simalliance_openmobileapi_service_terminals_ASSDTerminal
+ * Method: Open
+ * Signature: ()Z
+ */
+JNIEXPORT jboolean JNICALL Java_org_simalliance_openmobileapi_service_terminals_ASSDTerminal_Open
+ (JNIEnv *, jobject);
+
+/*
+ * Class: org_simalliance_openmobileapi_service_terminals_ASSDTerminal
+ * Method: IsPresent
+ * Signature: ()Z
+ */
+JNIEXPORT jboolean JNICALL Java_org_simalliance_openmobileapi_service_terminals_ASSDTerminal_IsPresent
+ (JNIEnv *, jobject);
+
+/*
+ * Class: org_simalliance_openmobileapi_service_terminals_ASSDTerminal
+ * Method: Transmit
+ * Signature: ([B)[B
+ */
+JNIEXPORT jbyteArray JNICALL Java_org_simalliance_openmobileapi_service_terminals_ASSDTerminal_Transmit
+ (JNIEnv *, jobject, jbyteArray);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/jni/assd/Android.mk b/jni/assd/Android.mk
new file mode 100644
index 0000000..72a530a
--- /dev/null
+++ b/jni/assd/Android.mk
@@ -0,0 +1,13 @@
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_PRELINK_MODULE := false
+LOCAL_MODULE := libassd
+LOCAL_MODULE_TAGS := optional
+LOCAL_SRC_FILES := ASSDTerminal.cpp
+LOCAL_LDLIBS := -L$(SYSROOT)/usr/lib -llog
+
+
+include $(BUILD_SHARED_LIBRARY)
+
diff --git a/jni/assd/assd.h b/jni/assd/assd.h
new file mode 100644
index 0000000..a6e9fb1
--- /dev/null
+++ b/jni/assd/assd.h
@@ -0,0 +1,45 @@
+
+#define ASSD_IOC_MAGIC 'A'
+
+/*
+ * Enable the ASSD capable card
+ * - EBUSY - Function temporary not available
+ * - ENODEV - No ASSD capable card is available
+ */
+#define ASSD_IOC_ENABLE _IO(ASSD_IOC_MAGIC, 0)
+
+/*
+ * Transceive secure token to secure element
+ * - EBUSY - Function temporary not available
+ * - ENODEV - No ASSD capable card is available
+ * - EINVAL - The secure token is invalid
+ * - ETIMEDOUT - Timeout for communication with secure element reached
+ * - EIO - Error in communication with secure element
+ */
+#define ASSD_IOC_TRANSCEIVE _IOWR(ASSD_IOC_MAGIC, 1, char *)
+
+/*
+ * Probe if currently an ASSD capable card is available
+ * - ENODEV - No ASSD capable card is available
+ */
+#define ASSD_IOC_PROBE _IO(ASSD_IOC_MAGIC, 2)
+
+/*
+ * Wait until an ASSD capable card is available
+ * - ENODEV - No ASSD capable card is available
+ * - ETIMEOUT - The timeout was reached
+ */
+#define ASSD_IOC_WAIT _IOW(ASSD_IOC_MAGIC, 3, int)
+
+/*
+ * Set timeout for communication with secure element
+ * - EINVAL - The value is invalid
+ */
+#define ASSD_IOC_SET_TIMEOUT _IOW(ASSD_IOC_MAGIC, 4, int)
+
+/*
+ * Get version information
+ * - EFAULT - The output buffer is invalid
+ */
+#define ASSD_IOC_GET_VERSION _IOR(ASSD_IOC_MAGIC, 5, char *)
+
diff --git a/openmobileapi/Android.mk b/openmobileapi/Android.mk
new file mode 100644
index 0000000..0cefe39
--- /dev/null
+++ b/openmobileapi/Android.mk
@@ -0,0 +1,36 @@
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := org.simalliance.openmobileapi.xml
+LOCAL_MODULE_TAGS := optional
+LOCAL_MODULE_CLASS := ETC
+LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)/permissions
+LOCAL_SRC_FILES := $(LOCAL_MODULE)
+
+include $(BUILD_PREBUILT)
+
+
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_SRC_FILES += \
+ src/org/simalliance/openmobileapi/service/ISmartcardServiceCallback.aidl \
+ src/org/simalliance/openmobileapi/service/ISmartcardServiceReader.aidl \
+ src/org/simalliance/openmobileapi/service/ISmartcardServiceChannel.aidl \
+ src/org/simalliance/openmobileapi/service/ISmartcardServiceSession.aidl \
+ src/org/simalliance/openmobileapi/service/ISmartcardService.aidl
+
+LOCAL_AIDL_INCLUDES := packages/apps/SmartCardService/openmobileapi/src/org/simalliance/openmobileapi/service
+
+LOCAL_MODULE:= org.simalliance.openmobileapi
+LOCAL_MODULE_TAGS := optional
+
+include $(BUILD_JAVA_LIBRARY)
+
+
+# put the classes.jar, with full class files instead of classes.dex inside, into the dist directory
+$(call dist-for-goals, droidcore, $(full_classes_jar):org.simalliance.openmobileapi.jar)
+
+
diff --git a/openmobileapi/org.simalliance.openmobileapi.xml b/openmobileapi/org.simalliance.openmobileapi.xml
new file mode 100644
index 0000000..643684d
--- /dev/null
+++ b/openmobileapi/org.simalliance.openmobileapi.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 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.
+-->
+
+<permissions>
+ <library name="org.simalliance.openmobileapi"
+ file="/system/framework/org.simalliance.openmobileapi.jar" />
+</permissions>
diff --git a/openmobileapi/src/org/simalliance/openmobileapi/Channel.java b/openmobileapi/src/org/simalliance/openmobileapi/Channel.java
new file mode 100644
index 0000000..7fe5a32
--- /dev/null
+++ b/openmobileapi/src/org/simalliance/openmobileapi/Channel.java
@@ -0,0 +1,259 @@
+/*
+ * Copyright (C) 2011, 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 org.simalliance.openmobileapi;
+
+import java.io.IOException;
+
+import org.simalliance.openmobileapi.service.ISmartcardServiceChannel;
+import org.simalliance.openmobileapi.service.SmartcardError;
+
+import android.os.RemoteException;
+
+/**
+ * Instances of this class represent an ISO7816-4 channel opened to a secure
+ * element. It can be either a logical channel or the default channel. They can
+ * be used to send APDUs to the secure element. Channels are opened by calling
+ * the Session.openBasicChannel(byte[]) or Session.openLogicalChannel(byte[])
+ * methods.
+ *
+ * @see <a href="http://simalliance.org">SIMalliance Open Mobile API v2.02</a>
+ */
+public class Channel {
+
+ private Session mSession;
+
+ private final ISmartcardServiceChannel mChannel;
+ private final SEService mService;
+
+ private final Object mLock = new Object();
+
+ Channel(SEService service, Session session, ISmartcardServiceChannel channel) {
+ mService = service;
+ mSession = session;
+ mChannel = channel;
+ }
+
+ /**
+ * Closes this channel to the Secure Element. If the method is called when the channel is already closed,
+ * this method will be ignored. The close() method shall wait for completion of any pending
+ * transmit(byte[] command) before closing the channel.
+ */
+ public void close() {
+ if (mService == null || mService.isConnected() == false) {
+ throw new IllegalStateException("service not connected to system");
+ }
+ if (mChannel == null) {
+ throw new NullPointerException("channel must not be null");
+ }
+ SmartcardError error = new SmartcardError();
+ try {
+ mChannel.close(error);
+ } catch (RemoteException e) {
+ }
+ SEService.checkForException(error);
+ }
+
+ /**
+ * Tells if this channel is closed.
+ *
+ * @return <code>true</code> if the channel is closed, <code>false</code> otherwise.
+ */
+ public boolean isClosed() {
+ if (mService == null || mService.isConnected() == false) {
+ throw new IllegalStateException("service not connected to system");
+ }
+ if (mChannel == null) {
+ throw new NullPointerException("channel must not be null");
+ }
+ try {
+ return mChannel.isClosed();
+ } catch (Exception e) {
+ throw new RuntimeException(e.getMessage());
+ }
+ }
+
+ /**
+ * Returns a boolean telling if this channel is the basic channel.
+ *
+ * @return <code>true</code> if this channel is a basic channel. <code>false</code> if
+ * this channel is a logical channel.
+ */
+ public boolean isBasicChannel() {
+ if (mService == null || mService.isConnected() == false) {
+ throw new IllegalStateException("service not connected to system");
+ }
+ if (mChannel == null) {
+ throw new NullPointerException("channel must not be null");
+ }
+ try {
+ return mChannel.isBasicChannel();
+ } catch (Exception e) {
+ throw new RuntimeException(e.getMessage());
+ }
+ }
+
+ /**
+ * Transmit an APDU command (as per ISO7816-4) to the secure element and
+ * wait for the response. The underlying layers might generate as much TPDUs
+ * as necessary to transport this APDU. The transport part is invisible from
+ * the application. <br>
+ * The system ensures the synchronization between all the concurrent calls
+ * to this method, and that only one APDU will be sent at a time,
+ * irrespective of the number of TPDUs that might be required to transport
+ * it to the SE. <br>
+ * The channel information in the class byte in the APDU will be completely
+ * ignored. The underlying system will add any required information to
+ * ensure the APDU is transported on this channel. There are restrictions on
+ * the set of commands that can be sent: <br>
+ *
+ * <ul>
+ * <li>MANAGE_CHANNEL commands are not allowed.</li>
+ * <li>SELECT by DF Name (p1=04) are not allowed.</li>
+ * <li>CLA bytes with channel numbers are de-masked.</li>
+ * </ul>
+ *
+ * @param command the APDU command to be transmitted, as a byte array.
+ *
+ * @return the response received, as a byte array.
+ *
+ * @throws IOException if there is a communication problem to the reader or the Secure Element.
+ * @throws IllegalStateException if the channel is used after being closed.
+ * @throws IllegalArgumentException if the command byte array is less than 4 bytes long.
+ * @throws IllegalArgumentException if the length of the APDU is not coherent with the length of the command byte array.
+ * @throws SecurityException if the command is filtered by the security
+ * policy
+ */
+ public byte[] transmit(byte[] command) throws IOException {
+ if (mService == null || mService.isConnected() == false) {
+ throw new IllegalStateException("service not connected to system");
+ }
+ if (mChannel == null) {
+ throw new NullPointerException("channel must not be null");
+ }
+
+ byte[] response;
+ synchronized( mLock ) {
+ SmartcardError error = new SmartcardError();
+ try {
+ response = mChannel.transmit(command, error);
+ } catch (Exception e) {
+ throw new IOException(e.getMessage());
+ }
+ SEService.checkForException(error);
+ }
+ return response;
+ }
+
+ /**
+ * Get the session that has opened this channel.
+ *
+ * @return the session object this channel is bound to.
+ */
+ public Session getSession() {
+ return mSession;
+ }
+
+ /**
+ * Returns the data as received from the application select command inclusively the status word.
+ * The returned byte array contains the data bytes in the following order:
+ * [<first data byte>, ..., <last data byte>, <sw1>, <sw2>]
+ * @return The data as returned by the application select command inclusively the status word.
+ * Only the status word if the application select command has no returned data.
+ * Returns null if an application select command has not been performed or the selection response can not
+ * be retrieved by the reader implementation.
+ */
+ public byte[] getSelectResponse()
+ {
+ if (mService == null || mService.isConnected() == false) {
+ throw new IllegalStateException("service not connected to system");
+ }
+ if (mChannel == null) {
+ throw new NullPointerException("channel must not be null");
+ }
+ try {
+ if (mChannel.isClosed()) {
+ throw new IllegalStateException("channel is closed");
+ }
+ } catch (Exception e1) {
+ throw new RuntimeException(e1.getMessage());
+ }
+
+ byte[] response;
+ try {
+ response = mChannel.getSelectResponse();
+ } catch (Exception e) {
+ throw new RuntimeException(e.getMessage());
+ }
+
+ if(response != null && response.length == 0)
+ response = null;
+ return response;
+ }
+
+ /**
+ * Performs a selection of the next Applet on this channel that matches to the partial AID specified
+ * in the openBasicChannel(byte[] aid) or openLogicalChannel(byte[] aid) method.
+ * This mechanism can be used by a device application to iterate through all Applets
+ * matching to the same partial AID.
+ * If selectNext() returns true a new Applet was successfully selected on this channel.
+ * If no further Applet exists with matches to the partial AID this method returns false
+ * and the already selected Applet stays selected.
+ *
+ * @return <code>true</code> if new Applet was successfully selected.
+ <code>false</code> if no further Applet exists which matches the partial AID.
+ *
+ * @throws IOException if there is a communication problem to the reader or the Secure Element.
+ * @throws IllegalStateException if the channel is used after being closed or it is not connected.
+ * @throws SecurityException if the command is filtered by the security policy
+ */
+ public boolean selectNext() throws IOException {
+ if (mService == null || mService.isConnected() == false) {
+ throw new IllegalStateException("service not connected to system");
+ }
+ if (mChannel == null) {
+ throw new IllegalStateException("channel must not be null");
+ }
+ try {
+ if (mChannel.isClosed()) {
+ throw new IllegalStateException("channel is closed");
+ }
+ } catch (Exception e1) {
+ throw new RuntimeException(e1.getMessage());
+ }
+
+ boolean response = false;
+ synchronized( mLock ) {
+ SmartcardError error = new SmartcardError();
+ try {
+ response = mChannel.selectNext(error);
+ } catch (Exception e) {
+ throw new IOException(e.getMessage());
+ }
+ SEService.checkForException(error);
+ }
+ return response;
+
+ }
+
+
+ // ******************************************************************
+ // package private methods
+ // ******************************************************************
+}
diff --git a/openmobileapi/src/org/simalliance/openmobileapi/Reader.java b/openmobileapi/src/org/simalliance/openmobileapi/Reader.java
new file mode 100644
index 0000000..9253e54
--- /dev/null
+++ b/openmobileapi/src/org/simalliance/openmobileapi/Reader.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2011, 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 org.simalliance.openmobileapi;
+
+import java.io.IOException;
+import org.simalliance.openmobileapi.service.ISmartcardServiceReader;
+import org.simalliance.openmobileapi.service.ISmartcardServiceSession;
+import org.simalliance.openmobileapi.service.SmartcardError;
+import android.os.RemoteException;
+
+/**
+ * Instances of this class represent Secure Element Readers connected to this
+ * device. These Readers can be physical devices or virtual devices. They can be
+ * removable or not. They can contain Secure Element that can or cannot be
+ * removed.
+ *
+ * @see <a href="http://simalliance.org">SIMalliance Open Mobile API v2.02</a>
+ */
+public class Reader {
+
+ private final String mName;
+ private final SEService mService;
+ private ISmartcardServiceReader mReader;
+
+ private final Object mLock = new Object();
+
+
+ Reader(SEService service, String name ) {
+ mName = name;
+ mService = service;
+ mReader = null;
+
+ }
+
+ /**
+ * Return the user-friendly name of this reader.
+ * <ul>
+ * <li>If this reader is a SIM reader, then its name must start with the "SIM" prefix.</li>
+ * <li>If the reader is a SD or micro SD reader, then its name must start with the "SD" prefix</li>
+ * <li>If the reader is a embedded SE reader, then its name must start with the "eSE" prefix</li>
+ * <ul>
+ *
+ * @return name of this Reader
+ */
+ public String getName() {
+ return mName;
+ }
+
+ /**
+ * Connects to a secure element in this reader. <br>
+ * This method prepares (initialises) the Secure Element for communication
+ * before the Session object is returned (e.g. powers the Secure Element by
+ * ICC ON if its not already on). There might be multiple sessions opened at
+ * the same time on the same reader. The system ensures the interleaving of
+ * APDUs between the respective sessions.
+ *
+ * @throws IOException if something went wrong with the communicating to the
+ * Secure Element or the reader.
+ * @return a Session object to be used to create Channels.
+ */
+ public Session openSession() throws IOException {
+
+ if( mService == null || mService.isConnected() == false ){
+ throw new IllegalStateException("service is not connected");
+ }
+ if( mReader == null ){
+ try {
+ mReader = mService.getReader(mName);
+ } catch (Exception e) {
+ throw new IOException("service reader cannot be accessed.");
+ }
+ }
+
+ synchronized (mLock) {
+ SmartcardError error = new SmartcardError();
+ ISmartcardServiceSession session;
+ try {
+ session = mReader.openSession(error);
+ } catch (RemoteException e) {
+ throw new IOException( e.getMessage() );
+ }
+ SEService.checkForException(error);
+
+ if( session == null ){
+ throw new IOException( "service session is null." );
+ }
+
+ return new Session(mService, session, this);
+ }
+ }
+
+ /**
+ * Check if a Secure Element is present in this reader.
+ *
+ * @return <code>true</code> if the SE is present, <code>false</code> otherwise.
+ */
+ public boolean isSecureElementPresent() {
+ if( mService == null || mService.isConnected() == false ){
+ throw new IllegalStateException("service is not connected");
+ }
+ if( mReader == null ){
+ try {
+ mReader = mService.getReader(mName);
+ } catch (Exception e) {
+ throw new IllegalStateException("service reader cannot be accessed. " + e.getLocalizedMessage());
+ }
+ }
+
+ SmartcardError error = new SmartcardError();
+ boolean flag;
+ try {
+ flag = mReader.isSecureElementPresent(error);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e.getMessage());
+ }
+ SEService.checkForException(error);
+ return flag;
+ }
+
+ /**
+ * Return the Secure Element service this reader is bound to.
+ *
+ * @return the SEService object.
+ */
+ public SEService getSEService() {
+ return mService;
+ }
+
+ /**
+ * Close all the sessions opened on this reader. All the channels opened by
+ * all these sessions will be closed.
+ */
+ public void closeSessions() {
+ if( mService == null || mService.isConnected() == false ){
+ throw new IllegalStateException("service is not connected");
+ }
+ if( mReader != null ) {
+ synchronized (mLock) {
+ SmartcardError error = new SmartcardError();
+ try {
+ mReader.closeSessions(error);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e.getMessage());
+ }
+ SEService.checkForException(error);
+ }
+ }
+ }
+
+ // ******************************************************************
+ // package private methods
+ // ******************************************************************
+}
diff --git a/openmobileapi/src/org/simalliance/openmobileapi/SEService.java b/openmobileapi/src/org/simalliance/openmobileapi/SEService.java
new file mode 100644
index 0000000..560c7b4
--- /dev/null
+++ b/openmobileapi/src/org/simalliance/openmobileapi/SEService.java
@@ -0,0 +1,241 @@
+/*
+ * Copyright (C) 2011, 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 org.simalliance.openmobileapi;
+
+import java.security.AccessControlException;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import org.simalliance.openmobileapi.service.CardException;
+import org.simalliance.openmobileapi.service.ISmartcardService;
+import org.simalliance.openmobileapi.service.ISmartcardServiceCallback;
+import org.simalliance.openmobileapi.service.ISmartcardServiceReader;
+import org.simalliance.openmobileapi.service.SmartcardError;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+/**
+ * The SEService realises the communication to available Secure Elements on the
+ * device. This is the entry point of this API. It is used to connect to the
+ * infrastructure and get access to a list of Secure Element Readers.
+ *
+ * @see <a href="http://simalliance.org">SIMalliance Open Mobile API v2.02</a>
+ */
+public class SEService {
+
+ private static final String SERVICE_TAG = "SEService";
+
+ private final Object mLock = new Object();
+
+ /** The client context (e.g. activity). */
+ private final Context mContext;
+
+ /** The backend system. */
+ private volatile ISmartcardService mSmartcardService;
+
+ /**
+ * Class for interacting with the main interface of the backend.
+ */
+ private ServiceConnection mConnection;
+
+ /**
+ * Collection of available readers
+ */
+ final private HashMap<String, Reader> mReaders = new HashMap<String,Reader>();
+
+ /**
+ * This implementation is used to receive callbacks from backend.
+ */
+ private final ISmartcardServiceCallback mCallback = new ISmartcardServiceCallback.Stub() {
+ };
+
+ /**
+ * Callback object that allows the notification of the caller if this
+ * SEService could be bound to the backend.
+ */
+ private CallBack mCallerCallback;
+
+ /**
+ * Interface to receive call-backs when the service is connected. If the
+ * target language and environment allows it, then this shall be an inner
+ * interface of the SEService class.
+ */
+ public interface CallBack {
+
+ /**
+ * Called by the framework when the service is connected.
+ *
+ * @param service the connected service.
+ */
+ void serviceConnected(SEService service);
+ }
+
+ /**
+ * Establishes a new connection that can be used to connect to all the
+ * Secure Elements available in the system. The connection process can be
+ * quite long, so it happens in an asynchronous way. It is usable only if
+ * the specified listener is called or if isConnected() returns
+ * <code>true</code>. <br>
+ * The call-back object passed as a parameter will have its
+ * serviceConnected() method called when the connection actually happen.
+ *
+ * @param context the context of the calling application. Cannot be
+ * <code>null</code>.
+ * @param listener a SEService.CallBack object. Can be <code>null</code>.
+ */
+ public SEService(Context context, SEService.CallBack listener) {
+
+ if (context == null) {
+ throw new NullPointerException("context must not be null");
+ }
+
+ mContext = context;
+ mCallerCallback = listener;
+
+ mConnection = new ServiceConnection() {
+
+ public synchronized void onServiceConnected(ComponentName className, IBinder service) {
+
+ mSmartcardService = ISmartcardService.Stub.asInterface(service);
+ if (mCallerCallback != null) {
+ mCallerCallback.serviceConnected(SEService.this);
+ }
+ Log.v(SERVICE_TAG, "Service onServiceConnected");
+ }
+
+ public void onServiceDisconnected(ComponentName className) {
+ mSmartcardService = null;
+ Log.v(SERVICE_TAG, "Service onServiceDisconnected");
+ }
+ };
+
+ boolean bindingSuccessful = mContext.bindService(new Intent(ISmartcardService.class
+ .getName()), mConnection, Context.BIND_AUTO_CREATE);
+ if (bindingSuccessful) {
+ Log.v(SERVICE_TAG, "bindService successful");
+ }
+ }
+
+ /**
+ * Tells whether or not the service is connected.
+ *
+ * @return <code>true</code> if the service is connected.
+ */
+ public boolean isConnected() {
+ if (mSmartcardService == null) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Returns the list of available Secure Element readers. More precisely it
+ * returns the list of readers that the calling application has the
+ * permission to connect to.
+ *
+ * @return The readers list, as an array of Readers. If there are no readers the returned array is of length 0.
+ */
+ public Reader[] getReaders() {
+ if (mSmartcardService == null) {
+ throw new IllegalStateException("service not connected to system");
+ }
+
+ SmartcardError error = new SmartcardError();
+ String[] readerNames;
+ try {
+ readerNames = mSmartcardService.getReaders(error);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+
+ mReaders.clear();
+ for (String readerName : readerNames) {
+ mReaders.put(readerName, new Reader( this, readerName ));
+ }
+ Collection<Reader> col = mReaders.values();
+ return col.toArray(new Reader[col.size()]);
+ }
+
+ /**
+ * Releases all Secure Elements resources allocated by this SEService. It is
+ * recommended to call this method in the termination method of the calling
+ * application (or part of this application) which is bound to this
+ * SEService.
+ * The SEService becomes invalid after calling shutdown().
+ */
+ public void shutdown() {
+ synchronized (mLock) {
+ if (mSmartcardService != null ) {
+ Collection<Reader> col = mReaders.values();
+ Iterator<Reader> iter = col.iterator();
+ while( iter.hasNext() ) {
+ try {
+ Reader reader = iter.next();
+ reader.closeSessions();
+ } catch (Exception ignore) {
+ }
+ }
+ }
+ try {
+ mContext.unbindService(mConnection);
+ } catch (IllegalArgumentException e) {
+ // Do nothing and fail silently since an error here indicates
+ // that binding never succeeded in the first place.
+ }
+ mSmartcardService = null;
+ }
+ }
+
+ // ******************************************************************
+ // package private methods
+ // ******************************************************************
+
+ ISmartcardServiceReader getReader( String name ){
+
+ SmartcardError error = new SmartcardError();
+ ISmartcardServiceReader reader = null;
+ try {
+ reader = mSmartcardService.getReader(name, error);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e.getMessage());
+ }
+ checkForException(error);
+ return reader;
+ }
+
+ static void checkForException(SmartcardError error) {
+ try {
+ error.throwException();
+ } catch (CardException exp) {
+ throw new IllegalStateException(exp.getMessage());
+ } catch (AccessControlException exp) {
+ throw new SecurityException(exp.getMessage());
+ }
+ }
+
+ ISmartcardServiceCallback getCallback() {
+ return mCallback;
+ }
+}
diff --git a/openmobileapi/src/org/simalliance/openmobileapi/Session.java b/openmobileapi/src/org/simalliance/openmobileapi/Session.java
new file mode 100644
index 0000000..a174215
--- /dev/null
+++ b/openmobileapi/src/org/simalliance/openmobileapi/Session.java
@@ -0,0 +1,370 @@
+/*
+ * Copyright (C) 2011, 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 org.simalliance.openmobileapi;
+
+import java.io.IOException;
+import java.util.MissingResourceException;
+import java.util.NoSuchElementException;
+
+import org.simalliance.openmobileapi.service.ISmartcardServiceChannel;
+import org.simalliance.openmobileapi.service.ISmartcardServiceSession;
+import org.simalliance.openmobileapi.service.SmartcardError;
+
+import android.os.RemoteException;
+
+/**
+ * Instances of this class represent a connection session to one of the secure
+ * elements available on the device. These objects can be used to get a
+ * communication channel with an application in the secure element. This channel
+ * can be the basic channel or a logical channel.
+ *
+ * @see <a href="http://simalliance.org">SIMalliance Open Mobile API v2.02</a>
+ */
+public class Session {
+
+ private final Object mLock = new Object();
+ private final SEService mService;
+ private final Reader mReader;
+ private final ISmartcardServiceSession mSession;
+
+ Session(SEService service, ISmartcardServiceSession session, Reader reader) {
+ mService = service;
+ mReader = reader;
+ mSession = session;
+ }
+
+ /**
+ * Get the reader that provides this session.
+ *
+ * @return The Reader object.
+ */
+ public Reader getReader() {
+ return mReader;
+ }
+
+ /**
+ * Get the Answer to Reset of this Secure Element. <br>
+ * The returned byte array can be null if the ATR for this Secure Element
+ * is not available.
+ *
+ * @return the ATR as a byte array or null.
+ */
+ public byte[] getATR() {
+ if (mService == null || mService.isConnected() == false) {
+ throw new IllegalStateException("service not connected to system");
+ }
+ if( mSession == null ){
+ throw new NullPointerException("service session is null");
+ }
+ try {
+ return mSession.getAtr();
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+ /**
+ * Close the connection with the Secure Element. This will close any
+ * channels opened by this application with this Secure Element.
+ */
+ public void close() {
+ if (mService == null || mService.isConnected() == false) {
+ throw new IllegalStateException("service not connected to system");
+ }
+ if( mSession != null ){
+ synchronized (mLock) {
+ SmartcardError error = new SmartcardError();
+ try {
+ mSession.close(error);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e.getMessage());
+ }
+ SEService.checkForException(error);
+ }
+ }
+ }
+
+ /**
+ * Tells if this session is closed.
+ *
+ * @return <code>true</code> if the session is closed, false otherwise.
+ */
+ public boolean isClosed() {
+ try {
+ if( mSession == null ){
+ return true;
+ }
+ return mSession.isClosed();
+ } catch (RemoteException e) {
+ throw new RuntimeException(e.getMessage());
+ }
+ }
+
+ /**
+ * Close any channel opened on this session.
+ * @throws IOException
+ */
+ public void closeChannels() {
+
+ if (mService == null || mService.isConnected() == false) {
+ throw new IllegalStateException("service not connected to system");
+ }
+
+ if( mSession != null ){
+ synchronized (mLock) {
+ SmartcardError error = new SmartcardError();
+ try {
+ mSession.closeChannels(error);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e.getMessage());
+ }
+ SEService.checkForException(error);
+ }
+ }
+ }
+
+
+ /**
+ * Get an access to the basic channel, as defined in the ISO/IEC 7816-4 specification (the one that has
+ * number 0). The obtained object is an instance of the Channel class.
+ * If the AID is null, which means no Applet is to be selected on this channel and the default Applet is
+ * used. If the AID is defined then the corresponding Applet is selected.
+ * Once this channel has been opened by a device application, it is considered as "locked" by this device
+ * application, and other calls to this method will return null, until the channel is closed. Some Secure
+ * Elements (like the UICC) might always keep the basic channel locked (i.e. return null to applications),
+ * to prevent access to the basic channel, while some other might return a channel object implementing
+ * some kind of filtering on the commands, restricting the set of accepted command to a smaller set.
+ * It is recommended for the UICC to reject the opening of the basic channel to a specific Applet, by
+ * always answering null to such a request.
+ * For other Secure Elements, the recommendation is to accept opening the basic channel on the default
+ * Applet until another Applet is selected on the basic channel. As there is no other way than a reset to
+ * select again the default Applet, the implementation of the transport API should guarantee that the
+ * openBasicChannel(null) command will return null until a reset occurs. If such a restriction is not
+ * possible, then openBasicChannel(null) should always return null and therefore prevent access to the
+ * default Applet on the basic channel.
+ * <p>
+ *
+ * The optional select response data of an applet can be retrieved with byte[] getSelectResponse().
+ *
+ * @param aid the AID of the Applet to be selected on this channel, as a byte array, or null if no Applet is to be
+ * selected.
+ * @throws IOException if there is a communication problem to the reader or the Secure Element (e.g. if the SE is not responding).
+ * @throws IllegalStateException if the Secure Element session is used after being closed.
+ * @throws IllegalArgumentException if the aid's length is not within 5 to
+ * 16 (inclusive).
+ * @throws SecurityException if the calling application cannot be granted
+ * access to this AID or the default application on this
+ * session.
+ *
+ * @throws NoSuchElementException if an Applet with the defined AID does not exist in the SE
+ *
+ * @return an instance of Channel if available or null.
+ */
+ public Channel openBasicChannel(byte[] aid) throws IOException {
+
+ if (mService == null || mService.isConnected() == false) {
+ throw new IllegalStateException("service not connected to system");
+ }
+ if( mSession == null ){
+ throw new NullPointerException("service session is null");
+ }
+ if (getReader() == null) {
+ throw new NullPointerException("reader must not be null");
+ }
+
+ synchronized (mLock) {
+ ISmartcardServiceChannel channel;
+ SmartcardError error = new SmartcardError();
+ try {
+ channel = mSession.openBasicChannelAid(
+ aid,
+ mService.getCallback(),
+ error);
+ } catch (Exception e) {
+ throw new IOException(e.getMessage());
+ }
+ SEService.checkForException(error);
+ error.clear();
+ boolean b = basicChannelInUse(error);
+ SEService.checkForException(error);
+ if ( b ) {
+ return null;
+ }
+ error.clear();
+ b = channelCannotBeEstablished(error);
+ SEService.checkForException(error);
+ if (b) {
+ return null;
+ }
+ if(aid == null || aid.length == 0)
+ {
+ error.clear();
+ b = isDefaultApplicationSelected(error);
+ SEService.checkForException(error);
+ if (!b) {
+ return null;
+ }
+ }
+ error.clear();
+ checkIfAppletAvailable(error);
+ SEService.checkForException(error);
+
+ if (channel == null)
+ return null;
+
+ return new Channel(mService, this, channel );
+ }
+ }
+
+ /**
+ * Open a logical channel with the Secure Element, selecting the Applet
+ * represented by the given AID. The AID can be null, which means no
+ * Applet is to be selected on this channel, the default Applet is
+ * used. It's up to the Secure Element to choose which logical channel will
+ * be used.
+ * <p>
+ * The optional select response data of an applet can be retrieved with byte[] getSelectResponse().
+ * <p>
+ * A logical channel to an applet can be opened multiple times if the applet implements MultiSelectable.
+ *
+ * @param aid the AID of the Applet to be selected on this channel, as
+ * a byte array.
+ * @throws IOException if there is a communication problem to the reader or the Secure Element. (e.g. if the SE is
+ * not responding)
+ * @throws IllegalStateException if the Secure Element is used after being
+ * closed.
+ * @throws IllegalArgumentException if the aid's length is not within 5 to
+ * 16 (inclusive).
+ * @throws SecurityException if the calling application cannot be granted
+ * access to this AID or the default application on this
+ * session.
+ * @throws NoSuchElementException if an Applet with the defined AID does not exist in the SE or a logical channel is already open to a non-multiselectable applet
+ *
+ * @return an instance of Channel. Null if the Secure Element is unable to
+ * provide a new logical channel.
+ */
+ public Channel openLogicalChannel(byte[] aid) throws IOException {
+
+ if (mService == null || mService.isConnected() == false) {
+ throw new IllegalStateException("service not connected to system");
+ }
+ if( mSession == null ){
+ throw new NullPointerException("service session is null");
+ }
+ if (getReader() == null) {
+ throw new NullPointerException("reader must not be null");
+ }
+ synchronized (mLock) {
+ SmartcardError error = new SmartcardError();
+ ISmartcardServiceChannel channel;
+ try {
+ channel = mSession.openLogicalChannel(
+ aid,
+ mService.getCallback(),
+ error);
+ } catch (Exception e) {
+ throw new IOException(e.getMessage());
+ }
+ SEService.checkForException(error);
+ error.clear();
+ boolean b = channelCannotBeEstablished(error);
+ SEService.checkForException(error);
+ if (b) {
+ return null;
+ }
+ error.clear();
+ checkIfAppletAvailable(error);
+ SEService.checkForException(error);
+
+ if (channel == null)
+ return null;
+
+ return new Channel(mService, this, channel);
+ }
+ }
+
+
+ // ******************************************************************
+ // package private methods
+ // ******************************************************************
+
+
+ private boolean isDefaultApplicationSelected(SmartcardError error) {
+ Exception exp = error.createException();
+ if (exp != null) {
+ String msg = exp.getMessage();
+ if (msg != null) {
+ if (msg.contains("default application is not selected")) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ private boolean basicChannelInUse(SmartcardError error) {
+ Exception exp = error.createException();
+ if (exp != null) {
+ String msg = exp.getMessage();
+ if (msg != null) {
+ if (msg.contains("basic channel in use")) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ private boolean channelCannotBeEstablished(SmartcardError error) {
+ Exception exp = error.createException();
+ if (exp != null) {
+ if (exp instanceof MissingResourceException) {
+ return true;
+ }
+ String msg = exp.getMessage();
+ if (msg != null) {
+ if (msg.contains("channel in use")) {
+ return true;
+ }
+ if (msg.contains("open channel failed")) {
+ return true;
+ }
+ if (msg.contains("out of channels")) {
+ return true;
+ }
+ if (msg.contains("MANAGE CHANNEL")) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ private void checkIfAppletAvailable(SmartcardError error) throws NoSuchElementException {
+ Exception exp = error.createException();
+ if (exp != null) {
+ if(exp instanceof NoSuchElementException) {
+ throw new NoSuchElementException("Applet with the defined aid does not exist in the SE");
+ }
+ }
+ }
+
+
+}
diff --git a/openmobileapi/src/org/simalliance/openmobileapi/service/CardException.java b/openmobileapi/src/org/simalliance/openmobileapi/service/CardException.java
new file mode 100644
index 0000000..112e108
--- /dev/null
+++ b/openmobileapi/src/org/simalliance/openmobileapi/service/CardException.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2011, 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 org.simalliance.openmobileapi.service;
+
+/**
+ * Generic exception of the smartcard system.
+ */
+public class CardException extends Exception {
+
+ private static final long serialVersionUID = -5298933800369298346L;
+
+ public CardException(String message) {
+ super(message);
+ }
+
+ public CardException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public CardException(Throwable cause) {
+ super(cause);
+ }
+
+}
diff --git a/openmobileapi/src/org/simalliance/openmobileapi/service/ISmartcardService.aidl b/openmobileapi/src/org/simalliance/openmobileapi/service/ISmartcardService.aidl
new file mode 100644
index 0000000..5a91b1d
--- /dev/null
+++ b/openmobileapi/src/org/simalliance/openmobileapi/service/ISmartcardService.aidl
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2011, 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 org.simalliance.openmobileapi.service;
+
+import org.simalliance.openmobileapi.service.ISmartcardServiceCallback;
+import org.simalliance.openmobileapi.service.ISmartcardServiceReader;
+import org.simalliance.openmobileapi.service.SmartcardError;
+
+/**
+ * Smartcard service interface.
+ */
+interface ISmartcardService {
+
+ /**
+ * Returns the friendly names of available smart card readers.
+ */
+ String[] getReaders(out SmartcardError error);
+
+ /**
+ * Returns Smartcard Service reader object to the given name.
+ */
+ ISmartcardServiceReader getReader(String reader, out SmartcardError error);
+
+ /**
+ * Checks if the application defined by the package name is allowed to receive
+ * NFC transaction events for the defined AID.
+ */
+ boolean[] isNFCEventAllowed(String reader, in byte[] aid, in String[] packageNames, ISmartcardServiceCallback callback, out SmartcardError error);
+
+}
diff --git a/openmobileapi/src/org/simalliance/openmobileapi/service/ISmartcardServiceCallback.aidl b/openmobileapi/src/org/simalliance/openmobileapi/service/ISmartcardServiceCallback.aidl
new file mode 100644
index 0000000..bb896ba
--- /dev/null
+++ b/openmobileapi/src/org/simalliance/openmobileapi/service/ISmartcardServiceCallback.aidl
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2011, 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 org.simalliance.openmobileapi.service;
+
+/**
+ * Callback interface used by ISmartcardService to check if clients are alive.
+ */
+oneway interface ISmartcardServiceCallback {
+}
diff --git a/openmobileapi/src/org/simalliance/openmobileapi/service/ISmartcardServiceChannel.aidl b/openmobileapi/src/org/simalliance/openmobileapi/service/ISmartcardServiceChannel.aidl
new file mode 100644
index 0000000..cdece89
--- /dev/null
+++ b/openmobileapi/src/org/simalliance/openmobileapi/service/ISmartcardServiceChannel.aidl
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2011, 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 org.simalliance.openmobileapi.service;
+
+import org.simalliance.openmobileapi.service.ISmartcardServiceSession;
+import org.simalliance.openmobileapi.service.SmartcardError;
+
+interface ISmartcardServiceChannel {
+
+ /**
+ * Closes the specified connection and frees internal resources.
+ * A logical channel will be closed.
+ */
+ void close(out SmartcardError error);
+
+ /**
+ * Tells if this channel is closed.
+ *
+ * @return <code>true</code> if the channel is closed, <code>false</code> otherwise.
+ */
+ boolean isClosed();
+
+ /**
+ * Returns a boolean telling if this channel is the basic channel.
+ *
+ * @return <code>true</code> if this channel is a basic channel. <code>false</code> if
+ * this channel is a logical channel.
+ */
+ boolean isBasicChannel();
+
+ /**
+ * Returns the data as received from the application select command inclusively the status word.
+ * The returned byte array contains the data bytes in the following order:
+ * [<first data byte>, ..., <last data byte>, <sw1>, <sw2>]
+ */
+ byte[] getSelectResponse();
+
+ /**
+ * Get the session that has opened this channel.
+ *
+ * @return the session object this channel is bound to.
+ */
+ ISmartcardServiceSession getSession();
+
+ /**
+ * Transmits the specified command APDU and returns the response APDU.
+ * MANAGE channel commands are not supported.
+ * Selection of applets is not supported in logical channels.
+ */
+ byte[] transmit(in byte[] command, out SmartcardError error);
+
+ /**
+ * Performs a selection of the next Applet on this channel that matches to the partial AID specified
+ * in the openBasicChannel(byte[] aid) or openLogicalChannel(byte[] aid) method.
+ * This mechanism can be used by a device application to iterate through all Applets
+ * matching to the same partial AID.
+ * If selectNext() returns true a new Applet was successfully selected on this channel.
+ * If no further Applet exists with matches to the partial AID this method returns false
+ * and the already selected Applet stays selected.
+ *
+ * @return <code>true</code> if new Applet was successfully selected.
+ <code>false</code> if no further Applet exists which matches the partial AID.
+ */
+ boolean selectNext(out SmartcardError error);
+}
\ No newline at end of file
diff --git a/openmobileapi/src/org/simalliance/openmobileapi/service/ISmartcardServiceReader.aidl b/openmobileapi/src/org/simalliance/openmobileapi/service/ISmartcardServiceReader.aidl
new file mode 100644
index 0000000..8f553de
--- /dev/null
+++ b/openmobileapi/src/org/simalliance/openmobileapi/service/ISmartcardServiceReader.aidl
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2011, 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 org.simalliance.openmobileapi.service;
+
+import org.simalliance.openmobileapi.service.ISmartcardServiceSession;
+import org.simalliance.openmobileapi.service.ISmartcardServiceCallback;
+import org.simalliance.openmobileapi.service.SmartcardError;
+
+interface ISmartcardServiceReader {
+ /**
+ * Return the user-friendly name of this reader.
+ * <ul>
+ * <li>If this reader is a SIM reader, then its name must start with the "SIM" prefix.</li>
+ * <li>If the reader is a SD or micro SD reader, then its name must start with the "SD" prefix</li>
+ * <li>If the reader is a embedded SE reader, then its name must start with the "eSE" prefix</li>
+ * <ul>
+ *
+ * @return name of this Reader
+ */
+ String getName(out SmartcardError error);
+
+ /**
+ * Returns true if a card is present in the specified reader.
+ * Returns false if a card is not present in the specified reader.
+ */
+ boolean isSecureElementPresent(out SmartcardError error);
+
+
+ /**
+ * Connects to a secure element in this reader. <br>
+ * This method prepares (initialises) the Secure Element for communication
+ * before the Session object is returned (e.g. powers the Secure Element by
+ * ICC ON if its not already on). There might be multiple sessions opened at
+ * the same time on the same reader. The system ensures the interleaving of
+ * APDUs between the respective sessions.
+ *
+ * @return a Session object to be used to create Channels.
+ */
+ ISmartcardServiceSession openSession(out SmartcardError error);
+
+ /**
+ * Close all the sessions opened on this reader. All the channels opened by
+ * all these sessions will be closed.
+ */
+ void closeSessions(out SmartcardError error);
+
+}
\ No newline at end of file
diff --git a/openmobileapi/src/org/simalliance/openmobileapi/service/ISmartcardServiceSession.aidl b/openmobileapi/src/org/simalliance/openmobileapi/service/ISmartcardServiceSession.aidl
new file mode 100644
index 0000000..583b4af
--- /dev/null
+++ b/openmobileapi/src/org/simalliance/openmobileapi/service/ISmartcardServiceSession.aidl
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2011, 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 org.simalliance.openmobileapi.service;
+
+import org.simalliance.openmobileapi.service.ISmartcardServiceChannel;
+import org.simalliance.openmobileapi.service.ISmartcardServiceReader;
+import org.simalliance.openmobileapi.service.ISmartcardServiceCallback;
+import org.simalliance.openmobileapi.service.SmartcardError;
+
+interface ISmartcardServiceSession {
+
+ /**
+ * Get the reader that provides this session.
+ *
+ * @return The Reader object.
+ */
+ ISmartcardServiceReader getReader();
+
+ /**
+ * Returns the ATR of the connected card or null if the ATR is not available.
+ */
+ byte[] getAtr();
+
+ /**
+ * Close the connection with the Secure Element. This will close any
+ * channels opened by this application with this Secure Element.
+ */
+ void close(out SmartcardError error);
+
+ /**
+ * Close any channel opened on this session.
+ */
+ void closeChannels(out SmartcardError error);
+
+
+ /**
+ * Tells if this session is closed.
+ *
+ * @return <code>true</code> if the session is closed, false otherwise.
+ */
+ boolean isClosed();
+
+ /**
+ * Opens a connection using the basic channel of the card in the
+ * specified reader and returns a channel handle.
+ * Logical channels cannot be opened with this connection.
+ * Use interface method openLogicalChannel() to open a logical channel.
+ */
+ ISmartcardServiceChannel openBasicChannel(ISmartcardServiceCallback callback, out SmartcardError error);
+
+ /**
+ * Opens a connection using the basic channel of the card in the
+ * specified reader and returns a channel handle. Selects the specified applet.
+ * Logical channels cannot be opened with this connection.
+ * Selection of other applets with this connection is not supported.
+ * Use interface method openLogicalChannel() to open a logical channel.
+ */
+ ISmartcardServiceChannel openBasicChannelAid(in byte[] aid, ISmartcardServiceCallback callback, out SmartcardError error);
+
+ /**
+ * Opens a connection using the next free logical channel of the card in the
+ * specified reader. Selects the specified applet.
+ * Selection of other applets with this connection is not supported.
+ */
+ ISmartcardServiceChannel openLogicalChannel(in byte[] aid, ISmartcardServiceCallback callback, out SmartcardError error);
+}
\ No newline at end of file
diff --git a/openmobileapi/src/org/simalliance/openmobileapi/service/SmartcardError.aidl b/openmobileapi/src/org/simalliance/openmobileapi/service/SmartcardError.aidl
new file mode 100644
index 0000000..3aed68b
--- /dev/null
+++ b/openmobileapi/src/org/simalliance/openmobileapi/service/SmartcardError.aidl
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2011, 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 org.simalliance.openmobileapi.service;
+
+parcelable SmartcardError;
diff --git a/openmobileapi/src/org/simalliance/openmobileapi/service/SmartcardError.java b/openmobileapi/src/org/simalliance/openmobileapi/service/SmartcardError.java
new file mode 100644
index 0000000..5dc61c4
--- /dev/null
+++ b/openmobileapi/src/org/simalliance/openmobileapi/service/SmartcardError.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2011, 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 org.simalliance.openmobileapi.service;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.reflect.Constructor;
+
+/**
+ * Smartcard service parameter class used to marshal exception information from
+ * the smartcard service to clients.
+ */
+public class SmartcardError implements Parcelable {
+ private String mClazz;
+
+ private String mMessage;
+
+ public static final Parcelable.Creator<SmartcardError> CREATOR = new Parcelable.Creator<SmartcardError>() {
+ public SmartcardError createFromParcel(Parcel in) {
+ return new SmartcardError(in);
+ }
+
+ public SmartcardError[] newArray(int size) {
+ return new SmartcardError[size];
+ }
+ };
+
+ /**
+ * Creates an empty smartcard error container.
+ */
+ public SmartcardError() {
+ this.mClazz = "";
+ this.mMessage = "";
+ }
+
+ private SmartcardError(Parcel in) {
+ mClazz = in.readString();
+ mMessage = in.readString();
+ }
+
+ /**
+ * Creates a smartcard error which creates the specified exception.
+ *
+ * @param clazz the exception class. <code>null</code> to reset the error
+ * information.
+ * @param message the exception message.
+ */
+ public SmartcardError(String clazz, String message) {
+ this.mClazz = (clazz == null) ? "" : clazz;
+ this.mMessage = (message == null) ? "" : message;
+ }
+
+ /**
+ * Clears the error.
+ */
+ public void clear() {
+ this.mClazz = "";
+ this.mMessage = "";
+ }
+
+ /**
+ * Creates the encoded exception. Returns <code>null</code> if empty. If the
+ * encoded exception is neither a RuntimeException nor a CardException, it
+ * is encapsulated in a RuntimeException.
+ *
+ * @return the encoded exception or <code>null</code> if empty.
+ */
+ @SuppressWarnings({ "rawtypes" })
+ public Exception createException() {
+ try {
+ if (mClazz.length() == 0) {
+ return null;
+ }
+ if (mMessage.length() == 0) {
+ return (Exception) Class.forName(mClazz).newInstance();
+ }
+ Constructor constructor = Class.forName(mClazz).getConstructor(String.class);
+ return (Exception) constructor.newInstance(mMessage);
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void readFromParcel(Parcel in) {
+ mClazz = in.readString();
+ mMessage = in.readString();
+ }
+
+ /**
+ * Sets the error information.
+ *
+ * @param clazz the exception class. <code>null</code> to reset the error
+ * information.
+ * @param message the exception message.
+ */
+ @SuppressWarnings({ "rawtypes" })
+ public void setError(Class clazz, String message) {
+ this.mClazz = (clazz == null) ? "" : clazz.getName();
+ this.mMessage = (message == null) ? "" : message;
+ }
+
+ /**
+ * Throws the encoded exception. Does not throw an exception if the
+ * container is empty. If the encoded exception is neither a
+ * RuntimeException nor a CardException, it is encapsulated in a
+ * RuntimeException.
+ *
+ * @throws RuntimeException if the encoded exception is not a CardException.
+ * @throws CardException if a CardException is encoded.
+ */
+ public void throwException() throws CardException {
+ Exception e = createException();
+ if (e == null) {
+ return;
+ }
+ if (e instanceof CardException) {
+ throw (CardException) e;
+ }
+ if (e instanceof RuntimeException) {
+ throw (RuntimeException) e;
+ }
+ throw new RuntimeException(e);
+ }
+
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeString(mClazz);
+ out.writeString(mMessage);
+ }
+}
diff --git a/src/org/simalliance/openmobileapi/service/AddonTerminal.java b/src/org/simalliance/openmobileapi/service/AddonTerminal.java
new file mode 100644
index 0000000..0abe52c
--- /dev/null
+++ b/src/org/simalliance/openmobileapi/service/AddonTerminal.java
@@ -0,0 +1,247 @@
+/*
+ * Copyright (C) 2011, 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 org.simalliance.openmobileapi.service;
+
+import android.content.Context;
+import android.content.pm.PackageInfo;
+
+import java.lang.reflect.Method;
+import java.util.LinkedList;
+import java.util.List;
+
+public class AddonTerminal extends Terminal {
+
+ private Object mInstance = null;
+
+ private Method mIsCardPresent = null;
+
+ private Method mInternalConnect = null;
+
+ private Method mInternalDisconnect = null;
+
+ private Method mInternalTransmit = null;
+
+ private Method mInternalOpenLogicalChannel = null;
+
+ private Method mInternalOpenLogicalChannelAID = null;
+
+ private Method mInternalCloseLogicalChannel = null;
+
+ private Method mGetName = null;
+
+ private Method mGetAtr = null;
+
+ private Method mGetSelectResponse = null;
+
+ public static String[] getPackageNames(Context context) {
+ List<String> packageNameList = new LinkedList<String>();
+ List<PackageInfo> pis = context.getPackageManager().getInstalledPackages(0);
+ for (PackageInfo p : pis) {
+ if (p.packageName.startsWith("org.simalliance.openmobileapi.service.terminals.")
+ || p.packageName.startsWith("org.simalliance.openmobileapi.cts")) {
+ packageNameList.add(p.packageName);
+ }
+ }
+ String[] rstrings = new String[packageNameList.size()];
+ packageNameList.toArray(rstrings);
+ return rstrings;
+ }
+
+ public AddonTerminal(Context context, String packageName, String className) {
+ super("Addon", context);
+
+ try {
+ Context ctx = context.createPackageContext(packageName, Context.CONTEXT_IGNORE_SECURITY
+ | Context.CONTEXT_INCLUDE_CODE);
+ ClassLoader cl = ctx.getClassLoader();
+ Class<?> cls = cl.loadClass(className);
+ mInstance = cls.getConstructor(new Class[] {
+ Context.class
+ }).newInstance(new Object[] {
+ context
+ });
+ if (mInstance != null) {
+ mGetAtr = mInstance.getClass().getDeclaredMethod("getAtr", (Class<?>[]) null);
+ mGetName = mInstance.getClass().getDeclaredMethod("getName", (Class<?>[]) null);
+ mIsCardPresent = mInstance.getClass().getDeclaredMethod("isCardPresent",
+ (Class<?>[]) null);
+ mInternalConnect = mInstance.getClass().getDeclaredMethod("internalConnect",
+ (Class<?>[]) null);
+ mInternalDisconnect = mInstance.getClass().getDeclaredMethod("internalDisconnect",
+ (Class<?>[]) null);
+ mInternalTransmit = mInstance.getClass().getDeclaredMethod("internalTransmit",
+ new Class[] {
+ byte[].class
+ });
+ mInternalOpenLogicalChannel = mInstance.getClass().getDeclaredMethod(
+ "internalOpenLogicalChannel", (Class<?>[]) null);
+ mInternalOpenLogicalChannelAID = mInstance.getClass().getDeclaredMethod(
+ "internalOpenLogicalChannel", new Class[] {
+ byte[].class
+ });
+ mInternalCloseLogicalChannel = mInstance.getClass().getDeclaredMethod(
+ "internalCloseLogicalChannel", new Class[] {
+ int.class
+ });
+ mGetSelectResponse = mInstance.getClass().getDeclaredMethod("getSelectResponse",
+ (Class<?>[]) null);
+ }
+ } catch (Exception e) {
+ throw new IllegalStateException("plugin internal error: " + e);
+ }
+ }
+
+ /**
+ * Returns the ATR of the connected card or null if the ATR is not
+ * available.
+ *
+ * @return the ATR of the connected card or null if the ATR is not
+ * available.
+ */
+ public byte[] getAtr() {
+ if (mGetAtr == null) {
+ throw new IllegalStateException("plugin error: Function String getAtr() not found");
+ }
+ try {
+ byte[] resp = (byte[]) mGetAtr.invoke(mInstance, (Object[]) null);
+ return resp;
+ } catch (Exception e) {
+ throw new IllegalStateException("plugin internal error: getAtr() execution: "
+ + e.getCause());
+ }
+ }
+
+ public String getName() {
+ if (mGetName == null) {
+ throw new IllegalStateException("plugin error: Function String getName() not found");
+ }
+ try {
+ String s = (String) mGetName.invoke(mInstance, (Object[]) null);
+ return s;
+ } catch (Exception e) {
+ throw new IllegalStateException("plugin internal error: getName() execution: "
+ + e.getCause());
+ }
+ }
+
+ public boolean isCardPresent() throws CardException {
+ if (mIsCardPresent == null) {
+ throw new IllegalStateException(
+ "plugin error: Function String isCardPresent() not found");
+ }
+ try {
+ Boolean v = (Boolean) mIsCardPresent.invoke(mInstance, (Object[]) null);
+ return v.booleanValue();
+ } catch (Exception e) {
+ throw new CardException("plugin internal error: isCardPresent() execution: "
+ + e.getCause());
+ }
+ }
+
+ protected void internalConnect() throws CardException {
+ if (mInternalConnect == null) {
+ throw new IllegalStateException(
+ "plugin error: Function String internalConnect() not found");
+ }
+ try {
+ mInternalConnect.invoke(mInstance, (Object[]) null);
+ mIsConnected = true;
+ } catch (Exception e) {
+ throw new CardException("plugin internal error: internalConnect() execution: "
+ + e.getCause());
+ }
+ }
+
+ protected void internalDisconnect() throws CardException {
+ if (mInternalDisconnect == null) {
+ throw new IllegalStateException(
+ "plugin error: Function String internalDisconnect() not found");
+ }
+ try {
+ mInternalDisconnect.invoke(mInstance, (Object[]) null);
+ mIsConnected = false;
+ } catch (Exception e) {
+ throw new CardException("plugin internal error: internalDisconnect() execution");
+ }
+ }
+
+ protected byte[] internalTransmit(byte[] command) throws CardException {
+ if (mInternalTransmit == null) {
+ throw new IllegalStateException(
+ "plugin error: Function String internalTransmit() not found");
+ }
+ try {
+ byte[] resp = (byte[]) mInternalTransmit.invoke(mInstance, new Object[] {
+ command
+ });
+ return resp;
+ } catch (Exception e) {
+ throw new CardException("plugin internal error: internalTransmit() execution: "
+ + e.getCause());
+ }
+ }
+
+ protected int internalOpenLogicalChannel() throws Exception {
+ if (mInternalOpenLogicalChannel == null) {
+ throw new IllegalStateException(
+ "plugin error: Function String internalOpenLogicalChannel() not found");
+ }
+ try {
+ Integer channel = (Integer) mInternalOpenLogicalChannel.invoke(mInstance,
+ (Object[]) null);
+ return channel.intValue();
+ } catch (Exception e) {
+ throw (Exception) e.getCause();
+ }
+ }
+
+ protected int internalOpenLogicalChannel(byte[] aid) throws Exception {
+ if (mInternalOpenLogicalChannelAID == null) {
+ throw new IllegalStateException(
+ "plugin error: Function internalOpenLogicalChannelAID() not found");
+ }
+ try {
+ Integer channel = (Integer) mInternalOpenLogicalChannelAID.invoke(mInstance,
+ new Object[] {
+ aid
+ });
+ mSelectResponse = (byte[]) mGetSelectResponse.invoke(mInstance, (Object[]) null);
+ return channel.intValue();
+ } catch (Exception e) {
+ throw (Exception) e.getCause();
+ }
+ }
+
+ protected void internalCloseLogicalChannel(int channelNumber) throws CardException {
+ if (mInternalCloseLogicalChannel == null) {
+ throw new IllegalStateException(
+ "plugin error: Function internalCloseLogicalChannel not found");
+ }
+ try {
+ mInternalCloseLogicalChannel.invoke(mInstance, new Object[] {
+ channelNumber
+ });
+ } catch (Exception e) {
+ throw new CardException(
+ "plugin internal error: internalOpenLogicalChannel() execution: "
+ + e.getCause());
+ }
+ }
+}
diff --git a/src/org/simalliance/openmobileapi/service/CardException.java b/src/org/simalliance/openmobileapi/service/CardException.java
new file mode 100644
index 0000000..112e108
--- /dev/null
+++ b/src/org/simalliance/openmobileapi/service/CardException.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2011, 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 org.simalliance.openmobileapi.service;
+
+/**
+ * Generic exception of the smartcard system.
+ */
+public class CardException extends Exception {
+
+ private static final long serialVersionUID = -5298933800369298346L;
+
+ public CardException(String message) {
+ super(message);
+ }
+
+ public CardException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public CardException(Throwable cause) {
+ super(cause);
+ }
+
+}
diff --git a/src/org/simalliance/openmobileapi/service/Channel.java b/src/org/simalliance/openmobileapi/service/Channel.java
new file mode 100644
index 0000000..5396b0e
--- /dev/null
+++ b/src/org/simalliance/openmobileapi/service/Channel.java
@@ -0,0 +1,469 @@
+/*
+ * Copyright (C) 2011, 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 org.simalliance.openmobileapi.service;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.RemoteException;
+import org.simalliance.openmobileapi.service.ISmartcardServiceCallback;
+import org.simalliance.openmobileapi.service.SmartcardService.SmartcardServiceSession;
+
+import android.util.Log;
+
+
+import java.security.AccessControlException;
+import java.util.NoSuchElementException;
+
+import org.simalliance.openmobileapi.service.security.AccessControlEnforcer;
+import org.simalliance.openmobileapi.service.security.ChannelAccess;
+
+
+/**
+ * Smartcard service base class for channel resources.
+ */
+class Channel implements IChannel, IBinder.DeathRecipient {
+
+ protected final int mChannelNumber;
+
+ protected boolean mIsClosed;
+
+ protected long mHandle;
+
+ protected ISmartcardServiceSession mSession;
+ protected Terminal mTerminal;
+
+ protected byte[] mSelectResponse;
+
+ protected final IBinder mBinder;
+
+
+ protected ChannelAccess mChannelAccess = null;
+ protected int mCallingPid = 0;
+
+
+ protected ISmartcardServiceCallback mCallback;
+
+ protected boolean mHasSelectedAid = false;
+ protected byte[] mAid = null;
+
+ Channel(SmartcardServiceSession session, Terminal terminal, int channelNumber, ISmartcardServiceCallback callback) {
+ this.mChannelNumber = channelNumber;
+ this.mSession = session;
+ this.mTerminal = terminal;
+ this.mCallback = callback;
+ this.mBinder = callback.asBinder();
+ this.mSelectResponse = terminal.getSelectResponse();
+ this.mIsClosed = false;
+ try {
+ mBinder.linkToDeath(this, 0);
+ } catch (RemoteException e) {
+ Log.e(SmartcardService._TAG, "Failed to register client callback");
+ }
+ }
+
+ public void binderDied() {
+ // Close this channel if the client died.
+ try {
+ Log.e(SmartcardService._TAG, Thread.currentThread().getName()
+ + " Client " + mBinder.toString() + " died");
+ close();
+ } catch (Exception ignore) {
+ }
+ }
+
+ public synchronized void close() throws CardException {
+
+
+ Terminal terminal = getTerminal();
+ if( terminal == null ){
+ throw new IllegalStateException( "channel is not attached to a terminal");
+ }
+
+ if (isBasicChannel() && hasSelectedAid()) {
+ try {
+ Log.v(SmartcardService._TAG, "Close basic channel - Select with out AID ...");
+ terminal.select();
+ } catch (NoSuchElementException exp) {
+//#if defined(sec)
+ // Selection of the default application fails
+ try {
+ Log.v(SmartcardService._TAG, "Close basic channel - Exception : " + exp.getLocalizedMessage());
+ AccessControlEnforcer access = terminal.getAccessControlEnforcer();
+ if( access != null ){
+ terminal.select(AccessControlEnforcer.getDefaultAccessControlAid());
+ }
+ } catch (NoSuchElementException exp2) {
+ // Access Control Applet not available => Don't care
+ }
+//#endif
+ }
+ }
+
+ try {
+ terminal.closeChannel(this);
+ this.mIsClosed = true;
+ } finally {
+ mBinder.unlinkToDeath(this, 0);
+ }
+ }
+
+ public int getChannelNumber() {
+ return mChannelNumber;
+ }
+
+ /**
+ * Returns if this channel is a basic channel
+ *
+ * @return true if this channel is a basic channel
+ */
+ public boolean isBasicChannel() {
+ return (mChannelNumber == 0) ? true : false;
+ }
+
+ public ISmartcardServiceCallback getCallback() {
+ return mCallback;
+ }
+
+ /**
+ * Returns the handle assigned to this channel.
+ *
+ * @return the handle assigned to this channel.
+ */
+ long getHandle() {
+ return mHandle;
+ }
+
+ /**
+ * Returns the associated terminal.
+ *
+ * @return the associated terminal.
+ */
+ public Terminal getTerminal() {
+ return mTerminal;
+ }
+
+ /**
+ * Assigns the channel handle.
+ *
+ * @param handle the channel handle to be assigned.
+ */
+ void setHandle(long handle) {
+ this.mHandle = handle;
+ }
+
+ public byte[] transmit(byte[] command) throws CardException {
+
+ if( mChannelAccess == null ){
+ throw new AccessControlException( " Channel access not set.");
+ }
+ if (mChannelAccess.getCallingPid() != mCallingPid) {
+
+
+
+ throw new AccessControlException(" Wrong Caller PID. ");
+ }
+
+
+
+ checkCommand(command);
+
+
+ if (command.length < 4) {
+ throw new IllegalArgumentException(
+ " command must not be smaller than 4 bytes");
+ }
+ if (((command[0] & (byte) 0x80) == 0)
+ && ((byte) (command[0] & (byte) 0x60) != (byte) 0x20)) {
+ // ISO command
+ if (command[1] == (byte) 0x70) {
+ throw new IllegalArgumentException(
+ "MANAGE CHANNEL command not allowed");
+ }
+ if ((command[1] == (byte) 0xA4) && (command[2] == (byte) 0x04)) {
+ throw new IllegalArgumentException("SELECT command not allowed");
+ }
+
+ } else {
+ // GlobalPlatform command
+ }
+
+ // set channel number bits
+ command[0] = setChannelToClassByte(command[0], mChannelNumber);
+
+ byte[] rsp = getTerminal().transmit(command, 2, 0, 0, null);
+
+ return rsp;
+ }
+
+ public boolean selectNext() throws CardException {
+
+ if( mChannelAccess == null ){
+ throw new AccessControlException( " Channel access not set.");
+ }
+ if (mChannelAccess.getCallingPid() != mCallingPid) {
+
+
+
+ throw new AccessControlException(" Wrong Caller PID. ");
+ }
+
+
+ if( mAid == null || mAid.length == 0){
+ throw new CardException(" no aid given");
+ }
+
+ mSelectResponse = null;
+ byte[] selectCommand = new byte[5 + mAid.length];
+ selectCommand[0] = 0x00;
+ selectCommand[1] = (byte) 0xA4;
+ selectCommand[2] = 0x04;
+ selectCommand[3] = 0x02; // next occurrence
+ selectCommand[4] = (byte)mAid.length;
+ System.arraycopy(mAid, 0, selectCommand, 5, mAid.length);
+
+ // set channel number bits
+ selectCommand[0] = setChannelToClassByte(selectCommand[0], mChannelNumber);
+
+ mSelectResponse = getTerminal().transmit(selectCommand, 2, 0, 0, "SELECT NEXT");
+
+
+
+ int sw1 = mSelectResponse[mSelectResponse.length - 2] & 0xFF;
+ int sw2 = mSelectResponse[mSelectResponse.length - 1] & 0xFF;
+ int sw = (sw1 << 8) | sw2;
+
+ if( (sw & 0xF000) == 0x9000 ){
+ return true;
+ } else if( sw == 0x6a82 ) {
+ return false;
+ } else {
+ throw new CardException(" invalid action" );
+ }
+ }
+
+
+ /**
+ * Returns a copy of the given CLA byte where the channel number bits are
+ * set as specified by the given channel number
+ *
+ * See GlobalPlatform Card Specification 2.2.0.7: 11.1.4 Class Byte Coding
+ *
+ * @param cla
+ * the CLA byte. Won't be modified
+ * @param channelNumber
+ * within [0..3] (for first interindustry class byte coding) or
+ * [4..19] (for further interindustry class byte coding)
+ * @return the CLA byte with set channel number bits. The seventh bit
+ * indicating the used coding (first/further interindustry class
+ * byte coding) might be modified
+ */
+ private byte setChannelToClassByte(byte cla, int channelNumber) {
+ if (channelNumber < 4) {
+ // b7 = 0 indicates the first interindustry class byte coding
+ cla = (byte) ((cla & 0xBC) | channelNumber);
+ } else if (channelNumber < 20) {
+ // b7 = 1 indicates the further interindustry class byte coding
+ boolean isSM = (cla & 0x0C) != 0;
+ cla = (byte) ((cla & 0xB0) | 0x40 | (channelNumber - 4));
+ if (isSM) cla |= 0x20;
+ } else {
+ throw new IllegalArgumentException(
+ "Channel number must be within [0..19]");
+ }
+ return cla;
+ }
+
+
+ public void setChannelAccess(ChannelAccess channelAccess) {
+ this.mChannelAccess = channelAccess;
+ }
+
+ public ChannelAccess getChannelAccess(){
+ return this.mChannelAccess;
+ }
+
+ public void setCallingPid( int pid) {
+
+
+
+ mCallingPid = pid;
+ }
+
+ private void checkCommand( byte[] command ) {
+ if( getTerminal().getAccessControlEnforcer() != null ) {
+ // check command if it complies to the access rules.
+ // if not an exception is thrown
+ getTerminal().getAccessControlEnforcer().checkCommand(this, command);
+ } else {
+ throw new AccessControlException( "FATAL: Access Controller not set for Terminal: " + getTerminal().getName());
+ }
+ }
+
+
+ /**
+ * true if aid during open xxx channel coud be selected.
+ * false if aid could not be or was not selected.
+ * @return boolean.
+ */
+ public boolean hasSelectedAid() {
+ return mHasSelectedAid;
+ }
+
+ /**
+ * set selected aid flag and aid (may be null)
+ */
+ public void hasSelectedAid(boolean has, byte[] aid) {
+ mHasSelectedAid = has;
+ mAid = aid;
+ }
+
+ /**
+ * Returns the data as received from the application select command inclusively the status word.
+ * The returned byte array contains the data bytes in the following order:
+ * [<first data byte>, ..., <last data byte>, <sw1>, <sw2>]
+ * @return The data as returned by the application select command inclusively the status word.
+ * @return Only the status word if the application select command has no returned data.
+ * @return null if an application select command has not been performed or the selection response can not
+ * be retrieved by the reader implementation.
+ */
+ public byte[] getSelectResponse()
+ {
+ return mSelectResponse;
+ }
+
+ boolean isClosed(){
+
+ return mIsClosed;
+ }
+
+ void setClosed(){
+
+ mIsClosed = true;
+ }
+
+ /**
+ * Implementation of the SmartcardService Channel interface according to OMAPI.
+ *
+ *
+ */
+ final class SmartcardServiceChannel extends ISmartcardServiceChannel.Stub {
+
+ private final SmartcardServiceSession mSession;
+ public SmartcardServiceChannel( SmartcardServiceSession session ){
+ mSession = session;
+ }
+
+ @Override
+ public void close(SmartcardError error) throws RemoteException {
+
+ SmartcardService.clearError(error);
+ try {
+ Channel.this.close();
+ } catch (Exception e) {
+ SmartcardService.setError(error, e);
+ } finally {
+ if( mSession != null )
+ mSession.removeChannel(Channel.this);
+ }
+ }
+
+ public void setClosed(){
+ Channel.this.setClosed();
+ }
+
+ @Override
+ public boolean isClosed() throws RemoteException {
+ return Channel.this.isClosed();
+ }
+
+ @Override
+ public boolean isBasicChannel()
+ throws RemoteException {
+ return Channel.this.isBasicChannel();
+ }
+
+ @Override
+ public byte[] getSelectResponse()
+ throws RemoteException {
+ return Channel.this.getSelectResponse();
+ }
+
+ @Override
+ public ISmartcardServiceSession getSession()
+ throws RemoteException {
+ return Channel.this.mSession;
+ }
+
+ @Override
+ public byte[] transmit(byte[] command, SmartcardError error)
+ throws RemoteException {
+ SmartcardService.clearError(error);
+
+ try {
+ if (isClosed()) {
+ SmartcardService.setError(error, IllegalStateException.class, "channel is closed");
+ return null;
+ }
+
+ if (command == null) {
+ SmartcardService.setError(error, NullPointerException.class, "command must not be null");
+ return null;
+ }
+ if (command.length < 4) {
+ SmartcardService.setError(error, IllegalArgumentException.class,
+ "command must have at least 4 bytes");
+ return null;
+ }
+
+ Channel.this.setCallingPid(Binder.getCallingPid());
+
+
+ byte[] response = Channel.this.transmit(command);
+
+ return response;
+ } catch (Exception e) {
+ Log.v(SmartcardService._TAG, "transmit Exception: " + e.getMessage() + " (Command: " + Util.bytesToString(command) + ")");
+ SmartcardService.setError(error, e);
+ return null;
+ }
+ }
+
+ @Override
+ public boolean selectNext(SmartcardError error)
+ throws RemoteException {
+ SmartcardService.clearError(error);
+
+ try {
+ if (isClosed()) {
+ SmartcardService.setError(error, IllegalStateException.class, "channel is closed");
+ return false;
+ }
+
+
+ Channel.this.setCallingPid(Binder.getCallingPid());
+
+ boolean response = Channel.this.selectNext();
+ return response;
+ } catch (Exception e) {
+ SmartcardService.setError(error, e);
+ return false;
+ }
+ }
+ };
+}
diff --git a/src/org/simalliance/openmobileapi/service/IChannel.java b/src/org/simalliance/openmobileapi/service/IChannel.java
new file mode 100644
index 0000000..06244fd
--- /dev/null
+++ b/src/org/simalliance/openmobileapi/service/IChannel.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2011, 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 org.simalliance.openmobileapi.service;
+
+
+import org.simalliance.openmobileapi.service.security.ChannelAccess;
+
+import org.simalliance.openmobileapi.service.ISmartcardServiceCallback;
+
+/**
+ * Smartcard service interface for channel resources.
+ */
+public interface IChannel {
+
+ /**
+ * Closes this channel.
+ *
+ * @throws CardException if closing the channel failed.
+ */
+ void close() throws CardException;
+
+ /**
+ * Returns the channel number according to ISO 7816-4.
+ *
+ * @return the channel number according to ISO 7816-4.
+ */
+ int getChannelNumber();
+
+ /**
+ * Returns if this channel is a basic channel
+ *
+ * @return true if this channel is a basic channel
+ */
+ boolean isBasicChannel();
+
+ /**
+ * Returns the associated terminal.
+ *
+ * @return the associated terminal.
+ */
+ ITerminal getTerminal();
+
+ /**
+ * Transmits the specified command APDU and returns the response APDU.
+ * MANAGE channel commands are not allowed. Applet selection commands are
+ * not allowed if this is a logical channel.
+ *
+ * @param command the command APDU to be transmitted.
+ * @return the response APDU.
+ * @throws CardException if command transmission failed or the command is
+ * not allowed.
+ */
+ byte[] transmit(byte[] command) throws CardException;
+
+
+ /**
+ * @param channelAccess
+ */
+ void setChannelAccess(ChannelAccess channelAccess);
+
+ /**
+ * @return channelAccess
+ */
+ ChannelAccess getChannelAccess();
+
+ /**
+ * Sets calling pid for validation in checkCommand.
+ * @param pid calling pid of the device application
+ */
+ void setCallingPid( int pid );
+
+
+ /**
+ * @return
+ */
+ ISmartcardServiceCallback getCallback();
+
+ /**
+ * true if aid during open xxx channel coud be selected.
+ * false if aid could not be or was not selected.
+ * @return
+ */
+ boolean hasSelectedAid();
+
+ /**
+ * set selected aid flag and aid (may be null)
+ */
+ void hasSelectedAid(boolean has, byte[] aid);
+
+ /**
+ * Returns the data as received from the application select command inclusively the status word.
+ * The returned byte array contains the data bytes in the following order:
+ * [<first data byte>, ..., <last data byte>, <sw1>, <sw2>]
+ * @return The data as returned by the application select command inclusively the status word.
+ * @return Only the status word if the application select command has no returned data.
+ * @return null if an application select command has not been performed or the selection response can not
+ * be retrieved by the reader implementation.
+ */
+ public byte[] getSelectResponse();
+}
diff --git a/src/org/simalliance/openmobileapi/service/ITerminal.java b/src/org/simalliance/openmobileapi/service/ITerminal.java
new file mode 100644
index 0000000..41ab5d0
--- /dev/null
+++ b/src/org/simalliance/openmobileapi/service/ITerminal.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2011, 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 org.simalliance.openmobileapi.service;
+
+import java.io.PrintWriter;
+
+import org.simalliance.openmobileapi.service.ISmartcardServiceCallback;
+
+import org.simalliance.openmobileapi.service.SmartcardService.SmartcardServiceSession;
+import org.simalliance.openmobileapi.service.security.AccessControlEnforcer;
+import org.simalliance.openmobileapi.service.security.ChannelAccess;
+import android.content.pm.PackageManager;
+
+
+/**
+ * Smartcard service interface for terminal resources.
+ */
+public interface ITerminal {
+
+ /**
+ * Closes all open channels of this terminal.
+ */
+ void closeChannels();
+
+ /**
+ * Returns the channel for the specified handle or <code>null</code> if this
+ * handle is not registered.
+ *
+ * @param hChannel the channel handle.
+ * @return the channel for the specified handle or <code>null</code> if this
+ * handle is not registered.
+ */
+ IChannel getChannel(long hChannel);
+
+ /**
+ * Returns the reader name.
+ *
+ * @return the reader name.
+ */
+ String getName();
+
+ /**
+ * Sends a select command on the basic channel. With this command the
+ * default application will be selected on the card. (e.g. CardManager)
+ *
+ * @throw NoSuchElementException if the default applet couldn't be found or
+ * selected
+ */
+ public void select();
+
+ /**
+ * Sends a select command on the basic channel.
+ *
+ * @param aid the aid which should be selected
+ * @throw NoSuchElementException if the corresponding applet couldn't be
+ * found
+ */
+ public void select(byte[] aid);
+
+ /**
+ * Opens the basic channel to the card.
+ *
+ * @param callback the callback used to react on the death of the client.
+ * @return a handle for the basic channel.
+ * @throws CardException if opening the basic channel failed or the basic
+ * channel is in use.
+ */
+ Channel openBasicChannel(SmartcardServiceSession session, ISmartcardServiceCallback callback) throws CardException;
+
+ /**
+ * Opens the basic channel to the card.
+ *
+ * @param aid the AID of the applet to be selected.
+ * @param callback the callback used to react on the death of the client.
+ * @return a handle for the basic channel.
+ * @throws CardException if opening the basic channel failed or the basic
+ * channel is in use.
+ */
+ Channel openBasicChannel(SmartcardServiceSession session, byte[] aid, ISmartcardServiceCallback callback) throws Exception;
+
+ /**
+ * Opens a logical channel to the card.
+ *
+ * @param callback the callback used to react on the death of the client.
+ * @return a handle for the logical channel.
+ * @throws CardException if opening the logical channel failed.
+ */
+ Channel openLogicalChannel(SmartcardServiceSession session, ISmartcardServiceCallback callback) throws Exception;
+
+ /**
+ * Opens a logical channel to the card.
+ *
+ * @param aid the AID of the applet to be selected.
+ * @param callback the callback used to react on the death of the client.
+ * @return a handle for the logical channel.
+ * @throws CardException if opening the logical channel failed.
+ */
+ Channel openLogicalChannel(SmartcardServiceSession session, byte[] aid, ISmartcardServiceCallback callback) throws Exception;
+
+ /**
+ * Returns <code>true</code> if a card is present; <code>false</code>
+ * otherwise.
+ *
+ * @return <code>true</code> if a card is present; <code>false</code>
+ * otherwise.
+ * @throws CardException if card presence information is not available.
+ */
+ boolean isCardPresent() throws CardException;
+
+ /**
+ * Returns <code>true</code> if terminal is connected <code>false</code>
+ * otherwise.
+ *
+ * @return <code>true</code> if at least one terminal is connected.
+ */
+ public boolean isConnected();
+
+ /**
+ * Returns the ATR of the connected card or null if the ATR is not
+ * available.
+ *
+ * @return the ATR of the connected card or null if the ATR is not
+ * available.
+ */
+ public byte[] getAtr();
+
+ /**
+ * Returns the data as received from the application select command inclusively the status word.
+ * The returned byte array contains the data bytes in the following order:
+ * [<first data byte>, ..., <last data byte>, <sw1>, <sw2>]
+ * @return The data as returned by the application select command inclusively the status word.
+ * @return Only the status word if the application select command has no returned data.
+ * @return null if an application select command has not been performed or the selection response can not
+ * be retrieved by the reader implementation.
+ */
+ public byte[] getSelectResponse();
+
+ /**
+ * Exchanges APDU (SELECT, READ/WRITE) to the
+ * given EF by File ID and file path via iccIO.
+ *
+ * The given command is checked and might be rejected.
+ *
+ * @param fileID
+ * @param filePath
+ * @param cmd
+ * @return
+ */
+ public byte[] simIOExchange(int fileID, String filePath, byte[] cmd) throws Exception;
+
+
+
+ /**
+ * Set ups the Channel Access object for access control
+ * from the cached access rules
+ * for the given packageNames and the AID of the applet to be accessed.
+ *
+ * @return ChannelAccess object containing the access flags/filter.
+ */
+ ChannelAccess setUpChannelAccess( PackageManager packageManager, byte[] aid, String packageName, ISmartcardServiceCallback callback);
+
+ /**
+ * Set up the correct access control hander ARA (or ARF)
+ * and if indicated loads all accesses rules for the terminal.
+ * @param boolean flag if Access Rules should be loaded.
+ * @return true if rules have been successfully loaded
+ */
+ boolean initializeAccessControl( boolean loadAtStartup, ISmartcardServiceCallback callback);
+
+ /**
+ * Reset the access control: should be called when the card is changed.
+ */
+ void resetAccessControl();
+
+
+ AccessControlEnforcer getAccessControlEnforcer();
+
+
+ /**
+ * Dump the terminal state (for debugging purpose and crash tools)
+ * @param writer uses to print the dump
+ * @param prefix to be added before any new line (mainly used for indentation)
+ */
+ public void dump(PrintWriter writer, String prefix);
+}
diff --git a/src/org/simalliance/openmobileapi/service/SmartcardError.java b/src/org/simalliance/openmobileapi/service/SmartcardError.java
new file mode 100644
index 0000000..5dc61c4
--- /dev/null
+++ b/src/org/simalliance/openmobileapi/service/SmartcardError.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2011, 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 org.simalliance.openmobileapi.service;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.reflect.Constructor;
+
+/**
+ * Smartcard service parameter class used to marshal exception information from
+ * the smartcard service to clients.
+ */
+public class SmartcardError implements Parcelable {
+ private String mClazz;
+
+ private String mMessage;
+
+ public static final Parcelable.Creator<SmartcardError> CREATOR = new Parcelable.Creator<SmartcardError>() {
+ public SmartcardError createFromParcel(Parcel in) {
+ return new SmartcardError(in);
+ }
+
+ public SmartcardError[] newArray(int size) {
+ return new SmartcardError[size];
+ }
+ };
+
+ /**
+ * Creates an empty smartcard error container.
+ */
+ public SmartcardError() {
+ this.mClazz = "";
+ this.mMessage = "";
+ }
+
+ private SmartcardError(Parcel in) {
+ mClazz = in.readString();
+ mMessage = in.readString();
+ }
+
+ /**
+ * Creates a smartcard error which creates the specified exception.
+ *
+ * @param clazz the exception class. <code>null</code> to reset the error
+ * information.
+ * @param message the exception message.
+ */
+ public SmartcardError(String clazz, String message) {
+ this.mClazz = (clazz == null) ? "" : clazz;
+ this.mMessage = (message == null) ? "" : message;
+ }
+
+ /**
+ * Clears the error.
+ */
+ public void clear() {
+ this.mClazz = "";
+ this.mMessage = "";
+ }
+
+ /**
+ * Creates the encoded exception. Returns <code>null</code> if empty. If the
+ * encoded exception is neither a RuntimeException nor a CardException, it
+ * is encapsulated in a RuntimeException.
+ *
+ * @return the encoded exception or <code>null</code> if empty.
+ */
+ @SuppressWarnings({ "rawtypes" })
+ public Exception createException() {
+ try {
+ if (mClazz.length() == 0) {
+ return null;
+ }
+ if (mMessage.length() == 0) {
+ return (Exception) Class.forName(mClazz).newInstance();
+ }
+ Constructor constructor = Class.forName(mClazz).getConstructor(String.class);
+ return (Exception) constructor.newInstance(mMessage);
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void readFromParcel(Parcel in) {
+ mClazz = in.readString();
+ mMessage = in.readString();
+ }
+
+ /**
+ * Sets the error information.
+ *
+ * @param clazz the exception class. <code>null</code> to reset the error
+ * information.
+ * @param message the exception message.
+ */
+ @SuppressWarnings({ "rawtypes" })
+ public void setError(Class clazz, String message) {
+ this.mClazz = (clazz == null) ? "" : clazz.getName();
+ this.mMessage = (message == null) ? "" : message;
+ }
+
+ /**
+ * Throws the encoded exception. Does not throw an exception if the
+ * container is empty. If the encoded exception is neither a
+ * RuntimeException nor a CardException, it is encapsulated in a
+ * RuntimeException.
+ *
+ * @throws RuntimeException if the encoded exception is not a CardException.
+ * @throws CardException if a CardException is encoded.
+ */
+ public void throwException() throws CardException {
+ Exception e = createException();
+ if (e == null) {
+ return;
+ }
+ if (e instanceof CardException) {
+ throw (CardException) e;
+ }
+ if (e instanceof RuntimeException) {
+ throw (RuntimeException) e;
+ }
+ throw new RuntimeException(e);
+ }
+
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeString(mClazz);
+ out.writeString(mMessage);
+ }
+}
diff --git a/src/org/simalliance/openmobileapi/service/SmartcardService.java b/src/org/simalliance/openmobileapi/service/SmartcardService.java
new file mode 100644
index 0000000..058243d
--- /dev/null
+++ b/src/org/simalliance/openmobileapi/service/SmartcardService.java
@@ -0,0 +1,986 @@
+/*
+ * Copyright (C) 2011, 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 org.simalliance.openmobileapi.service;
+
+import dalvik.system.DexClassLoader;
+import dalvik.system.DexFile;
+
+import android.annotation.SuppressLint;
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.app.Service;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.os.AsyncTask;
+import android.os.Binder;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+
+import org.simalliance.openmobileapi.service.Channel;
+import org.simalliance.openmobileapi.service.Channel.SmartcardServiceChannel;
+import org.simalliance.openmobileapi.service.ISmartcardService;
+import org.simalliance.openmobileapi.service.ISmartcardServiceCallback;
+import org.simalliance.openmobileapi.service.Terminal.SmartcardServiceReader;
+
+import android.util.Log;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.lang.reflect.Constructor;
+import java.security.AccessControlException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+
+
+
+import org.simalliance.openmobileapi.service.security.AccessControlEnforcer;
+import org.simalliance.openmobileapi.service.security.ChannelAccess;
+
+
+/**
+ * The smartcard service is setup with privileges to access smart card hardware.
+ * The service enforces the permission
+ * 'org.simalliance.openmobileapi.service.permission.BIND'.
+ */
+public final class SmartcardService extends Service {
+
+ public static final String _TAG = "SmartcardService";
+ public static final String _UICC_TERMINAL = "SIM";
+ public static final String _eSE_TERMINAL = "eSE";
+ public static final String _SD_TERMINAL = "SD";
+
+ static void clearError(SmartcardError error) {
+ if (error != null) {
+ error.clear();
+ }
+ }
+
+ @SuppressWarnings({ "rawtypes" })
+ static void setError(SmartcardError error, Class clazz, String message) {
+ if (error != null) {
+ error.setError(clazz, message);
+ }
+ }
+
+ static void setError(SmartcardError error, Exception e) {
+ if (error != null) {
+ error.setError(e.getClass(), e.getMessage());
+ }
+ }
+
+ /**
+ * For now this list is setup in onCreate(), not changed later and therefore
+ * not synchronized.
+ */
+ private Map<String, ITerminal> mTerminals = new TreeMap<String, ITerminal>();
+
+ /**
+ * For now this list is setup in onCreate(), not changed later and therefore
+ * not synchronized.
+ */
+ private Map<String, ITerminal> mAddOnTerminals = new TreeMap<String, ITerminal>();
+
+ /* Broadcast receivers */
+ private BroadcastReceiver mSimReceiver;
+ private BroadcastReceiver mNfcReceiver;
+ private BroadcastReceiver mMediaReceiver;
+
+ /* Async task */
+ InitialiseTask mInitialiseTask;
+
+ /**
+ * ServiceHandler use to load rules from the terminal
+ */
+ private ServiceHandler mServiceHandler;
+
+
+ public SmartcardService() {
+ super();
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ Log.v(_TAG, Thread.currentThread().getName()
+ + " smartcard service onBind");
+ if (ISmartcardService.class.getName().equals(intent.getAction())) {
+ return mSmartcardBinder;
+ }
+ return null;
+ }
+
+ @Override
+ public void onCreate() {
+ Log.v(_TAG, Thread.currentThread().getName()
+ + " smartcard service onCreate");
+
+ // Start up the thread running the service. Note that we create a
+ // separate thread because the service normally runs in the process's
+ // main thread, which we don't want to block. We also make it
+ // background priority so CPU-intensive work will not disrupt our UI.
+ HandlerThread thread = new HandlerThread("SmartCardServiceHandler");
+ thread.start();
+
+ // Get the HandlerThread's Looper and use it for our Handler
+ mServiceHandler = new ServiceHandler(thread.getLooper());
+
+ createTerminals();
+ mInitialiseTask = new InitialiseTask();
+ mInitialiseTask.execute();
+ }
+
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+ writer.println("SMARTCARD SERVICE (dumpsys activity service org.simalliance.openmobileapi)");
+ writer.println();
+
+ String prefix = " ";
+
+ if(!Build.IS_DEBUGGABLE) {
+ writer.println(prefix + "Your build is not debuggable!");
+ writer.println(prefix + "Smartcard service dump is only available for userdebug and eng build");
+ } else {
+ writer.println(prefix + "List of terminals:");
+ for (ITerminal terminal : mTerminals.values()) {
+ writer.println(prefix + " " + terminal.getName());
+ }
+ writer.println();
+
+ writer.println(prefix + "List of add-on terminals:");
+ for (ITerminal terminal : mAddOnTerminals.values()) {
+ writer.println(prefix + " " + terminal.getName());
+ }
+ writer.println();
+
+ for (ITerminal terminal : mTerminals.values()) {
+ terminal.dump(writer, prefix);
+ }
+ for (ITerminal terminal : mAddOnTerminals.values()) {
+ terminal.dump(writer, prefix);
+ }
+ }
+ }
+
+
+ private class InitialiseTask extends AsyncTask<Void, Void, Void> {
+
+ @Override
+ protected void onPreExecute() {
+ super.onPreExecute();
+
+ }
+
+ @Override
+ protected Void doInBackground(Void... arg0) {
+
+ try {
+ initializeAccessControl(null, null);
+ } catch( Exception e ){
+ // do nothing since this is called were nobody can react.
+ }
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(Void result) {
+ super.onPostExecute(result);
+ Log.i(_TAG, "OnPostExecute()");
+ registerSimStateChangedEvent(getApplicationContext()) ;
+ registerAdapterStateChangedEvent(getApplicationContext());
+ registerMediaMountedEvent(getApplicationContext());
+ mInitialiseTask = null;
+ }
+ }
+
+ private void registerSimStateChangedEvent(Context context) {
+ Log.v(_TAG, "register SIM_STATE_CHANGED event");
+
+ IntentFilter intentFilter = new IntentFilter("android.intent.action.SIM_STATE_CHANGED");
+ mSimReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if("android.intent.action.SIM_STATE_CHANGED".equals(intent.getAction())) {
+ final Bundle extras = intent.getExtras();
+ final boolean simReady = (extras != null) && "READY".equals(extras.getString("ss"));
+ final boolean simLoaded = (extras != null) && "LOADED".equals(extras.getString("ss"));
+ if( simReady ){
+ Log.i(_TAG, "SIM is ready. Checking access rules for updates.");
+ mServiceHandler.sendMessage(MSG_LOAD_UICC_RULES, 5);
+ }
+ else if( simLoaded){
+ Log.i(_TAG, "SIM is loaded. Checking access rules for updates.");
+ mServiceHandler.sendMessage(MSG_LOAD_UICC_RULES, 5);
+ }
+ }
+ }
+ };
+
+ context.registerReceiver(mSimReceiver, intentFilter);
+ }
+ private void unregisterSimStateChangedEvent(Context context) {
+ if(mSimReceiver!= null) {
+ Log.v(_TAG, "unregister SIM_STATE_CHANGED event");
+ context.unregisterReceiver(mSimReceiver);
+ mSimReceiver = null;
+ }
+ }
+
+
+ private void registerAdapterStateChangedEvent(Context context) {
+ Log.v(_TAG, "register ADAPTER_STATE_CHANGED event");
+
+ IntentFilter intentFilter = new IntentFilter("android.nfc.action.ADAPTER_STATE_CHANGED");
+ mNfcReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final boolean nfcAdapterAction = intent.getAction().equals("android.nfc.action.ADAPTER_STATE_CHANGED");
+ final boolean nfcAdapterOn = nfcAdapterAction && intent.getIntExtra("android.nfc.extra.ADAPTER_STATE", 1) == 3; // is NFC Adapter turned on ?
+ if( nfcAdapterOn){
+ Log.i(_TAG, "NFC Adapter is ON. Checking access rules for updates.");
+ mServiceHandler.sendMessage(MSG_LOAD_ESE_RULES, 5);
+ }
+ }
+ };
+ context.registerReceiver(mNfcReceiver, intentFilter);
+ }
+
+ private void unregisterAdapterStateChangedEvent(Context context) {
+ if(mNfcReceiver!= null) {
+ Log.v(_TAG, "unregister ADAPTER_STATE_CHANGED event");
+ context.unregisterReceiver(mNfcReceiver);
+ mNfcReceiver = null;
+ }
+ }
+
+ private void registerMediaMountedEvent(Context context) {
+ Log.v(_TAG, "register MEDIA_MOUNTED event");
+
+ IntentFilter intentFilter = new IntentFilter("android.intent.action.MEDIA_MOUNTED");
+ mMediaReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final boolean mediaMounted = intent.getAction().equals("android.intent.action.MEDIA_MOUNTED");
+ if( mediaMounted){
+ Log.i(_TAG, "New Media is mounted. Checking access rules for updates.");
+ mServiceHandler.sendMessage(MSG_LOAD_SD_RULES, 5);
+ }
+ }
+ };
+ context.registerReceiver(mMediaReceiver, intentFilter);
+ }
+
+ private void unregisterMediaMountedEvent(Context context) {
+ if(mMediaReceiver != null) {
+ Log.v(_TAG, "unregister MEDIA_MOUNTED event");
+ context.unregisterReceiver(mMediaReceiver);
+ mMediaReceiver = null;
+ }
+ }
+
+ /**
+ * Initalizes Access Control.
+ * At least the refresh tag is read and if it differs to the previous one (e.g. is null) the all
+ * access rules are read.
+ *
+ * @param se
+ */
+ public boolean initializeAccessControl(String se, ISmartcardServiceCallback callback ) {
+ return initializeAccessControl(false, se, callback);
+ }
+
+ public synchronized boolean initializeAccessControl(boolean reset, String se, ISmartcardServiceCallback callback ) {
+ boolean result = true;
+ Log.i(_TAG, "Initializing Access Control");
+
+ if( callback == null ) {
+ callback = new ISmartcardServiceCallback.Stub(){};
+ }
+
+ Collection<ITerminal>col = mTerminals.values();
+ Iterator<ITerminal> iter = col.iterator();
+ while(iter.hasNext()){
+ ITerminal terminal = iter.next();
+ if( terminal == null ){
+
+ continue;
+ }
+
+ if( se == null || terminal.getName().startsWith(se)) {
+ boolean isCardPresent = false;
+ try {
+ isCardPresent = terminal.isCardPresent();
+ } catch (CardException e) {
+ isCardPresent = false;
+
+ }
+
+ if(isCardPresent) {
+ Log.i(_TAG, "Initializing Access Control for " + terminal.getName());
+ if(reset) terminal.resetAccessControl();
+ result &= terminal.initializeAccessControl(true, callback);
+ } else {
+ Log.i(_TAG, "NOT initializing Access Control for " + terminal.getName() + " SE not present.");
+ }
+ }
+ }
+ col = this.mAddOnTerminals.values();
+ iter = col.iterator();
+ while(iter.hasNext()){
+ ITerminal terminal = iter.next();
+ if( terminal == null ){
+
+ continue;
+ }
+
+ if( se == null || terminal.getName().startsWith(se)) {
+ boolean isCardPresent = false;
+ try {
+ isCardPresent = terminal.isCardPresent();
+ } catch (CardException e) {
+ isCardPresent = false;
+
+ }
+
+ if(isCardPresent) {
+ Log.i(_TAG, "Initializing Access Control for " + terminal.getName());
+ if(reset) terminal.resetAccessControl();
+ result &= terminal.initializeAccessControl(true, callback);
+ } else {
+ Log.i(_TAG, "NOT initializing Access Control for " + terminal.getName() + " SE not present.");
+ }
+ }
+ }
+ return result;
+ }
+
+ public void onDestroy() {
+ Log.v(_TAG, " smartcard service onDestroy ...");
+ for (ITerminal terminal : mTerminals.values())
+ terminal.closeChannels();
+ for (ITerminal terminal : mAddOnTerminals.values())
+ terminal.closeChannels();
+
+ // Cancel the inialization background task if still running
+ if(mInitialiseTask != null) mInitialiseTask.cancel(true);
+ mInitialiseTask = null;
+
+ // Unregister all the broadcast receivers
+ unregisterSimStateChangedEvent(getApplicationContext()) ;
+ unregisterAdapterStateChangedEvent(getApplicationContext());
+ unregisterMediaMountedEvent(getApplicationContext());
+
+ mServiceHandler = null;
+
+ Log.v(_TAG, Thread.currentThread().getName()
+ + " ... smartcard service onDestroy");
+
+ }
+
+ private ITerminal getTerminal(String reader, SmartcardError error) {
+ if (reader == null) {
+ setError(error, NullPointerException.class, "reader must not be null");
+ return null;
+ }
+ ITerminal terminal = mTerminals.get(reader);
+ if (terminal == null) {
+ terminal = mAddOnTerminals.get(reader);
+ if (terminal == null) {
+ setError(error, IllegalArgumentException.class, "unknown reader");
+ }
+ }
+ return terminal;
+ }
+
+ private String[] createTerminals() {
+ createBuildinTerminals();
+
+ Set<String> names = mTerminals.keySet();
+ ArrayList<String> list = new ArrayList<String>(names);
+ Collections.sort(list);
+
+ // set UICC on the top
+ if(list.remove(_UICC_TERMINAL + " - UICC"))
+ list.add(0, _UICC_TERMINAL + " - UICC");
+
+ createAddonTerminals();
+ names = mAddOnTerminals.keySet();
+ for (String name : names) {
+ if (!list.contains(name)) {
+ list.add(name);
+ }
+ }
+
+ return list.toArray(new String[list.size()]);
+ }
+
+ private String[] updateTerminals() {
+ Set<String> names = mTerminals.keySet();
+ ArrayList<String> list = new ArrayList<String>(names);
+ Collections.sort(list);
+
+ // set UICC on the top
+ if(list.remove(_UICC_TERMINAL + " - UICC"))
+ list.add(0, _UICC_TERMINAL + " - UICC");
+
+ updateAddonTerminals();
+ names = mAddOnTerminals.keySet();
+ for (String name : names) {
+ if (!list.contains(name)) {
+ list.add(name);
+ }
+ }
+
+ return list.toArray(new String[list.size()]);
+ }
+
+ @SuppressWarnings({ "rawtypes", "unchecked" })
+ private void createBuildinTerminals() {
+ Class[] types = new Class[] {
+ Context.class
+ };
+ Object[] args = new Object[] {
+ this
+ };
+ Object[] classes = getBuildinTerminalClasses();
+ for (Object clazzO : classes) {
+ try {
+ Class clazz = (Class) clazzO;
+ Constructor constr = clazz.getDeclaredConstructor(types);
+ ITerminal terminal = (ITerminal) constr.newInstance(args);
+ mTerminals.put(terminal.getName(), terminal);
+ Log.v(_TAG, Thread.currentThread().getName() + " adding "
+ + terminal.getName());
+ } catch (Throwable t) {
+ Log.e(_TAG, Thread.currentThread().getName()
+ + " CreateReaders Error: "
+ + ((t.getMessage() != null) ? t.getMessage() : "unknown"));
+ }
+ }
+ }
+
+ private void createAddonTerminals() {
+ String[] packageNames = AddonTerminal.getPackageNames(this);
+ for (String packageName : packageNames) {
+ try {
+ String apkName = getPackageManager().getApplicationInfo(packageName, 0).sourceDir;
+ DexFile dexFile = new DexFile(apkName);
+ Enumeration<String> classFileNames = dexFile.entries();
+ while (classFileNames.hasMoreElements()) {
+ String className = classFileNames.nextElement();
+ if (className.endsWith("Terminal")) {
+ ITerminal terminal = new AddonTerminal(this, packageName, className);
+ mAddOnTerminals.put(terminal.getName(), terminal);
+ Log.v(_TAG, Thread.currentThread().getName() + " adding "
+ + terminal.getName());
+ }
+ }
+ } catch (Throwable t) {
+ Log.e(_TAG, Thread.currentThread().getName()
+ + " CreateReaders Error: "
+ + ((t.getMessage() != null) ? t.getMessage() : "unknown"));
+ }
+ }
+ }
+
+ private void updateAddonTerminals() {
+ Set<String> names = mAddOnTerminals.keySet();
+ ArrayList<String> namesToRemove = new ArrayList<String>();
+ for (String name : names) {
+ ITerminal terminal = mAddOnTerminals.get(name);
+ if (!terminal.isConnected()) {
+ namesToRemove.add(terminal.getName());
+ }
+ }
+ for (String name : namesToRemove) {
+ mAddOnTerminals.remove(name);
+ }
+
+ String[] packageNames = AddonTerminal.getPackageNames(this);
+ for (String packageName : packageNames) {
+ try {
+ String apkName = getPackageManager().getApplicationInfo(packageName, 0).sourceDir;
+ DexFile dexFile = new DexFile(apkName);
+ Enumeration<String> classFileNames = dexFile.entries();
+ while (classFileNames.hasMoreElements()) {
+ String className = classFileNames.nextElement();
+ if (className.endsWith("Terminal")) {
+ ITerminal terminal = new AddonTerminal(this, packageName, className);
+ if (!mAddOnTerminals.containsKey(terminal.getName())) {
+ mAddOnTerminals.put(terminal.getName(), terminal);
+ Log.v(_TAG, Thread.currentThread().getName()
+ + " adding " + terminal.getName());
+ }
+ }
+ }
+
+ } catch (Throwable t) {
+ Log.e(_TAG, Thread.currentThread().getName()
+ + " CreateReaders Error: "
+ + ((t.getMessage() != null) ? t.getMessage() : "unknown"));
+ }
+ }
+ }
+
+ @SuppressWarnings({ "rawtypes", "unchecked" })
+ private Object[] getBuildinTerminalClasses() {
+ ArrayList classes = new ArrayList();
+ try {
+ String packageName = "org.simalliance.openmobileapi.service";
+ String apkName = getPackageManager().getApplicationInfo(packageName, 0).sourceDir;
+ DexClassLoader dexClassLoader = new DexClassLoader(apkName, getApplicationContext().getFilesDir().getAbsolutePath(), null, getClass()
+ .getClassLoader());
+
+ Class terminalClass = Class.forName("org.simalliance.openmobileapi.service.Terminal", true, dexClassLoader);
+ if (terminalClass == null) {
+ return classes.toArray();
+ }
+
+ DexFile dexFile = new DexFile(apkName);
+ Enumeration<String> classFileNames = dexFile.entries();
+ while (classFileNames.hasMoreElements()) {
+ String className = classFileNames.nextElement();
+ Class clazz = Class.forName(className);
+ Class superClass = clazz.getSuperclass();
+ if (superClass != null && superClass.equals(terminalClass)
+ && !className.equals("org.simalliance.openmobileapi.service.AddonTerminal")) {
+ classes.add(clazz);
+ }
+ }
+ } catch (Throwable exp) {
+ // nothing to to
+ }
+ return classes.toArray();
+ }
+
+ /**
+ * Get package name from the user id.
+ *
+ * This shall fix the problem the issue that process name != package name
+ * due to anndroid:process attribute in manifest file.
+ *
+ * But this call is not really secure either since a uid can be shared between one
+ * and more apks
+ *
+ * @param uid
+ * @return The first package name associated with this uid.
+ */
+ public String getPackageNameFromCallingUid(int uid ){
+ PackageManager packageManager = getPackageManager();
+ if(packageManager != null){
+ String packageName[] = packageManager.getPackagesForUid(uid);
+ if( packageName != null && packageName.length > 0 ){
+ return packageName[0];
+ }
+ }
+ throw new AccessControlException("Caller PackageName can not be determined");
+ }
+
+ /**
+ * The smartcard service interface implementation.
+ */
+ private final ISmartcardService.Stub mSmartcardBinder = new ISmartcardService.Stub() {
+
+ @Override
+ public String[] getReaders(SmartcardError error) throws RemoteException {
+ clearError(error);
+ Log.v(_TAG, "getReaders()");
+ return updateTerminals();
+ }
+
+ @Override
+ public ISmartcardServiceReader getReader(String reader,
+ SmartcardError error) throws RemoteException {
+ clearError(error);
+ Terminal terminal = (Terminal)getTerminal(reader, error);
+ if( terminal != null ){
+ return terminal.new SmartcardServiceReader(SmartcardService.this);
+ }
+ setError(error, IllegalArgumentException.class, "invalid reader name");
+ return null;
+ }
+
+
+ @Override
+ public synchronized boolean[] isNFCEventAllowed(
+ String reader,
+ byte[] aid,
+ String[] packageNames,
+ ISmartcardServiceCallback callback,
+ SmartcardError error)
+ throws RemoteException
+ {
+ clearError(error);
+ try
+ {
+ if (callback == null) {
+ setError(error, NullPointerException.class, "callback must not be null");
+ return null;
+ }
+ ITerminal terminal = getTerminal(reader, error);
+ if (terminal == null) {
+ return null;
+ }
+ if (aid == null || aid.length == 0) {
+ aid = new byte[] {
+ 0x00, 0x00, 0x00, 0x00, 0x00
+ };
+ }
+ if (aid.length < 5 || aid.length > 16) {
+ setError(error, IllegalArgumentException.class, "AID out of range");
+ return null;
+ }
+ if (packageNames == null || packageNames.length == 0) {
+ setError(error, IllegalArgumentException.class, "process names not specified");
+ return null;
+ }
+ AccessControlEnforcer ac = null;
+ if( terminal.getAccessControlEnforcer() == null ) {
+ ac = new AccessControlEnforcer( terminal );
+ } else {
+ ac = terminal.getAccessControlEnforcer();
+ }
+ ac.setPackageManager(getPackageManager());
+ ac.initialize(true, callback);
+ return ac.isNFCEventAllowed(aid, packageNames, callback );
+ } catch (Exception e) {
+ setError(error, e);
+ Log.v(_TAG, "isNFCEventAllowed Exception: " + e.getMessage() );
+ return null;
+ }
+ }
+ };
+
+ /**
+ * The smartcard service interface implementation.
+ */
+ final class SmartcardServiceSession extends ISmartcardServiceSession.Stub {
+
+ private final SmartcardServiceReader mReader;
+ /** List of open channels in use of by this client. */
+ private final Set<Channel> mChannels = new HashSet<Channel>();
+
+ private final Object mLock = new Object();
+
+ private boolean mIsClosed;
+
+ private byte[] mAtr;
+
+ public SmartcardServiceSession(SmartcardServiceReader reader){
+ mReader = reader;
+ mAtr = mReader.getAtr();
+ mIsClosed = false;
+ }
+
+ @Override
+ public ISmartcardServiceReader getReader() throws RemoteException {
+ return mReader;
+ }
+
+ @Override
+ public byte[] getAtr() throws RemoteException {
+ return mAtr;
+ }
+
+ @Override
+ public void close(SmartcardError error) throws RemoteException {
+ clearError(error);
+ if (mReader == null) {
+ return;
+ }
+ try {
+ mReader.closeSession(this);
+ } catch (CardException e) {
+ setError(error,e);
+ }
+ }
+
+ @Override
+ public void closeChannels(SmartcardError error) throws RemoteException {
+ synchronized (mLock) {
+
+ Iterator<Channel>iter = mChannels.iterator();
+ try {
+ while(iter.hasNext()) {
+ Channel channel = iter.next();
+ if (channel != null && !channel.isClosed()) {
+ try {
+ channel.close();
+ // close changes indirectly mChannels, so we need a new iterator.
+ iter = mChannels.iterator();
+ } catch (Exception ignore) {
+ Log.e(_TAG, "ServiceSession channel - close Exception " + ignore.getMessage());
+ }
+ channel.setClosed();
+ }
+ }
+ mChannels.clear();
+ } catch( Exception e ) {
+ Log.e(_TAG, "ServiceSession closeChannels Exception " + e.getMessage());
+ }
+ }
+ }
+
+ @Override
+ public boolean isClosed() throws RemoteException {
+
+ return mIsClosed;
+ }
+
+ @Override
+ public ISmartcardServiceChannel openBasicChannel(
+ ISmartcardServiceCallback callback, SmartcardError error)
+ throws RemoteException {
+ return openBasicChannelAid( null, callback, error);
+ }
+
+ @Override
+ public ISmartcardServiceChannel openBasicChannelAid(byte[] aid,
+ ISmartcardServiceCallback callback, SmartcardError error)
+ throws RemoteException {
+ clearError(error);
+ if ( isClosed() ) {
+ setError( error, IllegalStateException.class, "session is closed");
+ return null;
+ }
+ if (callback == null) {
+ setError(error, NullPointerException.class, "callback must not be null");
+ return null;
+ }
+ if (mReader == null) {
+ setError(error, NullPointerException.class, "reader must not be null");
+ return null;
+ }
+
+ try {
+ boolean noAid = false;
+ if (aid == null || aid.length == 0) {
+ aid = new byte[] {0x00, 0x00, 0x00, 0x00, 0x00 };
+ noAid = true;
+ }
+
+ if (aid.length < 5 || aid.length > 16) {
+ setError(error, IllegalArgumentException.class, "AID out of range");
+ return null;
+ }
+
+
+ String packageName = getPackageNameFromCallingUid( Binder.getCallingUid());
+ Log.v(_TAG, "Enable access control on basic channel for " + packageName);
+ ChannelAccess channelAccess = mReader.getTerminal().setUpChannelAccess(
+ getPackageManager(),
+ aid,
+ packageName,
+ callback );
+ Log.v(_TAG, "Access control successfully enabled.");
+
+ channelAccess.setCallingPid(Binder.getCallingPid());
+
+
+
+ Log.v(_TAG, "OpenBasicChannel(AID)");
+ Channel channel = null;
+ if (noAid) {
+ channel = mReader.getTerminal().openBasicChannel(this, callback);
+ } else {
+ channel = mReader.getTerminal().openBasicChannel(this, aid, callback);
+ }
+
+ channel.setChannelAccess(channelAccess);
+
+ Log.v(_TAG, "Open basic channel success. Channel: " + channel.getChannelNumber() );
+
+ SmartcardServiceChannel basicChannel = channel.new SmartcardServiceChannel(this);
+ mChannels.add(channel);
+ return basicChannel;
+
+ } catch (Exception e) {
+ setError(error, e);
+ Log.v(_TAG, "OpenBasicChannel Exception: " + e.getMessage());
+ return null;
+ }
+ }
+
+ @Override
+ public ISmartcardServiceChannel openLogicalChannel(byte[] aid,
+ ISmartcardServiceCallback callback, SmartcardError error)
+ throws RemoteException {
+ clearError(error);
+
+ if ( isClosed() ) {
+ setError( error, IllegalStateException.class, "session is closed");
+ return null;
+ }
+
+ if (callback == null) {
+ setError(error, NullPointerException.class, "callback must not be null");
+ return null;
+ }
+ if (mReader == null) {
+ setError(error, NullPointerException.class, "reader must not be null");
+ return null;
+ }
+
+ try {
+ boolean noAid = false;
+ if (aid == null || aid.length == 0) {
+ aid = new byte[] {
+ 0x00, 0x00, 0x00, 0x00, 0x00
+ };
+ noAid = true;
+ }
+
+ if (aid.length < 5 || aid.length > 16) {
+ setError(error, IllegalArgumentException.class, "AID out of range");
+ return null;
+ }
+
+
+ String packageName = getPackageNameFromCallingUid( Binder.getCallingUid());
+ Log.v(_TAG, "Enable access control on logical channel for " + packageName);
+ ChannelAccess channelAccess = mReader.getTerminal().setUpChannelAccess(
+ getPackageManager(),
+ aid,
+ packageName,
+ callback );
+ Log.v(_TAG, "Access control successfully enabled.");
+ channelAccess.setCallingPid(Binder.getCallingPid());
+
+
+ Log.v(_TAG, "OpenLogicalChannel");
+ Channel channel = null;
+ if (noAid) {
+ channel = mReader.getTerminal().openLogicalChannel(this, callback);
+ } else {
+ channel = mReader.getTerminal().openLogicalChannel(this, aid, callback);
+ }
+
+ channel.setChannelAccess(channelAccess);
+
+ Log.v(_TAG, "Open logical channel successfull. Channel: " + channel.getChannelNumber());
+ SmartcardServiceChannel logicalChannel = channel.new SmartcardServiceChannel(this);
+ mChannels.add(channel);
+ return logicalChannel;
+ } catch (Exception e) {
+ setError(error, e);
+ Log.v(_TAG, "OpenLogicalChannel Exception: " + e.getMessage());
+ return null;
+ }
+ }
+
+ void setClosed(){
+ mIsClosed = true;
+
+ }
+
+ /**
+ * Closes the specified channel. <br>
+ * After calling this method the session can not be used for the
+ * communication with the secure element any more.
+ *
+ * @param hChannel the channel handle obtained by an open channel command.
+ */
+ void removeChannel(Channel channel) {
+ if (channel == null) {
+ return;
+ }
+ mChannels.remove(channel);
+ }
+ }
+
+ /*
+ * Handler Thread used to load and initiate ChannelAccess condition
+ */
+ public final static int MSG_LOAD_UICC_RULES = 1;
+ public final static int MSG_LOAD_ESE_RULES = 2;
+ public final static int MSG_LOAD_SD_RULES = 3;
+
+ public final static int NUMBER_OF_TRIALS = 3;
+ public final static long WAIT_TIME = 1000;
+
+ private final class ServiceHandler extends Handler {
+
+ @SuppressLint("HandlerLeak")
+ public ServiceHandler(Looper looper) {
+ super(looper);
+ }
+
+ public void sendMessage(int what, int nbTries) {
+ mServiceHandler.removeMessages(what);
+ Message newMsg = mServiceHandler.obtainMessage(what, nbTries, 0);
+ mServiceHandler.sendMessage(newMsg);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ boolean result = true;
+
+ Log.i(_TAG, "Handle msg: what=" + msg.what + " nbTries=" + msg.arg1);
+
+ switch(msg.what) {
+ case MSG_LOAD_UICC_RULES:
+ try {
+ result = initializeAccessControl(true, _UICC_TERMINAL, null );
+ } catch (Exception e) {
+ Log.e(_TAG, "Got exception:" + e);
+ }
+ break;
+
+ case MSG_LOAD_ESE_RULES:
+ try {
+ result = initializeAccessControl(true, _eSE_TERMINAL, null );
+ } catch (Exception e) {
+ Log.e(_TAG, "Got exception:" + e);
+ }
+ break;
+
+ case MSG_LOAD_SD_RULES:
+ try {
+ result = initializeAccessControl(true, _SD_TERMINAL, null );
+ } catch (Exception e) {
+ Log.e(_TAG, "Got exception:" + e);
+ }
+ break;
+ }
+
+ if(!result && msg.arg1 > 0) {
+ // Try to re-post the message
+ Log.e(_TAG, "Fail to load rules: Let's try another time (" + msg.arg1 + " remaining attempt");
+ Message newMsg = mServiceHandler.obtainMessage(msg.what, msg.arg1 - 1, 0);
+ mServiceHandler.sendMessageDelayed(newMsg, WAIT_TIME);
+ }
+ }
+ }
+}
diff --git a/src/org/simalliance/openmobileapi/service/SmartcardServiceBootCompletedBroadcastReceiver.java b/src/org/simalliance/openmobileapi/service/SmartcardServiceBootCompletedBroadcastReceiver.java
new file mode 100644
index 0000000..d136b06
--- /dev/null
+++ b/src/org/simalliance/openmobileapi/service/SmartcardServiceBootCompletedBroadcastReceiver.java
@@ -0,0 +1,26 @@
+
+package org.simalliance.openmobileapi.service;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+public class SmartcardServiceBootCompletedBroadcastReceiver extends BroadcastReceiver {
+ public final static String _TAG = "SmartcardService";
+ public final static String _SCAPI_SERVICE = "org.simalliance.openmobileapi.service.SmartcardService";
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final boolean bootCompleted = intent.getAction().equals("android.intent.action.BOOT_COMPLETED");
+
+ Log.v(_TAG, Thread.currentThread().getName() + " Received broadcast");
+ if( bootCompleted ){
+ Log.v(_TAG, "Starting smartcard service after boot completed");
+ Intent serviceIntent = new Intent(context, org.simalliance.openmobileapi.service.SmartcardService.class );
+ context.startService(serviceIntent);
+ } else {
+
+ }
+ }
+};
diff --git a/src/org/simalliance/openmobileapi/service/Terminal.java b/src/org/simalliance/openmobileapi/service/Terminal.java
new file mode 100644
index 0000000..120f1f1
--- /dev/null
+++ b/src/org/simalliance/openmobileapi/service/Terminal.java
@@ -0,0 +1,743 @@
+/*
+ * Copyright (C) 2011, 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 org.simalliance.openmobileapi.service;
+
+import android.content.Context;
+
+import org.simalliance.openmobileapi.service.ISmartcardServiceCallback;
+import org.simalliance.openmobileapi.service.SmartcardService.SmartcardServiceSession;
+
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.security.AccessControlException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Random;
+
+
+import android.content.pm.PackageManager;
+import org.simalliance.openmobileapi.service.security.AccessControlEnforcer;
+import org.simalliance.openmobileapi.service.security.ChannelAccess;
+
+
+/**
+ * Smartcard service base class for terminal resources.
+ */
+public abstract class Terminal implements ITerminal {
+
+ /** Random number generator used for handle creation. */
+ static Random mRandom = new Random();
+
+ protected Context mContext;
+
+ private final Map<Long, IChannel> mChannels = new HashMap<Long, IChannel>();
+
+ private final Object mLock = new Object();
+
+ protected final String mName;
+
+ public volatile boolean mIsConnected;
+
+ protected byte[] mSelectResponse;
+
+ protected boolean mDefaultApplicationSelectedOnBasicChannel = true;
+
+
+ /**
+ * For each Terminal there will be one AccessController object.
+ */
+ private AccessControlEnforcer mAccessControlEnforcer;
+
+
+ /**
+ * Returns a concatenated response.
+ *
+ * @param r1 the first part of the response.
+ * @param r2 the second part of the response.
+ * @param length the number of bytes of the second part to be appended.
+ * @return a concatenated response.
+ */
+ static byte[] appendResponse(byte[] r1, byte[] r2, int length) {
+ byte[] rsp = new byte[r1.length + length];
+ System.arraycopy(r1, 0, rsp, 0, r1.length);
+ System.arraycopy(r2, 0, rsp, r1.length, length);
+ return rsp;
+ }
+
+ /**
+ * Creates a formatted exception message.
+ *
+ * @param commandName the name of the command. <code>null</code> if not
+ * specified.
+ * @param sw the response status word.
+ * @return a formatted exception message.
+ */
+ static String createMessage(String commandName, int sw) {
+ StringBuffer message = new StringBuffer();
+ if (commandName != null) {
+ message.append(commandName).append(" ");
+ }
+ message.append("SW1/2 error: ");
+ message.append(Integer.toHexString(sw | 0x10000).substring(1));
+ return message.toString();
+ }
+
+ /**
+ * Creates a formatted exception message.
+ *
+ * @param commandName the name of the command. <code>null</code> if not
+ * specified.
+ * @param message the message to be formatted.
+ * @return a formatted exception message.
+ */
+ static String createMessage(String commandName, String message) {
+ if (commandName == null) {
+ return message;
+ }
+ return commandName + " " + message;
+ }
+
+ public Terminal(String name, Context context) {
+ this.mContext = context;
+ this.mName = name;
+ }
+
+ /**
+ * This method is called in SmartcardService:onDestroy
+ * to clean up all open channels
+ */
+ public synchronized void closeChannels() {
+ Collection<IChannel> col = mChannels.values();
+ IChannel[] channelList = col.toArray(new IChannel[col.size()]);
+ for (IChannel channel : channelList) {
+ try {
+ closeChannel((Channel)channel);
+ } catch (Exception ignore) {
+ }
+ }
+ }
+
+ /**
+ * Closes the specified channel.
+ *
+ * @param channel the channel to be closed.
+ * @throws CardException if closing the channel failed.
+ */
+ public void closeChannel(Channel channel) throws CardException {
+
+ synchronized(mLock) {
+ try {
+ internalCloseLogicalChannel(channel.getChannelNumber());
+ } finally {
+ mChannels.remove(channel.getHandle());
+ if (mIsConnected && mChannels.isEmpty()) {
+ try {
+ internalDisconnect();
+ } catch (Exception ignore) {
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Creates a channel instance.
+ *
+ * @param channelNumber the channel number according to ISO 7816-4.
+ * @param callback the callback used to detect the death of the client.
+ * @return a channel instance.
+ */
+ protected Channel createChannel(SmartcardServiceSession session, int channelNumber, ISmartcardServiceCallback callback) {
+ return new Channel(session, this, channelNumber, callback);
+ }
+
+ private IChannel getBasicChannel() {
+ for (IChannel channel : mChannels.values()) {
+ if (channel.getChannelNumber() == 0) {
+ return channel;
+ }
+ }
+ return null;
+ }
+
+ public synchronized IChannel getChannel(long hChannel) {
+ return mChannels.get(hChannel);
+ }
+
+ public String getName() {
+ return mName;
+ }
+
+ /**
+ * Implements the terminal specific connect operation.
+ *
+ * @throws CardException if connecting the card failed.
+ */
+ protected abstract void internalConnect() throws CardException;
+
+ /**
+ * Implements the terminal specific disconnect operation.
+ *
+ * @throws CardException if disconnecting from the card failed.
+ */
+ protected abstract void internalDisconnect() throws CardException;
+
+ /**
+ * Implementation of the SELECT command.
+ *
+ * @return the number of the logical channel according to ISO 7816-4.
+ * @throws CardException
+ */
+ abstract protected int internalOpenLogicalChannel() throws Exception;
+
+ /**
+ * Implementation of the MANAGE CHANNEL open and SELECT commands.
+ *
+ * @return the number of the logical channel according to ISO 7816-4.
+ * @throws CardException
+ */
+ abstract protected int internalOpenLogicalChannel(byte[] aid) throws Exception;
+
+ /**
+ * Implementation of the MANAGE CHANNEL close command.
+ *
+ * @param channelNumber
+ * @throws CardException
+ */
+ abstract protected void internalCloseLogicalChannel(int channelNumber) throws CardException;
+
+ /**
+ * Implements the terminal specific transmit operation.
+ *
+ * @param command the command APDU to be transmitted.
+ * @return the response APDU received.
+ * @throws CardException if the transmit operation failed.
+ */
+ protected abstract byte[] internalTransmit(byte[] command) throws CardException;
+
+ /**
+ * Performs a select command on the basic channel without an AID parameter. <br>
+ * The card manager will be selected.
+ */
+ public void select() {
+ mSelectResponse = null;
+ byte[] selectCommand = new byte[5];
+ selectCommand[0] = 0x00;
+ selectCommand[1] = (byte) 0xA4;
+ selectCommand[2] = 0x04;
+ selectCommand[3] = 0x00;
+ selectCommand[4] = 0x00;
+ try {
+ mSelectResponse = transmit(selectCommand, 2, 0x9000, 0xFFFF, "SELECT");
+
+ } catch (Exception exp) {
+ throw new NoSuchElementException(exp.getMessage());
+ }
+ }
+
+ /**
+ * Returns the ATR of the connected card or null if the ATR is not
+ * available.
+ *
+ * @return the ATR of the connected card or null if the ATR is not
+ * available.
+ */
+ public byte[] getAtr() {
+ return null;
+ }
+
+ /**
+ * Performs a select command on the basic channel
+ *
+ * @param aid the aid which should be selected.
+ */
+ public void select(byte[] aid) {
+ if (aid == null) {
+ throw new NullPointerException("aid must not be null");
+ }
+ mSelectResponse = null;
+ byte[] selectCommand = new byte[aid.length + 6];
+ selectCommand[0] = 0x00;
+ selectCommand[1] = (byte) 0xA4;
+ selectCommand[2] = 0x04;
+ selectCommand[3] = 0x00;
+ selectCommand[4] = (byte) aid.length;
+ System.arraycopy(aid, 0, selectCommand, 5, aid.length);
+ try {
+ mSelectResponse = transmit(selectCommand, 2, 0x9000, 0xFFFF, "SELECT");
+ } catch (Exception exp) {
+ throw new NoSuchElementException(exp.getMessage());
+ }
+ }
+
+ public Channel openBasicChannel(SmartcardServiceSession session, ISmartcardServiceCallback callback) throws CardException {
+ if (callback == null) {
+ throw new NullPointerException("callback must not be null");
+ }
+
+ synchronized (mLock) {
+ if(!mDefaultApplicationSelectedOnBasicChannel) {
+ throw new CardException("default application is not selected");
+ }
+ if (getBasicChannel() != null) {
+ throw new CardException("basic channel in use");
+ }
+ if (mChannels.isEmpty()) {
+ internalConnect();
+ }
+
+
+ Channel basicChannel = createChannel(session, 0, callback);
+ basicChannel.hasSelectedAid(false, null);
+ registerChannel(basicChannel);
+ return basicChannel;
+ }
+ }
+
+ public Channel openBasicChannel(SmartcardServiceSession session, byte[] aid, ISmartcardServiceCallback callback) throws Exception {
+ if (callback == null) {
+ throw new NullPointerException("callback must not be null");
+ }
+ if (aid == null) {
+ throw new NullPointerException("aid must not be null");
+ }
+
+ synchronized (mLock) {
+ if (getBasicChannel() != null) {
+ throw new CardException("basic channel in use");
+ }
+ if (mChannels.isEmpty()) {
+ internalConnect();
+ }
+
+ try {
+ select(aid);
+ } catch (Exception e) {
+ if (mIsConnected && mChannels.isEmpty()) {
+ internalDisconnect();
+ }
+ throw e;
+ }
+
+
+ Channel basicChannel = createChannel(session, 0, callback);
+ basicChannel.hasSelectedAid(true, aid);
+ mDefaultApplicationSelectedOnBasicChannel = false;
+ registerChannel(basicChannel);
+ return basicChannel;
+ }
+ }
+
+ public Channel openLogicalChannel(SmartcardServiceSession session, ISmartcardServiceCallback callback) throws Exception {
+ if (callback == null) {
+ throw new NullPointerException("callback must not be null");
+ }
+
+ synchronized (mLock) {
+ if (mChannels.isEmpty()) {
+ internalConnect();
+ }
+
+ int channelNumber = 0;
+ try {
+ channelNumber = internalOpenLogicalChannel();
+ } catch (Exception e) {
+ if (mIsConnected && mChannels.isEmpty()) {
+ internalDisconnect();
+ }
+ throw e;
+ }
+
+
+ Channel logicalChannel = createChannel(session, channelNumber, callback);
+ logicalChannel.hasSelectedAid(false, null);
+ registerChannel(logicalChannel);
+ return logicalChannel;
+ }
+ }
+
+ public Channel openLogicalChannel(SmartcardServiceSession session, byte[] aid, ISmartcardServiceCallback callback) throws Exception {
+ if (callback == null) {
+ throw new NullPointerException("callback must not be null");
+ }
+ if (aid == null) {
+ throw new NullPointerException("aid must not be null");
+ }
+
+ synchronized (mLock) {
+ if (mChannels.isEmpty()) {
+ internalConnect();
+ }
+
+ int channelNumber = 0;
+ try {
+ channelNumber = internalOpenLogicalChannel(aid);
+ } catch (Exception e) {
+ if (mIsConnected && mChannels.isEmpty()) {
+ internalDisconnect();
+ }
+ throw e;
+ }
+
+
+ Channel logicalChannel = createChannel(session, channelNumber, callback);
+ logicalChannel.hasSelectedAid(true, aid);
+ registerChannel(logicalChannel);
+ return logicalChannel;
+ }
+ }
+
+ public boolean isConnected() {
+ return mIsConnected;
+ }
+
+ /**
+ * Protocol specific implementation of the transmit operation. This method
+ * is synchronized in order to handle GET RESPONSE and command repetition
+ * without interruption by other commands.
+ *
+ * @param cmd the command to be transmitted.
+ * @return the response received.
+ * @throws CardException if the transmit operation failed.
+ */
+ protected synchronized byte[] protocolTransmit(byte[] cmd) throws CardException {
+ byte[] command = cmd;
+ byte[] rsp = null;
+ synchronized (mLock) {
+ rsp = internalTransmit(command);
+ }
+
+ if (rsp.length >= 2) {
+ int sw1 = rsp[rsp.length - 2] & 0xFF;
+ int sw2 = rsp[rsp.length - 1] & 0xFF;
+ if (sw1 == 0x6C) {
+ command[cmd.length - 1] = rsp[rsp.length - 1];
+ rsp = internalTransmit(command);
+ } else if (sw1 == 0x61) {
+ byte[] getResponseCmd = new byte[] {
+ command[0], (byte) 0xC0, 0x00, 0x00, 0x00
+ };
+ byte[] response = new byte[rsp.length - 2];
+ System.arraycopy(rsp, 0, response, 0, rsp.length - 2);
+ while (true) {
+ getResponseCmd[4] = rsp[rsp.length - 1];
+ rsp = internalTransmit(getResponseCmd);
+ if (rsp.length >= 2 && rsp[rsp.length - 2] == 0x61) {
+ response = appendResponse(response, rsp, rsp.length - 2);
+ } else {
+ response = appendResponse(response, rsp, rsp.length);
+ break;
+ }
+ }
+ rsp = response;
+ } else if (rsp.length == 2 && sw1 == 0x63 && sw2 == 0x10) {
+ byte[] getResponseCmd = new byte[] {
+ (byte)(command[0] & 0x03), (byte) 0xC0, 0x00, 0x00, 0x00
+ };
+ byte[] response = new byte[rsp.length - 2];
+ System.arraycopy(rsp, 0, response, 0, rsp.length - 2);
+ rsp[rsp.length - 1] = 0x00;
+ while (true) {
+ getResponseCmd[4] = rsp[rsp.length - 1];
+ rsp = internalTransmit(getResponseCmd);
+ if (rsp.length >= 2 && rsp[rsp.length - 2] == 0x61) {
+ response = appendResponse(response, rsp, rsp.length - 2);
+ } else {
+ response = appendResponse(response, rsp, rsp.length);
+ break;
+ }
+ }
+ rsp = response;
+ }
+ }
+ return rsp;
+ }
+
+ /**
+ * Creates a handle for the specified channel instances and adds the channel
+ * instance to the channel list.
+ *
+ * @param channel
+ * @return the channel handle.
+ */
+ private long registerChannel(Channel channel) {
+ long hChannel = mRandom.nextInt();
+ hChannel <<= 32;
+ hChannel |= (((long) channel.hashCode()) & 0xFFFFFFFFL);
+
+ channel.setHandle(hChannel);
+
+ mChannels.put(hChannel, channel);
+
+ return hChannel;
+ }
+
+ /**
+ * Transmits the specified command and returns the response. Optionally
+ * checks the response length and the response status word. The status word
+ * check is implemented as follows (sw = status word of the response):
+ * <p>
+ * if ((sw & swMask) != (swExpected & swMask)) throw new CardException();
+ * </p>
+ *
+ * @param cmd the command APDU to be transmitted.
+ * @param minRspLength the minimum length of received response to be
+ * checked.
+ * @param swExpected the response status word to be checked.
+ * @param swMask the mask to be used for response status word comparison.
+ * @param commandName the name of the smart card command for logging
+ * purposes. May be <code>null</code>.
+ * @return the response received.
+ * @throws CardException if the transmit operation or the minimum response
+ * length check or the status word check failed.
+ */
+ public byte[] transmit(byte[] cmd, int minRspLength, int swExpected, int swMask,
+ String commandName) throws CardException {
+ byte[] rsp = null;
+ try {
+ rsp = protocolTransmit(cmd);
+ } catch (CardException e) {
+ if (commandName == null) {
+ throw e;
+ } else {
+ throw new CardException(createMessage(commandName, "transmit failed"), e);
+ }
+ }
+ if (minRspLength > 0) {
+ if (rsp == null || rsp.length < minRspLength) {
+ throw new CardException(createMessage(commandName, "response too small"));
+ }
+ }
+ if (swMask != 0) {
+ if (rsp == null || rsp.length < 2) {
+ throw new CardException(createMessage(commandName, "SW1/2 not available"));
+ }
+ int sw1 = rsp[rsp.length - 2] & 0xFF;
+ int sw2 = rsp[rsp.length - 1] & 0xFF;
+ int sw = (sw1 << 8) | sw2;
+ if ((sw & swMask) != (swExpected & swMask)) {
+ throw new CardException(createMessage(commandName, sw));
+ }
+ }
+ return rsp;
+ }
+
+ /**
+ * Returns the data as received from the application select command inclusively the status word.
+ * The returned byte array contains the data bytes in the following order:
+ * [<first data byte>, ..., <last data byte>, <sw1>, <sw2>]
+ * @return The data as returned by the application select command inclusively the status word.
+ * @return Only the status word if the application select command has no returned data.
+ * @return null if an application select command has not been performed or the selection response can not
+ * be retrieved by the reader implementation.
+ */
+ public byte[] getSelectResponse()
+ {
+ return mSelectResponse;
+ }
+
+ /**
+ * Exchanges APDU (SELECT, READ/WRITE) to the
+ * given EF by File ID and file path via iccIO.
+ *
+ * The given command is checked and might be rejected.
+ *
+ * @param fileID
+ * @param filePath
+ * @param cmd
+ * @return
+ */
+ public byte[] simIOExchange(int fileID, String filePath, byte[] cmd)
+ throws Exception {
+ throw new Exception("SIM IO error!");
+ }
+
+
+
+ public ChannelAccess setUpChannelAccess(
+ PackageManager packageManager,
+ byte[] aid,
+ String packageName,
+ ISmartcardServiceCallback callback){
+ if( mAccessControlEnforcer == null ){
+ throw new AccessControlException("Access Control Enforcer not properly set up");
+ }
+ mAccessControlEnforcer.setPackageManager(packageManager);
+ return mAccessControlEnforcer.setUpChannelAccess(aid, packageName, callback);
+ }
+
+ public synchronized boolean initializeAccessControl(boolean loadAtStartup, ISmartcardServiceCallback callback ){
+ if( mAccessControlEnforcer == null ){
+ mAccessControlEnforcer = new AccessControlEnforcer(this);
+ }
+ return mAccessControlEnforcer.initialize( loadAtStartup, callback );
+ }
+
+ public AccessControlEnforcer getAccessControlEnforcer(){
+ return mAccessControlEnforcer;
+ }
+
+ public synchronized void resetAccessControl() {
+ if(mAccessControlEnforcer != null ) mAccessControlEnforcer.reset();
+ }
+
+
+ /**
+ * Implementation of the SmartcardService Reader interface according to OMAPI.
+ */
+ final class SmartcardServiceReader extends ISmartcardServiceReader.Stub {
+
+ protected final SmartcardService mService;
+
+ private final ArrayList<SmartcardServiceSession> mSessions = new ArrayList<SmartcardServiceSession>();
+
+ private final Object mLock = new Object();
+
+ public SmartcardServiceReader( SmartcardService service ){
+ this.mService = service;
+ }
+
+ public byte[] getAtr(){
+ return Terminal.this.getAtr();
+ }
+
+ @Override
+ public String getName(SmartcardError error) throws RemoteException {
+ SmartcardService.clearError(error);
+ return Terminal.this.getName();
+ }
+
+ @Override
+ public boolean isSecureElementPresent(SmartcardError error)
+ throws RemoteException {
+ SmartcardService.clearError(error);
+ try {
+ return Terminal.this.isCardPresent();
+ } catch (Exception e) {
+ SmartcardService.setError(error, e);
+ }
+ return false;
+ }
+
+ @Override
+ public ISmartcardServiceSession openSession(SmartcardError error)
+ throws RemoteException {
+ SmartcardService.clearError(error);
+ try {
+ if (!Terminal.this.isCardPresent()){
+ SmartcardService.setError(error,new IOException("Secure Element is not presented."));
+ return null;
+ }
+ } catch (CardException e) {
+ SmartcardService.setError(error,e);
+ return null;
+ }
+
+ synchronized (mLock) {
+ try {
+ mService.initializeAccessControl(Terminal.this.getName(), null);
+ } catch (Exception e ){
+ SmartcardService.setError(error,e);
+ return null; // Reader.openSession() will throw an IOException when session is null
+ }
+ SmartcardServiceSession session = mService.new SmartcardServiceSession(this);
+ mSessions.add(session);
+
+ return session;
+ }
+ }
+
+ @Override
+ public void closeSessions(SmartcardError error) throws RemoteException {
+
+ SmartcardService.clearError(error);
+ synchronized (mLock) {
+ for (SmartcardServiceSession session : mSessions) {
+ if (session != null && !session.isClosed()) {
+ session.closeChannels(error);
+ session.setClosed();
+ }
+ }
+ mSessions.clear();
+ }
+ }
+
+ /**
+ * Closes the defined Session and all its allocated resources. <br>
+ * After calling this method the Session can not be used for the
+ * communication with the Secure Element any more.
+ *
+ * @param session the Session that should be closed
+ * @throws RemoteException
+ * @throws CardException
+ * @throws NullPointerException if Session is null
+ */
+ void closeSession(SmartcardServiceSession session) throws RemoteException, CardException {
+ if (session == null) {
+ throw new NullPointerException("session is null");
+ }
+ synchronized (mLock) {
+ if (!session.isClosed()) {
+ SmartcardError error = new SmartcardError();
+ session.closeChannels(error);
+ error.throwException();
+ session.setClosed();
+ }
+ mSessions.remove(session);
+ }
+ }
+
+ Terminal getTerminal() {
+ return Terminal.this;
+ }
+ }
+
+ public void dump(PrintWriter writer, String prefix) {
+ writer.println(prefix + "SMARTCARD SERVICE TERMINAL: " + mName);
+ writer.println();
+
+ prefix += " ";
+
+ writer.println(prefix + "mIsConnected:" + mIsConnected);
+ writer.println();
+
+ /* Dump the list of currunlty openned channels */
+ writer.println(prefix + "List of open channels:");
+
+ for(IChannel channel: mChannels.values()) {
+ writer.println(prefix + " channel " + channel.getChannelNumber() + ": ");
+ writer.println(prefix + " package : " + channel.getChannelAccess().getPackageName());
+ writer.println(prefix + " pid : " + channel.getChannelAccess().getCallingPid());
+ writer.println(prefix + " aid selected : " + channel.hasSelectedAid());
+ writer.println(prefix + " basic channel: " + channel.isBasicChannel());
+ }
+
+ writer.println();
+
+ /* Dump ACE data */
+ if(mAccessControlEnforcer != null) mAccessControlEnforcer.dump(writer, prefix);
+ }
+}
diff --git a/src/org/simalliance/openmobileapi/service/Util.java b/src/org/simalliance/openmobileapi/service/Util.java
new file mode 100644
index 0000000..39b2a16
--- /dev/null
+++ b/src/org/simalliance/openmobileapi/service/Util.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2011, 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 org.simalliance.openmobileapi.service;
+
+public class Util {
+
+ public static final byte END = -1;
+
+ public static byte[] mergeBytes(byte[] array1, byte[] array2) {
+ byte[] data = new byte[array1.length + array2.length];
+ int i = 0;
+ for (; i < array1.length; i++)
+ data[i] = array1[i];
+ for (int j = 0; j < array2.length; j++)
+ data[j + i] = array2[j];
+ return data;
+ }
+
+ public static byte[] getMid(byte[] array, int start, int length) {
+ byte[] data = new byte[length];
+ System.arraycopy(array, start, data, 0, length);
+ return data;
+ }
+
+ public static String bytesToString(byte[] bytes) {
+ if(bytes == null)
+ return "";
+ StringBuffer sb = new StringBuffer();
+ for (byte b : bytes) {
+ sb.append(String.format("%02x ", b & 0xFF));
+ }
+ String str = sb.toString();
+ if (str.length() > 0) {
+ str = str.substring(0, str.length() - 1);
+ }
+ return str;
+ }
+
+ public static String bytesToString(byte[] array,int offset,int length,
+ String prefix) {
+ if (array==null) return null;
+ if (length==-1) length=array.length-offset;
+
+ StringBuffer buffer=new StringBuffer();
+ for (int ind=offset;ind<offset+length;ind++)
+ buffer.append(prefix+Integer.toHexString(
+ 0x100+(array[ind] & 0xFF)).substring(1));
+ return buffer.toString();
+ }
+
+}
diff --git a/src/org/simalliance/openmobileapi/service/security/AccessControlEnforcer.java b/src/org/simalliance/openmobileapi/service/security/AccessControlEnforcer.java
new file mode 100644
index 0000000..e45081c
--- /dev/null
+++ b/src/org/simalliance/openmobileapi/service/security/AccessControlEnforcer.java
@@ -0,0 +1,512 @@
+/*
+ * Copyright 2012 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 org.simalliance.openmobileapi.service.security;
+
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.Signature;
+import android.os.Build;
+import android.os.SystemProperties;
+import android.util.Log;
+
+import java.io.ByteArrayInputStream;
+import java.io.PrintWriter;
+import java.security.AccessControlException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.MissingResourceException;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import org.simalliance.openmobileapi.service.CardException;
+import org.simalliance.openmobileapi.service.IChannel;
+import org.simalliance.openmobileapi.service.ISmartcardServiceCallback;
+import org.simalliance.openmobileapi.service.ITerminal;
+import org.simalliance.openmobileapi.service.SmartcardService;
+import org.simalliance.openmobileapi.service.Util;
+import org.simalliance.openmobileapi.service.security.ChannelAccess.ACCESS;
+import org.simalliance.openmobileapi.service.security.ara.AraController;
+
+import org.simalliance.openmobileapi.service.security.arf.ArfController;
+
+
+public class AccessControlEnforcer {
+
+ private PackageManager mPackageManager = null;
+
+ private AraController mAraController = null;
+ private boolean mUseAra = true;
+
+ private ArfController mArfController = null;
+ private boolean mUseArf = false;
+
+ private AccessRuleCache mAccessRuleCache = null;
+ private boolean mRulesRead = false;
+
+ private ITerminal mTerminal = null;
+
+ private ChannelAccess mInitialChannelAccess = new ChannelAccess();
+ private boolean mFullAccess = false;
+
+ protected boolean[] mNfcEventFlags = null;
+
+ private final String ACCESS_CONTROL_ENFORCER = "Access Control Enforcer: ";
+
+ public AccessControlEnforcer( ITerminal terminal ) {
+
+ mTerminal = terminal;
+ mAccessRuleCache = new AccessRuleCache();
+ }
+
+ public PackageManager getPackageManager() {
+ return mPackageManager;
+ }
+
+ public void setPackageManager(PackageManager packageManager) {
+ this.mPackageManager = packageManager;
+ }
+
+ public ITerminal getTerminal(){
+ return mTerminal;
+ }
+
+ public AccessRuleCache getAccessRuleCache(){
+ return mAccessRuleCache;
+ }
+
+ public static byte[] getDefaultAccessControlAid(){
+ return AraController.getAraMAid();
+ }
+
+ public synchronized void reset() {
+ // Destroy any previous Controler
+ // in order to reset the ACE
+ Log.i(SmartcardService._TAG, "Reset the ACE for terminal:" + mTerminal.getName());
+ mAraController = null;
+ mArfController = null;
+ }
+
+ public synchronized boolean initialize(boolean loadAtStartup, ISmartcardServiceCallback callback) {
+ try {
+
+ boolean status = true;
+ String denyMsg = "";
+ // allow access to set up access control for a channel
+ mInitialChannelAccess.setApduAccess(ChannelAccess.ACCESS.ALLOWED);
+ mInitialChannelAccess.setNFCEventAccess(ChannelAccess.ACCESS.ALLOWED);
+ mInitialChannelAccess.setAccess(ChannelAccess.ACCESS.ALLOWED, "");
+
+ readSecurityProfile();
+
+ if(!mTerminal.getName().startsWith(SmartcardService._UICC_TERMINAL)) {
+ // When SE is not the UICC then it's allowed to grant full access if no
+ // rules can be retreived.
+ mFullAccess = true;
+ }
+
+ // 1 - Let's try to use ARA
+ if( mUseAra && mAraController == null)
+ mAraController = new AraController(this);
+
+ if( mUseAra && mAraController != null ){
+ try {
+ mAraController.initialize(loadAtStartup, callback);
+ // disable other access methods
+
+ Log.i(SmartcardService._TAG, "ARA applet is used for:" + mTerminal.getName());
+ mUseArf = false;
+ mFullAccess = false;
+
+ } catch( Exception e ) {
+
+ // ARA cannot be used since we got an exception during initialization
+ mUseAra = false;
+ denyMsg = e.getLocalizedMessage();
+
+ if( e instanceof MissingResourceException ) {
+ if(mTerminal.getName().startsWith(SmartcardService._UICC_TERMINAL)) {
+ // If the SE is a UICC then a possible explanation could simply
+ // be due to the fact that the UICC is old and doesn't
+ // support logical channel (and is not compliant with GP spec).
+ // in this case we should simply act as if no ARA was available
+ Log.w(SmartcardService._TAG, "Got MissingResourceException: Does the UICC support logical channel?");
+ Log.w(SmartcardService._TAG, "Full message: " + e.getMessage());
+ } else {
+ // If the SE is not a UICC then this exception means that something
+ // wrong has occured!
+ throw new MissingResourceException( e.getMessage(), "", "");
+ }
+ } else if( mAraController.isNoSuchElement() ) {
+ Log.i(SmartcardService._TAG, "No ARA applet found in: " + mTerminal.getName());
+ } else {
+ // ARA is available but doesn't work properly.
+ // We are going to disable everything per security req.
+ Log.i(SmartcardService._TAG, "AccessControlEnforcer - Problem accessing ARA, Access DENIED. " + e.getLocalizedMessage());
+
+ // access is denied for any terminal if exception during accessing ARA has any other reason.
+ mUseArf = false;
+ mFullAccess = false;
+ status = false;
+ }
+ }
+ }
+
+ // 2 - Let's try to use ARF since ARA cannot be used
+ if(mUseArf && !mTerminal.getName().startsWith(SmartcardService._UICC_TERMINAL)) {
+ Log.i(SmartcardService._TAG, "Disable ARF for terminal: " + mTerminal.getName() + " (ARF is only available for UICC)");
+ mUseArf = false; // Arf is only supproted on UICC
+ }
+
+ if( mUseArf && mArfController == null)
+ mArfController = new ArfController(this);
+
+ if( mUseArf && mArfController != null) {
+ try {
+ mArfController.initialize(callback);
+ // disable other access methods
+ Log.i(SmartcardService._TAG, "ARF rules are used for:" + mTerminal.getName());
+ mFullAccess = false;
+ } catch( Exception e ) {
+ // ARF cannot be used since we got an exception
+ mUseArf = false;
+ status = false;
+ denyMsg = e.getLocalizedMessage();
+ Log.e(SmartcardService._TAG, e.getMessage() );
+ }
+ }
+
+ /* 3 - Let's grant full access since neither ARA nor ARF can be used */
+ if(mFullAccess) {
+ mInitialChannelAccess.setApduAccess(ChannelAccess.ACCESS.ALLOWED);
+ mInitialChannelAccess.setNFCEventAccess(ChannelAccess.ACCESS.ALLOWED);
+ mInitialChannelAccess.setAccess(ChannelAccess.ACCESS.ALLOWED, "");
+
+ Log.i(SmartcardService._TAG, "Full access granted for:" + mTerminal.getName());
+ }
+
+ /* 4 - Let's block everything since neither ARA, ARF or fullaccess can be used */
+ if(!mUseArf && !mUseAra && !mFullAccess) {
+ mInitialChannelAccess.setApduAccess(ChannelAccess.ACCESS.DENIED);
+ mInitialChannelAccess.setNFCEventAccess(ChannelAccess.ACCESS.DENIED);
+ mInitialChannelAccess.setAccess(ChannelAccess.ACCESS.DENIED, denyMsg);
+
+ Log.i(SmartcardService._TAG, "Deny any access to:" + mTerminal.getName());
+ }
+ mRulesRead = status;
+ return status;
+ } finally {
+
+ }
+ }
+
+ public static Certificate decodeCertificate(byte[] certData) throws CertificateException {
+ CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
+ X509Certificate cert = (X509Certificate) certFactory
+ .generateCertificate(new ByteArrayInputStream(certData));
+
+ return cert;
+ }
+
+ public synchronized void checkCommand(IChannel channel, byte[] command) {
+
+ ChannelAccess ca = channel.getChannelAccess();
+ if (ca == null) {
+
+ throw new AccessControlException(ACCESS_CONTROL_ENFORCER + "Channel access not set");
+ }
+
+ String reason = ca.getReason();
+ if (reason.length() == 0) {
+ reason = "Command not allowed!";
+ }
+
+ if (ca.getAccess() != ACCESS.ALLOWED ) {
+
+ throw new AccessControlException(ACCESS_CONTROL_ENFORCER + reason);
+ }
+ if (ca.isUseApduFilter()) {
+ ApduFilter[] accessConditions = ca.getApduFilter();
+ if (accessConditions == null || accessConditions.length == 0) {
+
+ throw new AccessControlException(ACCESS_CONTROL_ENFORCER + "Access Rule not available: " + reason);
+ }
+ for (ApduFilter ac : accessConditions) {
+ if (CommandApdu.compareHeaders(command, ac.getMask(), ac.getApdu())) {
+
+ return;
+ }
+ }
+
+ throw new AccessControlException(ACCESS_CONTROL_ENFORCER + "Access Rule does not match: " + reason);
+ }
+ if (ca.getApduAccess() == ChannelAccess.ACCESS.ALLOWED) {
+
+ return;
+ } else {
+
+ throw new AccessControlException(ACCESS_CONTROL_ENFORCER + "APDU access NOT allowed" );
+ }
+ }
+
+ public ChannelAccess setUpChannelAccess(
+ byte[] aid,
+ String packageName,
+ ISmartcardServiceCallback callback) {
+ ChannelAccess channelAccess = null;
+
+ // check result of channel access during initialization procedure
+ if( mInitialChannelAccess.getAccess() == ChannelAccess.ACCESS.DENIED ){
+ throw new AccessControlException( ACCESS_CONTROL_ENFORCER + "access denied: " + mInitialChannelAccess.getReason() );
+ }
+ // this is the new GP Access Control Enforcer implementation
+ if( mUseAra || mUseArf ){
+
+ try {
+ channelAccess = internal_setUpChannelAccess(aid, packageName, callback);
+ } catch( Exception e ) {
+ if( e instanceof MissingResourceException ) {
+ throw new MissingResourceException( ACCESS_CONTROL_ENFORCER + e.getMessage(), "", "");
+ } else {
+ // access is denied for any terminal if exception during accessing ARA has any other reason.
+ throw new AccessControlException( ACCESS_CONTROL_ENFORCER + "access denied: " + e.getMessage() );
+ }
+ }
+ }
+
+ if( channelAccess == null || // precautionary check
+ (channelAccess.getApduAccess() != ChannelAccess.ACCESS.ALLOWED &&
+ channelAccess.isUseApduFilter() == false)) {
+
+ if( this.mFullAccess == true ){
+ // if full access is set then we reuse the initial channel access,
+ // since we got so far it allows everything with a descriptive reason.
+ channelAccess = mInitialChannelAccess;
+ } else {
+ throw new AccessControlException( ACCESS_CONTROL_ENFORCER + "no APDU access allowed!" );
+ }
+ }
+
+ channelAccess.setPackageName(packageName);
+
+ return channelAccess.clone();
+ }
+
+ private synchronized ChannelAccess internal_setUpChannelAccess(byte[] aid, String packageName,
+ ISmartcardServiceCallback callback) {
+
+ ChannelAccess channelAccess = new ChannelAccess();
+ if (packageName == null || packageName.isEmpty()) {
+ throw new AccessControlException("package names must be specified");
+ }
+ if (aid == null || aid.length == 0) {
+ throw new AccessControlException("AID must be specified");
+ }
+ if (aid.length < 5 || aid.length > 16) {
+ throw new AccessControlException("AID has an invalid length");
+ }
+
+ try {
+ // estimate SHA-1 hash value of the device application's certificate.
+ Certificate[] appCerts = getAPPCerts(packageName);
+
+ // APP certificates must be available => otherwise Exception
+ if (appCerts == null || appCerts.length == 0) {
+ throw new AccessControlException("Application certificates are invalid or do not exist.");
+ }
+
+
+ channelAccess = getAccessRule(aid, appCerts, callback );
+
+ } catch (Throwable exp) {
+
+
+ throw new AccessControlException(exp.getMessage());
+ }
+
+ return channelAccess;
+ }
+
+ public ChannelAccess getAccessRule( byte[] aid, Certificate[] appCerts, ISmartcardServiceCallback callback ) throws AccessControlException, CardException, CertificateEncodingException {
+
+ ChannelAccess channelAccess = null;
+
+ // if read all is true get rule from cache.
+ if( mRulesRead ){
+ // get rules from internal storage
+ channelAccess = mAccessRuleCache.findAccessRule( aid, appCerts );
+ }
+
+ // if no rule was found return an empty access rule
+ // with all access denied.
+ if( channelAccess == null ){
+ channelAccess = new ChannelAccess();
+ channelAccess.setAccess(ChannelAccess.ACCESS.DENIED, "no access rule found!" );
+ channelAccess.setApduAccess(ChannelAccess.ACCESS.DENIED);
+ channelAccess.setNFCEventAccess(ChannelAccess.ACCESS.DENIED);
+ }
+ return channelAccess;
+ }
+
+
+ /**
+ * Returns Certificate chain for one package.
+ *
+ * @param packageName
+ * @return
+ * @throws CertificateException
+ * @throws NoSuchAlgorithmException
+ * @throws AccessControlException
+ * @throws CardException
+ */
+ public Certificate[] getAPPCerts(String packageName)
+ throws CertificateException, NoSuchAlgorithmException, AccessControlException {
+
+ if(packageName == null || packageName.length() == 0)
+ throw new AccessControlException("Package Name not defined");
+
+ PackageInfo foundPkgInfo;
+
+ try {
+ foundPkgInfo = mPackageManager.getPackageInfo(packageName,
+ PackageManager.GET_SIGNATURES);
+ } catch (NameNotFoundException ne) {
+ throw new AccessControlException("Package does not exist");
+ }
+
+ if (foundPkgInfo == null) {
+ throw new AccessControlException("Package does not exist");
+ }
+
+ ArrayList<Certificate> appCerts = new ArrayList<Certificate>();
+
+ for (Signature signature : foundPkgInfo.signatures) {
+ appCerts.add(decodeCertificate(signature.toByteArray()));
+ }
+ return appCerts.toArray(new Certificate[appCerts.size()]);
+ }
+
+ public static byte[] getAppCertHash(Certificate appCert) throws CertificateEncodingException
+ {
+ /**
+ * Note: This loop is needed as workaround for a bug in Android 2.3.
+ * After a failed certificate verification in a previous step the
+ * MessageDigest.getInstance("SHA") call will fail with the
+ * AlgorithmNotSupported exception. But a second try will normally
+ * succeed.
+ */
+ MessageDigest md = null;
+ for (int i = 0; i < 10; i++) {
+ try {
+ md = MessageDigest.getInstance("SHA");
+ break;
+ } catch (Exception e) {
+ }
+ }
+ if (md == null) {
+ throw new AccessControlException("Hash can not be computed");
+ }
+ return md.digest(appCert.getEncoded());
+ }
+
+ public synchronized boolean[] isNFCEventAllowed(
+ byte[] aid,
+ String[] packageNames,
+ ISmartcardServiceCallback callback)
+ throws CardException
+ {
+ if( mUseAra || mUseArf ){
+ return internal_isNFCEventAllowed(aid, packageNames, callback);
+ } else {
+ // 2012-09-27
+ // if ARA and ARF is not available and terminal DOES NOT belong to a UICC -> mFullAccess is true
+ // if ARA and ARF is not available and terminal belongs to a UICC -> mFullAccess is false
+ boolean[] ret = new boolean[packageNames.length];
+ for( int i = 0; i < ret.length; i++ ){
+ ret[i] = this.mFullAccess;
+ }
+ return ret;
+ }
+ }
+
+ private synchronized boolean[] internal_isNFCEventAllowed(byte[] aid,
+ String[] packageNames,
+ ISmartcardServiceCallback callback)
+ throws CardException
+ {
+ // the NFC Event Flags boolean array is created and filled in internal_enableAccessConditions.
+ mNfcEventFlags = new boolean[packageNames.length];
+ int i=0;
+ ChannelAccess channelAccess = null;
+ for( String packageName : packageNames ) {
+ // estimate SHA-1 hash value of the device application's certificate.
+ Certificate[] appCerts;
+ try {
+ appCerts = getAPPCerts(packageName);
+
+ // APP certificates must be available => otherwise Exception
+ if (appCerts == null || appCerts.length == 0) {
+ throw new AccessControlException("Application certificates are invalid or do not exist.");
+ }
+
+ channelAccess = getAccessRule(aid, appCerts, callback);
+ mNfcEventFlags[i] = (channelAccess.getNFCEventAccess() == ChannelAccess.ACCESS.ALLOWED);
+
+ } catch (Exception e) {
+ Log.w(SmartcardService._TAG, " Access Rules for NFC: " + e.getLocalizedMessage());
+ mNfcEventFlags[i] = false;
+ }
+ i++;
+ }
+ return mNfcEventFlags;
+ }
+
+
+ public void dump(PrintWriter writer, String prefix) {
+ writer.println(prefix + SmartcardService._TAG + ":");
+ prefix += " ";
+
+ writer.println(prefix + "mUseArf: " + mUseArf);
+ writer.println(prefix + "mUseAra: " + mUseAra);
+ writer.println(prefix + "mInitialChannelAccess:");
+ writer.println(prefix + " " + mInitialChannelAccess.toString());
+ writer.println();
+
+ /* Dump the access rule cache */
+ if(mAccessRuleCache != null) mAccessRuleCache.dump(writer, prefix);
+ }
+
+ private void readSecurityProfile() {
+ if(!Build.IS_DEBUGGABLE) {
+ mUseArf = true;
+ mUseAra = true;
+ mFullAccess = false; // Per default we don't grant full access.
+ } else {
+ String level = SystemProperties.get("service.seek", "useara usearf");
+ level = SystemProperties.get("persist.service.seek", level);
+
+ if(level.contains("usearf")) mUseArf = true; else mUseArf = false;
+ if(level.contains("useara")) mUseAra = true; else mUseAra = false;
+ if(level.contains("fullaccess")) mFullAccess = true; else mFullAccess = false;
+ }
+ Log.i(SmartcardService._TAG, "Allowed ACE mode: ara=" + mUseAra + " arf=" + mUseArf + " fullaccess=" + mFullAccess );
+ }
+
+}
diff --git a/src/org/simalliance/openmobileapi/service/security/AccessFilterUtil.java b/src/org/simalliance/openmobileapi/service/security/AccessFilterUtil.java
new file mode 100644
index 0000000..e190e8a
--- /dev/null
+++ b/src/org/simalliance/openmobileapi/service/security/AccessFilterUtil.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2012 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 org.simalliance.openmobileapi.service.security;
+
+import org.simalliance.openmobileapi.service.Util;
+
+
+
+public class AccessFilterUtil {
+
+ public static ApduFilter[] parseAccessConditions(byte[] accessConditions) {
+ if (accessConditions.length == 0) {
+ return new ApduFilter[0];
+ }
+
+ if ((accessConditions.length % 8) != 0) {
+ throw new IllegalArgumentException("Access Conditions must have a length of 8 bytes");
+ }
+
+ int numOfACs = accessConditions.length / 8;
+ ApduFilter[] acs = new ApduFilter[numOfACs];
+ int offset = 0;
+ int length = 8;
+ int index = 0;
+ while ((offset + length) <= accessConditions.length && length != 0) {
+ acs[index] = new ApduFilter(Util.getMid(accessConditions, offset, length));
+ offset += length;
+ index++;
+ }
+ return acs;
+ }
+}
diff --git a/src/org/simalliance/openmobileapi/service/security/AccessRuleCache.java b/src/org/simalliance/openmobileapi/service/security/AccessRuleCache.java
new file mode 100644
index 0000000..2a86b66
--- /dev/null
+++ b/src/org/simalliance/openmobileapi/service/security/AccessRuleCache.java
@@ -0,0 +1,421 @@
+/*
+ * Copyright 2012 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 org.simalliance.openmobileapi.service.security;
+
+import java.io.PrintWriter;
+import java.security.AccessControlException;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateEncodingException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+import org.simalliance.openmobileapi.service.SmartcardService;
+import org.simalliance.openmobileapi.service.Util;
+import org.simalliance.openmobileapi.service.security.gpac.dataobjects.AID_REF_DO;
+import org.simalliance.openmobileapi.service.security.gpac.dataobjects.AR_DO;
+import org.simalliance.openmobileapi.service.security.gpac.dataobjects.Hash_REF_DO;
+import org.simalliance.openmobileapi.service.security.gpac.dataobjects.REF_DO;
+
+import android.util.Log;
+
+
+public class AccessRuleCache {
+ // Previous "RefreshTag"
+ // 2012-09-25
+ // the refresh tag has to be valid as long as AxxController is valid
+ // a pure static element would cause that rules are not read any longer once the AxxController is recreated.
+ private byte[] mRefreshTag=null;
+
+
+ private Map<REF_DO, ChannelAccess> mRuleCache = new HashMap<REF_DO, ChannelAccess>();
+
+ /**
+ * Clears access rule cache and refresh tag.
+ */
+ public void reset(){
+ mRefreshTag = null;
+ mRuleCache.clear();
+ }
+
+ /**
+ * Clears access rule cache only.
+ */
+ public void clearCache(){
+ mRuleCache.clear();
+ }
+
+ public ChannelAccess put(REF_DO ref_do_key, AR_DO ar_do) {
+
+ ChannelAccess channelAccess = mapArDo2ChannelAccess( ar_do );
+ this.mRuleCache.put(ref_do_key, channelAccess);
+ return channelAccess;
+ }
+
+ public void putWithMerge( REF_DO ref_do, AR_DO ar_do ) {
+
+ ChannelAccess channelAccess = mapArDo2ChannelAccess( ar_do );
+ putWithMerge( ref_do, channelAccess );
+ }
+
+ public void putWithMerge( REF_DO ref_do, ChannelAccess channelAccess ) {
+
+ if( mRuleCache.containsKey(ref_do)){
+ ChannelAccess ca = mRuleCache.get(ref_do);
+ Log.v(SmartcardService._TAG, "Access Rule with " + ref_do.toString() + " already exists.");
+
+ // if new ac condition is more restrictive then use their settings
+
+ // if new rule says NFC is denied then use it
+ // if current rule as undefined NFC rule then use setting of new rule.
+ // current NFC new NFC resulting NFC
+ // UNDEFINED x x
+ // ALLOWED !DENIED ALLOWED
+ // ALLOWED DENIED DENIED
+ // DENIED !DENIED DENIED
+ // DENEID DENIED DENIED
+ if( channelAccess.getNFCEventAccess() == ChannelAccess.ACCESS.DENIED ||
+ ca.getNFCEventAccess() == ChannelAccess.ACCESS.UNDEFINED ) {
+ ca.setNFCEventAccess(channelAccess.getNFCEventAccess());
+ }
+
+ // if new rule says APUD is denied then use it
+ // if current rule as undefined APDU rule then use setting of new rule.
+ // current APDU new APDU resulting APDU
+ // UNDEFINED x x
+ // ALLOWED !DENIED ALLOWED
+ // ALLOWED DENIED DENIED
+ // DENIED !DENIED DENIED
+ // DENEID DENIED DENIED
+ if( channelAccess.getApduAccess() == ChannelAccess.ACCESS.DENIED ||
+ ca.getApduAccess() == ChannelAccess.ACCESS.UNDEFINED ) {
+ ca.setApduAccess(channelAccess.getApduAccess());
+ }
+
+
+ // put APDU filter together if resulting APDU access is allowed.
+ if( ca.getApduAccess() == ChannelAccess.ACCESS.ALLOWED ){
+ if( channelAccess.isUseApduFilter() ){
+ ca.setUseApduFilter(true);
+ ApduFilter[] filter = ca.getApduFilter();
+ ApduFilter[] filter2 = channelAccess.getApduFilter();
+ if( filter == null || filter.length == 0 ){
+ ca.setApduFilter(filter2);
+ } else if( filter2 == null || filter2.length == 0){
+ ca.setApduFilter(filter);
+ } else {
+ ApduFilter[] sum = new ApduFilter[filter.length + filter2.length];
+ int i = 0;
+ for( ApduFilter f : filter ){
+ sum[i++] = f;
+ }
+ for( ApduFilter f : filter2 ){
+ sum[i++] = f;
+ }
+ ca.setApduFilter(sum);
+ }
+ }
+ } else {
+ // if APDU access is not allowed the remove also all apdu filter
+ ca.setUseApduFilter(false);
+ ca.setApduFilter(null);
+ }
+ Log.v(SmartcardService._TAG, "Merged Access Rule: " + ca.toString());
+ return;
+ }
+ mRuleCache.put(ref_do, channelAccess);
+ }
+
+
+ public ChannelAccess findAccessRule( byte[] aid, Certificate[] appCerts) throws AccessControlException {
+
+
+
+ // TODO: check difference between DeviceCertHash and Certificate Chain (EndEntityCertHash, IntermediateCertHash (1..n), RootCertHash)
+ // The DeviceCertificate is equal to the EndEntityCertificate.
+ // The android systems seems always to deliver only the EndEntityCertificate, but this seems not to be sure.
+ // thats why we implement the whole chain.
+
+ AID_REF_DO aid_ref_do = getAidRefDo(aid);
+ Hash_REF_DO hash_ref_do = null;
+ REF_DO ref_do = null;
+
+ // Search Rule A ( Certificate(s); AID )
+ // walk through certificate chain.
+ for( Certificate appCert : appCerts ){
+
+ try {
+ hash_ref_do = new Hash_REF_DO(AccessControlEnforcer.getAppCertHash(appCert));
+ ref_do = new REF_DO(aid_ref_do, hash_ref_do);
+
+ if( mRuleCache.containsKey( ref_do ) ){
+ return mRuleCache.get( ref_do );
+ }
+ } catch (CertificateEncodingException e) {
+ throw new AccessControlException("Problem with Application Certificate.");
+ }
+ }
+ // no rule found,
+ // now we have to check if the given AID
+ // is used together with another specific hash value (another device application)
+ if( searchForRulesWithSpecificAidButOtherHash(aid_ref_do) != null ){
+ Log.v(SmartcardService._TAG, "Conflict Resolution Case A returning access rule \'NEVER\'.");
+ ChannelAccess ca = new ChannelAccess();
+ ca.setApduAccess(ChannelAccess.ACCESS.DENIED);
+ ca.setAccess(ChannelAccess.ACCESS.DENIED, "AID has a specific access rule with a different hash. (Case A)");
+ ca.setNFCEventAccess(ChannelAccess.ACCESS.DENIED);
+ return ca;
+ }
+
+
+ // SearchRule B ( <AllDeviceApplications>; AID)
+ aid_ref_do = getAidRefDo(aid);
+ hash_ref_do = new Hash_REF_DO(); // empty hash ref
+ ref_do = new REF_DO(aid_ref_do, hash_ref_do);
+
+ if( mRuleCache.containsKey( ref_do ) ){
+ return mRuleCache.get( ref_do );
+ }
+
+ // Search Rule C ( Certificate(s); <AllSEApplications> )
+ aid_ref_do = new AID_REF_DO(AID_REF_DO._TAG);
+ for( Certificate appCert : appCerts ){
+ try {
+ hash_ref_do = new Hash_REF_DO(AccessControlEnforcer.getAppCertHash(appCert));
+ ref_do = new REF_DO(aid_ref_do, hash_ref_do);
+
+ if( mRuleCache.containsKey( ref_do ) ){
+ return mRuleCache.get( ref_do );
+ }
+ } catch (CertificateEncodingException e) {
+ throw new AccessControlException("Problem with Application Certificate.");
+ }
+ }
+
+ // no rule found,
+ // now we have to check if the all AID DO
+ // is used together with another Hash
+ if( this.searchForRulesWithAllAidButOtherHash() != null ){
+ Log.v(SmartcardService._TAG, "Conflict Resolution Case C returning access rule \'NEVER\'.");
+ ChannelAccess ca = new ChannelAccess();
+ ca.setApduAccess(ChannelAccess.ACCESS.DENIED);
+ ca.setAccess(ChannelAccess.ACCESS.DENIED, "An access rule with a different hash and all AIDs was found. (Case C)");
+ ca.setNFCEventAccess(ChannelAccess.ACCESS.DENIED);
+ return ca;
+ }
+
+
+ // SearchRule D ( <AllDeviceApplications>; <AllSEApplications>)
+ aid_ref_do = new AID_REF_DO(AID_REF_DO._TAG);
+ hash_ref_do = new Hash_REF_DO();
+ ref_do = new REF_DO(aid_ref_do, hash_ref_do);
+
+ if( mRuleCache.containsKey( ref_do ) ){
+ return mRuleCache.get( ref_do );
+ }
+ return null;
+ }
+
+ public static AID_REF_DO getAidRefDo( byte[] aid ){
+ AID_REF_DO aid_ref_do = null;
+ byte[] defaultAid = new byte[]{ 0x00, 0x00, 0x00, 0x00, 0x00 }; // this is the placeholder for the default aid.
+
+ if( aid == null || Arrays.equals( aid, defaultAid )){
+ aid_ref_do = new AID_REF_DO(AID_REF_DO._TAG_DEFAULT_APPLICATION);
+ } else {
+ aid_ref_do = new AID_REF_DO(AID_REF_DO._TAG, aid);
+ }
+
+ return aid_ref_do;
+ }
+
+ public static REF_DO buildHashMapKey( byte[] aid, byte[] appCertHash ){
+ // Build key
+ Hash_REF_DO hash_ref_do = new Hash_REF_DO(appCertHash) ;
+ REF_DO ref_do = new REF_DO(getAidRefDo(aid), hash_ref_do);
+
+ return ref_do;
+ }
+
+
+
+ /*
+ * The GP_SE_AC spec says:
+ * According to the rule conflict resolution process defined in section 3.2.1, if a specific rule exists
+ * that associates another device application with the SE application identified by AID (e.g. there is
+ * a rule associating AID with the hash of another device application), then the ARA-M (when
+ * using GET DATA [Specific]) or the Access Control Enforcer (when using GET DATA [All]) shall
+ * set the result of SearchRuleFor(DeviceApplicationCertificate, AID) to NEVER (i.e. precedence
+ * of specific rules over generic rules)
+ *
+ * In own words:
+ * Search the rules cache for a rule that contains the wanted AID but with another specific Hash value.
+ */
+ private REF_DO searchForRulesWithSpecificAidButOtherHash(AID_REF_DO aid_ref_do) {
+
+ // AID has to be specific
+ if( aid_ref_do == null ){
+ return null;
+ }
+ // C0 00 is specific -> default AID
+ // 4F 00 is NOT specific -> all AIDs
+ if( aid_ref_do.getTag() == AID_REF_DO._TAG &&
+ (aid_ref_do.getAid() == null || aid_ref_do.getAid().length == 0)){
+ return null;
+ }
+
+ Set<REF_DO> keySet = mRuleCache.keySet();
+ Iterator<REF_DO> iter = keySet.iterator();
+ while(iter.hasNext()){
+ REF_DO ref_do = iter.next();
+ if( aid_ref_do.equals(ref_do.getAidDo())) {
+ if( ref_do.getHashDo() != null &&
+ ref_do.getHashDo().getHash() != null &&
+ ref_do.getHashDo().getHash().length > 0 ){
+ // this ref_do contains the search AID and a specific hash value
+ return ref_do;
+ }
+ }
+ }
+ return null;
+ }
+
+ /*
+ * The GP_SE_AC spec says:
+ * According to the rule conflict resolution process defined in section 3.2.1, if a specific rule exists
+ * that associates another device application with the SE application identified by AID (e.g. there is
+ * a rule associating AID with the hash of another device application), then the ARA-M (when
+ * using GET DATA [Specific]) or the Access Control Enforcer (when using GET DATA [All]) shall
+ * set the result of SearchRuleFor(DeviceApplicationCertificate, AID) to NEVER (i.e. precedence
+ * of specific rules over generic rules)
+ *
+ * In own words:
+ * Search the rules cache for a rule that contains a Hash with an all SE AID (4F 00).
+ */
+ private Object searchForRulesWithAllAidButOtherHash() {
+
+ AID_REF_DO aid_ref_do = new AID_REF_DO(AID_REF_DO._TAG);
+
+ Set<REF_DO> keySet = mRuleCache.keySet();
+ Iterator<REF_DO> iter = keySet.iterator();
+ while(iter.hasNext()){
+ REF_DO ref_do = iter.next();
+ if( aid_ref_do.equals(ref_do.getAidDo())){
+ // aid tlv is equal
+ if( ref_do.getHashDo() != null &&
+ (ref_do.getHashDo().getHash() != null && ref_do.getHashDo().getHash().length > 0)) {
+ // return ref_do if
+ // a HASH value is available and has a length > 0 (SHA1_LEN)
+ return ref_do;
+ }
+ }
+ }
+ return null;
+ }
+
+ public static ChannelAccess mapArDo2ChannelAccess(AR_DO ar_do ){
+ ChannelAccess channelAccess = new ChannelAccess();
+
+ // check apdu access allowance
+ if( ar_do.getApduArDo() != null ){
+ // first if there is a rule for access, reset the general deny flag.
+ channelAccess.setAccess(ChannelAccess.ACCESS.ALLOWED, "");
+ channelAccess.setUseApduFilter(false);
+
+ if( ar_do.getApduArDo().isApduAllowed() ){
+ // check the apdu filter
+ ArrayList<byte[]> apduHeaders = ar_do.getApduArDo().getApduHeaderList();
+ ArrayList<byte[]> filterMasks = ar_do.getApduArDo().getFilterMaskList();
+ if( apduHeaders != null &&
+ filterMasks != null &&
+ apduHeaders.size() > 0 &&
+ apduHeaders.size() == filterMasks.size() ){
+
+ ApduFilter[] accessConditions = new ApduFilter[apduHeaders.size()];
+ for( int i = 0; i < apduHeaders.size(); i++){
+ accessConditions[i] = new ApduFilter( apduHeaders.get(i), filterMasks.get(i));
+ }
+ channelAccess.setUseApduFilter(true);
+ channelAccess.setApduFilter(accessConditions);
+ } else {
+ // general APDU access
+ channelAccess.setApduAccess(ChannelAccess.ACCESS.ALLOWED);
+ }
+ } else {
+ // apdu access is not allowed at all.
+ channelAccess.setApduAccess(ChannelAccess.ACCESS.DENIED);
+ }
+ } else {
+ channelAccess.setAccess(ChannelAccess.ACCESS.DENIED, "No APDU access rule available.!");
+ }
+
+ // check for NFC Event allowance
+ if( ar_do.getNfcArDo() != null ){
+ channelAccess.setNFCEventAccess(ar_do.getNfcArDo().isNfcAllowed() ? ChannelAccess.ACCESS.ALLOWED : ChannelAccess.ACCESS.DENIED);
+ } else {
+ // GP says that by default NFC should have the same right as for APDU access
+ channelAccess.setNFCEventAccess(channelAccess.getApduAccess());
+ }
+ return channelAccess;
+ }
+
+ public boolean isRefreshTagEqual(byte[] refreshTag ) {
+ if( refreshTag == null || mRefreshTag == null )
+ return false;
+
+ return Arrays.equals(refreshTag,mRefreshTag);
+ }
+
+ public byte[] getRefreshTag() {
+ return mRefreshTag;
+ }
+
+ public void setRefreshTag(byte[] refreshTag) {
+ this.mRefreshTag = refreshTag;
+ }
+
+
+ public void dump(PrintWriter writer, String prefix) {
+ writer.println(prefix + SmartcardService._TAG + ":");
+ prefix += " ";
+
+ /* Dump the refresh tag */
+ writer.print(prefix + "Current refresh tag is: ");
+ if(mRefreshTag == null) writer.print("<null>");
+ else for(byte oneByte: mRefreshTag) writer.printf("%02X:", oneByte);
+ writer.println();
+
+ /* Dump the rules cache */
+ writer.println(prefix + "rules dump:");
+ prefix += " ";
+
+ int i = 0;
+ for (Map.Entry<REF_DO, ChannelAccess> entry : mRuleCache.entrySet()) {
+ i++;
+ writer.print(prefix + "rule " + i + ": ");
+ writer.println(entry.getKey().toString());
+
+ writer.print(prefix + " ->");
+ writer.println(entry.getValue().toString());
+ }
+
+ writer.println();
+ }
+}
diff --git a/src/org/simalliance/openmobileapi/service/security/ApduFilter.java b/src/org/simalliance/openmobileapi/service/security/ApduFilter.java
new file mode 100644
index 0000000..d913d43
--- /dev/null
+++ b/src/org/simalliance/openmobileapi/service/security/ApduFilter.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2012 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 org.simalliance.openmobileapi.service.security;
+
+import org.simalliance.openmobileapi.service.Util;
+
+
+public class ApduFilter {
+
+ protected byte[] mApdu;
+
+ protected byte[] mMask;
+
+ public static final int LENGTH = 8;
+
+ protected ApduFilter() {
+
+ }
+
+ public ApduFilter(byte[] apdu, byte[] mask) {
+ if (apdu.length != 4) {
+ throw new IllegalArgumentException("apdu length must be 4 bytes");
+ }
+ if (mask.length != 4) {
+ throw new IllegalArgumentException("mask length must be 4 bytes");
+ }
+
+ mApdu = apdu;
+ mMask = mask;
+ }
+
+ public ApduFilter clone() {
+ ApduFilter apduFilter = new ApduFilter();
+ apduFilter.setApdu(mApdu.clone());
+ apduFilter.setMask(mMask.clone());
+
+ return apduFilter;
+ }
+
+ public ApduFilter(byte[] apduAndMask) {
+ if (apduAndMask.length != 8) {
+ throw new IllegalArgumentException("filter length must be 8 bytes");
+ }
+
+ mApdu = Util.getMid(apduAndMask, 0, 4);
+ mMask = Util.getMid(apduAndMask, 4, 4);
+ }
+
+ public byte[] getApdu() {
+ return mApdu;
+ }
+
+ public void setApdu(byte[] apdu) {
+ if (apdu.length != 4) {
+ throw new IllegalArgumentException("apdu length must be 4 bytes");
+ }
+ mApdu = apdu;
+ }
+
+ public byte[] getMask() {
+ return mMask;
+ }
+
+ public void setMask(byte[] mask) {
+ if (mask.length != 4) {
+ throw new IllegalArgumentException("mask length must be 4 bytes");
+ }
+ mMask = mask;
+ }
+
+ public byte[] toBytes() {
+ return Util.mergeBytes(mApdu, mMask);
+ }
+
+ @Override
+ public String toString() {
+ return "APDU Filter [apdu=" + Util.bytesToString(mApdu) + ", mask="
+ + Util.bytesToString(mMask) + "]";
+ }
+
+ public int getLength() {
+ return 8;
+ }
+
+}
diff --git a/src/org/simalliance/openmobileapi/service/security/ChannelAccess.java b/src/org/simalliance/openmobileapi/service/security/ChannelAccess.java
new file mode 100644
index 0000000..e72ec22
--- /dev/null
+++ b/src/org/simalliance/openmobileapi/service/security/ChannelAccess.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright 2012 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 org.simalliance.openmobileapi.service.security;
+
+
+public class ChannelAccess {
+
+ public enum ACCESS {
+ ALLOWED, DENIED, UNDEFINED;
+ }
+
+ protected String CHANNEL_ACCESS_TAG = "ChannelAccess";
+
+ protected String mPackageName = "";
+
+ protected ACCESS mAccess = ACCESS.UNDEFINED;
+
+ protected ACCESS mApduAccess = ACCESS.UNDEFINED;
+
+ protected boolean mUseApduFilter = false;
+
+ protected int mCallingPid = 0;
+
+ protected String mReason = "no access by default";
+
+ protected ACCESS mNFCEventAccess = ACCESS.UNDEFINED;
+
+ protected ApduFilter[] mApduFilter = null;
+
+ public ChannelAccess clone(){
+ ChannelAccess ca = new ChannelAccess();
+ ca.setAccess(this.mAccess, this.mReason);
+ ca.setPackageName( this.mPackageName);
+ ca.setApduAccess(this.mApduAccess);
+ ca.setCallingPid(this.mCallingPid);
+ ca.setNFCEventAccess(this.mNFCEventAccess);
+ ca.setUseApduFilter(this.mUseApduFilter);
+ if( this.mApduFilter != null ) {
+ ApduFilter[] apduFilter = new ApduFilter[this.mApduFilter.length];
+ int i = 0;
+ for( ApduFilter filter : mApduFilter ){
+ apduFilter[i++] = filter.clone();
+ }
+ ca.setApduFilter(apduFilter);
+ } else {
+ ca.setApduFilter(null);
+ }
+ return ca;
+ }
+
+ public String getPackageName(){
+ return mPackageName;
+ }
+
+ public void setPackageName( String name ){
+ this.mPackageName = name;
+ }
+
+ public ACCESS getApduAccess() {
+ return mApduAccess;
+ }
+
+ public void setApduAccess(ACCESS apduAccess) {
+ this.mApduAccess = apduAccess;
+ }
+
+
+ public ACCESS getAccess() {
+ return mAccess;
+ }
+
+ public void setAccess(ACCESS access, String reason) {
+ this.mAccess = access;
+ this.mReason = reason;
+ }
+
+ public boolean isUseApduFilter() {
+ return mUseApduFilter;
+ }
+
+ public void setUseApduFilter(boolean useApduFilter) {
+ this.mUseApduFilter = useApduFilter;
+ }
+
+ public void setCallingPid(int callingPid) {
+ this.mCallingPid = callingPid;
+ }
+
+ public int getCallingPid() {
+ return mCallingPid;
+ }
+
+ public String getReason() {
+ return mReason;
+ }
+ public ApduFilter[] getApduFilter() {
+ return mApduFilter;
+ }
+
+ public void setApduFilter(ApduFilter[] accessConditions) {
+ mApduFilter = accessConditions;
+ }
+ public ACCESS getNFCEventAccess() {
+ return mNFCEventAccess;
+ }
+
+ public void setNFCEventAccess(ACCESS access) {
+ this.mNFCEventAccess = access;
+ }
+
+ @Override
+ public String toString(){
+ StringBuilder sb = new StringBuilder();
+ sb.append(this.getClass().getName());
+ sb.append("\n [mPackageName=");
+ sb.append(mPackageName);
+ sb.append(", mAccess=");
+ sb.append(mAccess);
+ sb.append(", mApduAccess=");
+ sb.append(mApduAccess);
+ sb.append(", mUseApduFilter=");
+ sb.append(mUseApduFilter);
+ sb.append(", mApduFilter=");
+ if( mApduFilter != null ){
+ for( ApduFilter f : mApduFilter ){
+ sb.append(f.toString());
+ sb.append(" ");
+ }
+ } else {
+ sb.append("null");
+ }
+ sb.append(", mCallingPid=");
+ sb.append(mCallingPid);
+ sb.append(", mReason=");
+ sb.append(mReason);
+ sb.append(", mNFCEventAllowed=");
+ sb.append(mNFCEventAccess);
+ sb.append("]\n");
+
+ return sb.toString();
+
+ }
+}
diff --git a/src/org/simalliance/openmobileapi/service/security/CommandApdu.java b/src/org/simalliance/openmobileapi/service/security/CommandApdu.java
new file mode 100644
index 0000000..2a9aecb
--- /dev/null
+++ b/src/org/simalliance/openmobileapi/service/security/CommandApdu.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright 2010 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 org.simalliance.openmobileapi.service.security;
+
+public class CommandApdu {
+
+ protected int mCla = 0x00;
+
+ protected int mIns = 0x00;
+
+ protected int mP1 = 0x00;
+
+ protected int mP2 = 0x00;
+
+ protected int mLc = 0x00;
+
+ protected byte[] mData = new byte[0];
+
+ protected int mLe = 0x00;
+
+ protected boolean mLeUsed = false;
+
+ public CommandApdu(int cla, int ins, int p1, int p2) {
+ mCla = cla;
+ mIns = ins;
+ mP1 = p1;
+ mP2 = p2;
+ }
+
+ public CommandApdu() {
+
+ }
+
+ public CommandApdu(int cla, int ins, int p1, int p2, byte[] data) {
+ mCla = cla;
+ mIns = ins;
+ mLc = data.length;
+ mP1 = p1;
+ mP2 = p2;
+ mData = data;
+ }
+
+ public CommandApdu(int cla, int ins, int p1, int p2, byte[] data, int le) {
+ mCla = cla;
+ mIns = ins;
+ mLc = data.length;
+ mP1 = p1;
+ mP2 = p2;
+ mData = data;
+ mLe = le;
+ mLeUsed = true;
+ }
+
+ public CommandApdu(int cla, int ins, int p1, int p2, int le) {
+ mCla = cla;
+ mIns = ins;
+ mP1 = p1;
+ mP2 = p2;
+ mLe = le;
+ mLeUsed = true;
+ }
+
+ public void setP1(int p1) {
+ mP1 = p1;
+ }
+
+ public void setP2(int p2) {
+ mP2 = p2;
+ }
+
+ public void setData(byte[] data) {
+ mLc = data.length;
+ mData = data;
+ }
+
+ public void setLe(int le) {
+ mLe = le;
+ mLeUsed = true;
+ }
+
+ public int getP1() {
+ return mP1;
+ }
+
+ public int getP2() {
+ return mP2;
+ }
+
+ public int getLc() {
+ return mLc;
+ }
+
+ public byte[] getData() {
+ return mData;
+ }
+
+ public int getLe() {
+ return mLe;
+ }
+
+ public byte[] toBytes() {
+ int length = 4; // CLA, INS, P1, P2
+ if (mData.length != 0) {
+ length += 1; // LC
+ length += mData.length; // DATA
+ }
+ if (mLeUsed) {
+ length += 1; // LE
+ }
+
+ byte[] apdu = new byte[length];
+
+ int index = 0;
+ apdu[index] = (byte) mCla;
+ index++;
+ apdu[index] = (byte) mIns;
+ index++;
+ apdu[index] = (byte) mP1;
+ index++;
+ apdu[index] = (byte) mP2;
+ index++;
+ if (mData.length != 0) {
+ apdu[index] = (byte) mLc;
+ index++;
+ System.arraycopy(mData, 0, apdu, index, mData.length);
+ index += mData.length;
+ }
+ if (mLeUsed) {
+ apdu[index] += (byte) mLe; // LE
+ }
+
+ return apdu;
+ }
+
+ public static boolean compareHeaders(byte[] header1, byte[] mask, byte[] header2) {
+ if (header1.length < 4 || header2.length < 4) {
+ return false;
+ }
+ byte[] compHeader = new byte[4];
+ compHeader[0] = (byte) (header1[0] & mask[0]);
+ compHeader[1] = (byte) (header1[1] & mask[1]);
+ compHeader[2] = (byte) (header1[2] & mask[2]);
+ compHeader[3] = (byte) (header1[3] & mask[3]);
+
+ if (((byte) compHeader[0] == (byte) header2[0])
+ && ((byte) compHeader[1] == (byte) header2[1])
+ && ((byte) compHeader[2] == (byte) header2[2])
+ && ((byte) compHeader[3] == (byte) header2[3])) {
+ return true;
+ }
+ return false;
+ }
+
+ public CommandApdu clone() {
+ CommandApdu apdu = new CommandApdu();
+ apdu.mCla = mCla;
+ apdu.mIns = mIns;
+ apdu.mP1 = mP1;
+ apdu.mP2 = mP2;
+ apdu.mLc = mLc;
+ apdu.mData = new byte[mData.length];
+ System.arraycopy(mData, 0, apdu.mData, 0, mData.length);
+ apdu.mLe = mLe;
+ apdu.mLeUsed = mLeUsed;
+ return apdu;
+ }
+
+}
diff --git a/src/org/simalliance/openmobileapi/service/security/ResponseApdu.java b/src/org/simalliance/openmobileapi/service/security/ResponseApdu.java
new file mode 100644
index 0000000..0672d4b
--- /dev/null
+++ b/src/org/simalliance/openmobileapi/service/security/ResponseApdu.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2010 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 org.simalliance.openmobileapi.service.security;
+
+import java.security.AccessControlException;
+
+public class ResponseApdu {
+
+ protected int mSw1 = 0x00;
+
+ protected int mSw2 = 0x00;
+
+ protected byte[] mData = new byte[0];
+
+ public ResponseApdu(byte[] respApdu) {
+ if (respApdu.length < 2) {
+ return;
+ }
+ if (respApdu.length > 2) {
+ mData = new byte[respApdu.length - 2];
+ System.arraycopy(respApdu, 0, mData, 0, respApdu.length - 2);
+ }
+ mSw1 = 0x00FF & respApdu[respApdu.length - 2];
+ mSw2 = 0x00FF & respApdu[respApdu.length - 1];
+
+ }
+
+ public int getSW1() {
+ return mSw1;
+ }
+
+ public int getSW2() {
+ return mSw2;
+ }
+
+ public int getSW1SW2() {
+ return (mSw1 << 8) | mSw2;
+ }
+
+ public byte[] getData() {
+ return mData;
+ }
+
+ public void checkLengthAndStatus(int length, int sw1sw2, String message)
+ throws AccessControlException {
+ if (getSW1SW2() != sw1sw2 || mData.length != length) {
+ throw new AccessControlException("ResponseApdu is wrong at " + message);
+ }
+ }
+
+ public void checkLengthAndStatus(int length, int[] sw1sw2List, String message)
+ throws AccessControlException {
+ if (mData.length != length) {
+ throw new AccessControlException("ResponseApdu is wrong at " + message);
+ }
+ for (int sw1sw2 : sw1sw2List) {
+ if (getSW1SW2() == sw1sw2) {
+ return; // sw1sw2 matches => return
+ }
+ }
+ throw new AccessControlException("ResponseApdu is wrong at " + message);
+ }
+
+ public void checkStatus(int[] sw1sw2List, String message) throws AccessControlException {
+ for (int sw1sw2 : sw1sw2List) {
+ if (getSW1SW2() == sw1sw2) {
+ return; // sw1sw2 matches => return
+ }
+ }
+ throw new AccessControlException("ResponseApdu is wrong at " + message);
+ }
+
+ public void checkStatus(int sw1sw2, String message) throws AccessControlException {
+ if (getSW1SW2() != sw1sw2) {
+ throw new AccessControlException("ResponseApdu is wrong at " + message);
+ }
+ }
+
+ public boolean isStatus(int sw1sw2) {
+ if (getSW1SW2() == sw1sw2) {
+ return true;
+ }
+ else {
+ return false;
+ }
+ }
+
+}
diff --git a/src/org/simalliance/openmobileapi/service/security/ara/AccessRuleApplet.java b/src/org/simalliance/openmobileapi/service/security/ara/AccessRuleApplet.java
new file mode 100644
index 0000000..d08bed5
--- /dev/null
+++ b/src/org/simalliance/openmobileapi/service/security/ara/AccessRuleApplet.java
@@ -0,0 +1,221 @@
+/*
+ * Copyright 2010 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 org.simalliance.openmobileapi.service.security.ara;
+
+
+import android.util.Log;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.security.AccessControlException;
+
+import org.simalliance.openmobileapi.service.CardException;
+import org.simalliance.openmobileapi.service.IChannel;
+import org.simalliance.openmobileapi.service.Util;
+import org.simalliance.openmobileapi.service.security.CommandApdu;
+import org.simalliance.openmobileapi.service.security.ResponseApdu;
+import org.simalliance.openmobileapi.service.security.gpac.dataobjects.BerTlv;
+import org.simalliance.openmobileapi.service.security.gpac.dataobjects.ParserException;
+import org.simalliance.openmobileapi.service.security.gpac.dataobjects.Response_DO_Factory;
+import org.simalliance.openmobileapi.service.security.gpac.dataobjects.Response_RefreshTag_DO;
+
+public class AccessRuleApplet {
+
+ final private static String ACCESS_RULE_APPLET_TAG = "AccessRuleApplet";
+
+ final private static int _MAX_LEN = 0xF0; // should be adapted by OEM, this is a defensive value since some devices/modems have problems with Le=0x00 or 0xFF.
+
+ final private static CommandApdu mGetAll = new CommandApdu(0x80, 0xCA, 0xFF, 0x40, _MAX_LEN);
+
+ final private static CommandApdu mGetSpecific = new CommandApdu(0x80, 0xCA, 0xFF, 0x50, _MAX_LEN);
+
+ final private static CommandApdu mGetNext = new CommandApdu(0x80, 0xCA, 0xFF, 0x60, _MAX_LEN);
+
+ final private static CommandApdu mGetRefreshTag = new CommandApdu(0x80, 0xCA, 0xDF, 0x20, _MAX_LEN );
+
+
+ private IChannel mChannel = null;
+
+ public AccessRuleApplet(IChannel channel) {
+ mChannel = channel;
+ }
+
+ public byte[] readSpecificAccessRule( byte[] aid_ref_do ) throws AccessControlException, CardException {
+
+ if( aid_ref_do == null ){
+ throw new AccessControlException("GET DATA (specific): Reference data object must not be null.");
+ }
+
+ ByteArrayOutputStream stream = new ByteArrayOutputStream();
+ int overallLen = 0;
+
+ // send GET DATA (specific)
+ CommandApdu apdu = (CommandApdu) mGetSpecific.clone();
+ apdu.setData(aid_ref_do);
+ ResponseApdu response = send(apdu);
+
+ // OK
+ if( response.isStatus( 0x9000 ) ) {
+ // check if more data has to be fetched
+ BerTlv tempTlv = null;
+ try {
+ tempTlv = BerTlv.decode(response.getData(), 0, false);
+ } catch (ParserException e) {
+ throw new AccessControlException("GET DATA (specific) not successfull. Tlv encoding wrong.");
+ }
+
+ // the first data block contain the length of the TLV + Tag bytes + length bytes.
+ overallLen = tempTlv.getValueLength() + tempTlv.getValueIndex();
+ try {
+ stream.write(response.getData());
+ } catch (IOException e) {
+ throw new AccessControlException("GET DATA (specific) IO problem. " + e.getMessage() );
+ }
+
+ int le;
+ // send subsequent GET DATA (next) commands
+ while( stream.size() < overallLen ){
+ le = overallLen - stream.size();
+ if( le > _MAX_LEN ){
+ le = _MAX_LEN;
+ }
+ // send GET DATA (next)
+ apdu = (CommandApdu) mGetNext.clone();
+ apdu.setLe(le);
+ response = send(apdu);
+
+ // OK
+ if( response.isStatus( 0x9000 ) ){
+ try {
+ stream.write(response.getData());
+ } catch (IOException e) {
+ throw new AccessControlException("GET DATA (next) IO problem. " + e.getMessage() );
+ }
+ } else {
+ throw new AccessControlException( "GET DATA (next) not successfull, . SW1SW2=" + response.getSW1SW2());
+ }
+ }
+
+ return stream.toByteArray();
+ // referenced data not found
+ } else if( response.isStatus( 0x6A88 )){
+ return null;
+ } else {
+ throw new AccessControlException("GET DATA (specific) not successfull. SW1SW2=" + response.getSW1SW2());
+ }
+ }
+
+ public byte[] readAllAccessRules() throws AccessControlException, CardException {
+
+ ByteArrayOutputStream stream = new ByteArrayOutputStream();
+ int overallLen = 0;
+
+ // send GET DATA (specific)
+ CommandApdu apdu = (CommandApdu) mGetAll.clone();
+ ResponseApdu response = send(apdu);
+
+ // OK
+ if( response.isStatus( 0x9000 ) ){
+
+ // check if more data has to be fetched
+ BerTlv tempTlv = null;
+ try {
+ tempTlv = BerTlv.decode(response.getData(), 0, false);
+ } catch (ParserException e) {
+ throw new AccessControlException("GET DATA (all) not successfull. Tlv encoding wrong.");
+ }
+
+ // the first data block contain the length of the TLV + Tag bytes + length bytes.
+ overallLen = tempTlv.getValueLength() + tempTlv.getValueIndex();
+
+
+ try {
+ stream.write(response.getData());
+ } catch (IOException e) {
+ throw new AccessControlException("GET DATA (all) IO problem. " + e.getMessage() );
+ }
+
+ int le;
+ // send subsequent GET DATA (next) commands
+ while( stream.size() < overallLen ){
+ le = overallLen - stream.size();
+
+ if( le > _MAX_LEN ){
+ le = _MAX_LEN;
+ }
+ // send GET DATA (next)
+ apdu = (CommandApdu) mGetNext.clone();
+ apdu.setLe(le);
+
+ response = send(apdu);
+ // OK
+ if( response.isStatus( 0x9000 ) ) {
+ try {
+ stream.write(response.getData());
+ } catch (IOException e) {
+ throw new AccessControlException("GET DATA (next) IO problem. " + e.getMessage() );
+ }
+ } else {
+ throw new AccessControlException( "GET DATA (next) not successfull, . SW1SW2=" + response.getSW1SW2());
+ }
+ }
+
+ return stream.toByteArray();
+ // referenced data not found
+ } else if( response.isStatus( 0x6A88 )){
+ return null;
+ } else {
+ throw new AccessControlException("GET DATA (all) not successfull. SW1SW2=" + response.getSW1SW2());
+ }
+ }
+
+ public byte[] readRefreshTag() throws AccessControlException, CardException {
+
+ // send GET DATA (specific)
+ CommandApdu apdu = (CommandApdu) mGetRefreshTag.clone();
+ ResponseApdu response = send(apdu);
+
+ // OK
+ if( response.isStatus( 0x9000 ) ){
+
+ // check if more data has to be fetched
+ BerTlv tempTlv = null;
+ Response_RefreshTag_DO refreshDo;
+ try {
+ tempTlv = Response_DO_Factory.createDO(response.getData());
+ if( tempTlv instanceof Response_RefreshTag_DO ) {
+ refreshDo = (Response_RefreshTag_DO)tempTlv;
+ return refreshDo.getRefreshTagArray();
+ } else {
+ throw new AccessControlException("GET REFRESH TAG returned invalid Tlv.");
+ }
+ } catch (ParserException e) {
+ throw new AccessControlException("GET REFRESH TAG not successfull. Tlv encoding wrong.");
+ }
+ }
+ throw new AccessControlException("GET REFRESH TAG not successfull.");
+ }
+
+ private ResponseApdu send(CommandApdu cmdApdu) throws CardException {
+
+ byte[] response = mChannel.transmit(cmdApdu.toBytes());
+
+ ResponseApdu resApdu = new ResponseApdu(response);
+ return resApdu;
+ }
+}
diff --git a/src/org/simalliance/openmobileapi/service/security/ara/AraController.java b/src/org/simalliance/openmobileapi/service/security/ara/AraController.java
new file mode 100644
index 0000000..3b205e4
--- /dev/null
+++ b/src/org/simalliance/openmobileapi/service/security/ara/AraController.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright 2012 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 org.simalliance.openmobileapi.service.security.ara;
+
+import android.util.Log;
+
+import java.security.AccessControlException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+import java.util.MissingResourceException;
+
+import org.simalliance.openmobileapi.service.CardException;
+import org.simalliance.openmobileapi.service.IChannel;
+import org.simalliance.openmobileapi.service.ISmartcardServiceCallback;
+import org.simalliance.openmobileapi.service.ITerminal;
+import org.simalliance.openmobileapi.service.SmartcardService;
+import org.simalliance.openmobileapi.service.security.AccessControlEnforcer;
+import org.simalliance.openmobileapi.service.security.AccessRuleCache;
+import org.simalliance.openmobileapi.service.security.ChannelAccess;
+import org.simalliance.openmobileapi.service.security.gpac.dataobjects.BerTlv;
+import org.simalliance.openmobileapi.service.security.gpac.dataobjects.ParserException;
+import org.simalliance.openmobileapi.service.security.gpac.dataobjects.REF_AR_DO;
+import org.simalliance.openmobileapi.service.security.gpac.dataobjects.Response_ALL_AR_DO;
+import org.simalliance.openmobileapi.service.security.gpac.dataobjects.Response_DO_Factory;
+
+public class AraController {
+
+ private AccessControlEnforcer mMaster = null;
+ private AccessRuleCache mAccessRuleCache = null;
+
+ private ITerminal mTerminal = null;
+ private AccessRuleApplet mApplet = null;
+
+
+ private boolean mNoSuchElement = false;
+
+ private String ACCESS_CONTROL_ENFORCER_TAG = "ACE ARA";
+
+ public static final byte[] ARA_M_AID = new byte[] {
+ (byte)0xA0, (byte)0x00, (byte)0x00, (byte)0x01, (byte)0x51, (byte)0x41, (byte)0x43, (byte)0x4C,
+ (byte)0x00
+ };
+
+ public AraController(AccessControlEnforcer master ) {
+ mMaster = master;
+ mAccessRuleCache = mMaster.getAccessRuleCache();
+ mTerminal = mMaster.getTerminal();
+
+ }
+
+ public boolean isNoSuchElement(){
+ return mNoSuchElement;
+ }
+
+ public static byte[] getAraMAid() {
+ return ARA_M_AID;
+ }
+
+ public synchronized boolean initialize(
+ boolean loadAtStartup,
+ ISmartcardServiceCallback callback)
+ {
+
+ IChannel channel = null;
+ try {
+ channel = this.handleOpenChannel(callback);
+ } catch( MissingResourceException e ){
+ channel = null;
+ }
+
+ if( channel == null ){
+ throw new AccessControlException("could not open channel");
+ }
+
+ try {
+ // set new applet handler since a new channel is used.
+ mApplet = new AccessRuleApplet(channel);
+ byte[] tag = mApplet.readRefreshTag();
+ // if refresh tag is equal to the previous one it is not
+ // neccessary to read all rules again.
+ if( mAccessRuleCache.isRefreshTagEqual(tag)) {
+ Log.d(ACCESS_CONTROL_ENFORCER_TAG, "Refresh tag has not changed. Using access rules from cache.");
+ return false;
+ }
+ Log.d(ACCESS_CONTROL_ENFORCER_TAG, "Refresh tag has changed.");
+ // set new refresh tag and empty cache.
+ mAccessRuleCache.setRefreshTag(tag);
+ mAccessRuleCache.clearCache();
+
+ if( loadAtStartup ) {
+ // Read content from ARA
+ Log.d(ACCESS_CONTROL_ENFORCER_TAG, "Read ARs from ARA");
+ this.readAllAccessRules();
+ }
+ } catch (Exception e) {
+ Log.d(ACCESS_CONTROL_ENFORCER_TAG, "ARA error: " + e.getLocalizedMessage());
+ throw new AccessControlException(e.getLocalizedMessage()); // Throw Exception
+ } finally {
+ if( channel != null )
+ closeChannel(channel);
+ }
+ return true;
+ }
+
+ private IChannel handleOpenChannel( ISmartcardServiceCallback callback ){
+ IChannel channel = null;
+ String reason = "";
+
+ try {
+ channel = openChannel(mTerminal, getAraMAid(), callback);
+ } catch (Exception e) {
+ String msg = e.toString();
+ msg = " ARA-M couldn't be selected: " + msg;
+ Log.d(ACCESS_CONTROL_ENFORCER_TAG, msg);
+ if (e instanceof NoSuchElementException) {
+ mNoSuchElement = true;
+ // SELECT failed
+ // Access Rule Applet is not available => deny any access
+ reason = " No Access because ARA-M is not available";
+ Log.d(ACCESS_CONTROL_ENFORCER_TAG, msg );
+ throw new AccessControlException(reason);
+ } else if( e instanceof MissingResourceException ){
+ // re-throw exception
+ // fixes issue 23
+ // this indicates that no channel is left for accessing the SE element
+ Log.d(ACCESS_CONTROL_ENFORCER_TAG, "no channels left to access ARA-M: " + e.getMessage() );
+ throw (MissingResourceException)e;
+ } else {
+ // MANAGE CHANNEL failed or general error
+ // In order to be compliant with any UICC/SIM card on the market
+ // we are going to ignore the error and says that the ARA-M is not available.
+ // This not fully compliant with GP spec by required for mass compatibility.
+ mNoSuchElement = true;
+
+ reason = msg;
+ Log.d(ACCESS_CONTROL_ENFORCER_TAG," ARA-M can not be accessed: " + msg);
+ throw new AccessControlException(reason);
+ }
+ } // End of Exception handling
+ return channel;
+ }
+
+
+ /**
+ *
+ * @return true if rules are read, false if not necessary or not available, but no error
+ * @throws AccessControlException
+ * @throws CardException
+ */
+ private boolean readAllAccessRules() throws AccessControlException, CardException {
+
+ try {
+ byte[] data = mApplet.readAllAccessRules();
+ // no data returned, but no exception
+ // -> no rule.
+ if( data == null ) {
+ return false;
+ }
+
+ BerTlv tlv = Response_DO_Factory.createDO( data );
+ if( tlv == null ) {
+ throw new AccessControlException("No valid data object found" );
+ } if( tlv instanceof Response_ALL_AR_DO ){
+
+ ArrayList<REF_AR_DO> array = ((Response_ALL_AR_DO)tlv).getRefArDos();
+ if( array == null || array.size() == 0 ){
+ return false; // no rules
+ } else {
+ Iterator<REF_AR_DO> iter = array.iterator();
+ while( iter.hasNext() ){
+ REF_AR_DO ref_ar_do = iter.next();
+ this.mAccessRuleCache.putWithMerge(ref_ar_do.getRefDo(), ref_ar_do.getArDo());
+ }
+ }
+ } else {
+ throw new AccessControlException( "Applet returned invalid or wrong data object!");
+ }
+ } catch (ParserException e) {
+ throw new AccessControlException("Parsing Data Object Exception: " + e.getMessage());
+ }
+ return true;
+ }
+
+ private IChannel openChannel(ITerminal terminal, byte[] aid, ISmartcardServiceCallback callback) throws Exception
+ {
+
+
+ IChannel channel = terminal.openLogicalChannel(null, aid, callback);
+
+ // set access conditions to access ARA-M.
+ ChannelAccess araChannelAccess = new ChannelAccess();
+ araChannelAccess.setAccess(ChannelAccess.ACCESS.ALLOWED, ACCESS_CONTROL_ENFORCER_TAG);
+ araChannelAccess.setApduAccess(ChannelAccess.ACCESS.ALLOWED);
+ channel.setChannelAccess(araChannelAccess);
+
+ return channel;
+}
+
+ private void closeChannel(IChannel channel) {
+ try {
+ if (channel != null && channel.getChannelNumber() != 0) {
+
+ channel.close();
+
+ }
+ } catch (CardException e) {
+ }
+ }
+}
diff --git a/src/org/simalliance/openmobileapi/service/security/arf/ASN1.java b/src/org/simalliance/openmobileapi/service/security/arf/ASN1.java
new file mode 100644
index 0000000..b3b37c7
--- /dev/null
+++ b/src/org/simalliance/openmobileapi/service/security/arf/ASN1.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2011 Deutsche Telekom, A.G.
+ *
+ * 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 org.simalliance.openmobileapi.service.security.arf;
+
+/**
+ * Defines all tags for parsing PKCS#15 files
+ ***************************************************/
+public abstract class ASN1 {
+
+ // ASN.1 tags
+ public static final byte TAG_Sequence = 0x30;
+ public static final byte TAG_OctetString = 0x04;
+ public static final byte TAG_OID = 0x06;
+
+ // EF_DIR tags
+ public static final byte TAG_ApplTemplate = 0x61;
+ public static final byte TAG_ApplIdentifier = 0x4F;
+ public static final byte TAG_ApplLabel = 0x50;
+ public static final byte TAG_ApplPath = 0x51;
+ public static final byte TAG_FCP = 0x62;
+
+ // Others tags
+ public static final byte TAG_Padding = (byte)0xFF;
+}
\ No newline at end of file
diff --git a/src/org/simalliance/openmobileapi/service/security/arf/ArfController.java b/src/org/simalliance/openmobileapi/service/security/arf/ArfController.java
new file mode 100644
index 0000000..bae18ae
--- /dev/null
+++ b/src/org/simalliance/openmobileapi/service/security/arf/ArfController.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2012 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 org.simalliance.openmobileapi.service.security.arf;
+
+import android.util.Log;
+
+import org.simalliance.openmobileapi.service.ISmartcardServiceCallback;
+import org.simalliance.openmobileapi.service.ITerminal;
+import org.simalliance.openmobileapi.service.SmartcardService;
+import org.simalliance.openmobileapi.service.security.AccessControlEnforcer;
+import org.simalliance.openmobileapi.service.security.AccessRuleCache;
+import org.simalliance.openmobileapi.service.security.arf.SecureElement;
+import org.simalliance.openmobileapi.service.security.arf.PKCS15.PKCS15Handler;
+
+public class ArfController {
+
+ private PKCS15Handler mPkcs15Handler = null;
+ private SecureElement mSecureElement = null;
+
+ private AccessControlEnforcer mMaster = null;
+ private AccessRuleCache mAccessRuleCache = null;
+ private ITerminal mTerminal = null;
+
+ public ArfController(AccessControlEnforcer master) {
+ mMaster = master;
+ mAccessRuleCache = mMaster.getAccessRuleCache();
+ mTerminal = mMaster.getTerminal();
+
+ }
+
+ public synchronized boolean initialize(ISmartcardServiceCallback callback) {
+
+
+ if( mSecureElement == null ){
+ mSecureElement = new SecureElement(this, mTerminal);
+ }
+ if( mPkcs15Handler == null ) {
+ mPkcs15Handler = new PKCS15Handler(mSecureElement);
+ }
+ return mPkcs15Handler.loadAccessControlRules(mTerminal.getName());
+ }
+
+
+
+ public AccessRuleCache getAccessRuleCache(){
+ return mAccessRuleCache;
+ }
+}
diff --git a/src/org/simalliance/openmobileapi/service/security/arf/DERParser.java b/src/org/simalliance/openmobileapi/service/security/arf/DERParser.java
new file mode 100644
index 0000000..fb71932
--- /dev/null
+++ b/src/org/simalliance/openmobileapi/service/security/arf/DERParser.java
@@ -0,0 +1,222 @@
+/*
+ * Copyright (C) 2011 Deutsche Telekom, A.G.
+ *
+ * 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 org.simalliance.openmobileapi.service.security.arf;
+
+import android.util.Log;
+import java.util.Arrays;
+
+import org.simalliance.openmobileapi.service.security.arf.PKCS15.PKCS15Exception;
+
+/**
+ * Base class for parsing PKCS#15 files
+ ***************************************************/
+public class DERParser {
+
+ public static final String TAG = "AccessControl";
+ // DER parameters
+ private byte[] mDERBuffer;
+ private short mDERSize,mDERIndex,mTLVDataSize=0;
+
+ /**
+ * Returns "Base 128" encoded integer
+ * @return Converted integer
+ */
+ private int readIntBase128() {
+ int value=0;
+ // If the MSb is set to 0, it is the last byte
+ do {
+ value=(value<<7) + (mDERBuffer[mDERIndex] & 0x7F);
+ } while ((mDERBuffer[mDERIndex++]&0x80) != 0);
+ return value;
+ }
+
+ /**
+ * Returns size of the TLV encoded value
+ * @return Size of the TLV
+ */
+ private short getTLVSize()
+ throws PKCS15Exception {
+ int size,TLVSize=0;
+
+ if (isEndofBuffer())
+ throw new PKCS15Exception("[Parser] Cannot retreive size");
+ // Determine data size
+ if ((TLVSize=(mDERBuffer[mDERIndex++] & 0xff))>=128) {
+ size=TLVSize-128;
+ for(TLVSize=0;size>0;size--) {
+ if (!isEndofBuffer())
+ TLVSize=(TLVSize<<8)+(mDERBuffer[mDERIndex++] & 0xff);
+ else throw new PKCS15Exception("[Parser] Cannot retreive size");
+ }}
+
+ // Check if the buffer contains enough data
+ if ((mDERIndex+TLVSize)>mDERSize)
+ throw new PKCS15Exception("[Parser] Not enough data");
+ return (short)TLVSize;
+ }
+
+ /**
+ * Returns type of the TLV encoded value
+ * @return Type of the TLV
+ */
+ private byte getTLVType()
+ throws PKCS15Exception {
+ if (isEndofBuffer())
+ throw new PKCS15Exception("[Parser] Cannot retreive type");
+ return mDERBuffer[mDERIndex++];
+ }
+
+
+ /**
+ * Constructor
+ * @param buffer file data
+ */
+ public DERParser(byte[] buffer)
+ throws PKCS15Exception {
+ mDERBuffer=buffer;
+ mDERIndex=0; mDERSize=0;
+ if (mDERBuffer==null) return;
+ mDERSize=(short)mDERBuffer.length;
+ mTLVDataSize=mDERSize;
+
+ // Remove padding
+ if (mDERSize==0) return;
+ if (mDERBuffer[mDERIndex]==ASN1.TAG_Padding) {
+ mTLVDataSize=0;
+ while(++mDERIndex<mDERSize) {
+ if (mDERBuffer[mDERIndex]!=ASN1.TAG_Padding)
+ throw new PKCS15Exception("[Parser] Incorrect file format");
+ }}}
+
+ /**
+ * Determines if we reached the end of the buffer
+ * @return True if end of buffer is reached; False otherwise
+ */
+ public boolean isEndofBuffer()
+ throws PKCS15Exception {
+ if (mDERIndex==mDERSize) return true;
+ if (mDERBuffer[mDERIndex]==ASN1.TAG_Padding) {
+ // Remove padding
+ while(++mDERIndex<mDERSize) {
+ if (mDERBuffer[mDERIndex]!=ASN1.TAG_Padding)
+ throw new PKCS15Exception("[Parser] Incorrect file format");
+ } return true;
+ } return false;
+ }
+
+ /**
+ * Parses TLV from current index
+ * @return Type of TLV structure
+ */
+ public byte parseTLV()
+ throws PKCS15Exception {
+ byte type=getTLVType();
+ mTLVDataSize=getTLVSize();
+ return type;
+ }
+
+ /**
+ * Parses TLV from current index and check if type is correct
+ * @param type Type required
+ * @return Length of TLV data structure
+ */
+ public short parseTLV(byte type)
+ throws PKCS15Exception {
+ if (getTLVType()==type) {
+ mTLVDataSize=getTLVSize();
+ } else throw new PKCS15Exception("[Parser] Unexpected type");
+ return mTLVDataSize;
+ }
+
+ /**
+ * Skips data of the current TLV structure
+ */
+ public void skipTLVData() {
+ mDERIndex+=mTLVDataSize;
+ }
+
+ /**
+ * Returns data of the current TLV structure
+ * @return Data of current TLV structure
+ */
+ public byte[] getTLVData() {
+ byte[] data=Arrays.copyOfRange(mDERBuffer,mDERIndex,
+ mDERIndex+mTLVDataSize);
+ mDERIndex+=mTLVDataSize;
+ return data;
+ }
+
+ /**
+ * Takes snaptshot of the current context
+ * @return Saved context
+ */
+ public short[] saveContext() {
+ short[] context=new short[2];
+ context[0]=mDERIndex; context[1]=mTLVDataSize;
+ return context;
+ }
+
+ /**
+ * Restores a context from a snapshot previously saved
+ * @param context Context snapshot
+ */
+ public void restoreContext(short[] context)
+ throws PKCS15Exception {
+ if ((context==null)||(context.length!=2))
+ throw new PKCS15Exception("[Parser] Invalid context");
+ if ((context[0]<0)||(context[0]>mDERSize))
+ throw new PKCS15Exception("[Parser] Index out of bound");
+ mDERIndex=context[0]; mTLVDataSize=context[1];
+ }
+
+ /**
+ * Parses standardized OID
+ * @return String containing OID
+ */
+ public String parseOID()
+ throws PKCS15Exception {
+ if (parseTLV(ASN1.TAG_OID)==0)
+ throw new PKCS15Exception("[Parser] OID Length is null");
+
+ int end=mDERIndex+mTLVDataSize;
+ StringBuffer oid=new StringBuffer();
+
+ // First subidentifier
+ int subid=readIntBase128();
+ // The first subidentifier contains the first two OID components
+ // X.Y is encoded as (X*40)+Y (0<=X<=2 and 0<=Y<=39 for X=0 or X=1)
+ if (subid<=79)
+ oid.append(subid/40).append('.').append(subid%40);
+ else oid.append("2.").append(subid-80);
+
+ while (mDERIndex< end)
+ oid.append('.').append(readIntBase128());
+ Log.d(TAG,"Found OID: "+oid.toString());
+ return oid.toString();
+ }
+
+ /**
+ * Parses PKCS#15 path attribute
+ * @return Path retreived from the attribute
+ */
+ public byte[] parsePathAttributes()
+ throws PKCS15Exception {
+ parseTLV(ASN1.TAG_Sequence);
+ parseTLV(ASN1.TAG_OctetString);
+ return getTLVData();
+ }
+}
\ No newline at end of file
diff --git a/src/org/simalliance/openmobileapi/service/security/arf/PKCS15/EF.java b/src/org/simalliance/openmobileapi/service/security/arf/PKCS15/EF.java
new file mode 100644
index 0000000..90cd48a
--- /dev/null
+++ b/src/org/simalliance/openmobileapi/service/security/arf/PKCS15/EF.java
@@ -0,0 +1,285 @@
+
+/*
+ * Contributed by: Giesecke & Devrient GmbH.
+ */
+
+package org.simalliance.openmobileapi.service.security.arf.PKCS15;
+
+import java.util.Arrays;
+
+import org.simalliance.openmobileapi.service.Util;
+import org.simalliance.openmobileapi.service.security.arf.ASN1;
+import org.simalliance.openmobileapi.service.security.arf.DERParser;
+import org.simalliance.openmobileapi.service.security.arf.SecureElement;
+import org.simalliance.openmobileapi.service.security.arf.SecureElementException;
+
+import android.util.Log;
+
+public class EF {
+
+ public static final String TAG = "SmartcardService ACE ARF";
+
+ public static final int APDU_SUCCESS = 0x9000;
+ private static final int BUFFER_LEN = 253;
+
+ // 2012-04-13
+ private static final short EF = 0x04;
+ private static final short TRANSPARENT = 0x00;
+ private static final short LINEAR_FIXED = 0x01;
+ private static final short UNKNOWN = 0xFF;
+
+ // Selected file parameters
+ private short mFileType = UNKNOWN,mFileStructure = UNKNOWN, mFileNbRecords;
+ private int mFileID,mFileSize,mFileRecordSize;
+ private String mFilePath;
+
+
+ // Handle to "Secure Element" object
+ protected SecureElement mSEHandle=null;
+
+ public EF( SecureElement handle ){
+ mSEHandle = handle;
+ }
+
+ public int getFileId(){
+ return mFileID;
+ }
+ public String getFilePath(){
+ return mFilePath;
+ }
+
+ private void decodeFileProperties(byte[] data )
+ throws SecureElementException
+ {
+ if( data != null ){
+ // check if first byte is the FCP tag
+ // then do USIM decoding
+ if( data[0] == 0x62 ){
+ decodeUSIMFileProps(data);
+ } else {
+ // otherwise sim decoding
+ decodeSIMFileProps(data);
+ }
+ }
+ }
+
+ /**
+ * Decodes file properties (SIM cards)
+ * @param data TS 51.011 encoded characteristics
+ */
+ private void decodeSIMFileProps(byte[] data)
+ throws SecureElementException
+ {
+ if ((data==null)||(data.length<15))
+ throw new SecureElementException("Invalid Response data");
+
+ // 2012-04-13
+ // check type of file
+ if( (short)(data[6] & 0xFF) == (short)0x04 ){
+ mFileType = EF;
+ } else {
+ mFileType = UNKNOWN; // may also be DF or MF, but we are not interested in them.
+ }
+ if( (short)(data[13]&0xFF) == (short)0x00 ) {
+ mFileStructure = TRANSPARENT;
+ } else if( (short)(data[13]&0xFF) == (short)0x01 ) {
+ mFileStructure = LINEAR_FIXED;
+ } else {
+ mFileStructure = UNKNOWN; // may also be cyclic
+ }
+ mFileSize=((data[2] & 0xFF)<<8)|(data[3] & 0xFF);
+
+ // check if file is cyclic or linear fixed
+ if (mFileType == EF && // is EF ?
+ mFileStructure != TRANSPARENT ) {
+ mFileRecordSize=data[14] & 0xFF;
+ mFileNbRecords=(short)(mFileSize/mFileRecordSize);
+ }
+ }
+
+ /**
+ * Decodes file properties (USIM cards)
+ * @param data TLV encoded characteristics
+ */
+ private void decodeUSIMFileProps(byte[] data)
+ throws SecureElementException
+ {
+ try {
+ byte[] buffer=null;
+ DERParser DER=new DERParser(data);
+
+ DER.parseTLV(ASN1.TAG_FCP);
+ while(!DER.isEndofBuffer()) {
+ switch(DER.parseTLV()) {
+ case (byte)0x80: // File size
+ buffer=DER.getTLVData();
+ if ((buffer!=null)&&(buffer.length>=2))
+ mFileSize=((buffer[0] & 0xFF)<<8)|(buffer[1] & 0xFF);
+ break;
+ case (byte)0x82: // File descriptor
+ buffer=DER.getTLVData();
+ if ((buffer!=null)&&(buffer.length>=2)) {
+ if( (short)(buffer[0] & 0x07) == (short)0x01 ) {
+ mFileStructure = TRANSPARENT;
+ } else if( (short)(buffer[0] & 0x07) == (short)0x02 ) {
+ mFileStructure = LINEAR_FIXED;
+ } else {
+ mFileStructure = UNKNOWN; // may also be cyclic
+ }
+
+ // check if bit 4,5,6 are set
+ // then this is a DF or ADF, but we mark it with UNKNOWN,
+ // since we are only interested in EFs.
+ if( (short)(buffer[0] & 0x38) == (short)0x38 ) {
+ mFileType=UNKNOWN;
+ } else {
+ mFileType=EF;
+ }
+ if (buffer.length==5) {
+ mFileRecordSize=buffer[3] & 0xFF;
+ mFileNbRecords=(short)(buffer[4] & 0xFF);
+ }
+ } break;
+ default:
+ DER.skipTLVData(); break;
+ }
+ }
+ } catch(Exception e) {
+ throw new SecureElementException("Invalid GetResponse");
+ }
+ }
+
+
+ /**
+ * Selects a file (INS 0xA4)
+ * @param path Path of the file
+ * @return Command status code [sw1 sw2]
+ */
+ public int selectFile(byte[] path)
+ throws SecureElementException
+ {
+ if ((path==null) || (path.length==0) || ((path.length%2)!=0))
+ throw new SecureElementException("Incorrect path");
+
+ int index;
+ int length=path.length;
+ if ((mSEHandle.getSeInterface() == SecureElement.SIM_IO)&&(length>2)) {
+ index=length-2; // Only FileID is usefull
+ mFilePath=Util.bytesToString(path,0,index,"");
+ } else {
+ index=0;
+ mFilePath="";
+ }
+
+
+ byte[] data=null;
+ byte[] cmd= new byte[]{ 0x00,(byte)0xA4,0x00,0x04,0x02,0x00,0x00 };
+
+ mFileType=UNKNOWN;
+ mFileStructure = UNKNOWN;
+ mFileSize=0;
+ mFileRecordSize=0;
+ mFileNbRecords=0;
+
+ // iterate through path
+ for(int sw1;index<length;index+=2) {
+ mFileID=((path[index]&0xFF)<<8) | (path[index+1] & 0xFF);
+ cmd[5]=(byte)(mFileID>>8);
+ cmd[6]=(byte)mFileID;
+
+ data=mSEHandle.exchangeAPDU( this, cmd);
+
+ // Check ADPU status
+ sw1=data[data.length-2] & 0xFF;
+ if ( (sw1!=0x62) &&
+ (sw1!=0x63) &&
+ (sw1!=0x90) &&
+ (sw1!=0x91) )
+ {
+ return (sw1<<8) | (data[data.length-1] & 0xFF);
+ }
+ }
+
+ // Analyse file properties
+ decodeFileProperties(data);
+
+ if (mFileNbRecords==0)
+ Log.d(TAG,"SelectFile ["+mFileSize+"b]");
+ else
+ Log.d(TAG,"SelectFile ["+mFileNbRecords+"*"+mFileRecordSize+"b]");
+ return APDU_SUCCESS;
+ }
+
+ /**
+ * Reads data from the current selected file (INS 0xB0)
+ * @param offset Offset at which to start reading
+ * @param nbBytes Number of bytes to read
+ * @return Data retreived from the file
+ */
+ public byte[] readBinary(int offset,int nbBytes)
+ throws SecureElementException {
+ if (mFileSize==0) return null;
+ if (nbBytes==-1) nbBytes=mFileSize;
+ if (mFileType != EF)
+ throw new SecureElementException("Incorrect file type");
+ if (mFileStructure != TRANSPARENT )
+ throw new SecureElementException("Incorrect file structure");
+
+ int length,pos=0;
+ byte[] result=new byte[nbBytes];
+ byte[] cmd={ 0x00,(byte)0xB0,0x00,0x00,0x00 };
+
+ while (nbBytes!=0) {
+ if (nbBytes<BUFFER_LEN)
+ length=nbBytes;
+ else
+ length=BUFFER_LEN; // Set to max buffer size
+
+ Log.d(TAG,"ReadBinary ["+offset+".."+length+"b]");
+
+ cmd[2]=(byte)(offset>>8);
+ cmd[3]=(byte)offset;
+ cmd[4]=(byte)length;
+ System.arraycopy(mSEHandle.exchangeAPDU(this, cmd),0,result,pos,length);
+ nbBytes-=length; offset+=length; pos+=length;
+ }
+ return result;
+ }
+
+ /**
+ * Reads a record from the current selected file (INS 0xB2)
+ * @param record Record ID [0..n]
+ * @return Data from requested record
+ */
+ public byte[] readRecord(short record)
+ throws SecureElementException {
+ // Check the type of current selected file
+ if (mFileType != EF)
+ throw new SecureElementException("Incorrect file type");
+ if (mFileStructure != LINEAR_FIXED)
+ throw new SecureElementException("Incorrect file structure");
+
+ // Check if requested record is valid
+ if ((record<0) || (record>mFileNbRecords))
+ throw new SecureElementException("Incorrect record number");
+
+ Log.d(TAG,"ReadRecord ["+record+"/"+mFileRecordSize+"b]");
+ byte[] cmd= { 0x00,(byte)0xB2,(byte)record,0x04,(byte)mFileRecordSize };
+
+ return Arrays.copyOf(mSEHandle.exchangeAPDU(this, cmd),mFileRecordSize);
+ }
+
+ /**
+ * Returns the number of records in the current selected file
+ * @return Number of records [0..n]
+ */
+ public short getFileNbRecords()
+ throws SecureElementException {
+ // Check the type of current selected file
+ if (mFileNbRecords<0)
+ throw new SecureElementException("Incorrect file type");
+ return mFileNbRecords;
+ }
+
+
+}
diff --git a/src/org/simalliance/openmobileapi/service/security/arf/PKCS15/EFACConditions.java b/src/org/simalliance/openmobileapi/service/security/arf/PKCS15/EFACConditions.java
new file mode 100644
index 0000000..deba2fd
--- /dev/null
+++ b/src/org/simalliance/openmobileapi/service/security/arf/PKCS15/EFACConditions.java
@@ -0,0 +1,252 @@
+/*
+ * Copyright (C) 2011 Deutsche Telekom, A.G.
+ *
+ * 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 org.simalliance.openmobileapi.service.security.arf.PKCS15;
+
+import android.util.Log;
+
+import java.util.Vector;
+
+import org.simalliance.openmobileapi.service.security.ApduFilter;
+import org.simalliance.openmobileapi.service.security.ChannelAccess;
+import org.simalliance.openmobileapi.service.security.arf.ASN1;
+import org.simalliance.openmobileapi.service.security.arf.DERParser;
+import org.simalliance.openmobileapi.service.security.arf.SecureElement;
+import org.simalliance.openmobileapi.service.Util;
+import org.simalliance.openmobileapi.service.security.arf.PKCS15.PKCS15Exception;
+import org.simalliance.openmobileapi.service.security.gpac.dataobjects.AID_REF_DO;
+import org.simalliance.openmobileapi.service.security.gpac.dataobjects.Hash_REF_DO;
+
+/**
+ * EF_ACConditions related features
+ ***************************************************/
+public class EFACConditions extends EF {
+
+ public static final String TAG = "ACE ARF EF_ACConditions";
+
+ // Identification of the cardlet
+ private AID_REF_DO mAid_Ref_Do=null;
+
+ private byte[] mData = null;
+
+
+ /**
+ * Constructor
+ * @param secureElement SE on which ISO7816 commands are applied
+ * @param AID Identification of the applet
+ */
+ public EFACConditions(SecureElement handle,AID_REF_DO Aid_Ref_Do) {
+ super( handle );
+
+ mAid_Ref_Do=Aid_Ref_Do;
+ }
+
+ /**
+ * Decodes EF_ACConditions file
+ * @param buffer ASN.1 data
+ */
+ private void decodeDER(byte[] buffer)
+ throws PKCS15Exception
+ {
+ byte[] certificateHash=null;
+ DERParser DER=new DERParser(buffer);
+
+ // the default channelAccess will deny every access.
+ ChannelAccess channelAccess = new ChannelAccess();
+ Hash_REF_DO hash_ref_do = new Hash_REF_DO();
+
+ // empty condition file
+ if (DER.isEndofBuffer()) {
+ mSEHandle.putAccessRule(mAid_Ref_Do, hash_ref_do, channelAccess);
+ return;
+ }
+
+ //----
+ // 2012-04-16
+ /*
+ Condition ::= SEQUENCE {
+ cert CertHash OPTIONAL,
+ accessRules [0]AccessRules OPTIONAL
+ }
+
+ AccessRules ::= SEQUENCE OF AccessRule
+
+ AccessRule ::=CHOICE {
+ apduAccessRule [0]APDUAccessRule,
+ nfcAccessRule [1]NFCAccessRule
+ }
+
+ APDUAccessRule ::= CHOICE {
+ apduPermission [0] APDUPermission,
+ apduFilter [1] APDUFilter
+ }
+
+ APDUFilters ::= SEQUENCE OF APDUFilter
+
+ NFCAccessRule ::= CHOICE {
+ nfcPermission [0] NFCPermission
+ }
+ */
+ while(!DER.isEndofBuffer()) {
+
+ // if a hash value was found then access is allowed
+ // even if NO more access rule is given.
+ // missing APDU Permission will always allow APDU access
+ // missing NFC Permission will always allow NFC event.
+ // See GPAC Chapter 7.1.7
+ // See Examples in Annex C of GPAC
+ channelAccess = new ChannelAccess();
+ channelAccess.setAccess(ChannelAccess.ACCESS.ALLOWED, "");
+ channelAccess.setApduAccess(ChannelAccess.ACCESS.ALLOWED);
+ channelAccess.setNFCEventAccess(ChannelAccess.ACCESS.ALLOWED);
+ channelAccess.setUseApduFilter(false);
+
+ if ( DER.parseTLV(ASN1.TAG_Sequence) > 0 ) {
+ DERParser derRule = new DERParser( DER.getTLVData());
+ derRule.parseTLV(ASN1.TAG_OctetString);
+ certificateHash=derRule.getTLVData();
+
+ if (certificateHash.length!=Hash_REF_DO._SHA1_LEN &&
+ certificateHash.length!=0) {
+ // other hash than SHA-1 hash values are not supported.
+ throw new PKCS15Exception("Invalid hash found!");
+ } else {
+ hash_ref_do =new Hash_REF_DO(certificateHash);
+ }
+
+ // 2012-04-16
+ // parse optional Access Rule.
+ if( !derRule.isEndofBuffer() ) {
+
+ if( derRule.parseTLV() == (byte)0xA0 ) {
+
+ DERParser derAccessRules = new DERParser(derRule.getTLVData());
+
+ while(!derAccessRules.isEndofBuffer()) {
+ switch( derAccessRules.parseTLV() ){
+ // APDU Access Rule
+ case (byte)0xA0:
+ DERParser derApduRule = new DERParser( derAccessRules.getTLVData());
+ byte tagApduAccessRule = derApduRule.parseTLV();
+
+ if( tagApduAccessRule == (byte)0x80 ) { // APDU Permission (primitive)
+
+ channelAccess.setApduAccess(
+ derApduRule.getTLVData()[0] == 0x01 ? ChannelAccess.ACCESS.ALLOWED : ChannelAccess.ACCESS.DENIED);
+
+ } else if( tagApduAccessRule == (byte)0xA1 ) { // APDU Filter (constructed)
+
+ DERParser derApduFilter = new DERParser( derApduRule.getTLVData() );
+ byte tag = derApduFilter.parseTLV();
+
+ if( tag == ASN1.TAG_OctetString ) {
+
+ Vector<ApduFilter> apduFilter = new Vector<ApduFilter>();
+
+ // collect all apdu filter tlvs.
+ apduFilter.add(new ApduFilter( derApduFilter.getTLVData()));
+
+ while( !derApduFilter.isEndofBuffer()) {
+ if( derApduFilter.parseTLV() == ASN1.TAG_OctetString ) {
+ apduFilter.add(new ApduFilter( derApduFilter.getTLVData()));
+ }
+ }
+ channelAccess.setUseApduFilter(true);
+ channelAccess.setApduFilter(apduFilter.toArray(new ApduFilter[apduFilter.size()]));
+ } else {
+ throw new PKCS15Exception("Invalid element found!");
+ }
+
+ } else {
+ throw new PKCS15Exception("Invalid element found!");
+ }
+ break;
+ // NFC Access Rule
+ case (byte)0xA1:
+ DERParser derNfc = new DERParser(derAccessRules.getTLVData());
+
+ if( derNfc.parseTLV() == (byte)0x80 ) { // NFC Permission (primitive)
+ channelAccess.setNFCEventAccess(
+ derNfc.getTLVData()[0] == (byte)0x01 ? ChannelAccess.ACCESS.ALLOWED : ChannelAccess.ACCESS.DENIED);
+ } else {
+ throw new PKCS15Exception("Invalid element found!");
+ }
+ break;
+ default:
+ throw new PKCS15Exception("Invalid element found!");
+ }
+ }
+ } else {
+ // no explicit access rule given.
+ }
+ }
+ } else {
+ // coding 30 00 -> empty hash value given (all applications)
+ }
+ //----
+ mSEHandle.putAccessRule(mAid_Ref_Do, hash_ref_do, channelAccess);
+ } ;
+ }
+
+
+ /**
+ * Stores a restricted list of certificate hashes
+ * @param path Path of the "EF_ACConditions" file
+ */
+ public void addRestrictedHashes(byte[] path) {
+ try {
+ Log.v(TAG,"Reading and analysing EF_ACConditions...");
+ if (selectFile(path) == APDU_SUCCESS) {
+ mData = readBinary(0,Util.END);
+ decodeDER(mData);
+ } else {
+ Log.e(TAG,"EF_ACConditions not found!");
+ }
+ } catch (Exception e) {
+ /*Nothing to do*/
+ Log.e( TAG, "Exception: " + e.getMessage());
+ }
+ }
+
+ /**
+ * Stores a restricted list of certificate hashes
+ * @param path Path of the "EF_ACConditions" file
+ */
+ public void addRestrictedHashesFromData(byte[] data) {
+ try {
+ Log.v(TAG,"Analysing cached EF_ACConditions data...");
+ if( data != null ) {
+ mData = data;
+ decodeDER(mData);
+ } else {
+ Log.e(TAG,"EF_ACConditions data not available!");
+ }
+ } catch (Exception e) {
+ /*Nothing to do*/
+ Log.e( TAG, "Exception: " + e.getMessage());
+ }
+ }
+
+ public byte[] getData() {
+ return mData;
+ }
+
+}
+
diff --git a/src/org/simalliance/openmobileapi/service/security/arf/PKCS15/EFACMain.java b/src/org/simalliance/openmobileapi/service/security/arf/PKCS15/EFACMain.java
new file mode 100644
index 0000000..2c6932f
--- /dev/null
+++ b/src/org/simalliance/openmobileapi/service/security/arf/PKCS15/EFACMain.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2011 Deutsche Telekom, A.G.
+ *
+ * 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 org.simalliance.openmobileapi.service.security.arf.PKCS15;
+
+import android.util.Log;
+import java.util.Arrays;
+
+import org.simalliance.openmobileapi.service.security.arf.ASN1;
+import org.simalliance.openmobileapi.service.security.arf.DERParser;
+import org.simalliance.openmobileapi.service.security.arf.SecureElement;
+import org.simalliance.openmobileapi.service.security.arf.SecureElementException;
+import org.simalliance.openmobileapi.service.Util;
+import org.simalliance.openmobileapi.service.security.arf.PKCS15.PKCS15Exception;
+
+/**
+ * EF_ACMain related features
+ ***************************************************/
+public class EFACMain extends EF {
+
+ public static final String TAG = "ACE ARF EF_ACMain";
+ // Length of the "RefreshTag"
+ public static final short REFRESH_TAG_LEN=8;
+
+ // "EF Access Control Main" path
+ private byte[] mACMainPath=null;
+
+ /**
+ * Decodes EF_ACMain file
+ * @param buffer ASN.1 data
+ * @return Path to "Access Control Rules"
+ */
+ private byte[] decodeDER(byte[] buffer)
+ throws PKCS15Exception
+ {
+ DERParser DER=new DERParser(buffer);
+ DER.parseTLV(ASN1.TAG_Sequence);
+ if (DER.parseTLV(ASN1.TAG_OctetString)!=REFRESH_TAG_LEN)
+ throw new PKCS15Exception("[Parser] RefreshTag length not valid");
+
+ byte[] refreshTag=DER.getTLVData();
+ if (!Arrays.equals(refreshTag,this.mSEHandle.getRefreshTag())) {
+ mSEHandle.setRefreshTag(refreshTag);
+ return DER.parsePathAttributes();
+ }
+ return null; // RefreshTag not updated
+ }
+
+
+ /**
+ * Constructor
+ * @param secureElement SE on which ISO7816 commands are applied
+ */
+ public EFACMain(SecureElement handle,byte[] path) {
+ super(handle);
+ mACMainPath=path;
+ }
+
+ /**
+ * Selects and Analyses EF_ACMain file
+ * @return Path to "EF_ACRules" if "RefreshTag" has been updated;
+ * <code>null</code> otherwise
+ */
+ public byte[] analyseFile()
+ throws PKCS15Exception,SecureElementException
+ {
+ Log.v(TAG,"Analysing EF_ACMain...");
+ byte[] path = mACMainPath;
+
+ /*
+ // 2012-04-12
+ // extend path if ODF path was determined from EF DIR.
+ if( mSEHandle.getPKCS15Path() != null ) {
+ path = new byte[mSEHandle.getPKCS15Path().length + mACMainPath.length];
+ System.arraycopy(mSEHandle.getPKCS15Path(), 0, path, 0, mSEHandle.getPKCS15Path().length);
+ System.arraycopy(mACMainPath, 0, path, mSEHandle.getPKCS15Path().length, mACMainPath.length );
+ }
+ //---
+ *
+ */
+
+ if ( selectFile(path) != APDU_SUCCESS) {
+ throw new PKCS15Exception("EF_ACMain not found!");
+ }
+ return decodeDER(readBinary(0,Util.END));
+ }
+}
diff --git a/src/org/simalliance/openmobileapi/service/security/arf/PKCS15/EFACRules.java b/src/org/simalliance/openmobileapi/service/security/arf/PKCS15/EFACRules.java
new file mode 100644
index 0000000..6431bde
--- /dev/null
+++ b/src/org/simalliance/openmobileapi/service/security/arf/PKCS15/EFACRules.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2011 Deutsche Telekom, A.G.
+ *
+ * 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 org.simalliance.openmobileapi.service.security.arf.PKCS15;
+
+import android.util.Log;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.simalliance.openmobileapi.service.Util;
+import org.simalliance.openmobileapi.service.security.arf.ASN1;
+import org.simalliance.openmobileapi.service.security.arf.DERParser;
+import org.simalliance.openmobileapi.service.security.arf.SecureElement;
+import org.simalliance.openmobileapi.service.security.arf.SecureElementException;
+import org.simalliance.openmobileapi.service.security.arf.PKCS15.EFACConditions;
+import org.simalliance.openmobileapi.service.security.arf.PKCS15.PKCS15Exception;
+import org.simalliance.openmobileapi.service.security.gpac.dataobjects.AID_REF_DO;
+
+/**
+ * EF_ACRules related features
+ ***************************************************/
+public class EFACRules extends EF {
+
+ public static final String TAG = "ACE ARF EF_ACRules";
+ // AID used to store rules for default application
+ public static final byte[] DEFAULT_APP = new byte[0];
+
+ protected Map<String, byte[]> mAcConditionDataCache = new HashMap<String, byte[]>();
+
+
+ /**
+ * Decodes EF_ACRules file
+ * @param buffer ASN.1 data
+ */
+ private void decodeDER(byte[] buffer)
+ throws PKCS15Exception
+ {
+ byte[] AID=null;
+ DERParser DER=new DERParser(buffer);
+
+ // mapping to GPAC data objects
+ int tag = 0;
+
+ while(!DER.isEndofBuffer()) {
+ DER.parseTLV(ASN1.TAG_Sequence);
+ switch(DER.parseTLV()) {
+ case (byte)0xA0: // Restricted AID
+ DER.parseTLV(ASN1.TAG_OctetString);
+ AID=DER.getTLVData();
+ tag = AID_REF_DO._TAG;
+ break;
+ case (byte)0x81: // Rules for default Application
+ AID=null;
+ tag = AID_REF_DO._TAG_DEFAULT_APPLICATION;
+ break;
+ case (byte)0x82: // Rules for default case
+ AID=DEFAULT_APP;
+ tag = AID_REF_DO._TAG;
+ break;
+ default:
+ throw new PKCS15Exception("[Parser] Unexpected ACRules entry");
+ }
+ byte[] path = DER.parsePathAttributes();
+
+ // 2012-09-04
+ // optimization of reading EF ACCondition
+ if( path != null ){
+ String pathString = Util.bytesToString(path);
+ EFACConditions temp = new EFACConditions(mSEHandle,new AID_REF_DO(tag, AID ));
+ // check if EF was already read before
+ if( this.mAcConditionDataCache.containsKey(pathString )){
+ // yes, then reuse data
+ temp.addRestrictedHashesFromData(this.mAcConditionDataCache.get(pathString));
+ } else {
+ // no, read EF and add to rules cache
+ temp.addRestrictedHashes(path);
+ if( temp.getData() != null ){
+ // if data are read the put it into cache.
+ this.mAcConditionDataCache.put(pathString, temp.getData());
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Constructor
+ * @param secureElement SE on which ISO7816 commands are applied
+ */
+ public EFACRules(SecureElement handle) {
+ super( handle );
+ }
+
+ /**
+ * Selects and Analyses EF_ACRules file
+ * @param path Path of the "EF_ACRules" file
+ */
+ public void analyseFile(byte[] path)
+ throws PKCS15Exception,SecureElementException {
+
+ Log.v(TAG,"Analysing EF_ACRules...");
+
+ // clear EF AC Condition data cache.
+ mAcConditionDataCache.clear();
+
+ if ( selectFile(path)!= APDU_SUCCESS)
+ throw new PKCS15Exception("EF_ACRules not found!!");
+
+ try {
+ decodeDER( readBinary(0,Util.END));
+ } catch(PKCS15Exception e) {
+ throw e;
+ }}
+}
diff --git a/src/org/simalliance/openmobileapi/service/security/arf/PKCS15/EFDIR.java b/src/org/simalliance/openmobileapi/service/security/arf/PKCS15/EFDIR.java
new file mode 100644
index 0000000..992ae30
--- /dev/null
+++ b/src/org/simalliance/openmobileapi/service/security/arf/PKCS15/EFDIR.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2011 Deutsche Telekom, A.G.
+ *
+ * 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 org.simalliance.openmobileapi.service.security.arf.PKCS15;
+
+import android.util.Log;
+import java.util.Arrays;
+
+import org.simalliance.openmobileapi.service.security.arf.ASN1;
+import org.simalliance.openmobileapi.service.security.arf.DERParser;
+import org.simalliance.openmobileapi.service.security.arf.SecureElement;
+import org.simalliance.openmobileapi.service.security.arf.SecureElementException;
+import org.simalliance.openmobileapi.service.security.arf.PKCS15.PKCS15Exception;
+
+/**
+ * EF_DIR related features
+ ***************************************************/
+public class EFDIR extends EF{
+
+ public static final String TAG = "ACE ARF EF_Dir";
+ // Standardized ID for EF_DIR file
+ public static final byte[] EFDIR_PATH = { 0x3F,0x00,0x2F,0x00 };
+
+
+ /**
+ * Decodes EF_DIR file
+ * @param buffer ASN.1 data
+ * @param AID Record key to search for
+ * @return Path to "EF_ODF" when an expected record is found;
+ * <code>null</code> otherwise
+ */
+ private byte[] decodeDER(byte[] buffer,byte[] AID)
+ throws PKCS15Exception {
+ DERParser DER=new DERParser(buffer);
+ DER.parseTLV(ASN1.TAG_ApplTemplate);
+ // Application Identifier
+ DER.parseTLV(ASN1.TAG_ApplIdentifier);
+ if (!Arrays.equals(DER.getTLVData(),AID))
+ return null; // Record for another AID
+
+ // Application Label or Application Path
+ byte objectType=DER.parseTLV();
+ if (objectType==ASN1.TAG_ApplLabel) {
+ // Application Label [Optional]
+ DER.getTLVData();
+ DER.parseTLV(ASN1.TAG_ApplPath);
+ } else if (objectType!=ASN1.TAG_ApplPath)
+ throw new PKCS15Exception("[Parser] Application Tag expected");
+ // Application Path
+ return DER.getTLVData();
+ }
+
+
+ /**
+ * Constructor
+ * @param secureElement SE on which ISO7816 commands are applied
+ */
+ public EFDIR(SecureElement handle) {
+ super(handle);
+ }
+
+ /**
+ * Analyses DIR file and lookups for AID record
+ * @param AID Record key to search for
+ * @return Path to "EF_ODF" when an expected record is found;
+ * <code>null</code> otherwise
+ */
+ public byte[] lookupAID(byte[] AID) throws PKCS15Exception,SecureElementException {
+ Log.v(TAG,"Analysing EF_DIR...");
+
+ if (selectFile(EFDIR_PATH)!= APDU_SUCCESS)
+ throw new PKCS15Exception("EF_DIR not found!!");
+
+ byte[] data,ODFPath=null;
+ short index=1;
+ while(index<=getFileNbRecords()) {
+ data=readRecord(index++);
+ if ((ODFPath=decodeDER(data,AID))!=null)
+ break;
+ }
+ return ODFPath;
+ }
+
+}
diff --git a/src/org/simalliance/openmobileapi/service/security/arf/PKCS15/EFDODF.java b/src/org/simalliance/openmobileapi/service/security/arf/PKCS15/EFDODF.java
new file mode 100644
index 0000000..865271b
--- /dev/null
+++ b/src/org/simalliance/openmobileapi/service/security/arf/PKCS15/EFDODF.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2011 Deutsche Telekom, A.G.
+ *
+ * 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 org.simalliance.openmobileapi.service.security.arf.PKCS15;
+
+import org.simalliance.openmobileapi.service.security.arf.ASN1;
+import org.simalliance.openmobileapi.service.security.arf.DERParser;
+import org.simalliance.openmobileapi.service.security.arf.SecureElement;
+import org.simalliance.openmobileapi.service.security.arf.SecureElementException;
+import org.simalliance.openmobileapi.service.Util;
+import org.simalliance.openmobileapi.service.security.arf.PKCS15.PKCS15Exception;
+
+import android.util.Log;
+
+/**
+ * EF_DODF related features
+ ***************************************************/
+public class EFDODF extends EF{
+
+ public static final String TAG = "ACE ARF EF_DODF";
+ // OID defined by Global Platform for the "Access Control"
+ public static final String AC_OID = "1.2.840.114283.200.1.1";
+
+ /**
+ * Decodes EF_DODF file
+ * @param buffer ASN.1 data
+ * @return Path to "Access Control Main" from "Access Control" OID;
+ * <code>null</code> otherwise
+ */
+ private byte[] decodeDER(byte[] buffer)
+ throws PKCS15Exception {
+ byte objectType;
+ short[] context=null;
+ DERParser DER=new DERParser(buffer);
+
+ while(!DER.isEndofBuffer()) {
+ if (DER.parseTLV()==(byte)0xA1) { // OidDO Data Object
+ // Common Object Attributes
+ DER.parseTLV(ASN1.TAG_Sequence);
+ DER.skipTLVData();
+ // Common Data Object Attributes
+ DER.parseTLV(ASN1.TAG_Sequence);
+ DER.skipTLVData();
+
+ objectType=DER.parseTLV();
+ if (objectType==(byte)0xA0) { // SubClassAttributes [Optional]
+ DER.skipTLVData();
+ objectType=DER.parseTLV();
+ }
+ if (objectType==(byte)0xA1) { // OidDO
+ DER.parseTLV(ASN1.TAG_Sequence);
+ context=DER.saveContext();
+ if (DER.parseOID().compareTo(AC_OID)!=0) {
+ DER.restoreContext(context);
+ DER.skipTLVData();
+ } else return DER.parsePathAttributes();
+ } else throw new PKCS15Exception("[Parser] OID Tag expected");
+ } else DER.skipTLVData();
+ }
+ return null; // No "Access Control" OID found
+ }
+
+
+ /**
+ * Constructor
+ * @param secureElement SE on which ISO7816 commands are applied
+ */
+ public EFDODF(SecureElement handle) {
+ super(handle);
+ }
+
+ /**
+ * Selects and Analyses EF_DODF file
+ * @param path Path of the "EF_DODF" file
+ * @return Path to "EF_ACMain" from "Access Control" OID;
+ * <code>null</code> otherwise
+ */
+ public byte[] analyseFile(byte[] path)
+ throws PKCS15Exception,SecureElementException
+ {
+ Log.v(TAG,"Analysing EF_DODF...");
+
+ if (selectFile(path)!=APDU_SUCCESS)
+ throw new PKCS15Exception("EF_DODF not found!");
+
+ return decodeDER(readBinary(0,Util.END));
+ }
+
+}
+
+
+
diff --git a/src/org/simalliance/openmobileapi/service/security/arf/PKCS15/EFODF.java b/src/org/simalliance/openmobileapi/service/security/arf/PKCS15/EFODF.java
new file mode 100644
index 0000000..fdb344b
--- /dev/null
+++ b/src/org/simalliance/openmobileapi/service/security/arf/PKCS15/EFODF.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2011 Deutsche Telekom, A.G.
+ *
+ * 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 org.simalliance.openmobileapi.service.security.arf.PKCS15;
+
+import org.simalliance.openmobileapi.service.security.arf.DERParser;
+import org.simalliance.openmobileapi.service.security.arf.SecureElement;
+import org.simalliance.openmobileapi.service.security.arf.SecureElementException;
+import org.simalliance.openmobileapi.service.Util;
+import org.simalliance.openmobileapi.service.security.arf.PKCS15.PKCS15Exception;
+
+import android.util.Log;
+
+/**
+ * EF_ODF related features
+ ***************************************************/
+public class EFODF extends EF {
+
+ public static final String TAG = "SmartcardService ACE ARF";
+ // Standardized ID for EF_ODF file
+ public static final byte[] EFODF_PATH = { 0x50,0x31 };
+
+ /**
+ * Decodes EF_ODF file
+ * @param buffer ASN.1 data
+ * @return Path to "EF_DODF" from "DODF Tag" entry;
+ * <code>null</code> otherwise
+ */
+ private byte[] decodeDER(byte[] buffer)
+ throws PKCS15Exception {
+ DERParser DER=new DERParser(buffer);
+ while(!DER.isEndofBuffer()) {
+ if (DER.parseTLV()==(byte)0xA7) { // DODF
+ return DER.parsePathAttributes();
+ } else DER.skipTLVData();
+ } return null; // No "DODF Tag" entry found
+ }
+
+
+ /**
+ * Constructor
+ * @param secureElement SE on which ISO7816 commands are applied
+ */
+ public EFODF(SecureElement handle) {
+ super(handle);
+ }
+
+ /**
+ * Selects and Analyses EF_ODF file
+ * @return Path to "EF_DODF" from "DODF Tag" entry;
+ * <code>null</code> otherwise
+ */
+ public byte[] analyseFile( byte[] pkcs15Path ) throws PKCS15Exception,SecureElementException {
+ Log.v(TAG,"Analysing EF_ODF...");
+
+
+ // 2012-04-12
+ // extend path if ODF path was determined from EF DIR.
+ byte[] path = null;
+ if( pkcs15Path != null ){
+ path = new byte[pkcs15Path.length + EFODF_PATH.length];
+ System.arraycopy(pkcs15Path, 0, path, 0, pkcs15Path.length);
+ System.arraycopy(EFODF_PATH, 0, path, pkcs15Path.length, EFODF_PATH.length );
+ } else {
+ path = EFODF_PATH;
+ }
+ //---
+
+ if ( selectFile(path)!= APDU_SUCCESS)
+ throw new PKCS15Exception("EF_ODF not found!!");
+
+ return decodeDER(readBinary(0,Util.END));
+ }
+
+}
diff --git a/src/org/simalliance/openmobileapi/service/security/arf/PKCS15/PKCS15Exception.java b/src/org/simalliance/openmobileapi/service/security/arf/PKCS15/PKCS15Exception.java
new file mode 100644
index 0000000..b61b103
--- /dev/null
+++ b/src/org/simalliance/openmobileapi/service/security/arf/PKCS15/PKCS15Exception.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2011 Deutsche Telekom, A.G.
+ *
+ * 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 org.simalliance.openmobileapi.service.security.arf.PKCS15;
+
+/**
+ * Handles PKCS#15 errors
+ ***************************************************/
+public class PKCS15Exception extends Exception {
+
+ private static final long serialVersionUID = 1556408586814064005L;
+
+ public PKCS15Exception(String message) {
+ super(message);
+ }
+
+}
diff --git a/src/org/simalliance/openmobileapi/service/security/arf/PKCS15/PKCS15Handler.java b/src/org/simalliance/openmobileapi/service/security/arf/PKCS15/PKCS15Handler.java
new file mode 100644
index 0000000..08c22e1
--- /dev/null
+++ b/src/org/simalliance/openmobileapi/service/security/arf/PKCS15/PKCS15Handler.java
@@ -0,0 +1,270 @@
+/*
+ * Copyright (C) 2011 Deutsche Telekom, A.G.
+ *
+ * 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 org.simalliance.openmobileapi.service.security.arf.PKCS15;
+
+import java.security.AccessControlException;
+import java.util.MissingResourceException;
+
+import org.simalliance.openmobileapi.service.IChannel;
+import org.simalliance.openmobileapi.service.security.arf.SecureElement;
+import org.simalliance.openmobileapi.service.security.arf.SecureElementException;
+import org.simalliance.openmobileapi.service.security.arf.PKCS15.EFACMain;
+import org.simalliance.openmobileapi.service.security.arf.PKCS15.EFACRules;
+import org.simalliance.openmobileapi.service.security.arf.PKCS15.EFDIR;
+import org.simalliance.openmobileapi.service.security.arf.PKCS15.EFDODF;
+import org.simalliance.openmobileapi.service.security.arf.PKCS15.EFODF;
+import org.simalliance.openmobileapi.service.security.arf.PKCS15.PKCS15Exception;
+
+import android.os.Build;
+import android.os.SystemProperties;
+import android.util.Log;
+
+/**
+ * Handles PKCS#15 topology
+ ***************************************************/
+public class PKCS15Handler {
+
+ public static final String TAG = "SmartcardService ACE ARF";
+
+ // AID of the GPAC Applet/ADF
+ public static final byte[] GPAC_ARF_AID =
+ {(byte)0xA0,0x00,0x00,0x00,0x18,0x47,0x50,0x41,0x43,0x2D,0x31,0x35};
+ // AID of the PKCS#15 ADF
+ public static final byte[] PKCS15_AID =
+ { (byte)0xA0,0x00,0x00,0x00,0x63,0x50,0x4B,0x43,0x53,0x2D,0x31,0x35 };
+
+ // AIDs of "Access Control Rules" containers
+ public static final byte[][] CONTAINER_AIDS= {
+ PKCS15_AID,
+ GPAC_ARF_AID,
+ null
+ };
+
+ // Handle to "Secure Element"
+ private SecureElement mSEHandle;
+ // "Secure Element" label
+ private String mSELabel=null;
+
+ // Handle to "Logical Channel" allocated by the SE
+ private IChannel mArfChannel=null;
+
+ // "EF Access Control Main" object
+ private EFACMain mACMainObject=null;
+ // EF AC Rules object
+ private EFACRules mACRulesObject=null;
+
+ private byte[] mPkcs15Path = null;
+ private byte[] mACMainPath = null;
+
+ // SIM Allowed modes:
+ private boolean mSimIoAllowed;
+ private boolean mSimAllianceAllowed;
+
+ /**
+ * Updates "Access Control Rules"
+ */
+ private boolean updateACRules()
+ throws Exception, PKCS15Exception, SecureElementException
+ {
+ byte[] ACRulesPath=null;
+ try {
+ ACRulesPath=mACMainObject.analyseFile();
+ } catch (Exception e) {
+ mACMainObject=null;
+ mSEHandle.resetAccessRules();
+ throw e;
+ }
+ // Check if rules must be updated
+ if (ACRulesPath != null) {
+ Log.d(TAG, "Access Rules needs to be updated...");
+ if (mACRulesObject==null) {
+ mACRulesObject=new EFACRules(mSEHandle);
+ }
+ mSEHandle.clearAccessRuleCache();
+ mACRulesObject.analyseFile(ACRulesPath);
+ return true;
+ } else {
+ Log.d(TAG, "Refresh Tag has not been changed...");
+ return false;
+ }
+ }
+
+ /**
+ * Initializes "Access Control" entry point [ACMain]
+ */
+ private void initACEntryPoint()
+ throws PKCS15Exception, SecureElementException
+ {
+
+ byte[] DODFPath=null;
+
+ readAllowedSimMode();
+
+ for(int ind=0;ind<CONTAINER_AIDS.length;ind++) {
+ if (selectACRulesContainer(CONTAINER_AIDS[ind])) {
+
+ byte[] acMainPath = null;
+ if( mACMainPath==null){
+ EFODF ODFObject=new EFODF(mSEHandle);
+ DODFPath=ODFObject.analyseFile(mPkcs15Path);
+ EFDODF DODFObject=new EFDODF(mSEHandle);
+ acMainPath=DODFObject.analyseFile(DODFPath);
+ mACMainPath = acMainPath;
+ } else {
+ if( mPkcs15Path != null ) {
+ acMainPath = new byte[mPkcs15Path.length + mACMainPath.length];
+ System.arraycopy(mPkcs15Path, 0, acMainPath, 0, mPkcs15Path.length);
+ System.arraycopy(mACMainPath, 0, acMainPath, mPkcs15Path.length, mACMainPath.length );
+ } else {
+ acMainPath = mACMainPath;
+ }
+ }
+ mACMainObject=new EFACMain(mSEHandle,acMainPath);
+ break;
+ }
+ }
+ }
+
+ /**
+ * Selects "Access Control Rules" container
+ * @param AID Identification of the GPAC Applet/PKCS#15 ADF;
+ * <code>null</code> for EF_DIR file
+ * @return <code>true</code> when container is active;
+ * <code>false</code> otherwise
+ */
+ private boolean selectACRulesContainer(byte[] aid)
+ throws PKCS15Exception,SecureElementException
+ {
+ boolean isActiveContainer=true;
+
+ if (aid==null) {
+ mArfChannel = null;
+
+ // some devices use logical channels to access filesystem directly. This is done with an empty byte array.
+ // if open logical channel does not work, last fallback is using SIM_IO (AT-CRSM).
+ // 2012-11-08
+ if(mSimAllianceAllowed)
+ mArfChannel = mSEHandle.openLogicalArfChannel(new byte[]{});
+
+ if (mArfChannel != null) {
+ Log.i(TAG, "Logical channels are used to access to PKC15");
+ mSEHandle.setSeInterface(SecureElement.SIM_ALLIANCE);
+ }
+ else {
+ if(mSimIoAllowed) {
+ // Since ARF gets only active if the terminal belongs to a SIM/UICC
+ // we have to switch to SIM_IO
+ Log.i(TAG, "Fall back into ARF with SIM_IO");
+ mSEHandle.setSeInterface(SecureElement.SIM_IO);
+ }
+ else {
+ Log.i(TAG, "SIM IO is not allowed: cannot access to ARF");
+ isActiveContainer = false;
+ }
+ }
+
+ if(isActiveContainer && mPkcs15Path == null ) { // estimate PKCS15 path only if it is not known already.
+ mACMainPath = null;
+ // EF_DIR parsing
+ EFDIR DIRObject=new EFDIR(mSEHandle);
+ mPkcs15Path=DIRObject.lookupAID(PKCS15_AID);
+ if( mPkcs15Path == null ) {
+ Log.i(TAG, "Cannot use ARF: cannot select PKCS#15 directory via EF Dir");
+ // TODO: Here it might be possible to set a default path
+ // so that SIMs without EF-Dir could be supported.
+ throw new PKCS15Exception("Cannot select PKCS#15 directory via EF Dir");
+ }
+ }
+ }
+ // if an AID is given use logical channel.
+ else {
+ if(!mSimAllianceAllowed) {
+ isActiveContainer = false;
+ }
+ else {
+ // Selection of Applet/ADF via AID is done via SCAPI and logical Channels
+ mSEHandle.setSeInterface(SecureElement.SIM_ALLIANCE);
+ if ((mArfChannel=mSEHandle.openLogicalArfChannel(aid))==null) {
+ isActiveContainer=false;
+ Log.w(TAG,"GPAC/PKCS#15 ADF not found!!");
+ }
+ else {
+ // ARF is selected via AID.
+ if( mPkcs15Path != null ){ // if there is a change from path selection to AID selection, then reset AC Main path.
+ mACMainPath = null;
+ }
+ mPkcs15Path = null; // selection is done via AID
+ }
+ }
+ }
+ return isActiveContainer;
+ }
+
+ /**
+ * Constructor
+ * @param handle Handle to "Secure Element"
+ */
+ public PKCS15Handler(SecureElement handle) {
+ mSEHandle=handle;
+ }
+
+ /**
+ * Loads "Access Control Rules" from container
+ * @return false if access rules where not read due to constant refresh tag.
+ */
+ public synchronized boolean loadAccessControlRules(String secureElement) {
+ mSELabel=secureElement;
+ Log.v(TAG,"- Loading "+mSELabel+" rules...");
+ try {
+ initACEntryPoint();
+ return updateACRules();
+ } catch (Exception e) {
+ if( e instanceof MissingResourceException ){
+ // this indicates that no channel is left for accessing the SE element
+ throw (MissingResourceException)e;
+ }
+ Log.e(TAG,mSELabel+" rules not correctly initialized! " + e.getLocalizedMessage());
+ throw new AccessControlException(e.getLocalizedMessage());
+ } finally {
+ // Close previously opened channel
+ if (mArfChannel!=null)
+ mSEHandle.closeArfChannel();
+ }
+ }
+
+ /**
+ * Read security allowed sim mode
+ */
+ private void readAllowedSimMode() {
+ if(!Build.IS_DEBUGGABLE) {
+ mSimIoAllowed = true;
+ mSimAllianceAllowed = true;
+ } else {
+ String level = SystemProperties.get("service.seek.arf", "simio simalliance");
+ level = SystemProperties.get("persist.service.seek.arf", level);
+
+ if(level.contains("simio")) mSimIoAllowed = true; else mSimIoAllowed = false;
+ if(level.contains("simalliance")) mSimAllianceAllowed = true; else mSimAllianceAllowed = false;
+ }
+
+ Log.i(TAG, "Allowed SIM mode: SimIo=" + mSimIoAllowed + " SimAlliance=" + mSimAllianceAllowed );
+ }
+}
diff --git a/src/org/simalliance/openmobileapi/service/security/arf/SecureElement.java b/src/org/simalliance/openmobileapi/service/security/arf/SecureElement.java
new file mode 100644
index 0000000..cbe4952
--- /dev/null
+++ b/src/org/simalliance/openmobileapi/service/security/arf/SecureElement.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2011 Deutsche Telekom, A.G.
+ *
+ * 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 org.simalliance.openmobileapi.service.security.arf;
+
+import android.util.Log;
+import java.util.MissingResourceException;
+import org.simalliance.openmobileapi.service.IChannel;
+import org.simalliance.openmobileapi.service.ISmartcardServiceCallback;
+import org.simalliance.openmobileapi.service.ITerminal;
+import org.simalliance.openmobileapi.service.SmartcardService;
+import org.simalliance.openmobileapi.service.Util;
+import org.simalliance.openmobileapi.service.security.ChannelAccess;
+import org.simalliance.openmobileapi.service.security.arf.SecureElementException;
+import org.simalliance.openmobileapi.service.security.arf.PKCS15.EF;
+import org.simalliance.openmobileapi.service.security.gpac.dataobjects.AID_REF_DO;
+import org.simalliance.openmobileapi.service.security.gpac.dataobjects.Hash_REF_DO;
+import org.simalliance.openmobileapi.service.security.gpac.dataobjects.REF_DO;
+
+/**
+ * Provides high-level functions for SE communication
+ ***************************************************/
+public class SecureElement {
+
+ public static final String TAG = "SmartcardService ACE ARF";
+
+ // Logical channel used for SE communication (optional)
+ private IChannel mArfChannel=null;
+ // Handle to a built-in "Secure Element"
+ private ITerminal mTerminalHandle=null;
+ // Arf Controller within the SCAPI handler
+ private ArfController mArfHandler=null;
+ // Callback used during "Secure Element" communication
+ private final ISmartcardServiceCallback mCallback =
+ new ISmartcardServiceCallback.Stub(){};
+
+ public static final short SIM_IO = 1;
+ public static final short SIM_ALLIANCE = 0;
+
+ // Interface for exchanging APDU commands
+ private short mSEInterface=SIM_ALLIANCE;
+
+ /**
+ * Constructor
+ *
+ * @param arfHandler - handle to the owning arf controller object
+ * @param handle - handle to the SE terminal to be accessed.
+ */
+ public SecureElement(ArfController arfHandler,ITerminal handle) {
+ mTerminalHandle=handle;
+ mArfHandler=arfHandler;
+ }
+
+ public short getSeInterface(){
+ return mSEInterface;
+ }
+
+ public void setSeInterface(short seInterface){
+ mSEInterface = seInterface;
+ }
+
+ /**
+ * Transmits ADPU commands
+ * @param cmd APDU command
+ * @return Data returned by the APDU command
+ */
+ public byte[] exchangeAPDU(EF ef, byte[] cmd)
+ throws SecureElementException {
+ try {
+ if (mSEInterface==SIM_IO) {
+
+ return mTerminalHandle.simIOExchange(ef.getFileId(),ef.getFilePath(),cmd);
+ } else {
+
+ return mArfChannel.transmit(cmd);
+ }
+ } catch (Exception e) {
+ throw new SecureElementException("Secure Element access error " + e.getLocalizedMessage());
+ }
+ }
+
+ /**
+ * Opens a logical channel to ARF Applet or ADF
+ * @param AID Applet identifier
+ * @return Handle to "Logical Channel" allocated by the SE;
+ * <code>0</code> if error occurred
+ */
+ public IChannel openLogicalArfChannel(byte[] AID) {
+ try {
+
+ mArfChannel=mTerminalHandle.openLogicalChannel(null,AID,mCallback);
+ setUpChannelAccess(mArfChannel);
+ return mArfChannel;
+ } catch(Exception e) {
+ if( e instanceof MissingResourceException ){
+ // this indicates that no channel is left for accessing the SE element
+ Log.d(TAG, "no channels left to access ARF: " + e.getMessage() );
+ throw (MissingResourceException)e;
+ } else {
+ Log.e(TAG,"Error opening logical channel " + e.getLocalizedMessage());
+ }
+ mArfChannel = null;
+ return null;
+ }
+ }
+
+ /**
+ * Closes a logical channel previously allocated by the SE
+ * @param handle Handle to open channel
+ */
+ public void closeArfChannel() {
+ try {
+ if( mArfChannel != null){
+
+ mArfChannel.close();
+ mArfChannel = null;
+ } else {
+
+ }
+
+ } catch(Exception e) {
+ Log.e(TAG,"Error closing channel " + e.getLocalizedMessage());
+ }
+ }
+
+ /**
+ * Set up channel access to allow,
+ * so that PKCS15 files can be read.
+ *
+ * @param channel
+ */
+ private void setUpChannelAccess( IChannel channel ){
+ // set access conditions to access ARF.
+ ChannelAccess arfChannelAccess = new ChannelAccess();
+ arfChannelAccess.setAccess(ChannelAccess.ACCESS.ALLOWED, "");
+ arfChannelAccess.setApduAccess(ChannelAccess.ACCESS.ALLOWED);
+ channel.setChannelAccess(arfChannelAccess);
+
+ }
+
+ public byte[] getRefreshTag() {
+ if( mArfHandler != null ){
+ return mArfHandler.getAccessRuleCache().getRefreshTag();
+ }
+ return null;
+ }
+
+ public void setRefreshTag(byte[] refreshTag) {
+ if( mArfHandler != null ) {
+ mArfHandler.getAccessRuleCache().setRefreshTag(refreshTag);
+ }
+ }
+
+ public void putAccessRule( AID_REF_DO aid_ref_do, Hash_REF_DO hash_ref_do, ChannelAccess channelAccess ) {
+
+ REF_DO ref_do = new REF_DO(aid_ref_do, hash_ref_do);
+ mArfHandler.getAccessRuleCache().putWithMerge(ref_do, channelAccess);
+ }
+
+ public void resetAccessRules() {
+ this.mArfHandler.getAccessRuleCache().reset();
+ }
+ public void clearAccessRuleCache() {
+ this.mArfHandler.getAccessRuleCache().clearCache();
+ }
+}
diff --git a/src/org/simalliance/openmobileapi/service/security/arf/SecureElementException.java b/src/org/simalliance/openmobileapi/service/security/arf/SecureElementException.java
new file mode 100644
index 0000000..4580f85
--- /dev/null
+++ b/src/org/simalliance/openmobileapi/service/security/arf/SecureElementException.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2011 Deutsche Telekom, A.G.
+ *
+ * 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 org.simalliance.openmobileapi.service.security.arf;
+
+/**
+ * Handles "Secure Element" access errors
+ ***************************************************/
+public class SecureElementException extends Exception {
+
+ private static final long serialVersionUID = 530360632436123998L;
+
+ public SecureElementException(String message) {
+ super(message);
+ }
+
+}
\ No newline at end of file
diff --git a/src/org/simalliance/openmobileapi/service/security/gpac/dataobjects/AID_REF_DO.java b/src/org/simalliance/openmobileapi/service/security/gpac/dataobjects/AID_REF_DO.java
new file mode 100644
index 0000000..a092720
--- /dev/null
+++ b/src/org/simalliance/openmobileapi/service/security/gpac/dataobjects/AID_REF_DO.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright 2012 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 org.simalliance.openmobileapi.service.security.gpac.dataobjects;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.Arrays;
+
+
+/**
+ * The AID-REF-DO is used for retrieving and storing
+ * the corresponding access rules for an SE application
+ * (which is identified by its AID) from and to the ARA.
+ * Two different AID reference data objects exist and one
+ * of these can be chosen and applied for
+ * a GET DATA and STORE DATA command
+ *
+ *
+ *
+ */
+public class AID_REF_DO extends BerTlv {
+
+ public final static int _TAG = 0x4F;
+ public final static int _TAG_DEFAULT_APPLICATION = 0xC0;
+
+ private byte[] mAid = null;
+
+ public AID_REF_DO(byte[] rawData, int tag, int valueIndex, int valueLength) {
+ super(rawData, tag, valueIndex, valueLength);
+ }
+
+
+ public AID_REF_DO( int tag, byte[] aid){
+ super( aid, tag, 0, (aid==null ? 0 : aid.length));
+ mAid = aid;
+ }
+
+ public AID_REF_DO( int tag){
+ super( null, tag, 0, 0);
+ mAid = tag == _TAG_DEFAULT_APPLICATION ? null : new byte[0];
+ }
+
+ @Override
+ public String toString(){
+ StringBuilder b = new StringBuilder();
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ b.append("AID_REF_DO: ");
+ try {
+ this.build(out);
+ b.append(BerTlv.toHex(out.toByteArray()));
+ } catch (Exception e ){
+ b.append(e.getLocalizedMessage());
+ }
+ return b.toString();
+ }
+
+ public byte[] getAid(){
+ return mAid;
+ }
+
+ /**
+ * Tags: C0 -> Length: 0 -> Default selected application (all channels)
+ * 4F -> Length: 0 or 5 - 16 bytes
+ *
+ * Value:
+ * AID: identifies a specific application
+ * Empty: refers to all SE applications
+ *
+ * Length:
+ * 5-16 for an AID according to ISO/IEC7816-5
+ * 0 for empty value field
+ */
+ @Override
+ public void interpret()
+ throws ParserException {
+
+ mAid = null;
+
+ byte[] data = getRawData();
+ int index = getValueIndex();
+
+ if( getTag() == _TAG_DEFAULT_APPLICATION ) {
+ if( getValueLength() != 0 ){
+ throw new ParserException("Invalid value length for AID-REF-DO!");
+ }
+ } else if( getTag() == _TAG ){
+
+ // sanity checks
+ if( (getValueLength() < 5 || getValueLength() > 16) && getValueLength() != 0) {
+ throw new ParserException("Invalid value length for AID-REF-DO!");
+ }
+
+ if( index + getValueLength() > data.length){
+ throw new ParserException( "Not enough data for AID-REF-DO!");
+ }
+
+ mAid = new byte[getValueLength()];
+ System.arraycopy(data, index, mAid, 0, getValueLength());
+
+ } else {
+ throw new ParserException( "Invalid Tag for AID-REF-DO!");
+ }
+ }
+
+ /**
+ * Tags: C0 -> Length: 0 -> Default selected application (all channels)
+ * 4F -> Length: 0 or 5 - 16 bytes
+ *
+ * Value:
+ * AID: identifies a specific application
+ * Empty: refers to all SE applications
+ *
+ * Length:
+ * 5-16 for an AID according to ISO/IEC7816-5
+ * 0 for empty value field
+ */
+ @Override
+ public void build( ByteArrayOutputStream stream)
+ throws DO_Exception {
+
+ if( getTag() == _TAG_DEFAULT_APPLICATION ) {
+ if( mAid != null ){
+ throw new DO_Exception("No value allowed for default selected application!");
+ }
+ stream.write(getTag());
+ stream.write(0x00);
+ } else if( getTag() == _TAG ){
+
+ // sanity check
+ if( getValueLength() != 0 ) {
+ if (getValueLength() < 5 || getValueLength() > 16) {
+ throw new DO_Exception("Invalid length of AID!");
+ }
+ }
+
+ stream.write(getTag());
+ if( mAid != null && mAid.length > 0 ) {
+ stream.write(mAid.length);
+ try {
+ stream.write(mAid);
+ } catch( IOException ioe ){
+ throw new DO_Exception("AID could not be written!");
+ }
+ } else {
+ stream.write(0x00);
+ }
+
+ } else {
+ throw new DO_Exception( "AID-REF-DO must either be C0 or 4F!");
+ }
+ }
+
+ @Override
+ public boolean equals( Object obj ){
+ boolean equals = false;
+
+ if( obj instanceof AID_REF_DO ){
+ equals = super.equals(obj);
+
+ if( equals ){
+ AID_REF_DO aid_ref_do = (AID_REF_DO)obj;
+ if( this.mAid == null && aid_ref_do.mAid == null )
+ equals &= true;
+ else {
+ equals &= Arrays.equals(mAid, aid_ref_do.mAid);
+ }
+ }
+ }
+ return equals;
+ }
+}
diff --git a/src/org/simalliance/openmobileapi/service/security/gpac/dataobjects/APDU_AR_DO.java b/src/org/simalliance/openmobileapi/service/security/gpac/dataobjects/APDU_AR_DO.java
new file mode 100644
index 0000000..41f477e
--- /dev/null
+++ b/src/org/simalliance/openmobileapi/service/security/gpac/dataobjects/APDU_AR_DO.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright 2012 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 org.simalliance.openmobileapi.service.security.gpac.dataobjects;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+
+
+/**
+ * APDU-AR-DO:
+ * An APDU access rule data object defines an access rule for APDU access.
+ * The APDU access can either be restricted by a general rule
+ * based on an access is NEVER/ ALWAYS allowed policy or
+ * by a specific rule based on APDU filters which defines the range
+ * of allowed APDUs more precisely.
+ *
+ *
+ *
+ */
+public class APDU_AR_DO extends BerTlv {
+
+ public final static int _TAG = 0xD0;
+
+ private boolean mApduAllowed = false;
+ private ArrayList<byte[]> mApduHeader = new ArrayList<byte[]>();
+ private ArrayList<byte[]> mFilterMask = new ArrayList<byte[]>();
+
+ public APDU_AR_DO(byte[] rawData, int valueIndex, int valueLength) {
+ super(rawData, _TAG, valueIndex, valueLength);
+ }
+
+ public APDU_AR_DO( boolean allowed ){
+ super( null, _TAG, 0, 0);
+ mApduAllowed = allowed;
+ }
+
+ public APDU_AR_DO( ArrayList<byte[]> apduHeader, ArrayList<byte[]> filterMask ){
+ super( null, _TAG, 0, 0);
+ mApduHeader = apduHeader;
+ mFilterMask = filterMask;
+ }
+
+ public boolean isApduAllowed(){
+ return mApduAllowed;
+ }
+
+ public ArrayList<byte[]> getApduHeaderList(){
+ return mApduHeader;
+ }
+
+ public ArrayList<byte[]> getFilterMaskList(){
+ return mFilterMask;
+ }
+
+ @Override
+ /**
+ * Tag: D0
+ * Length: 1 or n*8
+ * 1 if value contains a general APDU access rule.
+ * n*8 if value contains a specific APDU access rule.
+
+ * Value:
+ * Contains a general APDU access rule:
+ * NEVER (00): APDU access is not allowed
+ * ALWAYS(01): APDU access is allowed
+ * or
+ * contains a specific APDU access rule based on one or more APDU filter(s):
+ * APDU filter: 8 bytes APDU filter mask consists of:
+ * 4 bytes APDU header (defines the header of allowed APDUs)
+ * 4 bytes APDU mask (bit set defines the bits which shall be considered
+ * for the APDU header comparison)
+ * An APDU filter has to be applied as follows:
+ * if((APDUHeader & FilterMask) == FilterAPDUHeader)
+ * then allow APDU
+ */
+ public void interpret()
+ throws ParserException {
+
+ mApduAllowed = false;
+ mApduHeader.clear();
+ mFilterMask.clear();
+
+ byte[] data = getRawData();
+ int index = getValueIndex();
+
+ if( index + getValueLength() > data.length){
+ throw new ParserException( "Not enough data for APDU_AR_DO!");
+ }
+
+ // APDU-AR-DO contains either a flag which allows/disallows APDU communication
+ // or
+ // it contains APDU filter (APDUHeader | FilterMask) which should have length n*8.
+ if( getValueLength() == 1 ){
+ mApduAllowed = (data[index] == 0x01);
+ } else if(getValueLength() % 8 == 0 ) {
+ mApduAllowed = true;
+
+ for( int i = index; i < index + getValueLength(); i +=8 ){
+ byte[] apduHeader = new byte[4];
+ byte[] filterMask = new byte[4];
+
+ apduHeader[0] = data[i+0];
+ apduHeader[1] = data[i+1];
+ apduHeader[2] = data[i+2];
+ apduHeader[3] = data[i+3];
+ filterMask[0] = data[i+4];
+ filterMask[1] = data[i+5];
+ filterMask[2] = data[i+6];
+ filterMask[3] = data[i+7];
+
+ mApduHeader.add(apduHeader);
+ mFilterMask.add(filterMask);
+ }
+ } else {
+ throw new ParserException( "Invalid length of APDU-AR-DO!" );
+ }
+ }
+
+ @Override
+ /**
+ * Tag: D0
+ * Length: 1 or n*8
+ * 1 if value contains a general APDU access rule.
+ * n*8 if value contains a specific APDU access rule.
+
+ * Value:
+ * Contains a general APDU access rule:
+ * NEVER (00): APDU access is not allowed
+ * ALWAYS(01): APDU access is allowed
+ * or
+ * contains a specific APDU access rule based on one or more APDU filter(s):
+ * APDU filter: 8 bytes APDU filter mask consists of:
+ * 4 bytes APDU header (defines the header of allowed APDUs)
+ * 4 bytes APDU mask (bit set defines the bits which shall be considered
+ * for the APDU header comparison)
+ * An APDU filter has to be applied as follows:
+ * if((APDUHeader & FilterMask) == FilterAPDUHeader)
+ * then allow APDU
+ */
+ public void build( ByteArrayOutputStream stream )
+ throws DO_Exception {
+
+ // APDU header and filter mask has to have the same size
+ // even if they are not used (then size() == 0 ).
+ if(mApduHeader.size() != this.mFilterMask.size()){
+ throw new DO_Exception( "APDU filter is invalid");
+ }
+
+ // write tag
+ stream.write(getTag());
+
+ // check if APDU Flag shall be written
+ if( mApduHeader.size() == 0){
+ stream.write(0x01);
+ stream.write(this.mApduAllowed ? 0x01 : 0x00 );
+ } else {
+ ByteArrayOutputStream temp = new ByteArrayOutputStream();
+ for( int i = 0; i < mApduHeader.size(); i++ ){
+ byte[] apduHeader = mApduHeader.get(i);
+ byte[] filterMask = mFilterMask.get(i);
+
+ if( apduHeader.length != 4 || filterMask.length != 4 ){
+ throw new DO_Exception("APDU filter is invalid!");
+ }
+
+ try {
+ temp.write(apduHeader);
+ temp.write(filterMask);
+ } catch (IOException e) {
+ throw new DO_Exception("APDU Filter Memory IO problem! " + e.getMessage());
+ }
+ }
+
+ BerTlv.encodeLength(temp.size(), stream);
+ try {
+ stream.write(temp.toByteArray());
+ } catch (IOException e) {
+ throw new DO_Exception("APDU Filter Memory IO problem! " + e.getMessage());
+ }
+ }
+ }
+}
diff --git a/src/org/simalliance/openmobileapi/service/security/gpac/dataobjects/AR_DO.java b/src/org/simalliance/openmobileapi/service/security/gpac/dataobjects/AR_DO.java
new file mode 100644
index 0000000..b87e7c6
--- /dev/null
+++ b/src/org/simalliance/openmobileapi/service/security/gpac/dataobjects/AR_DO.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright 2012 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 org.simalliance.openmobileapi.service.security.gpac.dataobjects;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+
+/**
+ * This class represents the Access rule data object (AR-DO), according
+ * to GP Secure Element Control Access.
+ *
+ * The AR-DO contains one or two access rules of type APDU or NFC.
+ *
+ *
+ *
+ */
+public class AR_DO extends BerTlv{
+
+ public final static int _TAG = 0xE3;
+
+ private APDU_AR_DO mApduAr = null;
+ private NFC_AR_DO mNfcAr = null;
+
+ public AR_DO(byte[] rawData, int valueIndex, int valueLength) {
+ super(rawData, _TAG, valueIndex, valueLength);
+ }
+
+ public AR_DO( APDU_AR_DO apdu_ar_do, NFC_AR_DO nfc_ar_do ){
+ super( null, _TAG, 0, 0);
+ mApduAr = apdu_ar_do;
+ mNfcAr = nfc_ar_do;
+ }
+
+ public APDU_AR_DO getApduArDo(){
+ return mApduAr;
+ }
+
+ public NFC_AR_DO getNfcArDo(){
+ return mNfcAr;
+ }
+
+ @Override
+ /**
+ * Interpret value.
+ *
+ * Tag: E3
+ *
+ * Value:
+ * Value can contain APDU-AR-DO or NFC-AR-DO or APDU-AR-DO | NFC-AR-DO
+ * A concatenation of one or two AR-DO(s). If two AR-DO(s) are present
+ * these must have different types.
+ */
+ public void interpret()
+ throws ParserException {
+
+ this.mApduAr = null;
+ this.mNfcAr = null;
+
+ byte[] data = getRawData();
+ int index = getValueIndex();
+
+ if( index + getValueLength() > data.length){
+ throw new ParserException( "Not enough data for AR_DO!");
+ }
+
+ do {
+ BerTlv temp = BerTlv.decode(data, index);
+
+ if( temp.getTag() == APDU_AR_DO._TAG ) { // APDU-AR-DO
+ mApduAr = new APDU_AR_DO( data, temp.getValueIndex(), temp.getValueLength());
+ mApduAr.interpret();
+ } else if( temp.getTag() == NFC_AR_DO._TAG ) { // NFC-AR-DO
+ mNfcAr = new NFC_AR_DO( data, temp.getValueIndex(), temp.getValueLength());
+ mNfcAr.interpret();
+ } else {
+ // un-comment following line if a more restrictive
+ // behavior is necessary.
+ //throw new ParserException("Invalid DO in AR-DO!");
+ }
+ index = temp.getValueIndex() + temp.getValueLength();
+ } while ( getValueIndex() + getValueLength() > index );
+
+ if( mApduAr == null && mNfcAr == null ){
+ throw new ParserException("No valid DO in AR-DO!");
+ }
+ }
+
+ @Override
+ /**
+ * Interpret value.
+ *
+ * Tag: E3
+ *
+ * Value:
+ * Value can contain APDU-AR-DO or NFC-AR-DO or APDU-AR-DO | NFC-AR-DO
+ * A concatenation of one or two AR-DO(s). If two AR-DO(s) are present
+ * these must have different types.
+ */
+ public void build( ByteArrayOutputStream stream )
+ throws DO_Exception {
+
+ // write tag
+ stream.write(getTag());
+
+ ByteArrayOutputStream temp = new ByteArrayOutputStream();
+ if( mApduAr != null ){
+ mApduAr.build(temp);
+ }
+
+ if( mNfcAr != null ){
+ mNfcAr.build(temp);
+ }
+
+ BerTlv.encodeLength(temp.size(), stream);
+ try {
+ stream.write(temp.toByteArray());
+ } catch (IOException e) {
+ throw new DO_Exception("AR-DO Memory IO problem! " + e.getMessage());
+ }
+ }
+}
diff --git a/src/org/simalliance/openmobileapi/service/security/gpac/dataobjects/BerTlv.java b/src/org/simalliance/openmobileapi/service/security/gpac/dataobjects/BerTlv.java
new file mode 100644
index 0000000..7acbc6d
--- /dev/null
+++ b/src/org/simalliance/openmobileapi/service/security/gpac/dataobjects/BerTlv.java
@@ -0,0 +1,259 @@
+/*
+ * Copyright 2012 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 org.simalliance.openmobileapi.service.security.gpac.dataobjects;
+
+import java.io.ByteArrayOutputStream;
+import java.util.Arrays;
+
+
+public class BerTlv {
+
+ private byte[] mRawData = null;
+
+ private int mTag = 0;
+
+ private int mValueIndex = 0;
+ private int mValueLength = 0;
+
+ public BerTlv( byte[] rawData, int tag, int valueIndex, int valueLength ) {
+ mRawData = rawData;
+ mTag = tag;
+ mValueIndex = valueIndex;
+ mValueLength = valueLength;
+ }
+
+ public static String toHex(byte[] digest) {
+ String digits = "0123456789abcdef";
+ StringBuilder sb = new StringBuilder(digest.length * 2);
+ for (byte b : digest) {
+ int bi = b & 0xff;
+ sb.append(digits.charAt(bi >> 4));
+ sb.append(digits.charAt(bi & 0xf));
+ }
+ return sb.toString();
+ }
+
+ public static BerTlv decode( byte[] data, int startIndex )
+ throws ParserException {
+ return BerTlv.decode(data, startIndex, true);
+ }
+
+ public static BerTlv decode( byte[] data, int startIndex, boolean containsAllData )
+ throws ParserException {
+
+ if( data == null || data.length == 0 ){
+ throw new ParserException("No data given!");
+ }
+
+ int curIndex = startIndex;
+ int tag = 0;
+
+ /* tag */
+ if( curIndex < data.length ) {
+ int temp = data[curIndex++] & 0xff;
+ switch (temp) {
+ case 0xff: // tag is in two byte format
+ case 0xdf:
+ if( curIndex < data.length ) {
+ tag = ((temp & 0xff) << 8) | (data[curIndex++] & 0xff);
+ } else {
+ throw new ParserException("Index " + curIndex + " out of range! [0..[" + data.length);
+ }
+ break;
+
+ default: // tag is in single-byte format
+ tag = temp;
+ break;
+ }
+ } else {
+ throw new ParserException("Index " + curIndex + " out of range! [0..[" + data.length);
+ }
+
+ /* length */
+ int length;
+ if( curIndex < data.length ) {
+ int temp = data[curIndex++] & 0xff;
+ if (temp < 0x80) {
+ length = temp;
+ } else if (temp == 0x81) {
+ if( curIndex < data.length ) {
+ length = data[curIndex++] & 0xff;
+ if (length < 0x80) {
+ throw new ParserException("Invalid TLV length encoding!");
+ }
+ if(containsAllData &&
+ data.length < length + curIndex) {
+ throw new ParserException("Not enough data provided!");
+ }
+ } else {
+ throw new ParserException("Index " + curIndex + " out of range! [0..[" + data.length);
+ }
+ } else if (temp == 0x82) {
+ if( (curIndex + 1)< data.length ) {
+ length = ((data[curIndex] & 0xff) << 8) | (data[curIndex + 1] & 0xff);
+ } else {
+ throw new ParserException("Index out of range! [0..[" + data.length);
+ }
+ curIndex += 2;
+ if (length < 0x100) {
+ throw new ParserException("Invalid TLV length encoding!");
+ }
+ if (containsAllData &&
+ data.length < length + curIndex) {
+ throw new ParserException("Not enough data provided!");
+ }
+ } else if (temp == 0x83) {
+ if( (curIndex + 2)< data.length ) {
+ length = ((data[curIndex] & 0xff) << 16)
+ | ((data[curIndex + 1] & 0xff) << 8)
+ | (data[curIndex + 2] & 0xff);
+ } else {
+ throw new ParserException("Index out of range! [0..[" + data.length);
+ }
+ curIndex += 3;
+ if (length < 0x10000) {
+ throw new ParserException("Invalid TLV length encoding!");
+ }
+ if (containsAllData &&
+ data.length < length + curIndex) {
+ throw new ParserException("Not enough data provided!");
+ }
+ } else {
+ throw new ParserException("Unsupported TLV length encoding!");
+ }
+ } else {
+ throw new ParserException("Index " + curIndex + " out of range! [0..[" + data.length);
+ }
+ // create object
+ return new BerTlv( data, tag, curIndex, length);
+ }
+
+ public void interpret()
+ throws ParserException {
+ // has to be overwritten in derived classes.
+ }
+
+ /**
+ * Builds up the TLV into a byte stream.
+ *
+ * Tags can be encoded as one or two bytes
+ *
+ * @param stream
+ * @throws DO_Exception
+ */
+ public void build( ByteArrayOutputStream stream )
+ throws DO_Exception {
+
+ // put tag into stream
+ if( mTag > 0xFF ){
+ stream.write(((mTag & 0x0000FF00)>>8));
+ stream.write((mTag & 0x000000FF));
+ } else {
+ stream.write((mTag & 0x000000FF));
+ }
+
+ // write length
+ encodeLength( mValueLength, stream );
+
+ // write value
+ if( mValueLength > 0 ){
+ stream.write(mRawData, mValueIndex, mValueLength);
+ }
+ }
+
+ public int getTag(){
+ return mTag;
+ }
+
+
+ public int getValueIndex(){
+ return mValueIndex;
+ }
+
+
+ public byte[] getValue(){
+ // sanity checks
+ if( mRawData == null ||
+ mValueLength == 0 ||
+ mValueIndex < 0 || mValueIndex > mRawData.length ||
+ mValueIndex + mValueLength > mRawData.length )
+ return null;
+
+ byte[] data = new byte[mValueLength];
+
+ System.arraycopy(mRawData, mValueIndex, data, 0, mValueLength);
+
+ return data;
+ }
+
+ protected byte[] getRawData(){
+ return mRawData;
+ }
+
+ public int getValueLength() {
+ return mValueLength;
+ }
+
+ /**
+ * Encodes length according to ASN1.
+ * Supported are length values up to 3 bytes -> 83 xx yy zz.
+ *
+ * @param length
+ * @param stream
+ */
+ public static void encodeLength( int length, ByteArrayOutputStream stream){
+
+ if (length > 0x0000FFFF ) {
+ stream.write(0x83);
+ stream.write(((length & 0x00FF0000)>>16));
+ stream.write(((length & 0x0000FF00)>>8));
+ stream.write((length & 0x000000FF));
+ } else if( length > 0x000000FF){
+ stream.write(0x82);
+ stream.write(((length & 0x0000FF00)>>8));
+ stream.write((length & 0x000000FF));
+ } else if( length > 0x0000007F){
+ stream.write(0x81);
+ stream.write((length & 0x000000FF));
+ } else {
+ stream.write((length & 0x000000FF));
+ }
+ }
+
+ @Override
+ public boolean equals(Object obj){
+ boolean equals = false;
+
+ if( obj instanceof BerTlv ){
+ BerTlv berTlv = (BerTlv)obj;
+
+ equals = this.mTag == berTlv.mTag;
+
+ if(equals ){
+ byte[] test1 = this.getValue();
+ byte[] test2 = berTlv.getValue();
+
+ if( test1 != null ){
+ //equals &= test1.equals(test2);
+ equals &= Arrays.equals(test1, test2);
+ } else if( test1 == null && test2 == null ){
+ equals &= true;
+ }
+ }
+ }
+ return equals;
+ }
+}
diff --git a/src/org/simalliance/openmobileapi/service/security/gpac/dataobjects/DO_Exception.java b/src/org/simalliance/openmobileapi/service/security/gpac/dataobjects/DO_Exception.java
new file mode 100644
index 0000000..e374371
--- /dev/null
+++ b/src/org/simalliance/openmobileapi/service/security/gpac/dataobjects/DO_Exception.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2012 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 org.simalliance.openmobileapi.service.security.gpac.dataobjects;
+
+public class DO_Exception extends Exception {
+
+ /**
+ *
+ */
+ private static final long serialVersionUID = -3917637590082486538L;
+
+ public DO_Exception() {
+ super();
+ // TODO Auto-generated constructor stub
+ }
+
+
+ public DO_Exception(String arg0, Throwable arg1) {
+ super(arg0, arg1);
+ // TODO Auto-generated constructor stub
+ }
+
+ public DO_Exception(String arg0) {
+ super(arg0);
+ // TODO Auto-generated constructor stub
+ }
+
+ public DO_Exception(Throwable arg0) {
+ super(arg0);
+ // TODO Auto-generated constructor stub
+ }
+
+
+
+}
diff --git a/src/org/simalliance/openmobileapi/service/security/gpac/dataobjects/Hash_REF_DO.java b/src/org/simalliance/openmobileapi/service/security/gpac/dataobjects/Hash_REF_DO.java
new file mode 100644
index 0000000..2763765
--- /dev/null
+++ b/src/org/simalliance/openmobileapi/service/security/gpac/dataobjects/Hash_REF_DO.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright 2012 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 org.simalliance.openmobileapi.service.security.gpac.dataobjects;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.Arrays;
+
+/**
+ * Hash-REF-DO:
+ * The Hash-REF-DO is used for retrieving and storing
+ * the corresponding access rules for a device application
+ * (which is identified by the hash value of its certificate)
+ * from and to the ARA
+ *
+ *
+ *
+ */
+public class Hash_REF_DO extends BerTlv {
+
+ public final static int _TAG = 0xC1;
+ public final static int _SHA1_LEN = 20;
+
+ private byte[] mHash = null;
+
+ public Hash_REF_DO(byte[] rawData, int valueIndex, int valueLength){
+ super(rawData, _TAG, valueIndex, valueLength);
+ }
+
+ public Hash_REF_DO(byte[] hash){
+ super(hash, _TAG, 0, (hash == null ? 0 : hash.length));
+ mHash = hash;
+ }
+
+ public Hash_REF_DO(){
+ super(null, _TAG, 0, 0);
+ mHash = null;
+ }
+
+ public byte[] getHash(){
+ return mHash;
+ }
+
+ @Override
+ public String toString(){
+ StringBuilder b = new StringBuilder();
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ b.append("Hash_REF_DO: ");
+ try {
+ this.build(out);
+ b.append(BerTlv.toHex(out.toByteArray()));
+ } catch (Exception e ){
+ b.append(e.getLocalizedMessage());
+ }
+ return b.toString();
+ }
+
+ /**
+ * Tags: C1
+ * Length: 0 or _SHA1_LEN bytes
+ *
+ * Value:
+ * Hash: identifies a specific device application
+ * Empty: refers to all device applications
+ *
+ * Length:
+ * _SHA1_LEN for 20 bytes SHA-1 hash value
+ * 0 for empty value field
+ */
+ @Override
+ public void interpret()
+ throws ParserException {
+
+ mHash = null;
+
+ byte[] data = getRawData();
+ int index = getValueIndex();
+
+ // sanity checks
+ if( getValueLength() != 0 && getValueLength() != _SHA1_LEN ) {
+ throw new ParserException("Invalid value length for Hash-REF-DO!");
+ }
+
+ if( getValueLength() == _SHA1_LEN ) {
+ if( index + getValueLength() > data.length){
+ throw new ParserException( "Not enough data for Hash-REF-DO!");
+ }
+
+ mHash = new byte[getValueLength()];
+ System.arraycopy(data, index, mHash, 0, getValueLength());
+ }
+ }
+
+ /**
+ * Tags: C1
+ * Length: 0 or 20 bytes
+ *
+ * Value:
+ * Hash: identifies a specific device application
+ * Empty: refers to all device applications
+ *
+ * Length:
+ * _SHA1_LEN for 20 bytes SHA-1 hash value
+ * 0 for empty value field
+ */
+ @Override
+ public void build( ByteArrayOutputStream stream)
+ throws DO_Exception {
+
+ // sanity checks
+ if( mHash != null &&
+ !(mHash.length != _SHA1_LEN || mHash.length != 0) ) {
+ throw new DO_Exception("Hash value must be " + _SHA1_LEN + " bytes in length!");
+ }
+
+ stream.write(getTag());
+
+ if( mHash == null ) {
+ stream.write(0x00);
+ } else {
+ try {
+ stream.write(mHash.length);
+ stream.write(mHash);
+ } catch( IOException ioe ){
+ throw new DO_Exception("Hash could not be written!");
+ }
+ }
+ }
+
+ @Override
+ public boolean equals( Object obj ){
+ boolean equals = false;
+
+ if( obj instanceof Hash_REF_DO ){
+ equals = super.equals(obj);
+
+ if( equals ){
+ Hash_REF_DO hash_ref_do = (Hash_REF_DO)obj;
+ if( this.mHash == null && hash_ref_do.mHash == null )
+ equals &= true;
+ else {
+ if( this.mHash == null && hash_ref_do.mHash != null ){
+ equals &= (hash_ref_do.mHash.length == 0);
+ } else if( this.mHash != null && hash_ref_do.mHash == null ){
+ equals &= (this.mHash.length == 0);
+ } else {
+ //equals &= this.mHash.equals(hash_ref_do.mHash);
+ equals &= Arrays.equals(mHash, hash_ref_do.mHash);
+ }
+ }
+ }
+ }
+ return equals;
+ }
+}
diff --git a/src/org/simalliance/openmobileapi/service/security/gpac/dataobjects/NFC_AR_DO.java b/src/org/simalliance/openmobileapi/service/security/gpac/dataobjects/NFC_AR_DO.java
new file mode 100644
index 0000000..5476aba
--- /dev/null
+++ b/src/org/simalliance/openmobileapi/service/security/gpac/dataobjects/NFC_AR_DO.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2012 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 org.simalliance.openmobileapi.service.security.gpac.dataobjects;
+
+import java.io.ByteArrayOutputStream;
+
+
+/**
+ * NFC-AR-DO:
+ * In the NFC use case, mobile device application gather information
+ * from their associated card application using the SE access API.
+ * However, when the card application needs to trigger its associated mobile application,
+ * it sends an HCI EVT_TRANSACTION according to ETSI TS 102 622 [102 622] over SWP to the device.
+ * This event is handled by the NFC chipset stack which has to start
+ * the corresponding device application. Disclosure of this event to malicious applications
+ * can lead to phishing and denial of service attacks.
+ * To prevent this, it shall be possible to use the applications signature to authorize
+ * device applications to receive HCI events issued by the secure element application.
+ * An NFC event data object defines an access rule for generating NFC events for
+ * a specific terminal application. The NFC event access can be restricted by a rule based
+ * on an event access is NEVER/ ALWAYS allowed policy.
+ *
+ *
+ *
+ */
+public class NFC_AR_DO extends BerTlv {
+
+ public final static int _TAG = 0xD1;
+
+ private boolean mNfcAllowed = false;
+
+ public NFC_AR_DO(byte[] rawData, int valueIndex, int valueLength) {
+ super(rawData, _TAG, valueIndex, valueLength);
+ }
+
+ public NFC_AR_DO( boolean allowed ){
+ super( null, _TAG, 0, 0);
+ mNfcAllowed = allowed;
+ }
+
+ public boolean isNfcAllowed(){
+ return mNfcAllowed;
+ }
+
+ @Override
+ /**
+ * Tag: D1
+ * Length: 1
+ * Value:
+ * Contains a NFC event access rule:
+ * NEVER (00): NFC event access is not allowed
+ * ALWAYS(01): NFC event access is allowed
+ *
+ */
+ public void interpret()
+ throws ParserException {
+
+ mNfcAllowed = false;
+
+ byte[] data = getRawData();
+ int index = getValueIndex();
+
+ if( index + getValueLength() > data.length){
+ throw new ParserException( "Not enough data for NFC_AR_DO!");
+ }
+
+ if( getValueLength() != 1 ){
+ throw new ParserException( "Invalid length of NFC-AR-DO!" );
+ }
+ mNfcAllowed = (data[index] == 0x01);
+ }
+
+ @Override
+ /**
+ * Tag: D1
+ * Length: 1
+ * Value:
+ * Contains a NFC event access rule:
+ * NEVER (00): NFC event access is not allowed
+ * ALWAYS(01): NFC event access is allowed
+ *
+ */
+ public void build( ByteArrayOutputStream stream )
+ throws DO_Exception {
+
+ // write tag
+ stream.write(getTag());
+ stream.write(0x01);
+ stream.write(mNfcAllowed ? 0x01 : 0x00 );
+ }
+
+}
diff --git a/src/org/simalliance/openmobileapi/service/security/gpac/dataobjects/ParserException.java b/src/org/simalliance/openmobileapi/service/security/gpac/dataobjects/ParserException.java
new file mode 100644
index 0000000..a12313c
--- /dev/null
+++ b/src/org/simalliance/openmobileapi/service/security/gpac/dataobjects/ParserException.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2012 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 org.simalliance.openmobileapi.service.security.gpac.dataobjects;
+
+public class ParserException extends Exception {
+
+ /**
+ *
+ */
+ private static final long serialVersionUID = -3917637590082486538L;
+
+ public ParserException() {
+ super();
+ // TODO Auto-generated constructor stub
+ }
+
+
+ public ParserException(String arg0, Throwable arg1) {
+ super(arg0, arg1);
+ // TODO Auto-generated constructor stub
+ }
+
+ public ParserException(String arg0) {
+ super(arg0);
+ // TODO Auto-generated constructor stub
+ }
+
+ public ParserException(Throwable arg0) {
+ super(arg0);
+ // TODO Auto-generated constructor stub
+ }
+
+
+
+}
diff --git a/src/org/simalliance/openmobileapi/service/security/gpac/dataobjects/REF_AR_DO.java b/src/org/simalliance/openmobileapi/service/security/gpac/dataobjects/REF_AR_DO.java
new file mode 100644
index 0000000..03555c6
--- /dev/null
+++ b/src/org/simalliance/openmobileapi/service/security/gpac/dataobjects/REF_AR_DO.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright 2012 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 org.simalliance.openmobileapi.service.security.gpac.dataobjects;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+
+/**
+ * REF-AR_DO:
+ * The REF-AR-DO contains access rules inclusively its corresponding references
+ * for the SE application (AID reference) and device application (hash reference).
+ *
+ *
+ *
+ */
+public class REF_AR_DO extends BerTlv {
+
+ public final static int _TAG = 0xE2;
+
+ private REF_DO mRefDo = null;
+ private AR_DO mArDo = null;
+
+ public REF_AR_DO(byte[] rawData, int valueIndex, int valueLength) {
+ super(rawData, _TAG, valueIndex, valueLength);
+ }
+
+ public REF_AR_DO() {
+ super(null, _TAG, 0, 0);
+ }
+
+ public REF_AR_DO(REF_DO ref_do, AR_DO ar_do ) {
+ super(null, _TAG, 0, 0);
+ mRefDo = ref_do;
+ mArDo = ar_do;
+ }
+
+ public REF_DO getRefDo() {
+ return mRefDo;
+ }
+
+ public AR_DO getArDo() {
+ return mArDo;
+ }
+
+ /**
+ * Interpret data.
+ *
+ * Tags: E2
+ * Length: n
+ *
+ * Value:
+ * REF-DO | AR-DO: A concatenation of an REF-DO and an AR-DO.
+ * The REF-DO must correspond to the succeeding AR-DO.
+ *
+ * Length:
+ * n bytes.
+ */
+ @Override
+ public void interpret()
+ throws ParserException {
+
+ mRefDo = null;
+ mArDo = null;
+
+ byte[] data = getRawData();
+ int index = getValueIndex();
+
+ if( index + getValueLength() > data.length){
+ throw new ParserException( "Not enough data for AR_DO!");
+ }
+
+ do {
+ BerTlv temp = BerTlv.decode(data, index);
+ if( temp.getTag() == REF_DO._TAG ) { // REF-DO
+ mRefDo = new REF_DO( data, temp.getValueIndex(), temp.getValueLength());
+ mRefDo.interpret();
+ } else if( temp.getTag() == AR_DO._TAG ) { // AR-DO
+ mArDo = new AR_DO( data, temp.getValueIndex(), temp.getValueLength());
+ mArDo.interpret();
+ } else {
+ // uncomment following line if a more restrictive
+ // behavior is necessary.
+ //throw new ParserException("Invalid DO in REF-AR-DO!");
+ }
+ index = temp.getValueIndex() + temp.getValueLength();
+ } while( getValueIndex() + getValueLength() > index );
+
+ // check for mandatory TLVs.
+ if( mRefDo == null ) {
+ throw new ParserException("Missing Ref-DO in REF-AR-DO!");
+ }
+ if( mArDo == null ) {
+ throw new ParserException("Missing AR-DO in REF-AR-DO!");
+ }
+ }
+
+
+ /**
+ * Tag: E2
+ * Length: n
+ * Value:
+ * REF-DO | AR-DO: A concatenation of an REF-DO and an AR-DO.
+ */
+ @Override
+ public void build(ByteArrayOutputStream stream )
+ throws DO_Exception {
+ ByteArrayOutputStream temp = new ByteArrayOutputStream();
+
+ if( mRefDo == null || mArDo == null ){
+ throw new DO_Exception( "REF-AR-DO: Required DO missing!");
+ }
+ stream.write(getTag());
+
+ mRefDo.build(temp);
+ mArDo.build(temp);
+
+ byte[] data = temp.toByteArray();
+ BerTlv.encodeLength(data.length, stream);
+ try {
+ stream.write(data);
+ } catch (IOException e) {
+ throw new DO_Exception("REF-AR-DO Memory IO problem! " + e.getMessage());
+ }
+ }
+}
diff --git a/src/org/simalliance/openmobileapi/service/security/gpac/dataobjects/REF_DO.java b/src/org/simalliance/openmobileapi/service/security/gpac/dataobjects/REF_DO.java
new file mode 100644
index 0000000..a31e539
--- /dev/null
+++ b/src/org/simalliance/openmobileapi/service/security/gpac/dataobjects/REF_DO.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright 2012 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 org.simalliance.openmobileapi.service.security.gpac.dataobjects;
+
+import java.io.ByteArrayOutputStream;
+import java.util.Arrays;
+
+/**
+ * REF-DO:
+ * The REF-DO contains a reference to uniquely assign
+ * or identify an access rule for an SE application (with an AID reference)
+ * and for a device application (with a hash reference).
+ *
+ *
+ *
+ */
+public class REF_DO extends BerTlv {
+
+ public final static int _TAG = 0xE1;
+
+ private AID_REF_DO mAidDo = null;
+ private Hash_REF_DO mHashDo = null;
+
+ public REF_DO(byte[] rawData, int valueIndex, int valueLength) {
+ super(rawData, _TAG, valueIndex, valueLength);
+ }
+
+ public REF_DO(AID_REF_DO aid_ref_do, Hash_REF_DO hash_ref_do ) {
+ super(null, _TAG, 0, 0);
+ mAidDo = aid_ref_do;
+ mHashDo = hash_ref_do;
+ }
+
+ @Override
+ public String toString(){
+ StringBuilder b = new StringBuilder();
+ b.append("REF_DO: ");
+ if( mAidDo != null ){
+ b.append(mAidDo.toString());
+ b.append(' ' );
+ }
+ if( mHashDo != null ){
+ b.append(mHashDo.toString());
+ }
+ return b.toString();
+ }
+
+
+ public AID_REF_DO getAidDo() {
+ return mAidDo;
+ }
+
+ public Hash_REF_DO getHashDo() {
+ return mHashDo;
+ }
+
+ /**
+ * Interpret data.
+ *
+ * Tags: E1 -> Length: n
+ *
+ * Value:
+ * AID-REF-DO | Hash-REF-DO: A concatenation of an AID-REF-DO and a Hash-REF-DO.
+ *
+ * Length:
+ * n bytes.
+ */
+ @Override
+ public void interpret()
+ throws ParserException {
+
+ mAidDo = null;
+ mHashDo = null;
+
+ byte[] data = getRawData();
+ int index = getValueIndex();
+
+ if( index + getValueLength() > data.length){
+ throw new ParserException( "Not enough data for AR_DO!");
+ }
+
+ do {
+ BerTlv temp = BerTlv.decode(data, index);
+
+ if( temp.getTag() == AID_REF_DO._TAG || temp.getTag() == AID_REF_DO._TAG_DEFAULT_APPLICATION ) { // AID-REF-DO
+ mAidDo = new AID_REF_DO( data, temp.getTag(), temp.getValueIndex(), temp.getValueLength());
+ mAidDo.interpret();
+ } else if( temp.getTag() == Hash_REF_DO._TAG ) { // Hash-REF-DO
+ mHashDo = new Hash_REF_DO( data, temp.getValueIndex(), temp.getValueLength());
+ mHashDo.interpret();
+ } else {
+ // uncomment following line if a more restrictive
+ // behaviour is necessary.
+ // throw new ParserException("Invalid DO in REF-DO!");
+ }
+ index = temp.getValueIndex() + temp.getValueLength();
+ } while( getValueIndex() + getValueLength() > index );
+
+ // check if there is a AID-REF-DO
+ if( mAidDo == null ){
+ throw new ParserException("Missing AID-REF-DO in REF-DO!");
+ }
+ // check if there is a Hash-REF-DO
+ if( mHashDo == null ){
+ throw new ParserException("Missing Hash-REF-DO in REF-DO!");
+ }
+ }
+
+ /**
+ * Tag: E1
+ * Length: n
+ * Value:
+ * AID-REF-DO | Hash-REF-DO: A concatenation of an AID-REF-DO and a Hash-REF-DO.
+ */
+ @Override
+ public void build(ByteArrayOutputStream stream )
+ throws DO_Exception {
+ ByteArrayOutputStream temp = new ByteArrayOutputStream();
+
+ if( mAidDo == null || mHashDo == null ){
+ throw new DO_Exception( "REF-DO: Required DO missing!");
+ }
+
+ mAidDo.build(temp);
+ mHashDo.build(temp);
+
+ byte[] data = temp.toByteArray();
+ BerTlv tlv = new BerTlv( data, getTag(), 0, data.length );
+ tlv.build(stream);
+ }
+
+ @Override
+ public boolean equals(Object obj ){
+ boolean equals = false;
+ if( obj instanceof REF_DO ){
+ equals = super.equals(obj);
+ REF_DO ref_do = (REF_DO)obj;
+ if( mAidDo == null && ref_do.mAidDo == null ){
+ equals &= true;
+ } else if( mAidDo != null && ref_do.mAidDo != null ){
+ equals &= mAidDo.equals(ref_do.mAidDo);
+ } else {
+ equals = false;
+ }
+ if( mHashDo == null && ref_do.mHashDo == null ){
+ equals &= true;
+ } else if( mHashDo != null && ref_do.mHashDo != null ){
+ equals &= mHashDo.equals(ref_do.mHashDo);
+ } else {
+ equals = false;
+ }
+ }
+ return equals;
+ }
+
+ @Override
+ public int hashCode () {
+ ByteArrayOutputStream stream = new ByteArrayOutputStream();
+ try {
+ this.build(stream);
+ } catch (DO_Exception e) {
+ return 1;
+ }
+ byte[] data = stream.toByteArray();
+ int hash = Arrays.hashCode(data);
+ //int hash = data.hashCode();
+ return hash;
+ }
+}
diff --git a/src/org/simalliance/openmobileapi/service/security/gpac/dataobjects/Response_ALL_AR_DO.java b/src/org/simalliance/openmobileapi/service/security/gpac/dataobjects/Response_ALL_AR_DO.java
new file mode 100644
index 0000000..bcf10b8
--- /dev/null
+++ b/src/org/simalliance/openmobileapi/service/security/gpac/dataobjects/Response_ALL_AR_DO.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2012 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 org.simalliance.openmobileapi.service.security.gpac.dataobjects;
+
+import java.util.ArrayList;
+
+/**
+ * Response-ALL-AR-DO
+ * All access rules stored in the Secure Element have to be returned by the ARA-M
+ * after a GET DATA (All) command in the response data field within a Response-ALL-AR-DO.
+ * The GET DATA command can also be applied iteratively with subsequent GET DATA (Next) commands
+ * if the Response-ALL-AR-DO is too large for the GET DATA (All) command.
+ * The length field of the Response-ALL-AR-DO shall always contain the full length
+ * of the DOs value to determine on device side if a subsequent GET DATA (Next) command
+ * is needed.
+ *
+ *
+ *
+ */
+public class Response_ALL_AR_DO extends BerTlv {
+
+ public final static int _TAG = 0xFF40;
+
+ private ArrayList<REF_AR_DO> mRefArDos = new ArrayList<REF_AR_DO>();
+
+ public Response_ALL_AR_DO(byte[] rawData, int valueIndex,
+ int valueLength) {
+ super(rawData, _TAG, valueIndex, valueLength);
+ }
+
+ public ArrayList<REF_AR_DO> getRefArDos(){
+ return mRefArDos;
+ }
+
+ @Override
+ /**
+ * Tag: FF 40
+ *
+ * Length: n or 0
+ * If n is equal to zero, then there are no rules to fetch.
+ *
+ * Value:
+ * REF-AR-DO 1..n or empty
+ * An REF-AR-DO if access rules exist.
+ * REF-AR-DOs can occur several times in a concatenated DO chain if several REF-AR-DO exist
+ * on the SE.
+ * The value is empty if access rules do not exist.
+ */
+ public void interpret()
+ throws ParserException {
+
+ mRefArDos.clear();
+
+ byte[] data = getRawData();
+ int index = getValueIndex();
+
+ if( getValueLength() == 0 ){
+ // No Access rule available for the requested reference.
+ return;
+ }
+
+ if( index + getValueLength() > data.length){
+ throw new ParserException( "Not enough data for Response_AR_DO!");
+ }
+
+ BerTlv temp;
+ int currentPos = index;
+ int endPos = index + getValueLength();
+ do {
+ temp = BerTlv.decode(data, currentPos);
+
+ REF_AR_DO tempRefArDo;
+
+ if( temp.getTag() == REF_AR_DO._TAG) { // REF-AR-DO tag
+ tempRefArDo = new REF_AR_DO( data, temp.getValueIndex(), temp.getValueLength());
+ tempRefArDo.interpret();
+ mRefArDos.add(tempRefArDo);
+ } else {
+ // uncomment following line if a more restrictive
+ // behavior is necessary.
+ //throw new ParserException("Invalid DO in Response-ALL-AR-DO!");
+ }
+ // get REF-AR-DOs as long as data is available.
+ currentPos = temp.getValueIndex() + temp.getValueLength();
+ } while( currentPos < endPos );
+ }
+}
diff --git a/src/org/simalliance/openmobileapi/service/security/gpac/dataobjects/Response_ARAC_AID_DO.java b/src/org/simalliance/openmobileapi/service/security/gpac/dataobjects/Response_ARAC_AID_DO.java
new file mode 100644
index 0000000..ed6b248
--- /dev/null
+++ b/src/org/simalliance/openmobileapi/service/security/gpac/dataobjects/Response_ARAC_AID_DO.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2012 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 org.simalliance.openmobileapi.service.security.gpac.dataobjects;
+
+import java.util.ArrayList;
+
+
+
+/**
+ * Response_ARAC_AID_DO
+ *
+ * A list of AIDs containing an AID for each ARA-C.
+ *
+ * In response to STORE DATA (Command-Get-ClientAIDs-DO),
+ * the ARA-M shall return the AID of each of the ARA-Cs
+ * currently registered within a Response-ARAC-AID-DO.
+ *
+ *
+ *
+ */
+public class Response_ARAC_AID_DO extends BerTlv {
+
+ public final static int _TAG = 0xFF70;
+
+ private ArrayList<AID_REF_DO> mAidDos = new ArrayList<AID_REF_DO>();
+
+ public Response_ARAC_AID_DO(byte[] rawData, int valueIndex,
+ int valueLength) {
+ super(rawData, _TAG, valueIndex, valueLength);
+ }
+
+ public ArrayList<AID_REF_DO> getAidRefDos(){
+ return mAidDos;
+ }
+
+ @Override
+ /**
+ * Tag: FF 70
+ *
+ * Length: n or 0
+ * If n is equal to zero, then there are no rules to fetch.
+ *
+ * Value:
+ * AID-REF-DO 1..n or empty
+ * AID-REF-DOs can occur several times in a concatenated DO chain if several ARA-C instances exist
+ * on the SE.
+ * The value is empty if no ARA-C instance exist.
+ */
+ public void interpret()
+ throws ParserException {
+
+ mAidDos.clear();
+
+ byte[] data = getRawData();
+ int index = getValueIndex();
+
+ if( getValueLength() == 0 ){
+ // No Access rule available for the requested reference.
+ return;
+ }
+
+ if( index + getValueLength() > data.length){
+ throw new ParserException( "Not enough data for Response_ARAC_AID_DO!");
+ }
+
+ BerTlv temp;
+ int currentPos = index;
+ int endPos = index + getValueLength();
+ do {
+ temp = BerTlv.decode(data, currentPos);
+
+ AID_REF_DO tempAidDo;
+
+ if( temp.getTag() == AID_REF_DO._TAG ||
+ temp.getTag() == AID_REF_DO._TAG_DEFAULT_APPLICATION ) {
+ tempAidDo = new AID_REF_DO( data, temp.getTag(), temp.getValueIndex(), temp.getValueLength());
+ tempAidDo.interpret();
+ mAidDos.add(tempAidDo);
+ } else {
+ // uncomment following line if a more restrictive
+ // behavior is necessary.
+ //throw new ParserException("Invalid DO in Response_ARAC_AID_DO!");
+ }
+ // get AID-REF-DOs as long as data is available.
+ currentPos = temp.getValueIndex() + temp.getValueLength();
+ } while( currentPos < endPos );
+ }
+}
diff --git a/src/org/simalliance/openmobileapi/service/security/gpac/dataobjects/Response_AR_DO.java b/src/org/simalliance/openmobileapi/service/security/gpac/dataobjects/Response_AR_DO.java
new file mode 100644
index 0000000..51483e9
--- /dev/null
+++ b/src/org/simalliance/openmobileapi/service/security/gpac/dataobjects/Response_AR_DO.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2012 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 org.simalliance.openmobileapi.service.security.gpac.dataobjects;
+
+
+
+/**
+ * Response-AR-DO
+ * If access rules can be found in the Secure Element which corresponds to the specified AR-DO
+ * in the GET DATA (Specific) command these must be returned by the ARA-M
+ * in the response data field within a Response-AR-DO.
+ * The GET DATA command can also be applied iteratively with subsequent GET DATA (next) commands
+ * if the Response-AR-DO is too large for the GET DATA (Specific) command.
+ * The length field of the Response-AR-DO shall always contain the full length of the DOs value
+ * to determine on device side if a subsequent GET DATA (Next) command is needed.
+ *
+ *
+ *
+ */
+public class Response_AR_DO extends BerTlv {
+
+ public final static int _TAG = 0xFF50;
+
+ private AR_DO mArDo = null;
+
+ public Response_AR_DO(byte[] rawData, int valueIndex,
+ int valueLength) {
+ super(rawData, _TAG, valueIndex, valueLength);
+ }
+
+ public AR_DO getArDo(){
+ return mArDo;
+ }
+
+ @Override
+ /**
+ * Tag: FF 50
+ *
+ * Length: n or 0
+ * If n is equal to zero, then there are no rules to fetch.
+ *
+ * Value:
+ * An AR-DO if the referenced access rules exist.
+ * The value is empty if access rules do not exist to the defined reference
+ */
+ public void interpret()
+ throws ParserException {
+
+ byte[] data = getRawData();
+ int index = getValueIndex();
+
+ if( getValueLength() == 0 ){
+ // No Access rule available for the requested reference.
+ return;
+ }
+
+ if( index + getValueLength() > data.length){
+ throw new ParserException( "Not enough data for Response_AR_DO!");
+ }
+
+ int currentPos = index;
+ int endPos = index + getValueLength();
+ do {
+ BerTlv temp = BerTlv.decode(data, currentPos);
+
+ if( temp.getTag() == AR_DO._TAG) { // AR-DO tag
+ mArDo = new AR_DO( data, temp.getValueIndex(), temp.getValueLength());
+ mArDo.interpret();
+ } else {
+ // un-comment following line if a more restrictive
+ // behavior is necessary.
+ //throw new ParserException("Invalid DO in Response-AR-DO!");
+ }
+ // get REF-AR-DOs as long as data is available.
+ currentPos = temp.getValueIndex() + temp.getValueLength();
+ } while( currentPos < endPos );
+ }
+}
diff --git a/src/org/simalliance/openmobileapi/service/security/gpac/dataobjects/Response_DO_Factory.java b/src/org/simalliance/openmobileapi/service/security/gpac/dataobjects/Response_DO_Factory.java
new file mode 100644
index 0000000..2816790
--- /dev/null
+++ b/src/org/simalliance/openmobileapi/service/security/gpac/dataobjects/Response_DO_Factory.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2012 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 org.simalliance.openmobileapi.service.security.gpac.dataobjects;
+
+
+
+public class Response_DO_Factory {
+
+ public static BerTlv createDO( byte[] data ) throws ParserException{
+
+ BerTlv tempTlv = BerTlv.decode(data, 0 );
+
+ BerTlv retTlv = null;
+
+ switch( tempTlv.getTag() ){
+
+ case Response_RefreshTag_DO._TAG:
+ retTlv = new Response_RefreshTag_DO( data, tempTlv.getValueIndex(), tempTlv.getValueLength());
+ break;
+ case Response_ARAC_AID_DO._TAG:
+ retTlv = new Response_ARAC_AID_DO( data, tempTlv.getValueIndex(), tempTlv.getValueLength());
+ break;
+
+ case Response_ALL_AR_DO._TAG:
+ retTlv = new Response_ALL_AR_DO( data, tempTlv.getValueIndex(), tempTlv.getValueLength());
+ break;
+ case Response_AR_DO._TAG:
+ retTlv = new Response_AR_DO( data, tempTlv.getValueIndex(), tempTlv.getValueLength());
+ break;
+ default:
+ retTlv = tempTlv;
+ }
+
+ retTlv.interpret();
+
+ return retTlv;
+ }
+}
diff --git a/src/org/simalliance/openmobileapi/service/security/gpac/dataobjects/Response_RefreshTag_DO.java b/src/org/simalliance/openmobileapi/service/security/gpac/dataobjects/Response_RefreshTag_DO.java
new file mode 100644
index 0000000..5ee7b85
--- /dev/null
+++ b/src/org/simalliance/openmobileapi/service/security/gpac/dataobjects/Response_RefreshTag_DO.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2012 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 org.simalliance.openmobileapi.service.security.gpac.dataobjects;
+
+
+/**
+ * Response-RefreshTag DO
+ * The GET DATA (RefreshTag) command has to return a refresh tag indicating changes
+ * in the access control data in a RefreshTag DO.
+ * This refresh tag is an attribute (8-byte random number) of the ARA-M which is
+ * newly generated if the ARA-M detects an update of access control data
+ * in the Secure Element.
+ *
+ *
+ *
+ */
+public class Response_RefreshTag_DO extends BerTlv {
+
+ public final static int _TAG = 0xDF20;
+
+ private long mRefreshTag;
+ private byte[] mRefreshTagArray = null;
+
+ public Response_RefreshTag_DO(byte[] rawData, int valueIndex,
+ int valueLength) {
+ super(rawData, _TAG, valueIndex, valueLength);
+
+ }
+
+ public long getRefreshTag(){
+ return mRefreshTag;
+ }
+
+ public byte[] getRefreshTagArray(){
+ return mRefreshTagArray;
+ }
+
+ @Override
+ /**
+ * Tag: DF 20
+ * Length: 8 bytes
+ * Value:
+ * The RefreshTag is an 8 bytes random number.
+ * A new RefreshTag value indicates changes in the access control data
+ * stored in the SE.
+ */
+ public void interpret()
+ throws ParserException {
+
+ mRefreshTag = 0;
+
+ if( super.getValueLength() != 8 ){
+ throw new ParserException( "Invalid length of RefreshTag DO!" );
+ }
+
+ byte[] data = super.getRawData();
+ int index = super.getValueIndex();
+
+ if( index + super.getValueLength() > data.length ){
+ throw new ParserException( "Not enough data for RefreshTag DO!" );
+ }
+ mRefreshTagArray = new byte[super.getValueLength()];
+ System.arraycopy(data, index, mRefreshTagArray, 0, mRefreshTagArray.length);
+
+ long temp;
+ temp = data[index++];
+ mRefreshTag =(temp << 56L);
+ temp = data[index++];
+ mRefreshTag +=(temp << 48L);
+ temp = data[index++];
+ mRefreshTag +=(temp << 40L);
+ temp = data[index++];
+ mRefreshTag +=(temp << 32L);
+ temp = data[index++];
+ mRefreshTag +=(temp << 24L);
+ temp = data[index++];
+ mRefreshTag +=(temp << 16L);
+ temp = data[index++];
+ mRefreshTag +=(temp << 8L);
+ temp = data[index++];
+ mRefreshTag +=(temp);
+ }
+}
diff --git a/src/org/simalliance/openmobileapi/service/terminals/ASSDTerminal.java b/src/org/simalliance/openmobileapi/service/terminals/ASSDTerminal.java
new file mode 100644
index 0000000..fc0b638
--- /dev/null
+++ b/src/org/simalliance/openmobileapi/service/terminals/ASSDTerminal.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2011, 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 org.simalliance.openmobileapi.service.terminals;
+
+import android.content.Context;
+
+
+import java.util.MissingResourceException;
+import java.util.NoSuchElementException;
+
+import org.simalliance.openmobileapi.service.CardException;
+import org.simalliance.openmobileapi.service.SmartcardService;
+import org.simalliance.openmobileapi.service.Terminal;
+
+final class ASSDTerminal extends Terminal {
+
+ private static boolean JNILoaded = false;
+
+ public ASSDTerminal(Context context) {
+ super(SmartcardService._SD_TERMINAL + " - Secure SD Card", context);
+ }
+
+ @Override
+ protected void internalConnect() throws CardException {
+ if (JNILoaded == false) {
+ throw new CardException("JNI failed");
+ }
+
+ try {
+ if (Open() == false) {
+ throw new CardException("open SE failed");
+ }
+ } catch (Exception e) {
+ throw new CardException("open SE failed");
+ }
+ mDefaultApplicationSelectedOnBasicChannel = true;
+ mIsConnected = true;
+ }
+
+ @Override
+ protected void internalDisconnect() throws CardException {
+ if (JNILoaded == false) {
+ throw new CardException("JNI failed");
+ }
+
+ try {
+ Close();
+ } catch (Exception e) {
+ } finally {
+ mIsConnected = false;
+ }
+ }
+
+ public boolean isCardPresent() throws CardException {
+ if (JNILoaded == false) {
+ return false;
+ }
+
+ try {
+ return IsPresent();
+ } catch (Exception e) {
+ return false;
+ }
+ }
+
+ @Override
+ protected byte[] internalTransmit(byte[] command) throws CardException {
+ if (JNILoaded == false) {
+ throw new CardException("JNI failed");
+ }
+
+ try {
+ byte[] response = Transmit(command);
+ if (response == null) {
+ throw new CardException("transmit failed");
+ }
+ return response;
+ } catch (Exception e) {
+ throw new CardException("transmit failed");
+ }
+ }
+
+ @Override
+ protected void internalCloseLogicalChannel(int channelNumber) throws CardException {
+ if (channelNumber > 0) {
+ byte cla = (byte) channelNumber;
+ if (channelNumber > 3) {
+ cla |= 0x40;
+ }
+ byte[] manageChannelClose = new byte[] {
+ cla, 0x70, (byte) 0x80, (byte) channelNumber
+ };
+ transmit(manageChannelClose, 2, 0x9000, 0xFFFF, "MANAGE CHANNEL");
+ }
+ }
+
+ @Override
+ protected int internalOpenLogicalChannel() throws Exception {
+
+ mSelectResponse = null;
+ byte[] manageChannelCommand = new byte[] {
+ 0x00, 0x70, 0x00, 0x00, 0x01
+ };
+ byte[] rsp = transmit(manageChannelCommand, 3, 0x9000, 0xFFFF, "MANAGE CHANNEL");
+ if (rsp.length != 3) {
+ throw new MissingResourceException("unsupported MANAGE CHANNEL response data", "", "");
+ }
+ int channelNumber = rsp[0] & 0xFF;
+ if (channelNumber == 0 || channelNumber > 19) {
+ throw new MissingResourceException("invalid logical channel number returned", "", "");
+ }
+
+ return channelNumber;
+ }
+
+ @Override
+ protected int internalOpenLogicalChannel(byte[] aid) throws Exception {
+ int channelNumber = internalOpenLogicalChannel();
+
+ if (aid == null) {
+ throw new NullPointerException("aid must not be null");
+ }
+ mSelectResponse = null;
+ byte[] selectCommand = new byte[aid.length + 6];
+ selectCommand[0] = (byte) channelNumber;
+ if (channelNumber > 3) {
+ selectCommand[0] |= 0x40;
+ }
+ selectCommand[1] = (byte) 0xA4;
+ selectCommand[2] = 0x04;
+ selectCommand[4] = (byte) aid.length;
+ System.arraycopy(aid, 0, selectCommand, 5, aid.length);
+ try {
+ mSelectResponse = transmit(selectCommand, 2, 0x9000, 0xFFFF, "SELECT");
+ } catch (CardException e) {
+ internalCloseLogicalChannel(channelNumber);
+ throw new NoSuchElementException(e.getMessage());
+ }
+
+ return channelNumber;
+ }
+
+ static {
+ try {
+ Runtime.getRuntime().loadLibrary("assd");
+ JNILoaded = true;
+ } catch (Throwable t) {
+ }
+ }
+
+ private native void Close() throws Exception;
+
+ private native boolean Open() throws Exception;
+
+ private native boolean IsPresent() throws Exception;
+
+ private native byte[] Transmit(byte[] command) throws Exception;
+}
diff --git a/src/org/simalliance/openmobileapi/service/terminals/SmartMxTerminal.java b/src/org/simalliance/openmobileapi/service/terminals/SmartMxTerminal.java
new file mode 100644
index 0000000..782581d
--- /dev/null
+++ b/src/org/simalliance/openmobileapi/service/terminals/SmartMxTerminal.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2011, 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 org.simalliance.openmobileapi.service.terminals;
+
+import android.content.Context;
+import android.nfc.INfcAdapterExtras;
+import android.nfc.NfcAdapter;
+import android.os.Binder;
+import android.os.Bundle;
+import android.util.Log;
+
+
+import java.util.MissingResourceException;
+import java.util.NoSuchElementException;
+
+import org.simalliance.openmobileapi.service.CardException;
+import org.simalliance.openmobileapi.service.SmartcardService;
+import org.simalliance.openmobileapi.service.Terminal;
+
+
+public class SmartMxTerminal extends Terminal {
+
+ private INfcAdapterExtras ex;
+ private Binder binder = new Binder();
+
+ public SmartMxTerminal(Context context) {
+ super(SmartcardService._eSE_TERMINAL + " - SmartMX", context);
+ }
+
+ public boolean isCardPresent() throws CardException {
+ try {
+ NfcAdapter adapter = NfcAdapter.getDefaultAdapter(mContext);
+ if(adapter == null) {
+ throw new CardException("Cannot get NFC Default Adapter");
+ }
+ return adapter.isEnabled();
+ } catch (Exception e) {
+ return false;
+ }
+ }
+
+ @Override
+ protected void internalConnect() throws CardException {
+ NfcAdapter adapter = NfcAdapter.getDefaultAdapter(mContext);
+ if(adapter == null) {
+ throw new CardException("Cannot get NFC Default Adapter");
+ }
+
+ ex = adapter.getNfcAdapterExtrasInterface();
+ if(ex == null) {
+ throw new CardException("Cannot get NFC Extra interface");
+ }
+
+ try {
+ Bundle b = ex.open("org.simalliance.openmobileapi.service", binder);
+ if (b == null) {
+ throw new CardException("open SE failed");
+ }
+ } catch (Exception e) {
+ throw new CardException("open SE failed");
+ }
+ mDefaultApplicationSelectedOnBasicChannel = true;
+ mIsConnected = true;
+ }
+
+ @Override
+ protected void internalDisconnect() throws CardException {
+ try {
+ Bundle b = ex.close("org.simalliance.openmobileapi.service", binder);
+ if (b == null) {
+ throw new CardException("close SE failed");
+ }
+ } catch (Exception e) {
+ throw new CardException("close SE failed");
+ }
+ }
+
+ @Override
+ protected byte[] internalTransmit(byte[] command) throws CardException {
+ try {
+ Bundle b = ex.transceive("org.simalliance.openmobileapi.service", command);
+ if (b == null) {
+ throw new CardException("exchange APDU failed");
+ }
+ return b.getByteArray("out");
+ } catch (Exception e) {
+ throw new CardException("exchange APDU failed");
+ }
+ }
+
+ @Override
+ protected int internalOpenLogicalChannel() throws Exception {
+
+ mSelectResponse = null;
+ byte[] manageChannelCommand = new byte[] {
+ 0x00, 0x70, 0x00, 0x00, 0x01
+ };
+ byte[] rsp = transmit(manageChannelCommand, 2, 0x9000, 0, "MANAGE CHANNEL");
+ if ((rsp.length == 2) && ((rsp[0] == (byte) 0x68) && (rsp[1] == (byte) 0x81))) {
+ throw new NoSuchElementException("logical channels not supported");
+ }
+ if (rsp.length == 2 && (rsp[0] == (byte) 0x6A && rsp[1] == (byte) 0x81)) {
+ throw new MissingResourceException("no free channel available", "", "");
+ }
+ if (rsp.length != 3) {
+ throw new MissingResourceException("unsupported MANAGE CHANNEL response data", "", "");
+ }
+ int channelNumber = rsp[0] & 0xFF;
+ if (channelNumber == 0 || channelNumber > 19) {
+ throw new MissingResourceException("invalid logical channel number returned", "", "");
+ }
+
+ return channelNumber;
+ }
+
+ @Override
+ protected int internalOpenLogicalChannel(byte[] aid) throws Exception {
+
+ if (aid == null) {
+ throw new NullPointerException("aid must not be null");
+ }
+ mSelectResponse = null;
+
+ byte[] manageChannelCommand = new byte[] {
+ 0x00, 0x70, 0x00, 0x00, 0x01
+ };
+ byte[] rsp = transmit(manageChannelCommand, 2, 0x9000, 0, "MANAGE CHANNEL");
+ if ((rsp.length == 2) && ((rsp[0] == (byte) 0x68) && (rsp[1] == (byte) 0x81))) {
+ throw new NoSuchElementException("logical channels not supported");
+ }
+ if (rsp.length == 2 && (rsp[0] == (byte) 0x6A && rsp[1] == (byte) 0x81)) {
+ throw new MissingResourceException("no free channel available", "", "");
+ }
+ if (rsp.length != 3) {
+ throw new MissingResourceException("unsupported MANAGE CHANNEL response data", "", "");
+ }
+ int channelNumber = rsp[0] & 0xFF;
+ if (channelNumber == 0 || channelNumber > 19) {
+ throw new MissingResourceException("invalid logical channel number returned", "", "");
+ }
+
+ byte[] selectCommand = new byte[aid.length + 6];
+ selectCommand[0] = (byte) channelNumber;
+ if (channelNumber > 3) {
+ selectCommand[0] |= 0x40;
+ }
+ selectCommand[1] = (byte) 0xA4;
+ selectCommand[2] = 0x04;
+ selectCommand[4] = (byte) aid.length;
+ System.arraycopy(aid, 0, selectCommand, 5, aid.length);
+ try {
+ mSelectResponse = transmit(selectCommand, 2, 0x9000, 0xFFFF, "SELECT");
+ } catch (CardException exp) {
+ internalCloseLogicalChannel(channelNumber);
+ throw new NoSuchElementException(exp.getMessage());
+ }
+
+ return channelNumber;
+ }
+
+ @Override
+ protected void internalCloseLogicalChannel(int channelNumber) throws CardException {
+ if (channelNumber > 0) {
+ byte cla = (byte) channelNumber;
+ if (channelNumber > 3) {
+ cla |= 0x40;
+ }
+ byte[] manageChannelClose = new byte[] {
+ cla, 0x70, (byte) 0x80, (byte) channelNumber
+ };
+ transmit(manageChannelClose, 2, 0x9000, 0xFFFF, "MANAGE CHANNEL");
+ }
+ }
+}
diff --git a/src/org/simalliance/openmobileapi/service/terminals/UiccTerminal.java b/src/org/simalliance/openmobileapi/service/terminals/UiccTerminal.java
new file mode 100644
index 0000000..d28bc87
--- /dev/null
+++ b/src/org/simalliance/openmobileapi/service/terminals/UiccTerminal.java
@@ -0,0 +1,279 @@
+/*
+ * Copyright (C) 2011, 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 org.simalliance.openmobileapi.service.terminals;
+
+import android.content.Context;
+import org.simalliance.openmobileapi.service.CardException;
+import org.simalliance.openmobileapi.service.SmartcardService;
+import org.simalliance.openmobileapi.service.Terminal;
+import org.simalliance.openmobileapi.service.Util;
+import org.simalliance.openmobileapi.service.security.arf.SecureElementException;
+
+
+import android.os.ServiceManager;
+import android.os.SystemProperties;
+import android.util.Log;
+
+import com.android.internal.telephony.ITelephony;
+import com.android.internal.telephony.TelephonyProperties;
+
+import java.util.MissingResourceException;
+import java.util.NoSuchElementException;
+
+public class UiccTerminal extends Terminal {
+
+ private ITelephony manager = null;
+
+ private int[] channelId = new int[20];
+
+ private String currentSelectedFilePath = "";
+
+ public UiccTerminal(Context context) {
+ super(SmartcardService._UICC_TERMINAL + " - UICC", context);
+
+ try {
+ manager = ITelephony.Stub.asInterface(ServiceManager
+ .getService(Context.TELEPHONY_SERVICE));
+ } catch (Exception ex) {
+ }
+
+ for (int i = 0; i < channelId.length; i++)
+ channelId[i] = 0;
+ }
+
+ public boolean isCardPresent() throws CardException {
+ String prop = SystemProperties
+ .get(TelephonyProperties.PROPERTY_SIM_STATE);
+ if ("READY".equals(prop)) {
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ protected void internalConnect() throws CardException {
+ if (manager == null) {
+ throw new CardException("Cannot connect to Telephony Service");
+ }
+ mIsConnected = true;
+ }
+
+ @Override
+ protected void internalDisconnect() throws CardException {
+ }
+
+ private byte[] StringToByteArray(String s) {
+ byte[] b = new byte[s.length() / 2];
+ for (int i = 0; i < b.length; i++) {
+ b[i] = (byte) Integer.parseInt(s.substring(2 * i, 2 * i + 2), 16);
+ }
+ return b;
+ }
+
+ private String ByteArrayToString(byte[] b, int start) {
+ StringBuffer s = new StringBuffer();
+ for (int i = start; i < b.length; i++) {
+ s.append(Integer.toHexString(0x100 + (b[i] & 0xff)).substring(1));
+ }
+ return s.toString();
+ }
+
+ /**
+ * Clear the channel number
+ *
+ * @param cla
+ *
+ * @return the cla without channel number
+ */
+ private byte clearChannelNumber(byte cla) {
+ // bit 7 determines which standard is used
+ boolean isFirstInterindustryClassByteCoding = ((cla & 0x40) == 0x00);
+
+ if(isFirstInterindustryClassByteCoding){
+ // First Interindustry Class Byte Coding
+ // see 11.1.4.1: channel number is encoded in the 2 rightmost bits
+ return (byte)(cla & 0xFC);
+ }else{
+ // Further Interindustry Class Byte Coding
+ // see 11.1.4.2: channel number is encoded in the 4 rightmost bits
+ return (byte)(cla & 0xF0);
+ }
+ }
+
+ @Override
+ protected byte[] internalTransmit(byte[] command) throws CardException {
+ int cla = clearChannelNumber(command[0]) & 0xff;
+ int ins = command[1] & 0xff;
+ int p1 = command[2] & 0xff;
+ int p2 = command[3] & 0xff;
+ int p3 = -1;
+ if (command.length > 4) {
+ p3 = command[4] & 0xff;
+ }
+ String data = null;
+ if (command.length > 5) {
+ data = ByteArrayToString(command, 5);
+ }
+
+ int channelNumber = parseChannelNumber(command[0]);
+
+ if (channelNumber == 0) {
+
+ try {
+ String response = manager.transmitIccBasicChannel(cla, ins, p1,
+ p2, p3, data);
+ return StringToByteArray(response);
+ } catch (Exception ex) {
+ throw new CardException("transmit command failed");
+ }
+
+ } else {
+ if ((channelNumber > 0) && (channelId[channelNumber] == 0)) {
+ throw new CardException("channel not open");
+ }
+
+ try {
+ String response = manager.transmitIccLogicalChannel(cla, ins, channelId[channelNumber], p1, p2, p3, data);
+ return StringToByteArray(response);
+ } catch (Exception ex) {
+ throw new CardException("transmit command failed");
+ }
+ }
+ }
+
+ /**
+ * Exchanges APDU (SELECT, READ/WRITE) to the
+ * given EF by File ID and file path via iccIO.
+ *
+ * The given command is checked and might be rejected.
+ *
+ * @param fileID
+ * @param filePath
+ * @param cmd
+ * @return
+ */
+ @Override
+ public byte[] simIOExchange(int fileID,String filePath,byte[] cmd)
+ throws Exception {
+ try {
+ int ins = 0;
+ int p1=cmd[2] & 0xff;
+ int p2=cmd[3] & 0xff;
+ int p3=cmd[4] & 0xff;
+ switch(cmd[1]) {
+ case (byte)0xB0: ins=176; break;
+ case (byte)0xB2: ins=178; break;
+ case (byte)0xA4: ins=192; p1=0; p2=0; p3=15; break;
+ default:
+ throw new SecureElementException("Unknown SIM_IO command");
+ }
+
+ if(filePath != null && filePath.length() > 0) {
+ currentSelectedFilePath = filePath;
+ }
+
+ byte[] ret = manager.transmitIccSimIO(fileID, ins, p1, p2, p3, currentSelectedFilePath);
+
+ return ret;
+ } catch (Exception e) {
+ throw new Exception("SIM IO access error");
+ }}
+
+
+ /**
+ * Extracts the channel number from a CLA byte. Specified in GlobalPlatform
+ * Card Specification 2.2.0.7: 11.1.4 Class Byte Coding
+ *
+ * @param cla
+ * the command's CLA byte
+ * @return the channel number within [0x00..0x0F]
+ */
+ private int parseChannelNumber(byte cla) {
+ // bit 7 determines which standard is used
+ boolean isFirstInterindustryClassByteCoding = ((cla & 0x40) == 0x00);
+
+ if(isFirstInterindustryClassByteCoding){
+ // First Interindustry Class Byte Coding
+ // see 11.1.4.1: channel number is encoded in the 2 rightmost bits
+ return cla & 0x03;
+ }else{
+ // Further Interindustry Class Byte Coding
+ // see 11.1.4.2: channel number is encoded in the 4 rightmost bits
+ return (cla & 0x0F) + 4;
+ }
+ }
+
+ @Override
+ protected int internalOpenLogicalChannel() throws Exception {
+
+ mSelectResponse = null;
+ throw new UnsupportedOperationException(
+ "open channel without select AID is not supported by UICC");
+ }
+
+ @Override
+ protected int internalOpenLogicalChannel(byte[] aid) throws Exception {
+
+ if (aid == null) {
+ throw new NullPointerException("aid must not be null");
+ }
+ mSelectResponse = null;
+ for (int i = 1; i < channelId.length; i++)
+ if (channelId[i] == 0) {
+ channelId[i] = manager.openIccLogicalChannel(ByteArrayToString(
+ aid, 0));
+
+ if (!(channelId[i] > 0)) { // channelId[i] == 0
+ channelId[i] = 0;
+ int lastError = manager.getLastError();
+
+ if (lastError == 2) {
+ throw new MissingResourceException(
+ "all channels are used", "", "");
+ }
+ if (lastError == 3) {
+ throw new NoSuchElementException("applet not found");
+ }
+ throw new CardException("open channel failed");
+ }
+ return i;
+ }
+ throw new MissingResourceException("out of channels", "","");
+ }
+
+ @Override
+ protected void internalCloseLogicalChannel(int channelNumber)
+ throws CardException {
+ if (channelNumber == 0) {
+ return;
+ }
+ if (channelId[channelNumber] == 0) {
+ throw new CardException("channel not open");
+ }
+ try {
+ if (manager.closeIccLogicalChannel(channelId[channelNumber]) == false) {
+ throw new CardException("close channel failed");
+ }
+ } catch (Exception ex) {
+ throw new CardException("close channel failed");
+ }
+ channelId[channelNumber] = 0;
+ }
+}