1 /*
2  * Copyright (C) 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.content.integrity;
18 
19 import android.annotation.IntDef;
20 import android.annotation.NonNull;
21 import android.annotation.SystemApi;
22 import android.content.integrity.AtomicFormula.BooleanAtomicFormula;
23 import android.content.integrity.AtomicFormula.LongAtomicFormula;
24 import android.content.integrity.AtomicFormula.StringAtomicFormula;
25 import android.os.Parcel;
26 import android.os.Parcelable;
27 
28 import com.android.internal.annotations.VisibleForTesting;
29 
30 import java.lang.annotation.Retention;
31 import java.lang.annotation.RetentionPolicy;
32 import java.util.Arrays;
33 
34 /**
35  * Represents a rule logic/content.
36  *
37  * @hide
38  */
39 @SystemApi
40 @VisibleForTesting
41 public abstract class IntegrityFormula {
42 
43     /** Factory class for creating integrity formulas based on the app being installed. */
44     public static final class Application {
45         /** Returns an integrity formula that checks the equality to a package name. */
46         @NonNull
packageNameEquals(@onNull String packageName)47         public static IntegrityFormula packageNameEquals(@NonNull String packageName) {
48             return new StringAtomicFormula(AtomicFormula.PACKAGE_NAME, packageName);
49         }
50 
51         /**
52          * Returns an integrity formula that checks if the app certificates contain the string
53          * provided by the appCertificate parameter.
54          */
55         @NonNull
certificatesContain(@onNull String appCertificate)56         public static IntegrityFormula certificatesContain(@NonNull String appCertificate) {
57             return new StringAtomicFormula(AtomicFormula.APP_CERTIFICATE, appCertificate);
58         }
59 
60         /**
61          * Returns an integrity formula that checks if the app certificate lineage contains the
62          * string provided by the appCertificate parameter.
63          */
64         @NonNull
certificateLineageContains(@onNull String appCertificate)65         public static IntegrityFormula certificateLineageContains(@NonNull String appCertificate) {
66             return new StringAtomicFormula(AtomicFormula.APP_CERTIFICATE_LINEAGE, appCertificate);
67         }
68 
69         /** Returns an integrity formula that checks the equality to a version code. */
70         @NonNull
versionCodeEquals(@onNull long versionCode)71         public static IntegrityFormula versionCodeEquals(@NonNull long versionCode) {
72             return new LongAtomicFormula(AtomicFormula.VERSION_CODE, AtomicFormula.EQ, versionCode);
73         }
74 
75         /**
76          * Returns an integrity formula that checks the app's version code is greater than the
77          * provided value.
78          */
79         @NonNull
versionCodeGreaterThan(@onNull long versionCode)80         public static IntegrityFormula versionCodeGreaterThan(@NonNull long versionCode) {
81             return new LongAtomicFormula(AtomicFormula.VERSION_CODE, AtomicFormula.GT, versionCode);
82         }
83 
84         /**
85          * Returns an integrity formula that checks the app's version code is greater than or equal
86          * to the provided value.
87          */
88         @NonNull
versionCodeGreaterThanOrEqualTo(@onNull long versionCode)89         public static IntegrityFormula versionCodeGreaterThanOrEqualTo(@NonNull long versionCode) {
90             return new LongAtomicFormula(
91                     AtomicFormula.VERSION_CODE, AtomicFormula.GTE, versionCode);
92         }
93 
94         /** Returns an integrity formula that is valid when app is pre-installed. */
95         @NonNull
isPreInstalled()96         public static IntegrityFormula isPreInstalled() {
97             return new BooleanAtomicFormula(AtomicFormula.PRE_INSTALLED, true);
98         }
99 
Application()100         private Application() {}
101     }
102 
103     /** Factory class for creating integrity formulas based on installer. */
104     public static final class Installer {
105         /** Returns an integrity formula that checks the equality to an installer name. */
106         @NonNull
packageNameEquals(@onNull String installerName)107         public static IntegrityFormula packageNameEquals(@NonNull String installerName) {
108             return new StringAtomicFormula(AtomicFormula.INSTALLER_NAME, installerName);
109         }
110 
111         /**
112          * An static formula that evaluates to true if the installer is NOT allowed according to the
113          * "allowed installer" field in the android manifest.
114          */
115         @NonNull
notAllowedByManifest()116         public static IntegrityFormula notAllowedByManifest() {
117             return not(new InstallerAllowedByManifestFormula());
118         }
119 
120         /**
121          * Returns an integrity formula that checks if the installer certificates contain {@code
122          * installerCertificate}.
123          */
124         @NonNull
certificatesContain(@onNull String installerCertificate)125         public static IntegrityFormula certificatesContain(@NonNull String installerCertificate) {
126             return new StringAtomicFormula(
127                     AtomicFormula.INSTALLER_CERTIFICATE, installerCertificate);
128         }
129 
Installer()130         private Installer() {}
131     }
132 
133     /** Factory class for creating integrity formulas based on source stamp. */
134     public static final class SourceStamp {
135         /** Returns an integrity formula that checks the equality to a stamp certificate hash. */
136         @NonNull
stampCertificateHashEquals( @onNull String stampCertificateHash)137         public static IntegrityFormula stampCertificateHashEquals(
138                 @NonNull String stampCertificateHash) {
139             return new StringAtomicFormula(
140                     AtomicFormula.STAMP_CERTIFICATE_HASH, stampCertificateHash);
141         }
142 
143         /**
144          * Returns an integrity formula that is valid when stamp embedded in the APK is NOT trusted.
145          */
146         @NonNull
notTrusted()147         public static IntegrityFormula notTrusted() {
148             return new BooleanAtomicFormula(AtomicFormula.STAMP_TRUSTED, /* value= */ false);
149         }
150 
SourceStamp()151         private SourceStamp() {}
152     }
153 
154     /** @hide */
155     @IntDef(
156             value = {
157                 COMPOUND_FORMULA_TAG,
158                 STRING_ATOMIC_FORMULA_TAG,
159                 LONG_ATOMIC_FORMULA_TAG,
160                 BOOLEAN_ATOMIC_FORMULA_TAG,
161                 INSTALLER_ALLOWED_BY_MANIFEST_FORMULA_TAG
162             })
163     @Retention(RetentionPolicy.SOURCE)
164     @interface Tag {}
165 
166     /** @hide */
167     public static final int COMPOUND_FORMULA_TAG = 0;
168     /** @hide */
169     public static final int STRING_ATOMIC_FORMULA_TAG = 1;
170     /** @hide */
171     public static final int LONG_ATOMIC_FORMULA_TAG = 2;
172     /** @hide */
173     public static final int BOOLEAN_ATOMIC_FORMULA_TAG = 3;
174     /** @hide */
175     public static final int INSTALLER_ALLOWED_BY_MANIFEST_FORMULA_TAG = 4;
176 
177     /**
178      * Returns the tag that identifies the current class.
179      *
180      * @hide
181      */
getTag()182     public abstract @Tag int getTag();
183 
184     /**
185      * Returns true when the integrity formula is satisfied by the {@code appInstallMetadata}.
186      *
187      * @hide
188      */
matches(AppInstallMetadata appInstallMetadata)189     public abstract boolean matches(AppInstallMetadata appInstallMetadata);
190 
191     /**
192      * Returns true when the formula (or one of its atomic formulas) has app certificate as key.
193      *
194      * @hide
195      */
isAppCertificateFormula()196     public abstract boolean isAppCertificateFormula();
197 
198     /**
199      * Returns true when the formula (or one of its atomic formulas) has app certificate lineage as
200      * key.
201      *
202      * @hide
203      */
isAppCertificateLineageFormula()204     public abstract boolean isAppCertificateLineageFormula();
205 
206     /**
207      * Returns true when the formula (or one of its atomic formulas) has installer package name or
208      * installer certificate as key.
209      *
210      * @hide
211      */
isInstallerFormula()212     public abstract boolean isInstallerFormula();
213 
214     /**
215      * Write an {@link IntegrityFormula} to {@link android.os.Parcel}.
216      *
217      * <p>This helper method is needed because non-final class/interface are not allowed to be
218      * {@link Parcelable}.
219      *
220      * @throws IllegalArgumentException if {@link IntegrityFormula} is not a recognized subclass
221      * @hide
222      */
writeToParcel( @onNull IntegrityFormula formula, @NonNull Parcel dest, int flags)223     public static void writeToParcel(
224             @NonNull IntegrityFormula formula, @NonNull Parcel dest, int flags) {
225         dest.writeInt(formula.getTag());
226         ((Parcelable) formula).writeToParcel(dest, flags);
227     }
228 
229     /**
230      * Read a {@link IntegrityFormula} from a {@link android.os.Parcel}.
231      *
232      * <p>We need this (hacky) helper method because non-final class/interface cannot be {@link
233      * Parcelable} (api lint error).
234      *
235      * @throws IllegalArgumentException if the parcel cannot be parsed
236      * @hide
237      */
238     @NonNull
readFromParcel(@onNull Parcel in)239     public static IntegrityFormula readFromParcel(@NonNull Parcel in) {
240         int tag = in.readInt();
241         switch (tag) {
242             case COMPOUND_FORMULA_TAG:
243                 return CompoundFormula.CREATOR.createFromParcel(in);
244             case STRING_ATOMIC_FORMULA_TAG:
245                 return StringAtomicFormula.CREATOR.createFromParcel(in);
246             case LONG_ATOMIC_FORMULA_TAG:
247                 return LongAtomicFormula.CREATOR.createFromParcel(in);
248             case BOOLEAN_ATOMIC_FORMULA_TAG:
249                 return BooleanAtomicFormula.CREATOR.createFromParcel(in);
250             case INSTALLER_ALLOWED_BY_MANIFEST_FORMULA_TAG:
251                 return InstallerAllowedByManifestFormula.CREATOR.createFromParcel(in);
252             default:
253                 throw new IllegalArgumentException("Unknown formula tag " + tag);
254         }
255     }
256 
257     /**
258      * Returns a formula that evaluates to true when any formula in {@code formulae} evaluates to
259      * true.
260      *
261      * <p>Throws an {@link IllegalArgumentException} if formulae has less than two elements.
262      */
263     @NonNull
any(@onNull IntegrityFormula... formulae)264     public static IntegrityFormula any(@NonNull IntegrityFormula... formulae) {
265         return new CompoundFormula(CompoundFormula.OR, Arrays.asList(formulae));
266     }
267 
268     /**
269      * Returns a formula that evaluates to true when all formula in {@code formulae} evaluates to
270      * true.
271      *
272      * <p>Throws an {@link IllegalArgumentException} if formulae has less than two elements.
273      */
274     @NonNull
all(@onNull IntegrityFormula... formulae)275     public static IntegrityFormula all(@NonNull IntegrityFormula... formulae) {
276         return new CompoundFormula(CompoundFormula.AND, Arrays.asList(formulae));
277     }
278 
279     /** Returns a formula that evaluates to true when {@code formula} evaluates to false. */
280     @NonNull
not(@onNull IntegrityFormula formula)281     public static IntegrityFormula not(@NonNull IntegrityFormula formula) {
282         return new CompoundFormula(CompoundFormula.NOT, Arrays.asList(formula));
283     }
284 
285     // Constructor is package private so it cannot be inherited outside of this package.
IntegrityFormula()286     IntegrityFormula() {}
287 }
288