| /* |
| * 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.compat.annotation.UnsupportedAppUsage; |
| import android.content.pm.PackageInfo; |
| import android.content.pm.PackageManager; |
| import android.content.pm.Signature; |
| import android.os.AsyncResult; |
| import android.os.Binder; |
| import android.os.Build; |
| import android.os.Handler; |
| import android.os.Message; |
| import android.telephony.TelephonyManager; |
| import android.telephony.UiccAccessRule; |
| import android.text.TextUtils; |
| import android.util.IndentingPrintWriter; |
| import android.util.LocalLog; |
| |
| import com.android.internal.annotations.VisibleForTesting; |
| import com.android.internal.telephony.CommandException; |
| import com.android.telephony.Rlog; |
| |
| import java.io.FileDescriptor; |
| import java.io.PrintWriter; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.Locale; |
| import java.util.concurrent.atomic.AtomicInteger; |
| |
| /** |
| * 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. |
| * |
| * Document: https://source.android.com/devices/tech/config/uicc.html |
| * |
| * {@hide} |
| */ |
| public class UiccCarrierPrivilegeRules extends Handler { |
| private static final String LOG_TAG = "UiccCarrierPrivilegeRules"; |
| private static final boolean DBG = false; |
| |
| private static final String ARAM_AID = "A00000015141434C00"; |
| private static final String ARAD_AID = "A00000015144414300"; |
| private static final int ARAM = 1; |
| private static final int ARAD = 0; |
| 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 P2_EXTENDED_DATA = 0x60; |
| private static final int P3 = 0x00; |
| private static final String DATA = ""; |
| |
| /* |
| * Rules format: |
| * ALL_REF_AR_DO = TAG_ALL_REF_AR_DO + len + [REF_AR_DO]*n |
| * REF_AR_DO = TAG_REF_AR_DO + len + REF-DO + AR-DO |
| * |
| * REF_DO = TAG_REF_DO + len + DEVICE_APP_ID_REF_DO + (optional) PKG_REF_DO |
| * AR_DO = TAG_AR_DO + len + PERM_AR_DO |
| * |
| * DEVICE_APP_ID_REF_DO = TAG_DEVICE_APP_ID_REF_DO + len + sha256 hexstring of cert |
| * PKG_REF_DO = TAG_PKG_REF_DO + len + package name |
| * PERM_AR_DO = TAG_PERM_AR_DO + len + detailed permission (8 bytes) |
| * |
| * Data objects hierarchy by TAG: |
| * FF40 |
| * E2 |
| * E1 |
| * C1 |
| * CA |
| * E3 |
| * DB |
| */ |
| // 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_DEVICE_APP_ID_REF_DO = "C1"; |
| private static final String TAG_PKG_REF_DO = "CA"; |
| private static final String TAG_AR_DO = "E3"; |
| private static final String TAG_PERM_AR_DO = "DB"; |
| private static final String TAG_AID_REF_DO = "4F"; |
| private static final String CARRIER_PRIVILEGE_AID = "FFFFFFFFFFFF"; |
| |
| 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; |
| private static final int EVENT_PKCS15_READ_DONE = 4; |
| |
| // 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; |
| |
| // Max number of retries for open logical channel, interval is 5s. |
| private static final int MAX_RETRY = 2; |
| private static final int RETRY_INTERVAL_MS = 5000; |
| private static final int STATUS_CODE_CONDITION_NOT_SATISFIED = 0x6985; |
| private static final int STATUS_CODE_APPLET_SELECT_FAILED = 0x6999; |
| |
| // Used for parsing the data from the UICC. |
| public static class TLV { |
| private static final int SINGLE_BYTE_MAX_LENGTH = 0x80; |
| private String tag; |
| // Length encoding is in GPC_Specification_2.2.1: 11.1.5 APDU Message and Data Length. |
| // Length field could be either 1 byte if length < 128, or multiple bytes with first byte |
| // specifying how many bytes are used for length, followed by length bytes. |
| // Bytes for the length field, in ASCII HEX string form. |
| private String lengthBytes; |
| // Decoded length as integer. |
| @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) |
| private Integer length; |
| @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) |
| private String value; |
| |
| public TLV(String tag) { |
| this.tag = tag; |
| } |
| |
| public String getValue() { |
| if (value == null) return ""; |
| return value; |
| } |
| |
| public String parseLength(String data) { |
| int offset = tag.length(); |
| int firstByte = Integer.parseInt(data.substring(offset, offset + 2), 16); |
| if (firstByte < SINGLE_BYTE_MAX_LENGTH) { |
| length = firstByte * 2; |
| lengthBytes = data.substring(offset, offset + 2); |
| } else { |
| int numBytes = firstByte - SINGLE_BYTE_MAX_LENGTH; |
| length = Integer.parseInt(data.substring(offset + 2, offset + 2 + numBytes * 2), 16) * 2; |
| lengthBytes = data.substring(offset, offset + 2 + numBytes * 2); |
| } |
| log("TLV parseLength length=" + length + "lenghtBytes: " + lengthBytes); |
| return lengthBytes; |
| } |
| |
| public String parse(String data, boolean shouldConsumeAll) { |
| log("Parse TLV: " + tag); |
| 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."); |
| } |
| |
| parseLength(data); |
| index += lengthBytes.length(); |
| |
| log("index="+index+" length="+length+"data.length="+data.length()); |
| 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); |
| |
| log("Got TLV: " + tag + "," + length + "," + value); |
| |
| return data.substring(index + length); |
| } |
| } |
| |
| private UiccProfile mUiccProfile; // Parent |
| private UiccPkcs15 mUiccPkcs15; // ARF fallback |
| @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) |
| private AtomicInteger mState; |
| private List<UiccAccessRule> mAccessRules; |
| private String mRules; |
| @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) |
| private Message mLoadedCallback; |
| // LocalLog buffer to hold important status messages for debugging. |
| private LocalLog mStatusMessage = new LocalLog(64); |
| private int mChannelId; // Channel Id for communicating with UICC. |
| private int mRetryCount; // Number of retries for open logical channel. |
| private boolean mCheckedRules = false; // Flag that used to mark whether get rules from ARA-D. |
| private int mAIDInUse; // Message component to identify which AID is currently in-use. |
| private final Runnable mRetryRunnable = new Runnable() { |
| @Override |
| public void run() { |
| openChannel(mAIDInUse); |
| } |
| }; |
| |
| private void openChannel(int aidId) { |
| // Send open logical channel request. |
| String aid = (aidId == ARAD) ? ARAD_AID : ARAM_AID; |
| int p2 = 0x00; |
| mUiccProfile.iccOpenLogicalChannel(aid, p2, /* supported p2 value */ |
| obtainMessage(EVENT_OPEN_LOGICAL_CHANNEL_DONE, 0, aidId, null)); |
| } |
| |
| public UiccCarrierPrivilegeRules(UiccProfile uiccProfile, Message loadedCallback) { |
| log("Creating UiccCarrierPrivilegeRules"); |
| mUiccProfile = uiccProfile; |
| mState = new AtomicInteger(STATE_LOADING); |
| mStatusMessage.log("Not loaded."); |
| mLoadedCallback = loadedCallback; |
| mRules = ""; |
| mAccessRules = new ArrayList<>(); |
| |
| // Open logical channel with ARA_D. |
| mAIDInUse = ARAD; |
| openChannel(mAIDInUse); |
| } |
| |
| @VisibleForTesting |
| public UiccCarrierPrivilegeRules(List<UiccAccessRule> rules) { |
| mAccessRules = rules; |
| mState = new AtomicInteger(STATE_LOADED); |
| mRules = ""; |
| mStatusMessage.log("Loaded from test rules."); |
| } |
| |
| /** |
| * Returns true if the carrier privilege rules have finished loading. |
| */ |
| public boolean areCarrierPriviligeRulesLoaded() { |
| return mState.get() != STATE_LOADING; |
| } |
| |
| /** |
| * Returns true if the carrier privilege rules have finished loading and some rules were |
| * specified. |
| */ |
| public boolean hasCarrierPrivilegeRules() { |
| return mState.get() != STATE_LOADING && mAccessRules != null && mAccessRules.size() > 0; |
| } |
| |
| /** |
| * Returns package names for privilege rules. |
| * Return empty list if no rules defined or package name is empty string. |
| */ |
| public List<String> getPackageNames() { |
| List<String> pkgNames = new ArrayList<String>(); |
| if (mAccessRules != null) { |
| for (UiccAccessRule ar : mAccessRules) { |
| if (!TextUtils.isEmpty(ar.getPackageName())) { |
| pkgNames.add(ar.getPackageName()); |
| } |
| } |
| } |
| return pkgNames; |
| } |
| |
| /** |
| * Returns list of access rules. |
| */ |
| public List<UiccAccessRule> getAccessRules() { |
| if (mAccessRules == null) { |
| return Collections.emptyList(); |
| } |
| return Collections.unmodifiableList(mAccessRules); |
| } |
| |
| /** |
| * Returns the status of the carrier privileges for the input certificate and package name. |
| * |
| * @param signature The signature of the certificate. |
| * @param packageName name of the package. |
| * @return Access status. |
| */ |
| public int getCarrierPrivilegeStatus(Signature signature, String packageName) { |
| int state = mState.get(); |
| if (state == STATE_LOADING) { |
| return TelephonyManager.CARRIER_PRIVILEGE_STATUS_RULES_NOT_LOADED; |
| } else if (state == STATE_ERROR) { |
| return TelephonyManager.CARRIER_PRIVILEGE_STATUS_ERROR_LOADING_RULES; |
| } |
| |
| for (UiccAccessRule ar : mAccessRules) { |
| int accessStatus = ar.getCarrierPrivilegeStatus(signature, packageName); |
| if (accessStatus != TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS) { |
| return accessStatus; |
| } |
| } |
| |
| return TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS; |
| } |
| |
| /** |
| * Returns the status of the carrier privileges for the input package name. |
| * |
| * @param packageManager PackageManager for getting signatures. |
| * @param packageName name of the package. |
| * @return Access status. |
| */ |
| public int getCarrierPrivilegeStatus(PackageManager packageManager, String packageName) { |
| try { |
| // Short-circuit if there are no rules to check against, so we don't need to fetch |
| // the package info with signatures. |
| if (!hasCarrierPrivilegeRules()) { |
| int state = mState.get(); |
| if (state == STATE_LOADING) { |
| return TelephonyManager.CARRIER_PRIVILEGE_STATUS_RULES_NOT_LOADED; |
| } else if (state == STATE_ERROR) { |
| return TelephonyManager.CARRIER_PRIVILEGE_STATUS_ERROR_LOADING_RULES; |
| } |
| return TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS; |
| } |
| // Include DISABLED_UNTIL_USED components. This facilitates cases where a carrier app |
| // is disabled by default, and some other component wants to enable it when it has |
| // gained carrier privileges (as an indication that a matching SIM has been inserted). |
| PackageInfo pInfo = packageManager.getPackageInfo(packageName, |
| PackageManager.GET_SIGNING_CERTIFICATES |
| | PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS |
| | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS); |
| return getCarrierPrivilegeStatus(pInfo); |
| } catch (PackageManager.NameNotFoundException ex) { |
| log("Package " + packageName + " not found for carrier privilege status check"); |
| } |
| return TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS; |
| } |
| |
| /** |
| * Returns the status of the carrier privileges for the input package info. |
| * |
| * @param packageInfo PackageInfo for the package, containing the package signatures. |
| * @return Access status. |
| */ |
| public int getCarrierPrivilegeStatus(PackageInfo packageInfo) { |
| int state = mState.get(); |
| if (state == STATE_LOADING) { |
| return TelephonyManager.CARRIER_PRIVILEGE_STATUS_RULES_NOT_LOADED; |
| } else if (state == STATE_ERROR) { |
| return TelephonyManager.CARRIER_PRIVILEGE_STATUS_ERROR_LOADING_RULES; |
| } |
| |
| for (UiccAccessRule ar : mAccessRules) { |
| int accessStatus = ar.getCarrierPrivilegeStatus(packageInfo); |
| if (accessStatus != TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS) { |
| return accessStatus; |
| } |
| } |
| return TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS; |
| } |
| |
| /** |
| * Returns the status of the carrier privileges for the caller of the current transaction. |
| * |
| * @param packageManager PackageManager for getting signatures and package names. |
| * @return Access status. |
| */ |
| public int getCarrierPrivilegeStatusForCurrentTransaction(PackageManager packageManager) { |
| return getCarrierPrivilegeStatusForUid(packageManager, Binder.getCallingUid()); |
| } |
| |
| /** |
| * Returns the status of the carrier privileges for the caller of the current transaction. |
| * |
| * @param packageManager PackageManager for getting signatures and package names. |
| * @return Access status. |
| */ |
| public int getCarrierPrivilegeStatusForUid( |
| PackageManager packageManager, int uid) { |
| String[] packages = packageManager.getPackagesForUid(uid); |
| |
| if (packages != null) { |
| for (String pkg : packages) { |
| int accessStatus = getCarrierPrivilegeStatus(packageManager, pkg); |
| if (accessStatus != TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS) { |
| return accessStatus; |
| } |
| } |
| } |
| return TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS; |
| } |
| |
| /** |
| * The following three situations could be due to logical channels temporarily unavailable, so |
| * we retry up to MAX_RETRY times, with an interval of RETRY_INTERVAL_MS: 1. MISSING_RESOURCE, |
| * 2. NO_SUCH_ELEMENT and the status code is 6985, 3. INTERNAL_ERR and the status code is 6999. |
| */ |
| public static boolean shouldRetry(AsyncResult ar, int retryCount) { |
| if (ar.exception instanceof CommandException && retryCount < MAX_RETRY) { |
| CommandException.Error error = ((CommandException) (ar.exception)).getCommandError(); |
| int[] results = (int[]) ar.result; |
| int statusCode = 0; |
| if (ar.result != null && results.length == 3) { |
| byte[] bytes = new byte[]{(byte) results[1], (byte) results[2]}; |
| statusCode = Integer.parseInt(IccUtils.bytesToHexString(bytes), 16); |
| log("status code: " + String.valueOf(statusCode)); |
| } |
| return (error == CommandException.Error.MISSING_RESOURCE) |
| || (error == CommandException.Error.NO_SUCH_ELEMENT |
| && statusCode == STATUS_CODE_CONDITION_NOT_SATISFIED) |
| || (error == CommandException.Error.INTERNAL_ERR |
| && statusCode == STATUS_CODE_APPLET_SELECT_FAILED); |
| } |
| return false; |
| } |
| |
| @Override |
| public void handleMessage(Message msg) { |
| AsyncResult ar; |
| mAIDInUse = msg.arg2; // 0 means ARA-D and 1 means ARA-M. |
| |
| switch (msg.what) { |
| |
| case EVENT_OPEN_LOGICAL_CHANNEL_DONE: |
| log("EVENT_OPEN_LOGICAL_CHANNEL_DONE"); |
| ar = (AsyncResult) msg.obj; |
| if (ar.exception == null && ar.result != null) { |
| mChannelId = ((int[]) ar.result)[0]; |
| mUiccProfile.iccTransmitApduLogicalChannel(mChannelId, CLA, COMMAND, P1, P2, P3, |
| DATA, false /*isEs10Command*/, obtainMessage( |
| EVENT_TRANSMIT_LOGICAL_CHANNEL_DONE, mChannelId, mAIDInUse)); |
| } else { |
| if (shouldRetry(ar, mRetryCount)) { |
| log("should retry"); |
| mRetryCount++; |
| removeCallbacks(mRetryRunnable); |
| postDelayed(mRetryRunnable, RETRY_INTERVAL_MS); |
| } else { |
| if (mAIDInUse == ARAD) { |
| // Open logical channel with ARA_M. |
| mRules = ""; |
| openChannel(ARAM); |
| } |
| if (mAIDInUse == ARAM) { |
| if (mCheckedRules) { |
| updateState(STATE_LOADED, "Success!"); |
| } else { |
| // if rules cannot be read from both ARA_D and ARA_M applet, |
| // fallback to PKCS15-based ARF. |
| log("No ARA, try ARF next."); |
| if (ar.exception instanceof CommandException |
| && ((CommandException) (ar.exception)).getCommandError() |
| != CommandException.Error.NO_SUCH_ELEMENT) { |
| updateStatusMessage("No ARA due to " |
| + |
| ((CommandException) (ar.exception)).getCommandError()); |
| } |
| mUiccPkcs15 = new UiccPkcs15(mUiccProfile, |
| obtainMessage(EVENT_PKCS15_READ_DONE)); |
| } |
| } |
| } |
| } |
| break; |
| |
| case EVENT_TRANSMIT_LOGICAL_CHANNEL_DONE: |
| log("EVENT_TRANSMIT_LOGICAL_CHANNEL_DONE"); |
| ar = (AsyncResult) msg.obj; |
| if (ar.exception == null && ar.result != null) { |
| IccIoResult response = (IccIoResult) ar.result; |
| if (response.sw1 == 0x90 && response.sw2 == 0x00 |
| && response.payload != null && response.payload.length > 0) { |
| try { |
| mRules += IccUtils.bytesToHexString(response.payload) |
| .toUpperCase(Locale.US); |
| if (isDataComplete()) { |
| //TODO: here's where AccessRules are being updated from the psim |
| // b/139133814 |
| mAccessRules.addAll(parseRules(mRules)); |
| if (mAIDInUse == ARAD) { |
| mCheckedRules = true; |
| } else { |
| updateState(STATE_LOADED, "Success!"); |
| } |
| } else { |
| mUiccProfile.iccTransmitApduLogicalChannel(mChannelId, CLA, COMMAND, |
| P1, P2_EXTENDED_DATA, P3, DATA, false /*isEs10Command*/, |
| obtainMessage(EVENT_TRANSMIT_LOGICAL_CHANNEL_DONE, |
| mChannelId, mAIDInUse)); |
| break; |
| } |
| } catch (IllegalArgumentException | IndexOutOfBoundsException ex) { |
| if (mAIDInUse == ARAM) { |
| updateState(STATE_ERROR, "Error parsing rules: " + ex); |
| } |
| } |
| } else { |
| if (mAIDInUse == ARAM) { |
| String errorMsg = "Invalid response: payload=" |
| + IccUtils.bytesToHexString(response.payload) |
| + " sw1=" + response.sw1 + " sw2=" + response.sw2; |
| updateState(STATE_ERROR, errorMsg); |
| } |
| } |
| } else { |
| String errorMsg = "Error reading value from SIM via " |
| + ((mAIDInUse == ARAD) ? "ARAD" : "ARAM") + " due to "; |
| if (ar.exception instanceof CommandException) { |
| CommandException.Error errorCode = |
| ((CommandException) (ar.exception)).getCommandError(); |
| errorMsg += "error code : " + errorCode; |
| } else { |
| errorMsg += "unknown exception : " + ar.exception.getMessage(); |
| } |
| if (mAIDInUse == ARAD) { |
| updateStatusMessage(errorMsg); |
| } else { |
| updateState(STATE_ERROR, errorMsg); |
| } |
| } |
| |
| mUiccProfile.iccCloseLogicalChannel(mChannelId, false /*isEs10*/, obtainMessage( |
| EVENT_CLOSE_LOGICAL_CHANNEL_DONE, 0, mAIDInUse)); |
| mChannelId = -1; |
| break; |
| |
| case EVENT_CLOSE_LOGICAL_CHANNEL_DONE: |
| log("EVENT_CLOSE_LOGICAL_CHANNEL_DONE"); |
| if (mAIDInUse == ARAD) { |
| // Close logical channel with ARA_D and then open logical channel with ARA_M. |
| mRules = ""; |
| openChannel(ARAM); |
| } |
| break; |
| |
| case EVENT_PKCS15_READ_DONE: |
| log("EVENT_PKCS15_READ_DONE"); |
| if (mUiccPkcs15 == null || mUiccPkcs15.getRules() == null) { |
| updateState(STATE_ERROR, "No ARA or ARF."); |
| } else { |
| for (String cert : mUiccPkcs15.getRules()) { |
| UiccAccessRule accessRule = new UiccAccessRule( |
| IccUtils.hexStringToBytes(cert), "", 0x00); |
| mAccessRules.add(accessRule); |
| } |
| updateState(STATE_LOADED, "Success!"); |
| } |
| break; |
| |
| default: |
| Rlog.e(LOG_TAG, "Unknown event " + msg.what); |
| } |
| } |
| |
| /* |
| * Check if all rule bytes have been read from UICC. |
| * For long payload, we need to fetch it repeatly before start parsing it. |
| */ |
| private boolean isDataComplete() { |
| log("isDataComplete mRules:" + mRules); |
| if (mRules.startsWith(TAG_ALL_REF_AR_DO)) { |
| TLV allRules = new TLV(TAG_ALL_REF_AR_DO); |
| String lengthBytes = allRules.parseLength(mRules); |
| log("isDataComplete lengthBytes: " + lengthBytes); |
| if (mRules.length() == TAG_ALL_REF_AR_DO.length() + lengthBytes.length() + |
| allRules.length) { |
| log("isDataComplete yes"); |
| return true; |
| } else { |
| log("isDataComplete no"); |
| return false; |
| } |
| } else { |
| throw new IllegalArgumentException("Tags don't match."); |
| } |
| } |
| |
| /* |
| * Parses the rules from the input string. |
| */ |
| private static List<UiccAccessRule> parseRules(String rules) { |
| log("Got rules: " + rules); |
| |
| TLV allRefArDo = new TLV(TAG_ALL_REF_AR_DO); //FF40 |
| allRefArDo.parse(rules, true); |
| |
| String arDos = allRefArDo.value; |
| List<UiccAccessRule> accessRules = new ArrayList<>(); |
| while (!arDos.isEmpty()) { |
| TLV refArDo = new TLV(TAG_REF_AR_DO); //E2 |
| arDos = refArDo.parse(arDos, false); |
| UiccAccessRule accessRule = parseRefArdo(refArDo.value); |
| if (accessRule != null) { |
| accessRules.add(accessRule); |
| } else { |
| Rlog.e(LOG_TAG, "Skip unrecognized rule." + refArDo.value); |
| } |
| } |
| return accessRules; |
| } |
| |
| /* |
| * Parses a single rule. |
| */ |
| private static UiccAccessRule parseRefArdo(String rule) { |
| log("Got rule: " + rule); |
| |
| String certificateHash = null; |
| String packageName = null; |
| String tmp = null; |
| long accessType = 0; |
| |
| while (!rule.isEmpty()) { |
| if (rule.startsWith(TAG_REF_DO)) { |
| TLV refDo = new TLV(TAG_REF_DO); //E1 |
| rule = refDo.parse(rule, false); |
| // Allow 4F tag with a default value "FF FF FF FF FF FF" to be compatible with |
| // devices having GP access control enforcer: |
| // - If no 4F tag is present, it's a CP rule. |
| // - If 4F tag has value "FF FF FF FF FF FF", it's a CP rule. |
| // - If 4F tag has other values, it's not a CP rule and Android should ignore it. |
| TLV deviceDo = new TLV(TAG_DEVICE_APP_ID_REF_DO); //C1 |
| if (refDo.value.startsWith(TAG_AID_REF_DO)) { |
| TLV cpDo = new TLV(TAG_AID_REF_DO); //4F |
| String remain = cpDo.parse(refDo.value, false); |
| if (!cpDo.lengthBytes.equals("06") || !cpDo.value.equals(CARRIER_PRIVILEGE_AID) |
| || remain.isEmpty() || !remain.startsWith(TAG_DEVICE_APP_ID_REF_DO)) { |
| return null; |
| } |
| tmp = deviceDo.parse(remain, false); |
| certificateHash = deviceDo.value; |
| } else if (refDo.value.startsWith(TAG_DEVICE_APP_ID_REF_DO)) { |
| tmp = deviceDo.parse(refDo.value, false); |
| certificateHash = deviceDo.value; |
| } else { |
| return null; |
| } |
| if (!tmp.isEmpty()) { |
| if (!tmp.startsWith(TAG_PKG_REF_DO)) { |
| return null; |
| } |
| TLV pkgDo = new TLV(TAG_PKG_REF_DO); //CA |
| pkgDo.parse(tmp, true); |
| packageName = new String(IccUtils.hexStringToBytes(pkgDo.value)); |
| } else { |
| packageName = null; |
| } |
| } else if (rule.startsWith(TAG_AR_DO)) { |
| TLV arDo = new TLV(TAG_AR_DO); //E3 |
| rule = arDo.parse(rule, false); |
| // Skip all the irrelevant tags (All the optional tags here are two bytes |
| // according to the spec GlobalPlatform Secure Element Access Control). |
| String remain = arDo.value; |
| while (!remain.isEmpty() && !remain.startsWith(TAG_PERM_AR_DO)) { |
| TLV tmpDo = new TLV(remain.substring(0, 2)); |
| remain = tmpDo.parse(remain, false); |
| } |
| if (remain.isEmpty()) { |
| return null; |
| } |
| TLV permDo = new TLV(TAG_PERM_AR_DO); //DB |
| permDo.parse(remain, true); |
| } else { |
| // Spec requires it must be either TAG_REF_DO or TAG_AR_DO. |
| throw new RuntimeException("Invalid Rule type"); |
| } |
| } |
| |
| UiccAccessRule accessRule = new UiccAccessRule( |
| IccUtils.hexStringToBytes(certificateHash), packageName, accessType); |
| return accessRule; |
| } |
| |
| /* |
| * Updates the state and notifies the UiccCard that the rules have finished loading. |
| */ |
| private void updateState(int newState, String statusMessage) { |
| mState.set(newState); |
| if (mLoadedCallback != null) { |
| mLoadedCallback.sendToTarget(); |
| } |
| |
| updateStatusMessage(statusMessage); |
| } |
| |
| private void updateStatusMessage(String statusMessage) { |
| mStatusMessage.log(statusMessage); |
| } |
| |
| private static void log(String msg) { |
| if (DBG) Rlog.d(LOG_TAG, msg); |
| } |
| |
| /** |
| * Dumps info to Dumpsys - useful for debugging. |
| */ |
| public void dump(FileDescriptor fd, PrintWriter printWriter, String[] args) { |
| IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, " "); |
| pw.println("UiccCarrierPrivilegeRules:"); |
| pw.increaseIndent(); |
| pw.println("mState=" + getStateString(mState.get())); |
| pw.println("mStatusMessage="); |
| mStatusMessage.dump(fd, pw, args); |
| if (mAccessRules != null) { |
| pw.println("mAccessRules: "); |
| pw.increaseIndent(); |
| for (UiccAccessRule ar : mAccessRules) { |
| pw.println(" rule='" + ar + "'"); |
| } |
| pw.decreaseIndent(); |
| } else { |
| pw.println(" mAccessRules: null"); |
| } |
| if (mUiccPkcs15 != null) { |
| pw.println(" mUiccPkcs15: " + mUiccPkcs15); |
| mUiccPkcs15.dump(fd, pw, args); |
| } else { |
| pw.println(" mUiccPkcs15: null"); |
| } |
| pw.decreaseIndent(); |
| pw.flush(); |
| } |
| |
| /* |
| * Converts state into human readable format. |
| */ |
| private String getStateString(int state) { |
| switch (state) { |
| case STATE_LOADING: |
| return "STATE_LOADING"; |
| case STATE_LOADED: |
| return "STATE_LOADED"; |
| case STATE_ERROR: |
| return "STATE_ERROR"; |
| default: |
| return "UNKNOWN"; |
| } |
| } |
| } |