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