XmlConfigSource.java revision 08d36202daeb3e668911c9902edb61b6894f822e
15f96702f582050c1598136ed2a748f76b981c94eChad Brubakerpackage android.security.net.config; 25f96702f582050c1598136ed2a748f76b981c94eChad Brubaker 35f96702f582050c1598136ed2a748f76b981c94eChad Brubakerimport android.content.Context; 45f96702f582050c1598136ed2a748f76b981c94eChad Brubakerimport android.content.res.Resources; 55f96702f582050c1598136ed2a748f76b981c94eChad Brubakerimport android.content.res.XmlResourceParser; 65f96702f582050c1598136ed2a748f76b981c94eChad Brubakerimport android.util.ArraySet; 75f96702f582050c1598136ed2a748f76b981c94eChad Brubakerimport android.util.Base64; 85f96702f582050c1598136ed2a748f76b981c94eChad Brubakerimport android.util.Pair; 95f96702f582050c1598136ed2a748f76b981c94eChad Brubakerimport com.android.internal.util.XmlUtils; 105f96702f582050c1598136ed2a748f76b981c94eChad Brubaker 115f96702f582050c1598136ed2a748f76b981c94eChad Brubakerimport org.xmlpull.v1.XmlPullParser; 125f96702f582050c1598136ed2a748f76b981c94eChad Brubakerimport org.xmlpull.v1.XmlPullParserException; 135f96702f582050c1598136ed2a748f76b981c94eChad Brubaker 145f96702f582050c1598136ed2a748f76b981c94eChad Brubakerimport java.io.IOException; 155f96702f582050c1598136ed2a748f76b981c94eChad Brubakerimport java.text.ParseException; 165f96702f582050c1598136ed2a748f76b981c94eChad Brubakerimport java.text.SimpleDateFormat; 175f96702f582050c1598136ed2a748f76b981c94eChad Brubakerimport java.util.ArrayList; 185f96702f582050c1598136ed2a748f76b981c94eChad Brubakerimport java.util.Collection; 195f96702f582050c1598136ed2a748f76b981c94eChad Brubakerimport java.util.Date; 205f96702f582050c1598136ed2a748f76b981c94eChad Brubakerimport java.util.List; 215f96702f582050c1598136ed2a748f76b981c94eChad Brubakerimport java.util.Locale; 225f96702f582050c1598136ed2a748f76b981c94eChad Brubakerimport java.util.Set; 235f96702f582050c1598136ed2a748f76b981c94eChad Brubaker 245f96702f582050c1598136ed2a748f76b981c94eChad Brubaker/** 255f96702f582050c1598136ed2a748f76b981c94eChad Brubaker * {@link ConfigSource} based on an XML configuration file. 265f96702f582050c1598136ed2a748f76b981c94eChad Brubaker * 275f96702f582050c1598136ed2a748f76b981c94eChad Brubaker * @hide 285f96702f582050c1598136ed2a748f76b981c94eChad Brubaker */ 295f96702f582050c1598136ed2a748f76b981c94eChad Brubakerpublic class XmlConfigSource implements ConfigSource { 3008d36202daeb3e668911c9902edb61b6894f822eChad Brubaker private static final int CONFIG_BASE = 0; 3108d36202daeb3e668911c9902edb61b6894f822eChad Brubaker private static final int CONFIG_DOMAIN = 1; 3208d36202daeb3e668911c9902edb61b6894f822eChad Brubaker private static final int CONFIG_DEBUG = 2; 3308d36202daeb3e668911c9902edb61b6894f822eChad Brubaker 345f96702f582050c1598136ed2a748f76b981c94eChad Brubaker private final Object mLock = new Object(); 355f96702f582050c1598136ed2a748f76b981c94eChad Brubaker private final int mResourceId; 3608d36202daeb3e668911c9902edb61b6894f822eChad Brubaker private final boolean mDebugBuild; 375f96702f582050c1598136ed2a748f76b981c94eChad Brubaker 385f96702f582050c1598136ed2a748f76b981c94eChad Brubaker private boolean mInitialized; 395f96702f582050c1598136ed2a748f76b981c94eChad Brubaker private NetworkSecurityConfig mDefaultConfig; 405f96702f582050c1598136ed2a748f76b981c94eChad Brubaker private Set<Pair<Domain, NetworkSecurityConfig>> mDomainMap; 415f96702f582050c1598136ed2a748f76b981c94eChad Brubaker private Context mContext; 425f96702f582050c1598136ed2a748f76b981c94eChad Brubaker 435f96702f582050c1598136ed2a748f76b981c94eChad Brubaker public XmlConfigSource(Context context, int resourceId) { 4408d36202daeb3e668911c9902edb61b6894f822eChad Brubaker this(context, resourceId, false); 4508d36202daeb3e668911c9902edb61b6894f822eChad Brubaker } 4608d36202daeb3e668911c9902edb61b6894f822eChad Brubaker 4708d36202daeb3e668911c9902edb61b6894f822eChad Brubaker public XmlConfigSource(Context context, int resourceId, boolean debugBuild) { 485f96702f582050c1598136ed2a748f76b981c94eChad Brubaker mResourceId = resourceId; 495f96702f582050c1598136ed2a748f76b981c94eChad Brubaker mContext = context; 5008d36202daeb3e668911c9902edb61b6894f822eChad Brubaker mDebugBuild = debugBuild; 515f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } 525f96702f582050c1598136ed2a748f76b981c94eChad Brubaker 535f96702f582050c1598136ed2a748f76b981c94eChad Brubaker public Set<Pair<Domain, NetworkSecurityConfig>> getPerDomainConfigs() { 545f96702f582050c1598136ed2a748f76b981c94eChad Brubaker ensureInitialized(); 555f96702f582050c1598136ed2a748f76b981c94eChad Brubaker return mDomainMap; 565f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } 575f96702f582050c1598136ed2a748f76b981c94eChad Brubaker 585f96702f582050c1598136ed2a748f76b981c94eChad Brubaker public NetworkSecurityConfig getDefaultConfig() { 595f96702f582050c1598136ed2a748f76b981c94eChad Brubaker ensureInitialized(); 605f96702f582050c1598136ed2a748f76b981c94eChad Brubaker return mDefaultConfig; 615f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } 625f96702f582050c1598136ed2a748f76b981c94eChad Brubaker 6308d36202daeb3e668911c9902edb61b6894f822eChad Brubaker private static final String getConfigString(int configType) { 6408d36202daeb3e668911c9902edb61b6894f822eChad Brubaker switch (configType) { 6508d36202daeb3e668911c9902edb61b6894f822eChad Brubaker case CONFIG_BASE: 6608d36202daeb3e668911c9902edb61b6894f822eChad Brubaker return "base-config"; 6708d36202daeb3e668911c9902edb61b6894f822eChad Brubaker case CONFIG_DOMAIN: 6808d36202daeb3e668911c9902edb61b6894f822eChad Brubaker return "domain-config"; 6908d36202daeb3e668911c9902edb61b6894f822eChad Brubaker case CONFIG_DEBUG: 7008d36202daeb3e668911c9902edb61b6894f822eChad Brubaker return "debug-overrides"; 7108d36202daeb3e668911c9902edb61b6894f822eChad Brubaker default: 7208d36202daeb3e668911c9902edb61b6894f822eChad Brubaker throw new IllegalArgumentException("Unknown config type: " + configType); 7308d36202daeb3e668911c9902edb61b6894f822eChad Brubaker } 7408d36202daeb3e668911c9902edb61b6894f822eChad Brubaker } 7508d36202daeb3e668911c9902edb61b6894f822eChad Brubaker 765f96702f582050c1598136ed2a748f76b981c94eChad Brubaker private void ensureInitialized() { 775f96702f582050c1598136ed2a748f76b981c94eChad Brubaker synchronized (mLock) { 785f96702f582050c1598136ed2a748f76b981c94eChad Brubaker if (mInitialized) { 795f96702f582050c1598136ed2a748f76b981c94eChad Brubaker return; 805f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } 815f96702f582050c1598136ed2a748f76b981c94eChad Brubaker try (XmlResourceParser parser = mContext.getResources().getXml(mResourceId)) { 825f96702f582050c1598136ed2a748f76b981c94eChad Brubaker parseNetworkSecurityConfig(parser); 835f96702f582050c1598136ed2a748f76b981c94eChad Brubaker mContext = null; 845f96702f582050c1598136ed2a748f76b981c94eChad Brubaker mInitialized = true; 855f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } catch (Resources.NotFoundException | XmlPullParserException | IOException 865f96702f582050c1598136ed2a748f76b981c94eChad Brubaker | ParserException e) { 875f96702f582050c1598136ed2a748f76b981c94eChad Brubaker throw new RuntimeException("Failed to parse XML configuration from " 885f96702f582050c1598136ed2a748f76b981c94eChad Brubaker + mContext.getResources().getResourceEntryName(mResourceId), e); 895f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } 905f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } 915f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } 925f96702f582050c1598136ed2a748f76b981c94eChad Brubaker 935f96702f582050c1598136ed2a748f76b981c94eChad Brubaker private Pin parsePin(XmlResourceParser parser) 945f96702f582050c1598136ed2a748f76b981c94eChad Brubaker throws IOException, XmlPullParserException, ParserException { 955f96702f582050c1598136ed2a748f76b981c94eChad Brubaker String digestAlgorithm = parser.getAttributeValue(null, "digest"); 965f96702f582050c1598136ed2a748f76b981c94eChad Brubaker if (!Pin.isSupportedDigestAlgorithm(digestAlgorithm)) { 975f96702f582050c1598136ed2a748f76b981c94eChad Brubaker throw new ParserException(parser, "Unsupported pin digest algorithm: " 985f96702f582050c1598136ed2a748f76b981c94eChad Brubaker + digestAlgorithm); 995f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } 1005f96702f582050c1598136ed2a748f76b981c94eChad Brubaker if (parser.next() != XmlPullParser.TEXT) { 1015f96702f582050c1598136ed2a748f76b981c94eChad Brubaker throw new ParserException(parser, "Missing pin digest"); 1025f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } 1035f96702f582050c1598136ed2a748f76b981c94eChad Brubaker String digest = parser.getText(); 1045f96702f582050c1598136ed2a748f76b981c94eChad Brubaker byte[] decodedDigest = null; 1055f96702f582050c1598136ed2a748f76b981c94eChad Brubaker try { 1065f96702f582050c1598136ed2a748f76b981c94eChad Brubaker decodedDigest = Base64.decode(digest, 0); 1075f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } catch (IllegalArgumentException e) { 1085f96702f582050c1598136ed2a748f76b981c94eChad Brubaker throw new ParserException(parser, "Invalid pin digest", e); 1095f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } 1105f96702f582050c1598136ed2a748f76b981c94eChad Brubaker int expectedLength = Pin.getDigestLength(digestAlgorithm); 1115f96702f582050c1598136ed2a748f76b981c94eChad Brubaker if (decodedDigest.length != expectedLength) { 1125f96702f582050c1598136ed2a748f76b981c94eChad Brubaker throw new ParserException(parser, "digest length " + decodedDigest.length 1135f96702f582050c1598136ed2a748f76b981c94eChad Brubaker + " does not match expected length for " + digestAlgorithm + " of " 1145f96702f582050c1598136ed2a748f76b981c94eChad Brubaker + expectedLength); 1155f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } 1165f96702f582050c1598136ed2a748f76b981c94eChad Brubaker if (parser.next() != XmlPullParser.END_TAG) { 1175f96702f582050c1598136ed2a748f76b981c94eChad Brubaker throw new ParserException(parser, "pin contains additional elements"); 1185f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } 1195f96702f582050c1598136ed2a748f76b981c94eChad Brubaker return new Pin(digestAlgorithm, decodedDigest); 1205f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } 1215f96702f582050c1598136ed2a748f76b981c94eChad Brubaker 1225f96702f582050c1598136ed2a748f76b981c94eChad Brubaker private PinSet parsePinSet(XmlResourceParser parser) 1235f96702f582050c1598136ed2a748f76b981c94eChad Brubaker throws IOException, XmlPullParserException, ParserException { 1245f96702f582050c1598136ed2a748f76b981c94eChad Brubaker String expirationDate = parser.getAttributeValue(null, "expiration"); 1255f96702f582050c1598136ed2a748f76b981c94eChad Brubaker long expirationTimestampMilis = Long.MAX_VALUE; 1265f96702f582050c1598136ed2a748f76b981c94eChad Brubaker if (expirationDate != null) { 1275f96702f582050c1598136ed2a748f76b981c94eChad Brubaker try { 1285f96702f582050c1598136ed2a748f76b981c94eChad Brubaker SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); 1295f96702f582050c1598136ed2a748f76b981c94eChad Brubaker sdf.setLenient(false); 1305f96702f582050c1598136ed2a748f76b981c94eChad Brubaker Date date = sdf.parse(expirationDate); 1315f96702f582050c1598136ed2a748f76b981c94eChad Brubaker if (date == null) { 1325f96702f582050c1598136ed2a748f76b981c94eChad Brubaker throw new ParserException(parser, "Invalid expiration date in pin-set"); 1335f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } 1345f96702f582050c1598136ed2a748f76b981c94eChad Brubaker expirationTimestampMilis = date.getTime(); 1355f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } catch (ParseException e) { 1365f96702f582050c1598136ed2a748f76b981c94eChad Brubaker throw new ParserException(parser, "Invalid expiration date in pin-set", e); 1375f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } 1385f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } 1395f96702f582050c1598136ed2a748f76b981c94eChad Brubaker 1405f96702f582050c1598136ed2a748f76b981c94eChad Brubaker int outerDepth = parser.getDepth(); 1415f96702f582050c1598136ed2a748f76b981c94eChad Brubaker Set<Pin> pins = new ArraySet<>(); 1425f96702f582050c1598136ed2a748f76b981c94eChad Brubaker while (XmlUtils.nextElementWithin(parser, outerDepth)) { 1435f96702f582050c1598136ed2a748f76b981c94eChad Brubaker String tagName = parser.getName(); 1445f96702f582050c1598136ed2a748f76b981c94eChad Brubaker if (tagName.equals("pin")) { 1455f96702f582050c1598136ed2a748f76b981c94eChad Brubaker pins.add(parsePin(parser)); 1465f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } else { 1475f96702f582050c1598136ed2a748f76b981c94eChad Brubaker XmlUtils.skipCurrentTag(parser); 1485f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } 1495f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } 1505f96702f582050c1598136ed2a748f76b981c94eChad Brubaker return new PinSet(pins, expirationTimestampMilis); 1515f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } 1525f96702f582050c1598136ed2a748f76b981c94eChad Brubaker 1535f96702f582050c1598136ed2a748f76b981c94eChad Brubaker private Domain parseDomain(XmlResourceParser parser, Set<String> seenDomains) 1545f96702f582050c1598136ed2a748f76b981c94eChad Brubaker throws IOException, XmlPullParserException, ParserException { 1555f96702f582050c1598136ed2a748f76b981c94eChad Brubaker boolean includeSubdomains = 1565f96702f582050c1598136ed2a748f76b981c94eChad Brubaker parser.getAttributeBooleanValue(null, "includeSubdomains", false); 1575f96702f582050c1598136ed2a748f76b981c94eChad Brubaker if (parser.next() != XmlPullParser.TEXT) { 1585f96702f582050c1598136ed2a748f76b981c94eChad Brubaker throw new ParserException(parser, "Domain name missing"); 1595f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } 1605f96702f582050c1598136ed2a748f76b981c94eChad Brubaker String domain = parser.getText().toLowerCase(Locale.US); 1615f96702f582050c1598136ed2a748f76b981c94eChad Brubaker if (parser.next() != XmlPullParser.END_TAG) { 1625f96702f582050c1598136ed2a748f76b981c94eChad Brubaker throw new ParserException(parser, "domain contains additional elements"); 1635f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } 1645f96702f582050c1598136ed2a748f76b981c94eChad Brubaker // Domains are matched using a most specific match, so don't allow duplicates. 1655f96702f582050c1598136ed2a748f76b981c94eChad Brubaker // includeSubdomains isn't relevant here, both android.com + subdomains and android.com 1665f96702f582050c1598136ed2a748f76b981c94eChad Brubaker // match for android.com equally. Do not allow any duplicates period. 1675f96702f582050c1598136ed2a748f76b981c94eChad Brubaker if (!seenDomains.add(domain)) { 1685f96702f582050c1598136ed2a748f76b981c94eChad Brubaker throw new ParserException(parser, domain + " has already been specified"); 1695f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } 1705f96702f582050c1598136ed2a748f76b981c94eChad Brubaker return new Domain(domain, includeSubdomains); 1715f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } 1725f96702f582050c1598136ed2a748f76b981c94eChad Brubaker 17308d36202daeb3e668911c9902edb61b6894f822eChad Brubaker private CertificatesEntryRef parseCertificatesEntry(XmlResourceParser parser, 17408d36202daeb3e668911c9902edb61b6894f822eChad Brubaker boolean defaultOverridePins) 1755f96702f582050c1598136ed2a748f76b981c94eChad Brubaker throws IOException, XmlPullParserException, ParserException { 17608d36202daeb3e668911c9902edb61b6894f822eChad Brubaker boolean overridePins = 17708d36202daeb3e668911c9902edb61b6894f822eChad Brubaker parser.getAttributeBooleanValue(null, "overridePins", defaultOverridePins); 1785f96702f582050c1598136ed2a748f76b981c94eChad Brubaker int sourceId = parser.getAttributeResourceValue(null, "src", -1); 1795f96702f582050c1598136ed2a748f76b981c94eChad Brubaker String sourceString = parser.getAttributeValue(null, "src"); 1805f96702f582050c1598136ed2a748f76b981c94eChad Brubaker CertificateSource source = null; 1815f96702f582050c1598136ed2a748f76b981c94eChad Brubaker if (sourceString == null) { 1825f96702f582050c1598136ed2a748f76b981c94eChad Brubaker throw new ParserException(parser, "certificates element missing src attribute"); 1835f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } 1845f96702f582050c1598136ed2a748f76b981c94eChad Brubaker if (sourceId != -1) { 1855f96702f582050c1598136ed2a748f76b981c94eChad Brubaker // TODO: Cache ResourceCertificateSources by sourceId 1865f96702f582050c1598136ed2a748f76b981c94eChad Brubaker source = new ResourceCertificateSource(sourceId, mContext); 1875f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } else if ("system".equals(sourceString)) { 1885f96702f582050c1598136ed2a748f76b981c94eChad Brubaker source = SystemCertificateSource.getInstance(); 1895f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } else if ("user".equals(sourceString)) { 1905f96702f582050c1598136ed2a748f76b981c94eChad Brubaker source = UserCertificateSource.getInstance(); 1915f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } else { 1925f96702f582050c1598136ed2a748f76b981c94eChad Brubaker throw new ParserException(parser, "Unknown certificates src. " 1935f96702f582050c1598136ed2a748f76b981c94eChad Brubaker + "Should be one of system|user|@resourceVal"); 1945f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } 1955f96702f582050c1598136ed2a748f76b981c94eChad Brubaker XmlUtils.skipCurrentTag(parser); 1965f96702f582050c1598136ed2a748f76b981c94eChad Brubaker return new CertificatesEntryRef(source, overridePins); 1975f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } 1985f96702f582050c1598136ed2a748f76b981c94eChad Brubaker 19908d36202daeb3e668911c9902edb61b6894f822eChad Brubaker private Collection<CertificatesEntryRef> parseTrustAnchors(XmlResourceParser parser, 20008d36202daeb3e668911c9902edb61b6894f822eChad Brubaker boolean defaultOverridePins) 2015f96702f582050c1598136ed2a748f76b981c94eChad Brubaker throws IOException, XmlPullParserException, ParserException { 2025f96702f582050c1598136ed2a748f76b981c94eChad Brubaker int outerDepth = parser.getDepth(); 2035f96702f582050c1598136ed2a748f76b981c94eChad Brubaker List<CertificatesEntryRef> anchors = new ArrayList<>(); 2045f96702f582050c1598136ed2a748f76b981c94eChad Brubaker while (XmlUtils.nextElementWithin(parser, outerDepth)) { 2055f96702f582050c1598136ed2a748f76b981c94eChad Brubaker String tagName = parser.getName(); 2065f96702f582050c1598136ed2a748f76b981c94eChad Brubaker if (tagName.equals("certificates")) { 20708d36202daeb3e668911c9902edb61b6894f822eChad Brubaker anchors.add(parseCertificatesEntry(parser, defaultOverridePins)); 2085f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } else { 2095f96702f582050c1598136ed2a748f76b981c94eChad Brubaker XmlUtils.skipCurrentTag(parser); 2105f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } 2115f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } 2125f96702f582050c1598136ed2a748f76b981c94eChad Brubaker return anchors; 2135f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } 2145f96702f582050c1598136ed2a748f76b981c94eChad Brubaker 215bd173c28fcded629da722c6669f1b6478cdcd94fChad Brubaker private List<Pair<NetworkSecurityConfig.Builder, Set<Domain>>> parseConfigEntry( 216bd173c28fcded629da722c6669f1b6478cdcd94fChad Brubaker XmlResourceParser parser, Set<String> seenDomains, 21708d36202daeb3e668911c9902edb61b6894f822eChad Brubaker NetworkSecurityConfig.Builder parentBuilder, int configType) 2185f96702f582050c1598136ed2a748f76b981c94eChad Brubaker throws IOException, XmlPullParserException, ParserException { 219bd173c28fcded629da722c6669f1b6478cdcd94fChad Brubaker List<Pair<NetworkSecurityConfig.Builder, Set<Domain>>> builders = new ArrayList<>(); 2205f96702f582050c1598136ed2a748f76b981c94eChad Brubaker NetworkSecurityConfig.Builder builder = new NetworkSecurityConfig.Builder(); 221bd173c28fcded629da722c6669f1b6478cdcd94fChad Brubaker builder.setParent(parentBuilder); 2225f96702f582050c1598136ed2a748f76b981c94eChad Brubaker Set<Domain> domains = new ArraySet<>(); 2235f96702f582050c1598136ed2a748f76b981c94eChad Brubaker boolean seenPinSet = false; 2245f96702f582050c1598136ed2a748f76b981c94eChad Brubaker boolean seenTrustAnchors = false; 22508d36202daeb3e668911c9902edb61b6894f822eChad Brubaker boolean defaultOverridePins = configType == CONFIG_DEBUG; 2265f96702f582050c1598136ed2a748f76b981c94eChad Brubaker String configName = parser.getName(); 2275f96702f582050c1598136ed2a748f76b981c94eChad Brubaker int outerDepth = parser.getDepth(); 228bd173c28fcded629da722c6669f1b6478cdcd94fChad Brubaker // Add this builder now so that this builder occurs before any of its children. This 229bd173c28fcded629da722c6669f1b6478cdcd94fChad Brubaker // makes the final build pass easier. 230bd173c28fcded629da722c6669f1b6478cdcd94fChad Brubaker builders.add(new Pair<>(builder, domains)); 2315f96702f582050c1598136ed2a748f76b981c94eChad Brubaker // Parse config attributes. Only set values that are present, config inheritence will 2325f96702f582050c1598136ed2a748f76b981c94eChad Brubaker // handle the rest. 2335f96702f582050c1598136ed2a748f76b981c94eChad Brubaker for (int i = 0; i < parser.getAttributeCount(); i++) { 2345f96702f582050c1598136ed2a748f76b981c94eChad Brubaker String name = parser.getAttributeName(i); 2355f96702f582050c1598136ed2a748f76b981c94eChad Brubaker if ("hstsEnforced".equals(name)) { 2365f96702f582050c1598136ed2a748f76b981c94eChad Brubaker builder.setHstsEnforced( 2375f96702f582050c1598136ed2a748f76b981c94eChad Brubaker parser.getAttributeBooleanValue(i, 2385f96702f582050c1598136ed2a748f76b981c94eChad Brubaker NetworkSecurityConfig.DEFAULT_HSTS_ENFORCED)); 2395f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } else if ("cleartextTrafficPermitted".equals(name)) { 2405f96702f582050c1598136ed2a748f76b981c94eChad Brubaker builder.setCleartextTrafficPermitted( 2415f96702f582050c1598136ed2a748f76b981c94eChad Brubaker parser.getAttributeBooleanValue(i, 2425f96702f582050c1598136ed2a748f76b981c94eChad Brubaker NetworkSecurityConfig.DEFAULT_CLEARTEXT_TRAFFIC_PERMITTED)); 2435f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } 2445f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } 2455f96702f582050c1598136ed2a748f76b981c94eChad Brubaker // Parse the config elements. 2465f96702f582050c1598136ed2a748f76b981c94eChad Brubaker while (XmlUtils.nextElementWithin(parser, outerDepth)) { 2475f96702f582050c1598136ed2a748f76b981c94eChad Brubaker String tagName = parser.getName(); 2485f96702f582050c1598136ed2a748f76b981c94eChad Brubaker if ("domain".equals(tagName)) { 24908d36202daeb3e668911c9902edb61b6894f822eChad Brubaker if (configType != CONFIG_DOMAIN) { 25008d36202daeb3e668911c9902edb61b6894f822eChad Brubaker throw new ParserException(parser, 25108d36202daeb3e668911c9902edb61b6894f822eChad Brubaker "domain element not allowed in " + getConfigString(configType)); 2525f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } 2535f96702f582050c1598136ed2a748f76b981c94eChad Brubaker Domain domain = parseDomain(parser, seenDomains); 2545f96702f582050c1598136ed2a748f76b981c94eChad Brubaker domains.add(domain); 2555f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } else if ("trust-anchors".equals(tagName)) { 2565f96702f582050c1598136ed2a748f76b981c94eChad Brubaker if (seenTrustAnchors) { 2575f96702f582050c1598136ed2a748f76b981c94eChad Brubaker throw new ParserException(parser, 2585f96702f582050c1598136ed2a748f76b981c94eChad Brubaker "Multiple trust-anchor elements not allowed"); 2595f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } 26008d36202daeb3e668911c9902edb61b6894f822eChad Brubaker builder.addCertificatesEntryRefs( 26108d36202daeb3e668911c9902edb61b6894f822eChad Brubaker parseTrustAnchors(parser, defaultOverridePins)); 2625f96702f582050c1598136ed2a748f76b981c94eChad Brubaker seenTrustAnchors = true; 2635f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } else if ("pin-set".equals(tagName)) { 26408d36202daeb3e668911c9902edb61b6894f822eChad Brubaker if (configType != CONFIG_DOMAIN) { 2655f96702f582050c1598136ed2a748f76b981c94eChad Brubaker throw new ParserException(parser, 26608d36202daeb3e668911c9902edb61b6894f822eChad Brubaker "pin-set element not allowed in " + getConfigString(configType)); 2675f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } 2685f96702f582050c1598136ed2a748f76b981c94eChad Brubaker if (seenPinSet) { 2695f96702f582050c1598136ed2a748f76b981c94eChad Brubaker throw new ParserException(parser, "Multiple pin-set elements not allowed"); 2705f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } 2715f96702f582050c1598136ed2a748f76b981c94eChad Brubaker builder.setPinSet(parsePinSet(parser)); 2725f96702f582050c1598136ed2a748f76b981c94eChad Brubaker seenPinSet = true; 273bd173c28fcded629da722c6669f1b6478cdcd94fChad Brubaker } else if ("domain-config".equals(tagName)) { 27408d36202daeb3e668911c9902edb61b6894f822eChad Brubaker if (configType != CONFIG_DOMAIN) { 275bd173c28fcded629da722c6669f1b6478cdcd94fChad Brubaker throw new ParserException(parser, 27608d36202daeb3e668911c9902edb61b6894f822eChad Brubaker "Nested domain-config not allowed in " + getConfigString(configType)); 277bd173c28fcded629da722c6669f1b6478cdcd94fChad Brubaker } 27808d36202daeb3e668911c9902edb61b6894f822eChad Brubaker builders.addAll(parseConfigEntry(parser, seenDomains, builder, configType)); 2795f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } else { 2805f96702f582050c1598136ed2a748f76b981c94eChad Brubaker XmlUtils.skipCurrentTag(parser); 2815f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } 2825f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } 28308d36202daeb3e668911c9902edb61b6894f822eChad Brubaker if (configType == CONFIG_DOMAIN && domains.isEmpty()) { 2845f96702f582050c1598136ed2a748f76b981c94eChad Brubaker throw new ParserException(parser, "No domain elements in domain-config"); 2855f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } 286bd173c28fcded629da722c6669f1b6478cdcd94fChad Brubaker return builders; 2875f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } 2885f96702f582050c1598136ed2a748f76b981c94eChad Brubaker 28908d36202daeb3e668911c9902edb61b6894f822eChad Brubaker private void addDebugAnchorsIfNeeded(NetworkSecurityConfig.Builder debugConfigBuilder, 29008d36202daeb3e668911c9902edb61b6894f822eChad Brubaker NetworkSecurityConfig.Builder builder) { 29108d36202daeb3e668911c9902edb61b6894f822eChad Brubaker if (debugConfigBuilder == null || !debugConfigBuilder.hasCertificatesEntryRefs()) { 29208d36202daeb3e668911c9902edb61b6894f822eChad Brubaker return; 29308d36202daeb3e668911c9902edb61b6894f822eChad Brubaker } 29408d36202daeb3e668911c9902edb61b6894f822eChad Brubaker // Don't add trust anchors if not already present, the builder will inherit the anchors 29508d36202daeb3e668911c9902edb61b6894f822eChad Brubaker // from its parent, and that's where the trust anchors should be added. 29608d36202daeb3e668911c9902edb61b6894f822eChad Brubaker if (!builder.hasCertificatesEntryRefs()) { 29708d36202daeb3e668911c9902edb61b6894f822eChad Brubaker return; 29808d36202daeb3e668911c9902edb61b6894f822eChad Brubaker } 29908d36202daeb3e668911c9902edb61b6894f822eChad Brubaker 30008d36202daeb3e668911c9902edb61b6894f822eChad Brubaker builder.addCertificatesEntryRefs(debugConfigBuilder.getCertificatesEntryRefs()); 30108d36202daeb3e668911c9902edb61b6894f822eChad Brubaker } 30208d36202daeb3e668911c9902edb61b6894f822eChad Brubaker 3035f96702f582050c1598136ed2a748f76b981c94eChad Brubaker private void parseNetworkSecurityConfig(XmlResourceParser parser) 3045f96702f582050c1598136ed2a748f76b981c94eChad Brubaker throws IOException, XmlPullParserException, ParserException { 3055f96702f582050c1598136ed2a748f76b981c94eChad Brubaker Set<String> seenDomains = new ArraySet<>(); 3065f96702f582050c1598136ed2a748f76b981c94eChad Brubaker List<Pair<NetworkSecurityConfig.Builder, Set<Domain>>> builders = new ArrayList<>(); 3075f96702f582050c1598136ed2a748f76b981c94eChad Brubaker NetworkSecurityConfig.Builder baseConfigBuilder = null; 30808d36202daeb3e668911c9902edb61b6894f822eChad Brubaker NetworkSecurityConfig.Builder debugConfigBuilder = null; 3095f96702f582050c1598136ed2a748f76b981c94eChad Brubaker boolean seenDebugOverrides = false; 3105f96702f582050c1598136ed2a748f76b981c94eChad Brubaker boolean seenBaseConfig = false; 3115f96702f582050c1598136ed2a748f76b981c94eChad Brubaker 3125f96702f582050c1598136ed2a748f76b981c94eChad Brubaker XmlUtils.beginDocument(parser, "network-security-config"); 3135f96702f582050c1598136ed2a748f76b981c94eChad Brubaker int outerDepth = parser.getDepth(); 3145f96702f582050c1598136ed2a748f76b981c94eChad Brubaker while (XmlUtils.nextElementWithin(parser, outerDepth)) { 3155f96702f582050c1598136ed2a748f76b981c94eChad Brubaker if ("base-config".equals(parser.getName())) { 3165f96702f582050c1598136ed2a748f76b981c94eChad Brubaker if (seenBaseConfig) { 3175f96702f582050c1598136ed2a748f76b981c94eChad Brubaker throw new ParserException(parser, "Only one base-config allowed"); 3185f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } 3195f96702f582050c1598136ed2a748f76b981c94eChad Brubaker seenBaseConfig = true; 32008d36202daeb3e668911c9902edb61b6894f822eChad Brubaker baseConfigBuilder = 32108d36202daeb3e668911c9902edb61b6894f822eChad Brubaker parseConfigEntry(parser, seenDomains, null, CONFIG_BASE).get(0).first; 3225f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } else if ("domain-config".equals(parser.getName())) { 32308d36202daeb3e668911c9902edb61b6894f822eChad Brubaker builders.addAll( 32408d36202daeb3e668911c9902edb61b6894f822eChad Brubaker parseConfigEntry(parser, seenDomains, baseConfigBuilder, CONFIG_DOMAIN)); 32508d36202daeb3e668911c9902edb61b6894f822eChad Brubaker } else if ("debug-overrides".equals(parser.getName())) { 32608d36202daeb3e668911c9902edb61b6894f822eChad Brubaker if (seenDebugOverrides) { 32708d36202daeb3e668911c9902edb61b6894f822eChad Brubaker throw new ParserException(parser, "Only one debug-overrides allowed"); 32808d36202daeb3e668911c9902edb61b6894f822eChad Brubaker } 32908d36202daeb3e668911c9902edb61b6894f822eChad Brubaker if (mDebugBuild) { 33008d36202daeb3e668911c9902edb61b6894f822eChad Brubaker debugConfigBuilder = 33108d36202daeb3e668911c9902edb61b6894f822eChad Brubaker parseConfigEntry(parser, seenDomains, null, CONFIG_DEBUG).get(0).first; 33208d36202daeb3e668911c9902edb61b6894f822eChad Brubaker } else { 33308d36202daeb3e668911c9902edb61b6894f822eChad Brubaker XmlUtils.skipCurrentTag(parser); 33408d36202daeb3e668911c9902edb61b6894f822eChad Brubaker } 33508d36202daeb3e668911c9902edb61b6894f822eChad Brubaker seenDebugOverrides = true; 3365f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } else { 3375f96702f582050c1598136ed2a748f76b981c94eChad Brubaker XmlUtils.skipCurrentTag(parser); 3385f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } 3395f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } 3405f96702f582050c1598136ed2a748f76b981c94eChad Brubaker 3415f96702f582050c1598136ed2a748f76b981c94eChad Brubaker // Use the platform default as the parent of the base config for any values not provided 3425f96702f582050c1598136ed2a748f76b981c94eChad Brubaker // there. If there is no base config use the platform default. 3435f96702f582050c1598136ed2a748f76b981c94eChad Brubaker NetworkSecurityConfig.Builder platformDefaultBuilder = 3445f96702f582050c1598136ed2a748f76b981c94eChad Brubaker NetworkSecurityConfig.getDefaultBuilder(); 34508d36202daeb3e668911c9902edb61b6894f822eChad Brubaker addDebugAnchorsIfNeeded(debugConfigBuilder, platformDefaultBuilder); 3465f96702f582050c1598136ed2a748f76b981c94eChad Brubaker if (baseConfigBuilder != null) { 3475f96702f582050c1598136ed2a748f76b981c94eChad Brubaker baseConfigBuilder.setParent(platformDefaultBuilder); 34808d36202daeb3e668911c9902edb61b6894f822eChad Brubaker addDebugAnchorsIfNeeded(debugConfigBuilder, baseConfigBuilder); 3495f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } else { 3505f96702f582050c1598136ed2a748f76b981c94eChad Brubaker baseConfigBuilder = platformDefaultBuilder; 3515f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } 3525f96702f582050c1598136ed2a748f76b981c94eChad Brubaker // Build the per-domain config mapping. 3535f96702f582050c1598136ed2a748f76b981c94eChad Brubaker Set<Pair<Domain, NetworkSecurityConfig>> configs = new ArraySet<>(); 3545f96702f582050c1598136ed2a748f76b981c94eChad Brubaker 3555f96702f582050c1598136ed2a748f76b981c94eChad Brubaker for (Pair<NetworkSecurityConfig.Builder, Set<Domain>> entry : builders) { 3565f96702f582050c1598136ed2a748f76b981c94eChad Brubaker NetworkSecurityConfig.Builder builder = entry.first; 3575f96702f582050c1598136ed2a748f76b981c94eChad Brubaker Set<Domain> domains = entry.second; 358bd173c28fcded629da722c6669f1b6478cdcd94fChad Brubaker // Set the parent of configs that do not have a parent to the base-config. This can 359bd173c28fcded629da722c6669f1b6478cdcd94fChad Brubaker // happen if the base-config comes after a domain-config in the file. 360bd173c28fcded629da722c6669f1b6478cdcd94fChad Brubaker // Note that this is safe with regards to children because of the order that 361bd173c28fcded629da722c6669f1b6478cdcd94fChad Brubaker // parseConfigEntry returns builders, the parent is always before the children. The 362bd173c28fcded629da722c6669f1b6478cdcd94fChad Brubaker // children builders will not have build called until _after_ their parents have their 363bd173c28fcded629da722c6669f1b6478cdcd94fChad Brubaker // parent set so everything is consistent. 364bd173c28fcded629da722c6669f1b6478cdcd94fChad Brubaker if (builder.getParent() == null) { 365bd173c28fcded629da722c6669f1b6478cdcd94fChad Brubaker builder.setParent(baseConfigBuilder); 366bd173c28fcded629da722c6669f1b6478cdcd94fChad Brubaker } 36708d36202daeb3e668911c9902edb61b6894f822eChad Brubaker addDebugAnchorsIfNeeded(debugConfigBuilder, builder); 3685f96702f582050c1598136ed2a748f76b981c94eChad Brubaker NetworkSecurityConfig config = builder.build(); 3695f96702f582050c1598136ed2a748f76b981c94eChad Brubaker for (Domain domain : domains) { 3705f96702f582050c1598136ed2a748f76b981c94eChad Brubaker configs.add(new Pair<>(domain, config)); 3715f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } 3725f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } 3735f96702f582050c1598136ed2a748f76b981c94eChad Brubaker mDefaultConfig = baseConfigBuilder.build(); 3745f96702f582050c1598136ed2a748f76b981c94eChad Brubaker mDomainMap = configs; 3755f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } 3765f96702f582050c1598136ed2a748f76b981c94eChad Brubaker 3775f96702f582050c1598136ed2a748f76b981c94eChad Brubaker public static class ParserException extends Exception { 3785f96702f582050c1598136ed2a748f76b981c94eChad Brubaker 3795f96702f582050c1598136ed2a748f76b981c94eChad Brubaker public ParserException(XmlPullParser parser, String message, Throwable cause) { 3805f96702f582050c1598136ed2a748f76b981c94eChad Brubaker super(message + " at: " + parser.getPositionDescription(), cause); 3815f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } 3825f96702f582050c1598136ed2a748f76b981c94eChad Brubaker 3835f96702f582050c1598136ed2a748f76b981c94eChad Brubaker public ParserException(XmlPullParser parser, String message) { 3845f96702f582050c1598136ed2a748f76b981c94eChad Brubaker this(parser, message, null); 3855f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } 3865f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } 3875f96702f582050c1598136ed2a748f76b981c94eChad Brubaker} 388