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.provider.Telephony; 36 import android.telephony.CarrierConfigManager; 37 import android.telephony.ImsiEncryptionInfo; 38 import android.telephony.SubscriptionManager; 39 import android.telephony.TelephonyManager; 40 import android.text.TextUtils; 41 import android.util.Log; 42 import android.util.Pair; 43 44 import com.android.internal.annotations.VisibleForTesting; 45 46 import org.json.JSONArray; 47 import org.json.JSONException; 48 import org.json.JSONObject; 49 50 import java.io.BufferedReader; 51 import java.io.ByteArrayInputStream; 52 import java.io.FileInputStream; 53 import java.io.IOException; 54 import java.io.InputStream; 55 import java.io.InputStreamReader; 56 import java.security.PublicKey; 57 import java.security.cert.CertificateFactory; 58 import java.security.cert.X509Certificate; 59 import java.util.Date; 60 import java.util.Random; 61 import java.util.zip.GZIPInputStream; 62 import java.util.zip.ZipException; 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 CERT_BEGIN_STRING = "-----BEGIN CERTIFICATE-----"; 72 73 private static final String CERT_END_STRING = "-----END CERTIFICATE-----"; 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 /* Intent for downloading the public key */ 86 private static final String INTENT_KEY_RENEWAL_ALARM_PREFIX = 87 "com.android.internal.telephony.carrier_key_download_alarm"; 88 89 @VisibleForTesting 90 public int mKeyAvailability = 0; 91 92 private static final String JSON_CERTIFICATE = "certificate"; 93 private static final String JSON_CERTIFICATE_ALTERNATE = "public-key"; 94 private static final String JSON_TYPE = "key-type"; 95 private static final String JSON_IDENTIFIER = "key-identifier"; 96 private static final String JSON_CARRIER_KEYS = "carrier-keys"; 97 private static final String JSON_TYPE_VALUE_WLAN = "WLAN"; 98 private static final String JSON_TYPE_VALUE_EPDG = "EPDG"; 99 100 private static final int EVENT_ALARM_OR_CONFIG_CHANGE = 0; 101 private static final int EVENT_DOWNLOAD_COMPLETE = 1; 102 103 104 private static final int[] CARRIER_KEY_TYPES = {TelephonyManager.KEY_TYPE_EPDG, 105 TelephonyManager.KEY_TYPE_WLAN}; 106 107 private final Phone mPhone; 108 private final Context mContext; 109 public final DownloadManager mDownloadManager; 110 private String mURL; 111 private boolean mAllowedOverMeteredNetwork = false; 112 private boolean mDeleteOldKeyAfterDownload = false; 113 private TelephonyManager mTelephonyManager; 114 115 @VisibleForTesting 116 public String mMccMncForDownload; 117 public int mCarrierId; 118 @VisibleForTesting 119 public long mDownloadId; 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(INTENT_KEY_RENEWAL_ALARM_PREFIX); 127 filter.addAction(TelephonyIntents.ACTION_CARRIER_CERTIFICATE_DOWNLOAD); 128 mContext.registerReceiver(mBroadcastReceiver, filter, null, phone); 129 mDownloadManager = (DownloadManager) mContext.getSystemService(Context.DOWNLOAD_SERVICE); 130 mTelephonyManager = mContext.getSystemService(TelephonyManager.class) 131 .createForSubscriptionId(mPhone.getSubId()); 132 } 133 134 private final BroadcastReceiver mDownloadReceiver = new BroadcastReceiver() { 135 @Override 136 public void onReceive(Context context, Intent intent) { 137 String action = intent.getAction(); 138 if (action.equals(DownloadManager.ACTION_DOWNLOAD_COMPLETE)) { 139 Log.d(LOG_TAG, "Download Complete"); 140 sendMessage(obtainMessage(EVENT_DOWNLOAD_COMPLETE, 141 intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, 0))); 142 } 143 } 144 }; 145 146 private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { 147 @Override 148 public void onReceive(Context context, Intent intent) { 149 String action = intent.getAction(); 150 int slotIndex = SubscriptionManager.getSlotIndex(mPhone.getSubId()); 151 int phoneId = mPhone.getPhoneId(); 152 if (action.equals(INTENT_KEY_RENEWAL_ALARM_PREFIX)) { 153 int slotIndexExtra = intent.getIntExtra(SubscriptionManager.EXTRA_SLOT_INDEX, -1); 154 if (slotIndexExtra == slotIndex) { 155 Log.d(LOG_TAG, "Handling key renewal alarm: " + action); 156 sendEmptyMessage(EVENT_ALARM_OR_CONFIG_CHANGE); 157 } 158 } else if (action.equals(TelephonyIntents.ACTION_CARRIER_CERTIFICATE_DOWNLOAD)) { 159 if (phoneId == intent.getIntExtra(PhoneConstants.PHONE_KEY, 160 SubscriptionManager.INVALID_SIM_SLOT_INDEX)) { 161 Log.d(LOG_TAG, "Handling reset intent: " + action); 162 sendEmptyMessage(EVENT_ALARM_OR_CONFIG_CHANGE); 163 } 164 } else if (action.equals(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED)) { 165 if (phoneId == intent.getIntExtra(PhoneConstants.PHONE_KEY, 166 SubscriptionManager.INVALID_SIM_SLOT_INDEX)) { 167 Log.d(LOG_TAG, "Carrier Config changed: " + 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 = carrierConfigManager.getConfigForSubId(subId); 393 if (b == null) { 394 return false; 395 } 396 mKeyAvailability = b.getInt(CarrierConfigManager.IMSI_KEY_AVAILABILITY_INT); 397 mURL = b.getString(CarrierConfigManager.IMSI_KEY_DOWNLOAD_URL_STRING); 398 mAllowedOverMeteredNetwork = b.getBoolean( 399 KEY_ALLOW_METERED_NETWORK_FOR_CERT_DOWNLOAD_BOOL); 400 if (mKeyAvailability == 0 || TextUtils.isEmpty(mURL)) { 401 Log.d(LOG_TAG, 402 "Carrier not enabled or invalid values. mKeyAvailability=" + mKeyAvailability 403 + " mURL=" + mURL); 404 return false; 405 } 406 for (int key_type : CARRIER_KEY_TYPES) { 407 if (isKeyEnabled(key_type)) { 408 return true; 409 } 410 } 411 return false; 412 } 413 convertToStringNoGZip(DownloadManager downloadManager, long downloadId)414 private static String convertToStringNoGZip(DownloadManager downloadManager, long downloadId) { 415 StringBuilder sb = new StringBuilder(); 416 try (InputStream source = new FileInputStream( 417 downloadManager.openDownloadedFile(downloadId).getFileDescriptor())) { 418 // If the carrier does not have the data gzipped, fallback to assuming it is not zipped. 419 // parseJsonAndPersistKey may still fail if the data is malformed, so we won't be 420 // persisting random bogus strings thinking it's the cert 421 BufferedReader reader = new BufferedReader(new InputStreamReader(source, UTF_8)); 422 423 String line; 424 while ((line = reader.readLine()) != null) { 425 sb.append(line).append('\n'); 426 } 427 } catch (IOException e) { 428 e.printStackTrace(); 429 return null; 430 } 431 return sb.toString(); 432 } 433 convertToString(DownloadManager downloadManager, long downloadId)434 private static String convertToString(DownloadManager downloadManager, long downloadId) { 435 try (InputStream source = new FileInputStream( 436 downloadManager.openDownloadedFile(downloadId).getFileDescriptor()); 437 InputStream gzipIs = new GZIPInputStream(source)) { 438 BufferedReader reader = new BufferedReader(new InputStreamReader(gzipIs, UTF_8)); 439 StringBuilder sb = new StringBuilder(); 440 441 String line; 442 while ((line = reader.readLine()) != null) { 443 sb.append(line).append('\n'); 444 } 445 return sb.toString(); 446 } catch (ZipException e) { 447 // GZIPInputStream constructor will throw exception if stream is not GZIP 448 Log.d(LOG_TAG, "Stream is not gzipped e=" + e); 449 return null; 450 } catch (IOException e) { 451 Log.e(LOG_TAG, "Unexpected exception in convertToString e=" + e); 452 return null; 453 } 454 } 455 456 /** 457 * Converts the string into a json object to retreive the nodes. The Json should have 3 nodes, 458 * including the Carrier public key, the key type and the key identifier. Once the nodes have 459 * been extracted, they get persisted to the database. Sample: 460 * "carrier-keys": [ { "certificate": "", 461 * "key-type": "WLAN", 462 * "key-identifier": "" 463 * } ] 464 * @param jsonStr the json string. 465 * @param mccMnc contains the mcc, mnc. 466 */ 467 @VisibleForTesting parseJsonAndPersistKey(String jsonStr, String mccMnc, int carrierId)468 public void parseJsonAndPersistKey(String jsonStr, String mccMnc, int carrierId) { 469 if (TextUtils.isEmpty(jsonStr) || TextUtils.isEmpty(mccMnc) 470 || carrierId == TelephonyManager.UNKNOWN_CARRIER_ID) { 471 Log.e(LOG_TAG, "jsonStr or mcc, mnc: is empty or carrierId is UNKNOWN_CARRIER_ID"); 472 return; 473 } 474 try { 475 String mcc = mccMnc.substring(0, 3); 476 String mnc = mccMnc.substring(3); 477 JSONObject jsonObj = new JSONObject(jsonStr); 478 JSONArray keys = jsonObj.getJSONArray(JSON_CARRIER_KEYS); 479 for (int i = 0; i < keys.length(); i++) { 480 JSONObject key = keys.getJSONObject(i); 481 // Support both "public-key" and "certificate" String property. 482 String cert = null; 483 if (key.has(JSON_CERTIFICATE)) { 484 cert = key.getString(JSON_CERTIFICATE); 485 } else { 486 cert = key.getString(JSON_CERTIFICATE_ALTERNATE); 487 } 488 // The key-type property is optional, therefore, the default value is WLAN type if 489 // not specified. 490 int type = TelephonyManager.KEY_TYPE_WLAN; 491 if (key.has(JSON_TYPE)) { 492 String typeString = key.getString(JSON_TYPE); 493 if (typeString.equals(JSON_TYPE_VALUE_EPDG)) { 494 type = TelephonyManager.KEY_TYPE_EPDG; 495 } else if (!typeString.equals(JSON_TYPE_VALUE_WLAN)) { 496 Log.e(LOG_TAG, "Invalid key-type specified: " + typeString); 497 } 498 } 499 String identifier = key.getString(JSON_IDENTIFIER); 500 Pair<PublicKey, Long> keyInfo = 501 getKeyInformation(cleanCertString(cert).getBytes()); 502 if (mDeleteOldKeyAfterDownload) { 503 mPhone.deleteCarrierInfoForImsiEncryption(TelephonyManager.UNKNOWN_CARRIER_ID); 504 mDeleteOldKeyAfterDownload = false; 505 } 506 savePublicKey(keyInfo.first, type, identifier, keyInfo.second, mcc, mnc, carrierId); 507 } 508 } catch (final JSONException e) { 509 Log.e(LOG_TAG, "Json parsing error: " + e.getMessage()); 510 } catch (final Exception e) { 511 Log.e(LOG_TAG, "Exception getting certificate: " + e); 512 } 513 } 514 515 /** 516 * introspects the mKeyAvailability bitmask 517 * @return true if the digit at position k is 1, else false. 518 */ 519 @VisibleForTesting isKeyEnabled(int keyType)520 public boolean isKeyEnabled(int keyType) { 521 // since keytype has values of 1, 2.... we need to subtract 1 from the keytype. 522 return isKeyEnabled(keyType, mKeyAvailability); 523 } 524 525 /** 526 * introspects the mKeyAvailability bitmask 527 * @return true if the digit at position k is 1, else false. 528 */ isKeyEnabled(int keyType, int keyAvailability)529 public static boolean isKeyEnabled(int keyType, int keyAvailability) { 530 // since keytype has values of 1, 2.... we need to subtract 1 from the keytype. 531 int returnValue = (keyAvailability >> (keyType - 1)) & 1; 532 return (returnValue == 1) ? true : false; 533 } 534 535 /** 536 * Checks whether is the keys are absent or close to expiration. Returns true, if either of 537 * those conditions are true. 538 * @return boolean returns true when keys are absent or close to expiration, else false. 539 */ 540 @VisibleForTesting areCarrierKeysAbsentOrExpiring()541 public boolean areCarrierKeysAbsentOrExpiring() { 542 for (int key_type : CARRIER_KEY_TYPES) { 543 if (!isKeyEnabled(key_type)) { 544 continue; 545 } 546 // get encryption info with fallback=false so that we attempt a download even if there's 547 // backup info stored in carrier config 548 ImsiEncryptionInfo imsiEncryptionInfo = 549 mPhone.getCarrierInfoForImsiEncryption(key_type, false); 550 if (imsiEncryptionInfo == null) { 551 Log.d(LOG_TAG, "Key not found for: " + key_type); 552 return true; 553 } else if (imsiEncryptionInfo.getCarrierId() == TelephonyManager.UNKNOWN_CARRIER_ID) { 554 Log.d(LOG_TAG, "carrier key is unknown carrier, so prefer to reDownload"); 555 mDeleteOldKeyAfterDownload = true; 556 return true; 557 } 558 Date imsiDate = imsiEncryptionInfo.getExpirationTime(); 559 long timeToExpire = imsiDate.getTime() - System.currentTimeMillis(); 560 return (timeToExpire < START_RENEWAL_WINDOW_DAYS * DAY_IN_MILLIS) ? true : false; 561 } 562 return false; 563 } 564 downloadKey()565 private boolean downloadKey() { 566 Log.d(LOG_TAG, "starting download from: " + mURL); 567 String mccMnc = getSimOperator(); 568 int carrierId = getSimCarrierId(); 569 if (!TextUtils.isEmpty(mccMnc) || carrierId != TelephonyManager.UNKNOWN_CARRIER_ID) { 570 Log.d(LOG_TAG, "downloading key for mccmnc : " + mccMnc + ", carrierId : " 571 + carrierId); 572 } else { 573 Log.e(LOG_TAG, "mccmnc or carrierId is UnKnown"); 574 return false; 575 } 576 try { 577 // register the broadcast receiver to listen for download complete 578 IntentFilter filter = new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE); 579 mContext.registerReceiver(mDownloadReceiver, filter, null, mPhone, 580 Context.RECEIVER_EXPORTED); 581 582 DownloadManager.Request request = new DownloadManager.Request(Uri.parse(mURL)); 583 584 // TODO(b/128550341): Implement the logic to minimize using metered network such as 585 // LTE for downloading a certificate. 586 request.setAllowedOverMetered(mAllowedOverMeteredNetwork); 587 request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_HIDDEN); 588 request.addRequestHeader("Accept-Encoding", "gzip"); 589 Long carrierKeyDownloadRequestId = mDownloadManager.enqueue(request); 590 591 Log.d(LOG_TAG, "saving values mccmnc: " + mccMnc + ", downloadId: " 592 + carrierKeyDownloadRequestId + ", carrierId: " + carrierId); 593 mMccMncForDownload = mccMnc; 594 mCarrierId = carrierId; 595 mDownloadId = carrierKeyDownloadRequestId; 596 } catch (Exception e) { 597 Log.e(LOG_TAG, "exception trying to download key from url: " + mURL); 598 return false; 599 } 600 return true; 601 } 602 603 /** 604 * Save the public key 605 * @param certificate certificate that contains the public key. 606 * @return Pair containing the Public Key and the expiration date. 607 **/ 608 @VisibleForTesting getKeyInformation(byte[] certificate)609 public static Pair<PublicKey, Long> getKeyInformation(byte[] certificate) throws Exception { 610 InputStream inStream = new ByteArrayInputStream(certificate); 611 CertificateFactory cf = CertificateFactory.getInstance("X.509"); 612 X509Certificate cert = (X509Certificate) cf.generateCertificate(inStream); 613 Pair<PublicKey, Long> keyInformation = 614 new Pair(cert.getPublicKey(), cert.getNotAfter().getTime()); 615 return keyInformation; 616 } 617 618 /** 619 * Save the public key 620 * @param publicKey public key. 621 * @param type key-type. 622 * @param identifier which is an opaque string. 623 * @param expirationDate expiration date of the key. 624 * @param mcc 625 * @param mnc 626 **/ 627 @VisibleForTesting savePublicKey(PublicKey publicKey, int type, String identifier, long expirationDate, String mcc, String mnc, int carrierId)628 public void savePublicKey(PublicKey publicKey, int type, String identifier, long expirationDate, 629 String mcc, String mnc, int carrierId) { 630 ImsiEncryptionInfo imsiEncryptionInfo = new ImsiEncryptionInfo(mcc, mnc, 631 type, identifier, publicKey, new Date(expirationDate), carrierId); 632 mPhone.setCarrierInfoForImsiEncryption(imsiEncryptionInfo); 633 } 634 635 /** 636 * Remove potential extraneous text in a certificate string 637 * @param cert certificate string 638 * @return Cleaned up version of the certificate string 639 */ 640 @VisibleForTesting cleanCertString(String cert)641 public static String cleanCertString(String cert) { 642 return cert.substring( 643 cert.indexOf(CERT_BEGIN_STRING), 644 cert.indexOf(CERT_END_STRING) + CERT_END_STRING.length()); 645 } 646 } 647