Convert value from/to hex encoding
Values in atomic formulas could be represented as a hash. If the value is hashed, it is represented in hex encoding. Binary parser and serializer convert the value accordingly. Bug: 143697198 Test: atest FrameworksServicesTests:RuleBinaryParserTest Test: atest FrameworksServicesTests:RuleBinarySerializerTest Change-Id: Ie10c4dcb0975c8d5768227ae224669f33cb16992
This commit is contained in:
@@ -34,16 +34,20 @@ 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;
|
||||
|
||||
/** A helper class to parse rules into the {@link Rule} model from Binary representation. */
|
||||
public class RuleBinaryParser implements RuleParser {
|
||||
|
||||
private static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray();
|
||||
|
||||
@Override
|
||||
public List<Rule> parse(byte[] ruleBytes) throws RuleParseException {
|
||||
try {
|
||||
@@ -122,26 +126,52 @@ public class RuleBinaryParser implements RuleParser {
|
||||
int key = bitInputStream.getNext(KEY_BITS);
|
||||
int operator = bitInputStream.getNext(OPERATOR_BITS);
|
||||
|
||||
boolean isHashedValue = bitInputStream.getNext(IS_HASHED_BITS) == 1;
|
||||
int valueSize = bitInputStream.getNext(VALUE_SIZE_BITS);
|
||||
StringBuilder value = new StringBuilder();
|
||||
while (valueSize-- > 0) {
|
||||
value.append((char) bitInputStream.getNext(/* numOfBits= */ 8));
|
||||
}
|
||||
|
||||
switch (key) {
|
||||
case AtomicFormula.PACKAGE_NAME:
|
||||
case AtomicFormula.APP_CERTIFICATE:
|
||||
case AtomicFormula.INSTALLER_NAME:
|
||||
case AtomicFormula.INSTALLER_CERTIFICATE:
|
||||
return new AtomicFormula.StringAtomicFormula(key, value.toString(), isHashedValue);
|
||||
boolean isHashedValue = bitInputStream.getNext(IS_HASHED_BITS) == 1;
|
||||
int valueSize = bitInputStream.getNext(VALUE_SIZE_BITS);
|
||||
String stringValue = getStringValue(bitInputStream, valueSize, isHashedValue);
|
||||
return new AtomicFormula.StringAtomicFormula(key, stringValue, isHashedValue);
|
||||
case AtomicFormula.VERSION_CODE:
|
||||
return new AtomicFormula.IntAtomicFormula(
|
||||
key, operator, Integer.parseInt(value.toString()));
|
||||
int intValue = getIntValue(bitInputStream);
|
||||
return new AtomicFormula.IntAtomicFormula(key, operator, intValue);
|
||||
case AtomicFormula.PRE_INSTALLED:
|
||||
return new AtomicFormula.BooleanAtomicFormula(key, value.toString().equals("1"));
|
||||
boolean booleanValue = getBooleanValue(bitInputStream);
|
||||
return new AtomicFormula.BooleanAtomicFormula(key, booleanValue);
|
||||
default:
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ 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.BitOutputStream;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
@@ -93,6 +94,9 @@ public class RuleBinarySerializer implements RuleSerializer {
|
||||
|
||||
private void serializeIndexedRules(List<Rule> rules, OutputStream outputStream)
|
||||
throws IOException {
|
||||
if (rules == null) {
|
||||
return;
|
||||
}
|
||||
BitOutputStream bitOutputStream = new BitOutputStream();
|
||||
for (Rule rule : rules) {
|
||||
bitOutputStream.clear();
|
||||
@@ -153,7 +157,7 @@ public class RuleBinarySerializer implements RuleSerializer {
|
||||
AtomicFormula.StringAtomicFormula stringAtomicFormula =
|
||||
(AtomicFormula.StringAtomicFormula) atomicFormula;
|
||||
bitOutputStream.setNext(OPERATOR_BITS, AtomicFormula.EQ);
|
||||
serializeValue(
|
||||
serializeStringValue(
|
||||
stringAtomicFormula.getValue(),
|
||||
stringAtomicFormula.getIsHashedValue(),
|
||||
bitOutputStream);
|
||||
@@ -161,27 +165,21 @@ public class RuleBinarySerializer implements RuleSerializer {
|
||||
AtomicFormula.IntAtomicFormula intAtomicFormula =
|
||||
(AtomicFormula.IntAtomicFormula) atomicFormula;
|
||||
bitOutputStream.setNext(OPERATOR_BITS, intAtomicFormula.getOperator());
|
||||
serializeValue(
|
||||
String.valueOf(intAtomicFormula.getValue()),
|
||||
/* isHashedValue= */ false,
|
||||
bitOutputStream);
|
||||
serializeIntValue(intAtomicFormula.getValue(), bitOutputStream);
|
||||
} else if (atomicFormula.getTag() == AtomicFormula.BOOLEAN_ATOMIC_FORMULA_TAG) {
|
||||
AtomicFormula.BooleanAtomicFormula booleanAtomicFormula =
|
||||
(AtomicFormula.BooleanAtomicFormula) atomicFormula;
|
||||
bitOutputStream.setNext(OPERATOR_BITS, AtomicFormula.EQ);
|
||||
serializeValue(
|
||||
booleanAtomicFormula.getValue() ? "1" : "0",
|
||||
/* isHashedValue= */ false,
|
||||
bitOutputStream);
|
||||
serializeBooleanValue(booleanAtomicFormula.getValue(), bitOutputStream);
|
||||
} else {
|
||||
throw new IllegalArgumentException(
|
||||
String.format("Invalid atomic formula type: %s", atomicFormula.getClass()));
|
||||
}
|
||||
}
|
||||
|
||||
private void serializeValue(
|
||||
private void serializeStringValue(
|
||||
String value, boolean isHashedValue, BitOutputStream bitOutputStream) {
|
||||
byte[] valueBytes = value.getBytes(StandardCharsets.UTF_8);
|
||||
byte[] valueBytes = getBytesForString(value, isHashedValue);
|
||||
|
||||
bitOutputStream.setNext(isHashedValue);
|
||||
bitOutputStream.setNext(VALUE_SIZE_BITS, valueBytes.length);
|
||||
@@ -189,4 +187,23 @@ public class RuleBinarySerializer implements RuleSerializer {
|
||||
bitOutputStream.setNext(/* numOfBits= */ 8, valueByte);
|
||||
}
|
||||
}
|
||||
|
||||
private void serializeIntValue(int value, BitOutputStream bitOutputStream) {
|
||||
bitOutputStream.setNext(/* numOfBits= */ 32, value);
|
||||
}
|
||||
|
||||
private void serializeBooleanValue(boolean value, BitOutputStream bitOutputStream) {
|
||||
bitOutputStream.setNext(value);
|
||||
}
|
||||
|
||||
// Get the byte array for a value.
|
||||
// If the value is not hashed, use its byte array form directly.
|
||||
// If the value is hashed, get the raw form decoding of the value. All hashed values are
|
||||
// hex-encoded. Serialized values are in raw form.
|
||||
private static byte[] getBytesForString(String value, boolean isHashedValue) {
|
||||
if (!isHashedValue) {
|
||||
return value.getBytes(StandardCharsets.UTF_8);
|
||||
}
|
||||
return IntegrityUtils.getBytesFromHexDigest(value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,6 +38,8 @@ import android.content.integrity.AtomicFormula;
|
||||
import android.content.integrity.CompoundFormula;
|
||||
import android.content.integrity.Rule;
|
||||
|
||||
import com.android.server.integrity.IntegrityUtils;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
@@ -45,6 +47,7 @@ import org.junit.runners.JUnit4;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
@@ -81,6 +84,7 @@ public class RuleBinaryParserTest {
|
||||
private static final String INVALID_OPERATOR = getBits(INVALID_OPERATOR_VALUE, OPERATOR_BITS);
|
||||
|
||||
private static final String IS_NOT_HASHED = "0";
|
||||
private static final String IS_HASHED = "1";
|
||||
|
||||
private static final String DENY = getBits(Rule.DENY, EFFECT_BITS);
|
||||
private static final int INVALID_EFFECT_VALUE = 5;
|
||||
@@ -299,17 +303,48 @@ public class RuleBinaryParserTest {
|
||||
assertThat(rules).isEqualTo(Collections.singletonList(expectedRule));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBinaryString_validAtomicFormula_hashedValue() throws Exception {
|
||||
String appCertificate = "test_cert";
|
||||
String ruleBits =
|
||||
START_BIT
|
||||
+ ATOMIC_FORMULA_START_BITS
|
||||
+ APP_CERTIFICATE
|
||||
+ EQ
|
||||
+ IS_HASHED
|
||||
+ getBits(appCertificate.length(), VALUE_SIZE_BITS)
|
||||
+ getValueBits(appCertificate)
|
||||
+ DENY
|
||||
+ END_BIT;
|
||||
byte[] ruleBytes = getBytes(ruleBits);
|
||||
ByteBuffer rule =
|
||||
ByteBuffer.allocate(DEFAULT_FORMAT_VERSION_BYTES.length + ruleBytes.length);
|
||||
rule.put(DEFAULT_FORMAT_VERSION_BYTES);
|
||||
rule.put(ruleBytes);
|
||||
RuleParser binaryParser = new RuleBinaryParser();
|
||||
Rule expectedRule =
|
||||
new Rule(
|
||||
new AtomicFormula.StringAtomicFormula(
|
||||
AtomicFormula.APP_CERTIFICATE,
|
||||
IntegrityUtils.getHexDigest(
|
||||
appCertificate.getBytes(StandardCharsets.UTF_8)),
|
||||
/* isHashedValue= */ true),
|
||||
Rule.DENY);
|
||||
|
||||
List<Rule> rules = binaryParser.parse(rule.array());
|
||||
|
||||
assertThat(rules).isEqualTo(Collections.singletonList(expectedRule));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBinaryString_validAtomicFormula_integerValue() throws Exception {
|
||||
String versionCode = "1";
|
||||
int versionCode = 1;
|
||||
String ruleBits =
|
||||
START_BIT
|
||||
+ ATOMIC_FORMULA_START_BITS
|
||||
+ VERSION_CODE
|
||||
+ EQ
|
||||
+ IS_NOT_HASHED
|
||||
+ getBits(versionCode.length(), VALUE_SIZE_BITS)
|
||||
+ getValueBits(versionCode)
|
||||
+ getBits(versionCode, /* numOfBits= */ 32)
|
||||
+ DENY
|
||||
+ END_BIT;
|
||||
byte[] ruleBytes = getBytes(ruleBits);
|
||||
@@ -337,9 +372,7 @@ public class RuleBinaryParserTest {
|
||||
+ ATOMIC_FORMULA_START_BITS
|
||||
+ PRE_INSTALLED
|
||||
+ EQ
|
||||
+ IS_NOT_HASHED
|
||||
+ getBits(isPreInstalled.length(), VALUE_SIZE_BITS)
|
||||
+ getValueBits(isPreInstalled)
|
||||
+ isPreInstalled
|
||||
+ DENY
|
||||
+ END_BIT;
|
||||
byte[] ruleBytes = getBytes(ruleBits);
|
||||
@@ -360,17 +393,14 @@ public class RuleBinaryParserTest {
|
||||
|
||||
@Test
|
||||
public void testBinaryString_invalidAtomicFormula() throws Exception {
|
||||
String versionCode = "test";
|
||||
int versionCode = 1;
|
||||
String ruleBits =
|
||||
START_BIT
|
||||
+ ATOMIC_FORMULA_START_BITS
|
||||
+ VERSION_CODE
|
||||
+ EQ
|
||||
+ IS_NOT_HASHED
|
||||
+ getBits(versionCode.length(), VALUE_SIZE_BITS)
|
||||
+ getValueBits(versionCode)
|
||||
+ DENY
|
||||
+ END_BIT;
|
||||
+ getBits(versionCode, /* numOfBits= */ 32)
|
||||
+ DENY;
|
||||
byte[] ruleBytes = getBytes(ruleBits);
|
||||
ByteBuffer rule =
|
||||
ByteBuffer.allocate(DEFAULT_FORMAT_VERSION_BYTES.length + ruleBytes.length);
|
||||
@@ -380,7 +410,7 @@ public class RuleBinaryParserTest {
|
||||
|
||||
assertExpectException(
|
||||
RuleParseException.class,
|
||||
/* expectedExceptionMessageRegex */ "For input string:",
|
||||
/* expectedExceptionMessageRegex */ "A rule must end with a '1' bit.",
|
||||
() -> binaryParser.parse(rule.array()));
|
||||
}
|
||||
|
||||
@@ -449,7 +479,7 @@ public class RuleBinaryParserTest {
|
||||
|
||||
@Test
|
||||
public void testBinaryString_invalidRule_invalidOperator() throws Exception {
|
||||
String versionCode = "1";
|
||||
int versionCode = 1;
|
||||
String ruleBits =
|
||||
START_BIT
|
||||
+ COMPOUND_FORMULA_START_BITS
|
||||
@@ -457,9 +487,7 @@ public class RuleBinaryParserTest {
|
||||
+ ATOMIC_FORMULA_START_BITS
|
||||
+ VERSION_CODE
|
||||
+ INVALID_OPERATOR
|
||||
+ IS_NOT_HASHED
|
||||
+ getBits(versionCode.length(), VALUE_SIZE_BITS)
|
||||
+ getValueBits(versionCode)
|
||||
+ getBits(versionCode, /* numOfBits= */ 32)
|
||||
+ COMPOUND_FORMULA_END_BITS
|
||||
+ DENY
|
||||
+ END_BIT;
|
||||
|
||||
@@ -42,11 +42,14 @@ import android.content.integrity.Rule;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.android.server.integrity.IntegrityUtils;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
@@ -81,6 +84,7 @@ public class RuleBinarySerializerTest {
|
||||
private static final String EQ = getBits(AtomicFormula.EQ, OPERATOR_BITS);
|
||||
|
||||
private static final String IS_NOT_HASHED = "0";
|
||||
private static final String IS_HASHED = "1";
|
||||
|
||||
private static final String DENY = getBits(Rule.DENY, EFFECT_BITS);
|
||||
|
||||
@@ -96,10 +100,9 @@ public class RuleBinarySerializerTest {
|
||||
|
||||
assertExpectException(
|
||||
RuleSerializeException.class,
|
||||
/* expectedExceptionMessageRegex= */
|
||||
"Index buckets cannot be created for null rule list.",
|
||||
() ->
|
||||
binarySerializer.serialize(null, /* formatVersion= */ Optional.empty()));
|
||||
/* expectedExceptionMessageRegex= */ "Index buckets cannot be created for null"
|
||||
+ " rule list.",
|
||||
() -> binarySerializer.serialize(null, /* formatVersion= */ Optional.empty()));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -329,15 +332,47 @@ public class RuleBinarySerializerTest {
|
||||
assertThat(actualRules).isEqualTo(expectedRules);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBinaryString_serializeValidAtomicFormula_hashedValue() throws Exception {
|
||||
String appCertificate = "test_cert";
|
||||
Rule rule =
|
||||
new Rule(
|
||||
new AtomicFormula.StringAtomicFormula(
|
||||
AtomicFormula.APP_CERTIFICATE,
|
||||
IntegrityUtils.getHexDigest(
|
||||
appCertificate.getBytes(StandardCharsets.UTF_8)),
|
||||
/* isHashedValue= */ true),
|
||||
Rule.DENY);
|
||||
RuleSerializer binarySerializer = new RuleBinarySerializer();
|
||||
String expectedBits =
|
||||
START_BIT
|
||||
+ ATOMIC_FORMULA_START_BITS
|
||||
+ APP_CERTIFICATE
|
||||
+ EQ
|
||||
+ IS_HASHED
|
||||
+ getBits(appCertificate.length(), VALUE_SIZE_BITS)
|
||||
+ getValueBits(appCertificate)
|
||||
+ DENY
|
||||
+ END_BIT;
|
||||
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
|
||||
byteArrayOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES);
|
||||
byteArrayOutputStream.write(getBytes(expectedBits));
|
||||
byte[] expectedRules = byteArrayOutputStream.toByteArray();
|
||||
|
||||
byte[] actualRules =
|
||||
binarySerializer.serialize(
|
||||
Collections.singletonList(rule), /* formatVersion= */ Optional.empty());
|
||||
|
||||
assertThat(actualRules).isEqualTo(expectedRules);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBinaryString_serializeValidAtomicFormula_integerValue() throws Exception {
|
||||
String versionCode = "1";
|
||||
int versionCode = 1;
|
||||
Rule rule =
|
||||
new Rule(
|
||||
new AtomicFormula.IntAtomicFormula(
|
||||
AtomicFormula.VERSION_CODE,
|
||||
AtomicFormula.EQ,
|
||||
Integer.parseInt(versionCode)),
|
||||
AtomicFormula.VERSION_CODE, AtomicFormula.EQ, versionCode),
|
||||
Rule.DENY);
|
||||
RuleSerializer binarySerializer = new RuleBinarySerializer();
|
||||
String expectedBits =
|
||||
@@ -345,9 +380,7 @@ public class RuleBinarySerializerTest {
|
||||
+ ATOMIC_FORMULA_START_BITS
|
||||
+ VERSION_CODE
|
||||
+ EQ
|
||||
+ IS_NOT_HASHED
|
||||
+ getBits(versionCode.length(), VALUE_SIZE_BITS)
|
||||
+ getValueBits(versionCode)
|
||||
+ getBits(versionCode, /* numOfBits= */ 32)
|
||||
+ DENY
|
||||
+ END_BIT;
|
||||
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
|
||||
@@ -375,9 +408,7 @@ public class RuleBinarySerializerTest {
|
||||
+ ATOMIC_FORMULA_START_BITS
|
||||
+ PRE_INSTALLED
|
||||
+ EQ
|
||||
+ IS_NOT_HASHED
|
||||
+ getBits(preInstalled.length(), VALUE_SIZE_BITS)
|
||||
+ getValueBits(preInstalled)
|
||||
+ preInstalled
|
||||
+ DENY
|
||||
+ END_BIT;
|
||||
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
|
||||
@@ -457,29 +488,34 @@ public class RuleBinarySerializerTest {
|
||||
byte[] actualRules =
|
||||
binarySerializer.serialize(ruleList, /* formatVersion= */ Optional.empty());
|
||||
|
||||
|
||||
// Note that ordering is important here and the test verifies that the rules are written
|
||||
// in this sorted order.
|
||||
ByteArrayOutputStream expectedArrayOutputStream = new ByteArrayOutputStream();
|
||||
expectedArrayOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES);
|
||||
expectedArrayOutputStream.write(
|
||||
getBytes(getSerializedCompoundRuleWithPackageNameAndSampleInstallerName(
|
||||
packageNameA)));
|
||||
getBytes(
|
||||
getSerializedCompoundRuleWithPackageNameAndSampleInstallerName(
|
||||
packageNameA)));
|
||||
expectedArrayOutputStream.write(
|
||||
getBytes(getSerializedCompoundRuleWithPackageNameAndSampleInstallerName(
|
||||
packageNameB)));
|
||||
getBytes(
|
||||
getSerializedCompoundRuleWithPackageNameAndSampleInstallerName(
|
||||
packageNameB)));
|
||||
expectedArrayOutputStream.write(
|
||||
getBytes(getSerializedCompoundRuleWithPackageNameAndSampleInstallerName(
|
||||
packageNameC)));
|
||||
getBytes(
|
||||
getSerializedCompoundRuleWithPackageNameAndSampleInstallerName(
|
||||
packageNameC)));
|
||||
expectedArrayOutputStream.write(
|
||||
getBytes(getSerializedCompoundRuleWithCertificateNameAndSampleInstallerName(
|
||||
appCert1)));
|
||||
getBytes(
|
||||
getSerializedCompoundRuleWithCertificateNameAndSampleInstallerName(
|
||||
appCert1)));
|
||||
expectedArrayOutputStream.write(
|
||||
getBytes(getSerializedCompoundRuleWithCertificateNameAndSampleInstallerName(
|
||||
appCert2)));
|
||||
getBytes(
|
||||
getSerializedCompoundRuleWithCertificateNameAndSampleInstallerName(
|
||||
appCert2)));
|
||||
expectedArrayOutputStream.write(
|
||||
getBytes(getSerializedCompoundRuleWithCertificateNameAndSampleInstallerName(
|
||||
appCert3)));
|
||||
getBytes(
|
||||
getSerializedCompoundRuleWithCertificateNameAndSampleInstallerName(
|
||||
appCert3)));
|
||||
String expectedBitsForInstallerRule =
|
||||
START_BIT
|
||||
+ COMPOUND_FORMULA_START_BITS
|
||||
|
||||
Reference in New Issue
Block a user