/* (C) 2024 by sysmocom s.f.m.c. GmbH * All Rights Reserved * * Author: Philipp Maier * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * */ package org.osmocom.androidApduProxy; public class Utils { /** * convert from an array of bytes to a string of hex nibbles * @param b array of bytes * @param length length of b * @return string of hex nibbles that represents content of b */ public static String b2h(byte[] b) { if (b == null) return ("null"); StringBuffer buf = new StringBuffer(); for (int i = 0; i < b.length; i++) { buf.append(String.format("%02X", b[i] & 0xFF)); } return buf.toString(); } /** * convert from a string of hex nibbles to an array of bytes. * @param s string of hex nibbles * @return array of bytes that correspond to the content of s */ public static byte[] h2b(String s) { int length = s.length(); byte[] buf = new byte[length / 2]; byte highNibble; byte lowNibble; for (int i = 0; i < buf.length; i++) { highNibble = (byte) Character.digit(s.charAt(i * 2), 16); lowNibble = (byte) Character.digit(s.charAt(i * 2 + 1), 16); buf[i] = (byte) (highNibble << 4 | lowNibble); } return buf; } /** * Take a byte array of arbitrary length as input and trim it to a specified length * @param array input byte array * @param length new length of the byte array * @return array of bytes that has the contents of array until length */ public static byte[] trimByteArray(byte[] array, int length) { if (array == null) return null; if (length > array.length) length = array.length; byte[] array_trim = new byte[length]; System.arraycopy(array, 0, array_trim,0, length); return array_trim; } private static int interfaceCharsAhead(byte Tx) { int count = 0; if (((Tx >> 4) & 1) == 1) count++; if (((Tx >> 5) & 1) == 1) count++; if (((Tx >> 6) & 1) == 1) count++; if (((Tx >> 7) & 1) == 1) count++; else return 0; return count; } private static byte[] extractHistoricalBytes(byte[] atr) { //Short ATR cannot have any historical bytes if (atr.length <= 2) return null; int interfaceCharsAhead; int historicalByteNum = atr[1] & 0x0F; //No historical bytes present if (historicalByteNum == 0) return null; byte[] historicalBytes = new byte[historicalByteNum]; boolean lastrun = false; int historicalBytesOffset = 2; // Count bytes until the beginning of the historical bytes do { //Hit end of ATR without => no historical bytes if (historicalBytesOffset > atr.length) return null; byte Tx = atr[historicalBytesOffset-1]; interfaceCharsAhead = 0; if (((Tx >> 4) & 1) == 1) interfaceCharsAhead++; if (((Tx >> 5) & 1) == 1) interfaceCharsAhead++; if (((Tx >> 6) & 1) == 1) interfaceCharsAhead++; if (((Tx >> 7) & 1) == 1) interfaceCharsAhead++; else lastrun = true; historicalBytesOffset +=interfaceCharsAhead; } while (lastrun == false); //Make sure we are still within bounds if (historicalBytesOffset + historicalByteNum > atr.length) return null; //Copy historical byte and return them. System.arraycopy(atr, historicalBytesOffset, historicalBytes, 0, historicalByteNum); return historicalBytes; } /** * Take a byte array with an ATR as input and modify the interface characters so that only T=1 * is available as transport protocol. * @param atr byte array containing the original ATR * @return modified ATR or null on error */ public static byte[] adjustATR(byte[] atr) { //No or short ATR if (atr == null) return null; if (atr.length < 2) return null; byte historicalBytesNum = 0; byte tck = 0x00; byte[] historicalBytes = extractHistoricalBytes(atr); //In case the historical bytes were not parseable, go ahead without them if (historicalBytes != null) historicalBytesNum = (byte)(historicalBytes.length & 0x0F); //Build new ATR, T=1 only + original historical bytes byte[] newAtr = new byte[2 + 1 + historicalBytesNum + 1]; newAtr[0] = 0x3B; newAtr[1] = (byte)(0x80 | historicalBytesNum); newAtr[2] = 0x01; if (historicalBytesNum > 0) System.arraycopy(historicalBytes, 0, newAtr, 3, historicalBytesNum); for (int i = 1; i < newAtr.length -1; i++) tck ^= newAtr[i]; newAtr[newAtr.length-1] = tck; //TODO: As we do not support multiple logical channels, we should also modify //the number of logical channels in the historical bytes. Since pySim-shell does //not need this information. However, if we try with other platforms (a phone with //simtrace2 cardem for example), there may be problems. return newAtr; } }