diff options
author | android-build-team Robot <android-build-team-robot@google.com> | 2018-02-05 08:21:16 +0000 |
---|---|---|
committer | android-build-team Robot <android-build-team-robot@google.com> | 2018-02-05 08:21:16 +0000 |
commit | b3266366690b2ec9b085c7ce0fab3cf167fed787 (patch) | |
tree | f533a03b008cbce04e8b5cadfef06cfecb8a7372 | |
parent | a59a73aeb8e3bd758c39dd7361296641ee221fa0 (diff) | |
parent | 799af455634b182e94042242c726ef77411aa909 (diff) | |
download | SecureElement-b3266366690b2ec9b085c7ce0fab3cf167fed787.tar.gz |
Snap for 4585119 from 799af455634b182e94042242c726ef77411aa909 to pi-release
Change-Id: I4fd8eebfb7eb43a18e9eba800aac5ae604a0c89e
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) @@ -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); + } +} |