You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@felix.apache.org by pa...@apache.org on 2008/03/07 01:37:47 UTC

svn commit: r634480 [2/3] - in /felix/trunk/framework/security: ./ src/ src/main/ src/main/java/ src/main/java/org/ src/main/java/org/apache/ src/main/java/org/apache/felix/ src/main/java/org/apache/felix/framework/ src/main/java/org/apache/felix/frame...

Added: felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/util/LocalPermissions.java
URL: http://svn.apache.org/viewvc/felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/util/LocalPermissions.java?rev=634480&view=auto
==============================================================================
--- felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/util/LocalPermissions.java (added)
+++ felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/util/LocalPermissions.java Thu Mar  6 16:37:30 2008
@@ -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);
+        }
+    }
+}

Added: felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/util/Permissions.java
URL: http://svn.apache.org/viewvc/felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/util/Permissions.java?rev=634480&view=auto
==============================================================================
--- felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/util/Permissions.java (added)
+++ felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/util/Permissions.java Thu Mar  6 16:37:30 2008
@@ -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;
+    }
+}

Added: felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/util/PropertiesCache.java
URL: http://svn.apache.org/viewvc/felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/util/PropertiesCache.java?rev=634480&view=auto
==============================================================================
--- felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/util/PropertiesCache.java (added)
+++ felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/util/PropertiesCache.java Thu Mar  6 16:37:30 2008
@@ -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());
+        }
+    }
+}

Added: felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/util/TrustManager.java
URL: http://svn.apache.org/viewvc/felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/util/TrustManager.java?rev=634480&view=auto
==============================================================================
--- felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/util/TrustManager.java (added)
+++ felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/util/TrustManager.java Thu Mar  6 16:37:30 2008
@@ -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

Added: felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/verifier/BundleDNParser.java
URL: http://svn.apache.org/viewvc/felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/verifier/BundleDNParser.java?rev=634480&view=auto
==============================================================================
--- felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/verifier/BundleDNParser.java (added)
+++ felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/verifier/BundleDNParser.java Thu Mar  6 16:37:30 2008
@@ -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;
+    }
+}

Added: felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/verifier/SignerMatcher.java
URL: http://svn.apache.org/viewvc/felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/verifier/SignerMatcher.java?rev=634480&view=auto
==============================================================================
--- felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/verifier/SignerMatcher.java (added)
+++ felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/verifier/SignerMatcher.java Thu Mar  6 16:37:30 2008
@@ -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;
+    }
+}

Added: felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/verifier/SubjectDNParser.java
URL: http://svn.apache.org/viewvc/felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/verifier/SubjectDNParser.java?rev=634480&view=auto
==============================================================================
--- felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/verifier/SubjectDNParser.java (added)
+++ felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/verifier/SubjectDNParser.java Thu Mar  6 16:37:30 2008
@@ -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