Merge "Introduce a RuleIndexingParser class that will help us to identify the range of the bytes to be read and parsed."

This commit is contained in:
TreeHugger Robot
2020-01-07 19:06:03 +00:00
committed by Android (Google) Code Review
8 changed files with 344 additions and 44 deletions

View File

@@ -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<Rule> readRules(AppInstallMetadata appInstallMetadata)
throws IOException, RuleParseException {
// TODO: select rules by index
synchronized (RULES_LOCK) {
// Try to identify indexes from the index file.
List<List<Integer>> 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<Rule> 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);

View File

@@ -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";
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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<String, Integer> sPackageNameBasedIndexes;
private static TreeMap<String, Integer> sAppCertificateBasedIndexes;
private static TreeMap<String, Integer> 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<List<Integer>> identifyRulesToEvaluate(AppInstallMetadata appInstallMetadata) {
// TODO(b/145493956): Identify and return the indexes that needs to be read.
return new ArrayList<>();
}
private TreeMap<String, Integer> getNextIndexGroup(BitInputStream bitInputStream)
throws IOException {
TreeMap<String, Integer> 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;
}
}

View File

@@ -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<Rule> rules, Optional<Integer> formatVersion)

View File

@@ -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);
}
}

View File

@@ -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;