Home | History | Annotate | Download | only in telephony
      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      */
     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      */
    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      */
    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      */
    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      */
    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      */
    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