1 /* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.internal.telephony.uicc; 18 19 import android.os.AsyncResult; 20 import android.os.Handler; 21 import android.os.Message; 22 import android.telephony.Rlog; 23 24 import com.android.internal.telephony.uicc.UiccCarrierPrivilegeRules.TLV; 25 26 import java.io.FileDescriptor; 27 import java.io.PrintWriter; 28 import java.util.ArrayList; 29 import java.util.List; 30 import java.util.Locale; 31 32 /** 33 * Class that reads PKCS15-based rules for carrier privileges. 34 * 35 * The spec for the rules: 36 * GP Secure Element Access Control: 37 * https://www.globalplatform.org/specificationsdevice.asp 38 * 39 * The UiccPkcs15 class handles overall flow of finding/selecting PKCS15 applet 40 * and reading/parsing each file. Because PKCS15 can be selected in 2 different ways: 41 * via logical channel or EF_DIR, PKCS15Selector is a handler to encapsulate the flow. 42 * Similarly, FileHandler is used for selecting/reading each file, so common codes are 43 * all in same place. 44 * 45 * {@hide} 46 */ 47 public class UiccPkcs15 extends Handler { 48 private static final String LOG_TAG = "UiccPkcs15"; 49 private static final boolean DBG = true; 50 51 // File handler for PKCS15 files, select file and read binary, 52 // convert to String then send to callback message. 53 private class FileHandler extends Handler { 54 // EF path for PKCS15 root, eg. "3F007F50" 55 // null if logical channel is used for PKCS15 access. 56 private final String mPkcs15Path; 57 // Message to send when file has been parsed. 58 private Message mCallback; 59 // File id to read data from, eg. "5031" 60 private String mFileId; 61 62 // async events for the sequence of select and read 63 static protected final int EVENT_SELECT_FILE_DONE = 101; 64 static protected final int EVENT_READ_BINARY_DONE = 102; 65 66 // pkcs15Path is nullable when using logical channel 67 public FileHandler(String pkcs15Path) { 68 log("Creating FileHandler, pkcs15Path: " + pkcs15Path); 69 mPkcs15Path = pkcs15Path; 70 } 71 72 public boolean loadFile(String fileId, Message callBack) { 73 log("loadFile: " + fileId); 74 if (fileId == null || callBack == null) return false; 75 mFileId = fileId; 76 mCallback = callBack; 77 selectFile(); 78 return true; 79 } 80 81 private void selectFile() { 82 if (mChannelId >= 0) { 83 mUiccProfile.iccTransmitApduLogicalChannel(mChannelId, 0x00, 0xA4, 0x00, 0x04, 0x02, 84 mFileId, obtainMessage(EVENT_SELECT_FILE_DONE)); 85 } else { 86 log("EF based"); 87 } 88 } 89 90 private void readBinary() { 91 if (mChannelId >=0 ) { 92 mUiccProfile.iccTransmitApduLogicalChannel(mChannelId, 0x00, 0xB0, 0x00, 0x00, 0x00, 93 "", obtainMessage(EVENT_READ_BINARY_DONE)); 94 } else { 95 log("EF based"); 96 } 97 } 98 99 @Override 100 public void handleMessage(Message msg) { 101 log("handleMessage: " + msg.what); 102 AsyncResult ar = (AsyncResult) msg.obj; 103 if (ar.exception != null || ar.result == null) { 104 log("Error: " + ar.exception); 105 AsyncResult.forMessage(mCallback, null, ar.exception); 106 mCallback.sendToTarget(); 107 return; 108 } 109 110 switch (msg.what) { 111 case EVENT_SELECT_FILE_DONE: 112 readBinary(); 113 break; 114 115 case EVENT_READ_BINARY_DONE: 116 IccIoResult response = (IccIoResult) ar.result; 117 String result = IccUtils.bytesToHexString(response.payload) 118 .toUpperCase(Locale.US); 119 log("IccIoResult: " + response + " payload: " + result); 120 AsyncResult.forMessage(mCallback, result, (result == null) ? 121 new IccException("Error: null response for " + mFileId) : null); 122 mCallback.sendToTarget(); 123 break; 124 125 default: 126 log("Unknown event" + msg.what); 127 } 128 } 129 } 130 131 private class Pkcs15Selector extends Handler { 132 private static final String PKCS15_AID = "A000000063504B43532D3135"; 133 private Message mCallback; 134 private static final int EVENT_OPEN_LOGICAL_CHANNEL_DONE = 201; 135 136 public Pkcs15Selector(Message callBack) { 137 mCallback = callBack; 138 // Specified in ISO 7816-4 clause 7.1.1 0x04 means that FCP template is requested. 139 int p2 = 0x04; 140 mUiccProfile.iccOpenLogicalChannel(PKCS15_AID, p2, /* supported P2 value */ 141 obtainMessage(EVENT_OPEN_LOGICAL_CHANNEL_DONE)); 142 } 143 144 @Override 145 public void handleMessage(Message msg) { 146 log("handleMessage: " + msg.what); 147 AsyncResult ar; 148 149 switch (msg.what) { 150 case EVENT_OPEN_LOGICAL_CHANNEL_DONE: 151 ar = (AsyncResult) msg.obj; 152 if (ar.exception == null && ar.result != null) { 153 mChannelId = ((int[]) ar.result)[0]; 154 log("mChannelId: " + mChannelId); 155 AsyncResult.forMessage(mCallback, null, null); 156 } else { 157 log("error: " + ar.exception); 158 AsyncResult.forMessage(mCallback, null, ar.exception); 159 // TODO: don't sendToTarget and read EF_DIR to find PKCS15 160 } 161 mCallback.sendToTarget(); 162 break; 163 164 default: 165 log("Unknown event" + msg.what); 166 } 167 } 168 } 169 170 private UiccProfile mUiccProfile; // Parent 171 private Message mLoadedCallback; 172 private int mChannelId = -1; // Channel Id for communicating with UICC. 173 private List<String> mRules = new ArrayList<String>(); 174 private Pkcs15Selector mPkcs15Selector; 175 private FileHandler mFh; 176 177 private static final int EVENT_SELECT_PKCS15_DONE = 1; 178 private static final int EVENT_LOAD_ODF_DONE = 2; 179 private static final int EVENT_LOAD_DODF_DONE = 3; 180 private static final int EVENT_LOAD_ACMF_DONE = 4; 181 private static final int EVENT_LOAD_ACRF_DONE = 5; 182 private static final int EVENT_LOAD_ACCF_DONE = 6; 183 private static final int EVENT_CLOSE_LOGICAL_CHANNEL_DONE = 7; 184 185 public UiccPkcs15(UiccProfile uiccProfile, Message loadedCallback) { 186 log("Creating UiccPkcs15"); 187 mUiccProfile = uiccProfile; 188 mLoadedCallback = loadedCallback; 189 mPkcs15Selector = new Pkcs15Selector(obtainMessage(EVENT_SELECT_PKCS15_DONE)); 190 } 191 192 @Override 193 public void handleMessage(Message msg) { 194 log("handleMessage: " + msg.what); 195 AsyncResult ar = (AsyncResult) msg.obj; 196 197 switch (msg.what) { 198 case EVENT_SELECT_PKCS15_DONE: 199 if (ar.exception == null) { 200 // ar.result is null if using logical channel, 201 // or string for pkcs15 path if using file access. 202 mFh = new FileHandler((String)ar.result); 203 if (!mFh.loadFile(ID_ACRF, obtainMessage(EVENT_LOAD_ACRF_DONE))) { 204 cleanUp(); 205 } 206 } else { 207 log("select pkcs15 failed: " + ar.exception); 208 // select PKCS15 failed, notify uiccCarrierPrivilegeRules 209 mLoadedCallback.sendToTarget(); 210 } 211 break; 212 213 case EVENT_LOAD_ACRF_DONE: 214 if (ar.exception == null && ar.result != null) { 215 String idAccf = parseAcrf((String)ar.result); 216 if (!mFh.loadFile(idAccf, obtainMessage(EVENT_LOAD_ACCF_DONE))) { 217 cleanUp(); 218 } 219 } else { 220 cleanUp(); 221 } 222 break; 223 224 case EVENT_LOAD_ACCF_DONE: 225 if (ar.exception == null && ar.result != null) { 226 parseAccf((String)ar.result); 227 } 228 // We are done here, no more file to read 229 cleanUp(); 230 break; 231 232 case EVENT_CLOSE_LOGICAL_CHANNEL_DONE: 233 break; 234 235 default: 236 Rlog.e(LOG_TAG, "Unknown event " + msg.what); 237 } 238 } 239 240 private void cleanUp() { 241 log("cleanUp"); 242 if (mChannelId >= 0) { 243 mUiccProfile.iccCloseLogicalChannel(mChannelId, obtainMessage( 244 EVENT_CLOSE_LOGICAL_CHANNEL_DONE)); 245 mChannelId = -1; 246 } 247 mLoadedCallback.sendToTarget(); 248 } 249 250 // Constants defined in specs, needed for parsing 251 private static final String CARRIER_RULE_AID = "FFFFFFFFFFFF"; // AID for carrier privilege rule 252 private static final String ID_ACRF = "4300"; 253 private static final String TAG_ASN_SEQUENCE = "30"; 254 private static final String TAG_ASN_OCTET_STRING = "04"; 255 private static final String TAG_TARGET_AID = "A0"; 256 257 // parse ACRF file to get file id for ACCF file 258 // data is hex string, return file id if parse success, null otherwise 259 private String parseAcrf(String data) { 260 String ret = null; 261 262 String acRules = data; 263 while (!acRules.isEmpty()) { 264 TLV tlvRule = new TLV(TAG_ASN_SEQUENCE); 265 try { 266 acRules = tlvRule.parse(acRules, false); 267 String ruleString = tlvRule.getValue(); 268 if (ruleString.startsWith(TAG_TARGET_AID)) { 269 // rule string consists of target AID + path, example: 270 // [A0] 08 [04] 06 FF FF FF FF FF FF [30] 04 [04] 02 43 10 271 // bytes in [] are tags for the data 272 TLV tlvTarget = new TLV(TAG_TARGET_AID); // A0 273 TLV tlvAid = new TLV(TAG_ASN_OCTET_STRING); // 04 274 TLV tlvAsnPath = new TLV(TAG_ASN_SEQUENCE); // 30 275 TLV tlvPath = new TLV(TAG_ASN_OCTET_STRING); // 04 276 277 // populate tlvTarget.value with aid data, 278 // ruleString has remaining data for path 279 ruleString = tlvTarget.parse(ruleString, false); 280 // parse tlvTarget.value to get actual strings for AID. 281 // no other tags expected so shouldConsumeAll is true. 282 tlvAid.parse(tlvTarget.getValue(), true); 283 284 if (CARRIER_RULE_AID.equals(tlvAid.getValue())) { 285 tlvAsnPath.parse(ruleString, true); 286 tlvPath.parse(tlvAsnPath.getValue(), true); 287 ret = tlvPath.getValue(); 288 } 289 } 290 continue; // skip current rule as it doesn't have expected TAG 291 } catch (IllegalArgumentException|IndexOutOfBoundsException ex) { 292 log("Error: " + ex); 293 break; // Bad data, ignore all remaining ACRules 294 } 295 } 296 return ret; 297 } 298 299 // parse ACCF and add to mRules 300 private void parseAccf(String data) { 301 String acCondition = data; 302 while (!acCondition.isEmpty()) { 303 TLV tlvCondition = new TLV(TAG_ASN_SEQUENCE); 304 TLV tlvCert = new TLV(TAG_ASN_OCTET_STRING); 305 try { 306 acCondition = tlvCondition.parse(acCondition, false); 307 tlvCert.parse(tlvCondition.getValue(), true); 308 if (!tlvCert.getValue().isEmpty()) { 309 mRules.add(tlvCert.getValue()); 310 } 311 } catch (IllegalArgumentException|IndexOutOfBoundsException ex) { 312 log("Error: " + ex); 313 break; // Bad data, ignore all remaining acCondition data 314 } 315 } 316 } 317 318 public List<String> getRules() { 319 return mRules; 320 } 321 322 private static void log(String msg) { 323 if (DBG) Rlog.d(LOG_TAG, msg); 324 } 325 326 /** 327 * Dumps info to Dumpsys - useful for debugging. 328 */ 329 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 330 if (mRules != null) { 331 pw.println(" mRules:"); 332 for (String cert : mRules) { 333 pw.println(" " + cert); 334 } 335 } 336 } 337 } 338