/* * Copyright (C) 2020 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.Nullable; 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.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Collections; import java.util.List; import java.util.Objects; /** * Represents a simple formula consisting of an app install metadata field and a value. * *
Instances of this class are immutable. * * @hide */ @VisibleForTesting public abstract class AtomicFormula extends IntegrityFormula { /** @hide */ @IntDef( value = { PACKAGE_NAME, APP_CERTIFICATE, INSTALLER_NAME, INSTALLER_CERTIFICATE, VERSION_CODE, PRE_INSTALLED, STAMP_TRUSTED, STAMP_CERTIFICATE_HASH, APP_CERTIFICATE_LINEAGE, }) @Retention(RetentionPolicy.SOURCE) public @interface Key {} /** @hide */ @IntDef(value = {EQ, GT, GTE}) @Retention(RetentionPolicy.SOURCE) public @interface Operator {} /** * Package name of the app. * *
Can only be used in {@link StringAtomicFormula}. */ public static final int PACKAGE_NAME = 0; /** * SHA-256 of the app certificate of the app. * *
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). * *
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). * *
Can only be used in {@link StringAtomicFormula}. */ public static final int INSTALLER_CERTIFICATE = 3; /** * Version code of the app. * *
Can only be used in {@link LongAtomicFormula}. */ public static final int VERSION_CODE = 4; /** * If the app is pre-installed on the device. * *
Can only be used in {@link BooleanAtomicFormula}. */ public static final int PRE_INSTALLED = 5; /** * If the APK has an embedded trusted stamp. * *
Can only be used in {@link BooleanAtomicFormula}. */ public static final int STAMP_TRUSTED = 6; /** * SHA-256 of the certificate used to sign the stamp embedded in the APK. * *
Can only be used in {@link StringAtomicFormula}. */ public static final int STAMP_CERTIFICATE_HASH = 7; /** * SHA-256 of a certificate in the signing lineage of the app. * *
Can only be used in {@link StringAtomicFormula}. */ public static final int APP_CERTIFICATE_LINEAGE = 8; public static final int EQ = 0; public static final int GT = 1; public static final int GTE = 2; private final @Key int mKey; public AtomicFormula(@Key int key) { checkArgument(isValidKey(key), "Unknown key: %d", key); mKey = key; } /** An {@link AtomicFormula} with an key and long value. */ public static final class LongAtomicFormula extends AtomicFormula implements Parcelable { private final Long mValue; private final @Operator Integer mOperator; /** * Constructs an empty {@link LongAtomicFormula}. This should only be used as a base. * *
This formula will always return false. * * @throws IllegalArgumentException if {@code key} cannot be used with long value */ public LongAtomicFormula(@Key int key) { super(key); checkArgument( key == VERSION_CODE, "Key %s cannot be used with LongAtomicFormula", keyToString(key)); mValue = null; mOperator = null; } /** * Constructs a new {@link LongAtomicFormula}. * *
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 long value
*/
public LongAtomicFormula(@Key int key, @Operator int operator, long value) {
super(key);
checkArgument(
key == VERSION_CODE,
"Key %s cannot be used with LongAtomicFormula", keyToString(key));
checkArgument(
isValidOperator(operator), "Unknown operator: %d", operator);
mOperator = operator;
mValue = value;
}
LongAtomicFormula(Parcel in) {
super(in.readInt());
mValue = in.readLong();
mOperator = in.readInt();
}
@NonNull
public static final Creator An empty formula will always match to false.
*
* @throws IllegalArgumentException if {@code key} cannot be used with string value
*/
public StringAtomicFormula(@Key int key) {
super(key);
checkArgument(
key == PACKAGE_NAME
|| key == APP_CERTIFICATE
|| key == INSTALLER_CERTIFICATE
|| key == INSTALLER_NAME
|| key == STAMP_CERTIFICATE_HASH
|| key == APP_CERTIFICATE_LINEAGE,
"Key %s cannot be used with StringAtomicFormula", keyToString(key));
mValue = null;
mIsHashedValue = null;
}
/**
* Constructs a new {@link StringAtomicFormula}.
*
* 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 isHashed) {
super(key);
checkArgument(
key == PACKAGE_NAME
|| key == APP_CERTIFICATE
|| key == INSTALLER_CERTIFICATE
|| key == INSTALLER_NAME
|| key == STAMP_CERTIFICATE_HASH
|| key == APP_CERTIFICATE_LINEAGE,
"Key %s cannot be used with StringAtomicFormula", keyToString(key));
mValue = value;
mIsHashedValue = isHashed;
}
/**
* Constructs a new {@link StringAtomicFormula} together with handling the necessary hashing
* for the given key.
*
* The value will be automatically hashed with SHA256 and the hex digest will be computed
* when the key is PACKAGE_NAME or INSTALLER_NAME and the value is more than 32 characters.
*
* The APP_CERTIFICATES, INSTALLER_CERTIFICATES, STAMP_CERTIFICATE_HASH and
* APP_CERTIFICATE_LINEAGE are always delivered in hashed form. So the isHashedValue is set
* to true by default.
*
* @throws IllegalArgumentException if {@code key} cannot be used with string value.
*/
public StringAtomicFormula(@Key int key, @NonNull String value) {
super(key);
checkArgument(
key == PACKAGE_NAME
|| key == APP_CERTIFICATE
|| key == INSTALLER_CERTIFICATE
|| key == INSTALLER_NAME
|| key == STAMP_CERTIFICATE_HASH
|| key == APP_CERTIFICATE_LINEAGE,
"Key %s cannot be used with StringAtomicFormula", keyToString(key));
mValue = hashValue(key, value);
mIsHashedValue =
(key == APP_CERTIFICATE
|| key == INSTALLER_CERTIFICATE
|| key == STAMP_CERTIFICATE_HASH
|| key == APP_CERTIFICATE_LINEAGE)
|| !mValue.equals(value);
}
StringAtomicFormula(Parcel in) {
super(in.readInt());
mValue = in.readStringNoHelper();
mIsHashedValue = in.readByte() != 0;
}
@NonNull
public static final Creator An empty formula will always match to false.
*
* @throws IllegalArgumentException if {@code key} cannot be used with boolean value
*/
public BooleanAtomicFormula(@Key int key) {
super(key);
checkArgument(
key == PRE_INSTALLED || key == STAMP_TRUSTED,
String.format(
"Key %s cannot be used with BooleanAtomicFormula", keyToString(key)));
mValue = null;
}
/**
* Constructs a new {@link BooleanAtomicFormula}.
*
* 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 || key == STAMP_TRUSTED,
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