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