1 /* 2 * Copyright (C) 2017 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.internal.telephony; 18 19 import android.content.ContentResolver; 20 import android.content.ContentValues; 21 import android.content.Context; 22 import android.content.Intent; 23 import android.database.Cursor; 24 import android.database.sqlite.SQLiteConstraintException; 25 import android.os.PersistableBundle; 26 import android.os.UserHandle; 27 import android.provider.Telephony; 28 import android.telephony.CarrierConfigManager; 29 import android.telephony.ImsiEncryptionInfo; 30 import android.telephony.SubscriptionManager; 31 import android.telephony.TelephonyManager; 32 import android.text.TextUtils; 33 import android.util.Log; 34 import android.util.Pair; 35 36 import com.android.internal.telephony.metrics.TelephonyMetrics; 37 38 import java.security.PublicKey; 39 import java.util.Date; 40 41 /** 42 * This class provides methods to retreive information from the CarrierKeyProvider. 43 */ 44 public class CarrierInfoManager { 45 private static final String LOG_TAG = "CarrierInfoManager"; 46 private static final String KEY_TYPE = "KEY_TYPE"; 47 48 /* 49 * Rate limit (in milliseconds) the number of times the Carrier keys can be reset. 50 * Do it at most once every 12 hours. 51 */ 52 private static final int RESET_CARRIER_KEY_RATE_LIMIT = 12 * 60 * 60 * 1000; 53 54 // Key ID used with the backup key from carrier config 55 private static final String EPDG_BACKUP_KEY_ID = "backup_key_from_carrier_config_epdg"; 56 private static final String WLAN_BACKUP_KEY_ID = "backup_key_from_carrier_config_wlan"; 57 58 // Last time the resetCarrierKeysForImsiEncryption API was called successfully. 59 private long mLastAccessResetCarrierKey = 0; 60 61 /** 62 * Returns Carrier specific information that will be used to encrypt the IMSI and IMPI. 63 * @param keyType whether the key is being used for WLAN or ePDG. 64 * @param context 65 * @param fallback whether to fallback to the IMSI key info stored in carrier config 66 * @return ImsiEncryptionInfo which contains the information, including the public key, to be 67 * used for encryption. 68 */ getCarrierInfoForImsiEncryption(int keyType, Context context, String operatorNumeric, boolean fallback, int subId)69 public static ImsiEncryptionInfo getCarrierInfoForImsiEncryption(int keyType, 70 Context context, 71 String operatorNumeric, 72 boolean fallback, 73 int subId) { 74 String mcc = ""; 75 String mnc = ""; 76 if (!TextUtils.isEmpty(operatorNumeric)) { 77 mcc = operatorNumeric.substring(0, 3); 78 mnc = operatorNumeric.substring(3); 79 Log.i(LOG_TAG, "using values for mcc, mnc: " + mcc + "," + mnc); 80 } else { 81 Log.e(LOG_TAG, "Invalid networkOperator: " + operatorNumeric); 82 return null; 83 } 84 Cursor findCursor = null; 85 try { 86 // In the current design, MVNOs are not supported. If we decide to support them, 87 // we'll need to add to this CL. 88 ContentResolver mContentResolver = context.getContentResolver(); 89 String[] columns = {Telephony.CarrierColumns.PUBLIC_KEY, 90 Telephony.CarrierColumns.EXPIRATION_TIME, 91 Telephony.CarrierColumns.KEY_IDENTIFIER}; 92 findCursor = mContentResolver.query(Telephony.CarrierColumns.CONTENT_URI, columns, 93 "mcc=? and mnc=? and key_type=?", 94 new String[]{mcc, mnc, String.valueOf(keyType)}, null); 95 if (findCursor == null || !findCursor.moveToFirst()) { 96 Log.d(LOG_TAG, "No rows found for keyType: " + keyType); 97 if (!fallback) { 98 Log.d(LOG_TAG, "Skipping fallback logic"); 99 return null; 100 } 101 // return carrier config key as fallback 102 CarrierConfigManager carrierConfigManager = (CarrierConfigManager) 103 context.getSystemService(Context.CARRIER_CONFIG_SERVICE); 104 if (carrierConfigManager == null) { 105 Log.d(LOG_TAG, "Could not get CarrierConfigManager for backup key"); 106 return null; 107 } 108 if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) { 109 Log.d(LOG_TAG, "Could not get carrier config with invalid subId"); 110 return null; 111 } 112 PersistableBundle b = carrierConfigManager.getConfigForSubId(subId); 113 if (b == null) { 114 Log.d(LOG_TAG, "Could not get carrier config bundle for backup key"); 115 return null; 116 } 117 int keyAvailabilityBitmask = b.getInt( 118 CarrierConfigManager.IMSI_KEY_AVAILABILITY_INT); 119 if (!CarrierKeyDownloadManager.isKeyEnabled(keyType, keyAvailabilityBitmask)) { 120 Log.d(LOG_TAG, "Backup key does not have matching keyType. keyType=" + keyType 121 + " keyAvailability=" + keyAvailabilityBitmask); 122 return null; 123 } 124 String keyString = null; 125 String keyId = null; 126 if (keyType == TelephonyManager.KEY_TYPE_EPDG) { 127 keyString = b.getString( 128 CarrierConfigManager.IMSI_CARRIER_PUBLIC_KEY_EPDG_STRING); 129 keyId = EPDG_BACKUP_KEY_ID; 130 } else if (keyType == TelephonyManager.KEY_TYPE_WLAN) { 131 keyString = b.getString( 132 CarrierConfigManager.IMSI_CARRIER_PUBLIC_KEY_WLAN_STRING); 133 keyId = WLAN_BACKUP_KEY_ID; 134 } 135 if (TextUtils.isEmpty(keyString)) { 136 Log.d(LOG_TAG, 137 "Could not get carrier config key string for backup key. keyType=" 138 + keyType); 139 return null; 140 } 141 Pair<PublicKey, Long> keyInfo = 142 CarrierKeyDownloadManager.getKeyInformation(keyString.getBytes()); 143 return new ImsiEncryptionInfo(mcc, mnc, keyType, keyId, 144 keyInfo.first, new Date(keyInfo.second)); 145 } 146 if (findCursor.getCount() > 1) { 147 Log.e(LOG_TAG, "More than 1 row found for the keyType: " + keyType); 148 } 149 byte[] carrier_key = findCursor.getBlob(0); 150 Date expirationTime = new Date(findCursor.getLong(1)); 151 String keyIdentifier = findCursor.getString(2); 152 return new ImsiEncryptionInfo(mcc, mnc, keyType, keyIdentifier, carrier_key, 153 expirationTime); 154 } catch (IllegalArgumentException e) { 155 Log.e(LOG_TAG, "Bad arguments:" + e); 156 } catch (Exception e) { 157 Log.e(LOG_TAG, "Query failed:" + e); 158 } finally { 159 if (findCursor != null) { 160 findCursor.close(); 161 } 162 } 163 return null; 164 } 165 166 /** 167 * Inserts or update the Carrier Key in the database 168 * @param imsiEncryptionInfo ImsiEncryptionInfo object. 169 * @param context Context. 170 */ updateOrInsertCarrierKey(ImsiEncryptionInfo imsiEncryptionInfo, Context context, int phoneId)171 public static void updateOrInsertCarrierKey(ImsiEncryptionInfo imsiEncryptionInfo, 172 Context context, int phoneId) { 173 byte[] keyBytes = imsiEncryptionInfo.getPublicKey().getEncoded(); 174 ContentResolver mContentResolver = context.getContentResolver(); 175 TelephonyMetrics tm = TelephonyMetrics.getInstance(); 176 // In the current design, MVNOs are not supported. If we decide to support them, 177 // we'll need to add to this CL. 178 ContentValues contentValues = new ContentValues(); 179 contentValues.put(Telephony.CarrierColumns.MCC, imsiEncryptionInfo.getMcc()); 180 contentValues.put(Telephony.CarrierColumns.MNC, imsiEncryptionInfo.getMnc()); 181 contentValues.put(Telephony.CarrierColumns.KEY_TYPE, 182 imsiEncryptionInfo.getKeyType()); 183 contentValues.put(Telephony.CarrierColumns.KEY_IDENTIFIER, 184 imsiEncryptionInfo.getKeyIdentifier()); 185 contentValues.put(Telephony.CarrierColumns.PUBLIC_KEY, keyBytes); 186 contentValues.put(Telephony.CarrierColumns.EXPIRATION_TIME, 187 imsiEncryptionInfo.getExpirationTime().getTime()); 188 boolean downloadSuccessfull = true; 189 try { 190 Log.i(LOG_TAG, "Inserting imsiEncryptionInfo into db"); 191 mContentResolver.insert(Telephony.CarrierColumns.CONTENT_URI, contentValues); 192 } catch (SQLiteConstraintException e) { 193 Log.i(LOG_TAG, "Insert failed, updating imsiEncryptionInfo into db"); 194 ContentValues updatedValues = new ContentValues(); 195 updatedValues.put(Telephony.CarrierColumns.PUBLIC_KEY, keyBytes); 196 updatedValues.put(Telephony.CarrierColumns.EXPIRATION_TIME, 197 imsiEncryptionInfo.getExpirationTime().getTime()); 198 updatedValues.put(Telephony.CarrierColumns.KEY_IDENTIFIER, 199 imsiEncryptionInfo.getKeyIdentifier()); 200 try { 201 int nRows = mContentResolver.update(Telephony.CarrierColumns.CONTENT_URI, 202 updatedValues, 203 "mcc=? and mnc=? and key_type=?", new String[]{ 204 imsiEncryptionInfo.getMcc(), 205 imsiEncryptionInfo.getMnc(), 206 String.valueOf(imsiEncryptionInfo.getKeyType())}); 207 if (nRows == 0) { 208 Log.d(LOG_TAG, "Error updating values:" + imsiEncryptionInfo); 209 downloadSuccessfull = false; 210 } 211 } catch (Exception ex) { 212 Log.d(LOG_TAG, "Error updating values:" + imsiEncryptionInfo + ex); 213 downloadSuccessfull = false; 214 } 215 } catch (Exception e) { 216 Log.d(LOG_TAG, "Error inserting/updating values:" + imsiEncryptionInfo + e); 217 downloadSuccessfull = false; 218 } finally { 219 tm.writeCarrierKeyEvent(phoneId, imsiEncryptionInfo.getKeyType(), downloadSuccessfull); 220 } 221 } 222 223 /** 224 * Sets the Carrier specific information that will be used to encrypt the IMSI and IMPI. 225 * This includes the public key and the key identifier. This information will be stored in the 226 * device keystore. 227 * @param imsiEncryptionInfo which includes the Key Type, the Public Key 228 * {@link java.security.PublicKey} and the Key Identifier. 229 * The keyIdentifier Attribute value pair that helps a server locate 230 * the private key to decrypt the permanent identity. 231 * @param context Context. 232 */ setCarrierInfoForImsiEncryption(ImsiEncryptionInfo imsiEncryptionInfo, Context context, int phoneId)233 public static void setCarrierInfoForImsiEncryption(ImsiEncryptionInfo imsiEncryptionInfo, 234 Context context, int phoneId) { 235 Log.i(LOG_TAG, "inserting carrier key: " + imsiEncryptionInfo); 236 updateOrInsertCarrierKey(imsiEncryptionInfo, context, phoneId); 237 //todo send key to modem. Will be done in a subsequent CL. 238 } 239 240 /** 241 * Resets the Carrier Keys in the database. This involves 2 steps: 242 * 1. Delete the keys from the database. 243 * 2. Send an intent to download new Certificates. 244 * @param context Context 245 * @param mPhoneId phoneId 246 * 247 */ resetCarrierKeysForImsiEncryption(Context context, int mPhoneId)248 public void resetCarrierKeysForImsiEncryption(Context context, int mPhoneId) { 249 Log.i(LOG_TAG, "resetting carrier key"); 250 // Check rate limit. 251 long now = System.currentTimeMillis(); 252 if (now - mLastAccessResetCarrierKey < RESET_CARRIER_KEY_RATE_LIMIT) { 253 Log.i(LOG_TAG, "resetCarrierKeysForImsiEncryption: Access rate exceeded"); 254 return; 255 } 256 mLastAccessResetCarrierKey = now; 257 int[] subIds = context.getSystemService(SubscriptionManager.class) 258 .getSubscriptionIds(mPhoneId); 259 if (subIds == null || subIds.length < 1) { 260 Log.e(LOG_TAG, "Could not reset carrier keys, subscription for mPhoneId=" + mPhoneId); 261 return; 262 } 263 deleteCarrierInfoForImsiEncryption(context, subIds[0]); 264 Intent resetIntent = new Intent(TelephonyIntents.ACTION_CARRIER_CERTIFICATE_DOWNLOAD); 265 SubscriptionManager.putPhoneIdAndSubIdExtra(resetIntent, mPhoneId); 266 context.sendBroadcastAsUser(resetIntent, UserHandle.ALL); 267 } 268 269 /** 270 * Deletes all the keys for a given Carrier from the device keystore. 271 * @param context Context 272 */ deleteCarrierInfoForImsiEncryption(Context context, int subId)273 public static void deleteCarrierInfoForImsiEncryption(Context context, int subId) { 274 Log.i(LOG_TAG, "deleting carrier key from db for subId=" + subId); 275 String mcc = ""; 276 String mnc = ""; 277 final TelephonyManager telephonyManager = context.getSystemService(TelephonyManager.class) 278 .createForSubscriptionId(subId); 279 String simOperator = telephonyManager.getSimOperator(); 280 if (!TextUtils.isEmpty(simOperator)) { 281 mcc = simOperator.substring(0, 3); 282 mnc = simOperator.substring(3); 283 } else { 284 Log.e(LOG_TAG, "Invalid networkOperator: " + simOperator); 285 return; 286 } 287 ContentResolver mContentResolver = context.getContentResolver(); 288 try { 289 String whereClause = "mcc=? and mnc=?"; 290 String[] whereArgs = new String[] { mcc, mnc }; 291 mContentResolver.delete(Telephony.CarrierColumns.CONTENT_URI, whereClause, whereArgs); 292 } catch (Exception e) { 293 Log.e(LOG_TAG, "Delete failed" + e); 294 } 295 } 296 297 /** 298 * Deletes all the keys from the device keystore. 299 * @param context Context 300 */ deleteAllCarrierKeysForImsiEncryption(Context context)301 public static void deleteAllCarrierKeysForImsiEncryption(Context context) { 302 Log.i(LOG_TAG, "deleting ALL carrier keys from db"); 303 ContentResolver mContentResolver = context.getContentResolver(); 304 try { 305 mContentResolver.delete(Telephony.CarrierColumns.CONTENT_URI, null, null); 306 } catch (Exception e) { 307 Log.e(LOG_TAG, "Delete failed" + e); 308 } 309 } 310 } 311