blob: f33b6fbf83ca99691d243ba73ac8eea9456e82da [file] [log] [blame]
Karl Pauls36407322008-03-07 00:37:30 +00001/*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19package org.apache.felix.framework;
20
21import java.io.File;
22import java.io.IOException;
23import java.util.HashMap;
24import java.util.Iterator;
25import java.util.Map;
26import java.util.StringTokenizer;
27import java.util.Map.Entry;
28
29import org.apache.felix.framework.security.SecurityConstants;
30import org.apache.felix.framework.security.condpermadmin.ConditionalPermissionAdminImpl;
31import org.apache.felix.framework.security.permissionadmin.PermissionAdminImpl;
32import org.apache.felix.framework.security.util.Conditions;
33import org.apache.felix.framework.security.util.LocalPermissions;
34import org.apache.felix.framework.security.util.Permissions;
35import org.apache.felix.framework.security.util.PropertiesCache;
36import org.apache.felix.framework.security.verifier.BundleDNParser;
37import org.apache.felix.framework.util.SecureAction;
38import org.osgi.framework.Bundle;
39import org.osgi.framework.BundleActivator;
40import org.osgi.framework.BundleContext;
41import org.osgi.framework.BundleException;
42import org.osgi.service.condpermadmin.ConditionalPermissionAdmin;
43import org.osgi.service.permissionadmin.PermissionAdmin;
44
45/**
46 * <p>This Felix specific activator installs a security provider with the Felix
47 * framework. The security settings can be changed via the
48 * {@link PermissionAdmin} and/or the
49 * {@link ConditionalPermissionAdmin} services that may be published by
50 * this class.
51 * </p>
52 * <p>
53 * Permission informations as well as caching data will be stored in several
54 * files in a directory called <tt>security</tt> obtained by a call to
55 * {@link BundleContext#getDataFile(String))}.
56 * </p>
57 * <p>
58 * The following properties are recognized:
59 * <p>
60 * {@link SecurityConstants#ENABLE_PERMISSIONADMIN_PROP} - Whether or not
61 * (<tt>true</tt>|<tt>false</tt>) to
62 * publish a{@link ConditionalPermissionAdmin} service. The default is
63 * {@link SecurityConstants#ENABLE_PERMISSIONADMIN_VALUE}.
64 * </p>
65 * <p>
66 * {@link SecurityConstants#ENABLE_CONDPERMADMIN_PROP} - Whether or not
67 * (<tt>true</tt>|<tt>false</tt>) to
68 * publish a{@link ConditionalPermissionAdmin} service. The default is
69 * {@link SecurityConstants#ENABLE_CONDPERMADMIN_VALUE}.
70 * </p>
71 * <p>
72 * {@link SecurityConstants#KEYSTORE_FILE_PROP} - The keystore URL(s) to use as
73 * trusted CA stores. The urls must be separated by a guard (i.e., <tt>|</tt>).
74 * The default is
75 * {@link SecurityConstants#KEYSTORE_FILE_VALUE}.
76 * </p>
77 * <p>
78 * {@link SecurityConstants#KEYSTORE_PASS_PROP} - The keystore password(s) to
79 * use for the given keystores. The passwords must be separated by a guard
80 * (i.e., <tt>|</tt>).The default is
81 * {@link SecurityConstants#KEYSTORE_PASS_VALUE}.
82 * </p>
83 * <p>
84 * {@link SecurityConstants#KEYSTORE_TYPE_PROP} - The keystore type(s) to use
85 * for the given keystores. The types must be separated by a guard
86 * (i.e., <tt>|</tt>).The default is
87 * {@link SecurityConstants#KEYSTORE_TYPE_VALUE}.
88 * </p>
89 * <p>
90 * {@link SecurityConstants#CRL_FILE_PROP} - The CRL URL(s) to use for revoked
91 * certificates. The urls must be separated by a guard (i.e., <tt>|</tt>).
92 * The default is {@link SecurityConstants#CRL_FILE_VALUE}.
93 * </p>
94 * </p>
95 */
96/*
97 * TODO: using a string for passwords is bad. We need to investigate
98 * alternatives.
99 *
100 * TODO: we might want to allow for the recognized properties to
101 * change without a restart. This is trick because we can not publish a managed
102 * service due to not being able to import as we are an extension bundle.
103 */
104public final class SecurityActivator implements BundleActivator
105{
106 private SecurityProviderImpl m_provider = null;
107 private PropertiesCache m_dnsCache = null;
108 private PropertiesCache m_localCache = null;
109 private LocalPermissions m_localPermissions = null;
110
111 public synchronized void start(BundleContext context) throws Exception
112 {
113 PermissionAdminImpl pai = null;
114
115 SecureAction action = new SecureAction();
116
117 Permissions permissions = new Permissions(context, action);
118
119 File tmp = context.getDataFile("security" + File.separator + "tmp");
120 if ((tmp == null) || (!tmp.isDirectory() && !tmp.mkdirs()))
121 {
122 throw new IOException("Can't create tmp dir.");
123 }
124 // TODO: log something if we can not clean-up the tmp dir
125 File[] old = tmp.listFiles();
126 if (old != null)
127 {
128 for (int i = 0; i < old.length; i++)
129 {
130 old[i].delete();
131 }
132 }
133
134 if ("TRUE".equalsIgnoreCase(getProperty(context,
135 SecurityConstants.ENABLE_PERMISSIONADMIN_PROP,
136 SecurityConstants.ENABLE_PERMISSIONADMIN_VALUE)))
137 {
138 File cache =
139 context.getDataFile("security" + File.separator + "pa.txt");
140 if ((cache == null) || (!cache.isFile() && !cache.createNewFile()))
141 {
142 throw new IOException("Can't create cache file");
143 }
144 pai =
145 new PermissionAdminImpl(permissions, new PropertiesCache(cache,
146 tmp, action));
147 }
148
149 ConditionalPermissionAdminImpl cpai = null;
150
151 if ("TRUE".equalsIgnoreCase(getProperty(context,
152 SecurityConstants.ENABLE_CONDPERMADMIN_PROP,
153 SecurityConstants.ENABLE_CONDPERMADMIN_VALUE)))
154 {
155 File cpaCache =
156 context.getDataFile("security" + File.separator + "cpa.txt");
157 if ((cpaCache == null) || (!cpaCache.isFile() && !cpaCache.createNewFile()))
158 {
159 throw new IOException("Can't create cache file");
160 }
161 File localCache =
162 context.getDataFile("security" + File.separator + "local.txt");
163 if ((localCache == null) || (!localCache.isFile() && !localCache.createNewFile()))
164 {
165 throw new IOException("Can't create cache file");
166 }
167
168 m_localCache = new PropertiesCache(localCache, tmp, action);
169 m_localPermissions = new LocalPermissions(permissions, m_localCache);
170
171 cpai =
172 new ConditionalPermissionAdminImpl(permissions, new Conditions(
173 action), m_localPermissions,
174 new PropertiesCache(cpaCache, tmp, action));
175 }
176
177 if ((pai != null) || (cpai != null))
178 {
179 String crlList =
180 getProperty(context, SecurityConstants.CRL_FILE_PROP,
181 SecurityConstants.CRL_FILE_VALUE);
182 String storeList =
183 getProperty(context, SecurityConstants.KEYSTORE_FILE_PROP,
184 SecurityConstants.KEYSTORE_FILE_VALUE);
185 String passwdList =
186 getProperty(context, SecurityConstants.KEYSTORE_PASS_PROP,
187 SecurityConstants.KEYSTORE_PASS_VALUE);
188 String typeList =
189 getProperty(context, SecurityConstants.KEYSTORE_TYPE_PROP,
190 SecurityConstants.KEYSTORE_TYPE_VALUE);
191
192 StringTokenizer storeTok = new StringTokenizer(storeList, "|");
193 StringTokenizer passwdTok = new StringTokenizer(passwdList, "|");
194 StringTokenizer typeTok = new StringTokenizer(typeList, "|");
195
196 if ((storeTok.countTokens() != passwdTok.countTokens())
197 || (passwdTok.countTokens() != typeTok.countTokens()))
198 {
199 throw new BundleException(
200 "Each CACerts keystore must have one type and one passwd entry and vice versa.");
201 }
202
203 m_provider =
204 new SecurityProviderImpl(crlList, typeList, passwdList,
205 storeList, pai, cpai, action);
206
207 File cache =
208 context.getDataFile("security" + File.separator + "dns.txt");
209 if ((cache == null) || (!cache.isFile() && !cache.createNewFile()))
210 {
211 throw new IOException("Can't create cache file");
212 }
213 m_dnsCache = new PropertiesCache(cache, tmp, action);
214
215 Map store = m_dnsCache.read(String[].class);
216
217 if (store != null)
218 {
219 BundleDNParser parser = m_provider.getParser();
220
221 for (Iterator iter = store.entrySet().iterator(); iter
222 .hasNext();)
223 {
224 Entry entry = (Entry) iter.next();
225 String[] value = (String[]) entry.getValue();
226 if ("none".equals(value[0]))
227 {
228 parser.put((String) entry.getKey(), null);
229 }
230 else if ("invalid".equals(value[0]))
231 {
232 parser.put((String) entry.getKey(), new String[0]);
233 }
234 else
235 {
236 parser.put((String) entry.getKey(), value);
237 }
238 }
239 }
240 ((Felix) context.getBundle(0)).setSecurityProvider(m_provider);
241 }
242
243 if (pai != null)
244 {
245 context.registerService(PermissionAdmin.class.getName(), pai, null);
246 }
247
248 if (cpai != null)
249 {
250 context.registerService(ConditionalPermissionAdmin.class.getName(),
251 cpai, null);
252 }
253 }
254
255 public synchronized void stop(BundleContext context) throws Exception
256 {
257 if (m_provider != null)
258 {
259 m_dnsCache.write(write(m_provider.getParser().getCache(), context));
260 }
261 if (m_localPermissions != null)
262 {
263 m_localCache.write(write(m_localPermissions.getStore(), context));
264 }
265 m_provider = null;
266 m_dnsCache = null;
267 m_localPermissions = null;
268 }
269
270 private Map write(Map cache, BundleContext context)
271 {
272 // Filter the cached dn chains and only store the latest for each
273 // bundle. This is ok because the framework will prune old revisions
274 // after a restart. The format is <id>-<timestamp>
275 Map store = new HashMap();
276 Map index = new HashMap();
277 for (Iterator iter = cache.entrySet().iterator(); iter.hasNext();)
278 {
279 Entry entry = (Entry) iter.next();
280
281 String key = (String) entry.getKey();
282 String id = key.substring(0, key.indexOf("-"));
283 String time = key.substring(key.indexOf("-") + 1);
284 Bundle bundle = context.getBundle(Long.parseLong(id));
285 long timeLong = Long.parseLong(time);
286 if ((bundle == null) ||
Karl Paulsd093f2d2009-11-24 23:23:26 +0000287 (bundle.getLastModified() > timeLong))
Karl Pauls36407322008-03-07 00:37:30 +0000288 {
289 continue;
290 }
291 String last = (String) index.get(id);
292
293 if ((last == null)
294 || (Long.parseLong(last) < timeLong))
295 {
296 index.put(id, time);
297 Object[] dns = (Object[]) entry.getValue();
298 store.remove(id + "-" + last);
299 if ((dns != null) && (dns.length > 0))
300 {
301 store.put(key, dns);
302 }
303 else
304 {
305 store.put(key, (dns == null) ? new String[] {"none"} : new String[] {"invalid"});
306 }
307 }
308 }
309 return store;
310 }
311
312 private String getProperty(BundleContext context, String key,
313 String defaultValue)
314 {
315 String result = context.getProperty(key);
316
317 return ((result != null) && (result.trim().length() > 0)) ? result
318 : defaultValue;
319 }
320}