Initial version of the OSGi security layer. (FELIX-116, FELIX-115, FELIX-22)
git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@634480 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/framework/security/pom.xml b/framework/security/pom.xml
new file mode 100644
index 0000000..122d2c9
--- /dev/null
+++ b/framework/security/pom.xml
@@ -0,0 +1,66 @@
+<!--
+ 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.
+-->
+<project>
+ <parent>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>felix</artifactId>
+ <version>1.0.2</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+ <packaging>bundle</packaging>
+ <name>Apache Felix Security Provider</name>
+ <artifactId>org.apache.felix.framework.security</artifactId>
+ <version>0.9.0-SNAPSHOT</version>
+ <description>
+ This bundle provides an implementation of the OSGi security for Apache Felix.
+ </description>
+ <dependencies>
+ <dependency>
+ <groupId>${pom.groupId}</groupId>
+ <artifactId>org.osgi.core</artifactId>
+ <version>1.1.0-SNAPSHOT</version>
+ </dependency>
+ <dependency>
+ <groupId>${pom.groupId}</groupId>
+ <artifactId>org.apache.felix.framework</artifactId>
+ <version>1.1.0-SNAPSHOT</version>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <version>1.4.0</version>
+ <extensions>true</extensions>
+ <configuration>
+ <instructions>
+ <Bundle-SymbolicName>${pom.artifactId}</Bundle-SymbolicName>
+ <Export-Package>org.osgi.service.permissionadmin;-split-package:=merge-first, org.osgi.service.condpermadmin;-split-package:=merge-first</Export-Package>
+ <Private-Package>org.apache.felix.framework.*</Private-Package>
+ <Import-Package>!*</Import-Package>
+ <Fragment-Host>system.bundle; extension:=framework</Fragment-Host>
+ <Felix-Activator>org.apache.felix.framework.SecurityActivator</Felix-Activator>
+ </instructions>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/framework/security/src/main/java/org/apache/felix/framework/SecurityActivator.java b/framework/security/src/main/java/org/apache/felix/framework/SecurityActivator.java
new file mode 100644
index 0000000..fe8bcc9
--- /dev/null
+++ b/framework/security/src/main/java/org/apache/felix/framework/SecurityActivator.java
@@ -0,0 +1,320 @@
+/*
+ * 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;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.StringTokenizer;
+import java.util.Map.Entry;
+
+import org.apache.felix.framework.security.SecurityConstants;
+import org.apache.felix.framework.security.condpermadmin.ConditionalPermissionAdminImpl;
+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.security.verifier.BundleDNParser;
+import org.apache.felix.framework.util.SecureAction;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleException;
+import org.osgi.service.condpermadmin.ConditionalPermissionAdmin;
+import org.osgi.service.permissionadmin.PermissionAdmin;
+
+/**
+ * <p>This Felix specific activator installs a security provider with the Felix
+ * framework. The security settings can be changed via the
+ * {@link PermissionAdmin} and/or the
+ * {@link ConditionalPermissionAdmin} services that may be published by
+ * this class.
+ * </p>
+ * <p>
+ * Permission informations as well as caching data will be stored in several
+ * files in a directory called <tt>security</tt> obtained by a call to
+ * {@link BundleContext#getDataFile(String))}.
+ * </p>
+ * <p>
+ * The following properties are recognized:
+ * <p>
+ * {@link SecurityConstants#ENABLE_PERMISSIONADMIN_PROP} - Whether or not
+ * (<tt>true</tt>|<tt>false</tt>) to
+ * publish a{@link ConditionalPermissionAdmin} service. The default is
+ * {@link SecurityConstants#ENABLE_PERMISSIONADMIN_VALUE}.
+ * </p>
+ * <p>
+ * {@link SecurityConstants#ENABLE_CONDPERMADMIN_PROP} - Whether or not
+ * (<tt>true</tt>|<tt>false</tt>) to
+ * publish a{@link ConditionalPermissionAdmin} service. The default is
+ * {@link SecurityConstants#ENABLE_CONDPERMADMIN_VALUE}.
+ * </p>
+ * <p>
+ * {@link SecurityConstants#KEYSTORE_FILE_PROP} - The keystore URL(s) to use as
+ * trusted CA stores. The urls must be separated by a guard (i.e., <tt>|</tt>).
+ * The default is
+ * {@link SecurityConstants#KEYSTORE_FILE_VALUE}.
+ * </p>
+ * <p>
+ * {@link SecurityConstants#KEYSTORE_PASS_PROP} - The keystore password(s) to
+ * use for the given keystores. The passwords must be separated by a guard
+ * (i.e., <tt>|</tt>).The default is
+ * {@link SecurityConstants#KEYSTORE_PASS_VALUE}.
+ * </p>
+ * <p>
+ * {@link SecurityConstants#KEYSTORE_TYPE_PROP} - The keystore type(s) to use
+ * for the given keystores. The types must be separated by a guard
+ * (i.e., <tt>|</tt>).The default is
+ * {@link SecurityConstants#KEYSTORE_TYPE_VALUE}.
+ * </p>
+ * <p>
+ * {@link SecurityConstants#CRL_FILE_PROP} - The CRL URL(s) to use for revoked
+ * certificates. The urls must be separated by a guard (i.e., <tt>|</tt>).
+ * The default is {@link SecurityConstants#CRL_FILE_VALUE}.
+ * </p>
+ * </p>
+ */
+/*
+ * TODO: using a string for passwords is bad. We need to investigate
+ * alternatives.
+ *
+ * TODO: we might want to allow for the recognized properties to
+ * change without a restart. This is trick because we can not publish a managed
+ * service due to not being able to import as we are an extension bundle.
+ */
+public final class SecurityActivator implements BundleActivator
+{
+ private SecurityProviderImpl m_provider = null;
+ private PropertiesCache m_dnsCache = null;
+ private PropertiesCache m_localCache = null;
+ private LocalPermissions m_localPermissions = null;
+
+ public synchronized void start(BundleContext context) throws Exception
+ {
+ PermissionAdminImpl pai = null;
+
+ SecureAction action = new SecureAction();
+
+ Permissions permissions = new Permissions(context, action);
+
+ File tmp = context.getDataFile("security" + File.separator + "tmp");
+ if ((tmp == null) || (!tmp.isDirectory() && !tmp.mkdirs()))
+ {
+ throw new IOException("Can't create tmp dir.");
+ }
+ // TODO: log something if we can not clean-up the tmp dir
+ File[] old = tmp.listFiles();
+ if (old != null)
+ {
+ for (int i = 0; i < old.length; i++)
+ {
+ old[i].delete();
+ }
+ }
+
+ if ("TRUE".equalsIgnoreCase(getProperty(context,
+ SecurityConstants.ENABLE_PERMISSIONADMIN_PROP,
+ SecurityConstants.ENABLE_PERMISSIONADMIN_VALUE)))
+ {
+ File cache =
+ context.getDataFile("security" + File.separator + "pa.txt");
+ if ((cache == null) || (!cache.isFile() && !cache.createNewFile()))
+ {
+ throw new IOException("Can't create cache file");
+ }
+ pai =
+ new PermissionAdminImpl(permissions, new PropertiesCache(cache,
+ tmp, action));
+ }
+
+ ConditionalPermissionAdminImpl cpai = null;
+
+ if ("TRUE".equalsIgnoreCase(getProperty(context,
+ SecurityConstants.ENABLE_CONDPERMADMIN_PROP,
+ SecurityConstants.ENABLE_CONDPERMADMIN_VALUE)))
+ {
+ File cpaCache =
+ context.getDataFile("security" + File.separator + "cpa.txt");
+ if ((cpaCache == null) || (!cpaCache.isFile() && !cpaCache.createNewFile()))
+ {
+ throw new IOException("Can't create cache file");
+ }
+ File localCache =
+ context.getDataFile("security" + File.separator + "local.txt");
+ if ((localCache == null) || (!localCache.isFile() && !localCache.createNewFile()))
+ {
+ throw new IOException("Can't create cache file");
+ }
+
+ m_localCache = new PropertiesCache(localCache, tmp, action);
+ m_localPermissions = new LocalPermissions(permissions, m_localCache);
+
+ cpai =
+ new ConditionalPermissionAdminImpl(permissions, new Conditions(
+ action), m_localPermissions,
+ new PropertiesCache(cpaCache, tmp, action));
+ }
+
+ if ((pai != null) || (cpai != null))
+ {
+ String crlList =
+ getProperty(context, SecurityConstants.CRL_FILE_PROP,
+ SecurityConstants.CRL_FILE_VALUE);
+ String storeList =
+ getProperty(context, SecurityConstants.KEYSTORE_FILE_PROP,
+ SecurityConstants.KEYSTORE_FILE_VALUE);
+ String passwdList =
+ getProperty(context, SecurityConstants.KEYSTORE_PASS_PROP,
+ SecurityConstants.KEYSTORE_PASS_VALUE);
+ String typeList =
+ getProperty(context, SecurityConstants.KEYSTORE_TYPE_PROP,
+ SecurityConstants.KEYSTORE_TYPE_VALUE);
+
+ StringTokenizer storeTok = new StringTokenizer(storeList, "|");
+ StringTokenizer passwdTok = new StringTokenizer(passwdList, "|");
+ StringTokenizer typeTok = new StringTokenizer(typeList, "|");
+
+ if ((storeTok.countTokens() != passwdTok.countTokens())
+ || (passwdTok.countTokens() != typeTok.countTokens()))
+ {
+ throw new BundleException(
+ "Each CACerts keystore must have one type and one passwd entry and vice versa.");
+ }
+
+ m_provider =
+ new SecurityProviderImpl(crlList, typeList, passwdList,
+ storeList, pai, cpai, action);
+
+ File cache =
+ context.getDataFile("security" + File.separator + "dns.txt");
+ if ((cache == null) || (!cache.isFile() && !cache.createNewFile()))
+ {
+ throw new IOException("Can't create cache file");
+ }
+ m_dnsCache = new PropertiesCache(cache, tmp, action);
+
+ Map store = m_dnsCache.read(String[].class);
+
+ if (store != null)
+ {
+ BundleDNParser parser = m_provider.getParser();
+
+ for (Iterator iter = store.entrySet().iterator(); iter
+ .hasNext();)
+ {
+ Entry entry = (Entry) iter.next();
+ String[] value = (String[]) entry.getValue();
+ if ("none".equals(value[0]))
+ {
+ parser.put((String) entry.getKey(), null);
+ }
+ else if ("invalid".equals(value[0]))
+ {
+ parser.put((String) entry.getKey(), new String[0]);
+ }
+ else
+ {
+ parser.put((String) entry.getKey(), value);
+ }
+ }
+ }
+ ((Felix) context.getBundle(0)).setSecurityProvider(m_provider);
+ }
+
+ if (pai != null)
+ {
+ context.registerService(PermissionAdmin.class.getName(), pai, null);
+ }
+
+ if (cpai != null)
+ {
+ context.registerService(ConditionalPermissionAdmin.class.getName(),
+ cpai, null);
+ }
+ }
+
+ public synchronized void stop(BundleContext context) throws Exception
+ {
+ if (m_provider != null)
+ {
+ m_dnsCache.write(write(m_provider.getParser().getCache(), context));
+ }
+ if (m_localPermissions != null)
+ {
+ m_localCache.write(write(m_localPermissions.getStore(), context));
+ }
+ m_provider = null;
+ m_dnsCache = null;
+ m_localPermissions = null;
+ }
+
+ private Map write(Map cache, BundleContext context)
+ {
+ // Filter the cached dn chains and only store the latest for each
+ // bundle. This is ok because the framework will prune old revisions
+ // after a restart. The format is <id>-<timestamp>
+ Map store = new HashMap();
+ Map index = new HashMap();
+ for (Iterator iter = cache.entrySet().iterator(); iter.hasNext();)
+ {
+ Entry entry = (Entry) iter.next();
+
+ String key = (String) entry.getKey();
+ String id = key.substring(0, key.indexOf("-"));
+ String time = key.substring(key.indexOf("-") + 1);
+ Bundle bundle = context.getBundle(Long.parseLong(id));
+ long timeLong = Long.parseLong(time);
+ if ((bundle == null) ||
+ ((FelixBundle) bundle).getInfo().getLastModified() > timeLong)
+ {
+ continue;
+ }
+ String last = (String) index.get(id);
+
+ if ((last == null)
+ || (Long.parseLong(last) < timeLong))
+ {
+ index.put(id, time);
+ Object[] dns = (Object[]) entry.getValue();
+ store.remove(id + "-" + last);
+ if ((dns != null) && (dns.length > 0))
+ {
+ store.put(key, dns);
+ }
+ else
+ {
+ store.put(key, (dns == null) ? new String[] {"none"} : new String[] {"invalid"});
+ }
+ }
+ }
+ return store;
+ }
+
+ private String getProperty(BundleContext context, String key,
+ String defaultValue)
+ {
+ String result = context.getProperty(key);
+
+ return ((result != null) && (result.trim().length() > 0)) ? result
+ : defaultValue;
+ }
+}
diff --git a/framework/security/src/main/java/org/apache/felix/framework/SecurityProviderImpl.java b/framework/security/src/main/java/org/apache/felix/framework/SecurityProviderImpl.java
new file mode 100644
index 0000000..6c0a482
--- /dev/null
+++ b/framework/security/src/main/java/org/apache/felix/framework/SecurityProviderImpl.java
@@ -0,0 +1,136 @@
+/*
+ * 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;
+
+import java.security.Permission;
+import java.security.ProtectionDomain;
+
+import org.apache.felix.framework.ext.SecurityProvider;
+import org.apache.felix.framework.security.condpermadmin.ConditionalPermissionAdminImpl;
+import org.apache.felix.framework.security.permissionadmin.PermissionAdminImpl;
+import org.apache.felix.framework.security.util.TrustManager;
+import org.apache.felix.framework.security.verifier.BundleDNParser;
+import org.apache.felix.framework.security.verifier.SignerMatcher;
+import org.apache.felix.framework.util.SecureAction;
+import org.osgi.framework.Bundle;
+
+/**
+ * This class is the entry point to the security. It is used to determine whether
+ * a given bundle is signed correctely and has permissions based on
+ * PermissionAdmin or ConditionalPermissionAdmin.
+ */
+public final class SecurityProviderImpl implements SecurityProvider
+{
+ private final BundleDNParser m_parser;
+ private final PermissionAdminImpl m_pai;
+ private final ConditionalPermissionAdminImpl m_cpai;
+ private final SecureAction m_action;
+
+ SecurityProviderImpl(String crlList, String typeList,
+ String passwdList, String storeList, PermissionAdminImpl pai,
+ ConditionalPermissionAdminImpl cpai, SecureAction action)
+ {
+ m_pai = pai;
+ m_cpai = cpai;
+ m_action = action;
+ m_parser =
+ new BundleDNParser(new TrustManager(crlList, typeList, passwdList,
+ storeList, m_action));
+ }
+
+ BundleDNParser getParser()
+ {
+ return m_parser;
+ }
+
+ /**
+ * If the given bundle is signed but can not be verified (e.g., missing files)
+ * then throw an exception.
+ */
+ public void checkBundle(Bundle bundle) throws Exception
+ {
+ BundleInfo info = ((FelixBundle) bundle).getInfo();
+
+ m_parser.checkDNChains(
+ (Long.toString(bundle.getBundleId()) + "-" + info.getArchive()
+ .getLastModified()), info.getCurrentModule().getContentLoader());
+ }
+
+ /**
+ * Get a signer matcher that can be used to match digital signed bundles.
+ */
+ public Object getSignerMatcher(final Bundle bundle)
+ {
+ return new SignerMatcher(Long.toString(bundle.getBundleId()),
+ ((FelixBundle) bundle).getInfo().getArchive(), m_parser);
+ }
+
+ /**
+ * If we have a permissionadmin then ask that one first and have it
+ * decide in case there is a location bound. If not then either use its
+ * default permission in case there is no conditional permission admin
+ * or else ask that one.
+ */
+ public boolean hasBundlePermission(ProtectionDomain bundleProtectionDomain,
+ Permission permission, boolean direct)
+ {
+ BundleProtectionDomain pd =
+ (BundleProtectionDomain) bundleProtectionDomain;
+ FelixBundle bundle = pd.getBundle();
+ BundleInfo info = bundle.getInfo();
+
+ if (info.getBundleId() == 0)
+ {
+ return true;
+ }
+
+ // TODO: using true, false, or null seems a bit awkward. Improve this.
+ Boolean result = null;
+ if (m_pai != null)
+ {
+ result =
+ m_pai.hasPermission(info.getLocation(), pd.getBundle(),
+ permission, m_cpai, pd);
+ }
+
+ if (result != null)
+ {
+ return result.booleanValue();
+ }
+
+ if (m_cpai != null)
+ {
+ try
+ {
+ return m_cpai.hasPermission(bundle,
+ info.getCurrentModule().getContentLoader(),
+ bundle.getBundleId() + "-" +
+ info.getArchive().getLastModified(),null, pd,
+ permission, direct);
+ }
+ catch (Exception e)
+ {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+
+ return false;
+ }
+}
\ No newline at end of file
diff --git a/framework/security/src/main/java/org/apache/felix/framework/security/SecurityConstants.java b/framework/security/src/main/java/org/apache/felix/framework/security/SecurityConstants.java
new file mode 100644
index 0000000..9ac88d9
--- /dev/null
+++ b/framework/security/src/main/java/org/apache/felix/framework/security/SecurityConstants.java
@@ -0,0 +1,55 @@
+/*
+ * 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;
+
+import java.io.File;
+
+public interface SecurityConstants
+{
+ public static final String KEYSTORE_FILE_PROP = "felix.keystore";
+
+ public static final String KEYSTORE_FILE_VALUE = "file:" +
+ System.getProperty("java.home") + File.separatorChar + "lib"
+ + File.separatorChar + "security" + File.separatorChar + "cacerts"
+ + "|file:" + System.getProperty("user.home") + File.separatorChar
+ + ".keystore";
+
+ public static final String KEYSTORE_TYPE_PROP = "felix.keystore.type";
+
+ public static final String KEYSTORE_TYPE_VALUE = "JKS" + "|" + "JKS";
+
+ public static final String KEYSTORE_PASS_PROP = "felix.keystore.pass";
+
+ public static final String KEYSTORE_PASS_VALUE =
+ "changeit" + "|" + "changeit";
+
+ public static final String CRL_FILE_PROP = "felix.crl";
+
+ public static final String CRL_FILE_VALUE = "";
+
+ public static final String ENABLE_CONDPERMADMIN_PROP =
+ "felix.security.conpermadmin";
+
+ public static final String ENABLE_CONDPERMADMIN_VALUE = "true";
+
+ public static final String ENABLE_PERMISSIONADMIN_PROP =
+ "felix.security.permissionadmin";
+
+ public static final String ENABLE_PERMISSIONADMIN_VALUE = "true";
+}
diff --git a/framework/security/src/main/java/org/apache/felix/framework/security/condpermadmin/ConditionalPermissionAdminImpl.java b/framework/security/src/main/java/org/apache/felix/framework/security/condpermadmin/ConditionalPermissionAdminImpl.java
new file mode 100644
index 0000000..f1f3264
--- /dev/null
+++ b/framework/security/src/main/java/org/apache/felix/framework/security/condpermadmin/ConditionalPermissionAdminImpl.java
@@ -0,0 +1,458 @@
+/*
+ * 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.security.AccessControlContext;
+import java.security.AccessController;
+import java.security.DomainCombiner;
+import java.security.Permission;
+import java.security.ProtectionDomain;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+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.IteratorToEnumeration;
+import org.apache.felix.moduleloader.IContentLoader;
+import org.osgi.framework.Bundle;
+import org.osgi.service.condpermadmin.ConditionInfo;
+import org.osgi.service.condpermadmin.ConditionalPermissionAdmin;
+import org.osgi.service.condpermadmin.ConditionalPermissionInfo;
+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 final ConditionInfo[] EMPTY_CONDITION_INFO = new ConditionInfo[0];
+ private static final PermissionInfo[] EMPTY_PERMISSION_INFO = new PermissionInfo[0];
+ private final Map m_condPermInfos = new HashMap();
+ private final PropertiesCache m_propertiesCache;
+ private final Permissions m_permissions;
+ private final Conditions m_conditions;
+ private final LocalPermissions m_localPermissions;
+
+ public ConditionalPermissionAdminImpl(Permissions permissions,
+ Conditions condtions, LocalPermissions localPermissions,
+ PropertiesCache cache) throws IOException
+ {
+ m_propertiesCache = cache;
+ m_permissions = permissions;
+ m_conditions = condtions;
+ m_localPermissions = localPermissions;
+ // Now try to restore the cache.
+ Map old = m_propertiesCache.read(ConditionalPermissionInfoImpl.class);
+ if (old != null)
+ {
+ 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));
+ }
+ }
+ }
+
+ 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);
+
+ return write(result.getName(), result);
+ }
+
+ ConditionalPermissionInfoImpl write(String name,
+ ConditionalPermissionInfoImpl cpi)
+ {
+ synchronized (m_propertiesCache)
+ {
+ Map tmp = null;
+
+ synchronized (m_condPermInfos)
+ {
+ tmp = new HashMap(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)
+ {
+ m_condPermInfos.clear();
+ m_condPermInfos.putAll(tmp);
+ }
+ ex.printStackTrace();
+ throw new IllegalStateException(ex.getMessage());
+ }
+ }
+ synchronized (m_condPermInfos)
+ {
+ return (ConditionalPermissionInfoImpl) m_condPermInfos.get(name);
+ }
+ }
+
+ // TODO: this is pretty much untested so it might not work like this
+ public AccessControlContext getAccessControlContext(String[] signers)
+ {
+ final String[] finalSigners =
+ (String[]) notNull(signers).toArray(new String[0]);
+ return new AccessControlContext(AccessController.getContext(),
+ new DomainCombiner()
+ {
+ public ProtectionDomain[] combine(ProtectionDomain[] arg0,
+ ProtectionDomain[] arg1)
+ {
+ return new ProtectionDomain[] { new ProtectionDomain(null,
+ null)
+ {
+ public boolean implies(Permission permission)
+ {
+ return hasPermission(null, null, null, finalSigners,
+ this, permission, true);
+ }
+ } };
+ }
+ });
+ }
+
+ 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);
+
+ result = write(result.getName(), result);
+ }
+
+ return result;
+ }
+
+ public Enumeration getConditionalPermissionInfos()
+ {
+ synchronized (m_condPermInfos)
+ {
+ return new IteratorToEnumeration((new ArrayList(m_condPermInfos
+ .values())).iterator());
+ }
+ }
+
+ 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);
+ }
+ else
+ {
+ result.setConditionsAndPermissions(conditions, permissions);
+ }
+ }
+ }
+ else
+ {
+ result =
+ new ConditionalPermissionInfoImpl(conditions, permissions, this);
+ }
+
+ 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(Bundle felixBundle, IContentLoader loader, String root,
+ String[] signers, ProtectionDomain pd, Permission permission,
+ boolean direct)
+ {
+ // 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).
+ if (signers == null)
+ {
+ // 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.grep()),
+ 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 (!m_localPermissions.implies(root, loader, felixBundle, permission))
+ {
+ return false;
+ }
+
+ List posts = new ArrayList();
+
+ boolean result = eval(posts, felixBundle, signers, permission);
+
+ if (signers != null)
+ {
+ return result;
+ }
+
+ 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;
+ }
+
+ // 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, Bundle bundle, String[] signers,
+ Permission permission)
+ {
+ List condPermInfos = null;
+
+ synchronized (m_condPermInfos)
+ {
+ if (m_condPermInfos.isEmpty())
+ {
+ return true;
+ }
+ condPermInfos = new ArrayList(m_condPermInfos.values());
+ }
+
+ // Check for implicit permissions like access to file area
+ if ((bundle != null)
+ && m_permissions.getPermissions(m_permissions.getImplicit(bundle))
+ .implies(permission, bundle))
+ {
+ return true;
+ }
+
+ // 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();
+
+ if (!m_conditions.getConditions(bundle, signers, conditions)
+ .isSatisfied(currentPosts))
+ {
+ continue;
+ }
+
+ if (!m_permissions.getPermissions(cpi._getPermissionInfos())
+ .implies(permission, null))
+ {
+ continue;
+ }
+
+ if (currentPosts.isEmpty())
+ {
+ posts.clear();
+ return true;
+ }
+
+ posts.add(currentPosts);
+ }
+
+ return !posts.isEmpty();
+ }
+}
diff --git a/framework/security/src/main/java/org/apache/felix/framework/security/condpermadmin/ConditionalPermissionInfoImpl.java b/framework/security/src/main/java/org/apache/felix/framework/security/condpermadmin/ConditionalPermissionInfoImpl.java
new file mode 100644
index 0000000..cfc4cb8
--- /dev/null
+++ b/framework/security/src/main/java/org/apache/felix/framework/security/condpermadmin/ConditionalPermissionInfoImpl.java
@@ -0,0 +1,212 @@
+/*
+ * 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.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+import java.util.StringTokenizer;
+
+import org.apache.felix.framework.security.util.Permissions;
+import org.osgi.service.condpermadmin.ConditionInfo;
+import org.osgi.service.condpermadmin.ConditionalPermissionInfo;
+import org.osgi.service.permissionadmin.PermissionInfo;
+
+/**
+ * Simple storage class for condperminfos. Additionally, this class can be used
+ * to encode and decode infos.
+ */
+public final class ConditionalPermissionInfoImpl implements ConditionalPermissionInfo
+{
+ private static final Random RANDOM = new Random();
+ static final ConditionInfo[] CONDITION_INFO = new ConditionInfo[0];
+ static final PermissionInfo[] PERMISSION_INFO = new PermissionInfo[0];
+ private final Object m_lock = new Object();
+ private final String m_name;
+ private volatile ConditionalPermissionAdminImpl m_cpai;
+ private ConditionInfo[] m_conditions;
+ private PermissionInfo[] m_permissions;
+
+ public ConditionalPermissionInfoImpl(String encoded)
+ {
+ StringTokenizer tok = new StringTokenizer(encoded, "\n");
+ if (!tok.nextToken().trim().equals("{"))
+ {
+ throw new IllegalArgumentException();
+ }
+ m_cpai = null;
+ m_name = tok.nextToken().trim().substring(1);
+ List conditions = new ArrayList();
+ List permissions = new ArrayList();
+ for (String current = tok.nextToken().trim();; current =
+ tok.nextToken().trim())
+ {
+ if (current.equals("}"))
+ {
+ break;
+ }
+ else if (current.startsWith("["))
+ {
+ conditions.add(new ConditionInfo(current));
+ }
+ else if (current.startsWith("("))
+ {
+ permissions.add(new PermissionInfo(current));
+ }
+ else
+ {
+ if (!current.startsWith("#"))
+ {
+ throw new IllegalArgumentException();
+ }
+ }
+ }
+
+ m_conditions =
+ conditions.isEmpty() ? CONDITION_INFO
+ : (ConditionInfo[]) conditions
+ .toArray(new ConditionInfo[conditions.size()]);
+ m_permissions =
+ permissions.isEmpty() ? PERMISSION_INFO
+ : (PermissionInfo[]) permissions
+ .toArray(new PermissionInfo[permissions.size()]);
+ }
+
+ public ConditionalPermissionInfoImpl(ConditionalPermissionAdminImpl cpai,
+ String name)
+ {
+ m_name = name;
+ m_cpai = cpai;
+ m_conditions = CONDITION_INFO;
+ m_permissions = PERMISSION_INFO;
+ }
+
+ public ConditionalPermissionInfoImpl(ConditionInfo[] conditions,
+ PermissionInfo[] permisions, ConditionalPermissionAdminImpl cpai)
+ {
+ m_name = Long.toString(RANDOM.nextLong() ^ System.currentTimeMillis());
+ m_cpai = cpai;
+ m_conditions = conditions;
+ m_permissions = permisions;
+ }
+
+ public ConditionalPermissionInfoImpl(String name,
+ ConditionInfo[] conditions, PermissionInfo[] permisions,
+ ConditionalPermissionAdminImpl cpai)
+ {
+ m_name = name;
+ m_conditions = conditions;
+ m_permissions = permisions;
+ m_cpai = cpai;
+ }
+
+ public void delete()
+ {
+ Object sm = System.getSecurityManager();
+ if (sm != null)
+ {
+ ((SecurityManager) sm).checkPermission(Permissions.ALL_PERMISSION);
+ }
+
+ synchronized (m_lock)
+ {
+ m_cpai.write(m_name, null);
+ m_conditions = CONDITION_INFO;
+ m_permissions = PERMISSION_INFO;
+ }
+ }
+
+ public ConditionInfo[] getConditionInfos()
+ {
+ synchronized (m_lock)
+ {
+ return (ConditionInfo[]) m_conditions.clone();
+ }
+ }
+
+ ConditionInfo[] _getConditionInfos()
+ {
+ synchronized (m_lock)
+ {
+ return m_conditions;
+ }
+ }
+
+ void setConditionsAndPermissions(ConditionInfo[] conditions,
+ PermissionInfo[] permissions)
+ {
+ synchronized (m_lock)
+ {
+ m_conditions = conditions;
+ m_permissions = permissions;
+ }
+ }
+
+ public String getName()
+ {
+ return m_name;
+ }
+
+ public PermissionInfo[] getPermissionInfos()
+ {
+ synchronized (m_lock)
+ {
+ return (PermissionInfo[]) m_permissions.clone();
+ }
+ }
+
+ PermissionInfo[] _getPermissionInfos()
+ {
+ synchronized (m_lock)
+ {
+ return m_permissions;
+ }
+ }
+
+ public String getEncoded()
+ {
+ StringBuffer buffer = new StringBuffer();
+ buffer.append('{');
+ buffer.append('\n');
+ buffer.append('#');
+ buffer.append(m_name);
+ buffer.append('\n');
+ synchronized (m_lock)
+ {
+ writeTo(m_conditions, buffer);
+ writeTo(m_permissions, buffer);
+ }
+ buffer.append('}');
+ return buffer.toString();
+ }
+
+ private void writeTo(Object[] elements, StringBuffer buffer)
+ {
+ for (int i = 0; i < elements.length; i++)
+ {
+ buffer.append(elements[i]);
+ buffer.append('\n');
+ }
+ }
+
+ public String toString()
+ {
+ return getEncoded();
+ }
+}
diff --git a/framework/security/src/main/java/org/apache/felix/framework/security/condpermadmin/DomainGripper.java b/framework/security/src/main/java/org/apache/felix/framework/security/condpermadmin/DomainGripper.java
new file mode 100644
index 0000000..a7aee58
--- /dev/null
+++ b/framework/security/src/main/java/org/apache/felix/framework/security/condpermadmin/DomainGripper.java
@@ -0,0 +1,134 @@
+/*
+ * 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.security.AccessControlContext;
+import java.security.AccessController;
+import java.security.AllPermission;
+import java.security.DomainCombiner;
+import java.security.Permission;
+import java.security.PrivilegedAction;
+import java.security.ProtectionDomain;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.felix.framework.BundleProtectionDomain;
+
+/**
+ * This class is a hack to get all BundleProtectionDomains currently on the
+ * security stack. This way we don't need to have our own security manager set.
+ */
+final class DomainGripper implements DomainCombiner, PrivilegedAction
+{
+ private static final ProtectionDomain[] ALL_PERMISSION_PD =
+ new ProtectionDomain[] { new ProtectionDomain(null, null)
+ {
+ public boolean implies(Permission perm)
+ {
+ return true;
+ }
+ } };
+
+ // A per thread cache of DomainGripper objects. We might want to wrap them
+ // in a softreference eventually
+ private static final ThreadLocal m_cache = new ThreadLocal();
+
+ private static final Permission ALL_PERMISSION = new AllPermission();
+
+ private final List m_domains = new ArrayList();
+
+ private AccessControlContext m_system = null;
+
+ /**
+ * Get all bundle protection domains and add them to the m_domains. Then
+ * return the ALL_PERMISSION_PD.
+ */
+ public ProtectionDomain[] combine(ProtectionDomain[] current,
+ ProtectionDomain[] assigned)
+ {
+ filter(current, m_domains);
+ filter(assigned, m_domains);
+
+ return ALL_PERMISSION_PD;
+ }
+
+ private void filter(ProtectionDomain[] assigned, List domains)
+ {
+ if (assigned != null)
+ {
+ for (int i = 0; i < assigned.length; i++)
+ {
+ if ((assigned[i].getClass() == BundleProtectionDomain.class)
+ && !domains.contains(assigned[i]))
+ {
+ domains.add(assigned[i]);
+ }
+ }
+ }
+ }
+
+ /**
+ * Get the current bundle protection domains on the stack up to the last
+ * privileged call.
+ */
+ public static List grep()
+ {
+ // First try to get a cached version. We cache by thread.
+ DomainGripper gripper = (DomainGripper) m_cache.get();
+ if (gripper == null)
+ {
+ // there is none so create one and cache it
+ gripper = new DomainGripper();
+ m_cache.set(gripper);
+ }
+ else
+ {
+ // This thread has a cached version so prepare it
+ gripper.m_domains.clear();
+ }
+
+ // Get the current context.
+ gripper.m_system = AccessController.getContext();
+
+ // and merge it with the current combiner (i.e., gripper)
+ AccessControlContext context =
+ (AccessControlContext) AccessController.doPrivileged(gripper);
+
+ gripper.m_system = null;
+
+ // now get the protection domains
+ AccessController.doPrivileged(gripper, context);
+
+ // and return them
+ return gripper.m_domains;
+ }
+
+ public Object run()
+ {
+ // this is a call to merge with the current context.
+ if (m_system != null)
+ {
+ return new AccessControlContext(m_system, this);
+ }
+
+ // this is a call to get the protection domains.
+ AccessController.checkPermission(ALL_PERMISSION);
+ return null;
+ }
+}
diff --git a/framework/security/src/main/java/org/apache/felix/framework/security/permissionadmin/PermissionAdminImpl.java b/framework/security/src/main/java/org/apache/felix/framework/security/permissionadmin/PermissionAdminImpl.java
new file mode 100644
index 0000000..3bb2a1a
--- /dev/null
+++ b/framework/security/src/main/java/org/apache/felix/framework/security/permissionadmin/PermissionAdminImpl.java
@@ -0,0 +1,291 @@
+/*
+ * 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.permissionadmin;
+
+import java.io.IOException;
+import java.security.AllPermission;
+import java.security.Permission;
+import java.security.ProtectionDomain;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.felix.framework.security.condpermadmin.ConditionalPermissionAdminImpl;
+import org.apache.felix.framework.security.util.Permissions;
+import org.apache.felix.framework.security.util.PropertiesCache;
+import org.osgi.framework.Bundle;
+import org.osgi.service.permissionadmin.PermissionAdmin;
+import org.osgi.service.permissionadmin.PermissionInfo;
+
+/**
+ * This class is a relatively straight forward implementation of the PermissionAdmin service.
+ * The only somewhat involved thing is that it respects the presents of a
+ * conditionalpermissionadmin service as per spec.
+ */
+// TODO: Do we need this class at all or can we just emulate it using the condpermadmin?
+public final class PermissionAdminImpl implements PermissionAdmin
+{
+ private static final PermissionInfo[] ALL_PERMISSION =
+ new PermissionInfo[] { new PermissionInfo(
+ AllPermission.class.getName(), "", "") };
+
+ private final Map m_store = new HashMap();
+
+ private final PropertiesCache m_cache;
+
+ private final Permissions m_permissions;
+
+ private PermissionInfo[] m_default = null;
+
+ public PermissionAdminImpl(Permissions permissions, PropertiesCache cache)
+ throws IOException
+ {
+ m_permissions = permissions;
+ m_cache = cache;
+ Map old = m_cache.read(PermissionInfo[].class);
+ if (old != null)
+ {
+ m_store.putAll(old);
+ }
+ }
+
+ public PermissionInfo[] getDefaultPermissions()
+ {
+ synchronized (m_store)
+ {
+ if (m_default == null)
+ {
+ return null;
+ }
+ return (PermissionInfo[]) m_default.clone();
+ }
+ }
+
+ public synchronized String[] getLocations()
+ {
+ synchronized (m_store)
+ {
+ if (m_store.isEmpty())
+ {
+ return null;
+ }
+
+ return (String[]) m_store.keySet().toArray(
+ new String[m_store.size()]);
+ }
+ }
+
+ public PermissionInfo[] getPermissions(String location)
+ {
+ synchronized (m_store)
+ {
+ if (m_store.containsKey(location))
+ {
+ return (PermissionInfo[]) ((PermissionInfo[]) m_store
+ .get(location)).clone();
+ }
+ return null;
+ }
+ }
+
+ /**
+ * This will do the actual permission check as described in the core spec 10.2
+ * It will respect a present condpermadmin service as described in 9.10.
+ *
+ * @param location the location of the bundle.
+ * @param bundle the bundle in question.
+ * @param permission the permission to check.
+ * @param cpai A condpermadmin if one is present else null.
+ * @param pd the protectiondomain
+ * @return Boolean.TRUE if the location is bound and the permission is
+ * granted or if there is no cpa and the default permissions imply the
+ * permission Boolean.FALSE otherwise unless the location is not bound and
+ * their is a cpa in which case null is returned.
+ */
+ public Boolean hasPermission(String location, Bundle bundle,
+ Permission permission, ConditionalPermissionAdminImpl cpai,
+ ProtectionDomain pd)
+ {
+ PermissionInfo[] permissions = null;
+ boolean file = false;
+ synchronized (m_store)
+ {
+ if (m_store.containsKey(location))
+ {
+ permissions = (PermissionInfo[]) m_store.get(location);
+ file = true;
+ }
+ else if (cpai == null)
+ {
+ if (m_default != null)
+ {
+ permissions = m_default;
+ }
+ else
+ {
+ permissions = ALL_PERMISSION;
+ }
+ }
+ }
+ if ((cpai == null) || file)
+ {
+ if (check(permissions, permission, file ? bundle : null))
+ {
+ return Boolean.TRUE;
+ }
+ }
+
+ permissions = m_permissions.getImplicit(bundle);
+
+ if (check(permissions, permission, bundle))
+ {
+ return Boolean.TRUE;
+ }
+
+ if ((cpai != null) && !file)
+ {
+ return null;
+ }
+ return Boolean.FALSE;
+ }
+
+ private boolean check(PermissionInfo[] permissions, Permission permission,
+ Bundle bundle)
+ {
+ Permissions permissionsObject =
+ m_permissions.getPermissions(permissions);
+
+ return permissionsObject.implies(permission, bundle);
+ }
+
+ public void setDefaultPermissions(PermissionInfo[] permissions)
+ {
+ Object sm = System.getSecurityManager();
+ if (sm != null)
+ {
+ ((SecurityManager) sm).checkPermission(Permissions.ALL_PERMISSION);
+ }
+
+ synchronized (m_cache)
+ {
+ PermissionInfo[] def = null;
+ Map store = null;
+ synchronized (m_store)
+ {
+ def = m_default;
+ store = new HashMap(m_store);
+
+ m_default = (permissions != null) ? notNull(permissions) : null;
+ }
+
+ try
+ {
+ m_cache.write(setDefaults(store, def));
+ }
+ catch (IOException ex)
+ {
+ synchronized (m_store)
+ {
+ m_default = def;
+ }
+
+ ex.printStackTrace();
+ // TODO: log this
+ throw new IllegalStateException(ex.getMessage());
+ }
+ }
+ }
+
+ public void setPermissions(String location, PermissionInfo[] permissions)
+ {
+ Object sm = System.getSecurityManager();
+ if (sm != null)
+ {
+ ((SecurityManager) sm).checkPermission(Permissions.ALL_PERMISSION);
+ }
+
+ synchronized (m_cache)
+ {
+ if (location != null)
+ {
+ Map store = null;
+ Map storeCopy = null;
+ PermissionInfo[] def = null;
+ synchronized (m_store)
+ {
+ storeCopy = new HashMap(m_store);
+ if (permissions != null)
+ {
+ m_store.put(location, notNull(permissions));
+ }
+ else
+ {
+ m_store.remove(location);
+ }
+ store = new HashMap(m_store);
+ }
+ try
+ {
+ m_cache.write(setDefaults(store, def));
+ }
+ catch (IOException ex)
+ {
+ synchronized (m_store)
+ {
+ m_store.clear();
+ m_store.putAll(storeCopy);
+ }
+
+ ex.printStackTrace();
+ // TODO: log this
+ throw new IllegalStateException(ex.getMessage());
+ }
+ }
+ }
+ }
+
+ private Map setDefaults(Map store, PermissionInfo[] def)
+ {
+ if (def != null)
+ {
+ store.put("DEFAULT", def);
+ }
+ else
+ {
+ store.remove("DEFAULT");
+ }
+ return store;
+ }
+
+ private PermissionInfo[] notNull(PermissionInfo[] permissions)
+ {
+ List result = new ArrayList();
+
+ for (int i = 0; i < permissions.length; i++)
+ {
+ if (permissions[i] != null)
+ {
+ result.add(permissions[i]);
+ }
+ }
+ return (PermissionInfo[]) result.toArray(new PermissionInfo[result
+ .size()]);
+ }
+}
diff --git a/framework/security/src/main/java/org/apache/felix/framework/security/util/BundleInputStream.java b/framework/security/src/main/java/org/apache/felix/framework/security/util/BundleInputStream.java
new file mode 100644
index 0000000..1e3f0cc
--- /dev/null
+++ b/framework/security/src/main/java/org/apache/felix/framework/security/util/BundleInputStream.java
@@ -0,0 +1,203 @@
+/*
+ * 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.util;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.jar.JarEntry;
+import java.util.jar.JarOutputStream;
+
+import org.apache.felix.framework.util.IteratorToEnumeration;
+import org.apache.felix.moduleloader.IContent;
+
+/**
+ * This class makes a given content available as a inputstream with a jar content.
+ * In other words the stream can be used as input to a JarInputStream.
+ */
+public final class BundleInputStream extends InputStream
+{
+ private final IContent m_root;
+ private final Enumeration m_content;
+ private final OutputStreamBuffer m_outputBuffer = new OutputStreamBuffer();
+
+ private ByteArrayInputStream m_buffer = null;
+ private JarOutputStream m_output = null;
+
+ public BundleInputStream(IContent root) throws IOException
+ {
+ m_root = root;
+
+ List entries = new ArrayList();
+
+ int count = 0;
+ String manifest = null;
+ for (Enumeration e = m_root.getEntries();e.hasMoreElements();)
+ {
+ String entry = (String) e.nextElement();
+ if (entry.equalsIgnoreCase("META-INF/MANIFEST.MF"))
+ {
+ if (manifest == null)
+ {
+ manifest = entry;
+ }
+ }
+ else if (entry.toUpperCase().startsWith("META-INF/"))
+ {
+ entries.add(count++, entry);
+ }
+ else
+ {
+ entries.add(entry);
+ }
+ }
+ if (manifest == null)
+ {
+ manifest = "META-INF/MANIFEST.MF";
+ }
+ m_content = new IteratorToEnumeration(entries.iterator());
+
+ try
+ {
+ m_output = new JarOutputStream(m_outputBuffer);
+ readNext(manifest);
+ m_buffer = new ByteArrayInputStream(
+ m_outputBuffer.m_outBuffer.toByteArray());
+
+ m_outputBuffer.m_outBuffer = null;
+ }
+ catch (IOException ex)
+ {
+ // TODO: figure out what is wrong
+ ex.printStackTrace();
+ throw ex;
+ }
+ }
+
+ public int read() throws IOException
+ {
+ if ((m_output == null) && (m_buffer == null))
+ {
+ return -1;
+ }
+
+ if (m_buffer != null)
+ {
+ int result = m_buffer.read();
+
+ if (result == -1)
+ {
+ m_buffer = null;
+ return read();
+ }
+
+ return result;
+ }
+
+ if (m_content.hasMoreElements())
+ {
+ String current = (String) m_content.nextElement();
+
+ readNext(current);
+
+ if (!m_content.hasMoreElements())
+ {
+ m_output.close();
+ m_output = null;
+ }
+
+ m_buffer = new ByteArrayInputStream(
+ m_outputBuffer.m_outBuffer.toByteArray());
+
+ m_outputBuffer.m_outBuffer = null;
+ }
+
+ return read();
+ }
+
+ private void readNext(String path) throws IOException
+ {
+ m_outputBuffer.m_outBuffer = new ByteArrayOutputStream();
+
+ InputStream in = null;
+ try
+ {
+ in = m_root.getEntryAsStream(path);
+
+ if (in == null)
+ {
+ throw new IOException("Missing entry");
+ }
+
+ JarEntry entry = new JarEntry(path);
+
+ m_output.putNextEntry(entry);
+
+ byte[] buffer = new byte[4 * 1024];
+
+ for (int c = in.read(buffer); c != -1; c = in.read(buffer))
+ {
+ m_output.write(buffer, 0, c);
+ }
+ }
+ finally
+ {
+ if (in != null)
+ {
+ try
+ {
+ in.close();
+ }
+ catch (Exception ex)
+ {
+ // Not much we can do
+ }
+ }
+ }
+
+ m_output.closeEntry();
+
+ m_output.flush();
+ }
+
+ private static final class OutputStreamBuffer extends OutputStream
+ {
+ ByteArrayOutputStream m_outBuffer = null;
+
+ public void write(int b)
+ {
+ m_outBuffer.write(b);
+ }
+
+ public void write(byte[] buffer) throws IOException
+ {
+ m_outBuffer.write(buffer);
+ }
+
+ public void write(byte[] buffer, int offset, int length)
+ {
+ m_outBuffer.write(buffer, offset, length);
+ }
+ }
+}
diff --git a/framework/security/src/main/java/org/apache/felix/framework/security/util/Conditions.java b/framework/security/src/main/java/org/apache/felix/framework/security/util/Conditions.java
new file mode 100644
index 0000000..e2cd50d
--- /dev/null
+++ b/framework/security/src/main/java/org/apache/felix/framework/security/util/Conditions.java
@@ -0,0 +1,387 @@
+/*
+ * 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.util;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.WeakHashMap;
+import java.util.Map.Entry;
+
+import org.apache.felix.framework.security.verifier.SignerMatcher;
+import org.apache.felix.framework.util.SecureAction;
+import org.osgi.framework.Bundle;
+import org.osgi.service.condpermadmin.BundleSignerCondition;
+import org.osgi.service.condpermadmin.Condition;
+import org.osgi.service.condpermadmin.ConditionInfo;
+
+/**
+ * This class caches conditions instances by their infos. Furthermore, it allows
+ * to eval postponed condition permission tuples as per spec (see 9.45).
+ */
+// TODO: maybe use bundle events instead of soft/weak references.
+public final class Conditions
+{
+ private static final ThreadLocal m_conditionStack = new ThreadLocal();
+
+ private final Map m_cache = new WeakHashMap();
+
+ private final Bundle m_bundle;
+ private final String[] m_signers;
+
+ private final ConditionInfo[] m_conditionInfos;
+ private final Condition[] m_conditions;
+ private final SecureAction m_action;
+
+ public Conditions(SecureAction action)
+ {
+ this(null, null, null, action);
+ }
+
+ private Conditions(Bundle bundle, String[] signers,
+ ConditionInfo[] conditions, SecureAction action)
+ {
+ m_bundle = bundle;
+ m_signers = signers;
+ m_conditionInfos = conditions;
+ m_conditions = ((conditions != null) && (bundle != null)) ? new Condition[m_conditionInfos.length] : null;
+ m_action = action;
+ }
+
+ public Conditions getConditions(Bundle bundle, String[] signers,
+ ConditionInfo[] conditions)
+ {
+ Conditions result = null;
+ Map index = null;
+ synchronized (m_cache)
+ {
+ index = (Map) m_cache.get(conditions);
+ if (index == null)
+ {
+ index = new WeakHashMap();
+ m_cache.put(conditions, index);
+ }
+ }
+ synchronized (index)
+ {
+ if (bundle != null)
+ {
+ result = (Conditions) index.get(bundle);
+ }
+ }
+
+ if (result == null)
+ {
+ result = new Conditions(bundle, signers, conditions, m_action);
+ synchronized (index)
+ {
+ index.put(bundle, result);
+ }
+ }
+
+ return result;
+ }
+
+ // See whether the given list is satisfied or not
+ public boolean isSatisfied(List posts)
+ {
+ for (int i = 0; i < m_conditions.length; i++)
+ {
+ if (m_bundle == null)
+ {
+ if (!m_conditionInfos[i].getType().equals(
+ BundleSignerCondition.class.getName()))
+ {
+ return false;
+ }
+ String[] args = m_conditionInfos[i].getArgs();
+
+ boolean match = false;
+ if (args.length == 0)
+ {
+ for (int j = 0; j < m_signers.length; j++)
+ {
+ if (SignerMatcher.match(args[0], m_signers[j]))
+ {
+ match = true;
+ break;
+ }
+ }
+ }
+ if (!match)
+ {
+ return false;
+ }
+ continue;
+ }
+ try
+ {
+ Condition condition = null;
+ boolean add = false;
+ Class clazz = Class.forName(m_conditionInfos[i].getType());
+
+ synchronized (m_conditionInfos)
+ {
+ condition = m_conditions[i];
+
+ }
+
+ if (condition == null)
+ {
+ add = true;
+ condition = createCondition(m_bundle, clazz, m_conditionInfos[i]);
+ }
+
+ if (condition.isPostponed())
+ {
+ posts.add(condition);
+ if (add)
+ {
+ synchronized (m_conditionInfos)
+ {
+ if (m_conditions[i] == null)
+ {
+ m_conditions[i] = condition;
+ }
+ }
+ }
+ }
+ else
+ {
+ Object current = m_conditionStack.get();
+
+ if (current == null)
+ {
+ m_conditionStack.set(clazz);
+ }
+ else
+ {
+ if (current instanceof HashSet)
+ {
+ if (((HashSet) current).contains(clazz))
+ {
+ return false;
+ }
+ ((HashSet) current).add(clazz);
+ }
+ else
+ {
+ if (current == clazz)
+ {
+ return false;
+ }
+ HashSet frame = new HashSet();
+ frame.add(current);
+ frame.add(clazz);
+ m_conditionStack.set(frame);
+ current = frame;
+ }
+ }
+ try
+ {
+ boolean result = condition.isSatisfied();
+
+ if (!condition.isMutable() && ((condition != Condition.TRUE) && (condition != Condition.FALSE)))
+ {
+ synchronized (m_conditionInfos)
+ {
+ m_conditions[i] = result ? Condition.TRUE : Condition.FALSE;
+ }
+ }
+ else
+ {
+ synchronized (m_conditionInfos)
+ {
+ m_conditions[i] = condition;
+ }
+ }
+ if (!result)
+ {
+ return false;
+ }
+ }
+ finally
+ {
+ if (current == null)
+ {
+ m_conditionStack.set(null);
+ }
+ else
+ {
+ ((HashSet) current).remove(clazz);
+ if (((HashSet) current).isEmpty())
+ {
+ m_conditionStack.set(null);
+ }
+ }
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ // TODO: log this as per spec
+ e.printStackTrace();
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public boolean evalRecursive(List entries)
+ {
+ return _evalRecursive(entries, 0, new ArrayList(), new HashMap());
+ }
+
+ private boolean _evalRecursive(List entries, int pos, List acc, Map contexts)
+ {
+ if (pos == entries.size())
+ {
+ // we need to group by type by tuple
+ Map conditions = new HashMap();
+ for (Iterator iter = acc.iterator(); iter.hasNext();)
+ {
+ for (Iterator iter2 = ((List) iter.next()).iterator(); iter2
+ .hasNext();)
+ {
+ Object entry = iter2.next();
+ Set group = (Set) conditions.get(entry.getClass());
+
+ if (group == null)
+ {
+ group = new HashSet();
+ }
+ group.add(entry);
+
+ conditions.put(entry.getClass(), group);
+ }
+ }
+
+ // and then eval per group
+ for (Iterator iter = conditions.entrySet().iterator(); iter.hasNext();)
+ {
+ Entry entry = (Entry) iter.next();
+ Class key = (Class) entry.getKey();
+
+ Hashtable context = (Hashtable) contexts.get(key);
+ if (context == null)
+ {
+ context = new Hashtable();
+ contexts.put(key, context);
+ }
+ Set set = (Set) entry.getValue();
+ Condition[] current =
+ (Condition[]) set.toArray(new Condition[set.size()]);
+
+ // We must be catching recursive evaluation as per spec, hence use a thread
+ // local stack to do so
+ Object currentCond = m_conditionStack.get();
+
+ if (currentCond == null)
+ {
+ m_conditionStack.set(key);
+ }
+ else
+ {
+ if (currentCond instanceof HashSet)
+ {
+ if (((HashSet) currentCond).contains(key))
+ {
+ return false;
+ }
+ ((HashSet) currentCond).add(key);
+ }
+ else
+ {
+ if (currentCond == key)
+ {
+ return false;
+ }
+ HashSet frame = new HashSet();
+ frame.add(current);
+ frame.add(key);
+ m_conditionStack.set(frame);
+ currentCond = frame;
+ }
+ }
+ try
+ {
+ if (!current[0].isSatisfied(current, context))
+ {
+ return false;
+ }
+ }
+ finally
+ {
+ if (currentCond == null)
+ {
+ m_conditionStack.set(null);
+ }
+ else
+ {
+ ((HashSet) currentCond).remove(key);
+ if (((HashSet) currentCond).isEmpty())
+ {
+ m_conditionStack.set(null);
+ }
+ }
+ }
+ }
+ return true;
+ }
+
+ List entry = (List) entries.get(pos);
+
+ for (int i = 0; i < entry.size(); i++)
+ {
+ acc.add(entry.get(i));
+
+ if (_evalRecursive(entries, pos + 1, acc, contexts))
+ {
+ return true;
+ }
+
+ acc.remove(acc.size() - 1);
+ }
+
+ return false;
+ }
+
+ private Condition createCondition(final Bundle bundle, final Class clazz,
+ final ConditionInfo info) throws Exception
+ {
+ try
+ {
+ return (Condition) m_action.getMethod(clazz, "getCondition",
+ new Class[] { Bundle.class, ConditionInfo.class }).invoke(null,
+ new Object[] { bundle, info });
+ }
+ catch (Exception ex)
+ {
+ ex.printStackTrace();
+ return (Condition) m_action.getConstructor(clazz,
+ new Class[] { Bundle.class, ConditionInfo.class }).newInstance(
+ new Object[] { bundle, info });
+ }
+ }
+}
diff --git a/framework/security/src/main/java/org/apache/felix/framework/security/util/LocalPermissions.java b/framework/security/src/main/java/org/apache/felix/framework/security/util/LocalPermissions.java
new file mode 100644
index 0000000..917dedd
--- /dev/null
+++ b/framework/security/src/main/java/org/apache/felix/framework/security/util/LocalPermissions.java
@@ -0,0 +1,168 @@
+/*
+ * 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.util;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.security.AllPermission;
+import java.security.Permission;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.apache.felix.moduleloader.IContent;
+import org.apache.felix.moduleloader.IContentLoader;
+import org.osgi.framework.Bundle;
+import org.osgi.service.permissionadmin.PermissionInfo;
+
+/**
+ * A cache for local permissions. Local permissions are read from a given bundle
+ * and cached for later lookup. See core spec 9.2.1.
+ */
+// TODO: maybe use bundle events to clean thing up or weak/soft references
+public final class LocalPermissions
+{
+ private static final PermissionInfo[] ALL_PERMISSION =
+ new PermissionInfo[] { new PermissionInfo(
+ AllPermission.class.getName(), "", "") };
+
+ private final Map m_cache = new HashMap();
+ private final Permissions m_permissions;
+
+ public LocalPermissions(Permissions permissions, PropertiesCache cache)
+ throws IOException
+ {
+ m_permissions = permissions;
+ for (Iterator iter =
+ cache.read(PermissionInfo[].class).entrySet().iterator(); iter
+ .hasNext();)
+ {
+ Entry entry = (Entry) iter.next();
+ PermissionInfo[] value = (PermissionInfo[]) entry.getValue();
+ if ((value.length == 1)
+ && (AllPermission.class.getName().equals(value[0].getType())))
+ {
+ value = ALL_PERMISSION;
+ }
+
+ m_cache.put(entry.getKey(), value);
+ }
+ }
+
+ /**
+ * Return true in case that the given permission is implied by the local
+ * permissions of the given bundle or if there are none otherwise, false.
+ * See core spec 9.2.1.
+ *
+ * @param root the root to use for cacheing as a key
+ * @param loader the loader to get the content of the bundle from
+ * @param bundle the bundle in quesiton
+ * @param permission the permission to check
+ * @return true if implied by local permissions.
+ */
+ public boolean implies(String root, IContentLoader loader, Bundle bundle,
+ Permission permission)
+ {
+ PermissionInfo[] permissions = null;
+
+ synchronized (m_cache)
+ {
+ if (!m_cache.containsKey(root))
+ {
+ InputStream in = null;
+ IContent content = null;
+ try
+ {
+ content = loader.getContent();
+
+ content.open();
+
+ in = content.getEntryAsStream("OSGI-INF/permissions.perm");
+ if (in != null)
+ {
+ ArrayList perms = new ArrayList();
+
+ BufferedReader reader =
+ new BufferedReader(new InputStreamReader(in,
+ "UTF-8"));
+ for (String line = reader.readLine(); line != null; line =
+ reader.readLine())
+ {
+ String trim = line.trim();
+ if (trim.startsWith("#") || trim.startsWith("//"))
+ {
+ continue;
+ }
+ perms.add(new PermissionInfo(line));
+ }
+
+ permissions =
+ (PermissionInfo[]) perms
+ .toArray(new PermissionInfo[perms.size()]);
+ }
+ }
+ catch (Exception ex)
+ {
+ ex.printStackTrace();
+ }
+ finally
+ {
+ if (in != null)
+ {
+ try
+ {
+ in.close();
+ }
+ catch (IOException ex)
+ {
+ // TODO Auto-generated catch block
+ ex.printStackTrace();
+ }
+ }
+ }
+
+ if (permissions == null)
+ {
+ permissions = ALL_PERMISSION;
+ }
+
+ m_cache.put(root, permissions);
+ }
+ else
+ {
+ permissions = (PermissionInfo[]) m_cache.get(root);
+ }
+ }
+
+ return m_permissions.getPermissions(permissions).implies(permission,
+ bundle);
+ }
+
+ public Map getStore()
+ {
+ synchronized (m_cache)
+ {
+ return new HashMap(m_cache);
+ }
+ }
+}
diff --git a/framework/security/src/main/java/org/apache/felix/framework/security/util/Permissions.java b/framework/security/src/main/java/org/apache/felix/framework/security/util/Permissions.java
new file mode 100644
index 0000000..2e93d61
--- /dev/null
+++ b/framework/security/src/main/java/org/apache/felix/framework/security/util/Permissions.java
@@ -0,0 +1,560 @@
+/*
+ * 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.util;
+
+import java.io.File;
+import java.io.FilePermission;
+import java.lang.ref.ReferenceQueue;
+import java.lang.ref.SoftReference;
+import java.lang.ref.WeakReference;
+import java.security.AllPermission;
+import java.security.Permission;
+import java.security.PermissionCollection;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+
+import org.apache.felix.framework.util.SecureAction;
+import org.osgi.framework.AdminPermission;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.packageadmin.ExportedPackage;
+import org.osgi.service.packageadmin.PackageAdmin;
+import org.osgi.service.permissionadmin.PermissionInfo;
+
+/**
+ * A permission cache that uses permisssion infos as keys. Permission are created
+ * from the parent classloader or any exported package.
+ */
+// TODO: maybe use bundle events instead of soft/weak references
+public final class Permissions
+{
+ private static final ClassLoader m_classLoader =
+ Permissions.class.getClassLoader();
+
+ private static final Map m_permissionCache = new HashMap();
+ private static final Map m_permissions = new HashMap();
+ private static final ReferenceQueue m_permissionsQueue =
+ new ReferenceQueue();
+
+ private static final ThreadLocal m_stack = new ThreadLocal();
+
+ private final Map m_cache;
+ private final ReferenceQueue m_queue;
+ private final BundleContext m_context;
+ private final PermissionInfo[] m_permissionInfos;
+ private final boolean m_allPermission;
+ private final SecureAction m_action;
+
+ public static final AllPermission ALL_PERMISSION = new AllPermission();
+
+ private static final PermissionInfo[] IMPLICIT =
+ new PermissionInfo[] { new PermissionInfo(FilePermission.class
+ .getName(), "-", "read,write,delete") };
+
+ Permissions(PermissionInfo[] permissionInfos, BundleContext context,
+ SecureAction action)
+ {
+ m_context = context;
+ m_permissionInfos = permissionInfos;
+ m_cache = new HashMap();
+ m_queue = new ReferenceQueue();
+ m_action = action;
+ for (int i = 0; i < m_permissionInfos.length; i++)
+ {
+ if (m_permissionInfos[i].getType().equals(
+ AllPermission.class.getName()))
+ {
+ m_allPermission = true;
+ return;
+ }
+ }
+ m_allPermission = false;
+ }
+
+ public Permissions(BundleContext context, SecureAction action)
+ {
+ m_context = context;
+ m_permissionInfos = null;
+ m_cache = null;
+ m_queue = null;
+ m_allPermission = true;
+ m_action = action;
+ }
+
+ public PermissionInfo[] getImplicit(Bundle bundle)
+ {
+ return new PermissionInfo[] {
+ IMPLICIT[0],
+ new PermissionInfo(AdminPermission.class.getName(), "(id="
+ + bundle.getBundleId() + ")", AdminPermission.METADATA) };
+ }
+
+ public Permissions getPermissions(PermissionInfo[] permissionInfos)
+ {
+ cleanUp(m_permissionsQueue, m_permissions);
+
+ Permissions result = null;
+ synchronized (m_permissions)
+ {
+ result = (Permissions) m_permissions.get(permissionInfos);
+ }
+ if (result == null)
+ {
+ result = new Permissions(permissionInfos, m_context, m_action);
+ synchronized (m_permissions)
+ {
+ m_permissions.put(
+ new Entry(permissionInfos, m_permissionsQueue), result);
+ }
+ }
+ return result;
+ }
+
+ private static final class Entry extends WeakReference
+ {
+ private final int m_hashCode;
+
+ Entry(Object entry, ReferenceQueue queue)
+ {
+ super(entry, queue);
+ m_hashCode = entry.hashCode();
+ }
+
+ Entry(Object entry)
+ {
+ super(entry);
+ m_hashCode = entry.hashCode();
+ }
+
+ public Object get()
+ {
+ return super.get();
+ }
+
+ public int hashCode()
+ {
+ return m_hashCode;
+ }
+
+ public boolean equals(Object o)
+ {
+ if (o == null)
+ {
+ return false;
+ }
+
+ if (o == this)
+ {
+ return true;
+ }
+
+ Object entry = super.get();
+
+ if (entry == null)
+ {
+ return false;
+ }
+
+ return entry.equals(o);
+ }
+ }
+
+ private static final class DefaultPermissionCollection extends
+ PermissionCollection
+ {
+ private final Map m_perms = new HashMap();
+
+ public void add(Permission perm)
+ {
+ synchronized (m_perms)
+ {
+ m_perms.put(perm, perm);
+ }
+ }
+
+ public Enumeration elements()
+ {
+ throw new IllegalStateException("Not implemented");
+ }
+
+ public boolean implies(Permission perm)
+ {
+ Map perms = null;
+
+ synchronized (m_perms)
+ {
+ perms = m_perms;
+ }
+
+ Permission permission = (Permission) perms.get(perm);
+
+ if ((permission != null) && permission.implies(perm))
+ {
+ return true;
+ }
+
+ for (Iterator iter = perms.values().iterator(); iter.hasNext();)
+ {
+ Permission current = (Permission) iter.next();
+ if ((current != null) && (current != permission)
+ && current.implies(perm))
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
+ private void cleanUp(ReferenceQueue queue, Map cache)
+ {
+ for (Entry entry = (Entry) queue.poll(); entry != null; entry =
+ (Entry) queue.poll())
+ {
+ synchronized (cache)
+ {
+ cache.remove(entry);
+ }
+ }
+ }
+
+ /**
+ * @param target the permission to be implied
+ * @param bundle if not null then allow implicit permissions like file
+ * access to local data area
+ * @return true if the permission is implied by this permissions object.
+ */
+ public boolean implies(Permission target, Bundle bundle)
+ {
+ if (m_allPermission)
+ {
+ return true;
+ }
+
+ Class targetClass = target.getClass();
+
+ cleanUp(m_queue, m_cache);
+
+ if ((bundle != null) && targetClass == FilePermission.class)
+ {
+ for (int i = 0; i < m_permissionInfos.length; i++)
+ {
+ if (m_permissionInfos[i].getType().equals(
+ FilePermission.class.getName()))
+ {
+ String postfix = "";
+ String name = m_permissionInfos[i].getName();
+ if (name.endsWith("*") || name.endsWith("-"))
+ {
+ postfix = name.substring(name.length() - 1);
+ name = name.substring(0, name.length() - 1);
+ }
+ if (!(new File(name)).isAbsolute())
+ {
+ BundleContext context = bundle.getBundleContext();
+ if (context == null)
+ {
+ break;
+ }
+ name =
+ (new File(context.getDataFile(""), name))
+ .getAbsolutePath();
+ }
+ if (postfix.length() > 0)
+ {
+ if ((name.length() > 0) && !name.endsWith("/"))
+ {
+ name += "/" + postfix;
+ }
+ else
+ {
+ name += postfix;
+ }
+ }
+ return createPermission(
+ new PermissionInfo(FilePermission.class.getName(),
+ name, m_permissionInfos[i].getActions()),
+ targetClass).implies(target);
+ }
+ }
+ return false;
+ }
+
+ Object current = m_stack.get();
+
+ if (current == null)
+ {
+ m_stack.set(targetClass);
+ }
+ else
+ {
+ if (current instanceof HashSet)
+ {
+ if (((HashSet) current).contains(targetClass))
+ {
+ return false;
+ }
+ ((HashSet) current).add(targetClass);
+ }
+ else
+ {
+ if (current == targetClass)
+ {
+ return false;
+ }
+ HashSet frame = new HashSet();
+ frame.add(current);
+ frame.add(targetClass);
+ m_stack.set(frame);
+ current = frame;
+ }
+ }
+
+ try
+ {
+ SoftReference collectionEntry = null;
+
+ PermissionCollection collection = null;
+
+ synchronized (m_cache)
+ {
+ collectionEntry = (SoftReference) m_cache.get(targetClass);
+ }
+
+ if (collectionEntry != null)
+ {
+ collection = (PermissionCollection) collectionEntry.get();
+ }
+
+ if (collection == null)
+ {
+ collection = target.newPermissionCollection();
+
+ if (collection == null)
+ {
+ collection = new DefaultPermissionCollection();
+ }
+
+ for (int i = 0; i < m_permissionInfos.length; i++)
+ {
+ PermissionInfo permissionInfo = m_permissionInfos[i];
+ String infoType = permissionInfo.getType();
+ String permissionType = targetClass.getName();
+
+ if (infoType.equals(permissionType))
+ {
+ Permission permission =
+ createPermission(permissionInfo, targetClass);
+
+ if (permission != null)
+ {
+ collection.add(permission);
+ }
+ }
+ }
+
+ synchronized (m_cache)
+ {
+ m_cache.put(new Entry(target.getClass(), m_queue),
+ new SoftReference(collection));
+ }
+ }
+
+ return collection.implies(target);
+ }
+ finally
+ {
+ if (current == null)
+ {
+ m_stack.set(null);
+ }
+ else
+ {
+ ((HashSet) current).remove(targetClass);
+ if (((HashSet) current).isEmpty())
+ {
+ m_stack.set(null);
+ }
+ }
+ }
+ }
+
+ private Permission addToCache(String encoded, Permission permission)
+ {
+ if (permission == null)
+ {
+ return null;
+ }
+
+ synchronized (m_permissionCache)
+ {
+ Map inner = null;
+
+ SoftReference ref = (SoftReference) m_permissionCache.get(encoded);
+ if (ref != null)
+ {
+ inner = (Map) ref.get();
+ }
+ if (inner == null)
+ {
+ inner = new HashMap();
+ m_permissionCache.put(encoded,
+ new SoftReference(inner, m_queue));
+ }
+
+ inner.put(new Entry(permission.getClass()), new Entry(permission));
+ }
+
+ return permission;
+ }
+
+ private Permission getFromCache(String encoded, Class target)
+ {
+ synchronized (m_permissionCache)
+ {
+ SoftReference ref = (SoftReference) m_permissionCache.get(encoded);
+ if (ref != null)
+ {
+ Map inner = (Map) ref.get();
+ if (inner != null)
+ {
+ Entry entry = (Entry) inner.get(target);
+ if (entry != null)
+ {
+ Permission result = (Permission) entry.get();
+ if (result != null)
+ {
+ return result;
+ }
+ inner.remove(entry);
+ }
+ if (inner.isEmpty())
+ {
+ m_permissionCache.remove(encoded);
+ }
+ }
+ else
+ {
+ m_permissionCache.remove(encoded);
+ }
+ }
+
+ }
+
+ return null;
+ }
+
+ private Permission createPermission(PermissionInfo permissionInfo,
+ Class target)
+ {
+ Permission cached = getFromCache(permissionInfo.getEncoded(), target);
+
+ if (cached != null)
+ {
+ return cached;
+ }
+
+ try
+ {
+ if (m_classLoader.loadClass(target.getName()) == target)
+ {
+ return addToCache(permissionInfo.getEncoded(),
+ createPermission(permissionInfo.getName(), permissionInfo
+ .getActions(), target));
+ }
+ }
+ catch (ClassNotFoundException e1)
+ {
+ }
+
+ ServiceReference[] refs = null;
+ try
+ {
+ refs =
+ m_context.getServiceReferences(PackageAdmin.class.getName(),
+ null);
+ }
+ catch (InvalidSyntaxException e)
+ {
+ }
+ if (refs != null)
+ {
+ for (int i = 0; i < refs.length; i++)
+ {
+ PackageAdmin admin =
+ (PackageAdmin) m_context.getService(refs[i]);
+
+ if (admin != null)
+ {
+ Permission result = null;
+ Bundle bundle = admin.getBundle(target);
+ if (bundle != null)
+ {
+ ExportedPackage[] exports =
+ admin.getExportedPackages(bundle);
+ if (exports != null)
+ {
+ String name = target.getName();
+ name = name.substring(0, name.lastIndexOf('.'));
+
+ for (int j = 0; j < exports.length; j++)
+ {
+ if (exports[j].getName().equals(name))
+ {
+ result =
+ createPermission(permissionInfo
+ .getName(), permissionInfo
+ .getActions(), target);
+ break;
+ }
+ }
+ }
+ }
+
+ m_context.ungetService(refs[i]);
+
+ return addToCache(permissionInfo.getEncoded(), result);
+ }
+ }
+ }
+
+ return null;
+ }
+
+ private Permission createPermission(String name, String action, Class target)
+ {
+ try
+ {
+ return (Permission) m_action.getConstructor(target,
+ new Class[] { String.class, String.class }).newInstance(
+ new Object[] { name, action });
+ }
+ catch (Exception ex)
+ {
+ ex.printStackTrace();
+ }
+
+ return null;
+ }
+}
diff --git a/framework/security/src/main/java/org/apache/felix/framework/security/util/PropertiesCache.java b/framework/security/src/main/java/org/apache/felix/framework/security/util/PropertiesCache.java
new file mode 100644
index 0000000..c00bb8c
--- /dev/null
+++ b/framework/security/src/main/java/org/apache/felix/framework/security/util/PropertiesCache.java
@@ -0,0 +1,236 @@
+/*
+ * 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.util;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.lang.reflect.Array;
+import java.lang.reflect.Constructor;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Map.Entry;
+
+import org.apache.felix.framework.util.SecureAction;
+
+public final class PropertiesCache
+{
+ private final File m_file;
+
+ private final File m_tmp;
+
+ private final SecureAction m_action;
+
+ public PropertiesCache(File store, File tmp, SecureAction action)
+ {
+ m_action = action;
+ m_file = store;
+ m_tmp = tmp;
+ }
+
+ public void write(Map data) throws IOException
+ {
+ OutputStream out = null;
+ File tmp = null;
+ File tmp2 = null;
+ try
+ {
+ tmp = m_action.createTempFile("tmp", null, m_tmp);
+ tmp2 = m_action.createTempFile("tmp", null, m_tmp);
+ m_action.deleteFile(tmp2);
+ Exception org = null;
+ try
+ {
+ out = m_action.getFileOutputStream(tmp);
+
+ Properties store = new Properties();
+
+ for (Iterator iter = data.entrySet().iterator(); iter.hasNext();)
+ {
+ Entry entry = (Entry) iter.next();
+ store.setProperty((String) entry.getKey(), getEncoded(entry
+ .getValue()));
+ }
+
+ store.store(out, null);
+ }
+ catch (IOException ex)
+ {
+ org = ex;
+ throw ex;
+ }
+ finally
+ {
+ if (out != null)
+ {
+ try
+ {
+ out.close();
+ }
+ catch (IOException ex)
+ {
+ if (org == null)
+ {
+ throw ex;
+ }
+ }
+ }
+ }
+ if ((m_action.fileExists(m_file) && !m_action.renameFile(m_file,
+ tmp2))
+ || !m_action.renameFile(tmp, m_file))
+ {
+ throw new IOException("Unable to write permissions");
+ }
+ }
+ catch (IOException ex)
+ {
+ if (!m_action.fileExists(m_file) && (tmp2 != null)
+ && m_action.fileExists(tmp2))
+ {
+ m_action.renameFile(tmp2, m_file);
+ }
+ throw ex;
+ }
+ finally
+ {
+ if (tmp != null)
+ {
+ m_action.deleteFile(tmp);
+ }
+ if (tmp2 != null)
+ {
+ m_action.deleteFile(tmp2);
+ }
+ }
+ }
+
+ public Map read(Class target) throws IOException
+ {
+ if (!m_file.isFile())
+ {
+ return null;
+ }
+ InputStream in = null;
+ Exception other = null;
+ Map result = new HashMap();
+ try
+ {
+ in = m_action.getFileInputStream(m_file);
+
+ Properties store = new Properties();
+ store.load(in);
+
+ for (Iterator iter = store.entrySet().iterator(); iter.hasNext();)
+ {
+ Entry entry = (Entry) iter.next();
+ result.put(entry.getKey(), getUnencoded((String) entry
+ .getValue(), target));
+ }
+ }
+ catch (IOException ex)
+ {
+ other = ex;
+ throw ex;
+ }
+ finally
+ {
+ if (in != null)
+ {
+ try
+ {
+ in.close();
+ }
+ catch (IOException ex)
+ {
+ if (other == null)
+ {
+ throw ex;
+ }
+ }
+ }
+ }
+ return result;
+ }
+
+ private String getEncoded(Object target) throws IOException
+ {
+ Properties props = new Properties();
+ if (target.getClass().isArray())
+ {
+
+ Object[] array = (Object[]) target;
+ for (int i = 0; i < array.length; i++)
+ {
+ props.setProperty(Integer.toString(i), array[i].toString());
+ }
+
+ ByteArrayOutputStream tmp = new ByteArrayOutputStream();
+ props.store(tmp, null);
+ return new String(tmp.toByteArray());
+ }
+
+ return target.toString();
+ }
+
+ private Object getUnencoded(String encoded, Class target)
+ throws IOException
+ {
+ try
+ {
+ if (target.isArray())
+ {
+ Properties props = new Properties();
+ props.load(new ByteArrayInputStream(encoded.getBytes()));
+ Class componentType = target.getComponentType();
+ Constructor constructor =
+ m_action.getConstructor(componentType,
+ new Class[] { String.class });
+ Object[] params = new Object[1];
+ Object[] result =
+ (Object[]) Array.newInstance(componentType, props.size());
+
+ for (Iterator iter = props.entrySet().iterator(); iter
+ .hasNext();)
+ {
+ Entry entry = (Entry) iter.next();
+ params[0] = entry.getValue();
+ result[Integer.parseInt((String) entry.getKey())] =
+ constructor.newInstance(params);
+ }
+
+ return result;
+ }
+
+ return m_action.invoke(m_action.getConstructor(target,
+ new Class[] { String.class }), new Object[] { encoded });
+ }
+ catch (Exception ex)
+ {
+ ex.printStackTrace();
+
+ throw new IOException(ex.getMessage());
+ }
+ }
+}
diff --git a/framework/security/src/main/java/org/apache/felix/framework/security/util/TrustManager.java b/framework/security/src/main/java/org/apache/felix/framework/security/util/TrustManager.java
new file mode 100644
index 0000000..7425de5
--- /dev/null
+++ b/framework/security/src/main/java/org/apache/felix/framework/security/util/TrustManager.java
@@ -0,0 +1,195 @@
+/*
+ * 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.util;
+
+import java.io.InputStream;
+import java.security.KeyStore;
+import java.security.cert.CertificateFactory;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.StringTokenizer;
+
+import org.apache.felix.framework.util.SecureAction;
+
+/*
+ * TODO: the certificate stores as well as the CRLs might change over time
+ * (added/removed certificates). We need a way to detect that and act on it.
+ * The problem is to find a good balance between re-checking and caching...
+ */
+public final class TrustManager
+{
+ private final SecureAction m_action;
+ private final String m_crlList;
+ private final String m_typeList;
+ private final String m_passwdList;
+ private final String m_storeList;
+ private Collection m_caCerts = null;
+ private Collection m_crls = null;
+
+ public TrustManager(String crlList, String typeList, String passwdList,
+ String storeList, SecureAction action)
+ {
+ m_crlList = crlList;
+ m_typeList = typeList;
+ m_passwdList = passwdList;
+ m_storeList = storeList;
+ m_action = action;
+ }
+
+ private synchronized void init()
+ {
+ if (m_caCerts == null)
+ {
+ try
+ {
+ initCRLs();
+ initCaCerts();
+ }
+ catch (Exception ex)
+ {
+ m_caCerts = new ArrayList();
+ m_crls = new ArrayList();
+ // TODO: log this
+ ex.printStackTrace();
+ }
+ }
+ }
+
+ private void initCRLs() throws Exception
+ {
+ final Collection result = new ArrayList();
+
+ if (m_crlList.trim().length() != 0)
+ {
+ CertificateFactory fac =
+ CertificateFactory.getInstance("X509");
+
+ for (StringTokenizer tok = new StringTokenizer(m_crlList, "|"); tok
+ .hasMoreElements();)
+ {
+ InputStream input = null;
+ try
+ {
+ input =
+ m_action.getURLConnectionInputStream(m_action
+ .createURL(null, tok.nextToken(), null)
+ .openConnection());
+ result.addAll(fac.generateCRLs(input));
+ }
+ catch (Exception ex)
+ {
+ // TODO: log this or something
+ ex.printStackTrace();
+ }
+ finally
+ {
+ if (input != null)
+ {
+ try
+ {
+ input.close();
+ }
+ catch (Exception ex)
+ {
+ // TODO: log this or something
+ ex.printStackTrace();
+ }
+ }
+ }
+ }
+ }
+
+ m_crls = result;
+ }
+
+ private void initCaCerts() throws Exception
+ {
+ final Collection result = new ArrayList();
+
+ if (m_storeList.trim().length() != 0)
+ {
+
+ StringTokenizer storeTok = new StringTokenizer(m_storeList, "|");
+ StringTokenizer passwdTok = new StringTokenizer(m_passwdList, "|");
+ StringTokenizer typeTok = new StringTokenizer(m_typeList, "|");
+
+ while (storeTok.hasMoreTokens())
+ {
+ KeyStore ks = KeyStore.getInstance(typeTok.nextToken().trim());
+
+ InputStream input = null;
+ try
+ {
+ input =
+ m_action.getURLConnectionInputStream(m_action
+ .createURL(null, storeTok.nextToken().trim(), null)
+ .openConnection());
+
+ ks.load(input, passwdTok.nextToken().trim().toCharArray());
+
+ for (Enumeration e = ks.aliases(); e.hasMoreElements();)
+ {
+ String alias = (String) e.nextElement();
+ if (ks.isCertificateEntry(alias))
+ {
+ result.add(ks.getCertificate(alias));
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ // TODO: log this or something
+ ex.printStackTrace();
+ }
+ finally
+ {
+ if (input != null)
+ {
+ try
+ {
+ input.close();
+ }
+ catch (Exception ex)
+ {
+ // TODO: log this or something
+ ex.printStackTrace();
+ }
+ }
+ }
+ }
+ }
+
+ m_caCerts = result;
+ }
+
+ public Collection getCRLs()
+ {
+ init();
+
+ return m_crls;
+ }
+
+ public Collection getCaCerts()
+ {
+ init();
+
+ return m_caCerts;
+ }
+}
\ No newline at end of file
diff --git a/framework/security/src/main/java/org/apache/felix/framework/security/verifier/BundleDNParser.java b/framework/security/src/main/java/org/apache/felix/framework/security/verifier/BundleDNParser.java
new file mode 100644
index 0000000..c50dcfc
--- /dev/null
+++ b/framework/security/src/main/java/org/apache/felix/framework/security/verifier/BundleDNParser.java
@@ -0,0 +1,521 @@
+/*
+ * 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.verifier;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.Method;
+import java.security.cert.CRL;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.jar.JarEntry;
+import java.util.jar.JarInputStream;
+
+import org.apache.felix.framework.cache.BundleRevision;
+import org.apache.felix.framework.security.util.BundleInputStream;
+import org.apache.felix.framework.security.util.TrustManager;
+import org.apache.felix.moduleloader.IContent;
+import org.apache.felix.moduleloader.IContentLoader;
+
+public final class BundleDNParser
+{
+ private static final Method m_getCodeSigners;
+ private static final Method m_getSignerCertPath;
+ private static final Method m_getCertificates;
+
+ static
+ {
+ Method getCodeSigners = null;
+ Method getSignerCertPath = null;
+ Method getCertificates = null;
+ try
+ {
+ getCodeSigners =
+ Class.forName("java.util.jar.JarEntry").getMethod(
+ "getCodeSigners", null);
+ getSignerCertPath =
+ Class.forName("java.security.CodeSigner").getMethod(
+ "getSignerCertPath", null);
+ getCertificates =
+ Class.forName("java.security.cert.CertPath").getMethod(
+ "getCertificates", null);
+ }
+ catch (Exception ex)
+ {
+ ex.printStackTrace();
+ getCodeSigners = null;
+ getSignerCertPath = null;
+ getCertificates = null;
+ }
+ m_getCodeSigners = getCodeSigners;
+ m_getSignerCertPath = getSignerCertPath;
+ m_getCertificates = getCertificates;
+ }
+
+ private final Map m_cache = new HashMap();
+ private final TrustManager m_manager;
+
+ public BundleDNParser(TrustManager manager)
+ {
+ m_manager = manager;
+ }
+
+ public Map getCache()
+ {
+ synchronized (m_cache)
+ {
+ return new HashMap(m_cache);
+ }
+ }
+
+ public void put(String root, String[] dnChains)
+ {
+ synchronized (m_cache)
+ {
+ m_cache.put(root, dnChains);
+ }
+ }
+
+ public void checkDNChains(String root, IContentLoader contentLoader) throws Exception
+ {
+ synchronized (m_cache)
+ {
+ if (m_cache.containsKey(root))
+ {
+ String[] result = (String[]) m_cache.get(root);
+ if ((result != null) && (result.length == 0))
+ {
+ throw new IOException("Bundle not properly signed");
+ }
+ return;
+ }
+ }
+
+ String[] result = new String[0];
+ Exception org = null;
+ try
+ {
+ result = _getDNChains(root, contentLoader.getContent());
+ }
+ catch (Exception ex)
+ {
+ org = ex;
+ }
+
+ synchronized (m_cache)
+ {
+ m_cache.put(root, result);
+ }
+
+ if (org != null)
+ {
+ throw org;
+ }
+ }
+
+ public String[] getDNChains(String root, BundleRevision bundleRevision)
+ {
+ synchronized (m_cache)
+ {
+ if (m_cache.containsKey(root))
+ {
+ String[] result = (String[]) m_cache.get(root);
+ if ((result != null) && (result.length == 0))
+ {
+ return null;
+ }
+ return result;
+ }
+ }
+
+ String[] result = new String[0];
+
+ IContent content = null;
+ try
+ {
+ content = bundleRevision.getContent();
+ content.open();
+ result = _getDNChains(root, content);
+ }
+ catch (Exception ex)
+ {
+ // Ignore
+ }
+ if (content != null)
+ {
+ try
+ {
+ content.close();
+ }
+ catch (Exception ex)
+ {
+ // Ignore
+ }
+ }
+
+ synchronized (m_cache)
+ {
+ m_cache.put(root, result);
+ }
+
+ return result;
+ }
+
+ private String[] _getDNChains(String root, IContent content)
+ throws IOException
+ {
+ X509Certificate[] certificates = null;
+
+ certificates = getCertificates(new BundleInputStream(content));
+
+ if (certificates == null)
+ {
+ return null;
+ }
+
+ List rootChains = new ArrayList();
+
+ getRootChains(certificates, rootChains);
+
+ List result = new ArrayList();
+
+ SubjectDNParser parser = new SubjectDNParser();
+
+ for (Iterator rootIter = rootChains.iterator(); rootIter.hasNext();)
+ {
+ StringBuffer buffer = new StringBuffer();
+
+ List chain = (List) rootIter.next();
+
+ Iterator iter = chain.iterator();
+
+ X509Certificate current = (X509Certificate) iter.next();
+
+ try
+ {
+ buffer.append(parser
+ .parseSubjectDN(current.getTBSCertificate()));
+
+ while (iter.hasNext())
+ {
+ buffer.append(';');
+
+ current = (X509Certificate) iter.next();
+
+ buffer.append(parser.parseSubjectDN(current
+ .getTBSCertificate()));
+ }
+
+ result.add(buffer.toString());
+
+ }
+ catch (Exception ex)
+ {
+ // something went wrong during parsing -
+ // it might be that the cert contained an unsupported OID
+ // TODO: log this or something
+ ex.printStackTrace();
+ }
+ }
+
+ if (!result.isEmpty())
+ {
+ return (String[]) result.toArray(new String[result.size()]);
+ }
+
+ throw new IOException();
+ }
+
+ private X509Certificate[] getCertificates(InputStream input)
+ throws IOException
+ {
+ JarInputStream bundle = new JarInputStream(input, true);
+
+ if (bundle.getManifest() == null)
+ {
+ return null;
+ }
+
+ List certificateChains = new ArrayList();
+
+ int count = certificateChains.size();
+
+ // This is tricky: jdk1.3 doesn't say anything about what is happening
+ // if a bad sig is detected on an entry - later jdk's do say that they
+ // will throw a security Exception. The below should cater for both
+ // behaviors.
+ for (JarEntry entry = bundle.getNextJarEntry(); entry != null; entry =
+ bundle.getNextJarEntry())
+ {
+
+ if (entry.isDirectory() || entry.getName().startsWith("META-INF"))
+ {
+ continue;
+ }
+
+ for (byte[] tmp = new byte[4096]; bundle.read(tmp, 0, tmp.length) != -1;)
+ {
+ }
+
+ Certificate[] certificates = entry.getCertificates();
+
+ // Workaround stupid bug in the sun jdk 1.5.x - getCertificates()
+ // returns null there even if there are valid certificates.
+ // This is a regression bug that has been fixed in 1.6.
+ //
+ // We use reflection to see whether we have a SignerCertPath
+ // for the entry (available >= 1.5) and if so check whether
+ // there are valid certificates - don't try this at home.
+ if ((certificates == null) && (m_getCodeSigners != null))
+ {
+ try
+ {
+ Object[] signers =
+ (Object[]) m_getCodeSigners.invoke(entry, null);
+
+ if (signers != null)
+ {
+ List certChains = new ArrayList();
+
+ for (int i = 0; i < signers.length; i++)
+ {
+ Object path =
+ m_getSignerCertPath.invoke(signers[i], null);
+
+ certChains.addAll((List) m_getCertificates.invoke(
+ path, null));
+ }
+
+ certificates =
+ (Certificate[]) certChains
+ .toArray(new Certificate[certChains.size()]);
+ }
+ }
+ catch (Exception ex)
+ {
+ ex.printStackTrace();
+ // Not much we can do - probably we are not on >= 1.5
+ }
+ }
+
+ if ((certificates == null) || (certificates.length == 0))
+ {
+ return null;
+ }
+
+ List chains = new ArrayList();
+
+ getRootChains(certificates, chains);
+
+ if (certificateChains.isEmpty())
+ {
+ certificateChains.addAll(chains);
+ count = certificateChains.size();
+ }
+ else
+ {
+ for (Iterator iter2 = certificateChains.iterator(); iter2
+ .hasNext();)
+ {
+ X509Certificate cert =
+ (X509Certificate) ((List) iter2.next()).get(0);
+ boolean found = false;
+ for (Iterator iter3 = chains.iterator(); iter3.hasNext();)
+ {
+ X509Certificate cert2 =
+ (X509Certificate) ((List) iter3.next()).get(0);
+
+ if (cert.getSubjectDN().equals(cert2.getSubjectDN())
+ && cert.equals(cert2))
+ {
+ found = true;
+ break;
+ }
+ }
+ if (!found)
+ {
+ iter2.remove();
+ }
+ }
+ }
+
+ if (certificateChains.isEmpty())
+ {
+ if (count > 0)
+ {
+ throw new IOException("Bad signers");
+ }
+ return null;
+ }
+ }
+
+ List result = new ArrayList();
+
+ for (Iterator iter = certificateChains.iterator(); iter.hasNext();)
+ {
+ result.addAll((List) iter.next());
+ }
+
+ return (X509Certificate[]) result.toArray(new X509Certificate[result
+ .size()]);
+ }
+
+ private boolean isRevoked(Certificate certificate)
+ {
+ for (Iterator iter = m_manager.getCRLs().iterator(); iter.hasNext();)
+ {
+ if (((CRL) iter.next()).isRevoked(certificate))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private void getRootChains(Certificate[] certificates, List chains)
+ {
+ List chain = new ArrayList();
+
+ boolean revoked = false;
+
+ for (int i = 0; i < certificates.length - 1; i++)
+ {
+ X509Certificate certificate = (X509Certificate) certificates[i];
+
+ if (!revoked && isRevoked(certificate))
+ {
+ revoked = true;
+ }
+ else if (!revoked)
+ {
+ try
+ {
+ certificate.checkValidity();
+
+ chain.add(certificate);
+ }
+ catch (CertificateException ex)
+ {
+ // TODO: log this or something
+ revoked = true;
+ }
+ }
+
+ if (!((X509Certificate) certificates[i + 1]).getSubjectDN().equals(
+ certificate.getIssuerDN()))
+ {
+ if (!revoked && trusted(certificate))
+ {
+ chains.add(chain);
+ }
+
+ revoked = false;
+
+ if (!chain.isEmpty())
+ {
+ chain = new ArrayList();
+ }
+ }
+ }
+ // The final entry in the certs array is always
+ // a "root" certificate
+ if (!revoked)
+ {
+ chain.add(certificates[certificates.length - 1]);
+ if (trusted((X509Certificate) certificates[certificates.length - 1]))
+ {
+ chains.add(chain);
+ }
+ }
+ }
+
+ private boolean trusted(X509Certificate cert)
+ {
+ if (m_manager.getCaCerts().isEmpty() || isRevoked(cert))
+ {
+ return false;
+ }
+
+ for (Iterator iter = m_manager.getCaCerts().iterator(); iter.hasNext();)
+ {
+ X509Certificate trustedCaCert = (X509Certificate) iter.next();
+
+ if (isRevoked(trustedCaCert))
+ {
+ continue;
+ }
+
+ // If the cert has the same SubjectDN
+ // as a trusted CA, check whether
+ // the two certs are the same.
+ if (cert.getSubjectDN().equals(trustedCaCert.getSubjectDN()))
+ {
+ if (cert.equals(trustedCaCert))
+ {
+ try
+ {
+ cert.checkValidity();
+ trustedCaCert.checkValidity();
+ return true;
+ }
+ catch (CertificateException ex)
+ {
+ // Not much we can do
+ // TODO: log this or something
+ }
+ }
+ }
+ }
+
+ // cert issued by any of m_trustedCaCerts ? return true : return false
+ for (Iterator iter = m_manager.getCaCerts().iterator(); iter.hasNext();)
+ {
+ X509Certificate trustedCaCert = (X509Certificate) iter.next();
+
+ if (isRevoked(trustedCaCert))
+ {
+ continue;
+ }
+
+ if (cert.getIssuerDN().equals(trustedCaCert.getSubjectDN()))
+ {
+ try
+ {
+ cert.verify(trustedCaCert.getPublicKey());
+ cert.checkValidity();
+ trustedCaCert.checkValidity();
+ return true;
+ }
+ catch (Exception ex)
+ {
+ // TODO: log this or something
+ }
+ }
+ }
+
+ return false;
+ }
+}
diff --git a/framework/security/src/main/java/org/apache/felix/framework/security/verifier/SignerMatcher.java b/framework/security/src/main/java/org/apache/felix/framework/security/verifier/SignerMatcher.java
new file mode 100644
index 0000000..19924e1
--- /dev/null
+++ b/framework/security/src/main/java/org/apache/felix/framework/security/verifier/SignerMatcher.java
@@ -0,0 +1,468 @@
+/*
+ * 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.verifier;
+
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+
+import org.apache.felix.framework.cache.BundleArchive;
+
+public final class SignerMatcher
+{
+ private final String m_filter;
+ private final String m_root;
+ private final BundleArchive m_archive;
+ private final BundleDNParser m_parser;
+
+ public SignerMatcher(String filter)
+ {
+ m_filter = filter;
+ m_root = null;
+ m_archive = null;
+ m_parser = null;
+ }
+
+ public SignerMatcher(String root, BundleArchive archive, BundleDNParser parser)
+ {
+ m_filter = null;
+ m_root = root;
+ m_archive = archive;
+ m_parser = parser;
+ }
+
+ public boolean equals(Object o)
+ {
+ if (!(o instanceof SignerMatcher))
+ {
+ return false;
+ }
+
+ String pattern = ((SignerMatcher) o).m_filter;
+
+ if (pattern == null)
+ {
+ return true;
+ }
+
+ if (m_archive == null)
+ {
+ return pattern.trim().equals("\\*");
+ }
+
+ String[] dns;
+ try
+ {
+ dns = m_parser.getDNChains(m_root + "-" + m_archive.getLastModified(),
+ m_archive.getRevision(m_archive.getRevisionCount() -1));
+ }
+ catch (Exception ex)
+ {
+ // TODO: log this or something
+ ex.printStackTrace();
+ return false;
+ }
+
+ if (dns == null)
+ {
+ return pattern.trim().equals("\\*");
+ }
+
+ for (int i = 0;i < dns.length;i++)
+ {
+ if (match(pattern, dns[i]))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public int hashCode()
+ {
+ return 42;
+ }
+
+ // see core spec 2.3
+ public static boolean match(String pattern, String dn)
+ {
+ try
+ {
+ return ((pattern != null) && (dn != null)) ?
+ matchDN(pattern.toCharArray(), 0, dn.toCharArray(), 0) : false;
+ }
+ catch (Exception ex)
+ {
+ // TODO: log this or something
+ ex.printStackTrace();
+ }
+
+ return false;
+ }
+
+ private static boolean matchDN(char[] pattern, int pPos, char[] dn, int dPos)
+ {
+ pPos = skip(pattern, pPos, ' ');
+
+ if (pPos >= pattern.length)
+ {
+ return true;
+ }
+
+ int befor = pPos;
+
+ if ((pPos < pattern.length -1) && (pattern[pPos] == '\\') && (pattern[pPos + 1] == '*'))
+ {
+ pPos = pPos + 1;
+ }
+
+ switch (pattern[pPos++])
+ {
+ case '*':
+ pPos = skip(pattern, pPos, ' ');
+ if ((pPos < pattern.length) && (pattern[pPos] == ';'))
+ {
+ if (matchDN(pattern, ++pPos, dn, dPos))
+ {
+ return true;
+ }
+ return matchDN(pattern, pPos, dn, skipEscapedUntil(dn, dPos, ';') + 1);
+ }
+ if (pPos >= pattern.length)
+ {
+ return true;
+ }
+ return matchRDN(pattern, befor, dn, dPos);
+ case '-':
+ pPos = skip(pattern, pPos, ' ');
+ if ((pPos < pattern.length) && (pattern[pPos] == ';'))
+ {
+ int next = dPos;
+ pPos++;
+ do
+ {
+ if (matchDN(pattern, pPos, dn, next))
+ {
+ return true;
+ }
+ next = skipEscapedUntil(dn, next, ';') + 1;
+ } while (next < dn.length);
+
+ return false;
+ }
+ if (pPos >= pattern.length)
+ {
+ return true;
+ }
+ throw new IllegalArgumentException("[" + pPos + "]" + new String(pattern));
+ default:
+ break;
+ }
+
+ return matchRDN(pattern, befor, dn, dPos);
+ }
+
+ private static boolean matchRDN(char[] pattern, int pPos, char[] dn, int dPos)
+ {
+ pPos = skip(pattern, pPos, ' ');
+
+ if (pPos >= pattern.length)
+ {
+ return true;
+ }
+
+ if ((pPos < pattern.length -1) && (pattern[pPos] == '\\') && (pattern[pPos + 1] == '*'))
+ {
+ pPos = pPos + 1;
+ }
+
+ switch (pattern[pPos++])
+ {
+ case '*':
+ pPos = skip(pattern, pPos, ' ');
+ if ((pPos < pattern.length) && (pattern[pPos] == ','))
+ {
+ pPos++;
+ do
+ {
+ if (matchKV(pattern, pPos, dn, dPos))
+ {
+ return true;
+ }
+
+ int comma = skipEscapedUntil(dn, dPos, ',');
+ int colon = skipEscapedUntil(dn, dPos, ';');
+
+ dPos = (comma > colon) ? colon : comma;
+ } while ((dPos < dn.length) && (dn[dPos++] == ','));
+ return false;
+ }
+ throw new IllegalArgumentException("[" + pPos + "]" + new String(pattern));
+ default:
+ break;
+ }
+
+ return matchKV(pattern, pPos - 1, dn, dPos);
+ }
+
+ private static boolean matchKV(char[] pattern, int pPos, char[] dn, int dPos)
+ {
+ pPos = skip(pattern, pPos, ' ');
+
+ if (pPos >= pattern.length)
+ {
+ return false;
+ }
+
+ int equals = skipEscapedUntil(pattern, pPos, '=');
+ int comma = skipEscapedUntil(pattern, pPos, ',');
+ int colon = skipEscapedUntil(pattern, pPos, ';');
+ if (((colon < pattern.length) && (colon < equals)) ||
+ ((comma < pattern.length) && (comma < equals)) ||
+ (equals >= pattern.length))
+ {
+ return false;
+ }
+
+ String key = (String) KEY2OIDSTRING.get(
+ new String(pattern, pPos, equals - pPos).toLowerCase(Locale.US).trim());
+
+ if (key == null)
+ {
+ throw new IllegalArgumentException("Bad key [" +
+ new String(pattern, pPos, equals - pPos) + "] in [" +
+ new String(pattern) + "]");
+ }
+
+ pPos = equals + 1;
+ int keylength = key.length();
+ for (int i = 0;i < keylength;i++)
+ {
+ if ((dPos >= dn.length) || (key.charAt(i) != dn[dPos++]))
+ {
+ return false;
+ }
+ }
+
+ if ((dPos >= dn.length) || (dn[dPos++] != '='))
+ {
+ return false;
+ }
+
+ pPos = skip(pattern, pPos, ' ');
+ if ((pPos < pattern.length -1) && (pattern[pPos] == '\\') && (pattern[pPos + 1] == '*'))
+ {
+ pPos = skip(pattern, pPos + 2, ' ');
+ if (pPos >= pattern.length)
+ {
+ return true;
+ }
+ comma = skipEscapedUntil(dn, dPos, ',');
+ colon = skipEscapedUntil(dn, dPos, ';');
+ if ((pattern[pPos] == ',') && (colon > comma))
+ {
+ return matchKV(pattern, ++pPos, dn, comma + 1);
+ }
+
+ if (pattern[pPos] == ';' )
+ {
+ return matchDN(pattern, ++pPos, dn, colon + 1);
+ }
+
+ return false;
+ }
+ boolean escaped = false;
+ while ((pPos < pattern.length) && (dPos < dn.length))
+ {
+ switch (Character.toLowerCase(pattern[pPos++]))
+ {
+ case ' ':
+ if ((pattern[pPos - 2] != ' ') && ((dn[dPos++] != ' ') &&
+ (dn[--dPos] != ';') && (dn[dPos] != ',')))
+ {
+ return false;
+ }
+ break;
+ case '\\':
+ escaped = !escaped;
+ break;
+
+ case '(':
+ case ')':
+ if (escaped)
+ {
+ if (dn[dPos++] != pattern[pPos - 1])
+ {
+ return false;
+ }
+ escaped = false;
+ break;
+ }
+ return false;
+ case ';':
+ if (!escaped)
+ {
+ if ((dPos < dn.length) && ((dn[dPos] == ',') || (dn[dPos] == ';')))
+ {
+ return matchDN(pattern, pPos, dn, skipEscapedUntil(dn, dPos, ';') + 1);
+ }
+ return false;
+ }
+ case ',':
+ if (!escaped)
+ {
+ if ((dPos < dn.length) && (dn[dPos] == ','))
+ {
+ return matchKV(pattern, pPos, dn, dPos + 1);
+ }
+ return false;
+ }
+ default:
+ if (escaped)
+ {
+ if (dn[dPos++] != '\\')
+ {
+ return false;
+ }
+ escaped = false;
+ }
+ if (dn[dPos++] != Character.toLowerCase(pattern[pPos - 1]))
+ {
+ return false;
+ }
+ break;
+ }
+ }
+
+ pPos = skip(pattern, pPos, ' ');
+ if (pPos >= pattern.length)
+ {
+ if ((dPos >= dn.length) || (dn[dPos] == ',') || (dn[dPos] == ';'))
+ {
+ return true;
+ }
+ }
+ else
+ {
+ switch (pattern[pPos++])
+ {
+ case ',':
+ return matchKV(pattern, pPos, dn, dPos);
+ case ';':
+ return matchDN(pattern, pPos, dn, dPos);
+ default:
+ break;
+ }
+ }
+
+ return false;
+ }
+
+ private static final Map KEY2OIDSTRING = new HashMap();
+
+ static {
+ KEY2OIDSTRING.put("2.5.4.3", "cn");
+ KEY2OIDSTRING.put("cn", "cn");
+ KEY2OIDSTRING.put("commonname", "cn");
+ KEY2OIDSTRING.put("2.5.4.4", "sn");
+ KEY2OIDSTRING.put("sn", "sn");
+ KEY2OIDSTRING.put("surname", "sn");
+ KEY2OIDSTRING.put("2.5.4.6", "c");
+ KEY2OIDSTRING.put("c", "c");
+ KEY2OIDSTRING.put("countryname", "c");
+ KEY2OIDSTRING.put("2.5.4.7", "l");
+ KEY2OIDSTRING.put("l", "l");
+ KEY2OIDSTRING.put("localityname", "l");
+ KEY2OIDSTRING.put("2.5.4.8", "st");
+ KEY2OIDSTRING.put("st", "st");
+ KEY2OIDSTRING.put("stateorprovincename", "st");
+ KEY2OIDSTRING.put("2.5.4.10", "o");
+ KEY2OIDSTRING.put("o", "o");
+ KEY2OIDSTRING.put("organizationname", "o");
+ KEY2OIDSTRING.put("2.5.4.11", "ou");
+ KEY2OIDSTRING.put("ou", "ou");
+ KEY2OIDSTRING.put("organizationalunitname", "ou");
+ KEY2OIDSTRING.put("2.5.4.12", "title");
+ KEY2OIDSTRING.put("t", "title");
+ KEY2OIDSTRING.put("title", "title");
+ KEY2OIDSTRING.put("2.5.4.42", "givenname");
+ KEY2OIDSTRING.put("givenname", "givenname");
+ KEY2OIDSTRING.put("2.5.4.43", "initials");
+ KEY2OIDSTRING.put("initials", "initials");
+ KEY2OIDSTRING.put("2.5.4.44", "generationqualifier");
+ KEY2OIDSTRING.put("generationqualifier", "generationqualifier");
+ KEY2OIDSTRING.put("2.5.4.46", "dnqualifier");
+ KEY2OIDSTRING.put("dnqualifier", "dnqualifier");
+ KEY2OIDSTRING.put("2.5.4.9", "street");
+ KEY2OIDSTRING.put("street", "street");
+ KEY2OIDSTRING.put("streetaddress", "street");
+ KEY2OIDSTRING.put("0.9.2342.19200300.100.1.25", "dc");
+ KEY2OIDSTRING.put("dc", "dc");
+ KEY2OIDSTRING.put("domaincomponent", "dc");
+ KEY2OIDSTRING.put("0.9.2342.19200300.100.1.1", "uid");
+ KEY2OIDSTRING.put("uid", "uid");
+ KEY2OIDSTRING.put("userid", "uid");
+ KEY2OIDSTRING.put("1.2.840.113549.1.9.1", "emailaddress");
+ KEY2OIDSTRING.put("emailaddress", "emailaddress");
+ KEY2OIDSTRING.put("2.5.4.5", "serialnumber");
+ KEY2OIDSTRING.put("serialnumber", "serialnumber");
+ }
+
+ private static int skipEscapedUntil(char[] string, int pos, char value)
+ {
+ boolean escaped = false;
+
+ while (pos < string.length)
+ {
+ switch (string[pos++])
+ {
+ case '\\':
+ escaped = true;
+ break;
+ default:
+ if (!escaped)
+ {
+ if (string[pos - 1] == value)
+ {
+ return pos - 1;
+ }
+ }
+ escaped = false;
+ break;
+ }
+ }
+
+ return pos;
+ }
+
+ private static int skip(char[] string, int pos, char value)
+ {
+ while (pos < string.length)
+ {
+ if (string[pos] != value)
+ {
+ break;
+ }
+ pos++;
+ }
+
+ return pos;
+ }
+}
diff --git a/framework/security/src/main/java/org/apache/felix/framework/security/verifier/SubjectDNParser.java b/framework/security/src/main/java/org/apache/felix/framework/security/verifier/SubjectDNParser.java
new file mode 100644
index 0000000..1c34021
--- /dev/null
+++ b/framework/security/src/main/java/org/apache/felix/framework/security/verifier/SubjectDNParser.java
@@ -0,0 +1,451 @@
+/*
+ * 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.verifier;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+public final class SubjectDNParser
+{
+ private static final Map OID2NAME = new HashMap();
+
+ static
+ {
+ // see core-spec 2.3.5
+ OID2NAME.put("2.5.4.3", "cn");
+ OID2NAME.put("2.5.4.4", "sn");
+ OID2NAME.put("2.5.4.6", "c");
+ OID2NAME.put("2.5.4.7", "l");
+ OID2NAME.put("2.5.4.8", "st");
+ OID2NAME.put("2.5.4.10", "o");
+ OID2NAME.put("2.5.4.11", "ou");
+ OID2NAME.put("2.5.4.12", "title");
+ OID2NAME.put("2.5.4.42", "givenname");
+ OID2NAME.put("2.5.4.43", "initials");
+ OID2NAME.put("2.5.4.44", "generationqualifier");
+ OID2NAME.put("2.5.4.46", "dnqualifier");
+ OID2NAME.put("2.5.4.9", "street");
+ OID2NAME.put("0.9.2342.19200300.100.1.25", "dc");
+ OID2NAME.put("0.9.2342.19200300.100.1.1", "uid");
+ OID2NAME.put("1.2.840.113549.1.9.1", "emailaddress");
+ OID2NAME.put("2.5.4.5", "serialnumber");
+ // p.s.: it sucks that the spec doesn't list some of the oids used
+ // p.p.s: it sucks that the spec doesn't list the short form for all names
+ // In summary, there is a certain amount of guess-work involved but I'm
+ // fairly certain I've got it right.
+ }
+
+ private byte[] m_buffer;
+ private int m_offset = 0;
+ private int m_tagOffset = 0;
+ private int m_tag = -1;
+ private int m_length = -1;
+ private int m_contentOffset = -1;
+
+ /*
+ * This is deep magiK, bare with me. The problem is that we don't get
+ * access to the original subject dn in a certificate without resorting to
+ * sun.* classes or running on something > OSGi-minimum/jdk1.3. Furthermore,
+ * we need access to it because there is no other way to escape it properly.
+ * Note, this is due to missing of a public X500Name in OSGI-minimum/jdk1.3
+ * a.k.a foundation.
+ *
+ * The solution is to get the DER encoded TBS certificate bytes via the
+ * available java methods and parse-out the subject dn in canonical form by
+ * hand. This is possible without deploying a full-blown BER encoder/decoder
+ * due to java already having done all the cumbersome verification and
+ * normalization work.
+ *
+ * The following skips through the TBS certificate bytes until it reaches and
+ * subsequently parses the subject dn. If the below makes immediate sense to
+ * you - you either are a X509/X501/DER expert or quite possibly mad. In any
+ * case, please seek medical care immediately.
+ */
+ public String parseSubjectDN(byte[] tbsBuffer) throws Exception
+ {
+ // init
+ m_buffer = tbsBuffer;
+ m_offset = 0;
+
+ // TBSCertificate ::= SEQUENCE {
+ // version [0] EXPLICIT Version DEFAULT v1,
+ // serialNumber CertificateSerialNumber,
+ // signature AlgorithmIdentifier,
+ // issuer Name,
+ // validity Validity,
+ // subject Name,
+ //
+ // WE CAN STOP!
+ //
+ // subjectPublicKeyInfo SubjectPublicKeyInfo,
+ // issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL,
+ // -- If present, version must be v2 or v3
+ // subjectUniqueID [2] IMPLICIT UniqueIdentifier OPTIONAL,
+ // -- If present, version must be v2 or v3
+ // extensions [3] EXPLICIT Extensions OPTIONAL
+ // -- If present, version must be v3
+ // }
+ try
+ {
+ next();
+ next();
+ // if a version is present skip it
+ if (m_tag == 0)
+ {
+ next();
+ m_offset += m_length;
+ }
+ m_offset += m_length;
+ // skip the serialNumber
+ next();
+ next();
+ m_offset += m_length;
+ // skip the signature
+ next();
+ m_offset += m_length;
+ // skip the issuer
+ // The issuer is a sequence of sets of issuer dns like the subject later on -
+ // we just skip it.
+ next();
+ int endOffset = m_offset + m_length;
+
+ int seqTagOffset = m_tagOffset;
+
+ // skip the sequence
+ while (endOffset > m_offset)
+ {
+ next();
+
+ int endOffset2 = m_offset + m_length;
+
+ int seqTagOffset2 = m_tagOffset;
+
+ // skip each set
+ while (endOffset2 > m_offset)
+ {
+ next();
+ next();
+ m_offset += m_length;
+ next();
+ m_offset += m_length;
+ }
+
+ m_tagOffset = seqTagOffset2;
+ }
+
+ m_tagOffset = seqTagOffset;
+ // skip the validity which contains two dates to be skiped
+ next();
+ next();
+ m_offset += m_length;
+ next();
+ m_offset += m_length;
+ next();
+ // Now extract the subject dns and add them to attributes
+ List attributes = new ArrayList();
+
+ endOffset = m_offset + m_length;
+
+ seqTagOffset = m_tagOffset;
+
+ // for each set of rdns
+ while (endOffset > m_offset)
+ {
+ next();
+ int endOffset2 = m_offset + m_length;
+
+ // store tag offset
+ int seqTagOffset2 = m_tagOffset;
+
+ List rdn = new ArrayList();
+
+ // for each rdn in the set
+ while (endOffset2 > m_offset)
+ {
+ next();
+ next();
+ m_offset += m_length;
+ // parse the oid of the rdn
+ int oidElement = 1;
+ for (int i = 0; i < m_length; i++, ++oidElement)
+ {
+ while ((m_buffer[m_contentOffset + i] & 0x80) == 0x80)
+ {
+ i++;
+ }
+ }
+ int[] oid = new int[oidElement];
+ for (int id = 1, i = 0; id < oid.length; id++, i++)
+ {
+ int octet = m_buffer[m_contentOffset + i];
+ oidElement = octet & 0x7F;
+ while ((octet & 0x80) != 0)
+ {
+ i++;
+ octet = m_buffer[m_contentOffset + i];
+ oidElement = oidElement << 7 | (octet & 0x7f);
+ }
+ oid[id] = oidElement;
+ }
+ // The first OID is special
+ if (oid[1] > 79)
+ {
+ oid[0] = 2;
+ oid[1] = oid[1] - 80;
+ }
+ else
+ {
+ oid[0] = oid[1] / 40;
+ oid[1] = oid[1] % 40;
+ }
+ // Now parse the value of the rdn
+ next();
+ String str = null;
+ int tagTmp = m_tag;
+ m_offset += m_length;
+ switch(tagTmp)
+ {
+ case 30: // BMPSTRING
+ case 22: // IA5STRING
+ case 27: // GENERALSTRING
+ case 19: // PRINTABLESTRING
+ case 20: // TELETEXSTRING && T61STRING
+ case 28: // UNIVERSALSTRING
+ str = new String(m_buffer, m_contentOffset,
+ m_length);
+ break;
+ case 12: // UTF8_STRING
+ str = new String(m_buffer, m_contentOffset,
+ m_length, "UTF-8");
+ break;
+ default: // OCTET
+ byte[] encoded = new byte[m_offset - m_tagOffset];
+ System.arraycopy(m_buffer, m_tagOffset, encoded,
+ 0, encoded.length);
+ // Note, I'm not sure this is allowed by the spec
+ // i.e., whether OCTET subjects are allowed at all
+ // but it shouldn't harm doing it anyways (we just
+ // convert it into a hex string prefixed with \#).
+ str = toHexString(encoded);
+ break;
+ }
+
+ rdn.add(new Object[]{mapOID(oid), makeCanonical(str)});
+ }
+
+ attributes.add(rdn);
+ m_tagOffset = seqTagOffset2;
+ }
+
+ m_tagOffset = seqTagOffset;
+
+ StringBuffer result = new StringBuffer();
+
+ for (int i = attributes.size() - 1; i >= 0; i--)
+ {
+ List rdn = (List) attributes.get(i);
+ Collections.sort(rdn, new Comparator()
+ {
+ public int compare(Object obj1, Object obj2)
+ {
+ return ((String) ((Object[]) obj1)[0]).compareTo(
+ ((String) ((Object[])obj2)[0]));
+ }
+ });
+
+ for (Iterator iter = rdn.iterator();iter.hasNext();)
+ {
+ Object[] att = (Object[]) iter.next();
+ result.append((String) att[0]);
+ result.append('=');
+ result.append((String) att[1]);
+
+ if (iter.hasNext())
+ {
+ // multi-valued RDN
+ result.append('+');
+ }
+ }
+
+ if (i != 0)
+ {
+ result.append(',');
+ }
+ }
+
+
+ // the spec says:
+ // return result.toString().toUpperCase(Locale.US).toLowerCase(Locale.US);
+ // which is needed because toLowerCase can be ambiguous in unicode when
+ // used on mixed case while toUpperCase not hence, this way its ok.
+ return result.toString().toUpperCase(Locale.US).toLowerCase(Locale.US);
+ }
+ finally
+ {
+ m_buffer = null;
+ }
+ }
+
+ // Determine the type of the current sequence (tbs_tab), and the length and
+ // offset of it (tbs_length and tbs_tagOffset) plus increment the global
+ // offset (tbs_offset) accordingly. Note, we don't need to check for
+ // the indefinite length because this is supposed to be DER not BER (and
+ // we implicitly assume that java only gives us valid DER).
+ private void next()
+ {
+ m_tagOffset = m_offset;
+ m_tag = m_buffer[m_offset++] & 0xFF;
+ m_length = m_buffer[m_offset++] & 0xFF;
+ // There are two kinds of length forms - make sure we use the right one
+ if ((m_length & 0x80) != 0)
+ {
+ // its the long kind
+ int numOctets = m_length & 0x7F;
+ // hence, convert it
+ m_length = m_buffer[m_offset++] & 0xFF;
+ for (int i = 1; i < numOctets; i++)
+ {
+ int ch = m_buffer[m_offset++] & 0xFF;
+ m_length = (m_length << 8) + ch;
+ }
+ }
+ m_contentOffset = m_offset;
+ }
+
+ private String makeCanonical(String value)
+ {
+ int len = value.length();
+
+ if (len == 0)
+ {
+ return value;
+ }
+
+ StringBuffer result = new StringBuffer(len);
+
+ int i = 0;
+ if (value.charAt(0) == '#')
+ {
+ result.append('\\');
+ result.append('#');
+ i++;
+ }
+ for (;i < len; i++)
+ {
+ char c = value.charAt(i);
+
+ switch (c)
+ {
+ case ' ':
+ int pos = result.length();
+ // remove leading spaces and
+ // remove all spaces except one in any sequence of spaces
+ if ((pos == 0) || (result.charAt(pos - 1) == ' '))
+ {
+ break;
+ }
+ result.append(' ');
+ break;
+ case '"':
+ case '\\':
+ case ',':
+ case '+':
+ case '<':
+ case '>':
+ case ';':
+ result.append('\\');
+ default:
+ result.append(c);
+ }
+ }
+
+ // count down until first none space to remove trailing spaces
+ i = result.length() - 1;
+ while ((i > -1) && (result.charAt(i) == ' '))
+ {
+ i--;
+ }
+
+ result.setLength(i + 1);
+
+ return result.toString();
+ }
+
+ private String toHexString(byte[] encoded)
+ {
+ StringBuffer result = new StringBuffer();
+
+ result.append('#');
+
+ for (int i = 0; i < encoded.length; i++)
+ {
+ int c = (encoded[i] >> 4) & 0x0F;
+ if (c < 10)
+ {
+ result.append((char) (c + 48));
+ }
+ else
+ {
+ result.append((char) (c + 87));
+ }
+
+ c = encoded[i] & 0x0F;
+
+ if (c < 10)
+ {
+ result.append((char) (c + 48));
+ }
+ else
+ {
+ result.append((char) (c + 87));
+ }
+ }
+
+ return result.toString();
+ }
+
+ // This just creates a string of the oid and looks for its name in the
+ // known names map OID2NAME. There might be faster implementations :-)
+ private String mapOID(int[] oid)
+ {
+ StringBuffer oidString = new StringBuffer();
+
+ oidString.append(oid[0]);
+ for (int i = 1;i < oid.length;i++)
+ {
+ oidString.append('.');
+ oidString.append(oid[i]);
+ }
+
+ String result = (String) OID2NAME.get(oidString.toString());
+
+ if (result == null)
+ {
+ throw new IllegalArgumentException("Unknown oid: " + oidString.toString());
+ }
+
+ return result;
+ }
+}
\ No newline at end of file
diff --git a/framework/security/src/main/java/org/osgi/service/condpermadmin/BundleLocationCondition.java b/framework/security/src/main/java/org/osgi/service/condpermadmin/BundleLocationCondition.java
new file mode 100644
index 0000000..6e6af96
--- /dev/null
+++ b/framework/security/src/main/java/org/osgi/service/condpermadmin/BundleLocationCondition.java
@@ -0,0 +1,133 @@
+/*
+ * $Header: /cvshome/build/org.osgi.service.condpermadmin/src/org/osgi/service/condpermadmin/BundleLocationCondition.java,v 1.18 2006/06/16 16:31:37 hargrave Exp $
+ *
+ * Copyright (c) OSGi Alliance (2005, 2006). All Rights Reserved.
+ *
+ * Licensed 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.osgi.service.condpermadmin;
+
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.Hashtable;
+
+import org.apache.felix.framework.FilterImpl;
+import org.osgi.framework.*;
+
+/**
+ * Condition to test if the location of a bundle matches a pattern. Pattern
+ * matching is done according to the filter string matching rules.
+ *
+ * @version $Revision: 1.18 $
+ */
+public class BundleLocationCondition
+{
+ private static final String CONDITION_TYPE =
+ "org.osgi.service.condpermadmin.BundleLocationCondition";
+
+ /**
+ * Constructs a condition that tries to match the passed Bundle's location
+ * to the location pattern.
+ *
+ * @param bundle
+ * The Bundle being evaluated.
+ * @param info
+ * The ConditionInfo to construct the condition for. The args
+ * of the ConditionInfo must be a single String which
+ * specifies the location pattern to match against the Bundle
+ * location. Matching is done according to the filter string
+ * matching rules. Any '*' characters in the location
+ * argument are used as wildcards when matching bundle
+ * locations unless they are escaped with a '\' character.
+ * @return Condition object for the requested condition.
+ */
+ static public Condition getCondition(final Bundle bundle, ConditionInfo info)
+ {
+ if (!CONDITION_TYPE.equals(info.getType()))
+ throw new IllegalArgumentException(
+ "ConditionInfo must be of type \"" + CONDITION_TYPE + "\"");
+ String[] args = info.getArgs();
+ if (args.length != 1)
+ throw new IllegalArgumentException("Illegal number of args: "
+ + args.length);
+ String bundleLocation =
+ (String) AccessController.doPrivileged(new PrivilegedAction()
+ {
+ public Object run()
+ {
+ return bundle.getLocation();
+ }
+ });
+ Filter filter = null;
+ try
+ {
+ filter =
+ new FilterImpl("(location=" + escapeLocation(args[0]) + ")");
+ }
+ catch (InvalidSyntaxException e)
+ {
+ // this should never happen, but just incase
+ throw new RuntimeException("Invalid filter: " + e.getFilter());
+ }
+ Hashtable matchProps = new Hashtable(2);
+ matchProps.put("location", bundleLocation);
+ return filter.match(matchProps) ? Condition.TRUE : Condition.FALSE;
+ }
+
+ private BundleLocationCondition()
+ {
+ // private constructor to prevent objects of this type
+ }
+
+ /**
+ * Escape the value string such that '(', ')' and '\' are escaped. The '\'
+ * char is only escaped if it is not followed by a '*'.
+ *
+ * @param value
+ * unescaped value string.
+ * @return escaped value string.
+ */
+ private static String escapeLocation(String value)
+ {
+ boolean escaped = false;
+ int inlen = value.length();
+ int outlen = inlen << 1; /* inlen * 2 */
+
+ char[] output = new char[outlen];
+ value.getChars(0, inlen, output, inlen);
+
+ int cursor = 0;
+ for (int i = inlen; i < outlen; i++)
+ {
+ char c = output[i];
+ switch (c)
+ {
+ case '\\':
+ if (i + 1 < outlen && output[i + 1] == '*')
+ break;
+ case '(':
+ case ')':
+ output[cursor] = '\\';
+ cursor++;
+ escaped = true;
+ break;
+ }
+
+ output[cursor] = c;
+ cursor++;
+ }
+
+ return escaped ? new String(output, 0, cursor) : value;
+ }
+}
diff --git a/framework/security/src/main/java/org/osgi/service/condpermadmin/BundleSignerCondition.java b/framework/security/src/main/java/org/osgi/service/condpermadmin/BundleSignerCondition.java
new file mode 100644
index 0000000..a57e318
--- /dev/null
+++ b/framework/security/src/main/java/org/osgi/service/condpermadmin/BundleSignerCondition.java
@@ -0,0 +1,229 @@
+/*
+ * $Header: /cvshome/build/org.osgi.service.condpermadmin/src/org/osgi/service/condpermadmin/BundleSignerCondition.java,v 1.10 2006/06/16 16:31:37 hargrave Exp $
+ *
+ * Copyright (c) OSGi Alliance (2005, 2006). All Rights Reserved.
+ *
+ * Licensed 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.osgi.service.condpermadmin;
+
+import java.lang.reflect.Method;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.util.Dictionary;
+import java.util.Hashtable;
+
+import org.apache.felix.framework.FilterImpl;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.Filter;
+import org.osgi.framework.InvalidSyntaxException;
+
+/**
+ * Condition to test if the signer of a bundle matches a pattern. Since the
+ * bundle's signer can only change when the bundle is updated, this condition is
+ * immutable.
+ * <p>
+ * The condition expressed using a single String that specifies a Distinguished
+ * Name (DN) chain to match bundle signers against. DN's are encoded using IETF
+ * RFC 2253. Usually signers use certificates that are issued by certificate
+ * authorities, which also have a corresponding DN and certificate. The
+ * certificate authorities can form a chain of trust where the last DN and
+ * certificate is known by the framework. The signer of a bundle is expressed as
+ * signers DN followed by the DN of its issuer followed by the DN of the next
+ * issuer until the DN of the root certificate authority. Each DN is separated
+ * by a semicolon.
+ * <p>
+ * A bundle can satisfy this condition if one of its signers has a DN chain that
+ * matches the DN chain used to construct this condition. Wildcards (`*') can be
+ * used to allow greater flexibility in specifying the DN chains. Wildcards can
+ * be used in place of DNs, RDNs, or the value in an RDN. If a wildcard is used
+ * for a value of an RDN, the value must be exactly "*" and will match any value
+ * for the corresponding type in that RDN. If a wildcard is used for a RDN, it
+ * must be the first RDN and will match any number of RDNs (including zero
+ * RDNs).
+ *
+ * @version $Revision: 1.10 $
+ */
+/*
+ * TODO: In our case the above is not correct. We don't make this an immutable
+ * condition because the spec is somewhat ambiguous in regard to when the
+ * signature change. This probably has to be clarified and then revisited later.
+ */
+public class BundleSignerCondition
+{
+ /*
+ * NOTE: A framework implementor may also choose to replace this class in
+ * their distribution with a class that directly interfaces with the
+ * framework implementation. This replacement class MUST NOT alter the
+ * public/protected signature of this class.
+ */
+
+ private static final String CONDITION_TYPE =
+ "org.osgi.service.condpermadmin.BundleSignerCondition";
+
+ /**
+ * Constructs a Condition that tries to match the passed Bundle's location
+ * to the location pattern.
+ *
+ * @param bundle
+ * The Bundle being evaluated.
+ * @param info
+ * The ConditionInfo to construct the condition for. The args
+ * of the ConditionInfo specify a single String specifying
+ * the chain of distinguished names pattern to match against
+ * the signer of the Bundle.
+ * @return A Condition which checks the signers of the specified bundle.
+ */
+ static public Condition getCondition(Bundle bundle, ConditionInfo info)
+ {
+ if (!CONDITION_TYPE.equals(info.getType()))
+ throw new IllegalArgumentException(
+ "ConditionInfo must be of type \"" + CONDITION_TYPE + "\"");
+ final String[] args = info.getArgs();
+ if (args.length != 1)
+ throw new IllegalArgumentException("Illegal number of args: "
+ + args.length);
+
+ return new ConditionImpl(bundle, "(signer=" + escapeFilter(args[0])
+ + ")");
+
+ }
+
+ private static String escapeFilter(String string)
+ {
+ boolean escaped = false;
+ int inlen = string.length();
+ int outlen = inlen << 1; /* inlen * 2 */
+
+ char[] output = new char[outlen];
+ string.getChars(0, inlen, output, inlen);
+
+ int cursor = 0;
+ for (int i = inlen; i < outlen; i++)
+ {
+ char c = output[i];
+ switch (c)
+ {
+ case '\\':
+ case '(':
+ case ')':
+ case '*':
+ output[cursor] = '\\';
+ cursor++;
+ escaped = true;
+ break;
+ }
+
+ output[cursor] = c;
+ cursor++;
+ }
+
+ return escaped ? new String(output, 0, cursor) : string;
+ }
+
+ private BundleSignerCondition()
+ {
+ // private constructor to prevent objects of this type
+ }
+}
+
+final class ConditionImpl implements Condition, PrivilegedExceptionAction
+{
+ private static final Method m_getSignerMatcher;
+
+ static
+ {
+ m_getSignerMatcher =
+ (Method) AccessController.doPrivileged(new PrivilegedAction()
+ {
+ public Object run()
+ {
+ Method getSignerMatcher = null;
+ try
+ {
+ getSignerMatcher =
+ Class.forName(
+ "org.apache.felix.framework.BundleImpl")
+ .getDeclaredMethod("getSignerMatcher", null);
+ getSignerMatcher.setAccessible(true);
+ }
+ catch (Exception ex)
+ {
+ ex.printStackTrace();
+ getSignerMatcher = null;
+ }
+ return getSignerMatcher;
+ }
+ });
+ }
+
+ private final Bundle m_bundle;
+ private final Filter m_filter;
+ private final Dictionary m_dict;
+
+ ConditionImpl(Bundle bundle, String filter)
+ {
+ m_bundle = bundle;
+ try
+ {
+ m_filter = new FilterImpl(filter);
+ }
+ catch (InvalidSyntaxException e)
+ {
+ throw new IllegalArgumentException(e.getMessage());
+ }
+ try
+ {
+ Object signerMatcher = AccessController.doPrivileged(this);
+ m_dict = new Hashtable();
+ m_dict.put("signer", signerMatcher);
+ }
+ catch (PrivilegedActionException e)
+ {
+ if (e.getException() instanceof RuntimeException)
+ {
+ throw (RuntimeException) e.getException();
+ }
+
+ throw new RuntimeException(e.getException().getMessage());
+ }
+ }
+
+ public boolean isMutable()
+ {
+ return true;
+ }
+
+ public boolean isPostponed()
+ {
+ return false;
+ }
+
+ public Object run() throws Exception
+ {
+ return m_getSignerMatcher.invoke(m_bundle, null);
+ }
+
+ public boolean isSatisfied()
+ {
+ return m_filter.match(m_dict);
+ }
+
+ public boolean isSatisfied(Condition[] conditions, Dictionary context)
+ {
+ return false;
+ }
+}
\ No newline at end of file