1 /* 2 * Copyright (C) 2021 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 package com.android.server.uwb.secure.iso7816; 17 18 import static com.android.server.uwb.secure.iso7816.StatusWord.SW_APPLET_SELECT_FAILED; 19 import static com.android.server.uwb.secure.iso7816.StatusWord.SW_CLA_NOT_SUPPORTED; 20 import static com.android.server.uwb.secure.iso7816.StatusWord.SW_CONDITIONS_NOT_SATISFIED; 21 import static com.android.server.uwb.secure.iso7816.StatusWord.SW_FILE_NOT_FOUND; 22 import static com.android.server.uwb.secure.iso7816.StatusWord.SW_INCORRECT_P1P2; 23 import static com.android.server.uwb.secure.iso7816.StatusWord.SW_INS_NOT_SUPPORTED; 24 import static com.android.server.uwb.secure.iso7816.StatusWord.SW_NO_ERROR; 25 import static com.android.server.uwb.secure.iso7816.StatusWord.SW_UNKNOWN_ERROR; 26 import static com.android.server.uwb.secure.iso7816.StatusWord.SW_WRONG_DATA; 27 import static com.android.server.uwb.secure.iso7816.StatusWord.SW_WRONG_LE; 28 import static com.android.server.uwb.secure.iso7816.StatusWord.SW_WRONG_LENGTH; 29 import static com.android.server.uwb.secure.iso7816.StatusWord.SW_WRONG_P1P2; 30 31 import androidx.annotation.Nullable; 32 import androidx.annotation.VisibleForTesting; 33 34 import com.android.server.uwb.util.Hex; 35 36 import com.google.common.base.Objects; 37 import com.google.common.base.Preconditions; 38 import com.google.common.primitives.Bytes; 39 import com.google.common.primitives.Shorts; 40 41 import java.nio.ByteBuffer; 42 import java.util.Arrays; 43 import java.util.List; 44 import java.util.Locale; 45 import java.util.concurrent.TimeUnit; 46 47 48 /** A class that represents the data contained in an ISO/IEC 7816-4 Response APDU. */ 49 public class ResponseApdu { 50 51 public static final ResponseApdu SW_CONDITIONS_NOT_SATISFIED_APDU = 52 ResponseApdu.fromStatusWord(SW_CONDITIONS_NOT_SATISFIED); 53 54 public static final ResponseApdu SW_INCORRECT_P1P2_APDU = 55 ResponseApdu.fromStatusWord(SW_INCORRECT_P1P2); 56 57 public static final ResponseApdu SW_FILE_NOT_FOUND_APDU = 58 ResponseApdu.fromStatusWord(SW_FILE_NOT_FOUND); 59 60 public static final ResponseApdu SW_WRONG_P1P2_APDU = 61 ResponseApdu.fromStatusWord(SW_WRONG_P1P2); 62 63 public static final ResponseApdu SW_WRONG_LE_APDU = 64 ResponseApdu.fromStatusWord(SW_WRONG_LE); 65 66 public static final ResponseApdu SW_WRONG_DATA_APDU = 67 ResponseApdu.fromStatusWord(SW_WRONG_DATA); 68 69 public static final ResponseApdu SW_WRONG_LENGTH_APDU = 70 ResponseApdu.fromStatusWord(SW_WRONG_LENGTH); 71 72 public static final ResponseApdu SW_CLA_NOT_SUPPORTED_APDU = 73 ResponseApdu.fromStatusWord(SW_CLA_NOT_SUPPORTED); 74 75 public static final ResponseApdu SW_INS_NOT_SUPPORTED_APDU = 76 ResponseApdu.fromStatusWord(SW_INS_NOT_SUPPORTED); 77 78 public static final ResponseApdu SW_WRONG_FILE_APDU = 79 ResponseApdu.fromStatusWord(SW_FILE_NOT_FOUND); 80 81 public static final ResponseApdu SW_UNKNOWN_APDU = ResponseApdu.fromStatusWord( 82 SW_UNKNOWN_ERROR); 83 84 public static final ResponseApdu SW_SUCCESS_APDU = ResponseApdu.fromStatusWord(SW_NO_ERROR); 85 86 public static final ResponseApdu SW_APPLET_SELECT_FAILED_APDU = 87 ResponseApdu.fromStatusWord(SW_APPLET_SELECT_FAILED); 88 89 private static final long NO_TIME_RECORDED = -1L; 90 91 private static final int SIZE_OF_SW = 2; 92 93 private static final int MASK_OF_SW = 0xffff; 94 95 private final byte[] mRdata; 96 97 private final int mSw; 98 99 private final long mCmdTimeMillis; 100 101 @VisibleForTesting ResponseApdu(byte[] rdata, int sw, long cmdTimeMillis)102 ResponseApdu(byte[] rdata, int sw, long cmdTimeMillis) { 103 this.mRdata = rdata; 104 this.mSw = sw; 105 this.mCmdTimeMillis = cmdTimeMillis; 106 } 107 108 /** 109 * Parses a raw APDU response to set the response data and status word. A response consists of 110 * at 111 * least a two byte status word and any number of data bytes. A standard length APDU supports 112 * 256 113 * bytes of data and 2 bytes of status word while and extended length APDU supports 32KB of 114 * response data. A minimum response is simply a status word. 115 * 116 * @param response The raw response from the card to parse. 117 * @throws IllegalArgumentException if the response is less than 2 bytes long. 118 */ fromResponse(byte[] response)119 public static ResponseApdu fromResponse(byte[] response) { 120 return fromResponse(response, NO_TIME_RECORDED, TimeUnit.MILLISECONDS); 121 } 122 123 /** 124 * Generate the ResponseApdu from the byte array(data) and status word. 125 */ fromDataAndStatusWord(byte[] data, int sw)126 public static ResponseApdu fromDataAndStatusWord(byte[] data, int sw) { 127 Preconditions.checkArgument((sw >> Short.SIZE) == 0); 128 return fromResponse( 129 Bytes.concat(data == null ? new byte[]{} : data, Shorts.toByteArray((short) sw))); 130 } 131 132 /** 133 * Generate the ResponseApdu form the list of TlvDatum and status word. 134 */ fromDataAndStatusWord(List<TlvDatum> data, int sw)135 public static ResponseApdu fromDataAndStatusWord(List<TlvDatum> data, int sw) { 136 byte[] dataBytes = new byte[]{}; 137 for (TlvDatum tlvDatum : data) { 138 dataBytes = Bytes.concat(dataBytes, tlvDatum.toBytes()); 139 } 140 return fromDataAndStatusWord(dataBytes, sw); 141 } 142 143 /** 144 * Generate the ResponseApdu form the status word. 145 */ fromStatusWord(StatusWord sw)146 public static ResponseApdu fromStatusWord(StatusWord sw) { 147 return fromResponse(sw.toBytes()); 148 } 149 150 /** 151 * Parses a raw APDU response to set the response data and status word. A response consists of 152 * at 153 * least a two byte status word and any number of data bytes. A standard length APDU supports 154 * 256 155 * bytes of data and 2 bytes of status word while and extended length APDU supports 32KB of 156 * response data. A minimum response is simply a status word. 157 * 158 * @param response The raw response from the card to parse. 159 * @param time the time for the command to execute. 160 * @param timeUnit the {@link TimeUnit} of the execution time. 161 * @throws IllegalArgumentException if the response is less than 2 bytes long. 162 */ fromResponse(byte[] response, long time, TimeUnit timeUnit)163 public static ResponseApdu fromResponse(byte[] response, long time, TimeUnit timeUnit) { 164 Preconditions.checkNotNull(response); 165 int len = response.length; 166 long cmdTimeMillis = timeUnit.toMillis(time); 167 168 // A response must at least have a status word (2 bytes). 169 Preconditions.checkArgument( 170 len >= SIZE_OF_SW, 171 "Invalid response APDU after %sms. Must be at least 2 bytes long: [%s]", 172 cmdTimeMillis, 173 Hex.encode(response)); 174 175 ByteBuffer buffer = ByteBuffer.wrap(response); 176 177 // Extract and store any response data. 178 int rdataLen = len - SIZE_OF_SW; 179 byte[] rdata = new byte[rdataLen]; 180 buffer.get(rdata, 0, rdataLen); 181 182 // Extract and set the status word. 183 int sw = buffer.getShort() & MASK_OF_SW; 184 185 return new ResponseApdu(rdata, sw, cmdTimeMillis); 186 } 187 188 /** 189 * Returns a copy of the response data for the APDU. Updates to this copy will not affect the 190 * internal copy in this instance. 191 */ getResponseData()192 public byte[] getResponseData() { 193 return mRdata.clone(); 194 } 195 196 /** 197 * Gets the status word. 198 */ getStatusWord()199 public int getStatusWord() { 200 return mSw; 201 } 202 203 /** 204 * Convert the ResponseApdu to the byte array. 205 */ toByteArray()206 public byte[] toByteArray() { 207 return Bytes.concat(mRdata, Shorts.toByteArray((short) mSw)); 208 } 209 210 @Override toString()211 public String toString() { 212 StringBuilder sb = new StringBuilder("Response: "); 213 214 if (mRdata != null && mRdata.length > 0) { 215 sb.append(Hex.encode(mRdata)).append(", "); 216 } 217 218 sb.append(String.format("SW=%04x", mSw)); 219 220 if (mCmdTimeMillis > NO_TIME_RECORDED) { 221 sb.append(String.format(Locale.US, ", elapsed: %dms", mCmdTimeMillis)); 222 } 223 224 return sb.toString(); 225 } 226 227 /** 228 * {@inheritDoc} 229 * 230 * <p>This ignores the time the APDU took to complete and only compares the response data and 231 * status word. 232 */ 233 @Override equals(@ullable Object obj)234 public boolean equals(@Nullable Object obj) { 235 if (obj == null) { 236 return false; 237 } 238 239 if (this.getClass() == obj.getClass()) { 240 ResponseApdu other = (ResponseApdu) obj; 241 return Arrays.equals(this.mRdata, other.mRdata) && this.mSw == other.mSw; 242 } 243 return false; 244 } 245 246 @Override hashCode()247 public int hashCode() { 248 return Objects.hashCode(Arrays.hashCode(mRdata), mSw); 249 } 250 } 251