UUID based access rules support for SE am: 10b0a90cc7

Original change: https://android-review.googlesource.com/c/platform/packages/apps/SecureElement/+/1769148

Change-Id: I2d9f3719ff1c23415afdc04ac096e177c200cfb5
diff --git a/etc/hal_uuid_map_config.xml b/etc/hal_uuid_map_config.xml
new file mode 100644
index 0000000..5388697
--- /dev/null
+++ b/etc/hal_uuid_map_config.xml
@@ -0,0 +1,41 @@
+<!-- Vendor mapping file -->
+<!-- Sample UUID to list of UIDs mapping file -->
+
+<!-- UUID: Universally Unique IDentifier -->
+<!-- 16 Byte UUID need to be generated by vendors to add new entry -->
+<!-- As per global platform access control spec, UUID is expected to be of -->
+<!-- length 20 bytes. While using this UUID, it is expected to be -->
+<!-- automatically padded with ffffffff in initial 4 bytes of 20 Byte length -->
+
+<!-- UID: user identifier of the service -->
+
+<!-- This mapping file should contain an entry for VTS tests, since VTS -->
+<!-- tests run as root, user identifier 0 should be mapped to its -->
+<!-- corresponding UUID to allow VTS tests to access secure element -->
+<!-- For VTS tests use UID: 0 and UUID: 9f36407ead0639fc966f14dde7970f68 -->
+
+<ref_do>
+    <!-- mapping entries to map unique identifiers to device hal services -->
+    <!-- uids -->
+
+    <!-- UUID would be automatically padding with ffffffff to fulfill 20 -->
+    <!-- bytes in access rule. For example for -->
+    <!-- UUID:9f36407ead0639fc966f14dde7970f68 after padding it should look -->
+    <!-- like ffffffff9f36407ead0639fc966f14dde7970f68 -->
+    <uuid_ref_do>
+        <uids>
+            <uid>0</uid>
+        </uids>
+        <uuid>9f36407ead0639fc966f14dde7970f68</uuid>
+    </uuid_ref_do>
+
+    <!-- Sample mapping entry with UIDs:1096 and 1097 mapped to -->
+    <!-- UUID:9f36407ead0639fc966f14dde7970f68 -->
+    <uuid_ref_do>
+        <uids>
+            <uid>1096</uid>
+            <uid>1097</uid>
+        </uids>
+        <uuid>a9b7ba70783b317e9998dc4dd82eb3c5</uuid>
+    </uuid_ref_do>
+</ref_do>
diff --git a/src/com/android/se/SecureElementService.java b/src/com/android/se/SecureElementService.java
index 38899fa..c7187e1 100644
--- a/src/com/android/se/SecureElementService.java
+++ b/src/com/android/se/SecureElementService.java
@@ -45,6 +45,7 @@
 
 import com.android.se.Terminal.SecureElementReader;
 import com.android.se.internal.ByteArrayConverter;
+import com.android.se.security.HalRefDoParser;
 
 import java.io.FileDescriptor;
 import java.io.IOException;
@@ -244,6 +245,16 @@
         throw new AccessControlException("PackageName can not be determined");
     }
 
+    private byte[] getUUIDFromCallingUid(int uid) {
+        byte[] uuid = HalRefDoParser.getInstance().findUUID(Binder.getCallingUid());
+
+        if (uuid != null) {
+            return uuid;
+        }
+
+        return null;
+    }
+
     final class SecureElementSession extends ISecureElementSession.Stub {
 
         private final SecureElementReader mReader;
@@ -326,12 +337,26 @@
                         + String.format("%02x ", p2 & 0xFF));
             }
 
-            String packageName = getPackageNameFromCallingUid(Binder.getCallingUid());
+            String packageName = null;
+            byte[] uuid = null;
+            try {
+                packageName = getPackageNameFromCallingUid(Binder.getCallingUid());
+            } catch (AccessControlException e) {
+                // Since packageName not found for calling process, try to find UUID mapping
+                // provided by vendors for the calling process UID
+                // (vendor provide UUID mapping for native services to access secure element)
+                Log.d(mTag, "openBasicChannel() trying to find mapping uuid");
+                uuid = getUUIDFromCallingUid(Binder.getCallingUid());
+                if (uuid == null) {
+                    Log.e(mTag, "openBasicChannel() uuid mapping for calling uid is not found");
+                    throw e;
+                }
+            }
             Channel channel = null;
 
             try {
                 channel = mReader.getTerminal().openBasicChannel(this, aid, p2, listener,
-                        packageName, Binder.getCallingPid());
+                        packageName, uuid, Binder.getCallingPid());
             } catch (IOException e) {
                 throw new ServiceSpecificException(SEService.IO_ERROR, e.getMessage());
             } catch (NoSuchElementException e) {
@@ -368,12 +393,26 @@
                         + String.format("%02x ", p2 & 0xFF));
             }
 
-            String packageName = getPackageNameFromCallingUid(Binder.getCallingUid());
+            String packageName = null;
+            byte[] uuid = null;
+            try {
+                packageName = getPackageNameFromCallingUid(Binder.getCallingUid());
+            } catch (AccessControlException e) {
+                // Since packageName not found for calling process, try to find UUID mapping
+                // provided by vendors for the calling process UID
+                // (vendor provide UUID mapping for native services to access secure element)
+                Log.d(mTag, "openLogicalChannel() trying to find mapping uuid");
+                uuid = getUUIDFromCallingUid(Binder.getCallingUid());
+                if (uuid == null) {
+                    Log.e(mTag, "openLogicalChannel() uuid mapping for calling uid is not found");
+                    throw e;
+                }
+            }
             Channel channel = null;
 
             try {
                 channel = mReader.getTerminal().openLogicalChannel(this, aid, p2, listener,
-                        packageName, Binder.getCallingPid());
+                        packageName, uuid, Binder.getCallingPid());
             } catch (IOException e) {
                 throw new ServiceSpecificException(SEService.IO_ERROR, e.getMessage());
             } catch (NoSuchElementException e) {
diff --git a/src/com/android/se/Terminal.java b/src/com/android/se/Terminal.java
index d97dd2f..1c055a0 100644
--- a/src/com/android/se/Terminal.java
+++ b/src/com/android/se/Terminal.java
@@ -32,6 +32,7 @@
 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.Binder;
 import android.os.Build;
 import android.os.Handler;
 import android.os.HwBinder;
@@ -432,9 +433,11 @@
 
     /**
      * Opens a Basic Channel with the given AID and P2 paramters
+     * with the given device app reference package name or uuid
      */
     public Channel openBasicChannel(SecureElementSession session, byte[] aid, byte p2,
-            ISecureElementListener listener, String packageName, int pid) throws IOException,
+            ISecureElementListener listener, String packageName, byte[] uuid,
+            int pid) throws IOException,
             NoSuchElementException {
         if (aid != null && aid.length == 0) {
             aid = null;
@@ -446,19 +449,31 @@
 
         ChannelAccess channelAccess = null;
         if (packageName != null) {
-            Log.w(mTag, "Enable access control on basic channel for " + packageName);
+            Log.w(mTag, "Enable access control on basic channel for package name: "
+                    + packageName);
             SecureElementStatsLog.write(
                     SecureElementStatsLog.SE_OMAPI_REPORTED,
                     SecureElementStatsLog.SE_OMAPI_REPORTED__OPERATION__OPEN_CHANNEL,
                     mName,
                     packageName);
-            try {
-                // For application without privilege permission or carrier privilege,
-                // openBasicChannel with UICC terminals should be rejected.
-                channelAccess = setUpChannelAccess(aid, packageName, pid, true);
-            } catch (MissingResourceException e) {
-                return null;
+        } else if (uuid != null) {
+            Log.w(mTag, "Enable access control on basic channel for uid: "
+                    + Binder.getCallingUid()
+                    + " UUID: " + Arrays.toString(uuid));
+            SecureElementStatsLog.write(
+                    SecureElementStatsLog.SE_OMAPI_REPORTED,
+                    SecureElementStatsLog.SE_OMAPI_REPORTED__OPERATION__OPEN_CHANNEL,
+                    mName,
+                    Arrays.toString(uuid));
+        }
+        try {
+            // For application without privilege permission or carrier privilege,
+            // openBasicChannel with UICC terminals should be rejected.
+            if (packageName != null || uuid != null) {
+                channelAccess = setUpChannelAccess(aid, packageName, uuid, pid, true);
             }
+        } catch (MissingResourceException e) {
+            return null;
         }
 
         synchronized (mLock) {
@@ -516,14 +531,15 @@
      */
     public Channel openLogicalChannelWithoutChannelAccess(byte[] aid) throws IOException,
             NoSuchElementException {
-        return openLogicalChannel(null, aid, (byte) 0x00, null, null, 0);
+        return openLogicalChannel(null, aid, (byte) 0x00, null, null, null, 0);
     }
 
     /**
-     * Opens a logical Channel with AID.
+     * Opens a logical Channel with AID for the given package name or uuid
      */
     public Channel openLogicalChannel(SecureElementSession session, byte[] aid, byte p2,
-            ISecureElementListener listener, String packageName, int pid) throws IOException,
+            ISecureElementListener listener, String packageName,
+            byte[] uuid, int pid) throws IOException,
             NoSuchElementException {
         if (aid != null && aid.length == 0) {
             aid = null;
@@ -541,11 +557,22 @@
                     SecureElementStatsLog.SE_OMAPI_REPORTED__OPERATION__OPEN_CHANNEL,
                     mName,
                     packageName);
-            try {
-                channelAccess = setUpChannelAccess(aid, packageName, pid, false);
-            } catch (MissingResourceException | UnsupportedOperationException e) {
-                return null;
+        } else if (uuid != null) {
+            Log.w(mTag, "Enable access control on logical channel for uid: "
+                    + Binder.getCallingUid()
+                    + " UUID: " + Arrays.toString(uuid));
+            SecureElementStatsLog.write(
+                    SecureElementStatsLog.SE_OMAPI_REPORTED,
+                    SecureElementStatsLog.SE_OMAPI_REPORTED__OPERATION__OPEN_CHANNEL,
+                    mName,
+                    Arrays.toString(uuid));
+        }
+        try {
+            if (packageName != null || uuid != null) {
+                channelAccess = setUpChannelAccess(aid, packageName, uuid, pid, false);
             }
+        } catch (MissingResourceException | UnsupportedOperationException e) {
+            return null;
         }
 
         synchronized (mLock) {
@@ -745,10 +772,10 @@
     /**
      * Initialize the Access Control and set up the channel access.
      */
-    private ChannelAccess setUpChannelAccess(byte[] aid, String packageName, int pid,
+    private ChannelAccess setUpChannelAccess(byte[] aid, String packageName, byte[] uuid, int pid,
             boolean isBasicChannel) throws IOException, MissingResourceException {
         boolean checkRefreshTag = true;
-        if (isPrivilegedApplication(packageName)) {
+        if (packageName != null && isPrivilegedApplication(packageName)) {
             return ChannelAccess.getPrivilegeAccess(packageName, pid);
         }
         // Attempt to initialize the access control enforcer if it failed
@@ -764,7 +791,7 @@
         mAccessControlEnforcer.setPackageManager(mContext.getPackageManager());
 
         // Check carrier privilege when AID is not ISD-R
-        if (getName().startsWith(SecureElementService.UICC_TERMINAL)
+        if (packageName != null && getName().startsWith(SecureElementService.UICC_TERMINAL)
                 && !Arrays.equals(aid, ISD_R_AID)) {
             try {
                 PackageManager pm = mContext.getPackageManager();
@@ -794,7 +821,7 @@
         synchronized (mLock) {
             try {
                 ChannelAccess channelAccess =
-                        mAccessControlEnforcer.setUpChannelAccess(aid, packageName,
+                        mAccessControlEnforcer.setUpChannelAccess(aid, packageName, uuid,
                                 checkRefreshTag);
                 channelAccess.setCallingPid(pid);
                 return channelAccess;
diff --git a/src/com/android/se/security/AccessControlEnforcer.java b/src/com/android/se/security/AccessControlEnforcer.java
index 6d595d8..e6f08cf 100644
--- a/src/com/android/se/security/AccessControlEnforcer.java
+++ b/src/com/android/se/security/AccessControlEnforcer.java
@@ -271,8 +271,8 @@
     }
 
     /** Sets up the Channel Access for the given Package */
-    public ChannelAccess setUpChannelAccess(byte[] aid, String packageName, boolean checkRefreshTag)
-            throws IOException, MissingResourceException {
+    public ChannelAccess setUpChannelAccess(byte[] aid, String packageName, byte[] uuid,
+            boolean checkRefreshTag) throws IOException, MissingResourceException {
         ChannelAccess channelAccess = null;
         // check result of channel access during initialization procedure
         if (mInitialChannelAccess.getAccess() == ChannelAccess.ACCESS.DENIED) {
@@ -281,7 +281,7 @@
         }
         // this is the new GP Access Control Enforcer implementation
         if (mUseAra || mUseArf) {
-            channelAccess = internal_setUpChannelAccess(aid, packageName,
+            channelAccess = internal_setUpChannelAccess(aid, packageName, uuid,
                     checkRefreshTag);
         }
         if (channelAccess == null || (channelAccess.getApduAccess() != ChannelAccess.ACCESS.ALLOWED
@@ -299,14 +299,23 @@
     }
 
     private synchronized ChannelAccess internal_setUpChannelAccess(byte[] aid,
-            String packageName, boolean checkRefreshTag) throws IOException,
+            String packageName, byte[] uuid, boolean checkRefreshTag) throws IOException,
             MissingResourceException {
-        if (packageName == null || packageName.isEmpty()) {
+        if (uuid == null && (packageName == null || packageName.isEmpty())) {
             throw new AccessControlException("package names must be specified");
         }
         try {
             // estimate SHA-1 and SHA-256 hash values of the device application's certificate.
-            List<byte[]> appCertHashes = getAppCertHashes(packageName);
+            List<byte[]> appCertHashes = null;
+            if (packageName != null) {
+                appCertHashes = getAppCertHashes(packageName);
+            } else {
+                if (uuid != null) {
+                    appCertHashes = new ArrayList<byte[]>();
+                    appCertHashes.add(uuid);
+                }
+            }
+
             // APP certificates must be available => otherwise Exception
             if (appCertHashes == null || appCertHashes.size() == 0) {
                 throw new AccessControlException(
diff --git a/src/com/android/se/security/HalRefDoParser.java b/src/com/android/se/security/HalRefDoParser.java
new file mode 100644
index 0000000..40ea7de
--- /dev/null
+++ b/src/com/android/se/security/HalRefDoParser.java
@@ -0,0 +1,250 @@
+/*
+ * Copyright (C) 2021 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.security;
+
+import android.os.Build;
+import android.os.SystemProperties;
+import android.util.Log;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlPullParserFactory;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Parser for HAL_UID_MAP.XML
+ * Parses the xml file and collects HAL references (UUID) to identify the corresponding
+ * access rules for the HAL services.
+ */
+public class HalRefDoParser {
+
+    private static final boolean DEBUG = Build.IS_DEBUGGABLE;
+    private final String mTag = "SecureElement-HalRefDoParser";
+
+    private static final String PROP_PRODUCT_HARDWARE_SKU = "ro.boot.product.hardware.sku";
+    private static final String UUID_MAPPING_CONFIG_PREFIX = "hal_uuid_map_";
+    private static final String UUID_MAPPING_CONFIG_EXT = ".xml";
+    private static final String[] UUID_MAPPING_CONFIG_PATHS = {"/odm/etc/", "/vendor/etc/",
+                                                               "/etc/"};
+
+    // Holds UUID to UIDs mapping
+    private final Map<Integer, byte[]> mUUIDMap = new HashMap<Integer, byte[]>();
+
+    private static final String REF_DO = "ref_do";
+    private static final String UUID_REF_DO = "uuid_ref_do";
+    private static final String UUID = "uuid";
+    private static final String UIDS = "uids";
+    private static final String UID = "uid";
+
+    private static final byte[] PADDING_BYTES = {
+        (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff};
+
+    private HalRefDoParser() {
+        parseUuidMappings();
+    }
+
+    private static class HalRefParserSingleton {
+        private static final HalRefDoParser INSTANCE = new HalRefDoParser();
+    }
+
+    public static HalRefDoParser getInstance() {
+        return HalRefParserSingleton.INSTANCE;
+    }
+
+    private File getUuidMapConfigFile() {
+        // default file name: hal_uuid_map_config.xml
+        String uuid_map_config_file_name = UUID_MAPPING_CONFIG_PREFIX
+                + SystemProperties.get(PROP_PRODUCT_HARDWARE_SKU, "config")
+                + UUID_MAPPING_CONFIG_EXT;
+        String uuid_map_config_path = null;
+
+        try {
+            // Search in predefined folders
+            for (String path : UUID_MAPPING_CONFIG_PATHS) {
+                uuid_map_config_path = path + uuid_map_config_file_name;
+                File confFile = new File(uuid_map_config_path);
+                if (confFile.exists()) {
+                    Log.d(mTag, "UUID mapping config file path: " + uuid_map_config_path);
+                    return confFile;
+                }
+            }
+        } catch (Exception e) {
+            Log.e(mTag, "Error in finding UUID mapping config file path: " + uuid_map_config_path);
+        }
+
+        return null;
+    }
+
+    /**
+     * Parses the below mapping structure -
+     *
+     * <ref_do>
+     *    <uuid_ref_do>
+     *        <uids>
+     *            <uid>1000</uid>
+     *        </uids>
+     *        <uuid>a9b7ba70783b317e9998dc4dd82eb3c5</uuid>
+     *      </uuid_ref_do>
+     * </ref_do>
+     */
+    private void parse(InputStream is) {
+        try {
+            XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
+            factory.setNamespaceAware(true);
+            XmlPullParser parser = factory.newPullParser();
+            String text = null;
+            List<Integer> uids = null;
+            byte[] uuid = null;
+
+            parser.setInput(is, null);
+
+            int eventType = parser.getEventType();
+            while (eventType != XmlPullParser.END_DOCUMENT) {
+                String tagname = parser.getName();
+                switch (eventType) {
+                    case XmlPullParser.START_TAG:
+                        if (tagname.equalsIgnoreCase(UIDS)) {
+                            uids = new ArrayList<Integer>();
+                        } else if (tagname.equalsIgnoreCase(UUID)) {
+                            uuid = null;
+                        }
+                        break;
+
+                    case XmlPullParser.TEXT:
+                        text = parser.getText();
+                        break;
+
+                    case XmlPullParser.END_TAG:
+                        if (tagname.equalsIgnoreCase(UUID_REF_DO)) {
+                            if (uuid != null) {
+                                for (int uid : uids) {
+                                    mUUIDMap.put(uid, uuid);
+                                }
+                            }
+                        } else if (tagname.equalsIgnoreCase(UID)) {
+                            uids.add(Integer.parseInt(text));
+                        } else if (tagname.equalsIgnoreCase(UUID)) {
+                            byte[] uuidValue = decodeHexUUID(text);
+                            uuid = new byte[uuidValue.length + PADDING_BYTES.length];
+                            System.arraycopy(PADDING_BYTES, 0, uuid, 0, PADDING_BYTES.length);
+                            System.arraycopy(uuidValue, 0, uuid, PADDING_BYTES.length,
+                                    uuidValue.length);
+
+                        }
+                        break;
+
+                    default:
+                        break;
+                }
+                eventType = parser.next();
+            }
+
+        } catch (XmlPullParserException e) {
+            Log.e(mTag, "Error while parsing hal uuid mappings");
+            Log.e(mTag, e.getMessage());
+        } catch (IOException e) {
+            Log.e(mTag, "IO error while parsing hal uuid mappings");
+            Log.e(mTag, e.getMessage());
+        }
+    }
+
+    /**
+     * Finds the uuid mapping config file path from predefined folders
+     * Parses the uuid mapping config file
+     */
+    private void parseUuidMappings() {
+        try {
+            File uuid_map_file = getUuidMapConfigFile();
+            if (uuid_map_file == null) {
+                Log.e(mTag, "Unable to determine UUID mapping config file path");
+                return;
+            }
+
+            parse(new FileInputStream(uuid_map_file));
+        } catch (Exception e) {
+            Log.e(mTag, "Unable to parse hal uuid mappings");
+            Log.e(mTag, e.getMessage());
+        }
+
+        if (DEBUG) {
+            for (Map.Entry<Integer, byte[]> entry : mUUIDMap.entrySet()) {
+                Log.d(mTag, "UID: " + entry.getKey());
+                Log.d(mTag, "UUID: " + Arrays.toString(entry.getValue()));
+            }
+        }
+    }
+
+    /**
+     * Finds UUID for the give UID
+     */
+    public byte[] findUUID(int uid) {
+        return mUUIDMap.get(uid);
+    }
+
+    /**
+     * Convert char to hex digit
+     * @param hexChar
+     * @return hex digit
+     */
+    private int toDigit(char hexChar) {
+        int digit = Character.digit(hexChar, 16);
+        if (digit == -1) {
+            throw new IllegalArgumentException(
+                    "Invalid Hexadecimal Character: " + hexChar);
+        }
+        return digit;
+    }
+
+    /**
+     * Convert hex digits string to bytes
+     * @param hextText
+     * @return hex byte
+     */
+    private byte hexToByte(char ch1, char ch2) {
+        int firstDigit = toDigit(ch1);
+        int secondDigit = toDigit(ch2);
+        return (byte) ((firstDigit << 4) + secondDigit);
+    }
+
+    /**
+     * Convert hex string to hex byte array
+     * @param hextText
+     * @return hex bytes
+     */
+    private byte[] decodeHexUUID(String hextText) {
+        if (hextText == null || hextText.length() != 32) {
+            throw new IllegalArgumentException(
+                    "Invalid UUID supplied");
+        }
+
+        byte[] bytes = new byte[hextText.length() / 2];
+        for (int i = 0; i < hextText.length(); i += 2) {
+            bytes[i / 2] = hexToByte(hextText.charAt(i), hextText.charAt(i + 1));
+        }
+        return bytes;
+    }
+
+}