1 /* 2 * Copyright (C) 2017 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 * Copyright (c) 2015-2017, The Linux Foundation. 18 */ 19 20 /* 21 * Contributed by: Giesecke & Devrient GmbH. 22 */ 23 24 package com.android.se.security.arf.pkcs15; 25 26 import android.util.Log; 27 28 import com.android.se.security.arf.ASN1; 29 import com.android.se.security.arf.DERParser; 30 import com.android.se.security.arf.SecureElement; 31 import com.android.se.security.arf.SecureElementException; 32 33 import java.io.IOException; 34 import java.util.Arrays; 35 36 /** Base class for ARF Data Objects */ 37 public class EF { 38 39 public static final String TAG = "SecureElementService ACE ARF"; 40 41 public static final int APDU_SUCCESS = 0x9000; 42 private static final int BUFFER_LEN = 253; 43 44 private static final short EF = 0x04; 45 private static final short TRANSPARENT = 0x00; 46 private static final short LINEAR_FIXED = 0x01; 47 private static final short UNKNOWN = 0xFF; 48 // Handle to "Secure Element" object 49 protected SecureElement mSEHandle = null; 50 // Selected file parameters 51 private short mFileType = UNKNOWN, mFileStructure = UNKNOWN, mFileNbRecords; 52 private int mFileID, mFileSize, mFileRecordSize; 53 54 public EF(SecureElement handle) { 55 mSEHandle = handle; 56 } 57 58 public int getFileId() { 59 return mFileID; 60 } 61 62 private void decodeFileProperties(byte[] data) throws SecureElementException { 63 if (data != null) { 64 // check if first byte is the FCP tag 65 // then do USIM decoding 66 if (data[0] == 0x62) { 67 decodeUSIMFileProps(data); 68 } else { 69 // otherwise sim decoding 70 decodeSIMFileProps(data); 71 } 72 } 73 } 74 75 /** 76 * Decodes file properties (SIM cards) 77 * 78 * @param data TS 51.011 encoded characteristics 79 */ 80 private void decodeSIMFileProps(byte[] data) throws SecureElementException { 81 if ((data == null) || (data.length < 15)) { 82 throw new SecureElementException("Invalid Response data"); 83 } 84 85 // 2012-04-13 86 // check type of file 87 if ((short) (data[6] & 0xFF) == (short) 0x04) { 88 mFileType = EF; 89 } else { 90 mFileType = UNKNOWN; // may also be DF or MF, but we are not interested in them. 91 } 92 if ((short) (data[13] & 0xFF) == (short) 0x00) { 93 mFileStructure = TRANSPARENT; 94 } else if ((short) (data[13] & 0xFF) == (short) 0x01) { 95 mFileStructure = LINEAR_FIXED; 96 } else { 97 mFileStructure = UNKNOWN; // may also be cyclic 98 } 99 mFileSize = ((data[2] & 0xFF) << 8) | (data[3] & 0xFF); 100 101 // check if file is cyclic or linear fixed 102 if (mFileType == EF 103 && // is EF ? 104 mFileStructure != TRANSPARENT) { 105 mFileRecordSize = data[14] & 0xFF; 106 mFileNbRecords = (short) (mFileSize / mFileRecordSize); 107 } 108 } 109 110 /** 111 * Decodes file properties (USIM cards) 112 * 113 * @param data TLV encoded characteristics 114 */ 115 private void decodeUSIMFileProps(byte[] data) throws SecureElementException { 116 try { 117 byte[] buffer = null; 118 DERParser der = new DERParser(data); 119 120 der.parseTLV(ASN1.TAG_FCP); 121 while (!der.isEndofBuffer()) { 122 switch (der.parseTLV()) { 123 case (byte) 0x80: // File size 124 buffer = der.getTLVData(); 125 if ((buffer != null) && (buffer.length >= 2)) { 126 mFileSize = ((buffer[0] & 0xFF) << 8) | (buffer[1] & 0xFF); 127 } 128 break; 129 case (byte) 0x82: // File descriptor 130 buffer = der.getTLVData(); 131 if ((buffer != null) && (buffer.length >= 2)) { 132 if ((short) (buffer[0] & 0x07) == (short) 0x01) { 133 mFileStructure = TRANSPARENT; 134 } else if ((short) (buffer[0] & 0x07) == (short) 0x02) { 135 mFileStructure = LINEAR_FIXED; 136 } else { 137 mFileStructure = UNKNOWN; // may also be cyclic 138 } 139 140 // check if bit 4,5,6 are set 141 // then this is a DF or ADF, but we mark it with UNKNOWN, 142 // since we are only interested in EFs. 143 if ((short) (buffer[0] & 0x38) == (short) 0x38) { 144 mFileType = UNKNOWN; 145 } else { 146 mFileType = EF; 147 } 148 if (buffer.length == 5) { 149 mFileRecordSize = buffer[3] & 0xFF; 150 mFileNbRecords = (short) (buffer[4] & 0xFF); 151 } 152 } 153 break; 154 default: 155 der.skipTLVData(); 156 break; 157 } 158 } 159 } catch (Exception e) { 160 throw new SecureElementException("Invalid GetResponse"); 161 } 162 } 163 164 /** 165 * Selects a file (INS 0xA4) 166 * 167 * @param path Path of the file 168 * @return Command status code [sw1 sw2] 169 */ 170 public int selectFile(byte[] path) throws IOException, SecureElementException { 171 if ((path == null) || (path.length == 0) || ((path.length % 2) != 0)) { 172 throw new SecureElementException("Incorrect path"); 173 } 174 int length = path.length; 175 176 byte[] data = null; 177 byte[] cmd = new byte[]{0x00, (byte) 0xA4, 0x00, 0x04, 0x02, 0x00, 0x00}; 178 179 mFileType = UNKNOWN; 180 mFileStructure = UNKNOWN; 181 mFileSize = 0; 182 mFileRecordSize = 0; 183 mFileNbRecords = 0; 184 185 // iterate through path 186 for (int index = 0; index < length; index += 2) { 187 mFileID = ((path[index] & 0xFF) << 8) | (path[index + 1] & 0xFF); 188 cmd[5] = (byte) (mFileID >> 8); 189 cmd[6] = (byte) mFileID; 190 191 data = mSEHandle.exchangeAPDU(this, cmd); 192 193 // Check ADPU status 194 int sw1 = data[data.length - 2] & 0xFF; 195 if ((sw1 != 0x62) && (sw1 != 0x63) && (sw1 != 0x90) && (sw1 != 0x91)) { 196 return (sw1 << 8) | (data[data.length - 1] & 0xFF); 197 } 198 } 199 200 // Analyse file properties 201 decodeFileProperties(data); 202 203 return APDU_SUCCESS; 204 } 205 206 /** 207 * Reads data from the current selected file (INS 0xB0) 208 * 209 * @param offset Offset at which to start reading 210 * @param nbBytes Number of bytes to read 211 * @return Data retreived from the file 212 */ 213 public byte[] readBinary(int offset, int nbBytes) throws IOException, SecureElementException { 214 if (mFileSize == 0) return null; 215 if (nbBytes == -1) nbBytes = mFileSize; 216 if (mFileType != EF) throw new SecureElementException("Incorrect file type"); 217 if (mFileStructure != TRANSPARENT) { 218 throw new SecureElementException( 219 "Incorrect file structure"); 220 } 221 222 int length, pos = 0; 223 byte[] result = new byte[nbBytes]; 224 byte[] cmd = {0x00, (byte) 0xB0, 0x00, 0x00, 0x00}; 225 226 while (nbBytes != 0) { 227 if (nbBytes < BUFFER_LEN) { 228 length = nbBytes; 229 } else { 230 length = BUFFER_LEN; // Set to max buffer size 231 } 232 233 cmd[2] = (byte) (offset >> 8); 234 cmd[3] = (byte) offset; 235 cmd[4] = (byte) length; 236 System.arraycopy(mSEHandle.exchangeAPDU(this, cmd), 0, result, pos, length); 237 nbBytes -= length; 238 offset += length; 239 pos += length; 240 } 241 return result; 242 } 243 244 /** 245 * Reads a record from the current selected file (INS 0xB2) 246 * 247 * @param record Record ID [0..n] 248 * @return Data from requested record 249 */ 250 public byte[] readRecord(short record) throws IOException, SecureElementException { 251 // Check the type of current selected file 252 if (mFileType != EF) throw new SecureElementException("Incorrect file type"); 253 if (mFileStructure != LINEAR_FIXED) { 254 throw new SecureElementException("Incorrect file structure"); 255 } 256 257 // Check if requested record is valid 258 if ((record < 0) || (record > mFileNbRecords)) { 259 throw new SecureElementException("Incorrect record number"); 260 } 261 262 Log.i(TAG, "ReadRecord [" + record + "/" + mFileRecordSize + "b]"); 263 byte[] cmd = {0x00, (byte) 0xB2, (byte) record, 0x04, (byte) mFileRecordSize}; 264 265 return Arrays.copyOf(mSEHandle.exchangeAPDU(this, cmd), mFileRecordSize); 266 } 267 268 /** 269 * Returns the number of records in the current selected file 270 * 271 * @return Number of records [0..n] 272 */ 273 public short getFileNbRecords() throws SecureElementException { 274 // Check the type of current selected file 275 if (mFileNbRecords < 0) throw new SecureElementException("Incorrect file type"); 276 return mFileNbRecords; 277 } 278 } 279