Merge "Mods to the SELinuxMMAC engine code."

This commit is contained in:
Nick Kralevich
2014-12-23 19:42:05 +00:00
committed by Gerrit Code Review

View File

@@ -35,13 +35,22 @@ import java.io.IOException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
/**
* Centralized access to SELinux MMAC (middleware MAC) implementation.
* Centralized access to SELinux MMAC (middleware MAC) implementation. This
* class is responsible for loading the appropriate mac_permissions.xml file
* as well as providing an interface for assigning seinfo values to apks.
*
* {@hide}
*/
public final class SELinuxMMAC {
@@ -51,11 +60,9 @@ public final class SELinuxMMAC {
private static final boolean DEBUG_POLICY = false;
private static final boolean DEBUG_POLICY_INSTALL = DEBUG_POLICY || false;
// Signature seinfo values read from policy.
private static HashMap<Signature, Policy> sSigSeinfo = new HashMap<Signature, Policy>();
// Default seinfo read from policy.
private static String sDefaultSeinfo = null;
// All policy stanzas read from mac_permissions.xml. This is also the lock
// to synchronize access during policy load and access attempts.
private static final List<Policy> sPolicies = new ArrayList<Policy>();
// Data policy override version file.
private static final String DATA_VERSION_FILE =
@@ -94,293 +101,289 @@ public final class SELinuxMMAC {
private static final String SEAPP_HASH_FILE =
Environment.getDataDirectory().toString() + "/system/seapp_hash";
// Signature policy stanzas
static class Policy {
private String seinfo;
private final HashMap<String, String> pkgMap;
Policy() {
seinfo = null;
pkgMap = new HashMap<String, String>();
}
void putSeinfo(String seinfoValue) {
seinfo = seinfoValue;
}
void putPkg(String pkg, String seinfoValue) {
pkgMap.put(pkg, seinfoValue);
}
// Valid policy stanza means there exists a global
// seinfo value or at least one package policy.
boolean isValid() {
return (seinfo != null) || (!pkgMap.isEmpty());
}
String checkPolicy(String pkgName) {
// Check for package name seinfo value first.
String seinfoValue = pkgMap.get(pkgName);
if (seinfoValue != null) {
return seinfoValue;
}
// Return the global seinfo value.
return seinfo;
}
}
private static void flushInstallPolicy() {
sSigSeinfo.clear();
sDefaultSeinfo = null;
}
/**
* Load the mac_permissions.xml file containing all seinfo assignments used to
* label apps. The loaded mac_permissions.xml file is determined by the
* MAC_PERMISSIONS class variable which is set at class load time which itself
* is based on the USE_OVERRIDE_POLICY class variable. For further guidance on
* the proper structure of a mac_permissions.xml file consult the source code
* located at external/sepolicy/mac_permissions.xml.
*
* @return boolean indicating if policy was correctly loaded. A value of false
* typically indicates a structural problem with the xml or incorrectly
* constructed policy stanzas. A value of true means that all stanzas
* were loaded successfully; no partial loading is possible.
*/
public static boolean readInstallPolicy() {
return readInstallPolicy(MAC_PERMISSIONS);
}
// Temp structure to hold the rules while we parse the xml file. We add
// all the rules once we know there's no problems.
List<Policy> policies = new ArrayList<>();
public static boolean readInstallPolicy(String macPermsPath) {
if (macPermsPath == null) {
throw new NullPointerException("mac_permissions.xml file path is null");
}
// A separate structure to hold the default stanza. We need to add this to
// the end of the policies list structure.
Policy defaultPolicy = null;
// Temp structures to hold the rules while we parse the xml file.
// We add all the rules together once we know there's no structural problems.
HashMap<Signature, Policy> sigSeinfo = new HashMap<Signature, Policy>();
String defaultSeinfo = null;
// Track sets of known policy certs so we can enforce rules across stanzas.
Set<Set<Signature>> knownCerts = new HashSet<>();
FileReader policyFile = null;
XmlPullParser parser = Xml.newPullParser();
try {
policyFile = new FileReader(macPermsPath);
Slog.d(TAG, "Using policy file " + macPermsPath);
policyFile = new FileReader(MAC_PERMISSIONS);
Slog.d(TAG, "Using policy file " + MAC_PERMISSIONS);
XmlPullParser parser = Xml.newPullParser();
parser.setInput(policyFile);
parser.nextTag();
parser.require(XmlPullParser.START_TAG, null, "policy");
XmlUtils.beginDocument(parser, "policy");
while (true) {
XmlUtils.nextElement(parser);
if (parser.getEventType() == XmlPullParser.END_DOCUMENT) {
break;
while (parser.next() != XmlPullParser.END_TAG) {
if (parser.getEventType() != XmlPullParser.START_TAG) {
continue;
}
String tagName = parser.getName();
if ("signer".equals(tagName)) {
String cert = parser.getAttributeValue(null, "signature");
if (cert == null) {
Slog.w(TAG, "<signer> without signature at "
+ parser.getPositionDescription());
XmlUtils.skipCurrentTag(parser);
continue;
}
Signature signature;
try {
signature = new Signature(cert);
} catch (IllegalArgumentException e) {
Slog.w(TAG, "<signer> with bad signature at "
+ parser.getPositionDescription(), e);
XmlUtils.skipCurrentTag(parser);
continue;
}
Policy policy = readPolicyTags(parser);
if (policy.isValid()) {
sigSeinfo.put(signature, policy);
Policy signerPolicy = readSignerOrThrow(parser);
// Return of a Policy instance ensures certain invariants have
// passed, however, we still want to do some cross policy checking.
// Thus, check that we haven't seen the certs in another stanza.
Set<Signature> certs = signerPolicy.getSignatures();
if (knownCerts.contains(certs)) {
String msg = "Separate stanzas have identical certs";
throw new IllegalStateException(msg);
}
knownCerts.add(certs);
policies.add(signerPolicy);
} else if ("default".equals(tagName)) {
// Value is null if default tag is absent or seinfo tag is malformed.
defaultSeinfo = readSeinfoTag(parser);
if (DEBUG_POLICY_INSTALL)
Slog.i(TAG, "<default> tag assigned seinfo=" + defaultSeinfo);
Policy defPolicy = readDefaultOrThrow(parser);
// Return of a Policy instance ensures certain invariants have
// passed, however, we still want to do some cross policy checking.
// Thus, check that we haven't already seen a default stanza.
if (defaultPolicy != null) {
String msg = "Multiple default stanzas identified";
throw new IllegalStateException(msg);
}
defaultPolicy = defPolicy;
} else {
XmlUtils.skipCurrentTag(parser);
skip(parser);
}
}
} catch (XmlPullParserException xpe) {
Slog.w(TAG, "Got exception parsing " + macPermsPath, xpe);
} catch (IllegalStateException | IllegalArgumentException |
XmlPullParserException ex) {
StringBuilder sb = new StringBuilder("Exception @");
sb.append(parser.getPositionDescription());
sb.append(" while parsing ");
sb.append(MAC_PERMISSIONS);
sb.append(":");
sb.append(ex);
Slog.w(TAG, sb.toString());
return false;
} catch (IOException ioe) {
Slog.w(TAG, "Got exception parsing " + macPermsPath, ioe);
Slog.w(TAG, "Exception parsing " + MAC_PERMISSIONS, ioe);
return false;
} finally {
IoUtils.closeQuietly(policyFile);
}
flushInstallPolicy();
sSigSeinfo = sigSeinfo;
sDefaultSeinfo = defaultSeinfo;
// Add the default policy to the end if there is one. This will ensure that
// the default stanza is consulted last when performing policy lookups.
if (defaultPolicy != null) {
policies.add(defaultPolicy);
}
synchronized (sPolicies) {
sPolicies.clear();
sPolicies.addAll(policies);
}
return true;
}
private static Policy readPolicyTags(XmlPullParser parser) throws
IOException, XmlPullParserException {
/**
* Loop over a signer tag looking for seinfo, package and cert tags. A {@link Policy}
* instance will be created and returned in the process. During the pass all other
* tag elements will be skipped.
*
* @param parser an XmlPullParser object representing a signer element.
* @return the constructed {@link Policy} instance
* @throws IOException
* @throws XmlPullParserException
* @throws IllegalArgumentException if any of the validation checks fail while
* parsing tag values.
* @throws IllegalStateException if any of the invariants fail when constructing
* the {@link Policy} instance.
*/
private static Policy readSignerOrThrow(XmlPullParser parser) throws IOException,
XmlPullParserException {
int type;
int outerDepth = parser.getDepth();
Policy policy = new Policy();
while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
&& (type != XmlPullParser.END_TAG
|| parser.getDepth() > outerDepth)) {
if (type == XmlPullParser.END_TAG
|| type == XmlPullParser.TEXT) {
parser.require(XmlPullParser.START_TAG, null, "signer");
Policy.PolicyBuilder pb = new Policy.PolicyBuilder();
// Check for a cert attached to the signer tag. We allow a signature
// to appear as an attribute as well as those attached to cert tags.
String cert = parser.getAttributeValue(null, "signature");
if (cert != null) {
pb.addSignature(cert);
}
while (parser.next() != XmlPullParser.END_TAG) {
if (parser.getEventType() != XmlPullParser.START_TAG) {
continue;
}
String tagName = parser.getName();
if ("seinfo".equals(tagName)) {
String seinfo = parseSeinfo(parser);
if (seinfo != null) {
policy.putSeinfo(seinfo);
}
XmlUtils.skipCurrentTag(parser);
String seinfo = parser.getAttributeValue(null, "value");
pb.setGlobalSeinfoOrThrow(seinfo);
readSeinfo(parser);
} else if ("package".equals(tagName)) {
String pkg = parser.getAttributeValue(null, "name");
if (!validatePackageName(pkg)) {
Slog.w(TAG, "<package> without valid name at "
+ parser.getPositionDescription());
XmlUtils.skipCurrentTag(parser);
continue;
}
String seinfo = readSeinfoTag(parser);
if (seinfo != null) {
policy.putPkg(pkg, seinfo);
}
readPackageOrThrow(parser, pb);
} else if ("cert".equals(tagName)) {
String sig = parser.getAttributeValue(null, "signature");
pb.addSignature(sig);
readCert(parser);
} else {
XmlUtils.skipCurrentTag(parser);
skip(parser);
}
}
return policy;
return pb.build();
}
private static String readSeinfoTag(XmlPullParser parser) throws
IOException, XmlPullParserException {
/**
* Loop over a default element looking for seinfo child tags. A {@link Policy}
* instance will be created and returned in the process. All other tags encountered
* will be skipped.
*
* @param parser an XmlPullParser object representing a default element.
* @return the constructed {@link Policy} instance
* @throws IOException
* @throws XmlPullParserException
* @throws IllegalArgumentException if any of the validation checks fail while
* parsing tag values.
* @throws IllegalStateException if any of the invariants fail when constructing
* the {@link Policy} instance.
*/
private static Policy readDefaultOrThrow(XmlPullParser parser) throws IOException,
XmlPullParserException {
int type;
int outerDepth = parser.getDepth();
String seinfo = null;
while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
&& (type != XmlPullParser.END_TAG
|| parser.getDepth() > outerDepth)) {
if (type == XmlPullParser.END_TAG
|| type == XmlPullParser.TEXT) {
parser.require(XmlPullParser.START_TAG, null, "default");
Policy.PolicyBuilder pb = new Policy.PolicyBuilder();
pb.setAsDefaultPolicy();
while (parser.next() != XmlPullParser.END_TAG) {
if (parser.getEventType() != XmlPullParser.START_TAG) {
continue;
}
String tagName = parser.getName();
if ("seinfo".equals(tagName)) {
seinfo = parseSeinfo(parser);
String seinfo = parser.getAttributeValue(null, "value");
pb.setGlobalSeinfoOrThrow(seinfo);
readSeinfo(parser);
} else {
skip(parser);
}
XmlUtils.skipCurrentTag(parser);
}
return seinfo;
}
private static String parseSeinfo(XmlPullParser parser) {
String seinfoValue = parser.getAttributeValue(null, "value");
if (!validateValue(seinfoValue)) {
Slog.w(TAG, "<seinfo> without valid value at "
+ parser.getPositionDescription());
seinfoValue = null;
}
return seinfoValue;
return pb.build();
}
/**
* General validation routine for package names.
* Returns a boolean indicating if the passed string
* is a valid android package name.
* Loop over a package element looking for seinfo child tags. If found return the
* value attribute of the seinfo tag, otherwise return null. All other tags encountered
* will be skipped.
*
* @param parser an XmlPullParser object representing a package element.
* @param pb a Policy.PolicyBuilder instance to build
* @throws IOException
* @throws XmlPullParserException
* @throws IllegalArgumentException if any of the validation checks fail while
* parsing tag values.
* @throws IllegalStateException if there is a duplicate seinfo tag for the current
* package tag.
*/
private static boolean validatePackageName(String name) {
if (name == null)
return false;
private static void readPackageOrThrow(XmlPullParser parser, Policy.PolicyBuilder pb) throws
IOException, XmlPullParserException {
parser.require(XmlPullParser.START_TAG, null, "package");
String pkgName = parser.getAttributeValue(null, "name");
final int N = name.length();
boolean hasSep = false;
boolean front = true;
for (int i=0; i<N; i++) {
final char c = name.charAt(i);
if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {
front = false;
while (parser.next() != XmlPullParser.END_TAG) {
if (parser.getEventType() != XmlPullParser.START_TAG) {
continue;
}
if (!front) {
if ((c >= '0' && c <= '9') || c == '_') {
continue;
}
String tagName = parser.getName();
if ("seinfo".equals(tagName)) {
String seinfo = parser.getAttributeValue(null, "value");
pb.addInnerPackageMapOrThrow(pkgName, seinfo);
readSeinfo(parser);
} else {
skip(parser);
}
}
}
private static void readCert(XmlPullParser parser) throws IOException,
XmlPullParserException {
parser.require(XmlPullParser.START_TAG, null, "cert");
parser.nextTag();
}
private static void readSeinfo(XmlPullParser parser) throws IOException,
XmlPullParserException {
parser.require(XmlPullParser.START_TAG, null, "seinfo");
parser.nextTag();
}
private static void skip(XmlPullParser p) throws IOException, XmlPullParserException {
if (p.getEventType() != XmlPullParser.START_TAG) {
throw new IllegalStateException();
}
int depth = 1;
while (depth != 0) {
switch (p.next()) {
case XmlPullParser.END_TAG:
depth--;
break;
case XmlPullParser.START_TAG:
depth++;
break;
}
if (c == '.') {
hasSep = true;
front = true;
continue;
}
return false;
}
return hasSep;
}
/**
* General validation routine for tag values.
* Returns a boolean indicating if the passed string
* contains only letters or underscores.
*/
private static boolean validateValue(String name) {
if (name == null)
return false;
final int N = name.length();
if (N == 0)
return false;
for (int i = 0; i < N; i++) {
final char c = name.charAt(i);
if ((c < 'a' || c > 'z') && (c < 'A' || c > 'Z') && (c != '_')) {
return false;
}
}
return true;
}
/**
* Labels a package based on an seinfo tag from install policy.
* The label is attached to the ApplicationInfo instance of the package.
* Applies a security label to a package based on an seinfo tag taken from a matched
* policy. All signature based policy stanzas are consulted first and, if no match
* is found, the default policy stanza is then consulted. The security label is
* attached to the ApplicationInfo instance of the package in the event that a matching
* policy was found.
*
* @param pkg object representing the package to be labeled.
* @return boolean which determines whether a non null seinfo label
* was assigned to the package. A null value simply meaning that
* no policy matched.
* @return boolean which determines whether a non null seinfo label was assigned
* to the package. A null value simply represents that no policy matched.
*/
public static boolean assignSeinfoValue(PackageParser.Package pkg) {
// We just want one of the signatures to match.
for (Signature s : pkg.mSignatures) {
if (s == null)
continue;
Policy policy = sSigSeinfo.get(s);
if (policy != null) {
String seinfo = policy.checkPolicy(pkg.packageName);
synchronized (sPolicies) {
for (Policy policy : sPolicies) {
String seinfo = policy.getMatchedSeinfo(pkg);
if (seinfo != null) {
pkg.applicationInfo.seinfo = seinfo;
if (DEBUG_POLICY_INSTALL)
Slog.i(TAG, "package (" + pkg.packageName +
") labeled with seinfo=" + seinfo);
if (DEBUG_POLICY_INSTALL) {
Slog.i(TAG, "package (" + pkg.packageName + ") labeled with " +
"seinfo=" + seinfo);
}
return true;
}
}
}
// If we have a default seinfo value then great, otherwise
// we set a null object and that is what we started with.
pkg.applicationInfo.seinfo = sDefaultSeinfo;
if (DEBUG_POLICY_INSTALL)
Slog.i(TAG, "package (" + pkg.packageName + ") labeled with seinfo="
+ (sDefaultSeinfo == null ? "null" : sDefaultSeinfo));
return (sDefaultSeinfo != null);
if (DEBUG_POLICY_INSTALL) {
Slog.i(TAG, "package (" + pkg.packageName + ") doesn't match any policy; " +
"seinfo will remain null");
}
return false;
}
/**
@@ -485,3 +488,312 @@ public final class SELinuxMMAC {
return false;
}
}
/**
* Holds valid policy representations of individual stanzas from a mac_permissions.xml
* file. Each instance can further be used to assign seinfo values to apks using the
* {@link Policy#getMatchedSeinfo} method. To create an instance of this use the
* {@link PolicyBuilder} pattern class, where each instance is validated against a set
* of invariants before being built and returned. Each instance can be guaranteed to
* hold one valid policy stanza as outlined in the external/sepolicy/mac_permissions.xml
* file.
* </p>
* The following is an example of how to use {@link Policy.PolicyBuilder} to create a
* signer based Policy instance.
* </p>
* <pre>
* {@code
* Policy policy = new Policy.PolicyBuilder()
* .addSignature("308204a8...")
* .addSignature("483538c8...")
* .setGlobalSeinfoOrThrow("paltform")
* .addInnerPackageMapOrThrow("com.foo.", "bar")
* .addInnerPackageMapOrThrow("com.foo.other", "bar")
* .build();
* }
* </pre>
* <p>
* An example of how to use {@link Policy.PolicyBuilder} to create a default based Policy
* instance.
* </p>
* <pre>
* {@code
* Policy policy = new Policy.PolicyBuilder()
* .setAsDefaultPolicy()
* .setGlobalSeinfoOrThrow("defualt")
* .build();
* }
* </pre>
*/
final class Policy {
private final String mSeinfo;
private final boolean mDefaultStanza;
private final Set<Signature> mCerts;
private final Map<String, String> mPkgMap;
// Use the PolicyBuilder pattern to instantiate
private Policy(PolicyBuilder builder) {
mSeinfo = builder.mSeinfo;
mDefaultStanza = builder.mDefaultStanza;
mCerts = Collections.unmodifiableSet(builder.mCerts);
mPkgMap = Collections.unmodifiableMap(builder.mPkgMap);
}
/**
* Return all the certs stored with this policy stanza.
*
* @return A set of Signature objects representing all the certs stored
* with the policy.
*/
public Set<Signature> getSignatures() {
return mCerts;
}
/**
* <p>
* Determine the seinfo value to assign to an apk. The appropriate seinfo value
* is determined using the following steps:
* </p>
* <ul>
* <li> If this Policy instance is defined as a default stanza:
* <ul><li>Return the global seinfo value</li></ul>
* </li>
* <li> If this Policy instance is defined as a signer stanza:
* <ul>
* <li> All certs used to sign the apk and all certs stored with this policy
* instance are tested for set equality. If this fails then null is returned.
* </li>
* <li> If all certs match then an appropriate inner package stanza is
* searched based on package name alone. If matched, the stored seinfo
* value for that mapping is returned.
* </li>
* <li> If all certs matched and no inner package stanza matches then return
* the global seinfo value. The returned value can be null in this case.
* </li>
* </ul>
* </li>
* </ul>
* <p>
* In all cases, a return value of null should be interpreted as the apk failing
* to match this Policy instance; i.e. failing this policy stanza.
* </p>
* @param pkg the apk to check given as a PackageParser.Package object
* @return A string representing the seinfo matched during policy lookup.
* A value of null can also be returned if no match occured.
*/
public String getMatchedSeinfo(PackageParser.Package pkg) {
if (!mDefaultStanza) {
// Check for exact signature matches across all certs.
Signature[] certs = mCerts.toArray(new Signature[0]);
if (!Signature.areExactMatch(certs, pkg.mSignatures)) {
return null;
}
// Check for inner package name matches given that the
// signature checks already passed.
String seinfoValue = mPkgMap.get(pkg.packageName);
if (seinfoValue != null) {
return seinfoValue;
}
}
// Return the global seinfo value (even if it's null).
return mSeinfo;
}
/**
* A nested builder class to create {@link Policy} instances. A {@link Policy}
* class instance represents one valid policy stanza found in a mac_permissions.xml
* file. A valid policy stanza is defined to be either a signer or default stanza
* which obeys the rules outlined in external/sepolicy/mac_permissions.xml. The
* {@link #build} method ensures a set of invariants are upheld enforcing the correct
* stanza structure before returning a valid Policy object.
*/
public static final class PolicyBuilder {
private String mSeinfo;
private boolean mDefaultStanza;
private final Set<Signature> mCerts;
private final Map<String, String> mPkgMap;
public PolicyBuilder() {
mCerts = new HashSet<Signature>(2);
mPkgMap = new HashMap<String, String>(2);
}
/**
* Sets this stanza as a defualt stanza. All policy stanzas are assumed to
* be signer stanzas unless this method is explicitly called. Default stanzas
* are treated differently with respect to allowable child tags, ordering and
* when and how policy decisions are enforced.
*
* @return The reference to this PolicyBuilder.
*/
public PolicyBuilder setAsDefaultPolicy() {
mDefaultStanza = true;
return this;
}
/**
* Adds a signature to the set of certs used for validation checks. The purpose
* being that all contained certs will need to be matched against all certs
* contained with an apk.
*
* @param cert the signature to add given as a String.
* @return The reference to this PolicyBuilder.
* @throws IllegalArgumentException if the cert value fails validation;
* null or is an invalid hex-encoded ASCII string.
*/
public PolicyBuilder addSignature(String cert) {
if (cert == null) {
String err = "Invalid signature value " + cert;
throw new IllegalArgumentException(err);
}
mCerts.add(new Signature(cert));
return this;
}
/**
* Set the global seinfo tag for this policy stanza. The global seinfo tag
* represents the seinfo element that is used in one of two ways depending on
* its context. When attached to a signer tag the global seinfo represents an
* assignment when there isn't a further inner package refinement in policy.
* When used with a default tag, it represents the only allowable assignment
* value.
*
* @param seinfo the seinfo value given as a String.
* @return The reference to this PolicyBuilder.
* @throws IllegalArgumentException if the seinfo value fails validation;
* null, zero length or contains non-valid characters [^a-zA-Z_\._0-9].
* @throws IllegalStateException if an seinfo value has already been found
*/
public PolicyBuilder setGlobalSeinfoOrThrow(String seinfo) {
if (!validateValue(seinfo)) {
String err = "Invalid seinfo value " + seinfo;
throw new IllegalArgumentException(err);
}
if (mSeinfo != null && !mSeinfo.equals(seinfo)) {
String err = "Duplicate seinfo tag found";
throw new IllegalStateException(err);
}
mSeinfo = seinfo;
return this;
}
/**
* Create a package name to seinfo value mapping. Each mapping represents
* the seinfo value that will be assigned to the described package name.
* These localized mappings allow the global seinfo to be overriden. This
* mapping provides no value when used in conjunction with a default stanza;
* enforced through the {@link #build} method.
*
* @param pkgName the android package name given to the app
* @param seinfo the seinfo value that will be assigned to the passed pkgName
* @return The reference to this PolicyBuilder.
* @throws IllegalArgumentException if the seinfo value fails validation;
* null, zero length or contains non-valid characters [^a-zA-Z_\.0-9].
* Or, if the package name isn't a valid android package name.
* @throws IllegalStateException if trying to reset a package mapping with a
* different seinfo value.
*/
public PolicyBuilder addInnerPackageMapOrThrow(String pkgName, String seinfo) {
if (!validateValue(pkgName)) {
String err = "Invalid package name " + pkgName;
throw new IllegalArgumentException(err);
}
if (!validateValue(seinfo)) {
String err = "Invalid seinfo value " + seinfo;
throw new IllegalArgumentException(err);
}
String pkgValue = mPkgMap.get(pkgName);
if (pkgValue != null && !pkgValue.equals(seinfo)) {
String err = "Conflicting seinfo value found";
throw new IllegalStateException(err);
}
mPkgMap.put(pkgName, seinfo);
return this;
}
/**
* General validation routine for the attribute strings of an element. Checks
* if the string is non-null, positive length and only contains [a-zA-Z_\.0-9].
*
* @param name the string to validate.
* @return boolean indicating if the string was valid.
*/
private boolean validateValue(String name) {
if (name == null)
return false;
// Want to match on [0-9a-zA-Z_.]
if (!name.matches("\\A[\\.\\w]+\\z")) {
return false;
}
return true;
}
/**
* <p>
* Create a {@link Policy} instance based on the current configuration. This
* method checks for certain policy invariants used to enforce certain guarantees
* about the expected structure of a policy stanza.
* Those invariants are:
* </p>
* <ul>
* <li> If a default stanza
* <ul>
* <li> an attached global seinfo tag must be present </li>
* <li> no signatures and no package names can be present </li>
* </ul>
* </li>
* <li> If a signer stanza
* <ul>
* <li> at least one cert must be found </li>
* <li> either a global seinfo value is present OR at least one
* inner package mapping must be present. </li>
* </ul>
* </li>
* </ul>
*
* @return an instance of {@link Policy} with the options set from this builder
* @throws IllegalStateException if an invariant is violated.
*/
public Policy build() {
Policy p = new Policy(this);
if (p.mDefaultStanza) {
if (p.mSeinfo == null) {
String err = "Missing global seinfo tag with default stanza.";
throw new IllegalStateException(err);
}
if (p.mCerts.size() != 0) {
String err = "Certs not allowed with default stanza.";
throw new IllegalStateException(err);
}
if (!p.mPkgMap.isEmpty()) {
String err = "Inner package mappings not allowed with default stanza.";
throw new IllegalStateException(err);
}
} else {
if (p.mCerts.size() == 0) {
String err = "Missing certs with signer tag. Expecting at least one.";
throw new IllegalStateException(err);
}
if ((p.mSeinfo == null) && (p.mPkgMap.isEmpty())) {
String err = "Missing seinfo OR package tags with signer tag. At " +
"least one must be present.";
throw new IllegalStateException(err);
}
}
return p;
}
}
}