content: /*
   * 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.annotation.SystemApi;
  import android.content.pm.PackageInfo;
  import android.content.pm.Signature;
  import android.content.pm.SigningInfo;
  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;
  import java.util.Objects;
  
  /**
   * Describes a single UICC access rule according to the GlobalPlatform Secure Element Access Control
   * specification.
   *
   * @hide
   */
  @SystemApi
  public final class UiccAccessRule implements Parcelable {
      private static final String TAG = "UiccAccessRule";
  
      private static final int ENCODING_VERSION = 1;
  
      public static final @android.annotation.NonNull 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 hex string of the certificate hash.
       */
      public String getCertificateHexString() {
          return IccUtils.bytesToHexString(mCertificateHash);
      }
  
      /**
       * 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_SIGNING_CERTIFICATES} 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) {
          Signature[] signatures = packageInfo.signatures;
          SigningInfo sInfo = packageInfo.signingInfo;
  
          if (sInfo != null) {
              signatures = sInfo.getSigningCertificateHistory();
              if (sInfo.hasMultipleSigners()) {
                  signatures = sInfo.getApkContentsSigners();
              }
          }
  
          if (signatures == null || signatures.length == 0) {
              throw new IllegalArgumentException(
                      "Must use GET_SIGNING_CERTIFICATES when looking up package info");
          }
  
          for (Signature sig : 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 boolean equals(Object obj) {
          if (this == obj) {
              return true;
          }
          if (obj == null || getClass() != obj.getClass()) {
              return false;
          }
  
          UiccAccessRule that = (UiccAccessRule) obj;
          return Arrays.equals(mCertificateHash, that.mCertificateHash)
                  && Objects.equals(mPackageName, that.mPackageName)
                  && mAccessType == that.mAccessType;
      }
  
      @Override
      public int hashCode() {
          int result = 1;
          result = 31 * result + Arrays.hashCode(mCertificateHash);
          result = 31 * result + Objects.hashCode(mPackageName);
          result = 31 * result + Objects.hashCode(mAccessType);
          return result;
      }
  
      @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;
      }
  }
  