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