379 lines
18 KiB
Java
379 lines
18 KiB
Java
/*
|
|
* 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<PublicKey, Long> 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);
|
|
}
|
|
}
|
|
}
|