SIM based carrier app privileges.

Adding support for SIM based carrier app privileges.

WIP - Missing bits:
- Notifications.
- Certificate check.

Change-Id: Ied3aa6c7d51cc0cde06f5bb58f30893d7a7b1c34
diff --git a/src/java/com/android/internal/telephony/uicc/UiccCard.java b/src/java/com/android/internal/telephony/uicc/UiccCard.java
index 3d8cb3c..2edff80 100644
--- a/src/java/com/android/internal/telephony/uicc/UiccCard.java
+++ b/src/java/com/android/internal/telephony/uicc/UiccCard.java
@@ -22,6 +22,7 @@
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.Intent;
+import android.content.pm.Signature;
 import android.content.res.Resources;
 import android.os.AsyncResult;
 import android.os.Handler;
@@ -30,6 +31,7 @@
 import android.os.Registrant;
 import android.os.RegistrantList;
 import android.telephony.Rlog;
+import android.telephony.TelephonyManager;
 import android.view.WindowManager;
 
 import com.android.internal.telephony.CommandsInterface;
@@ -72,6 +74,7 @@
     private CatService mCatService;
     private boolean mDestroyed = false; //set to true once this card is commanded to be disposed of.
     private RadioState mLastRadioState =  RadioState.RADIO_UNAVAILABLE;
+    private UiccCarrierPrivilegeRules mCarrierPrivilegeRules;
 
     private RegistrantList mAbsentRegistrants = new RegistrantList();
 
@@ -109,6 +112,7 @@
             }
             mCatService = null;
             mUiccApplications = null;
+            mCarrierPrivilegeRules = null;
         }
     }
 
@@ -146,6 +150,15 @@
             }
 
             createAndUpdateCatService();
+
+            // Reload the carrier privilege rules if necessary.
+            log("Before privilege rules: " + mCarrierPrivilegeRules + " : " + mCardState);
+            if (mCarrierPrivilegeRules == null && mCardState == CardState.CARDSTATE_PRESENT) {
+                mCarrierPrivilegeRules = new UiccCarrierPrivilegeRules(this);
+            } else if (mCarrierPrivilegeRules != null && mCardState != CardState.CARDSTATE_PRESENT) {
+                mCarrierPrivilegeRules = null;
+            }
+
             sanitizeApplicationIndexes();
 
             RadioState radioState = mCi.getRadioState();
@@ -442,6 +455,15 @@
         return count;
     }
 
+    /**
+     * Exposes {@link UiccCarrierPrivilegeRules.hasCarrierPrivileges}.
+     */
+    public int hasCarrierPrivileges(Signature signature, String packageName) {
+        return mCarrierPrivilegeRules == null ?
+            TelephonyManager.CARRIER_PRIVILEGE_STATUS_RULES_NOT_LOADED :
+            mCarrierPrivilegeRules.hasCarrierPrivileges(signature, packageName);
+    }
+
     private void log(String msg) {
         Rlog.d(LOG_TAG, msg);
     }
diff --git a/src/java/com/android/internal/telephony/uicc/UiccCarrierPrivilegeRules.java b/src/java/com/android/internal/telephony/uicc/UiccCarrierPrivilegeRules.java
new file mode 100644
index 0000000..8278fb2
--- /dev/null
+++ b/src/java/com/android/internal/telephony/uicc/UiccCarrierPrivilegeRules.java
@@ -0,0 +1,354 @@
+/*
+ * Copyright (C) 2014 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.internal.telephony.uicc;
+
+import android.content.pm.Signature;
+import android.os.AsyncResult;
+import android.os.Handler;
+import android.os.Message;
+import android.telephony.Rlog;
+import android.telephony.TelephonyManager;
+
+import com.android.internal.telephony.CommandsInterface;
+import com.android.internal.telephony.uicc.IccUtils;
+
+import java.io.ByteArrayInputStream;
+import java.lang.IllegalArgumentException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * Class that reads and stores the carrier privileged rules from the UICC.
+ *
+ * The rules are read when the class is created, hence it should only be created
+ * after the UICC can be read. And it should be deleted when a UICC is changed.
+ *
+ * The spec for the rules:
+ *     TODO: Put link here.
+ *
+ *
+ * TODO: Notifications.
+ *
+ * {@hide}
+ */
+public class UiccCarrierPrivilegeRules extends Handler {
+    private static final String LOG_TAG = "UiccCarrierPrivilegeRules";
+
+    // TODO: These are temporary values. Put real ones here.
+    private static final String AID = "A0000000015141434D";
+    private static final int CLA = 0x80;
+    private static final int COMMAND = 0xCA;
+    private static final int P1 = 0xFF;
+    private static final int P2 = 0x40;
+    private static final int P3 = 0x00;
+    private static final String DATA = "";
+
+    // Values from the data standard.
+    private static final String TAG_ALL_REF_AR_DO = "FF40";
+    private static final String TAG_REF_AR_DO = "E2";
+    private static final String TAG_REF_DO = "E1";
+    private static final String TAG_AR_DO = "E3";
+    private static final String TAG_DEVICE_APP_ID_REF_DO = "C1";
+    private static final String TAG_PERM_AR_DO = "DB";
+    private static final String TAG_PKG_REF_DO = "CA";
+
+    private static final int EVENT_OPEN_LOGICAL_CHANNEL_DONE = 1;
+    private static final int EVENT_TRANSMIT_LOGICAL_CHANNEL_DONE = 2;
+    private static final int EVENT_CLOSE_LOGICAL_CHANNEL_DONE = 3;
+
+    // State of the object.
+    private static final int STATE_LOADING  = 0;
+    private static final int STATE_LOADED   = 1;
+    private static final int STATE_ERROR    = 2;
+
+    // Describes a single rule.
+    private static class AccessRule {
+        public byte[] certificateHash;
+        public String packageName;
+        public long accessType;   // This bit is not currently used, but reserved for future use.
+
+        AccessRule(byte[] certificateHash, String packageName, long accessType) {
+            this.certificateHash = certificateHash;
+            this.packageName = packageName;
+            this.accessType = accessType;
+        }
+
+        boolean matches(byte[] certHash, String packageName) {
+          return certHash != null && Arrays.equals(this.certificateHash, certHash) &&
+                (this.packageName == null || this.packageName.equals(packageName));
+        }
+
+        @Override
+        public String toString() {
+            return "cert: " + certificateHash + " pkg: " + packageName +
+                " access: " + accessType;
+        }
+    }
+
+    // Used for parsing the data from the UICC.
+    private static class TLV {
+        private String tag;
+        private Integer length;
+        private String value;
+
+        public TLV(String tag) {
+            this.tag = tag;
+        }
+
+        public String parse(String data, boolean shouldConsumeAll) {
+            if (!data.startsWith(tag)) {
+                throw new IllegalArgumentException("Tags don't match.");
+            }
+            int index = tag.length();
+            if (index + 2 > data.length()) {
+                throw new IllegalArgumentException("No length.");
+            }
+            length = new Integer(2 * Integer.parseInt(
+                    data.substring(index, index + 2), 16));
+            index += 2;
+
+            int remainingLength = data.length() - (index + length);
+            if (remainingLength < 0) {
+                throw new IllegalArgumentException("Not enough data.");
+            }
+            if (shouldConsumeAll && (remainingLength != 0)) {
+                throw new IllegalArgumentException("Did not consume all.");
+            }
+            value = data.substring(index, index + length);
+
+            Rlog.e(LOG_TAG, "Got TLV: " + tag + "," + length + "," + value);
+
+            return data.substring(index + length);
+        }
+    }
+
+    private UiccCard mUiccCard;  // Parent
+    private AtomicInteger mState;
+    private List<AccessRule> mAccessRules;
+
+    public UiccCarrierPrivilegeRules(UiccCard uiccCard) {
+        Rlog.d(LOG_TAG, "Creating UiccCarrierPrivilegeRules");
+        mUiccCard = uiccCard;
+        mState = new AtomicInteger(STATE_LOADING);
+
+        // Start loading the rules.
+        mUiccCard.iccOpenLogicalChannel(AID,
+            obtainMessage(EVENT_OPEN_LOGICAL_CHANNEL_DONE, null));
+    }
+
+    /**
+     * Returns true if the certificate and packageName has carrier privileges.
+     *
+     * @param signature The signature of the certificate.
+     * @param packageName name of the package.
+     * @return Access status.
+     */
+    public int hasCarrierPrivileges(Signature signature, String packageName) {
+        Rlog.d(LOG_TAG, "hasCarrierPrivileges: " + signature + " : " + packageName);
+        int state = mState.get();
+        if (state == STATE_LOADING) {
+            Rlog.d(LOG_TAG, "Rules not loaded.");
+            return TelephonyManager.CARRIER_PRIVILEGE_STATUS_RULES_NOT_LOADED;
+        } else if (state == STATE_ERROR) {
+            Rlog.d(LOG_TAG, "Error loading rules.");
+            return TelephonyManager.CARRIER_PRIVILEGE_STATUS_ERROR_LOADING_RULES;
+        }
+
+        byte[] certHash = getCertHash(signature);
+        if (certHash == null) {
+          return TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS;
+        }
+        Rlog.e(LOG_TAG, "Checking: " + IccUtils.bytesToHexString(certHash) + " : " + packageName);
+
+        for (AccessRule ar : mAccessRules) {
+            if (ar.matches(certHash, packageName)) {
+                return TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS;
+            }
+        }
+
+        Rlog.d(LOG_TAG, "No matching rule found. Returning false.");
+        return TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS;
+    }
+
+    @Override
+    public void handleMessage(Message msg) {
+        AsyncResult ar;
+
+        switch (msg.what) {
+
+          case EVENT_OPEN_LOGICAL_CHANNEL_DONE:
+              Rlog.d(LOG_TAG, "EVENT_OPEN_LOGICAL_CHANNEL_DONE");
+              ar = (AsyncResult) msg.obj;
+              if (ar.exception == null && ar.result != null) {
+                  int channelId = ((int[]) ar.result)[0];
+                  mUiccCard.iccTransmitApduLogicalChannel(channelId, CLA, COMMAND, P1, P2, P3, DATA,
+                      obtainMessage(EVENT_TRANSMIT_LOGICAL_CHANNEL_DONE, new Integer(channelId)));
+              } else {
+                  Rlog.e(LOG_TAG, "Error opening channel");
+                  mState.set(STATE_ERROR);
+              }
+              break;
+
+          case EVENT_TRANSMIT_LOGICAL_CHANNEL_DONE:
+              Rlog.d(LOG_TAG, "EVENT_TRANSMIT_LOGICAL_CHANNEL_DONE");
+              ar = (AsyncResult) msg.obj;
+              if (ar.exception == null && ar.result != null) {
+                  IccIoResult response = (IccIoResult) ar.result;
+                  if (response.payload != null && response.sw1 == 0x90 && response.sw2 == 0x00) {
+                      try {
+                          mAccessRules = parseRules(IccUtils.bytesToHexString(response.payload));
+                          mState.set(STATE_LOADED);
+                      } catch (IllegalArgumentException ex) {
+                          Rlog.e(LOG_TAG, "Error parsing rules: " + ex);
+                          mState.set(STATE_ERROR);
+                      }
+                   } else {
+                      Rlog.e(LOG_TAG, "Invalid response: payload=" + response.payload +
+                              " sw1=" + response.sw1 + " sw2=" + response.sw2);
+                   }
+              } else {
+                  Rlog.e(LOG_TAG, "Error reading value from SIM.");
+                  mState.set(STATE_ERROR);
+              }
+
+              int channelId = (Integer) ar.userObj;
+              mUiccCard.iccCloseLogicalChannel(channelId, obtainMessage(
+                      EVENT_CLOSE_LOGICAL_CHANNEL_DONE));
+              break;
+
+          case EVENT_CLOSE_LOGICAL_CHANNEL_DONE:
+              Rlog.d(LOG_TAG, "EVENT_CLOSE_LOGICAL_CHANNEL_DONE");
+              break;
+
+          default:
+              Rlog.e(LOG_TAG, "Unknown event " + msg.what);
+        }
+    }
+
+    /*
+     * Parses the rules from the input string.
+     */
+    private static List<AccessRule> parseRules(String rules) {
+        rules = rules.toUpperCase(Locale.US);
+        Rlog.d(LOG_TAG, "Got rules: " + rules);
+
+        /*
+         * Rules format.
+         *   ALL_REF_AR_DO = TAG_ALL_REF_AR_DO + len + [REF_AR_DO]xn
+         *   REF_AR_DO = TAG_REF_AR_DO + len + REF-DO | AR-DO
+         */
+        TLV allRefArDo = new TLV(TAG_ALL_REF_AR_DO);
+        allRefArDo.parse(rules, true);
+
+        String arDos = allRefArDo.value;
+        List<AccessRule> accessRules = new ArrayList<AccessRule>();
+        while (!arDos.isEmpty()) {
+            TLV refArDo = new TLV(TAG_REF_AR_DO);
+            arDos = refArDo.parse(arDos, false);
+            accessRules.add(parseRefArdo(refArDo.value));
+        }
+        return accessRules;
+    }
+
+    /*
+     * Parses a single rule.
+     */
+    private static AccessRule parseRefArdo(String rule) {
+        Rlog.d(LOG_TAG, "Got rule: " + rule);
+
+        /*
+         *   REF_AR_DO = TAG_REF_AR_DO + len + REF-DO | AR-DO
+         *   REF_DO = TAG_REF_DO + len + DEVICE_APP_ID_REF_DO | PKG_REF_DO
+         *   AR_DO = TAG_AR_DO + len + PERM_AR_DO
+         *   DEVICE_APP_ID_REF_DO = TAG_DEVICE_APP_ID_REF_DO + 20 | 0 + 20 byte hash (padded with FF)
+         *   PKG_REF_DO = TAG_PKG_REF_DO + 20 + 20 byte hash of package name (padded with FF)
+         *   PERM_AR_DO = TAG_PERM_AR_DO + 8 + 8 bytes
+         */
+
+        String certificateHash = null;
+        String packageName = null;
+        long accessType = 0;
+
+        while (!rule.isEmpty()) {
+            if (rule.startsWith(TAG_REF_DO)) {
+                TLV refDo = new TLV(TAG_REF_DO);
+                rule = refDo.parse(rule, false);
+
+                if (refDo.value.startsWith(TAG_DEVICE_APP_ID_REF_DO)) {
+                    TLV deviceDo = new TLV(TAG_DEVICE_APP_ID_REF_DO);
+                    deviceDo.parse(refDo.value, true);
+                    certificateHash = deviceDo.value;
+                } else if (refDo.value.startsWith(TAG_PKG_REF_DO)) {
+                    TLV pkgDo = new TLV(TAG_PKG_REF_DO);
+                    pkgDo.parse(refDo.value, true);
+                    packageName = pkgDo.value;
+                } else {
+                    throw new IllegalArgumentException(
+                        "Invalid REF_DO value tag: " + refDo.value);
+                }
+            } else if (rule.startsWith(TAG_AR_DO)) {
+                TLV arDo = new TLV(TAG_AR_DO);
+                rule = arDo.parse(rule, false);
+
+                TLV permDo = new TLV(TAG_PERM_AR_DO);
+                permDo.parse(arDo.value, true);
+                Rlog.e(LOG_TAG, permDo.value);
+            } else  {
+                // TODO: Mayabe just throw away rules that are not parseable.
+                throw new RuntimeException("Invalid Rule type");
+            }
+        }
+
+        Rlog.e(LOG_TAG, "Adding: " + certificateHash + " : " + packageName + " : " + accessType);
+
+        AccessRule accessRule = new AccessRule(IccUtils.hexStringToBytes(certificateHash),
+            packageName, accessType);
+        Rlog.e(LOG_TAG, "Parsed rule: " + accessRule);
+        return accessRule;
+    }
+
+    /*
+     * Converts a Signature into a Certificate hash usable for comparison.
+     */
+    private static byte[] getCertHash(Signature signature) {
+        // TODO: Is the following sufficient.
+        try {
+            CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
+            X509Certificate cert = (X509Certificate) certFactory.generateCertificate(
+                    new ByteArrayInputStream(signature.toByteArray()));
+
+            MessageDigest md = MessageDigest.getInstance("SHA");
+            return md.digest(cert.getEncoded());
+        } catch (CertificateException ex) {
+            Rlog.e(LOG_TAG, "CertificateException: " + ex);
+        } catch (NoSuchAlgorithmException ex) {
+            Rlog.e(LOG_TAG, "NoSuchAlgorithmException: " + ex);
+        }
+
+        Rlog.e(LOG_TAG, "Cannot compute cert hash");
+        return null;
+    }
+}