blob: 55ddef8e128c1e1fa3384ff8872c0d4f8b9a42cd [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.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.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:
* 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 = 0xB0;
private static final int P1 = 0x00;
private static final int P2 = 0x00;
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 + sha1 hexstring of cert (20 bytes)
* 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: " + 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) {
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.");
}
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.d(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 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;
}
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)) {
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;
}
/**
* Given an intent, returns the package name of the carrier app that should handle the intent.
*
* @param packageManager PackageManager for getting receivers.
* @param intent Intent that will be broadcast.
* @return packageName name of package that should handle the intent. Will return an empty
* string if no matching package is found.
*/
public String getCarrierPackageNameForBroadcastIntent(
PackageManager packageManager, Intent intent) {
List<ResolveInfo> receivers = packageManager.queryBroadcastReceivers(intent, 0);
for (ResolveInfo resolveInfo : receivers) {
if (resolveInfo.activityInfo == null) {
continue;
}
String packageName = resolveInfo.activityInfo.packageName;
if (getCarrierPrivilegeStatus(packageManager, packageName) ==
TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) {
return packageName;
}
}
// Return an empty package name so that no packages match.
// TODO: This creates an unnecessary ordered broadcast that can be avoided.
return "";
}
@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);
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);
accessRules.add(parseRefArdo(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);
TLV deviceDo = new TLV(TAG_DEVICE_APP_ID_REF_DO); //C1
tmp = deviceDo.parse(refDo.value, false);
certificateHash = deviceDo.value;
if (!tmp.isEmpty()) {
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);
TLV permDo = new TLV(TAG_PERM_AR_DO); //DB
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;
}
}