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