• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.ArrayList;
39 import java.util.Arrays;
40 import java.util.Collections;
41 import java.util.List;
42 import java.util.Objects;
43 
44 /**
45  * Describes a single UICC access rule according to the GlobalPlatform Secure Element Access Control
46  * specification.
47  *
48  * @hide
49  */
50 @SystemApi
51 public final class UiccAccessRule implements Parcelable {
52     private static final String TAG = "UiccAccessRule";
53 
54     private static final int ENCODING_VERSION = 1;
55 
56     /**
57      * Delimiter used to decode {@link CarrierConfigManager#KEY_CARRIER_CERTIFICATE_STRING_ARRAY}.
58      */
59     private static final String DELIMITER_CERTIFICATE_HASH_PACKAGE_NAMES = ":";
60 
61     /**
62      * Delimiter used to decode {@link CarrierConfigManager#KEY_CARRIER_CERTIFICATE_STRING_ARRAY}.
63      */
64     private static final String DELIMITER_INDIVIDUAL_PACKAGE_NAMES = ",";
65 
66     public static final @android.annotation.NonNull Creator<UiccAccessRule> CREATOR = new Creator<UiccAccessRule>() {
67         @Override
68         public UiccAccessRule createFromParcel(Parcel in) {
69             return new UiccAccessRule(in);
70         }
71 
72         @Override
73         public UiccAccessRule[] newArray(int size) {
74             return new UiccAccessRule[size];
75         }
76     };
77 
78     /**
79      * Encode these access rules as a byte array which can be parsed with {@link #decodeRules}.
80      * @hide
81      */
82     @Nullable
encodeRules(@ullable UiccAccessRule[] accessRules)83     public static byte[] encodeRules(@Nullable UiccAccessRule[] accessRules) {
84         if (accessRules == null) {
85             return null;
86         }
87         try {
88             ByteArrayOutputStream baos = new ByteArrayOutputStream();
89             DataOutputStream output = new DataOutputStream(baos);
90             output.writeInt(ENCODING_VERSION);
91             output.writeInt(accessRules.length);
92             for (UiccAccessRule accessRule : accessRules) {
93                 output.writeInt(accessRule.mCertificateHash.length);
94                 output.write(accessRule.mCertificateHash);
95                 if (accessRule.mPackageName != null) {
96                     output.writeBoolean(true);
97                     output.writeUTF(accessRule.mPackageName);
98                 } else {
99                     output.writeBoolean(false);
100                 }
101                 output.writeLong(accessRule.mAccessType);
102             }
103             output.close();
104             return baos.toByteArray();
105         } catch (IOException e) {
106             throw new IllegalStateException(
107                     "ByteArrayOutputStream should never lead to an IOException", e);
108         }
109     }
110 
111     /**
112      * Decodes {@link CarrierConfigManager#KEY_CARRIER_CERTIFICATE_STRING_ARRAY} values.
113      * @hide
114      */
115     @Nullable
decodeRulesFromCarrierConfig(@ullable String[] certs)116     public static UiccAccessRule[] decodeRulesFromCarrierConfig(@Nullable String[] certs) {
117         if (certs == null) {
118             return null;
119         }
120         List<UiccAccessRule> carrierConfigAccessRulesArray = new ArrayList();
121         for (String cert : certs) {
122             String[] splitStr = cert.split(DELIMITER_CERTIFICATE_HASH_PACKAGE_NAMES);
123             byte[] certificateHash = IccUtils.hexStringToBytes(splitStr[0]);
124             if (splitStr.length == 1) {
125                 // The value is a certificate hash, without any package name
126                 carrierConfigAccessRulesArray.add(new UiccAccessRule(certificateHash, null, 0));
127             } else {
128                 // The value is composed of the certificate hash followed by at least one
129                 // package name
130                 String[] packageNames = splitStr[1].split(DELIMITER_INDIVIDUAL_PACKAGE_NAMES);
131                 for (String packageName : packageNames) {
132                     carrierConfigAccessRulesArray.add(
133                             new UiccAccessRule(certificateHash, packageName, 0));
134                 }
135             }
136         }
137         return carrierConfigAccessRulesArray.toArray(
138             new UiccAccessRule[carrierConfigAccessRulesArray.size()]);
139     }
140 
141     /**
142      * Decodes a byte array generated with {@link #encodeRules}.
143      * @hide
144      */
145     @Nullable
decodeRules(@ullable byte[] encodedRules)146     public static UiccAccessRule[] decodeRules(@Nullable byte[] encodedRules) {
147         if (encodedRules == null) {
148             return null;
149         }
150         ByteArrayInputStream bais = new ByteArrayInputStream(encodedRules);
151         try (DataInputStream input = new DataInputStream(bais)) {
152             input.readInt(); // version; currently ignored
153             int count = input.readInt();
154             UiccAccessRule[] accessRules = new UiccAccessRule[count];
155             for (int i = 0; i < count; i++) {
156                 int certificateHashLength = input.readInt();
157                 byte[] certificateHash = new byte[certificateHashLength];
158                 input.readFully(certificateHash);
159                 String packageName = input.readBoolean() ? input.readUTF() : null;
160                 long accessType = input.readLong();
161                 accessRules[i] = new UiccAccessRule(certificateHash, packageName, accessType);
162             }
163             input.close();
164             return accessRules;
165         } catch (IOException e) {
166             throw new IllegalStateException(
167                     "ByteArrayInputStream should never lead to an IOException", e);
168         }
169     }
170 
171     private final byte[] mCertificateHash;
172     private final @Nullable String mPackageName;
173     // This bit is not currently used, but reserved for future use.
174     private final long mAccessType;
175 
UiccAccessRule(byte[] certificateHash, @Nullable String packageName, long accessType)176     public UiccAccessRule(byte[] certificateHash, @Nullable String packageName, long accessType) {
177         this.mCertificateHash = certificateHash;
178         this.mPackageName = packageName;
179         this.mAccessType = accessType;
180     }
181 
UiccAccessRule(Parcel in)182     UiccAccessRule(Parcel in) {
183         mCertificateHash = in.createByteArray();
184         mPackageName = in.readString();
185         mAccessType = in.readLong();
186     }
187 
188     @Override
writeToParcel(Parcel dest, int flags)189     public void writeToParcel(Parcel dest, int flags) {
190         dest.writeByteArray(mCertificateHash);
191         dest.writeString(mPackageName);
192         dest.writeLong(mAccessType);
193     }
194 
195     /**
196      * Return the package name this rule applies to.
197      *
198      * @return the package name, or null if this rule applies to any package signed with the given
199      *     certificate.
200      */
getPackageName()201     public @Nullable String getPackageName() {
202         return mPackageName;
203     }
204 
205     /**
206      * Returns the hex string of the certificate hash.
207      */
getCertificateHexString()208     public String getCertificateHexString() {
209         return IccUtils.bytesToHexString(mCertificateHash);
210     }
211 
212     /**
213      * Returns the carrier privilege status associated with the given package.
214      *
215      * @param packageInfo package info fetched from
216      *     {@link android.content.pm.PackageManager#getPackageInfo}.
217      *     {@link android.content.pm.PackageManager#GET_SIGNING_CERTIFICATES} must have been
218      *         passed in.
219      * @return either {@link TelephonyManager#CARRIER_PRIVILEGE_STATUS_HAS_ACCESS} or
220      *     {@link TelephonyManager#CARRIER_PRIVILEGE_STATUS_NO_ACCESS}.
221      */
getCarrierPrivilegeStatus(PackageInfo packageInfo)222     public int getCarrierPrivilegeStatus(PackageInfo packageInfo) {
223         List<Signature> signatures = getSignatures(packageInfo);
224         if (signatures.isEmpty()) {
225             throw new IllegalArgumentException(
226                     "Must use GET_SIGNING_CERTIFICATES when looking up package info");
227         }
228 
229         for (Signature sig : signatures) {
230             int accessStatus = getCarrierPrivilegeStatus(sig, packageInfo.packageName);
231             if (accessStatus != TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS) {
232                 return accessStatus;
233             }
234         }
235 
236         return TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS;
237     }
238 
239     /**
240      * Returns the carrier privilege status for the given certificate and package name.
241      *
242      * @param signature The signature of the certificate.
243      * @param packageName name of the package.
244      * @return either {@link TelephonyManager#CARRIER_PRIVILEGE_STATUS_HAS_ACCESS} or
245      *     {@link TelephonyManager#CARRIER_PRIVILEGE_STATUS_NO_ACCESS}.
246      */
getCarrierPrivilegeStatus(Signature signature, String packageName)247     public int getCarrierPrivilegeStatus(Signature signature, String packageName) {
248         byte[] certHash256 = getCertHash(signature, "SHA-256");
249         // Check SHA-256 first as it's the new standard.
250         if (matches(certHash256, packageName)) {
251             return TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS;
252         }
253 
254         // Then check SHA-1 for backward compatibility. This should be removed
255         // in the near future when GPD_SPE_068 fully replaces GPD_SPE_013.
256         if (this.mCertificateHash.length == 20) {
257             byte[] certHash = getCertHash(signature, "SHA-1");
258             if (matches(certHash, packageName)) {
259                 return TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS;
260             }
261         }
262 
263         return TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS;
264     }
265 
266     /**
267      * Returns true if the given certificate and package name match this rule's values.
268      * @hide
269      */
matches(@ullable String certHash, @Nullable String packageName)270     public boolean matches(@Nullable String certHash, @Nullable String packageName) {
271         return matches(IccUtils.hexStringToBytes(certHash), packageName);
272     }
273 
matches(byte[] certHash, String packageName)274     private boolean matches(byte[] certHash, String packageName) {
275         return certHash != null && Arrays.equals(this.mCertificateHash, certHash) &&
276                 (TextUtils.isEmpty(this.mPackageName) || this.mPackageName.equals(packageName));
277     }
278 
279     @Override
equals(@ullable Object obj)280     public boolean equals(@Nullable Object obj) {
281         if (this == obj) {
282             return true;
283         }
284         if (obj == null || getClass() != obj.getClass()) {
285             return false;
286         }
287 
288         UiccAccessRule that = (UiccAccessRule) obj;
289         return Arrays.equals(mCertificateHash, that.mCertificateHash)
290                 && Objects.equals(mPackageName, that.mPackageName)
291                 && mAccessType == that.mAccessType;
292     }
293 
294     @Override
hashCode()295     public int hashCode() {
296         int result = 1;
297         result = 31 * result + Arrays.hashCode(mCertificateHash);
298         result = 31 * result + Objects.hashCode(mPackageName);
299         result = 31 * result + Objects.hashCode(mAccessType);
300         return result;
301     }
302 
303     @NonNull
304     @Override
toString()305     public String toString() {
306         return "cert: " + IccUtils.bytesToHexString(mCertificateHash) + " pkg: " +
307                 mPackageName + " access: " + mAccessType;
308     }
309 
310     @Override
describeContents()311     public int describeContents() {
312         return 0;
313     }
314 
315     /**
316      * Gets all of the Signatures from the given PackageInfo.
317      * @hide
318      */
319     @NonNull
getSignatures(PackageInfo packageInfo)320     public static List<Signature> getSignatures(PackageInfo packageInfo) {
321         Signature[] signatures = packageInfo.signatures;
322         SigningInfo signingInfo = packageInfo.signingInfo;
323 
324         if (signingInfo != null) {
325             signatures = signingInfo.getSigningCertificateHistory();
326             if (signingInfo.hasMultipleSigners()) {
327                 signatures = signingInfo.getApkContentsSigners();
328             }
329         }
330 
331         return (signatures == null) ? Collections.EMPTY_LIST : Arrays.asList(signatures);
332     }
333 
334     /**
335      * Converts a Signature into a Certificate hash usable for comparison.
336      * @hide
337      */
getCertHash(Signature signature, String algo)338     public static byte[] getCertHash(Signature signature, String algo) {
339         try {
340             MessageDigest md = MessageDigest.getInstance(algo);
341             return md.digest(signature.toByteArray());
342         } catch (NoSuchAlgorithmException ex) {
343             Rlog.e(TAG, "NoSuchAlgorithmException: " + ex);
344         }
345         return null;
346     }
347 }
348