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