blob: 5b3c0963e11958b17c26c4f97fafcad8c7a80fcd [file] [log] [blame]
/*
* 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.annotation.Nullable;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.Signature;
import android.os.AsyncResult;
import android.os.Binder;
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.io.FileDescriptor;
import java.io.PrintWriter;
import java.lang.IllegalArgumentException;
import java.lang.IndexOutOfBoundsException;
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.Arrays;
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.
*
* The spec for the rules:
* GP Secure Element Access Control:
* http://www.globalplatform.org/specifications/review/GPD_SE_Access_Control_v1.0.20.pdf
* Extension spec:
* https://code.google.com/p/seek-for-android/
*
*
* TODO: Notifications.
*
* {@hide}
*/
public class UiccCarrierPrivilegeRules extends Handler {
private static final String LOG_TAG = "UiccCarrierPrivilegeRules";
private static final String AID = "A00000015141434C00";
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 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: " + IccUtils.bytesToHexString(certificateHash) + " pkg: " +
packageName + " access: " + accessType;
}
}
// Used for parsing the data from the UICC.
private 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.
private Integer length;
private String value;
public TLV(String tag) {
this.tag = tag;
}
public String parseLength(String data) {
int offset = tag.length();
int firstByte = Integer.parseInt(data.substring(offset, offset + 2), 16);
// TODO: remove second condition before launch. b/18012893
if (firstByte < SINGLE_BYTE_MAX_LENGTH || (offset + 2 + firstByte * 2 == data.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);
}
Rlog.d(LOG_TAG, "TLV parseLength length=" + length + "lenghtBytes: " + lengthBytes);
return lengthBytes;
}
public String parse(String data, boolean shouldConsumeAll) {
Rlog.d(LOG_TAG, "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();
Rlog.d(LOG_TAG, "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);
Rlog.d(LOG_TAG, "Got TLV: " + tag + "," + length + "," + value);
return data.substring(index + length);
}
}
private UiccCard mUiccCard; // Parent
private AtomicInteger mState;
private List<AccessRule> mAccessRules;
private String mRules;
private Message mLoadedCallback;
private String mStatusMessage; // Only used for debugging.
private int mChannelId; // Channel Id for communicating with UICC.
public UiccCarrierPrivilegeRules(UiccCard uiccCard, Message loadedCallback) {
Rlog.d(LOG_TAG, "Creating UiccCarrierPrivilegeRules");
mUiccCard = uiccCard;
mState = new AtomicInteger(STATE_LOADING);
mStatusMessage = "Not loaded.";
mLoadedCallback = loadedCallback;
mRules = "";
// Start loading the rules.
mUiccCard.iccOpenLogicalChannel(AID,
obtainMessage(EVENT_OPEN_LOGICAL_CHANNEL_DONE, null));
}
/**
* Returns true if the carrier privilege rules have finished loading.
*/
public boolean areCarrierPriviligeRulesLoaded() {
return mState.get() != STATE_LOADING;
}
/**
* 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) {
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;
}
// SHA-1 is for backward compatible support only, strongly discouraged for new use.
byte[] certHash = getCertHash(signature, "SHA-1");
byte[] certHash256 = getCertHash(signature, "SHA-256");
Rlog.d(LOG_TAG, "Checking SHA1: " + IccUtils.bytesToHexString(certHash) + " : " + packageName);
Rlog.d(LOG_TAG, "Checking SHA256: " + IccUtils.bytesToHexString(certHash256) + " : " + packageName);
for (AccessRule ar : mAccessRules) {
if (ar.matches(certHash, packageName) || ar.matches(certHash256, packageName)) {
Rlog.d(LOG_TAG, "Match found!");
return TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS;
}
}
Rlog.d(LOG_TAG, "No matching rule found. Returning false.");
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 {
PackageInfo pInfo = packageManager.getPackageInfo(packageName,
PackageManager.GET_SIGNATURES);
Signature[] signatures = pInfo.signatures;
for (Signature sig : signatures) {
int accessStatus = getCarrierPrivilegeStatus(sig, pInfo.packageName);
if (accessStatus != TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS) {
return accessStatus;
}
}
} catch (PackageManager.NameNotFoundException ex) {
Rlog.e(LOG_TAG, "NameNotFoundException", ex);
}
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) {
String[] packages = packageManager.getPackagesForUid(Binder.getCallingUid());
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;
}
/**
* Returns the package name of the carrier app that should handle the input intent.
*
* @param packageManager PackageManager for getting receivers.
* @param intent Intent that will be sent.
* @return list of carrier app package names that can handle the intent.
* Returns null if there is an error and an empty list if there
* are no matching packages.
*/
public List<String> getCarrierPackageNamesForIntent(
PackageManager packageManager, Intent intent) {
List<String> packages = new ArrayList<String>();
List<ResolveInfo> receivers = new ArrayList<ResolveInfo>();
receivers.addAll(packageManager.queryBroadcastReceivers(intent, 0));
receivers.addAll(packageManager.queryIntentContentProviders(intent, 0));
receivers.addAll(packageManager.queryIntentActivities(intent, 0));
receivers.addAll(packageManager.queryIntentServices(intent, 0));
for (ResolveInfo resolveInfo : receivers) {
String packageName = getPackageName(resolveInfo);
if (packageName == null) {
continue;
}
int status = getCarrierPrivilegeStatus(packageManager, packageName);
if (status == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) {
packages.add(packageName);
} else if (status != TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS) {
// Any status apart from HAS_ACCESS and NO_ACCESS is considered an error.
return null;
}
}
return packages;
}
@Nullable
private String getPackageName(ResolveInfo resolveInfo) {
if (resolveInfo.activityInfo != null) {
return resolveInfo.activityInfo.packageName;
} else if (resolveInfo.serviceInfo != null) {
return resolveInfo.serviceInfo.packageName;
} else if (resolveInfo.providerInfo != null) {
return resolveInfo.providerInfo.packageName;
}
return null;
}
@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) {
mChannelId = ((int[]) ar.result)[0];
mUiccCard.iccTransmitApduLogicalChannel(mChannelId, CLA, COMMAND, P1, P2, P3, DATA,
obtainMessage(EVENT_TRANSMIT_LOGICAL_CHANNEL_DONE, new Integer(mChannelId)));
} else {
updateState(STATE_ERROR, "Error opening channel");
}
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.sw1 == 0x90 && response.sw2 == 0x00 &&
response.payload != null && response.payload.length > 0) {
try {
mRules += IccUtils.bytesToHexString(response.payload).toUpperCase(Locale.US);
if (isDataComplete()) {
mAccessRules = parseRules(mRules);
updateState(STATE_LOADED, "Success!");
} else {
mUiccCard.iccTransmitApduLogicalChannel(mChannelId, CLA, COMMAND, P1, P2_EXTENDED_DATA, P3, DATA,
obtainMessage(EVENT_TRANSMIT_LOGICAL_CHANNEL_DONE, new Integer(mChannelId)));
break;
}
} catch (IllegalArgumentException ex) {
updateState(STATE_ERROR, "Error parsing rules: " + ex);
} catch (IndexOutOfBoundsException ex) {
updateState(STATE_ERROR, "Error parsing rules: " + ex);
}
} else {
String errorMsg = "Invalid response: payload=" + response.payload +
" sw1=" + response.sw1 + " sw2=" + response.sw2;
updateState(STATE_ERROR, errorMsg);
}
} else {
updateState(STATE_ERROR, "Error reading value from SIM.");
}
mUiccCard.iccCloseLogicalChannel(mChannelId, obtainMessage(
EVENT_CLOSE_LOGICAL_CHANNEL_DONE));
mChannelId = -1;
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);
}
}
/*
* 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() {
Rlog.d(LOG_TAG, "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);
Rlog.d(LOG_TAG, "isDataComplete lengthBytes: " + lengthBytes);
if (mRules.length() == TAG_ALL_REF_AR_DO.length() + lengthBytes.length() +
allRules.length) {
Rlog.d(LOG_TAG, "isDataComplete yes");
return true;
} else {
Rlog.d(LOG_TAG, "isDataComplete no");
return false;
}
} else {
throw new IllegalArgumentException("Tags don't match.");
}
}
/*
* Parses the rules from the input string.
*/
private static List<AccessRule> parseRules(String rules) {
Rlog.d(LOG_TAG, "Got rules: " + rules);
TLV allRefArDo = new TLV(TAG_ALL_REF_AR_DO); //FF40
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); //E2
arDos = refArDo.parse(arDos, false);
AccessRule 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 AccessRule parseRefArdo(String rule) {
Rlog.d(LOG_TAG, "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);
// Skip unrelated rules.
if (!refDo.value.startsWith(TAG_DEVICE_APP_ID_REF_DO)) {
return null;
}
TLV deviceDo = new TLV(TAG_DEVICE_APP_ID_REF_DO); //C1
tmp = deviceDo.parse(refDo.value, false);
certificateHash = deviceDo.value;
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 unrelated rules.
if (!arDo.value.startsWith(TAG_PERM_AR_DO)) {
return null;
}
TLV permDo = new TLV(TAG_PERM_AR_DO); //DB
permDo.parse(arDo.value, true);
Rlog.e(LOG_TAG, permDo.value);
} else {
// Spec requires it must be either TAG_REF_DO or TAG_AR_DO.
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, String algo) {
try {
MessageDigest md = MessageDigest.getInstance(algo);
return md.digest(signature.toByteArray());
} catch (NoSuchAlgorithmException ex) {
Rlog.e(LOG_TAG, "NoSuchAlgorithmException: " + ex);
}
return null;
}
/*
* 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();
}
mStatusMessage = statusMessage;
Rlog.e(LOG_TAG, mStatusMessage);
}
/**
* Dumps info to Dumpsys - useful for debugging.
*/
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println("UiccCarrierPrivilegeRules: " + this);
pw.println(" mState=" + getStateString(mState.get()));
pw.println(" mStatusMessage='" + mStatusMessage + "'");
if (mAccessRules != null) {
pw.println(" mAccessRules: ");
for (AccessRule ar : mAccessRules) {
pw.println(" rule='" + ar + "'");
}
} else {
pw.println(" mAccessRules: null");
}
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";
}
}
}