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