Home | History | Annotate | Download | only in PKCS15
      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