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 17 package com.android.server.uwb.secure; 18 19 import static com.android.server.uwb.secure.iso7816.StatusWord.SW_NO_ERROR; 20 import static com.android.server.uwb.secure.iso7816.StatusWord.SW_NO_SPECIFIC_DIAGNOSTIC; 21 22 import android.util.Log; 23 24 import androidx.annotation.NonNull; 25 import androidx.annotation.VisibleForTesting; 26 import androidx.annotation.WorkerThread; 27 28 import com.android.server.uwb.secure.csml.FiRaCommand; 29 import com.android.server.uwb.secure.iso7816.CommandApdu; 30 import com.android.server.uwb.secure.iso7816.ResponseApdu; 31 import com.android.server.uwb.secure.iso7816.StatusWord; 32 import com.android.server.uwb.secure.omapi.OmapiConnection; 33 import com.android.server.uwb.secure.omapi.OmapiConnection.InitCompletionCallback; 34 35 import java.io.IOException; 36 import java.util.Arrays; 37 38 /** Manages the Secure Element and allows communications with the FiRa applet. */ 39 @WorkerThread 40 public class SecureElementChannel { 41 private static final String LOG_TAG = "SecureElementChannel"; 42 private static final int MAX_SE_OPERATION_RETRIES = 3; 43 private static final int DELAY_BETWEEN_SE_RETRY_ATTEMPTS_MILLIS = 10; 44 45 private static final StatusWord SW_TEMPORARILY_UNAVAILABLE = 46 StatusWord.SW_CONDITIONS_NOT_SATISFIED; 47 48 private final OmapiConnection mOmapiConnection; 49 private final boolean mRemoveDelayBetweenRetriesForTest; 50 51 private boolean mIsOpened = false; 52 53 /** 54 * The constructor of the SecureElementChannel. 55 */ SecureElementChannel(@onNull OmapiConnection omapiConnection)56 public SecureElementChannel(@NonNull OmapiConnection omapiConnection) { 57 this(omapiConnection, /* removeDelayBetweenRetries= */ false); 58 } 59 60 // This constructor is made visible because we need to remove the delay between SE operations 61 // during tests. Calling Thread.sleep in tests actually causes the thread running the test to 62 // sleep and leads to the test timing out. 63 @VisibleForTesting SecureElementChannel( @onNull OmapiConnection omapiConnection, boolean removeDelayBetweenRetriesForTest)64 SecureElementChannel( 65 @NonNull OmapiConnection omapiConnection, boolean removeDelayBetweenRetriesForTest) { 66 this.mOmapiConnection = omapiConnection; 67 this.mRemoveDelayBetweenRetriesForTest = removeDelayBetweenRetriesForTest; 68 } 69 70 /** 71 * Initializes the SecureElementChannel. 72 */ init(@onNull InitCompletionCallback callback)73 public void init(@NonNull InitCompletionCallback callback) { 74 mOmapiConnection.init(callback::onInitCompletion); 75 } 76 77 /** 78 * Opens the channel to the FiRa applet, true if success. 79 */ openChannel()80 public boolean openChannel() { 81 try { 82 ResponseApdu responseApdu = openChannelWithResponse(); 83 if (responseApdu.getStatusWord() != SW_NO_ERROR.toInt()) { 84 logw("Received error [" + responseApdu + "] while opening channel"); 85 return false; 86 } 87 } catch (IOException e) { 88 loge("Encountered exception while opening channel" + e); 89 return false; 90 } 91 return true; 92 } 93 94 /** 95 * Opens the channel to the FiRa applet, returns the Response APDU. 96 */ 97 @NonNull openChannelWithResponse()98 public ResponseApdu openChannelWithResponse() throws IOException { 99 ResponseApdu responseApdu = ResponseApdu.fromStatusWord(SW_TEMPORARILY_UNAVAILABLE); 100 for (int i = 0; i < MAX_SE_OPERATION_RETRIES; i++) { 101 responseApdu = mOmapiConnection.openChannel(); 102 103 if (!shouldRetryOpenChannel(responseApdu)) { 104 break; 105 } 106 107 logw( 108 "Open channel failed because SE is temporarily unavailable. " 109 + "Total attempts so far: " 110 + (i + 1)); 111 112 threadSleep(DELAY_BETWEEN_SE_RETRY_ATTEMPTS_MILLIS); 113 } 114 115 if (responseApdu.getStatusWord() == StatusWord.SW_NO_ERROR.toInt()) { 116 mIsOpened = true; 117 } else { 118 logw("All open channel attempts failed!"); 119 } 120 return responseApdu; 121 } 122 123 /** 124 * Checks if current channel is opened or not. 125 */ isOpened()126 public boolean isOpened() { 127 return mIsOpened; 128 } 129 shouldRetryOpenChannel(ResponseApdu responseApdu)130 private boolean shouldRetryOpenChannel(ResponseApdu responseApdu) { 131 return Arrays.asList(SW_TEMPORARILY_UNAVAILABLE, SW_NO_SPECIFIC_DIAGNOSTIC) 132 .contains(StatusWord.fromInt(responseApdu.getStatusWord())); 133 } 134 135 /** 136 * Closes the channel to the FiRa applet. 137 * @return 138 */ closeChannel()139 public boolean closeChannel() { 140 try { 141 mOmapiConnection.closeChannel(); 142 } catch (IOException e) { 143 logw("Encountered exception while closing channel" + e); 144 return false; 145 } 146 mIsOpened = false; 147 return true; 148 } 149 150 /** 151 * Transmits a Command APDU defined by the FiRa to the FiRa applet. 152 */ 153 @NonNull transmit(@onNull FiRaCommand fiRaCommand)154 public ResponseApdu transmit(@NonNull FiRaCommand fiRaCommand) throws IOException { 155 return transmit(fiRaCommand.getCommandApdu()); 156 } 157 158 /** 159 * Transmits a Command APDU to FiRa applet. 160 */ 161 @NonNull transmit(@onNull CommandApdu command)162 public ResponseApdu transmit(@NonNull CommandApdu command) throws IOException { 163 ResponseApdu responseApdu = ResponseApdu.fromStatusWord(SW_TEMPORARILY_UNAVAILABLE); 164 165 if (!mIsOpened) { 166 return responseApdu; 167 } 168 for (int i = 0; i < MAX_SE_OPERATION_RETRIES; i++) { 169 responseApdu = mOmapiConnection.transmit(command); 170 if (responseApdu.getStatusWord() != SW_TEMPORARILY_UNAVAILABLE.toInt()) { 171 return responseApdu; 172 } 173 logw( 174 "Transmit failed because SE is temporarily unavailable. " 175 + "Total attempts so far: " 176 + (i + 1)); 177 threadSleep(DELAY_BETWEEN_SE_RETRY_ATTEMPTS_MILLIS); 178 } 179 logw("All transmit attempts for SE failed!"); 180 return responseApdu; 181 } 182 threadSleep(long millis)183 private void threadSleep(long millis) { 184 if (!mRemoveDelayBetweenRetriesForTest) { 185 try { 186 Thread.sleep(millis); 187 } catch (InterruptedException e) { 188 logw("Thread sleep interrupted."); 189 } 190 } 191 } 192 logw(String dbgMsg)193 private void logw(String dbgMsg) { 194 Log.w(LOG_TAG, dbgMsg); 195 } 196 loge(String dbgMsg)197 private void loge(String dbgMsg) { 198 Log.e(LOG_TAG, dbgMsg); 199 } 200 } 201