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