Home | History | Annotate | Download | only in telephony
      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 package android.telephony;
     17 
     18 import android.annotation.Nullable;
     19 import android.annotation.SystemApi;
     20 import android.content.pm.PackageInfo;
     21 import android.content.pm.Signature;
     22 import android.os.Parcel;
     23 import android.os.Parcelable;
     24 import android.text.TextUtils;
     25 
     26 import com.android.internal.telephony.uicc.IccUtils;
     27 
     28 import java.io.ByteArrayInputStream;
     29 import java.io.ByteArrayOutputStream;
     30 import java.io.DataInputStream;
     31 import java.io.DataOutputStream;
     32 import java.io.IOException;
     33 import java.security.MessageDigest;
     34 import java.security.NoSuchAlgorithmException;
     35 import java.util.Arrays;
     36 import java.util.Objects;
     37 
     38 /**
     39  * Describes a single UICC access rule according to the GlobalPlatform Secure Element Access Control
     40  * specification.
     41  *
     42  * @hide
     43  */
     44 @SystemApi
     45 public final class UiccAccessRule implements Parcelable {
     46     private static final String TAG = "UiccAccessRule";
     47 
     48     private static final int ENCODING_VERSION = 1;
     49 
     50     public static final Creator<UiccAccessRule> CREATOR = new Creator<UiccAccessRule>() {
     51         @Override
     52         public UiccAccessRule createFromParcel(Parcel in) {
     53             return new UiccAccessRule(in);
     54         }
     55 
     56         @Override
     57         public UiccAccessRule[] newArray(int size) {
     58             return new UiccAccessRule[size];
     59         }
     60     };
     61 
     62     /**
     63      * Encode these access rules as a byte array which can be parsed with {@link #decodeRules}.
     64      * @hide
     65      */
     66     @Nullable
     67     public static byte[] encodeRules(@Nullable UiccAccessRule[] accessRules) {
     68         if (accessRules == null) {
     69             return null;
     70         }
     71         try {
     72             ByteArrayOutputStream baos = new ByteArrayOutputStream();
     73             DataOutputStream output = new DataOutputStream(baos);
     74             output.writeInt(ENCODING_VERSION);
     75             output.writeInt(accessRules.length);
     76             for (UiccAccessRule accessRule : accessRules) {
     77                 output.writeInt(accessRule.mCertificateHash.length);
     78                 output.write(accessRule.mCertificateHash);
     79                 if (accessRule.mPackageName != null) {
     80                     output.writeBoolean(true);
     81                     output.writeUTF(accessRule.mPackageName);
     82                 } else {
     83                     output.writeBoolean(false);
     84                 }
     85                 output.writeLong(accessRule.mAccessType);
     86             }
     87             output.close();
     88             return baos.toByteArray();
     89         } catch (IOException e) {
     90             throw new IllegalStateException(
     91                     "ByteArrayOutputStream should never lead to an IOException", e);
     92         }
     93     }
     94 
     95     /**
     96      * Decodes a byte array generated with {@link #encodeRules}.
     97      * @hide
     98      */
     99     @Nullable
    100     public static UiccAccessRule[] decodeRules(@Nullable byte[] encodedRules) {
    101         if (encodedRules == null) {
    102             return null;
    103         }
    104         ByteArrayInputStream bais = new ByteArrayInputStream(encodedRules);
    105         try (DataInputStream input = new DataInputStream(bais)) {
    106             input.readInt(); // version; currently ignored
    107             int count = input.readInt();
    108             UiccAccessRule[] accessRules = new UiccAccessRule[count];
    109             for (int i = 0; i < count; i++) {
    110                 int certificateHashLength = input.readInt();
    111                 byte[] certificateHash = new byte[certificateHashLength];
    112                 input.readFully(certificateHash);
    113                 String packageName = input.readBoolean() ? input.readUTF() : null;
    114                 long accessType = input.readLong();
    115                 accessRules[i] = new UiccAccessRule(certificateHash, packageName, accessType);
    116             }
    117             input.close();
    118             return accessRules;
    119         } catch (IOException e) {
    120             throw new IllegalStateException(
    121                     "ByteArrayInputStream should never lead to an IOException", e);
    122         }
    123     }
    124 
    125     private final byte[] mCertificateHash;
    126     private final @Nullable String mPackageName;
    127     // This bit is not currently used, but reserved for future use.
    128     private final long mAccessType;
    129 
    130     public UiccAccessRule(byte[] certificateHash, @Nullable String packageName, long accessType) {
    131         this.mCertificateHash = certificateHash;
    132         this.mPackageName = packageName;
    133         this.mAccessType = accessType;
    134     }
    135 
    136     UiccAccessRule(Parcel in) {
    137         mCertificateHash = in.createByteArray();
    138         mPackageName = in.readString();
    139         mAccessType = in.readLong();
    140     }
    141 
    142     @Override
    143     public void writeToParcel(Parcel dest, int flags) {
    144         dest.writeByteArray(mCertificateHash);
    145         dest.writeString(mPackageName);
    146         dest.writeLong(mAccessType);
    147     }
    148 
    149     /**
    150      * Return the package name this rule applies to.
    151      *
    152      * @return the package name, or null if this rule applies to any package signed with the given
    153      *     certificate.
    154      */
    155     public @Nullable String getPackageName() {
    156         return mPackageName;
    157     }
    158 
    159     /**
    160      * Returns the hex string of the certificate hash.
    161      */
    162     public String getCertificateHexString() {
    163         return IccUtils.bytesToHexString(mCertificateHash);
    164     }
    165 
    166     /**
    167      * Returns the carrier privilege status associated with the given package.
    168      *
    169      * @param packageInfo package info fetched from
    170      *     {@link android.content.pm.PackageManager#getPackageInfo}.
    171      *     {@link android.content.pm.PackageManager#GET_SIGNATURES} must have been passed in.
    172      * @return either {@link TelephonyManager#CARRIER_PRIVILEGE_STATUS_HAS_ACCESS} or
    173      *     {@link TelephonyManager#CARRIER_PRIVILEGE_STATUS_NO_ACCESS}.
    174      */
    175     public int getCarrierPrivilegeStatus(PackageInfo packageInfo) {
    176         if (packageInfo.signatures == null || packageInfo.signatures.length == 0) {
    177             throw new IllegalArgumentException(
    178                     "Must use GET_SIGNATURES when looking up package info");
    179         }
    180 
    181         for (Signature sig : packageInfo.signatures) {
    182             int accessStatus = getCarrierPrivilegeStatus(sig, packageInfo.packageName);
    183             if (accessStatus != TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS) {
    184                 return accessStatus;
    185             }
    186         }
    187 
    188         return TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS;
    189     }
    190 
    191     /**
    192      * Returns the carrier privilege status for the given certificate and package name.
    193      *
    194      * @param signature The signature of the certificate.
    195      * @param packageName name of the package.
    196      * @return either {@link TelephonyManager#CARRIER_PRIVILEGE_STATUS_HAS_ACCESS} or
    197      *     {@link TelephonyManager#CARRIER_PRIVILEGE_STATUS_NO_ACCESS}.
    198      */
    199     public int getCarrierPrivilegeStatus(Signature signature, String packageName) {
    200         // SHA-1 is for backward compatible support only, strongly discouraged for new use.
    201         byte[] certHash = getCertHash(signature, "SHA-1");
    202         byte[] certHash256 = getCertHash(signature, "SHA-256");
    203         if (matches(certHash, packageName) || matches(certHash256, packageName)) {
    204             return TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS;
    205         }
    206 
    207         return TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS;
    208     }
    209 
    210     private boolean matches(byte[] certHash, String packageName) {
    211         return certHash != null && Arrays.equals(this.mCertificateHash, certHash) &&
    212                 (TextUtils.isEmpty(this.mPackageName) || this.mPackageName.equals(packageName));
    213     }
    214 
    215     @Override
    216     public boolean equals(Object obj) {
    217         if (this == obj) {
    218             return true;
    219         }
    220         if (obj == null || getClass() != obj.getClass()) {
    221             return false;
    222         }
    223 
    224         UiccAccessRule that = (UiccAccessRule) obj;
    225         return Arrays.equals(mCertificateHash, that.mCertificateHash)
    226                 && Objects.equals(mPackageName, that.mPackageName)
    227                 && mAccessType == that.mAccessType;
    228     }
    229 
    230     @Override
    231     public int hashCode() {
    232         int result = 1;
    233         result = 31 * result + Arrays.hashCode(mCertificateHash);
    234         result = 31 * result + Objects.hashCode(mPackageName);
    235         result = 31 * result + Objects.hashCode(mAccessType);
    236         return result;
    237     }
    238 
    239     @Override
    240     public String toString() {
    241         return "cert: " + IccUtils.bytesToHexString(mCertificateHash) + " pkg: " +
    242                 mPackageName + " access: " + mAccessType;
    243     }
    244 
    245     @Override
    246     public int describeContents() {
    247         return 0;
    248     }
    249 
    250     /**
    251      * Converts a Signature into a Certificate hash usable for comparison.
    252      */
    253     private static byte[] getCertHash(Signature signature, String algo) {
    254         try {
    255             MessageDigest md = MessageDigest.getInstance(algo);
    256             return md.digest(signature.toByteArray());
    257         } catch (NoSuchAlgorithmException ex) {
    258             Rlog.e(TAG, "NoSuchAlgorithmException: " + ex);
    259         }
    260         return null;
    261     }
    262 }
    263