diff --git a/api/system-current.txt b/api/system-current.txt index 27104d0544aa0..616da7fc7e2ea 100755 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -1958,6 +1958,11 @@ package android.content.integrity { method @NonNull public static android.content.integrity.IntegrityFormula packageNameEquals(@NonNull String); } + public static final class IntegrityFormula.SourceStamp { + method @NonNull public static android.content.integrity.IntegrityFormula notTrusted(); + method @NonNull public static android.content.integrity.IntegrityFormula stampCertificateHashEquals(@NonNull String); + } + public final class Rule implements android.os.Parcelable { ctor public Rule(@NonNull android.content.integrity.IntegrityFormula, int); method public int describeContents(); diff --git a/api/test-current.txt b/api/test-current.txt index 957794cac0573..882ee3a1a5f5d 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -846,6 +846,11 @@ package android.content.integrity { method @NonNull public static android.content.integrity.IntegrityFormula packageNameEquals(@NonNull String); } + public static final class IntegrityFormula.SourceStamp { + method @NonNull public static android.content.integrity.IntegrityFormula notTrusted(); + method @NonNull public static android.content.integrity.IntegrityFormula stampCertificateHashEquals(@NonNull String); + } + public final class Rule implements android.os.Parcelable { ctor public Rule(@NonNull android.content.integrity.IntegrityFormula, int); method public int describeContents(); diff --git a/core/java/android/content/integrity/AppInstallMetadata.java b/core/java/android/content/integrity/AppInstallMetadata.java index 4be7e6df46e0a..4ec94762ac346 100644 --- a/core/java/android/content/integrity/AppInstallMetadata.java +++ b/core/java/android/content/integrity/AppInstallMetadata.java @@ -42,6 +42,9 @@ public final class AppInstallMetadata { private final List mInstallerCertificates; private final long mVersionCode; private final boolean mIsPreInstalled; + private final boolean mIsStampTrusted; + // Raw string encoding for the SHA-256 hash of the certificate of the stamp. + private final String mStampCertificateHash; private final Map mAllowedInstallersAndCertificates; private AppInstallMetadata(Builder builder) { @@ -51,6 +54,8 @@ public final class AppInstallMetadata { this.mInstallerCertificates = builder.mInstallerCertificates; this.mVersionCode = builder.mVersionCode; this.mIsPreInstalled = builder.mIsPreInstalled; + this.mIsStampTrusted = builder.mIsStampTrusted; + this.mStampCertificateHash = builder.mStampCertificateHash; this.mAllowedInstallersAndCertificates = builder.mAllowedInstallersAndCertificates; } @@ -84,9 +89,17 @@ public final class AppInstallMetadata { return mIsPreInstalled; } - /** - * Get the allowed installers and their corresponding cert. - */ + /** @see AppInstallMetadata.Builder#setIsStampTrusted(boolean) */ + public boolean isStampTrusted() { + return mIsStampTrusted; + } + + /** @see AppInstallMetadata.Builder#setStampCertificateHash(String) */ + public String getStampCertificateHash() { + return mStampCertificateHash; + } + + /** Get the allowed installers and their corresponding cert. */ public Map getAllowedInstallersAndCertificates() { return mAllowedInstallersAndCertificates; } @@ -95,13 +108,16 @@ public final class AppInstallMetadata { public String toString() { return String.format( "AppInstallMetadata { PackageName = %s, AppCerts = %s, InstallerName = %s," - + " InstallerCerts = %s, VersionCode = %d, PreInstalled = %b }", + + " InstallerCerts = %s, VersionCode = %d, PreInstalled = %b, " + + "StampTrusted = %b, StampCert = %s }", mPackageName, mAppCertificates, mInstallerName == null ? "null" : mInstallerName, mInstallerCertificates == null ? "null" : mInstallerCertificates, mVersionCode, - mIsPreInstalled); + mIsPreInstalled, + mIsStampTrusted, + mStampCertificateHash == null ? "null" : mStampCertificateHash); } /** Builder class for constructing {@link AppInstallMetadata} objects. */ @@ -112,6 +128,8 @@ public final class AppInstallMetadata { private List mInstallerCertificates; private long mVersionCode; private boolean mIsPreInstalled; + private boolean mIsStampTrusted; + private String mStampCertificateHash; private Map mAllowedInstallersAndCertificates; public Builder() { @@ -202,6 +220,31 @@ public final class AppInstallMetadata { return this; } + /** + * Set certificate hash of the stamp embedded in the APK. + * + *

It is represented as the raw string encoding for the SHA-256 hash of the certificate + * of the stamp. + * + * @see AppInstallMetadata#getStampCertificateHash() + */ + @NonNull + public Builder setStampCertificateHash(@NonNull String stampCertificateHash) { + this.mStampCertificateHash = Objects.requireNonNull(stampCertificateHash); + return this; + } + + /** + * Set whether the stamp embedded in the APK is trusted or not. + * + * @see AppInstallMetadata#isStampTrusted() + */ + @NonNull + public Builder setIsStampTrusted(boolean isStampTrusted) { + this.mIsStampTrusted = isStampTrusted; + return this; + } + /** * Build {@link AppInstallMetadata}. * diff --git a/core/java/android/content/integrity/AtomicFormula.java b/core/java/android/content/integrity/AtomicFormula.java index d911eabc3b839..977a631cecd85 100644 --- a/core/java/android/content/integrity/AtomicFormula.java +++ b/core/java/android/content/integrity/AtomicFormula.java @@ -47,12 +47,14 @@ public abstract class AtomicFormula extends IntegrityFormula { /** @hide */ @IntDef( value = { - PACKAGE_NAME, - APP_CERTIFICATE, - INSTALLER_NAME, - INSTALLER_CERTIFICATE, - VERSION_CODE, - PRE_INSTALLED, + PACKAGE_NAME, + APP_CERTIFICATE, + INSTALLER_NAME, + INSTALLER_CERTIFICATE, + VERSION_CODE, + PRE_INSTALLED, + STAMP_TRUSTED, + STAMP_CERTIFICATE_HASH, }) @Retention(RetentionPolicy.SOURCE) public @interface Key {} @@ -105,6 +107,20 @@ public abstract class AtomicFormula extends IntegrityFormula { */ 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; + public static final int EQ = 0; public static final int GT = 1; public static final int GTE = 2; @@ -266,9 +282,7 @@ public abstract class AtomicFormula extends IntegrityFormula { } private static boolean isValidOperator(int operator) { - return operator == EQ - || operator == GT - || operator == GTE; + return operator == EQ || operator == GT || operator == GTE; } private static long getLongMetadataValue(AppInstallMetadata appInstallMetadata, int key) { @@ -300,7 +314,8 @@ public abstract class AtomicFormula extends IntegrityFormula { key == PACKAGE_NAME || key == APP_CERTIFICATE || key == INSTALLER_CERTIFICATE - || key == INSTALLER_NAME, + || key == INSTALLER_NAME + || key == STAMP_CERTIFICATE_HASH, String.format( "Key %s cannot be used with StringAtomicFormula", keyToString(key))); mValue = null; @@ -321,7 +336,8 @@ public abstract class AtomicFormula extends IntegrityFormula { key == PACKAGE_NAME || key == APP_CERTIFICATE || key == INSTALLER_CERTIFICATE - || key == INSTALLER_NAME, + || key == INSTALLER_NAME + || key == STAMP_CERTIFICATE_HASH, String.format( "Key %s cannot be used with StringAtomicFormula", keyToString(key))); mValue = value; @@ -329,15 +345,14 @@ public abstract class AtomicFormula extends IntegrityFormula { } /** - * Constructs a new {@link StringAtomicFormula} together with handling the necessary - * hashing for the given key. + * 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 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 and INSTALLER_CERTIFICATES are always delivered in hashed - * form. So the isHashedValue is set to true by default. + *

The APP_CERTIFICATES, INSTALLER_CERTIFICATES, and STAMP_CERTIFICATE_HASH 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. */ @@ -347,13 +362,15 @@ public abstract class AtomicFormula extends IntegrityFormula { key == PACKAGE_NAME || key == APP_CERTIFICATE || key == INSTALLER_CERTIFICATE - || key == INSTALLER_NAME, + || key == INSTALLER_NAME + || key == STAMP_CERTIFICATE_HASH, String.format( "Key %s cannot be used with StringAtomicFormula", keyToString(key))); mValue = hashValue(key, value); mIsHashedValue = key == APP_CERTIFICATE - || key == INSTALLER_CERTIFICATE + || key == INSTALLER_CERTIFICATE + || key == STAMP_CERTIFICATE_HASH ? true : !mValue.equals(value); } @@ -460,6 +477,8 @@ public abstract class AtomicFormula extends IntegrityFormula { return appInstallMetadata.getInstallerCertificates(); case AtomicFormula.INSTALLER_NAME: return Collections.singletonList(appInstallMetadata.getInstallerName()); + case AtomicFormula.STAMP_CERTIFICATE_HASH: + return Collections.singletonList(appInstallMetadata.getStampCertificateHash()); default: throw new IllegalStateException( "Unexpected key in StringAtomicFormula: " + key); @@ -502,7 +521,7 @@ public abstract class AtomicFormula extends IntegrityFormula { public BooleanAtomicFormula(@Key int key) { super(key); checkArgument( - key == PRE_INSTALLED, + key == PRE_INSTALLED || key == STAMP_TRUSTED, String.format( "Key %s cannot be used with BooleanAtomicFormula", keyToString(key))); mValue = null; @@ -519,7 +538,7 @@ public abstract class AtomicFormula extends IntegrityFormula { public BooleanAtomicFormula(@Key int key, boolean value) { super(key); checkArgument( - key == PRE_INSTALLED, + key == PRE_INSTALLED || key == STAMP_TRUSTED, String.format( "Key %s cannot be used with BooleanAtomicFormula", keyToString(key))); mValue = value; @@ -615,6 +634,8 @@ public abstract class AtomicFormula extends IntegrityFormula { switch (key) { case AtomicFormula.PRE_INSTALLED: return appInstallMetadata.isPreInstalled(); + case AtomicFormula.STAMP_TRUSTED: + return appInstallMetadata.isStampTrusted(); default: throw new IllegalStateException( "Unexpected key in BooleanAtomicFormula: " + key); @@ -640,6 +661,10 @@ public abstract class AtomicFormula extends IntegrityFormula { return "INSTALLER_CERTIFICATE"; case PRE_INSTALLED: return "PRE_INSTALLED"; + case STAMP_TRUSTED: + return "STAMP_TRUSTED"; + case STAMP_CERTIFICATE_HASH: + return "STAMP_CERTIFICATE_HASH"; default: throw new IllegalArgumentException("Unknown key " + key); } @@ -664,6 +689,8 @@ public abstract class AtomicFormula extends IntegrityFormula { || key == VERSION_CODE || key == INSTALLER_NAME || key == INSTALLER_CERTIFICATE - || key == PRE_INSTALLED; + || key == PRE_INSTALLED + || key == STAMP_TRUSTED + || key == STAMP_CERTIFICATE_HASH; } } diff --git a/core/java/android/content/integrity/IntegrityFormula.java b/core/java/android/content/integrity/IntegrityFormula.java index c5e5c8a8daad3..fc177721240cf 100644 --- a/core/java/android/content/integrity/IntegrityFormula.java +++ b/core/java/android/content/integrity/IntegrityFormula.java @@ -90,8 +90,7 @@ public abstract class IntegrityFormula { return new BooleanAtomicFormula(AtomicFormula.PRE_INSTALLED, true); } - private Application() { - } + private Application() {} } /** Factory class for creating integrity formulas based on installer. */ @@ -117,26 +116,45 @@ public abstract class IntegrityFormula { */ @NonNull public static IntegrityFormula certificatesContain(@NonNull String installerCertificate) { - return new StringAtomicFormula(AtomicFormula.INSTALLER_CERTIFICATE, - installerCertificate); + return new StringAtomicFormula( + AtomicFormula.INSTALLER_CERTIFICATE, installerCertificate); } - private Installer() { + private Installer() {} + } + + /** Factory class for creating integrity formulas based on source stamp. */ + public static final class SourceStamp { + /** Returns an integrity formula that checks the equality to a stamp certificate hash. */ + @NonNull + public static IntegrityFormula stampCertificateHashEquals( + @NonNull String stampCertificateHash) { + return new StringAtomicFormula( + AtomicFormula.STAMP_CERTIFICATE_HASH, stampCertificateHash); } + + /** + * Returns an integrity formula that is valid when stamp embedded in the APK is NOT trusted. + */ + @NonNull + public static IntegrityFormula notTrusted() { + return new BooleanAtomicFormula(AtomicFormula.STAMP_TRUSTED, /* value= */ false); + } + + private SourceStamp() {} } /** @hide */ @IntDef( value = { - COMPOUND_FORMULA_TAG, - STRING_ATOMIC_FORMULA_TAG, - LONG_ATOMIC_FORMULA_TAG, - BOOLEAN_ATOMIC_FORMULA_TAG, - INSTALLER_ALLOWED_BY_MANIFEST_FORMULA_TAG + COMPOUND_FORMULA_TAG, + STRING_ATOMIC_FORMULA_TAG, + LONG_ATOMIC_FORMULA_TAG, + BOOLEAN_ATOMIC_FORMULA_TAG, + INSTALLER_ALLOWED_BY_MANIFEST_FORMULA_TAG }) @Retention(RetentionPolicy.SOURCE) - @interface Tag { - } + @interface Tag {} /** @hide */ public static final int COMPOUND_FORMULA_TAG = 0; @@ -171,8 +189,8 @@ public abstract class IntegrityFormula { public abstract boolean isAppCertificateFormula(); /** - * Returns true when the formula (or one of its atomic formulas) has installer package name - * or installer certificate as key. + * Returns true when the formula (or one of its atomic formulas) has installer package name or + * installer certificate as key. * * @hide */ @@ -243,15 +261,12 @@ public abstract class IntegrityFormula { return new CompoundFormula(CompoundFormula.AND, Arrays.asList(formulae)); } - /** - * Returns a formula that evaluates to true when {@code formula} evaluates to false. - */ + /** Returns a formula that evaluates to true when {@code formula} evaluates to false. */ @NonNull public static IntegrityFormula not(@NonNull IntegrityFormula formula) { return new CompoundFormula(CompoundFormula.NOT, Arrays.asList(formula)); } // Constructor is package private so it cannot be inherited outside of this package. - IntegrityFormula() { - } + IntegrityFormula() {} } diff --git a/core/tests/coretests/src/android/content/integrity/IntegrityFormulaTest.java b/core/tests/coretests/src/android/content/integrity/IntegrityFormulaTest.java index 62c9c98f4e1d3..7e4c138ccd3c9 100644 --- a/core/tests/coretests/src/android/content/integrity/IntegrityFormulaTest.java +++ b/core/tests/coretests/src/android/content/integrity/IntegrityFormulaTest.java @@ -109,8 +109,8 @@ public class IntegrityFormulaTest { @Test public void createGreaterThanOrEqualsToFormula_versionCode() { int versionCode = 12; - IntegrityFormula formula = IntegrityFormula.Application.versionCodeGreaterThanOrEqualTo( - versionCode); + IntegrityFormula formula = + IntegrityFormula.Application.versionCodeGreaterThanOrEqualTo(versionCode); AtomicFormula.LongAtomicFormula stringAtomicFormula = (AtomicFormula.LongAtomicFormula) formula; @@ -124,11 +124,11 @@ public class IntegrityFormulaTest { public void createIsTrueFormula_preInstalled() { IntegrityFormula formula = IntegrityFormula.Application.isPreInstalled(); - AtomicFormula.BooleanAtomicFormula stringAtomicFormula = + AtomicFormula.BooleanAtomicFormula booleanAtomicFormula = (AtomicFormula.BooleanAtomicFormula) formula; - assertThat(stringAtomicFormula.getKey()).isEqualTo(AtomicFormula.PRE_INSTALLED); - assertThat(stringAtomicFormula.getValue()).isTrue(); + assertThat(booleanAtomicFormula.getKey()).isEqualTo(AtomicFormula.PRE_INSTALLED); + assertThat(booleanAtomicFormula.getValue()).isTrue(); } @Test @@ -136,8 +136,8 @@ public class IntegrityFormulaTest { String packageName = "com.test.package"; String certificateName = "certificate"; IntegrityFormula formula1 = IntegrityFormula.Application.packageNameEquals(packageName); - IntegrityFormula formula2 = IntegrityFormula.Application.certificatesContain( - certificateName); + IntegrityFormula formula2 = + IntegrityFormula.Application.certificatesContain(certificateName); IntegrityFormula compoundFormula = IntegrityFormula.all(formula1, formula2); @@ -149,8 +149,8 @@ public class IntegrityFormulaTest { String packageName = "com.test.package"; String certificateName = "certificate"; IntegrityFormula formula1 = IntegrityFormula.Application.packageNameEquals(packageName); - IntegrityFormula formula2 = IntegrityFormula.Application.certificatesContain( - certificateName); + IntegrityFormula formula2 = + IntegrityFormula.Application.certificatesContain(certificateName); IntegrityFormula compoundFormula = IntegrityFormula.any(formula1, formula2); @@ -166,4 +166,29 @@ public class IntegrityFormulaTest { assertThat(compoundFormula.getTag()).isEqualTo(COMPOUND_FORMULA_TAG); } + + @Test + public void createIsTrueFormula_stampNotTrusted() { + IntegrityFormula formula = IntegrityFormula.SourceStamp.notTrusted(); + + AtomicFormula.BooleanAtomicFormula booleanAtomicFormula = + (AtomicFormula.BooleanAtomicFormula) formula; + + assertThat(booleanAtomicFormula.getKey()).isEqualTo(AtomicFormula.STAMP_TRUSTED); + assertThat(booleanAtomicFormula.getValue()).isFalse(); + } + + @Test + public void createEqualsFormula_stampCertificateHash() { + String stampCertificateHash = "test-cert"; + IntegrityFormula formula = + IntegrityFormula.SourceStamp.stampCertificateHashEquals(stampCertificateHash); + + AtomicFormula.StringAtomicFormula stringAtomicFormula = + (AtomicFormula.StringAtomicFormula) formula; + + assertThat(stringAtomicFormula.getKey()).isEqualTo(AtomicFormula.STAMP_CERTIFICATE_HASH); + assertThat(stringAtomicFormula.getValue()).matches(stampCertificateHash); + assertThat(stringAtomicFormula.getIsHashedValue()).isTrue(); + } } diff --git a/services/core/java/com/android/server/integrity/engine/RuleLoader.java b/services/core/java/com/android/server/integrity/engine/RuleLoader.java deleted file mode 100644 index 4ba2bfb00d05c..0000000000000 --- a/services/core/java/com/android/server/integrity/engine/RuleLoader.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * 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 com.android.server.integrity.engine; - -import android.content.integrity.Rule; - -import java.util.ArrayList; -import java.util.List; - -/** - * A helper class for loading rules to the rule evaluation engine. - * - *

Expose fine-grained APIs for loading rules to be passed to the rule evaluation engine. - * - *

It supports: - *

    - *
  • Loading rules based on some keys, such as PACKAGE_NAME and APP_CERT.
  • - *
- * - *

It does NOT support: - *

    - *
  • Loading the list of all rules.
  • - *
  • Merging rules resulting from different APIs.
  • - *
- */ -final class RuleLoader { - - List loadRulesByPackageName(String packageName) { - // TODO: Add logic based on rule storage. - return new ArrayList<>(); - } - - List loadRulesByAppCertificate(String appCertificate) { - // TODO: Add logic based on rule storage. - return new ArrayList<>(); - } - - List loadRulesByInstallerName(String installerName) { - // TODO: Add logic based on rule storage. - return new ArrayList<>(); - } - - List loadRulesByInstallerCertificate(String installerCertificate) { - // TODO: Add logic based on rule storage. - return new ArrayList<>(); - } -} diff --git a/services/core/java/com/android/server/integrity/parser/RuleBinaryParser.java b/services/core/java/com/android/server/integrity/parser/RuleBinaryParser.java index 11e8d91dde124..a290eb3a3e2fa 100644 --- a/services/core/java/com/android/server/integrity/parser/RuleBinaryParser.java +++ b/services/core/java/com/android/server/integrity/parser/RuleBinaryParser.java @@ -68,8 +68,7 @@ public class RuleBinaryParser implements RuleParser { } private List parseRules( - RandomAccessInputStream randomAccessInputStream, - List indexRanges) + RandomAccessInputStream randomAccessInputStream, List indexRanges) throws IOException { // Read the rule binary file format version. @@ -96,8 +95,7 @@ public class RuleBinaryParser implements RuleParser { } private List parseIndexedRules( - RandomAccessInputStream randomAccessInputStream, - List indexRanges) + RandomAccessInputStream randomAccessInputStream, List indexRanges) throws IOException { List parsedRules = new ArrayList<>(); @@ -172,6 +170,7 @@ public class RuleBinaryParser implements RuleParser { case AtomicFormula.APP_CERTIFICATE: case AtomicFormula.INSTALLER_NAME: case AtomicFormula.INSTALLER_CERTIFICATE: + case AtomicFormula.STAMP_CERTIFICATE_HASH: boolean isHashedValue = bitInputStream.getNext(IS_HASHED_BITS) == 1; int valueSize = bitInputStream.getNext(VALUE_SIZE_BITS); String stringValue = getStringValue(bitInputStream, valueSize, isHashedValue); @@ -183,6 +182,7 @@ public class RuleBinaryParser implements RuleParser { long longValue = (upper << 32) | lower; return new AtomicFormula.LongAtomicFormula(key, operator, longValue); case AtomicFormula.PRE_INSTALLED: + case AtomicFormula.STAMP_TRUSTED: boolean booleanValue = getBooleanValue(bitInputStream); return new AtomicFormula.BooleanAtomicFormula(key, booleanValue); default: diff --git a/services/tests/servicestests/src/com/android/server/integrity/parser/RuleBinaryParserTest.java b/services/tests/servicestests/src/com/android/server/integrity/parser/RuleBinaryParserTest.java index 3dc26afdb9af3..ab21ab05ab5f8 100644 --- a/services/tests/servicestests/src/com/android/server/integrity/parser/RuleBinaryParserTest.java +++ b/services/tests/servicestests/src/com/android/server/integrity/parser/RuleBinaryParserTest.java @@ -73,7 +73,7 @@ public class RuleBinaryParserTest { private static final String APP_CERTIFICATE = getBits(AtomicFormula.APP_CERTIFICATE, KEY_BITS); private static final String VERSION_CODE = getBits(AtomicFormula.VERSION_CODE, KEY_BITS); private static final String PRE_INSTALLED = getBits(AtomicFormula.PRE_INSTALLED, KEY_BITS); - private static final int INVALID_KEY_VALUE = 6; + private static final int INVALID_KEY_VALUE = 8; private static final String INVALID_KEY = getBits(INVALID_KEY_VALUE, KEY_BITS); private static final String EQ = getBits(AtomicFormula.EQ, OPERATOR_BITS);