Use carrier key from carrier config

When no key is available, check if a public carrier key is stored in
carrier config as a backup for the carrier.

Bug: 175801497
Test: manual and atest TelephonyManagerTest
Change-Id: I8e855cca847d65276bfb34a797f5a2fd076a0763
Merged-In: I8e855cca847d65276bfb34a797f5a2fd076a0763
diff --git a/src/java/com/android/internal/telephony/CarrierInfoManager.java b/src/java/com/android/internal/telephony/CarrierInfoManager.java
index ea4d75e..c73214a 100644
--- a/src/java/com/android/internal/telephony/CarrierInfoManager.java
+++ b/src/java/com/android/internal/telephony/CarrierInfoManager.java
@@ -22,16 +22,20 @@
 import android.content.Intent;
 import android.database.Cursor;
 import android.database.sqlite.SQLiteConstraintException;
+import android.os.PersistableBundle;
 import android.os.UserHandle;
 import android.provider.Telephony;
+import android.telephony.CarrierConfigManager;
 import android.telephony.ImsiEncryptionInfo;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 import android.util.Log;
+import android.util.Pair;
 
 import com.android.internal.telephony.metrics.TelephonyMetrics;
 
+import java.security.PublicKey;
 import java.util.Date;
 
 /**
@@ -47,6 +51,10 @@
     */
     private static final int RESET_CARRIER_KEY_RATE_LIMIT = 12 * 60 * 60 * 1000;
 
+    // Key ID used with the backup key from carrier config
+    private static final String EPDG_BACKUP_KEY_ID = "backup_key_from_carrier_config_epdg";
+    private static final String WLAN_BACKUP_KEY_ID = "backup_key_from_carrier_config_wlan";
+
     // Last time the resetCarrierKeysForImsiEncryption API was called successfully.
     private long mLastAccessResetCarrierKey = 0;
 
@@ -54,18 +62,21 @@
      * Returns Carrier specific information that will be used to encrypt the IMSI and IMPI.
      * @param keyType whether the key is being used for WLAN or ePDG.
      * @param context
+     * @param fallback whether to fallback to the IMSI key info stored in carrier config
      * @return ImsiEncryptionInfo which contains the information, including the public key, to be
      *         used for encryption.
      */
     public static ImsiEncryptionInfo getCarrierInfoForImsiEncryption(int keyType,
                                                                      Context context,
-                                                                     String operatorNumeric) {
+                                                                     String operatorNumeric,
+                                                                     boolean fallback,
+                                                                     int subId) {
         String mcc = "";
         String mnc = "";
         if (!TextUtils.isEmpty(operatorNumeric)) {
             mcc = operatorNumeric.substring(0, 3);
             mnc = operatorNumeric.substring(3);
-            Log.i(LOG_TAG, "using values for mnc, mcc: " + mnc + "," + mcc);
+            Log.i(LOG_TAG, "using values for mcc, mnc: " + mcc + "," + mnc);
         } else {
             Log.e(LOG_TAG, "Invalid networkOperator: " + operatorNumeric);
             return null;
@@ -83,7 +94,54 @@
                     new String[]{mcc, mnc, String.valueOf(keyType)}, null);
             if (findCursor == null || !findCursor.moveToFirst()) {
                 Log.d(LOG_TAG, "No rows found for keyType: " + keyType);
-                return null;
+                if (!fallback) {
+                    Log.d(LOG_TAG, "Skipping fallback logic");
+                    return null;
+                }
+                // return carrier config key as fallback
+                CarrierConfigManager carrierConfigManager = (CarrierConfigManager)
+                        context.getSystemService(Context.CARRIER_CONFIG_SERVICE);
+                if (carrierConfigManager == null) {
+                    Log.d(LOG_TAG, "Could not get CarrierConfigManager for backup key");
+                    return null;
+                }
+                if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+                    Log.d(LOG_TAG, "Could not get carrier config with invalid subId");
+                    return null;
+                }
+                PersistableBundle b = carrierConfigManager.getConfigForSubId(subId);
+                if (b == null) {
+                    Log.d(LOG_TAG, "Could not get carrier config bundle for backup key");
+                    return null;
+                }
+                int keyAvailabilityBitmask = b.getInt(
+                        CarrierConfigManager.IMSI_KEY_AVAILABILITY_INT);
+                if (!CarrierKeyDownloadManager.isKeyEnabled(keyType, keyAvailabilityBitmask)) {
+                    Log.d(LOG_TAG, "Backup key does not have matching keyType. keyType=" + keyType
+                            + " keyAvailability=" + keyAvailabilityBitmask);
+                    return null;
+                }
+                String keyString = null;
+                String keyId = null;
+                if (keyType == TelephonyManager.KEY_TYPE_EPDG) {
+                    keyString = b.getString(
+                            CarrierConfigManager.IMSI_CARRIER_PUBLIC_KEY_EPDG_STRING);
+                    keyId = EPDG_BACKUP_KEY_ID;
+                } else if (keyType == TelephonyManager.KEY_TYPE_WLAN) {
+                    keyString = b.getString(
+                            CarrierConfigManager.IMSI_CARRIER_PUBLIC_KEY_WLAN_STRING);
+                    keyId = WLAN_BACKUP_KEY_ID;
+                }
+                if (TextUtils.isEmpty(keyString)) {
+                    Log.d(LOG_TAG,
+                            "Could not get carrier config key string for backup key. keyType="
+                                    + keyType);
+                    return null;
+                }
+                Pair<PublicKey, Long> keyInfo =
+                        CarrierKeyDownloadManager.getKeyInformation(keyString.getBytes());
+                return new ImsiEncryptionInfo(mcc, mnc, keyType, keyId,
+                        keyInfo.first, new Date(keyInfo.second));
             }
             if (findCursor.getCount() > 1) {
                 Log.e(LOG_TAG, "More than 1 row found for the keyType: " + keyType);
diff --git a/src/java/com/android/internal/telephony/CarrierKeyDownloadManager.java b/src/java/com/android/internal/telephony/CarrierKeyDownloadManager.java
index 3e94f4f..2f985af40e 100644
--- a/src/java/com/android/internal/telephony/CarrierKeyDownloadManager.java
+++ b/src/java/com/android/internal/telephony/CarrierKeyDownloadManager.java
@@ -184,7 +184,7 @@
         if (carrierUsesKeys()) {
             if (areCarrierKeysAbsentOrExpiring()) {
                 boolean downloadStartedSuccessfully = downloadKey();
-                // if the download was attemped, but not started successfully, and if carriers uses
+                // if the download was attempted, but not started successfully, and if carriers uses
                 // keys, we'll still want to renew the alarms, and try downloading the key a day
                 // later.
                 if (!downloadStartedSuccessfully) {
@@ -229,7 +229,7 @@
                 continue;
             }
             ImsiEncryptionInfo imsiEncryptionInfo =
-                    mPhone.getCarrierInfoForImsiEncryption(key_type);
+                    mPhone.getCarrierInfoForImsiEncryption(key_type, false);
             if (imsiEncryptionInfo != null && imsiEncryptionInfo.getExpirationTime() != null) {
                 if (minExpirationDate > imsiEncryptionInfo.getExpirationTime().getTime()) {
                     minExpirationDate = imsiEncryptionInfo.getExpirationTime().getTime();
@@ -273,7 +273,7 @@
         PendingIntent carrierKeyDownloadIntent = PendingIntent.getBroadcast(mContext, 0, intent,
                 PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
         alarmManager.set(AlarmManager.RTC_WAKEUP, minExpirationDate, carrierKeyDownloadIntent);
-        Log.d(LOG_TAG, "setRenewelAlarm: action=" + intent.getAction() + " time="
+        Log.d(LOG_TAG, "setRenewalAlarm: action=" + intent.getAction() + " time="
                 + new Date(minExpirationDate));
     }
 
@@ -389,8 +389,10 @@
         mURL = b.getString(CarrierConfigManager.IMSI_KEY_DOWNLOAD_URL_STRING);
         mAllowedOverMeteredNetwork = b.getBoolean(
                 KEY_ALLOW_METERED_NETWORK_FOR_CERT_DOWNLOAD_BOOL);
-        if (TextUtils.isEmpty(mURL) || mKeyAvailability == 0) {
-            Log.d(LOG_TAG, "Carrier not enabled or invalid values");
+        if (mKeyAvailability == 0 || TextUtils.isEmpty(mURL)) {
+            Log.d(LOG_TAG,
+                    "Carrier not enabled or invalid values. mKeyAvailability=" + mKeyAvailability
+                            + " mURL=" + mURL);
             return false;
         }
         for (int key_type : CARRIER_KEY_TYPES) {
@@ -484,8 +486,17 @@
      */
     @VisibleForTesting
     public boolean isKeyEnabled(int keyType) {
-        //since keytype has values of 1, 2.... we need to subtract 1 from the keytype.
-        int returnValue = (mKeyAvailability >> (keyType - 1)) & 1;
+        // since keytype has values of 1, 2.... we need to subtract 1 from the keytype.
+        return isKeyEnabled(keyType, mKeyAvailability);
+    }
+
+    /**
+     * introspects the mKeyAvailability bitmask
+     * @return true if the digit at position k is 1, else false.
+     */
+    public static boolean isKeyEnabled(int keyType, int keyAvailability) {
+        // since keytype has values of 1, 2.... we need to subtract 1 from the keytype.
+        int returnValue = (keyAvailability >> (keyType - 1)) & 1;
         return (returnValue == 1) ? true : false;
     }
 
@@ -500,8 +511,10 @@
             if (!isKeyEnabled(key_type)) {
                 continue;
             }
+            // get encryption info with fallback=false so that we attempt a download even if there's
+            // backup info stored in carrier config
             ImsiEncryptionInfo imsiEncryptionInfo =
-                    mPhone.getCarrierInfoForImsiEncryption(key_type);
+                    mPhone.getCarrierInfoForImsiEncryption(key_type, false);
             if (imsiEncryptionInfo == null) {
                 Log.d(LOG_TAG, "Key not found for: " + key_type);
                 return true;
@@ -533,7 +546,6 @@
             // TODO(b/128550341): Implement the logic to minimize using metered network such as
             // LTE for downloading a certificate.
             request.setAllowedOverMetered(mAllowedOverMeteredNetwork);
-            request.setVisibleInDownloadsUi(false);
             request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_HIDDEN);
             request.addRequestHeader("Accept-Encoding", "gzip");
             Long carrierKeyDownloadRequestId = mDownloadManager.enqueue(request);
@@ -546,7 +558,7 @@
             editor.putString(MCC_MNC_PREF_TAG + slotId, mccMnc);
             editor.commit();
         } catch (Exception e) {
-            Log.e(LOG_TAG, "exception trying to dowload key from url: " + mURL);
+            Log.e(LOG_TAG, "exception trying to download key from url: " + mURL);
             return false;
         }
         return true;
diff --git a/src/java/com/android/internal/telephony/GsmCdmaPhone.java b/src/java/com/android/internal/telephony/GsmCdmaPhone.java
index 1311a41..41a6f6d 100644
--- a/src/java/com/android/internal/telephony/GsmCdmaPhone.java
+++ b/src/java/com/android/internal/telephony/GsmCdmaPhone.java
@@ -1842,11 +1842,11 @@
     }
 
     @Override
-    public ImsiEncryptionInfo getCarrierInfoForImsiEncryption(int keyType) {
+    public ImsiEncryptionInfo getCarrierInfoForImsiEncryption(int keyType, boolean fallback) {
         String operatorNumeric = TelephonyManager.from(mContext)
                 .getSimOperatorNumericForPhone(mPhoneId);
         return CarrierInfoManager.getCarrierInfoForImsiEncryption(keyType,
-                mContext, operatorNumeric);
+                mContext, operatorNumeric, fallback, getSubId());
     }
 
     @Override
diff --git a/src/java/com/android/internal/telephony/Phone.java b/src/java/com/android/internal/telephony/Phone.java
index 44870e8..a5c67e0 100644
--- a/src/java/com/android/internal/telephony/Phone.java
+++ b/src/java/com/android/internal/telephony/Phone.java
@@ -3664,12 +3664,14 @@
     /**
      * Returns Carrier specific information that will be used to encrypt the IMSI and IMPI.
      * @param keyType whether the key is being used for WLAN or ePDG.
+     * @param fallback whether or not to fall back to the encryption key info stored in carrier
+     *                 config
      * @return ImsiEncryptionInfo which includes the Key Type, the Public Key
      *        {@link java.security.PublicKey} and the Key Identifier.
      *        The keyIdentifier This is used by the server to help it locate the private key to
      *        decrypt the permanent identity.
      */
-    public ImsiEncryptionInfo getCarrierInfoForImsiEncryption(int keyType) {
+    public ImsiEncryptionInfo getCarrierInfoForImsiEncryption(int keyType, boolean fallback) {
         return null;
     }
 
diff --git a/src/java/com/android/internal/telephony/PhoneInternalInterface.java b/src/java/com/android/internal/telephony/PhoneInternalInterface.java
index c399098..73a335e 100644
--- a/src/java/com/android/internal/telephony/PhoneInternalInterface.java
+++ b/src/java/com/android/internal/telephony/PhoneInternalInterface.java
@@ -936,12 +936,13 @@
     /**
      * Returns Carrier specific information that will be used to encrypt the IMSI and IMPI.
      * @param keyType whether the key is being used for WLAN or ePDG.
+     * @param fallback whether to fall back to the encryption key stored in carrier config
      * @return ImsiEncryptionInfo which includes the Key Type, the Public Key
      *        {@link java.security.PublicKey} and the Key Identifier.
      *        The keyIdentifier This is used by the server to help it locate the private key to
      *        decrypt the permanent identity.
      */
-    public ImsiEncryptionInfo getCarrierInfoForImsiEncryption(int keyType);
+    ImsiEncryptionInfo getCarrierInfoForImsiEncryption(int keyType, boolean fallback);
 
     /**
      * Resets the Carrier Keys, by deleting them from the database and sending a download intent.
diff --git a/src/java/com/android/internal/telephony/PhoneSubInfoController.java b/src/java/com/android/internal/telephony/PhoneSubInfoController.java
index 398bb64..8211f2d 100644
--- a/src/java/com/android/internal/telephony/PhoneSubInfoController.java
+++ b/src/java/com/android/internal/telephony/PhoneSubInfoController.java
@@ -92,7 +92,7 @@
                                                               String callingPackage) {
         return callPhoneMethodForSubIdWithPrivilegedCheck(subId,
                 "getCarrierInfoForImsiEncryption",
-                (phone)-> phone.getCarrierInfoForImsiEncryption(keyType));
+                (phone)-> phone.getCarrierInfoForImsiEncryption(keyType, true));
     }
 
     public void setCarrierInfoForImsiEncryption(int subId, String callingPackage,
diff --git a/tests/telephonytests/src/com/android/internal/telephony/CarrierKeyDownloadMgrTest.java b/tests/telephonytests/src/com/android/internal/telephony/CarrierKeyDownloadMgrTest.java
index 5a7f688..d6a9f7e 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/CarrierKeyDownloadMgrTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/CarrierKeyDownloadMgrTest.java
@@ -22,6 +22,7 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.anyBoolean;
 import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -104,7 +105,8 @@
         String dateExpected = dt.format(expectedCal.getTime());
         ImsiEncryptionInfo imsiEncryptionInfo = new ImsiEncryptionInfo("mcc", "mnc", 1,
                 "keyIdentifier", publicKey, date);
-        when(mPhone.getCarrierInfoForImsiEncryption(anyInt())).thenReturn(imsiEncryptionInfo);
+        when(mPhone.getCarrierInfoForImsiEncryption(anyInt(), anyBoolean()))
+                .thenReturn(imsiEncryptionInfo);
         Date expirationDate = new Date(mCarrierKeyDM.getExpirationDate());
         assertTrue(dt.format(expirationDate).equals(dateExpected));
     }
@@ -130,7 +132,8 @@
         Date maxExpirationDate = maxExpirationCal.getTime();
         ImsiEncryptionInfo imsiEncryptionInfo = new ImsiEncryptionInfo("mcc", "mnc", 1,
                 "keyIdentifier", publicKey, date);
-        when(mPhone.getCarrierInfoForImsiEncryption(anyInt())).thenReturn(imsiEncryptionInfo);
+        when(mPhone.getCarrierInfoForImsiEncryption(anyInt(), anyBoolean()))
+                .thenReturn(imsiEncryptionInfo);
         Date expirationDate = new Date(mCarrierKeyDM.getExpirationDate());
         assertTrue(expirationDate.before(minExpirationDate));
         assertTrue(expirationDate.after(maxExpirationDate));