Loading services/core/java/com/android/server/integrity/parser/RuleBinaryParser.java +41 −11 Original line number Diff line number Diff line Loading @@ -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 { Loading Loading @@ -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; } } services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java +28 −11 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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(); Loading Loading @@ -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); Loading @@ -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); Loading @@ -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); } } services/tests/servicestests/src/com/android/server/integrity/parser/RuleBinaryParserTest.java +46 −18 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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); Loading Loading @@ -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); Loading @@ -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); Loading @@ -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())); } Loading Loading @@ -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 Loading @@ -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; Loading services/tests/servicestests/src/com/android/server/integrity/serializer/RuleBinarySerializerTest.java +63 −27 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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); Loading @@ -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 Loading Loading @@ -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 = Loading @@ -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(); Loading Loading @@ -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(); Loading Loading @@ -457,28 +488,33 @@ 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( getBytes( getSerializedCompoundRuleWithPackageNameAndSampleInstallerName( packageNameA))); expectedArrayOutputStream.write( getBytes(getSerializedCompoundRuleWithPackageNameAndSampleInstallerName( getBytes( getSerializedCompoundRuleWithPackageNameAndSampleInstallerName( packageNameB))); expectedArrayOutputStream.write( getBytes(getSerializedCompoundRuleWithPackageNameAndSampleInstallerName( getBytes( getSerializedCompoundRuleWithPackageNameAndSampleInstallerName( packageNameC))); expectedArrayOutputStream.write( getBytes(getSerializedCompoundRuleWithCertificateNameAndSampleInstallerName( getBytes( getSerializedCompoundRuleWithCertificateNameAndSampleInstallerName( appCert1))); expectedArrayOutputStream.write( getBytes(getSerializedCompoundRuleWithCertificateNameAndSampleInstallerName( getBytes( getSerializedCompoundRuleWithCertificateNameAndSampleInstallerName( appCert2))); expectedArrayOutputStream.write( getBytes(getSerializedCompoundRuleWithCertificateNameAndSampleInstallerName( getBytes( getSerializedCompoundRuleWithCertificateNameAndSampleInstallerName( appCert3))); String expectedBitsForInstallerRule = START_BIT Loading Loading
services/core/java/com/android/server/integrity/parser/RuleBinaryParser.java +41 −11 Original line number Diff line number Diff line Loading @@ -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 { Loading Loading @@ -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; } }
services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java +28 −11 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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(); Loading Loading @@ -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); Loading @@ -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); Loading @@ -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); } }
services/tests/servicestests/src/com/android/server/integrity/parser/RuleBinaryParserTest.java +46 −18 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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); Loading Loading @@ -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); Loading @@ -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); Loading @@ -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())); } Loading Loading @@ -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 Loading @@ -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; Loading
services/tests/servicestests/src/com/android/server/integrity/serializer/RuleBinarySerializerTest.java +63 −27 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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); Loading @@ -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 Loading Loading @@ -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 = Loading @@ -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(); Loading Loading @@ -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(); Loading Loading @@ -457,28 +488,33 @@ 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( getBytes( getSerializedCompoundRuleWithPackageNameAndSampleInstallerName( packageNameA))); expectedArrayOutputStream.write( getBytes(getSerializedCompoundRuleWithPackageNameAndSampleInstallerName( getBytes( getSerializedCompoundRuleWithPackageNameAndSampleInstallerName( packageNameB))); expectedArrayOutputStream.write( getBytes(getSerializedCompoundRuleWithPackageNameAndSampleInstallerName( getBytes( getSerializedCompoundRuleWithPackageNameAndSampleInstallerName( packageNameC))); expectedArrayOutputStream.write( getBytes(getSerializedCompoundRuleWithCertificateNameAndSampleInstallerName( getBytes( getSerializedCompoundRuleWithCertificateNameAndSampleInstallerName( appCert1))); expectedArrayOutputStream.write( getBytes(getSerializedCompoundRuleWithCertificateNameAndSampleInstallerName( getBytes( getSerializedCompoundRuleWithCertificateNameAndSampleInstallerName( appCert2))); expectedArrayOutputStream.write( getBytes(getSerializedCompoundRuleWithCertificateNameAndSampleInstallerName( getBytes( getSerializedCompoundRuleWithCertificateNameAndSampleInstallerName( appCert3))); String expectedBitsForInstallerRule = START_BIT Loading