blob: 3bef55ec5d9e61d617a09e2c7890455c3c0e0784 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.felix.framework.security.condpermadmin;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.net.URL;
import java.security.AccessControlContext;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.Permission;
import java.security.Principal;
import java.security.ProtectionDomain;
import java.security.PublicKey;
import java.security.SignatureException;
import java.security.cert.X509Certificate;
import java.util.AbstractSet;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.Map.Entry;
import org.apache.felix.framework.BundleProtectionDomain;
import org.apache.felix.framework.security.permissionadmin.PermissionAdminImpl;
import org.apache.felix.framework.security.util.Conditions;
import org.apache.felix.framework.security.util.LocalPermissions;
import org.apache.felix.framework.security.util.Permissions;
import org.apache.felix.framework.security.util.PropertiesCache;
import org.apache.felix.framework.util.manifestparser.R4Library;
/*
import org.apache.felix.moduleloader.ICapability;
import org.apache.felix.moduleloader.IContent;
import org.apache.felix.moduleloader.IModule;
import org.apache.felix.moduleloader.IRequirement;
import org.apache.felix.moduleloader.IWire;
*/
import org.apache.felix.framework.capabilityset.Capability;
import org.apache.felix.framework.capabilityset.Requirement;
import org.apache.felix.framework.resolver.Content;
import org.apache.felix.framework.resolver.Module;
import org.apache.felix.framework.resolver.Wire;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleException;
import org.osgi.framework.ServiceReference;
import org.osgi.framework.Version;
import org.osgi.service.condpermadmin.ConditionInfo;
import org.osgi.service.condpermadmin.ConditionalPermissionAdmin;
import org.osgi.service.condpermadmin.ConditionalPermissionInfo;
import org.osgi.service.condpermadmin.ConditionalPermissionUpdate;
import org.osgi.service.permissionadmin.PermissionInfo;
/**
* An implementation of the ConditionalPermissionAdmin service that doesn't need
* to have a framework specific security manager set. It use the DomainGripper
* to know what bundleprotectiondomains are expected.
*/
public final class ConditionalPermissionAdminImpl implements
ConditionalPermissionAdmin
{
private static class OrderedHashMap extends HashMap
{
private final List m_order = new ArrayList();
public Object put(Object key, Object value)
{
Object result = super.put(key, value);
if (result != value)
{
m_order.remove(key);
m_order.add(key);
}
return result;
};
public void putAll(Map map)
{
for (Iterator iter = map.entrySet().iterator(); iter.hasNext();)
{
Entry entry = (Entry) iter.next();
put(entry.getKey(), entry.getValue());
}
};
public Set keySet()
{
return new AbstractSet()
{
public Iterator iterator()
{
return m_order.iterator();
}
public int size()
{
return m_order.size();
}
};
};
public Set entrySet()
{
return new AbstractSet()
{
public Iterator iterator()
{
return new Iterator()
{
Iterator m_iter = m_order.iterator();
public boolean hasNext()
{
return m_iter.hasNext();
}
public Object next()
{
final Object key = m_iter.next();
return new Entry()
{
public Object getKey()
{
return key;
}
public Object getValue()
{
return get(key);
}
public Object setValue(Object arg0)
{
throw new IllegalStateException(
"Not Implemented");
}
};
}
public void remove()
{
throw new IllegalStateException("Not Implemented");
}
};
}
public int size()
{
return m_order.size();
}
};
};
public Collection values()
{
List result = new ArrayList();
for (Iterator iter = m_order.iterator(); iter.hasNext();)
{
result.add(super.get(iter.next()));
}
return result;
};
public Object remove(Object key)
{
Object result = super.remove(key);
if (result != null)
{
m_order.remove(key);
}
return result;
};
public void clear()
{
super.clear();
m_order.clear();
};
};
private static final ConditionInfo[] EMPTY_CONDITION_INFO = new ConditionInfo[0];
private static final PermissionInfo[] EMPTY_PERMISSION_INFO = new PermissionInfo[0];
private final Map m_condPermInfos = new OrderedHashMap();
private final PropertiesCache m_propertiesCache;
private final Permissions m_permissions;
private final Conditions m_conditions;
private final LocalPermissions m_localPermissions;
private final PermissionAdminImpl m_pai;
public ConditionalPermissionAdminImpl(Permissions permissions,
Conditions condtions, LocalPermissions localPermissions,
PropertiesCache cache, PermissionAdminImpl pai) throws IOException
{
m_propertiesCache = cache;
m_permissions = permissions;
m_conditions = condtions;
m_localPermissions = localPermissions;
Map old = new OrderedHashMap();
// Now try to restore the cache.
m_propertiesCache.read(ConditionalPermissionInfoImpl.class, old);
for (Iterator iter = old.entrySet().iterator(); iter.hasNext();)
{
Entry entry = (Entry) iter.next();
String name = (String) entry.getKey();
ConditionalPermissionInfoImpl cpi = ((ConditionalPermissionInfoImpl) entry
.getValue());
m_condPermInfos.put(name, new ConditionalPermissionInfoImpl(name,
cpi._getConditionInfos(), cpi._getPermissionInfos(), this, cpi
.isAllow()));
}
m_pai = pai;
}
public ConditionalPermissionInfo addConditionalPermissionInfo(
ConditionInfo[] conditions, PermissionInfo[] permissions)
{
Object sm = System.getSecurityManager();
if (sm != null)
{
((SecurityManager) sm).checkPermission(Permissions.ALL_PERMISSION);
}
ConditionalPermissionInfoImpl result = new ConditionalPermissionInfoImpl(
notNull(conditions), notNull(permissions), this, true);
return write(result.getName(), result);
}
ConditionalPermissionInfoImpl write(String name,
ConditionalPermissionInfoImpl cpi)
{
synchronized (m_propertiesCache)
{
Map tmp = null;
synchronized (m_condPermInfos)
{
tmp = new OrderedHashMap();
tmp.putAll(m_condPermInfos);
if ((name != null) && (cpi != null))
{
m_condPermInfos.put(name, cpi);
}
else if (name != null)
{
m_condPermInfos.remove(name);
}
else
{
tmp = null;
}
}
try
{
m_propertiesCache.write(m_condPermInfos);
}
catch (IOException ex)
{
synchronized (m_condPermInfos)
{
if (tmp != null)
{
m_condPermInfos.clear();
m_condPermInfos.putAll(tmp);
}
}
ex.printStackTrace();
throw new IllegalStateException(ex.getMessage());
}
}
synchronized (m_condPermInfos)
{
return (ConditionalPermissionInfoImpl) m_condPermInfos.get(name);
}
}
private static class FakeBundle implements Bundle
{
private final Map m_certs;
public FakeBundle(Map certs)
{
m_certs = Collections.unmodifiableMap(certs);
}
public Enumeration findEntries(String arg0, String arg1, boolean arg2)
{
return null;
}
public BundleContext getBundleContext()
{
return null;
}
public long getBundleId()
{
return -1;
}
public URL getEntry(String arg0)
{
return null;
}
public Enumeration getEntryPaths(String arg0)
{
return null;
}
public Dictionary getHeaders()
{
return new Hashtable();
}
public Dictionary getHeaders(String arg0)
{
return new Hashtable();
}
public long getLastModified()
{
return 0;
}
public String getLocation()
{
return "";
}
public ServiceReference[] getRegisteredServices()
{
return null;
}
public URL getResource(String arg0)
{
return null;
}
public Enumeration getResources(String arg0) throws IOException
{
return null;
}
public ServiceReference[] getServicesInUse()
{
return null;
}
public Map getSignerCertificates(int arg0)
{
return m_certs;
}
public int getState()
{
return Bundle.UNINSTALLED;
}
public String getSymbolicName()
{
return null;
}
public Version getVersion()
{
return Version.emptyVersion;
}
public boolean hasPermission(Object arg0)
{
return false;
}
public Class loadClass(String arg0) throws ClassNotFoundException
{
return null;
}
public void start() throws BundleException
{
throw new IllegalStateException();
}
public void start(int arg0) throws BundleException
{
throw new IllegalStateException();
}
public void stop() throws BundleException
{
throw new IllegalStateException();
}
public void stop(int arg0) throws BundleException
{
throw new IllegalStateException();
}
public void uninstall() throws BundleException
{
throw new IllegalStateException();
}
public void update() throws BundleException
{
throw new IllegalStateException();
}
public void update(InputStream arg0) throws BundleException
{
throw new IllegalStateException();
}
public boolean equals(Object o)
{
return this == o;
}
public int hashCode()
{
return System.identityHashCode(this);
}
}
private static class FakeCert extends X509Certificate
{
private final Principal m_principal;
public FakeCert(final String principal)
{
m_principal = new Principal()
{
public String getName()
{
return principal;
}
};
}
public void checkValidity()
throws java.security.cert.CertificateExpiredException,
java.security.cert.CertificateNotYetValidException
{
}
public void checkValidity(Date date)
throws java.security.cert.CertificateExpiredException,
java.security.cert.CertificateNotYetValidException
{
}
public int getBasicConstraints()
{
return 0;
}
public Principal getIssuerDN()
{
return null;
}
public boolean[] getIssuerUniqueID()
{
return null;
}
public boolean[] getKeyUsage()
{
return null;
}
public Date getNotAfter()
{
return null;
}
public Date getNotBefore()
{
return null;
}
public BigInteger getSerialNumber()
{
return null;
}
public String getSigAlgName()
{
return null;
}
public String getSigAlgOID()
{
return null;
}
public byte[] getSigAlgParams()
{
return null;
}
public byte[] getSignature()
{
return null;
}
public Principal getSubjectDN()
{
return m_principal;
}
public boolean[] getSubjectUniqueID()
{
return null;
}
public byte[] getTBSCertificate()
throws java.security.cert.CertificateEncodingException
{
return null;
}
public int getVersion()
{
return 0;
}
public byte[] getEncoded()
throws java.security.cert.CertificateEncodingException
{
return null;
}
public PublicKey getPublicKey()
{
return null;
}
public String toString()
{
return m_principal.getName();
}
public void verify(PublicKey key)
throws java.security.cert.CertificateException,
NoSuchAlgorithmException, InvalidKeyException,
NoSuchProviderException, SignatureException
{
}
public void verify(PublicKey key, String sigProvider)
throws java.security.cert.CertificateException,
NoSuchAlgorithmException, InvalidKeyException,
NoSuchProviderException, SignatureException
{
}
public Set getCriticalExtensionOIDs()
{
return null;
}
public byte[] getExtensionValue(String arg0)
{
return null;
}
public Set getNonCriticalExtensionOIDs()
{
return null;
}
public boolean hasUnsupportedCriticalExtension()
{
return false;
}
public boolean equals(Object o)
{
return this == o;
}
public int hashCode()
{
return System.identityHashCode(this);
}
}
public AccessControlContext getAccessControlContext(final String[] signers)
{
Map certificates = new HashMap();
for (int i = 0; i < signers.length; i++)
{
StringTokenizer tok = new StringTokenizer(signers[i], ";");
List certsList = new ArrayList();
while (tok.hasMoreTokens())
{
certsList.add(tok.nextToken());
}
String[] certs = (String[]) certsList.toArray(new String[certsList
.size()]);
X509Certificate key = new FakeCert(certs[0]);
List certList = new ArrayList();
certificates.put(key, certList);
certList.add(key);
for (int j = 1; j < certs.length; j++)
{
certList.add(new FakeCert(certs[j]));
}
}
final Bundle fake = new FakeBundle(certificates);
ProtectionDomain domain = new ProtectionDomain(null, null)
{
public boolean implies(Permission permission)
{
List posts = new ArrayList();
Boolean result = m_pai.hasPermission("", fake, permission,
ConditionalPermissionAdminImpl.this, this, null);
if (result != null)
{
return result.booleanValue();
}
if (eval(posts, new Module()
{
public Bundle getBundle()
{
return fake;
}
public List<Capability> getCapabilities()
{
return null;
}
public Class getClassByDelegation(String arg0)
throws ClassNotFoundException
{
return null;
}
public Content getContent()
{
return null;
}
public int getDeclaredActivationPolicy()
{
return 0;
}
public List<Requirement> getDynamicRequirements()
{
return null;
}
public URL getEntry(String arg0)
{
return null;
}
public Map getHeaders()
{
return null;
}
public String getId()
{
return null;
}
public InputStream getInputStream(int arg0, String arg1)
throws IOException
{
return null;
}
public List<R4Library> getNativeLibraries()
{
return null;
}
public List<Requirement> getRequirements()
{
return null;
}
public URL getResourceByDelegation(String arg0)
{
return null;
}
public Enumeration getResourcesByDelegation(String arg0)
{
return null;
}
public Object getSecurityContext()
{
return null;
}
public String getSymbolicName()
{
return null;
}
public Version getVersion()
{
return null;
}
public List<Wire> getWires()
{
return null;
}
public boolean hasInputStream(int arg0, String arg1)
throws IOException
{
return false;
}
public boolean isExtension()
{
return false;
}
public boolean isResolved()
{
return false;
}
public void setSecurityContext(Object arg0)
{
}
public URL getLocalURL(int arg0, String arg1)
{
// TODO Auto-generated method stub
return null;
}
}, permission, m_pai))
{
if (!posts.isEmpty())
{
return m_conditions.evalRecursive(posts);
}
return true;
}
return false;
}
};
return new AccessControlContext(new ProtectionDomain[] { domain });
}
public ConditionalPermissionInfo getConditionalPermissionInfo(String name)
{
if (name == null)
{
throw new IllegalArgumentException("Name may not be null");
}
ConditionalPermissionInfoImpl result = null;
synchronized (m_condPermInfos)
{
result = (ConditionalPermissionInfoImpl) m_condPermInfos.get(name);
}
if (result == null)
{
result = new ConditionalPermissionInfoImpl(this, name, true);
result = write(result.getName(), result);
}
return result;
}
public Enumeration getConditionalPermissionInfos()
{
synchronized (m_condPermInfos)
{
return Collections.enumeration(new ArrayList(m_condPermInfos
.values()));
}
}
public ConditionalPermissionInfo setConditionalPermissionInfo(String name,
ConditionInfo[] conditions, PermissionInfo[] permissions)
{
Object sm = System.getSecurityManager();
if (sm != null)
{
((SecurityManager) sm).checkPermission(Permissions.ALL_PERMISSION);
}
ConditionalPermissionInfoImpl result = null;
conditions = notNull(conditions);
permissions = notNull(permissions);
if (name != null)
{
synchronized (m_condPermInfos)
{
result = (ConditionalPermissionInfoImpl) m_condPermInfos
.get(name);
if (result == null)
{
result = new ConditionalPermissionInfoImpl(name,
conditions, permissions, this, true);
}
else
{
result.setConditionsAndPermissions(conditions, permissions);
}
}
}
else
{
result = new ConditionalPermissionInfoImpl(conditions, permissions,
this, true);
}
return write(result.getName(), result);
}
private PermissionInfo[] notNull(PermissionInfo[] permissions)
{
if (permissions == null)
{
return ConditionalPermissionInfoImpl.PERMISSION_INFO;
}
return (PermissionInfo[]) notNull((Object[]) permissions).toArray(
EMPTY_PERMISSION_INFO);
}
private ConditionInfo[] notNull(ConditionInfo[] conditions)
{
if (conditions == null)
{
return ConditionalPermissionInfoImpl.CONDITION_INFO;
}
return (ConditionInfo[]) notNull((Object[]) conditions).toArray(
EMPTY_CONDITION_INFO);
}
private List notNull(Object[] elements)
{
List result = new ArrayList();
for (int i = 0; i < elements.length; i++)
{
if (elements[i] != null)
{
result.add(elements[i]);
}
}
return result;
}
// The thread local stack used to keep track of bundle protection domains we
// still expect to see.
private final ThreadLocal m_stack = new ThreadLocal();
/**
* This method does the actual permission check. If it is not a direct check
* it will try to determine the other bundle domains that will follow
* automatically in case this is the first check in one permission check. If
* not then it will keep track of which domains we have already see. While
* it keeps track it builds up a list of postponed tuples which it will
* evaluate at the last domain. See the core spec 9.5.1 and following for a
* general description.
*
* @param felixBundle
* the bundle in question.
* @param loader
* the content loader of the bundle to get access to the jar to
* check for local permissions.
* @param root
* the bundle id.
* @param signers
* the signers (this is to support the ACC based on signers)
* @param pd
* the bundle protection domain
* @param permission
* the permission currently checked
* @param direct
* whether this is a direct check or not. direct check will not
* expect any further bundle domains on the stack
* @return true in case the permission is granted or there are postponed
* tuples false if not. Again, see the spec for more explanations.
*/
public boolean hasPermission(Module module, Content content,
ProtectionDomain pd, Permission permission, boolean direct, Object admin)
{
// System.out.println(felixBundle + "-" + permission);
List domains = null;
List tuples = null;
Object[] entry = null;
// first see whether this is the normal case (the special case is for
// the ACC based on signers).
// In case of a direct call we don't need to look for other pds
if (direct)
{
domains = new ArrayList();
tuples = new ArrayList();
domains.add(pd);
}
else
{
// Get the other pds from the stck
entry = (Object[]) m_stack.get();
// if there are none then get them from the gripper
if (entry == null)
{
entry = new Object[] { new ArrayList(DomainGripper.grab()),
new ArrayList() };
}
else
{
m_stack.set(null);
}
domains = (List) entry[0];
tuples = (List) entry[1];
if (!domains.contains(pd))
{
// We have been called directly without the direct flag
domains.clear();
domains.add(pd);
}
}
// check the local permissions. they need to all the permission if there
// are any
if (!impliesLocal(module.getBundle(), content, permission))
{
return false;
}
List posts = new ArrayList();
boolean result = eval(posts, module, permission, admin);
domains.remove(pd);
// We postponed tuples
if (!posts.isEmpty())
{
tuples.add(posts);
}
// Are we at the end or this was a direct call?
if (domains.isEmpty())
{
m_stack.set(null);
// Now eval the postponed tupels. if the previous eval did return
// false
// tuples will be empty so we don't return from here.
if (!tuples.isEmpty())
{
return m_conditions.evalRecursive(tuples);
}
}
else
{
// this is to support recursive permission checks. In case we
// trigger
// a permission check while eval the stack is null until this point
m_stack.set(entry);
}
return result;
}
public boolean impliesLocal(Bundle felixBundle, Content content,
Permission permission)
{
return m_localPermissions.implies(content, felixBundle, permission);
}
public boolean isEmpty()
{
synchronized (m_condPermInfos)
{
return m_condPermInfos.isEmpty();
}
}
// we need to find all conditions that apply and then check whether they
// de note the permission in question unless the conditions are postponed
// then we make sure their permissions imply the permission and add them
// to the list of posts. Return true in case we pass or have posts
// else falls and clear the posts first.
private boolean eval(List posts, Module module, Permission permission,
Object admin)
{
List condPermInfos = null;
synchronized (m_condPermInfos)
{
if (isEmpty() && (admin == null))
{
return true;
}
condPermInfos = new ArrayList(m_condPermInfos.values());
}
// Check for implicit permissions like access to file area
if (m_permissions.getPermissions(
m_permissions.getImplicit(module.getBundle())).implies(permission,
module.getBundle()))
{
return true;
}
List pls = new ArrayList();
// now do the real thing
for (Iterator iter = condPermInfos.iterator(); iter.hasNext();)
{
ConditionalPermissionInfoImpl cpi = (ConditionalPermissionInfoImpl) iter
.next();
ConditionInfo[] conditions = cpi._getConditionInfos();
List currentPosts = new ArrayList();
Conditions conds = m_conditions.getConditions(module, conditions);
if (!conds.isSatisfied(currentPosts, m_permissions
.getPermissions(cpi._getPermissionInfos()), permission))
{
continue;
}
if (!m_permissions.getPermissions(cpi._getPermissionInfos())
.implies(permission, null))
{
continue;
}
if (currentPosts.isEmpty())
{
pls.add(new Object[] { cpi, null });
break;
}
pls.add(new Object[] { cpi, currentPosts, conds });
}
while (pls.size() > 1)
{
if (!((ConditionalPermissionInfoImpl) ((Object[]) pls.get(pls
.size() - 1))[0]).isAllow())
{
pls.remove(pls.size() - 1);
}
else
{
break;
}
}
if (pls.size() == 1)
{
if (((Object[]) pls.get(0))[1] != null)
{
posts.add(pls.get(0));
}
return ((ConditionalPermissionInfoImpl) ((Object[]) pls.get(0))[0])
.isAllow();
}
for (Iterator iter = pls.iterator(); iter.hasNext();)
{
posts.add(iter.next());
}
return !posts.isEmpty();
}
public ConditionalPermissionInfo newConditionalPermissionInfo(
String encodedConditionalPermissionInfo)
{
return new ConditionalPermissionInfoImpl(
encodedConditionalPermissionInfo);
}
public ConditionalPermissionInfo newConditionalPermissionInfo(String name,
ConditionInfo[] conditions, PermissionInfo[] permissions, String access)
{
return new ConditionalPermissionInfoImpl(name, conditions, permissions,
ConditionalPermissionAdminImpl.this, access
.equals(ConditionalPermissionInfo.ALLOW));
}
public ConditionalPermissionUpdate newConditionalPermissionUpdate()
{
return new ConditionalPermissionUpdate()
{
List current = null;
List out = null;
{
synchronized (m_condPermInfos)
{
current = new ArrayList(m_condPermInfos.values());
out = new ArrayList(m_condPermInfos.values());
}
}
public boolean commit()
{
synchronized (m_condPermInfos)
{
if (current.equals(new ArrayList(m_condPermInfos.values())))
{
m_condPermInfos.clear();
write(null, null);
for (Iterator iter = out.iterator(); iter.hasNext();)
{
ConditionalPermissionInfoImpl cpii = (ConditionalPermissionInfoImpl) iter
.next();
write(cpii.getName(), cpii);
}
}
else
{
return false;
}
}
return true;
}
public List getConditionalPermissionInfos()
{
return out;
}
};
}
public boolean handlePAHandle(BundleProtectionDomain pd)
{
Object[] entry = (Object[]) m_stack.get();
if (entry == null)
{
entry = new Object[] { new ArrayList(DomainGripper.grab()),
new ArrayList() };
}
((List) entry[0]).remove(pd);
if (((List) entry[0]).isEmpty())
{
m_stack.set(null);
if (!((List) entry[1]).isEmpty())
{
return m_conditions.evalRecursive(((List) entry[1]));
}
}
else
{
m_stack.set(entry);
}
return true;
}
public void clearPD()
{
m_stack.set(null);
}
}