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