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 android.security.cts; 18 19 import static com.google.common.truth.Truth.assertThat; 20 21 import static org.junit.Assert.assertFalse; 22 import static org.junit.Assert.assertThrows; 23 import static org.junit.Assert.assertTrue; 24 import static org.junit.Assume.assumeTrue; 25 26 import android.content.Context; 27 import android.content.pm.PackageManager; 28 import android.os.Build; 29 import android.os.Environment; 30 import android.platform.test.annotations.AppModeFull; 31 import android.platform.test.annotations.RequiresFlagsEnabled; 32 import android.platform.test.annotations.RestrictedBuildTest; 33 import android.platform.test.flag.junit.CheckFlagsRule; 34 import android.platform.test.flag.junit.DeviceFlagsValueProvider; 35 import android.security.FileIntegrityManager; 36 import android.security.Flags; 37 import android.util.Log; 38 39 import androidx.test.platform.app.InstrumentationRegistry; 40 import androidx.test.runner.AndroidJUnit4; 41 42 import com.android.compatibility.common.util.AdoptShellPermissionsRule; 43 import com.android.compatibility.common.util.ApiTest; 44 import com.android.compatibility.common.util.CddTest; 45 import com.android.compatibility.common.util.PropertyUtil; 46 47 import org.junit.After; 48 import org.junit.Before; 49 import org.junit.Rule; 50 import org.junit.Test; 51 import org.junit.runner.RunWith; 52 53 import java.io.ByteArrayInputStream; 54 import java.io.ByteArrayOutputStream; 55 import java.io.File; 56 import java.io.FileOutputStream; 57 import java.io.IOException; 58 import java.io.InputStream; 59 import java.security.cert.CertificateException; 60 import java.security.cert.CertificateFactory; 61 import java.security.cert.X509Certificate; 62 import java.util.ArrayList; 63 import java.util.Arrays; 64 import java.util.HexFormat; 65 import java.util.List; 66 67 @AppModeFull 68 @RunWith(AndroidJUnit4.class) 69 public class FileIntegrityManagerTest { 70 71 private static final String TAG = "FileIntegrityManagerTest"; 72 private static final String FILENAME = "test.file"; 73 private static final String TEST_FILE_CONTENT = "fs-verity"; 74 // Produced by shell command: echo -n 'fs-verity' > t; fsverity digest --compact t 75 private static final String TEST_FILE_DIGEST = 76 "1e5300d45dce778c0aa6ced72954a8a4398d8f5d590c73cb431fe5fe2adbeeed"; 77 private static final int MIN_REQUIRED_API_LEVEL = 30; 78 79 private Context mContext; 80 private FileIntegrityManager mFileIntegrityManager; 81 private CertificateFactory mCertFactory; 82 83 @Rule 84 public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); 85 86 @Rule 87 public AdoptShellPermissionsRule mAdoptShellPermissionsRule = new AdoptShellPermissionsRule( 88 InstrumentationRegistry.getInstrumentation().getUiAutomation(), 89 android.Manifest.permission.SETUP_FSVERITY); 90 91 @Before setUp()92 public void setUp() throws Exception { 93 mContext = InstrumentationRegistry.getInstrumentation().getContext(); 94 // This feature name check only applies to devices that first shipped with 95 // SC or later. 96 final int firstApiLevel = 97 Math.min(PropertyUtil.getFirstApiLevel(), PropertyUtil.getVendorApiLevel()); 98 if (firstApiLevel >= Build.VERSION_CODES.S) { 99 // Assumes every test in this file asserts a requirement of CDD section 9. 100 assumeTrue("Skipping test: FEATURE_SECURITY_MODEL_COMPATIBLE missing.", 101 mContext.getPackageManager() 102 .hasSystemFeature(PackageManager.FEATURE_SECURITY_MODEL_COMPATIBLE)); 103 } 104 105 mFileIntegrityManager = mContext.getSystemService(FileIntegrityManager.class); 106 mCertFactory = CertificateFactory.getInstance("X.509"); 107 } 108 109 @After tearDown()110 public void tearDown() throws Exception { 111 var files = newSupportedFiles(); 112 files.addAll(newUnsupportedFiles()); 113 for (var file : files) { 114 if (file.exists()) { 115 file.delete(); 116 } 117 } 118 } 119 120 @CddTest(requirement="9.10/C-0-3,C-1-1") 121 @Test testSupportedOnDevicesFirstLaunchedWithR()122 public void testSupportedOnDevicesFirstLaunchedWithR() throws Exception { 123 if (PropertyUtil.getFirstApiLevel() >= MIN_REQUIRED_API_LEVEL) { 124 assertTrue(mFileIntegrityManager.isApkVeritySupported()); 125 } 126 } 127 128 @CddTest(requirement="9.10/C-0-3,C-1-1") 129 @Test testIsAppSourceCertificateTrusted()130 public void testIsAppSourceCertificateTrusted() throws Exception { 131 boolean isReleaseCertTrusted = mFileIntegrityManager.isAppSourceCertificateTrusted( 132 readAssetAsX509Certificate("fsverity-release.x509.der")); 133 if (!Flags.deprecateFsvSig() 134 && mFileIntegrityManager.isApkVeritySupported()) { 135 assertTrue(isReleaseCertTrusted); 136 } else { 137 assertFalse(isReleaseCertTrusted); 138 } 139 } 140 141 @CddTest(requirement="9.10/C-0-3,C-1-1") 142 @RestrictedBuildTest 143 @Test testPlatformDebugCertificateNotTrusted()144 public void testPlatformDebugCertificateNotTrusted() throws Exception { 145 boolean isDebugCertTrusted = mFileIntegrityManager.isAppSourceCertificateTrusted( 146 readAssetAsX509Certificate("fsverity-debug.x509.der")); 147 assertFalse(isDebugCertTrusted); 148 } 149 readAssetAsX509Certificate(String assetName)150 private X509Certificate readAssetAsX509Certificate(String assetName) 151 throws CertificateException, IOException { 152 InputStream is = mContext.getAssets().open(assetName); 153 return toX509Certificate(readAllBytes(is)); 154 } 155 156 // TODO: Switch to InputStream#readAllBytes when Java 9 is supported readAllBytes(InputStream is)157 private byte[] readAllBytes(InputStream is) throws IOException { 158 ByteArrayOutputStream output = new ByteArrayOutputStream(); 159 byte[] buf = new byte[8192]; 160 int len; 161 while ((len = is.read(buf, 0, buf.length)) > 0) { 162 output.write(buf, 0, len); 163 } 164 return output.toByteArray(); 165 } 166 toX509Certificate(byte[] bytes)167 private X509Certificate toX509Certificate(byte[] bytes) throws CertificateException { 168 return (X509Certificate) mCertFactory.generateCertificate(new ByteArrayInputStream(bytes)); 169 } 170 171 @Test 172 @ApiTest(apis = {"android.security.FileIntegrityManager#setupFsVerity", 173 "android.security.FileIntegrityManager#getFsVerityDigest"}) 174 @RequiresFlagsEnabled(Flags.FLAG_FSVERITY_API) testEnableAndMeasureFsVerityByFile()175 public void testEnableAndMeasureFsVerityByFile() throws Exception { 176 var files = newSupportedFiles(); 177 for (var file : files) { 178 Log.d(TAG, "testEnableAndMeasureFsVerityByFile: " + file); 179 180 createTestFile(file); 181 mFileIntegrityManager.setupFsVerity(file); 182 183 byte[] actualDigest = mFileIntegrityManager.getFsVerityDigest(file); 184 assertThat(actualDigest).isNotNull(); 185 assertThat(HexFormat.of().formatHex(actualDigest)).isEqualTo(TEST_FILE_DIGEST); 186 } 187 } 188 189 @Test 190 @ApiTest(apis = {"android.security.FileIntegrityManager#setupFsVerity"}) 191 @RequiresFlagsEnabled(Flags.FLAG_FSVERITY_API) testFailToEnableUnsupportedLocation()192 public void testFailToEnableUnsupportedLocation() throws Exception { 193 var files = newUnsupportedFiles(); 194 for (var file : files) { 195 Log.d(TAG, "testFailToEnableUnsupportedLocation: " + file); 196 197 createTestFile(file); 198 assertThrows(Exception.class, () -> mFileIntegrityManager.setupFsVerity(file)); 199 } 200 } 201 202 @Test 203 @ApiTest(apis = {"android.security.FileIntegrityManager#setupFsVerity"}) 204 @RequiresFlagsEnabled(Flags.FLAG_FSVERITY_API) testFailToEnableWithOpenedWritableFd()205 public void testFailToEnableWithOpenedWritableFd() throws Exception { 206 var files = newSupportedFiles(); 207 for (var file : files) { 208 Log.d(TAG, "testFailToEnableWithOpenedWritableFd: " + file); 209 210 var fos = new FileOutputStream(file); 211 fos.write(TEST_FILE_CONTENT.getBytes()); 212 213 // With any writable fd, the call will fail. 214 assertThrows(IOException.class, () -> 215 mFileIntegrityManager.setupFsVerity(file)); 216 } 217 } 218 219 @Test 220 @ApiTest(apis = {"android.security.FileIntegrityManager#getFsVerityDigest"}) 221 @RequiresFlagsEnabled(Flags.FLAG_FSVERITY_API) testMeasureWithoutFsVerity()222 public void testMeasureWithoutFsVerity() throws Exception { 223 var files = newSupportedFiles(); 224 for (var file : files) { 225 Log.d(TAG, "testMeasureWithoutFsVerity: " + file); 226 227 createTestFile(file); 228 229 byte[] actualDigest = mFileIntegrityManager.getFsVerityDigest(file); 230 assertThat(actualDigest).isNull(); 231 } 232 } 233 createTestFile(File file)234 private void createTestFile(File file) throws IOException { 235 try (var fos = new FileOutputStream(file)) { 236 fos.write(TEST_FILE_CONTENT.getBytes()); 237 } 238 } 239 newSupportedFiles()240 private List<File> newSupportedFiles() { 241 var ceContext = mContext.createCredentialProtectedStorageContext(); 242 var deContext = mContext.createDeviceProtectedStorageContext(); 243 return new ArrayList<>(Arrays.asList( 244 new File(ceContext.getDataDir(), FILENAME), 245 new File(ceContext.getFilesDir(), FILENAME), 246 new File(deContext.getDataDir(), FILENAME), 247 new File(deContext.getFilesDir(), FILENAME))); 248 } 249 newUnsupportedFiles()250 private List<File> newUnsupportedFiles() { 251 return new ArrayList<>(Arrays.asList( 252 new File(mContext.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), FILENAME), 253 new File(mContext.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS), FILENAME))); 254 } 255 } 256