Two new APIs will list all embedded subscriptions along with any active ones. One API requires the ability to read phone state and returns all subscriptions. The other requires no special permissions but will only return those subscriptions which the caller may manage according to their metadata. A list result from the LPA includes whether the current eUICC is removable. If true, subscriptions in the list are considered transient and always removed upon the next list update (i.e. SIM card state change) unless that update includes the subscription. Otherwise, they will be retained across future list operations for which the current eUICC is removable. This allows callers to retain knowledge about available embedded subscriptions on an inactive but still accessible eUICC, as long as that eUICC is permanent. The LPA may request a refresh of the list at any time; this is intended to be used of the list or metadata is updated through a non-API operation, e.g. a server-initiated metadata update. For operations driven through a platform API, the list will be refreshed automatically. Bug: 33075886 Test: TreeHugger Change-Id: I1887cbca835c304b9eb285fd972c7c8eaffa6e1d
231 lines
8.4 KiB
Java
231 lines
8.4 KiB
Java
/*
|
|
* Copyright (C) 2017 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
package android.telephony;
|
|
|
|
import android.annotation.Nullable;
|
|
import android.content.pm.PackageInfo;
|
|
import android.content.pm.Signature;
|
|
import android.os.Parcel;
|
|
import android.os.Parcelable;
|
|
import android.text.TextUtils;
|
|
|
|
import com.android.internal.telephony.uicc.IccUtils;
|
|
|
|
import java.io.ByteArrayInputStream;
|
|
import java.io.ByteArrayOutputStream;
|
|
import java.io.DataInputStream;
|
|
import java.io.DataOutputStream;
|
|
import java.io.IOException;
|
|
import java.security.MessageDigest;
|
|
import java.security.NoSuchAlgorithmException;
|
|
import java.util.Arrays;
|
|
|
|
/**
|
|
* Describes a single UICC access rule according to the GlobalPlatform Secure Element Access Control
|
|
* specification.
|
|
*
|
|
* @hide
|
|
*
|
|
* TODO(b/35851809): Make this a SystemApi.
|
|
*/
|
|
public final class UiccAccessRule implements Parcelable {
|
|
private static final String TAG = "UiccAccessRule";
|
|
|
|
private static final int ENCODING_VERSION = 1;
|
|
|
|
public static final Creator<UiccAccessRule> CREATOR = new Creator<UiccAccessRule>() {
|
|
@Override
|
|
public UiccAccessRule createFromParcel(Parcel in) {
|
|
return new UiccAccessRule(in);
|
|
}
|
|
|
|
@Override
|
|
public UiccAccessRule[] newArray(int size) {
|
|
return new UiccAccessRule[size];
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Encode these access rules as a byte array which can be parsed with {@link #decodeRules}.
|
|
* @hide
|
|
*/
|
|
@Nullable
|
|
public static byte[] encodeRules(@Nullable UiccAccessRule[] accessRules) {
|
|
if (accessRules == null) {
|
|
return null;
|
|
}
|
|
try {
|
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
|
DataOutputStream output = new DataOutputStream(baos);
|
|
output.writeInt(ENCODING_VERSION);
|
|
output.writeInt(accessRules.length);
|
|
for (UiccAccessRule accessRule : accessRules) {
|
|
output.writeInt(accessRule.mCertificateHash.length);
|
|
output.write(accessRule.mCertificateHash);
|
|
if (accessRule.mPackageName != null) {
|
|
output.writeBoolean(true);
|
|
output.writeUTF(accessRule.mPackageName);
|
|
} else {
|
|
output.writeBoolean(false);
|
|
}
|
|
output.writeLong(accessRule.mAccessType);
|
|
}
|
|
output.close();
|
|
return baos.toByteArray();
|
|
} catch (IOException e) {
|
|
throw new IllegalStateException(
|
|
"ByteArrayOutputStream should never lead to an IOException", e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Decodes a byte array generated with {@link #encodeRules}.
|
|
* @hide
|
|
*/
|
|
@Nullable
|
|
public static UiccAccessRule[] decodeRules(@Nullable byte[] encodedRules) {
|
|
if (encodedRules == null) {
|
|
return null;
|
|
}
|
|
ByteArrayInputStream bais = new ByteArrayInputStream(encodedRules);
|
|
try (DataInputStream input = new DataInputStream(bais)) {
|
|
input.readInt(); // version; currently ignored
|
|
int count = input.readInt();
|
|
UiccAccessRule[] accessRules = new UiccAccessRule[count];
|
|
for (int i = 0; i < count; i++) {
|
|
int certificateHashLength = input.readInt();
|
|
byte[] certificateHash = new byte[certificateHashLength];
|
|
input.readFully(certificateHash);
|
|
String packageName = input.readBoolean() ? input.readUTF() : null;
|
|
long accessType = input.readLong();
|
|
accessRules[i] = new UiccAccessRule(certificateHash, packageName, accessType);
|
|
}
|
|
input.close();
|
|
return accessRules;
|
|
} catch (IOException e) {
|
|
throw new IllegalStateException(
|
|
"ByteArrayInputStream should never lead to an IOException", e);
|
|
}
|
|
}
|
|
|
|
private final byte[] mCertificateHash;
|
|
private final @Nullable String mPackageName;
|
|
// This bit is not currently used, but reserved for future use.
|
|
private final long mAccessType;
|
|
|
|
public UiccAccessRule(byte[] certificateHash, @Nullable String packageName, long accessType) {
|
|
this.mCertificateHash = certificateHash;
|
|
this.mPackageName = packageName;
|
|
this.mAccessType = accessType;
|
|
}
|
|
|
|
UiccAccessRule(Parcel in) {
|
|
mCertificateHash = in.createByteArray();
|
|
mPackageName = in.readString();
|
|
mAccessType = in.readLong();
|
|
}
|
|
|
|
@Override
|
|
public void writeToParcel(Parcel dest, int flags) {
|
|
dest.writeByteArray(mCertificateHash);
|
|
dest.writeString(mPackageName);
|
|
dest.writeLong(mAccessType);
|
|
}
|
|
|
|
/**
|
|
* Return the package name this rule applies to.
|
|
*
|
|
* @return the package name, or null if this rule applies to any package signed with the given
|
|
* certificate.
|
|
*/
|
|
public @Nullable String getPackageName() {
|
|
return mPackageName;
|
|
}
|
|
|
|
/**
|
|
* Returns the carrier privilege status associated with the given package.
|
|
*
|
|
* @param packageInfo package info fetched from
|
|
* {@link android.content.pm.PackageManager#getPackageInfo}.
|
|
* {@link android.content.pm.PackageManager#GET_SIGNATURES} must have been passed in.
|
|
* @return either {@link TelephonyManager#CARRIER_PRIVILEGE_STATUS_HAS_ACCESS} or
|
|
* {@link TelephonyManager#CARRIER_PRIVILEGE_STATUS_NO_ACCESS}.
|
|
*/
|
|
public int getCarrierPrivilegeStatus(PackageInfo packageInfo) {
|
|
if (packageInfo.signatures == null || packageInfo.signatures.length == 0) {
|
|
throw new IllegalArgumentException(
|
|
"Must use GET_SIGNATURES when looking up package info");
|
|
}
|
|
|
|
for (Signature sig : packageInfo.signatures) {
|
|
int accessStatus = getCarrierPrivilegeStatus(sig, packageInfo.packageName);
|
|
if (accessStatus != TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS) {
|
|
return accessStatus;
|
|
}
|
|
}
|
|
|
|
return TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS;
|
|
}
|
|
|
|
/**
|
|
* Returns the carrier privilege status for the given certificate and package name.
|
|
*
|
|
* @param signature The signature of the certificate.
|
|
* @param packageName name of the package.
|
|
* @return either {@link TelephonyManager#CARRIER_PRIVILEGE_STATUS_HAS_ACCESS} or
|
|
* {@link TelephonyManager#CARRIER_PRIVILEGE_STATUS_NO_ACCESS}.
|
|
*/
|
|
public int getCarrierPrivilegeStatus(Signature signature, String packageName) {
|
|
// SHA-1 is for backward compatible support only, strongly discouraged for new use.
|
|
byte[] certHash = getCertHash(signature, "SHA-1");
|
|
byte[] certHash256 = getCertHash(signature, "SHA-256");
|
|
if (matches(certHash, packageName) || matches(certHash256, packageName)) {
|
|
return TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS;
|
|
}
|
|
|
|
return TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS;
|
|
}
|
|
|
|
private boolean matches(byte[] certHash, String packageName) {
|
|
return certHash != null && Arrays.equals(this.mCertificateHash, certHash) &&
|
|
(TextUtils.isEmpty(this.mPackageName) || this.mPackageName.equals(packageName));
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
return "cert: " + IccUtils.bytesToHexString(mCertificateHash) + " pkg: " +
|
|
mPackageName + " access: " + mAccessType;
|
|
}
|
|
|
|
@Override
|
|
public int describeContents() {
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Converts a Signature into a Certificate hash usable for comparison.
|
|
*/
|
|
private static byte[] getCertHash(Signature signature, String algo) {
|
|
try {
|
|
MessageDigest md = MessageDigest.getInstance(algo);
|
|
return md.digest(signature.toByteArray());
|
|
} catch (NoSuchAlgorithmException ex) {
|
|
Rlog.e(TAG, "NoSuchAlgorithmException: " + ex);
|
|
}
|
|
return null;
|
|
}
|
|
}
|