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 int mCertificateHashHashCode; 173 private final @Nullable String mPackageName; 174 // This bit is not currently used, but reserved for future use. 175 private final long mAccessType; 176 UiccAccessRule(byte[] certificateHash, @Nullable String packageName, long accessType)177 public UiccAccessRule(byte[] certificateHash, @Nullable String packageName, long accessType) { 178 this.mCertificateHash = certificateHash; 179 this.mCertificateHashHashCode = getCertificateHashHashCode(this.mCertificateHash); 180 this.mPackageName = packageName; 181 this.mAccessType = accessType; 182 } 183 UiccAccessRule(Parcel in)184 UiccAccessRule(Parcel in) { 185 mCertificateHash = in.createByteArray(); 186 mCertificateHashHashCode = getCertificateHashHashCode(mCertificateHash); 187 mPackageName = in.readString(); 188 mAccessType = in.readLong(); 189 } 190 191 @Override writeToParcel(Parcel dest, int flags)192 public void writeToParcel(Parcel dest, int flags) { 193 dest.writeByteArray(mCertificateHash); 194 dest.writeString(mPackageName); 195 dest.writeLong(mAccessType); 196 } 197 198 /** 199 * Return the package name this rule applies to. 200 * 201 * @return the package name, or null if this rule applies to any package signed with the given 202 * certificate. 203 */ getPackageName()204 public @Nullable String getPackageName() { 205 return mPackageName; 206 } 207 208 /** 209 * Returns the hex string of the certificate hash. 210 */ getCertificateHexString()211 public String getCertificateHexString() { 212 return IccUtils.bytesToHexString(mCertificateHash); 213 } 214 215 /** 216 * Returns the carrier privilege status associated with the given package. 217 * 218 * @param packageInfo package info fetched from 219 * {@link android.content.pm.PackageManager#getPackageInfo}. 220 * {@link android.content.pm.PackageManager#GET_SIGNING_CERTIFICATES} must have been 221 * passed in. 222 * @return either {@link TelephonyManager#CARRIER_PRIVILEGE_STATUS_HAS_ACCESS} or 223 * {@link TelephonyManager#CARRIER_PRIVILEGE_STATUS_NO_ACCESS}. 224 */ getCarrierPrivilegeStatus(PackageInfo packageInfo)225 public int getCarrierPrivilegeStatus(PackageInfo packageInfo) { 226 List<Signature> signatures = getSignatures(packageInfo); 227 if (signatures.isEmpty()) { 228 throw new IllegalArgumentException( 229 "Must use GET_SIGNING_CERTIFICATES when looking up package info"); 230 } 231 232 for (Signature sig : signatures) { 233 int accessStatus = getCarrierPrivilegeStatus(sig, packageInfo.packageName); 234 if (accessStatus != TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS) { 235 return accessStatus; 236 } 237 } 238 239 return TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS; 240 } 241 242 /** 243 * Returns the carrier privilege status for the given certificate and package name. 244 * 245 * @param signature The signature of the certificate. 246 * @param packageName name of the package. 247 * @return either {@link TelephonyManager#CARRIER_PRIVILEGE_STATUS_HAS_ACCESS} or 248 * {@link TelephonyManager#CARRIER_PRIVILEGE_STATUS_NO_ACCESS}. 249 */ getCarrierPrivilegeStatus(Signature signature, String packageName)250 public int getCarrierPrivilegeStatus(Signature signature, String packageName) { 251 byte[] certHash256 = getCertHash(signature, "SHA-256"); 252 // Check SHA-256 first as it's the new standard. 253 if (hasMatchingCertificateHashAndPackageName(certHash256, packageName)) { 254 return TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS; 255 } 256 257 // Then check SHA-1 for backward compatibility. This should be removed 258 // in the near future when GPD_SPE_068 fully replaces GPD_SPE_013. 259 if (this.mCertificateHash.length == 20) { 260 byte[] certHash = getCertHash(signature, "SHA-1"); 261 if (hasMatchingCertificateHashAndPackageName(certHash, packageName)) { 262 return TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS; 263 } 264 } 265 266 return TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS; 267 } 268 269 /** 270 * Returns true if the given certificate and package name match this rule's values. 271 * @hide 272 */ hasMatchingCertificateHashAndPackageName( @ullable String certHash, @Nullable String packageName)273 public boolean hasMatchingCertificateHashAndPackageName( 274 @Nullable String certHash, @Nullable String packageName) { 275 return hasMatchingCertificateHashAndPackageName( 276 IccUtils.hexStringToBytes(certHash), packageName); 277 } 278 279 /** 280 * Returns true if the given certificate and package name match this rule's values. 281 * @hide 282 */ hasMatchingCertificateHashAndPackageName( @ullable byte[] certHash, @Nullable String packageName)283 public boolean hasMatchingCertificateHashAndPackageName( 284 @Nullable byte[] certHash, @Nullable String packageName) { 285 return certHash != null && Arrays.equals(this.mCertificateHash, certHash) && 286 (TextUtils.isEmpty(this.mPackageName) || this.mPackageName.equals(packageName)); 287 } 288 289 /** 290 * Returns true if the given certificate hash hash 291 * and package name both match this rules' values. 292 * 293 * @hide 294 */ hasMatchingCertificateHashHashAndPackageName( int certHashHashCode, String packageName)295 public boolean hasMatchingCertificateHashHashAndPackageName( 296 int certHashHashCode, String packageName) { 297 return certHashHashCode == this.mCertificateHashHashCode 298 && (TextUtils.isEmpty(this.mPackageName) || this.mPackageName.equals(packageName)); 299 } 300 301 /** 302 * @hide 303 */ getCertificateHashHashCode(byte[] certHash)304 public static int getCertificateHashHashCode(byte[] certHash) { 305 return Arrays.hashCode(certHash); 306 } 307 308 @Override equals(@ullable Object obj)309 public boolean equals(@Nullable Object obj) { 310 if (this == obj) { 311 return true; 312 } 313 if (obj == null || getClass() != obj.getClass()) { 314 return false; 315 } 316 317 UiccAccessRule that = (UiccAccessRule) obj; 318 return Arrays.equals(mCertificateHash, that.mCertificateHash) 319 && Objects.equals(mPackageName, that.mPackageName) 320 && mAccessType == that.mAccessType; 321 } 322 323 @Override hashCode()324 public int hashCode() { 325 int result = 1; 326 result = 31 * result + Arrays.hashCode(mCertificateHash); 327 result = 31 * result + Objects.hashCode(mPackageName); 328 result = 31 * result + Objects.hashCode(mAccessType); 329 return result; 330 } 331 332 @NonNull 333 @Override toString()334 public String toString() { 335 return "cert: " + IccUtils.bytesToHexString(mCertificateHash) + " pkg: " + 336 mPackageName + " access: " + mAccessType; 337 } 338 339 @Override describeContents()340 public int describeContents() { 341 return 0; 342 } 343 344 /** 345 * Gets all of the Signatures from the given PackageInfo. 346 * @hide 347 */ 348 @NonNull getSignatures(PackageInfo packageInfo)349 public static List<Signature> getSignatures(PackageInfo packageInfo) { 350 Signature[] signatures = packageInfo.signatures; 351 SigningInfo signingInfo = packageInfo.signingInfo; 352 353 if (signingInfo != null) { 354 signatures = signingInfo.getSigningCertificateHistory(); 355 if (signingInfo.hasMultipleSigners()) { 356 signatures = signingInfo.getApkContentsSigners(); 357 } 358 } 359 360 return (signatures == null) ? Collections.EMPTY_LIST : Arrays.asList(signatures); 361 } 362 363 /** 364 * Converts a Signature into a Certificate hash usable for comparison. 365 * @hide 366 */ getCertHash(Signature signature, String algo)367 public static byte[] getCertHash(Signature signature, String algo) { 368 try { 369 MessageDigest md = MessageDigest.getInstance(algo); 370 return md.digest(signature.toByteArray()); 371 } catch (NoSuchAlgorithmException ex) { 372 Rlog.e(TAG, "NoSuchAlgorithmException: " + ex); 373 } 374 return null; 375 } 376 } 377