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