• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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