blob: 12bb36647f8f747ce9300f2766d7354e1e12bd16 [file] [log] [blame]
Jeff Davidson35cda392017-02-27 09:46:00 -08001/*
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 */
16package android.telephony;
17
Aurimas Liutikas00be9512019-08-28 13:01:05 -070018import android.annotation.NonNull;
Jeff Davidson35cda392017-02-27 09:46:00 -080019import android.annotation.Nullable;
Holly Jiuyu Sun4f73b9c2017-12-12 20:17:09 -080020import android.annotation.SystemApi;
Jeff Davidson35cda392017-02-27 09:46:00 -080021import android.content.pm.PackageInfo;
22import android.content.pm.Signature;
Cheonho Park51586d52019-07-31 14:19:41 +090023import android.content.pm.SigningInfo;
Jeff Davidson35cda392017-02-27 09:46:00 -080024import android.os.Parcel;
25import android.os.Parcelable;
26import android.text.TextUtils;
27
28import com.android.internal.telephony.uicc.IccUtils;
Meng Wang1dbea2f2020-01-30 12:25:08 -080029import com.android.telephony.Rlog;
Jeff Davidson35cda392017-02-27 09:46:00 -080030
31import java.io.ByteArrayInputStream;
32import java.io.ByteArrayOutputStream;
33import java.io.DataInputStream;
34import java.io.DataOutputStream;
35import java.io.IOException;
36import java.security.MessageDigest;
37import java.security.NoSuchAlgorithmException;
38import java.util.Arrays;
Cody Kestinga0104452020-03-19 13:07:19 -070039import java.util.Collections;
40import java.util.List;
Holly Jiuyu Sune6153b92017-12-07 15:35:49 -080041import java.util.Objects;
Jeff Davidson35cda392017-02-27 09:46:00 -080042
43/**
44 * Describes a single UICC access rule according to the GlobalPlatform Secure Element Access Control
45 * specification.
46 *
47 * @hide
Jeff Davidson35cda392017-02-27 09:46:00 -080048 */
Holly Jiuyu Sun4f73b9c2017-12-12 20:17:09 -080049@SystemApi
Jeff Davidson35cda392017-02-27 09:46:00 -080050public final class UiccAccessRule implements Parcelable {
51 private static final String TAG = "UiccAccessRule";
52
53 private static final int ENCODING_VERSION = 1;
54
Jeff Sharkey9e8f83d2019-02-28 12:06:45 -070055 public static final @android.annotation.NonNull Creator<UiccAccessRule> CREATOR = new Creator<UiccAccessRule>() {
Jeff Davidson35cda392017-02-27 09:46:00 -080056 @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 */
Jeff Davidsond02731f2017-04-09 14:31:09 -070071 @Nullable
72 public static byte[] encodeRules(@Nullable UiccAccessRule[] accessRules) {
73 if (accessRules == null) {
74 return null;
75 }
Jeff Davidson35cda392017-02-27 09:46:00 -080076 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);
Jeff Davidsond02731f2017-04-09 14:31:09 -070084 if (accessRule.mPackageName != null) {
85 output.writeBoolean(true);
86 output.writeUTF(accessRule.mPackageName);
87 } else {
88 output.writeBoolean(false);
89 }
Jeff Davidson35cda392017-02-27 09:46:00 -080090 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 */
Jeff Davidsond02731f2017-04-09 14:31:09 -0700104 @Nullable
105 public static UiccAccessRule[] decodeRules(@Nullable byte[] encodedRules) {
106 if (encodedRules == null) {
107 return null;
108 }
Jeff Davidson35cda392017-02-27 09:46:00 -0800109 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);
Jeff Davidsond02731f2017-04-09 14:31:09 -0700118 String packageName = input.readBoolean() ? input.readUTF() : null;
Jeff Davidson35cda392017-02-27 09:46:00 -0800119 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
135 public UiccAccessRule(byte[] certificateHash, @Nullable String packageName, long accessType) {
136 this.mCertificateHash = certificateHash;
137 this.mPackageName = packageName;
138 this.mAccessType = accessType;
139 }
140
141 UiccAccessRule(Parcel in) {
142 mCertificateHash = in.createByteArray();
143 mPackageName = in.readString();
144 mAccessType = in.readLong();
145 }
146
147 @Override
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 */
160 public @Nullable String getPackageName() {
161 return mPackageName;
162 }
163
164 /**
goneil6db18a9b2018-01-10 16:23:11 -0800165 * Returns the hex string of the certificate hash.
166 */
167 public String getCertificateHexString() {
168 return IccUtils.bytesToHexString(mCertificateHash);
169 }
170
171 /**
Jeff Davidson35cda392017-02-27 09:46:00 -0800172 * 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}.
Cheonho Park51586d52019-07-31 14:19:41 +0900176 * {@link android.content.pm.PackageManager#GET_SIGNING_CERTIFICATES} must have been
177 * passed in.
Jeff Davidson35cda392017-02-27 09:46:00 -0800178 * @return either {@link TelephonyManager#CARRIER_PRIVILEGE_STATUS_HAS_ACCESS} or
179 * {@link TelephonyManager#CARRIER_PRIVILEGE_STATUS_NO_ACCESS}.
180 */
181 public int getCarrierPrivilegeStatus(PackageInfo packageInfo) {
Cody Kestinga0104452020-03-19 13:07:19 -0700182 List<Signature> signatures = getSignatures(packageInfo);
183 if (signatures.isEmpty()) {
Cheonho Park51586d52019-07-31 14:19:41 +0900184 throw new IllegalArgumentException(
185 "Must use GET_SIGNING_CERTIFICATES when looking up package info");
186 }
187
188 for (Signature sig : signatures) {
Jeff Davidson35cda392017-02-27 09:46:00 -0800189 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 */
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
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
Aurimas Liutikas00be9512019-08-28 13:01:05 -0700223 public boolean equals(@Nullable Object obj) {
Holly Jiuyu Sune6153b92017-12-07 15:35:49 -0800224 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
Holly Jiuyu Suneafc9522018-02-22 15:47:27 -0800238 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
Aurimas Liutikas00be9512019-08-28 13:01:05 -0700246 @NonNull
Holly Jiuyu Suneafc9522018-02-22 15:47:27 -0800247 @Override
Jeff Davidson35cda392017-02-27 09:46:00 -0800248 public String toString() {
249 return "cert: " + IccUtils.bytesToHexString(mCertificateHash) + " pkg: " +
250 mPackageName + " access: " + mAccessType;
251 }
252
253 @Override
254 public int describeContents() {
255 return 0;
256 }
257
258 /**
Cody Kestinga0104452020-03-19 13:07:19 -0700259 * Gets all of the Signatures from the given PackageInfo.
260 * @hide
Jeff Davidson35cda392017-02-27 09:46:00 -0800261 */
Cody Kestinga0104452020-03-19 13:07:19 -0700262 @NonNull
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 */
281 public static byte[] getCertHash(Signature signature, String algo) {
Jeff Davidson35cda392017-02-27 09:46:00 -0800282 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}