• 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 static android.telephony.CarrierConfigManager.KEY_ALLOW_METERED_NETWORK_FOR_CERT_DOWNLOAD_BOOL;
20 
21 import static java.nio.charset.StandardCharsets.UTF_8;
22 
23 import android.app.AlarmManager;
24 import android.app.DownloadManager;
25 import android.app.PendingIntent;
26 import android.content.BroadcastReceiver;
27 import android.content.Context;
28 import android.content.Intent;
29 import android.content.IntentFilter;
30 import android.database.Cursor;
31 import android.net.Uri;
32 import android.os.Handler;
33 import android.os.Message;
34 import android.os.PersistableBundle;
35 import android.provider.Telephony;
36 import android.telephony.CarrierConfigManager;
37 import android.telephony.ImsiEncryptionInfo;
38 import android.telephony.SubscriptionManager;
39 import android.telephony.TelephonyManager;
40 import android.text.TextUtils;
41 import android.util.Log;
42 import android.util.Pair;
43 
44 import com.android.internal.annotations.VisibleForTesting;
45 
46 import org.json.JSONArray;
47 import org.json.JSONException;
48 import org.json.JSONObject;
49 
50 import java.io.BufferedReader;
51 import java.io.ByteArrayInputStream;
52 import java.io.FileInputStream;
53 import java.io.IOException;
54 import java.io.InputStream;
55 import java.io.InputStreamReader;
56 import java.security.PublicKey;
57 import java.security.cert.CertificateFactory;
58 import java.security.cert.X509Certificate;
59 import java.util.Date;
60 import java.util.Random;
61 import java.util.zip.GZIPInputStream;
62 import java.util.zip.ZipException;
63 
64 /**
65  * This class contains logic to get Certificates and keep them current.
66  * The class will be instantiated by various Phone implementations.
67  */
68 public class CarrierKeyDownloadManager extends Handler {
69     private static final String LOG_TAG = "CarrierKeyDownloadManager";
70 
71     private static final String CERT_BEGIN_STRING = "-----BEGIN CERTIFICATE-----";
72 
73     private static final String CERT_END_STRING = "-----END CERTIFICATE-----";
74 
75     private static final int DAY_IN_MILLIS = 24 * 3600 * 1000;
76 
77     // Create a window prior to the key expiration, during which the cert will be
78     // downloaded. Defines the start date of that window. So if the key expires on
79     // Dec  21st, the start of the renewal window will be Dec 1st.
80     private static final int START_RENEWAL_WINDOW_DAYS = 21;
81 
82     // This will define the end date of the window.
83     private static final int END_RENEWAL_WINDOW_DAYS = 7;
84 
85     /* Intent for downloading the public key */
86     private static final String INTENT_KEY_RENEWAL_ALARM_PREFIX =
87             "com.android.internal.telephony.carrier_key_download_alarm";
88 
89     @VisibleForTesting
90     public int mKeyAvailability = 0;
91 
92     private static final String JSON_CERTIFICATE = "certificate";
93     private static final String JSON_CERTIFICATE_ALTERNATE = "public-key";
94     private static final String JSON_TYPE = "key-type";
95     private static final String JSON_IDENTIFIER = "key-identifier";
96     private static final String JSON_CARRIER_KEYS = "carrier-keys";
97     private static final String JSON_TYPE_VALUE_WLAN = "WLAN";
98     private static final String JSON_TYPE_VALUE_EPDG = "EPDG";
99 
100     private static final int EVENT_ALARM_OR_CONFIG_CHANGE = 0;
101     private static final int EVENT_DOWNLOAD_COMPLETE = 1;
102 
103 
104     private static final int[] CARRIER_KEY_TYPES = {TelephonyManager.KEY_TYPE_EPDG,
105             TelephonyManager.KEY_TYPE_WLAN};
106 
107     private final Phone mPhone;
108     private final Context mContext;
109     public final DownloadManager mDownloadManager;
110     private String mURL;
111     private boolean mAllowedOverMeteredNetwork = false;
112     private boolean mDeleteOldKeyAfterDownload = false;
113     private TelephonyManager mTelephonyManager;
114 
115     @VisibleForTesting
116     public String mMccMncForDownload;
117     public int mCarrierId;
118     @VisibleForTesting
119     public long mDownloadId;
120 
CarrierKeyDownloadManager(Phone phone)121     public CarrierKeyDownloadManager(Phone phone) {
122         mPhone = phone;
123         mContext = phone.getContext();
124         IntentFilter filter = new IntentFilter();
125         filter.addAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
126         filter.addAction(INTENT_KEY_RENEWAL_ALARM_PREFIX);
127         filter.addAction(TelephonyIntents.ACTION_CARRIER_CERTIFICATE_DOWNLOAD);
128         mContext.registerReceiver(mBroadcastReceiver, filter, null, phone);
129         mDownloadManager = (DownloadManager) mContext.getSystemService(Context.DOWNLOAD_SERVICE);
130         mTelephonyManager = mContext.getSystemService(TelephonyManager.class)
131                 .createForSubscriptionId(mPhone.getSubId());
132     }
133 
134     private final BroadcastReceiver mDownloadReceiver = new BroadcastReceiver() {
135         @Override
136         public void onReceive(Context context, Intent intent) {
137             String action = intent.getAction();
138             if (action.equals(DownloadManager.ACTION_DOWNLOAD_COMPLETE)) {
139                 Log.d(LOG_TAG, "Download Complete");
140                 sendMessage(obtainMessage(EVENT_DOWNLOAD_COMPLETE,
141                         intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, 0)));
142             }
143         }
144     };
145 
146     private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
147         @Override
148         public void onReceive(Context context, Intent intent) {
149             String action = intent.getAction();
150             int slotIndex = SubscriptionManager.getSlotIndex(mPhone.getSubId());
151             int phoneId = mPhone.getPhoneId();
152             if (action.equals(INTENT_KEY_RENEWAL_ALARM_PREFIX)) {
153                 int slotIndexExtra = intent.getIntExtra(SubscriptionManager.EXTRA_SLOT_INDEX, -1);
154                 if (slotIndexExtra == slotIndex) {
155                     Log.d(LOG_TAG, "Handling key renewal alarm: " + action);
156                     sendEmptyMessage(EVENT_ALARM_OR_CONFIG_CHANGE);
157                 }
158             } else if (action.equals(TelephonyIntents.ACTION_CARRIER_CERTIFICATE_DOWNLOAD)) {
159                 if (phoneId == intent.getIntExtra(PhoneConstants.PHONE_KEY,
160                         SubscriptionManager.INVALID_SIM_SLOT_INDEX)) {
161                     Log.d(LOG_TAG, "Handling reset intent: " + action);
162                     sendEmptyMessage(EVENT_ALARM_OR_CONFIG_CHANGE);
163                 }
164             } else if (action.equals(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED)) {
165                 if (phoneId == intent.getIntExtra(PhoneConstants.PHONE_KEY,
166                         SubscriptionManager.INVALID_SIM_SLOT_INDEX)) {
167                     Log.d(LOG_TAG, "Carrier Config changed: " + action);
168                     sendEmptyMessage(EVENT_ALARM_OR_CONFIG_CHANGE);
169                 }
170             }
171         }
172     };
173 
174     @Override
handleMessage(Message msg)175     public void handleMessage (Message msg) {
176         switch (msg.what) {
177             case EVENT_ALARM_OR_CONFIG_CHANGE:
178                 handleAlarmOrConfigChange();
179                 break;
180             case EVENT_DOWNLOAD_COMPLETE:
181                 long carrierKeyDownloadIdentifier = (long) msg.obj;
182                 String currentMccMnc = getSimOperator();
183                 int carrierId = getSimCarrierId();
184                 if (isValidDownload(currentMccMnc, carrierKeyDownloadIdentifier, carrierId)) {
185                     onDownloadComplete(carrierKeyDownloadIdentifier, currentMccMnc, carrierId);
186                     onPostDownloadProcessing(carrierKeyDownloadIdentifier);
187                 }
188                 break;
189         }
190     }
191 
onPostDownloadProcessing(long carrierKeyDownloadIdentifier)192     private void onPostDownloadProcessing(long carrierKeyDownloadIdentifier) {
193         resetRenewalAlarm();
194         cleanupDownloadInfo();
195 
196         // unregister from DOWNLOAD_COMPLETE
197         mContext.unregisterReceiver(mDownloadReceiver);
198     }
199 
handleAlarmOrConfigChange()200     private void handleAlarmOrConfigChange() {
201         if (carrierUsesKeys()) {
202             if (areCarrierKeysAbsentOrExpiring()) {
203                 boolean downloadStartedSuccessfully = downloadKey();
204                 // if the download was attempted, but not started successfully, and if carriers uses
205                 // keys, we'll still want to renew the alarms, and try downloading the key a day
206                 // later.
207                 if (!downloadStartedSuccessfully) {
208                     resetRenewalAlarm();
209                 }
210             } else {
211                 return;
212             }
213         } else {
214             // delete any existing alarms.
215             cleanupRenewalAlarms();
216             mPhone.deleteCarrierInfoForImsiEncryption(getSimCarrierId());
217         }
218     }
219 
cleanupDownloadInfo()220     private void cleanupDownloadInfo() {
221         Log.d(LOG_TAG, "Cleaning up download info");
222         mDownloadId = -1;
223         mMccMncForDownload = null;
224         mCarrierId = TelephonyManager.UNKNOWN_CARRIER_ID;
225     }
226 
cleanupRenewalAlarms()227     private void cleanupRenewalAlarms() {
228         Log.d(LOG_TAG, "Cleaning up existing renewal alarms");
229         int slotIndex = SubscriptionManager.getSlotIndex(mPhone.getSubId());
230         Intent intent = new Intent(INTENT_KEY_RENEWAL_ALARM_PREFIX);
231         intent.putExtra(SubscriptionManager.EXTRA_SLOT_INDEX, slotIndex);
232         PendingIntent carrierKeyDownloadIntent = PendingIntent.getBroadcast(mContext, 0, intent,
233                 PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
234         AlarmManager alarmManager =
235                 (AlarmManager) mContext.getSystemService(mContext.ALARM_SERVICE);
236         alarmManager.cancel(carrierKeyDownloadIntent);
237     }
238 
239     /**
240      * this method returns the date to be used to decide on when to start downloading the key.
241      * from the carrier.
242      **/
243     @VisibleForTesting
getExpirationDate()244     public long getExpirationDate()  {
245         long minExpirationDate = Long.MAX_VALUE;
246         for (int key_type : CARRIER_KEY_TYPES) {
247             if (!isKeyEnabled(key_type)) {
248                 continue;
249             }
250             ImsiEncryptionInfo imsiEncryptionInfo =
251                     mPhone.getCarrierInfoForImsiEncryption(key_type, false);
252             if (imsiEncryptionInfo != null && imsiEncryptionInfo.getExpirationTime() != null) {
253                 if (minExpirationDate > imsiEncryptionInfo.getExpirationTime().getTime()) {
254                     minExpirationDate = imsiEncryptionInfo.getExpirationTime().getTime();
255                 }
256             }
257         }
258 
259         // if there are no keys, or expiration date is in the past, or within 7 days, then we
260         // set the alarm to run in a day. Else, we'll set the alarm to run 7 days prior to
261         // expiration.
262         if (minExpirationDate == Long.MAX_VALUE || (minExpirationDate
263                 < System.currentTimeMillis() + END_RENEWAL_WINDOW_DAYS * DAY_IN_MILLIS)) {
264             minExpirationDate = System.currentTimeMillis() + DAY_IN_MILLIS;
265         } else {
266             // We don't want all the phones to download the certs simultaneously, so
267             // we pick a random time during the download window to avoid this situation.
268             Random random = new Random();
269             int max = START_RENEWAL_WINDOW_DAYS * DAY_IN_MILLIS;
270             int min = END_RENEWAL_WINDOW_DAYS * DAY_IN_MILLIS;
271             int randomTime = random.nextInt(max - min) + min;
272             minExpirationDate = minExpirationDate - randomTime;
273         }
274         return minExpirationDate;
275     }
276 
277     /**
278      * this method resets the alarm. Starts by cleaning up the existing alarms.
279      * We look at the earliest expiration date, and setup an alarms X days prior.
280      * If the expiration date is in the past, we'll setup an alarm to run the next day. This
281      * could happen if the download has failed.
282      **/
283     @VisibleForTesting
resetRenewalAlarm()284     public void resetRenewalAlarm() {
285         cleanupRenewalAlarms();
286         int slotIndex = SubscriptionManager.getSlotIndex(mPhone.getSubId());
287         long minExpirationDate = getExpirationDate();
288         Log.d(LOG_TAG, "minExpirationDate: " + new Date(minExpirationDate));
289         final AlarmManager alarmManager = (AlarmManager) mContext.getSystemService(
290                 Context.ALARM_SERVICE);
291         Intent intent = new Intent(INTENT_KEY_RENEWAL_ALARM_PREFIX);
292         intent.putExtra(SubscriptionManager.EXTRA_SLOT_INDEX, slotIndex);
293         PendingIntent carrierKeyDownloadIntent = PendingIntent.getBroadcast(mContext, 0, intent,
294                 PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
295         alarmManager.set(AlarmManager.RTC_WAKEUP, minExpirationDate, carrierKeyDownloadIntent);
296         Log.d(LOG_TAG, "setRenewalAlarm: action=" + intent.getAction() + " time="
297                 + new Date(minExpirationDate));
298     }
299 
300     /**
301      * Returns the sim operator.
302      **/
303     @VisibleForTesting
getSimOperator()304     public String getSimOperator() {
305         return mTelephonyManager.getSimOperator(mPhone.getSubId());
306     }
307 
308     /**
309      * Returns the sim operator.
310      **/
311     @VisibleForTesting
getSimCarrierId()312     public int getSimCarrierId() {
313         return mTelephonyManager.getSimCarrierId();
314     }
315 
316     /**
317      *  checks if the download was sent by this particular instance. We do this by including the
318      *  slot id in the key. If no value is found, we know that the download was not for this
319      *  instance of the phone.
320      **/
321     @VisibleForTesting
isValidDownload(String currentMccMnc, long currentDownloadId, int carrierId)322     public boolean isValidDownload(String currentMccMnc, long currentDownloadId, int carrierId) {
323         if (currentDownloadId != mDownloadId) {
324             Log.e(LOG_TAG, "download ID=" + currentDownloadId
325                     + " for completed download does not match stored id=" + mDownloadId);
326             return false;
327         }
328 
329         if (TextUtils.isEmpty(currentMccMnc) || TextUtils.isEmpty(mMccMncForDownload)
330                 || !TextUtils.equals(currentMccMnc, mMccMncForDownload)
331                 || mCarrierId != carrierId) {
332             Log.e(LOG_TAG, "currentMccMnc=" + currentMccMnc + " storedMccMnc =" + mMccMncForDownload
333                     + "currentCarrierId = " + carrierId + "  storedCarrierId = " + mCarrierId);
334             return false;
335         }
336 
337         Log.d(LOG_TAG, "Matched MccMnc =  " + currentMccMnc + ", carrierId = " + carrierId
338                 + ", downloadId: " + currentDownloadId);
339         return true;
340     }
341 
342     /**
343      * This method will try to parse the downloaded information, and persist it in the database.
344      **/
onDownloadComplete(long carrierKeyDownloadIdentifier, String mccMnc, int carrierId)345     private void onDownloadComplete(long carrierKeyDownloadIdentifier, String mccMnc,
346             int carrierId) {
347         Log.d(LOG_TAG, "onDownloadComplete: " + carrierKeyDownloadIdentifier);
348         String jsonStr;
349         DownloadManager.Query query = new DownloadManager.Query();
350         query.setFilterById(carrierKeyDownloadIdentifier);
351         Cursor cursor = mDownloadManager.query(query);
352 
353         if (cursor == null) {
354             return;
355         }
356         if (cursor.moveToFirst()) {
357             int columnIndex = cursor.getColumnIndex(DownloadManager.COLUMN_STATUS);
358             if (DownloadManager.STATUS_SUCCESSFUL == cursor.getInt(columnIndex)) {
359                 try {
360                     jsonStr = convertToString(mDownloadManager, carrierKeyDownloadIdentifier);
361                     if (TextUtils.isEmpty(jsonStr)) {
362                         Log.d(LOG_TAG, "fallback to no gzip");
363                         jsonStr = convertToStringNoGZip(mDownloadManager,
364                                 carrierKeyDownloadIdentifier);
365                     }
366                     parseJsonAndPersistKey(jsonStr, mccMnc, carrierId);
367                 } catch (Exception e) {
368                     Log.e(LOG_TAG, "Error in download:" + carrierKeyDownloadIdentifier
369                             + ". " + e);
370                 } finally {
371                     mDownloadManager.remove(carrierKeyDownloadIdentifier);
372                 }
373             }
374             Log.d(LOG_TAG, "Completed downloading keys");
375         }
376         cursor.close();
377         return;
378     }
379 
380     /**
381      * This method checks if the carrier requires key. We'll read the carrier config to make that
382      * determination.
383      * @return boolean returns true if carrier requires keys, else false.
384      **/
carrierUsesKeys()385     private boolean carrierUsesKeys() {
386         CarrierConfigManager carrierConfigManager = (CarrierConfigManager)
387                 mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE);
388         if (carrierConfigManager == null) {
389             return false;
390         }
391         int subId = mPhone.getSubId();
392         PersistableBundle b = carrierConfigManager.getConfigForSubId(subId);
393         if (b == null) {
394             return false;
395         }
396         mKeyAvailability = b.getInt(CarrierConfigManager.IMSI_KEY_AVAILABILITY_INT);
397         mURL = b.getString(CarrierConfigManager.IMSI_KEY_DOWNLOAD_URL_STRING);
398         mAllowedOverMeteredNetwork = b.getBoolean(
399                 KEY_ALLOW_METERED_NETWORK_FOR_CERT_DOWNLOAD_BOOL);
400         if (mKeyAvailability == 0 || TextUtils.isEmpty(mURL)) {
401             Log.d(LOG_TAG,
402                     "Carrier not enabled or invalid values. mKeyAvailability=" + mKeyAvailability
403                             + " mURL=" + mURL);
404             return false;
405         }
406         for (int key_type : CARRIER_KEY_TYPES) {
407             if (isKeyEnabled(key_type)) {
408                 return true;
409             }
410         }
411         return false;
412     }
413 
convertToStringNoGZip(DownloadManager downloadManager, long downloadId)414     private static String convertToStringNoGZip(DownloadManager downloadManager, long downloadId) {
415         StringBuilder sb = new StringBuilder();
416         try (InputStream source = new FileInputStream(
417                     downloadManager.openDownloadedFile(downloadId).getFileDescriptor())) {
418             // If the carrier does not have the data gzipped, fallback to assuming it is not zipped.
419             // parseJsonAndPersistKey may still fail if the data is malformed, so we won't be
420             // persisting random bogus strings thinking it's the cert
421             BufferedReader reader = new BufferedReader(new InputStreamReader(source, UTF_8));
422 
423             String line;
424             while ((line = reader.readLine()) != null) {
425                 sb.append(line).append('\n');
426             }
427         } catch (IOException e) {
428             e.printStackTrace();
429             return null;
430         }
431         return sb.toString();
432     }
433 
convertToString(DownloadManager downloadManager, long downloadId)434     private static String convertToString(DownloadManager downloadManager, long downloadId) {
435         try (InputStream source = new FileInputStream(
436                     downloadManager.openDownloadedFile(downloadId).getFileDescriptor());
437                     InputStream gzipIs = new GZIPInputStream(source)) {
438             BufferedReader reader = new BufferedReader(new InputStreamReader(gzipIs, UTF_8));
439             StringBuilder sb = new StringBuilder();
440 
441             String line;
442             while ((line = reader.readLine()) != null) {
443                 sb.append(line).append('\n');
444             }
445             return sb.toString();
446         } catch (ZipException e) {
447             // GZIPInputStream constructor will throw exception if stream is not GZIP
448             Log.d(LOG_TAG, "Stream is not gzipped e=" + e);
449             return null;
450         } catch (IOException e) {
451             Log.e(LOG_TAG, "Unexpected exception in convertToString e=" + e);
452             return null;
453         }
454     }
455 
456     /**
457      * Converts the string into a json object to retreive the nodes. The Json should have 3 nodes,
458      * including the Carrier public key, the key type and the key identifier. Once the nodes have
459      * been extracted, they get persisted to the database. Sample:
460      *      "carrier-keys": [ { "certificate": "",
461      *                         "key-type": "WLAN",
462      *                         "key-identifier": ""
463      *                        } ]
464      * @param jsonStr the json string.
465      * @param mccMnc contains the mcc, mnc.
466      */
467     @VisibleForTesting
parseJsonAndPersistKey(String jsonStr, String mccMnc, int carrierId)468     public void parseJsonAndPersistKey(String jsonStr, String mccMnc, int carrierId) {
469         if (TextUtils.isEmpty(jsonStr) || TextUtils.isEmpty(mccMnc)
470                 || carrierId == TelephonyManager.UNKNOWN_CARRIER_ID) {
471             Log.e(LOG_TAG, "jsonStr or mcc, mnc: is empty or carrierId is UNKNOWN_CARRIER_ID");
472             return;
473         }
474         try {
475             String mcc = mccMnc.substring(0, 3);
476             String mnc = mccMnc.substring(3);
477             JSONObject jsonObj = new JSONObject(jsonStr);
478             JSONArray keys = jsonObj.getJSONArray(JSON_CARRIER_KEYS);
479             for (int i = 0; i < keys.length(); i++) {
480                 JSONObject key = keys.getJSONObject(i);
481                 // Support both "public-key" and "certificate" String property.
482                 String cert = null;
483                 if (key.has(JSON_CERTIFICATE)) {
484                     cert = key.getString(JSON_CERTIFICATE);
485                 } else {
486                     cert = key.getString(JSON_CERTIFICATE_ALTERNATE);
487                 }
488                 // The key-type property is optional, therefore, the default value is WLAN type if
489                 // not specified.
490                 int type = TelephonyManager.KEY_TYPE_WLAN;
491                 if (key.has(JSON_TYPE)) {
492                     String typeString = key.getString(JSON_TYPE);
493                     if (typeString.equals(JSON_TYPE_VALUE_EPDG)) {
494                         type = TelephonyManager.KEY_TYPE_EPDG;
495                     } else if (!typeString.equals(JSON_TYPE_VALUE_WLAN)) {
496                         Log.e(LOG_TAG, "Invalid key-type specified: " + typeString);
497                     }
498                 }
499                 String identifier = key.getString(JSON_IDENTIFIER);
500                 Pair<PublicKey, Long> keyInfo =
501                         getKeyInformation(cleanCertString(cert).getBytes());
502                 if (mDeleteOldKeyAfterDownload) {
503                     mPhone.deleteCarrierInfoForImsiEncryption(TelephonyManager.UNKNOWN_CARRIER_ID);
504                     mDeleteOldKeyAfterDownload = false;
505                 }
506                 savePublicKey(keyInfo.first, type, identifier, keyInfo.second, mcc, mnc, carrierId);
507             }
508         } catch (final JSONException e) {
509             Log.e(LOG_TAG, "Json parsing error: " + e.getMessage());
510         } catch (final Exception e) {
511             Log.e(LOG_TAG, "Exception getting certificate: " + e);
512         }
513     }
514 
515     /**
516      * introspects the mKeyAvailability bitmask
517      * @return true if the digit at position k is 1, else false.
518      */
519     @VisibleForTesting
isKeyEnabled(int keyType)520     public boolean isKeyEnabled(int keyType) {
521         // since keytype has values of 1, 2.... we need to subtract 1 from the keytype.
522         return isKeyEnabled(keyType, mKeyAvailability);
523     }
524 
525     /**
526      * introspects the mKeyAvailability bitmask
527      * @return true if the digit at position k is 1, else false.
528      */
isKeyEnabled(int keyType, int keyAvailability)529     public static boolean isKeyEnabled(int keyType, int keyAvailability) {
530         // since keytype has values of 1, 2.... we need to subtract 1 from the keytype.
531         int returnValue = (keyAvailability >> (keyType - 1)) & 1;
532         return (returnValue == 1) ? true : false;
533     }
534 
535     /**
536      * Checks whether is the keys are absent or close to expiration. Returns true, if either of
537      * those conditions are true.
538      * @return boolean returns true when keys are absent or close to expiration, else false.
539      */
540     @VisibleForTesting
areCarrierKeysAbsentOrExpiring()541     public boolean areCarrierKeysAbsentOrExpiring() {
542         for (int key_type : CARRIER_KEY_TYPES) {
543             if (!isKeyEnabled(key_type)) {
544                 continue;
545             }
546             // get encryption info with fallback=false so that we attempt a download even if there's
547             // backup info stored in carrier config
548             ImsiEncryptionInfo imsiEncryptionInfo =
549                     mPhone.getCarrierInfoForImsiEncryption(key_type, false);
550             if (imsiEncryptionInfo == null) {
551                 Log.d(LOG_TAG, "Key not found for: " + key_type);
552                 return true;
553             } else if (imsiEncryptionInfo.getCarrierId() == TelephonyManager.UNKNOWN_CARRIER_ID) {
554                 Log.d(LOG_TAG, "carrier key is unknown carrier, so prefer to reDownload");
555                 mDeleteOldKeyAfterDownload = true;
556                 return true;
557             }
558             Date imsiDate = imsiEncryptionInfo.getExpirationTime();
559             long timeToExpire = imsiDate.getTime() - System.currentTimeMillis();
560             return (timeToExpire < START_RENEWAL_WINDOW_DAYS * DAY_IN_MILLIS) ? true : false;
561         }
562         return false;
563     }
564 
downloadKey()565     private boolean downloadKey() {
566         Log.d(LOG_TAG, "starting download from: " + mURL);
567         String mccMnc = getSimOperator();
568         int carrierId = getSimCarrierId();
569         if (!TextUtils.isEmpty(mccMnc) || carrierId != TelephonyManager.UNKNOWN_CARRIER_ID) {
570             Log.d(LOG_TAG, "downloading key for mccmnc : " + mccMnc + ", carrierId : "
571                     + carrierId);
572         } else {
573             Log.e(LOG_TAG, "mccmnc or carrierId is UnKnown");
574             return false;
575         }
576         try {
577             // register the broadcast receiver to listen for download complete
578             IntentFilter filter = new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE);
579             mContext.registerReceiver(mDownloadReceiver, filter, null, mPhone,
580                     Context.RECEIVER_EXPORTED);
581 
582             DownloadManager.Request request = new DownloadManager.Request(Uri.parse(mURL));
583 
584             // TODO(b/128550341): Implement the logic to minimize using metered network such as
585             // LTE for downloading a certificate.
586             request.setAllowedOverMetered(mAllowedOverMeteredNetwork);
587             request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_HIDDEN);
588             request.addRequestHeader("Accept-Encoding", "gzip");
589             Long carrierKeyDownloadRequestId = mDownloadManager.enqueue(request);
590 
591             Log.d(LOG_TAG, "saving values mccmnc: " + mccMnc + ", downloadId: "
592                     + carrierKeyDownloadRequestId + ", carrierId: " + carrierId);
593             mMccMncForDownload = mccMnc;
594             mCarrierId = carrierId;
595             mDownloadId = carrierKeyDownloadRequestId;
596         } catch (Exception e) {
597             Log.e(LOG_TAG, "exception trying to download key from url: " + mURL);
598             return false;
599         }
600         return true;
601     }
602 
603     /**
604      * Save the public key
605      * @param certificate certificate that contains the public key.
606      * @return Pair containing the Public Key and the expiration date.
607      **/
608     @VisibleForTesting
getKeyInformation(byte[] certificate)609     public static Pair<PublicKey, Long> getKeyInformation(byte[] certificate) throws Exception {
610         InputStream inStream = new ByteArrayInputStream(certificate);
611         CertificateFactory cf = CertificateFactory.getInstance("X.509");
612         X509Certificate cert = (X509Certificate) cf.generateCertificate(inStream);
613         Pair<PublicKey, Long> keyInformation =
614                 new Pair(cert.getPublicKey(), cert.getNotAfter().getTime());
615         return keyInformation;
616     }
617 
618     /**
619      * Save the public key
620      * @param publicKey public key.
621      * @param type key-type.
622      * @param identifier which is an opaque string.
623      * @param expirationDate expiration date of the key.
624      * @param mcc
625      * @param mnc
626      **/
627     @VisibleForTesting
savePublicKey(PublicKey publicKey, int type, String identifier, long expirationDate, String mcc, String mnc, int carrierId)628     public void savePublicKey(PublicKey publicKey, int type, String identifier, long expirationDate,
629             String mcc, String mnc, int carrierId) {
630         ImsiEncryptionInfo imsiEncryptionInfo = new ImsiEncryptionInfo(mcc, mnc,
631                 type, identifier, publicKey, new Date(expirationDate), carrierId);
632         mPhone.setCarrierInfoForImsiEncryption(imsiEncryptionInfo);
633     }
634 
635     /**
636      * Remove potential extraneous text in a certificate string
637      * @param cert certificate string
638      * @return Cleaned up version of the certificate string
639      */
640     @VisibleForTesting
cleanCertString(String cert)641     public static String cleanCertString(String cert) {
642         return cert.substring(
643                 cert.indexOf(CERT_BEGIN_STRING),
644                 cert.indexOf(CERT_END_STRING) + CERT_END_STRING.length());
645     }
646 }
647