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