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 
EF(SecureElement handle)54     public EF(SecureElement handle) {
55         mSEHandle = handle;
56     }
57 
getFileId()58     public int getFileId() {
59         return mFileID;
60     }
61 
decodeFileProperties(byte[] data)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      */
decodeSIMFileProps(byte[] data)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      */
decodeUSIMFileProps(byte[] data)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      */
selectFile(byte[] path)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      */
readBinary(int offset, int nbBytes)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      */
readRecord(short record)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      */
getFileNbRecords()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