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; 632d2a1024f75f7e917f2aca18d34322a46d36bcbChad Brubakerimport android.os.Build; 75f96702f582050c1598136ed2a748f76b981c94eChad Brubakerimport android.util.ArraySet; 85f96702f582050c1598136ed2a748f76b981c94eChad Brubakerimport android.util.Base64; 95f96702f582050c1598136ed2a748f76b981c94eChad Brubakerimport android.util.Pair; 1032d2a1024f75f7e917f2aca18d34322a46d36bcbChad Brubakerimport com.android.internal.annotations.VisibleForTesting; 115f96702f582050c1598136ed2a748f76b981c94eChad Brubakerimport com.android.internal.util.XmlUtils; 125f96702f582050c1598136ed2a748f76b981c94eChad Brubaker 135f96702f582050c1598136ed2a748f76b981c94eChad Brubakerimport org.xmlpull.v1.XmlPullParser; 145f96702f582050c1598136ed2a748f76b981c94eChad Brubakerimport org.xmlpull.v1.XmlPullParserException; 155f96702f582050c1598136ed2a748f76b981c94eChad Brubaker 165f96702f582050c1598136ed2a748f76b981c94eChad Brubakerimport java.io.IOException; 175f96702f582050c1598136ed2a748f76b981c94eChad Brubakerimport java.text.ParseException; 185f96702f582050c1598136ed2a748f76b981c94eChad Brubakerimport java.text.SimpleDateFormat; 195f96702f582050c1598136ed2a748f76b981c94eChad Brubakerimport java.util.ArrayList; 205f96702f582050c1598136ed2a748f76b981c94eChad Brubakerimport java.util.Collection; 215f96702f582050c1598136ed2a748f76b981c94eChad Brubakerimport java.util.Date; 225f96702f582050c1598136ed2a748f76b981c94eChad Brubakerimport java.util.List; 235f96702f582050c1598136ed2a748f76b981c94eChad Brubakerimport java.util.Locale; 245f96702f582050c1598136ed2a748f76b981c94eChad Brubakerimport java.util.Set; 255f96702f582050c1598136ed2a748f76b981c94eChad Brubaker 265f96702f582050c1598136ed2a748f76b981c94eChad Brubaker/** 275f96702f582050c1598136ed2a748f76b981c94eChad Brubaker * {@link ConfigSource} based on an XML configuration file. 285f96702f582050c1598136ed2a748f76b981c94eChad Brubaker * 295f96702f582050c1598136ed2a748f76b981c94eChad Brubaker * @hide 305f96702f582050c1598136ed2a748f76b981c94eChad Brubaker */ 315f96702f582050c1598136ed2a748f76b981c94eChad Brubakerpublic class XmlConfigSource implements ConfigSource { 3208d36202daeb3e668911c9902edb61b6894f822eChad Brubaker private static final int CONFIG_BASE = 0; 3308d36202daeb3e668911c9902edb61b6894f822eChad Brubaker private static final int CONFIG_DOMAIN = 1; 3408d36202daeb3e668911c9902edb61b6894f822eChad Brubaker private static final int CONFIG_DEBUG = 2; 3508d36202daeb3e668911c9902edb61b6894f822eChad Brubaker 365f96702f582050c1598136ed2a748f76b981c94eChad Brubaker private final Object mLock = new Object(); 375f96702f582050c1598136ed2a748f76b981c94eChad Brubaker private final int mResourceId; 3808d36202daeb3e668911c9902edb61b6894f822eChad Brubaker private final boolean mDebugBuild; 3932d2a1024f75f7e917f2aca18d34322a46d36bcbChad Brubaker private final int mTargetSdkVersion; 405f96702f582050c1598136ed2a748f76b981c94eChad Brubaker 415f96702f582050c1598136ed2a748f76b981c94eChad Brubaker private boolean mInitialized; 425f96702f582050c1598136ed2a748f76b981c94eChad Brubaker private NetworkSecurityConfig mDefaultConfig; 435f96702f582050c1598136ed2a748f76b981c94eChad Brubaker private Set<Pair<Domain, NetworkSecurityConfig>> mDomainMap; 445f96702f582050c1598136ed2a748f76b981c94eChad Brubaker private Context mContext; 455f96702f582050c1598136ed2a748f76b981c94eChad Brubaker 4632d2a1024f75f7e917f2aca18d34322a46d36bcbChad Brubaker @VisibleForTesting 475f96702f582050c1598136ed2a748f76b981c94eChad Brubaker public XmlConfigSource(Context context, int resourceId) { 4808d36202daeb3e668911c9902edb61b6894f822eChad Brubaker this(context, resourceId, false); 4908d36202daeb3e668911c9902edb61b6894f822eChad Brubaker } 5008d36202daeb3e668911c9902edb61b6894f822eChad Brubaker 5132d2a1024f75f7e917f2aca18d34322a46d36bcbChad Brubaker @VisibleForTesting 5208d36202daeb3e668911c9902edb61b6894f822eChad Brubaker public XmlConfigSource(Context context, int resourceId, boolean debugBuild) { 5332d2a1024f75f7e917f2aca18d34322a46d36bcbChad Brubaker this(context, resourceId, debugBuild, Build.VERSION_CODES.CUR_DEVELOPMENT); 5432d2a1024f75f7e917f2aca18d34322a46d36bcbChad Brubaker } 5532d2a1024f75f7e917f2aca18d34322a46d36bcbChad Brubaker 5632d2a1024f75f7e917f2aca18d34322a46d36bcbChad Brubaker public XmlConfigSource(Context context, int resourceId, boolean debugBuild, 5732d2a1024f75f7e917f2aca18d34322a46d36bcbChad Brubaker int targetSdkVersion) { 585f96702f582050c1598136ed2a748f76b981c94eChad Brubaker mResourceId = resourceId; 595f96702f582050c1598136ed2a748f76b981c94eChad Brubaker mContext = context; 6008d36202daeb3e668911c9902edb61b6894f822eChad Brubaker mDebugBuild = debugBuild; 6132d2a1024f75f7e917f2aca18d34322a46d36bcbChad Brubaker mTargetSdkVersion = targetSdkVersion; 625f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } 635f96702f582050c1598136ed2a748f76b981c94eChad Brubaker 645f96702f582050c1598136ed2a748f76b981c94eChad Brubaker public Set<Pair<Domain, NetworkSecurityConfig>> getPerDomainConfigs() { 655f96702f582050c1598136ed2a748f76b981c94eChad Brubaker ensureInitialized(); 665f96702f582050c1598136ed2a748f76b981c94eChad Brubaker return mDomainMap; 675f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } 685f96702f582050c1598136ed2a748f76b981c94eChad Brubaker 695f96702f582050c1598136ed2a748f76b981c94eChad Brubaker public NetworkSecurityConfig getDefaultConfig() { 705f96702f582050c1598136ed2a748f76b981c94eChad Brubaker ensureInitialized(); 715f96702f582050c1598136ed2a748f76b981c94eChad Brubaker return mDefaultConfig; 725f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } 735f96702f582050c1598136ed2a748f76b981c94eChad Brubaker 7408d36202daeb3e668911c9902edb61b6894f822eChad Brubaker private static final String getConfigString(int configType) { 7508d36202daeb3e668911c9902edb61b6894f822eChad Brubaker switch (configType) { 7608d36202daeb3e668911c9902edb61b6894f822eChad Brubaker case CONFIG_BASE: 7708d36202daeb3e668911c9902edb61b6894f822eChad Brubaker return "base-config"; 7808d36202daeb3e668911c9902edb61b6894f822eChad Brubaker case CONFIG_DOMAIN: 7908d36202daeb3e668911c9902edb61b6894f822eChad Brubaker return "domain-config"; 8008d36202daeb3e668911c9902edb61b6894f822eChad Brubaker case CONFIG_DEBUG: 8108d36202daeb3e668911c9902edb61b6894f822eChad Brubaker return "debug-overrides"; 8208d36202daeb3e668911c9902edb61b6894f822eChad Brubaker default: 8308d36202daeb3e668911c9902edb61b6894f822eChad Brubaker throw new IllegalArgumentException("Unknown config type: " + configType); 8408d36202daeb3e668911c9902edb61b6894f822eChad Brubaker } 8508d36202daeb3e668911c9902edb61b6894f822eChad Brubaker } 8608d36202daeb3e668911c9902edb61b6894f822eChad Brubaker 875f96702f582050c1598136ed2a748f76b981c94eChad Brubaker private void ensureInitialized() { 885f96702f582050c1598136ed2a748f76b981c94eChad Brubaker synchronized (mLock) { 895f96702f582050c1598136ed2a748f76b981c94eChad Brubaker if (mInitialized) { 905f96702f582050c1598136ed2a748f76b981c94eChad Brubaker return; 915f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } 925f96702f582050c1598136ed2a748f76b981c94eChad Brubaker try (XmlResourceParser parser = mContext.getResources().getXml(mResourceId)) { 935f96702f582050c1598136ed2a748f76b981c94eChad Brubaker parseNetworkSecurityConfig(parser); 945f96702f582050c1598136ed2a748f76b981c94eChad Brubaker mContext = null; 955f96702f582050c1598136ed2a748f76b981c94eChad Brubaker mInitialized = true; 965f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } catch (Resources.NotFoundException | XmlPullParserException | IOException 975f96702f582050c1598136ed2a748f76b981c94eChad Brubaker | ParserException e) { 985f96702f582050c1598136ed2a748f76b981c94eChad Brubaker throw new RuntimeException("Failed to parse XML configuration from " 995f96702f582050c1598136ed2a748f76b981c94eChad Brubaker + mContext.getResources().getResourceEntryName(mResourceId), e); 1005f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } 1015f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } 1025f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } 1035f96702f582050c1598136ed2a748f76b981c94eChad Brubaker 1045f96702f582050c1598136ed2a748f76b981c94eChad Brubaker private Pin parsePin(XmlResourceParser parser) 1055f96702f582050c1598136ed2a748f76b981c94eChad Brubaker throws IOException, XmlPullParserException, ParserException { 1065f96702f582050c1598136ed2a748f76b981c94eChad Brubaker String digestAlgorithm = parser.getAttributeValue(null, "digest"); 1075f96702f582050c1598136ed2a748f76b981c94eChad Brubaker if (!Pin.isSupportedDigestAlgorithm(digestAlgorithm)) { 1085f96702f582050c1598136ed2a748f76b981c94eChad Brubaker throw new ParserException(parser, "Unsupported pin digest algorithm: " 1095f96702f582050c1598136ed2a748f76b981c94eChad Brubaker + digestAlgorithm); 1105f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } 1115f96702f582050c1598136ed2a748f76b981c94eChad Brubaker if (parser.next() != XmlPullParser.TEXT) { 1125f96702f582050c1598136ed2a748f76b981c94eChad Brubaker throw new ParserException(parser, "Missing pin digest"); 1135f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } 1147cc736da82b814b383daaa59609372917fd004cdChad Brubaker String digest = parser.getText().trim(); 1155f96702f582050c1598136ed2a748f76b981c94eChad Brubaker byte[] decodedDigest = null; 1165f96702f582050c1598136ed2a748f76b981c94eChad Brubaker try { 1175f96702f582050c1598136ed2a748f76b981c94eChad Brubaker decodedDigest = Base64.decode(digest, 0); 1185f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } catch (IllegalArgumentException e) { 1195f96702f582050c1598136ed2a748f76b981c94eChad Brubaker throw new ParserException(parser, "Invalid pin digest", e); 1205f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } 1215f96702f582050c1598136ed2a748f76b981c94eChad Brubaker int expectedLength = Pin.getDigestLength(digestAlgorithm); 1225f96702f582050c1598136ed2a748f76b981c94eChad Brubaker if (decodedDigest.length != expectedLength) { 1235f96702f582050c1598136ed2a748f76b981c94eChad Brubaker throw new ParserException(parser, "digest length " + decodedDigest.length 1245f96702f582050c1598136ed2a748f76b981c94eChad Brubaker + " does not match expected length for " + digestAlgorithm + " of " 1255f96702f582050c1598136ed2a748f76b981c94eChad Brubaker + expectedLength); 1265f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } 1275f96702f582050c1598136ed2a748f76b981c94eChad Brubaker if (parser.next() != XmlPullParser.END_TAG) { 1285f96702f582050c1598136ed2a748f76b981c94eChad Brubaker throw new ParserException(parser, "pin contains additional elements"); 1295f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } 1305f96702f582050c1598136ed2a748f76b981c94eChad Brubaker return new Pin(digestAlgorithm, decodedDigest); 1315f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } 1325f96702f582050c1598136ed2a748f76b981c94eChad Brubaker 1335f96702f582050c1598136ed2a748f76b981c94eChad Brubaker private PinSet parsePinSet(XmlResourceParser parser) 1345f96702f582050c1598136ed2a748f76b981c94eChad Brubaker throws IOException, XmlPullParserException, ParserException { 1355f96702f582050c1598136ed2a748f76b981c94eChad Brubaker String expirationDate = parser.getAttributeValue(null, "expiration"); 1365f96702f582050c1598136ed2a748f76b981c94eChad Brubaker long expirationTimestampMilis = Long.MAX_VALUE; 1375f96702f582050c1598136ed2a748f76b981c94eChad Brubaker if (expirationDate != null) { 1385f96702f582050c1598136ed2a748f76b981c94eChad Brubaker try { 1395f96702f582050c1598136ed2a748f76b981c94eChad Brubaker SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); 1405f96702f582050c1598136ed2a748f76b981c94eChad Brubaker sdf.setLenient(false); 1415f96702f582050c1598136ed2a748f76b981c94eChad Brubaker Date date = sdf.parse(expirationDate); 1425f96702f582050c1598136ed2a748f76b981c94eChad Brubaker if (date == null) { 1435f96702f582050c1598136ed2a748f76b981c94eChad Brubaker throw new ParserException(parser, "Invalid expiration date in pin-set"); 1445f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } 1455f96702f582050c1598136ed2a748f76b981c94eChad Brubaker expirationTimestampMilis = date.getTime(); 1465f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } catch (ParseException e) { 1475f96702f582050c1598136ed2a748f76b981c94eChad Brubaker throw new ParserException(parser, "Invalid expiration date in pin-set", e); 1485f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } 1495f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } 1505f96702f582050c1598136ed2a748f76b981c94eChad Brubaker 1515f96702f582050c1598136ed2a748f76b981c94eChad Brubaker int outerDepth = parser.getDepth(); 1525f96702f582050c1598136ed2a748f76b981c94eChad Brubaker Set<Pin> pins = new ArraySet<>(); 1535f96702f582050c1598136ed2a748f76b981c94eChad Brubaker while (XmlUtils.nextElementWithin(parser, outerDepth)) { 1545f96702f582050c1598136ed2a748f76b981c94eChad Brubaker String tagName = parser.getName(); 1555f96702f582050c1598136ed2a748f76b981c94eChad Brubaker if (tagName.equals("pin")) { 1565f96702f582050c1598136ed2a748f76b981c94eChad Brubaker pins.add(parsePin(parser)); 1575f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } else { 1585f96702f582050c1598136ed2a748f76b981c94eChad Brubaker XmlUtils.skipCurrentTag(parser); 1595f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } 1605f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } 1615f96702f582050c1598136ed2a748f76b981c94eChad Brubaker return new PinSet(pins, expirationTimestampMilis); 1625f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } 1635f96702f582050c1598136ed2a748f76b981c94eChad Brubaker 1645f96702f582050c1598136ed2a748f76b981c94eChad Brubaker private Domain parseDomain(XmlResourceParser parser, Set<String> seenDomains) 1655f96702f582050c1598136ed2a748f76b981c94eChad Brubaker throws IOException, XmlPullParserException, ParserException { 1665f96702f582050c1598136ed2a748f76b981c94eChad Brubaker boolean includeSubdomains = 1675f96702f582050c1598136ed2a748f76b981c94eChad Brubaker parser.getAttributeBooleanValue(null, "includeSubdomains", false); 1685f96702f582050c1598136ed2a748f76b981c94eChad Brubaker if (parser.next() != XmlPullParser.TEXT) { 1695f96702f582050c1598136ed2a748f76b981c94eChad Brubaker throw new ParserException(parser, "Domain name missing"); 1705f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } 1717cc736da82b814b383daaa59609372917fd004cdChad Brubaker String domain = parser.getText().trim().toLowerCase(Locale.US); 1725f96702f582050c1598136ed2a748f76b981c94eChad Brubaker if (parser.next() != XmlPullParser.END_TAG) { 1735f96702f582050c1598136ed2a748f76b981c94eChad Brubaker throw new ParserException(parser, "domain contains additional elements"); 1745f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } 1755f96702f582050c1598136ed2a748f76b981c94eChad Brubaker // Domains are matched using a most specific match, so don't allow duplicates. 1765f96702f582050c1598136ed2a748f76b981c94eChad Brubaker // includeSubdomains isn't relevant here, both android.com + subdomains and android.com 1775f96702f582050c1598136ed2a748f76b981c94eChad Brubaker // match for android.com equally. Do not allow any duplicates period. 1785f96702f582050c1598136ed2a748f76b981c94eChad Brubaker if (!seenDomains.add(domain)) { 1795f96702f582050c1598136ed2a748f76b981c94eChad Brubaker throw new ParserException(parser, domain + " has already been specified"); 1805f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } 1815f96702f582050c1598136ed2a748f76b981c94eChad Brubaker return new Domain(domain, includeSubdomains); 1825f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } 1835f96702f582050c1598136ed2a748f76b981c94eChad Brubaker 18408d36202daeb3e668911c9902edb61b6894f822eChad Brubaker private CertificatesEntryRef parseCertificatesEntry(XmlResourceParser parser, 18508d36202daeb3e668911c9902edb61b6894f822eChad Brubaker boolean defaultOverridePins) 1865f96702f582050c1598136ed2a748f76b981c94eChad Brubaker throws IOException, XmlPullParserException, ParserException { 18708d36202daeb3e668911c9902edb61b6894f822eChad Brubaker boolean overridePins = 18808d36202daeb3e668911c9902edb61b6894f822eChad Brubaker parser.getAttributeBooleanValue(null, "overridePins", defaultOverridePins); 1895f96702f582050c1598136ed2a748f76b981c94eChad Brubaker int sourceId = parser.getAttributeResourceValue(null, "src", -1); 1905f96702f582050c1598136ed2a748f76b981c94eChad Brubaker String sourceString = parser.getAttributeValue(null, "src"); 1915f96702f582050c1598136ed2a748f76b981c94eChad Brubaker CertificateSource source = null; 1925f96702f582050c1598136ed2a748f76b981c94eChad Brubaker if (sourceString == null) { 1935f96702f582050c1598136ed2a748f76b981c94eChad Brubaker throw new ParserException(parser, "certificates element missing src attribute"); 1945f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } 1955f96702f582050c1598136ed2a748f76b981c94eChad Brubaker if (sourceId != -1) { 1965f96702f582050c1598136ed2a748f76b981c94eChad Brubaker // TODO: Cache ResourceCertificateSources by sourceId 1975f96702f582050c1598136ed2a748f76b981c94eChad Brubaker source = new ResourceCertificateSource(sourceId, mContext); 1985f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } else if ("system".equals(sourceString)) { 1995f96702f582050c1598136ed2a748f76b981c94eChad Brubaker source = SystemCertificateSource.getInstance(); 2005f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } else if ("user".equals(sourceString)) { 2015f96702f582050c1598136ed2a748f76b981c94eChad Brubaker source = UserCertificateSource.getInstance(); 2025f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } else { 2035f96702f582050c1598136ed2a748f76b981c94eChad Brubaker throw new ParserException(parser, "Unknown certificates src. " 2045f96702f582050c1598136ed2a748f76b981c94eChad Brubaker + "Should be one of system|user|@resourceVal"); 2055f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } 2065f96702f582050c1598136ed2a748f76b981c94eChad Brubaker XmlUtils.skipCurrentTag(parser); 2075f96702f582050c1598136ed2a748f76b981c94eChad Brubaker return new CertificatesEntryRef(source, overridePins); 2085f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } 2095f96702f582050c1598136ed2a748f76b981c94eChad Brubaker 21008d36202daeb3e668911c9902edb61b6894f822eChad Brubaker private Collection<CertificatesEntryRef> parseTrustAnchors(XmlResourceParser parser, 21108d36202daeb3e668911c9902edb61b6894f822eChad Brubaker boolean defaultOverridePins) 2125f96702f582050c1598136ed2a748f76b981c94eChad Brubaker throws IOException, XmlPullParserException, ParserException { 2135f96702f582050c1598136ed2a748f76b981c94eChad Brubaker int outerDepth = parser.getDepth(); 2145f96702f582050c1598136ed2a748f76b981c94eChad Brubaker List<CertificatesEntryRef> anchors = new ArrayList<>(); 2155f96702f582050c1598136ed2a748f76b981c94eChad Brubaker while (XmlUtils.nextElementWithin(parser, outerDepth)) { 2165f96702f582050c1598136ed2a748f76b981c94eChad Brubaker String tagName = parser.getName(); 2175f96702f582050c1598136ed2a748f76b981c94eChad Brubaker if (tagName.equals("certificates")) { 21808d36202daeb3e668911c9902edb61b6894f822eChad Brubaker anchors.add(parseCertificatesEntry(parser, defaultOverridePins)); 2195f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } else { 2205f96702f582050c1598136ed2a748f76b981c94eChad Brubaker XmlUtils.skipCurrentTag(parser); 2215f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } 2225f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } 2235f96702f582050c1598136ed2a748f76b981c94eChad Brubaker return anchors; 2245f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } 2255f96702f582050c1598136ed2a748f76b981c94eChad Brubaker 226bd173c28fcded629da722c6669f1b6478cdcd94fChad Brubaker private List<Pair<NetworkSecurityConfig.Builder, Set<Domain>>> parseConfigEntry( 227bd173c28fcded629da722c6669f1b6478cdcd94fChad Brubaker XmlResourceParser parser, Set<String> seenDomains, 22808d36202daeb3e668911c9902edb61b6894f822eChad Brubaker NetworkSecurityConfig.Builder parentBuilder, int configType) 2295f96702f582050c1598136ed2a748f76b981c94eChad Brubaker throws IOException, XmlPullParserException, ParserException { 230bd173c28fcded629da722c6669f1b6478cdcd94fChad Brubaker List<Pair<NetworkSecurityConfig.Builder, Set<Domain>>> builders = new ArrayList<>(); 2315f96702f582050c1598136ed2a748f76b981c94eChad Brubaker NetworkSecurityConfig.Builder builder = new NetworkSecurityConfig.Builder(); 232bd173c28fcded629da722c6669f1b6478cdcd94fChad Brubaker builder.setParent(parentBuilder); 2335f96702f582050c1598136ed2a748f76b981c94eChad Brubaker Set<Domain> domains = new ArraySet<>(); 2345f96702f582050c1598136ed2a748f76b981c94eChad Brubaker boolean seenPinSet = false; 2355f96702f582050c1598136ed2a748f76b981c94eChad Brubaker boolean seenTrustAnchors = false; 23608d36202daeb3e668911c9902edb61b6894f822eChad Brubaker boolean defaultOverridePins = configType == CONFIG_DEBUG; 2375f96702f582050c1598136ed2a748f76b981c94eChad Brubaker String configName = parser.getName(); 2385f96702f582050c1598136ed2a748f76b981c94eChad Brubaker int outerDepth = parser.getDepth(); 239bd173c28fcded629da722c6669f1b6478cdcd94fChad Brubaker // Add this builder now so that this builder occurs before any of its children. This 240bd173c28fcded629da722c6669f1b6478cdcd94fChad Brubaker // makes the final build pass easier. 241bd173c28fcded629da722c6669f1b6478cdcd94fChad Brubaker builders.add(new Pair<>(builder, domains)); 2425f96702f582050c1598136ed2a748f76b981c94eChad Brubaker // Parse config attributes. Only set values that are present, config inheritence will 2435f96702f582050c1598136ed2a748f76b981c94eChad Brubaker // handle the rest. 2445f96702f582050c1598136ed2a748f76b981c94eChad Brubaker for (int i = 0; i < parser.getAttributeCount(); i++) { 2455f96702f582050c1598136ed2a748f76b981c94eChad Brubaker String name = parser.getAttributeName(i); 2465f96702f582050c1598136ed2a748f76b981c94eChad Brubaker if ("hstsEnforced".equals(name)) { 2475f96702f582050c1598136ed2a748f76b981c94eChad Brubaker builder.setHstsEnforced( 2485f96702f582050c1598136ed2a748f76b981c94eChad Brubaker parser.getAttributeBooleanValue(i, 2495f96702f582050c1598136ed2a748f76b981c94eChad Brubaker NetworkSecurityConfig.DEFAULT_HSTS_ENFORCED)); 2505f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } else if ("cleartextTrafficPermitted".equals(name)) { 2515f96702f582050c1598136ed2a748f76b981c94eChad Brubaker builder.setCleartextTrafficPermitted( 2525f96702f582050c1598136ed2a748f76b981c94eChad Brubaker parser.getAttributeBooleanValue(i, 2535f96702f582050c1598136ed2a748f76b981c94eChad Brubaker NetworkSecurityConfig.DEFAULT_CLEARTEXT_TRAFFIC_PERMITTED)); 2545f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } 2555f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } 2565f96702f582050c1598136ed2a748f76b981c94eChad Brubaker // Parse the config elements. 2575f96702f582050c1598136ed2a748f76b981c94eChad Brubaker while (XmlUtils.nextElementWithin(parser, outerDepth)) { 2585f96702f582050c1598136ed2a748f76b981c94eChad Brubaker String tagName = parser.getName(); 2595f96702f582050c1598136ed2a748f76b981c94eChad Brubaker if ("domain".equals(tagName)) { 26008d36202daeb3e668911c9902edb61b6894f822eChad Brubaker if (configType != CONFIG_DOMAIN) { 26108d36202daeb3e668911c9902edb61b6894f822eChad Brubaker throw new ParserException(parser, 26208d36202daeb3e668911c9902edb61b6894f822eChad Brubaker "domain element not allowed in " + getConfigString(configType)); 2635f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } 2645f96702f582050c1598136ed2a748f76b981c94eChad Brubaker Domain domain = parseDomain(parser, seenDomains); 2655f96702f582050c1598136ed2a748f76b981c94eChad Brubaker domains.add(domain); 2665f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } else if ("trust-anchors".equals(tagName)) { 2675f96702f582050c1598136ed2a748f76b981c94eChad Brubaker if (seenTrustAnchors) { 2685f96702f582050c1598136ed2a748f76b981c94eChad Brubaker throw new ParserException(parser, 2695f96702f582050c1598136ed2a748f76b981c94eChad Brubaker "Multiple trust-anchor elements not allowed"); 2705f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } 27108d36202daeb3e668911c9902edb61b6894f822eChad Brubaker builder.addCertificatesEntryRefs( 27208d36202daeb3e668911c9902edb61b6894f822eChad Brubaker parseTrustAnchors(parser, defaultOverridePins)); 2735f96702f582050c1598136ed2a748f76b981c94eChad Brubaker seenTrustAnchors = true; 2745f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } else if ("pin-set".equals(tagName)) { 27508d36202daeb3e668911c9902edb61b6894f822eChad Brubaker if (configType != CONFIG_DOMAIN) { 2765f96702f582050c1598136ed2a748f76b981c94eChad Brubaker throw new ParserException(parser, 27708d36202daeb3e668911c9902edb61b6894f822eChad Brubaker "pin-set element not allowed in " + getConfigString(configType)); 2785f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } 2795f96702f582050c1598136ed2a748f76b981c94eChad Brubaker if (seenPinSet) { 2805f96702f582050c1598136ed2a748f76b981c94eChad Brubaker throw new ParserException(parser, "Multiple pin-set elements not allowed"); 2815f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } 2825f96702f582050c1598136ed2a748f76b981c94eChad Brubaker builder.setPinSet(parsePinSet(parser)); 2835f96702f582050c1598136ed2a748f76b981c94eChad Brubaker seenPinSet = true; 284bd173c28fcded629da722c6669f1b6478cdcd94fChad Brubaker } else if ("domain-config".equals(tagName)) { 28508d36202daeb3e668911c9902edb61b6894f822eChad Brubaker if (configType != CONFIG_DOMAIN) { 286bd173c28fcded629da722c6669f1b6478cdcd94fChad Brubaker throw new ParserException(parser, 28708d36202daeb3e668911c9902edb61b6894f822eChad Brubaker "Nested domain-config not allowed in " + getConfigString(configType)); 288bd173c28fcded629da722c6669f1b6478cdcd94fChad Brubaker } 28908d36202daeb3e668911c9902edb61b6894f822eChad Brubaker builders.addAll(parseConfigEntry(parser, seenDomains, builder, configType)); 2905f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } else { 2915f96702f582050c1598136ed2a748f76b981c94eChad Brubaker XmlUtils.skipCurrentTag(parser); 2925f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } 2935f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } 29408d36202daeb3e668911c9902edb61b6894f822eChad Brubaker if (configType == CONFIG_DOMAIN && domains.isEmpty()) { 2955f96702f582050c1598136ed2a748f76b981c94eChad Brubaker throw new ParserException(parser, "No domain elements in domain-config"); 2965f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } 297bd173c28fcded629da722c6669f1b6478cdcd94fChad Brubaker return builders; 2985f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } 2995f96702f582050c1598136ed2a748f76b981c94eChad Brubaker 30008d36202daeb3e668911c9902edb61b6894f822eChad Brubaker private void addDebugAnchorsIfNeeded(NetworkSecurityConfig.Builder debugConfigBuilder, 30108d36202daeb3e668911c9902edb61b6894f822eChad Brubaker NetworkSecurityConfig.Builder builder) { 30208d36202daeb3e668911c9902edb61b6894f822eChad Brubaker if (debugConfigBuilder == null || !debugConfigBuilder.hasCertificatesEntryRefs()) { 30308d36202daeb3e668911c9902edb61b6894f822eChad Brubaker return; 30408d36202daeb3e668911c9902edb61b6894f822eChad Brubaker } 30508d36202daeb3e668911c9902edb61b6894f822eChad Brubaker // Don't add trust anchors if not already present, the builder will inherit the anchors 30608d36202daeb3e668911c9902edb61b6894f822eChad Brubaker // from its parent, and that's where the trust anchors should be added. 30708d36202daeb3e668911c9902edb61b6894f822eChad Brubaker if (!builder.hasCertificatesEntryRefs()) { 30808d36202daeb3e668911c9902edb61b6894f822eChad Brubaker return; 30908d36202daeb3e668911c9902edb61b6894f822eChad Brubaker } 31008d36202daeb3e668911c9902edb61b6894f822eChad Brubaker 31108d36202daeb3e668911c9902edb61b6894f822eChad Brubaker builder.addCertificatesEntryRefs(debugConfigBuilder.getCertificatesEntryRefs()); 31208d36202daeb3e668911c9902edb61b6894f822eChad Brubaker } 31308d36202daeb3e668911c9902edb61b6894f822eChad Brubaker 3145f96702f582050c1598136ed2a748f76b981c94eChad Brubaker private void parseNetworkSecurityConfig(XmlResourceParser parser) 3155f96702f582050c1598136ed2a748f76b981c94eChad Brubaker throws IOException, XmlPullParserException, ParserException { 3165f96702f582050c1598136ed2a748f76b981c94eChad Brubaker Set<String> seenDomains = new ArraySet<>(); 3175f96702f582050c1598136ed2a748f76b981c94eChad Brubaker List<Pair<NetworkSecurityConfig.Builder, Set<Domain>>> builders = new ArrayList<>(); 3185f96702f582050c1598136ed2a748f76b981c94eChad Brubaker NetworkSecurityConfig.Builder baseConfigBuilder = null; 31908d36202daeb3e668911c9902edb61b6894f822eChad Brubaker NetworkSecurityConfig.Builder debugConfigBuilder = null; 3205f96702f582050c1598136ed2a748f76b981c94eChad Brubaker boolean seenDebugOverrides = false; 3215f96702f582050c1598136ed2a748f76b981c94eChad Brubaker boolean seenBaseConfig = false; 3225f96702f582050c1598136ed2a748f76b981c94eChad Brubaker 3235f96702f582050c1598136ed2a748f76b981c94eChad Brubaker XmlUtils.beginDocument(parser, "network-security-config"); 3245f96702f582050c1598136ed2a748f76b981c94eChad Brubaker int outerDepth = parser.getDepth(); 3255f96702f582050c1598136ed2a748f76b981c94eChad Brubaker while (XmlUtils.nextElementWithin(parser, outerDepth)) { 3265f96702f582050c1598136ed2a748f76b981c94eChad Brubaker if ("base-config".equals(parser.getName())) { 3275f96702f582050c1598136ed2a748f76b981c94eChad Brubaker if (seenBaseConfig) { 3285f96702f582050c1598136ed2a748f76b981c94eChad Brubaker throw new ParserException(parser, "Only one base-config allowed"); 3295f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } 3305f96702f582050c1598136ed2a748f76b981c94eChad Brubaker seenBaseConfig = true; 33108d36202daeb3e668911c9902edb61b6894f822eChad Brubaker baseConfigBuilder = 33208d36202daeb3e668911c9902edb61b6894f822eChad Brubaker parseConfigEntry(parser, seenDomains, null, CONFIG_BASE).get(0).first; 3335f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } else if ("domain-config".equals(parser.getName())) { 33408d36202daeb3e668911c9902edb61b6894f822eChad Brubaker builders.addAll( 33508d36202daeb3e668911c9902edb61b6894f822eChad Brubaker parseConfigEntry(parser, seenDomains, baseConfigBuilder, CONFIG_DOMAIN)); 33608d36202daeb3e668911c9902edb61b6894f822eChad Brubaker } else if ("debug-overrides".equals(parser.getName())) { 33708d36202daeb3e668911c9902edb61b6894f822eChad Brubaker if (seenDebugOverrides) { 33808d36202daeb3e668911c9902edb61b6894f822eChad Brubaker throw new ParserException(parser, "Only one debug-overrides allowed"); 33908d36202daeb3e668911c9902edb61b6894f822eChad Brubaker } 34008d36202daeb3e668911c9902edb61b6894f822eChad Brubaker if (mDebugBuild) { 34108d36202daeb3e668911c9902edb61b6894f822eChad Brubaker debugConfigBuilder = 342567f6f24747c80b4ab362a22985576c4f8a418fdChad Brubaker parseConfigEntry(parser, null, null, CONFIG_DEBUG).get(0).first; 34308d36202daeb3e668911c9902edb61b6894f822eChad Brubaker } else { 34408d36202daeb3e668911c9902edb61b6894f822eChad Brubaker XmlUtils.skipCurrentTag(parser); 34508d36202daeb3e668911c9902edb61b6894f822eChad Brubaker } 34608d36202daeb3e668911c9902edb61b6894f822eChad Brubaker seenDebugOverrides = true; 3475f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } else { 3485f96702f582050c1598136ed2a748f76b981c94eChad Brubaker XmlUtils.skipCurrentTag(parser); 3495f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } 3505f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } 351567f6f24747c80b4ab362a22985576c4f8a418fdChad Brubaker // If debug is true and there was no debug-overrides in the file check for an extra 352567f6f24747c80b4ab362a22985576c4f8a418fdChad Brubaker // _debug resource. 353567f6f24747c80b4ab362a22985576c4f8a418fdChad Brubaker if (mDebugBuild && debugConfigBuilder == null) { 354567f6f24747c80b4ab362a22985576c4f8a418fdChad Brubaker debugConfigBuilder = parseDebugOverridesResource(); 355567f6f24747c80b4ab362a22985576c4f8a418fdChad Brubaker } 3565f96702f582050c1598136ed2a748f76b981c94eChad Brubaker 3575f96702f582050c1598136ed2a748f76b981c94eChad Brubaker // Use the platform default as the parent of the base config for any values not provided 3585f96702f582050c1598136ed2a748f76b981c94eChad Brubaker // there. If there is no base config use the platform default. 3595f96702f582050c1598136ed2a748f76b981c94eChad Brubaker NetworkSecurityConfig.Builder platformDefaultBuilder = 36032d2a1024f75f7e917f2aca18d34322a46d36bcbChad Brubaker NetworkSecurityConfig.getDefaultBuilder(mTargetSdkVersion); 36108d36202daeb3e668911c9902edb61b6894f822eChad Brubaker addDebugAnchorsIfNeeded(debugConfigBuilder, platformDefaultBuilder); 3625f96702f582050c1598136ed2a748f76b981c94eChad Brubaker if (baseConfigBuilder != null) { 3635f96702f582050c1598136ed2a748f76b981c94eChad Brubaker baseConfigBuilder.setParent(platformDefaultBuilder); 36408d36202daeb3e668911c9902edb61b6894f822eChad Brubaker addDebugAnchorsIfNeeded(debugConfigBuilder, baseConfigBuilder); 3655f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } else { 3665f96702f582050c1598136ed2a748f76b981c94eChad Brubaker baseConfigBuilder = platformDefaultBuilder; 3675f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } 3685f96702f582050c1598136ed2a748f76b981c94eChad Brubaker // Build the per-domain config mapping. 3695f96702f582050c1598136ed2a748f76b981c94eChad Brubaker Set<Pair<Domain, NetworkSecurityConfig>> configs = new ArraySet<>(); 3705f96702f582050c1598136ed2a748f76b981c94eChad Brubaker 3715f96702f582050c1598136ed2a748f76b981c94eChad Brubaker for (Pair<NetworkSecurityConfig.Builder, Set<Domain>> entry : builders) { 3725f96702f582050c1598136ed2a748f76b981c94eChad Brubaker NetworkSecurityConfig.Builder builder = entry.first; 3735f96702f582050c1598136ed2a748f76b981c94eChad Brubaker Set<Domain> domains = entry.second; 374bd173c28fcded629da722c6669f1b6478cdcd94fChad Brubaker // Set the parent of configs that do not have a parent to the base-config. This can 375bd173c28fcded629da722c6669f1b6478cdcd94fChad Brubaker // happen if the base-config comes after a domain-config in the file. 376bd173c28fcded629da722c6669f1b6478cdcd94fChad Brubaker // Note that this is safe with regards to children because of the order that 377bd173c28fcded629da722c6669f1b6478cdcd94fChad Brubaker // parseConfigEntry returns builders, the parent is always before the children. The 378bd173c28fcded629da722c6669f1b6478cdcd94fChad Brubaker // children builders will not have build called until _after_ their parents have their 379bd173c28fcded629da722c6669f1b6478cdcd94fChad Brubaker // parent set so everything is consistent. 380bd173c28fcded629da722c6669f1b6478cdcd94fChad Brubaker if (builder.getParent() == null) { 381bd173c28fcded629da722c6669f1b6478cdcd94fChad Brubaker builder.setParent(baseConfigBuilder); 382bd173c28fcded629da722c6669f1b6478cdcd94fChad Brubaker } 38308d36202daeb3e668911c9902edb61b6894f822eChad Brubaker addDebugAnchorsIfNeeded(debugConfigBuilder, builder); 3845f96702f582050c1598136ed2a748f76b981c94eChad Brubaker NetworkSecurityConfig config = builder.build(); 3855f96702f582050c1598136ed2a748f76b981c94eChad Brubaker for (Domain domain : domains) { 3865f96702f582050c1598136ed2a748f76b981c94eChad Brubaker configs.add(new Pair<>(domain, config)); 3875f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } 3885f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } 3895f96702f582050c1598136ed2a748f76b981c94eChad Brubaker mDefaultConfig = baseConfigBuilder.build(); 3905f96702f582050c1598136ed2a748f76b981c94eChad Brubaker mDomainMap = configs; 3915f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } 3925f96702f582050c1598136ed2a748f76b981c94eChad Brubaker 393567f6f24747c80b4ab362a22985576c4f8a418fdChad Brubaker private NetworkSecurityConfig.Builder parseDebugOverridesResource() 394567f6f24747c80b4ab362a22985576c4f8a418fdChad Brubaker throws IOException, XmlPullParserException, ParserException { 395567f6f24747c80b4ab362a22985576c4f8a418fdChad Brubaker Resources resources = mContext.getResources(); 396567f6f24747c80b4ab362a22985576c4f8a418fdChad Brubaker String packageName = resources.getResourcePackageName(mResourceId); 397567f6f24747c80b4ab362a22985576c4f8a418fdChad Brubaker String entryName = resources.getResourceEntryName(mResourceId); 398567f6f24747c80b4ab362a22985576c4f8a418fdChad Brubaker int resId = resources.getIdentifier(entryName + "_debug", "xml", packageName); 399567f6f24747c80b4ab362a22985576c4f8a418fdChad Brubaker // No debug-overrides resource was found, nothing to parse. 400567f6f24747c80b4ab362a22985576c4f8a418fdChad Brubaker if (resId == 0) { 401567f6f24747c80b4ab362a22985576c4f8a418fdChad Brubaker return null; 402567f6f24747c80b4ab362a22985576c4f8a418fdChad Brubaker } 403567f6f24747c80b4ab362a22985576c4f8a418fdChad Brubaker NetworkSecurityConfig.Builder debugConfigBuilder = null; 404567f6f24747c80b4ab362a22985576c4f8a418fdChad Brubaker // Parse debug-overrides out of the _debug resource. 405567f6f24747c80b4ab362a22985576c4f8a418fdChad Brubaker try (XmlResourceParser parser = resources.getXml(resId)) { 406567f6f24747c80b4ab362a22985576c4f8a418fdChad Brubaker XmlUtils.beginDocument(parser, "network-security-config"); 407567f6f24747c80b4ab362a22985576c4f8a418fdChad Brubaker int outerDepth = parser.getDepth(); 408567f6f24747c80b4ab362a22985576c4f8a418fdChad Brubaker boolean seenDebugOverrides = false; 409567f6f24747c80b4ab362a22985576c4f8a418fdChad Brubaker while (XmlUtils.nextElementWithin(parser, outerDepth)) { 410567f6f24747c80b4ab362a22985576c4f8a418fdChad Brubaker if ("debug-overrides".equals(parser.getName())) { 411567f6f24747c80b4ab362a22985576c4f8a418fdChad Brubaker if (seenDebugOverrides) { 412567f6f24747c80b4ab362a22985576c4f8a418fdChad Brubaker throw new ParserException(parser, "Only one debug-overrides allowed"); 413567f6f24747c80b4ab362a22985576c4f8a418fdChad Brubaker } 414567f6f24747c80b4ab362a22985576c4f8a418fdChad Brubaker if (mDebugBuild) { 415567f6f24747c80b4ab362a22985576c4f8a418fdChad Brubaker debugConfigBuilder = 416567f6f24747c80b4ab362a22985576c4f8a418fdChad Brubaker parseConfigEntry(parser, null, null, CONFIG_DEBUG).get(0).first; 417567f6f24747c80b4ab362a22985576c4f8a418fdChad Brubaker } else { 418567f6f24747c80b4ab362a22985576c4f8a418fdChad Brubaker XmlUtils.skipCurrentTag(parser); 419567f6f24747c80b4ab362a22985576c4f8a418fdChad Brubaker } 420567f6f24747c80b4ab362a22985576c4f8a418fdChad Brubaker seenDebugOverrides = true; 421567f6f24747c80b4ab362a22985576c4f8a418fdChad Brubaker } else { 422567f6f24747c80b4ab362a22985576c4f8a418fdChad Brubaker XmlUtils.skipCurrentTag(parser); 423567f6f24747c80b4ab362a22985576c4f8a418fdChad Brubaker } 424567f6f24747c80b4ab362a22985576c4f8a418fdChad Brubaker } 425567f6f24747c80b4ab362a22985576c4f8a418fdChad Brubaker } 426567f6f24747c80b4ab362a22985576c4f8a418fdChad Brubaker 427567f6f24747c80b4ab362a22985576c4f8a418fdChad Brubaker return debugConfigBuilder; 428567f6f24747c80b4ab362a22985576c4f8a418fdChad Brubaker } 429567f6f24747c80b4ab362a22985576c4f8a418fdChad Brubaker 4305f96702f582050c1598136ed2a748f76b981c94eChad Brubaker public static class ParserException extends Exception { 4315f96702f582050c1598136ed2a748f76b981c94eChad Brubaker 4325f96702f582050c1598136ed2a748f76b981c94eChad Brubaker public ParserException(XmlPullParser parser, String message, Throwable cause) { 4335f96702f582050c1598136ed2a748f76b981c94eChad Brubaker super(message + " at: " + parser.getPositionDescription(), cause); 4345f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } 4355f96702f582050c1598136ed2a748f76b981c94eChad Brubaker 4365f96702f582050c1598136ed2a748f76b981c94eChad Brubaker public ParserException(XmlPullParser parser, String message) { 4375f96702f582050c1598136ed2a748f76b981c94eChad Brubaker this(parser, message, null); 4385f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } 4395f96702f582050c1598136ed2a748f76b981c94eChad Brubaker } 4405f96702f582050c1598136ed2a748f76b981c94eChad Brubaker} 441