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
17package com.android.internal.telephony.uicc;
18
19import android.os.AsyncResult;
20import android.os.Handler;
21import android.os.Message;
22import android.telephony.Rlog;
23
24import com.android.internal.telephony.uicc.UiccCarrierPrivilegeRules.TLV;
25
26import java.io.FileDescriptor;
27import java.io.PrintWriter;
28import java.util.ArrayList;
29import java.util.List;
30import 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 */
47public 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