blob: cc99ae9b8fed58174b5735bb02daf9d28a5d8840 [file] [log] [blame]
/*
* Copyright (C) 2016 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.os.AsyncResult;
import android.os.Handler;
import android.os.Message;
import android.telephony.Rlog;
import com.android.internal.telephony.uicc.UiccCarrierPrivilegeRules.TLV;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
/**
* Class that reads PKCS15-based rules for carrier privileges.
*
* The spec for the rules:
* GP Secure Element Access Control:
* https://www.globalplatform.org/specificationsdevice.asp
*
* The UiccPkcs15 class handles overall flow of finding/selecting PKCS15 applet
* and reading/parsing each file. Because PKCS15 can be selected in 2 different ways:
* via logical channel or EF_DIR, PKCS15Selector is a handler to encapsulate the flow.
* Similarly, FileHandler is used for selecting/reading each file, so common codes are
* all in same place.
*
* {@hide}
*/
public class UiccPkcs15 extends Handler {
private static final String LOG_TAG = "UiccPkcs15";
private static final boolean DBG = true;
// File handler for PKCS15 files, select file and read binary,
// convert to String then send to callback message.
private class FileHandler extends Handler {
// EF path for PKCS15 root, eg. "3F007F50"
// null if logical channel is used for PKCS15 access.
private final String mPkcs15Path;
// Message to send when file has been parsed.
private Message mCallback;
// File id to read data from, eg. "5031"
private String mFileId;
// async events for the sequence of select and read
static protected final int EVENT_SELECT_FILE_DONE = 101;
static protected final int EVENT_READ_BINARY_DONE = 102;
// pkcs15Path is nullable when using logical channel
public FileHandler(String pkcs15Path) {
log("Creating FileHandler, pkcs15Path: " + pkcs15Path);
mPkcs15Path = pkcs15Path;
}
public boolean loadFile(String fileId, Message callBack) {
log("loadFile: " + fileId);
if (fileId == null || callBack == null) return false;
mFileId = fileId;
mCallback = callBack;
selectFile();
return true;
}
private void selectFile() {
if (mChannelId >= 0) {
mUiccProfile.iccTransmitApduLogicalChannel(mChannelId, 0x00, 0xA4, 0x00, 0x04, 0x02,
mFileId, obtainMessage(EVENT_SELECT_FILE_DONE));
} else {
log("EF based");
}
}
private void readBinary() {
if (mChannelId >=0 ) {
mUiccProfile.iccTransmitApduLogicalChannel(mChannelId, 0x00, 0xB0, 0x00, 0x00, 0x00,
"", obtainMessage(EVENT_READ_BINARY_DONE));
} else {
log("EF based");
}
}
@Override
public void handleMessage(Message msg) {
log("handleMessage: " + msg.what);
AsyncResult ar = (AsyncResult) msg.obj;
if (ar.exception != null || ar.result == null) {
log("Error: " + ar.exception);
AsyncResult.forMessage(mCallback, null, ar.exception);
mCallback.sendToTarget();
return;
}
switch (msg.what) {
case EVENT_SELECT_FILE_DONE:
readBinary();
break;
case EVENT_READ_BINARY_DONE:
IccIoResult response = (IccIoResult) ar.result;
String result = IccUtils.bytesToHexString(response.payload)
.toUpperCase(Locale.US);
log("IccIoResult: " + response + " payload: " + result);
AsyncResult.forMessage(mCallback, result, (result == null) ?
new IccException("Error: null response for " + mFileId) : null);
mCallback.sendToTarget();
break;
default:
log("Unknown event" + msg.what);
}
}
}
private class Pkcs15Selector extends Handler {
private static final String PKCS15_AID = "A000000063504B43532D3135";
private Message mCallback;
private static final int EVENT_OPEN_LOGICAL_CHANNEL_DONE = 201;
public Pkcs15Selector(Message callBack) {
mCallback = callBack;
// Specified in ISO 7816-4 clause 7.1.1 0x04 means that FCP template is requested.
int p2 = 0x04;
mUiccProfile.iccOpenLogicalChannel(PKCS15_AID, p2, /* supported P2 value */
obtainMessage(EVENT_OPEN_LOGICAL_CHANNEL_DONE));
}
@Override
public void handleMessage(Message msg) {
log("handleMessage: " + msg.what);
AsyncResult ar;
switch (msg.what) {
case EVENT_OPEN_LOGICAL_CHANNEL_DONE:
ar = (AsyncResult) msg.obj;
if (ar.exception == null && ar.result != null) {
mChannelId = ((int[]) ar.result)[0];
log("mChannelId: " + mChannelId);
AsyncResult.forMessage(mCallback, null, null);
} else {
log("error: " + ar.exception);
AsyncResult.forMessage(mCallback, null, ar.exception);
// TODO: don't sendToTarget and read EF_DIR to find PKCS15
}
mCallback.sendToTarget();
break;
default:
log("Unknown event" + msg.what);
}
}
}
private UiccProfile mUiccProfile; // Parent
private Message mLoadedCallback;
private int mChannelId = -1; // Channel Id for communicating with UICC.
private List<String> mRules = null;
private Pkcs15Selector mPkcs15Selector;
private FileHandler mFh;
private static final int EVENT_SELECT_PKCS15_DONE = 1;
private static final int EVENT_LOAD_ODF_DONE = 2;
private static final int EVENT_LOAD_DODF_DONE = 3;
private static final int EVENT_LOAD_ACMF_DONE = 4;
private static final int EVENT_LOAD_ACRF_DONE = 5;
private static final int EVENT_LOAD_ACCF_DONE = 6;
private static final int EVENT_CLOSE_LOGICAL_CHANNEL_DONE = 7;
public UiccPkcs15(UiccProfile uiccProfile, Message loadedCallback) {
log("Creating UiccPkcs15");
mUiccProfile = uiccProfile;
mLoadedCallback = loadedCallback;
mPkcs15Selector = new Pkcs15Selector(obtainMessage(EVENT_SELECT_PKCS15_DONE));
}
@Override
public void handleMessage(Message msg) {
log("handleMessage: " + msg.what);
AsyncResult ar = (AsyncResult) msg.obj;
switch (msg.what) {
case EVENT_SELECT_PKCS15_DONE:
if (ar.exception == null) {
// ar.result is null if using logical channel,
// or string for pkcs15 path if using file access.
mFh = new FileHandler((String) ar.result);
if (!mFh.loadFile(ID_ACRF, obtainMessage(EVENT_LOAD_ACRF_DONE))) {
cleanUp();
}
} else {
log("select pkcs15 failed: " + ar.exception);
// select PKCS15 failed, notify uiccCarrierPrivilegeRules
mLoadedCallback.sendToTarget();
}
break;
case EVENT_LOAD_ACRF_DONE:
if (ar.exception == null && ar.result != null) {
mRules = new ArrayList<String>();
String idAccf = parseAcrf((String) ar.result);
if (!mFh.loadFile(idAccf, obtainMessage(EVENT_LOAD_ACCF_DONE))) {
cleanUp();
}
} else {
cleanUp();
}
break;
case EVENT_LOAD_ACCF_DONE:
if (ar.exception == null && ar.result != null) {
parseAccf((String) ar.result);
}
// We are done here, no more file to read
cleanUp();
break;
case EVENT_CLOSE_LOGICAL_CHANNEL_DONE:
break;
default:
Rlog.e(LOG_TAG, "Unknown event " + msg.what);
}
}
private void cleanUp() {
log("cleanUp");
if (mChannelId >= 0) {
mUiccProfile.iccCloseLogicalChannel(mChannelId, obtainMessage(
EVENT_CLOSE_LOGICAL_CHANNEL_DONE));
mChannelId = -1;
}
mLoadedCallback.sendToTarget();
}
// Constants defined in specs, needed for parsing
private static final String CARRIER_RULE_AID = "FFFFFFFFFFFF"; // AID for carrier privilege rule
private static final String ID_ACRF = "4300";
private static final String TAG_ASN_SEQUENCE = "30";
private static final String TAG_ASN_OCTET_STRING = "04";
private static final String TAG_TARGET_AID = "A0";
// parse ACRF file to get file id for ACCF file
// data is hex string, return file id if parse success, null otherwise
private String parseAcrf(String data) {
String ret = null;
String acRules = data;
while (!acRules.isEmpty()) {
TLV tlvRule = new TLV(TAG_ASN_SEQUENCE);
try {
acRules = tlvRule.parse(acRules, false);
String ruleString = tlvRule.getValue();
if (ruleString.startsWith(TAG_TARGET_AID)) {
// rule string consists of target AID + path, example:
// [A0] 08 [04] 06 FF FF FF FF FF FF [30] 04 [04] 02 43 10
// bytes in [] are tags for the data
TLV tlvTarget = new TLV(TAG_TARGET_AID); // A0
TLV tlvAid = new TLV(TAG_ASN_OCTET_STRING); // 04
TLV tlvAsnPath = new TLV(TAG_ASN_SEQUENCE); // 30
TLV tlvPath = new TLV(TAG_ASN_OCTET_STRING); // 04
// populate tlvTarget.value with aid data,
// ruleString has remaining data for path
ruleString = tlvTarget.parse(ruleString, false);
// parse tlvTarget.value to get actual strings for AID.
// no other tags expected so shouldConsumeAll is true.
tlvAid.parse(tlvTarget.getValue(), true);
if (CARRIER_RULE_AID.equals(tlvAid.getValue())) {
tlvAsnPath.parse(ruleString, true);
tlvPath.parse(tlvAsnPath.getValue(), true);
ret = tlvPath.getValue();
}
}
continue; // skip current rule as it doesn't have expected TAG
} catch (IllegalArgumentException|IndexOutOfBoundsException ex) {
log("Error: " + ex);
break; // Bad data, ignore all remaining ACRules
}
}
return ret;
}
// parse ACCF and add to mRules
private void parseAccf(String data) {
String acCondition = data;
while (!acCondition.isEmpty()) {
TLV tlvCondition = new TLV(TAG_ASN_SEQUENCE);
TLV tlvCert = new TLV(TAG_ASN_OCTET_STRING);
try {
acCondition = tlvCondition.parse(acCondition, false);
tlvCert.parse(tlvCondition.getValue(), true);
if (!tlvCert.getValue().isEmpty()) {
mRules.add(tlvCert.getValue());
}
} catch (IllegalArgumentException|IndexOutOfBoundsException ex) {
log("Error: " + ex);
break; // Bad data, ignore all remaining acCondition data
}
}
}
public List<String> getRules() {
return mRules;
}
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 pw, String[] args) {
if (mRules != null) {
pw.println(" mRules:");
for (String cert : mRules) {
pw.println(" " + cert);
}
}
}
}