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