Switch to protection domains and implement support for digitally signed bundles.
I'll comment on the concerned JIRA issues later (FELIX-21)(FELIX-22).
git-svn-id: https://svn.apache.org/repos/asf/incubator/felix/trunk@434386 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/framework/src/main/java/org/apache/felix/framework/BundleImpl.java b/framework/src/main/java/org/apache/felix/framework/BundleImpl.java
index f30af56..77fffc9 100644
--- a/framework/src/main/java/org/apache/felix/framework/BundleImpl.java
+++ b/framework/src/main/java/org/apache/felix/framework/BundleImpl.java
@@ -19,10 +19,7 @@
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
-import java.util.ArrayList;
-import java.util.Dictionary;
-import java.util.Enumeration;
-import java.util.List;
+import java.util.*;
import org.osgi.framework.*;
@@ -65,70 +62,70 @@
public URL getEntry(String name)
{
Object sm = System.getSecurityManager();
-
+
if (sm != null)
{
- try
+ try
{
- ((SecurityManager) sm).checkPermission(new AdminPermission(this,
+ ((SecurityManager) sm).checkPermission(new AdminPermission(this,
AdminPermission.RESOURCE));
- }
+ }
catch (Exception e)
{
return null; // No permission
}
}
-
+
return m_felix.getBundleEntry(this, name);
}
public Enumeration getEntryPaths(String path)
{
Object sm = System.getSecurityManager();
-
+
if (sm != null)
{
- try
+ try
{
- ((SecurityManager) sm).checkPermission(new AdminPermission(this,
+ ((SecurityManager) sm).checkPermission(new AdminPermission(this,
AdminPermission.RESOURCE));
- }
+ }
catch (Exception e)
{
return null; // No permission
}
}
-
+
return m_felix.getBundleEntryPaths(this, path);
}
public Enumeration findEntries(String path, String filePattern, boolean recurse)
{
Object sm = System.getSecurityManager();
-
+
if (sm != null)
{
- try
+ try
{
- ((SecurityManager) sm).checkPermission(new AdminPermission(this,
+ ((SecurityManager) sm).checkPermission(new AdminPermission(this,
AdminPermission.RESOURCE));
- }
+ }
catch (Exception e)
{
return null; // No permission
}
}
-
+
return m_felix.findBundleEntries(this, path, filePattern, recurse);
}
public Dictionary getHeaders()
{
Object sm = System.getSecurityManager();
-
+
if (sm != null)
{
- ((SecurityManager) sm).checkPermission(new AdminPermission(this,
+ ((SecurityManager) sm).checkPermission(new AdminPermission(this,
AdminPermission.METADATA));
}
return m_felix.getBundleHeaders(this);
@@ -142,10 +139,10 @@
public String getLocation()
{
Object sm = System.getSecurityManager();
-
+
if (sm != null)
{
- ((SecurityManager) sm).checkPermission(new AdminPermission(this,
+ ((SecurityManager) sm).checkPermission(new AdminPermission(this,
AdminPermission.METADATA));
}
return m_felix.getBundleLocation(this);
@@ -170,51 +167,51 @@
public ServiceReference[] getRegisteredServices()
{
Object sm = System.getSecurityManager();
-
+
if (sm != null)
{
ServiceReference[] refs = m_felix.getBundleRegisteredServices(this);
-
+
if (refs == null)
{
return refs;
}
-
+
List result = new ArrayList();
-
+
for (int i = 0;i < refs.length;i++)
{
String[] objectClass = (String[]) refs[i].getProperty(
Constants.OBJECTCLASS);
-
+
if (objectClass == null)
{
continue;
}
-
+
for (int j = 0;j < objectClass.length;j++)
{
try
{
((SecurityManager) sm).checkPermission(new ServicePermission(
objectClass[j], ServicePermission.GET));
-
+
result.add(refs[i]);
-
+
break;
- }
+ }
catch (Exception e)
{
-
+
}
}
}
-
+
if (result.isEmpty())
{
return null;
}
-
+
return (ServiceReference[]) result.toArray(new ServiceReference[result.size()]);
}
else
@@ -226,54 +223,54 @@
public ServiceReference[] getServicesInUse()
{
Object sm = System.getSecurityManager();
-
+
if (sm != null)
{
ServiceReference[] refs = m_felix.getBundleServicesInUse(this);
-
+
if (refs == null)
{
return refs;
}
-
+
List result = new ArrayList();
-
+
for (int i = 0;i < refs.length;i++)
{
String[] objectClass = (String[]) refs[i].getProperty(
Constants.OBJECTCLASS);
-
+
if (objectClass == null)
{
continue;
}
-
+
for (int j = 0;j < objectClass.length;j++)
{
try
{
((SecurityManager) sm).checkPermission(new ServicePermission(
objectClass[j], ServicePermission.GET));
-
+
result.add(refs[i]);
-
+
break;
- }
+ }
catch (Exception e)
{
-
+
}
}
}
-
+
if (result.isEmpty())
{
return null;
}
-
+
return (ServiceReference[]) result.toArray(new ServiceReference[result.size()]);
}
-
+
return m_felix.getBundleServicesInUse(this);
}
@@ -296,33 +293,33 @@
public Class loadClass(String name) throws ClassNotFoundException
{
Object sm = System.getSecurityManager();
-
+
if (sm != null)
{
- try
+ try
{
- ((SecurityManager) sm).checkPermission(new AdminPermission(this,
+ ((SecurityManager) sm).checkPermission(new AdminPermission(this,
AdminPermission.CLASS));
- }
+ }
catch (Exception e)
{
throw new ClassNotFoundException("No permission.", e);
}
}
-
+
return m_felix.loadBundleClass(this, name);
}
public void start() throws BundleException
{
Object sm = System.getSecurityManager();
-
+
if (sm != null)
{
- ((SecurityManager) sm).checkPermission(new AdminPermission(this,
+ ((SecurityManager) sm).checkPermission(new AdminPermission(this,
AdminPermission.EXECUTE));
}
-
+
m_felix.startBundle(this, true);
}
@@ -334,39 +331,39 @@
public void update(InputStream is) throws BundleException
{
Object sm = System.getSecurityManager();
-
+
if (sm != null)
{
- ((SecurityManager) sm).checkPermission(new AdminPermission(this,
+ ((SecurityManager) sm).checkPermission(new AdminPermission(this,
AdminPermission.LIFECYCLE));
}
-
+
m_felix.updateBundle(this, is);
}
public void stop() throws BundleException
{
Object sm = System.getSecurityManager();
-
+
if (sm != null)
{
- ((SecurityManager) sm).checkPermission(new AdminPermission(this,
+ ((SecurityManager) sm).checkPermission(new AdminPermission(this,
AdminPermission.EXECUTE));
}
-
+
m_felix.stopBundle(this, true);
}
public void uninstall() throws BundleException
{
Object sm = System.getSecurityManager();
-
+
if (sm != null)
{
- ((SecurityManager) sm).checkPermission(new AdminPermission(this,
+ ((SecurityManager) sm).checkPermission(new AdminPermission(this,
AdminPermission.LIFECYCLE));
}
-
+
m_felix.uninstallBundle(this);
}
@@ -384,13 +381,13 @@
// TODO: Implement Bundle.getHeaders(String locale)
// Should be done after [#FELIX-27] resolution
Object sm = System.getSecurityManager();
-
+
if (sm != null)
{
- ((SecurityManager) sm).checkPermission(new AdminPermission(this,
+ ((SecurityManager) sm).checkPermission(new AdminPermission(this,
AdminPermission.METADATA));
}
-
+
return null;
}
@@ -398,20 +395,20 @@
{
// TODO: Implement Bundle.getResources()
Object sm = System.getSecurityManager();
-
+
if (sm != null)
{
- try
+ try
{
- ((SecurityManager) sm).checkPermission(new AdminPermission(this,
+ ((SecurityManager) sm).checkPermission(new AdminPermission(this,
AdminPermission.RESOURCE));
- }
+ }
catch (Exception e)
{
return null; // No permission
}
}
-
+
return null;
}
@@ -423,4 +420,13 @@
}
return false;
}
+
+ /*
+ * This is a hack to get access to the subject-dns of the current revision
+ * from inside the AdminPermission.
+ */
+ String[] getSubjectDNs()
+ {
+ return m_info.getArchive().getDNChains();
+ }
}
\ No newline at end of file
diff --git a/framework/src/main/java/org/apache/felix/framework/Felix.java b/framework/src/main/java/org/apache/felix/framework/Felix.java
index 2a56a40..0ed5465 100644
--- a/framework/src/main/java/org/apache/felix/framework/Felix.java
+++ b/framework/src/main/java/org/apache/felix/framework/Felix.java
@@ -18,6 +18,7 @@
import java.io.*;
import java.net.*;
+import java.security.*;
import java.util.*;
import org.apache.felix.framework.cache.*;
@@ -94,6 +95,11 @@
// Reusable bundle URL stream handler.
private URLStreamHandler m_bundleStreamHandler = null;
+ // The secure action used to do privileged calls
+ private SecureAction m_secureAction = new SecureAction();
+
+ private Collection m_trustedCaCerts = null;
+
/**
* <p>
* This method starts the framework instance; instances of the framework
@@ -182,22 +188,30 @@
* able to take advantage of the features it provides; refer to its
* class documentation for more information.
* </p>
- *
+ *
* @param configMutable An object for obtaining configuration properties,
* may be <tt>null</tt>.
* @param frameworkProps An object for obtaining framework properties,
* may be <tt>null</tt>.
* @param activatorList A list of System Bundle activators.
**/
+ public synchronized void start(MutablePropertyResolver configMutable,
+ List activatorList)
+ {
+ start(configMutable, activatorList, (Collection) null);
+ }
+
public synchronized void start(
MutablePropertyResolver configMutable,
- List activatorList)
+ List activatorList, Collection trustedCaCerts)
{
if (m_frameworkStatus != INITIAL_STATUS)
{
throw new IllegalStateException("Invalid framework status: " + m_frameworkStatus);
}
+ m_trustedCaCerts = trustedCaCerts;
+
// The framework is now in its startup sequence.
m_frameworkStatus = STARTING_STATUS;
@@ -227,7 +241,7 @@
try
{
- m_cache = new BundleCache(m_config, m_logger);
+ m_cache = new BundleCache(m_config, m_logger, m_trustedCaCerts);
}
catch (Exception ex)
{
@@ -241,23 +255,7 @@
? false : embedded.equals("true");
if (!isEmbedded)
{
- if (System.getSecurityManager() != null)
- {
- java.security.AccessController.doPrivileged(
- new java.security.PrivilegedAction()
- {
- public Object run()
- {
- System.exit(-1);
-
- return null;
- }
- });
- }
- else
- {
- System.exit(-1);
- }
+ m_secureAction.exit(-1);
}
else
{
@@ -339,7 +337,8 @@
// Create a simple bundle info for the system bundle.
BundleInfo info = new BundleInfo(
m_logger, new SystemBundleArchive(), null);
- systembundle = new SystemBundle(this, info, activatorList);
+ systembundle = new SystemBundle(this, info, activatorList,
+ m_secureAction);
// Create a module for the system bundle.
IModuleDefinition md = new ModuleDefinition(
systembundle.getExports(), null, null, null);
@@ -350,6 +349,9 @@
m_factory.setContentLoader(
systembundle.getInfo().getCurrentModule(),
systembundle.getContentLoader());
+ m_factory.setSecurityContext(
+ systembundle.getInfo().getCurrentModule(),
+ systembundle.getClass().getProtectionDomain());
m_installedBundleMap.put(
systembundle.getInfo().getLocation(), systembundle);
@@ -380,7 +382,7 @@
m_logger.log(Logger.LOG_ERROR, "Unable to start system bundle.", ex);
throw new RuntimeException("Unable to start system bundle.");
}
-
+
// Reload and cached bundles.
BundleArchive[] archives = null;
@@ -672,10 +674,10 @@
// Determine if we are lowering or raising the
// active start level.
boolean lowering = (requestedLevel < m_activeStartLevel);
-
+
// Record new start level.
m_activeStartLevel = requestedLevel;
-
+
// Get a snapshot of all installed bundles.
bundles = getBundles();
@@ -883,7 +885,7 @@
{
// Acquire bundle lock.
acquireBundleLock((BundleImpl) bundle);
-
+
Throwable rethrow = null;
try
@@ -897,7 +899,7 @@
{
BundleImpl impl = (BundleImpl) bundle;
impl.getInfo().setStartLevel(startLevel);
-
+
try
{
// Start the bundle if necessary.
@@ -1070,11 +1072,9 @@
try
{
return (obj instanceof java.security.Permission)
- ? java.security.Policy.getPolicy().getPermissions(
- new java.security.CodeSource(
- new java.net.URL(bundle.getInfo().getLocation()),
- (java.security.cert.Certificate[]) null))
- .implies((java.security.Permission) obj)
+ ? ((ProtectionDomain)
+ bundle.getInfo().getCurrentModule().getSecurityContext())
+ .implies((java.security.Permission) obj)
: false;
}
catch (Exception ex)
@@ -1083,7 +1083,7 @@
Logger.LOG_WARNING,
"Exception while evaluating the permission.",
ex);
- return false;
+ return false;
}
}
@@ -1124,7 +1124,7 @@
// we only acquire the lock for the bundle being started, because
// when resolve is called on this bundle, it will eventually
// call resolve on the module loader search policy, which does
- // its own locking on the module factory instance. Since the
+ // its own locking on the module factory instance. Since the
// resolve algorithm is locking the module factory instance, it
// is not possible for other bundles to be installed or removed,
// so we don't have to worry about these possibilities.
@@ -1205,16 +1205,8 @@
// Activate the bundle if it has an activator.
if (bundle.getInfo().getActivator() != null)
{
- if (System.getSecurityManager() != null)
- {
- java.security.AccessController.doPrivileged(
- new PrivilegedActivatorCall(PrivilegedActivatorCall.START,
- info.getActivator(), info.getContext()));
- }
- else
- {
- info.getActivator().start(info.getContext());
- }
+ m_secureAction.startActivator(info.getActivator(),
+ info.getContext());
}
// TODO: CONCURRENCY - Reconsider firing event outside of the
@@ -1251,7 +1243,7 @@
{
throw (SecurityException) th;
}
- else if ((System.getSecurityManager() != null) &&
+ else if ((System.getSecurityManager() != null) &&
(th instanceof java.security.PrivilegedActionException))
{
th = ((java.security.PrivilegedActionException) th).getException();
@@ -1269,33 +1261,39 @@
// to import the necessary packages.
if (System.getSecurityManager() != null)
{
- URL url = null;
- try
+
+ ProtectionDomain pd = (ProtectionDomain)
+ bundle.getInfo().getCurrentModule().getSecurityContext();
+
+ R4Import[] imports =
+ bundle.getInfo().getCurrentModule().getDefinition().getImports();
+
+ for (int i = 0;i < imports.length; i++)
{
- url = new URL(bundle.getInfo().getLocation());
- }
- catch (MalformedURLException ex)
- {
- throw new BundleException("Cannot resolve, bad URL "
- + bundle.getInfo().getLocation());
- }
-
- try
- {
- java.security.AccessController.doPrivileged(
- new CheckImportsPrivileged(url, bundle));
- }
- catch (java.security.PrivilegedActionException ex)
- {
- Exception thrown =
- ((java.security.PrivilegedActionException) ex).getException();
- if (thrown instanceof SecurityException)
+ PackagePermission perm = new PackagePermission(imports[i].getName(),
+ PackagePermission.IMPORT);
+
+ if (!pd.implies(perm))
{
- throw (SecurityException) thrown;
+ throw new java.security.AccessControlException(
+ "PackagePermission.IMPORT denied for import: " +
+ imports[i].getName(), perm);
}
- else
+ }
+ // Check export permission for all exports of the current module.
+ R4Export[] implicitImports =
+ bundle.getInfo().getCurrentModule().getDefinition().getExports();
+
+ for (int i = 0;i < implicitImports.length; i++)
+ {
+ PackagePermission perm = new PackagePermission(
+ implicitImports[i].getName(), PackagePermission.EXPORT);
+
+ if (!pd.implies(perm))
{
- throw new BundleException("Problem resolving: " + ex);
+ throw new java.security.AccessControlException(
+ "PackagePermission.EXPORT denied for implicit export: " +
+ implicitImports[i].getName(), perm);
}
}
}
@@ -1343,7 +1341,7 @@
{
// We guarantee to close the input stream, so put it in a
// finally clause.
-
+
try
{
// Variable to indicate whether bundle is active or not.
@@ -1385,21 +1383,21 @@
info.getBundleId(),
archive.getRevisionCount() - 1,
info.getCurrentHeader());
-
+
Object sm = System.getSecurityManager();
-
+
if (sm != null)
{
((SecurityManager) sm).checkPermission(
new AdminPermission(bundle, AdminPermission.LIFECYCLE));
}
-
+
// Add module to bundle info.
info.addModule(module);
- }
+ }
catch (Exception ex)
{
- try
+ try
{
archive.undoRevise();
}
@@ -1407,7 +1405,7 @@
{
m_logger.log(Logger.LOG_ERROR, "Unable to rollback.", busted);
}
-
+
throw ex;
}
}
@@ -1417,22 +1415,22 @@
rethrow = ex;
}
- // Set new state, mark as needing a refresh, and fire updated event
+ // Set new state, mark as needing a refresh, and fire updated event
// if successful.
if (rethrow == null)
{
info.setLastModified(System.currentTimeMillis());
info.setState(Bundle.INSTALLED);
fireBundleEvent(BundleEvent.UNRESOLVED, bundle);
-
+
// Mark previous the bundle's old module for removal since
// it can no longer be used to resolve other modules per the spec.
((ModuleImpl) info.getModules()[info.getModules().length - 2])
.setRemovalPending(true);
-
+
fireBundleEvent(BundleEvent.UPDATED, bundle);
}
-
+
// Restart bundle, but do not change the persistent state.
// This will not start the bundle if it was not previously
// active.
@@ -1441,12 +1439,12 @@
// If update failed, rethrow exception.
if (rethrow != null)
{
- if ((System.getSecurityManager() != null) &&
+ if ((System.getSecurityManager() != null) &&
(rethrow instanceof SecurityException))
{
throw (SecurityException) rethrow;
}
-
+
throw new BundleException("Update failed.", rethrow);
}
}
@@ -1484,7 +1482,7 @@
throws BundleException
{
Throwable rethrow = null;
-
+
// Set the bundle's persistent state to inactive if necessary.
if (record)
{
@@ -1492,7 +1490,7 @@
}
BundleInfo info = bundle.getInfo();
-
+
switch (info.getState())
{
case Bundle.UNINSTALLED:
@@ -1514,18 +1512,10 @@
{
if (bundle.getInfo().getActivator() != null)
{
- if (System.getSecurityManager() != null)
- {
- java.security.AccessController.doPrivileged(
- new PrivilegedActivatorCall(PrivilegedActivatorCall.STOP,
- info.getActivator(), info.getContext()));
- }
- else
- {
- info.getActivator().stop(info.getContext());
- }
+ m_secureAction.stopActivator(info.getActivator(),
+ info.getContext());
}
-
+
// Try to save the activator in the cache.
// NOTE: This is non-standard OSGi behavior and only
// occurs if strictness is disabled.
@@ -1557,17 +1547,17 @@
// Unregister any services offered by this bundle.
m_registry.unregisterServices(bundle);
-
+
// Release any services being used by this bundle.
m_registry.ungetServices(bundle);
-
+
// The spec says that we must remove all event
// listeners for a bundle when it is stopped.
m_dispatcher.removeListeners(bundle);
-
+
info.setState(Bundle.RESOLVED);
fireBundleEvent(BundleEvent.STOPPED, bundle);
-
+
// Throw activator error if there was one.
if (rethrow != null)
{
@@ -1581,12 +1571,12 @@
{
throw (SecurityException) rethrow;
}
- else if ((System.getSecurityManager() != null) &&
+ else if ((System.getSecurityManager() != null) &&
(rethrow instanceof java.security.PrivilegedActionException))
{
rethrow = ((java.security.PrivilegedActionException) rethrow).getException();
}
-
+
// Rethrow all other exceptions as a BundleException.
throw new BundleException("Activator stop error.", rethrow);
}
@@ -1772,9 +1762,9 @@
{
BundleArchive archive = m_cache.getArchive(id);
bundle = new BundleImpl(this, createBundleInfo(archive));
-
+
Object sm = System.getSecurityManager();
-
+
if (sm != null)
{
((SecurityManager) sm).checkPermission(
@@ -1798,13 +1788,13 @@
"Could not remove from cache.", ex1);
}
}
-
- if ((System.getSecurityManager() != null) &&
+
+ if ((System.getSecurityManager() != null) &&
(ex instanceof SecurityException))
{
throw (SecurityException) ex;
}
-
+
throw new BundleException("Could not create bundle object.", ex);
}
@@ -1840,10 +1830,10 @@
// Not much else we can do.
}
}
-
+
// Fire bundle event.
fireBundleEvent(BundleEvent.INSTALLED, bundle);
-
+
// Return new bundle.
return bundle;
}
@@ -1982,7 +1972,7 @@
/**
* Implementation for BundleContext.removeServiceListener().
* Removes service listeners from the listener list.
- *
+ *
* @param bundle The context bundle of the listener
* @param l The service listener to remove from the listener list.
**/
@@ -2205,7 +2195,7 @@
* the class was loaded from a bundle from this framework instance. If the
* class was not loaded from a bundle or was loaded by a bundle in another
* framework instance, then <tt>null</tt> is returned.
- *
+ *
* @param clazz the class for which to find its associated bundle.
* @return the bundle associated with the specified class or <tt>null</tt>
* if the class was not loaded by a bundle or its associated
@@ -2476,7 +2466,7 @@
// At this point the map contains every bundle that has been
// updated and/or removed as well as all bundles that import
// packages from these bundles.
-
+
// Create refresh helpers for each bundle.
RefreshHelper[] helpers = new RefreshHelper[bundles.length];
for (int i = 0; i < bundles.length; i++)
@@ -2626,16 +2616,38 @@
revision,
m_config.get(Constants.FRAMEWORK_OS_NAME),
m_config.get(Constants.FRAMEWORK_PROCESSOR)));
-
+
// Create the module using the module definition.
IModule module = m_factory.createModule(
Long.toString(targetId) + "." + Integer.toString(revision), md);
+ ProtectionDomain pd = null;
+
+ if (System.getSecurityManager() != null)
+ {
+ String location = m_cache.getArchive(targetId).getLocation();
+
+ if (location.startsWith("reference:"))
+ {
+ location = location.substring("reference:".length());
+ }
+
+ CodeSource codesource = new CodeSource(
+ new URL(location),
+ m_cache.getArchive(targetId).getCertificates());
+
+ pd = new ProtectionDomain(codesource,
+ m_secureAction.getPolicy().getPermissions(codesource));
+ }
+
+ m_factory.setSecurityContext(module, pd);
+
// Create the content loader from the module archive.
IContentLoader contentLoader = new ContentLoaderImpl(
m_logger,
m_cache.getArchive(targetId).getRevision(revision).getContent(),
- m_cache.getArchive(targetId).getRevision(revision).getContentPath());
+ m_cache.getArchive(targetId).getRevision(revision).getContentPath(),
+ pd);
// Set the content loader's search policy.
contentLoader.setSearchPolicy(
new R4SearchPolicy(m_policyCore, module));
@@ -2658,9 +2670,9 @@
// CONCURRENCY NOTE:
// This method is called indirectly from startBundle() (via _startBundle()),
// which has the exclusion lock, so there is no need to do any locking here.
-
+
BundleActivator activator = null;
-
+
String strict = m_config.get(FelixConstants.STRICT_OSGI_PROP);
boolean isStrict = (strict == null) ? true : strict.equals("true");
if (!isStrict)
@@ -2676,7 +2688,7 @@
activator = null;
}
}
-
+
// If there was no cached activator, then get the activator
// class from the bundle manifest.
if (activator == null)
@@ -2702,7 +2714,7 @@
activator = (BundleActivator) clazz.newInstance();
}
}
-
+
return activator;
}
@@ -2714,7 +2726,7 @@
try
{
BundleInfo info = bundle.getInfo();
-
+
// In case of a refresh, then we want to physically
// remove the bundle's modules from the module manager.
// This is necessary for two reasons: 1) because
@@ -2844,8 +2856,8 @@
catch (IOException ex)
{
ex.printStackTrace();
- }
-
+ }
+
// Maven uses a '-' to separate the version qualifier,
// while OSGi uses a '.', so we need to convert to a '.'
StringBuffer sb =
@@ -2858,7 +2870,7 @@
}
return sb.toString();
}
-
+
private void processAutoProperties()
{
// The auto-install property specifies a space-delimited list of
@@ -3228,7 +3240,7 @@
{
return;
}
-
+
int idx = -1;
for (int i = 0; i < m_uninstalledBundles.length; i++)
{
@@ -3238,7 +3250,7 @@
break;
}
}
-
+
if (idx >= 0)
{
// If this is the only bundle, then point to empty list.
@@ -3280,11 +3292,11 @@
throw new BundleException("Unable to install, thread interrupted.");
}
}
-
+
m_installRequestMap.put(location, location);
}
}
-
+
protected void releaseInstallLock(String location)
{
synchronized (m_installRequestLock_Priority1)
@@ -3312,7 +3324,7 @@
bundle.getInfo().lock();
}
}
-
+
protected boolean acquireBundleLockOrFail(BundleImpl bundle)
{
synchronized (m_bundleLock)
@@ -3379,7 +3391,7 @@
bundles = (BundleImpl[]) list.toArray(new BundleImpl[list.size()]);
}
}
-
+
// Check if all unresolved bundles can be locked.
boolean lockable = true;
if (bundles != null)
@@ -3388,7 +3400,7 @@
{
lockable = bundles[i].getInfo().isLockable();
}
-
+
// If we can lock all bundles, then lock them.
if (lockable)
{
@@ -3487,10 +3499,10 @@
// Add all importing bundles to map.
populateImportGraph(target, map);
}
-
+
bundles = (BundleImpl[]) map.values().toArray(new BundleImpl[map.size()]);
}
-
+
// Check if all corresponding bundles can be locked.
boolean lockable = true;
if (bundles != null)
@@ -3499,7 +3511,7 @@
{
lockable = bundles[i].getInfo().isLockable();
}
-
+
// If we can lock all bundles, then lock them.
if (lockable)
{
@@ -3546,100 +3558,4 @@
m_bundleLock.notifyAll();
}
}
-
- private static class PrivilegedActivatorCall implements
- java.security.PrivilegedExceptionAction
- {
- private static final int START = 1;
- private static final int STOP = 2;
- private int m_action;
- private BundleActivator m_activator;
- private BundleContext m_context;
-
- PrivilegedActivatorCall(int action, BundleActivator activator, BundleContext context)
- {
- m_action = action;
- m_activator = activator;
- m_context = context;
- }
- public Object run() throws Exception
- {
- switch (m_action)
- {
- case START:
- m_activator.start(m_context);
- break;
- case STOP:
- m_activator.stop(m_context);
- break;
- default:
- throw new IllegalStateException("Unknown activator action.");
- }
-
- return null;
- }
- }
-
- /**
- * This simple class is used to perform the privileged action of
- * checking if a bundle has permission to import its packages.
- **/
- private class CheckImportsPrivileged implements java.security.PrivilegedExceptionAction
- {
- private URL m_url = null;
- private BundleImpl m_bundle = null;
-
- public CheckImportsPrivileged(URL url, BundleImpl bundle)
- {
- m_url = url;
- m_bundle = bundle;
- }
-
- public Object run() throws Exception
- {
- // Get permission collection for code source; we cannot
- // call AccessController.checkPermission() directly since
- // the bundle's code is not on the access context yet because
- // it has not started yet...we are simply resolving it to see
- // if we can start it. We must check for import permission
- // on the exports as well, since export implies import.
- java.security.CodeSource cs = new java.security.CodeSource(m_url,
- (java.security.cert.Certificate[]) null);
-
- java.security.PermissionCollection pc =
- java.security.Policy.getPolicy().getPermissions(cs);
-
- R4Import[] imports =
- m_bundle.getInfo().getCurrentModule().getDefinition().getImports();
-
- for (int i = 0;i < imports.length; i++)
- {
- PackagePermission perm = new PackagePermission(imports[i].getName(),
- PackagePermission.IMPORT);
- if (!pc.implies(perm))
- {
- throw new java.security.AccessControlException(
- "PackagePermission.IMPORT denied for import: " +
- imports[i].getName(), perm);
- }
- }
- // Check export permission for all exports of the current module.
- R4Export[] implicitImports =
- m_bundle.getInfo().getCurrentModule().getDefinition().getExports();
-
- for (int i = 0;i < implicitImports.length; i++)
- {
- PackagePermission perm = new PackagePermission(
- implicitImports[i].getName(), PackagePermission.EXPORT);
- if (!pc.implies(perm))
- {
- throw new java.security.AccessControlException(
- "PackagePermission.EXPORT denied for implicit export: " +
- implicitImports[i].getName(), perm);
- }
- }
-
- return null;
- }
- }
}
diff --git a/framework/src/main/java/org/apache/felix/framework/SystemBundle.java b/framework/src/main/java/org/apache/felix/framework/SystemBundle.java
index fdfdf3c..e19f07e 100644
--- a/framework/src/main/java/org/apache/felix/framework/SystemBundle.java
+++ b/framework/src/main/java/org/apache/felix/framework/SystemBundle.java
@@ -23,6 +23,7 @@
import org.apache.felix.framework.searchpolicy.R4Export;
import org.apache.felix.framework.searchpolicy.R4Package;
import org.apache.felix.framework.util.FelixConstants;
+import org.apache.felix.framework.util.SecureAction;
import org.apache.felix.framework.util.StringMap;
import org.apache.felix.moduleloader.IContentLoader;
import org.osgi.framework.*;
@@ -34,12 +35,15 @@
private Thread m_shutdownThread = null;
private R4Export[] m_exports = null;
private IContentLoader m_contentLoader = null;
+ private SecureAction m_secureAction = null;
- protected SystemBundle(Felix felix, BundleInfo info, List activatorList)
- throws BundleException
+ protected SystemBundle(Felix felix, BundleInfo info, List activatorList,
+ SecureAction secureAction) throws BundleException
{
super(felix, info);
+ m_secureAction = secureAction;
+
// Create an activator list if necessary.
if (activatorList == null)
{
@@ -142,10 +146,13 @@
getInfo().setState(Bundle.STARTING);
- try {
+ try
+ {
getInfo().setContext(new BundleContextImpl(getFelix(), this));
getActivator().start(getInfo().getContext());
- } catch (Throwable throwable) {
+ }
+ catch (Throwable throwable)
+ {
throw new BundleException(
"Unable to start system bundle.", throwable);
}
@@ -158,13 +165,13 @@
public synchronized void stop() throws BundleException
{
Object sm = System.getSecurityManager();
-
- if(sm != null)
+
+ if (sm != null)
{
- ((SecurityManager) sm).checkPermission(new AdminPermission(this,
+ ((SecurityManager) sm).checkPermission(new AdminPermission(this,
AdminPermission.EXECUTE));
}
-
+
// Spec says stop() on SystemBundle should return immediately and
// shutdown framework on another thread.
if (getFelix().getStatus() == Felix.RUNNING_STATUS)
@@ -191,23 +198,7 @@
? false : embedded.equals("true");
if (!isEmbedded)
{
- if (System.getSecurityManager() != null)
- {
- java.security.AccessController.doPrivileged(
- new java.security.PrivilegedAction()
- {
- public Object run()
- {
- System.exit(0);
-
- return null;
- }
- });
- }
- else
- {
- System.exit(0);
- }
+ m_secureAction.exit(0);
}
}
};
@@ -243,10 +234,10 @@
public synchronized void update(InputStream is) throws BundleException
{
Object sm = System.getSecurityManager();
-
+
if (sm != null)
{
- ((SecurityManager) sm).checkPermission(new AdminPermission(this,
+ ((SecurityManager) sm).checkPermission(new AdminPermission(this,
AdminPermission.EXECUTE));
}
diff --git a/framework/src/main/java/org/apache/felix/framework/cache/BundleArchive.java b/framework/src/main/java/org/apache/felix/framework/cache/BundleArchive.java
index 9cd4e81..514fdc4 100644
--- a/framework/src/main/java/org/apache/felix/framework/cache/BundleArchive.java
+++ b/framework/src/main/java/org/apache/felix/framework/cache/BundleArchive.java
@@ -17,6 +17,7 @@
package org.apache.felix.framework.cache;
import java.io.*;
+import java.util.Collection;
import org.apache.felix.framework.Logger;
import org.apache.felix.framework.util.ObjectInputStreamX;
@@ -80,7 +81,7 @@
private static final transient String ACTIVE_STATE = "active";
private static final transient String INSTALLED_STATE = "installed";
private static final transient String UNINSTALLED_STATE = "uninstalled";
-
+
private Logger m_logger = null;
private long m_id = -1;
private File m_archiveRootDir = null;
@@ -89,6 +90,7 @@
private int m_persistentState = -1;
private int m_startLevel = -1;
private BundleRevision[] m_revisions = null;
+ private Collection m_trustedCaCerts = null;
private long m_refreshCount = -1;
@@ -120,7 +122,8 @@
* @throws Exception if any error occurs.
**/
public BundleArchive(
- Logger logger, File archiveRootDir, long id, String location, InputStream is)
+ Logger logger, File archiveRootDir, long id, String location, InputStream is,
+ Collection trustedCaCerts)
throws Exception
{
m_logger = logger;
@@ -132,6 +135,7 @@
"Bundle ID cannot be less than or equal to zero.");
}
m_originalLocation = location;
+ m_trustedCaCerts = trustedCaCerts;
// Save state.
initialize();
@@ -152,11 +156,13 @@
* @param id the bundle identifier associated with the archive.
* @throws Exception if any error occurs.
**/
- public BundleArchive(Logger logger, File archiveRootDir)
+ public BundleArchive(Logger logger, File archiveRootDir,
+ Collection trustedCaCerts)
throws Exception
{
m_logger = logger;
m_archiveRootDir = archiveRootDir;
+ m_trustedCaCerts = trustedCaCerts;
// Add a revision for each one that already exists in the file
// system. The file system might contain more than one revision
@@ -192,12 +198,12 @@
}
// Add the revision object for the most recent revision. We first try to read
- // the location from the current revision - if that fails we likely have
- // an old bundle cache and read the location the old way. The next
+ // the location from the current revision - if that fails we likely have
+ // an old bundle cache and read the location the old way. The next
// revision will update the bundle cache.
// TODO: FRAMEWORK - This try catch block can eventually be deleted when we decide to remove
// support for the old way, then we only need the first call to revise().
- try
+ try
{
revise(getRevisionLocation(revisionCount - 1), null);
}
@@ -638,7 +644,7 @@
setCurrentLocation(location);
setRevisionLocation(location, (m_revisions == null) ? 0 : m_revisions.length);
-
+
// Add new revision to revision array.
if (m_revisions == null)
{
@@ -672,47 +678,57 @@
{
return false;
}
-
+
String location = getRevisionLocation(m_revisions.length - 2);
// TODO: FRAMEWORK - This can eventually be deleted when we removed
// support for the old way of doing things.
setCurrentLocation(location);
-
+
try
{
m_revisions[m_revisions.length - 1].dispose();
- }
+ }
catch(Exception ex)
{
- m_logger.log(Logger.LOG_ERROR, getClass().getName() +
- ": Unable to dispose latest revision", ex);
+ m_logger.log(Logger.LOG_ERROR, getClass().getName() +
+ ": Unable to dispose latest revision", ex);
}
- File revisionDir = new File(m_archiveRootDir, REVISION_DIRECTORY +
+ File revisionDir = new File(m_archiveRootDir, REVISION_DIRECTORY +
getRefreshCount() + "." + (m_revisions.length - 1));
-
+
if (BundleCache.getSecureAction().fileExists(revisionDir))
{
BundleCache.deleteDirectoryTree(revisionDir);
}
-
+
BundleRevision[] tmp = new BundleRevision[m_revisions.length - 1];
System.arraycopy(m_revisions, 0, tmp, 0, m_revisions.length - 1);
-
+
return true;
}
-
+
+ public synchronized java.security.cert.Certificate[] getCertificates()
+ {
+ return m_revisions[m_revisions.length -1].getCertificates();
+ }
+
+ public synchronized String[] getDNChains()
+ {
+ return m_revisions[m_revisions.length -1].getDNChains();
+ }
+
private synchronized String getRevisionLocation(int revision) throws Exception
- {
+ {
InputStream is = null;
BufferedReader br = null;
try
{
is = BundleCache.getSecureAction().getFileInputStream(new File(
- new File(m_archiveRootDir, REVISION_DIRECTORY +
+ new File(m_archiveRootDir, REVISION_DIRECTORY +
getRefreshCount() + "." + revision), REVISION_LOCATION_FILE));
-
+
br = new BufferedReader(new InputStreamReader(is));
return br.readLine();
}
@@ -722,7 +738,7 @@
if (is != null) is.close();
}
}
-
+
private synchronized void setRevisionLocation(String location, int revision) throws Exception
{
// Save current revision location.
@@ -732,7 +748,7 @@
{
os = BundleCache.getSecureAction()
.getFileOutputStream(new File(
- new File(m_archiveRootDir, REVISION_DIRECTORY +
+ new File(m_archiveRootDir, REVISION_DIRECTORY +
getRefreshCount() + "." + revision), REVISION_LOCATION_FILE));
bw = new BufferedWriter(new OutputStreamWriter(os));
bw.write(location, 0, location.length());
@@ -743,7 +759,7 @@
if (os != null) os.close();
}
}
-
+
/**
* <p>
* This method removes all old revisions associated with the archive
@@ -785,10 +801,10 @@
// Save the current revision location for use later when
// we recreate the revision.
String location = getRevisionLocation(count -1);
-
+
// Increment the refresh count.
setRefreshCount(refreshCount + 1);
-
+
// Rename the current revision directory to be the zero revision
// of the new refresh level.
File currentDir = new File(m_archiveRootDir, REVISION_DIRECTORY + (refreshCount + 1) + ".0");
@@ -962,6 +978,8 @@
File revisionRootDir = new File(m_archiveRootDir,
REVISION_DIRECTORY + getRefreshCount() + "." + getRevisionCount());
+ BundleRevision result = null;
+
try
{
// Check if the location string represents a reference URL.
@@ -986,22 +1004,22 @@
// flag set to true.
if (BundleCache.getSecureAction().isFileDirectory(file))
{
- return new DirectoryRevision(m_logger, revisionRootDir, location);
+ result = new DirectoryRevision(m_logger, revisionRootDir, location);
}
else
{
- return new JarRevision(m_logger, revisionRootDir, location, true);
+ result = new JarRevision(m_logger, revisionRootDir, location, true);
}
}
else if (location.startsWith(INPUTSTREAM_PROTOCOL))
{
// Assume all input streams point to JAR files.
- return new JarRevision(m_logger, revisionRootDir, location, false, is);
+ result = new JarRevision(m_logger, revisionRootDir, location, false, is);
}
else
{
// Anything else is assumed to be a URL to a JAR file.
- return new JarRevision(m_logger, revisionRootDir, location, false);
+ result = new JarRevision(m_logger, revisionRootDir, location, false);
}
}
catch (Exception ex)
@@ -1019,6 +1037,10 @@
}
throw ex;
}
+
+ result.setTrustedCaCerts(m_trustedCaCerts);
+
+ return result;
}
/**
diff --git a/framework/src/main/java/org/apache/felix/framework/cache/BundleCache.java b/framework/src/main/java/org/apache/felix/framework/cache/BundleCache.java
index 7141bdc..edcf5bd 100644
--- a/framework/src/main/java/org/apache/felix/framework/cache/BundleCache.java
+++ b/framework/src/main/java/org/apache/felix/framework/cache/BundleCache.java
@@ -17,8 +17,7 @@
package org.apache.felix.framework.cache;
import java.io.*;
-import java.util.ArrayList;
-import java.util.List;
+import java.util.*;
import org.apache.felix.framework.Logger;
import org.apache.felix.framework.util.PropertyResolver;
@@ -84,14 +83,17 @@
private Logger m_logger = null;
private File m_profileDir = null;
private BundleArchive[] m_archives = null;
+ private Collection m_trustedCaCerts = null;
private static SecureAction m_secureAction = new SecureAction();
- public BundleCache(PropertyResolver cfg, Logger logger)
+ public BundleCache(PropertyResolver cfg, Logger logger,
+ Collection trustedCaCerts)
throws Exception
{
m_cfg = cfg;
m_logger = logger;
+ m_trustedCaCerts = trustedCaCerts;
initialize();
}
@@ -143,7 +145,8 @@
{
// Create the archive and add it to the list of archives.
BundleArchive ba =
- new BundleArchive(m_logger, archiveRootDir, id, location, is);
+ new BundleArchive(m_logger, archiveRootDir, id, location, is,
+ m_trustedCaCerts);
BundleArchive[] tmp = new BundleArchive[m_archives.length + 1];
System.arraycopy(m_archives, 0, tmp, 0, m_archives.length);
tmp[m_archives.length] = ba;
@@ -330,7 +333,7 @@
try
{
archiveList.add(
- new BundleArchive(m_logger, children[i]));
+ new BundleArchive(m_logger, children[i], m_trustedCaCerts));
}
catch (Exception ex)
{
@@ -340,7 +343,7 @@
}
}
}
-
+
m_archives = (BundleArchive[])
archiveList.toArray(new BundleArchive[archiveList.size()]);
}
diff --git a/framework/src/main/java/org/apache/felix/framework/cache/BundleRevision.java b/framework/src/main/java/org/apache/felix/framework/cache/BundleRevision.java
index 7607e5e..55b47b4 100644
--- a/framework/src/main/java/org/apache/felix/framework/cache/BundleRevision.java
+++ b/framework/src/main/java/org/apache/felix/framework/cache/BundleRevision.java
@@ -17,7 +17,10 @@
package org.apache.felix.framework.cache;
import java.io.File;
-import java.util.Map;
+import java.io.InputStream;
+import java.security.cert.*;
+import java.util.*;
+import java.util.jar.*;
import org.apache.felix.framework.Logger;
import org.apache.felix.moduleloader.IContent;
@@ -43,6 +46,11 @@
private Logger m_logger;
private File m_revisionRootDir = null;
private String m_location = null;
+ private Collection m_trustedCaCerts = null;
+ private X509Certificate[] m_certificates = null;
+ private String[] m_subjectDNChain = null;
+ private boolean m_certInitDone = (System.getSecurityManager() == null);
+ private boolean m_subjectDNInitDone = (System.getSecurityManager() == null);
/**
* <p>
@@ -68,6 +76,7 @@
* @param revisionRootDir the root directory to be used by the revision
* subclass for storing any state.
* @param location the location string associated with the revision.
+ * @param trustedCaCerts the trusted CA certificates if any.
* @throws Exception if any errors occur.
**/
public BundleRevision(Logger logger, File revisionRootDir, String location)
@@ -163,4 +172,708 @@
* @throws Exception if any error occurs.
**/
public abstract void dispose() throws Exception;
+
+ protected void setTrustedCaCerts(Collection trustedCaCerts)
+ {
+ m_trustedCaCerts = trustedCaCerts;
+ }
+
+ public X509Certificate[] getCertificates()
+ {
+ if (m_certInitDone)
+ {
+ return m_certificates;
+ }
+
+ if (m_trustedCaCerts == null)
+ {
+ return null;
+ }
+
+ try
+ {
+ m_certificates = getRevisionCertificates();
+ }
+ catch (Exception ex)
+ {
+ ex.printStackTrace();
+ // TODO: log this or something
+ }
+ finally
+ {
+ m_certInitDone = true;
+ }
+
+ return m_certificates;
+ }
+
+ protected abstract X509Certificate[] getRevisionCertificates() throws Exception;
+
+ public String[] getDNChains()
+ {
+ if (m_subjectDNInitDone)
+ {
+ return m_subjectDNChain;
+ }
+
+ try
+ {
+ X509Certificate[] certificates = getCertificates();
+
+ if (certificates == null)
+ {
+ return null;
+ }
+
+ List rootChains = new ArrayList();
+
+ getRootChains(certificates, rootChains);
+
+ List result = new ArrayList();
+
+ 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(parseSubjectDN(current.getTBSCertificate()));
+
+ while (iter.hasNext())
+ {
+ buffer.append(';');
+
+ current = (X509Certificate) iter.next();
+
+ buffer.append(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
+ ex.printStackTrace();
+ // TODO: log this or something
+ }
+ }
+
+ if (!result.isEmpty())
+ {
+ m_subjectDNChain = (String[]) result.toArray(new String[result.size()]);
+ }
+ }
+ finally
+ {
+ m_subjectDNInitDone = true;
+ }
+
+ return m_subjectDNChain;
+ }
+
+ protected X509Certificate[] getCertificatesForJar(JarFile bundle)
+ throws Exception
+ {
+ if (bundle.getManifest() == null)
+ {
+ return null;
+ }
+
+ List bundleEntries = new ArrayList();
+
+ Enumeration entries = bundle.entries();
+
+ while (entries.hasMoreElements())
+ {
+ JarEntry entry = (JarEntry) entries.nextElement();
+ bundleEntries.add(entry);
+ InputStream is = bundle.getInputStream(entry);
+ byte[] read = new byte[4096];
+ while (is.read(read) != -1)
+ {
+ // read the entry
+ }
+ is.close();
+ }
+ bundle.close();
+
+ List certificateChains = new ArrayList();
+
+ for (Iterator iter = bundleEntries.iterator();iter.hasNext();)
+ {
+ JarEntry entry = (JarEntry) iter.next();
+
+ if (entry.isDirectory() || entry.getName().startsWith("META-INF"))
+ {
+ continue;
+ }
+
+ Certificate[] certificates = entry.getCertificates();
+
+ if ((certificates == null) || (certificates.length == 0))
+ {
+ return null;
+ }
+
+ List chains = new ArrayList();
+
+ getRootChains(certificates, chains);
+
+ if (certificateChains.isEmpty())
+ {
+ certificateChains.addAll(chains);
+ }
+ 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())
+ {
+ 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()]);
+ }
+
+ protected void getRootChains(Certificate[] certificates, List chains)
+ {
+ List chain = new ArrayList();
+
+ for (int i = 0; i < certificates.length - 1; i++)
+ {
+ chain.add(certificates[i]);
+ if (!((X509Certificate)certificates[i + 1]).getSubjectDN().equals(
+ ((X509Certificate)certificates[i]).getIssuerDN()))
+ {
+
+ if (trusted((X509Certificate) certificates[i]))
+ {
+ chains.add(chain);
+ }
+
+ chain = new ArrayList();
+ }
+ }
+ // The final entry in the certs array is always
+ // a "root" certificate
+ chain.add(certificates[certificates.length - 1]);
+
+ if (trusted((X509Certificate) certificates[certificates.length - 1]))
+ {
+ chains.add(chain);
+ }
+ }
+
+ // @return true if
+ // m_trustedCaCerts.contains(cert) || cert issued by any of m_trustedCaCerts
+ protected boolean trusted(X509Certificate cert)
+ {
+ if (m_trustedCaCerts == null)
+ {
+ return false;
+ }
+
+ // m_trustedCaCerts.contains(cert) ? return true
+ for (Iterator iter = m_trustedCaCerts.iterator();iter.hasNext();)
+ {
+ X509Certificate trustedCaCert = (X509Certificate) iter.next();
+
+ // 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)
+ {
+ System.err.println("WARNING: Invalid certificate [" + ex + "]");
+ }
+ }
+ }
+ }
+
+ // cert issued by any of m_trustedCaCerts ? return true : return false
+ for (Iterator iter = m_trustedCaCerts.iterator();iter.hasNext();)
+ {
+ X509Certificate trustedCaCert = (X509Certificate) iter.next();
+
+ if (cert.getIssuerDN().equals(trustedCaCert.getSubjectDN()))
+ {
+ try
+ {
+ cert.verify(trustedCaCert.getPublicKey());
+ cert.checkValidity();
+ trustedCaCert.checkValidity();
+ return true;
+ }
+ catch (Exception ex)
+ {
+ System.err.println("WARNING: Invalid certificate [" + ex + "]");
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /*
+ * 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.
+ */
+ protected String parseSubjectDN(byte[] tbsCertEncoded) throws Exception
+ {
+ // init
+ tbs_buffer = tbsCertEncoded;
+ tbs_offset = 0;
+
+ try // this is a finally block that resets the tbs_buffer to null after we're done
+ {
+ // 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
+ // }
+
+ next();
+ next();
+ // if a version is present skip it
+ if (tbs_tag == 0)
+ {
+ next();
+ tbs_offset += tbs_length;
+ }
+ tbs_offset += tbs_length;
+ // skip the serialNumber
+ next();
+ next();
+ tbs_offset += tbs_length;
+ // skip the signature
+ next();
+ tbs_offset += tbs_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 = tbs_offset + tbs_length;
+
+ int seqTagOffset = tbs_tagOffset;
+
+ // skip the sequence
+ while (endOffset > tbs_offset)
+ {
+ next();
+
+ int endOffset2 = tbs_offset + tbs_length;
+
+ int seqTagOffset2 = tbs_tagOffset;
+
+ // skip each set
+ while (endOffset2 > tbs_offset)
+ {
+ next();
+ next();
+ tbs_offset += tbs_length;
+ next();
+ tbs_offset += tbs_length;
+ }
+
+ tbs_tagOffset = seqTagOffset2;
+ }
+
+ tbs_tagOffset = seqTagOffset;
+ // skip the validity which contains two dates to be skiped
+ next();
+ next();
+ tbs_offset += tbs_length;
+ next();
+ tbs_offset += tbs_length;
+ next();
+ // Now extract the subject dns and add them to attributes
+ List attributes = new ArrayList();
+
+ endOffset = tbs_offset + tbs_length;
+
+ seqTagOffset = tbs_tagOffset;
+
+ // for each set of rdns
+ while (endOffset > tbs_offset)
+ {
+ next();
+ int endOffset2 = tbs_offset + tbs_length;
+
+ // store tag offset
+ int seqTagOffset2 = tbs_tagOffset;
+
+ List rdn = new ArrayList();
+
+ // for each rdn in the set
+ while (endOffset2 > tbs_offset)
+ {
+ next();
+ next();
+ tbs_offset += tbs_length;
+ // parse the oid of the rdn
+ int oidElement = 1;
+ for (int i = 0; i < tbs_length; i++, ++oidElement)
+ {
+ while ((tbs_buffer[tbs_contentOffset + i] & 0x80) == 0x80)
+ {
+ i++;
+ }
+ }
+ int[] oid = new int[oidElement];
+ for (int id = 1, i = 0; id < oid.length; id++, i++)
+ {
+ int octet = tbs_buffer[tbs_contentOffset + i];
+ oidElement = octet & 0x7F;
+ while ((octet & 0x80) != 0)
+ {
+ i++;
+ octet = tbs_buffer[tbs_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 = tbs_tag;
+ tbs_offset += tbs_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(tbs_buffer, tbs_contentOffset,
+ tbs_length);
+ break;
+ case 12: // UTF8_STRING
+ str = new String(tbs_buffer, tbs_contentOffset,
+ tbs_length, "UTF-8");
+ break;
+ default: // OCTET
+ byte[] encoded = new byte[tbs_offset - tbs_tagOffset];
+ System.arraycopy(tbs_buffer, tbs_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);
+ tbs_tagOffset = seqTagOffset2;
+ }
+
+ tbs_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);
+ // but that doesn't make no sense to me whatsoever hence,
+ return result.toString().toLowerCase(Locale.US);
+ }
+ finally
+ {
+ tbs_buffer = null;
+ }
+ }
+
+ private byte[] tbs_buffer = null;
+ private int tbs_offset = 0;
+ private int tbs_tagOffset = 0;
+ private int tbs_tag = -1;
+ private int tbs_length = -1;
+ private int tbs_contentOffset = -1;
+
+ // 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()
+ {
+ tbs_tagOffset = tbs_offset;
+ tbs_tag = tbs_buffer[tbs_offset++] & 0xFF;
+ tbs_length = tbs_buffer[tbs_offset++] & 0xFF;
+ // There are two kinds of length forms - make sure we use the right one
+ if ((tbs_length & 0x80) != 0)
+ {
+ // its the long kind
+ int numOctets = tbs_length & 0x7F;
+ // hence, convert it
+ tbs_length = tbs_buffer[tbs_offset++] & 0xFF;
+ for (int i = 1; i < numOctets; i++)
+ {
+ int ch = tbs_buffer[tbs_offset++] & 0xFF;
+ tbs_length = (tbs_length << 8) + ch;
+ }
+ }
+ tbs_contentOffset = tbs_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();
+ }
+
+ 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.
+ }
+
+ // This just creates a string of the oid and looks for its name in the
+ // known names map OID2NAME. There might be faster implementations :-)
+ private String mapOID(int[] oid)
+ {
+ StringBuffer oidString = new StringBuffer();
+
+ oidString.append(oid[0]);
+ for (int i = 1;i < oid.length;i++)
+ {
+ oidString.append('.');
+ oidString.append(oid[i]);
+ }
+
+ String result = (String) OID2NAME.get(oidString.toString());
+
+ if (result == null)
+ {
+ throw new IllegalArgumentException("Unknown oid: " + oidString.toString());
+ }
+
+ return result;
+ }
}
\ No newline at end of file
diff --git a/framework/src/main/java/org/apache/felix/framework/cache/DirectoryRevision.java b/framework/src/main/java/org/apache/felix/framework/cache/DirectoryRevision.java
index 340be6f..e2ffde5 100644
--- a/framework/src/main/java/org/apache/felix/framework/cache/DirectoryRevision.java
+++ b/framework/src/main/java/org/apache/felix/framework/cache/DirectoryRevision.java
@@ -17,8 +17,9 @@
package org.apache.felix.framework.cache;
import java.io.*;
+import java.security.cert.X509Certificate;
import java.util.Map;
-import java.util.jar.Manifest;
+import java.util.jar.*;
import org.apache.felix.framework.Logger;
import org.apache.felix.framework.util.*;
@@ -33,12 +34,13 @@
**/
class DirectoryRevision extends BundleRevision
{
+ private static final transient String BUNDLE_JAR_FILE = "bundle.jar";
+
private File m_refDir = null;
private Map m_header = null;
public DirectoryRevision(
- Logger logger, File revisionRootDir, String location)
- throws Exception
+ Logger logger, File revisionRootDir, String location) throws Exception
{
super(logger, revisionRootDir, location);
m_refDir = new File(location.substring(
@@ -109,23 +111,23 @@
// class path to determine whether the bundle JAR file itself
// is on the bundle's class path and then creating content
// objects for everything on the class path.
-
+
// Get the bundle's manifest header.
Map map = getManifestHeader();
-
+
// Find class path meta-data.
String classPath = (map == null)
? null : (String) map.get(FelixConstants.BUNDLE_CLASSPATH);
-
+
// Parse the class path into strings.
String[] classPathStrings = Util.parseDelimitedString(
classPath, FelixConstants.CLASS_PATH_SEPARATOR);
-
+
if (classPathStrings == null)
{
classPathStrings = new String[0];
}
-
+
// Create the bundles class path.
IContent self = new DirectoryContent(m_refDir);
IContent[] contentPath = new IContent[classPathStrings.length];
@@ -149,14 +151,14 @@
}
}
}
-
+
// If there is nothing on the class path, then include
// "." by default, as per the spec.
if (contentPath.length == 0)
{
contentPath = new IContent[] { self };
}
-
+
return contentPath;
}
@@ -172,4 +174,181 @@
// of the revision directory, which will be automatically deleted
// by the parent bundle archive.
}
+
+ protected X509Certificate[] getRevisionCertificates() throws Exception
+ {
+ File tmp = new File(getRevisionRootDir(), BUNDLE_JAR_FILE);
+
+ if (BundleCache.getSecureAction().fileExists(tmp))
+ {
+ BundleCache.getSecureAction().deleteFile(tmp);
+ }
+
+ try
+ {
+ BundleCache.copyStreamToFile(new RevisionToInputStream(m_refDir),
+ tmp);
+
+ JarFile bundle = BundleCache.getSecureAction().openJAR(tmp);
+
+ return getCertificatesForJar(bundle);
+ }
+ finally
+ {
+ try
+ {
+ if (BundleCache.getSecureAction().fileExists(tmp))
+ {
+ BundleCache.getSecureAction().deleteFile(tmp);
+ }
+ }
+ catch (Exception e)
+ {
+ // Not much we can do
+ }
+ }
+ }
+
+ private class RevisionToInputStream extends InputStream
+ {
+ class OutputStreamBuffer extends OutputStream
+ {
+ ByteArrayOutputStream outBuffer = null;
+
+ public void write(int b)
+ {
+ outBuffer.write(b);
+ }
+ }
+
+ private File m_revisionDir = null;
+ private File[] m_content = null;
+ private File m_manifest = null;
+ private ByteArrayInputStream m_buffer = null;
+ private int m_current = 0;
+ private OutputStreamBuffer m_outputBuffer = new OutputStreamBuffer();
+ private JarOutputStream m_output = null;
+
+ RevisionToInputStream(File revisionDir) throws IOException
+ {
+ m_revisionDir = revisionDir;
+
+ m_outputBuffer.outBuffer = new ByteArrayOutputStream();
+
+ m_manifest = new File(m_revisionDir, "META-INF/MANIFEST.MF");
+
+ m_output = new JarOutputStream(m_outputBuffer);
+
+ readNext(m_manifest, false);
+
+ m_content = listFilesRecursive(revisionDir);
+ }
+
+ private File[] listFilesRecursive(File dir)
+ {
+ File[] children = BundleCache.getSecureAction().listDirectory(dir);
+ File[] combined = children;
+ for (int i = 0; i < children.length; i++)
+ {
+ if (BundleCache.getSecureAction().isFileDirectory(children[i]))
+ {
+ File[] grandchildren = listFilesRecursive(children[i]);
+ if (grandchildren.length > 0)
+ {
+ File[] tmp = new File[combined.length + grandchildren.length];
+ System.arraycopy(combined, 0, tmp, 0, combined.length);
+ System.arraycopy(grandchildren, 0, tmp, combined.length, grandchildren.length);
+ combined = tmp;
+ }
+ }
+ }
+ return combined;
+ }
+
+ private boolean readNext(File file, boolean close) throws IOException
+ {
+ if (BundleCache.getSecureAction().isFileDirectory(file))
+ {
+ return false;
+ }
+
+ m_outputBuffer.outBuffer = new ByteArrayOutputStream();
+
+ InputStream in = null;
+ try
+ {
+ in = BundleCache.getSecureAction().getFileInputStream(file);
+
+ JarEntry entry = new JarEntry(
+ file.getPath().substring(m_revisionDir.getPath().length() + 1));
+
+
+ m_output.putNextEntry(entry);
+
+ int c = -1;
+
+ while ((c = in.read()) != -1)
+ {
+ m_output.write(c);
+ }
+ }
+ finally
+ {
+ if (in != null)
+ {
+ in.close();
+ }
+ }
+
+ m_output.closeEntry();
+
+ m_output.flush();
+
+ if (close)
+ {
+ m_output.close();
+ m_output = null;
+ }
+
+ m_buffer = new ByteArrayInputStream(m_outputBuffer.outBuffer.toByteArray());
+
+ m_outputBuffer.outBuffer = null;
+
+ return true;
+ }
+
+ public int read() throws IOException
+ {
+ if ((m_output == null) && (m_buffer == null))
+ {
+ return -1;
+ }
+
+ if (m_buffer != null)
+ {
+ int result = m_buffer.read();
+
+ if (result == -1)
+ {
+ m_buffer = null;
+ return read();
+ }
+ else
+ {
+ return result;
+ }
+ }
+
+ while ((m_current < m_content.length) &&
+ (m_content[m_current].equals(m_manifest) ||
+ !readNext(m_content[m_current], (m_current + 1) == m_content.length)))
+ {
+ m_current++;
+ }
+
+ m_current++;
+
+ return read();
+ }
+ }
}
\ No newline at end of file
diff --git a/framework/src/main/java/org/apache/felix/framework/cache/JarRevision.java b/framework/src/main/java/org/apache/felix/framework/cache/JarRevision.java
index faa24c1..23d6f9a 100644
--- a/framework/src/main/java/org/apache/felix/framework/cache/JarRevision.java
+++ b/framework/src/main/java/org/apache/felix/framework/cache/JarRevision.java
@@ -20,9 +20,9 @@
import java.net.URL;
import java.net.URLConnection;
import java.security.PrivilegedActionException;
-import java.util.Map;
-import java.util.jar.JarFile;
-import java.util.jar.Manifest;
+import java.security.cert.X509Certificate;
+import java.util.*;
+import java.util.jar.*;
import java.util.zip.ZipEntry;
import org.apache.felix.framework.Logger;
@@ -50,7 +50,7 @@
private Map m_header = null;
public JarRevision(
- Logger logger, File revisionRootDir, String location, boolean byReference)
+ Logger logger, File revisionRootDir, String location, boolean byReference)
throws Exception
{
this(logger, revisionRootDir, location, byReference, null);
@@ -58,7 +58,7 @@
public JarRevision(
Logger logger, File revisionRootDir, String location,
- boolean byReference, InputStream is)
+ boolean byReference, InputStream is)
throws Exception
{
super(logger, revisionRootDir, location);
@@ -168,7 +168,7 @@
}
}
}
-
+
// If there is nothing on the class path, then include
// "." by default, as per the spec.
if (contentPath.length == 0)
@@ -272,11 +272,11 @@
{
if (is == null)
{
- // Do it the manual way to have a chance to
+ // Do it the manual way to have a chance to
// set request properties such as proxy auth.
URL url = new URL(getLocation());
- URLConnection conn = url.openConnection();
-
+ URLConnection conn = url.openConnection();
+
// Support for http proxy authentication.
String auth = BundleCache.getSecureAction()
.getSystemProperty("http.proxyAuth", null);
@@ -292,7 +292,7 @@
}
is = BundleCache.getSecureAction().getURLConnectionInputStream(conn);
}
-
+
// Save the bundle jar file.
BundleCache.copyStreamToFile(is, m_bundleFile);
}
@@ -421,7 +421,7 @@
throw new IOException("Unable to create embedded JAR directory.");
}
}
-
+
// Extract embedded JAR into its directory.
is = new BufferedInputStream(bundleJar.getInputStream(ze), BundleCache.BUFSIZE);
if (is == null)
@@ -439,4 +439,9 @@
}
}
}
+
+ protected X509Certificate[] getRevisionCertificates() throws Exception
+ {
+ return getCertificatesForJar(BundleCache.getSecureAction().openJAR(m_bundleFile, true));
+ }
}
diff --git a/framework/src/main/java/org/apache/felix/framework/cache/SystemBundleArchive.java b/framework/src/main/java/org/apache/felix/framework/cache/SystemBundleArchive.java
index 5a53a2b..99bd5b0 100644
--- a/framework/src/main/java/org/apache/felix/framework/cache/SystemBundleArchive.java
+++ b/framework/src/main/java/org/apache/felix/framework/cache/SystemBundleArchive.java
@@ -18,6 +18,7 @@
import java.io.File;
import java.io.InputStream;
+import java.security.cert.X509Certificate;
import java.util.Map;
import org.apache.felix.framework.util.FelixConstants;
@@ -65,6 +66,11 @@
public void dispose() throws Exception
{
}
+
+ protected X509Certificate[] getRevisionCertificates()
+ {
+ return null;
+ }
};
}
@@ -148,7 +154,7 @@
{
return m_headerMap;
}
-
+
public void setManifestHeader(Map headerMap)
{
m_headerMap = headerMap;