/* * 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.File; 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.BundleRevisionImpl; import org.apache.felix.framework.FakeBundle; 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.NativeLibrary; /* 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.cache.Content; 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 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 FakeBundle 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 BundleRevisionImpl(fake, Long.toString(fake.getBundleId())), 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(BundleRevisionImpl 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, BundleRevisionImpl 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); } }