diff options
| author | TreeHugger Robot <treehugger-gerrit@google.com> | 2019-11-26 20:31:33 +0000 |
|---|---|---|
| committer | Android (Google) Code Review <android-gerrit@google.com> | 2019-11-26 20:31:33 +0000 |
| commit | ca4f0580fe986cd0cbeeb6ccdc6acd824c9bb995 (patch) | |
| tree | d100022d1ee8f58b146e939477f642f70f1fc20e /core/java | |
| parent | c3c4e7f0fe25d1c2b3469385b38e7063a931e7f3 (diff) | |
| parent | 75147d5967fc1f78dc76292f96380e05c28e23a0 (diff) | |
Merge "Add public API and stub implementations for app integrity manager (service)."
Diffstat (limited to 'core/java')
| -rw-r--r-- | core/java/android/content/Context.java | 8 | ||||
| -rw-r--r-- | core/java/android/content/Intent.java | 16 | ||||
| -rw-r--r-- | core/java/android/content/integrity/AppInstallMetadata.java | 182 | ||||
| -rw-r--r-- | core/java/android/content/integrity/AppIntegrityManager.java | 103 | ||||
| -rw-r--r-- | core/java/android/content/integrity/AtomicFormula.java | 518 | ||||
| -rw-r--r-- | core/java/android/content/integrity/CompoundFormula.java | 223 | ||||
| -rw-r--r-- | core/java/android/content/integrity/Formula.java | 102 | ||||
| -rw-r--r-- | core/java/android/content/integrity/IAppIntegrityManager.aidl | 28 | ||||
| -rw-r--r-- | core/java/android/content/integrity/Rule.aidl | 19 | ||||
| -rw-r--r-- | core/java/android/content/integrity/Rule.java | 147 | ||||
| -rw-r--r-- | core/java/android/content/integrity/RuleSet.java | 92 |
11 files changed, 1438 insertions, 0 deletions
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 226af9ed0baa..4a0fc6689fa7 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -4909,6 +4909,14 @@ public abstract class Context { public static final String APP_SEARCH_SERVICE = "app_search"; /** + * Use with {@link #getSystemService(String)} to retrieve an + * {@link android.content.integrity.AppIntegrityManager}. + * @hide + */ + @SystemApi + public static final String APP_INTEGRITY_SERVICE = "app_integrity"; + + /** * Determine whether the given permission is allowed for a particular * process and user ID running in the system. * diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index ca374f938f1e..40aca0ef2033 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -4417,6 +4417,22 @@ public class Intent implements Parcelable, Cloneable { @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) public static final String ACTION_VIEW_LOCUS = "android.intent.action.VIEW_LOCUS"; + /** + * Broadcast Action: Sent to the integrity component when a package + * needs to be verified. The data contains the package URI along with other relevant + * information. + * + * <p class="note"> + * This is a protected intent that can only be sent by the system. + * </p> + * + * @hide + */ + @SystemApi + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_PACKAGE_NEEDS_INTEGRITY_VERIFICATION = + "android.intent.action.PACKAGE_NEEDS_INTEGRITY_VERIFICATION"; + // --------------------------------------------------------------------- // --------------------------------------------------------------------- // Standard intent categories (see addCategory()). diff --git a/core/java/android/content/integrity/AppInstallMetadata.java b/core/java/android/content/integrity/AppInstallMetadata.java new file mode 100644 index 000000000000..c9634758f63f --- /dev/null +++ b/core/java/android/content/integrity/AppInstallMetadata.java @@ -0,0 +1,182 @@ +/* + * Copyright (C) 2019 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.content.integrity; + +import static com.android.internal.util.Preconditions.checkArgument; +import static com.android.internal.util.Preconditions.checkNotNull; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; + +import com.android.internal.annotations.VisibleForTesting; + +/** + * The app install metadata. + * + * <p>The integrity component retrieves metadata for app installs from package manager, passing it + * to the rule evaluation engine to evaluate the metadata against the rules. + * + * <p>Instances of this class are immutable. + * + * @hide + */ +@SystemApi +@VisibleForTesting +public final class AppInstallMetadata { + private final String mPackageName; + // Raw string encoding for the SHA-256 hash of the certificate of the app. + private final String mAppCertificate; + private final String mInstallerName; + // Raw string encoding for the SHA-256 hash of the certificate of the installer. + private final String mInstallerCertificate; + private final int mVersionCode; + private final boolean mIsPreInstalled; + + private AppInstallMetadata(Builder builder) { + this.mPackageName = builder.mPackageName; + this.mAppCertificate = builder.mAppCertificate; + this.mInstallerName = builder.mInstallerName; + this.mInstallerCertificate = builder.mInstallerCertificate; + this.mVersionCode = builder.mVersionCode; + this.mIsPreInstalled = builder.mIsPreInstalled; + } + + @NonNull + public String getPackageName() { + return mPackageName; + } + + @NonNull + public String getAppCertificate() { + return mAppCertificate; + } + + @Nullable + public String getInstallerName() { + return mInstallerName; + } + + @Nullable + public String getInstallerCertificate() { + return mInstallerCertificate; + } + + /** @see AppInstallMetadata.Builder#setVersionCode(int) */ + public int getVersionCode() { + return mVersionCode; + } + + /** @see AppInstallMetadata.Builder#setIsPreInstalled(boolean) */ + public boolean isPreInstalled() { + return mIsPreInstalled; + } + + /** Builder class for constructing {@link AppInstallMetadata} objects. */ + public static final class Builder { + private String mPackageName; + private String mAppCertificate; + private String mInstallerName; + private String mInstallerCertificate; + private int mVersionCode; + private boolean mIsPreInstalled; + + /** + * Set package name of the app to be installed. + * + * @see AppInstallMetadata#getPackageName() + */ + @NonNull + public Builder setPackageName(@NonNull String packageName) { + this.mPackageName = checkNotNull(packageName); + return this; + } + + /** + * Set certificate of the app to be installed. + * + * <p>It is represented as the raw string encoding for the SHA-256 hash of the certificate + * of the app. + * + * @see AppInstallMetadata#getAppCertificate() + */ + @NonNull + public Builder setAppCertificate(@NonNull String appCertificate) { + this.mAppCertificate = checkNotNull(appCertificate); + return this; + } + + /** + * Set name of the installer installing the app. + * + * @see AppInstallMetadata#getInstallerName() + */ + @NonNull + public Builder setInstallerName(@NonNull String installerName) { + this.mInstallerName = checkNotNull(installerName); + return this; + } + + /** + * Set certificate of the installer installing the app. + * + * <p>It is represented as the raw string encoding for the SHA-256 hash of the certificate + * of the installer. + * + * @see AppInstallMetadata#getInstallerCertificate() + */ + @NonNull + public Builder setInstallerCertificate(@NonNull String installerCertificate) { + this.mInstallerCertificate = checkNotNull(installerCertificate); + return this; + } + + /** + * Set version code of the app to be installed. + * + * @see AppInstallMetadata#getVersionCode() + */ + @NonNull + public Builder setVersionCode(int versionCode) { + this.mVersionCode = versionCode; + return this; + } + + /** + * Set whether the app is pre-installed on the device or not. + * + * @see AppInstallMetadata#isPreInstalled() + */ + @NonNull + public Builder setIsPreInstalled(boolean isPreInstalled) { + this.mIsPreInstalled = isPreInstalled; + return this; + } + + /** + * Build {@link AppInstallMetadata}. + * + * @throws IllegalArgumentException if package name or app certificate is null + */ + @NonNull + public AppInstallMetadata build() { + checkArgument(mPackageName != null); + checkArgument(mAppCertificate != null); + return new AppInstallMetadata(this); + } + } +} diff --git a/core/java/android/content/integrity/AppIntegrityManager.java b/core/java/android/content/integrity/AppIntegrityManager.java new file mode 100644 index 000000000000..e53ef66020f1 --- /dev/null +++ b/core/java/android/content/integrity/AppIntegrityManager.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2019 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.content.integrity; + +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.annotation.SystemService; +import android.content.Context; +import android.content.IntentSender; +import android.content.pm.ParceledListSlice; +import android.os.RemoteException; + +/** + * Class for pushing rules used to check the integrity of app installs. + * + * <p>Note: applications using methods of this class must be a system app and have their package + * name whitelisted as an integrity rule provider. Otherwise a {@link SecurityException} will be + * thrown. + * + * @hide + */ +@SystemApi +@SystemService(Context.APP_INTEGRITY_SERVICE) +public class AppIntegrityManager { + + /** The operation succeeded. */ + public static final int STATUS_SUCCESS = 0; + + /** The operation failed. */ + public static final int STATUS_FAILURE = 1; + + /** + * Current status of an operation. Will be one of {@link #STATUS_SUCCESS}, {@link + * #STATUS_FAILURE}. + * + * <p>More information about a status may be available through additional extras; see the + * individual status documentation for details. + * + * @see android.content.Intent#getIntExtra(String, int) + */ + public static final String EXTRA_STATUS = "android.content.integrity.extra.STATUS"; + + IAppIntegrityManager mManager; + + /** @hide */ + public AppIntegrityManager(IAppIntegrityManager manager) { + mManager = manager; + } + + /** + * Update the rules to evaluate during install time. + * + * @param updateRequest request containing the data of the rule set update + * @param statusReceiver Called when the state of the session changes. Intents sent to this + * receiver contain {@link #EXTRA_STATUS}. Refer to the individual status codes on how to + * handle them. + */ + public void updateRuleSet( + @NonNull RuleSet updateRequest, @NonNull IntentSender statusReceiver) { + try { + mManager.updateRuleSet( + updateRequest.getVersion(), + new ParceledListSlice<>(updateRequest.getRules()), + statusReceiver); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + + /** Get the current version of the rule set. */ + @NonNull + public String getCurrentRuleSetVersion() { + try { + return mManager.getCurrentRuleSetVersion(); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + + /** Get the name of the package that provided the current rule set. */ + @NonNull + public String getCurrentRuleSetProvider() { + try { + return mManager.getCurrentRuleSetProvider(); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } +} diff --git a/core/java/android/content/integrity/AtomicFormula.java b/core/java/android/content/integrity/AtomicFormula.java new file mode 100644 index 000000000000..c8e164f1f232 --- /dev/null +++ b/core/java/android/content/integrity/AtomicFormula.java @@ -0,0 +1,518 @@ +/* + * Copyright (C) 2019 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.content.integrity; + +import static com.android.internal.util.Preconditions.checkArgument; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.Slog; + +import com.android.internal.annotations.VisibleForTesting; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Objects; + +/** + * Represents a simple formula consisting of an app install metadata field and a value. + * + * <p>Instances of this class are immutable. + * + * @hide + */ +@SystemApi +@VisibleForTesting +public abstract class AtomicFormula implements Formula { + + private static final String TAG = "AtomicFormula"; + + @IntDef( + value = { + PACKAGE_NAME, + APP_CERTIFICATE, + INSTALLER_NAME, + INSTALLER_CERTIFICATE, + VERSION_CODE, + PRE_INSTALLED, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface Key {} + + @IntDef(value = {EQ, LT, LE, GT, GE}) + @Retention(RetentionPolicy.SOURCE) + public @interface Operator {} + + /** + * Package name of the app. + * + * <p>Can only be used in {@link StringAtomicFormula}. + */ + public static final int PACKAGE_NAME = 0; + + /** + * SHA-256 of the app certificate of the app. + * + * <p>Can only be used in {@link StringAtomicFormula}. + */ + public static final int APP_CERTIFICATE = 1; + + /** + * Package name of the installer. Will be empty string if installed by the system (e.g., adb). + * + * <p>Can only be used in {@link StringAtomicFormula}. + */ + public static final int INSTALLER_NAME = 2; + + /** + * SHA-256 of the cert of the installer. Will be empty string if installed by the system (e.g., + * adb). + * + * <p>Can only be used in {@link StringAtomicFormula}. + */ + public static final int INSTALLER_CERTIFICATE = 3; + + /** + * Version code of the app. + * + * <p>Can only be used in {@link IntAtomicFormula}. + */ + public static final int VERSION_CODE = 4; + + /** + * If the app is pre-installed on the device. + * + * <p>Can only be used in {@link BooleanAtomicFormula}. + */ + public static final int PRE_INSTALLED = 5; + + public static final int EQ = 0; + public static final int LT = 1; + public static final int LE = 2; + public static final int GT = 3; + public static final int GE = 4; + + private final @Key int mKey; + + public AtomicFormula(@Key int key) { + checkArgument(isValidKey(key), String.format("Unknown key: %d", key)); + mKey = key; + } + + /** An {@link AtomicFormula} with an key and int value. */ + public static final class IntAtomicFormula extends AtomicFormula implements Parcelable { + private final int mValue; + private final @Operator int mOperator; + + /** + * Constructs a new {@link IntAtomicFormula}. + * + * <p>This formula will hold if and only if the corresponding information of an install + * specified by {@code key} is of the correct relationship to {@code value} as specified by + * {@code operator}. + * + * @throws IllegalArgumentException if {@code key} cannot be used with integer value + */ + public IntAtomicFormula(@Key int key, @Operator int operator, int value) { + super(key); + checkArgument( + key == VERSION_CODE, + String.format("Key %s cannot be used with IntAtomicFormula", keyToString(key))); + checkArgument(isValidOperator(operator), + String.format("Unknown operator: %d", operator)); + mOperator = operator; + mValue = value; + } + + IntAtomicFormula(Parcel in) { + super(in.readInt()); + mValue = in.readInt(); + mOperator = in.readInt(); + } + + @NonNull + public static final Creator<IntAtomicFormula> CREATOR = + new Creator<IntAtomicFormula>() { + @Override + public IntAtomicFormula createFromParcel(Parcel in) { + return new IntAtomicFormula(in); + } + + @Override + public IntAtomicFormula[] newArray(int size) { + return new IntAtomicFormula[size]; + } + }; + + @Override + public boolean isSatisfied(@NonNull AppInstallMetadata appInstallMetadata) { + int metadataValue = getMetadataValueByKey(appInstallMetadata); + switch (mOperator) { + case EQ: + return metadataValue == mValue; + case LE: + return metadataValue <= mValue; + case LT: + return metadataValue < mValue; + case GE: + return metadataValue >= mValue; + case GT: + return metadataValue > mValue; + default: + Slog.i(TAG, String.format("Unexpected operator %d", mOperator)); + return false; + } + } + + @Override + public int getTag() { + return Formula.INT_ATOMIC_FORMULA_TAG; + } + + @Override + public String toString() { + return String.format( + "(%s %s %s)", keyToString(getKey()), operatorToString(mOperator), mValue); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + IntAtomicFormula that = (IntAtomicFormula) o; + return getKey() == that.getKey() + && mValue == that.mValue + && mOperator == that.mOperator; + } + + @Override + public int hashCode() { + return Objects.hash(getKey(), mOperator, mValue); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(getKey()); + dest.writeInt(mValue); + dest.writeInt(mOperator); + } + + public int getValue() { + return mValue; + } + + public int getOperator() { + return mOperator; + } + + private int getMetadataValueByKey(AppInstallMetadata appInstallMetadata) { + switch (getKey()) { + case VERSION_CODE: + return appInstallMetadata.getVersionCode(); + default: + throw new IllegalStateException( + "Unexpected key in IntAtomicFormula" + getKey()); + } + } + + private static boolean isValidOperator(int operator) { + return operator == EQ + || operator == LT + || operator == LE + || operator == GT + || operator == GE; + } + } + + /** An {@link AtomicFormula} with a key and string value. */ + public static final class StringAtomicFormula extends AtomicFormula implements Parcelable { + private final String mValue; + // Indicates whether the value is the actual value or the hashed value. + private final boolean mIsHashedValue; + + /** + * Constructs a new {@link StringAtomicFormula}. + * + * <p>This formula will hold if and only if the corresponding information of an install + * specified by {@code key} equals {@code value}. + * + * @throws IllegalArgumentException if {@code key} cannot be used with string value + */ + public StringAtomicFormula(@Key int key, @NonNull String value, boolean isHashedValue) { + super(key); + mIsHashedValue = isHashedValue; + checkArgument( + key == PACKAGE_NAME + || key == APP_CERTIFICATE + || key == INSTALLER_CERTIFICATE + || key == INSTALLER_NAME, + String.format( + "Key %s cannot be used with StringAtomicFormula", keyToString(key))); + mValue = value; + } + + StringAtomicFormula(Parcel in) { + super(in.readInt()); + mValue = in.readStringNoHelper(); + mIsHashedValue = in.readByte() != 0; + } + + @NonNull + public static final Creator<StringAtomicFormula> CREATOR = + new Creator<StringAtomicFormula>() { + @Override + public StringAtomicFormula createFromParcel(Parcel in) { + return new StringAtomicFormula(in); + } + + @Override + public StringAtomicFormula[] newArray(int size) { + return new StringAtomicFormula[size]; + } + }; + + @Override + public boolean isSatisfied(@NonNull AppInstallMetadata appInstallMetadata) { + String metadataValue = getMetadataValueByKey(appInstallMetadata); + return metadataValue.equals(mValue); + } + + @Override + public int getTag() { + return Formula.STRING_ATOMIC_FORMULA_TAG; + } + + @Override + public String toString() { + return String.format("(%s %s %s)", keyToString(getKey()), operatorToString(EQ), mValue); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + StringAtomicFormula that = (StringAtomicFormula) o; + return getKey() == that.getKey() && Objects.equals(mValue, that.mValue); + } + + @Override + public int hashCode() { + return Objects.hash(getKey(), mValue); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(getKey()); + dest.writeStringNoHelper(mValue); + dest.writeByte((byte) (mIsHashedValue ? 1 : 0)); + } + + @NonNull + public String getValue() { + return mValue; + } + + public boolean getIsHashedValue() { + return mIsHashedValue; + } + + private String getMetadataValueByKey(AppInstallMetadata appInstallMetadata) { + switch (getKey()) { + case PACKAGE_NAME: + return appInstallMetadata.getPackageName(); + case APP_CERTIFICATE: + return appInstallMetadata.getAppCertificate(); + case INSTALLER_CERTIFICATE: + return appInstallMetadata.getInstallerCertificate(); + case INSTALLER_NAME: + return appInstallMetadata.getInstallerName(); + default: + throw new IllegalStateException( + "Unexpected key in StringAtomicFormula: " + getKey()); + } + } + } + + /** An {@link AtomicFormula} with a key and boolean value. */ + public static final class BooleanAtomicFormula extends AtomicFormula implements Parcelable { + private final boolean mValue; + + /** + * Constructs a new {@link BooleanAtomicFormula}. + * + * <p>This formula will hold if and only if the corresponding information of an install + * specified by {@code key} equals {@code value}. + * + * @throws IllegalArgumentException if {@code key} cannot be used with boolean value + */ + public BooleanAtomicFormula(@Key int key, boolean value) { + super(key); + checkArgument( + key == PRE_INSTALLED, + String.format( + "Key %s cannot be used with BooleanAtomicFormula", keyToString(key))); + mValue = value; + } + + BooleanAtomicFormula(Parcel in) { + super(in.readInt()); + mValue = in.readByte() != 0; + } + + @NonNull + public static final Creator<BooleanAtomicFormula> CREATOR = + new Creator<BooleanAtomicFormula>() { + @Override + public BooleanAtomicFormula createFromParcel(Parcel in) { + return new BooleanAtomicFormula(in); + } + + @Override + public BooleanAtomicFormula[] newArray(int size) { + return new BooleanAtomicFormula[size]; + } + }; + + @Override + public boolean isSatisfied(@NonNull AppInstallMetadata appInstallMetadata) { + boolean metadataValue = getMetadataValueByKey(appInstallMetadata); + return metadataValue == mValue; + } + + @Override + public int getTag() { + return Formula.BOOLEAN_ATOMIC_FORMULA_TAG; + } + + @Override + public String toString() { + return String.format("(%s %s %s)", keyToString(getKey()), operatorToString(EQ), mValue); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + BooleanAtomicFormula that = (BooleanAtomicFormula) o; + return getKey() == that.getKey() && mValue == that.mValue; + } + + @Override + public int hashCode() { + return Objects.hash(getKey(), mValue); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(getKey()); + dest.writeByte((byte) (mValue ? 1 : 0)); + } + + public boolean getValue() { + return mValue; + } + + private boolean getMetadataValueByKey(AppInstallMetadata appInstallMetadata) { + switch (getKey()) { + case PRE_INSTALLED: + return appInstallMetadata.isPreInstalled(); + default: + throw new IllegalStateException( + "Unexpected key in BooleanAtomicFormula: " + getKey()); + } + } + } + + public int getKey() { + return mKey; + } + + static String keyToString(int key) { + switch (key) { + case PACKAGE_NAME: + return "PACKAGE_NAME"; + case APP_CERTIFICATE: + return "APP_CERTIFICATE"; + case VERSION_CODE: + return "VERSION_CODE"; + case INSTALLER_NAME: + return "INSTALLER_NAME"; + case INSTALLER_CERTIFICATE: + return "INSTALLER_CERTIFICATE"; + case PRE_INSTALLED: + return "PRE_INSTALLED"; + default: + throw new IllegalArgumentException("Unknown key " + key); + } + } + + static String operatorToString(int op) { + switch (op) { + case EQ: + return "EQ"; + case LT: + return "LT"; + case LE: + return "LE"; + case GT: + return "GT"; + case GE: + return "GE"; + default: + throw new IllegalArgumentException("Unknown operator " + op); + } + } + + private static boolean isValidKey(int key) { + return key == PACKAGE_NAME + || key == APP_CERTIFICATE + || key == VERSION_CODE + || key == INSTALLER_NAME + || key == INSTALLER_CERTIFICATE + || key == PRE_INSTALLED; + } +} diff --git a/core/java/android/content/integrity/CompoundFormula.java b/core/java/android/content/integrity/CompoundFormula.java new file mode 100644 index 000000000000..53a99534906c --- /dev/null +++ b/core/java/android/content/integrity/CompoundFormula.java @@ -0,0 +1,223 @@ +/* + * Copyright (C) 2019 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.content.integrity; + +import static com.android.internal.util.Preconditions.checkArgument; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.Slog; + +import com.android.internal.annotations.VisibleForTesting; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +/** + * Represents a compound formula formed by joining other simple and complex formulas with boolean + * connectors. + * + * <p>Instances of this class are immutable. + * + * @hide + */ +@SystemApi +@VisibleForTesting +public final class CompoundFormula implements Formula, Parcelable { + private static final String TAG = "OpenFormula"; + + @IntDef( + value = { + AND, OR, NOT, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface Connector {} + + /** Boolean AND operator. */ + public static final int AND = 0; + + /** Boolean OR operator. */ + public static final int OR = 1; + + /** Boolean NOT operator. */ + public static final int NOT = 2; + + private final @Connector int mConnector; + private final @NonNull List<Formula> mFormulas; + + @NonNull + public static final Creator<CompoundFormula> CREATOR = + new Creator<CompoundFormula>() { + @Override + public CompoundFormula createFromParcel(Parcel in) { + return new CompoundFormula(in); + } + + @Override + public CompoundFormula[] newArray(int size) { + return new CompoundFormula[size]; + } + }; + + /** + * Create a new formula from operator and operands. + * + * @throws IllegalArgumentException if the number of operands is not matching the requirements + * for that operator (at least 2 for {@link #AND} and {@link #OR}, 1 for {@link #NOT}). + */ + public CompoundFormula(@Connector int connector, @NonNull List<Formula> formulas) { + checkArgument( + isValidConnector(connector), String.format("Unknown connector: %d", connector)); + validateFormulas(connector, formulas); + this.mConnector = connector; + this.mFormulas = Collections.unmodifiableList(formulas); + } + + CompoundFormula(Parcel in) { + mConnector = in.readInt(); + int length = in.readInt(); + checkArgument(length >= 0, "Must have non-negative length. Got " + length); + mFormulas = new ArrayList<>(length); + for (int i = 0; i < length; i++) { + mFormulas.add(Formula.readFromParcel(in)); + } + validateFormulas(mConnector, mFormulas); + } + + public @Connector int getConnector() { + return mConnector; + } + + @NonNull + public List<Formula> getFormulas() { + return mFormulas; + } + + @Override + public boolean isSatisfied(@NonNull AppInstallMetadata appInstallMetadata) { + switch (mConnector) { + case NOT: + return !mFormulas.get(0).isSatisfied(appInstallMetadata); + case AND: + return mFormulas.stream() + .allMatch(formula -> formula.isSatisfied(appInstallMetadata)); + case OR: + return mFormulas.stream() + .anyMatch(formula -> formula.isSatisfied(appInstallMetadata)); + default: + Slog.i(TAG, "Unknown connector " + mConnector); + return false; + } + } + + @Override + public int getTag() { + return Formula.COMPOUND_FORMULA_TAG; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + if (mFormulas.size() == 1) { + sb.append(String.format("%s ", connectorToString(mConnector))); + sb.append(mFormulas.get(0).toString()); + } else { + for (int i = 0; i < mFormulas.size(); i++) { + if (i > 0) { + sb.append(String.format(" %s ", connectorToString(mConnector))); + } + sb.append(mFormulas.get(i).toString()); + } + } + return sb.toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + CompoundFormula that = (CompoundFormula) o; + return mConnector == that.mConnector && mFormulas.equals(that.mFormulas); + } + + @Override + public int hashCode() { + return Objects.hash(mConnector, mFormulas); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(mConnector); + dest.writeInt(mFormulas.size()); + for (Formula formula : mFormulas) { + Formula.writeToParcel(formula, dest, flags); + } + } + + private static void validateFormulas(@Connector int connector, List<Formula> formulas) { + switch (connector) { + case AND: + case OR: + checkArgument( + formulas.size() >= 2, + String.format( + "Connector %s must have at least 2 formulas", + connectorToString(connector))); + break; + case NOT: + checkArgument( + formulas.size() == 1, + String.format( + "Connector %s must have 1 formula only", + connectorToString(connector))); + break; + } + } + + private static String connectorToString(int connector) { + switch (connector) { + case AND: + return "AND"; + case OR: + return "OR"; + case NOT: + return "NOT"; + default: + throw new IllegalArgumentException("Unknown connector " + connector); + } + } + + private static boolean isValidConnector(int connector) { + return connector == AND || connector == OR || connector == NOT; + } +} diff --git a/core/java/android/content/integrity/Formula.java b/core/java/android/content/integrity/Formula.java new file mode 100644 index 000000000000..030ff6b66e1f --- /dev/null +++ b/core/java/android/content/integrity/Formula.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2019 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.content.integrity; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.content.integrity.AtomicFormula.BooleanAtomicFormula; +import android.content.integrity.AtomicFormula.IntAtomicFormula; +import android.content.integrity.AtomicFormula.StringAtomicFormula; +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.internal.annotations.VisibleForTesting; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Represents a rule logic/content. + * + * @hide + */ +@SystemApi +@VisibleForTesting +public interface Formula { + @IntDef( + value = { + COMPOUND_FORMULA_TAG, + STRING_ATOMIC_FORMULA_TAG, + INT_ATOMIC_FORMULA_TAG, + BOOLEAN_ATOMIC_FORMULA_TAG + }) + @Retention(RetentionPolicy.SOURCE) + public @interface Tag {} + + int COMPOUND_FORMULA_TAG = 0; + int STRING_ATOMIC_FORMULA_TAG = 1; + int INT_ATOMIC_FORMULA_TAG = 2; + int BOOLEAN_ATOMIC_FORMULA_TAG = 3; + + /** + * Returns if this formula can be satisfied by substituting the corresponding information of + * {@code appInstallMetadata} into the formula. + */ + boolean isSatisfied(@NonNull AppInstallMetadata appInstallMetadata); + + /** Returns the tag that identifies the current class. */ + @Tag int getTag(); + + /** + * Write a {@link Formula} to {@link android.os.Parcel}. + * + * <p>This helper method is needed because non-final class/interface are not allowed to be + * {@link Parcelable}. + * + * @throws IllegalArgumentException if {@link Formula} is not a recognized subclass + */ + static void writeToParcel(@NonNull Formula formula, @NonNull Parcel dest, int flags) { + dest.writeInt(formula.getTag()); + ((Parcelable) formula).writeToParcel(dest, flags); + } + + /** + * Read a {@link Formula} from a {@link android.os.Parcel}. + * + * <p>We need this (hacky) helper method because non-final class/interface cannot be {@link + * Parcelable} (api lint error). + * + * @throws IllegalArgumentException if the parcel cannot be parsed + */ + @NonNull + static Formula readFromParcel(@NonNull Parcel in) { + int tag = in.readInt(); + switch (tag) { + case COMPOUND_FORMULA_TAG: + return CompoundFormula.CREATOR.createFromParcel(in); + case STRING_ATOMIC_FORMULA_TAG: + return StringAtomicFormula.CREATOR.createFromParcel(in); + case INT_ATOMIC_FORMULA_TAG: + return IntAtomicFormula.CREATOR.createFromParcel(in); + case BOOLEAN_ATOMIC_FORMULA_TAG: + return BooleanAtomicFormula.CREATOR.createFromParcel(in); + default: + throw new IllegalArgumentException("Unknown formula tag " + tag); + } + } +} diff --git a/core/java/android/content/integrity/IAppIntegrityManager.aidl b/core/java/android/content/integrity/IAppIntegrityManager.aidl new file mode 100644 index 000000000000..6b73fd70bb99 --- /dev/null +++ b/core/java/android/content/integrity/IAppIntegrityManager.aidl @@ -0,0 +1,28 @@ +/* +** +** Copyright 2019, 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.content.integrity; + +import android.content.integrity.Rule; +import android.content.IntentSender; +import android.content.pm.ParceledListSlice; + +/** @hide */ +interface IAppIntegrityManager { + void updateRuleSet(String version, in ParceledListSlice<Rule> rules, in IntentSender statusReceiver); + String getCurrentRuleSetVersion(); + String getCurrentRuleSetProvider(); +} diff --git a/core/java/android/content/integrity/Rule.aidl b/core/java/android/content/integrity/Rule.aidl new file mode 100644 index 000000000000..a6634ee67905 --- /dev/null +++ b/core/java/android/content/integrity/Rule.aidl @@ -0,0 +1,19 @@ +/* +** +** Copyright 2007, 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.content.integrity; + +parcelable Rule; diff --git a/core/java/android/content/integrity/Rule.java b/core/java/android/content/integrity/Rule.java new file mode 100644 index 000000000000..914f1479edf3 --- /dev/null +++ b/core/java/android/content/integrity/Rule.java @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2019 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.content.integrity; + +import static com.android.internal.util.Preconditions.checkArgument; +import static com.android.internal.util.Preconditions.checkNotNull; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.internal.annotations.VisibleForTesting; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Objects; + +/** + * Represent rules to be used in the rule evaluation engine to match against app installs. + * + * <p>Instances of this class are immutable. + * + * @hide + */ +@SystemApi +@VisibleForTesting +public final class Rule implements Parcelable { + + @IntDef( + value = { + DENY, + FORCE_ALLOW, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface Effect {} + + /** If this rule matches the install, the install should be denied. */ + public static final int DENY = 0; + + /** + * If this rule matches the install, the install will be allowed regardless of other matched + * rules. + */ + public static final int FORCE_ALLOW = 1; + + private final @NonNull Formula mFormula; + private final @Effect int mEffect; + + public Rule(@NonNull Formula formula, @Effect int effect) { + checkArgument(isValidEffect(effect), String.format("Unknown effect: %d", effect)); + this.mFormula = checkNotNull(formula); + this.mEffect = effect; + } + + Rule(Parcel in) { + mFormula = Formula.readFromParcel(in); + mEffect = in.readInt(); + } + + @NonNull + public static final Creator<Rule> CREATOR = + new Creator<Rule>() { + @Override + public Rule createFromParcel(Parcel in) { + return new Rule(in); + } + + @Override + public Rule[] newArray(int size) { + return new Rule[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + Formula.writeToParcel(mFormula, dest, flags); + dest.writeInt(mEffect); + } + + @NonNull + public Formula getFormula() { + return mFormula; + } + + public @Effect int getEffect() { + return mEffect; + } + + @Override + public String toString() { + return String.format("Rule: %s, %s", mFormula, effectToString(mEffect)); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Rule that = (Rule) o; + return mEffect == that.mEffect && Objects.equals(mFormula, that.mFormula); + } + + @Override + public int hashCode() { + return Objects.hash(mFormula, mEffect); + } + + private static String effectToString(int effect) { + switch (effect) { + case DENY: + return "DENY"; + case FORCE_ALLOW: + return "FORCE_ALLOW"; + default: + throw new IllegalArgumentException("Unknown effect " + effect); + } + } + + private static boolean isValidEffect(int effect) { + return effect == DENY + || effect == FORCE_ALLOW; + } +} diff --git a/core/java/android/content/integrity/RuleSet.java b/core/java/android/content/integrity/RuleSet.java new file mode 100644 index 000000000000..a78f8c97d553 --- /dev/null +++ b/core/java/android/content/integrity/RuleSet.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2019 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.content.integrity; + +import static com.android.internal.util.Preconditions.checkNotNull; + +import android.annotation.NonNull; +import android.annotation.SystemApi; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Immutable data class encapsulating all parameters of a rule set. + * + * @hide + */ +@SystemApi +public class RuleSet { + private final String mVersion; + private final List<Rule> mRules; + + private RuleSet(String version, List<Rule> rules) { + mVersion = version; + mRules = Collections.unmodifiableList(rules); + } + + /** @see Builder#setVersion(String). */ + @NonNull + public String getVersion() { + return mVersion; + } + + /** @see Builder#addRules(List). */ + @NonNull + public List<Rule> getRules() { + return mRules; + } + + /** Builder class for RuleSetUpdateRequest. */ + public static class Builder { + private String mVersion; + private List<Rule> mRules; + + public Builder() { + mRules = new ArrayList<>(); + } + + /** + * Set a version string to identify this rule set. This can be retrieved by {@link + * AppIntegrityManager#getCurrentRuleSetVersion()}. + */ + @NonNull + public Builder setVersion(@NonNull String version) { + mVersion = version; + return this; + } + + /** Add the rules to include. */ + @NonNull + public Builder addRules(@NonNull List<Rule> rules) { + mRules.addAll(rules); + return this; + } + + /** + * Builds a {@link RuleSet}. + * + * @throws IllegalArgumentException if version is null + */ + @NonNull + public RuleSet build() { + checkNotNull(mVersion); + return new RuleSet(mVersion, mRules); + } + } +} |
