• 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 java.nio.charset.StandardCharsets.UTF_8;
20 
21 import android.app.AlarmManager;
22 import android.app.DownloadManager;
23 import android.app.PendingIntent;
24 import android.content.BroadcastReceiver;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.IntentFilter;
28 import android.database.Cursor;
29 import android.net.Uri;
30 import android.os.Handler;
31 import android.os.Message;
32 import android.os.PersistableBundle;
33 import android.telephony.CarrierConfigManager;
34 import android.telephony.ImsiEncryptionInfo;
35 import android.telephony.SubscriptionManager;
36 import android.telephony.TelephonyManager;
37 import android.text.TextUtils;
38 import android.util.Log;
39 import android.util.Pair;
40 
41 import com.android.internal.annotations.VisibleForTesting;
42 
43 import org.json.JSONArray;
44 import org.json.JSONException;
45 import org.json.JSONObject;
46 
47 import java.io.BufferedReader;
48 import java.io.ByteArrayInputStream;
49 import java.io.FileInputStream;
50 import java.io.IOException;
51 import java.io.InputStream;
52 import java.io.InputStreamReader;
53 import java.security.PublicKey;
54 import java.security.cert.CertificateFactory;
55 import java.security.cert.X509Certificate;
56 import java.util.Date;
57 import java.util.Random;
58 import java.util.zip.GZIPInputStream;
59 import java.util.zip.ZipException;
60 
61 /**
62  * This class contains logic to get Certificates and keep them current.
63  * The class will be instantiated by various Phone implementations.
64  */
65 public class CarrierKeyDownloadManager extends Handler {
66     private static final String LOG_TAG = "CarrierKeyDownloadManager";
67 
68     private static final String CERT_BEGIN_STRING = "-----BEGIN CERTIFICATE-----";
69 
70     private static final String CERT_END_STRING = "-----END CERTIFICATE-----";
71 
72     private static final int DAY_IN_MILLIS = 24 * 3600 * 1000;
73 
74     // Create a window prior to the key expiration, during which the cert will be
75     // downloaded. Defines the start date of that window. So if the key expires on
76     // Dec  21st, the start of the renewal window will be Dec 1st.
77     private static final int START_RENEWAL_WINDOW_DAYS = 21;
78 
79     // This will define the end date of the window.
80     private static final int END_RENEWAL_WINDOW_DAYS = 7;
81 
82     /* Intent for downloading the public key */
83     private static final String INTENT_KEY_RENEWAL_ALARM_PREFIX =
84             "com.android.internal.telephony.carrier_key_download_alarm";
85 
86     @VisibleForTesting
87     public int mKeyAvailability = 0;
88 
89     private static final String JSON_CERTIFICATE = "certificate";
90     private static final String JSON_CERTIFICATE_ALTERNATE = "public-key";
91     private static final String JSON_TYPE = "key-type";
92     private static final String JSON_IDENTIFIER = "key-identifier";
93     private static final String JSON_CARRIER_KEYS = "carrier-keys";
94     private static final String JSON_TYPE_VALUE_WLAN = "WLAN";
95     private static final String JSON_TYPE_VALUE_EPDG = "EPDG";
96 
97     private static final int EVENT_ALARM_OR_CONFIG_CHANGE = 0;
98     private static final int EVENT_DOWNLOAD_COMPLETE = 1;
99 
100 
101     private static final int[] CARRIER_KEY_TYPES = {TelephonyManager.KEY_TYPE_EPDG,
102             TelephonyManager.KEY_TYPE_WLAN};
103 
104     private final Phone mPhone;
105     private final Context mContext;
106     public final DownloadManager mDownloadManager;
107     private String mURL;
108     private boolean mAllowedOverMeteredNetwork = false;
109     private boolean mDeleteOldKeyAfterDownload = false;
110     private TelephonyManager mTelephonyManager;
111 
112     @VisibleForTesting
113     public String mMccMncForDownload;
114     public int mCarrierId;
115     @VisibleForTesting
116     public long mDownloadId;
117 
CarrierKeyDownloadManager(Phone phone)118     public CarrierKeyDownloadManager(Phone phone) {
119         mPhone = phone;
120         mContext = phone.getContext();
121         IntentFilter filter = new IntentFilter();
122         filter.addAction(INTENT_KEY_RENEWAL_ALARM_PREFIX);
123         filter.addAction(TelephonyIntents.ACTION_CARRIER_CERTIFICATE_DOWNLOAD);
124         mContext.registerReceiver(mBroadcastReceiver, filter, null, phone);
125         mDownloadManager = (DownloadManager) mContext.getSystemService(Context.DOWNLOAD_SERVICE);
126         mTelephonyManager = mContext.getSystemService(TelephonyManager.class)
127                 .createForSubscriptionId(mPhone.getSubId());
128         CarrierConfigManager carrierConfigManager = mContext.getSystemService(
129                 CarrierConfigManager.class);
130         // Callback which directly handle config change should be executed on handler thread
131         carrierConfigManager.registerCarrierConfigChangeListener(this::post,
132                 (slotIndex, subId, carrierId, specificCarrierId) -> {
133                     if (slotIndex == mPhone.getPhoneId()) {
134                         Log.d(LOG_TAG, "Carrier Config changed: slotIndex=" + slotIndex);
135                         handleAlarmOrConfigChange();
136                     }
137                 });
138     }
139 
140     private final BroadcastReceiver mDownloadReceiver = new BroadcastReceiver() {
141         @Override
142         public void onReceive(Context context, Intent intent) {
143             String action = intent.getAction();
144             if (action.equals(DownloadManager.ACTION_DOWNLOAD_COMPLETE)) {
145                 Log.d(LOG_TAG, "Download Complete");
146                 sendMessage(obtainMessage(EVENT_DOWNLOAD_COMPLETE,
147                         intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, 0)));
148             }
149         }
150     };
151 
152     private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
153         @Override
154         public void onReceive(Context context, Intent intent) {
155             String action = intent.getAction();
156             int slotIndex = SubscriptionManager.getSlotIndex(mPhone.getSubId());
157             int phoneId = mPhone.getPhoneId();
158             if (action.equals(INTENT_KEY_RENEWAL_ALARM_PREFIX)) {
159                 int slotIndexExtra = intent.getIntExtra(SubscriptionManager.EXTRA_SLOT_INDEX, -1);
160                 if (slotIndexExtra == slotIndex) {
161                     Log.d(LOG_TAG, "Handling key renewal alarm: " + action);
162                     sendEmptyMessage(EVENT_ALARM_OR_CONFIG_CHANGE);
163                 }
164             } else if (action.equals(TelephonyIntents.ACTION_CARRIER_CERTIFICATE_DOWNLOAD)) {
165                 if (phoneId == intent.getIntExtra(PhoneConstants.PHONE_KEY,
166                         SubscriptionManager.INVALID_SIM_SLOT_INDEX)) {
167                     Log.d(LOG_TAG, "Handling reset intent: " + 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 = null;
393         try {
394             b = carrierConfigManager.getConfigForSubId(subId,
395                     CarrierConfigManager.IMSI_KEY_AVAILABILITY_INT,
396                     CarrierConfigManager.IMSI_KEY_DOWNLOAD_URL_STRING,
397                     CarrierConfigManager.KEY_ALLOW_METERED_NETWORK_FOR_CERT_DOWNLOAD_BOOL);
398         } catch (RuntimeException e) {
399             Log.e(LOG_TAG, "CarrierConfigLoader is not available.");
400         }
401         if (b == null || b.isEmpty()) {
402             return false;
403         }
404 
405         mKeyAvailability = b.getInt(CarrierConfigManager.IMSI_KEY_AVAILABILITY_INT);
406         mURL = b.getString(CarrierConfigManager.IMSI_KEY_DOWNLOAD_URL_STRING);
407         mAllowedOverMeteredNetwork = b.getBoolean(
408                 CarrierConfigManager.KEY_ALLOW_METERED_NETWORK_FOR_CERT_DOWNLOAD_BOOL);
409         if (mKeyAvailability == 0 || TextUtils.isEmpty(mURL)) {
410             Log.d(LOG_TAG,
411                     "Carrier not enabled or invalid values. mKeyAvailability=" + mKeyAvailability
412                             + " mURL=" + mURL);
413             return false;
414         }
415         for (int key_type : CARRIER_KEY_TYPES) {
416             if (isKeyEnabled(key_type)) {
417                 return true;
418             }
419         }
420         return false;
421     }
422 
convertToStringNoGZip(DownloadManager downloadManager, long downloadId)423     private static String convertToStringNoGZip(DownloadManager downloadManager, long downloadId) {
424         StringBuilder sb = new StringBuilder();
425         try (InputStream source = new FileInputStream(
426                     downloadManager.openDownloadedFile(downloadId).getFileDescriptor())) {
427             // If the carrier does not have the data gzipped, fallback to assuming it is not zipped.
428             // parseJsonAndPersistKey may still fail if the data is malformed, so we won't be
429             // persisting random bogus strings thinking it's the cert
430             BufferedReader reader = new BufferedReader(new InputStreamReader(source, UTF_8));
431 
432             String line;
433             while ((line = reader.readLine()) != null) {
434                 sb.append(line).append('\n');
435             }
436         } catch (IOException e) {
437             e.printStackTrace();
438             return null;
439         }
440         return sb.toString();
441     }
442 
convertToString(DownloadManager downloadManager, long downloadId)443     private static String convertToString(DownloadManager downloadManager, long downloadId) {
444         try (InputStream source = new FileInputStream(
445                     downloadManager.openDownloadedFile(downloadId).getFileDescriptor());
446                     InputStream gzipIs = new GZIPInputStream(source)) {
447             BufferedReader reader = new BufferedReader(new InputStreamReader(gzipIs, UTF_8));
448             StringBuilder sb = new StringBuilder();
449 
450             String line;
451             while ((line = reader.readLine()) != null) {
452                 sb.append(line).append('\n');
453             }
454             return sb.toString();
455         } catch (ZipException e) {
456             // GZIPInputStream constructor will throw exception if stream is not GZIP
457             Log.d(LOG_TAG, "Stream is not gzipped e=" + e);
458             return null;
459         } catch (IOException e) {
460             Log.e(LOG_TAG, "Unexpected exception in convertToString e=" + e);
461             return null;
462         }
463     }
464 
465     /**
466      * Converts the string into a json object to retreive the nodes. The Json should have 3 nodes,
467      * including the Carrier public key, the key type and the key identifier. Once the nodes have
468      * been extracted, they get persisted to the database. Sample:
469      *      "carrier-keys": [ { "certificate": "",
470      *                         "key-type": "WLAN",
471      *                         "key-identifier": ""
472      *                        } ]
473      * @param jsonStr the json string.
474      * @param mccMnc contains the mcc, mnc.
475      */
476     @VisibleForTesting
parseJsonAndPersistKey(String jsonStr, String mccMnc, int carrierId)477     public void parseJsonAndPersistKey(String jsonStr, String mccMnc, int carrierId) {
478         if (TextUtils.isEmpty(jsonStr) || TextUtils.isEmpty(mccMnc)
479                 || carrierId == TelephonyManager.UNKNOWN_CARRIER_ID) {
480             Log.e(LOG_TAG, "jsonStr or mcc, mnc: is empty or carrierId is UNKNOWN_CARRIER_ID");
481             return;
482         }
483         try {
484             String mcc = mccMnc.substring(0, 3);
485             String mnc = mccMnc.substring(3);
486             JSONObject jsonObj = new JSONObject(jsonStr);
487             JSONArray keys = jsonObj.getJSONArray(JSON_CARRIER_KEYS);
488             for (int i = 0; i < keys.length(); i++) {
489                 JSONObject key = keys.getJSONObject(i);
490                 // Support both "public-key" and "certificate" String property.
491                 String cert = null;
492                 if (key.has(JSON_CERTIFICATE)) {
493                     cert = key.getString(JSON_CERTIFICATE);
494                 } else {
495                     cert = key.getString(JSON_CERTIFICATE_ALTERNATE);
496                 }
497                 // The key-type property is optional, therefore, the default value is WLAN type if
498                 // not specified.
499                 int type = TelephonyManager.KEY_TYPE_WLAN;
500                 if (key.has(JSON_TYPE)) {
501                     String typeString = key.getString(JSON_TYPE);
502                     if (typeString.equals(JSON_TYPE_VALUE_EPDG)) {
503                         type = TelephonyManager.KEY_TYPE_EPDG;
504                     } else if (!typeString.equals(JSON_TYPE_VALUE_WLAN)) {
505                         Log.e(LOG_TAG, "Invalid key-type specified: " + typeString);
506                     }
507                 }
508                 String identifier = key.getString(JSON_IDENTIFIER);
509                 Pair<PublicKey, Long> keyInfo =
510                         getKeyInformation(cleanCertString(cert).getBytes());
511                 if (mDeleteOldKeyAfterDownload) {
512                     mPhone.deleteCarrierInfoForImsiEncryption(TelephonyManager.UNKNOWN_CARRIER_ID);
513                     mDeleteOldKeyAfterDownload = false;
514                 }
515                 savePublicKey(keyInfo.first, type, identifier, keyInfo.second, mcc, mnc, carrierId);
516             }
517         } catch (final JSONException e) {
518             Log.e(LOG_TAG, "Json parsing error: " + e.getMessage());
519         } catch (final Exception e) {
520             Log.e(LOG_TAG, "Exception getting certificate: " + e);
521         }
522     }
523 
524     /**
525      * introspects the mKeyAvailability bitmask
526      * @return true if the digit at position k is 1, else false.
527      */
528     @VisibleForTesting
isKeyEnabled(int keyType)529     public boolean isKeyEnabled(int keyType) {
530         // since keytype has values of 1, 2.... we need to subtract 1 from the keytype.
531         return isKeyEnabled(keyType, mKeyAvailability);
532     }
533 
534     /**
535      * introspects the mKeyAvailability bitmask
536      * @return true if the digit at position k is 1, else false.
537      */
isKeyEnabled(int keyType, int keyAvailability)538     public static boolean isKeyEnabled(int keyType, int keyAvailability) {
539         // since keytype has values of 1, 2.... we need to subtract 1 from the keytype.
540         int returnValue = (keyAvailability >> (keyType - 1)) & 1;
541         return (returnValue == 1) ? true : false;
542     }
543 
544     /**
545      * Checks whether is the keys are absent or close to expiration. Returns true, if either of
546      * those conditions are true.
547      * @return boolean returns true when keys are absent or close to expiration, else false.
548      */
549     @VisibleForTesting
areCarrierKeysAbsentOrExpiring()550     public boolean areCarrierKeysAbsentOrExpiring() {
551         for (int key_type : CARRIER_KEY_TYPES) {
552             if (!isKeyEnabled(key_type)) {
553                 continue;
554             }
555             // get encryption info with fallback=false so that we attempt a download even if there's
556             // backup info stored in carrier config
557             ImsiEncryptionInfo imsiEncryptionInfo =
558                     mPhone.getCarrierInfoForImsiEncryption(key_type, false);
559             if (imsiEncryptionInfo == null) {
560                 Log.d(LOG_TAG, "Key not found for: " + key_type);
561                 return true;
562             } else if (imsiEncryptionInfo.getCarrierId() == TelephonyManager.UNKNOWN_CARRIER_ID) {
563                 Log.d(LOG_TAG, "carrier key is unknown carrier, so prefer to reDownload");
564                 mDeleteOldKeyAfterDownload = true;
565                 return true;
566             }
567             Date imsiDate = imsiEncryptionInfo.getExpirationTime();
568             long timeToExpire = imsiDate.getTime() - System.currentTimeMillis();
569             return (timeToExpire < START_RENEWAL_WINDOW_DAYS * DAY_IN_MILLIS) ? true : false;
570         }
571         return false;
572     }
573 
downloadKey()574     private boolean downloadKey() {
575         Log.d(LOG_TAG, "starting download from: " + mURL);
576         String mccMnc = getSimOperator();
577         int carrierId = getSimCarrierId();
578         if (!TextUtils.isEmpty(mccMnc) || carrierId != TelephonyManager.UNKNOWN_CARRIER_ID) {
579             Log.d(LOG_TAG, "downloading key for mccmnc : " + mccMnc + ", carrierId : "
580                     + carrierId);
581         } else {
582             Log.e(LOG_TAG, "mccmnc or carrierId is UnKnown");
583             return false;
584         }
585         try {
586             // register the broadcast receiver to listen for download complete
587             IntentFilter filter = new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE);
588             mContext.registerReceiver(mDownloadReceiver, filter, null, mPhone,
589                     Context.RECEIVER_EXPORTED);
590 
591             DownloadManager.Request request = new DownloadManager.Request(Uri.parse(mURL));
592 
593             // TODO(b/128550341): Implement the logic to minimize using metered network such as
594             // LTE for downloading a certificate.
595             request.setAllowedOverMetered(mAllowedOverMeteredNetwork);
596             request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_HIDDEN);
597             request.addRequestHeader("Accept-Encoding", "gzip");
598             Long carrierKeyDownloadRequestId = mDownloadManager.enqueue(request);
599 
600             Log.d(LOG_TAG, "saving values mccmnc: " + mccMnc + ", downloadId: "
601                     + carrierKeyDownloadRequestId + ", carrierId: " + carrierId);
602             mMccMncForDownload = mccMnc;
603             mCarrierId = carrierId;
604             mDownloadId = carrierKeyDownloadRequestId;
605         } catch (Exception e) {
606             Log.e(LOG_TAG, "exception trying to download key from url: " + mURL);
607             return false;
608         }
609         return true;
610     }
611 
612     /**
613      * Save the public key
614      * @param certificate certificate that contains the public key.
615      * @return Pair containing the Public Key and the expiration date.
616      **/
617     @VisibleForTesting
getKeyInformation(byte[] certificate)618     public static Pair<PublicKey, Long> getKeyInformation(byte[] certificate) throws Exception {
619         InputStream inStream = new ByteArrayInputStream(certificate);
620         CertificateFactory cf = CertificateFactory.getInstance("X.509");
621         X509Certificate cert = (X509Certificate) cf.generateCertificate(inStream);
622         Pair<PublicKey, Long> keyInformation =
623                 new Pair(cert.getPublicKey(), cert.getNotAfter().getTime());
624         return keyInformation;
625     }
626 
627     /**
628      * Save the public key
629      * @param publicKey public key.
630      * @param type key-type.
631      * @param identifier which is an opaque string.
632      * @param expirationDate expiration date of the key.
633      * @param mcc
634      * @param mnc
635      **/
636     @VisibleForTesting
savePublicKey(PublicKey publicKey, int type, String identifier, long expirationDate, String mcc, String mnc, int carrierId)637     public void savePublicKey(PublicKey publicKey, int type, String identifier, long expirationDate,
638             String mcc, String mnc, int carrierId) {
639         ImsiEncryptionInfo imsiEncryptionInfo = new ImsiEncryptionInfo(mcc, mnc,
640                 type, identifier, publicKey, new Date(expirationDate), carrierId);
641         mPhone.setCarrierInfoForImsiEncryption(imsiEncryptionInfo);
642     }
643 
644     /**
645      * Remove potential extraneous text in a certificate string
646      * @param cert certificate string
647      * @return Cleaned up version of the certificate string
648      */
649     @VisibleForTesting
cleanCertString(String cert)650     public static String cleanCertString(String cert) {
651         return cert.substring(
652                 cert.indexOf(CERT_BEGIN_STRING),
653                 cert.indexOf(CERT_END_STRING) + CERT_END_STRING.length());
654     }
655 }
656