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