blob: fe8bcc930a09ea38ca804bab2a1849fcbd57d5ca [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.felix.framework;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.Map.Entry;
import org.apache.felix.framework.security.SecurityConstants;
import org.apache.felix.framework.security.condpermadmin.ConditionalPermissionAdminImpl;
import org.apache.felix.framework.security.permissionadmin.PermissionAdminImpl;
import org.apache.felix.framework.security.util.Conditions;
import org.apache.felix.framework.security.util.LocalPermissions;
import org.apache.felix.framework.security.util.Permissions;
import org.apache.felix.framework.security.util.PropertiesCache;
import org.apache.felix.framework.security.verifier.BundleDNParser;
import org.apache.felix.framework.util.SecureAction;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleException;
import org.osgi.service.condpermadmin.ConditionalPermissionAdmin;
import org.osgi.service.permissionadmin.PermissionAdmin;
/**
* <p>This Felix specific activator installs a security provider with the Felix
* framework. The security settings can be changed via the
* {@link PermissionAdmin} and/or the
* {@link ConditionalPermissionAdmin} services that may be published by
* this class.
* </p>
* <p>
* Permission informations as well as caching data will be stored in several
* files in a directory called <tt>security</tt> obtained by a call to
* {@link BundleContext#getDataFile(String))}.
* </p>
* <p>
* The following properties are recognized:
* <p>
* {@link SecurityConstants#ENABLE_PERMISSIONADMIN_PROP} - Whether or not
* (<tt>true</tt>|<tt>false</tt>) to
* publish a{@link ConditionalPermissionAdmin} service. The default is
* {@link SecurityConstants#ENABLE_PERMISSIONADMIN_VALUE}.
* </p>
* <p>
* {@link SecurityConstants#ENABLE_CONDPERMADMIN_PROP} - Whether or not
* (<tt>true</tt>|<tt>false</tt>) to
* publish a{@link ConditionalPermissionAdmin} service. The default is
* {@link SecurityConstants#ENABLE_CONDPERMADMIN_VALUE}.
* </p>
* <p>
* {@link SecurityConstants#KEYSTORE_FILE_PROP} - The keystore URL(s) to use as
* trusted CA stores. The urls must be separated by a guard (i.e., <tt>|</tt>).
* The default is
* {@link SecurityConstants#KEYSTORE_FILE_VALUE}.
* </p>
* <p>
* {@link SecurityConstants#KEYSTORE_PASS_PROP} - The keystore password(s) to
* use for the given keystores. The passwords must be separated by a guard
* (i.e., <tt>|</tt>).The default is
* {@link SecurityConstants#KEYSTORE_PASS_VALUE}.
* </p>
* <p>
* {@link SecurityConstants#KEYSTORE_TYPE_PROP} - The keystore type(s) to use
* for the given keystores. The types must be separated by a guard
* (i.e., <tt>|</tt>).The default is
* {@link SecurityConstants#KEYSTORE_TYPE_VALUE}.
* </p>
* <p>
* {@link SecurityConstants#CRL_FILE_PROP} - The CRL URL(s) to use for revoked
* certificates. The urls must be separated by a guard (i.e., <tt>|</tt>).
* The default is {@link SecurityConstants#CRL_FILE_VALUE}.
* </p>
* </p>
*/
/*
* TODO: using a string for passwords is bad. We need to investigate
* alternatives.
*
* TODO: we might want to allow for the recognized properties to
* change without a restart. This is trick because we can not publish a managed
* service due to not being able to import as we are an extension bundle.
*/
public final class SecurityActivator implements BundleActivator
{
private SecurityProviderImpl m_provider = null;
private PropertiesCache m_dnsCache = null;
private PropertiesCache m_localCache = null;
private LocalPermissions m_localPermissions = null;
public synchronized void start(BundleContext context) throws Exception
{
PermissionAdminImpl pai = null;
SecureAction action = new SecureAction();
Permissions permissions = new Permissions(context, action);
File tmp = context.getDataFile("security" + File.separator + "tmp");
if ((tmp == null) || (!tmp.isDirectory() && !tmp.mkdirs()))
{
throw new IOException("Can't create tmp dir.");
}
// TODO: log something if we can not clean-up the tmp dir
File[] old = tmp.listFiles();
if (old != null)
{
for (int i = 0; i < old.length; i++)
{
old[i].delete();
}
}
if ("TRUE".equalsIgnoreCase(getProperty(context,
SecurityConstants.ENABLE_PERMISSIONADMIN_PROP,
SecurityConstants.ENABLE_PERMISSIONADMIN_VALUE)))
{
File cache =
context.getDataFile("security" + File.separator + "pa.txt");
if ((cache == null) || (!cache.isFile() && !cache.createNewFile()))
{
throw new IOException("Can't create cache file");
}
pai =
new PermissionAdminImpl(permissions, new PropertiesCache(cache,
tmp, action));
}
ConditionalPermissionAdminImpl cpai = null;
if ("TRUE".equalsIgnoreCase(getProperty(context,
SecurityConstants.ENABLE_CONDPERMADMIN_PROP,
SecurityConstants.ENABLE_CONDPERMADMIN_VALUE)))
{
File cpaCache =
context.getDataFile("security" + File.separator + "cpa.txt");
if ((cpaCache == null) || (!cpaCache.isFile() && !cpaCache.createNewFile()))
{
throw new IOException("Can't create cache file");
}
File localCache =
context.getDataFile("security" + File.separator + "local.txt");
if ((localCache == null) || (!localCache.isFile() && !localCache.createNewFile()))
{
throw new IOException("Can't create cache file");
}
m_localCache = new PropertiesCache(localCache, tmp, action);
m_localPermissions = new LocalPermissions(permissions, m_localCache);
cpai =
new ConditionalPermissionAdminImpl(permissions, new Conditions(
action), m_localPermissions,
new PropertiesCache(cpaCache, tmp, action));
}
if ((pai != null) || (cpai != null))
{
String crlList =
getProperty(context, SecurityConstants.CRL_FILE_PROP,
SecurityConstants.CRL_FILE_VALUE);
String storeList =
getProperty(context, SecurityConstants.KEYSTORE_FILE_PROP,
SecurityConstants.KEYSTORE_FILE_VALUE);
String passwdList =
getProperty(context, SecurityConstants.KEYSTORE_PASS_PROP,
SecurityConstants.KEYSTORE_PASS_VALUE);
String typeList =
getProperty(context, SecurityConstants.KEYSTORE_TYPE_PROP,
SecurityConstants.KEYSTORE_TYPE_VALUE);
StringTokenizer storeTok = new StringTokenizer(storeList, "|");
StringTokenizer passwdTok = new StringTokenizer(passwdList, "|");
StringTokenizer typeTok = new StringTokenizer(typeList, "|");
if ((storeTok.countTokens() != passwdTok.countTokens())
|| (passwdTok.countTokens() != typeTok.countTokens()))
{
throw new BundleException(
"Each CACerts keystore must have one type and one passwd entry and vice versa.");
}
m_provider =
new SecurityProviderImpl(crlList, typeList, passwdList,
storeList, pai, cpai, action);
File cache =
context.getDataFile("security" + File.separator + "dns.txt");
if ((cache == null) || (!cache.isFile() && !cache.createNewFile()))
{
throw new IOException("Can't create cache file");
}
m_dnsCache = new PropertiesCache(cache, tmp, action);
Map store = m_dnsCache.read(String[].class);
if (store != null)
{
BundleDNParser parser = m_provider.getParser();
for (Iterator iter = store.entrySet().iterator(); iter
.hasNext();)
{
Entry entry = (Entry) iter.next();
String[] value = (String[]) entry.getValue();
if ("none".equals(value[0]))
{
parser.put((String) entry.getKey(), null);
}
else if ("invalid".equals(value[0]))
{
parser.put((String) entry.getKey(), new String[0]);
}
else
{
parser.put((String) entry.getKey(), value);
}
}
}
((Felix) context.getBundle(0)).setSecurityProvider(m_provider);
}
if (pai != null)
{
context.registerService(PermissionAdmin.class.getName(), pai, null);
}
if (cpai != null)
{
context.registerService(ConditionalPermissionAdmin.class.getName(),
cpai, null);
}
}
public synchronized void stop(BundleContext context) throws Exception
{
if (m_provider != null)
{
m_dnsCache.write(write(m_provider.getParser().getCache(), context));
}
if (m_localPermissions != null)
{
m_localCache.write(write(m_localPermissions.getStore(), context));
}
m_provider = null;
m_dnsCache = null;
m_localPermissions = null;
}
private Map write(Map cache, BundleContext context)
{
// Filter the cached dn chains and only store the latest for each
// bundle. This is ok because the framework will prune old revisions
// after a restart. The format is <id>-<timestamp>
Map store = new HashMap();
Map index = new HashMap();
for (Iterator iter = cache.entrySet().iterator(); iter.hasNext();)
{
Entry entry = (Entry) iter.next();
String key = (String) entry.getKey();
String id = key.substring(0, key.indexOf("-"));
String time = key.substring(key.indexOf("-") + 1);
Bundle bundle = context.getBundle(Long.parseLong(id));
long timeLong = Long.parseLong(time);
if ((bundle == null) ||
((FelixBundle) bundle).getInfo().getLastModified() > timeLong)
{
continue;
}
String last = (String) index.get(id);
if ((last == null)
|| (Long.parseLong(last) < timeLong))
{
index.put(id, time);
Object[] dns = (Object[]) entry.getValue();
store.remove(id + "-" + last);
if ((dns != null) && (dns.length > 0))
{
store.put(key, dns);
}
else
{
store.put(key, (dns == null) ? new String[] {"none"} : new String[] {"invalid"});
}
}
}
return store;
}
private String getProperty(BundleContext context, String key,
String defaultValue)
{
String result = context.getProperty(key);
return ((result != null) && (result.trim().length() > 0)) ? result
: defaultValue;
}
}