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