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.telephony.Rlog; 21 import android.util.ArrayMap; 22 23 import com.android.internal.annotations.VisibleForTesting; 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 B7_MASK = 0x40; 42 private static final int B2_MASK = 0x02; 43 44 public static final byte EUICC_SUPPORTED = (byte) 0x82; 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 value of the first tB after T=15 is 82. 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() == EUICC_SUPPORTED) { 157 mIsEuiccSupported = true; 158 return; 159 } 160 } 161 } 162 parseConventionByte(byte[] atrBytes, int index)163 private int parseConventionByte(byte[] atrBytes, int index) { 164 if (index >= atrBytes.length) { 165 loge("Failed to read the convention byte."); 166 return -1; 167 } 168 byte value = atrBytes[index]; 169 if (value == DIRECT_CONVENTION) { 170 mIsDirectConvention = true; 171 } else if (value == INVERSE_CONVENTION) { 172 mIsDirectConvention = false; 173 } else { 174 loge("Unrecognized convention byte " + IccUtils.byteToHex(value)); 175 return -1; 176 } 177 return index + 1; 178 } 179 parseFormatByte(byte[] atrBytes, int index)180 private int parseFormatByte(byte[] atrBytes, int index) { 181 if (index >= atrBytes.length) { 182 loge("Failed to read the format byte."); 183 return -1; 184 } 185 mFormatByte = atrBytes[index]; 186 if (VDBG) log("mHistoricalBytesLength: " + (mFormatByte & T_MASK)); 187 return index + 1; 188 } 189 parseInterfaceBytes(byte[] atrBytes, int index)190 private int parseInterfaceBytes(byte[] atrBytes, int index) { 191 // The first lastTD is actually not any TD but instead the format byte. 192 byte lastTD = mFormatByte; 193 while (true) { 194 if (VDBG) log("lastTD: " + IccUtils.byteToHex(lastTD)); 195 // Parse the interface bytes. 196 if ((lastTD & INTERFACE_BYTES_MASK) == 0) { 197 break; 198 } 199 200 InterfaceByte interfaceByte = new InterfaceByte(); 201 if (VDBG) log("lastTD & TA_MASK: " + IccUtils.byteToHex((byte) (lastTD & TA_MASK))); 202 if ((lastTD & TA_MASK) != 0) { 203 if (index >= atrBytes.length) { 204 loge("Failed to read the byte for TA."); 205 return -1; 206 } 207 interfaceByte.setTA(atrBytes[index]); 208 index++; 209 } 210 if (VDBG) log("lastTD & TB_MASK: " + IccUtils.byteToHex((byte) (lastTD & TB_MASK))); 211 if ((lastTD & TB_MASK) != 0) { 212 if (index >= atrBytes.length) { 213 loge("Failed to read the byte for TB."); 214 return -1; 215 } 216 interfaceByte.setTB(atrBytes[index]); 217 index++; 218 } 219 if (VDBG) log("lastTD & TC_MASK: " + IccUtils.byteToHex((byte) (lastTD & TC_MASK))); 220 if ((lastTD & TC_MASK) != 0) { 221 if (index >= atrBytes.length) { 222 loge("Failed to read the byte for TC."); 223 return -1; 224 } 225 interfaceByte.setTC(atrBytes[index]); 226 index++; 227 } 228 if (VDBG) log("lastTD & TD_MASK: " + IccUtils.byteToHex((byte) (lastTD & TD_MASK))); 229 if ((lastTD & TD_MASK) != 0) { 230 if (index >= atrBytes.length) { 231 loge("Failed to read the byte for TD."); 232 return -1; 233 } 234 interfaceByte.setTD(atrBytes[index]); 235 index++; 236 } 237 mInterfaceBytes.add(interfaceByte); 238 Byte newTD = interfaceByte.getTD(); 239 if (VDBG) log("index=" + index + ", " + toString()); 240 if (newTD == null) { 241 break; 242 } 243 lastTD = newTD; 244 // Parse the T values from all the TD, here we only check whether T is equal to any 245 // other values other than 0, since the check byte can be absent only when T is 246 // equal to 0. 247 if ((lastTD & T_MASK) != 0) { 248 mOnlyTEqualsZero = false; 249 } 250 } 251 return index; 252 } 253 parseHistoricalBytes(byte[] atrBytes, int index)254 private int parseHistoricalBytes(byte[] atrBytes, int index) { 255 int length = mFormatByte & T_MASK; 256 if (length + index > atrBytes.length) { 257 loge("Failed to read the historical bytes."); 258 return -1; 259 } 260 if (length > 0) { 261 mHistoricalBytes = HistoricalBytes.parseHistoricalBytes(atrBytes, index, length); 262 } 263 return index + length; 264 } 265 parseCheckBytes(byte[] atrBytes, int index)266 private int parseCheckBytes(byte[] atrBytes, int index) { 267 if (index < atrBytes.length) { 268 mCheckByte = atrBytes[index]; 269 index++; 270 } else { 271 if (!mOnlyTEqualsZero) { 272 loge("Check byte must be present because T equals to values other than 0."); 273 return -1; 274 } else { 275 log("Check byte can be absent because T=0."); 276 } 277 } 278 return index; 279 } 280 parseAtrString(String atr)281 private boolean parseAtrString(String atr) { 282 if (atr == null) { 283 loge("The input ATR string can not be null"); 284 return false; 285 } 286 287 if (atr.length() % 2 != 0) { 288 loge("The length of input ATR string " + atr.length() + " is not even."); 289 return false; 290 } 291 292 if (atr.length() < 4) { 293 loge("Valid ATR string must at least contains TS and T0."); 294 return false; 295 } 296 297 byte[] atrBytes = IccUtils.hexStringToBytes(atr); 298 if (atrBytes == null) { 299 return false; 300 } 301 302 int index = parseConventionByte(atrBytes, 0); 303 if (index == -1) { 304 return false; 305 } 306 307 index = parseFormatByte(atrBytes, index); 308 if (index == -1) { 309 return false; 310 } 311 312 index = parseInterfaceBytes(atrBytes, index); 313 if (index == -1) { 314 return false; 315 } 316 317 index = parseHistoricalBytes(atrBytes, index); 318 if (index == -1) { 319 return false; 320 } 321 322 index = parseCheckBytes(atrBytes, index); 323 if (index == -1) { 324 return false; 325 } 326 327 if (index != atrBytes.length) { 328 loge("Unexpected bytes after the check byte."); 329 return false; 330 } 331 log("Successfully parsed the ATR string " + atr + " into " + toString()); 332 checkIsEuiccSupported(); 333 return true; 334 } 335 336 /** 337 * This class holds the interface bytes. 338 */ 339 public static class InterfaceByte { 340 private Byte mTA; 341 private Byte mTB; 342 private Byte mTC; 343 private Byte mTD; 344 345 @Nullable getTA()346 public Byte getTA() { 347 return mTA; 348 } 349 350 @Nullable getTB()351 public Byte getTB() { 352 return mTB; 353 } 354 355 @Nullable getTC()356 public Byte getTC() { 357 return mTC; 358 } 359 360 @Nullable getTD()361 public Byte getTD() { 362 return mTD; 363 } 364 setTA(Byte tA)365 public void setTA(Byte tA) { 366 mTA = tA; 367 } 368 setTB(Byte tB)369 public void setTB(Byte tB) { 370 mTB = tB; 371 } 372 setTC(Byte tC)373 public void setTC(Byte tC) { 374 mTC = tC; 375 } 376 setTD(Byte tD)377 public void setTD(Byte tD) { 378 mTD = tD; 379 } 380 InterfaceByte()381 private InterfaceByte() { 382 mTA = null; 383 mTB = null; 384 mTC = null; 385 mTD = null; 386 } 387 388 @VisibleForTesting InterfaceByte(Byte tA, Byte tB, Byte tC, Byte tD)389 public InterfaceByte(Byte tA, Byte tB, Byte tC, Byte tD) { 390 this.mTA = tA; 391 this.mTB = tB; 392 this.mTC = tC; 393 this.mTD = tD; 394 } 395 396 @Override equals(Object o)397 public boolean equals(Object o) { 398 if (this == o) { 399 return true; 400 } 401 if (o == null || getClass() != o.getClass()) { 402 return false; 403 } 404 InterfaceByte ib = (InterfaceByte) o; 405 return (Objects.equals(mTA, ib.getTA()) 406 && Objects.equals(mTB, ib.getTB()) 407 && Objects.equals(mTC, ib.getTC()) 408 && Objects.equals(mTD, ib.getTD())); 409 } 410 411 @Override hashCode()412 public int hashCode() { 413 return Objects.hash(mTA, mTB, mTC, mTD); 414 } 415 416 @Override toString()417 public String toString() { 418 StringBuffer sb = new StringBuffer(); 419 sb.append("{"); 420 sb.append("TA=").append(byteToStringHex(mTA)).append(","); 421 sb.append("TB=").append(byteToStringHex(mTB)).append(","); 422 sb.append("TC=").append(byteToStringHex(mTC)).append(","); 423 sb.append("TD=").append(byteToStringHex(mTD)); 424 sb.append("}"); 425 return sb.toString(); 426 } 427 }; 428 log(String msg)429 private static void log(String msg) { 430 Rlog.d(TAG, msg); 431 } 432 loge(String msg)433 private static void loge(String msg) { 434 Rlog.e(TAG, msg); 435 } 436 getConventionByte()437 public byte getConventionByte() { 438 return mIsDirectConvention ? DIRECT_CONVENTION : INVERSE_CONVENTION; 439 } 440 getFormatByte()441 public byte getFormatByte() { 442 return mFormatByte; 443 } 444 getInterfaceBytes()445 public List<InterfaceByte> getInterfaceBytes() { 446 return mInterfaceBytes; 447 } 448 449 @Nullable getHistoricalBytes()450 public HistoricalBytes getHistoricalBytes() { 451 return mHistoricalBytes; 452 } 453 454 @Nullable getCheckByte()455 public Byte getCheckByte() { 456 return mCheckByte; 457 } 458 isEuiccSupported()459 public boolean isEuiccSupported() { 460 return mIsEuiccSupported; 461 } 462 463 /** Return whether the extended LC & LE is supported. */ isExtendedApduSupported()464 public boolean isExtendedApduSupported() { 465 if (mHistoricalBytes == null) { 466 return false; 467 } 468 byte[] cardCapabilities = mHistoricalBytes.getValue(TAG_CARD_CAPABILITIES); 469 if (cardCapabilities == null || cardCapabilities.length < 3) { 470 return false; 471 } 472 if (mIsDirectConvention) { 473 return (cardCapabilities[EXTENDED_APDU_INDEX] & B7_MASK) > 0; 474 } else { 475 return (cardCapabilities[EXTENDED_APDU_INDEX] & B2_MASK) > 0; 476 } 477 } 478 479 @Override toString()480 public String toString() { 481 StringBuffer sb = new StringBuffer(); 482 483 sb.append("AnswerToReset:{"); 484 sb.append("mConventionByte=") 485 .append(IccUtils.byteToHex(getConventionByte())).append(","); 486 sb.append("mFormatByte=").append(byteToStringHex(mFormatByte)).append(","); 487 sb.append("mInterfaceBytes={"); 488 for (InterfaceByte ib : mInterfaceBytes) { 489 sb.append(ib.toString()); 490 } 491 sb.append("},"); 492 sb.append("mHistoricalBytes={"); 493 if (mHistoricalBytes != null) { 494 for (byte b : mHistoricalBytes.getRawData()) { 495 sb.append(IccUtils.byteToHex(b)).append(","); 496 } 497 } 498 sb.append("},"); 499 sb.append("mCheckByte=").append(byteToStringHex(mCheckByte)); 500 sb.append("}"); 501 return sb.toString(); 502 } 503 504 /** 505 * Dump 506 */ dump(FileDescriptor fd, PrintWriter pw, String[] args)507 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 508 pw.println("AnswerToReset:"); 509 pw.println(toString()); 510 pw.flush(); 511 } 512 } 513