summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorandroid-build-team Robot <android-build-team-robot@google.com>2018-02-05 08:21:16 +0000
committerandroid-build-team Robot <android-build-team-robot@google.com>2018-02-05 08:21:16 +0000
commitb3266366690b2ec9b085c7ce0fab3cf167fed787 (patch)
treef533a03b008cbce04e8b5cadfef06cfecb8a7372
parenta59a73aeb8e3bd758c39dd7361296641ee221fa0 (diff)
parent799af455634b182e94042242c726ef77411aa909 (diff)
downloadSecureElement-b3266366690b2ec9b085c7ce0fab3cf167fed787.tar.gz
Snap for 4585119 from 799af455634b182e94042242c726ef77411aa909 to pi-release
Change-Id: I4fd8eebfb7eb43a18e9eba800aac5ae604a0c89e
-rwxr-xr-xAndroid.mk16
-rwxr-xr-xAndroidManifest.xml14
-rwxr-xr-xCleanSpec.mk2
-rwxr-xr-xNOTICE235
-rw-r--r--PREUPLOAD.cfg8
-rwxr-xr-xsrc/com/android/se/Channel.java323
-rw-r--r--src/com/android/se/SEApplication.java32
-rwxr-xr-xsrc/com/android/se/SecureElementService.java308
-rwxr-xr-xsrc/com/android/se/Terminal.java645
-rwxr-xr-xsrc/com/android/se/internal/ByteArrayConverter.java209
-rwxr-xr-xsrc/com/android/se/internal/Util.java190
-rwxr-xr-xsrc/com/android/se/security/AccessControlEnforcer.java469
-rw-r--r--src/com/android/se/security/AccessRuleCache.java497
-rwxr-xr-xsrc/com/android/se/security/ApduFilter.java118
-rwxr-xr-xsrc/com/android/se/security/ChannelAccess.java172
-rwxr-xr-xsrc/com/android/se/security/CommandApdu.java197
-rwxr-xr-xsrc/com/android/se/security/ResponseApdu.java77
-rwxr-xr-xsrc/com/android/se/security/ara/AccessRuleApplet.java167
-rwxr-xr-xsrc/com/android/se/security/ara/AraController.java153
-rwxr-xr-xsrc/com/android/se/security/arf/ASN1.java73
-rwxr-xr-xsrc/com/android/se/security/arf/ArfController.java68
-rwxr-xr-xsrc/com/android/se/security/arf/DERParser.java249
-rwxr-xr-xsrc/com/android/se/security/arf/PKCS15/EF.java277
-rwxr-xr-xsrc/com/android/se/security/arf/PKCS15/EFACConditions.java297
-rwxr-xr-xsrc/com/android/se/security/arf/PKCS15/EFACMain.java120
-rwxr-xr-xsrc/com/android/se/security/arf/PKCS15/EFACRules.java147
-rwxr-xr-xsrc/com/android/se/security/arf/PKCS15/EFDIR.java109
-rwxr-xr-xsrc/com/android/se/security/arf/PKCS15/EFDODF.java119
-rwxr-xr-xsrc/com/android/se/security/arf/PKCS15/EFODF.java135
-rwxr-xr-xsrc/com/android/se/security/arf/PKCS15/PKCS15Exception.java46
-rwxr-xr-xsrc/com/android/se/security/arf/PKCS15/PKCS15Handler.java237
-rwxr-xr-xsrc/com/android/se/security/arf/SecureElement.java177
-rwxr-xr-xsrc/com/android/se/security/arf/SecureElementException.java45
-rw-r--r--src/com/android/se/security/gpac/AID_REF_DO.java184
-rwxr-xr-xsrc/com/android/se/security/gpac/APDU_AR_DO.java197
-rwxr-xr-xsrc/com/android/se/security/gpac/AR_DO.java144
-rw-r--r--src/com/android/se/security/gpac/BerTlv.java264
-rwxr-xr-xsrc/com/android/se/security/gpac/DO_Exception.java62
-rw-r--r--src/com/android/se/security/gpac/Hash_REF_DO.java161
-rwxr-xr-xsrc/com/android/se/security/gpac/NFC_AR_DO.java110
-rwxr-xr-xsrc/com/android/se/security/gpac/ParserException.java62
-rwxr-xr-xsrc/com/android/se/security/gpac/REF_AR_DO.java144
-rw-r--r--src/com/android/se/security/gpac/REF_DO.java182
-rwxr-xr-xsrc/com/android/se/security/gpac/Response_ALL_AR_DO.java108
-rwxr-xr-xsrc/com/android/se/security/gpac/Response_ARAC_AID_DO.java111
-rwxr-xr-xsrc/com/android/se/security/gpac/Response_AR_DO.java100
-rwxr-xr-xsrc/com/android/se/security/gpac/Response_DO_Factory.java74
-rwxr-xr-xsrc/com/android/se/security/gpac/Response_RefreshTag_DO.java103
48 files changed, 7937 insertions, 0 deletions
diff --git a/Android.mk b/Android.mk
new file mode 100755
index 0000000..55ecd5b
--- /dev/null
+++ b/Android.mk
@@ -0,0 +1,16 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := SecureElement
+LOCAL_CERTIFICATE := platform
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_STATIC_JAVA_LIBRARIES := android.hardware.secure_element-V1.0-java
+
+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 100755
index 0000000..3412d66
--- /dev/null
+++ b/AndroidManifest.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.se"
+ android:sharedUserId="android.uid.se">
+ <application android:name=".SEApplication"
+ android:label="SecureElementApplication"
+ android:persistent="true">
+ <service android:name=".SecureElementService">
+ <intent-filter>
+ <action android:name="android.omapi.ISecureElementService"/>
+ </intent-filter>
+ </service>
+ </application>
+</manifest>
diff --git a/CleanSpec.mk b/CleanSpec.mk
new file mode 100755
index 0000000..eef469e
--- /dev/null
+++ b/CleanSpec.mk
@@ -0,0 +1,2 @@
+$(call add-clean-step, rm -f $(PRODUCT_OUT)/system/app/SecureElement/SecureElement.apk)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/APPS/SecureElement_intermediates)
diff --git a/NOTICE b/NOTICE
new file mode 100755
index 0000000..659696b
--- /dev/null
+++ b/NOTICE
@@ -0,0 +1,235 @@
+
+ Copyright (c) 2017, The Linux Foundation. All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are
+ met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+ * Neither the name of The Linux Foundation nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+ ----------------------------------------------------------------------------
+
+ 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.
+
+ ----------------------------------------------------------------------------
+
+ 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.
+
+
+ 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/PREUPLOAD.cfg b/PREUPLOAD.cfg
new file mode 100644
index 0000000..92e4293
--- /dev/null
+++ b/PREUPLOAD.cfg
@@ -0,0 +1,8 @@
+[Hook Scripts]
+checkstyle_hook = ${REPO_ROOT}/prebuilts/checkstyle/checkstyle.py --sha ${PREUPLOAD_COMMIT}
+ -fw src/com/android/se/
+ src/com/android/se/internal/
+ src/com/android/se/security/
+ src/com/android/se/security/ara/
+ src/com/android/se/security/arf/
+ src/com/android/se/security/gpac/
diff --git a/src/com/android/se/Channel.java b/src/com/android/se/Channel.java
new file mode 100755
index 0000000..2d6eaa2
--- /dev/null
+++ b/src/com/android/se/Channel.java
@@ -0,0 +1,323 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+/*
+ * Copyright (c) 2015-2017, The Linux Foundation.
+ */
+/*
+ * Contributed by: Giesecke & Devrient GmbH.
+ */
+
+package com.android.se;
+
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.se.omapi.ISecureElementChannel;
+import android.se.omapi.ISecureElementListener;
+import android.util.Log;
+
+import com.android.se.SecureElementService.SecureElementSession;
+import com.android.se.security.ChannelAccess;
+
+/**
+ * Represents a Channel opened with the Secure Element
+ */
+public class Channel implements IBinder.DeathRecipient {
+
+ private final String mTag = "SecureElement-Channel";
+ private final int mChannelNumber;
+ private final Object mLock = new Object();
+ private IBinder mBinder = null;
+ private boolean mIsClosed;
+ private SecureElementSession mSession;
+ private Terminal mTerminal;
+ private byte[] mSelectResponse;
+ private ChannelAccess mChannelAccess = null;
+ private int mCallingPid = 0;
+ private boolean mHasSelectedAid = false;
+ private byte[] mAid = null;
+
+ Channel(SecureElementSession session, Terminal terminal, int channelNumber,
+ byte[] selectResponse, ISecureElementListener listener) {
+ if (terminal == null) {
+ throw new IllegalArgumentException("Arguments can't be null");
+ }
+ mSession = session;
+ mTerminal = terminal;
+ mIsClosed = false;
+ mSelectResponse = selectResponse;
+ mChannelNumber = channelNumber;
+ if (listener != null) {
+ try {
+ mBinder = listener.asBinder();
+ mBinder.linkToDeath(this, 0);
+ } catch (RemoteException e) {
+ Log.e(mTag, "Failed to register client listener");
+ }
+ }
+ }
+
+ /**
+ * Close this channel if the client died.
+ */
+ public void binderDied() {
+ try {
+ Log.e(mTag, Thread.currentThread().getName() + " Client "
+ + mBinder.toString() + " died");
+ close();
+ } catch (Exception ignore) {
+ }
+ }
+
+ /**
+ * Closes the channel.
+ */
+ public synchronized void close() {
+ synchronized (mLock) {
+ if (isBasicChannel() && hasSelectedAid()) {
+ Log.i(mTag, "Close basic channel - Select without AID ...");
+ mTerminal.selectDefaultApplication();
+ }
+
+ mTerminal.closeChannel(this);
+ mIsClosed = true;
+ if (mBinder != null) {
+ mBinder.unlinkToDeath(this, 0);
+ }
+ if (mSession != null) {
+ mSession.removeChannel(this);
+ }
+ }
+ }
+
+ /**
+ * Transmits the given byte and returns the response.
+ */
+ public byte[] transmit(byte[] command) throws RemoteException {
+ if (isClosed()) {
+ throw new IllegalStateException("Channel is closed");
+ }
+ if (command == null) {
+ throw new NullPointerException("Command must not be null");
+ }
+ if (mChannelAccess == null) {
+ throw new SecurityException("Channel access not set");
+ }
+ if (mChannelAccess.getCallingPid() != mCallingPid) {
+ throw new SecurityException("Wrong Caller PID.");
+ }
+ if (command.length < 4) {
+ throw new IllegalArgumentException("Command too short");
+ }
+
+ if (((command[0] & (byte) 0x80) == 0)
+ && ((command[0] & (byte) 0x60) != (byte) 0x20)) {
+ // ISO command
+ if (command[1] == (byte) 0x70) {
+ throw new SecurityException("MANAGE CHANNEL command not allowed");
+ }
+ if ((command[1] == (byte) 0xA4) && (command[2] == (byte) 0x04)) {
+ throw new SecurityException("SELECT by DF name command not allowed");
+ }
+ } else if (command[0] == (byte) 0xFF) {
+ throw new IllegalArgumentException("CLA(0xFF) not allowed");
+ } else if ((command[1] == (byte) 0x9F) || (command[1] == (byte) 0x6F)) {
+ throw new IllegalArgumentException("the command INS is invalid "
+ + "(INS = 0x6F or INS = 0x9F)");
+ }
+
+ checkCommand(command);
+ synchronized (mLock) {
+ // set channel number bits
+ command[0] = setChannelToClassByte(command[0], mChannelNumber);
+ return mTerminal.transmit(command);
+ }
+ }
+
+ private boolean selectNext() throws RemoteException {
+ if (isClosed()) {
+ throw new IllegalStateException("Channel is closed");
+ } else if (mChannelAccess == null) {
+ throw new IllegalStateException("Channel access not set.");
+ } else if (mChannelAccess.getCallingPid() != mCallingPid) {
+ throw new SecurityException("Wrong Caller PID.");
+ } else if (mAid == null || mAid.length == 0) {
+ throw new UnsupportedOperationException("No aid given");
+ }
+
+ 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);
+
+ byte[] bufferSelectResponse = mTerminal.transmit(selectCommand);
+
+ if (bufferSelectResponse.length < 2) {
+ throw new UnsupportedOperationException("Transmit failed");
+ }
+ int sw1 = bufferSelectResponse[bufferSelectResponse.length - 2] & 0xFF;
+ int sw2 = bufferSelectResponse[bufferSelectResponse.length - 1] & 0xFF;
+ int sw = (sw1 << 8) | sw2;
+
+ if (((sw & 0xF000) == 0x9000) || ((sw & 0xFF00) == 0x6200)
+ || ((sw & 0xFF00) == 0x6300)) {
+ mSelectResponse = bufferSelectResponse.clone();
+ return true;
+ } else if ((sw & 0xFF00) == 0x6A00) {
+ return false;
+ } else {
+ throw new UnsupportedOperationException("Unsupported operation");
+ }
+ }
+
+ /**
+ * Returns a copy of the given CLA byte where the channel number bits are set
+ * as specified by the given channel number
+ *
+ * <p>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 inter-industry class byte
+ * coding) or [4..19] (for further inter-industry 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 ChannelAccess getChannelAccess() {
+ return this.mChannelAccess;
+ }
+
+ public void setChannelAccess(ChannelAccess channelAccess) {
+ this.mChannelAccess = channelAccess;
+ }
+
+ private void setCallingPid(int pid) {
+ mCallingPid = pid;
+ }
+
+ private void checkCommand(byte[] command) {
+ if (mTerminal.getAccessControlEnforcer() != null) {
+ // check command if it complies to the access rules.
+ // if not an exception is thrown
+ mTerminal.getAccessControlEnforcer().checkCommand(this, command);
+ } else {
+ throw new SecurityException("Access Controller not set for Terminal: "
+ + mTerminal.getName());
+ }
+ }
+
+ /**
+ * true if aid could be selected during opening the channel
+ * 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;
+ }
+
+ public int getChannelNumber() {
+ return mChannelNumber;
+ }
+
+ /**
+ * 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 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;
+ }
+
+ public boolean isBasicChannel() {
+ return (mChannelNumber == 0) ? true : false;
+ }
+
+ public boolean isClosed() {
+ return mIsClosed;
+ }
+
+ // Implementation of the SecureElement Channel interface according to OMAPI.
+ final class SecureElementChannel extends ISecureElementChannel.Stub {
+
+ @Override
+ public void close() throws RemoteException {
+ Channel.this.close();
+ }
+
+ @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 byte[] transmit(byte[] command) throws RemoteException {
+ Channel.this.setCallingPid(Binder.getCallingPid());
+ return Channel.this.transmit(command);
+ }
+
+ @Override
+ public boolean selectNext() throws RemoteException {
+ Channel.this.setCallingPid(Binder.getCallingPid());
+ return Channel.this.selectNext();
+ }
+ }
+}
diff --git a/src/com/android/se/SEApplication.java b/src/com/android/se/SEApplication.java
new file mode 100644
index 0000000..55a9e84
--- /dev/null
+++ b/src/com/android/se/SEApplication.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+package com.android.se;
+
+import android.app.Application;
+import android.content.Intent;
+
+/**
+ * Starts the SecureElementService.
+ */
+public class SEApplication extends Application {
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ Intent serviceIntent = new Intent(getApplicationContext(), SecureElementService.class);
+ startService(serviceIntent);
+ }
+}
diff --git a/src/com/android/se/SecureElementService.java b/src/com/android/se/SecureElementService.java
new file mode 100755
index 0000000..7ecaaf6
--- /dev/null
+++ b/src/com/android/se/SecureElementService.java
@@ -0,0 +1,308 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+/*
+ * Copyright (c) 2014-2017, The Linux Foundation.
+ */
+/*
+ * Contributed by: Giesecke & Devrient GmbH.
+ */
+
+package com.android.se;
+
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.hardware.secure_element.V1_0.ISecureElement;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.se.omapi.ISecureElementChannel;
+import android.se.omapi.ISecureElementListener;
+import android.se.omapi.ISecureElementReader;
+import android.se.omapi.ISecureElementService;
+import android.se.omapi.ISecureElementSession;
+import android.util.Log;
+
+import com.android.se.Terminal.SecureElementReader;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.security.AccessControlException;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.NoSuchElementException;
+
+/**
+ * Underlying implementation for OMAPI SEService
+ */
+public final class SecureElementService extends Service {
+
+ public static final String UICC_TERMINAL = "SIM";
+ public static final String ESE_TERMINAL = "eSE";
+ private final String mTag = "SecureElementService";
+ // LinkedHashMap will maintain the order of insertion
+ private LinkedHashMap<String, Terminal> mTerminals = new LinkedHashMap<String, Terminal>();
+ private final ISecureElementService.Stub mSecureElementServiceBinder =
+ new ISecureElementService.Stub() {
+
+ @Override
+ public String[] getReaders() throws RemoteException {
+ return mTerminals.keySet().toArray(new String[mTerminals.size()]);
+ }
+
+ @Override
+ public ISecureElementReader getReader(String reader)
+ throws RemoteException {
+ Log.d(mTag, "getReader() " + reader);
+ Terminal terminal = getTerminal(reader);
+ return terminal.new SecureElementReader(SecureElementService.this);
+ }
+
+ @Override
+ public synchronized boolean[] isNFCEventAllowed(String reader, byte[] aid,
+ String[] packageNames)
+ throws RemoteException {
+ if (aid == null || aid.length == 0) {
+ aid = new byte[]{0x00, 0x00, 0x00, 0x00, 0x00};
+ }
+ if (aid.length < 5 || aid.length > 16) {
+ throw new IllegalArgumentException("AID out of range");
+ }
+ if (packageNames == null || packageNames.length == 0) {
+ throw new IllegalArgumentException("package names not specified");
+ }
+ Terminal terminal = getTerminal(reader);
+ return terminal.isNfcEventAllowed(getPackageManager(), aid, packageNames, true);
+ }
+
+ @Override
+ protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+ for (Terminal terminal : mTerminals.values()) {
+ terminal.dump(writer);
+ }
+ }
+ };
+ private Context mContext;
+
+ public SecureElementService() {
+ super();
+ }
+
+ /** Returns the terminal from the Reader name. */
+ private Terminal getTerminal(String reader) {
+ if (reader == null) {
+ throw new NullPointerException("reader must not be null");
+ }
+ if (reader.equals("SIM")) {
+ reader = "SIM1";
+ }
+ Terminal terminal = mTerminals.get(reader);
+ if (terminal == null) {
+ throw new IllegalArgumentException("Reader: " + reader + " doesn't exist");
+ }
+ return terminal;
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ Log.i(mTag, Thread.currentThread().getName() + " onBind");
+ if (ISecureElementService.class.getName().equals(intent.getAction())) {
+ return mSecureElementServiceBinder;
+ }
+ return null;
+ }
+
+ @Override
+ public void onCreate() {
+ Log.i(mTag, Thread.currentThread().getName() + " onCreate");
+
+ mContext = getApplicationContext();
+ createTerminals();
+ ServiceManager.addService(Context.SECURE_ELEMENT_SERVICE, mSecureElementServiceBinder);
+ }
+
+ /**
+ * In case the onDestroy is called, we free the memory and
+ * close all the channels.
+ */
+ public void onDestroy() {
+ Log.i(mTag, "onDestroy");
+ for (Terminal terminal : mTerminals.values()) {
+ terminal.closeChannels();
+ }
+ }
+
+ private void addTerminals(String terminalName) {
+ int index = 1;
+ String name = terminalName + Integer.toString(index);
+ try {
+ ISecureElement seHal = ISecureElement.getService(name);
+ while (seHal != null) {
+ Terminal terminal = new Terminal(name, mContext, seHal);
+ mTerminals.put(name, terminal);
+ index++;
+ name = terminalName + Integer.toString(index);
+ seHal = ISecureElement.getService(name);
+ }
+ } catch (NoSuchElementException e) {
+ //Thrown if the HAL implementation doesn't exist.
+ } catch (RemoteException e) {
+ Log.e(mTag, "Error in getService() for " + name);
+ }
+ }
+
+ private void createTerminals() {
+ // Check for all eSE HAL implementations
+ addTerminals(ESE_TERMINAL);
+ addTerminals(UICC_TERMINAL);
+ return;
+ }
+
+ private 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("PackageName can not be determined");
+ }
+
+ final class SecureElementSession extends ISecureElementSession.Stub {
+
+ private final SecureElementReader mReader;
+ /** List of open channels in use of by this client. */
+ private final List<Channel> mChannels = new ArrayList<>();
+ private final Object mLock = new Object();
+ private boolean mIsClosed;
+ private byte[] mAtr;
+
+ SecureElementSession(SecureElementReader reader) {
+ if (reader == null) {
+ throw new NullPointerException("SecureElementReader cannot be null");
+ }
+ mReader = reader;
+ mAtr = mReader.getAtr();
+ mIsClosed = false;
+ }
+
+ public ISecureElementReader getReader() throws RemoteException {
+ return mReader;
+ }
+
+ @Override
+ public byte[] getAtr() throws RemoteException {
+ return mAtr;
+ }
+
+ @Override
+ public void close() throws RemoteException {
+ closeChannels();
+ mReader.removeSession(this);
+ synchronized (mLock) {
+ mIsClosed = true;
+ }
+ }
+
+ void removeChannel(Channel channel) {
+ synchronized (mLock) {
+ if (mChannels != null) {
+ mChannels.remove(channel);
+ }
+ }
+ }
+
+ @Override
+ public void closeChannels() throws RemoteException {
+ synchronized (mLock) {
+ while (mChannels.size() > 0) {
+ try {
+ mChannels.get(0).close();
+ } catch (Exception ignore) {
+ Log.e(mTag, "SecureElementSession Channel - close Exception "
+ + ignore.getMessage());
+ }
+ }
+ }
+ }
+
+ @Override
+ public boolean isClosed() throws RemoteException {
+ synchronized (mLock) {
+ return mIsClosed;
+ }
+ }
+
+ @Override
+ public ISecureElementChannel openBasicChannel(byte[] aid, byte p2,
+ ISecureElementListener listener) throws RemoteException {
+ if (isClosed()) {
+ throw new IllegalStateException("Session is closed");
+ } else if (listener == null) {
+ throw new NullPointerException("listener must not be null");
+ } else if ((p2 != 0x00) && (p2 != 0x04) && (p2 != 0x08)
+ && (p2 != (byte) 0x0C)) {
+ throw new UnsupportedOperationException("p2 not supported: "
+ + String.format("%02x ", p2 & 0xFF));
+ }
+
+ String packageName = getPackageNameFromCallingUid(Binder.getCallingUid());
+ Channel channel = mReader.getTerminal().openBasicChannel(this, aid,
+ p2, listener, packageName, Binder.getCallingPid());
+ if (channel == null) {
+ Log.i(mTag, "OpenBasicChannel() - returning null");
+ return null;
+ }
+ Log.i(mTag, "Open basic channel success. Channel: "
+ + channel.getChannelNumber());
+
+ mChannels.add(channel);
+ return channel.new SecureElementChannel();
+ }
+
+ @Override
+ public ISecureElementChannel openLogicalChannel(byte[] aid, byte p2,
+ ISecureElementListener listener) throws RemoteException {
+ if (isClosed()) {
+ throw new IllegalStateException("Session is closed");
+ } else if (listener == null) {
+ throw new NullPointerException("listener must not be null");
+ } else if ((p2 != 0x00) && (p2 != 0x04) && (p2 != 0x08)
+ && (p2 != (byte) 0x0C)) {
+ throw new UnsupportedOperationException("p2 not supported: "
+ + String.format("%02x ", p2 & 0xFF));
+ }
+
+ String packageName = getPackageNameFromCallingUid(Binder.getCallingUid());
+ Channel channel = mReader.getTerminal().openLogicalChannel(this, aid, p2,
+ listener, packageName, Binder.getCallingPid());
+
+ if (channel == null) {
+ Log.i(mTag, "openLogicalChannel() - returning null");
+ return null;
+ }
+ Log.i(mTag, "openLogicalChannel() Success. Channel: "
+ + channel.getChannelNumber());
+
+ mChannels.add(channel);
+ return channel.new SecureElementChannel();
+ }
+ }
+}
diff --git a/src/com/android/se/Terminal.java b/src/com/android/se/Terminal.java
new file mode 100755
index 0000000..c7348cd
--- /dev/null
+++ b/src/com/android/se/Terminal.java
@@ -0,0 +1,645 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+/*
+ * Copyright (c) 2015-2017, The Linux Foundation.
+ */
+
+/*
+ * Contributed by: Giesecke & Devrient GmbH.
+ */
+
+package com.android.se;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.hardware.secure_element.V1_0.ISecureElement;
+import android.hardware.secure_element.V1_0.ISecureElementHalCallback;
+import android.hardware.secure_element.V1_0.LogicalChannelResponse;
+import android.hardware.secure_element.V1_0.SecureElementStatus;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
+import android.se.omapi.ISecureElementListener;
+import android.se.omapi.ISecureElementReader;
+import android.se.omapi.ISecureElementSession;
+import android.se.omapi.SEService;
+import android.util.Log;
+
+import com.android.se.SecureElementService.SecureElementSession;
+import com.android.se.internal.ByteArrayConverter;
+import com.android.se.security.AccessControlEnforcer;
+import com.android.se.security.ChannelAccess;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.NoSuchElementException;
+
+/**
+ * Each Terminal represents a Secure Element.
+ * Communicates to the SE via SecureElement HAL.
+ */
+public class Terminal {
+
+ private final String mTag;
+ private final Map<Integer, Channel> mChannels = new HashMap<Integer, Channel>();
+ private final Object mLock = new Object();
+ private final String mName;
+ public boolean mIsConnected = false;
+ private Context mContext;
+ private boolean mDefaultApplicationSelectedOnBasicChannel = true;
+
+ private boolean mDebug = true;
+
+ private ISecureElement mSEHal;
+
+ /** For each Terminal there will be one AccessController object. */
+ private AccessControlEnforcer mAccessControlEnforcer;
+
+ private ISecureElementHalCallback.Stub mHalCallback = new ISecureElementHalCallback.Stub() {
+ @Override
+ public void onStateChange(boolean state) {
+ synchronized (mLock) {
+ Log.i(mTag, "OnStateChange:" + state);
+ mIsConnected = state;
+ if (!state) {
+ if (mAccessControlEnforcer != null) {
+ mAccessControlEnforcer.reset();
+ }
+ } else {
+ initializeAccessControl();
+ }
+ }
+ }
+ };
+
+ public Terminal(String name, Context context, ISecureElement seHal) {
+ if (seHal == null) {
+ throw new IllegalArgumentException("ISecureElement cannot be null ");
+ }
+ mContext = context;
+ mName = name;
+ mTag = "SecureElement-Terminal-" + getName();
+ mSEHal = seHal;
+ try {
+ seHal.init(mHalCallback);
+ } catch (RemoteException e) {
+ }
+ }
+
+ private ArrayList<Byte> byteArrayToArrayList(byte[] array) {
+ ArrayList<Byte> list = new ArrayList<Byte>();
+ if (array == null) {
+ return list;
+ }
+
+ for (Byte b : array) {
+ list.add(b);
+ }
+ return list;
+ }
+
+ private byte[] arrayListToByteArray(ArrayList<Byte> list) {
+ Byte[] byteArray = list.toArray(new Byte[list.size()]);
+ int i = 0;
+ byte[] result = new byte[list.size()];
+ for (Byte b : byteArray) {
+ result[i++] = b.byteValue();
+ }
+ return result;
+ }
+
+ /**
+ * Closes the given channel
+ */
+ public void closeChannel(Channel channel) {
+ if (channel == null) {
+ return;
+ }
+ if (mIsConnected && !channel.isBasicChannel()) {
+ try {
+ byte status = mSEHal.closeChannel((byte) channel.getChannelNumber());
+ if (status != SecureElementStatus.SUCCESS) {
+ Log.e(mTag, "Error closing channel " + channel.getChannelNumber());
+ }
+ } catch (RemoteException e) {
+ Log.e(mTag, "Exception in closeChannel() " + e);
+ }
+ }
+ synchronized (mLock) {
+ mChannels.remove(channel.getChannelNumber(), channel);
+ if (mChannels.get(channel.getChannelNumber()) != null) {
+ Log.e(mTag, "Removing channel failed");
+ }
+ }
+ }
+
+ /**
+ * This method is called in SecureElementService:onDestroy to clean up
+ * all open channels.
+ */
+ public synchronized void closeChannels() {
+ Collection<Channel> col = mChannels.values();
+ Channel[] channelList = col.toArray(new Channel[col.size()]);
+ for (Channel channel : channelList) {
+ closeChannel(channel);
+ }
+ }
+
+ public String getName() {
+ return mName;
+ }
+
+ /**
+ * Returns the ATR of the Secure Element, or null if not available.
+ */
+ public byte[] getAtr() {
+ if (!mIsConnected) {
+ return null;
+ }
+
+ try {
+ ArrayList<Byte> responseList = mSEHal.getAtr();
+ if (responseList.isEmpty()) {
+ return null;
+ }
+ return arrayListToByteArray(responseList);
+ } catch (RemoteException e) {
+ Log.e(mTag, "Exception in getAtr()" + e);
+ return null;
+ }
+ }
+
+ /**
+ * Selects the default application on the basic channel.
+ *
+ * If there is an exception selecting the default application, select
+ * is performed with the default access control aid.
+ */
+ public void selectDefaultApplication() {
+ try {
+ select(null);
+ } catch (NoSuchElementException e) {
+ if (getAccessControlEnforcer() != null) {
+ try {
+ select(mAccessControlEnforcer.getDefaultAccessControlAid());
+ } catch (Exception ignore) {
+ }
+ }
+ } catch (RemoteException ignore) {
+ }
+ synchronized (mLock) {
+ mDefaultApplicationSelectedOnBasicChannel = true;
+ }
+ }
+
+ private void select(byte[] aid) throws RemoteException {
+ int commandSize = (aid == null ? 0 : aid.length) + 5;
+ byte[] selectCommand = new byte[commandSize];
+ selectCommand[0] = 0x00;
+ selectCommand[1] = (byte) 0xA4;
+ selectCommand[2] = 0x04;
+ selectCommand[3] = 0x00;
+ if (aid != null && aid.length != 0) {
+ selectCommand[4] = (byte) aid.length;
+ System.arraycopy(aid, 0, selectCommand, 5, aid.length);
+ } else {
+ selectCommand[4] = 0x00;
+ }
+ byte[] selectResponse = transmit(selectCommand);
+ if (selectResponse.length < 2) {
+ selectResponse = null;
+ throw new NoSuchElementException("Response length is too small");
+ }
+ int sw1 = selectResponse[selectResponse.length - 2];
+ int sw2 = selectResponse[selectResponse.length - 1];
+ if (sw1 != 0x90 || sw2 != 0x00) {
+ selectResponse = null;
+ throw new NoSuchElementException("Status word is incorrect");
+ }
+ }
+
+ /**
+ * Opens a Basic Channel with the given AID and P2 paramters
+ */
+ public Channel openBasicChannel(SecureElementSession session, byte[] aid, byte p2,
+ ISecureElementListener listener, String packageName,
+ int pid) throws RemoteException {
+ if (aid != null && aid.length == 0) {
+ aid = null;
+ } else if (aid != null && (aid.length < 5 || aid.length > 16)) {
+ throw new IllegalArgumentException("AID out of range");
+ }
+
+ Log.w(mTag, "Enable access control on basic channel for " + packageName);
+ ChannelAccess channelAccess = setUpChannelAccess(aid, packageName, true, pid);
+
+ synchronized (mLock) {
+ if (mChannels.get(0) != null) {
+ Log.e(mTag, "basic channel in use");
+ return null;
+ }
+ if (aid == null && !mDefaultApplicationSelectedOnBasicChannel) {
+ Log.e(mTag, "default application is not selected");
+ return null;
+ }
+
+ ArrayList<byte[]> responseList = new ArrayList<byte[]>();
+ byte[] status = new byte[1];
+ mSEHal.openBasicChannel(byteArrayToArrayList(aid), p2,
+ new ISecureElement.openBasicChannelCallback() {
+ @Override
+ public void onValues(ArrayList<Byte> responseObject, byte halStatus) {
+ status[0] = halStatus;
+ responseList.add(arrayListToByteArray(responseObject));
+ return;
+ }
+ });
+ byte[] selectResponse = responseList.get(0);
+ if (status[0] == SecureElementStatus.CHANNEL_NOT_AVAILABLE) {
+ return null;
+ } else if (status[0] == SecureElementStatus.UNSUPPORTED_OPERATION) {
+ throw new UnsupportedOperationException("OpenBasicChannel() failed");
+ } else if (status[0] == SecureElementStatus.IOERROR) {
+ throw new ServiceSpecificException(SEService.IO_ERROR, "OpenBasicChannel() failed");
+ }
+ Channel basicChannel = new Channel(session, this, 0, selectResponse,
+ listener);
+ basicChannel.setChannelAccess(channelAccess);
+
+ byte[] selectedAid = getSelectedAid(selectResponse);
+ if (selectedAid != null) {
+ basicChannel.hasSelectedAid(true, selectedAid);
+ } else {
+ basicChannel.hasSelectedAid((aid != null) ? true : false, aid);
+ }
+
+ if (aid != null) {
+ mDefaultApplicationSelectedOnBasicChannel = false;
+ }
+ mChannels.put(0, basicChannel);
+ return basicChannel;
+ }
+ }
+
+ /**
+ * Opens a logical Channel without Channel Access initialization.
+ */
+ public Channel openLogicalChannelWithoutChannelAccess(byte[] aid) throws RemoteException {
+ return openLogicalChannel(null, aid, (byte) 0x00, null, null, 0);
+ }
+
+ /**
+ * Opens a logical Channel with AID.
+ */
+ public Channel openLogicalChannel(
+ SecureElementSession session, byte[] aid, byte p2,
+ ISecureElementListener listener, String packageName,
+ int pid) throws RemoteException {
+ if (aid != null && aid.length == 0) {
+ aid = null;
+ } else if (aid != null && (aid.length < 5 || aid.length > 16)) {
+ throw new IllegalArgumentException("AID out of range");
+ } else if (!mIsConnected) {
+ throw new ServiceSpecificException(SEService.IO_ERROR,
+ "Secure Element is not connected");
+ }
+
+ ChannelAccess channelAccess = null;
+ if (packageName != null) {
+ Log.w(mTag, "Enable access control on logical channel for " + packageName);
+ channelAccess = setUpChannelAccess(aid, packageName, true, pid);
+ }
+
+ synchronized (mLock) {
+ LogicalChannelResponse[] responseArray = new LogicalChannelResponse[1];
+ byte[] status = new byte[1];
+ mSEHal.openLogicalChannel(byteArrayToArrayList(aid), p2,
+ new ISecureElement.openLogicalChannelCallback() {
+ @Override
+ public void onValues(LogicalChannelResponse response, byte halStatus) {
+ status[0] = halStatus;
+ responseArray[0] = response;
+ return;
+ }
+ });
+ if (status[0] == SecureElementStatus.CHANNEL_NOT_AVAILABLE) {
+ return null;
+ } else if (status[0] == SecureElementStatus.UNSUPPORTED_OPERATION) {
+ throw new UnsupportedOperationException("OpenLogicalChannel() failed");
+ } else if (status[0] == SecureElementStatus.IOERROR) {
+ throw new ServiceSpecificException(SEService.IO_ERROR,
+ "OpenLogicalChannel() failed");
+ } else if (status[0] == SecureElementStatus.NO_SUCH_ELEMENT_ERROR) {
+ throw new ServiceSpecificException(SEService.NO_SUCH_ELEMENT_ERROR,
+ "OpenLogicalChannel() failed");
+ }
+ if (responseArray[0].channelNumber <= 0 || status[0] != SecureElementStatus.SUCCESS) {
+ return null;
+ }
+ int channelNumber = responseArray[0].channelNumber;
+ byte[] selectResponse = arrayListToByteArray(responseArray[0].selectResponse);
+ Channel logicalChannel = new Channel(session, this, channelNumber,
+ selectResponse, listener);
+ logicalChannel.setChannelAccess(channelAccess);
+
+ byte[] selectedAid = selectedAid = getSelectedAid(selectResponse);
+ if (selectedAid != null) {
+ logicalChannel.hasSelectedAid(true, selectedAid);
+ } else {
+ logicalChannel.hasSelectedAid((aid != null) ? true : false, aid);
+ }
+
+ mChannels.put(channelNumber, logicalChannel);
+ return logicalChannel;
+ }
+ }
+
+ /**
+ * Returns true if the given AID can be selected on the Terminal
+ */
+ public boolean isAidSelectable(byte[] aid) {
+ if (aid == null) {
+ throw new NullPointerException("aid must not be null");
+ } else if (!mIsConnected) {
+ Log.e(mTag, "Secure Element is not connected");
+ return false;
+ }
+
+ synchronized (mLock) {
+ LogicalChannelResponse[] responseArray = new LogicalChannelResponse[1];
+ byte[] status = new byte[1];
+ try {
+ mSEHal.openLogicalChannel(byteArrayToArrayList(aid), (byte) 0x00,
+ new ISecureElement.openLogicalChannelCallback() {
+ @Override
+ public void onValues(LogicalChannelResponse response, byte halStatus) {
+ status[0] = halStatus;
+ responseArray[0] = response;
+ return;
+ }
+ });
+ if (status[0] == SecureElementStatus.SUCCESS) {
+ mSEHal.closeChannel(responseArray[0].channelNumber);
+ return true;
+ }
+ return false;
+ } catch (RemoteException e) {
+ Log.e(mTag, "Error in isAidSelectable() returning false" + e);
+ return false;
+ }
+ }
+ }
+
+ /**
+ * Transmits the specified command and returns the response.
+ *
+ * @param cmd the command APDU to be transmitted.
+ * @return the response received.
+ */
+ public byte[] transmit(byte[] cmd) throws RemoteException {
+ if (!mIsConnected) {
+ Log.e(mTag, "Secure Element is not connected");
+ throw new ServiceSpecificException(SEService.IO_ERROR,
+ "Secure Element is not connected");
+ }
+
+ byte[] rsp = transmitInternal(cmd);
+ int sw1 = rsp[rsp.length - 2] & 0xFF;
+ int sw2 = rsp[rsp.length - 1] & 0xFF;
+
+ if (sw1 == 0x6C) {
+ cmd[cmd.length - 1] = rsp[rsp.length - 1];
+ rsp = transmitInternal(cmd);
+ } else if (sw1 == 0x61) {
+ do {
+ byte[] getResponseCmd = new byte[]{
+ cmd[0], (byte) 0xC0, 0x00, 0x00, (byte) sw2
+ };
+ byte[] tmp = transmitInternal(getResponseCmd);
+ byte[] aux = rsp;
+ rsp = new byte[aux.length + tmp.length - 2];
+ System.arraycopy(aux, 0, rsp, 0, aux.length - 2);
+ System.arraycopy(tmp, 0, rsp, aux.length - 2, tmp.length);
+ sw1 = rsp[rsp.length - 2] & 0xFF;
+ sw2 = rsp[rsp.length - 1] & 0xFF;
+ } while (sw1 == 0x61);
+ }
+ return rsp;
+ }
+
+ private byte[] transmitInternal(byte[] cmd) throws RemoteException {
+ ArrayList<Byte> response = mSEHal.transmit(byteArrayToArrayList(cmd));
+ if (response.isEmpty()) {
+ throw new ServiceSpecificException(SEService.IO_ERROR, "Error in transmit()");
+ }
+ byte[] rsp = arrayListToByteArray(response);
+ if (mDebug) {
+ Log.i(mTag, "Sent : " + ByteArrayConverter.byteArrayToHexString(cmd));
+ Log.i(mTag, "Received : " + ByteArrayConverter.byteArrayToHexString(rsp));
+ }
+ return rsp;
+ }
+
+ /**
+ * Checks if the application is authorized to receive the transaction event.
+ */
+ public boolean[] isNfcEventAllowed(
+ PackageManager packageManager,
+ byte[] aid,
+ String[] packageNames,
+ boolean checkRefreshTag) {
+ if (mAccessControlEnforcer == null) {
+ Log.e(mTag, "Access Control Enforcer not properly set up");
+ initializeAccessControl();
+ }
+ mAccessControlEnforcer.setPackageManager(packageManager);
+
+ synchronized (mLock) {
+ try {
+ return mAccessControlEnforcer.isNfcEventAllowed(aid, packageNames,
+ checkRefreshTag);
+ } catch (Exception e) {
+ Log.i(mTag, "isNfcEventAllowed Exception: " + e.getMessage());
+ return null;
+ }
+ }
+ }
+
+ /**
+ * Returns true if the Secure Element is present
+ */
+ public boolean isSecureElementPresent() {
+ try {
+ return mSEHal.isCardPresent();
+ } catch (RemoteException e) {
+ Log.e(mTag, "Error in isSecureElementPresent() " + e);
+ return false;
+ }
+ }
+
+ /**
+ * Initialize the Access Control and set up the channel access.
+ */
+ public ChannelAccess setUpChannelAccess(byte[] aid, String packageName,
+ boolean checkRefreshTag, int pid) {
+ if (mAccessControlEnforcer == null) {
+ Log.e(mTag, "Access Control Enforcer not properly set up");
+ initializeAccessControl();
+ }
+ mAccessControlEnforcer.setPackageManager(mContext.getPackageManager());
+
+ synchronized (mLock) {
+ try {
+ ChannelAccess channelAccess =
+ mAccessControlEnforcer.setUpChannelAccess(aid, packageName,
+ checkRefreshTag);
+ channelAccess.setCallingPid(pid);
+ return channelAccess;
+ } catch (Exception e) {
+ throw new SecurityException("Exception in setUpChannelAccess()" + e);
+ }
+ }
+ }
+
+ /**
+ * Initializes the Access Control for this Terminal
+ */
+ private synchronized void initializeAccessControl() {
+ synchronized (mLock) {
+ if (mAccessControlEnforcer == null) {
+ mAccessControlEnforcer = new AccessControlEnforcer(this);
+ }
+ mAccessControlEnforcer.initialize(true);
+ }
+ }
+
+ public AccessControlEnforcer getAccessControlEnforcer() {
+ return mAccessControlEnforcer;
+ }
+
+ private byte[] getSelectedAid(byte[] selectResponse) {
+ byte[] selectedAid = null;
+ if ((selectResponse != null && selectResponse.length >= 2)
+ && (selectResponse.length == (selectResponse[1] + 4))
+ && // header(2) + SW(2)
+ ((selectResponse[0] == (byte) 0x62)
+ || (selectResponse[0] == (byte) 0x6F))) { // FCP or FCI template
+ int nextTlv = 2;
+ while (selectResponse.length > nextTlv) {
+ if (selectResponse[nextTlv] == (byte) 0x84) {
+ if (selectResponse.length >= (nextTlv + selectResponse[nextTlv + 1] + 2)) {
+ selectedAid = new byte[selectResponse[nextTlv + 1]];
+ System.arraycopy(
+ selectResponse, nextTlv + 2, selectedAid, 0,
+ selectResponse[nextTlv + 1]);
+ }
+ break;
+ } else {
+ nextTlv += 2 + selectResponse[nextTlv + 1];
+ }
+ }
+ }
+ return selectedAid;
+ }
+
+ /** Dump data for debug purpose . */
+ public void dump(PrintWriter writer) {
+ writer.println("SECURE ELEMENT SERVICE TERMINAL: " + mName);
+ writer.println();
+
+ writer.println("mIsConnected:" + mIsConnected);
+ writer.println();
+
+ /* Dump the list of currunlty openned channels */
+ writer.println("List of open channels:");
+
+ for (Channel channel : mChannels.values()) {
+ writer.println("channel " + channel.getChannelNumber() + ": ");
+ writer.println("package: " + channel.getChannelAccess().getPackageName());
+ writer.println("pid: " + channel.getChannelAccess().getCallingPid());
+ writer.println("aid selected: " + channel.hasSelectedAid());
+ writer.println("basic channel: " + channel.isBasicChannel());
+ writer.println();
+ }
+ writer.println();
+
+ /* Dump ACE data */
+ if (mAccessControlEnforcer != null) {
+ mAccessControlEnforcer.dump(writer);
+ }
+ }
+
+ // Implementation of the SecureElement Reader interface according to OMAPI.
+ final class SecureElementReader extends ISecureElementReader.Stub {
+
+ private final SecureElementService mService;
+ private final ArrayList<SecureElementSession> mSessions =
+ new ArrayList<SecureElementSession>();
+
+ SecureElementReader(SecureElementService service) {
+ mService = service;
+ }
+
+ public byte[] getAtr() {
+ return Terminal.this.getAtr();
+ }
+
+ @Override
+ public boolean isSecureElementPresent() throws RemoteException {
+ return Terminal.this.isSecureElementPresent();
+ }
+
+ @Override
+ public void closeSessions() {
+ synchronized (mLock) {
+ while (mSessions.size() > 0) {
+ try {
+ mSessions.get(0).close();
+ } catch (Exception ignore) {
+ }
+ }
+ mSessions.clear();
+ }
+ }
+
+ public void removeSession(SecureElementSession session) {
+ if (session == null) {
+ throw new NullPointerException("session is null");
+ }
+ mSessions.remove(session);
+ }
+
+ @Override
+ public ISecureElementSession openSession() throws RemoteException {
+ if (!isSecureElementPresent()) {
+ throw new ServiceSpecificException(SEService.IO_ERROR,
+ "Secure Element is not present.");
+ }
+
+ synchronized (mLock) {
+ SecureElementSession session = mService.new SecureElementSession(this);
+ mSessions.add(session);
+ return session;
+ }
+ }
+
+ Terminal getTerminal() {
+ return Terminal.this;
+ }
+ }
+}
diff --git a/src/com/android/se/internal/ByteArrayConverter.java b/src/com/android/se/internal/ByteArrayConverter.java
new file mode 100755
index 0000000..f655a6e
--- /dev/null
+++ b/src/com/android/se/internal/ByteArrayConverter.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+/*
+ * Copyright (c) 2015-2017, The Linux Foundation.
+ */
+/*
+ * Copyright 2013 Giesecke & Devrient GmbH.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.se.internal;
+
+/** Utilities class. */
+public final class ByteArrayConverter {
+
+ /** Override the default constructor to make it private. */
+ private ByteArrayConverter() {
+ }
+
+ /**
+ * Forms a FileViewProvider-compatible path String (i.e., transforms the byte array {0x3F, 0x00,
+ * 0x2F, 0xE2} into the String "3F00:2FE2").
+ *
+ * @param rawPath The byte array containing the path component.
+ * @return A FileViewProvider-compatible path String.
+ * @throws IllegalArgumentException if the path has a bad coding.
+ */
+ public static String byteArrayToPathString(byte[] rawPath) throws IllegalArgumentException {
+ if (rawPath.length % 2 != 0) {
+ throw new IllegalArgumentException("Invald path");
+ }
+
+ byte[] buffer = new byte[2];
+ String path = "";
+ for (int i = 0; i < rawPath.length; i += 2) {
+ System.arraycopy(rawPath, i, buffer, 0, 2);
+ String fid = byteArrayToHexString(buffer);
+ if (fid.equalsIgnoreCase("3F00")) {
+ // MF should not be included in path
+ continue;
+ }
+ path = path.concat(fid);
+ if (i != rawPath.length - 2) {
+ path = path.concat(":");
+ }
+ }
+ return path;
+ }
+
+ /**
+ * Forms an hex-encoded String of the specified byte array.
+ *
+ * @param array The byte array to be hex-encoded.
+ * @param offset The start position.
+ * @param length The number of bytes to be converted.
+ * @return A hex-encoded String of the specified byte array.
+ */
+ public static String byteArrayToHexString(byte[] array, int offset, int length) {
+ if (array == null) {
+ return "";
+ }
+
+ StringBuilder sb = new StringBuilder();
+
+ for (int i = 0; i < length; i++) {
+ sb.append(String.format("%02x", array[offset + i] & 0xFF));
+ }
+
+ return sb.toString();
+ }
+
+ /** Converts the byte array to Hex String from a given offset */
+ public static String byteArrayToHexString(byte[] array, int offset) {
+ StringBuffer s = new StringBuffer();
+ for (int i = offset; i < array.length; i++) {
+ s.append(Integer.toHexString(0x100 + (array[i] & 0xff)).substring(1));
+ }
+ return s.toString();
+ }
+
+ /**
+ * Forms an hex-encoded String of the specified byte array.
+ *
+ * @param byteArray The byte array to be hex-encoded.
+ * @return A hex-encoded String of the specified byte array.
+ */
+ public static String byteArrayToHexString(byte[] byteArray) {
+ if (byteArray == null) {
+ return "";
+ }
+ return byteArrayToHexString(byteArray, 0, byteArray.length);
+ }
+
+ /**
+ * Forms a byte array containing the values of the hex-encoded string.
+ *
+ * @param str The hex-encoded string to be converted to byte-array.
+ * @param offset The start position.
+ * @param length The number of chars to be converted (must be multiple of 2).
+ * @return A byte array containing the values of the hex-encoded string.
+ */
+ public static byte[] hexStringToByteArray(String str, int offset, int length) {
+ if (length % 2 != 0) {
+ throw new IllegalArgumentException("length must be multiple of 2");
+ }
+
+ str = str.toUpperCase();
+
+ byte[] outputBytes = new byte[str.length() / 2];
+
+ for (int i = 0; i < length; i += 2) {
+ char c1 = str.charAt(i + offset);
+ char c2 = str.charAt(i + 1 + offset);
+ if (!isHexChar(c1) || !isHexChar(c2)) {
+ throw new IllegalArgumentException("Invalid char found");
+ }
+
+ outputBytes[i / 2] = (byte) ((Character.digit(c1, 16) << 4) + Character.digit(c2, 16));
+ }
+
+ return outputBytes;
+ }
+
+ /**
+ * Forms a byte array containing the values of the hex-encoded string.
+ *
+ * @param str The hex-encoded string to be converted to byte-array.
+ * @return A byte array containing the values of the hex-encoded string.
+ */
+ public static byte[] hexStringToByteArray(String str) {
+ return hexStringToByteArray(str, 0, str.length());
+ }
+
+ /**
+ * Forms a byte array containing the specified integer value.
+ *
+ * @param value The integer value to be converted to byte array.
+ * @return A byte array containing the specified integer value.
+ */
+ public static byte[] intToByteArray(int value) {
+ return new byte[]{
+ (byte) (value >>> 24), (byte) (value >>> 16), (byte) (value >>> 8), (byte) value
+ };
+ }
+
+ /**
+ * Forms an integer from a byte array.
+ *
+ * @param byteArray The byte array from where to form the integer.
+ * @return The integer value representing the specified byte array. 0 if the array is empty. If
+ * the array is longer than 4 bytes, only bytes 0 to 3 will be considered.
+ */
+ public static int byteArrayToInt(byte[] byteArray) {
+ switch (byteArray.length) {
+ case 0:
+ return 0;
+ case 1:
+ return (byteArray[0] & 0xFF);
+ case 2:
+ return (byteArray[0] & 0xFF) << 8 | (byteArray[1] & 0xFF);
+ case 3:
+ return (byteArray[0] & 0xFF) << 16 | (byteArray[1] & 0xFF) << 8 | (byteArray[2]
+ & 0xFF);
+ default:
+ return (byteArray[0] & 0xFF) << 24
+ | (byteArray[1] & 0xFF) << 16
+ | (byteArray[2] & 0xFF) << 8
+ | byteArray[3] & 0xFF;
+ }
+ }
+
+ /**
+ * Decides whether a char is a valid hex value or not.
+ *
+ * @param c The char to be evaluated.
+ * @return true if the specified char is a valid hex value, false otherwise.
+ */
+ public static boolean isHexChar(char c) {
+ if (Character.isLowerCase(c)) {
+ c = Character.toUpperCase(c);
+ }
+
+ return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F');
+ }
+}
diff --git a/src/com/android/se/internal/Util.java b/src/com/android/se/internal/Util.java
new file mode 100755
index 0000000..8e9cfe3
--- /dev/null
+++ b/src/com/android/se/internal/Util.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+/*
+ * Copyright (c) 2017, The Linux Foundation.
+ */
+
+/*
+ * Contributed by: Giesecke & Devrient GmbH.
+ */
+
+package com.android.se.internal;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+
+import java.security.AccessControlException;
+
+/** Util class for byte[] operations */
+public class Util {
+
+ public static final byte END = -1;
+
+ /** Returns a new array containing both the arrays appended */
+ public static byte[] mergeBytes(byte[] array1, byte[] array2) {
+ byte[] data = new byte[array1.length + array2.length];
+ System.arraycopy(array1, 0, data, 0, array1.length);
+ System.arraycopy(array2, 0, data, array1.length, array2.length);
+ return data;
+ }
+
+ /** Extracts the required bytes from the array */
+ public static byte[] getMid(byte[] array, int start, int length) {
+ byte[] data = new byte[length];
+ System.arraycopy(array, start, data, 0, length);
+ return data;
+ }
+
+ /**
+ * 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.
+ */
+ public 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.
+ */
+ public static String createMessage(String commandName, int sw) {
+ StringBuilder message = new StringBuilder();
+ 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.
+ */
+ public static String createMessage(String commandName, String message) {
+ if (commandName == null) {
+ return message;
+ }
+ return commandName + " " + message;
+ }
+
+ /**
+ * Get package name from the user id.
+ *
+ * <p>This shall fix the problem the issue that process name != package name due to
+ * anndroid:process attribute in manifest file.
+ *
+ * <p>But this call is not really secure either since a uid can be shared between one and more
+ * apks
+ *
+ * @return The first package name associated with this uid.
+ */
+ public static String getPackageNameFromCallingUid(Context context, int uid) {
+ PackageManager packageManager = context.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");
+ }
+
+ /**
+ * 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
+ */
+ public static 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;
+ }
+
+ /**
+ * Clear the channel number.
+ *
+ * @return the cla without channel number
+ */
+ public static 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);
+ }
+ }
+
+ /**
+ * 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]
+ */
+ public static 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;
+ }
+ }
+}
diff --git a/src/com/android/se/security/AccessControlEnforcer.java b/src/com/android/se/security/AccessControlEnforcer.java
new file mode 100755
index 0000000..da41956
--- /dev/null
+++ b/src/com/android/se/security/AccessControlEnforcer.java
@@ -0,0 +1,469 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+/*
+ * Copyright (c) 2014-2017, The Linux Foundation.
+ */
+
+/*
+ * 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 com.android.se.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 com.android.se.Channel;
+import com.android.se.SecureElementService;
+import com.android.se.Terminal;
+import com.android.se.security.ChannelAccess.ACCESS;
+import com.android.se.security.ara.AraController;
+import com.android.se.security.arf.ArfController;
+
+import java.io.ByteArrayInputStream;
+import java.io.PrintWriter;
+import java.security.AccessControlException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+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;
+
+/** Reads and Maintains the ARF and ARA access control for a particular Secure Element */
+public class AccessControlEnforcer {
+
+ private final String mTag = "SecureElement-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 Terminal mTerminal = null;
+ private ChannelAccess mInitialChannelAccess = new ChannelAccess();
+ private boolean mFullAccess = false;
+
+ public AccessControlEnforcer(Terminal terminal) {
+
+ mTerminal = terminal;
+ mAccessRuleCache = new AccessRuleCache();
+ }
+
+ public static byte[] getDefaultAccessControlAid() {
+ return AraController.getAraMAid();
+ }
+
+ private static Certificate decodeCertificate(byte[] certData) throws CertificateException {
+ CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
+ X509Certificate cert =
+ (X509Certificate) certFactory.generateCertificate(
+ new ByteArrayInputStream(certData));
+ return cert;
+ }
+
+ /** Returns the Hash of the Application */
+ public static byte[] getAppCertHash(Certificate appCert) throws CertificateEncodingException {
+ MessageDigest md = null;
+ try {
+ md = MessageDigest.getInstance("SHA");
+ } catch (NoSuchAlgorithmException e) {
+ throw new AccessControlException("Exception getting SHA for the signature");
+ }
+ if (md == null) {
+ throw new AccessControlException("Hash can not be computed");
+ }
+ return md.digest(appCert.getEncoded());
+ }
+
+ public PackageManager getPackageManager() {
+ return mPackageManager;
+ }
+
+ public void setPackageManager(PackageManager packageManager) {
+ mPackageManager = packageManager;
+ }
+
+ public Terminal getTerminal() {
+ return mTerminal;
+ }
+
+ public AccessRuleCache getAccessRuleCache() {
+ return mAccessRuleCache;
+ }
+
+ /** Resets the Access Control for the Secure Element */
+ public synchronized void reset() {
+ // Destroy any previous Controler
+ // in order to reset the ACE
+ Log.i(mTag, "Reset the ACE for terminal:" + mTerminal.getName());
+ mAraController = null;
+ mArfController = null;
+ }
+
+ /** Initializes the Access Control for the Secure Element */
+ public synchronized boolean initialize(boolean loadAtStartup) {
+ 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(SecureElementService.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(mAccessRuleCache, mTerminal);
+ }
+
+ if (mUseAra && mAraController != null) {
+ try {
+ mAraController.initialize();
+ Log.i(mTag, "ARA applet is used for:" + mTerminal.getName());
+ // disable other access methods
+ mUseArf = false;
+ mFullAccess = false;
+ } catch (Exception e) {
+ // ARA cannot be used since we got an exception during initialization
+ mUseAra = false;
+ denyMsg = e.getLocalizedMessage();
+ /* 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.
+ *
+ * Or if eSE doesn't have ARA applet then full access
+ * should be permitted.
+ */
+ if (mTerminal.getName().startsWith(SecureElementService.UICC_TERMINAL)
+ || e instanceof UnsupportedOperationException) {
+ Log.i(mTag, "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.
+ mUseArf = false;
+ mFullAccess = false;
+ status = false;
+ Log.i(mTag, "Problem accessing ARA, Access DENIED "
+ + e.getLocalizedMessage());
+ }
+ }
+ }
+
+ // 2 - Let's try to use ARF since ARA cannot be used
+ if (mUseArf && !mTerminal.getName().startsWith(SecureElementService.UICC_TERMINAL)) {
+ Log.i(mTag, "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(mAccessRuleCache, mTerminal);
+ }
+
+ if (mUseArf && mArfController != null) {
+ try {
+ mArfController.initialize();
+ // disable other access methods
+ Log.i(mTag, "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(mTag, e.getMessage());
+ }
+ }
+
+ /* 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(mTag, "Deny any access to:" + mTerminal.getName());
+ }
+
+ mRulesRead = status;
+ return status;
+ }
+
+ /** Check if the Channel has permission for the given APDU */
+ public synchronized void checkCommand(Channel channel, byte[] command) {
+ ChannelAccess ca = channel.getChannelAccess();
+ if (ca == null) {
+ throw new AccessControlException(mTag + "Channel access not set");
+ }
+ String reason = ca.getReason();
+ if (reason.length() == 0) {
+ reason = "Command not allowed!";
+ }
+ if (ca.getAccess() != ACCESS.ALLOWED) {
+ throw new AccessControlException(mTag + reason);
+ }
+ if (ca.isUseApduFilter()) {
+ ApduFilter[] accessConditions = ca.getApduFilter();
+ if (accessConditions == null || accessConditions.length == 0) {
+ throw new AccessControlException(mTag + "Access Rule not available:"
+ + reason);
+ }
+ for (ApduFilter ac : accessConditions) {
+ if (CommandApdu.compareHeaders(command, ac.getMask(), ac.getApdu())) {
+ return;
+ }
+ }
+ throw new AccessControlException(mTag + "Access Rule does not match: "
+ + reason);
+ }
+ if (ca.getApduAccess() == ChannelAccess.ACCESS.ALLOWED) {
+ return;
+ } else {
+ throw new AccessControlException(mTag + "APDU access NOT allowed");
+ }
+ }
+
+ /** Sets up the Channel Access for the given Package */
+ public ChannelAccess setUpChannelAccess(
+ byte[] aid, String packageName, boolean checkRefreshTag) {
+ ChannelAccess channelAccess = null;
+ // check result of channel access during initialization procedure
+ if (mInitialChannelAccess.getAccess() == ChannelAccess.ACCESS.DENIED) {
+ throw new AccessControlException(
+ mTag + "access denied: " + mInitialChannelAccess.getReason());
+ }
+ // this is the new GP Access Control Enforcer implementation
+ if (mUseAra || mUseArf) {
+ channelAccess = internal_setUpChannelAccess(aid, packageName,
+ checkRefreshTag);
+ }
+ if (channelAccess == null || (channelAccess.getApduAccess() != ChannelAccess.ACCESS.ALLOWED
+ && !channelAccess.isUseApduFilter())) {
+ if (mFullAccess) {
+ // 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(mTag + "no APDU access allowed!");
+ }
+ }
+ channelAccess.setPackageName(packageName);
+ return channelAccess.clone();
+ }
+
+ private synchronized ChannelAccess internal_setUpChannelAccess(byte[] aid,
+ String packageName, boolean checkRefreshTag) {
+ if (packageName == null || packageName.isEmpty()) {
+ throw new AccessControlException("package names must be specified");
+ }
+ 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.");
+ }
+ if (checkRefreshTag) {
+ updateAccessRuleIfNeed();
+ }
+ return getAccessRule(aid, appCerts);
+ } catch (Throwable exp) {
+ throw new AccessControlException(exp.getMessage());
+ }
+ }
+
+ /** Fetches the Access Rules for the given application and AID pair */
+ public ChannelAccess getAccessRule(
+ byte[] aid, Certificate[] appCerts)
+ throws AccessControlException, 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.
+ */
+ private 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()]);
+ }
+
+ /** Returns true if the given application is allowed to recieve NFC Events */
+ public synchronized boolean[] isNfcEventAllowed(byte[] aid,
+ String[] packageNames, boolean checkRefreshTag) {
+ if (mUseAra || mUseArf) {
+ return internal_isNfcEventAllowed(aid, packageNames, checkRefreshTag);
+ } else {
+ // if ARA and ARF is not available and
+ // - terminal DOES NOT belong to a UICC -> mFullAccess is true
+ // - terminal belongs to a UICC -> mFullAccess is false
+ boolean[] ret = new boolean[packageNames.length];
+ for (int i = 0; i < ret.length; i++) {
+ ret[i] = mFullAccess;
+ }
+ return ret;
+ }
+ }
+
+ private synchronized boolean[] internal_isNfcEventAllowed(byte[] aid,
+ String[] packageNames, boolean checkRefreshTag) {
+ if (checkRefreshTag) {
+ updateAccessRuleIfNeed();
+ }
+
+ int i = 0;
+ boolean[] nfcEventFlags = new boolean[packageNames.length];
+ for (String packageName : packageNames) {
+ // estimate SHA-1 hash value of the device application's certificate.
+ try {
+ Certificate[] appCerts = getAPPCerts(packageName);
+ // APP certificates must be available => otherwise Exception
+ if (appCerts == null || appCerts.length == 0) {
+ nfcEventFlags[i] = false;
+ } else {
+ ChannelAccess channelAccess = getAccessRule(aid, appCerts);
+ nfcEventFlags[i] =
+ (channelAccess.getNFCEventAccess() == ChannelAccess.ACCESS.ALLOWED);
+ }
+ } catch (Exception e) {
+ Log.w(mTag, " Access Rules for NFC: " + e.getLocalizedMessage());
+ nfcEventFlags[i] = false;
+ }
+ i++;
+ }
+ return nfcEventFlags;
+ }
+
+ private void updateAccessRuleIfNeed() {
+ if (mUseAra && mAraController != null) {
+ try {
+ mAraController.initialize();
+ mUseArf = false;
+ mFullAccess = false;
+ } catch (Exception e) {
+ throw new AccessControlException("No ARA applet found in " + mTerminal.getName());
+ }
+ } else if (mUseArf && mArfController != null) {
+ try {
+ mArfController.initialize();
+ } catch (Exception e) {
+ Log.e(mTag, e.getMessage());
+ }
+ }
+ }
+
+ /** Debug information to be used by dumpsys */
+ public void dump(PrintWriter writer) {
+ writer.println(mTag + ":");
+
+ writer.println("mUseArf: " + mUseArf);
+ writer.println("mUseAra: " + mUseAra);
+ writer.println("mInitialChannelAccess:");
+ writer.println(mInitialChannelAccess.toString());
+ writer.println();
+
+ /* Dump the access rule cache */
+ if (mAccessRuleCache != null) mAccessRuleCache.dump(writer);
+ }
+
+ 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(
+ mTag,
+ "Allowed ACE mode: ara=" + mUseAra + " arf=" + mUseArf + " fullaccess="
+ + mFullAccess);
+ }
+}
diff --git a/src/com/android/se/security/AccessRuleCache.java b/src/com/android/se/security/AccessRuleCache.java
new file mode 100644
index 0000000..5ae9ee8
--- /dev/null
+++ b/src/com/android/se/security/AccessRuleCache.java
@@ -0,0 +1,497 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+/*
+ * Copyright (c) 2015-2017, The Linux Foundation.
+ */
+
+/*
+ * 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 com.android.se.security;
+
+import android.os.Build;
+import android.util.Log;
+
+import com.android.se.security.gpac.AID_REF_DO;
+import com.android.se.security.gpac.AR_DO;
+import com.android.se.security.gpac.Hash_REF_DO;
+import com.android.se.security.gpac.REF_DO;
+
+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;
+
+/** Stores all the access rules from the Secure Element */
+public class AccessRuleCache {
+ private final boolean mDBG = Build.IS_DEBUGGABLE;
+ private final String mTag = "SecureElement-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>();
+
+ private static AID_REF_DO getAidRefDo(byte[] aid) {
+ byte[] defaultAid = new byte[]{0x00, 0x00, 0x00, 0x00, 0x00};
+ if (aid == null || Arrays.equals(aid, defaultAid)) {
+ return new AID_REF_DO(AID_REF_DO.TAG_DEFAULT_APPLICATION);
+ } else {
+ return new AID_REF_DO(AID_REF_DO.TAG, aid);
+ }
+ }
+
+ private static ChannelAccess mapArDo2ChannelAccess(AR_DO arDo) {
+ ChannelAccess channelAccess = new ChannelAccess();
+
+ // check apdu access allowance
+ if (arDo.getApduArDo() != null) {
+ // first if there is a rule for access, reset the general deny flag.
+ channelAccess.setAccess(ChannelAccess.ACCESS.ALLOWED, "");
+ channelAccess.setUseApduFilter(false);
+
+ if (arDo.getApduArDo().isApduAllowed()) {
+ // check the apdu filter
+ ArrayList<byte[]> apduHeaders = arDo.getApduArDo().getApduHeaderList();
+ ArrayList<byte[]> filterMasks = arDo.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 (arDo.getNfcArDo() != null) {
+ channelAccess.setNFCEventAccess(
+ arDo.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;
+ }
+
+ /** Clears access rule cache and refresh tag. */
+ public void reset() {
+ mRefreshTag = null;
+ mRuleCache.clear();
+ }
+
+ /** Clears access rule cache only. */
+ public void clearCache() {
+ mRuleCache.clear();
+ }
+
+ /** Adds the Rule to the Cache */
+ public void putWithMerge(REF_DO refDo, AR_DO arDo) {
+ ChannelAccess channelAccess = mapArDo2ChannelAccess(arDo);
+ putWithMerge(refDo, channelAccess);
+ }
+
+ /** Adds the Rule to the Cache */
+ public void putWithMerge(REF_DO refDo, ChannelAccess channelAccess) {
+ if (mRuleCache.containsKey(refDo)) {
+ ChannelAccess ca = mRuleCache.get(refDo);
+
+ // if new ac condition is more restrictive then use their settings
+
+ if ((channelAccess.getAccess() == ChannelAccess.ACCESS.DENIED)
+ || (ca.getAccess() == ChannelAccess.ACCESS.DENIED)) {
+ ca.setAccess(ChannelAccess.ACCESS.DENIED, channelAccess.getReason());
+ } else if ((channelAccess.getAccess() == ChannelAccess.ACCESS.UNDEFINED)
+ && (ca.getAccess() != ChannelAccess.ACCESS.UNDEFINED)) {
+ ca.setAccess(ca.getAccess(), ca.getReason());
+ } else if ((channelAccess.getAccess() != ChannelAccess.ACCESS.UNDEFINED)
+ && (ca.getAccess() == ChannelAccess.ACCESS.UNDEFINED)) {
+ ca.setAccess(channelAccess.getAccess(), channelAccess.getReason());
+ } else {
+ ca.setAccess(ChannelAccess.ACCESS.ALLOWED, ca.getReason());
+ }
+
+ // 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
+ // DENIED DENIED DENIED
+
+ if ((channelAccess.getNFCEventAccess() == ChannelAccess.ACCESS.DENIED)
+ || (ca.getNFCEventAccess() == ChannelAccess.ACCESS.DENIED)) {
+ ca.setNFCEventAccess(ChannelAccess.ACCESS.DENIED);
+ } else if ((channelAccess.getNFCEventAccess() == ChannelAccess.ACCESS.UNDEFINED)
+ && (ca.getNFCEventAccess() != ChannelAccess.ACCESS.UNDEFINED)) {
+ ca.setNFCEventAccess(ca.getNFCEventAccess());
+ } else if ((channelAccess.getNFCEventAccess() != ChannelAccess.ACCESS.UNDEFINED)
+ && (ca.getNFCEventAccess() == ChannelAccess.ACCESS.UNDEFINED)) {
+ ca.setNFCEventAccess(channelAccess.getNFCEventAccess());
+ } else {
+ ca.setNFCEventAccess(ChannelAccess.ACCESS.ALLOWED);
+ }
+ // 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.DENIED)) {
+ ca.setApduAccess(ChannelAccess.ACCESS.DENIED);
+ } else if ((channelAccess.getApduAccess() == ChannelAccess.ACCESS.UNDEFINED)
+ && (ca.getApduAccess() != ChannelAccess.ACCESS.UNDEFINED)) {
+ ca.setApduAccess(ca.getApduAccess());
+ } else if ((channelAccess.getApduAccess() == ChannelAccess.ACCESS.UNDEFINED)
+ && (ca.getApduAccess() == ChannelAccess.ACCESS.UNDEFINED)
+ && !channelAccess.isUseApduFilter()) {
+ ca.setApduAccess(ChannelAccess.ACCESS.DENIED);
+ } else if ((channelAccess.getApduAccess() != ChannelAccess.ACCESS.UNDEFINED)
+ && (ca.getApduAccess() == ChannelAccess.ACCESS.UNDEFINED)) {
+ ca.setApduAccess(channelAccess.getApduAccess());
+ } else {
+ ca.setApduAccess(ChannelAccess.ACCESS.ALLOWED);
+ }
+
+ // put APDU filter together if resulting APDU access is allowed.
+ if ((ca.getApduAccess() == ChannelAccess.ACCESS.ALLOWED)
+ || (ca.getApduAccess() == ChannelAccess.ACCESS.UNDEFINED)) {
+ Log.i(mTag, "Merged Access Rule: APDU filter together");
+ 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);
+ }
+ if (mDBG) {
+ Log.i(mTag, "Merged Access Rule: " + refDo.toString() + ", " + ca.toString());
+ }
+ return;
+ }
+ if (mDBG) {
+ Log.i(mTag, "Add Access Rule: " + refDo.toString() + ", " + channelAccess.toString());
+ }
+ mRuleCache.put(refDo, channelAccess);
+ }
+
+ /** Find Access Rule for the given AID and Application */
+ 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.
+
+
+ /* Search Rule A ( Certificate(s); AID ) */
+ AID_REF_DO aid_ref_do = getAidRefDo(aid);
+ REF_DO ref_do;
+ Hash_REF_DO hash_ref_do;
+ 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)) {
+ // let's take care about the undefined rules, according to the GP specification:
+ ChannelAccess ca = mRuleCache.get(ref_do);
+ if (ca.getApduAccess() == ChannelAccess.ACCESS.UNDEFINED) {
+ ca.setApduAccess(ChannelAccess.ACCESS.ALLOWED);
+ }
+ if ((ca.getNFCEventAccess() == ChannelAccess.ACCESS.UNDEFINED)
+ && (ca.getApduAccess() != ChannelAccess.ACCESS.UNDEFINED)) {
+ ca.setNFCEventAccess(ca.getApduAccess());
+ }
+ if (mDBG) {
+ Log.i(mTag, "findAccessRule() " + ref_do.toString() + ", "
+ + mRuleCache.get(ref_do).toString());
+ }
+ 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) {
+ if (mDBG) {
+ Log.i(mTag, "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)) {
+ if (mDBG) {
+ Log.i(mTag, "findAccessRule() " + ref_do.toString() + ", "
+ + mRuleCache.get(ref_do).toString());
+ }
+ 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)) {
+ // let's take care about the undefined rules, according to the GP specification:
+ ChannelAccess ca = mRuleCache.get(ref_do);
+ if (ca.getApduAccess() == ChannelAccess.ACCESS.UNDEFINED) {
+ ca.setApduAccess(ChannelAccess.ACCESS.ALLOWED);
+ }
+ if ((ca.getNFCEventAccess() == ChannelAccess.ACCESS.UNDEFINED)
+ && (ca.getApduAccess() != ChannelAccess.ACCESS.UNDEFINED)) {
+ ca.setNFCEventAccess(ca.getApduAccess());
+ }
+ if (mDBG) {
+ Log.i(mTag, "findAccessRule() " + ref_do.toString() + ", "
+ + mRuleCache.get(ref_do).toString());
+ }
+ 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 (searchForRulesWithAllAidButOtherHash() != null) {
+ if (mDBG) {
+ Log.i(mTag, "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)) {
+ if (mDBG) {
+ Log.i(mTag, "findAccessRule() " + ref_do.toString() + ", "
+ + mRuleCache.get(ref_do).toString());
+ }
+ return mRuleCache.get(ref_do);
+ }
+
+ if (mDBG) Log.i(mTag, "findAccessRule() not found");
+ 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 the wanted AID but with another specific
+ * Hash value.
+ */
+ private REF_DO searchForRulesWithSpecificAidButOtherHash(AID_REF_DO aidRefDo) {
+
+ // AID has to be specific
+ if (aidRefDo == null) {
+ return null;
+ }
+ // C0 00 is specific -> default AID
+ // 4F 00 is NOT specific -> all AIDs
+ if (aidRefDo.getTag() == AID_REF_DO.TAG || aidRefDo.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 (aidRefDo.equals(ref_do.getAidDo())) {
+ if (ref_do.getHashDo() != 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().length > 0) {
+ // return ref_do if
+ // a HASH value is available and has a length > 0 (SHA1_LEN)
+ return ref_do;
+ }
+ }
+ }
+ return null;
+ }
+
+ /** Check if the given Refresh Tag is equal to the last known */
+ public boolean isRefreshTagEqual(byte[] refreshTag) {
+ if (refreshTag == null || mRefreshTag == null) return false;
+
+ return Arrays.equals(refreshTag, mRefreshTag);
+ }
+
+ public byte[] getRefreshTag() {
+ return mRefreshTag;
+ }
+
+ /** Sets the Refresh Tag */
+ public void setRefreshTag(byte[] refreshTag) {
+ mRefreshTag = refreshTag;
+ }
+
+ /** Debug information to be used by dumpsys */
+ public void dump(PrintWriter writer) {
+ writer.println(mTag + ":");
+
+ /* Dump the refresh tag */
+ writer.print("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("Rules:");
+ int i = 0;
+ for (Map.Entry<REF_DO, ChannelAccess> entry : mRuleCache.entrySet()) {
+ i++;
+ writer.print("rule " + i + ": ");
+ writer.println(entry.getKey().toString() + " -> " + entry.getValue().toString());
+ }
+ writer.println();
+ }
+}
diff --git a/src/com/android/se/security/ApduFilter.java b/src/com/android/se/security/ApduFilter.java
new file mode 100755
index 0000000..f7d09be
--- /dev/null
+++ b/src/com/android/se/security/ApduFilter.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+/*
+ * Copyright (c) 2015-2017, The Linux Foundation.
+ */
+
+/*
+ * 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 com.android.se.security;
+
+import com.android.se.internal.ByteArrayConverter;
+import com.android.se.internal.Util;
+
+/** Represents the masked APDU for the Access Rules */
+public class ApduFilter {
+
+ public static final int LENGTH = 8;
+ protected byte[] mApdu;
+ protected byte[] mMask;
+
+ 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(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);
+ }
+
+ /** Clones the APDU Filter */
+ public ApduFilter clone() {
+ ApduFilter apduFilter = new ApduFilter();
+ apduFilter.setApdu(mApdu.clone());
+ apduFilter.setMask(mMask.clone());
+ return apduFilter;
+ }
+
+ public byte[] getApdu() {
+ return mApdu;
+ }
+
+ /** Sets the APDU */
+ 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;
+ }
+
+ /** Sets the Mask */
+ public void setMask(byte[] mask) {
+ if (mask.length != 4) {
+ throw new IllegalArgumentException("mask length must be 4 bytes");
+ }
+ mMask = mask;
+ }
+
+ /** Converts the filter into bytes */
+ public byte[] toBytes() {
+ return Util.mergeBytes(mApdu, mMask);
+ }
+
+ @Override
+ public String toString() {
+ return "APDU Filter [apdu="
+ + ByteArrayConverter.byteArrayToHexString(mApdu)
+ + ", mask="
+ + ByteArrayConverter.byteArrayToHexString(mMask)
+ + "]";
+ }
+
+ public int getLength() {
+ return 8;
+ }
+}
diff --git a/src/com/android/se/security/ChannelAccess.java b/src/com/android/se/security/ChannelAccess.java
new file mode 100755
index 0000000..3dcb966
--- /dev/null
+++ b/src/com/android/se/security/ChannelAccess.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+/*
+ * Copyright (c) 2017, The Linux Foundation.
+ */
+
+/*
+ * 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 com.android.se.security;
+
+/** Class for Storing the APDU and NFC Access for a particular Channel */
+public class ChannelAccess {
+
+ private final String mTag = "SecureElement-ChannelAccess";
+ private String mPackageName = "";
+ private ACCESS mAccess = ACCESS.UNDEFINED;
+ private ACCESS mApduAccess = ACCESS.UNDEFINED;
+ private boolean mUseApduFilter = false;
+ private int mCallingPid = 0;
+ private String mReason = "no access by default";
+ private ACCESS mNFCEventAccess = ACCESS.UNDEFINED;
+ private ApduFilter[] mApduFilter = null;
+
+ /** Clones the ChannelAccess */
+ public ChannelAccess clone() {
+ ChannelAccess ca = new ChannelAccess();
+ ca.setAccess(mAccess, mReason);
+ ca.setPackageName(mPackageName);
+ ca.setApduAccess(mApduAccess);
+ ca.setCallingPid(mCallingPid);
+ ca.setNFCEventAccess(mNFCEventAccess);
+ ca.setUseApduFilter(mUseApduFilter);
+ if (mApduFilter != null) {
+ ApduFilter[] apduFilter = new ApduFilter[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) {
+ mPackageName = name;
+ }
+
+ public ACCESS getApduAccess() {
+ return mApduAccess;
+ }
+
+ public void setApduAccess(ACCESS apduAccess) {
+ mApduAccess = apduAccess;
+ }
+
+ public ACCESS getAccess() {
+ return mAccess;
+ }
+
+ /** Sets the Access for the ChannelAccess */
+ public void setAccess(ACCESS access, String reason) {
+ mAccess = access;
+ mReason = reason;
+ }
+
+ public boolean isUseApduFilter() {
+ return mUseApduFilter;
+ }
+
+ public void setUseApduFilter(boolean useApduFilter) {
+ mUseApduFilter = useApduFilter;
+ }
+
+ public int getCallingPid() {
+ return mCallingPid;
+ }
+
+ public void setCallingPid(int callingPid) {
+ mCallingPid = callingPid;
+ }
+
+ 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) {
+ 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();
+ }
+
+ public enum ACCESS {
+ ALLOWED,
+ DENIED,
+ UNDEFINED;
+ }
+}
diff --git a/src/com/android/se/security/CommandApdu.java b/src/com/android/se/security/CommandApdu.java
new file mode 100755
index 0000000..89fd8d8
--- /dev/null
+++ b/src/com/android/se/security/CommandApdu.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+/*
+ * Copyright (c) 2017, The Linux Foundation.
+ */
+
+/*
+ * 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 com.android.se.security;
+
+/** Represents the APDU command splitting all the individual fields */
+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;
+ }
+
+ /** Returns true if both the headers are same */
+ 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 int getP1() {
+ return mP1;
+ }
+
+ public void setP1(int p1) {
+ mP1 = p1;
+ }
+
+ public int getP2() {
+ return mP2;
+ }
+
+ public void setP2(int p2) {
+ mP2 = p2;
+ }
+
+ public int getLc() {
+ return mLc;
+ }
+
+ public byte[] getData() {
+ return mData;
+ }
+
+ /** Sets Data field of the APDU */
+ public void setData(byte[] data) {
+ mLc = data.length;
+ mData = data;
+ }
+
+ public int getLe() {
+ return mLe;
+ }
+
+ /** Sets the Le field of the command */
+ public void setLe(int le) {
+ mLe = le;
+ mLeUsed = true;
+ }
+
+ /** Returns the APDU in byte[] format */
+ 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;
+ }
+
+ /** Clones the APDU */
+ 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/com/android/se/security/ResponseApdu.java b/src/com/android/se/security/ResponseApdu.java
new file mode 100755
index 0000000..c0510d3
--- /dev/null
+++ b/src/com/android/se/security/ResponseApdu.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+/*
+ * Copyright (c) 2017, The Linux Foundation.
+ */
+
+/*
+ * 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 com.android.se.security;
+
+/** Represents the Response received from the Secure Element */
+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;
+ }
+
+ /** Returns true if the Status Words are equal */
+ public boolean isStatus(int sw1sw2) {
+ return (getSW1SW2() == sw1sw2);
+ }
+}
diff --git a/src/com/android/se/security/ara/AccessRuleApplet.java b/src/com/android/se/security/ara/AccessRuleApplet.java
new file mode 100755
index 0000000..c5bc560
--- /dev/null
+++ b/src/com/android/se/security/ara/AccessRuleApplet.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+/*
+ * Copyright (c) 2015-2017, The Linux Foundation.
+ */
+
+/*
+ * 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 com.android.se.security.ara;
+
+import android.os.RemoteException;
+
+import com.android.se.Channel;
+import com.android.se.security.CommandApdu;
+import com.android.se.security.ResponseApdu;
+import com.android.se.security.gpac.BerTlv;
+import com.android.se.security.gpac.ParserException;
+import com.android.se.security.gpac.Response_DO_Factory;
+import com.android.se.security.gpac.Response_RefreshTag_DO;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.security.AccessControlException;
+
+/** Reads the ARA Rules from the Secure Element */
+public class AccessRuleApplet {
+ private static final int MAX_LEN = 0x00;
+ private static final CommandApdu GET_ALL_CMD = new CommandApdu(0x80, 0xCA, 0xFF, 0x40, MAX_LEN);
+ // MAX_LEN should be adapted by OEM, this is a defensive value since some devices/modems have
+ // problems with Le=0x00 or 0xFF.
+ private static final CommandApdu GET_NEXT_CMD = new CommandApdu(0x80, 0xCA, 0xFF, 0x60,
+ MAX_LEN);
+ private static final CommandApdu GET_REFRESH_TAG = new CommandApdu(0x80, 0xCA, 0xDF, 0x20,
+ MAX_LEN);
+ private final String mTag = "SecureElement-AccessRuleApplet";
+ private Channel mChannel = null;
+
+ public AccessRuleApplet(Channel channel) {
+ mChannel = channel;
+ }
+
+ /** Reads all the access rules from the secure element */
+ public byte[] readAllAccessRules() throws AccessControlException {
+ ByteArrayOutputStream stream = new ByteArrayOutputStream();
+ int overallLen = 0;
+
+ // send GET DATA (specific)
+ CommandApdu apdu = (CommandApdu) GET_ALL_CMD.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) GET_NEXT_CMD.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());
+ }
+ }
+
+ /** Fetches the Refresh Tag from the Secure Element */
+ public byte[] readRefreshTag() throws AccessControlException {
+ CommandApdu apdu = (CommandApdu) GET_REFRESH_TAG.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) {
+ try {
+ byte[] response = mChannel.transmit(cmdApdu.toBytes());
+ return new ResponseApdu(response);
+ } catch (RemoteException e) {
+ return null;
+ }
+ }
+}
diff --git a/src/com/android/se/security/ara/AraController.java b/src/com/android/se/security/ara/AraController.java
new file mode 100755
index 0000000..680a66b
--- /dev/null
+++ b/src/com/android/se/security/ara/AraController.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+/*
+ * Copyright (c) 2015-2017, The Linux Foundation.
+ */
+
+/*
+ * 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 com.android.se.security.ara;
+
+import android.util.Log;
+
+import com.android.se.Channel;
+import com.android.se.Terminal;
+import com.android.se.security.AccessRuleCache;
+import com.android.se.security.ChannelAccess;
+import com.android.se.security.gpac.BerTlv;
+import com.android.se.security.gpac.ParserException;
+import com.android.se.security.gpac.REF_AR_DO;
+import com.android.se.security.gpac.Response_ALL_AR_DO;
+import com.android.se.security.gpac.Response_DO_Factory;
+
+import java.security.AccessControlException;
+import java.util.ArrayList;
+import java.util.Iterator;
+
+/** Reads and Maintains the ARA access for the Secure Element */
+public class AraController {
+
+ 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
+ };
+ private final String mTag = "SecureElement-AraController";
+ private AccessRuleCache mAccessRuleCache = null;
+ private Terminal mTerminal = null;
+ private AccessRuleApplet mApplet = null;
+
+ public AraController(AccessRuleCache cache, Terminal terminal) {
+ mAccessRuleCache = cache;
+ mTerminal = terminal;
+ }
+
+ public static byte[] getAraMAid() {
+ return ARA_M_AID;
+ }
+
+ /**
+ * Initialize the AraController, reads the refresh tag.
+ * and fetch the access rules
+ */
+ public synchronized void initialize() throws Exception {
+ Channel channel = mTerminal.openLogicalChannelWithoutChannelAccess(getAraMAid());
+ if (channel == null) {
+ throw new AccessControlException("could not open channel");
+ }
+
+ // set access conditions to access ARA-M.
+ ChannelAccess araChannelAccess = new ChannelAccess();
+ araChannelAccess.setAccess(ChannelAccess.ACCESS.ALLOWED, mTag);
+ araChannelAccess.setApduAccess(ChannelAccess.ACCESS.ALLOWED);
+ channel.setChannelAccess(araChannelAccess);
+
+ 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.i(mTag, "Refresh tag unchanged. Using access rules from cache.");
+ return;
+ }
+ Log.i(mTag, "Refresh tag has changed.");
+ // set new refresh tag and empty cache.
+ mAccessRuleCache.setRefreshTag(tag);
+ mAccessRuleCache.clearCache();
+
+ Log.i(mTag, "Read ARs from ARA");
+ readAllAccessRules();
+ } catch (Exception e) {
+ Log.i(mTag, "ARA error: " + e.getLocalizedMessage());
+ throw new AccessControlException(e.getLocalizedMessage());
+ } finally {
+ if (channel != null) {
+ channel.close();
+ }
+ }
+ }
+
+ private void readAllAccessRules() throws AccessControlException {
+ try {
+ byte[] data = mApplet.readAllAccessRules();
+ // no data returned, but no exception
+ // -> no rule.
+ if (data == null) {
+ return;
+ }
+
+ BerTlv tlv = Response_DO_Factory.createDO(data);
+ if (tlv == null || !(tlv instanceof Response_ALL_AR_DO)) {
+ throw new AccessControlException("No valid data object found");
+ }
+ ArrayList<REF_AR_DO> array = ((Response_ALL_AR_DO) tlv).getRefArDos();
+
+ if (array != null && array.size() != 0) {
+ Iterator<REF_AR_DO> iter = array.iterator();
+ while (iter.hasNext()) {
+ REF_AR_DO refArDo = iter.next();
+ mAccessRuleCache.putWithMerge(refArDo.getRefDo(), refArDo.getArDo());
+ }
+ }
+ } catch (ParserException e) {
+ throw new AccessControlException("Parsing Data Object Exception: "
+ + e.getMessage());
+ }
+ }
+}
diff --git a/src/com/android/se/security/arf/ASN1.java b/src/com/android/se/security/arf/ASN1.java
new file mode 100755
index 0000000..c8d6fd3
--- /dev/null
+++ b/src/com/android/se/security/arf/ASN1.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+/*
+ * Copyright (c) 2015-2017, The Linux Foundation.
+ */
+
+/*
+ * 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 com.android.se.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_Integer = 0x02;
+ public static final byte TAG_OctetString = 0x04;
+ public static final byte TAG_OID = 0x06;
+ public static final byte TAG_ContextSpecPrim0 = (byte) 0x80;
+ public static final byte TAG_ContextSpecPrim1 = (byte) 0x81;
+ public static final byte TAG_ContextSpecPrim2 = (byte) 0x82;
+ public static final byte TAG_ContextSpecPrim3 = (byte) 0x83;
+ public static final byte TAG_ContextSpecPrim4 = (byte) 0x84;
+ public static final byte TAG_ContextSpecPrim5 = (byte) 0x85;
+ public static final byte TAG_ContextSpecPrim6 = (byte) 0x86;
+ public static final byte TAG_ContextSpecPrim7 = (byte) 0x87;
+ public static final byte TAG_ContextSpecPrim8 = (byte) 0x88;
+ public static final byte TAG_PrivateKey = (byte) 0xA0;
+ public static final byte TAG_PublicKey = (byte) 0xA1;
+ public static final byte TAG_PublicKeyTrusted = (byte) 0xA2;
+ public static final byte TAG_SecretKey = (byte) 0xA3;
+ public static final byte TAG_Certificate = (byte) 0xA4;
+ public static final byte TAG_CertificateTrusted = (byte) 0xA5;
+ public static final byte TAG_CertificateUseful = (byte) 0xA6;
+ public static final byte TAG_DataObject = (byte) 0xA7;
+ public static final byte TAG_AuthenticateObject = (byte) 0xA8;
+
+ // 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;
+}
diff --git a/src/com/android/se/security/arf/ArfController.java b/src/com/android/se/security/arf/ArfController.java
new file mode 100755
index 0000000..0257b5d
--- /dev/null
+++ b/src/com/android/se/security/arf/ArfController.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+/*
+ * Copyright (c) 2014-2017, The Linux Foundation.
+ */
+
+/*
+ * 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 com.android.se.security.arf;
+
+import com.android.se.Terminal;
+import com.android.se.security.AccessRuleCache;
+import com.android.se.security.arf.pkcs15.PKCS15Handler;
+
+/** Initializes and maintains the ARF access rules of a Secure Element */
+public class ArfController {
+
+ private PKCS15Handler mPkcs15Handler = null;
+ private SecureElement mSecureElement = null;
+ private AccessRuleCache mAccessRuleCache = null;
+ private Terminal mTerminal = null;
+
+ public ArfController(AccessRuleCache cache, Terminal terminal) {
+ mAccessRuleCache = cache;
+ mTerminal = terminal;
+ }
+
+ /** Initializes the ARF Rules for the Secure Element */
+ public synchronized boolean initialize() {
+ 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/com/android/se/security/arf/DERParser.java b/src/com/android/se/security/arf/DERParser.java
new file mode 100755
index 0000000..808ff69
--- /dev/null
+++ b/src/com/android/se/security/arf/DERParser.java
@@ -0,0 +1,249 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+/*
+ * Copyright (c) 2015-2017, The Linux Foundation.
+ */
+
+/*
+ * 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 com.android.se.security.arf;
+
+import android.util.Log;
+
+import com.android.se.security.arf.pkcs15.PKCS15Exception;
+
+import java.util.Arrays;
+
+/** Base class for parsing PKCS#15 files */
+public class DERParser {
+ public final String mTag = "SecureElement-DERParser";
+ // DER parameters
+ private byte[] mDERBuffer;
+ private short mDERSize, mDERIndex, mTLVDataSize = 0;
+
+ public DERParser(byte[] buffer) throws PKCS15Exception {
+ mDERBuffer = buffer;
+ mDERIndex = 0;
+ mDERSize = 0;
+ if (mDERBuffer == null) return;
+ mDERSize = (short) mDERBuffer.length;
+ mTLVDataSize = mDERSize;
+
+ if (mDERSize == 0) return;
+ // Remove padding
+ if (mDERBuffer[mDERIndex] == ASN1.TAG_Padding) {
+ mTLVDataSize = 0;
+ while (++mDERIndex < mDERSize) {
+ if (mDERBuffer[mDERIndex] != ASN1.TAG_Padding) {
+ throw new PKCS15Exception("[Parser] Incorrect file format");
+ }
+ }
+ }
+ }
+
+ /**
+ * 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++];
+ }
+
+ /**
+ * 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 {
+ byte typeInBuffer = getTLVType();
+ if (typeInBuffer == type) {
+ mTLVDataSize = getTLVSize();
+ } else {
+ Log.e(mTag, "parseTLV expected: " + type + " got:" + typeInBuffer);
+ Log.e(mTag, "parseTLV mDERIndex: " + mDERIndex + " mDERSize:" + mDERSize);
+ 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.i(mTag, "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();
+ }
+}
diff --git a/src/com/android/se/security/arf/PKCS15/EF.java b/src/com/android/se/security/arf/PKCS15/EF.java
new file mode 100755
index 0000000..bd929ea
--- /dev/null
+++ b/src/com/android/se/security/arf/PKCS15/EF.java
@@ -0,0 +1,277 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+/*
+ * Copyright (c) 2015-2017, The Linux Foundation.
+ */
+
+/*
+ * Contributed by: Giesecke & Devrient GmbH.
+ */
+
+package com.android.se.security.arf.pkcs15;
+
+import android.util.Log;
+
+import com.android.se.security.arf.ASN1;
+import com.android.se.security.arf.DERParser;
+import com.android.se.security.arf.SecureElement;
+import com.android.se.security.arf.SecureElementException;
+
+import java.util.Arrays;
+
+/** Base class for ARF Data Objects */
+public class EF {
+
+ public static final String TAG = "SecureElementService ACE ARF";
+
+ public static final int APDU_SUCCESS = 0x9000;
+ private static final int BUFFER_LEN = 253;
+
+ 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;
+ // Handle to "Secure Element" object
+ protected SecureElement mSEHandle = null;
+ // Selected file parameters
+ private short mFileType = UNKNOWN, mFileStructure = UNKNOWN, mFileNbRecords;
+ private int mFileID, mFileSize, mFileRecordSize;
+
+ public EF(SecureElement handle) {
+ mSEHandle = handle;
+ }
+
+ public int getFileId() {
+ return mFileID;
+ }
+
+ 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 length = path.length;
+
+ 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 index = 0; 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
+ int 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);
+
+ 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
+ }
+
+ 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.i(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/com/android/se/security/arf/PKCS15/EFACConditions.java b/src/com/android/se/security/arf/PKCS15/EFACConditions.java
new file mode 100755
index 0000000..8cb09e5
--- /dev/null
+++ b/src/com/android/se/security/arf/PKCS15/EFACConditions.java
@@ -0,0 +1,297 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+/*
+ * Copyright (c) 2015-2017, The Linux Foundation.
+ */
+
+/*
+ * 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 com.android.se.security.arf.pkcs15;
+
+import android.util.Log;
+
+import com.android.se.internal.Util;
+import com.android.se.security.ApduFilter;
+import com.android.se.security.ChannelAccess;
+import com.android.se.security.arf.ASN1;
+import com.android.se.security.arf.DERParser;
+import com.android.se.security.arf.SecureElement;
+import com.android.se.security.gpac.AID_REF_DO;
+import com.android.se.security.gpac.Hash_REF_DO;
+
+import java.util.Vector;
+
+/** 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 mAidRefDo = 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 aidRefDo) {
+ super(handle);
+ mAidRefDo = aidRefDo;
+ }
+
+ /**
+ * 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()) {
+ channelAccess.setAccess(ChannelAccess.ACCESS.DENIED, "Empty ACCondition");
+ channelAccess.setNFCEventAccess(ChannelAccess.ACCESS.DENIED);
+ channelAccess.setApduAccess(ChannelAccess.ACCESS.DENIED);
+ channelAccess.setUseApduFilter(false);
+ channelAccess.setApduFilter(null);
+ Log.i(TAG, "Empty ACCondition: Access Deny for all apps");
+
+ mSEHandle.putAccessRule(mAidRefDo, 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.UNDEFINED);
+ channelAccess.setUseApduFilter(false);
+
+ if (der.parseTLV(ASN1.TAG_Sequence) > 0) {
+ byte[] tempTLVData = der.getTLVData();
+ DERParser derRule = new DERParser(tempTLVData);
+
+ if (tempTLVData[0] == ASN1.TAG_OctetString) {
+ 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);
+ }
+ } else if (tempTLVData[0] == ASN1.TAG_Padding) {
+ throw new PKCS15Exception("Invalid hash found!");
+ } else {
+ Log.i(TAG, "No hash included");
+ // Let's put a null hash, to prioritize any more specific rule.
+ hash_ref_do = new Hash_REF_DO(null);
+ }
+
+ // 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)
+ if ((channelAccess.getNFCEventAccess() == ChannelAccess.ACCESS.UNDEFINED)
+ && (channelAccess.getApduAccess() != ChannelAccess.ACCESS.UNDEFINED)) {
+ channelAccess.setNFCEventAccess(channelAccess.getApduAccess());
+ }
+ }
+ // ----
+ mSEHandle.putAccessRule(mAidRefDo, hash_ref_do, channelAccess);
+ }
+ }
+
+ /**
+ * Stores a restricted list of certificate hashes
+ *
+ * @param path Path of the "EF_ACConditions" file
+ */
+ public void addRestrictedHashes(byte[] path) throws PKCS15Exception {
+ try {
+ Log.i(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) {
+ throw new PKCS15Exception(e.getMessage());
+ }
+ }
+
+ /**
+ * Stores a restricted list of certificate hashes
+ */
+ public void addRestrictedHashesFromData(byte[] data) throws PKCS15Exception {
+ try {
+ Log.i(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) {
+ throw new PKCS15Exception(e.getMessage());
+ }
+ }
+
+ public byte[] getData() {
+ return mData;
+ }
+}
diff --git a/src/com/android/se/security/arf/PKCS15/EFACMain.java b/src/com/android/se/security/arf/PKCS15/EFACMain.java
new file mode 100755
index 0000000..2124333
--- /dev/null
+++ b/src/com/android/se/security/arf/PKCS15/EFACMain.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+/*
+ * Copyright (c) 2015-2017, The Linux Foundation.
+ */
+
+/*
+ * 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 com.android.se.security.arf.pkcs15;
+
+import android.util.Log;
+
+import com.android.se.internal.Util;
+import com.android.se.security.arf.ASN1;
+import com.android.se.security.arf.DERParser;
+import com.android.se.security.arf.SecureElement;
+import com.android.se.security.arf.SecureElementException;
+
+import java.util.Arrays;
+
+/** 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 REFRESHTAG_LEN = 8;
+
+ // "EF Access Control Main" path
+ private byte[] mACMainPath = null;
+
+ /**
+ * Constructor
+ *
+ * @param secureElement SE on which ISO7816 commands are applied
+ */
+ public EFACMain(SecureElement handle, byte[] path) {
+ super(handle);
+ mACMainPath = path;
+ }
+
+ /**
+ * 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) != REFRESHTAG_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
+ }
+
+ /**
+ * 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.i(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/com/android/se/security/arf/PKCS15/EFACRules.java b/src/com/android/se/security/arf/PKCS15/EFACRules.java
new file mode 100755
index 0000000..15ed4e0
--- /dev/null
+++ b/src/com/android/se/security/arf/PKCS15/EFACRules.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+/*
+ * Copyright (c) 2015-2017, The Linux Foundation.
+ */
+
+/*
+ * 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 com.android.se.security.arf.pkcs15;
+
+import android.util.Log;
+
+import com.android.se.internal.ByteArrayConverter;
+import com.android.se.internal.Util;
+import com.android.se.security.arf.ASN1;
+import com.android.se.security.arf.DERParser;
+import com.android.se.security.arf.SecureElement;
+import com.android.se.security.arf.SecureElementException;
+import com.android.se.security.gpac.AID_REF_DO;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/** 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[]>();
+
+ /**
+ * Constructor
+ *
+ * @param secureElement SE on which ISO7816 commands are applied
+ */
+ public EFACRules(SecureElement handle) {
+ super(handle);
+ }
+
+ /**
+ * 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 = ByteArrayConverter.byteArrayToHexString(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());
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Selects and Analyses EF_ACRules file
+ *
+ * @param path Path of the "EF_ACRules" file
+ */
+ public void analyseFile(byte[] path) throws PKCS15Exception, SecureElementException {
+
+ Log.i(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/com/android/se/security/arf/PKCS15/EFDIR.java b/src/com/android/se/security/arf/PKCS15/EFDIR.java
new file mode 100755
index 0000000..1e2fb18
--- /dev/null
+++ b/src/com/android/se/security/arf/PKCS15/EFDIR.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+/*
+ * Copyright (c) 2015-2017, The Linux Foundation.
+ */
+
+/*
+ * 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 com.android.se.security.arf.pkcs15;
+
+import android.util.Log;
+
+import com.android.se.security.arf.ASN1;
+import com.android.se.security.arf.DERParser;
+import com.android.se.security.arf.SecureElement;
+import com.android.se.security.arf.SecureElementException;
+
+import java.util.Arrays;
+
+/** 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};
+
+ /**
+ * Constructor
+ *
+ * @param secureElement SE on which ISO7816 commands are applied
+ */
+ public EFDIR(SecureElement handle) {
+ super(handle);
+ }
+
+ /**
+ * 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();
+ }
+
+ /**
+ * 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.i(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/com/android/se/security/arf/PKCS15/EFDODF.java b/src/com/android/se/security/arf/PKCS15/EFDODF.java
new file mode 100755
index 0000000..a8eb228
--- /dev/null
+++ b/src/com/android/se/security/arf/PKCS15/EFDODF.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+/*
+ * Copyright (c) 2015-2017, The Linux Foundation.
+ */
+
+/*
+ * 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 com.android.se.security.arf.pkcs15;
+
+import android.util.Log;
+
+import com.android.se.internal.Util;
+import com.android.se.security.arf.ASN1;
+import com.android.se.security.arf.DERParser;
+import com.android.se.security.arf.SecureElement;
+import com.android.se.security.arf.SecureElementException;
+
+/** 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";
+
+ /**
+ * Constructor
+ *
+ * @param secureElement SE on which ISO7816 commands are applied
+ */
+ public EFDODF(SecureElement handle) {
+ super(handle);
+ }
+
+ /**
+ * 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
+ }
+
+ /**
+ * 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.i(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/com/android/se/security/arf/PKCS15/EFODF.java b/src/com/android/se/security/arf/PKCS15/EFODF.java
new file mode 100755
index 0000000..e751908
--- /dev/null
+++ b/src/com/android/se/security/arf/PKCS15/EFODF.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+/*
+ * Copyright (c) 2015-2017, The Linux Foundation.
+ */
+
+/*
+ * 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 com.android.se.security.arf.pkcs15;
+
+import android.util.Log;
+
+import com.android.se.internal.ByteArrayConverter;
+import com.android.se.internal.Util;
+import com.android.se.security.arf.DERParser;
+import com.android.se.security.arf.SecureElement;
+import com.android.se.security.arf.SecureElementException;
+
+/** EF_ODF related features */
+public class EFODF extends EF {
+
+ // Standardized ID for EF_ODF file
+ public static final byte[] EFODF_PATH = {0x50, 0x31};
+ public final String mTag = "SecureElement-ARF-EFODF";
+ byte[] mCDFPath = null;
+
+ /**
+ * Constructor
+ *
+ * @param secureElement SE on which ISO7816 commands are applied
+ */
+ public EFODF(SecureElement handle) {
+ super(handle);
+ }
+
+ /**
+ * 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) 0xA5) { // CDF
+ mCDFPath = der.parsePathAttributes();
+ Log.i(
+ mTag,
+ "ODF content found mCDFPath :" + ByteArrayConverter.byteArrayToHexString(
+ mCDFPath));
+ } else {
+ der.skipTLVData();
+ }
+ }
+
+ DERParser der2 = new DERParser(buffer);
+ while (!der2.isEndofBuffer()) {
+ if (der2.parseTLV() == (byte) 0xA7) { // DODF
+ return der2.parsePathAttributes();
+ } else {
+ der2.skipTLVData();
+ }
+ }
+ return null;
+ }
+
+ public byte[] getCDFPath() {
+ return mCDFPath;
+ }
+
+ /**
+ * 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.i(mTag, "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) {
+ // let's give it another try..
+ try {
+ synchronized (this) {
+ wait(1000);
+ }
+ } catch (InterruptedException e) {
+ Log.e(mTag, "Interupted while waiting for EF_ODF : " + e);
+ }
+ if (selectFile(path) != APDU_SUCCESS) throw new PKCS15Exception("EF_ODF not found!!");
+ }
+ return decodeDER(readBinary(0, Util.END));
+ }
+}
diff --git a/src/com/android/se/security/arf/PKCS15/PKCS15Exception.java b/src/com/android/se/security/arf/PKCS15/PKCS15Exception.java
new file mode 100755
index 0000000..9059500
--- /dev/null
+++ b/src/com/android/se/security/arf/PKCS15/PKCS15Exception.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+/*
+ * Copyright (c) 2015-2017, The Linux Foundation.
+ */
+
+/*
+ * 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 com.android.se.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/com/android/se/security/arf/PKCS15/PKCS15Handler.java b/src/com/android/se/security/arf/PKCS15/PKCS15Handler.java
new file mode 100755
index 0000000..6bee6ea
--- /dev/null
+++ b/src/com/android/se/security/arf/PKCS15/PKCS15Handler.java
@@ -0,0 +1,237 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+/*
+ * Copyright (c) 2014-2017, The Linux Foundation.
+ */
+
+/*
+ * 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 com.android.se.security.arf.pkcs15;
+
+import android.util.Log;
+
+import com.android.se.Channel;
+import com.android.se.security.arf.SecureElement;
+import com.android.se.security.arf.SecureElementException;
+
+import java.security.AccessControlException;
+import java.security.cert.CertificateException;
+import java.util.MissingResourceException;
+
+/** Handles PKCS#15 topology */
+public class PKCS15Handler {
+
+ // 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};
+ public final String mTag = "SecureElement-PKCS15Handler";
+ // Handle to "Secure Element"
+ private SecureElement mSEHandle;
+ // "Secure Element" label
+ private String mSELabel = null;
+ // Handle to "Logical Channel" allocated by the SE
+ private Channel 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;
+ private boolean mACMFfound = true;
+
+ /**
+ * Constructor
+ *
+ * @param handle Handle to "Secure Element"
+ */
+ public PKCS15Handler(SecureElement handle) {
+ mSEHandle = handle;
+ }
+
+ /** Updates "Access Control Rules" */
+ private boolean updateACRules() throws Exception, PKCS15Exception, SecureElementException {
+ byte[] ACRulesPath = null;
+ if (!mACMFfound) {
+ mSEHandle.resetAccessRules();
+ mACMainPath = null;
+ if (mArfChannel != null) mSEHandle.closeArfChannel();
+ this.initACEntryPoint();
+ }
+ try {
+ ACRulesPath = mACMainObject.analyseFile();
+ mACMFfound = true;
+ } catch (Exception e) {
+ Log.i(mTag, "ACMF Not found !");
+ mACMainObject = null;
+ mSEHandle.resetAccessRules();
+ mACMFfound = false;
+ throw e;
+ }
+ // Check if rules must be updated
+ if (ACRulesPath != null) {
+ Log.i(mTag, "Access Rules needs to be updated...");
+ if (mACRulesObject == null) {
+ mACRulesObject = new EFACRules(mSEHandle);
+ }
+ mSEHandle.clearAccessRuleCache();
+ mACMainPath = null;
+ if (mArfChannel != null) mSEHandle.closeArfChannel();
+
+ this.initACEntryPoint();
+
+ try {
+ mACRulesObject.analyseFile(ACRulesPath);
+ } catch (Exception e) {
+ Log.i(mTag, "Exception: clear access rule cache and refresh tag");
+ mSEHandle.resetAccessRules();
+ throw e;
+ }
+ return true;
+ } else {
+ Log.i(mTag, "Refresh Tag has not been changed...");
+ return false;
+ }
+ }
+
+ /** Initializes "Access Control" entry point [ACMain] */
+ private void initACEntryPoint()
+ throws PKCS15Exception, SecureElementException, CertificateException {
+
+ byte[] DODFPath = null;
+ 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 {
+ if (aid == null) {
+ mArfChannel = mSEHandle.openLogicalArfChannel(new byte[]{});
+ if (mArfChannel != null) {
+ Log.i(mTag, "Logical channels are used to access to PKC15");
+ } else {
+ return false;
+ }
+ // estimate PKCS15 path only if it is not known already.
+ if (mPkcs15Path == null) {
+ mACMainPath = null;
+ // EF_DIR parsing
+ EFDIR DIRObject = new EFDIR(mSEHandle);
+ mPkcs15Path = DIRObject.lookupAID(PKCS15_AID);
+ if (mPkcs15Path == null) {
+ Log.i(mTag, "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");
+ }
+ }
+ } else {
+ // if an AID is given use logical channel.
+ // Selection of Applet/ADF via AID is done via SCAPI and logical Channels
+ mArfChannel = mSEHandle.openLogicalArfChannel(aid);
+ if (mArfChannel == null) {
+ Log.w(mTag, "GPAC/PKCS#15 ADF not found!!");
+ return false;
+ }
+ // ARF is selected via AID.
+ // if there is a change from path selection to AID
+ // selection, then reset AC Main path.
+ if (mPkcs15Path != null) {
+ mACMainPath = null;
+ }
+ mPkcs15Path = null; // selection is done via AID
+ }
+ return true;
+ }
+
+ /**
+ * 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.i(mTag, "- 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(mTag, mSELabel + " rules not correctly initialized! " + e.getLocalizedMessage());
+ throw new AccessControlException(e.getLocalizedMessage());
+ } finally {
+ // Close previously opened channel
+ if (mArfChannel != null) mSEHandle.closeArfChannel();
+ }
+ }
+}
diff --git a/src/com/android/se/security/arf/SecureElement.java b/src/com/android/se/security/arf/SecureElement.java
new file mode 100755
index 0000000..c5b8da5
--- /dev/null
+++ b/src/com/android/se/security/arf/SecureElement.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+/*
+ * Copyright (c) 2015-2017, The Linux Foundation.
+ */
+
+/*
+ * 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 com.android.se.security.arf;
+
+import android.util.Log;
+
+import com.android.se.Channel;
+import com.android.se.Terminal;
+import com.android.se.security.ChannelAccess;
+import com.android.se.security.arf.pkcs15.EF;
+import com.android.se.security.gpac.AID_REF_DO;
+import com.android.se.security.gpac.Hash_REF_DO;
+import com.android.se.security.gpac.REF_DO;
+
+import java.util.MissingResourceException;
+
+/**
+ * Provides high-level functions for SE communication
+ */
+public class SecureElement {
+ public final String mTag = "SecureElement-ARF";
+ // Logical channel used for SE communication (optional)
+ private Channel mArfChannel = null;
+ // Handle to a built-in "Secure Element"
+ private Terminal mTerminalHandle = null;
+ // Arf Controller within the SCAPI handler
+ private ArfController mArfHandler = null;
+
+ /**
+ * Constructor
+ *
+ * @param arfHandler - handle to the owning arf controller object
+ * @param handle - handle to the SE terminal to be accessed.
+ */
+ public SecureElement(ArfController arfHandler, Terminal handle) {
+ mTerminalHandle = handle;
+ mArfHandler = arfHandler;
+ }
+
+ /**
+ * Transmits ADPU commands
+ *
+ * @param cmd APDU command
+ * @return Data returned by the APDU command
+ */
+ public byte[] exchangeAPDU(EF ef, byte[] cmd) throws SecureElementException {
+ try {
+ 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 Channel openLogicalArfChannel(byte[] aid) {
+ try {
+ mArfChannel = mTerminalHandle.openLogicalChannelWithoutChannelAccess(aid);
+ if (mArfChannel == null) {
+ return null;
+ }
+ setUpChannelAccess(mArfChannel);
+ return mArfChannel;
+ } catch (Exception e) {
+ if (e instanceof MissingResourceException) {
+ // this indicates that no channel is left for accessing the SE element
+ Log.e(mTag, "no channels left to access ARF: " + e.getMessage());
+ throw (MissingResourceException) e;
+ } else {
+ Log.e(mTag, "Error opening logical channel " + e.getLocalizedMessage());
+ }
+ mArfChannel = null;
+ return null;
+ }
+ }
+
+ /**
+ * Closes a logical channel previously allocated by the SE
+ */
+ public void closeArfChannel() {
+ try {
+ if (mArfChannel != null) {
+ mArfChannel.close();
+ mArfChannel = null;
+ }
+
+ } catch (Exception e) {
+ Log.e(mTag, "Error closing channel " + e.getLocalizedMessage());
+ }
+ }
+
+ /**
+ * Set up channel access to allow, so that PKCS15 files can be read.
+ */
+ private void setUpChannelAccess(Channel channel) {
+ // set access conditions to access ARF.
+ ChannelAccess arfChannelAccess = new ChannelAccess();
+ arfChannelAccess.setAccess(ChannelAccess.ACCESS.ALLOWED, "");
+ arfChannelAccess.setApduAccess(ChannelAccess.ACCESS.ALLOWED);
+ channel.setChannelAccess(arfChannelAccess);
+ }
+
+ /** Fetches the Refresh Tag */
+ public byte[] getRefreshTag() {
+ if (mArfHandler != null) {
+ return mArfHandler.getAccessRuleCache().getRefreshTag();
+ }
+ return null;
+ }
+
+ /** Sets the given Refresh Tag */
+ public void setRefreshTag(byte[] refreshTag) {
+ if (mArfHandler != null) {
+ mArfHandler.getAccessRuleCache().setRefreshTag(refreshTag);
+ }
+ }
+
+ /** Addsthe Access Rule to the Cache */
+ public void putAccessRule(
+ AID_REF_DO aidRefDo, Hash_REF_DO hashRefDo, ChannelAccess channelAccess) {
+
+ REF_DO ref_do = new REF_DO(aidRefDo, hashRefDo);
+ mArfHandler.getAccessRuleCache().putWithMerge(ref_do, channelAccess);
+ }
+
+ /** Resets the Access Rule Cache */
+ public void resetAccessRules() {
+ this.mArfHandler.getAccessRuleCache().reset();
+ }
+
+ /** Clears the Access Rule Cache */
+ public void clearAccessRuleCache() {
+ this.mArfHandler.getAccessRuleCache().clearCache();
+ }
+}
diff --git a/src/com/android/se/security/arf/SecureElementException.java b/src/com/android/se/security/arf/SecureElementException.java
new file mode 100755
index 0000000..f68334d
--- /dev/null
+++ b/src/com/android/se/security/arf/SecureElementException.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+/*
+ * Copyright (c) 2017, The Linux Foundation.
+ */
+
+/*
+ * 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 com.android.se.security.arf;
+
+/** Handles "Secure Element" access errors ************************************************* */
+public class SecureElementException extends Exception {
+
+ private static final long serialVersionUID = 530360632436123998L;
+
+ public SecureElementException(String message) {
+ super(message);
+ }
+}
diff --git a/src/com/android/se/security/gpac/AID_REF_DO.java b/src/com/android/se/security/gpac/AID_REF_DO.java
new file mode 100644
index 0000000..75b9dae
--- /dev/null
+++ b/src/com/android/se/security/gpac/AID_REF_DO.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+/*
+ * Copyright (c) 2017, The Linux Foundation.
+ */
+
+/*
+ * 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 com.android.se.security.gpac;
+
+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 static final int TAG = 0x4F;
+ public static final int TAG_DEFAULT_APPLICATION = 0xC0;
+
+ private byte[] mAid = new byte[0];
+
+ 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));
+ if (aid != null) mAid = aid;
+ }
+
+ public AID_REF_DO(int tag) {
+ super(null, tag, 0, 0);
+ }
+
+ /**
+ * Comapares two AID_REF_DO objects and returns true if they are equal
+ */
+ public static boolean equals(AID_REF_DO obj1, AID_REF_DO obj2) {
+ if (obj1 == null) {
+ return (obj2 == null) ? true : false;
+ }
+ return obj1.equals(obj2);
+ }
+
+ @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
+ *
+ * <p>Value: AID: identifies a specific application Empty: refers to all SE applications
+ *
+ * <p>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
+ *
+ * <p>Value: AID: identifies a specific application Empty: refers to all SE applications
+ *
+ * <p>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.length > 0) {
+ 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());
+ stream.write(mAid.length);
+ try {
+ stream.write(mAid);
+ } catch (IOException ioe) {
+ throw new DO_Exception("AID could not be written!");
+ }
+ } else {
+ throw new DO_Exception("AID-REF-DO must either be C0 or 4F!");
+ }
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof AID_REF_DO) {
+ AID_REF_DO aid_ref_do = (AID_REF_DO) obj;
+ if (getTag() == aid_ref_do.getTag()) {
+ return Arrays.equals(mAid, aid_ref_do.mAid);
+ }
+ }
+ return false;
+ }
+}
diff --git a/src/com/android/se/security/gpac/APDU_AR_DO.java b/src/com/android/se/security/gpac/APDU_AR_DO.java
new file mode 100755
index 0000000..5dc79ff
--- /dev/null
+++ b/src/com/android/se/security/gpac/APDU_AR_DO.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+/*
+ * Copyright (c) 2017, The Linux Foundation.
+ */
+
+/*
+ * 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 com.android.se.security.gpac;
+
+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 static final 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.
+ *
+ * <p>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) && (getValueLength() != 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 if (getValueLength() == 0) {
+ mApduAllowed = false;
+ } 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.
+ *
+ * <p>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/com/android/se/security/gpac/AR_DO.java b/src/com/android/se/security/gpac/AR_DO.java
new file mode 100755
index 0000000..ebb5a6e
--- /dev/null
+++ b/src/com/android/se/security/gpac/AR_DO.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+/*
+ * Copyright (c) 2017, The Linux Foundation.
+ */
+
+/*
+ * 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 com.android.se.security.gpac;
+
+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.
+ *
+ * <p>The AR-DO contains one or two access rules of type APDU or NFC.
+ */
+public class AR_DO extends BerTlv {
+
+ public static final 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 apduArDo, NFC_AR_DO nfcArDo) {
+ super(null, TAG, 0, 0);
+ mApduAr = apduArDo;
+ mNfcAr = nfcArDo;
+ }
+
+ public APDU_AR_DO getApduArDo() {
+ return mApduAr;
+ }
+
+ public NFC_AR_DO getNfcArDo() {
+ return mNfcAr;
+ }
+
+ @Override
+ /**
+ * Interpret value.
+ *
+ * <p>Tag: E3
+ *
+ * <p>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.
+ *
+ * <p>Tag: E3
+ *
+ * <p>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/com/android/se/security/gpac/BerTlv.java b/src/com/android/se/security/gpac/BerTlv.java
new file mode 100644
index 0000000..066bf5e
--- /dev/null
+++ b/src/com/android/se/security/gpac/BerTlv.java
@@ -0,0 +1,264 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+/*
+ * Copyright (c) 2017, The Linux Foundation.
+ */
+
+/*
+ * 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 com.android.se.security.gpac;
+
+import java.io.ByteArrayOutputStream;
+import java.util.Arrays;
+
+/** Base Class for all Access Control Data Objects */
+public class BerTlv {
+
+ private byte[] mRawData = new byte[0];
+
+ private int mTag = 0;
+
+ private int mValueIndex = 0;
+ private int mValueLength = 0;
+
+ public BerTlv(byte[] rawData, int tag, int valueIndex, int valueLength) {
+ if (rawData != null) mRawData = rawData;
+ mTag = tag;
+ mValueIndex = valueIndex;
+ mValueLength = valueLength;
+ }
+
+ /** Converts the byte into Hex */
+ 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();
+ }
+
+ /** Decodes the byte array into BerTlv Object. Performs checks for length and tag */
+ public static BerTlv decode(byte[] data, int startIndex) throws ParserException {
+ return BerTlv.decode(data, startIndex, true);
+ }
+
+ /** Decodes the byte array into BerTlv Object. Performs checks for length and tag */
+ 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);
+ }
+
+ /**
+ * Encodes length according to ASN1. Supported are length values up to 3 bytes -> 83 xx yy zz.
+ */
+ 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));
+ }
+ }
+
+ /**
+ * Interprets the byte stream and performs the required checks.
+ * To to overridden in the derived class.
+ */
+ public void interpret() throws ParserException {
+ }
+
+ /**
+ * Builds up the TLV into a byte stream.
+ *
+ * <p>Tags can be encoded as one or two bytes
+ */
+ 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;
+ }
+
+ /** Returns the byte array of only the data values */
+ public byte[] getValue() {
+ // sanity checks
+ if (mRawData == null
+ || mValueLength == 0
+ || mValueIndex < 0
+ || mValueIndex > mRawData.length
+ || mValueIndex + mValueLength > mRawData.length) {
+ return new byte[0];
+ }
+
+ byte[] data = new byte[mValueLength];
+
+ System.arraycopy(mRawData, mValueIndex, data, 0, mValueLength);
+
+ return data;
+ }
+
+ protected byte[] getRawData() {
+ return mRawData;
+ }
+
+ public int getValueLength() {
+ return mValueLength;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof BerTlv) {
+ BerTlv berTlv = (BerTlv) obj;
+ if (mTag == berTlv.mTag) {
+ byte[] test1 = this.getValue();
+ byte[] test2 = berTlv.getValue();
+ return Arrays.equals(test1, test2);
+ }
+ }
+ return false;
+ }
+}
diff --git a/src/com/android/se/security/gpac/DO_Exception.java b/src/com/android/se/security/gpac/DO_Exception.java
new file mode 100755
index 0000000..01055a3
--- /dev/null
+++ b/src/com/android/se/security/gpac/DO_Exception.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+/*
+ * Copyright (c) 2017, The Linux Foundation.
+ */
+
+/*
+ * 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 com.android.se.security.gpac;
+
+/** Exceptions with the Access Data Objects are not formatted correctly */
+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/com/android/se/security/gpac/Hash_REF_DO.java b/src/com/android/se/security/gpac/Hash_REF_DO.java
new file mode 100644
index 0000000..7b8a861
--- /dev/null
+++ b/src/com/android/se/security/gpac/Hash_REF_DO.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+/*
+ * Copyright (c) 2017, The Linux Foundation.
+ */
+
+/*
+ * 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 com.android.se.security.gpac;
+
+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 static final int TAG = 0xC1;
+ public static final int SHA1_LEN = 20;
+
+ private byte[] mHash = new byte[0];
+
+ 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));
+ if (hash != null) mHash = hash;
+ }
+
+ public Hash_REF_DO() {
+ super(null, TAG, 0, 0);
+ }
+
+ /**
+ * Comapares two Hash_REF_DO objects and returns true if they are equal
+ */
+ public static boolean equals(Hash_REF_DO obj1, Hash_REF_DO obj2) {
+ if (obj1 == null) {
+ return (obj2 == null) ? true : false;
+ }
+ return obj1.equals(obj2);
+ }
+
+ 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
+ *
+ * <p>Value: Hash: identifies a specific device application Empty: refers to all device
+ * applications
+ *
+ * <p>Length: SHA1_LEN for 20 bytes SHA-1 hash value 0 for empty value field
+ */
+ @Override
+ public void interpret() throws ParserException {
+
+ mHash = new byte[0];
+
+ 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
+ *
+ * <p>Value: Hash: identifies a specific device application Empty: refers to all device
+ * applications
+ *
+ * <p>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.length != SHA1_LEN || mHash.length != 0)) {
+ throw new DO_Exception("Hash value must be " + SHA1_LEN + " bytes in length!");
+ }
+
+ stream.write(getTag());
+
+ 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) {
+ if (obj instanceof Hash_REF_DO) {
+ Hash_REF_DO hash_ref_do = (Hash_REF_DO) obj;
+ if (getTag() == hash_ref_do.getTag()) {
+ return Arrays.equals(mHash, hash_ref_do.mHash);
+ }
+ }
+ return false;
+ }
+}
diff --git a/src/com/android/se/security/gpac/NFC_AR_DO.java b/src/com/android/se/security/gpac/NFC_AR_DO.java
new file mode 100755
index 0000000..466706a
--- /dev/null
+++ b/src/com/android/se/security/gpac/NFC_AR_DO.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+/*
+ * Copyright (c) 2017, The Linux Foundation.
+ */
+
+/*
+ * 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 com.android.se.security.gpac;
+
+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 static final 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!");
+ } else if ((data[index] != 0x01) && (data[index] != 0x00)) {
+ throw new ParserException(
+ "Invalid value of NFC-AR-DO : " + String.format("%02x", data[index] & 0xff));
+ }
+
+ 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/com/android/se/security/gpac/ParserException.java b/src/com/android/se/security/gpac/ParserException.java
new file mode 100755
index 0000000..9195dd0
--- /dev/null
+++ b/src/com/android/se/security/gpac/ParserException.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+/*
+ * Copyright (c) 2017, The Linux Foundation.
+ */
+
+/*
+ * 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 com.android.se.security.gpac;
+
+/** Exceptions when there is an error parcing the Data Objects for Access Rules */
+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/com/android/se/security/gpac/REF_AR_DO.java b/src/com/android/se/security/gpac/REF_AR_DO.java
new file mode 100755
index 0000000..fd4231c
--- /dev/null
+++ b/src/com/android/se/security/gpac/REF_AR_DO.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+/*
+ * Copyright (c) 2017, The Linux Foundation.
+ */
+
+/*
+ * 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 com.android.se.security.gpac;
+
+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 static final 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 refDo, AR_DO arDo) {
+ super(null, TAG, 0, 0);
+ mRefDo = refDo;
+ mArDo = arDo;
+ }
+
+ public REF_DO getRefDo() {
+ return mRefDo;
+ }
+
+ public AR_DO getArDo() {
+ return mArDo;
+ }
+
+ /**
+ * Interpret data.
+ *
+ * <p>Tags: E2 Length: n
+ *
+ * <p>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.
+ *
+ * <p>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/com/android/se/security/gpac/REF_DO.java b/src/com/android/se/security/gpac/REF_DO.java
new file mode 100644
index 0000000..fd73e0d
--- /dev/null
+++ b/src/com/android/se/security/gpac/REF_DO.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+/*
+ * Copyright (c) 2017, The Linux Foundation.
+ */
+
+/*
+ * 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 com.android.se.security.gpac;
+
+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 static final 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 aidRefDo, Hash_REF_DO hashRefDo) {
+ super(null, TAG, 0, 0);
+ mAidDo = aidRefDo;
+ mHashDo = hashRefDo;
+ }
+
+ @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.
+ *
+ * <p>Tags: E1 -> Length: n
+ *
+ * <p>Value: AID-REF-DO | Hash-REF-DO: A concatenation of an AID-REF-DO and a Hash-REF-DO.
+ *
+ * <p>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) {
+ if (obj instanceof REF_DO) {
+ REF_DO ref_do = (REF_DO) obj;
+ if (getTag() == ref_do.getTag()) {
+ if (AID_REF_DO.equals(mAidDo, ref_do.mAidDo)) {
+ if (Hash_REF_DO.equals(mHashDo, ref_do.mHashDo)) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ @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/com/android/se/security/gpac/Response_ALL_AR_DO.java b/src/com/android/se/security/gpac/Response_ALL_AR_DO.java
new file mode 100755
index 0000000..efbf608
--- /dev/null
+++ b/src/com/android/se/security/gpac/Response_ALL_AR_DO.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+/*
+ * Copyright (c) 2017, The Linux Foundation.
+ */
+
+/*
+ * 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 com.android.se.security.gpac;
+
+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 static final 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
+ *
+ * <p>Length: n or 0 If n is equal to zero, then there are no rules to fetch.
+ *
+ * <p>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/com/android/se/security/gpac/Response_ARAC_AID_DO.java b/src/com/android/se/security/gpac/Response_ARAC_AID_DO.java
new file mode 100755
index 0000000..240ef9d
--- /dev/null
+++ b/src/com/android/se/security/gpac/Response_ARAC_AID_DO.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+/*
+ * Copyright (c) 2017, The Linux Foundation.
+ */
+
+/*
+ * 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 com.android.se.security.gpac;
+
+import java.util.ArrayList;
+
+/**
+ * Response_ARAC_AID_DO
+ *
+ * <p>A list of AIDs containing an AID for each ARA-C.
+ *
+ * <p>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 static final 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
+ *
+ * <p>Length: n or 0 If n is equal to zero, then there are no rules to fetch.
+ *
+ * <p>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/com/android/se/security/gpac/Response_AR_DO.java b/src/com/android/se/security/gpac/Response_AR_DO.java
new file mode 100755
index 0000000..607414c
--- /dev/null
+++ b/src/com/android/se/security/gpac/Response_AR_DO.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+/*
+ * Copyright (c) 2017, The Linux Foundation.
+ */
+
+/*
+ * 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 com.android.se.security.gpac;
+
+/**
+ * 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 static final 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
+ *
+ * <p>Length: n or 0 If n is equal to zero, then there are no rules to fetch.
+ *
+ * <p>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/com/android/se/security/gpac/Response_DO_Factory.java b/src/com/android/se/security/gpac/Response_DO_Factory.java
new file mode 100755
index 0000000..ec824d8
--- /dev/null
+++ b/src/com/android/se/security/gpac/Response_DO_Factory.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+/*
+ * Copyright (c) 2017, The Linux Foundation.
+ */
+
+/*
+ * 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 com.android.se.security.gpac;
+
+/** Matches the Tags for the data object */
+public class Response_DO_Factory {
+
+ /** Creates and interprets the bytes into the respective types */
+ 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/com/android/se/security/gpac/Response_RefreshTag_DO.java b/src/com/android/se/security/gpac/Response_RefreshTag_DO.java
new file mode 100755
index 0000000..d34ac2e
--- /dev/null
+++ b/src/com/android/se/security/gpac/Response_RefreshTag_DO.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+/*
+ * Copyright (c) 2017, The Linux Foundation.
+ */
+
+/*
+ * 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 com.android.se.security.gpac;
+
+/**
+ * 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 static final 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);
+ }
+}