/* * Copyright (C) 2017 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; import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; import android.content.Intent; import android.database.Cursor; import android.database.sqlite.SQLiteConstraintException; import android.os.PersistableBundle; import android.os.UserHandle; import android.provider.Telephony; import android.telephony.CarrierConfigManager; import android.telephony.ImsiEncryptionInfo; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.text.TextUtils; import android.util.Log; import android.util.Pair; import com.android.internal.telephony.flags.Flags; import com.android.internal.telephony.metrics.TelephonyMetrics; import java.security.PublicKey; import java.util.Date; /** * This class provides methods to retreive information from the CarrierKeyProvider. */ public class CarrierInfoManager { private static final String LOG_TAG = "CarrierInfoManager"; private static final String KEY_TYPE = "KEY_TYPE"; /* * Rate limit (in milliseconds) the number of times the Carrier keys can be reset. * Do it at most once every 12 hours. */ private static final int RESET_CARRIER_KEY_RATE_LIMIT = 12 * 60 * 60 * 1000; // Key ID used with the backup key from carrier config private static final String EPDG_BACKUP_KEY_ID = "backup_key_from_carrier_config_epdg"; private static final String WLAN_BACKUP_KEY_ID = "backup_key_from_carrier_config_wlan"; // Last time the resetCarrierKeysForImsiEncryption API was called successfully. private long mLastAccessResetCarrierKey = 0; /** * Returns Carrier specific information that will be used to encrypt the IMSI and IMPI. * @param keyType whether the key is being used for WLAN or ePDG. * @param context * @param fallback whether to fallback to the IMSI key info stored in carrier config * @return ImsiEncryptionInfo which contains the information, including the public key, to be * used for encryption. */ public static ImsiEncryptionInfo getCarrierInfoForImsiEncryption(int keyType, Context context, String operatorNumeric, int carrierId, boolean fallback, int subId) { String mcc = ""; String mnc = ""; if (!TextUtils.isEmpty(operatorNumeric)) { mcc = operatorNumeric.substring(0, 3); mnc = operatorNumeric.substring(3); Log.i(LOG_TAG, "using values for mcc, mnc: " + mcc + "," + mnc); } else { Log.e(LOG_TAG, "Invalid networkOperator: " + operatorNumeric); return null; } Cursor findCursor = null; try { // In the current design, MVNOs are not supported. If we decide to support them, // we'll need to add to this CL. ContentResolver mContentResolver = context.getContentResolver(); String[] columns = {Telephony.CarrierColumns.PUBLIC_KEY, Telephony.CarrierColumns.EXPIRATION_TIME, Telephony.CarrierColumns.KEY_IDENTIFIER, Telephony.CarrierColumns.CARRIER_ID}; findCursor = mContentResolver.query(Telephony.CarrierColumns.CONTENT_URI, columns, "mcc=? and mnc=? and key_type=?", new String[]{mcc, mnc, String.valueOf(keyType)}, null); if (findCursor == null || !findCursor.moveToFirst()) { Log.d(LOG_TAG, "No rows found for keyType: " + keyType); if (!fallback) { Log.d(LOG_TAG, "Skipping fallback logic"); return null; } // return carrier config key as fallback CarrierConfigManager carrierConfigManager = (CarrierConfigManager) context.getSystemService(Context.CARRIER_CONFIG_SERVICE); if (carrierConfigManager == null) { Log.d(LOG_TAG, "Could not get CarrierConfigManager for backup key"); return null; } if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) { Log.d(LOG_TAG, "Could not get carrier config with invalid subId"); return null; } PersistableBundle b = carrierConfigManager.getConfigForSubId(subId); if (b == null) { Log.d(LOG_TAG, "Could not get carrier config bundle for backup key"); return null; } int keyAvailabilityBitmask = b.getInt( CarrierConfigManager.IMSI_KEY_AVAILABILITY_INT); if (!CarrierKeyDownloadManager.isKeyEnabled(keyType, keyAvailabilityBitmask)) { Log.d(LOG_TAG, "Backup key does not have matching keyType. keyType=" + keyType + " keyAvailability=" + keyAvailabilityBitmask); return null; } String keyString = null; String keyId = null; if (keyType == TelephonyManager.KEY_TYPE_EPDG) { keyString = b.getString( CarrierConfigManager.IMSI_CARRIER_PUBLIC_KEY_EPDG_STRING); keyId = EPDG_BACKUP_KEY_ID; } else if (keyType == TelephonyManager.KEY_TYPE_WLAN) { keyString = b.getString( CarrierConfigManager.IMSI_CARRIER_PUBLIC_KEY_WLAN_STRING); keyId = WLAN_BACKUP_KEY_ID; } if (TextUtils.isEmpty(keyString)) { Log.d(LOG_TAG, "Could not get carrier config key string for backup key. keyType=" + keyType); return null; } Pair keyInfo = CarrierKeyDownloadManager.getKeyInformation(keyString.getBytes()); return new ImsiEncryptionInfo(mcc, mnc, keyType, keyId, keyInfo.first, new Date(keyInfo.second), carrierId); } if (findCursor.getCount() > 1) { Log.e(LOG_TAG, "More than 1 row found for the keyType: " + keyType); // Lookup for the carrier_id String carrierIdStr = ""; while (findCursor.moveToNext()) { carrierIdStr = findCursor.getString(3); int cursorCarrierId = (TextUtils.isEmpty(carrierIdStr)) ? TelephonyManager.UNKNOWN_CARRIER_ID : Integer.parseInt( carrierIdStr); if (cursorCarrierId != TelephonyManager.UNKNOWN_CARRIER_ID && cursorCarrierId == carrierId) { return getImsiEncryptionInfo(findCursor, mcc, mnc, keyType, cursorCarrierId); } } findCursor.moveToFirst(); } String carrierIdStr = findCursor.getString(3); int cursorCarrierId = (TextUtils.isEmpty(carrierIdStr)) ? TelephonyManager.UNKNOWN_CARRIER_ID : Integer.parseInt(carrierIdStr); return getImsiEncryptionInfo(findCursor, mcc, mnc, keyType, cursorCarrierId); } catch (IllegalArgumentException e) { Log.e(LOG_TAG, "Bad arguments:" + e); } catch (Exception e) { Log.e(LOG_TAG, "Query failed:" + e); } finally { if (findCursor != null) { findCursor.close(); } } return null; } private static ImsiEncryptionInfo getImsiEncryptionInfo(Cursor findCursor, String mcc, String mnc, int keyType, int carrierId) { byte[] carrier_key = findCursor.getBlob(0); Date expirationTime = new Date(findCursor.getLong(1)); String keyIdentifier = findCursor.getString(2); ImsiEncryptionInfo imsiEncryptionInfo = null; try { imsiEncryptionInfo = new ImsiEncryptionInfo(mcc, mnc, keyType, keyIdentifier, carrier_key, expirationTime, carrierId); } catch (Exception exp) { Log.e(LOG_TAG, "Exception = " + exp.getMessage()); } return imsiEncryptionInfo; } /** * Inserts or update the Carrier Key in the database * @param imsiEncryptionInfo ImsiEncryptionInfo object. * @param context Context. */ public static void updateOrInsertCarrierKey(ImsiEncryptionInfo imsiEncryptionInfo, Context context, int phoneId) { byte[] keyBytes = imsiEncryptionInfo.getPublicKey().getEncoded(); ContentResolver mContentResolver = context.getContentResolver(); TelephonyMetrics tm = TelephonyMetrics.getInstance(); // In the current design, MVNOs are not supported. If we decide to support them, // we'll need to add to this CL. ContentValues contentValues = new ContentValues(); contentValues.put(Telephony.CarrierColumns.MCC, imsiEncryptionInfo.getMcc()); contentValues.put(Telephony.CarrierColumns.MNC, imsiEncryptionInfo.getMnc()); contentValues.put(Telephony.CarrierColumns.CARRIER_ID, imsiEncryptionInfo.getCarrierId()); contentValues.put(Telephony.CarrierColumns.KEY_TYPE, imsiEncryptionInfo.getKeyType()); contentValues.put(Telephony.CarrierColumns.KEY_IDENTIFIER, imsiEncryptionInfo.getKeyIdentifier()); contentValues.put(Telephony.CarrierColumns.PUBLIC_KEY, keyBytes); contentValues.put(Telephony.CarrierColumns.EXPIRATION_TIME, imsiEncryptionInfo.getExpirationTime().getTime()); boolean downloadSuccessfull = true; try { Log.i(LOG_TAG, "Inserting imsiEncryptionInfo into db"); mContentResolver.insert(Telephony.CarrierColumns.CONTENT_URI, contentValues); } catch (SQLiteConstraintException e) { Log.i(LOG_TAG, "Insert failed, updating imsiEncryptionInfo into db"); ContentValues updatedValues = new ContentValues(); updatedValues.put(Telephony.CarrierColumns.PUBLIC_KEY, keyBytes); updatedValues.put(Telephony.CarrierColumns.EXPIRATION_TIME, imsiEncryptionInfo.getExpirationTime().getTime()); updatedValues.put(Telephony.CarrierColumns.KEY_IDENTIFIER, imsiEncryptionInfo.getKeyIdentifier()); try { int nRows = mContentResolver.update(Telephony.CarrierColumns.CONTENT_URI, updatedValues, "mcc=? and mnc=? and key_type=? and carrier_id=?", new String[]{ imsiEncryptionInfo.getMcc(), imsiEncryptionInfo.getMnc(), String.valueOf(imsiEncryptionInfo.getKeyType()), String.valueOf(imsiEncryptionInfo.getCarrierId())}); if (nRows == 0) { Log.d(LOG_TAG, "Error updating values:" + imsiEncryptionInfo); downloadSuccessfull = false; } } catch (Exception ex) { Log.d(LOG_TAG, "Error updating values:" + imsiEncryptionInfo + ex); downloadSuccessfull = false; } } catch (Exception e) { Log.d(LOG_TAG, "Error inserting/updating values:" + imsiEncryptionInfo + e); downloadSuccessfull = false; } finally { tm.writeCarrierKeyEvent(phoneId, imsiEncryptionInfo.getKeyType(), downloadSuccessfull); } } /** * Sets the Carrier specific information that will be used to encrypt the IMSI and IMPI. * This includes the public key and the key identifier. This information will be stored in the * device keystore. * @param imsiEncryptionInfo which includes the Key Type, the Public Key * {@link java.security.PublicKey} and the Key Identifier. * The keyIdentifier Attribute value pair that helps a server locate * the private key to decrypt the permanent identity. * @param context Context. */ public static void setCarrierInfoForImsiEncryption(ImsiEncryptionInfo imsiEncryptionInfo, Context context, int phoneId) { Log.i(LOG_TAG, "inserting carrier key: " + imsiEncryptionInfo); updateOrInsertCarrierKey(imsiEncryptionInfo, context, phoneId); //todo send key to modem. Will be done in a subsequent CL. } /** * Resets the Carrier Keys in the database. This involves 2 steps: * 1. Delete the keys from the database. * 2. Send an intent to download new Certificates. * @param context Context * @param mPhoneId phoneId * */ public void resetCarrierKeysForImsiEncryption(Context context, int mPhoneId) { Log.i(LOG_TAG, "resetting carrier key"); // Check rate limit. long now = System.currentTimeMillis(); if (now - mLastAccessResetCarrierKey < RESET_CARRIER_KEY_RATE_LIMIT) { Log.i(LOG_TAG, "resetCarrierKeysForImsiEncryption: Access rate exceeded"); return; } mLastAccessResetCarrierKey = now; int subId = SubscriptionManager.getSubscriptionId(mPhoneId); if (!SubscriptionManager.isValidSubscriptionId(subId)) { Log.e(LOG_TAG, "Could not reset carrier keys, subscription for mPhoneId=" + mPhoneId); return; } final TelephonyManager telephonyManager = context.getSystemService(TelephonyManager.class) .createForSubscriptionId(subId); int carrierId = telephonyManager.getSimCarrierId(); if (Flags.imsiKeyRetryDownloadOnPhoneUnlock()) { String simOperator = telephonyManager.getSimOperator(); deleteCarrierInfoForImsiEncryption(context, subId, carrierId, simOperator); } else { deleteCarrierInfoForImsiEncryption(context, subId, carrierId); } Intent resetIntent = new Intent(TelephonyIntents.ACTION_CARRIER_CERTIFICATE_DOWNLOAD); SubscriptionManager.putPhoneIdAndSubIdExtra(resetIntent, mPhoneId); context.sendBroadcastAsUser(resetIntent, UserHandle.ALL); } /** * Deletes all the keys for a given Carrier from the device keystore. * @param context Context * @param subId * @param carrierId delete the key which matches the carrierId * */ public static void deleteCarrierInfoForImsiEncryption(Context context, int subId, int carrierId) { deleteCarrierInfoForImsiEncryption(context, subId, carrierId, null); } /** * Deletes all the keys for a given Carrier from the device keystore. * @param context Context * @param subId SubscriptionId * @param carrierId delete the key which matches the carrierId * @param simOperator delete the key which matches the MCCMNC * */ public static void deleteCarrierInfoForImsiEncryption(Context context, int subId, int carrierId, String simOperator) { Log.i(LOG_TAG, "deleting carrier key from db for subId=" + subId); String mcc = ""; String mnc = ""; if (TextUtils.isEmpty(simOperator)) { final TelephonyManager telephonyManager = context.getSystemService( TelephonyManager.class) .createForSubscriptionId(subId); simOperator = telephonyManager.getSimOperator(); } if (!TextUtils.isEmpty(simOperator)) { mcc = simOperator.substring(0, 3); mnc = simOperator.substring(3); } else { Log.e(LOG_TAG, "Invalid networkOperator: " + simOperator); return; } String carriedIdStr = String.valueOf(carrierId); ContentResolver mContentResolver = context.getContentResolver(); try { String whereClause = "mcc=? and mnc=? and carrier_id=?"; String[] whereArgs = new String[] { mcc, mnc, carriedIdStr }; int count = mContentResolver.delete(Telephony.CarrierColumns.CONTENT_URI, whereClause, whereArgs); Log.i(LOG_TAG, "Deleting the number of entries = " + count + " for carrierId = " + carriedIdStr); } catch (Exception e) { Log.e(LOG_TAG, "Delete failed" + e); } } /** * Deletes all the keys from the device keystore. * @param context Context */ public static void deleteAllCarrierKeysForImsiEncryption(Context context) { Log.i(LOG_TAG, "deleting ALL carrier keys from db"); ContentResolver mContentResolver = context.getContentResolver(); try { mContentResolver.delete(Telephony.CarrierColumns.CONTENT_URI, null, null); } catch (Exception e) { Log.e(LOG_TAG, "Delete failed" + e); } } }