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 {@code
53          * appCertificate}.
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         /** Returns an integrity formula that checks the equality to a version code. */
61         @NonNull
versionCodeEquals(@onNull long versionCode)62         public static IntegrityFormula versionCodeEquals(@NonNull long versionCode) {
63             return new LongAtomicFormula(AtomicFormula.VERSION_CODE, AtomicFormula.EQ, versionCode);
64         }
65 
66         /**
67          * Returns an integrity formula that checks the app's version code is greater than the
68          * provided value.
69          */
70         @NonNull
versionCodeGreaterThan(@onNull long versionCode)71         public static IntegrityFormula versionCodeGreaterThan(@NonNull long versionCode) {
72             return new LongAtomicFormula(AtomicFormula.VERSION_CODE, AtomicFormula.GT, versionCode);
73         }
74 
75         /**
76          * Returns an integrity formula that checks the app's version code is greater than or equal
77          * to the provided value.
78          */
79         @NonNull
versionCodeGreaterThanOrEqualTo(@onNull long versionCode)80         public static IntegrityFormula versionCodeGreaterThanOrEqualTo(@NonNull long versionCode) {
81             return new LongAtomicFormula(
82                     AtomicFormula.VERSION_CODE, AtomicFormula.GTE, versionCode);
83         }
84 
85         /** Returns an integrity formula that is valid when app is pre-installed. */
86         @NonNull
isPreInstalled()87         public static IntegrityFormula isPreInstalled() {
88             return new BooleanAtomicFormula(AtomicFormula.PRE_INSTALLED, true);
89         }
90 
Application()91         private Application() {}
92     }
93 
94     /** Factory class for creating integrity formulas based on installer. */
95     public static final class Installer {
96         /** Returns an integrity formula that checks the equality to an installer name. */
97         @NonNull
packageNameEquals(@onNull String installerName)98         public static IntegrityFormula packageNameEquals(@NonNull String installerName) {
99             return new StringAtomicFormula(AtomicFormula.INSTALLER_NAME, installerName);
100         }
101 
102         /**
103          * An static formula that evaluates to true if the installer is NOT allowed according to the
104          * "allowed installer" field in the android manifest.
105          */
106         @NonNull
notAllowedByManifest()107         public static IntegrityFormula notAllowedByManifest() {
108             return not(new InstallerAllowedByManifestFormula());
109         }
110 
111         /**
112          * Returns an integrity formula that checks if the installer certificates contain {@code
113          * installerCertificate}.
114          */
115         @NonNull
certificatesContain(@onNull String installerCertificate)116         public static IntegrityFormula certificatesContain(@NonNull String installerCertificate) {
117             return new StringAtomicFormula(
118                     AtomicFormula.INSTALLER_CERTIFICATE, installerCertificate);
119         }
120 
Installer()121         private Installer() {}
122     }
123 
124     /** Factory class for creating integrity formulas based on source stamp. */
125     public static final class SourceStamp {
126         /** Returns an integrity formula that checks the equality to a stamp certificate hash. */
127         @NonNull
stampCertificateHashEquals( @onNull String stampCertificateHash)128         public static IntegrityFormula stampCertificateHashEquals(
129                 @NonNull String stampCertificateHash) {
130             return new StringAtomicFormula(
131                     AtomicFormula.STAMP_CERTIFICATE_HASH, stampCertificateHash);
132         }
133 
134         /**
135          * Returns an integrity formula that is valid when stamp embedded in the APK is NOT trusted.
136          */
137         @NonNull
notTrusted()138         public static IntegrityFormula notTrusted() {
139             return new BooleanAtomicFormula(AtomicFormula.STAMP_TRUSTED, /* value= */ false);
140         }
141 
SourceStamp()142         private SourceStamp() {}
143     }
144 
145     /** @hide */
146     @IntDef(
147             value = {
148                 COMPOUND_FORMULA_TAG,
149                 STRING_ATOMIC_FORMULA_TAG,
150                 LONG_ATOMIC_FORMULA_TAG,
151                 BOOLEAN_ATOMIC_FORMULA_TAG,
152                 INSTALLER_ALLOWED_BY_MANIFEST_FORMULA_TAG
153             })
154     @Retention(RetentionPolicy.SOURCE)
155     @interface Tag {}
156 
157     /** @hide */
158     public static final int COMPOUND_FORMULA_TAG = 0;
159     /** @hide */
160     public static final int STRING_ATOMIC_FORMULA_TAG = 1;
161     /** @hide */
162     public static final int LONG_ATOMIC_FORMULA_TAG = 2;
163     /** @hide */
164     public static final int BOOLEAN_ATOMIC_FORMULA_TAG = 3;
165     /** @hide */
166     public static final int INSTALLER_ALLOWED_BY_MANIFEST_FORMULA_TAG = 4;
167 
168     /**
169      * Returns the tag that identifies the current class.
170      *
171      * @hide
172      */
getTag()173     public abstract @Tag int getTag();
174 
175     /**
176      * Returns true when the integrity formula is satisfied by the {@code appInstallMetadata}.
177      *
178      * @hide
179      */
matches(AppInstallMetadata appInstallMetadata)180     public abstract boolean matches(AppInstallMetadata appInstallMetadata);
181 
182     /**
183      * Returns true when the formula (or one of its atomic formulas) has app certificate as key.
184      *
185      * @hide
186      */
isAppCertificateFormula()187     public abstract boolean isAppCertificateFormula();
188 
189     /**
190      * Returns true when the formula (or one of its atomic formulas) has installer package name or
191      * installer certificate as key.
192      *
193      * @hide
194      */
isInstallerFormula()195     public abstract boolean isInstallerFormula();
196 
197     /**
198      * Write an {@link IntegrityFormula} to {@link android.os.Parcel}.
199      *
200      * <p>This helper method is needed because non-final class/interface are not allowed to be
201      * {@link Parcelable}.
202      *
203      * @throws IllegalArgumentException if {@link IntegrityFormula} is not a recognized subclass
204      * @hide
205      */
writeToParcel( @onNull IntegrityFormula formula, @NonNull Parcel dest, int flags)206     public static void writeToParcel(
207             @NonNull IntegrityFormula formula, @NonNull Parcel dest, int flags) {
208         dest.writeInt(formula.getTag());
209         ((Parcelable) formula).writeToParcel(dest, flags);
210     }
211 
212     /**
213      * Read a {@link IntegrityFormula} from a {@link android.os.Parcel}.
214      *
215      * <p>We need this (hacky) helper method because non-final class/interface cannot be {@link
216      * Parcelable} (api lint error).
217      *
218      * @throws IllegalArgumentException if the parcel cannot be parsed
219      * @hide
220      */
221     @NonNull
readFromParcel(@onNull Parcel in)222     public static IntegrityFormula readFromParcel(@NonNull Parcel in) {
223         int tag = in.readInt();
224         switch (tag) {
225             case COMPOUND_FORMULA_TAG:
226                 return CompoundFormula.CREATOR.createFromParcel(in);
227             case STRING_ATOMIC_FORMULA_TAG:
228                 return StringAtomicFormula.CREATOR.createFromParcel(in);
229             case LONG_ATOMIC_FORMULA_TAG:
230                 return LongAtomicFormula.CREATOR.createFromParcel(in);
231             case BOOLEAN_ATOMIC_FORMULA_TAG:
232                 return BooleanAtomicFormula.CREATOR.createFromParcel(in);
233             case INSTALLER_ALLOWED_BY_MANIFEST_FORMULA_TAG:
234                 return InstallerAllowedByManifestFormula.CREATOR.createFromParcel(in);
235             default:
236                 throw new IllegalArgumentException("Unknown formula tag " + tag);
237         }
238     }
239 
240     /**
241      * Returns a formula that evaluates to true when any formula in {@code formulae} evaluates to
242      * true.
243      *
244      * <p>Throws an {@link IllegalArgumentException} if formulae has less than two elements.
245      */
246     @NonNull
any(@onNull IntegrityFormula... formulae)247     public static IntegrityFormula any(@NonNull IntegrityFormula... formulae) {
248         return new CompoundFormula(CompoundFormula.OR, Arrays.asList(formulae));
249     }
250 
251     /**
252      * Returns a formula that evaluates to true when all formula in {@code formulae} evaluates to
253      * true.
254      *
255      * <p>Throws an {@link IllegalArgumentException} if formulae has less than two elements.
256      */
257     @NonNull
all(@onNull IntegrityFormula... formulae)258     public static IntegrityFormula all(@NonNull IntegrityFormula... formulae) {
259         return new CompoundFormula(CompoundFormula.AND, Arrays.asList(formulae));
260     }
261 
262     /** Returns a formula that evaluates to true when {@code formula} evaluates to false. */
263     @NonNull
not(@onNull IntegrityFormula formula)264     public static IntegrityFormula not(@NonNull IntegrityFormula formula) {
265         return new CompoundFormula(CompoundFormula.NOT, Arrays.asList(formula));
266     }
267 
268     // Constructor is package private so it cannot be inherited outside of this package.
IntegrityFormula()269     IntegrityFormula() {}
270 }
271