Khaled Abdelmohsen | 7c56df6 | 2019-10-31 16:59:43 +0000 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2019 The Android Open Source Project |
| 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License. |
| 15 | */ |
| 16 | |
| 17 | package com.android.server.integrity.parser; |
| 18 | |
Song Pan | 75147d5 | 2019-11-19 00:57:46 +0000 | [diff] [blame] | 19 | import android.content.integrity.AtomicFormula; |
| 20 | import android.content.integrity.CompoundFormula; |
| 21 | import android.content.integrity.Formula; |
| 22 | import android.content.integrity.Rule; |
Khaled Abdelmohsen | 216109e | 2019-11-04 17:49:18 +0000 | [diff] [blame] | 23 | import android.util.Xml; |
| 24 | |
Khaled Abdelmohsen | 216109e | 2019-11-04 17:49:18 +0000 | [diff] [blame] | 25 | import org.xmlpull.v1.XmlPullParser; |
| 26 | import org.xmlpull.v1.XmlPullParserException; |
Khaled Abdelmohsen | 7c56df6 | 2019-10-31 16:59:43 +0000 | [diff] [blame] | 27 | |
Khaled Abdelmohsen | 216109e | 2019-11-04 17:49:18 +0000 | [diff] [blame] | 28 | import java.io.IOException; |
| 29 | import java.io.InputStream; |
| 30 | import java.io.StringReader; |
| 31 | import java.nio.charset.StandardCharsets; |
| 32 | import java.util.ArrayList; |
| 33 | import java.util.List; |
Khaled Abdelmohsen | e39fa0f | 2019-11-25 17:24:22 +0000 | [diff] [blame] | 34 | import java.util.Optional; |
Khaled Abdelmohsen | 216109e | 2019-11-04 17:49:18 +0000 | [diff] [blame] | 35 | |
Song Pan | 75147d5 | 2019-11-19 00:57:46 +0000 | [diff] [blame] | 36 | /** A helper class to parse rules into the {@link Rule} model from Xml representation. */ |
Khaled Abdelmohsen | 7c56df6 | 2019-10-31 16:59:43 +0000 | [diff] [blame] | 37 | public final class RuleXmlParser implements RuleParser { |
| 38 | |
Khaled Abdelmohsen | 216109e | 2019-11-04 17:49:18 +0000 | [diff] [blame] | 39 | public static final String TAG = "RuleXmlParser"; |
| 40 | |
Khaled Abdelmohsen | b05d706 | 2019-11-19 01:22:23 +0000 | [diff] [blame] | 41 | private static final String NAMESPACE = ""; |
Khaled Abdelmohsen | 6d77f94 | 2019-11-12 16:47:01 +0000 | [diff] [blame] | 42 | private static final String RULE_LIST_TAG = "RL"; |
| 43 | private static final String RULE_TAG = "R"; |
| 44 | private static final String OPEN_FORMULA_TAG = "OF"; |
| 45 | private static final String ATOMIC_FORMULA_TAG = "AF"; |
Khaled Abdelmohsen | b05d706 | 2019-11-19 01:22:23 +0000 | [diff] [blame] | 46 | private static final String EFFECT_ATTRIBUTE = "E"; |
| 47 | private static final String KEY_ATTRIBUTE = "K"; |
| 48 | private static final String OPERATOR_ATTRIBUTE = "O"; |
| 49 | private static final String VALUE_ATTRIBUTE = "V"; |
| 50 | private static final String CONNECTOR_ATTRIBUTE = "C"; |
Khaled Abdelmohsen | 1efff87 | 2019-11-25 16:44:20 +0000 | [diff] [blame] | 51 | private static final String IS_HASHED_VALUE_ATTRIBUTE = "H"; |
Khaled Abdelmohsen | 216109e | 2019-11-04 17:49:18 +0000 | [diff] [blame] | 52 | |
Khaled Abdelmohsen | 7c56df6 | 2019-10-31 16:59:43 +0000 | [diff] [blame] | 53 | @Override |
Khaled Abdelmohsen | 21a9605 | 2019-11-06 12:23:22 +0000 | [diff] [blame] | 54 | public List<Rule> parse(String ruleText) throws RuleParseException { |
Khaled Abdelmohsen | 216109e | 2019-11-04 17:49:18 +0000 | [diff] [blame] | 55 | try { |
| 56 | XmlPullParser xmlPullParser = Xml.newPullParser(); |
| 57 | xmlPullParser.setInput(new StringReader(ruleText)); |
| 58 | return parseRules(xmlPullParser); |
Khaled Abdelmohsen | 21a9605 | 2019-11-06 12:23:22 +0000 | [diff] [blame] | 59 | } catch (Exception e) { |
| 60 | throw new RuleParseException(e.getMessage(), e); |
Khaled Abdelmohsen | 216109e | 2019-11-04 17:49:18 +0000 | [diff] [blame] | 61 | } |
Khaled Abdelmohsen | 7c56df6 | 2019-10-31 16:59:43 +0000 | [diff] [blame] | 62 | } |
| 63 | |
| 64 | @Override |
Khaled Abdelmohsen | 21a9605 | 2019-11-06 12:23:22 +0000 | [diff] [blame] | 65 | public List<Rule> parse(InputStream inputStream) throws RuleParseException { |
Khaled Abdelmohsen | 216109e | 2019-11-04 17:49:18 +0000 | [diff] [blame] | 66 | try { |
| 67 | XmlPullParser xmlPullParser = Xml.newPullParser(); |
| 68 | xmlPullParser.setInput(inputStream, StandardCharsets.UTF_8.name()); |
| 69 | return parseRules(xmlPullParser); |
Khaled Abdelmohsen | 21a9605 | 2019-11-06 12:23:22 +0000 | [diff] [blame] | 70 | } catch (Exception e) { |
| 71 | throw new RuleParseException(e.getMessage(), e); |
Khaled Abdelmohsen | 216109e | 2019-11-04 17:49:18 +0000 | [diff] [blame] | 72 | } |
Khaled Abdelmohsen | 216109e | 2019-11-04 17:49:18 +0000 | [diff] [blame] | 73 | } |
| 74 | |
Khaled Abdelmohsen | 12b110e | 2019-11-05 17:24:48 +0000 | [diff] [blame] | 75 | private static List<Rule> parseRules(XmlPullParser parser) |
| 76 | throws IOException, XmlPullParserException { |
Khaled Abdelmohsen | 216109e | 2019-11-04 17:49:18 +0000 | [diff] [blame] | 77 | List<Rule> rules = new ArrayList<>(); |
| 78 | |
| 79 | // Skipping the first event type, which is always {@link XmlPullParser.START_DOCUMENT} |
| 80 | parser.next(); |
| 81 | |
Khaled Abdelmohsen | 6d77f94 | 2019-11-12 16:47:01 +0000 | [diff] [blame] | 82 | // Processing the first tag; which should always be a RuleList <RL> tag. |
Khaled Abdelmohsen | 216109e | 2019-11-04 17:49:18 +0000 | [diff] [blame] | 83 | String nodeName = parser.getName(); |
Khaled Abdelmohsen | 6d77f94 | 2019-11-12 16:47:01 +0000 | [diff] [blame] | 84 | // Validating that the XML is starting with a RuleList <RL> tag. |
Khaled Abdelmohsen | 216109e | 2019-11-04 17:49:18 +0000 | [diff] [blame] | 85 | // Note: This is the only breaking validation to run against XML files in the platform. |
| 86 | // All rules inside are assumed to be validated at the server. If a rule is found to be |
| 87 | // corrupt in the XML, it will be skipped to the next rule. |
| 88 | if (!nodeName.equals(RULE_LIST_TAG)) { |
| 89 | throw new RuntimeException( |
Song Pan | 75147d5 | 2019-11-19 00:57:46 +0000 | [diff] [blame] | 90 | String.format( |
| 91 | "Rules must start with RuleList <RL> tag. Found: %s at %s", |
Khaled Abdelmohsen | 6d77f94 | 2019-11-12 16:47:01 +0000 | [diff] [blame] | 92 | nodeName, parser.getPositionDescription())); |
Khaled Abdelmohsen | 216109e | 2019-11-04 17:49:18 +0000 | [diff] [blame] | 93 | } |
| 94 | |
| 95 | int eventType; |
| 96 | while ((eventType = parser.next()) != XmlPullParser.END_DOCUMENT) { |
| 97 | nodeName = parser.getName(); |
| 98 | if (eventType != XmlPullParser.START_TAG || !nodeName.equals(RULE_TAG)) { |
| 99 | continue; |
| 100 | } |
Khaled Abdelmohsen | 21a9605 | 2019-11-06 12:23:22 +0000 | [diff] [blame] | 101 | rules.add(parseRule(parser)); |
Khaled Abdelmohsen | 216109e | 2019-11-04 17:49:18 +0000 | [diff] [blame] | 102 | } |
| 103 | |
| 104 | return rules; |
| 105 | } |
| 106 | |
Khaled Abdelmohsen | 21a9605 | 2019-11-06 12:23:22 +0000 | [diff] [blame] | 107 | private static Rule parseRule(XmlPullParser parser) throws IOException, XmlPullParserException { |
| 108 | Formula formula = null; |
Khaled Abdelmohsen | e39fa0f | 2019-11-25 17:24:22 +0000 | [diff] [blame] | 109 | int effect = Integer.parseInt(extractAttributeValue(parser, EFFECT_ATTRIBUTE).orElse("-1")); |
Khaled Abdelmohsen | 12b110e | 2019-11-05 17:24:48 +0000 | [diff] [blame] | 110 | |
Khaled Abdelmohsen | 21a9605 | 2019-11-06 12:23:22 +0000 | [diff] [blame] | 111 | int eventType; |
| 112 | while ((eventType = parser.next()) != XmlPullParser.END_DOCUMENT) { |
| 113 | String nodeName = parser.getName(); |
Khaled Abdelmohsen | 12b110e | 2019-11-05 17:24:48 +0000 | [diff] [blame] | 114 | |
Khaled Abdelmohsen | 21a9605 | 2019-11-06 12:23:22 +0000 | [diff] [blame] | 115 | if (eventType == XmlPullParser.END_TAG && parser.getName().equals(RULE_TAG)) { |
| 116 | break; |
Khaled Abdelmohsen | 12b110e | 2019-11-05 17:24:48 +0000 | [diff] [blame] | 117 | } |
| 118 | |
Khaled Abdelmohsen | 21a9605 | 2019-11-06 12:23:22 +0000 | [diff] [blame] | 119 | if (eventType == XmlPullParser.START_TAG) { |
| 120 | switch (nodeName) { |
| 121 | case OPEN_FORMULA_TAG: |
| 122 | formula = parseOpenFormula(parser); |
| 123 | break; |
| 124 | case ATOMIC_FORMULA_TAG: |
| 125 | formula = parseAtomicFormula(parser); |
| 126 | break; |
Khaled Abdelmohsen | 21a9605 | 2019-11-06 12:23:22 +0000 | [diff] [blame] | 127 | default: |
| 128 | throw new RuntimeException( |
| 129 | String.format("Found unexpected tag: %s", nodeName)); |
| 130 | } |
| 131 | } else { |
| 132 | throw new RuntimeException( |
| 133 | String.format("Found unexpected event type: %d", eventType)); |
| 134 | } |
Khaled Abdelmohsen | 12b110e | 2019-11-05 17:24:48 +0000 | [diff] [blame] | 135 | } |
Khaled Abdelmohsen | 21a9605 | 2019-11-06 12:23:22 +0000 | [diff] [blame] | 136 | |
| 137 | return new Rule(formula, effect); |
Khaled Abdelmohsen | 12b110e | 2019-11-05 17:24:48 +0000 | [diff] [blame] | 138 | } |
| 139 | |
Khaled Abdelmohsen | 21a9605 | 2019-11-06 12:23:22 +0000 | [diff] [blame] | 140 | private static Formula parseOpenFormula(XmlPullParser parser) |
| 141 | throws IOException, XmlPullParserException { |
Song Pan | 75147d5 | 2019-11-19 00:57:46 +0000 | [diff] [blame] | 142 | int connector = |
| 143 | Integer.parseInt(extractAttributeValue(parser, CONNECTOR_ATTRIBUTE).orElse("-1")); |
Khaled Abdelmohsen | 21a9605 | 2019-11-06 12:23:22 +0000 | [diff] [blame] | 144 | List<Formula> formulas = new ArrayList<>(); |
| 145 | |
| 146 | int eventType; |
| 147 | while ((eventType = parser.next()) != XmlPullParser.END_DOCUMENT) { |
| 148 | String nodeName = parser.getName(); |
| 149 | |
| 150 | if (eventType == XmlPullParser.END_TAG && parser.getName().equals(OPEN_FORMULA_TAG)) { |
| 151 | break; |
| 152 | } |
| 153 | |
| 154 | if (eventType == XmlPullParser.START_TAG) { |
| 155 | switch (nodeName) { |
Khaled Abdelmohsen | 21a9605 | 2019-11-06 12:23:22 +0000 | [diff] [blame] | 156 | case ATOMIC_FORMULA_TAG: |
| 157 | formulas.add(parseAtomicFormula(parser)); |
| 158 | break; |
| 159 | case OPEN_FORMULA_TAG: |
| 160 | formulas.add(parseOpenFormula(parser)); |
| 161 | break; |
| 162 | default: |
| 163 | throw new RuntimeException( |
| 164 | String.format("Found unexpected tag: %s", nodeName)); |
| 165 | } |
| 166 | } else { |
| 167 | throw new RuntimeException( |
| 168 | String.format("Found unexpected event type: %d", eventType)); |
| 169 | } |
| 170 | } |
| 171 | |
Song Pan | 75147d5 | 2019-11-19 00:57:46 +0000 | [diff] [blame] | 172 | return new CompoundFormula(connector, formulas); |
Khaled Abdelmohsen | 7c56df6 | 2019-10-31 16:59:43 +0000 | [diff] [blame] | 173 | } |
Khaled Abdelmohsen | 12b110e | 2019-11-05 17:24:48 +0000 | [diff] [blame] | 174 | |
| 175 | private static Formula parseAtomicFormula(XmlPullParser parser) |
| 176 | throws IOException, XmlPullParserException { |
Khaled Abdelmohsen | e39fa0f | 2019-11-25 17:24:22 +0000 | [diff] [blame] | 177 | int key = Integer.parseInt(extractAttributeValue(parser, KEY_ATTRIBUTE).orElse("-1")); |
Song Pan | 75147d5 | 2019-11-19 00:57:46 +0000 | [diff] [blame] | 178 | int operator = |
| 179 | Integer.parseInt(extractAttributeValue(parser, OPERATOR_ATTRIBUTE).orElse("-1")); |
Khaled Abdelmohsen | e39fa0f | 2019-11-25 17:24:22 +0000 | [diff] [blame] | 180 | String value = extractAttributeValue(parser, VALUE_ATTRIBUTE).orElse(null); |
Song Pan | 75147d5 | 2019-11-19 00:57:46 +0000 | [diff] [blame] | 181 | String isHashedValue = |
| 182 | extractAttributeValue(parser, IS_HASHED_VALUE_ATTRIBUTE).orElse(null); |
Khaled Abdelmohsen | 12b110e | 2019-11-05 17:24:48 +0000 | [diff] [blame] | 183 | |
Khaled Abdelmohsen | 12b110e | 2019-11-05 17:24:48 +0000 | [diff] [blame] | 184 | int eventType; |
| 185 | while ((eventType = parser.next()) != XmlPullParser.END_DOCUMENT) { |
Khaled Abdelmohsen | 12b110e | 2019-11-05 17:24:48 +0000 | [diff] [blame] | 186 | if (eventType == XmlPullParser.END_TAG && parser.getName().equals(ATOMIC_FORMULA_TAG)) { |
| 187 | break; |
| 188 | } |
Khaled Abdelmohsen | 12b110e | 2019-11-05 17:24:48 +0000 | [diff] [blame] | 189 | } |
Khaled Abdelmohsen | 1efff87 | 2019-11-25 16:44:20 +0000 | [diff] [blame] | 190 | return constructAtomicFormulaBasedOnKey(key, operator, value, isHashedValue); |
Khaled Abdelmohsen | 12b110e | 2019-11-05 17:24:48 +0000 | [diff] [blame] | 191 | } |
| 192 | |
Song Pan | 75147d5 | 2019-11-19 00:57:46 +0000 | [diff] [blame] | 193 | private static Formula constructAtomicFormulaBasedOnKey( |
| 194 | @AtomicFormula.Key int key, |
| 195 | @AtomicFormula.Operator int operator, |
| 196 | String value, |
| 197 | String isHashedValue) { |
Khaled Abdelmohsen | 12b110e | 2019-11-05 17:24:48 +0000 | [diff] [blame] | 198 | switch (key) { |
| 199 | case AtomicFormula.PACKAGE_NAME: |
| 200 | case AtomicFormula.INSTALLER_NAME: |
| 201 | case AtomicFormula.APP_CERTIFICATE: |
| 202 | case AtomicFormula.INSTALLER_CERTIFICATE: |
Song Pan | 75147d5 | 2019-11-19 00:57:46 +0000 | [diff] [blame] | 203 | return new AtomicFormula.StringAtomicFormula( |
| 204 | key, value, Boolean.parseBoolean(isHashedValue)); |
Khaled Abdelmohsen | 12b110e | 2019-11-05 17:24:48 +0000 | [diff] [blame] | 205 | case AtomicFormula.PRE_INSTALLED: |
| 206 | return new AtomicFormula.BooleanAtomicFormula(key, Boolean.parseBoolean(value)); |
| 207 | case AtomicFormula.VERSION_CODE: |
| 208 | return new AtomicFormula.IntAtomicFormula(key, operator, Integer.parseInt(value)); |
| 209 | default: |
Khaled Abdelmohsen | 21a9605 | 2019-11-06 12:23:22 +0000 | [diff] [blame] | 210 | throw new RuntimeException(String.format("Found unexpected key: %d", key)); |
Khaled Abdelmohsen | 12b110e | 2019-11-05 17:24:48 +0000 | [diff] [blame] | 211 | } |
| 212 | } |
| 213 | |
Khaled Abdelmohsen | e39fa0f | 2019-11-25 17:24:22 +0000 | [diff] [blame] | 214 | private static Optional<String> extractAttributeValue(XmlPullParser parser, String attribute) { |
| 215 | return Optional.ofNullable(parser.getAttributeValue(NAMESPACE, attribute)); |
Khaled Abdelmohsen | 12b110e | 2019-11-05 17:24:48 +0000 | [diff] [blame] | 216 | } |
Khaled Abdelmohsen | 7c56df6 | 2019-10-31 16:59:43 +0000 | [diff] [blame] | 217 | } |