blob: 28851247c2e62ff58e1f042ff6b42046956d3d98 [file] [log] [blame]
/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.internal.telephony.uicc;
import static android.security.keystore.KeyProperties.AUTH_DEVICE_CREDENTIAL;
import static android.security.keystore.KeyProperties.BLOCK_MODE_GCM;
import static android.security.keystore.KeyProperties.ENCRYPTION_PADDING_NONE;
import static android.security.keystore.KeyProperties.KEY_ALGORITHM_AES;
import static android.security.keystore.KeyProperties.PURPOSE_DECRYPT;
import static android.security.keystore.KeyProperties.PURPOSE_ENCRYPT;
import static com.android.internal.telephony.TelephonyStatsLog.PIN_STORAGE_EVENT;
import static com.android.internal.telephony.TelephonyStatsLog.PIN_STORAGE_EVENT__EVENT__CACHED_PIN_DISCARDED;
import static com.android.internal.telephony.TelephonyStatsLog.PIN_STORAGE_EVENT__EVENT__PIN_COUNT_NOT_MATCHING_AFTER_REBOOT;
import static com.android.internal.telephony.TelephonyStatsLog.PIN_STORAGE_EVENT__EVENT__PIN_DECRYPTION_ERROR;
import static com.android.internal.telephony.TelephonyStatsLog.PIN_STORAGE_EVENT__EVENT__PIN_ENCRYPTION_ERROR;
import static com.android.internal.telephony.TelephonyStatsLog.PIN_STORAGE_EVENT__EVENT__PIN_REQUIRED_AFTER_REBOOT;
import static com.android.internal.telephony.TelephonyStatsLog.PIN_STORAGE_EVENT__EVENT__PIN_STORED_FOR_VERIFICATION;
import static com.android.internal.telephony.TelephonyStatsLog.PIN_STORAGE_EVENT__EVENT__PIN_VERIFICATION_FAILURE;
import static com.android.internal.telephony.TelephonyStatsLog.PIN_STORAGE_EVENT__EVENT__PIN_VERIFICATION_SKIPPED_SIM_CARD_MISMATCH;
import static com.android.internal.telephony.TelephonyStatsLog.PIN_STORAGE_EVENT__EVENT__PIN_VERIFICATION_SUCCESS;
import static com.android.internal.telephony.uicc.IccCardStatus.PinState.PINSTATE_ENABLED_NOT_VERIFIED;
import static com.android.internal.telephony.uicc.IccCardStatus.PinState.PINSTATE_ENABLED_VERIFIED;
import android.annotation.Nullable;
import android.app.KeyguardManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.os.AsyncResult;
import android.os.Handler;
import android.os.Message;
import android.os.PersistableBundle;
import android.provider.Settings;
import android.security.keystore.KeyGenParameterSpec;
import android.telephony.CarrierConfigManager;
import android.telephony.TelephonyManager;
import android.telephony.TelephonyManager.SimState;
import android.util.Base64;
import android.util.SparseArray;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.PhoneConstants;
import com.android.internal.telephony.PhoneFactory;
import com.android.internal.telephony.SubscriptionInfoUpdater;
import com.android.internal.telephony.TelephonyStatsLog;
import com.android.internal.telephony.nano.StoredPinProto.EncryptedPin;
import com.android.internal.telephony.nano.StoredPinProto.StoredPin;
import com.android.internal.telephony.nano.StoredPinProto.StoredPin.PinStatus;
import com.android.internal.telephony.uicc.IccCardStatus.PinState;
import com.android.internal.util.ArrayUtils;
import com.android.telephony.Rlog;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.security.KeyStore;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
/**
* This class stores the SIM PIN for automatic verification after an unattended reboot.
*/
public class PinStorage extends Handler {
private static final String TAG = "PinStorage";
private static final boolean VDBG = false; // STOPSHIP if true
/**
* Time duration in milliseconds to allow automatic PIN verification after reboot. All unused
* PINs are discarded when the timer expires.
*/
private static final int TIMER_VALUE_AFTER_OTA_MILLIS = 20_000;
/**
* Time duration in milliseconds to reboot the device after {@code prepareUnattendedReboot}
* is invoked. After the time expires, a new invocation of {@code prepareUnattendedReboot} is
* required to perform the automatic PIN verification after reboot.
*/
private static final int TIMER_VALUE_BEFORE_OTA_MILLIS = 20_000;
/** Minimum valid length of the ICCID. */
private static final int MIN_ICCID_LENGTH = 12;
/** Minimum length of the SIM PIN, as per 3GPP TS 31.101. */
private static final int MIN_PIN_LENGTH = 4;
/** Maximum length of the SIM PIN, as per 3GPP TS 31.101. */
private static final int MAX_PIN_LENGTH = 8;
// Variables related to the encryption of the SIM PIN.
private static final String ANDROID_KEY_STORE_PROVIDER = "AndroidKeyStore";
private static final String CIPHER_TRANSFORMATION = "AES/GCM/NoPadding";
private static final int GCM_PARAMETER_TAG_BIT_LEN = 128;
private static final int SHORT_TERM_KEY_DURATION_MINUTES = 15;
/** Alias of the long-term key that does not require user authentication. */
private static final String KEYSTORE_ALIAS_LONG_TERM_ALWAYS = "PinStorage_longTerm_always_key";
/** Alias of the user authentication blound long-term key. */
private static final String KEYSTORE_ALIAS_LONG_TERM_USER_AUTH = "PinStorage_longTerm_ua_key";
/** Alias of the short-term key (30 minutes) used before and after an unattended reboot. */
private static final String KEYSTORE_ALIAS_SHORT_TERM = "PinStorage_shortTerm_key";
// Constants related to the storage of the encrypted SIM PIN to non-volatile memory.
// Data is stored in two separate files:
// - "available" is for the PIN(s) in AVAILABLE state and uses a key that does not expire
// - "reboot" is for the PIN(s) in other states and uses a short-term key (30 minutes)
private static final String SHARED_PREFS_NAME = "pinstorage_prefs";
private static final String SHARED_PREFS_AVAILABLE_PIN_BASE_KEY = "encrypted_pin_available_";
private static final String SHARED_PREFS_REBOOT_PIN_BASE_KEY = "encrypted_pin_reboot_";
private static final String SHARED_PREFS_STORED_PINS = "stored_pins";
// Events
private static final int ICC_CHANGED_EVENT = 1;
private static final int CARRIER_CONFIG_CHANGED_EVENT = 2;
private static final int TIMER_EXPIRATION_EVENT = 3;
private static final int USER_UNLOCKED_EVENT = 4;
private static final int SUPPLY_PIN_COMPLETE = 5;
private final Context mContext;
private final int mBootCount;
private final KeyStore mKeyStore;
private SecretKey mLongTermSecretKey;
private SecretKey mShortTermSecretKey;
private boolean mIsDeviceSecure;
private boolean mIsDeviceLocked;
private boolean mLastCommitResult = true;
/** Duration of the short-term key, in minutes. */
@VisibleForTesting
public int mShortTermSecretKeyDurationMinutes;
/** RAM storage is used on secure devices before the device is unlocked. */
private final SparseArray<byte[]> mRamStorage;
/** Receiver for the required intents. */
private final BroadcastReceiver mCarrierConfigChangedReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED.equals(action)) {
int slotId = intent.getIntExtra(CarrierConfigManager.EXTRA_SLOT_INDEX, -1);
sendMessage(obtainMessage(CARRIER_CONFIG_CHANGED_EVENT, slotId, 0));
} else if (TelephonyManager.ACTION_SIM_CARD_STATE_CHANGED.equals(action)
|| TelephonyManager.ACTION_SIM_APPLICATION_STATE_CHANGED.equals(action)) {
int slotId = intent.getIntExtra(PhoneConstants.PHONE_KEY, -1);
int state = intent.getIntExtra(
TelephonyManager.EXTRA_SIM_STATE, TelephonyManager.SIM_STATE_UNKNOWN);
if (validateSlotId(slotId)) {
sendMessage(obtainMessage(ICC_CHANGED_EVENT, slotId, state));
}
} else if (Intent.ACTION_USER_UNLOCKED.equals(action)) {
sendMessage(obtainMessage(USER_UNLOCKED_EVENT));
}
}
};
public PinStorage(Context context) {
mContext = context;
mBootCount = getBootCount();
mKeyStore = initializeKeyStore();
mShortTermSecretKeyDurationMinutes = SHORT_TERM_KEY_DURATION_MINUTES;
mIsDeviceSecure = isDeviceSecure();
mIsDeviceLocked = mIsDeviceSecure ? isDeviceLocked() : false;
// Register for necessary intents.
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
intentFilter.addAction(TelephonyManager.ACTION_SIM_CARD_STATE_CHANGED);
intentFilter.addAction(TelephonyManager.ACTION_SIM_APPLICATION_STATE_CHANGED);
intentFilter.addAction(Intent.ACTION_USER_UNLOCKED);
mContext.registerReceiver(mCarrierConfigChangedReceiver, intentFilter);
// Initialize the long term secret key. This needs to be present in all cases:
// - if the device is not secure or is locked: key does not require user authentication
// - if the device is secure and unlocked: key requires user authentication.
// The short term key is retrieved later when needed.
String alias = (!mIsDeviceSecure || mIsDeviceLocked)
? KEYSTORE_ALIAS_LONG_TERM_ALWAYS : KEYSTORE_ALIAS_LONG_TERM_USER_AUTH;
mLongTermSecretKey = initializeSecretKey(alias, /*createIfAbsent=*/ true);
// If the device is not securee or is unlocked, we can start logic. Otherwise we need to
// wait for the device to be unlocked and store any temporary PIN in RAM.
if (!mIsDeviceSecure || !mIsDeviceLocked) {
mRamStorage = null;
onDeviceReady();
} else {
logd("Device is locked - Postponing initialization");
mRamStorage = new SparseArray<>();
}
}
/** Store the {@code pin} for the {@code slotId}. */
public synchronized void storePin(String pin, int slotId) {
String iccid = getIccid(slotId);
if (!validatePin(pin) || !validateIccid(iccid) || !validateSlotId(slotId)) {
// We are unable to store the PIN. At least clear the old one, if present.
loge("storePin[%d] - Invalid PIN, slotId or ICCID", slotId);
clearPin(slotId);
return;
}
if (!isCacheAllowed(slotId)) {
logd("storePin[%d]: caching it not allowed", slotId);
return;
}
logd("storePin[%d]", slotId);
StoredPin storedPin = new StoredPin();
storedPin.iccid = iccid;
storedPin.pin = pin;
storedPin.slotId = slotId;
storedPin.status = PinStatus.AVAILABLE;
savePinInformation(slotId, storedPin);
}
/** Clear the cached pin for the {@code slotId}. */
public synchronized void clearPin(int slotId) {
logd("clearPin[%d]", slotId);
if (!validateSlotId(slotId)) {
return;
}
savePinInformation(slotId, null);
}
/**
* Return the cached pin for the SIM card identified by {@code slotId} and {@code iccid}, or
* an empty string if it is not available.
*
* The method returns the PIN only if the state is VERIFICATION_READY. If the PIN is found,
* its state changes to AVAILABLE, so that it cannot be retrieved a second time during the
* same boot cycle. If the PIN verification fails, it will be removed after the failed attempt.
*/
public synchronized String getPin(int slotId, String iccid) {
if (!validateSlotId(slotId) || !validateIccid(iccid)) {
return "";
}
StoredPin storedPin = loadPinInformation(slotId);
if (storedPin != null) {
if (!storedPin.iccid.equals(iccid)) {
// The ICCID does not match: it's possible that the SIM card was changed.
// Delete the cached PIN.
savePinInformation(slotId, null);
TelephonyStatsLog.write(PIN_STORAGE_EVENT,
PIN_STORAGE_EVENT__EVENT__PIN_VERIFICATION_SKIPPED_SIM_CARD_MISMATCH,
/* number_of_pins= */ 1);
} else if (storedPin.status == PinStatus.VERIFICATION_READY) {
logd("getPin[%d] - Found PIN ready for verification", slotId);
// Move the state to AVAILABLE, so that it cannot be retrieved again.
storedPin.status = PinStatus.AVAILABLE;
savePinInformation(slotId, storedPin);
return storedPin.pin;
}
}
return "";
}
/**
* Prepare for an unattended reboot.
*
* All PINs in AVAILABLE and VERIFICATION_READY state are moved to REBOOT_READY state. A
* timer is started to make sure that reboot occurs shortly after invoking this method.
*
* @return The result of the reboot preparation.
*/
@TelephonyManager.PrepareUnattendedRebootResult
public synchronized int prepareUnattendedReboot() {
// Unattended reboot should never occur before the device is unlocked.
if (mIsDeviceLocked) {
loge("prepareUnattendedReboot - Device is locked");
return TelephonyManager.PREPARE_UNATTENDED_REBOOT_ERROR;
}
// Start timer to make sure that device is rebooted shortly after this is executed.
if (!startTimer(TIMER_VALUE_BEFORE_OTA_MILLIS)) {
return TelephonyManager.PREPARE_UNATTENDED_REBOOT_ERROR;
}
int numSlots = getSlotCount();
SparseArray<StoredPin> storedPins = loadPinInformation();
// Delete any previous short-term key, if present: a new one is created (if needed).
deleteSecretKey(KEYSTORE_ALIAS_SHORT_TERM);
mShortTermSecretKey = null;
// If any PIN is present, generate a new short-term key to save PIN(s) to
// non-volatile memory.
if (storedPins.size() > 0) {
mShortTermSecretKey =
initializeSecretKey(KEYSTORE_ALIAS_SHORT_TERM, /*createIfAbsent=*/ true);
}
@TelephonyManager.PrepareUnattendedRebootResult
int result = TelephonyManager.PREPARE_UNATTENDED_REBOOT_SUCCESS;
int storedCount = 0;
int notAvailableCount = 0;
for (int slotId = 0; slotId < numSlots; slotId++) {
StoredPin storedPin = storedPins.get(slotId);
if (storedPin != null) {
storedPin.status = PinStatus.REBOOT_READY;
if (!savePinInformation(slotId, storedPin)) {
result = TelephonyManager.PREPARE_UNATTENDED_REBOOT_ERROR;
break;
}
storedCount++;
} else if (isPinState(slotId, PINSTATE_ENABLED_VERIFIED)) {
// If PIN is not available, check if PIN will be required after reboot (current PIN
// status is enabled and verified).
loge("Slot %d requires PIN and is not cached", slotId);
result = TelephonyManager.PREPARE_UNATTENDED_REBOOT_PIN_REQUIRED;
notAvailableCount++;
}
}
// Generate metrics
if (result == TelephonyManager.PREPARE_UNATTENDED_REBOOT_SUCCESS) {
logd("prepareUnattendedReboot - Stored %d PINs", storedCount);
TelephonyStatsLog.write(PIN_STORAGE_EVENT,
PIN_STORAGE_EVENT__EVENT__PIN_STORED_FOR_VERIFICATION, storedCount);
} else if (result == TelephonyManager.PREPARE_UNATTENDED_REBOOT_PIN_REQUIRED) {
logd("prepareUnattendedReboot - Required %d PINs after reboot", notAvailableCount);
TelephonyStatsLog.write(PIN_STORAGE_EVENT,
PIN_STORAGE_EVENT__EVENT__PIN_REQUIRED_AFTER_REBOOT, notAvailableCount);
}
// Save number of PINs to generate metrics after reboot
saveNumberOfCachedPins(storedCount);
return result;
}
/**
* Execute logic when a secure device is unlocked.
*
* The temporary long-term key that does not require user verification is replaced by the long
* term key that requires user verification. The cached PIN temporarily stored in RAM are
* merged with those on disk from the previous boot.
*/
private synchronized void onUserUnlocked() {
if (!mIsDeviceLocked) {
// This should never happen.
// Nothing to do because the device was already unlocked before
return;
}
logd("onUserUnlocked - Device is unlocked");
// It's possible that SIM PIN was already verified and stored temporarily in RAM. Load the
// data and erase the memory.
SparseArray<StoredPin> storedPinInRam = loadPinInformation();
cleanRamStorage();
// Mark the device as unlocked
mIsDeviceLocked = false;
// Replace the temporary long-term key without user authentication with a new long-term
// key that requires user authentication to save all PINs previously in RAM (all in
// AVAILABLE state) to disk.
mLongTermSecretKey =
initializeSecretKey(KEYSTORE_ALIAS_LONG_TERM_USER_AUTH, /*createIfAbsent=*/ true);
// Save the PINs previously in RAM to disk, overwriting any PIN that might already exists.
for (int i = 0; i < storedPinInRam.size(); i++) {
savePinInformation(storedPinInRam.keyAt(i), storedPinInRam.valueAt(i));
}
// At this point the module is fully initialized. Execute the start logic.
onDeviceReady();
// Verify any pending PIN for SIM cards that need it.
verifyPendingPins();
}
/**
* Executes logic when module is fully ready. This occurs immediately if the device is not
* secure or after the user unlocks the device.
*
* At this point, the short-term key is initialized (if present), the configuration is read
* and the status of each PIN is updated as needed.
*/
private void onDeviceReady() {
logd("onDeviceReady");
// Try to initialize the short term key, if present, as this would be required to read
// stored PIN for verification.
mShortTermSecretKey =
initializeSecretKey(KEYSTORE_ALIAS_SHORT_TERM, /*createIfAbsent=*/ false);
int verificationReadyCount = 0;
int slotCount = getSlotCount();
for (int slotId = 0; slotId < slotCount; slotId++) {
// Read PIN information from storage
StoredPin storedPin = loadPinInformation(slotId);
if (storedPin == null) {
continue;
}
// For each PIN in AVAILABLE state, check the boot count.
// If the boot count matches, it means that module crashed and it's ok to preserve
// the PIN code. If the boot count does not match, then delete those PINs.
if (storedPin.status == PinStatus.AVAILABLE) {
if (storedPin.bootCount != mBootCount) {
logd("Boot count [%d] does not match - remove PIN", slotId);
savePinInformation(slotId, null);
continue;
}
logd("Boot count [%d] matches - keep stored PIN", slotId);
}
// If there is any PIN in REBOOT_READY state, move it to VERIFICATION_READY and start
// the timer. Don't change PINs that might be already in VERIFICATION_READY state
// (e.g. due to crash).
if (storedPin.status == PinStatus.REBOOT_READY) {
storedPin.status = PinStatus.VERIFICATION_READY;
savePinInformation(slotId, storedPin);
verificationReadyCount++;
}
}
if (verificationReadyCount > 0) {
startTimer(TIMER_VALUE_AFTER_OTA_MILLIS);
}
// Generate metrics for PINs that had been stored before reboot, but are not available
// after. This can happen if there is an excessive delay in unlocking the device (short
// term key expires), but also if a new SIM card without PIN is present.
int prevCachedPinCount = saveNumberOfCachedPins(0);
if (prevCachedPinCount > verificationReadyCount) {
TelephonyStatsLog.write(PIN_STORAGE_EVENT,
PIN_STORAGE_EVENT__EVENT__PIN_COUNT_NOT_MATCHING_AFTER_REBOOT,
prevCachedPinCount - verificationReadyCount);
}
}
/**
* Executes logic at the expiration of the timer. This method is common for two cases:
* - timer started after unattended reeboot to verify the SIM PIN automatically
* - timer started after prepareUnattendedReboot() is invoked.
*/
private synchronized void onTimerExpiration() {
logd("onTimerExpiration");
int discardedPin = 0;
int slotCount = getSlotCount();
for (int slotId = 0; slotId < slotCount; slotId++) {
// Read PIN information from storage
StoredPin storedPin = loadPinInformation(slotId);
if (storedPin == null) {
continue;
}
// Delete all PINs in VERIFICATION_READY state. This happens when reboot occurred after
// OTA, but the SIM card is not detected on the device.
if (storedPin.status == PinStatus.VERIFICATION_READY) {
logd("onTimerExpiration - Discarding PIN in slot %d", slotId);
savePinInformation(slotId, null);
discardedPin++;
continue;
}
// Move all PINs in REBOOT_READY to AVAILABLE. This happens when
// prepareUnattendedReboot() is invoked, but the reboot does not occur.
if (storedPin.status == PinStatus.REBOOT_READY) {
logd("onTimerExpiration - Moving PIN in slot %d back to AVAILABLE", slotId);
storedPin.status = PinStatus.AVAILABLE;
savePinInformation(slotId, storedPin);
continue;
}
}
// Delete short term key no matter the reason of the timer expiration.
// This is done after loading the PIN information, so that it's possible to change
// the status of the PIN as needed.
deleteSecretKey(KEYSTORE_ALIAS_SHORT_TERM);
mShortTermSecretKey = null;
// Reset number of stored PINs (applicable if timer expired before unattended reboot).
saveNumberOfCachedPins(0);
// Write metrics about number of discarded PINs
if (discardedPin > 0) {
TelephonyStatsLog.write(PIN_STORAGE_EVENT,
PIN_STORAGE_EVENT__EVENT__CACHED_PIN_DISCARDED, discardedPin);
}
}
/** Handle the update of the {@code state} of the SIM card in {@code slotId}. */
private synchronized void onSimStatusChange(int slotId, @SimState int state) {
logd("SIM card/application changed[%d]: %s",
slotId, SubscriptionInfoUpdater.simStateString(state));
switch (state) {
case TelephonyManager.SIM_STATE_ABSENT:
case TelephonyManager.SIM_STATE_PIN_REQUIRED: {
// These states are likely to occur after a reboot, so we don't clear cached PINs
// in VERIFICATION_READY state, as they might be verified later, when the SIM is
// detected. On the other hand, we remove PINs in AVAILABLE state.
StoredPin storedPin = loadPinInformation(slotId);
if (storedPin != null && storedPin.status != PinStatus.VERIFICATION_READY) {
savePinInformation(slotId, null);
}
break;
}
case TelephonyManager.SIM_STATE_PUK_REQUIRED:
case TelephonyManager.SIM_STATE_PERM_DISABLED:
case TelephonyManager.SIM_STATE_CARD_IO_ERROR:
// These states indicate that the SIM card will need a manual PIN verification.
// Delete the cached PIN regardless of its state.
clearPin(slotId);
break;
case TelephonyManager.SIM_STATE_NETWORK_LOCKED:
case TelephonyManager.SIM_STATE_CARD_RESTRICTED:
case TelephonyManager.SIM_STATE_LOADED:
case TelephonyManager.SIM_STATE_READY: {
// These states can occur after successful PIN caching, so we don't clear cached
// PINs in AVAILABLE state, as they need to be retained. We clear any PIN in
// other states, as they are no longer needed for automatic verification.
StoredPin storedPin = loadPinInformation(slotId);
if (storedPin != null && storedPin.status != PinStatus.AVAILABLE) {
savePinInformation(slotId, null);
}
break;
}
case TelephonyManager.SIM_STATE_NOT_READY:
case TelephonyManager.SIM_STATE_PRESENT:
default:
break;
}
}
private void onCarrierConfigChanged(int slotId) {
logv("onCarrierConfigChanged[%d]", slotId);
if (!isCacheAllowed(slotId)) {
logd("onCarrierConfigChanged[%d] - PIN caching not allowed", slotId);
clearPin(slotId);
}
}
private void onSupplyPinComplete(int slotId, boolean success) {
logd("onSupplyPinComplete[%d] - success: %s", slotId, success);
if (!success) {
// In case of failure to verify the PIN, delete the stored value.
// Otherwise nothing to do.
clearPin(slotId);
}
// Update metrics:
TelephonyStatsLog.write(
PIN_STORAGE_EVENT,
success
? PIN_STORAGE_EVENT__EVENT__PIN_VERIFICATION_SUCCESS
: PIN_STORAGE_EVENT__EVENT__PIN_VERIFICATION_FAILURE,
/* number_of_pins= */ 1);
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case ICC_CHANGED_EVENT:
onSimStatusChange(/* slotId= */ msg.arg1, /* state= */ msg.arg2);
break;
case CARRIER_CONFIG_CHANGED_EVENT:
onCarrierConfigChanged(/* slotId= */ msg.arg1);
break;
case TIMER_EXPIRATION_EVENT:
onTimerExpiration();
break;
case USER_UNLOCKED_EVENT:
onUserUnlocked();
break;
case SUPPLY_PIN_COMPLETE:
AsyncResult ar = (AsyncResult) msg.obj;
boolean success = ar != null && ar.exception == null;
onSupplyPinComplete(/* slotId= */ msg.arg2, success);
break;
default:
// Nothing to do
break;
}
}
/** Return if the device is secure (device PIN is enabled). */
private boolean isDeviceSecure() {
KeyguardManager keyguardManager = mContext.getSystemService(KeyguardManager.class);
return keyguardManager != null ? keyguardManager.isDeviceSecure() : false;
}
/** Return if the device is locked (device PIN is enabled and not verified). */
private boolean isDeviceLocked() {
KeyguardManager keyguardManager = mContext.getSystemService(KeyguardManager.class);
return keyguardManager != null
? keyguardManager.isDeviceSecure() && keyguardManager.isDeviceLocked()
: false;
}
/** Loads the stored PIN informations for all SIM slots. */
private SparseArray<StoredPin> loadPinInformation() {
SparseArray<StoredPin> result = new SparseArray<>();
int slotCount = getSlotCount();
for (int slotId = 0; slotId < slotCount; slotId++) {
StoredPin storedPin = loadPinInformation(slotId);
if (storedPin != null) {
result.put(slotId, storedPin);
}
}
return result;
}
/**
* Loads the stored PIN information for the {@code slotId}.
*
* The RAM storage is used if the device is locked, the disk storage is used otherwise.
* This method tries to use both the long-term key and the short-term key (if available)
* to retrieve the PIN information, regardless of its status.
*
* @return the stored {@code StoredPin}, or null if not present.
*/
@Nullable
private StoredPin loadPinInformation(int slotId) {
if (!mLastCommitResult) {
// If the last commit failed, do not read from file, as we might retrieve stale data.
loge("Last commit failed - returning empty values");
return null;
}
StoredPin result = null;
if (mIsDeviceLocked) {
// If the device is still locked, retrieve data from RAM storage.
if (mRamStorage != null && mRamStorage.get(slotId) != null) {
result = decryptStoredPin(mRamStorage.get(slotId), mLongTermSecretKey);
}
} else {
// Load both the stored PIN in available state (with long-term key) and in other states
// (with short-term key). At most one of them should be present at any given time and
// we treat the case wheere both are present as an error.
StoredPin availableStoredPin = loadPinInformationFromDisk(
slotId, SHARED_PREFS_AVAILABLE_PIN_BASE_KEY, mLongTermSecretKey);
StoredPin rebootStoredPin = loadPinInformationFromDisk(
slotId, SHARED_PREFS_REBOOT_PIN_BASE_KEY, mShortTermSecretKey);
if (availableStoredPin != null && rebootStoredPin == null) {
result = availableStoredPin;
} else if (availableStoredPin == null && rebootStoredPin != null) {
result = rebootStoredPin;
}
}
// Validate the slot ID of the retrieved PIN information
if (result != null && result.slotId != slotId) {
loge("Load PIN: slot ID does not match (%d != %d)", result.slotId, slotId);
result = null;
}
if (result != null) {
logv("Load PIN: %s", result.toString());
} else {
logv("Load PIN for slot %d: null", slotId);
}
return result;
}
/**
* Load the PIN information from a specific file in non-volatile memory.
*
* @param key the key in the {@code SharedPreferences} to read
* @param secretKey the key used for encryption/decryption
* @return the {@code StoredPin} from non-volatile memory. It returns a default instance in
* case of error.
*/
@Nullable
private StoredPin loadPinInformationFromDisk(
int slotId, String key, @Nullable SecretKey secretKey) {
String base64encryptedPin =
mContext.getSharedPreferences(SHARED_PREFS_NAME, Context.MODE_PRIVATE)
.getString(key + slotId, "");
if (!base64encryptedPin.isEmpty()) {
try {
byte[] blob = Base64.decode(base64encryptedPin, Base64.DEFAULT);
return decryptStoredPin(blob, secretKey);
} catch (Exception e) {
// Nothing to do
}
}
return null;
}
/** Load the PIN information from an encrypted binary blob.
*
* @param blob the encrypted binary blob
* @param secretKey the key used for encryption/decryption
* @return the decrypted {@code StoredPin}, or null in case of error.
*/
@Nullable
private StoredPin decryptStoredPin(byte[] blob, @Nullable SecretKey secretKey) {
if (secretKey != null) {
try {
byte[] decryptedPin = decrypt(secretKey, blob);
if (decryptedPin.length > 0) {
return StoredPin.parseFrom(decryptedPin);
}
} catch (Exception e) {
loge("cannot decrypt/parse PIN information", e);
}
}
return null;
}
/**
* Stores the PIN information.
*
* If the device is locked, the PIN information is stored to RAM, othewrwise to disk.
* The PIN information is divided based on the PIN status and stored in two separate
* files in non-volatile memory, each encrypted with a different key.
*
* @param slotId the slot ID
* @param storedPin the PIN information to be stored
* @return true if the operation was successfully done, false otherwise.
*/
private boolean savePinInformation(int slotId, @Nullable StoredPin storedPin) {
// Populate the boot count
if (storedPin != null) {
storedPin.bootCount = mBootCount;
}
// If the device is still locked, we can only save PINs in AVAILABLE state in RAM.
// NOTE: at this point, there should not be any PIN in any other state.
if (mIsDeviceLocked) {
return savePinInformationToRam(slotId, storedPin);
}
// Remove any prvious key related to this slot.
SharedPreferences.Editor editor =
mContext.getSharedPreferences(SHARED_PREFS_NAME, Context.MODE_PRIVATE)
.edit()
.remove(SHARED_PREFS_AVAILABLE_PIN_BASE_KEY + slotId)
.remove(SHARED_PREFS_REBOOT_PIN_BASE_KEY + slotId);
boolean result = true;
if (storedPin != null) {
// Available PINs are stored with a long-term key, while the PINs in other states
// are stored with a short-term key.
logd("Saving PIN for slot %d", slotId);
if (storedPin.status == PinStatus.AVAILABLE) {
result = savePinInformation(editor, slotId, storedPin,
SHARED_PREFS_AVAILABLE_PIN_BASE_KEY, mLongTermSecretKey);
} else {
result = savePinInformation(editor, slotId, storedPin,
SHARED_PREFS_REBOOT_PIN_BASE_KEY, mShortTermSecretKey);
}
} else {
logv("Deleting PIN for slot %d (if existed)", slotId);
}
mLastCommitResult = editor.commit() && result;
return mLastCommitResult;
}
/**
* Store the PIN information to a specific file in non-volatile memory.
*
* @param editor the {@code SharedPreferences.Editor} to use for storage
* @param slotId the slot ID
* @param storedPin the PIN information to store
* @param baseKey the base name of the key in the {@code SharedPreferences}. The full name is
* derived appending the value of {@code slotId}.
* @param secretKey the key used for encryption/decryption
* @return true if the operation was successful, false otherwise
*/
private boolean savePinInformation(SharedPreferences.Editor editor, int slotId,
StoredPin storedPin, String baseKey, SecretKey secretKey) {
if (secretKey == null) {
// Secret key for encryption is missing
return false;
}
if (slotId != storedPin.slotId) {
loge("Save PIN: the slotId does not match (%d != %d)", slotId, storedPin.slotId);
return false;
}
logv("Save PIN: %s", storedPin.toString());
byte[] encryptedPin = encrypt(secretKey, StoredPin.toByteArray(storedPin));
if (encryptedPin.length > 0) {
editor.putString(
baseKey + slotId, Base64.encodeToString(encryptedPin, Base64.DEFAULT));
return true;
} else {
return false;
}
}
/** Stored PIN information for slot {@code slotId} in RAM. */
private boolean savePinInformationToRam(int slotId, @Nullable StoredPin storedPin) {
// Clear the RAM in all cases, to avoid leaking any previous PIN.
cleanRamStorage(slotId);
if (storedPin == null) {
return true;
}
if (storedPin.status == PinStatus.AVAILABLE) {
byte[] encryptedPin = encrypt(mLongTermSecretKey, StoredPin.toByteArray(storedPin));
if (encryptedPin != null && encryptedPin.length > 0) {
logd("Saving PIN for slot %d in RAM", slotId);
mRamStorage.put(slotId, encryptedPin);
return true;
}
}
return false;
}
/** Erases all the PINs stored in RAM before a secure device is unlocked. */
private void cleanRamStorage() {
int slotCount = getSlotCount();
for (int slotId = 0; slotId < slotCount; slotId++) {
cleanRamStorage(slotId);
}
}
/** Erases the PIN of slot {@code slotId} stored in RAM before a secure device is unlocked. */
private void cleanRamStorage(int slotId) {
if (mRamStorage != null) {
byte[] data = mRamStorage.get(slotId);
if (data != null) {
Arrays.fill(data, (byte) 0);
}
mRamStorage.delete(slotId);
}
}
/**
* Verifies all pending PIN codes that are ready for verification.
*
* The PIN verificartion is done if the PIN state is VERIFICATION_READY and the SIM
* card has the PIN enabled and not verified.
*/
private void verifyPendingPins() {
int slotCount = getSlotCount();
for (int slotId = 0; slotId < slotCount; slotId++) {
if (isPinState(slotId, PINSTATE_ENABLED_NOT_VERIFIED)) {
verifyPendingPin(slotId);
}
}
}
/** Verifies the PIN code for a given SIM card in slot {@code slotId}. */
private void verifyPendingPin(int slotId) {
// We intentionally invoke getPin() here, as it updates the status and makes sure that
// same PIN is not used more than once
String pin = getPin(slotId, getIccid(slotId));
if (pin.isEmpty()) {
// PIN is not available for verification: return.
return;
}
logd("Perform automatic verification of PIN in slot %d", slotId);
UiccProfile profile = UiccController.getInstance().getUiccProfileForPhone(slotId);
if (profile != null) {
Message onComplete = obtainMessage(SUPPLY_PIN_COMPLETE);
onComplete.arg2 = slotId; // arg1 is the number of remaining attempts in the response
profile.supplyPin(pin, onComplete);
} else {
logd("Perform automatic verification of PIN in slot %d not possible", slotId);
}
}
/** Returns the boot count. */
private int getBootCount() {
return Settings.Global.getInt(
mContext.getContentResolver(),
Settings.Global.BOOT_COUNT,
-1);
}
/** Returns the number of available SIM slots. */
private int getSlotCount() {
// Count the number of slots as the number of Phones.
// At power up, it is possible that number of phones is still unknown, so we query
// TelephonyManager for it.
try {
return PhoneFactory.getPhones().length;
} catch (Exception ex) {
return TelephonyManager.getDefault().getActiveModemCount();
}
}
/**
* Saves the number of cached PINs ready for verification after reboot and returns the
* previous value.
*/
private int saveNumberOfCachedPins(int storedCount) {
SharedPreferences sharedPrefs =
mContext.getSharedPreferences(SHARED_PREFS_NAME, Context.MODE_PRIVATE);
int previousValue = sharedPrefs.getInt(SHARED_PREFS_STORED_PINS, 0);
sharedPrefs.edit().putInt(SHARED_PREFS_STORED_PINS, storedCount).commit();
return previousValue;
}
private boolean startTimer(int duration) {
removeMessages(TIMER_EXPIRATION_EVENT);
return duration > 0 ? sendEmptyMessageDelayed(TIMER_EXPIRATION_EVENT, duration) : true;
}
/** Returns the ICCID of the SIM card for the given {@code slotId}. */
private String getIccid(int slotId) {
Phone phone = PhoneFactory.getPhone(slotId);
return phone != null ? phone.getFullIccSerialNumber() : "";
}
private boolean validatePin(String pin) {
return pin != null && pin.length() >= MIN_PIN_LENGTH && pin.length() <= MAX_PIN_LENGTH;
}
private boolean validateIccid(String iccid) {
return iccid != null && iccid.length() >= MIN_ICCID_LENGTH;
}
private boolean validateSlotId(int slotId) {
return slotId >= 0 && slotId < getSlotCount();
}
/** Checks if the PIN status of the SIM in slot {@code slotId} is a given {@code PinState}. */
private boolean isPinState(int slotId, PinState pinState) {
UiccProfile profile = UiccController.getInstance().getUiccProfileForPhone(slotId);
if (profile != null) {
// Loop thru all possible app families to identify at least one that is available in
// order to check the PIN state.
int[] families = {
UiccController.APP_FAM_3GPP,
UiccController.APP_FAM_3GPP2,
UiccController.APP_FAM_IMS };
for (int i = 0; i < families.length; i++) {
UiccCardApplication app = profile.getApplication(i);
if (app != null) {
return app.getPin1State() == pinState;
}
}
}
return false;
}
/** Returns if the PIN cache is allowed for a given slot. */
private boolean isCacheAllowed(int slotId) {
return isCacheAllowedByDevice() && isCacheAllowedByCarrier(slotId);
}
/** Returns if the PIN cache is allowed by the device. */
private boolean isCacheAllowedByDevice() {
if (!mContext.getResources().getBoolean(
R.bool.config_allow_pin_storage_for_unattended_reboot)) {
logv("Pin caching disabled in resources");
return false;
}
return true;
}
/** Returns if the PIN cache is allowed by carrier for a given slot. */
private boolean isCacheAllowedByCarrier(int slotId) {
PersistableBundle config = null;
CarrierConfigManager configManager =
mContext.getSystemService(CarrierConfigManager.class);
if (configManager != null) {
Phone phone = PhoneFactory.getPhone(slotId);
if (phone != null) {
// If an invalid subId is used, this bundle will contain default values.
config = configManager.getConfigForSubId(phone.getSubId());
}
}
if (config == null) {
config = CarrierConfigManager.getDefaultConfig();
}
return config.getBoolean(
CarrierConfigManager.KEY_STORE_SIM_PIN_FOR_UNATTENDED_REBOOT_BOOL, true);
}
/** Initializes KeyStore and returns the instance. */
@Nullable
private static KeyStore initializeKeyStore() {
KeyStore keyStore = null;
try {
keyStore = KeyStore.getInstance(ANDROID_KEY_STORE_PROVIDER);
keyStore.load(/*param=*/ null);
} catch (Exception e) {
// Should never happen.
loge("Error loading KeyStore", e);
return null;
}
logv("KeyStore ready");
return keyStore;
}
/**
* Initializes a secret key and returns it.
*
* @param alias alias of the key in {@link KeyStore}.
* @param createIfAbsent indicates weather the key must be created if not already present.
* @return the {@link SecretKey}, or null if the key does not exist.
*/
@Nullable
private SecretKey initializeSecretKey(String alias, boolean createIfAbsent) {
if (mKeyStore == null) {
return null;
}
SecretKey secretKey = getSecretKey(alias);
if (secretKey != null) {
logd("KeyStore: alias %s exists", alias);
return secretKey;
} else if (createIfAbsent) {
Date expiration =
KEYSTORE_ALIAS_SHORT_TERM.equals(alias) ? getShortLivedKeyValidityEnd() : null;
boolean isUserAuthRequired =
!KEYSTORE_ALIAS_LONG_TERM_ALWAYS.equals(alias) && isDeviceSecure();
logd("KeyStore: alias %s does not exist - Creating (exp=%s, auth=%s)",
alias, expiration != null ? expiration.toString() : "", isUserAuthRequired);
return createSecretKey(alias, expiration, isUserAuthRequired);
} else {
// Nothing to do
logd("KeyStore: alias %s does not exist - Nothing to do", alias);
return null;
}
}
/**
* Retrieves the secret key previously stored in {@link KeyStore}.
*
* @param alias alias of the key in {@link KeyStore}.
* @return the {@link SecretKey}, or null in case of error or if the key does not exist.
*/
@Nullable
private SecretKey getSecretKey(String alias) {
try {
final KeyStore.SecretKeyEntry secretKeyEntry =
(KeyStore.SecretKeyEntry) mKeyStore.getEntry(alias, null);
if (secretKeyEntry != null) {
return secretKeyEntry.getSecretKey();
}
} catch (Exception e) {
// In case of exception, it means that key exists, but cannot be retrieved
// We delete the old key, so that a new key can be created.
loge("Exception with getting the key " + alias, e);
deleteSecretKey(alias);
}
return null;
}
/**
* Generates a new secret key in {@link KeyStore}.
*
* @param alias alias of the key in {@link KeyStore}.
* @param expiration expiration of the key, or null if the key does not expire.
* @param isUserAuthRequired indicates if user authentication is required to use the key
* @return the created {@link SecretKey}, or null in case of error
*/
@Nullable
private SecretKey createSecretKey(String alias, Date expiration, boolean isUserAuthRequired) {
try {
final KeyGenerator keyGenerator =
KeyGenerator.getInstance(KEY_ALGORITHM_AES, ANDROID_KEY_STORE_PROVIDER);
KeyGenParameterSpec.Builder keyGenParameterSpec =
new KeyGenParameterSpec.Builder(alias, PURPOSE_ENCRYPT | PURPOSE_DECRYPT)
.setBlockModes(BLOCK_MODE_GCM)
.setEncryptionPaddings(ENCRYPTION_PADDING_NONE);
if (expiration != null) {
keyGenParameterSpec = keyGenParameterSpec
.setKeyValidityEnd(expiration);
}
if (isUserAuthRequired) {
keyGenParameterSpec = keyGenParameterSpec
.setUserAuthenticationRequired(true)
.setUserAuthenticationParameters(Integer.MAX_VALUE, AUTH_DEVICE_CREDENTIAL);
}
keyGenerator.init(keyGenParameterSpec.build());
return keyGenerator.generateKey();
} catch (Exception e) {
loge("Create key exception", e);
return null;
}
}
/** Returns the validity end of a new short-lived key, or null if key does not expire. */
@Nullable
private Date getShortLivedKeyValidityEnd() {
if (mShortTermSecretKeyDurationMinutes > 0) {
Calendar calendar = Calendar.getInstance();
calendar.setTime(new Date());
calendar.add(Calendar.MINUTE, mShortTermSecretKeyDurationMinutes);
return calendar.getTime();
} else {
return null;
}
}
/** Deletes the short term key from KeyStore, if it exists. */
private void deleteSecretKey(String alias) {
if (mKeyStore != null) {
logd("Delete key: %s", alias);
try {
mKeyStore.deleteEntry(alias);
} catch (Exception e) {
// Nothing to do. Even if the key removal fails, it becomes unusable.
loge("Delete key exception");
}
}
}
/** Returns the encrypted version of {@code input}, or an empty array in case of error. */
private byte[] encrypt(SecretKey secretKey, byte[] input) {
if (secretKey == null) {
loge("Encrypt: Secret key is null");
return new byte[0];
}
try {
final Cipher cipher = Cipher.getInstance(CIPHER_TRANSFORMATION);
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
EncryptedPin encryptedPin = new EncryptedPin();
encryptedPin.iv = cipher.getIV();
encryptedPin.encryptedStoredPin = cipher.doFinal(input);
return EncryptedPin.toByteArray(encryptedPin);
} catch (Exception e) {
loge("Encrypt exception", e);
TelephonyStatsLog.write(PIN_STORAGE_EVENT,
PIN_STORAGE_EVENT__EVENT__PIN_ENCRYPTION_ERROR, 1);
}
return new byte[0];
}
/** Returns the decrypted version of {@code input}, or an empty array in case of error. */
private byte[] decrypt(SecretKey secretKey, byte[] input) {
if (secretKey == null) {
loge("Decrypt: Secret key is null");
return new byte[0];
}
try {
EncryptedPin encryptedPin = EncryptedPin.parseFrom(input);
if (!ArrayUtils.isEmpty(encryptedPin.encryptedStoredPin)
&& !ArrayUtils.isEmpty(encryptedPin.iv)) {
final Cipher cipher = Cipher.getInstance(CIPHER_TRANSFORMATION);
final GCMParameterSpec spec =
new GCMParameterSpec(GCM_PARAMETER_TAG_BIT_LEN, encryptedPin.iv);
cipher.init(Cipher.DECRYPT_MODE, secretKey, spec);
return cipher.doFinal(encryptedPin.encryptedStoredPin);
}
} catch (Exception e) {
loge("Decrypt exception", e);
TelephonyStatsLog.write(PIN_STORAGE_EVENT,
PIN_STORAGE_EVENT__EVENT__PIN_DECRYPTION_ERROR, 1);
}
return new byte[0];
}
private static void logv(String format, Object... args) {
if (VDBG) {
Rlog.d(TAG, String.format(format, args));
}
}
private static void logd(String format, Object... args) {
Rlog.d(TAG, String.format(format, args));
}
private static void loge(String format, Object... args) {
Rlog.e(TAG, String.format(format, args));
}
private static void loge(String msg, Throwable tr) {
Rlog.e(TAG, msg, tr);
}
void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println("PinStorage:");
pw.println(" mIsDeviceSecure=" + mIsDeviceSecure);
pw.println(" mIsDeviceLocked=" + mIsDeviceLocked);
pw.println(" isLongTermSecretKey=" + (boolean) (mLongTermSecretKey != null));
pw.println(" isShortTermSecretKey=" + (boolean) (mShortTermSecretKey != null));
pw.println(" isCacheAllowedByDevice=" + isCacheAllowedByDevice());
int slotCount = getSlotCount();
for (int i = 0; i < slotCount; i++) {
pw.println(" isCacheAllowedByCarrier[" + i + "]=" + isCacheAllowedByCarrier(i));
}
if (VDBG) {
SparseArray<StoredPin> storedPins = loadPinInformation();
for (int i = 0; i < storedPins.size(); i++) {
pw.println(" pin=" + storedPins.valueAt(i).toString());
}
}
}
}