1 /* 2 * Copyright 2018 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.annotation.Nullable; 20 import android.util.ArrayMap; 21 22 import com.android.internal.annotations.VisibleForTesting; 23 import com.android.telephony.Rlog; 24 25 import java.io.FileDescriptor; 26 import java.io.PrintWriter; 27 import java.util.ArrayList; 28 import java.util.List; 29 import java.util.Objects; 30 31 /** 32 * This class parses an Answer To Reset (ATR) message. 33 * The ATR message structure is defined in standard ISO/IEC 7816-3. The eUICC related ATR message 34 * is defined in standard ETSI TS 102 221 V14.0.0. 35 */ 36 public class AnswerToReset { 37 private static final String TAG = "AnswerToReset"; 38 private static final boolean VDBG = false; // STOPSHIP if true 39 private static final int TAG_CARD_CAPABILITIES = 0x07; 40 private static final int EXTENDED_APDU_INDEX = 2; 41 private static final int B8_MASK = 0x80; 42 private static final int B7_MASK = 0x40; 43 private static final int B2_MASK = 0x02; 44 45 public static final byte DIRECT_CONVENTION = (byte) 0x3B; 46 public static final byte INVERSE_CONVENTION = (byte) 0x3F; 47 public static final int INTERFACE_BYTES_MASK = 0xF0; 48 public static final int T_MASK = 0x0F; 49 public static final int T_VALUE_FOR_GLOBAL_INTERFACE = 15; 50 public static final int TA_MASK = 0x10; 51 public static final int TB_MASK = 0x20; 52 public static final int TC_MASK = 0x40; 53 public static final int TD_MASK = 0x80; 54 55 private boolean mIsDirectConvention; 56 private boolean mOnlyTEqualsZero = true; 57 private boolean mIsEuiccSupported; 58 private byte mFormatByte; 59 private ArrayList<InterfaceByte> mInterfaceBytes = new ArrayList<>(); 60 private HistoricalBytes mHistoricalBytes; 61 private Byte mCheckByte; 62 63 /** Class for the historical bytes. */ 64 public static class HistoricalBytes { 65 private static final int TAG_MASK = 0xF0; 66 private static final int LENGTH_MASK = 0x0F; 67 68 private final byte[] mRawData; 69 private final ArrayMap<Integer, byte[]> mNodes; 70 private final byte mCategory; 71 72 /** Get the category of the historical bytes. */ getCategory()73 public byte getCategory() { 74 return mCategory; 75 } 76 77 /** Get the raw data of historical bytes. */ getRawData()78 public byte[] getRawData() { 79 return mRawData; 80 } 81 82 /** Get the value of the tag in historical bytes. */ 83 @Nullable getValue(int tag)84 public byte[] getValue(int tag) { 85 return mNodes.get(tag); 86 } 87 88 @Nullable parseHistoricalBytes( byte[] originalData, int startIndex, int length)89 private static HistoricalBytes parseHistoricalBytes( 90 byte[] originalData, int startIndex, int length) { 91 if (length <= 0 || startIndex + length > originalData.length) { 92 return null; 93 } 94 ArrayMap<Integer, byte[]> nodes = new ArrayMap<>(); 95 96 // Start parsing from second byte since the first one is category. 97 int index = startIndex + 1; 98 while (index < startIndex + length && index > 0) { 99 index = parseLtvNode(index, nodes, originalData, startIndex + length - 1); 100 } 101 if (index < 0) { 102 return null; 103 } 104 byte[] rawData = new byte[length]; 105 System.arraycopy(originalData, startIndex, rawData, 0, length); 106 return new HistoricalBytes(rawData, nodes, rawData[0]); 107 } 108 HistoricalBytes(byte[] rawData, ArrayMap<Integer, byte[]> nodes, byte category)109 private HistoricalBytes(byte[] rawData, ArrayMap<Integer, byte[]> nodes, byte category) { 110 mRawData = rawData; 111 mNodes = nodes; 112 mCategory = category; 113 } 114 parseLtvNode( int index, ArrayMap<Integer, byte[]> nodes, byte[] data, int lastByteIndex)115 private static int parseLtvNode( 116 int index, ArrayMap<Integer, byte[]> nodes, byte[] data, int lastByteIndex) { 117 if (index > lastByteIndex) { 118 return -1; 119 } 120 int tag = (data[index] & TAG_MASK) >> 4; 121 int length = data[index++] & LENGTH_MASK; 122 if (index + length > lastByteIndex + 1 || length == 0) { 123 return -1; 124 } 125 byte[] value = new byte[length]; 126 System.arraycopy(data, index, value, 0, length); 127 nodes.put(tag, value); 128 return index + length; 129 } 130 } 131 132 133 /** 134 * Returns an AnswerToReset by parsing the input atr string, return null if the parsing fails. 135 */ parseAtr(String atr)136 public static AnswerToReset parseAtr(String atr) { 137 AnswerToReset answerToReset = new AnswerToReset(); 138 if (answerToReset.parseAtrString(atr)) { 139 return answerToReset; 140 } 141 return null; 142 } 143 AnswerToReset()144 private AnswerToReset() {} 145 byteToStringHex(Byte b)146 private static String byteToStringHex(Byte b) { 147 return b == null ? null : IccUtils.byteToHex(b); 148 } 149 checkIsEuiccSupported()150 private void checkIsEuiccSupported() { 151 // eUICC is supported only if the b8 and b2 of the first tB after T=15 are set to 1. 152 for (int i = 0; i < mInterfaceBytes.size() - 1; i++) { 153 if (mInterfaceBytes.get(i).getTD() != null 154 && (mInterfaceBytes.get(i).getTD() & T_MASK) == T_VALUE_FOR_GLOBAL_INTERFACE 155 && mInterfaceBytes.get(i + 1).getTB() != null 156 && (mInterfaceBytes.get(i + 1).getTB() & B8_MASK) != 0 157 && (mInterfaceBytes.get(i + 1).getTB() & B2_MASK) != 0) { 158 mIsEuiccSupported = true; 159 return; 160 } 161 } 162 } 163 parseConventionByte(byte[] atrBytes, int index)164 private int parseConventionByte(byte[] atrBytes, int index) { 165 if (index >= atrBytes.length) { 166 loge("Failed to read the convention byte."); 167 return -1; 168 } 169 byte value = atrBytes[index]; 170 if (value == DIRECT_CONVENTION) { 171 mIsDirectConvention = true; 172 } else if (value == INVERSE_CONVENTION) { 173 mIsDirectConvention = false; 174 } else { 175 loge("Unrecognized convention byte " + IccUtils.byteToHex(value)); 176 return -1; 177 } 178 return index + 1; 179 } 180 parseFormatByte(byte[] atrBytes, int index)181 private int parseFormatByte(byte[] atrBytes, int index) { 182 if (index >= atrBytes.length) { 183 loge("Failed to read the format byte."); 184 return -1; 185 } 186 mFormatByte = atrBytes[index]; 187 if (VDBG) log("mHistoricalBytesLength: " + (mFormatByte & T_MASK)); 188 return index + 1; 189 } 190 parseInterfaceBytes(byte[] atrBytes, int index)191 private int parseInterfaceBytes(byte[] atrBytes, int index) { 192 // The first lastTD is actually not any TD but instead the format byte. 193 byte lastTD = mFormatByte; 194 while (true) { 195 if (VDBG) log("lastTD: " + IccUtils.byteToHex(lastTD)); 196 // Parse the interface bytes. 197 if ((lastTD & INTERFACE_BYTES_MASK) == 0) { 198 break; 199 } 200 201 InterfaceByte interfaceByte = new InterfaceByte(); 202 if (VDBG) log("lastTD & TA_MASK: " + IccUtils.byteToHex((byte) (lastTD & TA_MASK))); 203 if ((lastTD & TA_MASK) != 0) { 204 if (index >= atrBytes.length) { 205 loge("Failed to read the byte for TA."); 206 return -1; 207 } 208 interfaceByte.setTA(atrBytes[index]); 209 index++; 210 } 211 if (VDBG) log("lastTD & TB_MASK: " + IccUtils.byteToHex((byte) (lastTD & TB_MASK))); 212 if ((lastTD & TB_MASK) != 0) { 213 if (index >= atrBytes.length) { 214 loge("Failed to read the byte for TB."); 215 return -1; 216 } 217 interfaceByte.setTB(atrBytes[index]); 218 index++; 219 } 220 if (VDBG) log("lastTD & TC_MASK: " + IccUtils.byteToHex((byte) (lastTD & TC_MASK))); 221 if ((lastTD & TC_MASK) != 0) { 222 if (index >= atrBytes.length) { 223 loge("Failed to read the byte for TC."); 224 return -1; 225 } 226 interfaceByte.setTC(atrBytes[index]); 227 index++; 228 } 229 if (VDBG) log("lastTD & TD_MASK: " + IccUtils.byteToHex((byte) (lastTD & TD_MASK))); 230 if ((lastTD & TD_MASK) != 0) { 231 if (index >= atrBytes.length) { 232 loge("Failed to read the byte for TD."); 233 return -1; 234 } 235 interfaceByte.setTD(atrBytes[index]); 236 index++; 237 } 238 mInterfaceBytes.add(interfaceByte); 239 Byte newTD = interfaceByte.getTD(); 240 if (VDBG) log("index=" + index + ", " + toString()); 241 if (newTD == null) { 242 break; 243 } 244 lastTD = newTD; 245 // Parse the T values from all the TD, here we only check whether T is equal to any 246 // other values other than 0, since the check byte can be absent only when T is 247 // equal to 0. 248 if ((lastTD & T_MASK) != 0) { 249 mOnlyTEqualsZero = false; 250 } 251 } 252 return index; 253 } 254 parseHistoricalBytes(byte[] atrBytes, int index)255 private int parseHistoricalBytes(byte[] atrBytes, int index) { 256 int length = mFormatByte & T_MASK; 257 if (length + index > atrBytes.length) { 258 loge("Failed to read the historical bytes."); 259 return -1; 260 } 261 if (length > 0) { 262 mHistoricalBytes = HistoricalBytes.parseHistoricalBytes(atrBytes, index, length); 263 } 264 return index + length; 265 } 266 parseCheckBytes(byte[] atrBytes, int index)267 private int parseCheckBytes(byte[] atrBytes, int index) { 268 if (index < atrBytes.length) { 269 mCheckByte = atrBytes[index]; 270 index++; 271 } else { 272 if (!mOnlyTEqualsZero) { 273 loge("Check byte must be present because T equals to values other than 0."); 274 return -1; 275 } else { 276 log("Check byte can be absent because T=0."); 277 } 278 } 279 return index; 280 } 281 parseAtrString(String atr)282 private boolean parseAtrString(String atr) { 283 if (atr == null) { 284 loge("The input ATR string can not be null"); 285 return false; 286 } 287 288 if (atr.length() % 2 != 0) { 289 loge("The length of input ATR string " + atr.length() + " is not even."); 290 return false; 291 } 292 293 if (atr.length() < 4) { 294 loge("Valid ATR string must at least contains TS and T0."); 295 return false; 296 } 297 298 byte[] atrBytes = IccUtils.hexStringToBytes(atr); 299 if (atrBytes == null) { 300 return false; 301 } 302 303 int index = parseConventionByte(atrBytes, 0); 304 if (index == -1) { 305 return false; 306 } 307 308 index = parseFormatByte(atrBytes, index); 309 if (index == -1) { 310 return false; 311 } 312 313 index = parseInterfaceBytes(atrBytes, index); 314 if (index == -1) { 315 return false; 316 } 317 318 index = parseHistoricalBytes(atrBytes, index); 319 if (index == -1) { 320 return false; 321 } 322 323 index = parseCheckBytes(atrBytes, index); 324 if (index == -1) { 325 return false; 326 } 327 328 if (index != atrBytes.length) { 329 loge("Unexpected bytes after the check byte."); 330 return false; 331 } 332 log("Successfully parsed the ATR string " + atr + " into " + toString()); 333 checkIsEuiccSupported(); 334 return true; 335 } 336 337 /** 338 * This class holds the interface bytes. 339 */ 340 public static class InterfaceByte { 341 private Byte mTA; 342 private Byte mTB; 343 private Byte mTC; 344 private Byte mTD; 345 346 @Nullable getTA()347 public Byte getTA() { 348 return mTA; 349 } 350 351 @Nullable getTB()352 public Byte getTB() { 353 return mTB; 354 } 355 356 @Nullable getTC()357 public Byte getTC() { 358 return mTC; 359 } 360 361 @Nullable getTD()362 public Byte getTD() { 363 return mTD; 364 } 365 setTA(Byte tA)366 public void setTA(Byte tA) { 367 mTA = tA; 368 } 369 setTB(Byte tB)370 public void setTB(Byte tB) { 371 mTB = tB; 372 } 373 setTC(Byte tC)374 public void setTC(Byte tC) { 375 mTC = tC; 376 } 377 setTD(Byte tD)378 public void setTD(Byte tD) { 379 mTD = tD; 380 } 381 InterfaceByte()382 private InterfaceByte() { 383 mTA = null; 384 mTB = null; 385 mTC = null; 386 mTD = null; 387 } 388 389 @VisibleForTesting InterfaceByte(Byte tA, Byte tB, Byte tC, Byte tD)390 public InterfaceByte(Byte tA, Byte tB, Byte tC, Byte tD) { 391 this.mTA = tA; 392 this.mTB = tB; 393 this.mTC = tC; 394 this.mTD = tD; 395 } 396 397 @Override equals(Object o)398 public boolean equals(Object o) { 399 if (this == o) { 400 return true; 401 } 402 if (o == null || getClass() != o.getClass()) { 403 return false; 404 } 405 InterfaceByte ib = (InterfaceByte) o; 406 return (Objects.equals(mTA, ib.getTA()) 407 && Objects.equals(mTB, ib.getTB()) 408 && Objects.equals(mTC, ib.getTC()) 409 && Objects.equals(mTD, ib.getTD())); 410 } 411 412 @Override hashCode()413 public int hashCode() { 414 return Objects.hash(mTA, mTB, mTC, mTD); 415 } 416 417 @Override toString()418 public String toString() { 419 StringBuffer sb = new StringBuffer(); 420 sb.append("{"); 421 sb.append("TA=").append(byteToStringHex(mTA)).append(","); 422 sb.append("TB=").append(byteToStringHex(mTB)).append(","); 423 sb.append("TC=").append(byteToStringHex(mTC)).append(","); 424 sb.append("TD=").append(byteToStringHex(mTD)); 425 sb.append("}"); 426 return sb.toString(); 427 } 428 }; 429 log(String msg)430 private static void log(String msg) { 431 Rlog.d(TAG, msg); 432 } 433 loge(String msg)434 private static void loge(String msg) { 435 Rlog.e(TAG, msg); 436 } 437 getConventionByte()438 public byte getConventionByte() { 439 return mIsDirectConvention ? DIRECT_CONVENTION : INVERSE_CONVENTION; 440 } 441 getFormatByte()442 public byte getFormatByte() { 443 return mFormatByte; 444 } 445 getInterfaceBytes()446 public List<InterfaceByte> getInterfaceBytes() { 447 return mInterfaceBytes; 448 } 449 450 @Nullable getHistoricalBytes()451 public HistoricalBytes getHistoricalBytes() { 452 return mHistoricalBytes; 453 } 454 455 @Nullable getCheckByte()456 public Byte getCheckByte() { 457 return mCheckByte; 458 } 459 isEuiccSupported()460 public boolean isEuiccSupported() { 461 return mIsEuiccSupported; 462 } 463 464 /** Return whether the extended LC & LE is supported. */ isExtendedApduSupported()465 public boolean isExtendedApduSupported() { 466 if (mHistoricalBytes == null) { 467 return false; 468 } 469 byte[] cardCapabilities = mHistoricalBytes.getValue(TAG_CARD_CAPABILITIES); 470 if (cardCapabilities == null || cardCapabilities.length < 3) { 471 return false; 472 } 473 if (mIsDirectConvention) { 474 return (cardCapabilities[EXTENDED_APDU_INDEX] & B7_MASK) > 0; 475 } else { 476 return (cardCapabilities[EXTENDED_APDU_INDEX] & B2_MASK) > 0; 477 } 478 } 479 480 @Override toString()481 public String toString() { 482 StringBuffer sb = new StringBuffer(); 483 484 sb.append("AnswerToReset:{"); 485 sb.append("mConventionByte=") 486 .append(IccUtils.byteToHex(getConventionByte())).append(","); 487 sb.append("mFormatByte=").append(byteToStringHex(mFormatByte)).append(","); 488 sb.append("mInterfaceBytes={"); 489 for (InterfaceByte ib : mInterfaceBytes) { 490 sb.append(ib.toString()); 491 } 492 sb.append("},"); 493 sb.append("mHistoricalBytes={"); 494 if (mHistoricalBytes != null) { 495 for (byte b : mHistoricalBytes.getRawData()) { 496 sb.append(IccUtils.byteToHex(b)).append(","); 497 } 498 } 499 sb.append("},"); 500 sb.append("mCheckByte=").append(byteToStringHex(mCheckByte)); 501 sb.append("}"); 502 return sb.toString(); 503 } 504 505 /** 506 * Dump 507 */ dump(FileDescriptor fd, PrintWriter pw, String[] args)508 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 509 pw.println("AnswerToReset:"); 510 pw.println(toString()); 511 pw.flush(); 512 } 513 } 514