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