diff --git a/services/core/java/com/android/server/integrity/IntegrityFileManager.java b/services/core/java/com/android/server/integrity/IntegrityFileManager.java index 46daf12b101df..f90fab4666d6f 100644 --- a/services/core/java/com/android/server/integrity/IntegrityFileManager.java +++ b/services/core/java/com/android/server/integrity/IntegrityFileManager.java @@ -25,6 +25,7 @@ import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; import com.android.server.integrity.model.RuleMetadata; import com.android.server.integrity.parser.RuleBinaryParser; +import com.android.server.integrity.parser.RuleIndexingController; import com.android.server.integrity.parser.RuleMetadataParser; import com.android.server.integrity.parser.RuleParseException; import com.android.server.integrity.parser.RuleParser; @@ -61,7 +62,10 @@ public class IntegrityFileManager { // update rules atomically. private final File mStagingDir; - @Nullable private RuleMetadata mRuleMetadataCache; + @Nullable + private RuleMetadata mRuleMetadataCache; + @Nullable + private RuleIndexingController mRuleIndexingController; /** Get the singleton instance of this class. */ public static synchronized IntegrityFileManager getInstance() { @@ -100,6 +104,8 @@ public class IntegrityFileManager { Slog.e(TAG, "Error reading metadata file.", e); } } + + updateRuleIndexingController(); } /** @@ -109,7 +115,8 @@ public class IntegrityFileManager { */ public boolean initialized() { return new File(mRulesDir, RULES_FILE).exists() - && new File(mRulesDir, METADATA_FILE).exists(); + && new File(mRulesDir, METADATA_FILE).exists() + && new File(mRulesDir, INDEXING_FILE).exists(); } /** Write rules to persistent storage. */ @@ -131,6 +138,9 @@ public class IntegrityFileManager { } switchStagingRulesDir(); + + // Update object holding the indexing information. + updateRuleIndexingController(); } /** @@ -140,8 +150,13 @@ public class IntegrityFileManager { */ public List readRules(AppInstallMetadata appInstallMetadata) throws IOException, RuleParseException { - // TODO: select rules by index synchronized (RULES_LOCK) { + // Try to identify indexes from the index file. + List> ruleReadingIndexes = + mRuleIndexingController.identifyRulesToEvaluate(appInstallMetadata); + + // Read the rules based on the index information. + // TODO(b/145493956): Provide the identified indexes to the rule reader. try (FileInputStream inputStream = new FileInputStream(new File(mRulesDir, RULES_FILE))) { List rules = mRuleParser.parse(inputStream); @@ -168,6 +183,17 @@ public class IntegrityFileManager { } } + private void updateRuleIndexingController() { + File ruleIndexingFile = new File(mRulesDir, INDEXING_FILE); + if (ruleIndexingFile.exists()) { + try (FileInputStream inputStream = new FileInputStream(ruleIndexingFile)) { + mRuleIndexingController = new RuleIndexingController(inputStream); + } catch (Exception e) { + Slog.e(TAG, "Error parsing the rule indexing file.", e); + } + } + } + private void writeMetadata(File directory, String ruleProvider, String version) throws IOException { mRuleMetadataCache = new RuleMetadata(ruleProvider, version); diff --git a/services/core/java/com/android/server/integrity/model/IndexingFileConstants.java b/services/core/java/com/android/server/integrity/model/IndexingFileConstants.java new file mode 100644 index 0000000000000..52df898706d6b --- /dev/null +++ b/services/core/java/com/android/server/integrity/model/IndexingFileConstants.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.integrity.model; + +/** A helper class containing special indexing file constants. */ +public final class IndexingFileConstants { + // The parsing time seems acceptable for this block size based on the tests in + // go/ic-rule-file-format. + public static final int INDEXING_BLOCK_SIZE = 100; + + public static final String START_INDEXING_KEY = "START_KEY"; + public static final String END_INDEXING_KEY = "END_KEY"; +} diff --git a/services/core/java/com/android/server/integrity/parser/BinaryFileOperations.java b/services/core/java/com/android/server/integrity/parser/BinaryFileOperations.java new file mode 100644 index 0000000000000..2c5b7d3c122c1 --- /dev/null +++ b/services/core/java/com/android/server/integrity/parser/BinaryFileOperations.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.integrity.parser; + +import static com.android.server.integrity.model.ComponentBitSize.IS_HASHED_BITS; +import static com.android.server.integrity.model.ComponentBitSize.VALUE_SIZE_BITS; + +import com.android.server.integrity.IntegrityUtils; +import com.android.server.integrity.model.BitInputStream; + +import java.io.IOException; +import java.nio.ByteBuffer; + +/** + * Helper methods for reading standard data structures from {@link BitInputStream}. + */ +public class BinaryFileOperations { + + /** + * Read an string value with the given size and hash status from a {@code BitInputStream}. + * + * If the value is hashed, get the hex-encoding of the value. Serialized values are in raw form. + * All hashed values are hex-encoded. + */ + public static String getStringValue(BitInputStream bitInputStream) throws IOException { + boolean isHashedValue = bitInputStream.getNext(IS_HASHED_BITS) == 1; + int valueSize = bitInputStream.getNext(VALUE_SIZE_BITS); + return getStringValue(bitInputStream, valueSize, isHashedValue); + } + + /** + * Read an string value with the given size and hash status from a {@code BitInputStream}. + * + * If the value is hashed, get the hex-encoding of the value. Serialized values are in raw form. + * All hashed values are hex-encoded. + */ + public static String getStringValue( + BitInputStream bitInputStream, int valueSize, boolean isHashedValue) + throws IOException { + if (!isHashedValue) { + StringBuilder value = new StringBuilder(); + while (valueSize-- > 0) { + value.append((char) bitInputStream.getNext(/* numOfBits= */ 8)); + } + return value.toString(); + } + ByteBuffer byteBuffer = ByteBuffer.allocate(valueSize); + while (valueSize-- > 0) { + byteBuffer.put((byte) (bitInputStream.getNext(/* numOfBits= */ 8) & 0xFF)); + } + return IntegrityUtils.getHexDigest(byteBuffer.array()); + } + + /** Read an integer value from a {@code BitInputStream}. */ + public static int getIntValue(BitInputStream bitInputStream) throws IOException { + return bitInputStream.getNext(/* numOfBits= */ 32); + } + + /** Read an boolean value from a {@code BitInputStream}. */ + public static boolean getBooleanValue(BitInputStream bitInputStream) throws IOException { + return bitInputStream.getNext(/* numOfBits= */ 1) == 1; + } +} diff --git a/services/core/java/com/android/server/integrity/parser/RuleBinaryParser.java b/services/core/java/com/android/server/integrity/parser/RuleBinaryParser.java index 8f84abc88752b..cbb6e4e8e06f8 100644 --- a/services/core/java/com/android/server/integrity/parser/RuleBinaryParser.java +++ b/services/core/java/com/android/server/integrity/parser/RuleBinaryParser.java @@ -28,18 +28,19 @@ import static com.android.server.integrity.model.ComponentBitSize.OPERATOR_BITS; import static com.android.server.integrity.model.ComponentBitSize.SEPARATOR_BITS; import static com.android.server.integrity.model.ComponentBitSize.SIGNAL_BIT; import static com.android.server.integrity.model.ComponentBitSize.VALUE_SIZE_BITS; +import static com.android.server.integrity.parser.BinaryFileOperations.getBooleanValue; +import static com.android.server.integrity.parser.BinaryFileOperations.getIntValue; +import static com.android.server.integrity.parser.BinaryFileOperations.getStringValue; import android.content.integrity.AtomicFormula; import android.content.integrity.CompoundFormula; import android.content.integrity.Formula; import android.content.integrity.Rule; -import com.android.server.integrity.IntegrityUtils; import com.android.server.integrity.model.BitInputStream; import java.io.IOException; import java.io.InputStream; -import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; @@ -145,33 +146,4 @@ public class RuleBinaryParser implements RuleParser { throw new IllegalArgumentException(String.format("Unknown key: %d", key)); } } - - // Get value string from stream. - // If the value is not hashed, get its raw form directly. - // If the value is hashed, get the hex-encoding of the value. Serialized values are in raw form. - // All hashed values are hex-encoded. - private static String getStringValue( - BitInputStream bitInputStream, int valueSize, boolean isHashedValue) - throws IOException { - if (!isHashedValue) { - StringBuilder value = new StringBuilder(); - while (valueSize-- > 0) { - value.append((char) bitInputStream.getNext(/* numOfBits= */ 8)); - } - return value.toString(); - } - ByteBuffer byteBuffer = ByteBuffer.allocate(valueSize); - while (valueSize-- > 0) { - byteBuffer.put((byte) (bitInputStream.getNext(/* numOfBits= */ 8) & 0xFF)); - } - return IntegrityUtils.getHexDigest(byteBuffer.array()); - } - - private static int getIntValue(BitInputStream bitInputStream) throws IOException { - return bitInputStream.getNext(/* numOfBits= */ 32); - } - - private static boolean getBooleanValue(BitInputStream bitInputStream) throws IOException { - return bitInputStream.getNext(/* numOfBits= */ 1) == 1; - } } diff --git a/services/core/java/com/android/server/integrity/parser/RuleIndexingController.java b/services/core/java/com/android/server/integrity/parser/RuleIndexingController.java new file mode 100644 index 0000000000000..b642fa64baaf9 --- /dev/null +++ b/services/core/java/com/android/server/integrity/parser/RuleIndexingController.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.integrity.parser; + +import static com.android.server.integrity.model.IndexingFileConstants.END_INDEXING_KEY; +import static com.android.server.integrity.parser.BinaryFileOperations.getIntValue; +import static com.android.server.integrity.parser.BinaryFileOperations.getStringValue; + +import android.content.integrity.AppInstallMetadata; + +import com.android.server.integrity.model.BitInputStream; + +import java.io.FileInputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.TreeMap; + +/** Helper class to identify the necessary indexes that needs to be read. */ +public class RuleIndexingController { + + private static TreeMap sPackageNameBasedIndexes; + private static TreeMap sAppCertificateBasedIndexes; + private static TreeMap sUnindexedRuleIndexes; + + /** + * Provide the indexing file to read and the object will be constructed by reading and + * identifying the indexes. + */ + public RuleIndexingController(FileInputStream fileInputStream) throws IOException { + BitInputStream bitInputStream = new BitInputStream(fileInputStream); + sPackageNameBasedIndexes = getNextIndexGroup(bitInputStream); + sAppCertificateBasedIndexes = getNextIndexGroup(bitInputStream); + sUnindexedRuleIndexes = getNextIndexGroup(bitInputStream); + } + + /** + * Returns a list of integers with the starting and ending bytes of the rules that needs to be + * read and evaluated. + */ + public List> identifyRulesToEvaluate(AppInstallMetadata appInstallMetadata) { + // TODO(b/145493956): Identify and return the indexes that needs to be read. + return new ArrayList<>(); + } + + private TreeMap getNextIndexGroup(BitInputStream bitInputStream) + throws IOException { + TreeMap keyToIndexMap = new TreeMap<>(); + while (bitInputStream.hasNext()) { + String key = getStringValue(bitInputStream); + int value = getIntValue(bitInputStream); + + keyToIndexMap.put(key, value); + + if (key == END_INDEXING_KEY) { + break; + } + } + return keyToIndexMap; + } +} diff --git a/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java b/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java index 35e673f4405fe..b8791c3c34893 100644 --- a/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java +++ b/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java @@ -27,6 +27,9 @@ import static com.android.server.integrity.model.ComponentBitSize.KEY_BITS; import static com.android.server.integrity.model.ComponentBitSize.OPERATOR_BITS; import static com.android.server.integrity.model.ComponentBitSize.SEPARATOR_BITS; import static com.android.server.integrity.model.ComponentBitSize.VALUE_SIZE_BITS; +import static com.android.server.integrity.model.IndexingFileConstants.END_INDEXING_KEY; +import static com.android.server.integrity.model.IndexingFileConstants.INDEXING_BLOCK_SIZE; +import static com.android.server.integrity.model.IndexingFileConstants.START_INDEXING_KEY; import static com.android.server.integrity.serializer.RuleIndexingDetails.APP_CERTIFICATE_INDEXED; import static com.android.server.integrity.serializer.RuleIndexingDetails.NOT_INDEXED; import static com.android.server.integrity.serializer.RuleIndexingDetails.PACKAGE_NAME_INDEXED; @@ -52,13 +55,6 @@ import java.util.TreeMap; /** A helper class to serialize rules from the {@link Rule} model to Binary representation. */ public class RuleBinarySerializer implements RuleSerializer { - // The parsing time seems acceptable for this block size based on the tests in - // go/ic-rule-file-format. - public static final int INDEXING_BLOCK_SIZE = 100; - - public static final String START_INDEXING_KEY = "START_KEY"; - public static final String END_INDEXING_KEY = "END_KEY"; - // Get the byte representation for a list of rules. @Override public byte[] serialize(List rules, Optional formatVersion) diff --git a/services/tests/servicestests/src/com/android/server/integrity/parser/BinaryFileOperationsTest.java b/services/tests/servicestests/src/com/android/server/integrity/parser/BinaryFileOperationsTest.java new file mode 100644 index 0000000000000..94f68a59072ee --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/integrity/parser/BinaryFileOperationsTest.java @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.integrity.parser; + +import static com.android.server.integrity.model.ComponentBitSize.VALUE_SIZE_BITS; +import static com.android.server.integrity.parser.BinaryFileOperations.getBooleanValue; +import static com.android.server.integrity.parser.BinaryFileOperations.getIntValue; +import static com.android.server.integrity.parser.BinaryFileOperations.getStringValue; +import static com.android.server.integrity.utils.TestUtils.getBits; +import static com.android.server.integrity.utils.TestUtils.getBytes; +import static com.android.server.integrity.utils.TestUtils.getValueBits; + +import static com.google.common.truth.Truth.assertThat; + +import com.android.server.integrity.IntegrityUtils; +import com.android.server.integrity.model.BitInputStream; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; + +@RunWith(JUnit4.class) +public class BinaryFileOperationsTest { + + private static final String IS_NOT_HASHED = "0"; + private static final String IS_HASHED = "1"; + private static final String PACKAGE_NAME = "com.test.app"; + private static final String APP_CERTIFICATE = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; + + @Test + public void testGetStringValue() throws IOException { + byte[] stringBytes = + getBytes( + IS_NOT_HASHED + + getBits(PACKAGE_NAME.length(), VALUE_SIZE_BITS) + + getValueBits(PACKAGE_NAME)); + ByteBuffer rule = ByteBuffer.allocate(stringBytes.length); + rule.put(stringBytes); + BitInputStream inputStream = new BitInputStream(rule.array()); + + String resultString = getStringValue(inputStream); + + assertThat(resultString).isEqualTo(PACKAGE_NAME); + } + + @Test + public void testGetHashedStringValue() throws IOException { + byte[] ruleBytes = + getBytes( + IS_HASHED + + getBits(APP_CERTIFICATE.length(), VALUE_SIZE_BITS) + + getValueBits(APP_CERTIFICATE)); + ByteBuffer rule = ByteBuffer.allocate(ruleBytes.length); + rule.put(ruleBytes); + BitInputStream inputStream = new BitInputStream(rule.array()); + + String resultString = getStringValue(inputStream); + + assertThat(resultString) + .isEqualTo(IntegrityUtils.getHexDigest( + APP_CERTIFICATE.getBytes(StandardCharsets.UTF_8))); + } + + @Test + public void testGetStringValue_withSizeAndHashingInfo() throws IOException { + byte[] ruleBytes = getBytes(getValueBits(PACKAGE_NAME)); + ByteBuffer rule = ByteBuffer.allocate(ruleBytes.length); + rule.put(ruleBytes); + BitInputStream inputStream = new BitInputStream(rule.array()); + + String resultString = getStringValue(inputStream, + PACKAGE_NAME.length(), /* isHashedValue= */false); + + assertThat(resultString).isEqualTo(PACKAGE_NAME); + } + + @Test + public void testGetIntValue() throws IOException { + int randomValue = 15; + byte[] ruleBytes = getBytes(getBits(randomValue, /* numOfBits= */ 32)); + ByteBuffer rule = ByteBuffer.allocate(ruleBytes.length); + rule.put(ruleBytes); + BitInputStream inputStream = new BitInputStream(rule.array()); + + assertThat(getIntValue(inputStream)).isEqualTo(randomValue); + } + + @Test + public void testGetBooleanValue_true() throws IOException { + String booleanValue = "1"; + byte[] ruleBytes = getBytes(booleanValue); + ByteBuffer rule = ByteBuffer.allocate(ruleBytes.length); + rule.put(ruleBytes); + BitInputStream inputStream = new BitInputStream(rule.array()); + + assertThat(getBooleanValue(inputStream)).isEqualTo(true); + } + + @Test + public void testGetBooleanValue_false() throws IOException { + String booleanValue = "0"; + byte[] ruleBytes = getBytes(booleanValue); + ByteBuffer rule = ByteBuffer.allocate(ruleBytes.length); + rule.put(ruleBytes); + BitInputStream inputStream = new BitInputStream(rule.array()); + + assertThat(getBooleanValue(inputStream)).isEqualTo(false); + } +} diff --git a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleBinarySerializerTest.java b/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleBinarySerializerTest.java index 97aa3102e2d05..eb6698b0d4794 100644 --- a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleBinarySerializerTest.java +++ b/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleBinarySerializerTest.java @@ -27,9 +27,9 @@ import static com.android.server.integrity.model.ComponentBitSize.KEY_BITS; import static com.android.server.integrity.model.ComponentBitSize.OPERATOR_BITS; import static com.android.server.integrity.model.ComponentBitSize.SEPARATOR_BITS; import static com.android.server.integrity.model.ComponentBitSize.VALUE_SIZE_BITS; -import static com.android.server.integrity.serializer.RuleBinarySerializer.END_INDEXING_KEY; -import static com.android.server.integrity.serializer.RuleBinarySerializer.INDEXING_BLOCK_SIZE; -import static com.android.server.integrity.serializer.RuleBinarySerializer.START_INDEXING_KEY; +import static com.android.server.integrity.model.IndexingFileConstants.END_INDEXING_KEY; +import static com.android.server.integrity.model.IndexingFileConstants.INDEXING_BLOCK_SIZE; +import static com.android.server.integrity.model.IndexingFileConstants.START_INDEXING_KEY; import static com.android.server.integrity.utils.TestUtils.getBits; import static com.android.server.integrity.utils.TestUtils.getBytes; import static com.android.server.integrity.utils.TestUtils.getValueBits;