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