1 /*
2  * Copyright (C) 2020 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 com.android.server.integrity;
18 
19 import static com.android.server.integrity.model.IndexingFileConstants.INDEXING_BLOCK_SIZE;
20 
21 import static com.google.common.truth.Truth.assertThat;
22 
23 import android.content.integrity.AppInstallMetadata;
24 import android.content.integrity.AtomicFormula;
25 import android.content.integrity.AtomicFormula.LongAtomicFormula;
26 import android.content.integrity.AtomicFormula.StringAtomicFormula;
27 import android.content.integrity.CompoundFormula;
28 import android.content.integrity.Rule;
29 import android.util.Slog;
30 
31 import com.android.server.integrity.parser.RuleBinaryParser;
32 import com.android.server.integrity.serializer.RuleBinarySerializer;
33 
34 import org.junit.After;
35 import org.junit.Before;
36 import org.junit.Test;
37 import org.junit.runner.RunWith;
38 import org.junit.runners.JUnit4;
39 
40 import java.io.File;
41 import java.nio.file.Files;
42 import java.nio.file.Path;
43 import java.util.ArrayList;
44 import java.util.Arrays;
45 import java.util.Collections;
46 import java.util.Comparator;
47 import java.util.List;
48 
49 /** Unit test for {@link IntegrityFileManager} */
50 @RunWith(JUnit4.class)
51 public class IntegrityFileManagerTest {
52     private static final String TAG = "IntegrityFileManagerTest";
53 
54     private static final String VERSION = "version";
55     private static final String RULE_PROVIDER = "rule_provider";
56 
57     private File mTmpDir;
58 
59     // under test
60     private IntegrityFileManager mIntegrityFileManager;
61 
62     @Before
setUp()63     public void setUp() throws Exception {
64         mTmpDir = Files.createTempDirectory("IntegrityFileManagerTest").toFile();
65         Slog.i(TAG, "Using temp directory " + mTmpDir);
66 
67         // Use Xml Parser/Serializer to help with debugging since we can just print the file.
68         mIntegrityFileManager =
69                 new IntegrityFileManager(
70                         new RuleBinaryParser(), new RuleBinarySerializer(), mTmpDir);
71         Files.walk(mTmpDir.toPath())
72                 .forEach(
73                         path -> {
74                             Slog.i(TAG, "before " + path);
75                         });
76     }
77 
78     @After
tearDown()79     public void tearDown() throws Exception {
80         Files.walk(mTmpDir.toPath())
81                 .forEach(
82                         path -> {
83                             Slog.i(TAG, "after " + path);
84                         });
85         // Sorting paths in reverse order guarantees that we delete inside files before deleting
86         // directory.
87         Files.walk(mTmpDir.toPath())
88                 .sorted(Comparator.reverseOrder())
89                 .map(Path::toFile)
90                 .forEach(File::delete);
91     }
92 
93     @Test
testGetMetadata()94     public void testGetMetadata() throws Exception {
95         assertThat(mIntegrityFileManager.readMetadata()).isNull();
96         mIntegrityFileManager.writeRules(VERSION, RULE_PROVIDER, Collections.EMPTY_LIST);
97 
98         assertThat(mIntegrityFileManager.readMetadata()).isNotNull();
99         assertThat(mIntegrityFileManager.readMetadata().getVersion()).isEqualTo(VERSION);
100         assertThat(mIntegrityFileManager.readMetadata().getRuleProvider()).isEqualTo(RULE_PROVIDER);
101     }
102 
103     @Test
testIsInitialized()104     public void testIsInitialized() throws Exception {
105         assertThat(mIntegrityFileManager.initialized()).isFalse();
106         mIntegrityFileManager.writeRules(VERSION, RULE_PROVIDER, Collections.EMPTY_LIST);
107         assertThat(mIntegrityFileManager.initialized()).isTrue();
108     }
109 
110     @Test
testGetRules()111     public void testGetRules() throws Exception {
112         String packageName = "package";
113         String packageCert = "cert";
114         int version = 123;
115         Rule packageNameRule = getPackageNameIndexedRule(packageName);
116         Rule packageCertRule = getAppCertificateIndexedRule(packageCert);
117         Rule versionCodeRule =
118                 new Rule(
119                         new LongAtomicFormula(
120                                 AtomicFormula.VERSION_CODE, AtomicFormula.EQ, version),
121                         Rule.DENY);
122         Rule randomRule =
123                 new Rule(
124                         new CompoundFormula(
125                                 CompoundFormula.OR,
126                                 Arrays.asList(
127                                         new StringAtomicFormula(
128                                                 AtomicFormula.PACKAGE_NAME,
129                                                 "abc",
130                                                 /* isHashedValue= */ false),
131                                         new LongAtomicFormula(
132                                                 AtomicFormula.VERSION_CODE,
133                                                 AtomicFormula.EQ,
134                                                 version))),
135                         Rule.DENY);
136 
137         List<Rule> rules =
138                 Arrays.asList(packageNameRule, packageCertRule, versionCodeRule, randomRule);
139         mIntegrityFileManager.writeRules(VERSION, RULE_PROVIDER, rules);
140 
141         AppInstallMetadata appInstallMetadata =
142                 new AppInstallMetadata.Builder()
143                         .setPackageName(packageName)
144                         .setAppCertificates(Collections.singletonList(packageCert))
145                         .setAppCertificateLineage(Collections.singletonList(packageCert))
146                         .setVersionCode(version)
147                         .setInstallerName("abc")
148                         .setInstallerCertificates(Collections.singletonList("abc"))
149                         .setIsPreInstalled(true)
150                         .build();
151         List<Rule> rulesFetched = mIntegrityFileManager.readRules(appInstallMetadata);
152 
153         assertThat(rulesFetched)
154                 .containsExactly(packageNameRule, packageCertRule, versionCodeRule, randomRule);
155     }
156 
157     @Test
testGetRules_indexedForManyRules()158     public void testGetRules_indexedForManyRules() throws Exception {
159         String packageName = "package";
160         String installerName = "installer";
161         String appCertificate = "cert";
162 
163         // Create a rule set with 2500 package name indexed, 2500 app certificate indexed and
164         // 500 unindexed rules.
165         List<Rule> rules = new ArrayList<>();
166         int unindexedRuleCount = 70;
167 
168         for (int i = 0; i < 2500; i++) {
169             rules.add(getPackageNameIndexedRule(String.format("%s%04d", packageName, i)));
170             rules.add(getAppCertificateIndexedRule(String.format("%s%04d", appCertificate, i)));
171         }
172 
173         for (int i = 0; i < unindexedRuleCount; i++) {
174             rules.add(getInstallerCertificateRule(String.format("%s%04d", installerName, i)));
175         }
176 
177         // Write the rules and get them indexed.
178         mIntegrityFileManager.writeRules(VERSION, RULE_PROVIDER, rules);
179 
180         // Read the rules for a specific rule.
181         String installedPackageName = String.format("%s%04d", packageName, 264);
182         String installedAppCertificate = String.format("%s%04d", appCertificate, 1264);
183         AppInstallMetadata appInstallMetadata =
184                 new AppInstallMetadata.Builder()
185                         .setPackageName(installedPackageName)
186                         .setAppCertificates(Collections.singletonList(installedAppCertificate))
187                         .setAppCertificateLineage(
188                                 Collections.singletonList(installedAppCertificate))
189                         .setVersionCode(250)
190                         .setInstallerName("abc")
191                         .setInstallerCertificates(Collections.singletonList("abc"))
192                         .setIsPreInstalled(true)
193                         .build();
194         List<Rule> rulesFetched = mIntegrityFileManager.readRules(appInstallMetadata);
195 
196         // Verify that we do not load all the rules and we have the necessary rules to evaluate.
197         assertThat(rulesFetched.size())
198                 .isEqualTo(INDEXING_BLOCK_SIZE * 2 + unindexedRuleCount);
199         assertThat(rulesFetched)
200                 .containsAtLeast(
201                         getPackageNameIndexedRule(installedPackageName),
202                         getAppCertificateIndexedRule(installedAppCertificate));
203     }
204 
getPackageNameIndexedRule(String packageName)205     private Rule getPackageNameIndexedRule(String packageName) {
206         return new Rule(
207                 new StringAtomicFormula(
208                         AtomicFormula.PACKAGE_NAME, packageName, /* isHashedValue= */false),
209                 Rule.DENY);
210     }
211 
getAppCertificateIndexedRule(String appCertificate)212     private Rule getAppCertificateIndexedRule(String appCertificate) {
213         return new Rule(
214                 new StringAtomicFormula(
215                         AtomicFormula.APP_CERTIFICATE,
216                         appCertificate, /* isHashedValue= */ false),
217                 Rule.DENY);
218     }
219 
getInstallerCertificateRule(String installerCert)220     private Rule getInstallerCertificateRule(String installerCert) {
221         return new Rule(
222                 new StringAtomicFormula(
223                         AtomicFormula.INSTALLER_NAME, installerCert, /* isHashedValue= */false),
224                 Rule.DENY);
225     }
226 
227     @Test
testStagingDirectoryCleared()228     public void testStagingDirectoryCleared() throws Exception {
229         // We must push rules two times to ensure that staging directory is empty because we cleared
230         // it, rather than because original rules directory is empty.
231         mIntegrityFileManager.writeRules(VERSION, RULE_PROVIDER, Collections.EMPTY_LIST);
232         mIntegrityFileManager.writeRules(VERSION, RULE_PROVIDER, Collections.EMPTY_LIST);
233 
234         assertStagingDirectoryCleared();
235     }
236 
assertStagingDirectoryCleared()237     private void assertStagingDirectoryCleared() {
238         File stagingDir = new File(mTmpDir, "integrity_staging");
239         assertThat(stagingDir.exists()).isTrue();
240         assertThat(stagingDir.isDirectory()).isTrue();
241         assertThat(stagingDir.listFiles()).isEmpty();
242     }
243 }
244